diff --git a/include/maxscale/protocol/mysql.h b/include/maxscale/protocol/mysql.h index e9f5e4d30..18760dc9b 100644 --- a/include/maxscale/protocol/mysql.h +++ b/include/maxscale/protocol/mysql.h @@ -12,29 +12,6 @@ * Public License. */ -/* - * Revision History - * - * Date Who Description - * 01-06-2013 Mark Riddoch Initial implementation - * 14-06-2013 Massimiliano Pinto Added specific data - * for MySQL session - * 04-07-2013 Massimiliano Pinto Added new MySQL protocol status for asynchronous connection - * Added authentication reply status - * 12-07-2013 Massimiliano Pinto Added routines for change_user - * 14-02-2014 Massimiliano Pinto setipaddress returns int - * 25-02-2014 Massimiliano Pinto Added dcb parameter to gw_find_mysql_user_password_sha1() - * and repository to gw_check_mysql_scramble_data() - * It's now possible to specify a different users' table than - * dcb->service->users default - * 26-02-2014 Massimiliano Pinto Removed previously added parameters to gw_check_mysql_scramble_data() and - * gw_find_mysql_user_password_sha1() - * 28-02-2014 Massimiliano Pinto MYSQL_DATABASE_MAXLEN,MYSQL_USER_MAXLEN moved to dbusers.h - * 07-02-2016 Martin Brampton Extend MYSQL_session type; add MYSQL_AUTH_SUCCEEDED - * 17-05-2016 Martin Brampton Moved gw_find_mysql_user_password_sha1 to mysql_auth.c - * - */ - #include #include #include @@ -431,6 +408,30 @@ void init_response_status(GWBUF* buf, uint8_t cmd, int* npackets, size_t* nbytes bool read_complete_packet(DCB *dcb, GWBUF **readbuf); bool gw_get_shared_session_auth_info(DCB* dcb, MYSQL_session* session); +/** + * Decode server handshake + * + * @param conn The MySQLProtocol structure + * @param payload The handshake payload without the network header + * + * @return 0 on success, -1 on failure + * + */ +int gw_decode_mysql_server_handshake(MySQLProtocol *conn, uint8_t *payload); + +/** + * Create a response to the server handshake + * + * @param session Session object + * @param conn MySQL Protocol object for this connection + * @param with_ssl Whether to create an SSL response or a normal response packet + * @param ssl_established Set to true if the SSL response has been sent + * + * @return Generated response packet + */ +GWBUF* gw_generate_auth_response(MXS_SESSION* session, MySQLProtocol *conn, + bool with_ssl, bool ssl_established); + /** Read the backend server's handshake */ bool gw_read_backend_handshake(DCB *dcb, GWBUF *buffer); diff --git a/server/modules/protocol/MySQL/MySQLBackend/mysql_backend.c b/server/modules/protocol/MySQL/MySQLBackend/mysql_backend.c index 5ca831b62..74a0b7419 100644 --- a/server/modules/protocol/MySQL/MySQLBackend/mysql_backend.c +++ b/server/modules/protocol/MySQL/MySQLBackend/mysql_backend.c @@ -26,30 +26,6 @@ /* * MySQL Protocol module for handling the protocol between the gateway * and the backend MySQL database. - * - * Revision History - * Date Who Description - * 14/06/2013 Mark Riddoch Initial version - * 17/06/2013 Massimiliano Pinto Added MaxScale To Backends routines - * 01/07/2013 Massimiliano Pinto Put Log Manager example code behind SS_DEBUG macros. - * 03/07/2013 Massimiliano Pinto Added delayq for incoming data before mysql connection - * 04/07/2013 Massimiliano Pinto Added asynchronous MySQL protocol connection to backend - * 05/07/2013 Massimiliano Pinto Added closeSession if backend auth fails - * 12/07/2013 Massimiliano Pinto Added Mysql Change User via dcb->func.auth() - * 15/07/2013 Massimiliano Pinto Added Mysql session change via dcb->func.session() - * 17/07/2013 Massimiliano Pinto Added dcb->command update from gwbuf->command for proper routing - * server replies to client via router->clientReply - * 04/09/2013 Massimiliano Pinto Added dcb->session and dcb->session->client checks for NULL - * 12/09/2013 Massimiliano Pinto Added checks in gw_read_backend_event() for gw_read_backend_handshake - * 27/09/2013 Massimiliano Pinto Changed in gw_read_backend_event the check for dcb_read(), - * now is if rc less than 0 - * 24/10/2014 Massimiliano Pinto Added Mysql user@host @db authentication support - * 10/11/2014 Massimiliano Pinto Client charset is passed to backend - * 19/06/2015 Martin Brampton Persistent connection handling - * 07/10/2015 Martin Brampton Remove calls to dcb_close - should be done by routers - * 27/10/2015 Martin Brampton Test for RCAP_TYPE_NO_RSESSION before calling clientReply - * 23/05/2016 Martin Brampton Provide for backend SSL - * */ static int gw_create_backend_connection(DCB *backend, SERVER *server, MXS_SESSION *in_session); @@ -68,7 +44,6 @@ extern char* create_auth_failed_msg(GWBUF* readbuf, char* hostaddr, uint8_t* sha static bool sescmd_response_complete(DCB* dcb); static void gw_reply_on_error(DCB *dcb, mxs_auth_state_t state); static int gw_read_and_write(DCB *dcb); -static int gw_decode_mysql_server_handshake(MySQLProtocol *conn, uint8_t *payload); static int gw_do_connect_to_backend(char *host, int port, int *fd); static void inline close_socket(int socket); static GWBUF *gw_create_change_user_packet(MYSQL_session* mses, diff --git a/server/modules/protocol/MySQL/mysql_common.c b/server/modules/protocol/MySQL/mysql_common.c index f61a83623..d0b67c604 100644 --- a/server/modules/protocol/MySQL/mysql_common.c +++ b/server/modules/protocol/MySQL/mysql_common.c @@ -13,33 +13,6 @@ /* * 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 - * 12/09/2013 Massimiliano Pinto Added checks in gw_decode_mysql_server_handshake and - * gw_read_backend_handshake - * 10/02/2014 Massimiliano Pinto Added MySQL Authentication with user@host - * 10/09/2014 Massimiliano Pinto Added MySQL Authentication option enabling localhost - * match with any host (wildcard %) - * Backend server configuration may differ so default is 0, - * don't match and an explicit - * localhost entry should be added for the selected user - * in the backends. - * Setting to 1 allow localhost (127.0.0.1 or socket) to - * match the any host grant via - * user@% - * 29/09/2014 Massimiliano Pinto Added Mysql user@host authentication with wildcard in IPv4 hosts: - * x.y.z.%, x.y.%.%, x.%.%.% - * 03/10/2014 Massimiliano Pinto Added netmask for wildcard in IPv4 hosts. - * 24/10/2014 Massimiliano Pinto Added Mysql user@host @db authentication support - * 10/11/2014 Massimiliano Pinto Charset at connect is passed to backend during authentication - * 07/07/2015 Martin Brampton Fix problem recognising null password - * 07/02/2016 Martin Brampton Remove authentication functions to mysql_auth.c - * 31/05/2016 Martin Brampton Add mysql_create_standard_error function - * */ #include @@ -1137,18 +1110,20 @@ int mxs_mysql_send_ok(DCB *dcb, int sequence, uint8_t affected_rows, const char* * Otherwise, the packet size is computed, based on the minimum size and * increased by the optional or variable elements. * - * @param conn The MySQLProtocol structure for the connection - * @param user Name of the user seeking to connect - * @param passwd Password for the user seeking to connect - * @param dbname Name of the database to be made default, if any + * @param with_ssl SSL is used + * @param ssl_established SSL is established + * @param user Name of the user seeking to connect + * @param passwd Password for the user seeking to connect + * @param dbname Name of the database to be made default, if any + * * @return The length of the response packet */ -static int -response_length(MySQLProtocol *conn, char *user, uint8_t *passwd, char *dbname, const char *auth_module) +static int response_length(bool with_ssl, bool ssl_established, char *user, uint8_t *passwd, + char *dbname, const char *auth_module) { long bytes; - if (conn->owner_dcb->server->server_ssl && conn->owner_dcb->ssl_state != SSL_ESTABLISHED) + if (with_ssl && !ssl_established) { return MYSQL_AUTH_PACKET_BASE_SIZE; } @@ -1243,14 +1218,14 @@ load_hashed_password(uint8_t *scramble, uint8_t *payload, uint8_t *passwd) * @note Capability bits are defined in maxscale/protocol/mysql.h */ static uint32_t -create_capabilities(MySQLProtocol *conn, bool db_specified, bool compress) +create_capabilities(MySQLProtocol *conn, bool with_ssl, bool db_specified, bool compress) { uint32_t final_capabilities; /** Copy client's flags to backend but with the known capabilities mask */ final_capabilities = (conn->client_capabilities & (uint32_t)GW_MYSQL_CAPABILITIES_CLIENT); - if (conn->owner_dcb->server->server_ssl) + if (with_ssl) { final_capabilities |= (uint32_t)GW_MYSQL_CAPABILITIES_SSL; /* Unclear whether we should include this */ @@ -1259,12 +1234,10 @@ create_capabilities(MySQLProtocol *conn, bool db_specified, bool compress) } /* Compression is not currently supported */ + ss_dassert(!compress); if (compress) { final_capabilities |= (uint32_t)GW_MYSQL_CAPABILITIES_COMPRESS; -#ifdef DEBUG_MYSQL_CONN - fprintf(stderr, ">>>> Backend Connection with compression\n"); -#endif } if (db_specified) @@ -1283,35 +1256,21 @@ create_capabilities(MySQLProtocol *conn, bool db_specified, bool compress) return final_capabilities; } -/** - * Write MySQL authentication packet to backend server - * - * @param dcb Backend DCB - * @return True on success, false on failure - */ -mxs_auth_state_t gw_send_backend_auth(DCB *dcb) +GWBUF* gw_generate_auth_response(MXS_SESSION* session, MySQLProtocol *conn, + bool with_ssl, bool ssl_established) { - MYSQL_session local_session; - gw_get_shared_session_auth_info(dcb, &local_session); + MYSQL_session client; + gw_get_shared_session_auth_info(session->client_dcb, &client); uint8_t client_capabilities[4] = {0, 0, 0, 0}; - uint8_t *curr_passwd = memcmp(local_session.client_sha1, null_client_sha1, MYSQL_SCRAMBLE_LEN) ? - local_session.client_sha1 : NULL; + uint8_t *curr_passwd = NULL; - /** - * If session is stopping or has failed return with error. - */ - if (dcb->session == NULL || - (dcb->session->state != SESSION_STATE_READY && - dcb->session->state != SESSION_STATE_ROUTER_READY) || - (dcb->server->server_ssl && - dcb->ssl_state == SSL_HANDSHAKE_FAILED)) + if (memcmp(client.client_sha1, null_client_sha1, MYSQL_SCRAMBLE_LEN) != 0) { - return MXS_AUTH_STATE_FAILED; + curr_passwd = client.client_sha1; } - MySQLProtocol *conn = (MySQLProtocol*)dcb->protocol; - uint32_t capabilities = create_capabilities(conn, (local_session.db && strlen(local_session.db)), false); + uint32_t capabilities = create_capabilities(conn, with_ssl, client.db[0], false); gw_mysql_set_byte4(client_capabilities, capabilities); /** @@ -1319,10 +1278,10 @@ mxs_auth_state_t gw_send_backend_auth(DCB *dcb) * different authentication mechanism, it will send an AuthSwitchRequest * packet. */ - const char* auth_plugin_name = DEFAULT_MYSQL_AUTH_PLUGIN; + const char* auth_plugin_name = DEFAULT_MYSQL_AUTH_PLUGIN; - long bytes = response_length(conn, local_session.user, curr_passwd, - local_session.db, auth_plugin_name); + long bytes = response_length(with_ssl, ssl_established, client.user, + curr_passwd, client.db, auth_plugin_name); // allocating the GWBUF GWBUF *buffer = gwbuf_alloc(bytes); @@ -1335,7 +1294,7 @@ mxs_auth_state_t gw_send_backend_auth(DCB *dcb) gw_mysql_set_byte3(payload, (bytes - 4)); // set packet # = 1 - payload[3] = (SSL_ESTABLISHED == dcb->ssl_state) ? '\x02' : '\x01'; + payload[3] = ssl_established ? '\x02' : '\x01'; payload += 4; // set client capabilities @@ -1358,53 +1317,79 @@ mxs_auth_state_t gw_send_backend_auth(DCB *dcb) memcpy(payload, &conn->extra_capabilities, sizeof(conn->extra_capabilities)); payload += 4; - if (dcb->server->server_ssl && dcb->ssl_state != SSL_ESTABLISHED) + if (!with_ssl || ssl_established) { - if (dcb_write(dcb, buffer) && dcb_connect_SSL(dcb) >= 0) + // 4 + 4 + 4 + 1 + 23 = 36, this includes the 4 bytes packet header + memcpy(payload, client.user, strlen(client.user)); + payload += strlen(client.user); + payload++; + + if (curr_passwd) { - return MXS_AUTH_STATE_CONNECTED; + payload = load_hashed_password(conn->scramble, payload, curr_passwd); + } + else + { + payload++; } - return MXS_AUTH_STATE_FAILED; + // if the db is not NULL append it + if (client.db[0]) + { + memcpy(payload, client.db, strlen(client.db)); + payload += strlen(client.db); + payload++; + } + + memcpy(payload, auth_plugin_name, strlen(auth_plugin_name)); + } - // 4 + 4 + 4 + 1 + 23 = 36, this includes the 4 bytes packet header - memcpy(payload, local_session.user, strlen(local_session.user)); - payload += strlen(local_session.user); - payload++; - - if (curr_passwd != NULL) - { - payload = load_hashed_password(conn->scramble, payload, curr_passwd); - } - else - { - payload++; - } - - // if the db is not NULL append it - if (local_session.db[0]) - { - memcpy(payload, local_session.db, strlen(local_session.db)); - payload += strlen(local_session.db); - payload++; - } - - memcpy(payload, auth_plugin_name, strlen(auth_plugin_name)); - - return dcb_write(dcb, buffer) ? MXS_AUTH_STATE_RESPONSE_SENT : MXS_AUTH_STATE_FAILED; + return 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 + * Write MySQL authentication packet to backend server * + * @param dcb Backend DCB + * @return Authentication state after sending handshake response */ -static int -gw_decode_mysql_server_handshake(MySQLProtocol *conn, uint8_t *payload) +mxs_auth_state_t gw_send_backend_auth(DCB *dcb) +{ + mxs_auth_state_t rval = MXS_AUTH_STATE_FAILED; + + if (dcb->session == NULL || + (dcb->session->state != SESSION_STATE_READY && + dcb->session->state != SESSION_STATE_ROUTER_READY) || + (dcb->server->server_ssl && + dcb->ssl_state == SSL_HANDSHAKE_FAILED)) + { + return rval; + } + + bool with_ssl = dcb->server->server_ssl; + bool ssl_established = dcb->ssl_state == SSL_ESTABLISHED; + + GWBUF* buffer = gw_generate_auth_response(dcb->session, dcb->protocol, + with_ssl, ssl_established); + ss_dassert(buffer); + + if (with_ssl) + { + if (dcb_write(dcb, buffer) && dcb_connect_SSL(dcb) >= 0) + { + rval = MXS_AUTH_STATE_CONNECTED; + } + } + else if (dcb_write(dcb, buffer)) + { + rval = MXS_AUTH_STATE_RESPONSE_SENT; + } + + return rval; +} + +int gw_decode_mysql_server_handshake(MySQLProtocol *conn, uint8_t *payload) { uint8_t *server_version_end = NULL; uint16_t mysql_server_capabilities_one = 0;