if (func.auth ==)gw_change_user->gw_send_change_user_to_backend is called before backend has its scramble, auth packet is set to backend's delauqueue instead of writing it to backend. When backend_write_delayqueue is called COM_CHANGE_USER packets are rewritten with backend's current data.
This commit is contained in:
VilhoRaatikka
2014-11-13 17:55:29 +02:00
parent 70acd60117
commit 175711a1bc
4 changed files with 258 additions and 186 deletions

View File

@ -297,6 +297,7 @@ typedef struct {
#define MYSQL_GET_STMTOK_NATTR(payload) (gw_mysql_get_byte2(&payload[11])) #define MYSQL_GET_STMTOK_NATTR(payload) (gw_mysql_get_byte2(&payload[11]))
#define MYSQL_IS_ERROR_PACKET(payload) (MYSQL_GET_COMMAND(payload)==0xff) #define MYSQL_IS_ERROR_PACKET(payload) (MYSQL_GET_COMMAND(payload)==0xff)
#define MYSQL_IS_COM_QUIT(payload) (MYSQL_GET_COMMAND(payload)==0x01) #define MYSQL_IS_COM_QUIT(payload) (MYSQL_GET_COMMAND(payload)==0x01)
#define MYSQL_IS_CHANGE_USER(payload) (MYSQL_GET_COMMAND(payload)==0x11)
#define MYSQL_GET_NATTR(payload) ((int)payload[4]) #define MYSQL_GET_NATTR(payload) ((int)payload[4])
#endif /** _MYSQL_PROTOCOL_H */ #endif /** _MYSQL_PROTOCOL_H */
@ -314,6 +315,7 @@ int gw_send_authentication_to_backend(
char *user, char *user,
uint8_t *passwd, uint8_t *passwd,
MySQLProtocol *protocol); MySQLProtocol *protocol);
const char *gw_mysql_protocol_state2string(int state); const char *gw_mysql_protocol_state2string(int state);
int gw_do_connect_to_backend(char *host, int port, int* fd); int gw_do_connect_to_backend(char *host, int port, int* fd);
int mysql_send_com_quit(DCB* dcb, int packet_number, GWBUF* buf); int mysql_send_com_quit(DCB* dcb, int packet_number, GWBUF* buf);
@ -335,6 +337,11 @@ int gw_send_change_user_to_backend(
char *user, char *user,
uint8_t *passwd, uint8_t *passwd,
MySQLProtocol *protocol); MySQLProtocol *protocol);
GWBUF* gw_create_change_user_packet(
MYSQL_session* mses,
MySQLProtocol* protocol);
int gw_find_mysql_user_password_sha1( int gw_find_mysql_user_password_sha1(
char *username, char *username,
uint8_t *gateway_password, uint8_t *gateway_password,

View File

@ -1157,7 +1157,24 @@ static int backend_write_delayqueue(DCB *dcb)
localq = dcb->delayq; localq = dcb->delayq;
dcb->delayq = NULL; dcb->delayq = NULL;
spinlock_release(&dcb->delayqlock); spinlock_release(&dcb->delayqlock);
rc = dcb_write(dcb, localq);
if (MYSQL_IS_CHANGE_USER(((uint8_t *)GWBUF_DATA(localq))))
{
MYSQL_session* mses;
GWBUF* new_packet;
mses = (MYSQL_session *)dcb->session->client->data;
new_packet = gw_create_change_user_packet(
mses,
(MySQLProtocol *)dcb->protocol);
/**
* Remove previous packet which lacks scramble
* and append the new.
*/
localq = gwbuf_consume(localq, GWBUF_LENGTH(localq));
localq = gwbuf_append(localq, new_packet);
}
rc = dcb_write(dcb, localq);
} }
if (rc == 0) if (rc == 0)
@ -1289,13 +1306,25 @@ static int gw_change_user(
* decode the token and check the password. * decode the token and check the password.
* Note: if auth_token_len == 0 && auth_token == NULL, user is without password * Note: if auth_token_len == 0 && auth_token == NULL, user is without password
*/ */
auth_ret = gw_check_mysql_scramble_data(backend->session->client, auth_token, auth_token_len, client_protocol->scramble, sizeof(client_protocol->scramble), username, client_sha1); auth_ret = gw_check_mysql_scramble_data(backend->session->client,
auth_token,
auth_token_len,
client_protocol->scramble,
sizeof(client_protocol->scramble),
username,
client_sha1);
if (auth_ret != 0) { if (auth_ret != 0) {
if (!service_refresh_users(backend->session->client->service)) { if (!service_refresh_users(backend->session->client->service)) {
/* Try authentication again with new repository data */ /* Try authentication again with new repository data */
/* Note: if no auth client authentication will fail */ /* Note: if no auth client authentication will fail */
auth_ret = gw_check_mysql_scramble_data(backend->session->client, auth_token, auth_token_len, client_protocol->scramble, sizeof(client_protocol->scramble), username, client_sha1); auth_ret = gw_check_mysql_scramble_data(
backend->session->client,
auth_token, auth_token_len,
client_protocol->scramble,
sizeof(client_protocol->scramble),
username,
client_sha1);
} }
} }
@ -1359,7 +1388,7 @@ static int gw_change_user(
rv = gw_send_change_user_to_backend(database, username, client_sha1, backend_protocol); rv = gw_send_change_user_to_backend(database, username, client_sha1, backend_protocol);
/* /*
* Now copy new data into user session * Now copy new data into user session
*/ */
strcpy(current_session->user, username); strcpy(current_session->user, username);
strcpy(current_session->db, database); strcpy(current_session->db, database);
memcpy(current_session->client_sha1, client_sha1, sizeof(current_session->client_sha1)); memcpy(current_session->client_sha1, client_sha1, sizeof(current_session->client_sha1));

View File

@ -479,7 +479,8 @@ static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue) {
auth_ret = gw_check_mysql_scramble_data(dcb, auth_ret = gw_check_mysql_scramble_data(dcb,
auth_token, auth_token,
auth_token_len, auth_token_len,
protocol->scramble, sizeof(protocol->scramble), protocol->scramble,
sizeof(protocol->scramble),
username, username,
stage1_hash); stage1_hash);
@ -491,7 +492,14 @@ static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue) {
if (!service_refresh_users(dcb->service)) { if (!service_refresh_users(dcb->service)) {
/* Try authentication again with new repository data */ /* Try authentication again with new repository data */
/* Note: if no auth client authentication will fail */ /* Note: if no auth client authentication will fail */
auth_ret = gw_check_mysql_scramble_data(dcb, auth_token, auth_token_len, protocol->scramble, sizeof(protocol->scramble), username, stage1_hash); auth_ret = gw_check_mysql_scramble_data(
dcb,
auth_token,
auth_token_len,
protocol->scramble,
sizeof(protocol->scramble),
username,
stage1_hash);
} }
} }

View File

@ -275,8 +275,7 @@ int gw_read_backend_handshake(
payload += 4; payload += 4;
//Now decode mysql handshake //Now decode mysql handshake
success = gw_decode_mysql_server_handshake(conn, success = gw_decode_mysql_server_handshake(conn, payload);
payload);
if (success < 0) { if (success < 0) {
/* MySQL handshake has not been properly decoded /* MySQL handshake has not been properly decoded
@ -1054,197 +1053,226 @@ int mysql_send_custom_error (
return dcb->func.write(dcb, buf); return dcb->func.write(dcb, buf);
} }
/**
* Create COM_CHANGE_USER packet and store it to GWBUF
*
* @param mses MySQL session
* @param protocol protocol structure of the backend
*
* @return GWBUF buffer consisting of COM_CHANGE_USER packet
*
* @note the function doesn't fail
*/
GWBUF* gw_create_change_user_packet(
MYSQL_session* mses,
MySQLProtocol* protocol)
{
char* db;
char* user;
uint8_t* pwd;
GWBUF* buffer;
int compress = 0;
uint8_t* payload = NULL;
uint8_t* payload_start = NULL;
long bytes;
uint8_t client_scramble[GW_MYSQL_SCRAMBLE_SIZE];
uint32_t server_capabilities = 0;
uint32_t final_capabilities = 0;
char dbpass[MYSQL_USER_MAXLEN + 1]="";
char* curr_db = NULL;
uint8_t* curr_passwd = NULL;
unsigned int charset;
db = mses->db;
user = mses->user;
pwd = mses->client_sha1;
if (strlen(db) > 0)
{
curr_db = db;
}
if (strlen((char *)pwd) > 0)
{
curr_passwd = pwd;
}
final_capabilities = gw_mysql_get_byte4((uint8_t *)&server_capabilities);
/** Copy client's flags to backend */
final_capabilities |= protocol->client_capabilities;
/* get charset the client sent and use it for connection auth */
charset = protocol->charset;
if (compress)
{
final_capabilities |= GW_MYSQL_CAPABILITIES_COMPRESS;
#ifdef DEBUG_MYSQL_CONN
fprintf(stderr, ">>>> Backend Connection with compression\n");
#endif
}
if (curr_passwd != NULL)
{
uint8_t hash1[GW_MYSQL_SCRAMBLE_SIZE]="";
uint8_t hash2[GW_MYSQL_SCRAMBLE_SIZE]="";
uint8_t new_sha[GW_MYSQL_SCRAMBLE_SIZE]="";
/** hash1 is the function input, SHA1(real_password) */
memcpy(hash1, pwd, GW_MYSQL_SCRAMBLE_SIZE);
/**
* hash2 is the SHA1(input data), where
* input_data = SHA1(real_password)
*/
gw_sha1_str(hash1, GW_MYSQL_SCRAMBLE_SIZE, hash2);
/** dbpass is the HEX form of SHA1(SHA1(real_password)) */
gw_bin2hex(dbpass, hash2, GW_MYSQL_SCRAMBLE_SIZE);
/** new_sha is the SHA1(CONCAT(scramble, hash2) */
gw_sha1_2_str(protocol->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);
}
if (curr_db == NULL)
{
final_capabilities &= ~GW_MYSQL_CAPABILITIES_CONNECT_WITH_DB;
}
else
{
final_capabilities |= GW_MYSQL_CAPABILITIES_CONNECT_WITH_DB;
}
final_capabilities |= GW_MYSQL_CAPABILITIES_PLUGIN_AUTH;
/**
* Protocol MySQL COM_CHANGE_USER for CLIENT_PROTOCOL_41
* 1 byte COMMAND
*/
bytes = 1;
/** add the user and a terminating char */
bytes += strlen(user);
bytes++;
/**
* next will be + 1 (scramble_len) + 20 (fixed_scramble) +
* (db + NULL term) + 2 bytes charset
*/
if (curr_passwd != NULL)
{
bytes += GW_MYSQL_SCRAMBLE_SIZE;
}
/** 1 byte for scramble_len */
bytes++;
/** db name and terminating char */
if (curr_db != NULL)
{
bytes += strlen(curr_db);
}
bytes++;
/** the charset */
bytes += 2;
bytes += strlen("mysql_native_password");
bytes++;
/** the packet header */
bytes += 4;
buffer = gwbuf_alloc(bytes);
/**
* Set correct type to GWBUF so that it will be handled like session
* commands
*/
buffer->gwbuf_type =
GWBUF_TYPE_MYSQL|GWBUF_TYPE_SINGLE_STMT|GWBUF_TYPE_SESCMD;
payload = GWBUF_DATA(buffer);
memset(payload, '\0', bytes);
payload_start = payload;
/** set packet number to 0 */
payload[3] = 0x00;
payload += 4;
/** set the command COM_CHANGE_USER 0x11 */
payload[0] = 0x11;
payload++;
memcpy(payload, user, strlen(user));
payload += strlen(user);
payload++;
if (curr_passwd != NULL)
{
/** 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;
}
else
{
/** skip the auth-length and write a NULL */
payload++;
}
/** if the db is not NULL append it */
if (curr_db != NULL)
{
memcpy(payload, curr_db, strlen(curr_db));
payload += strlen(curr_db);
}
payload++;
/** set the charset, 2 bytes */
*payload = charset;
payload++;
*payload = '\x00';
payload++;
memcpy(payload, "mysql_native_password", strlen("mysql_native_password"));
payload += strlen("mysql_native_password");
payload++;
/** put here the paylod size: bytes to write - 4 bytes packet header */
gw_mysql_set_byte3(payload_start, (bytes-4));
return buffer;
}
/** /**
* Write a MySQL CHANGE_USER packet to backend server * Write a MySQL CHANGE_USER packet to backend server
* *
* @param conn MySQL protocol structure * @param conn MySQL protocol structure
* @param dbname The selected database * @param dbname The selected database
* @param user The selected user * @param user The selected user
* @param passwd The SHA1(real_password): Note real_password is unknown * @param passwd The SHA1(real_password)
* @return 1 on success, 0 on failure * @return 1 on success, 0 on failure
*/ */
int gw_send_change_user_to_backend( int gw_send_change_user_to_backend(
char *dbname, char *dbname,
char *user, char *user,
uint8_t *passwd, uint8_t *passwd,
MySQLProtocol *conn) MySQLProtocol *conn)
{ {
int compress = 0; GWBUF *buffer;
int rv; int rc;
uint8_t *payload = NULL; MYSQL_session* mses;
uint8_t *payload_start = NULL;
long bytes;
uint8_t client_scramble[GW_MYSQL_SCRAMBLE_SIZE];
uint8_t client_capabilities[4];
uint32_t server_capabilities = 0;
uint32_t final_capabilities = 0;
char dbpass[MYSQL_USER_MAXLEN + 1]="";
GWBUF *buffer;
DCB *dcb;
char *curr_db = NULL;
uint8_t *curr_passwd = NULL;
unsigned int charset;
if (strlen(dbname))
curr_db = dbname;
if (strlen((char *)passwd))
curr_passwd = passwd;
dcb = conn->owner_dcb;
final_capabilities = gw_mysql_get_byte4((uint8_t *)&server_capabilities);
/** Copy client's flags to backend */
final_capabilities |= conn->client_capabilities;
/* get charset the client sent and use it for connection auth */
charset = conn->charset;
if (compress) {
final_capabilities |= GW_MYSQL_CAPABILITIES_COMPRESS;
#ifdef DEBUG_MYSQL_CONN
fprintf(stderr, ">>>> Backend Connection with compression\n");
#endif
}
if (curr_passwd != NULL) {
uint8_t hash1[GW_MYSQL_SCRAMBLE_SIZE]="";
uint8_t hash2[GW_MYSQL_SCRAMBLE_SIZE]="";
uint8_t new_sha[GW_MYSQL_SCRAMBLE_SIZE]="";
// hash1 is the function input, SHA1(real_password)
memcpy(hash1, passwd, GW_MYSQL_SCRAMBLE_SIZE);
// hash2 is the SHA1(input data), where input_data = SHA1(real_password)
gw_sha1_str(hash1, GW_MYSQL_SCRAMBLE_SIZE, hash2);
// dbpass is the HEX form of SHA1(SHA1(real_password))
gw_bin2hex(dbpass, hash2, GW_MYSQL_SCRAMBLE_SIZE);
// new_sha is the SHA1(CONCAT(scramble, hash2)
gw_sha1_2_str(conn->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);
}
if (curr_db == NULL) {
// without db
final_capabilities &= ~GW_MYSQL_CAPABILITIES_CONNECT_WITH_DB;
} else {
final_capabilities |= GW_MYSQL_CAPABILITIES_CONNECT_WITH_DB;
}
final_capabilities |= GW_MYSQL_CAPABILITIES_PLUGIN_AUTH;
gw_mysql_set_byte4(client_capabilities, final_capabilities);
// Protocol MySQL COM_CHANGE_USER for CLIENT_PROTOCOL_41
// 1 byte COMMAND
bytes = 1;
// add the user
bytes += strlen(user);
// NULL byte for user string
bytes++;
// next will be + 1 (scramble_len) + 20 (fixed_scramble) + (dbname + NULL term) + 2 bytes charset
if (curr_passwd != NULL) {
bytes += GW_MYSQL_SCRAMBLE_SIZE;
}
// 1 byte for scramble_len
bytes++;
if (curr_db != NULL) {
bytes += strlen(curr_db);
}
// NULL byte for dbname string
bytes++;
// the charset
bytes += 2;
bytes += strlen("mysql_native_password");
bytes++;
// the packet header
bytes += 4;
// allocating the GWBUF
buffer = gwbuf_alloc(bytes);
/**
* Set correct type to GWBUF so that it will be handled like session
* commands should
*/
buffer->gwbuf_type = GWBUF_TYPE_MYSQL|GWBUF_TYPE_SINGLE_STMT|GWBUF_TYPE_SESCMD;
payload = GWBUF_DATA(buffer);
// clearing data
memset(payload, '\0', bytes);
// save the start pointer mses = (MYSQL_session*)conn->owner_dcb->session->client->data;
payload_start = payload; buffer = gw_create_change_user_packet(mses, conn);
rc = conn->owner_dcb->func.write(conn->owner_dcb, buffer);
// set packet # = 1 if (rc != 0)
payload[3] = 0x00; {
payload += 4; rc = 1;
// set the command COM_CHANGE_USER \x11
payload[0] = 0x11;
payload++;
memcpy(payload, user, strlen(user));
payload += strlen(user);
payload++;
if (curr_passwd != NULL) {
// 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;
} else {
// skip the auth-length and write a NULL
payload++;
}
// if the db is not NULL append it
if (curr_db != NULL) {
memcpy(payload, curr_db, strlen(curr_db));
payload += strlen(curr_db);
payload++;
} else {
// skip the NULL
payload++;
} }
return rc;
// set the charset, 2 bytes!!!!
*payload = charset;
payload++;
*payload = '\x00';
payload++;
memcpy(payload, "mysql_native_password", strlen("mysql_native_password"));
payload += strlen("mysql_native_password");
payload++;
// put here the paylod size: bytes to write - 4 bytes packet header
gw_mysql_set_byte3(payload_start, (bytes-4));
rv = dcb->func.write(dcb, buffer);
if (rv == 0)
return 0;
else
return 1;
} }
/** /**