From dbbd0e957a5adee90e1ef92bb60e53962eccd8ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Tue, 8 May 2018 10:25:12 +0300 Subject: [PATCH 1/9] MXS-1852: Close partially connected DCBs if killed If a connection is killed but the backend DCBs have not yet received their thread IDs, the connections can be forcibly closed. This removes the possibility of stale connections caused by an unfortunately timed KILL query to a session that has partially connected to some servers. --- server/modules/protocol/MySQL/mysql_common.cc | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/server/modules/protocol/MySQL/mysql_common.cc b/server/modules/protocol/MySQL/mysql_common.cc index 73b8e93b0..3069dc201 100644 --- a/server/modules/protocol/MySQL/mysql_common.cc +++ b/server/modules/protocol/MySQL/mysql_common.cc @@ -29,6 +29,7 @@ #include #include #include +#include uint8_t null_client_sha1[MYSQL_SCRAMBLE_LEN] = ""; @@ -1694,7 +1695,17 @@ static bool kill_func(DCB *dcb, void *data) dcb->session->ses_id == info->target_id) { MySQLProtocol* proto = (MySQLProtocol*)dcb->protocol; - info->targets.push_back(std::make_pair(dcb->server, proto->thread_id)); + + if (proto->thread_id) + { + // DCB is connected and we know the thread ID so we can kill it + info->targets.push_back(std::make_pair(dcb->server, proto->thread_id)); + } + else + { + // DCB is not yet connected, send a hangup to forcibly close it + poll_fake_hangup_event(dcb); + } } return true; From a544239540612c59b717273c919bddd40d6521d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Thu, 10 May 2018 22:24:14 +0300 Subject: [PATCH 2/9] Fix masking filter documentation The example had invalid JSON in it. --- Documentation/Filters/Masking.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/Filters/Masking.md b/Documentation/Filters/Masking.md index d15eea215..17a0f37f6 100644 --- a/Documentation/Filters/Masking.md +++ b/Documentation/Filters/Masking.md @@ -241,7 +241,7 @@ of the mask value to get the lengths to match. "column": "creditcard" }, "with": { - "value": "1234123412341234" + "value": "1234123412341234", "fill": "0" }, "applies_to": [ ... ], From 7d784001df25cb6b1a257e772c5e2af97ffb33cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Thu, 10 May 2018 12:45:43 +0300 Subject: [PATCH 3/9] MXS-1628: Respond with correct error to malformed packets If the client sent a malformed authentication response packet, MaxScale would interpret that as failed authentication. --- include/maxscale/authenticator.h | 1 + .../authenticator/MySQLAuth/mysql_auth.c | 32 +++++++++++++------ .../protocol/MySQL/MySQLClient/mysql_client.c | 4 +++ 3 files changed, 28 insertions(+), 9 deletions(-) diff --git a/include/maxscale/authenticator.h b/include/maxscale/authenticator.h index 94346c909..af65e9a2f 100644 --- a/include/maxscale/authenticator.h +++ b/include/maxscale/authenticator.h @@ -99,6 +99,7 @@ typedef struct mxs_authenticator #define MXS_AUTH_INCOMPLETE 4 /**< Authentication is not yet complete */ #define MXS_AUTH_SSL_INCOMPLETE 5 /**< SSL connection is not yet complete */ #define MXS_AUTH_NO_SESSION 6 +#define MXS_AUTH_BAD_HANDSHAKE 7 /**< Malformed client packet */ /** Return values for the loadusers entry point */ #define MXS_AUTH_LOADUSERS_OK 0 /**< Users loaded successfully */ diff --git a/server/modules/authenticator/MySQLAuth/mysql_auth.c b/server/modules/authenticator/MySQLAuth/mysql_auth.c index 2b9177e21..672abd1d1 100644 --- a/server/modules/authenticator/MySQLAuth/mysql_auth.c +++ b/server/modules/authenticator/MySQLAuth/mysql_auth.c @@ -475,11 +475,21 @@ mysql_auth_set_client_data( if (client_auth_packet_size > MYSQL_AUTH_PACKET_BASE_SIZE) { /* Should have a username */ - char *first_letter_of_username = (char *)(client_auth_packet + MYSQL_AUTH_PACKET_BASE_SIZE); - int user_length = strlen(first_letter_of_username); + uint8_t* name = client_auth_packet + MYSQL_AUTH_PACKET_BASE_SIZE; + uint8_t* end = client_auth_packet + sizeof(client_auth_packet); + int user_length = 0; - ss_dassert(client_auth_packet_size > (MYSQL_AUTH_PACKET_BASE_SIZE + user_length) - && user_length <= MYSQL_USER_MAXLEN); + while (name < end && *name) + { + name++; + user_length++; + } + + if (name == end) + { + // The name is not null terminated + return MXS_AUTH_BAD_HANDSHAKE; + } if (client_auth_packet_size > (MYSQL_AUTH_PACKET_BASE_SIZE + user_length + 1)) { @@ -487,14 +497,14 @@ mysql_auth_set_client_data( packet_length_used = MYSQL_AUTH_PACKET_BASE_SIZE + user_length + 1; /* We should find an authentication token next */ /* One byte of packet is the length of authentication token */ - memcpy(&client_data->auth_token_len, - client_auth_packet + packet_length_used, 1); + client_data->auth_token_len = client_auth_packet[packet_length_used]; if (client_auth_packet_size > (packet_length_used + client_data->auth_token_len)) { - /* Packet is large enough for authentication token */ - if (NULL != (client_data->auth_token = (uint8_t *)MXS_MALLOC(client_data->auth_token_len))) + client_data->auth_token = (uint8_t*)MXS_MALLOC(client_data->auth_token_len); + + if (client_data->auth_token) { /* The extra 1 is for the token length byte, just extracted*/ memcpy(client_data->auth_token, @@ -510,9 +520,13 @@ mysql_auth_set_client_data( else { /* Packet was too small to contain authentication token */ - return MXS_AUTH_FAILED; + return MXS_AUTH_BAD_HANDSHAKE; } } + else + { + return MXS_AUTH_BAD_HANDSHAKE; + } } return MXS_AUTH_SUCCEEDED; } diff --git a/server/modules/protocol/MySQL/MySQLClient/mysql_client.c b/server/modules/protocol/MySQL/MySQLClient/mysql_client.c index ef96a5c15..532357020 100644 --- a/server/modules/protocol/MySQL/MySQLClient/mysql_client.c +++ b/server/modules/protocol/MySQL/MySQLClient/mysql_client.c @@ -1132,6 +1132,10 @@ mysql_client_auth_error_handling(DCB *dcb, int auth_val, int packet_number) modutil_send_mysql_err_packet(dcb, packet_number, 0, 1045, "28000", fail_str); break; + case MXS_AUTH_BAD_HANDSHAKE: + modutil_send_mysql_err_packet(dcb, packet_number, 0, 1045, "08S01", "Bad handshake"); + break; + default: MXS_DEBUG("%lu [gw_read_client_event] authentication failed. fd %d, " "state unrecognized.", pthread_self(), dcb->fd); From d7ca5f5b5dc3a12e84f8e42cf9c532c054e8e4ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Thu, 10 May 2018 12:48:23 +0300 Subject: [PATCH 4/9] MXS-1628: Add bad handshake test case The test checks that a bad handshake error is returned when a malformed packet is sent. --- maxscale-system-test/CMakeLists.txt | 8 +- .../mxs1628_bad_handshake.cpp | 71 +++++++++++++++ maxscale-system-test/tcp_connection.cpp | 89 +++++++++++++++++++ maxscale-system-test/tcp_connection.h | 49 ++++++++++ 4 files changed, 215 insertions(+), 2 deletions(-) create mode 100644 maxscale-system-test/mxs1628_bad_handshake.cpp create mode 100644 maxscale-system-test/tcp_connection.cpp create mode 100644 maxscale-system-test/tcp_connection.h diff --git a/maxscale-system-test/CMakeLists.txt b/maxscale-system-test/CMakeLists.txt index 73d22d729..762d3a685 100644 --- a/maxscale-system-test/CMakeLists.txt +++ b/maxscale-system-test/CMakeLists.txt @@ -50,8 +50,8 @@ add_library(testcore SHARED testconnections.cpp mariadb_nodes.cpp mariadb_func.cpp get_com_select_insert.cpp maxadmin_operations.cpp big_transaction.cpp sql_t1.cpp test_binlog_fnc.cpp get_my_ip.cpp big_load.cpp get_com_select_insert.cpp different_size.cpp fw_copy_rules maxinfo_func.cpp config_operations.cpp rds_vpc.cpp execute_cmd.cpp - blob_test.cpp cdc_connector.cpp) -target_link_libraries(testcore ${MYSQL_CLIENT} z crypt nsl m pthread ssl crypto dl rt jansson) + blob_test.cpp cdc_connector.cpp tcp_connection.cpp) +target_link_libraries(testcore ${MYSQL_CLIENT} z crypt m pthread ssl crypto dl rt jansson) install(TARGETS testcore DESTINATION system-test) add_dependencies(testcore connector-c) @@ -768,6 +768,10 @@ add_test_executable_notest(long_sysbench.cpp long_sysbench replication LABELS re # test effect of local_address in configuration file add_test_executable(local_address.cpp local_address local_address LABELS REPL_BACKEND) +# MXS-1628: Security scanner says MaxScale is vulnerable to ancient MySQL vulnerability +# https://jira.mariadb.org/browse/MXS-1628 +add_test_executable(mxs1628_bad_handshake.cpp mxs1628_bad_handshake replication LABELS REPL_BACKEND) + configure_file(templates.h.in templates.h @ONLY) include(CTest) diff --git a/maxscale-system-test/mxs1628_bad_handshake.cpp b/maxscale-system-test/mxs1628_bad_handshake.cpp new file mode 100644 index 000000000..d8bc704fa --- /dev/null +++ b/maxscale-system-test/mxs1628_bad_handshake.cpp @@ -0,0 +1,71 @@ +#include "testconnections.h" +#include "tcp_connection.h" + +#include + +int main(int argc, char *argv[]) +{ + TestConnections test(argc, argv); + test.set_timeout(30); + + uint32_t caps = 1 | 8 | 512; + uint32_t max_packet = 65535; + uint8_t charset = 8; + std::string username = "username"; + uint8_t token_len = 20; // SHA1 hash size + std::string database = "database"; + + // Capabilities, max packet size and client charset + std::vector wbuf; + auto it = std::back_inserter(wbuf); + + for (auto a: {(uint8_t)(caps), (uint8_t)(caps >> 8), (uint8_t)(caps >> 16), (uint8_t)(caps >> 24), + (uint8_t)(max_packet), (uint8_t)(max_packet >> 8), (uint8_t)(max_packet >> 16), (uint8_t)(max_packet >> 24), + charset}) + { + *it++ = a; + } + + // Reserved filler space + std::fill_n(it, 23, 0); + + // Username without terminating null character + for (auto a: username) + { + *it++ = (uint8_t)a; + } + + // Auth token length and the token itself + *it++ = token_len; + std::fill_n(it, token_len, 123); + + // Database without terminating null character + for (auto a: database) + { + *it++ = (uint8_t)a; + } + + // Payload length and sequence number + uint8_t bufsize = wbuf.size(); + wbuf.insert(wbuf.begin(), {(uint8_t)(bufsize), (uint8_t)(bufsize >> 8), (uint8_t)(bufsize >> 16), 2}); + + + tcp::Connection conn; + conn.connect(test.maxscale_ip(), test.rwsplit_port); + + // Read the handshake + uint8_t buf[512] = {}; + conn.read(buf, sizeof(buf)); + + // Send the handshake response + conn.write(&wbuf[0], wbuf.size()); + + // Read MaxScale's response + conn.read(buf, sizeof(buf)); + + const char response[] = "Bad handshake"; + test.add_result(memmem(buf, sizeof(buf), response, sizeof(response) - 1) == NULL, + "MaxScale should respond with 'Bad handshake'"); + + return test.global_result; +} diff --git a/maxscale-system-test/tcp_connection.cpp b/maxscale-system-test/tcp_connection.cpp new file mode 100644 index 000000000..7c1df4007 --- /dev/null +++ b/maxscale-system-test/tcp_connection.cpp @@ -0,0 +1,89 @@ +#include "tcp_connection.h" + +#include +#include +#include +#include +#include +#include + + +namespace +{ + +static void set_port(struct sockaddr_storage *addr, uint16_t port) +{ + if (addr->ss_family == AF_INET) + { + struct sockaddr_in *ip = (struct sockaddr_in*)addr; + ip->sin_port = htons(port); + } + else if (addr->ss_family == AF_INET6) + { + struct sockaddr_in6 *ip = (struct sockaddr_in6*)addr; + ip->sin6_port = htons(port); + } +} + +int open_network_socket(struct sockaddr_storage *addr, const char *host, uint16_t port) +{ + struct addrinfo *ai = NULL, hint = {}; + int so = -1, rc = 0; + hint.ai_socktype = SOCK_STREAM; + hint.ai_family = AF_UNSPEC; + hint.ai_flags = AI_ALL; + + /* Take the first one */ + if (getaddrinfo(host, NULL, &hint, &ai) == 0 && ai) + { + if ((so = socket(ai->ai_family, SOCK_STREAM, 0)) != -1) + { + memcpy(addr, ai->ai_addr, ai->ai_addrlen); + set_port(addr, port); + freeaddrinfo(ai); + } + } + + return so; +} + +} + +namespace tcp +{ + +Connection::~Connection() +{ + if (m_so != -1) + { + close(m_so); + } +} + +bool Connection::connect(const char* host, uint16_t port) +{ + struct sockaddr_storage addr; + + if ((m_so = open_network_socket(&addr, host, port)) != -1) + { + if (::connect(m_so, (struct sockaddr*)&addr, sizeof(addr)) != 0) + { + close(m_so); + m_so = -1; + } + } + + return m_so != -1; +} + +int Connection::write(void* buf, size_t size) +{ + return ::write(m_so, buf, size); +} + +int Connection::read(void* buf, size_t size) +{ + return ::read(m_so, buf, size); +} + +} diff --git a/maxscale-system-test/tcp_connection.h b/maxscale-system-test/tcp_connection.h new file mode 100644 index 000000000..928b0c0cb --- /dev/null +++ b/maxscale-system-test/tcp_connection.h @@ -0,0 +1,49 @@ +#pragma once + +#include +#include + +namespace tcp +{ + +// A raw TCP connection +class Connection +{ +public: + ~Connection(); + + /** + * Connect to the target server + * + * @param host Server hostname + * @param port Server port + * + * @return True if connection was successfully created + */ + bool connect(const char* host, uint16_t port); + + /** + * Write to socket + * + * @param buf Buffer to read from + * @param size Number of bytes to read from @c buf + * + * @return Number of written bytes or -1 on error + */ + int write(void* buf, size_t size); + + /** + * Read from socket + * + * @param buf Buffer to write to + * @param size Size of @c buf + * + * @return Number of read bytes or -1 on error + */ + int read(void* buf, size_t size); + +private: + int m_so = -1; +}; + +} From 48aa76e1d4f7c59ee2e858678383ff3719b350bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Thu, 10 May 2018 22:26:32 +0300 Subject: [PATCH 5/9] Fix invalid JSON in masking filter documentation The example JSON was missing a comma where one was expected. --- Documentation/Filters/Masking.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/Filters/Masking.md b/Documentation/Filters/Masking.md index 90df5f52b..d269601fb 100644 --- a/Documentation/Filters/Masking.md +++ b/Documentation/Filters/Masking.md @@ -231,8 +231,8 @@ The `match` value must be a valid pcre2 regular expression. ``` "replace": { - "column": "ssn" - "match": "(123)", + "column": "ssn", + "match": "(123)" }, "with": { "fill": "X#" From 9c8cd07a00186bef0f6c4ed6441519e8d80b1dd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Thu, 10 May 2018 13:16:07 +0300 Subject: [PATCH 6/9] MXS-1628: Prevent out-of-bounds read on bad handshake packet If the client would send a malformed handshake packet, out-of-bounds memory could be read. --- .../protocol/MySQL/MySQLClient/mysql_client.c | 52 +++++++++++++++---- 1 file changed, 43 insertions(+), 9 deletions(-) diff --git a/server/modules/protocol/MySQL/MySQLClient/mysql_client.c b/server/modules/protocol/MySQL/MySQLClient/mysql_client.c index 532357020..3ce99b8e8 100644 --- a/server/modules/protocol/MySQL/MySQLClient/mysql_client.c +++ b/server/modules/protocol/MySQL/MySQLClient/mysql_client.c @@ -542,6 +542,34 @@ int gw_read_client_event(DCB* dcb) return return_code; } +/** + * Get length of a null-terminated string + * + * @param str String to measure + * @param len Maximum length to read + * + * @return Length of @c str or -1 if the string is not null-terminated + */ +static int get_zstr_len(const char* str, int len) +{ + const char* end = str + len; + int slen = 0; + + while (str < end && *str) + { + str++; + slen++; + } + + if (str == end) + { + // The string is not null terminated + slen = -1; + } + + return slen; +} + /** * @brief Store client connection information into the DCB * @param dcb Client DCB @@ -570,22 +598,28 @@ static void store_client_information(DCB *dcb, GWBUF *buffer) if (len > MYSQL_AUTH_PACKET_BASE_SIZE) { - strcpy(ses->user, (char*)data + MYSQL_AUTH_PACKET_BASE_SIZE); + const char* username = (const char*)data + MYSQL_AUTH_PACKET_BASE_SIZE; + int userlen = get_zstr_len(username, len - MYSQL_AUTH_PACKET_BASE_SIZE); + + if (userlen != -1 && (int)sizeof(ses->user) > userlen) + { + strcpy(ses->user, username); + } if (proto->client_capabilities & GW_MYSQL_CAPABILITIES_CONNECT_WITH_DB) { - /** Client supports default database on connect */ - size_t userlen = strlen(ses->user) + 1; - - /** Skip the authentication token, it is handled by the authenticators */ + /** Client is connecting with a default database */ uint8_t authlen = data[MYSQL_AUTH_PACKET_BASE_SIZE + userlen]; - size_t dboffset = MYSQL_AUTH_PACKET_BASE_SIZE + userlen + authlen + 1; - if (data[dboffset]) + if (dboffset < len) { - /** Client is connecting with a default database */ - strcpy(ses->db, (char*)data + dboffset); + int dblen = get_zstr_len((const char*)data + dboffset, len - dboffset); + + if (dblen != -1 && (int)sizeof(ses->db) < dblen) + { + strcpy(ses->db, (const char*)data + dboffset); + } } } } From 0a39fab562a3b7bb95c73255c853bd47b75fb308 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Thu, 10 May 2018 20:55:26 +0300 Subject: [PATCH 7/9] Update 2.1.17 release notes Added newly fixed bugs into the release notes. --- Documentation/Release-Notes/MaxScale-2.1.17-Release-Notes.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Documentation/Release-Notes/MaxScale-2.1.17-Release-Notes.md b/Documentation/Release-Notes/MaxScale-2.1.17-Release-Notes.md index ec9ca73a6..f13d0bfa9 100644 --- a/Documentation/Release-Notes/MaxScale-2.1.17-Release-Notes.md +++ b/Documentation/Release-Notes/MaxScale-2.1.17-Release-Notes.md @@ -17,11 +17,14 @@ For any problems you encounter, please consider submitting a bug report at [Here is a list of bugs fixed in MaxScale 2.1.17.](https://jira.mariadb.org/issues/?jql=project%20%3D%20MXS%20AND%20issuetype%20%3D%20Bug%20AND%20status%20%3D%20Closed%20AND%20fixVersion%20%3D%202.1.17) +* [MXS-1839](https://jira.mariadb.org/browse/MXS-1839) show sessions leaks memory * [MXS-1819](https://jira.mariadb.org/browse/MXS-1819) log_info does not log to syslog * [MXS-1788](https://jira.mariadb.org/browse/MXS-1788) MaxInfo crash +* [MXS-1772](https://jira.mariadb.org/browse/MXS-1772) Netmask limitations are not documented * [MXS-1767](https://jira.mariadb.org/browse/MXS-1767) Server capabilities are not correct * [MXS-1762](https://jira.mariadb.org/browse/MXS-1762) The client IP should be considered when choosing a persistent connection * [MXS-1618](https://jira.mariadb.org/browse/MXS-1618) Maxadmin Binary Hangs on a Big endian Platform +* [MXS-1419](https://jira.mariadb.org/browse/MXS-1419) maxadmin history is missing ## Packaging From b3d29da3d2b0396fe1a4fe8aefb775d7dc6fe73f Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Tue, 8 May 2018 15:04:22 +0300 Subject: [PATCH 8/9] MXS-1861 Test that reveals problem As this is so closely related to MXS-1719 the test program for that is slightly modified to catch this one as well. --- maxscale-system-test/mxs1719.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/maxscale-system-test/mxs1719.cpp b/maxscale-system-test/mxs1719.cpp index b206a0cab..110a94bc0 100644 --- a/maxscale-system-test/mxs1719.cpp +++ b/maxscale-system-test/mxs1719.cpp @@ -44,6 +44,13 @@ void run(TestConnections& test) { // One multi-statement with two UPDATEs. test.try_query(pMysql, "UPDATE MXS_1719 SET a=1; UPDATE MXS_1719 SET a=1;"); + + // Sleep a while, so that the log is flushed. + sleep(5); + // This is actually related to MXS-1861 "masking filter logs warnings with + // multistatements" but it seems excessive to create a specific test for that. + test.log_excludes(0, "Received data, although expected nothing"); + // This will hang immediately, so we can shorten the timeout. test.set_timeout(5); test.try_query(pMysql, "SELECT * FROM MXS_1719"); From 5eb6718b752b12e8b7eb5f3cd8c9ed25efd0172e Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Tue, 8 May 2018 15:06:47 +0300 Subject: [PATCH 9/9] MXS-1861 Detect and handle multistatement responses Multi-statement SELECTs were properly detected and handled, but e.g. multi-statement UPDATESs were not, with the result that erronous warnings were logged. Now the responses are detected and handled properly. --- .../filter/masking/maskingfiltersession.cc | 16 ++++- server/modules/filter/masking/mysql.hh | 59 +++++++++++++++++++ 2 files changed, 74 insertions(+), 1 deletion(-) diff --git a/server/modules/filter/masking/maskingfiltersession.cc b/server/modules/filter/masking/maskingfiltersession.cc index 2dd3503e3..49ab3f652 100644 --- a/server/modules/filter/masking/maskingfiltersession.cc +++ b/server/modules/filter/masking/maskingfiltersession.cc @@ -131,7 +131,21 @@ void MaskingFilterSession::handle_response(GWBUF* pPacket) switch (response.type()) { case ComResponse::OK_PACKET: - // We'll end up here also in the case of a multi-result. + { + ComOK ok(response); + + if (ok.status() & SERVER_MORE_RESULTS_EXIST) + { + m_res.reset_multi(); + m_state = EXPECTING_RESPONSE; + } + else + { + m_state = EXPECTING_NOTHING; + } + } + break; + case ComResponse::LOCAL_INFILE_PACKET: // GET_MORE_CLIENT_DATA/SEND_MORE_CLIENT_DATA m_state = EXPECTING_NOTHING; break; diff --git a/server/modules/filter/masking/mysql.hh b/server/modules/filter/masking/mysql.hh index 47654e458..6d5ba0d92 100644 --- a/server/modules/filter/masking/mysql.hh +++ b/server/modules/filter/masking/mysql.hh @@ -658,6 +658,65 @@ private: uint16_t m_status; }; +class ComOK : public ComResponse +{ +public: + ComOK(GWBUF* pPacket) + : ComResponse(pPacket) + { + ss_dassert(m_type == OK_PACKET); + + extract_payload(); + } + + ComOK(const ComResponse& response) + : ComResponse(response) + { + ss_dassert(m_type == OK_PACKET); + + extract_payload(); + } + + uint64_t affected_rows() const + { + return m_affected_rows; + } + + uint64_t last_insert_id() const + { + return m_last_insert_id; + } + + uint16_t warnings() const + { + return m_warnings; + } + + uint16_t status() const + { + return m_status; + } + +private: + void extract_payload() + { + m_affected_rows = LEncInt(&m_pData).value(); + m_last_insert_id = LEncInt(&m_pData).value(); + + m_status = *m_pData++; + m_status += (*m_pData++ << 8); + + m_warnings = *m_pData++; + m_warnings += (*m_pData++ << 8); + } + +private: + uint64_t m_affected_rows; + uint64_t m_last_insert_id; + uint16_t m_status; + uint16_t m_warnings; +}; + /** * @class ComRequest *