/* TNS PDU have a header consisting of (in network byte order) : * * | 2 bytes | 2 bytes | 1 byte | 1 byte | 2 bytes | * | Length | Checksum | Type | a zero | Header checksum | */ static enum proto_parse_status cursor_read_tns_hdr(struct cursor *cursor, size_t *out_len, unsigned *out_type, size_t wire_len) { SLOG(LOG_DEBUG, "Reading a TNS PDU"); CHECK_LEN(cursor, 8, 0); size_t len = cursor_read_u16n(cursor); if (len < 8 || len < wire_len) return PROTO_PARSE_ERR; len -= 8; SLOG(LOG_DEBUG, "TNS PDU len == %zu", len); // Checksum should be 0 uint_least16_t checksum = cursor_read_u16n(cursor); if (checksum > 0) { SLOG(LOG_DEBUG, "Tns checksum should be 0, got %u", checksum); return PROTO_PARSE_ERR; } unsigned type = cursor_read_u8(cursor); if (type >= TNS_TYPE_MAX) { SLOG(LOG_DEBUG, "Tns type invalid, sould be < %u, got %u", TNS_TYPE_MAX, type); return PROTO_PARSE_ERR; } // reserved byte and header checksum should be 0 uint_least32_t head_checksum =cursor_read_u24(cursor); if (head_checksum > 0) { SLOG(LOG_DEBUG, "Reserved byte and checksum should be 0, got %u", head_checksum); return PROTO_PARSE_ERR; } if (out_len) *out_len = len; if (out_type) *out_type = type; // Check we have the msg payload CHECK(len); return PROTO_OK; }
static enum proto_parse_status tns_parse_connect(struct tns_parser unused_ *tns_parser, struct sql_proto_info *info, struct cursor *cursor) { if (! info->is_query) return PROTO_PARSE_ERR; SLOG(LOG_DEBUG, "Parsing TNS connect PDU of size %zu", cursor->cap_len); /* A connect is (in network byte order) : * - 2 bytes version * - 2 bytes back compatibility * - 2 bytes service options * - 2 bytes session data unit size * - 2 bytes max transm. data unit size * - 2 bytes proto characteristics * - 2 bytes line turnaround * - 2 bytes value of one * - 2 bytes connect data length * - 2 bytes connect data offset * - 4 bytes connect data max * - 1 byte connect flags 0 * - 1 byte connect flags 1 * - optionaly, 16 bytes for trace things * - padding until data offset * - then connect data */ size_t const pdu_len = cursor->cap_len; if (pdu_len < 26) return PROTO_PARSE_ERR; unsigned version = cursor_read_u16n(cursor); info->version_maj = version/100; info->version_min = version%100; info->set_values |= SQL_VERSION; cursor_drop(cursor, 14); // jump to connect data length unsigned data_length = cursor_read_u16n(cursor); unsigned data_offset = cursor_read_u16n(cursor); SLOG(LOG_DEBUG, "Connect, data length=%u, data offset=%u", data_length, data_offset); if (data_offset > pdu_len || data_offset < 26 + 8) return PROTO_PARSE_ERR; if (data_length + data_offset > pdu_len + 8) return PROTO_PARSE_ERR; cursor_drop(cursor, data_offset - 20 - 8); // jump to data // Now look for user and dbname (ie. service_name) # define USER_TOKEN "(USER="******"(SERVICE_NAME=" char const *data_end = (char const *)(cursor->head + data_length); char const *str; if (NULL != (str = strnstr((char const *)cursor->head, USER_TOKEN, data_length))) { str += strlen(USER_TOKEN); info->set_values |= SQL_USER; copy_token(info->u.startup.user, sizeof(info->u.startup.user), str, data_end-str); } if (NULL != (str = strnstr((char const *)cursor->head, DBNAME_TOKEN, data_length))) { str += strlen(DBNAME_TOKEN); info->set_values |= SQL_DBNAME; copy_token(info->u.startup.dbname, sizeof(info->u.startup.dbname), str, data_end-str); } 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; }
static enum proto_parse_status cursor_read_tns_hdr(struct cursor *cursor, size_t *out_len, unsigned *out_type) { /* TNS PDU have a header consisting of (in network byte order) : * - a 2 bytes length * - a 2 bytes checksum * - a one byte type * - a one byte 0 * - a 2 bytes header checksum (or 0) */ SLOG(LOG_DEBUG, "Reading a TNS PDU"); CHECK_LEN(cursor, 8, 0); size_t len = cursor_read_u16n(cursor); if (len < 8) return PROTO_PARSE_ERR; len -= 8; SLOG(LOG_DEBUG, "TNS PDU len == %zu", len); // skip packet checksum (2 bytes) cursor_drop(cursor, 2); unsigned type = cursor_read_u8(cursor); // Skip Reserved byte and header checksum (1 + 2 bytes) cursor_drop(cursor, 3); if (type > TNS_TYPE_MAX) return PROTO_PARSE_ERR; // Check we have the msg payload CHECK_LEN(cursor, len, 8); if (out_len) *out_len = len; if (out_type) *out_type = type; return PROTO_OK; }
static enum proto_parse_status tds_parse_header(struct cursor *cursor, struct tds_header *out_header, bool *unknown_token) { # define TDS_PKT_HDR_LEN 8 CHECK_LEN(cursor, TDS_PKT_HDR_LEN, 0); struct tds_header header; header.type = cursor_read_u8(cursor); header.status = cursor_read_u8(cursor); header.len = cursor_read_u16n(cursor); header.channel = cursor_read_u16n(cursor); header.pkt_number = cursor_read_u8(cursor); header.window = cursor_read_u8(cursor); SLOG(LOG_DEBUG, "Reading new TDS packet %s", tds_header_2_str(&header)); // sanity check if (header.len < TDS_PKT_HDR_LEN) return PROTO_PARSE_ERR; switch (header.type) { case TDS_PKT_TYPE_SQL_BATCH: case TDS_PKT_TYPE_LOGIN: case TDS_PKT_TYPE_RPC: case TDS_PKT_TYPE_RESULT: case TDS_PKT_TYPE_ATTENTION: case TDS_PKT_TYPE_BULK_LOAD: case TDS_PKT_TYPE_MANAGER_REQ: case TDS_PKT_TYPE_TDS7_LOGIN: case TDS_PKT_TYPE_SSPI: case TDS_PKT_TYPE_PRELOGIN: break; default: SLOG(LOG_DEBUG, "Unknown tds type %u", header.type); if (unknown_token) *unknown_token = true; return PROTO_PARSE_ERR; } if (header.window != 0) { SLOG(LOG_DEBUG, "Window is %"PRIu8" instead of 0", header.window); return PROTO_PARSE_ERR; } size_t data_left = header.len - TDS_PKT_HDR_LEN; if ((data_left > 0) != tds_packet_has_data(header.type)) { SLOG(LOG_DEBUG, "This TDS packet of type %s has %zu bytes of data, but should%s have data", tds_packet_type_2_str(header.type), data_left, tds_packet_has_data(header.type) ? "":" not"); return PROTO_PARSE_ERR; } if (out_header) *out_header = header; return PROTO_OK; }
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 tns_parse_accept(struct tns_parser unused_ *tns_parser, struct sql_proto_info *info, struct cursor *cursor) { if (info->is_query) return PROTO_PARSE_ERR; SLOG(LOG_DEBUG, "Parsing TNS accept PDU of size %zu", cursor->cap_len); /* An accept message is constitued of : * - 2 bytes version * - 2 bytes service options * - 2 bytes session data unit size * - 2 bytes max transm. data unit size * - 2 bytes value of one * - 2 bytes data length * - 2 bytes data offset * - 1 byte connect flag 0 * - 1 byte connect flag 1 */ CHECK(10); unsigned version = cursor_read_u16n(cursor); info->version_maj = version/100; info->version_min = version%100; info->set_values |= SQL_VERSION; sql_set_request_status(info, SQL_REQUEST_COMPLETE); return PROTO_OK; }
uint_least32_t cursor_read_u32n(struct cursor *cursor) { uint_least32_t a = cursor_read_u16n(cursor); uint_least32_t b = cursor_read_u16n(cursor); return (a << 16) | b; }
/* * | 2 bytes | 1 byte | Variable | * | Flags | TTC code | TTC body | */ static enum proto_parse_status tns_parse_data(struct tns_parser *tns_parser, struct sql_proto_info *info, struct cursor *cursor, unsigned way) { SLOG(LOG_DEBUG, "Parsing TNS data PDU of size %zu", cursor->cap_len); enum proto_parse_status status = PROTO_OK; // First, read the data flags CHECK(2); unsigned flags = cursor_read_u16n(cursor); SLOG(LOG_DEBUG, "Data flags = 0x%x", flags); if (flags & 0x40) { // End Of File if (cursor->cap_len != 0) return PROTO_PARSE_ERR; // This may be wrong, maybe a command is allowed anyway info->msg_type = SQL_EXIT; sql_set_request_status(info, SQL_REQUEST_COMPLETE); return PROTO_OK; } info->msg_type = tns_parser->msg_type; while (status == PROTO_OK && cursor->cap_len) { CHECK(1); enum ttc_code ttc_code = cursor_read_u8(cursor); SLOG(LOG_DEBUG, "Ttc code = 0x%02x, msg type %s", ttc_code, sql_msg_type_2_str(tns_parser->msg_type)); switch (ttc_code) { case TTC_ROW_PREFIX: status = tns_parse_row_prefix(tns_parser, info, cursor); break; case TTC_ROW_DATA: status = tns_parse_row_data(tns_parser, info, cursor); break; case TTC_ROW_DESCRIPTION_PREFIX: status = tns_parse_row_description_prefix(tns_parser, info, cursor); break; case TTC_ROW_RECAP: status = tns_parse_row_recap(cursor); break; case TTC_ROW_DESCRIPTION: status = tns_parse_row_description(cursor); break; case TTC_LOGIN_PROPERTY: status = tns_parse_login_property(info, cursor); break; case TTC_QUERY: status = tns_parse_query(tns_parser, info, cursor); break; case TTC_END_MESSAGE: status = tns_parse_end(info, cursor); break; case TTC_CLOSE: status = tns_parse_close_statement(cursor); break; default: SLOG(LOG_DEBUG, "Unknown ttc_code = %u", ttc_code); return PROTO_OK; } if (status == PROTO_OK) { enum sql_msg_type ttc_msg_type = ttc_to_msg_type(tns_parser, ttc_code); if (ttc_msg_type != SQL_UNKNOWN) { info->msg_type = ttc_msg_type; tns_parser->msg_type = ttc_msg_type; } // Fix c2s_way bool old_c2s_way = tns_parser->c2s_way; switch (ttc_code) { case TTC_ROW_DESCRIPTION_PREFIX: case TTC_ROW_RECAP: case TTC_ROW_DESCRIPTION: case TTC_END_MESSAGE: tns_parser->c2s_way = !way; break; case TTC_QUERY: case TTC_CLOSE: tns_parser->c2s_way = way; break; default: break; } if (old_c2s_way != tns_parser->c2s_way) { SLOG(LOG_DEBUG, "Fix c2s way from %d to %d", old_c2s_way, tns_parser->c2s_way); } info->is_query = way == tns_parser->c2s_way; } } return status; }
static enum proto_parse_status pg_parse_query(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) { enum proto_parse_status status; info->msg_type = SQL_QUERY; struct cursor cursor; cursor_ctor(&cursor, payload, cap_len); uint8_t type; size_t len; /* In this phase, we are looking for SimpleQuery from the client and Data from the server. * This is very simplistic, to be completed later with more interresting query types. * Also, the client can send a termination request. */ if (info->is_query) { status = cursor_read_msg(&cursor, &type, &len); if (status != PROTO_OK) return status; if (type == 'Q') { // simple query char *sql; status = cursor_read_string(&cursor, &sql, len); if (status != PROTO_OK) return status; info->set_values |= SQL_SQL; snprintf(info->u.query.sql, sizeof(info->u.query.sql), "%s", sql); } else if (type == 'X') { info->msg_type = SQL_EXIT; info->set_values |= SQL_REQUEST_STATUS; info->request_status = SQL_REQUEST_COMPLETE; pg_parser->phase = EXIT; } else return PROTO_PARSE_ERR; } else { while (! cursor_is_empty(&cursor)) { uint8_t const *const msg_start = cursor.head; status = cursor_read_msg(&cursor, &type, &len); if (status == PROTO_PARSE_ERR) return status; else if (status == PROTO_TOO_SHORT) { SLOG(LOG_DEBUG, "Payload too short for parsing message, will restart"); status = proto_parse(NULL, &info->info, way, NULL, 0, 0, now, tot_cap_len, tot_packet); // ack what we had so far streambuf_set_restart(&pg_parser->sbuf, way, msg_start, true); return PROTO_OK; } uint8_t const *const msg_end = cursor.head + len; if (type == 'T') { // row description (fetch nb_fields) if (len < 2) return PROTO_PARSE_ERR; info->u.query.nb_fields = cursor_read_u16n(&cursor); info->set_values |= SQL_NB_FIELDS; SLOG(LOG_DEBUG, "Setting nb_fields to %u", info->u.query.nb_fields); } else if (type == 'D') { // data row if (len < 2) return PROTO_PARSE_ERR; if (! (info->set_values & SQL_NB_ROWS)) { info->set_values |= SQL_NB_ROWS; info->u.query.nb_rows = 0; } info->u.query.nb_rows ++; SLOG(LOG_DEBUG, "Incrementing nb_rows (now %u)", info->u.query.nb_rows); } else if (type == 'C') { // command complete (fetch nb rows) char *result; info->set_values |= SQL_REQUEST_STATUS; info->request_status = SQL_REQUEST_COMPLETE; status = cursor_read_string(&cursor, &result, len); if (status != PROTO_OK) return status; status = fetch_nb_rows(result, &info->u.query.nb_rows); if (status == PROTO_OK) { info->set_values |= SQL_NB_ROWS; } else { //return status; // Do not use this as the actual protocol does not seam to implement the doc :-< } } else if (type == 'E') { // error status = pg_parse_error(info, &cursor, len); if (status != PROTO_OK) return status; } // Skip what's left of this message and go for the next assert(msg_end >= cursor.head); cursor_drop(&cursor, msg_end - cursor.head); } } return proto_parse(NULL, &info->info, way, NULL, 0, 0, now, tot_cap_len, tot_packet); }