From 0daa167873ff15cd3938bb429cf8e2e64204dd0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Thu, 17 Aug 2017 23:47:46 +0300 Subject: [PATCH 1/6] Fix bug519 The test attempted to copy the generated CSV file from the MaxScale server when in fact it was on the master server. Removed HEAVY label from the test as it takes about 10 seconds to run. Cherry-picked from commit f80cde2af149fda9c47d8f9131f82527b76af487. --- maxscale-system-test/CMakeLists.txt | 2 +- maxscale-system-test/bug519.cpp | 7 +------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/maxscale-system-test/CMakeLists.txt b/maxscale-system-test/CMakeLists.txt index b2798cb78..96caa88e6 100644 --- a/maxscale-system-test/CMakeLists.txt +++ b/maxscale-system-test/CMakeLists.txt @@ -109,7 +109,7 @@ add_test_executable(bug507.cpp bug507 replication LABELS readwritesplit LIGHT RE add_test_executable(bug509.cpp bug509 galera LABELS readwritesplit GALERA_BACKEND) # Checks "SELECT * INTO OUTFILE" and "LOAD DATA LOCAL INFILE" -add_test_executable(bug519.cpp bug519 replication LABELS readwritesplit HEAVY REPL_BACKEND) +add_test_executable(bug519.cpp bug519 replication LABELS readwritesplit REPL_BACKEND) # Regression case for the bug "'Current no. of conns' not going down" add_test_executable(bug529.cpp bug529 replication LABELS readwritesplit readconnroute maxscale REPL_BACKEND) diff --git a/maxscale-system-test/bug519.cpp b/maxscale-system-test/bug519.cpp index 9cfb474bb..79b4c34da 100644 --- a/maxscale-system-test/bug519.cpp +++ b/maxscale-system-test/bug519.cpp @@ -106,12 +106,7 @@ int main(int argc, char *argv[]) Test->try_query(Test->conn_slave, (char *) "SELECT * INTO OUTFILE '/tmp/t3.csv' FROM t1;"); Test->tprintf("Copying t1.cvs from Maxscale machine:\n"); - Test->copy_from_maxscale((char *) "/tmp/t1.csv", (char *) "./"); - /*sprintf(str, - "scp -i %s -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o LogLevel=quiet %s@%s:/tmp/t1.csv ./", - Test->repl->sshkey[0], Test->repl->access_user[0], Test->repl->IP[0]); - Test->tprintf("%s\n", str); - system(str);*/ + Test->repl->copy_from_node("/tmp/t1.csv", "./t1.csv", 0); MYSQL *srv[2]; From f4cd2c62b6abb2cf4a27a262caea84a6834ab08c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Thu, 7 Sep 2017 18:38:08 +0300 Subject: [PATCH 2/6] Backport mxs1110_16mb fixes to 2.1 Backported changes to mxs1110_16mb from develop into 2.1. --- maxscale-system-test/mxs1110_16mb.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/maxscale-system-test/mxs1110_16mb.cpp b/maxscale-system-test/mxs1110_16mb.cpp index 7788b7116..5497373af 100644 --- a/maxscale-system-test/mxs1110_16mb.cpp +++ b/maxscale-system-test/mxs1110_16mb.cpp @@ -13,6 +13,7 @@ int main(int argc, char *argv[]) { TestConnections::skip_maxscale_start(true); TestConnections * Test = new TestConnections(argc, argv); + Test->stop_maxscale(); Test->set_timeout(60); int chunk_size = 2500000; int chunk_num = 5; @@ -26,6 +27,7 @@ int main(int argc, char *argv[]) Test->start_maxscale(); Test->repl->execute_query_all_nodes( (char *) "set global max_allowed_packet=100000000"); + Test->galera->execute_query_all_nodes( (char *) "set global max_allowed_packet=100000000"); Test->connect_maxscale(); Test->repl->connect(); @@ -34,6 +36,7 @@ int main(int argc, char *argv[]) Test->repl->close_connections(); Test->close_maxscale_connections(); + Test->repl->sync_slaves(); Test->connect_maxscale(); Test->tprintf("Checking data via RWSplit\n"); check_longblob_data(Test, Test->conn_rwsplit, chunk_size, chunk_num, 2); From 9ceb23dd65b02733aadbb2aacc3de4e61933bb9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Thu, 7 Sep 2017 17:19:16 +0300 Subject: [PATCH 3/6] MXS-1396: Fix persistent connection hangs When a COM_CHANGE_USER was executed, it is possible that the server responds with a AuthSwitchRequest packet instead of an OK packet. In this case, the server sends a new scramble which must be used to create the 20 byte hash that is expected as the response. --- include/maxscale/protocol/mysql.h | 3 + .../MySQL/MySQLBackend/mysql_backend.c | 50 +++++++++++---- server/modules/protocol/MySQL/mysql_common.c | 61 +++++++++++++------ 3 files changed, 85 insertions(+), 29 deletions(-) diff --git a/include/maxscale/protocol/mysql.h b/include/maxscale/protocol/mysql.h index f9d206615..b67876630 100644 --- a/include/maxscale/protocol/mysql.h +++ b/include/maxscale/protocol/mysql.h @@ -425,6 +425,9 @@ bool gw_read_backend_handshake(DCB *dcb, GWBUF *buffer); /** Send the server handshake response packet to the backend server */ mxs_auth_state_t gw_send_backend_auth(DCB *dcb); +/** Sends a response for an AuthSwitchRequest to the default auth plugin */ +int send_mysql_native_password_response(DCB* dcb); + /** Write an OK packet to a DCB */ int mxs_mysql_send_ok(DCB *dcb, int sequence, uint8_t affected_rows, const char* message); diff --git a/server/modules/protocol/MySQL/MySQLBackend/mysql_backend.c b/server/modules/protocol/MySQL/MySQLBackend/mysql_backend.c index c187f91df..5999c4052 100644 --- a/server/modules/protocol/MySQL/MySQLBackend/mysql_backend.c +++ b/server/modules/protocol/MySQL/MySQLBackend/mysql_backend.c @@ -766,6 +766,43 @@ gw_read_and_write(DCB *dcb) MXS_INFO("Response to COM_CHANGE_USER is OK, writing stored query"); rval = query ? dcb->func.write(dcb, query) : 1; } + else if (result == MYSQL_REPLY_AUTHSWITCHREQUEST && + gwbuf_length(reply) > MYSQL_EOF_PACKET_LEN) + { + /** + * The server requested a change of authentication methods. + * If we're changing the authentication method to the same one we + * are using now, it means that the server is simply generating + * a new scramble for the re-authentication process. + */ + if (strcmp((char*)GWBUF_DATA(reply) + 5, DEFAULT_MYSQL_AUTH_PLUGIN) == 0) + { + /** Load the new scramble into the protocol... */ + gwbuf_copy_data(reply, 5 + strlen(DEFAULT_MYSQL_AUTH_PLUGIN) + 1, + GW_MYSQL_SCRAMBLE_SIZE, proto->scramble); + + /** ... and use it to send the encrypted password to the server */ + rval = send_mysql_native_password_response(dcb); + + /** Store the query until we know the result of the authentication + * method switch. */ + proto->stored_query = query; + proto->ignore_reply = true; + return rval; + } + else + { + /** The server requested a change to something other than + * the default auth plugin */ + gwbuf_free(query); + poll_fake_hangup_event(dcb); + + // TODO: Use the authenticators to handle COM_CHANGE_USER responses + MXS_ERROR("Received AuthSwitchRequest to '%s' when '%s' was expected", + (char*)GWBUF_DATA(reply) + 5, DEFAULT_MYSQL_AUTH_PLUGIN); + + } + } else { if (result == MYSQL_REPLY_ERR) @@ -774,22 +811,13 @@ gw_read_and_write(DCB *dcb) * close the DCB and send an error to the client. */ log_error_response(dcb, reply); } - else if (result == MYSQL_REPLY_AUTHSWITCHREQUEST && - gwbuf_length(reply) > MYSQL_EOF_PACKET_LEN) - { - MXS_ERROR("Received AuthSwitchRequest to '%s' when '%s' was expected", - (char*)GWBUF_DATA(reply) + 5, DEFAULT_MYSQL_AUTH_PLUGIN); - } else { /** This should never happen */ MXS_ERROR("Unknown response to COM_CHANGE_USER (0x%02hhx), " - "ignoring and waiting for correct result", result); - gwbuf_free(reply); - proto->stored_query = query; - proto->ignore_reply = true; - return 1; + "closing connection", result); } + gwbuf_free(query); poll_fake_hangup_event(dcb); } diff --git a/server/modules/protocol/MySQL/mysql_common.c b/server/modules/protocol/MySQL/mysql_common.c index 5433f0678..67654bee7 100644 --- a/server/modules/protocol/MySQL/mysql_common.c +++ b/server/modules/protocol/MySQL/mysql_common.c @@ -1205,19 +1205,19 @@ response_length(MySQLProtocol *conn, char *user, uint8_t *passwd, char *dbname, } /** - * @brief Helper function to load hashed password - * @param conn DCB Protocol object - * @param payload Destination where hashed password is written - * @param passwd Client's double SHA1 password - * @return Address of the next byte after the end of the stored password + * Calculates the a hash from a scramble and a password + * + * The algorithm used is: `SHA1(scramble + SHA1(SHA1(password))) ^ SHA1(password)` + * + * @param scramble The 20 byte scramble sent by the server + * @param passwd The SHA1(password) sent by the client + * @param output Pointer where the resulting 20 byte hash is stored */ -static uint8_t * -load_hashed_password(uint8_t *scramble, uint8_t *payload, uint8_t *passwd) +static void calculate_hash(uint8_t *scramble, uint8_t *passwd, uint8_t *output) { uint8_t hash1[GW_MYSQL_SCRAMBLE_SIZE] = ""; uint8_t hash2[GW_MYSQL_SCRAMBLE_SIZE] = ""; uint8_t new_sha[GW_MYSQL_SCRAMBLE_SIZE] = ""; - uint8_t client_scramble[GW_MYSQL_SCRAMBLE_SIZE]; // hash1 is the function input, SHA1(real_password) memcpy(hash1, passwd, GW_MYSQL_SCRAMBLE_SIZE); @@ -1229,17 +1229,24 @@ load_hashed_password(uint8_t *scramble, uint8_t *payload, uint8_t *passwd) gw_sha1_2_str(scramble, GW_MYSQL_SCRAMBLE_SIZE, hash2, GW_MYSQL_SCRAMBLE_SIZE, new_sha); // compute the xor in client_scramble - gw_str_xor(client_scramble, new_sha, hash1, GW_MYSQL_SCRAMBLE_SIZE); + gw_str_xor(output, new_sha, hash1, GW_MYSQL_SCRAMBLE_SIZE); +} - // set the auth-length - *payload = GW_MYSQL_SCRAMBLE_SIZE; - payload++; - - //copy the 20 bytes scramble data after packet_buffer + 36 + user + NULL + 1 (byte of auth-length) - memcpy(payload, client_scramble, GW_MYSQL_SCRAMBLE_SIZE); - - payload += GW_MYSQL_SCRAMBLE_SIZE; - return payload; +/** + * @brief Helper function to load hashed password + * + * @param conn DCB Protocol object + * @param payload Destination where hashed password is written + * @param passwd Client's double SHA1 password + * + * @return Address of the next byte after the end of the stored password + */ +static uint8_t * +load_hashed_password(uint8_t *scramble, uint8_t *payload, uint8_t *passwd) +{ + *payload++ = GW_MYSQL_SCRAMBLE_SIZE; + calculate_hash(scramble, passwd, payload); + return payload + GW_MYSQL_SCRAMBLE_SIZE; } /** @@ -1411,6 +1418,24 @@ mxs_auth_state_t gw_send_backend_auth(DCB *dcb) return dcb_write(dcb, buffer) ? MXS_AUTH_STATE_RESPONSE_SENT : MXS_AUTH_STATE_FAILED; } +int send_mysql_native_password_response(DCB* dcb) +{ + MySQLProtocol* proto = (MySQLProtocol*) dcb->protocol; + MYSQL_session local_session; + gw_get_shared_session_auth_info(dcb, &local_session); + + uint8_t *curr_passwd = memcmp(local_session.client_sha1, null_client_sha1, MYSQL_SCRAMBLE_LEN) ? + local_session.client_sha1 : null_client_sha1; + + GWBUF* buffer = gwbuf_alloc(MYSQL_HEADER_LEN + GW_MYSQL_SCRAMBLE_SIZE); + uint8_t* data = GWBUF_DATA(buffer); + gw_mysql_set_byte3(data, GW_MYSQL_SCRAMBLE_SIZE); + data[3] = 2; // This is the third packet after the COM_CHANGE_USER + calculate_hash(proto->scramble, curr_passwd, data + MYSQL_HEADER_LEN); + + return dcb_write(dcb, buffer); +} + /** * Decode mysql server handshake * From 81202eac89cd98485795e0db0698a45daaa68575 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Fri, 8 Sep 2017 22:44:57 +0300 Subject: [PATCH 4/6] MXS-1400: Fix crash with OpenSSL 1.1 Added the missing parameter for the RSA key generation function. --- server/core/listener.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/core/listener.c b/server/core/listener.c index 98322f8c0..8d2adc1ad 100644 --- a/server/core/listener.c +++ b/server/core/listener.c @@ -223,7 +223,7 @@ RSA* create_rsa(int bits) BIGNUM* bn = BN_new(); BN_set_word(bn, RSA_F4); RSA* rsa = RSA_new(); - RSA_generate_key_ex(rsa, bits, NULL, NULL); + RSA_generate_key_ex(rsa, bits, bn, NULL); BN_free(bn); return rsa; #else From 7a8a12dd28d378650187da055c64c857823fd63f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Mon, 11 Sep 2017 21:55:59 +0300 Subject: [PATCH 5/6] Update CDash drop site address The drop site was not set to the correct one. --- maxscale-system-test/CTestConfig.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maxscale-system-test/CTestConfig.cmake b/maxscale-system-test/CTestConfig.cmake index 1b96cfda2..5982e436a 100644 --- a/maxscale-system-test/CTestConfig.cmake +++ b/maxscale-system-test/CTestConfig.cmake @@ -10,6 +10,6 @@ set(CTEST_PROJECT_NAME "MaxScale") set(CTEST_NIGHTLY_START_TIME "01:00:00 UTC") set(CTEST_DROP_METHOD "http") -set(CTEST_DROP_SITE "jenkins.engskysql.com") +set(CTEST_DROP_SITE "maxscale-jenkins.mariadb.com") set(CTEST_DROP_LOCATION "/CDash/submit.php?project=MaxScale") set(CTEST_DROP_SITE_CDASH TRUE) From 4a42ddfe5ffaefafdd862f5f11fd6d7630515380 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Mon, 11 Sep 2017 23:59:59 +0300 Subject: [PATCH 6/6] Null-terminate decrypted passwords The decrypted passwords returned by `decrypt_password` are now properly null-terminated. --- server/core/secrets.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/core/secrets.c b/server/core/secrets.c index 054ee314a..18963e425 100644 --- a/server/core/secrets.c +++ b/server/core/secrets.c @@ -382,7 +382,7 @@ decrypt_password(const char *crypt) enlen = strlen(crypt) / 2; gw_hex2bin(encrypted, crypt, strlen(crypt)); - if ((plain = (unsigned char *) MXS_MALLOC(80)) == NULL) + if ((plain = (unsigned char *) MXS_MALLOC(enlen + 1)) == NULL) { MXS_FREE(keys); return NULL; @@ -391,6 +391,7 @@ decrypt_password(const char *crypt) AES_set_decrypt_key(keys->enckey, 8 * MAXSCALE_KEYLEN, &aeskey); AES_cbc_encrypt(encrypted, plain, enlen, &aeskey, keys->initvector, AES_DECRYPT); + plain[enlen] = '\0'; MXS_FREE(keys); return (char *) plain;