From 971d1d5de2a32de7304cc64b9fbb3d14d3e5e743 Mon Sep 17 00:00:00 2001 From: counterpoint Date: Wed, 3 Feb 2016 08:54:55 +0000 Subject: [PATCH] Redesign logic of SSL connections to work with status in client DCB instead of in the protocol. This eases the way to moving SSL logic out of a specific protocol (currently MySQL) so as to be available across any protocol. Also, some simplification. --- server/core/dcb.c | 30 ++- server/include/dcb.h | 13 + .../include/mysql_client_server_protocol.h | 66 ++--- server/modules/protocol/mysql_client.c | 230 +++--------------- 4 files changed, 106 insertions(+), 233 deletions(-) diff --git a/server/core/dcb.c b/server/core/dcb.c index c0b6caed6..39754fd0a 100644 --- a/server/core/dcb.c +++ b/server/core/dcb.c @@ -223,6 +223,9 @@ dcb_alloc(dcb_role_t role) newdcb->callbacks = NULL; newdcb->data = NULL; + newdcb->listen_ssl = NULL; + newdcb->ssl_state = SSL_HANDSHAKE_UNKNOWN; + newdcb->remote = NULL; newdcb->user = NULL; newdcb->flags = 0; @@ -280,6 +283,7 @@ dcb_clone(DCB *orig) clonedcb->state = orig->state; clonedcb->data = orig->data; clonedcb->listen_ssl = orig->listen_ssl; + clonedcb->ssl_state = orig->ssl_state; if (orig->remote) { clonedcb->remote = strdup(orig->remote); @@ -2863,42 +2867,56 @@ int dcb_create_SSL(DCB* dcb) int dcb_accept_SSL(DCB* dcb) { int ssl_rval; + char *remote; + char *user; + + if (dcb->ssl == NULL && dcb_create_SSL(dcb) != 0) + { + return -1; + } + + remote = dcb->remote ? dcb->remote : ""; + user = dcb->user ? dcb->user : ""; ssl_rval = SSL_accept(dcb->ssl); + switch (SSL_get_error(dcb->ssl, ssl_rval)) { case SSL_ERROR_NONE: - MXS_DEBUG("SSL_accept done for %s", dcb->remote); + MXS_DEBUG("SSL_accept done for %s@%s", user, remote); + dcb->ssl_state = SSL_HANDSHAKE_DONE; return 1; break; case SSL_ERROR_WANT_READ: - MXS_DEBUG("SSL_accept ongoing want read for %s", dcb->remote); + MXS_DEBUG("SSL_accept ongoing want read for %s@%s", user, remote); return 0; break; case SSL_ERROR_WANT_WRITE: - MXS_DEBUG("SSL_accept ongoing want write for %s", dcb->remote); + MXS_DEBUG("SSL_accept ongoing want write for %s@%s", user, remote); return 0; break; case SSL_ERROR_ZERO_RETURN: - MXS_DEBUG("SSL error, shut down cleanly during SSL accept %s", dcb->remote); + MXS_DEBUG("SSL error, shut down cleanly during SSL accept %s@%s", user, remote); dcb_log_errors_SSL(dcb, __func__, 0); poll_fake_hangup_event(dcb); return 0; break; case SSL_ERROR_SYSCALL: - MXS_DEBUG("SSL connection SSL_ERROR_SYSCALL error during accept %s", dcb->remote); + MXS_DEBUG("SSL connection SSL_ERROR_SYSCALL error during accept %s@%s", user, remote); dcb_log_errors_SSL(dcb, __func__, ssl_rval); + dcb->ssl_state = SSL_HANDSHAKE_FAILED; poll_fake_hangup_event(dcb); return -1; break; default: - MXS_DEBUG("SSL connection shut down with error during SSL accept %s", dcb->remote); + MXS_DEBUG("SSL connection shut down with error during SSL accept %s@%s", user, remote); dcb_log_errors_SSL(dcb, __func__, 0); + dcb->ssl_state = SSL_HANDSHAKE_FAILED; poll_fake_hangup_event(dcb); return -1; break; diff --git a/server/include/dcb.h b/server/include/dcb.h index c772e9c98..2818814f6 100644 --- a/server/include/dcb.h +++ b/server/include/dcb.h @@ -59,6 +59,7 @@ struct service; * 23/09/2014 Mark Riddoch New poll processing queue * 19/06/2015 Martin Brampton Provision of persistent connections * 20/01/2016 Martin Brampton Moved GWPROTOCOL to gw_protocol.h + * 01/02/2016 Martin Brampton Added fields for SSL and authentication * * @endverbatim */ @@ -173,6 +174,17 @@ typedef struct dcb_callback struct dcb_callback *next; /*< Next callback for this DCB */ } DCB_CALLBACK; +/** + * State of SSL connection + */ +typedef enum +{ + SSL_HANDSHAKE_UNKNOWN, /*< The DCB has unknown SSL status */ + SSL_HANDSHAKE_REQUIRED, /*< SSL handshake is needed */ + SSL_HANDSHAKE_DONE, /*< The SSL handshake completed OK */ + SSL_ESTABLISHED, /*< The SSL connection is in use */ + SSL_HANDSHAKE_FAILED /*< The SSL handshake failed */ +} SSL_STATE; /** * Descriptor Control Block @@ -195,6 +207,7 @@ typedef struct dcb DCBEVENTQ evq; /**< The event queue for this DCB */ int fd; /**< The descriptor */ dcb_state_t state; /**< Current descriptor state */ + SSL_STATE ssl_state; /**< Current state of SSL if in use */ int flags; /**< DCB flags */ char *remote; /**< Address of remote end */ char *user; /**< User name for connection */ diff --git a/server/modules/include/mysql_client_server_protocol.h b/server/modules/include/mysql_client_server_protocol.h index ffb04cfe0..22fad2b9e 100644 --- a/server/modules/include/mysql_client_server_protocol.h +++ b/server/modules/include/mysql_client_server_protocol.h @@ -96,13 +96,19 @@ struct dcb; #define MYSQL_FAILED_AUTH_SSL 3 typedef enum { - MYSQL_ALLOC, + MYSQL_ALLOC, /* Initial state of protocol auth state */ + /* The following are used only for backend connections */ MYSQL_PENDING_CONNECT, MYSQL_CONNECTED, + /* The following can be used for either client or backend */ + /* The comments have only been checked for client use at present */ MYSQL_AUTH_SENT, - MYSQL_AUTH_RECV, - MYSQL_AUTH_FAILED, + MYSQL_AUTH_RECV, /* This is only ever a transient value */ + MYSQL_AUTH_FAILED, /* Once this is set, the connection */ + /* will be ended, so this is transient */ + /* The following is used only for backend connections */ MYSQL_HANDSHAKE_FAILED, + /* The following are obsolete and will be removed */ MYSQL_AUTH_SSL_REQ, /*< client requested SSL but SSL_accept hasn't beed called */ MYSQL_AUTH_SSL_HANDSHAKE_DONE, /*< SSL handshake has been fully completed */ MYSQL_AUTH_SSL_HANDSHAKE_FAILED, /*< SSL handshake failed for any reason */ @@ -232,38 +238,38 @@ typedef enum mysql_server_cmd { MYSQL_COM_INIT_DB, MYSQL_COM_QUERY, MYSQL_COM_FIELD_LIST, - MYSQL_COM_CREATE_DB, + MYSQL_COM_CREATE_DB, MYSQL_COM_DROP_DB, - MYSQL_COM_REFRESH, - MYSQL_COM_SHUTDOWN, + MYSQL_COM_REFRESH, + MYSQL_COM_SHUTDOWN, MYSQL_COM_STATISTICS, - MYSQL_COM_PROCESS_INFO, - MYSQL_COM_CONNECT, - MYSQL_COM_PROCESS_KILL, - MYSQL_COM_DEBUG, + MYSQL_COM_PROCESS_INFO, + MYSQL_COM_CONNECT, + MYSQL_COM_PROCESS_KILL, + MYSQL_COM_DEBUG, MYSQL_COM_PING, - MYSQL_COM_TIME, - MYSQL_COM_DELAYED_INSERT, - MYSQL_COM_CHANGE_USER, + MYSQL_COM_TIME, + MYSQL_COM_DELAYED_INSERT, + MYSQL_COM_CHANGE_USER, MYSQL_COM_BINLOG_DUMP, - MYSQL_COM_TABLE_DUMP, - MYSQL_COM_CONNECT_OUT, + MYSQL_COM_TABLE_DUMP, + MYSQL_COM_CONNECT_OUT, MYSQL_COM_REGISTER_SLAVE, - MYSQL_COM_STMT_PREPARE, - MYSQL_COM_STMT_EXECUTE, - MYSQL_COM_STMT_SEND_LONG_DATA, + MYSQL_COM_STMT_PREPARE, + MYSQL_COM_STMT_EXECUTE, + MYSQL_COM_STMT_SEND_LONG_DATA, MYSQL_COM_STMT_CLOSE, - MYSQL_COM_STMT_RESET, - MYSQL_COM_SET_OPTION, - MYSQL_COM_STMT_FETCH, + MYSQL_COM_STMT_RESET, + MYSQL_COM_SET_OPTION, + MYSQL_COM_STMT_FETCH, MYSQL_COM_DAEMON, MYSQL_COM_END /*< Must be the last */ } mysql_server_cmd_t; -/** +/** * List of server commands, and number of response packets are stored here. - * server_command_t is used in MySQLProtocol structure, so for each DCB there is + * server_command_t is used in MySQLProtocol structure, so for each DCB there is * one MySQLProtocol and one server command list. */ typedef struct server_command_st { @@ -275,8 +281,8 @@ typedef struct server_command_st { /** * MySQL Protocol specific state data. - * - * Protocol carries information from client side to backend side, such as + * + * Protocol carries information from client side to backend side, such as * MySQL session command information and history of earlier session commands. */ typedef struct { @@ -286,7 +292,7 @@ typedef struct { int fd; /*< The socket descriptor */ struct dcb *owner_dcb; /*< The DCB of the socket * we are running on */ - SPINLOCK protocol_lock; + SPINLOCK protocol_lock; server_command_t protocol_command; /*< session command list */ server_command_t* protocol_cmd_history; /*< session command history */ mysql_auth_state_t protocol_auth_state; /*< Authentication status */ @@ -346,7 +352,7 @@ int mysql_send_custom_error ( const char* mysql_message); GWBUF* mysql_create_custom_error( - int packet_number, + int packet_number, int affected_rows, const char* msg); @@ -411,9 +417,9 @@ void protocol_archive_srv_command(MySQLProtocol* p); void init_response_status ( - GWBUF* buf, - mysql_server_cmd_t cmd, - int* npackets, + GWBUF* buf, + mysql_server_cmd_t cmd, + int* npackets, ssize_t* nbytes); #endif /** _MYSQL_PROTOCOL_H */ diff --git a/server/modules/protocol/mysql_client.c b/server/modules/protocol/mysql_client.c index 81440455f..7c8db1914 100644 --- a/server/modules/protocol/mysql_client.c +++ b/server/modules/protocol/mysql_client.c @@ -77,14 +77,12 @@ static int gw_client_close(DCB *dcb); static int gw_client_hangup_event(DCB *dcb); static int mysql_send_ok(DCB *dcb, int packet_number, int in_affected_rows, const char* mysql_message); static int MySQLSendHandshake(DCB* dcb); -static int gw_mysql_do_authentication(DCB *dcb, GWBUF **queue); +static int gw_mysql_do_authentication(DCB *dcb, GWBUF **buf); static int route_by_statement(SESSION *, GWBUF **); extern char* get_username_from_auth(char* ptr, uint8_t* data); extern int check_db_name_after_auth(DCB *, char *, int); extern char* create_auth_fail_str(char *username, char *hostaddr, char *sha1, char *db,int); -static int do_ssl_accept(MySQLProtocol* protocol); - /* * The "module object" for the mysqld client protocol module. */ @@ -485,7 +483,7 @@ static int gw_mysql_do_authentication(DCB *dcb, GWBUF **buf) /** Skip this if the SSL handshake is already done. * If not, start the SSL handshake. */ - if (protocol->protocol_auth_state != MYSQL_AUTH_SSL_HANDSHAKE_DONE) + if (protocol->owner_dcb->ssl_state != SSL_HANDSHAKE_DONE && protocol->owner_dcb->ssl_state != SSL_ESTABLISHED) { ssl = protocol->client_capabilities & GW_MYSQL_CAPABILITIES_SSL; @@ -510,11 +508,13 @@ static int gw_mysql_do_authentication(DCB *dcb, GWBUF **buf) /** Do the SSL Handshake */ /* if (ssl && protocol->owner_dcb->service->ssl_mode != SSL_DISABLED) */ - if (ssl && NULL != protocol->owner_dcb->listen_ssl) + if (NULL != protocol->owner_dcb->listen_ssl) { - protocol->protocol_auth_state = MYSQL_AUTH_SSL_REQ; - - if (do_ssl_accept(protocol) < 0) + if (SSL_HANDSHAKE_UNKNOWN == protocol->owner_dcb->ssl_state) + { + protocol->owner_dcb->ssl_state = SSL_HANDSHAKE_REQUIRED; + } + if (dcb_accept_SSL(protocol->owner_dcb) < 0) { return MYSQL_FAILED_AUTH; } @@ -611,7 +611,7 @@ static int gw_mysql_do_authentication(DCB *dcb, GWBUF **buf) } } - /* on succesful auth set user into dcb field */ + /* on successful auth set user into dcb field */ if (auth_ret == 0) { dcb->user = strdup(client_data->user); @@ -679,13 +679,11 @@ int gw_read_client_event(DCB* dcb) #endif - /** SSL authentication is still going on, we need to call do_ssl_accept + /** SSL authentication is still going on, we need to call dcb_accept_SSL * until it return 1 for success or -1 for error */ - if (protocol->protocol_auth_state == MYSQL_AUTH_SSL_HANDSHAKE_ONGOING || - protocol->protocol_auth_state == MYSQL_AUTH_SSL_REQ) + if (protocol->owner_dcb->ssl_state == SSL_HANDSHAKE_REQUIRED) { - - switch(do_ssl_accept(protocol)) + switch(dcb_accept_SSL(protocol->owner_dcb)) { case 0: return 0; @@ -711,7 +709,7 @@ int gw_read_client_event(DCB* dcb) } } - if (protocol->use_ssl) + if (SSL_HANDSHAKE_DONE == protocol->owner_dcb->ssl_state || SSL_ESTABLISHED == protocol->owner_dcb->ssl_state) { /** SSL handshake is done, communication is now encrypted with SSL */ rc = dcb_read_SSL(dcb, &read_buffer); @@ -837,21 +835,28 @@ int gw_read_client_event(DCB* dcb) { case MYSQL_AUTH_SENT: { - int auth_val; + int auth_val, packet_number; + packet_number = protocol->owner_dcb->listen_ssl ? 3 : 2; auth_val = gw_mysql_do_authentication(dcb, &read_buffer); - if (protocol->protocol_auth_state == MYSQL_AUTH_SSL_REQ || - protocol->protocol_auth_state == MYSQL_AUTH_SSL_HANDSHAKE_ONGOING || - protocol->protocol_auth_state == MYSQL_AUTH_SSL_HANDSHAKE_DONE || - protocol->protocol_auth_state == MYSQL_AUTH_SSL_HANDSHAKE_FAILED) + if (protocol->owner_dcb->ssl_state == SSL_HANDSHAKE_REQUIRED || + protocol->owner_dcb->ssl_state == SSL_HANDSHAKE_DONE || + protocol->owner_dcb->ssl_state == SSL_HANDSHAKE_FAILED) { /** SSL was requested and the handshake is either done or * still ongoing. After the handshake is done, the client * will send another auth packet. */ - gwbuf_free(read_buffer); - read_buffer = NULL; - break; + if (protocol->owner_dcb->ssl_state == SSL_HANDSHAKE_DONE) + { + protocol->owner_dcb->ssl_state = SSL_ESTABLISHED; + } + else + { + gwbuf_free(read_buffer); + read_buffer = NULL; + break; + } } if (auth_val == 0) @@ -875,9 +880,9 @@ int gw_read_client_event(DCB* dcb) protocol->protocol_auth_state = MYSQL_IDLE; /** * Send an AUTH_OK packet to the client, - * packet sequence is # 2 + * packet sequence is # packet_number */ - mysql_send_ok(dcb, 2, 0, NULL); + mysql_send_ok(dcb, packet_number, 0, NULL); } else { @@ -890,7 +895,7 @@ int gw_read_client_event(DCB* dcb) /** Send ERR 1045 to client */ mysql_send_auth_error(dcb, - 2, + packet_number, 0, "failed to create new session"); @@ -912,7 +917,7 @@ int gw_read_client_event(DCB* dcb) snprintf(fail_str, message_len, "Unknown database '%s'", (char*)((MYSQL_session *)dcb->data)->db); - modutil_send_mysql_err_packet(dcb, 2, 0, 1049, "42000", fail_str); + modutil_send_mysql_err_packet(dcb, packet_number, 0, 1049, "42000", fail_str); } else { @@ -921,108 +926,7 @@ int gw_read_client_event(DCB* dcb) dcb->remote, (char*)((MYSQL_session *)dcb->data)->client_sha1, (char*)((MYSQL_session *)dcb->data)->db,auth_val); - modutil_send_mysql_err_packet(dcb, 2, 0, 1045, "28000", fail_str); - } - if (fail_str) - { - free(fail_str); - } - - MXS_DEBUG("%lu [gw_read_client_event] after " - "gw_mysql_do_authentication, fd %d, " - "state = MYSQL_AUTH_FAILED.", - pthread_self(), - protocol->owner_dcb->fd); - /** - * Release MYSQL_session since it is not used anymore. - */ - if (!DCB_IS_CLONE(dcb)) - { - free(dcb->data); - } - dcb->data = NULL; - - dcb_close(dcb); - } - gwbuf_free(read_buffer); - read_buffer = NULL; - } - break; - - case MYSQL_AUTH_SSL_HANDSHAKE_DONE: - { - int auth_val; - - auth_val = gw_mysql_do_authentication(dcb, &read_buffer); - - if (auth_val == 0) - { - SESSION *session; - - protocol->protocol_auth_state = MYSQL_AUTH_RECV; - /** - * Create session, and a router session for it. - * If successful, there will be backend connection(s) - * after this point. - */ - session = session_alloc(dcb->service, dcb); - - if (session != NULL) - { - CHK_SESSION(session); - ss_dassert(session->state != SESSION_STATE_ALLOC && - session->state != SESSION_STATE_DUMMY); - - protocol->protocol_auth_state = MYSQL_IDLE; - /** - * Send an AUTH_OK packet to the client, - * packet sequence is # 2 - */ - mysql_send_ok(dcb, 3, 0, NULL); - } - else - { - protocol->protocol_auth_state = MYSQL_AUTH_FAILED; - MXS_DEBUG("%lu [gw_read_client_event] session " - "creation failed. fd %d, " - "state = MYSQL_AUTH_FAILED.", - pthread_self(), - protocol->owner_dcb->fd); - - /** Send ERR 1045 to client */ - mysql_send_auth_error(dcb, - 3, - 0, - "failed to create new session"); - - dcb_close(dcb); - } - } - else - { - char* fail_str = NULL; - - protocol->protocol_auth_state = MYSQL_AUTH_FAILED; - - if (auth_val == 2) - { - /** Send error 1049 to client */ - int message_len = 25 + MYSQL_DATABASE_MAXLEN; - - fail_str = calloc(1, message_len+1); - snprintf(fail_str, message_len, "Unknown database '%s'", - (char*)((MYSQL_session *)dcb->data)->db); - - modutil_send_mysql_err_packet(dcb, 3, 0, 1049, "42000", fail_str); - } - else - { - /** Send error 1045 to client */ - fail_str = create_auth_fail_str((char *)((MYSQL_session *)dcb->data)->user, - dcb->remote, - (char*)((MYSQL_session *)dcb->data)->client_sha1, - (char*)((MYSQL_session *)dcb->data)->db,auth_val); - modutil_send_mysql_err_packet(dcb, 3, 0, 1045, "28000", fail_str); + modutil_send_mysql_err_packet(dcb, packet_number, 0, 1045, "28000", fail_str); } if (fail_str) { @@ -1875,71 +1779,3 @@ static int route_by_statement(SESSION* session, GWBUF** p_readbuf) return_rc: return rc; } - -/** - * Do the SSL authentication handshake. - * This creates the DCB SSL structure if one has not been created and starts the - * SSL handshake handling. - * @param protocol Protocol to connect with SSL - * @return 1 on success, 0 when the handshake is ongoing or -1 on error - */ -int do_ssl_accept(MySQLProtocol* protocol) -{ - int rval,errnum; - char errbuf[2014]; - DCB* dcb = protocol->owner_dcb; - if (dcb->ssl == NULL) - { - if (dcb_create_SSL(dcb) != 0) - { - return -1; - } - } - - rval = dcb_accept_SSL(dcb); - - switch(rval) - { - case 0: - /** Not all of the data has been read. Go back to the poll - queue and wait for more.*/ - - rval = 0; - MXS_INFO("SSL_accept ongoing for %s@%s", - protocol->owner_dcb->user, - protocol->owner_dcb->remote); - return 0; - break; - case 1: - spinlock_acquire(&protocol->protocol_lock); - protocol->protocol_auth_state = MYSQL_AUTH_SSL_HANDSHAKE_DONE; - protocol->use_ssl = true; - spinlock_release(&protocol->protocol_lock); - - rval = 1; - - MXS_INFO("SSL_accept done for %s@%s", - protocol->owner_dcb->user, - protocol->owner_dcb->remote); - break; - - case -1: - spinlock_acquire(&protocol->protocol_lock); - protocol->protocol_auth_state = MYSQL_AUTH_SSL_HANDSHAKE_FAILED; - spinlock_release(&protocol->protocol_lock); - rval = -1; - MXS_ERROR("Fatal error in SSL_accept for %s", - protocol->owner_dcb->remote); - break; - - default: - MXS_ERROR("Fatal error in SSL_accept, returned value was %d.", rval); - break; - } -#ifdef SS_DEBUG - MXS_DEBUG("[do_ssl_accept] Protocol state: %s", - gw_mysql_protocol_state2string(protocol->protocol_auth_state)); -#endif - - return rval; -}