Skip to content

Commit 47ef8ea

Browse files
committed
Add plaintext and MD5 password authentication
1 parent ceaac70 commit 47ef8ea

4 files changed

Lines changed: 77 additions & 27 deletions

File tree

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
RUSTC ?= rustc
2-
RUSTFLAGS += -L. --cfg debug
2+
RUSTFLAGS += -L. --cfg debug -Z debug-info
33

44
.PHONY: all
55
all: postgres.dummy

src/lib.rs

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
extern mod extra;
22

3+
use extra::digest::Digest;
4+
use extra::md5::Md5;
5+
use extra::url::Url;
36
use std::cell::Cell;
4-
use std::hashmap::HashMap;
57
use std::rt::io::net::ip::SocketAddr;
68
use std::rt::io::net::tcp::TcpStream;
7-
use extra::url::Url;
89
use std::str;
910

1011
use message::*;
@@ -24,29 +25,29 @@ impl Drop for PostgresConnection {
2425

2526
impl PostgresConnection {
2627
pub fn connect(url: &str) -> PostgresConnection {
27-
let parsed_url: Url = FromStr::from_str(url).unwrap();
28+
let url: Url = FromStr::from_str(url).unwrap();
2829

29-
let socket_url = fmt!("%s:%s", parsed_url.host,
30-
parsed_url.port.get_ref().as_slice());
30+
let socket_url = fmt!("%s:%s", url.host,
31+
url.port.get_ref().as_slice());
3132
let addr: SocketAddr = FromStr::from_str(socket_url).unwrap();
3233
let conn = PostgresConnection {
3334
stream: Cell::new(TcpStream::connect(addr).unwrap()),
3435
next_stmt_id: Cell::new(0)
3536
};
3637

37-
let mut args = HashMap::new();
38-
args.insert(&"user", parsed_url.user.get_ref().user.as_slice());
39-
conn.write_message(&StartupMessage(args));
40-
41-
match conn.read_message() {
42-
AuthenticationOk => (),
43-
resp => fail!("Bad response: %?", resp.to_str())
38+
let mut args = url.query.clone();
39+
args.push((~"user", url.user.get_ref().user.clone()));
40+
if !url.path.is_empty() {
41+
args.push((~"database", url.path.clone()));
4442
}
43+
conn.write_message(&StartupMessage(args.as_slice()));
44+
45+
conn.handle_auth(&url);
4546

4647
loop {
4748
match conn.read_message() {
4849
ParameterStatus(param, value) =>
49-
info!("Param %s = %s", param, value),
50+
info!("Parameter %s = %s", param, value),
5051
BackendKeyData(*) => (),
5152
ReadyForQuery(*) => break,
5253
resp => fail!("Bad response: %?", resp.to_str())
@@ -68,6 +69,31 @@ impl PostgresConnection {
6869
}
6970
}
7071

72+
fn handle_auth(&self, url: &Url) {
73+
loop {
74+
match self.read_message() {
75+
AuthenticationOk => break,
76+
AuthenticationCleartextPassword => {
77+
let pass = url.user.get_ref().pass.get_ref().as_slice();
78+
self.write_message(&PasswordMessage(pass));
79+
}
80+
AuthenticationMD5Password(nonce) => {
81+
let input = url.user.get_ref().pass.get_ref().as_slice() +
82+
url.user.get_ref().user.as_slice();
83+
let mut md5 = Md5::new();
84+
md5.input_str(input);
85+
let output = md5.result_str();
86+
md5.reset();
87+
md5.input_str(output);
88+
md5.input(nonce);
89+
let output = "md5" + md5.result_str();
90+
self.write_message(&PasswordMessage(output.as_slice()));
91+
}
92+
resp => fail!("Bad response: %?", resp.to_str())
93+
}
94+
}
95+
}
96+
7197
pub fn prepare<'a>(&'a self, query: &str) -> PostgresStatement<'a> {
7298
let id = self.next_stmt_id.take();
7399
let stmt_name = ifmt!("statement_{}", id);

src/message.rs

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,25 @@ use std::rt::io::{Decorator, Reader, Writer};
33
use std::rt::io::extensions::{ReaderUtil, ReaderByteConversions,
44
WriterByteConversions};
55
use std::rt::io::mem::{MemWriter, MemReader};
6-
use std::hashmap::HashMap;
76
use std::sys;
87
use std::vec;
98

109
pub static PROTOCOL_VERSION: i32 = 0x0003_0000;
1110

1211
#[deriving(ToStr)]
1312
pub enum BackendMessage {
13+
AuthenticationCleartextPassword,
14+
AuthenticationMD5Password(~[u8]),
1415
AuthenticationOk,
1516
BackendKeyData(i32, i32),
1617
BindComplete,
1718
CloseComplete,
1819
CommandComplete(~str),
1920
DataRow(~[Option<~[u8]>]),
2021
EmptyQueryResponse,
21-
ErrorResponse(HashMap<u8, ~str>),
22+
ErrorResponse(~[(u8, ~str)]),
2223
NoData,
23-
NoticeResponse(HashMap<u8, ~str>),
24+
NoticeResponse(~[(u8, ~str)]),
2425
ParameterDescription(~[i32]),
2526
ParameterStatus(~str, ~str),
2627
ParseComplete,
@@ -48,8 +49,9 @@ pub enum FrontendMessage<'self> {
4849
Execute(&'self str, i32),
4950
/// name, query, parameter types
5051
Parse(&'self str, &'self str, &'self [i32]),
52+
PasswordMessage(&'self str),
5153
Query(&'self str),
52-
StartupMessage(HashMap<&'self str, &'self str>),
54+
StartupMessage(&'self [(~str, ~str)]),
5355
Sync,
5456
Terminate
5557
}
@@ -128,15 +130,19 @@ impl<W: Writer> WriteMessage for W {
128130
buf.write_be_i32_(*ty);
129131
}
130132
}
133+
PasswordMessage(password) => {
134+
ident = Some('p');
135+
buf.write_string(password);
136+
}
131137
Query(query) => {
132138
ident = Some('Q');
133139
buf.write_string(query);
134140
}
135141
StartupMessage(ref params) => {
136142
buf.write_be_i32_(PROTOCOL_VERSION);
137-
for (k, v) in params.iter() {
138-
buf.write_string(*k);
139-
buf.write_string(*v);
143+
for &(ref k, ref v) in params.iter() {
144+
buf.write_string(k.as_slice());
145+
buf.write_string(v.as_slice());
140146
}
141147
buf.write_u8_(0);
142148
}
@@ -199,11 +205,11 @@ impl<R: Reader> ReadMessage for R {
199205
'3' => CloseComplete,
200206
'C' => CommandComplete(buf.read_string()),
201207
'D' => read_data_row(&mut buf),
202-
'E' => ErrorResponse(read_hash(&mut buf)),
208+
'E' => ErrorResponse(read_fields(&mut buf)),
203209
'I' => EmptyQueryResponse,
204210
'K' => BackendKeyData(buf.read_be_i32_(), buf.read_be_i32_()),
205211
'n' => NoData,
206-
'N' => NoticeResponse(read_hash(&mut buf)),
212+
'N' => NoticeResponse(read_fields(&mut buf)),
207213
'R' => read_auth_message(&mut buf),
208214
'S' => ParameterStatus(buf.read_string(), buf.read_string()),
209215
't' => read_parameter_description(&mut buf),
@@ -217,15 +223,15 @@ impl<R: Reader> ReadMessage for R {
217223
}
218224
}
219225

220-
fn read_hash(buf: &mut MemReader) -> HashMap<u8, ~str> {
221-
let mut fields = HashMap::new();
226+
fn read_fields(buf: &mut MemReader) -> ~[(u8, ~str)] {
227+
let mut fields = ~[];
222228
loop {
223229
let ty = buf.read_u8_();
224230
if ty == 0 {
225231
break;
226232
}
227233

228-
fields.insert(ty, buf.read_string());
234+
fields.push((ty, buf.read_string()));
229235
}
230236

231237
fields
@@ -249,6 +255,8 @@ fn read_data_row(buf: &mut MemReader) -> BackendMessage {
249255
fn read_auth_message(buf: &mut MemReader) -> BackendMessage {
250256
match buf.read_be_i32_() {
251257
0 => AuthenticationOk,
258+
3 => AuthenticationCleartextPassword,
259+
5 => AuthenticationMD5Password(buf.read_bytes(4)),
252260
val => fail!("Unknown Authentication identifier `%?`", val)
253261
}
254262
}

src/test.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ fn test_nulls() {
3838
conn.prepare("CREATE TABLE foo (
3939
id BIGINT PRIMARY KEY,
4040
val VARCHAR
41-
)").update([]);
41+
)").update([]);
4242
conn.prepare("INSERT INTO foo (id, val) VALUES ($1, $2), ($3, $4)")
4343
.update([&1 as &ToSql, & &"foobar" as &ToSql,
4444
&2 as &ToSql, &None::<~str> as &ToSql]);
@@ -51,3 +51,19 @@ fn test_nulls() {
5151
Err::<(), ()>(())
5252
};
5353
}
54+
55+
#[test]
56+
fn test_plaintext_pass() {
57+
PostgresConnection::connect("postgres://pass_user:password@127.0.0.1:5432");
58+
}
59+
60+
#[test]
61+
#[should_fail]
62+
fn test_plaintext_pass_no_pass() {
63+
PostgresConnection::connect("postgres://pass_user@127.0.0.1:5432");
64+
}
65+
66+
#[test]
67+
fn test_md5_pass() {
68+
PostgresConnection::connect("postgres://md5_user:password@127.0.0.1:5432");
69+
}

0 commit comments

Comments
 (0)