static enum proto_parse_status parse_rpc_call(struct cursor *cursor, struct rpc_proto_info *info) { CHECK(28); info->u.call_msg.rpc_version = cursor_read_u32n(cursor); if (info->u.call_msg.rpc_version != 2) { SLOG(LOG_DEBUG, "Rpc version should be 2, got %"PRIu32, info->u.call_msg.rpc_version); return PROTO_PARSE_ERR; } info->u.call_msg.program = cursor_read_u32n(cursor); info->u.call_msg.program_version = cursor_read_u32n(cursor); info->u.call_msg.procedure = cursor_read_u32n(cursor); RPC_CHECK_AUTH(credential); RPC_CHECK_AUTH(auth); return PROTO_OK; }
enum proto_parse_status cursor_read_fixed_int_n(struct cursor *cursor, uint_least64_t *out_res, unsigned len) { uint_least64_t res; if (cursor->cap_len < len) return PROTO_TOO_SHORT; switch (len) { case 0: res = 0; break; case 1: res = cursor_read_u8(cursor); break; case 2: res = cursor_read_u16n(cursor); break; case 3: res = cursor_read_u24n(cursor); break; case 4: res = cursor_read_u32n(cursor); break; case 8: res = cursor_read_u64n(cursor); break; default: SLOG(LOG_DEBUG, "Can't read a %d bytes long number", len); return PROTO_PARSE_ERR; } if (out_res) *out_res = res; return PROTO_OK; }
/* Read a message header, return type and msg length, and advance the cursor to the msg payload. * if type is NULL that means no type are read from the cursor. * return PROTO_TOO_SHORT if the msg content is not available. */ static enum proto_parse_status cursor_read_msg(struct cursor *cursor, uint8_t *type, size_t *len_) { SLOG(LOG_DEBUG, "Reading new message"); unsigned rollback = 0; if (type) { // read type first CHECK_LEN(cursor, 1, rollback); *type = cursor_read_u8(cursor); rollback++; SLOG(LOG_DEBUG, "... of type %u ('%c')", (unsigned)*type, *type); } // read length CHECK_LEN(cursor, 4, rollback); size_t len = cursor_read_u32n(cursor); rollback += 4; if (len < 4) return PROTO_PARSE_ERR; // as length includes itself len -= 4; SLOG(LOG_DEBUG, "... of length %zu", len); if (len_) *len_ = len; // read payload CHECK_LEN(cursor, len, rollback); return PROTO_OK; }
static enum proto_parse_status pg_parse_startup(struct pgsql_parser *pg_parser, struct sql_proto_info *info, unsigned way, uint8_t const *payload, size_t cap_len, size_t unused_ wire_len, struct timeval const *now, size_t tot_cap_len, uint8_t const *tot_packet) { info->msg_type = SQL_STARTUP; struct cursor cursor; cursor_ctor(&cursor, payload, cap_len); uint8_t type; size_t len; enum proto_parse_status status = cursor_read_msg(&cursor, &type, &len); if (status != PROTO_OK) return status; /* In this phase, we expect to see from the client the pwd message, * and from the server the authentication request. */ if (info->is_query) { // password message if (type != 'p') return PROTO_PARSE_ERR; char *passwd; status = cursor_read_string(&cursor, &passwd, len); if (status == PROTO_PARSE_ERR) return status; if (status == PROTO_TOO_SHORT) { // in case of GSSAPI or SSPI authentication then the "string" is in fact arbitrary bytes passwd = "GSSAPI/SSPI"; } info->set_values |= SQL_PASSWD; snprintf(info->u.startup.passwd, sizeof(info->u.startup.passwd), "%s", passwd); } else { // Authentication request SLOG(LOG_DEBUG, "Authentification response from server with type %c", type); if (len < 4) return PROTO_PARSE_ERR; if (type == 'E') { status = pg_parse_error(info, &cursor, len); if (status != PROTO_OK) return status; } else if (type == 'R' ) { // We don't care about the auth method, we just want to know when auth is complete uint32_t auth_type = cursor_read_u32n(&cursor); if (auth_type == 0) { // AuthenticationOK pg_parser->phase = QUERY; // we don't wait for the ReadyForQuery msg since we are not interrested in following messages info->set_values |= SQL_REQUEST_STATUS; info->request_status = SQL_REQUEST_COMPLETE; } } else { SLOG(LOG_DEBUG, "Unknown startup message with type %c", type); return PROTO_PARSE_ERR; } } // Discard the rest of the packet return proto_parse(NULL, &info->info, way, NULL, 0, 0, now, tot_cap_len, tot_packet); }
static void cursor_check(void) { struct cursor cursor; static uint8_t const data[] = { 1, 2 }; cursor_ctor(&cursor, data, sizeof(data)); assert(cursor_peek_u8(&cursor, 0) == 0x01U); assert(cursor_peek_u8(&cursor, 1) == 0x02U); assert(cursor_read_u8(&cursor) == 0x01U); assert(cursor_read_u8(&cursor) == 0x02U); static uint16_t const data16[] = { 0x0102, 0x0304 }; cursor_ctor(&cursor, (uint8_t *)data16, sizeof(data16)); assert(cursor_peek_u16le(&cursor, 0) == 0x0102U); assert(cursor_peek_u16le(&cursor, 2) == 0x0304U); assert(cursor_read_u16(&cursor) == 0x0102U); assert(cursor_read_u16(&cursor) == 0x0304U); static uint32_t const data32[] = { 0x01020304U, 0x05060708U }; cursor_ctor(&cursor, (uint8_t *)data32, sizeof(data32)); assert(cursor_peek_u32le(&cursor, 0) == 0x01020304U); assert(cursor_peek_u32le(&cursor, 4) == 0x05060708U); assert(cursor_read_u32(&cursor) == 0x01020304U); assert(cursor_read_u32(&cursor) == 0x05060708U); static uint64_t const data64[] = { 0x0102030405060708ULL }; cursor_ctor(&cursor, (uint8_t *)data64, sizeof(data64)); assert(cursor_peek_u64le(&cursor, 0) == 0x0102030405060708ULL); assert(cursor_read_u64(&cursor) == 0x0102030405060708ULL); static uint8_t const datan[] = { 1, 2, 3, 4 }; cursor_ctor(&cursor, datan, sizeof(datan)); assert(cursor_peek_u32n(&cursor, 0) == 0x01020304); assert(cursor_read_u32n(&cursor) == 0x01020304); cursor_ctor(&cursor, datan, sizeof(datan)); assert(cursor_peek_u16n(&cursor, 0) == 0x0102); assert(cursor_read_u16n(&cursor) == 0x0102); }
static enum proto_parse_status rpc_parse(struct parser *parser, struct proto_info *parent, unsigned unused_ way, uint8_t const *packet, size_t cap_len, size_t wire_len, struct timeval const unused_ *now, size_t unused_ tot_cap_len, uint8_t const unused_ *tot_packet) { struct cursor cursor; cursor_ctor(&cursor, packet, cap_len); if (wire_len < 12) return PROTO_PARSE_ERR; if (cap_len < 12) return PROTO_TOO_SHORT; ASSIGN_INFO_OPT(tcp, parent); struct rpc_proto_info info; proto_info_ctor(&info.info, parser, parent, wire_len, 0); if (tcp) cursor_drop(&cursor, 4); // Drop fragment header cursor_drop(&cursor, 4); info.msg_type = cursor_read_u32n(&cursor); enum proto_parse_status status = PROTO_OK; switch (info.msg_type) { case RPC_CALL: if (wire_len < 40) return PROTO_PARSE_ERR; status = parse_rpc_call(&cursor, &info); break; case RPC_REPLY: status = parse_rpc_reply(&cursor, &info); break; default: return PROTO_PARSE_ERR; } SLOG(LOG_DEBUG, "Parsed rpc status %s, %s", proto_parse_status_2_str(status), rpc_info_2_str(&info.info)); if (status == PROTO_OK) { // TODO We can have a nfs payload (void)proto_parse(NULL, &info.info, way, NULL, 0, 0, now, tot_cap_len, tot_packet); } return status; }
static enum proto_parse_status pg_parse_init(struct pgsql_parser *pg_parser, struct sql_proto_info *info, unsigned way, uint8_t const *payload, size_t cap_len, size_t wire_len, struct timeval const *now, size_t tot_cap_len, uint8_t const *tot_packet) { info->msg_type = SQL_STARTUP; /* NONE phase is when we haven't seen the startup message yet. * In this phase, we expect to see from the client a startup message, * and from the server nothing but an answer to an SSL request. */ if (info->is_query) { struct cursor cursor; cursor_ctor(&cursor, payload, cap_len); // Startup message comes without a type tag size_t len; enum proto_parse_status status = cursor_read_msg(&cursor, NULL, &len); if (status != PROTO_OK) return status; SLOG(LOG_DEBUG, "Msg of length %zu", len); if (len < 4) return PROTO_PARSE_ERR; uint32_t msg = cursor_read_u32n(&cursor); if (msg == 80877103) { // magic value for SSL request SLOG(LOG_DEBUG, "Msg is an SSL request"); info->set_values |= SQL_SSL_REQUEST; info->u.startup.ssl_request = SQL_SSL_REQUESTED; } else if (msg == 196608) { // version number, here 00 03 00 00 (ie. 3.0), which is parsed here SLOG(LOG_DEBUG, "Msg is a startup message for v3.0"); info->version_maj = 3; info->version_min = 0; info->set_values |= SQL_VERSION; // fine, now parse all the strings that follow do { char *name, *value; status = cursor_read_string(&cursor, &name, len); if (status != PROTO_OK) return status; if (name[0] == '\0') break; status = cursor_read_string(&cursor, &value, len); if (status != PROTO_OK) return status; if (0 == strcmp(name, "user")) { info->set_values |= SQL_USER; snprintf(info->u.startup.user, sizeof(info->u.startup.user), "%s", value); } else if (0 == strcmp(name, "database")) { info->set_values |= SQL_DBNAME; snprintf(info->u.startup.dbname, sizeof(info->u.startup.dbname), "%s", value); } } while (1); // and enter "startup phase" untill the server is ready for query pg_parser->phase = STARTUP; } else { SLOG(LOG_DEBUG, "Unknown message"); return PROTO_PARSE_ERR; } } else { // reply (to an SSL request) if (wire_len != 1 || cap_len < 1) return PROTO_TOO_SHORT; info->set_values |= SQL_SSL_REQUEST; if (payload[0] == 'S') { info->u.startup.ssl_request = SQL_SSL_GRANTED; // We will get parse errors from now on :-< } else if (payload[0] == 'N') { info->u.startup.ssl_request = SQL_SSL_REFUSED; } else { return PROTO_PARSE_ERR; } } return proto_parse(NULL, &info->info, way, NULL, 0, 0, now, tot_cap_len, tot_packet); }