Added support form COM_CHANGE_USER in readwritesplit.c
Added func.auth() and func.generic() in dcb.h Some mysql routines moved into mysql_common.c New session user still not saved in the dcb->data
This commit is contained in:
@ -35,13 +35,14 @@ struct service;
|
|||||||
* Revision History
|
* Revision History
|
||||||
*
|
*
|
||||||
* Date Who Description
|
* Date Who Description
|
||||||
* 01/06/13 Mark Riddoch Initial implementation
|
* 01/06/2013 Mark Riddoch Initial implementation
|
||||||
* 11/06/13 Mark Riddoch Updated GWPROTOCOL structure with new
|
* 11/06/2013 Mark Riddoch Updated GWPROTOCOL structure with new
|
||||||
* entry points
|
* entry points
|
||||||
* 18/06/13 Mark Riddoch Addition of the listener entry point
|
* 18/06/2013 Mark Riddoch Addition of the listener entry point
|
||||||
* 02/06/2013 Massimiliano Pinto Addition of delayqlock, delayq and authlock
|
* 02/07/2013 Massimiliano Pinto Addition of delayqlock, delayq and authlock
|
||||||
* for handling backend asynchronous protocol connection
|
* for handling backend asynchronous protocol connection
|
||||||
* and a generic lock for backend authentication
|
* and a generic lock for backend authentication
|
||||||
|
* 12/07/2013 Massimiliano Pinto Added auth and generic func pointers
|
||||||
*
|
*
|
||||||
* @endverbatim
|
* @endverbatim
|
||||||
*/
|
*/
|
||||||
@ -79,6 +80,8 @@ typedef struct gw_protocol {
|
|||||||
int (*connect)(struct dcb *, struct server *, struct session *);
|
int (*connect)(struct dcb *, struct server *, struct session *);
|
||||||
int (*close)(struct dcb *);
|
int (*close)(struct dcb *);
|
||||||
int (*listen)(struct dcb *, char *);
|
int (*listen)(struct dcb *, char *);
|
||||||
|
int (*auth)(struct dcb *, struct server *, struct session *, GWBUF *);
|
||||||
|
int (*generic)(struct dcb *, void *);
|
||||||
} GWPROTOCOL;
|
} GWPROTOCOL;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -162,20 +165,20 @@ typedef struct dcb {
|
|||||||
#define DCB_PROTOCOL(x, type) (type *)((x)->protocol)
|
#define DCB_PROTOCOL(x, type) (type *)((x)->protocol)
|
||||||
#define DCB_ISZOMBIE(x) ((x)->state == DCB_STATE_ZOMBIE)
|
#define DCB_ISZOMBIE(x) ((x)->state == DCB_STATE_ZOMBIE)
|
||||||
|
|
||||||
extern DCB *dcb_alloc(); /* Allocate a DCB */
|
extern DCB *dcb_alloc(); /* Allocate a DCB */
|
||||||
extern void dcb_free(DCB *); /* Free a DCB */
|
extern void dcb_free(DCB *); /* Free a DCB */
|
||||||
extern DCB *dcb_connect(struct server *, struct session *, const char *);
|
extern DCB *dcb_connect(struct server *, struct session *, const char *); /* prepare Backend connection */
|
||||||
extern int dcb_read(DCB *, GWBUF **); /* Generic read routine */
|
extern int dcb_read(DCB *, GWBUF **); /* Generic read routine */
|
||||||
extern int dcb_write(DCB *, GWBUF *); /* Generic write routine */
|
extern int dcb_write(DCB *, GWBUF *); /* Generic write routine */
|
||||||
extern int dcb_drain_writeq(DCB *); /* Generic write routine */
|
extern int dcb_drain_writeq(DCB *); /* Generic write routine */
|
||||||
extern void dcb_close(DCB *); /* Generic close functionality */
|
extern void dcb_close(DCB *); /* Generic close functionality */
|
||||||
extern void dcb_process_zombies(int); /* Process Zombies */
|
extern void dcb_process_zombies(int); /* Process Zombies */
|
||||||
extern void printAllDCBs(); /* Debug to print all DCB in the system */
|
extern void printAllDCBs(); /* Debug to print all DCB in the system */
|
||||||
extern void printDCB(DCB *); /* Debug print routine */
|
extern void printDCB(DCB *); /* Debug print routine */
|
||||||
extern void dprintAllDCBs(DCB *); /* Debug to print all DCB in the system */
|
extern void dprintAllDCBs(DCB *); /* Debug to print all DCB in the system */
|
||||||
extern void dprintDCB(DCB *, DCB *); /* Debug to print a DCB in the system */
|
extern void dprintDCB(DCB *, DCB *); /* Debug to print a DCB in the system */
|
||||||
extern const char *gw_dcb_state2string(int); /* DCB state to string */
|
extern const char *gw_dcb_state2string(int); /* DCB state to string */
|
||||||
extern void dcb_printf(DCB *, const char *, ...); /* DCB version of printf */
|
extern void dcb_printf(DCB *, const char *, ...); /* DCB version of printf */
|
||||||
extern int dcb_isclient(DCB *);
|
extern int dcb_isclient(DCB *); /* the DCB is the client of the session */
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -27,6 +27,7 @@
|
|||||||
* for MySQL session
|
* for MySQL session
|
||||||
* 04-07-2013 Massimiliano Pinto Added new MySQL protocol status for asynchronous connection
|
* 04-07-2013 Massimiliano Pinto Added new MySQL protocol status for asynchronous connection
|
||||||
* Added authentication reply status
|
* Added authentication reply status
|
||||||
|
* 12-07-2013 Massimiliano Pinto Added routines for change_user
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
@ -220,6 +221,9 @@ int gw_send_authentication_to_backend(char *dbname, char *user, uint8_t *passwd,
|
|||||||
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, MySQLProtocol *conn);
|
int gw_do_connect_to_backend(char *host, int port, MySQLProtocol *conn);
|
||||||
int mysql_send_custom_error (DCB *dcb, int packet_number, int in_affected_rows, const char* mysql_message);
|
int mysql_send_custom_error (DCB *dcb, int packet_number, int in_affected_rows, const char* mysql_message);
|
||||||
|
int gw_send_change_user_to_backend(char *dbname, char *user, uint8_t *passwd, MySQLProtocol *conn);
|
||||||
|
int gw_find_mysql_user_password_sha1(char *username, uint8_t *gateway_password, void *repository);
|
||||||
|
int gw_check_mysql_scramble_data(DCB *dcb, uint8_t *token, unsigned int token_len, uint8_t *scramble, unsigned int scramble_len, char *username, uint8_t *stage1_hash);
|
||||||
|
|
||||||
extern void gw_sha1_str(const uint8_t *in, int in_len, uint8_t *out);
|
extern void gw_sha1_str(const uint8_t *in, int in_len, uint8_t *out);
|
||||||
extern void gw_sha1_2_str(const uint8_t *in, int in_len, const uint8_t *in2, int in2_len, uint8_t *out);
|
extern void gw_sha1_2_str(const uint8_t *in, int in_len, const uint8_t *in2, int in2_len, uint8_t *out);
|
||||||
|
|||||||
@ -66,7 +66,9 @@ static GWPROTOCOL MyObject = {
|
|||||||
httpd_accept, /**< Accept */
|
httpd_accept, /**< Accept */
|
||||||
NULL, /**< Connect */
|
NULL, /**< Connect */
|
||||||
httpd_close, /**< Close */
|
httpd_close, /**< Close */
|
||||||
httpd_listen /**< Create a listener */
|
httpd_listen, /**< Create a listener */
|
||||||
|
NULL, /**< Authentication */
|
||||||
|
NULL /**< Generic */
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -36,11 +36,12 @@
|
|||||||
* 03/07/2013 Massimiliano Pinto Added delayq for incoming data before mysql connection
|
* 03/07/2013 Massimiliano Pinto Added delayq for incoming data before mysql connection
|
||||||
* 04/07/2013 Massimiliano Pinto Added asyncrhronous MySQL protocol connection to backend
|
* 04/07/2013 Massimiliano Pinto Added asyncrhronous MySQL protocol connection to backend
|
||||||
* 05/07/2013 Massimiliano Pinto Added closeSession if backend auth fails
|
* 05/07/2013 Massimiliano Pinto Added closeSession if backend auth fails
|
||||||
|
* 12/07/2013 Massimiliano Pinto Addesd Mysql Change User via dcb->func.auth()
|
||||||
*/
|
*/
|
||||||
|
|
||||||
static char *version_str = "V2.0.0";
|
static char *version_str = "V2.0.0";
|
||||||
int gw_mysql_connect(char *host, int port, char *dbname, char *user, uint8_t *passwd, MySQLProtocol *conn);
|
int gw_mysql_connect(char *host, int port, char *dbname, char *user, uint8_t *passwd, MySQLProtocol *conn);
|
||||||
static int gw_create_backend_connection(DCB *client_dcb, SERVER *server, SESSION *in_session);
|
static int gw_create_backend_connection(DCB *backend, SERVER *server, SESSION *in_session);
|
||||||
static int gw_read_backend_event(DCB* dcb);
|
static int gw_read_backend_event(DCB* dcb);
|
||||||
static int gw_write_backend_event(DCB *dcb);
|
static int gw_write_backend_event(DCB *dcb);
|
||||||
static int gw_MySQLWrite_backend(DCB *dcb, GWBUF *queue);
|
static int gw_MySQLWrite_backend(DCB *dcb, GWBUF *queue);
|
||||||
@ -49,6 +50,7 @@ static int gw_backend_close(DCB *dcb);
|
|||||||
static int gw_backend_hangup(DCB *dcb);
|
static int gw_backend_hangup(DCB *dcb);
|
||||||
static int backend_write_delayqueue(DCB *dcb);
|
static int backend_write_delayqueue(DCB *dcb);
|
||||||
static void backend_set_delayqueue(DCB *dcb, GWBUF *queue);
|
static void backend_set_delayqueue(DCB *dcb, GWBUF *queue);
|
||||||
|
static int gw_change_user(DCB *backend_dcb, SERVER *server, SESSION *in_session, GWBUF *queue);
|
||||||
|
|
||||||
extern char *gw_strend(register const char *s);
|
extern char *gw_strend(register const char *s);
|
||||||
|
|
||||||
@ -61,7 +63,9 @@ static GWPROTOCOL MyObject = {
|
|||||||
NULL, /* Accept */
|
NULL, /* Accept */
|
||||||
gw_create_backend_connection, /* Connect */
|
gw_create_backend_connection, /* Connect */
|
||||||
gw_backend_close, /* Close */
|
gw_backend_close, /* Close */
|
||||||
NULL /* Listen */
|
NULL, /* Listen */
|
||||||
|
gw_change_user, /* Authentication */
|
||||||
|
NULL /* Generic */
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -298,8 +302,6 @@ static int gw_create_backend_connection(DCB *backend, SERVER *server, SESSION *s
|
|||||||
MYSQL_session *s_data = NULL;
|
MYSQL_session *s_data = NULL;
|
||||||
int rv = -1;
|
int rv = -1;
|
||||||
|
|
||||||
//fprintf(stderr, "HERE, the server to connect is [%s]:[%i]\n", server->name, server->port);
|
|
||||||
|
|
||||||
protocol = (MySQLProtocol *) calloc(1, sizeof(MySQLProtocol));
|
protocol = (MySQLProtocol *) calloc(1, sizeof(MySQLProtocol));
|
||||||
protocol->state = MYSQL_ALLOC;
|
protocol->state = MYSQL_ALLOC;
|
||||||
|
|
||||||
@ -419,4 +421,73 @@ static int backend_write_delayqueue(DCB *dcb)
|
|||||||
|
|
||||||
return dcb_write(dcb, localq);
|
return dcb_write(dcb, localq);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
static int gw_change_user(DCB *backend, SERVER *server, SESSION *in_session, GWBUF *queue) {
|
||||||
|
MYSQL_session *current_session = NULL;
|
||||||
|
MySQLProtocol *backend_protocol = NULL;
|
||||||
|
MySQLProtocol *client_protocol = NULL;
|
||||||
|
char username[MYSQL_USER_MAXLEN+1]="";
|
||||||
|
char database[MYSQL_DATABASE_MAXLEN+1]="";
|
||||||
|
uint8_t client_sha1[MYSQL_SCRAMBLE_LEN]="";
|
||||||
|
uint8_t *client_auth_packet = GWBUF_DATA(queue);
|
||||||
|
unsigned int auth_token_len = 0;
|
||||||
|
uint8_t *auth_token = NULL;
|
||||||
|
int rv = -1;
|
||||||
|
int len = 0;
|
||||||
|
int auth_ret = 1;
|
||||||
|
|
||||||
|
current_session = (MYSQL_session *)in_session->client->data;
|
||||||
|
backend_protocol = backend->protocol;
|
||||||
|
client_protocol = in_session->client->protocol;
|
||||||
|
|
||||||
|
// now get the user, after 4 bytes header and 1 byte command
|
||||||
|
client_auth_packet += 5;
|
||||||
|
strcpy(username, (char *)client_auth_packet);
|
||||||
|
fprintf(stderr, "<<< The NEW Client username is [%s]\n", client_auth_packet);
|
||||||
|
client_auth_packet += strlen(username) + 1;
|
||||||
|
|
||||||
|
// get the auth token len
|
||||||
|
memcpy(&auth_token_len, client_auth_packet, 1);
|
||||||
|
client_auth_packet++;
|
||||||
|
|
||||||
|
fprintf(stderr, "<<< Now decoding the NEW user %i...\n", auth_token_len);
|
||||||
|
|
||||||
|
// allocate memory for token only if auth_token_len > 0
|
||||||
|
if (auth_token_len) {
|
||||||
|
auth_token = (uint8_t *)malloc(auth_token_len);
|
||||||
|
memcpy(auth_token, client_auth_packet, auth_token_len);
|
||||||
|
client_auth_packet += auth_token_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf(stderr, "<<< Now decoding the NEW token [%s]\n", auth_token);
|
||||||
|
|
||||||
|
// decode the token and check the 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);
|
||||||
|
|
||||||
|
fprintf(stderr, "<<< HERE after gw_check_mysql_scramble_data()\n");
|
||||||
|
|
||||||
|
if (auth_ret != 0) {
|
||||||
|
fprintf(stderr, "<<< CLIENT AUTH FAILED for user [%s]\n", username);
|
||||||
|
}
|
||||||
|
|
||||||
|
// let's free the auth_token now
|
||||||
|
if (auth_token)
|
||||||
|
free(auth_token);
|
||||||
|
|
||||||
|
// get db name
|
||||||
|
strcpy(database, (char *)client_auth_packet);
|
||||||
|
fprintf(stderr, "<<< The NEW Client selected db is [%s]\n", database);
|
||||||
|
|
||||||
|
fprintf(stderr, "<<<< Backend session data is [%s],[%s],[%s]\n", current_session->user, current_session->client_sha1, current_session->db);
|
||||||
|
fprintf(stderr, "<<<< NEW Backend session data will be is [%s],[%s],[%s]\n", username, client_sha1, database);
|
||||||
|
rv = gw_send_change_user_to_backend(database, username, client_sha1, backend_protocol);
|
||||||
|
|
||||||
|
len = GWBUF_LENGTH(queue);
|
||||||
|
queue = gwbuf_consume(queue, len);
|
||||||
|
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
/////
|
/////
|
||||||
|
|||||||
@ -42,8 +42,6 @@ static int gw_error_client_event(DCB *dcb);
|
|||||||
static int gw_client_close(DCB *dcb);
|
static int gw_client_close(DCB *dcb);
|
||||||
static int gw_client_hangup_event(DCB *dcb);
|
static int gw_client_hangup_event(DCB *dcb);
|
||||||
|
|
||||||
static int gw_check_mysql_scramble_data(DCB *dcb, uint8_t *token, unsigned int token_len, uint8_t *scramble, unsigned int scramble_len, char *username, uint8_t *stage1_hash);
|
|
||||||
static int gw_find_mysql_user_password_sha1(char *username, uint8_t *gateway_password, void *repository);
|
|
||||||
int mysql_send_ok(DCB *dcb, int packet_number, int in_affected_rows, const char* mysql_message);
|
int mysql_send_ok(DCB *dcb, int packet_number, int in_affected_rows, const char* mysql_message);
|
||||||
int mysql_send_auth_error (DCB *dcb, int packet_number, int in_affected_rows, const char* mysql_message);
|
int mysql_send_auth_error (DCB *dcb, int packet_number, int in_affected_rows, const char* mysql_message);
|
||||||
int MySQLSendHandshake(DCB* dcb);
|
int MySQLSendHandshake(DCB* dcb);
|
||||||
@ -61,7 +59,9 @@ static GWPROTOCOL MyObject = {
|
|||||||
gw_MySQLAccept, /* Accept */
|
gw_MySQLAccept, /* Accept */
|
||||||
NULL, /* Connect */
|
NULL, /* Connect */
|
||||||
gw_client_close, /* Close */
|
gw_client_close, /* Close */
|
||||||
gw_MySQLListener /* Listen */
|
gw_MySQLListener, /* Listen */
|
||||||
|
NULL, /* Authentication */
|
||||||
|
NULL /* Generic */
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -482,122 +482,6 @@ static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue) {
|
|||||||
return auth_ret;
|
return auth_ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/////////////////////////////////////////////////
|
|
||||||
// get the sha1(sha1(password) from repository
|
|
||||||
/////////////////////////////////////////////////
|
|
||||||
static int gw_find_mysql_user_password_sha1(char *username, uint8_t *gateway_password, void *repository) {
|
|
||||||
SERVICE *service = NULL;
|
|
||||||
char *user_password = NULL;
|
|
||||||
|
|
||||||
if (strcmp(username , "root") == 0) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
service = (SERVICE *) ((DCB *)repository)->service;
|
|
||||||
|
|
||||||
user_password = (char *)users_fetch(service->users, username);
|
|
||||||
|
|
||||||
if (!user_password) {
|
|
||||||
fprintf(stderr, ">>> MYSQL user NOT FOUND: %s\n", username);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// convert hex data (40 bytes) to binary (20 bytes)
|
|
||||||
// gateway_password represents the SHA1(SHA1(real_password))
|
|
||||||
// please not real_password is unknown and SHA1(real_password)
|
|
||||||
// is unknown as well
|
|
||||||
|
|
||||||
if (strlen(user_password))
|
|
||||||
gw_hex2bin(gateway_password, user_password, SHA_DIGEST_LENGTH * 2);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int gw_check_mysql_scramble_data(DCB *dcb, uint8_t *token, unsigned int token_len, uint8_t *scramble, unsigned int scramble_len, char *username, uint8_t *stage1_hash) {
|
|
||||||
uint8_t step1[GW_MYSQL_SCRAMBLE_SIZE]="";
|
|
||||||
uint8_t step2[GW_MYSQL_SCRAMBLE_SIZE +1]="";
|
|
||||||
uint8_t check_hash[GW_MYSQL_SCRAMBLE_SIZE]="";
|
|
||||||
char hex_double_sha1[2 * GW_MYSQL_SCRAMBLE_SIZE + 1]="";
|
|
||||||
uint8_t password[GW_MYSQL_SCRAMBLE_SIZE]="";
|
|
||||||
int ret_val = 1;
|
|
||||||
|
|
||||||
if ((username == NULL) || (scramble == NULL) || (stage1_hash == NULL)) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the user's password from repository in SHA1(SHA1(real_password));
|
|
||||||
// please note 'real_password' is unknown!
|
|
||||||
ret_val = gw_find_mysql_user_password_sha1(username, password, (DCB *) dcb);
|
|
||||||
|
|
||||||
if (ret_val) {
|
|
||||||
fprintf(stderr, "<<<< User [%s] was not found\n", username);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (token && token_len) {
|
|
||||||
// convert in hex format: this is the content of mysql.user table, field password without the '*' prefix
|
|
||||||
// and it is 40 bytes long
|
|
||||||
gw_bin2hex(hex_double_sha1, password, SHA_DIGEST_LENGTH);
|
|
||||||
} else {
|
|
||||||
// check if the password is not set in the user table
|
|
||||||
if (!strlen((char *)password)) {
|
|
||||||
fprintf(stderr, ">>> continue WITHOUT auth, no password\n");
|
|
||||||
return 0;
|
|
||||||
} else {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
///////////////////////////
|
|
||||||
// Auth check in 3 steps
|
|
||||||
//////////////////////////
|
|
||||||
|
|
||||||
// Note: token = XOR (SHA1(real_password), SHA1(CONCAT(scramble, SHA1(SHA1(real_password)))))
|
|
||||||
// the client sends token
|
|
||||||
//
|
|
||||||
// Now, server side:
|
|
||||||
//
|
|
||||||
/////////////
|
|
||||||
// step 1: compute the STEP1 = SHA1(CONCAT(scramble, gateway_password))
|
|
||||||
// the result in step1 is SHA_DIGEST_LENGTH long
|
|
||||||
////////////
|
|
||||||
gw_sha1_2_str(scramble, scramble_len, password, SHA_DIGEST_LENGTH, step1);
|
|
||||||
|
|
||||||
////////////
|
|
||||||
// step2: STEP2 = XOR(token, STEP1)
|
|
||||||
////////////
|
|
||||||
// token is trasmitted form client and it's based on the handshake scramble and SHA1(real_passowrd)
|
|
||||||
// step1 has been computed in the previous step
|
|
||||||
// the result STEP2 is SHA1(the_password_to_check) and is SHA_DIGEST_LENGTH long
|
|
||||||
|
|
||||||
gw_str_xor(step2, token, step1, token_len);
|
|
||||||
|
|
||||||
// copy the stage1_hash back to the caller
|
|
||||||
// stage1_hash will be used for backend authentication
|
|
||||||
|
|
||||||
memcpy(stage1_hash, step2, SHA_DIGEST_LENGTH);
|
|
||||||
|
|
||||||
///////////
|
|
||||||
// step 3: prepare the check_hash
|
|
||||||
///////////
|
|
||||||
// compute the SHA1(STEP2) that is SHA1(SHA1(the_password_to_check)), and is SHA_DIGEST_LENGTH long
|
|
||||||
|
|
||||||
gw_sha1_str(step2, SHA_DIGEST_LENGTH, check_hash);
|
|
||||||
|
|
||||||
|
|
||||||
#ifdef GW_DEBUG_CLIENT_AUTH
|
|
||||||
{
|
|
||||||
char inpass[128]="";
|
|
||||||
gw_bin2hex(inpass, check_hash, SHA_DIGEST_LENGTH);
|
|
||||||
|
|
||||||
fprintf(stderr, "The CLIENT hex(SHA1(SHA1(password))) for \"%s\" is [%s]", username, inpass);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// now compare SHA1(SHA1(gateway_password)) and check_hash: return 0 is MYSQL_AUTH_OK
|
|
||||||
return memcmp(password, check_hash, SHA_DIGEST_LENGTH);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Write function for client DCB: writes data from Gateway to Client
|
* Write function for client DCB: writes data from Gateway to Client
|
||||||
*
|
*
|
||||||
|
|||||||
@ -591,3 +591,308 @@ mysql_send_custom_error (DCB *dcb, int packet_number, int in_affected_rows, cons
|
|||||||
return sizeof(mysql_packet_header) + mysql_payload_size;
|
return sizeof(mysql_packet_header) + mysql_payload_size;
|
||||||
}
|
}
|
||||||
/////
|
/////
|
||||||
|
/**
|
||||||
|
* Write a MySQL CHANGE_USER packet to backend server
|
||||||
|
*
|
||||||
|
* @param conn MySQL protocol structure
|
||||||
|
* @param dbname The selected database
|
||||||
|
* @param user The selected user
|
||||||
|
* @param passwd The SHA1(real_password): Note real_password is unknown
|
||||||
|
* @return 0 on success, 1 on failure
|
||||||
|
*/
|
||||||
|
int gw_send_change_user_to_backend(char *dbname, char *user, uint8_t *passwd, MySQLProtocol *conn) {
|
||||||
|
int compress = 0;
|
||||||
|
int rv;
|
||||||
|
uint8_t *payload = NULL;
|
||||||
|
uint8_t *payload_start = NULL;
|
||||||
|
long bytes;
|
||||||
|
uint8_t client_scramble[GW_MYSQL_SCRAMBLE_SIZE];
|
||||||
|
uint8_t client_capabilities[4];
|
||||||
|
uint32_t server_capabilities;
|
||||||
|
uint32_t final_capabilities;
|
||||||
|
char dbpass[129]="";
|
||||||
|
GWBUF *buffer;
|
||||||
|
DCB *dcb;
|
||||||
|
|
||||||
|
char *curr_db = NULL;
|
||||||
|
uint8_t *curr_passwd = NULL;
|
||||||
|
|
||||||
|
if (strlen(dbname))
|
||||||
|
curr_db = dbname;
|
||||||
|
|
||||||
|
if (strlen((char *)passwd))
|
||||||
|
curr_passwd = passwd;
|
||||||
|
|
||||||
|
dcb = conn->descriptor;
|
||||||
|
|
||||||
|
//#ifdef DEBUG_MYSQL_CONN
|
||||||
|
fprintf(stderr, ">> Sending credentials %s, %s, db %s\n", user, passwd, dbname);
|
||||||
|
//#endif
|
||||||
|
|
||||||
|
// Zero the vars
|
||||||
|
memset(&server_capabilities, '\0', sizeof(server_capabilities));
|
||||||
|
memset(&final_capabilities, '\0', sizeof(final_capabilities));
|
||||||
|
|
||||||
|
final_capabilities = gw_mysql_get_byte4((uint8_t *)&server_capabilities);
|
||||||
|
|
||||||
|
final_capabilities |= GW_MYSQL_CAPABILITIES_PROTOCOL_41;
|
||||||
|
final_capabilities |= GW_MYSQL_CAPABILITIES_CLIENT;
|
||||||
|
|
||||||
|
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);
|
||||||
|
// the NULL
|
||||||
|
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;
|
||||||
|
bytes++;
|
||||||
|
} else {
|
||||||
|
bytes++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (curr_db != NULL) {
|
||||||
|
bytes += strlen(curr_db);
|
||||||
|
bytes++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// the charset
|
||||||
|
bytes += 2;
|
||||||
|
bytes += strlen("mysql_native_password");
|
||||||
|
bytes++;
|
||||||
|
|
||||||
|
// the packet header
|
||||||
|
bytes += 4;
|
||||||
|
|
||||||
|
// allocating the GWBUF
|
||||||
|
buffer = gwbuf_alloc(bytes);
|
||||||
|
payload = GWBUF_DATA(buffer);
|
||||||
|
|
||||||
|
// clearing data
|
||||||
|
memset(payload, '\0', bytes);
|
||||||
|
|
||||||
|
// save the start pointer
|
||||||
|
payload_start = payload;
|
||||||
|
|
||||||
|
// set packet # = 1
|
||||||
|
payload[3] = 0x00;
|
||||||
|
payload += 4;
|
||||||
|
|
||||||
|
// 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++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// set the charset, 2 bytes!!!!
|
||||||
|
*payload = '\x08';
|
||||||
|
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));
|
||||||
|
|
||||||
|
// write to backend dcb
|
||||||
|
// ToDO: handle the EAGAIN | EWOULDBLOCK
|
||||||
|
rv = write(dcb->fd, GWBUF_DATA(buffer), bytes);
|
||||||
|
gwbuf_consume(buffer, bytes);
|
||||||
|
|
||||||
|
conn->state = MYSQL_IDLE;
|
||||||
|
|
||||||
|
if (rv < 0)
|
||||||
|
return rv;
|
||||||
|
else
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int gw_check_mysql_scramble_data(DCB *dcb, uint8_t *token, unsigned int token_len, uint8_t *scramble, unsigned int scramble_len, char *username, uint8_t *stage1_hash) {
|
||||||
|
uint8_t step1[GW_MYSQL_SCRAMBLE_SIZE]="";
|
||||||
|
uint8_t step2[GW_MYSQL_SCRAMBLE_SIZE +1]="";
|
||||||
|
uint8_t check_hash[GW_MYSQL_SCRAMBLE_SIZE]="";
|
||||||
|
char hex_double_sha1[2 * GW_MYSQL_SCRAMBLE_SIZE + 1]="";
|
||||||
|
uint8_t password[GW_MYSQL_SCRAMBLE_SIZE]="";
|
||||||
|
int ret_val = 1;
|
||||||
|
|
||||||
|
if ((username == NULL) || (scramble == NULL) || (stage1_hash == NULL)) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the user's password from repository in SHA1(SHA1(real_password));
|
||||||
|
// please note 'real_password' is unknown!
|
||||||
|
ret_val = gw_find_mysql_user_password_sha1(username, password, (DCB *) dcb);
|
||||||
|
|
||||||
|
if (ret_val) {
|
||||||
|
fprintf(stderr, "<<<< User [%s] was not found\n", username);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (token && token_len) {
|
||||||
|
// convert in hex format: this is the content of mysql.user table, field password without the '*' prefix
|
||||||
|
// and it is 40 bytes long
|
||||||
|
gw_bin2hex(hex_double_sha1, password, SHA_DIGEST_LENGTH);
|
||||||
|
} else {
|
||||||
|
// check if the password is not set in the user table
|
||||||
|
if (!strlen((char *)password)) {
|
||||||
|
fprintf(stderr, ">>> continue WITHOUT auth, no password\n");
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////
|
||||||
|
// Auth check in 3 steps
|
||||||
|
//////////////////////////
|
||||||
|
|
||||||
|
// Note: token = XOR (SHA1(real_password), SHA1(CONCAT(scramble, SHA1(SHA1(real_password)))))
|
||||||
|
// the client sends token
|
||||||
|
//
|
||||||
|
// Now, server side:
|
||||||
|
//
|
||||||
|
/////////////
|
||||||
|
// step 1: compute the STEP1 = SHA1(CONCAT(scramble, gateway_password))
|
||||||
|
// the result in step1 is SHA_DIGEST_LENGTH long
|
||||||
|
////////////
|
||||||
|
gw_sha1_2_str(scramble, scramble_len, password, SHA_DIGEST_LENGTH, step1);
|
||||||
|
|
||||||
|
////////////
|
||||||
|
// step2: STEP2 = XOR(token, STEP1)
|
||||||
|
////////////
|
||||||
|
// token is trasmitted form client and it's based on the handshake scramble and SHA1(real_passowrd)
|
||||||
|
// step1 has been computed in the previous step
|
||||||
|
// the result STEP2 is SHA1(the_password_to_check) and is SHA_DIGEST_LENGTH long
|
||||||
|
|
||||||
|
gw_str_xor(step2, token, step1, token_len);
|
||||||
|
|
||||||
|
// copy the stage1_hash back to the caller
|
||||||
|
// stage1_hash will be used for backend authentication
|
||||||
|
|
||||||
|
memcpy(stage1_hash, step2, SHA_DIGEST_LENGTH);
|
||||||
|
|
||||||
|
///////////
|
||||||
|
// step 3: prepare the check_hash
|
||||||
|
///////////
|
||||||
|
// compute the SHA1(STEP2) that is SHA1(SHA1(the_password_to_check)), and is SHA_DIGEST_LENGTH long
|
||||||
|
|
||||||
|
gw_sha1_str(step2, SHA_DIGEST_LENGTH, check_hash);
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef GW_DEBUG_CLIENT_AUTH
|
||||||
|
{
|
||||||
|
char inpass[128]="";
|
||||||
|
gw_bin2hex(inpass, check_hash, SHA_DIGEST_LENGTH);
|
||||||
|
|
||||||
|
fprintf(stderr, "The CLIENT hex(SHA1(SHA1(password))) for \"%s\" is [%s]", username, inpass);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// now compare SHA1(SHA1(gateway_password)) and check_hash: return 0 is MYSQL_AUTH_OK
|
||||||
|
return memcmp(password, check_hash, SHA_DIGEST_LENGTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////
|
||||||
|
// get the sha1(sha1(password) from repository
|
||||||
|
/////////////////////////////////////////////////
|
||||||
|
int gw_find_mysql_user_password_sha1(char *username, uint8_t *gateway_password, void *repository) {
|
||||||
|
SERVICE *service = NULL;
|
||||||
|
char *user_password = NULL;
|
||||||
|
|
||||||
|
if (strcmp(username , "root") == 0) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
service = (SERVICE *) ((DCB *)repository)->service;
|
||||||
|
|
||||||
|
user_password = (char *)users_fetch(service->users, username);
|
||||||
|
|
||||||
|
if (!user_password) {
|
||||||
|
fprintf(stderr, ">>> MYSQL user NOT FOUND: %s\n", username);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert hex data (40 bytes) to binary (20 bytes)
|
||||||
|
// gateway_password represents the SHA1(SHA1(real_password))
|
||||||
|
// please not real_password is unknown and SHA1(real_password)
|
||||||
|
// is unknown as well
|
||||||
|
|
||||||
|
if (strlen(user_password))
|
||||||
|
gw_hex2bin(gateway_password, user_password, SHA_DIGEST_LENGTH * 2);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@ -76,7 +76,9 @@ static GWPROTOCOL MyObject = {
|
|||||||
telnetd_accept, /**< Accept */
|
telnetd_accept, /**< Accept */
|
||||||
NULL, /**< Connect */
|
NULL, /**< Connect */
|
||||||
telnetd_close, /**< Close */
|
telnetd_close, /**< Close */
|
||||||
telnetd_listen /**< Create a listener */
|
telnetd_listen, /**< Create a listener */
|
||||||
|
NULL, /**< Authentication */
|
||||||
|
NULL /**< Generic */
|
||||||
};
|
};
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
|||||||
@ -377,6 +377,8 @@ static int routeQuery(
|
|||||||
len += 255*packet[1];
|
len += 255*packet[1];
|
||||||
len += 255*255*packet[2];
|
len += 255*255*packet[2];
|
||||||
|
|
||||||
|
fprintf(stderr, "\n\n\n>>> Packet type is [%x]\n\n\n", packet_type);
|
||||||
|
|
||||||
switch(packet_type) {
|
switch(packet_type) {
|
||||||
case COM_INIT_DB: /**< 2 DDL must go to the master */
|
case COM_INIT_DB: /**< 2 DDL must go to the master */
|
||||||
case COM_REFRESH: /**< 7 - I guess this is session but not sure */
|
case COM_REFRESH: /**< 7 - I guess this is session but not sure */
|
||||||
@ -446,10 +448,18 @@ static int routeQuery(
|
|||||||
* TODO! Connection to all servers must be established, and
|
* TODO! Connection to all servers must be established, and
|
||||||
* the command must be executed in them.
|
* the command must be executed in them.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
if (packet_type != COM_CHANGE_USER)
|
||||||
{
|
{
|
||||||
GWBUF *cq = gwbuf_clone(queue);
|
GWBUF *cq = gwbuf_clone(queue);
|
||||||
|
fprintf(stderr, "\n\n>>>> SESSION WRITE type [%x]\n\n", packet_type);
|
||||||
ret = session->masterconn->func.write(session->masterconn, queue);
|
ret = session->masterconn->func.write(session->masterconn, queue);
|
||||||
session->slaveconn->func.write(session->slaveconn, cq);
|
session->slaveconn->func.write(session->slaveconn, cq);
|
||||||
|
} else {
|
||||||
|
GWBUF *cq = gwbuf_clone(queue);
|
||||||
|
fprintf(stderr, "\n\n>>>> COM_CHANGE_USER here\n\n");
|
||||||
|
session->masterconn->func.auth(session->masterconn, NULL, session->masterconn->session, queue);
|
||||||
|
session->slaveconn->func.auth(session->slaveconn, NULL, session->masterconn->session, cq);
|
||||||
}
|
}
|
||||||
atomic_add(&inst->stats.n_all, 1);
|
atomic_add(&inst->stats.n_all, 1);
|
||||||
goto return_ret;
|
goto return_ret;
|
||||||
|
|||||||
Reference in New Issue
Block a user