/* * After a query, server sends a list of column name with their types * * | 1 byte | 1 byte | 0-8 bytes | variable bytes | * | Length | Size number of fields | Number fields | unknown flags and fields | */ static enum proto_parse_status tns_parse_row_description_prefix(struct tns_parser *tns_parser, struct sql_proto_info *info, struct cursor *cursor) { enum proto_parse_status status; SLOG(LOG_DEBUG, "Parsing row description prefix"); CHECK(1); unsigned length = cursor_read_u8(cursor); DROP_FIX(cursor, length); DROP_VAR(cursor); if (PROTO_OK != (status = read_field_count(tns_parser, info, cursor))) return status; DROP_FIX(cursor, 1); for (unsigned i = 0; i < info->u.query.nb_fields; i++) { DROP_FIX(cursor, 3); DROP_VARS(cursor, 4); DROP_DALC(cursor); DROP_VARS(cursor, 2); DROP_FIX(cursor, 1); DROP_VAR(cursor); DROP_FIX(cursor, 2); for (unsigned i = 0; i < 3; ++i) { DROP_DALC(cursor); } DROP_VAR(cursor); } DROP_DALC(cursor); DROP_VARS(cursor, 2); return PROTO_OK; }
static enum proto_parse_status tns_parse_sql_query_jdbc(struct sql_proto_info *info, struct cursor *cursor) { SLOG(LOG_DEBUG, "Parsing a jdbc query"); enum proto_parse_status status; DROP_FIX(cursor, 1); uint_least64_t sql_len; status = cursor_read_variable_int(cursor, &sql_len); if (status != PROTO_OK) return status; SLOG(LOG_DEBUG, "Size sql %zu", sql_len); DROP_FIX(cursor, 1); // We have a number of fields at the end of the query uint_least64_t end_len; status = cursor_read_variable_int(cursor, &end_len); if (status != PROTO_OK) return status; DROP_FIX(cursor, 2); DROP_VARS(cursor, 3); DROP_FIX(cursor, 1); DROP_VAR(cursor); DROP_FIX(cursor, 6); DROP_VAR(cursor); char *sql = ""; if (sql_len > 0) { // Some unknown bytes while (cursor->cap_len > 1 && !isprint(cursor_peek_u8(cursor, 1))) { cursor_drop(cursor, 1); } CHECK(1); if (sql_len > 0xff && 0xff == cursor_peek_u8(cursor, 0)) { SLOG(LOG_DEBUG, "Looks like prefixed length chunks of size 0xff..."); status = cursor_read_chunked_string(cursor, &sql, 0xff); } else if (sql_len > 0x40 && 0x40 == cursor_peek_u8(cursor, 1)) { SLOG(LOG_DEBUG, "Looks like prefixed length chunks of size 0x40..."); cursor_drop(cursor, 1); status = cursor_read_chunked_string(cursor, &sql, 0x40); } else { if (!isprint(cursor_peek_u8(cursor, 0))) { cursor_drop(cursor, 1); } SLOG(LOG_DEBUG, "Looks like a fixed string of size %zu", sql_len); status = cursor_read_fixed_string(cursor, &sql, sql_len); } if (status != PROTO_OK) return status; } SLOG(LOG_DEBUG, "Sql parsed: %s", sql); sql_set_query(info, "%s", sql); SLOG(LOG_DEBUG, "Skipping %zu end variable fields", end_len); DROP_VARS(cursor, end_len); return PROTO_OK; }
/* * | 1 byte | (length + 1) * variable | 1 + 0-8 bytes | nb_ignore * variable | Variable until new ttc | * | length | ? | Nb ignore | ? | ? | */ static enum proto_parse_status tns_parse_row_description(struct cursor *cursor) { enum proto_parse_status status; SLOG(LOG_DEBUG, "Parsing a row description"); uint_least64_t length; status = cursor_read_variable_int(cursor, &length); if (status != PROTO_OK) return status; DROP_VARS(cursor, length); DROP_VAR(cursor); uint_least64_t nb_ignore; status = cursor_read_variable_int(cursor, &nb_ignore); if (status != PROTO_OK) return status; for (unsigned i = 0; i < nb_ignore; i++) { DROP_VAR(cursor); DROP_DALC(cursor); DROP_VAR(cursor); } CHECK(1); // Sometimes, we have some strange bytes... while (*cursor->head < 0x03 || *cursor->head > 0x15) { CHECK(2); DROP_FIX(cursor, 1); } return PROTO_OK; }
static enum proto_parse_status tns_parse_sql_query_oci(struct sql_proto_info *info, struct cursor *cursor) { SLOG(LOG_DEBUG, "Parsing an oci query"); enum proto_parse_status status; char pattern[] = {0xfe, MAX_OCI_CHUNK}; uint8_t const *new_head = memmem(cursor->head, cursor->cap_len, pattern, sizeof(pattern)); if (new_head != NULL) { size_t gap_size = new_head - cursor->head; SLOG(LOG_DEBUG, "%zu bytes before sql", gap_size); DROP_FIX(cursor, gap_size + 1); } else { SLOG(LOG_DEBUG, "{0xfe 0x40} not found, size might be < 0x40"); if (!lookup_query(cursor)) return PROTO_PARSE_ERR; } char *sql; if (PROTO_OK != (status = cursor_read_chunked_string(cursor, &sql, MAX_OCI_CHUNK))) return status; SLOG(LOG_DEBUG, "Sql parsed: %s", sql); sql_set_query(info, "%s", sql); // Drop the rest if(cursor->cap_len > 0) cursor_drop(cursor, cursor->cap_len - 1); return PROTO_OK; }
static enum proto_parse_status tns_parse_close_statement(struct cursor *cursor) { SLOG(LOG_DEBUG, "Parsing a close statement"); // Subcode DROP_FIX(cursor, 1); // Sequence DROP_FIX(cursor, 1); // Pointer DROP_FIX(cursor, 1); // We seek the next query uint8_t marker[2] = {0x03, 0x5e}; if (cursor_drop_until(cursor, marker, sizeof(marker), cursor->cap_len) < 0) return PROTO_PARSE_ERR; SLOG(LOG_DEBUG, "Found a possible query ttc, exiting close statement"); return PROTO_OK; }
static enum proto_parse_status tns_parse_sql_query(struct sql_proto_info *info, struct cursor *cursor) { DROP_FIX(cursor, 1); if (is_oci(cursor)) { // Option is not prefix based, seems like an oci query return tns_parse_sql_query_oci(info, cursor); } else { return tns_parse_sql_query_jdbc(info, cursor); } }
static enum proto_parse_status tns_parse_login_property(struct sql_proto_info *info, struct cursor *cursor) { SLOG(LOG_DEBUG, "Parsing tns login property"); // We are only interested in response if (info->is_query) return PROTO_OK; if (info->msg_type != SQL_UNKNOWN && info->msg_type != SQL_STARTUP) return PROTO_PARSE_ERR; // Drop Server version DROP_FIX(cursor, 3); // Drop Server version text uint8_t marker = 0x00; enum proto_parse_status status = cursor_drop_until(cursor, &marker, sizeof(marker)); if (status != PROTO_OK) return status; // Drop Null byte DROP_FIX(cursor, 1); CHECK(2); uint16_t charset = cursor_read_u16le(cursor); SLOG(LOG_DEBUG, "Found a charset of 0x%02x", charset); switch (charset) { case 0x01: case 0x02: case 0x1f: case 0xb2: sql_set_encoding(info, SQL_ENCODING_LATIN1); break; case 0x366: case 0x367: case 0x369: sql_set_encoding(info, SQL_ENCODING_UTF8); break; default: SLOG(LOG_DEBUG, "Unknown charset"); break; } // We don't care of the rest... cursor_drop(cursor, cursor->cap_len); return PROTO_OK; }
/* * | 1 byte | 1 + 0-8 bytes | up to 5 vars | * | Flag | Number column | unknown | */ static enum proto_parse_status tns_parse_row_prefix(struct tns_parser *tns_parser, struct sql_proto_info *info, struct cursor *cursor) { enum proto_parse_status status; SLOG(LOG_DEBUG, "Parsing Row prefix"); DROP_FIX(cursor, 1); if (PROTO_OK != (status = read_field_count(tns_parser, info, cursor))) return status; for (unsigned i = 0; i < 5; i++) { CHECK(1); char c = cursor_peek_u8(cursor, 0); if (c == TTC_ROW_DATA || c == TTC_END_MESSAGE) return PROTO_OK; DROP_VAR(cursor); } return PROTO_OK; }
static enum proto_parse_status tns_parse_row_recap(struct cursor *cursor) { enum proto_parse_status status; SLOG(LOG_DEBUG, "Parsing Row recap"); /* A row recap contains : * - 1 var number of column sent * - <number of fields> bytes to ignore */ uint_least64_t num_fields; status = cursor_read_variable_int(cursor, &num_fields); if (status != PROTO_OK) return status; unsigned nb_ignore = (num_fields + 7) / 8; DROP_FIX(cursor, nb_ignore); return PROTO_OK; }
static enum proto_parse_status tns_parse_row_prefix(struct tns_parser *tns_parser, struct sql_proto_info *info, struct cursor *cursor) { enum proto_parse_status status; SLOG(LOG_DEBUG, "Parsing Row prefix"); /* A row prefix contains * - 1 byte flag * - Number column * - 5 var */ DROP_FIX(cursor, 1); if (PROTO_OK != (status = read_field_count(tns_parser, info, cursor))) return status; DROP_VARS(cursor, 5); return PROTO_OK; }
static enum proto_parse_status tns_parse_sql_query(struct sql_proto_info *info, struct cursor *cursor) { DROP_FIX(cursor, 1); if (is_oci(cursor)) { // Option is not prefix based, seems like an oci query return tns_parse_sql_query_oci(info, cursor); } else { struct cursor save_cursor = *cursor; if (tns_parse_sql_query_jdbc(info, cursor) != PROTO_OK) { // Fallback to query guessing SLOG(LOG_DEBUG, "jdbc query failed, fallback to oci"); *cursor = save_cursor; return tns_parse_sql_query_oci(info, cursor); } else { return PROTO_OK; } } }
/* * Sometimes, we have up to 10 {0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff} patterns. * Skip them to avoid breaking on an eventual 0x07 (TTC_ROW_DATA) * The query size seems to be at the end of the first pattern */ static uint8_t parse_query_header(struct cursor *cursor) { uint8_t const *new_head = cursor->head; uint8_t query_size = 0; for (unsigned i = 0; i < 10 && new_head; i++) { char pattern[8] = {0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; new_head = memmem(cursor->head, cursor->cap_len, pattern, sizeof(pattern)); if (new_head) { size_t gap_size = new_head - cursor->head; DROP_FIX(cursor, gap_size + sizeof(pattern)); if (i == 0) { CHECK(1); query_size = cursor_read_u8(cursor); SLOG(LOG_DEBUG, "Found potential query size: %d", query_size); } } }; return query_size; }
static enum proto_parse_status tns_parse_sql_query_oci(struct sql_proto_info *info, struct cursor *cursor) { SLOG(LOG_DEBUG, "Parsing an oci query"); uint8_t query_size = parse_query_header(cursor); struct query_candidate candidate = {.num_candidate_size = 0}; if (query_size > 0) { candidate.candidate_sizes[candidate.num_candidate_size++] = query_size; } bool has_query = lookup_query(cursor, &candidate); info->u.query.sql[0] = '\0'; info->u.query.truncated = 0; if (has_query) { SLOG(LOG_DEBUG, "Found a query, parsing it"); if (candidate.is_chunked) { cursor_read_chunked_string(cursor, info->u.query.sql, sizeof(info->u.query.sql), MAX_OCI_CHUNK); } else { cursor_read_fixed_string(cursor, info->u.query.sql, sizeof(info->u.query.sql), candidate.query_size); } } SLOG(LOG_DEBUG, "Sql parsed: %s", info->u.query.sql); info->set_values |= SQL_SQL; // Drop the rest if(cursor->cap_len > 0) cursor_drop(cursor, cursor->cap_len - 1); return PROTO_OK; } /* * | 1 byte | 1 + 0-8 bytes | 1 byte | 1 + 0-4 bytes | Lots of unknown bytes | variable | * | Unk | Sql len | Unk | Num end fields | Unk | sql query | */ static enum proto_parse_status tns_parse_sql_query_jdbc(struct sql_proto_info *info, struct cursor *cursor) { SLOG(LOG_DEBUG, "Parsing a jdbc query"); enum proto_parse_status status = PROTO_OK; DROP_FIX(cursor, 1); uint_least64_t sql_len; if (PROTO_OK != (status = cursor_read_variable_int(cursor, &sql_len))) return status; SLOG(LOG_DEBUG, "Size sql %zu", sql_len); DROP_FIX(cursor, 1); DROP_VAR(cursor); DROP_FIX(cursor, 2); info->u.query.sql[0] = '\0'; info->u.query.truncated = 0; // TODO Handle truncated if (sql_len > 0) { // Some unknown bytes while (cursor->cap_len > 1 && PROTO_OK != is_range_print(cursor, MIN(MIN_QUERY_SIZE, sql_len), 1)) { // TODO drop to the first non printable cursor_drop(cursor, 1); } CHECK(1); if (sql_len > 0xff && 0xff == cursor_peek_u8(cursor, 0)) { SLOG(LOG_DEBUG, "Looks like prefixed length chunks of size 0xff..."); status = cursor_read_chunked_string(cursor, info->u.query.sql, sizeof(info->u.query.sql), 0xff); } else if (sql_len > MAX_OCI_CHUNK && MAX_OCI_CHUNK == cursor_peek_u8(cursor, 0)) { SLOG(LOG_DEBUG, "Looks like prefixed length chunks of size 0x40..."); status = cursor_read_chunked_string(cursor, info->u.query.sql, sizeof(info->u.query.sql), MAX_OCI_CHUNK); } else { // We don't care about the first non printable character cursor_drop(cursor, 1); CHECK(1); // In rare occurrence where sql_len == first character, we check the byte after the expected query, // if it's printable, the first character is probably the prefixed size. if (cursor_peek_u8(cursor, 0) == sql_len && sql_len < cursor->cap_len && is_print(cursor_peek_u8(cursor, sql_len))) cursor_drop(cursor, 1); SLOG(LOG_DEBUG, "Looks like a fixed string of size %zu", sql_len); int written_bytes = cursor_read_fixed_string(cursor, info->u.query.sql, sizeof(info->u.query.sql), sql_len); if (written_bytes < 0) return PROTO_TOO_SHORT; } if (status != PROTO_OK) return status; } SLOG(LOG_DEBUG, "Sql parsed: %s", info->u.query.sql); info->set_values |= SQL_SQL; return PROTO_OK; }
static enum proto_parse_status tns_parse_end(struct sql_proto_info *info, struct cursor *cursor) { SLOG(LOG_DEBUG, "Parsing tns end packet"); enum proto_parse_status status; uint_least64_t var[6]; for (unsigned i = 0; i < 6; i++) { if (PROTO_OK != (status = cursor_read_variable_int(cursor, var + i))) return status; } uint_least64_t nb_rows; uint_least64_t error_code; // let's use the double 0x00 to guess the position of row number and error code if (var[0] > 0 && var[4] == 0 && var[5] == 0) { // var[0] is unknown? // var[1] == sequence // var[2] == rows // var[3] == error code SLOG(LOG_DEBUG, "Unknown bits after ttc code"); nb_rows = var[2]; error_code = var[3]; DROP_VAR(cursor); } else if (var[3] == 0 && var[4] == 0) { // var[0] == sequence // var[1] == rows // var[2] == error code nb_rows = var[1]; error_code = var[2]; } else { // var[0] == rows // var[1] == error code nb_rows = var[0]; error_code = var[1]; } if (info->msg_type == SQL_QUERY) { sql_set_row_count(info, nb_rows); SLOG(LOG_DEBUG, "Nb rows %d", info->u.query.nb_rows); } SLOG(LOG_DEBUG, "Error code is %zu", error_code); DROP_VARS(cursor, 1); DROP_FIX(cursor, 2); DROP_VARS(cursor, 2); DROP_FIX(cursor, 2); DROP_VARS(cursor, 2); DROP_FIX(cursor, 1); DROP_VARS(cursor, 3); if (error_code != 0) { SLOG(LOG_DEBUG, "Parsing error message"); char *error_msg = tempstr(); unsigned error_len; // Drop an unknown number of bytes here while(cursor->cap_len > 2 && (cursor_peek_u8(cursor, 0) == 0 || !is_print(cursor_peek_u8(cursor, 1)))){ DROP_FIX(cursor, 1); } SLOG(LOG_DEBUG, "First printable char is %c", cursor_peek_u8(cursor, 1)); status = cursor_read_variable_string(cursor, error_msg, TEMPSTR_SIZE, &error_len); if (status != PROTO_OK) return status; if (error_len < 12) return PROTO_PARSE_ERR; // Split "ORA-XXXX: msg" // Part before : is the error code // Part after is the localized message char *colon_pos = memchr(error_msg, ':', error_len); sql_set_request_status(info, SQL_REQUEST_ERROR); if (colon_pos) { // We extract the error code unsigned error_code_size = colon_pos - error_msg; int size_err = MIN(error_code_size, sizeof(info->error_code)); memcpy(info->error_code, error_msg, size_err); info->error_code[size_err] = '\0'; info->set_values |= SQL_ERROR_CODE; if (0 == strcmp("ORA-01403", info->error_code)) info->request_status = SQL_REQUEST_COMPLETE; // We skip ':' in errror message char const *start_message = colon_pos + 1; // We skip spaces before errror message while (start_message < error_len + error_msg && *start_message == ' ') start_message++; copy_string(info->error_message, start_message, sizeof(info->error_message)); info->set_values |= SQL_ERROR_MESSAGE; } else { copy_string(info->error_message, error_msg, sizeof(info->error_message)); info->set_values |= SQL_ERROR_MESSAGE; } } return PROTO_OK; }
static enum proto_parse_status tns_parse_end(struct sql_proto_info *info, struct cursor *cursor) { SLOG(LOG_DEBUG, "Parsing tns end packet"); enum proto_parse_status status; uint_least64_t var0; uint_least64_t var1; uint_least64_t var2; uint_least64_t var3; uint_least64_t var4; uint_least64_t var5; if (PROTO_OK != (status = cursor_read_variable_int(cursor, &var0))) return status; if (PROTO_OK != (status = cursor_read_variable_int(cursor, &var1))) return status; if (PROTO_OK != (status = cursor_read_variable_int(cursor, &var2))) return status; if (PROTO_OK != (status = cursor_read_variable_int(cursor, &var3))) return status; if (PROTO_OK != (status = cursor_read_variable_int(cursor, &var4))) return status; if (PROTO_OK != (status = cursor_read_variable_int(cursor, &var5))) return status; uint_least64_t nb_rows; uint_least64_t error_code; if (var0 != 0 && var4 == 0 && var5 == 0) { // First var is unknown? SLOG(LOG_DEBUG, "Unknown bits after ttc code"); nb_rows = var2; error_code = var3; DROP_VAR(cursor); } else { // var0 == sequence // var1 == rows // var2 == error code nb_rows = var1; error_code = var2; } if (info->msg_type == SQL_QUERY) { sql_set_row_count(info, nb_rows); SLOG(LOG_DEBUG, "Nb rows %d", info->u.query.nb_rows); } SLOG(LOG_DEBUG, "Error code is %zu", error_code); DROP_VARS(cursor, 1); DROP_FIX(cursor, 2); DROP_VARS(cursor, 2); DROP_FIX(cursor, 2); DROP_VARS(cursor, 2); DROP_FIX(cursor, 1); DROP_VARS(cursor, 3); DROP_FIX(cursor, 2); DROP_VARS(cursor, 2); if (error_code != 0) { SLOG(LOG_DEBUG, "Parsing error message"); char *error_msg; unsigned error_len; // Drop an unknown number of column here while(cursor->cap_len > 1 && !isprint(*(cursor->head + 1))){ DROP_FIX(cursor, 1); } status = cursor_read_variable_string(cursor, &error_msg, &error_len); if (status != PROTO_OK) return status; // Split "ORA-XXXX: msg" // Part before : is the error code // Part after is the localized message char *colon_pos = memchr(error_msg, ':', error_len); sql_set_request_status(info, SQL_REQUEST_ERROR); if (colon_pos) { // We extract the error code unsigned error_code_size = colon_pos - error_msg; int size_err = MIN(error_code_size, sizeof(info->error_code)); memcpy(info->error_code, error_msg, size_err); info->error_code[size_err] = '\0'; info->set_values |= SQL_ERROR_CODE; if (0 == strcmp("ORA-01403", info->error_code)) info->request_status = SQL_REQUEST_COMPLETE; // We skip ':' in errror message char const *start_message = colon_pos + 1; // We skip spaces before errror message while (start_message < error_len + error_msg && *start_message == ' ') start_message++; copy_string(info->error_message, start_message, sizeof(info->error_message)); info->set_values |= SQL_ERROR_MESSAGE; } else { copy_string(info->error_message, error_msg, sizeof(info->error_message)); info->set_values |= SQL_ERROR_MESSAGE; } } return PROTO_OK; }