From 43936e99a9fea0a25002450a27e3b16d86999719 Mon Sep 17 00:00:00 2001 From: lisael Date: Sun, 18 Sep 2016 19:50:57 +0200 Subject: [PATCH] clear text authentication --- pg/conversation.pony | 35 +++++- pg/example/main.pony | 2 +- pg/pg.pony | 245 ++++++++++++++---------------------------- pg/server_parser.pony | 158 +++++++++++++++++++-------- 4 files changed, 224 insertions(+), 216 deletions(-) diff --git a/pg/conversation.pony b/pg/conversation.pony index 76d3ad7..7dba64f 100644 --- a/pg/conversation.pony +++ b/pg/conversation.pony @@ -1,3 +1,6 @@ +use "debug" +use "crypto" + trait Interaction be apply(c: Connection) @@ -29,5 +32,35 @@ actor AuthInteraction is Interaction end c.writev(data) - be message(m: ServerMessage val) => None + be send_clear_pass(pass: String) => + _conn.writev(recover val PasswordMessage(pass).done() end) + + be send_md5_pass(pass: String, username: String, salt: Array[U8] val) => + // from PG doc : concat('md5', md5(concat(md5(concat(password, username)), random-salt))) + var result = "md5" + ToHexString( + MD5( + ToHexString(MD5(pass+username)) + String.from_array(salt) + ) + ) + // Debug(recover val ToHexString(MD5(pass+username)) + String.from_array(salt') end) + // Debug(result) + _conn.writev(recover val PasswordMessage(result).done() end) + + be got_md5_pass(pass: String, req: MD5PwdRequest val) => + Debug.out(pass) + let that: AuthInteraction tag = this + _pool.get_user(recover lambda(u: String)(that, pass, req) => that.send_md5_pass(pass, u, req.salt) end end) + + be message(m: ServerMessage val) => + let that: AuthInteraction tag = this + match m + | let r: ClearTextPwdRequest val => + _pool.get_pass(recover lambda(s: String)(that) => that.send_clear_pass(s) end end) + | let r: MD5PwdRequest val => + // _pool.log("md5Req") + _pool.get_pass(recover lambda(s: String)(that, r) => that.got_md5_pass(s, r) end end) + | let r: AuthenticationOkMessage val => None + else + log("Unknown ServerMessage") + end diff --git a/pg/example/main.pony b/pg/example/main.pony index aa9ded9..422757e 100644 --- a/pg/example/main.pony +++ b/pg/example/main.pony @@ -8,5 +8,5 @@ use "net" actor Main new create(env: Env) => - let sess = Session(env where user="abcdefghijkl", password="macflytest", database="macflytest") + let sess = Session(env where user="macflytest", password="macflytest", database="macflytest") sess.connect() diff --git a/pg/pg.pony b/pg/pg.pony index c57de04..ea3ac65 100644 --- a/pg/pg.pony +++ b/pg/pg.pony @@ -9,6 +9,30 @@ use "collections" use "promises" use "debug" +interface StringCB + fun apply(s: String) +interface PassCB is StringCB +interface UserCB is StringCB + +primitive IdleTransction +primitive ActiveTransaction +primitive ErrorTransaction +primitive UnknownTransactionStatus + +type TransactionStatus is (IdleTransction + | ActiveTransaction + | ErrorTransaction + | UnknownTransactionStatus) + +primitive _StatusFromByte + fun apply(b: U8): TransactionStatus => + match b + | 73 => IdleTransction // I + | 84 => ActiveTransaction // T + | 69 => ErrorTransaction // E + else + UnknownTransactionStatus + end trait Message interface ClientMessage is Message @@ -28,7 +52,7 @@ class ClientMessageBase is ClientMessage fun ref _write(s: String) => _w.write(s) fun ref _i32(i: I32) => _w.i32_be(i) fun ref _done(id: U8): Array[ByteSeq] iso^ => - if id != 0 then _out.u8(id) else Debug.out("Nope")end + if id != 0 then _out.u8(id) end _out.i32_be(_w.size().i32() + 4) _out.writev(_w.done()) _out.done() @@ -81,11 +105,31 @@ class NullServerMessage is ServerMessage class AuthenticationOkMessage is ServerMessage class ClearTextPwdRequest is ServerMessage class MD5PwdRequest is ServerMessage + let salt: Array[U8] val + new val create(salt': Array[U8] val)=> + salt = salt' class ErrorMessage is ServerMessage let items: Array[(U8, Array[U8] val)] val new val create(it: Array[(U8, Array[U8] val)] val) => items = it +class ParameterStatusMessage is ServerMessage + let key: String val + let value: String val + new val create(k: Array[U8] val, v: Array[U8] val) => + key = String.from_array(k) + value = String.from_array(v) + +class ReadyForQueryMessage is ServerMessage + let status: TransactionStatus + new val create(b: U8) => + status = _StatusFromByte(b) + +class BackendKeyDataMessage is ServerMessage + let data: (U32, U32) + new val create(pid: U32, key: U32) => + data = (pid,key) + type Param is (String, String) class PGNotify is TCPConnectionNotify @@ -107,6 +151,8 @@ actor Connection let _pool: ConnectionPool tag let _listener: Listener tag let _params: Array[Param] val + var _interaction: Interaction tag + var _backend_key: (U32, U32) = (0, 0) new create(auth: AmbientAuth, session: Session, @@ -118,20 +164,33 @@ actor Connection _conn = TCPConnection(auth, PGNotify(this, _listener), host, service) _pool = pool _params = params + _interaction = AuthInteraction(_pool, this, _params) be writev(data: ByteSeqIter) => _conn.writev(data) be connected() => - log("connected") - let data = recover val - let msg = StartupMessage(_params) - msg.done() - end - _conn.writev(data) + _interaction(this) + + be _set_backend_key(m: BackendKeyDataMessage val) => + Debug("set backend key") + _backend_key = m.data be log(msg: String) => _pool.log(msg) + be update_param(p: ParameterStatusMessage val) => + // TODO + Debug.out("Update param " + p.key + " " + p.value) + + be received(s: ServerMessage val) => + match s + | let m: ParameterStatusMessage val => update_param(m) + | let m: ReadyForQueryMessage val => None + | let m: BackendKeyDataMessage val => _set_backend_key(m) + else + _interaction.message(s) + end + actor ConnectionPool let _connections: Array[Connection] = Array[Connection tag] @@ -139,12 +198,14 @@ actor ConnectionPool let _sess: Session tag let _host: String let _service: String + let _user: String - new create(session: Session tag, host: String, service: String, params: Array[Param] val) => + new create(session: Session tag, host: String, service: String, user: String, params: Array[Param] val) => _params = params _sess = session _host = host _service = service + _user = user be log(msg: String) => _sess.log(msg) @@ -153,17 +214,16 @@ actor ConnectionPool log("connecting") let conn = Connection(auth, _sess, _host, _service, _params, this) - be got_pass(pass: String) => - None + be got_pass(pass: String, f: PassCB iso) => + f(pass) - be get_pass() => - Debug.out("get_pass") - got_pass("hop") - + be get_pass(f: PassCB iso) => + // Debug.out("get_pass") + got_pass("macflytest", consume f) -type PGDo[B: Any #share] is Fulfill[Connection, B] -type PGPromise is Promise[Connection] - + be get_user(f: UserCB iso) => + f(_user) + actor Session let _env: Env @@ -176,7 +236,7 @@ actor Session password: String = "", database: String = "") => _env = env - _pool = ConnectionPool(this, host, service, + _pool = ConnectionPool(this, host, service, user, recover val [("user", user), ("database", database)] end) be log(msg: String) => @@ -190,152 +250,3 @@ actor Session be terminate()=> None -/* -class StartupHandler is Handler - let _base: HandlerBase delegate Handler - let _params: Array[Param] val - - new create(p: ConnectionPoolOld, params: Array[Param] val) => - _base = HandlerBase(p) - _params = params - - fun ref connected(conn: TCPConnection ref) => - log("connected") - let data = recover val - let msgi = StartupMessage(_params) - msgi.done() - end - conn.writev(data) - - fun ref received(conn: TCPConnection ref, data': Array[U8] iso) => - match filter(consume data') - | None => None - | let r: ClearTextPwdRequest val => - pool().get_pass(this) - | let r: MD5PwdRequest val => - pool().get_pass(this) - else - log("Unknown ServerMessage") - end - - - - - - - - - - - - - - - - -actor ConnectionPoolOld - let _ready: Array[TCPConnection] = Array[TCPConnection tag] - let _params: Array[Param] val - let _auth: (AmbientAuth | None) - let _sess: Session - var _connections: MapIs[Handler tag, TCPConnection] = MapIs[Handler tag, TCPConnection] - - new create(auth: (AmbientAuth | None), session: Session tag, params: Array[Param] val) => - _auth = auth - _params = params - _sess = session - - be handle(h: Handler iso, old: None = None) => - let t: Handler tag = recover tag h end - try - let conn = TCPConnection(_auth as AmbientAuth, consume h, "", "5432") - _connections.insert(t, conn) - end - - be handle(h: Handler iso, old: Handler tag) => - try - let data: Array[ByteSeq] val = h.data() - /*for s in data.values() do*/ - /*match s*/ - /*| let s': Array[U8 val] val =>*/ - /*for c in s'.values() do*/ - /*log(c.string())*/ - /*end*/ - /*| let s': String =>*/ - /*for c in s'.values() do*/ - /*log(c.string())*/ - /*end*/ - /*end*/ - /*end*/ - let conn = _connections(old) - _connections.insert(h, conn) - conn.set_notify(consume h) - _connections.remove(old) - conn.writev(data) - end - - be log(msg: String) => - _sess.log(msg) - - be connect() => - None - /*let h: Handler iso = recover StartupHandler(this, _params) end*/ - /*handle(consume h)*/ - - be got_pass(pass: String, h: Handler tag) => - Debug.out("got_pass") - handle(recover PasswordHandler(this, pass) end, h) - - be get_pass(h: Handler tag) => - Debug.out("get_pass") - got_pass("hop", h) - - -trait Handler is TCPConnectionNotify - fun log(msg: String) - fun pool(): ConnectionPoolOld - fun ref data(): Array[ByteSeq] iso^ - fun ref set_data(d: Array[ByteSeq] iso) - fun box filter(data': Array[U8] iso): (ServerMessage val | None) - - -class HandlerBase is Handler - let _pool: ConnectionPoolOld - var _data: Array[ByteSeq] iso = recover Array[ByteSeq] end - - new create(pool': ConnectionPoolOld) => _pool = pool' - fun log(msg: String) => _pool.log(msg) - fun pool(): ConnectionPoolOld => _pool - fun ref data(): Array[ByteSeq] iso^ => _data = recover Array[ByteSeq] end - fun ref set_data(d: Array[ByteSeq] iso) => _data = consume d - fun box filter(data': Array[U8] iso): (ServerMessage val | None) => - match ParseResponse(consume data') - | let r: ParseError val => log(r.msg) - | let r: ErrorMessage val => - log("Error:") - for (typ, value) in r.items.values() do - log(" " + typ.string() + ": " + String.from_array(value)) - end - | let r: ServerMessage val => r - end - - -class PasswordHandler is Handler - let _base: HandlerBase delegate Handler - - new create(p: ConnectionPoolOld, pass: String) => - _base = HandlerBase(p) - set_data(PasswordMessage(pass).done()) - - fun ref received(conn: TCPConnection ref, data': Array[U8] iso) => - match filter(consume data') - | None => None - | let r: ClearTextPwdRequest val => - pool().get_pass(this) - | let r: MD5PwdRequest val => - pool().get_pass(this) - else - log("Unknown ServerMessage") - end - -*/ diff --git a/pg/server_parser.pony b/pg/server_parser.pony index fd86e53..c4298f4 100644 --- a/pg/server_parser.pony +++ b/pg/server_parser.pony @@ -1,74 +1,150 @@ use "buffered" use "debug" -class ParseError is ServerMessage +trait ParseEvent +primitive ParsePending is ParseEvent +class ParseError is ParseEvent let msg: String new iso create(msg': String)=> msg = msg' -primitive ParseResponse - fun apply(s: Array[U8] iso): ServerMessage val => - ResponseParser(consume s)() + actor Listener let _conn: Connection + var r: Reader iso = Reader // current reader + var _ctype: U8 = 0 // current type (keep it if the data is chuncked) + var _clen: USize = 0 // current message len (as given by server) new create(c: Connection) => _conn = c be received(data: Array[U8] iso) => - match ParseResponse(consume data) - | let r: ParseError val => _conn.log(r.msg) - | let r: ErrorMessage val => - _conn.log("Error:") - for (typ, value) in r.items.values() do - _conn.log(" " + typ.string() + ": " + String.from_array(value)) + let data' = recover val (consume data).slice() end + r.append(data') + while r.size() > _clen do + match parse_response() + | let result: ParseError val => _conn.log(result.msg) + | let result: ErrorMessage val => + _conn.log("Error:") + for (typ, value) in result.items.values() do + _conn.log(" " + typ.string() + ": " + String.from_array(value)) + end + | let result: ParsePending val => Debug.out("pending") + | let result: ServerMessage val => Debug("sm");_conn.received(result) end - | let r: ServerMessage val => r - end + end + + fun ref parse_type(): U8 ? => + if _ctype > 0 then return _ctype end + _ctype = r.u8() + + fun ref parse_len(): USize ? => + if _clen > 0 then return _clen end + _clen = r.i32_be().usize() -class ResponseParser - let r: Reader = Reader - let src: Array[U8] val - - new create(s: Array[U8] val) => - src=s - r.append(s) - - fun ref apply(): ServerMessage val => - let id = try r.u8() else return ParseError("Empty message") end - Debug.out(id) - match id - | 82 => return try // R + fun ref parse_response(): (ServerMessage val|ParseEvent val) => + Debug.out("parse response:") + Debug.out(" _ctype: " + _ctype.string()) + Debug.out(" _clen: " + _clen.string()) + try + parse_type() + parse_len() + else + Debug.out(" Pending"); return ParsePending + end + Debug.out(" parse len and type: ") + Debug.out(" _ctype: " + _ctype.string()) + Debug.out(" _clen: " + _clen.string()) + if _clen > ( r.size() + 4) then + Debug.out(" Pending (_clen: " + _clen.string() + + ", r.size: " + r.size().string() + ")" ) + return ParsePending + end + let result = match _ctype + | 69 => parse_err_resp()// E + | 75 => parse_backend_key_data() //k + | 82 => try // R parse_auth_resp() else ParseError("Couldn't parse auth message") end - | 69 => return try // E - parse_err_resp() + | 83 => parse_parameter_status() // S + | 90 => parse_ready_for_query() // Z + else + try r.block(_clen-4) else return ParseError("") end + let ret = ParseError("Unknown message ID " + _ctype.string()) + _ctype = 0 + _clen = 0 + ret + end + match result + | let res: ServerMessage val => + _ctype = 0 + _clen = 0 + end + result + + fun ref parse_backend_key_data(): ServerMessage val => + try + let pid = r.u32_be() + let key = r.u32_be() + BackendKeyDataMessage(pid, key) + else + ParseError("Unreachable") + end + + fun ref parse_ready_for_query(): ServerMessage val => + let b = try r.u8() else return ParseError("Unreachable") end + ReadyForQueryMessage(b) + + fun ref parse_parameter_status(): ServerMessage val => + let item = try + recover val r.block(_clen-4).slice() end else - ParseError("Couldn't parse error message") + return ParseError("This should never happen") end - else - return ParseError("Unknown message ID " + id.string()) + Debug.out(String.from_array(item)) + let end_idx = try item.find(0) else return ParseError("Malformed parameter message") end + ParameterStatusMessage( + recover val item.trim(0, end_idx) end, + recover val item.trim(end_idx + 1) end) + + fun ref parse_auth_resp(): ServerMessage val ?=> + Debug.out("parse_auth_resp") + let msg_type = r.i32_be() + /*Debug.out(msg_type)*/ + let result: ServerMessage val = match msg_type // auth message type + | 0 => AuthenticationOkMessage + | 3 => ClearTextPwdRequest + | 5 => MD5PwdRequest(recover val [r.u8(), r.u8(), r.u8(), r.u8()] end) + else + ParseError("Unknown auth message") end + result - fun ref parse_err_resp(): ServerMessage val ?=> - r.i32_be().usize() // msg length TODO: check the actual length + fun ref parse_err_resp(): ServerMessage val => + // TODO: This is ugly. it used to work with other + // capabilities, so I adapted to get a val fields. It copies + // all, it should not. + Debug.out("parse_err_resp") let it = recover val let items = Array[(U8, Array[U8] val)] - let fields = src.trim(5) + let fields' = try r.block(_clen - 4) else + return ParseError("") + end + let fields = recover val (consume fields').slice() end var pos: USize = 1 var start_pos = pos let iter = fields.values() var c = try iter.next() else return ParseError("Bad error format") end var typ = c repeat - Debug.out(c) + //Debug.out(c) /*Debug.out("#" + pos.string())*/ if c == 0 then - Debug.out("*" + typ.string()) + //Debug.out("*" + typ.string()) if typ == 0 then break else items.push((typ, fields.trim(start_pos, pos))) @@ -85,16 +161,4 @@ class ResponseParser end ErrorMessage(it) - fun ref parse_auth_resp(): ServerMessage val ?=> - r.i32_be() // msg length TODO: check the actual length - let msg_type = r.i32_be() - Debug.out(msg_type) - let result: ServerMessage val = match msg_type // auth message type - | 0 => AuthenticationOkMessage - | 3 => ClearTextPwdRequest - | 5 => MD5PwdRequest - else - ParseError("Unknown auth message") - end - result