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.
This commit is contained in:
Markus Mäkelä
2017-09-07 17:19:16 +03:00
parent faa1cc815d
commit 9ceb23dd65
3 changed files with 85 additions and 29 deletions

View File

@ -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);

View File

@ -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);
}

View File

@ -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
*