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.

This commit is contained in:
counterpoint 2016-02-03 08:54:55 +00:00
parent 05fa498dc0
commit 971d1d5de2
4 changed files with 106 additions and 233 deletions

View File

@ -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;

View File

@ -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 */

View File

@ -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 */

View File

@ -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;
}