diff --git a/include/maxscale/protocol/mysql.h b/include/maxscale/protocol/mysql.h index 49c2c102c..563aadef1 100644 --- a/include/maxscale/protocol/mysql.h +++ b/include/maxscale/protocol/mysql.h @@ -452,6 +452,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/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) diff --git a/server/core/listener.cc b/server/core/listener.cc index 04f28b0ba..d8e007434 100644 --- a/server/core/listener.cc +++ b/server/core/listener.cc @@ -227,7 +227,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 diff --git a/server/core/secrets.cc b/server/core/secrets.cc index 840664436..c56d4b429 100644 --- a/server/core/secrets.cc +++ b/server/core/secrets.cc @@ -369,7 +369,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; @@ -378,6 +378,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; diff --git a/server/modules/protocol/MySQL/MySQLBackend/mysql_backend.c b/server/modules/protocol/MySQL/MySQLBackend/mysql_backend.c index 6eb1b16a2..c1f1b994e 100644 --- a/server/modules/protocol/MySQL/MySQLBackend/mysql_backend.c +++ b/server/modules/protocol/MySQL/MySQLBackend/mysql_backend.c @@ -814,6 +814,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_replies++; + 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 { /** @@ -827,21 +864,11 @@ 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_replies++; - return 1; + "closing connection", result); } gwbuf_free(query); diff --git a/server/modules/protocol/MySQL/mysql_common.c b/server/modules/protocol/MySQL/mysql_common.c index 10ec8af86..5811a760d 100644 --- a/server/modules/protocol/MySQL/mysql_common.c +++ b/server/modules/protocol/MySQL/mysql_common.c @@ -1164,19 +1164,19 @@ static int response_length(bool with_ssl, bool ssl_established, char *user, uint } /** - * @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); @@ -1188,17 +1188,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; } /** @@ -1389,6 +1396,32 @@ mxs_auth_state_t gw_send_backend_auth(DCB *dcb) return rval; } +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 + * + * @param conn The MySQLProtocol structure + * @param payload The bytes just read from the net + * @return 0 on success, < 0 on failure + * + */ int gw_decode_mysql_server_handshake(MySQLProtocol *conn, uint8_t *payload) { uint8_t *server_version_end = NULL;