MaxScale/server/modules/protocol/mysql_common.c
2013-09-04 15:31:04 +02:00

1093 lines
31 KiB
C

/*
* This file is distributed as part of the SkySQL Gateway. It is free
* software: you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation,
* version 2.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Copyright SkySQL Ab 2013
*/
/*
* MySQL Protocol common routines for client to gateway and gateway to backend
*
* Revision History
* Date Who Description
* 17/06/2013 Massimiliano Pinto Common MySQL protocol routines
* 02/06/2013 Massimiliano Pinto MySQL connect asynchronous phases
* 04/09/2013 Massimiliano Pinto Added dcb NULL assert in mysql_send_custom_error
*
*/
#include "mysql_client_server_protocol.h"
#include <skygw_types.h>
#include <skygw_utils.h>
#include <log_manager.h>
extern int gw_read_backend_event(DCB* dcb);
extern int gw_write_backend_event(DCB *dcb);
extern int gw_MySQLWrite_backend(DCB *dcb, GWBUF *queue);
extern int gw_error_backend_event(DCB *dcb);
MySQLProtocol* mysql_protocol_init(
DCB* dcb)
{
MySQLProtocol* p;
if (dcb != NULL) {
CHK_DCB(dcb);
}
p = (MySQLProtocol *) calloc(1, sizeof(MySQLProtocol));
if (p == NULL) {
int eno = errno;
errno = 0;
skygw_log_write_flush(
LOGFILE_ERROR,
"%lu [mysql_init_protocol] MySQL protocol init failed : "
"memory allocation due error %d, %s.",
pthread_self(),
eno,
strerror(eno));
goto return_p;
}
p->state = MYSQL_ALLOC;
p->protocol_chk_top = CHK_NUM_PROTOCOL;
p->protocol_chk_tail = CHK_NUM_PROTOCOL;
p->fd = dcb->fd;
p->descriptor = dcb;
dcb->protocol = p;
CHK_PROTOCOL(p);
return_p:
return p;
}
#if 0
/**
* gw_mysql_init
*
* Initialize mysql protocol struct
*
* @param data The MySQLProtocol pointer, usually NULL
* @return The new MySQLProtocol allocated
*
*/
MySQLProtocol *gw_mysql_init(MySQLProtocol *data) {
MySQLProtocol *input = NULL;
// structure allocation
input = calloc(1, sizeof(MySQLProtocol));
if (input == NULL) {
int eno = errno;
errno = 0;
skygw_log_write_flush(
LOGFILE_ERROR,
"%lu [gw_mysql_init] failed to allocate memory for MySQL "
"protocol object. Errno %d, %s.",
pthread_self(),
eno,
strerror(eno));
return NULL;
}
input->protocol_chk_top = CHK_NUM_PROTOCOL;
input->protocol_chk_tail = CHK_NUM_PROTOCOL;
#ifdef MYSQL_CONN_DEBUG
fprintf(stderr, "gw_mysql_init() called\n");
#endif
return input;
}
#endif
/**
* gw_mysql_close
*
* close a connection if opened
* free data scructure for MySQLProtocol
*
* @param ptr The MySQLProtocol ** to close/free
*
*/
void gw_mysql_close(MySQLProtocol **ptr) {
MySQLProtocol *conn = *ptr;
if (*ptr == NULL)
return;
#ifdef MYSQL_CONN_DEBUG
fprintf(stderr, "Closing MySQL connection %i, [%s]\n", conn->fd, conn->scramble);
#endif
if (conn->fd > 0) {
/* COM_QUIT will not be sent here, but from the caller of this routine! */
#ifdef MYSQL_CONN_DEBUG
fprintf(stderr, "mysqlgw_mysql_close() called for %i\n", conn->fd);
#endif
close(conn->fd);
} else {
#ifdef MYSQL_CONN_DEBUG
fprintf(stderr, "mysqlgw_mysql_close() called, no socket %i\n", conn->fd);
#endif
}
free(*ptr);
*ptr = NULL;
#ifdef MYSQL_CONN_DEBUG
fprintf(stderr, "mysqlgw_mysql_close() free(conn) done\n");
#endif
}
/**
* Read the backend server MySQL handshake
*
* @param conn MySQL protocol structure
* @return 0 on success, 1 on failure
*/
int gw_read_backend_handshake(MySQLProtocol *conn) {
GWBUF *head = NULL;
DCB *dcb = conn->descriptor;
int n = -1;
uint8_t *payload = NULL;
if ((n = dcb_read(dcb, &head)) != -1) {
if (head) {
payload = GWBUF_DATA(head);
// skip the 4 bytes header
payload += 4;
//Now decode mysql handshake
gw_decode_mysql_server_handshake(conn, payload);
conn->state = MYSQL_AUTH_SENT;
// consume all the data here
head = gwbuf_consume(head, gwbuf_length(head));
return 0;
}
}
return 1;
}
/**
* gw_decode_mysql_server_handshake
*
* Decode mysql server handshake
*
* @param conn The MySQLProtocol structure
* @param payload The bytes just read from the net
* @return 0 always
*
*/
int gw_decode_mysql_server_handshake(MySQLProtocol *conn, uint8_t *payload) {
uint8_t *server_version_end = NULL;
uint16_t mysql_server_capabilities_one;
uint16_t mysql_server_capabilities_two;
unsigned long tid =0;
uint8_t scramble_data_1[8] = "";
uint8_t scramble_data_2[12] = "";
uint8_t capab_ptr[4];
int scramble_len;
uint8_t scramble[GW_MYSQL_SCRAMBLE_SIZE];
payload++;
// Get server version (string)
server_version_end = (uint8_t *) gw_strend((char*) payload);
payload = server_version_end + 1;
// get ThreadID
tid = gw_mysql_get_byte4(payload);
memcpy(&conn->tid, &tid, 4);
payload +=4;
// scramble_part 1
memcpy(scramble_data_1, payload, 8);
payload += 8;
// 1 filler
payload++;
mysql_server_capabilities_one = gw_mysql_get_byte2(payload);
//Get capabilities_part 1 (2 bytes) + 1 language + 2 server_status
payload +=5;
mysql_server_capabilities_two = gw_mysql_get_byte2(payload);
memcpy(&capab_ptr, &mysql_server_capabilities_one, 2);
// get capabilities part 2 (2 bytes)
memcpy(&(capab_ptr[2]), &mysql_server_capabilities_two, 2);
// 2 bytes shift
payload+=2;
// get scramble len
scramble_len = payload[0] -1;
// skip 10 zero bytes
payload += 11;
// copy the second part of the scramble
memcpy(scramble_data_2, payload, scramble_len - 8);
memcpy(scramble, scramble_data_1, 8);
memcpy(scramble + 8, scramble_data_2, scramble_len - 8);
// full 20 bytes scramble is ready
memcpy(conn->scramble, scramble, GW_MYSQL_SCRAMBLE_SIZE);
return 0;
}
/**
* Receive the MySQL authentication packet from backend, packet # is 2
*
* @param conn The MySQL protocol structure
* @return 0 for user authenticated or 1 for authentication failed
*/
int gw_receive_backend_auth(MySQLProtocol *conn) {
int rv = 1;
int n = -1;
GWBUF *head = NULL;
DCB *dcb = conn->descriptor;
uint8_t *ptr = NULL;
if ((n = dcb_read(dcb, &head)) != -1) {
if (head) {
ptr = GWBUF_DATA(head);
// check if the auth is SUCCESFUL
if (ptr[4] == '\x00') {
// Auth is OK
rv = 0;
} else {
rv = 1;
}
// consume all the data here
head = gwbuf_consume(head, gwbuf_length(head));
}
}
return rv;
}
/**
* Write MySQL authentication 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_authentication_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 HandshakeResponse for CLIENT_PROTOCOL_41
// 4 bytes capabilities + 4 bytes max packet size + 1 byte charset + 23 '\0' bytes
// 4 + 4 + 1 + 23 = 32
bytes = 32;
bytes += strlen(user);
// the NULL
bytes++;
// next will be + 1 (scramble_len) + 20 (fixed_scramble) + 1 (user NULL term) + 1 (db NULL term)
if (curr_passwd != NULL) {
bytes++;
bytes += GW_MYSQL_SCRAMBLE_SIZE;
} else {
bytes++;
}
if (curr_db != NULL) {
bytes += strlen(curr_db);
bytes++;
}
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] = '\x01';
payload += 4;
// set client capabilities
memcpy(payload, client_capabilities, 4);
// set now the max-packet size
payload += 4;
gw_mysql_set_byte4(payload, 16777216);
// set the charset
payload += 4;
*payload = '\x08';
payload++;
// 23 bytes of 0
payload += 23;
// 4 + 4 + 4 + 1 + 23 = 36, this includes the 4 bytes packet header
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++;
}
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);
/* Set the new state, next would be MYSQL_IDLE or MYSQL_AUTH_FAILED */
conn->state = MYSQL_AUTH_RECV;
if (rv < 0)
return rv;
else
return 0;
}
/**
* gw_do_connect_to_backend
*
* This routine connects to a backend server using connect() in NON_BLOCKING mode
*
* @param host The host to connect to
* @param port The host TCP/IP port
* @param conn The MySQLProtocol structure to fill
* @return 0 on success and !=0 on failure
*
*/
int gw_do_connect_to_backend(
char *host,
int port,
MySQLProtocol *conn)
{
struct sockaddr_in serv_addr;
int rv;
int so = 0;
memset(&serv_addr, 0, sizeof serv_addr);
serv_addr.sin_family = AF_INET;
so = socket(AF_INET,SOCK_STREAM,0);
conn->fd = so;
if (so < 0) {
int eno = errno;
errno = 0;
skygw_log_write_flush(
LOGFILE_ERROR,
"%lu [gw_do_connect_to_backend] Establishing connection to "
"back-end server failed. Socket creation failed due %d, %s.",
pthread_self(),
eno,
strerror(eno));
rv = -1;
goto return_rv;
}
/* Assign so to the caller dcb, conn->descriptor */
conn->descriptor->fd = so;
/* prepare for connect */
setipaddress(&serv_addr.sin_addr, host);
serv_addr.sin_port = htons(port);
/* set socket to as non-blocking here */
setnonblocking(so);
rv = connect(so, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
if (rv != 0) {
int eno = errno;
errno = 0;
if (eno == EINPROGRESS) {
rv = 1;
} else {
skygw_log_write_flush(
LOGFILE_ERROR,
"%lu [gw_do_connect_to_backend] Failed to "
"connect backend server %s:%d, "
"due %d, %s.",
pthread_self(),
host,
port,
eno,
strerror(eno));
}
}
/**
* Add the dcb in the poll set
*/
poll_add_dcb(conn->descriptor);
return_rv:
return rv;
}
/**
* Return a string representation of a MySQL protocol state.
*
* @param state The protocol state
* @return String representation of the state
*
*/
const char *
gw_mysql_protocol_state2string (int state) {
switch(state) {
case MYSQL_ALLOC:
return "MySQL Protocl struct allocated";
case MYSQL_PENDING_CONNECT:
return "MySQL Backend socket PENDING connect";
case MYSQL_CONNECTED:
return "MySQL Backend socket CONNECTED";
case MYSQL_AUTH_SENT:
return "MySQL Authentication handshake has been sent";
case MYSQL_AUTH_RECV:
return "MySQL Received user, password, db and capabilities";
case MYSQL_AUTH_FAILED:
return "MySQL Authentication failed";
case MYSQL_IDLE:
return "MySQL Auth done. Protocol is idle, waiting for statements";
case MYSQL_ROUTING:
return "MySQL received command has been routed to backend(s)";
case MYSQL_WAITING_RESULT:
return "MySQL Waiting for result set";
case MYSQL_SESSION_CHANGE:
return "MySQL change session";
default:
return "MySQL (unknown protocol state)";
}
}
/**
* mysql_send_custom_error
*
* Send a MySQL protocol Generic ERR message, to the dcb
* Note the errno and state are still fixed now
*
* @param dcb Descriptor Control Block for the connection to which the OK is sent
* @param packet_number
* @param in_affected_rows
* @param mysql_message
* @return packet length
*
*/
int
mysql_send_custom_error (DCB *dcb, int packet_number, int in_affected_rows, const char* mysql_message) {
uint8_t *outbuf = NULL;
uint8_t mysql_payload_size = 0;
uint8_t mysql_packet_header[4];
uint8_t *mysql_payload = NULL;
uint8_t field_count = 0;
uint8_t mysql_err[2];
uint8_t mysql_statemsg[6];
unsigned int mysql_errno = 0;
const char *mysql_error_msg = NULL;
const char *mysql_state = NULL;
GWBUF *buf = NULL;
ss_dassert(dcb != NULL);
mysql_errno = 2003;
mysql_error_msg = "An errorr occurred ...";
mysql_state = "HY000";
field_count = 0xff;
gw_mysql_set_byte2(mysql_err, mysql_errno);
mysql_statemsg[0]='#';
memcpy(mysql_statemsg+1, mysql_state, 5);
if (mysql_message != NULL) {
mysql_error_msg = mysql_message;
}
mysql_payload_size = sizeof(field_count) + sizeof(mysql_err) + sizeof(mysql_statemsg) + strlen(mysql_error_msg);
// allocate memory for packet header + payload
if ((buf = gwbuf_alloc(sizeof(mysql_packet_header) + mysql_payload_size)) == NULL)
{
return 0;
}
outbuf = GWBUF_DATA(buf);
// write packet header with packet number
gw_mysql_set_byte3(mysql_packet_header, mysql_payload_size);
mysql_packet_header[3] = packet_number;
// write header
memcpy(outbuf, mysql_packet_header, sizeof(mysql_packet_header));
mysql_payload = outbuf + sizeof(mysql_packet_header);
// write field
memcpy(mysql_payload, &field_count, sizeof(field_count));
mysql_payload = mysql_payload + sizeof(field_count);
// write errno
memcpy(mysql_payload, mysql_err, sizeof(mysql_err));
mysql_payload = mysql_payload + sizeof(mysql_err);
// write sqlstate
memcpy(mysql_payload, mysql_statemsg, sizeof(mysql_statemsg));
mysql_payload = mysql_payload + sizeof(mysql_statemsg);
// write err messg
memcpy(mysql_payload, mysql_error_msg, strlen(mysql_error_msg));
// writing data in the Client buffer queue
dcb->func.write(dcb, buf);
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));
rv = dcb->func.write(dcb, buffer);
if (rv < 0)
return rv;
else
return 0;
}
/**
* gw_check_mysql_scramble_data
*
* Check authentication token received against stage1_hash and scramble
*
* @param dcb The current dcb
* @param token The token sent by the client in the authentication request
* @param token_len The token size in bytes
* @param scramble The scramble data sent by the server during handshake
* @param scramble_len The scrable size in bytes
* @param username The current username in the authentication request
* @param stage1_hash The SHA1(candidate_password) decoded by this routine
* @return 0 on succesful check or != 0 on failure
*
*/
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.
* The field password is 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)) {
/* Username without 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);
}
/**
* gw_find_mysql_user_password_sha1
*
* The routine fetches look for an user int he Gateway users' tableg
* If found the HEX passwotd, representing sha1(sha1(password)), is converted in binary data and
* copied into gateway_password
*
* @param username The user to look for
* @param gateway_password The related SHA1(SHA1(password)), the pointer must be preallocated
* @param repository The pointer to users' table data, passed as void *
* @return 1 if user is not found or 0 if the user exists
*
*/
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) {
return 1;
}
/**
* Convert now the hex data (40 bytes) to binary (20 bytes).
* The 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;
}
/**
* mysql_send_auth_error
*
* Send a MySQL protocol ERR message, for gateway authentication error to the dcb
*
* @param dcb Descriptor Control Block for the connection to which the OK is sent
* @param packet_number
* @param in_affected_rows
* @param mysql_message
* @return packet length
*
*/
int
mysql_send_auth_error (DCB *dcb, int packet_number, int in_affected_rows, const char* mysql_message) {
uint8_t *outbuf = NULL;
uint8_t mysql_payload_size = 0;
uint8_t mysql_packet_header[4];
uint8_t *mysql_payload = NULL;
uint8_t field_count = 0;
uint8_t mysql_err[2];
uint8_t mysql_statemsg[6];
unsigned int mysql_errno = 0;
const char *mysql_error_msg = NULL;
const char *mysql_state = NULL;
GWBUF *buf;
mysql_errno = 1045;
mysql_error_msg = "Access denied!";
mysql_state = "2800";
field_count = 0xff;
gw_mysql_set_byte2(mysql_err, mysql_errno);
mysql_statemsg[0]='#';
memcpy(mysql_statemsg+1, mysql_state, 5);
if (mysql_message != NULL) {
mysql_error_msg = mysql_message;
}
mysql_payload_size = sizeof(field_count) + sizeof(mysql_err) + sizeof(mysql_statemsg) + strlen(mysql_error_msg);
// allocate memory for packet header + payload
if ((buf = gwbuf_alloc(sizeof(mysql_packet_header) + mysql_payload_size)) == NULL)
{
return 0;
}
outbuf = GWBUF_DATA(buf);
// write packet header with packet number
gw_mysql_set_byte3(mysql_packet_header, mysql_payload_size);
mysql_packet_header[3] = packet_number;
// write header
memcpy(outbuf, mysql_packet_header, sizeof(mysql_packet_header));
mysql_payload = outbuf + sizeof(mysql_packet_header);
// write field
memcpy(mysql_payload, &field_count, sizeof(field_count));
mysql_payload = mysql_payload + sizeof(field_count);
// write errno
memcpy(mysql_payload, mysql_err, sizeof(mysql_err));
mysql_payload = mysql_payload + sizeof(mysql_err);
// write sqlstate
memcpy(mysql_payload, mysql_statemsg, sizeof(mysql_statemsg));
mysql_payload = mysql_payload + sizeof(mysql_statemsg);
// write err messg
memcpy(mysql_payload, mysql_error_msg, strlen(mysql_error_msg));
// writing data in the Client buffer queue
dcb->func.write(dcb, buf);
return sizeof(mysql_packet_header) + mysql_payload_size;
}
///