From 0f814d3e73c8e57f1f78ef91a8999bf5672f099a Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Fri, 29 May 2015 13:00:37 +0300 Subject: [PATCH] Added SSL write and read functions. --- server/core/config.c | 19 +- server/core/dcb.c | 529 +++++++++++++++++- server/include/dcb.h | 8 +- .../include/mysql_client_server_protocol.h | 1 + server/modules/protocol/mysql_client.c | 245 +++++--- server/modules/protocol/mysql_common.c | 5 +- 6 files changed, 726 insertions(+), 81 deletions(-) diff --git a/server/core/config.c b/server/core/config.c index 12d4c099e..640437270 100644 --- a/server/core/config.c +++ b/server/core/config.c @@ -452,11 +452,17 @@ hashtable_memory_fns(monitorhash,strdup,NULL,free,NULL); if(ssl) { if(ssl_cert == NULL) - skygw_log_write(LE,"Error: Server certificate missing for service '%s'.",obj->object); + skygw_log_write(LE,"Error: Server certificate missing for service '%s'." + "Please provide the path to the server certificate by adding the ssl_cert= parameter", + obj->object); if(ssl_ca_cert == NULL) - skygw_log_write(LE,"Error: CA Certificate missing for service '%s'.",obj->object); + skygw_log_write(LE,"Error: CA Certificate missing for service '%s'." + "Please provide the path to the certificate authority certificate by adding the ssl_ca_cert= parameter", + obj->object); if(ssl_key == NULL) - skygw_log_write(LE,"Error: Server private key missing for service '%s'.",obj->object); + skygw_log_write(LE,"Error: Server private key missing for service '%s'. " + "Please provide the path to the server certificate key by adding the ssl_key= parameter" + ,obj->object); if(ssl_ca_cert != NULL && ssl_cert != NULL && ssl_key != NULL) { @@ -470,6 +476,13 @@ hashtable_memory_fns(monitorhash,strdup,NULL,free,NULL); serviceSetCertificates(obj->element,ssl_cert,ssl_key,ssl_ca_cert); } } + else + { + /** If SSL was configured wrong, the + * service needs to fail.*/ + skygw_log_write_flush(LE,"Error: Missing SSL certificate paths found in the configuration. " + "This service will not use SSL."); + } } diff --git a/server/core/dcb.c b/server/core/dcb.c index 6717aea41..ba2028de2 100644 --- a/server/core/dcb.c +++ b/server/core/dcb.c @@ -49,6 +49,7 @@ * backend * 07/05/2014 Mark Riddoch Addition of callback mechanism * 20/06/2014 Mark Riddoch Addition of dcb_clone + * 29/05/2015 Markus Makela Addition of dcb_write_SSL * * @endverbatim */ @@ -880,6 +881,152 @@ return_n: return n; } + +/** + * General purpose read routine to read data from a socket in the + * Descriptor Control Block and append it to a linked list of buffers. + * The list may be empty, in which case *head == NULL + * + * @param dcb The DCB to read from + * @param head Pointer to linked list to append data to + * @return -1 on error, otherwise the number of read bytes on the last + * iteration of while loop. 0 is returned if no data available. + */ +int dcb_read_SSL( + DCB *dcb, + SSL* ssl, + GWBUF **head) +{ + GWBUF *buffer = NULL; + int b; + int rc; + int n; + int nread = 0; + + CHK_DCB(dcb); + + if (dcb->fd <= 0) + { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Read failed, dcb is %s.", + dcb->fd == DCBFD_CLOSED ? "closed" : "cloned, not readable"))); + n = 0; + goto return_n; + } + + while (true) + { + int bufsize; + + rc = ioctl(dcb->fd, FIONREAD, &b); + + if (rc == -1) + { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : ioctl FIONREAD for dcb %p in " + "state %s fd %d failed due error %d, %s.", + dcb, + STRDCBSTATE(dcb->state), + dcb->fd, + errno, + strerror(errno)))); + n = -1; + goto return_n; + } + + if (b == 0 && nread == 0) + { + /** Handle closed client socket */ + if (dcb_isclient(dcb)) + { + char c; + int l_errno = 0; + int r = -1; + + /* try to read 1 byte, without consuming the socket buffer */ + r = recv(dcb->fd, &c, sizeof(char), MSG_PEEK); + l_errno = errno; + + if (r <= 0 && + l_errno != EAGAIN && + l_errno != EWOULDBLOCK && + l_errno != 0) + { + n = -1; + goto return_n; + } + } + n = 0; + goto return_n; + } + else if (b == 0) + { + n = 0; + goto return_n; + } + + dcb->last_read = hkheartbeat; + + bufsize = MIN(b, MAX_BUFFER_SIZE); + + if ((buffer = gwbuf_alloc(bufsize)) == NULL) + { + /*< + * This is a fatal error which should cause shutdown. + * Todo shutdown if memory allocation fails. + */ + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Failed to allocate read buffer " + "for dcb %p fd %d, due %d, %s.", + dcb, + dcb->fd, + errno, + strerror(errno)))); + + n = -1; + goto return_n; + } + GW_NOINTR_CALL(n = SSL_read(ssl, GWBUF_DATA(buffer), bufsize); + dcb->stats.n_reads++); + + if (n <= 0) + { + int ssl_errno = ERR_get_error(); + if(ssl_errno != SSL_ERROR_WANT_READ) + { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Read failed, dcb %p in state " + "%s fd %d: %s.", + dcb, + STRDCBSTATE(dcb->state), + dcb->fd, + ERR_error_string(ssl_errno,NULL)))); + + gwbuf_free(buffer); + goto return_n; + } + } + nread += n; + + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, + "%lu [dcb_read] Read %d bytes from dcb %p in state %s " + "fd %d.", + pthread_self(), + n, + dcb, + STRDCBSTATE(dcb->state), + dcb->fd))); + /*< Append read data to the gwbuf */ + *head = gwbuf_append(*head, buffer); + } /*< while (true) */ +return_n: + return n; +} /** * General purpose routine to write to a DCB * @@ -905,11 +1052,11 @@ int below_water; return 0; } /** - * SESSION_STATE_STOPPING means that one of the backends is closing - * the router session. Some backends may have not completed + * SESSION_STATE_STOPPING means that one of the backends is closing + * the router session. Some backends may have not completed * authentication yet and thus they have no information about router * being closed. Session state is changed to SESSION_STATE_STOPPING - * before router's closeSession is called and that tells that DCB may + * before router's closeSession is called and that tells that DCB may * still be writable. */ if (queue == NULL || @@ -932,9 +1079,9 @@ int below_water; //ss_dassert(false); return 0; } - + spinlock_acquire(&dcb->writeqlock); - + if (dcb->writeq != NULL) { /* @@ -949,7 +1096,7 @@ int below_water; if (queue) { int qlen; - + qlen = gwbuf_length(queue); atomic_add(&dcb->writeqlen, qlen); dcb->writeq = gwbuf_append(dcb->writeq, queue); @@ -998,7 +1145,7 @@ int below_water; w = gw_write(dcb, GWBUF_DATA(queue), qlen); dcb->stats.n_writes++; ); - + if (w < 0) { saved_errno = errno; @@ -1006,7 +1153,7 @@ int below_water; if (LOG_IS_ENABLED(LOGFILE_DEBUG)) { - if (saved_errno == EPIPE) + if (saved_errno == EPIPE) { LOGIF(LD, (skygw_log_write( LOGFILE_DEBUG, @@ -1019,9 +1166,9 @@ int below_water; dcb->fd, saved_errno, strerror(saved_errno)))); - } + } } - + if (LOG_IS_ENABLED(LOGFILE_ERROR)) { if (saved_errno != EPIPE && @@ -1066,7 +1213,7 @@ int below_water; if (queue) { int qlen; - + qlen = gwbuf_length(queue); atomic_add(&dcb->writeqlen, qlen); dcb->stats.n_buffered++; @@ -1086,7 +1233,7 @@ int below_water; if (GWBUF_IS_TYPE_MYSQL(queue)) { uint8_t* data = GWBUF_DATA(queue); - + if (data[4] == 0x01) { dolog = false; @@ -1116,6 +1263,262 @@ int below_water; return 1; } +/** + * General purpose routine to write to an SSL enabled DCB + * + * @param dcb The DCB of the client + * @param ssl The SSL structure for this DCB + * @param queue Queue of buffers to write + * @return 0 on failure, 1 on success + */ +int +dcb_write_SSL(DCB *dcb, SSL* ssl, GWBUF *queue) +{ + int w; + int saved_errno = 0; + int below_water; + + below_water = (dcb->high_water && dcb->writeqlen < dcb->high_water) ? 1 : 0; + ss_dassert(queue != NULL); + + if (dcb->fd <= 0) + { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Write failed, dcb is %s.", + dcb->fd == DCBFD_CLOSED ? "closed" : "cloned, not writable"))); + return 0; + } + + /** + * SESSION_STATE_STOPPING means that one of the backends is closing + * the router session. Some backends may have not completed + * authentication yet and thus they have no information about router + * being closed. Session state is changed to SESSION_STATE_STOPPING + * before router's closeSession is called and that tells that DCB may + * still be writable. + */ + if (queue == NULL || + (dcb->state != DCB_STATE_ALLOC && + dcb->state != DCB_STATE_POLLING && + dcb->state != DCB_STATE_LISTENING && + dcb->state != DCB_STATE_NOPOLLING && + (dcb->session == NULL || + dcb->session->state != SESSION_STATE_STOPPING))) + { + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, + "%lu [dcb_write] Write aborted to dcb %p because " + "it is in state %s", + pthread_self(), + dcb->stats.n_buffered, + dcb, + STRDCBSTATE(dcb->state), + dcb->fd))); + //ss_dassert(false); + return 0; + } + + spinlock_acquire(&dcb->writeqlock); + + if (dcb->writeq != NULL) + { + /* + * We have some queued data, so add our data to + * the write queue and return. + * The assumption is that there will be an EPOLLOUT + * event to drain what is already queued. We are protected + * by the spinlock, which will also be acquired by the + * the routine that drains the queue data, so we should + * not have a race condition on the event. + */ + if (queue) + { + int qlen; + + qlen = gwbuf_length(queue); + atomic_add(&dcb->writeqlen, qlen); + dcb->writeq = gwbuf_append(dcb->writeq, queue); + dcb->stats.n_buffered++; + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, + "%lu [dcb_write] Append to writequeue. %d writes " + "buffered for dcb %p in state %s fd %d", + pthread_self(), + dcb->stats.n_buffered, + dcb, + STRDCBSTATE(dcb->state), + dcb->fd))); + } + } + else + { + /* + * Loop over the buffer chain that has been passed to us + * from the reading side. + * Send as much of the data in that chain as possible and + * add any balance to the write queue. + */ + while (queue != NULL) + { + int qlen; +#if defined(FAKE_CODE) + if (dcb->dcb_role == DCB_ROLE_REQUEST_HANDLER && + dcb->session != NULL) + { + if (dcb_isclient(dcb) && fail_next_client_fd) { + dcb_fake_write_errno[dcb->fd] = 32; + dcb_fake_write_ev[dcb->fd] = 29; + fail_next_client_fd = false; + } else if (!dcb_isclient(dcb) && + fail_next_backend_fd) + { + dcb_fake_write_errno[dcb->fd] = 32; + dcb_fake_write_ev[dcb->fd] = 29; + fail_next_backend_fd = false; + } + } +#endif /* FAKE_CODE */ + qlen = GWBUF_LENGTH(queue); + GW_NOINTR_CALL( + w = gw_write_SSL(ssl, GWBUF_DATA(queue), qlen); + dcb->stats.n_writes++; + ); + + if (w < 0) + { + int ssl_errno = ERR_get_error(); + + if (LOG_IS_ENABLED(LOGFILE_DEBUG)) + { + switch(ssl_errno) + { + case SSL_ERROR_WANT_READ: + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, + "%lu [dcb_write] Write to dcb " + "%p in state %s fd %d failed " + "due error SSL_ERROR_WANT_READ", + pthread_self(), + dcb, + STRDCBSTATE(dcb->state), + dcb->fd))); + break; + case SSL_ERROR_WANT_WRITE: + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, + "%lu [dcb_write] Write to dcb " + "%p in state %s fd %d failed " + "due error SSL_ERROR_WANT_WRITE", + pthread_self(), + dcb, + STRDCBSTATE(dcb->state), + dcb->fd))); + break; + default: + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, + "%lu [dcb_write] Write to dcb " + "%p in state %s fd %d failed " + "due error %d", + pthread_self(), + dcb, + STRDCBSTATE(dcb->state), + dcb->fd,ssl_errno))); + } + } + + if (LOG_IS_ENABLED(LOGFILE_ERROR)) + { + if (ssl_errno != 0) + { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Write to dcb %p in " + "state %s fd %d failed due " + "SSL error %d", + dcb, + STRDCBSTATE(dcb->state), + dcb->fd, + ssl_errno))); + } + } + break; + } + /* + * Pull the number of bytes we have written from + * queue with have. + */ + queue = gwbuf_consume(queue, w); + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, + "%lu [dcb_write] Wrote %d Bytes to dcb %p in " + "state %s fd %d", + pthread_self(), + w, + dcb, + STRDCBSTATE(dcb->state), + dcb->fd))); + } /*< while (queue != NULL) */ + /*< + * What wasn't successfully written is stored to write queue + * for suspended write. + */ + dcb->writeq = queue; + + if (queue) + { + int qlen; + + qlen = gwbuf_length(queue); + atomic_add(&dcb->writeqlen, qlen); + dcb->stats.n_buffered++; + } + } /* if (dcb->writeq) */ + + if (saved_errno != 0 && + queue != NULL && + saved_errno != EAGAIN && + saved_errno != EWOULDBLOCK) + { + bool dolog = true; + + /** + * Do not log if writing COM_QUIT to backend failed. + */ + if (GWBUF_IS_TYPE_MYSQL(queue)) + { + uint8_t* data = GWBUF_DATA(queue); + + if (data[4] == 0x01) + { + dolog = false; + } + } + if (dolog) + { + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, + "%lu [dcb_write] Writing to %s socket failed due %d, %s.", + pthread_self(), + dcb_isclient(dcb) ? "client" : "backend server", + saved_errno, + strerror(saved_errno)))); + } + spinlock_release(&dcb->writeqlock); + return 0; + } + spinlock_release(&dcb->writeqlock); + + if (dcb->high_water && dcb->writeqlen > dcb->high_water && below_water) + { + atomic_add(&dcb->stats.n_high_water, 1); + dcb_call_callback(dcb, DCB_REASON_HIGH_WATER); + } + + return 1; +} + /** * Drain the write queue of a DCB. This is called as part of the EPOLLOUT handling * of a socket and will try to send any buffered data from the write queue @@ -1208,6 +1611,85 @@ int above_water; return n; } + +/** + * Drain the write queue of a DCB. This is called as part of the EPOLLOUT handling + * of a socket and will try to send any buffered data from the write queue + * up until the point the write would block. + * + * @param dcb DCB to drain the write queue of + * @return The number of bytes written + */ +int +dcb_drain_writeq_SSL(DCB *dcb, SSL* ssl) +{ + int n = 0; + int w; + int saved_errno = 0; + int above_water; + + above_water = (dcb->low_water && dcb->writeqlen > dcb->low_water) ? 1 : 0; + + spinlock_acquire(&dcb->writeqlock); + + if (dcb->writeq) + { + int len; + /* + * Loop over the buffer chain in the pending writeq + * Send as much of the data in that chain as possible and + * leave any balance on the write queue. + */ + while (dcb->writeq != NULL) + { + len = GWBUF_LENGTH(dcb->writeq); + GW_NOINTR_CALL(w = gw_write_SSL(ssl, GWBUF_DATA(dcb->writeq), len);); + + if (w < 0) + { + int ssl_errno = ERR_get_error(); + + if(ssl_errno == SSL_ERROR_WANT_WRITE || + ssl_errno == SSL_ERROR_WANT_ACCEPT || + ssl_errno == SSL_ERROR_WANT_READ) + { + break; + } + + skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Write to dcb %p " + "in state %s fd %d failed: %s", + dcb, + STRDCBSTATE(dcb->state), + dcb->fd, + ERR_error_string(ssl_errno,NULL)); + break; + } + /* + * Pull the number of bytes we have written from + * queue with have. + */ + dcb->writeq = gwbuf_consume(dcb->writeq, w); + n += w; + } + } + spinlock_release(&dcb->writeqlock); + atomic_add(&dcb->writeqlen, -n); + + /* The write queue has drained, potentially need to call a callback function */ + if (dcb->writeq == NULL) + dcb_call_callback(dcb, DCB_REASON_DRAINED); + + if (above_water && dcb->writeqlen < dcb->low_water) + { + atomic_add(&dcb->stats.n_low_water, 1); + dcb_call_callback(dcb, DCB_REASON_LOW_WATER); + } + + return n; +} + /** * Removes dcb from poll set, and adds it to zombies list. As a consequense, * dcb first moves to DCB_STATE_NOPOLLING, and then to DCB_STATE_ZOMBIE state. @@ -1792,6 +2274,29 @@ static bool dcb_set_state_nomutex( return succp; } +/** + * Write data to a DCB + * + * @param ssl The SSL to write the buffer to + * @param buf Buffer to write + * @param nbytes Number of bytes to write + * @return Number of written bytes + */ +int +gw_write_SSL(SSL* ssl, const void *buf, size_t nbytes) +{ + int w = 0; + int fd = SSL_get_fd(ssl); + + if (fd > 0) + { + w = SSL_write(ssl, buf, nbytes); + } + return w; +} + + + /** * Write data to a DCB * diff --git a/server/include/dcb.h b/server/include/dcb.h index 7eedfbec3..38ebdc299 100644 --- a/server/include/dcb.h +++ b/server/include/dcb.h @@ -23,6 +23,9 @@ #include #include #include +#include +#include +#include #define ERRHANDLE @@ -337,7 +340,10 @@ bool dcb_set_state(DCB* dcb, dcb_state_t new_state, dcb_state_t* old_state); void dcb_call_foreach (struct server* server, DCB_REASON reason); size_t dcb_get_session_id(DCB* dcb); bool dcb_get_ses_log_info(DCB* dcb, size_t* sesid, int* enabled_logs); - +int gw_write_SSL(SSL* ssl, const void *buf, size_t nbytes); +int dcb_write_SSL(DCB *dcb, SSL* ssl, GWBUF *queue); +int dcb_read_SSL(DCB *dcb,SSL* ssl,GWBUF **head); +int dcb_drain_writeq_SSL(DCB *dcb, SSL* ssl); /** diff --git a/server/modules/include/mysql_client_server_protocol.h b/server/modules/include/mysql_client_server_protocol.h index 87dbc50ee..6c841d9c9 100644 --- a/server/modules/include/mysql_client_server_protocol.h +++ b/server/modules/include/mysql_client_server_protocol.h @@ -297,6 +297,7 @@ typedef struct { * handshake */ unsigned int charset; /*< MySQL character set at connect time */ SSL* ssl; /*< SSL struct for client connection */ + bool use_ssl; #if defined(SS_DEBUG) skygw_chk_t protocol_chk_tail; #endif diff --git a/server/modules/protocol/mysql_client.c b/server/modules/protocol/mysql_client.c index 387b59aa0..f1aa0aa94 100644 --- a/server/modules/protocol/mysql_client.c +++ b/server/modules/protocol/mysql_client.c @@ -37,7 +37,7 @@ * 09/09/2014 Massimiliano Pinto Added: 777 permission for socket path * 13/10/2014 Massimiliano Pinto Added: dbname authentication check * 10/11/2014 Massimiliano Pinto Added: client charset added to protocol struct - * + * 29/05/2015 Markus Makela Added SSL support */ #include #include @@ -69,7 +69,9 @@ static int gw_MySQLWrite_client(DCB *dcb, GWBUF *queue); static int gw_error_client_event(DCB *dcb); static int gw_client_close(DCB *dcb); static int gw_client_hangup_event(DCB *dcb); - +int gw_read_client_event_SSL(DCB* dcb); +int gw_MySQLWrite_client_SSL(DCB *dcb, GWBUF *queue); +int gw_write_client_event_SSL(DCB *dcb); int mysql_send_ok(DCB *dcb, int packet_number, int in_affected_rows, const char* mysql_message); int MySQLSendHandshake(DCB* dcb); static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue); @@ -464,6 +466,8 @@ static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue) { &protocol->client_capabilities); */ + if(protocol->protocol_auth_state == MYSQL_AUTH_SSL_EXCHANGE_DONE) + goto ssl_hs_done; ssl = protocol->client_capabilities & GW_MYSQL_CAPABILITIES_SSL; @@ -497,48 +501,19 @@ static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue) { protocol->ssl = SSL_new(protocol->owner_dcb->service->ctx); SSL_set_fd(protocol->ssl,dcb->fd); protocol->protocol_auth_state = MYSQL_AUTH_SSL_REQ; - printf("%s\n",SSL_get_version(protocol->ssl)); - int errnum,rval; - char errbuf[1024]; - switch((rval = SSL_accept(protocol->ssl))) + if(do_ssl_accept(protocol) < 0) { - case 0: - errnum = SSL_get_error(protocol->ssl,rval); - ERR_error_string(errnum,errbuf); - skygw_log_write_flush(LOGFILE_ERROR,"SSL_accept: %s",errbuf); - ERR_print_errors_fp(stdout); - ERR_error_string(errnum,errbuf); - printf("%s\n",errbuf); - fflush(stdout); - break; - case 1: - protocol->protocol_auth_state = MYSQL_AUTH_SSL_EXCHANGE_DONE; - break; - - default: - errnum = SSL_get_error(protocol->ssl,rval); - if(errnum == SSL_ERROR_WANT_READ) - { - /** Not all of the data has been read. Go back to the poll - queue and wait for more.*/ - protocol->protocol_auth_state = MYSQL_AUTH_SSL_RECV; - return 0; - } - else - { - ERR_print_errors_fp(stdout); - ERR_error_string(errnum,errbuf); - printf("%s\n",errbuf); - fflush(stdout); - skygw_log_write_flush(LOGFILE_ERROR,"Error: Fatal error in SSL_accept: %s",errbuf); - protocol->protocol_auth_state = MYSQL_AUTH_SSL_EXCHANGE_ERR; - } - break; - + return 1; + } + else + { + return 0; } } + ssl_hs_done: + username = get_username_from_auth(username, client_auth_packet); if (username == NULL) @@ -645,6 +620,65 @@ gw_MySQLWrite_client(DCB *dcb, GWBUF *queue) return dcb_write(dcb, queue); } + +/** + * Write function for client DCB: writes data from MaxScale to Client + * + * @param dcb The DCB of the client + * @param queue Queue of buffers to write + */ +int +gw_MySQLWrite_client_SSL(DCB *dcb, GWBUF *queue) +{ + MySQLProtocol *protocol = NULL; + CHK_DCB(dcb); + protocol = DCB_PROTOCOL(dcb, MySQLProtocol); + CHK_PROTOCOL(protocol); + return dcb_write_SSL(dcb, protocol->ssl, queue); +} + + +int gw_read_client_event_SSL( +DCB* dcb) +{ + SESSION *session = NULL; + ROUTER_OBJECT *router = NULL; + ROUTER *router_instance = NULL; + void *rsession = NULL; + MySQLProtocol *protocol = NULL; + GWBUF *read_buffer = NULL; + int rc = 0; + int nbytes_read = 0; + uint8_t cap = 0; + bool stmt_input = false; /*< router input type */ + + CHK_DCB(dcb); + protocol = DCB_PROTOCOL(dcb, MySQLProtocol); + CHK_PROTOCOL(protocol); + + + if(protocol->protocol_auth_state == MYSQL_AUTH_SSL_REQ) + { + if(do_ssl_accept(protocol) == 1) + { + spinlock_acquire(&protocol->protocol_lock); + protocol->protocol_auth_state = MYSQL_AUTH_SSL_EXCHANGE_DONE; + spinlock_release(&protocol->protocol_lock); + + spinlock_acquire(&dcb->authlock); + dcb->func.read = gw_read_client_event_SSL; + dcb->func.write = gw_MySQLWrite_client_SSL; + dcb->func.write_ready = gw_write_client_event; + spinlock_release(&dcb->authlock); + } + goto return_rc; + } + + return_rc: + + return rc; +} + /** * Client read event triggered by EPOLLIN * @@ -668,13 +702,36 @@ int gw_read_client_event( CHK_DCB(dcb); protocol = DCB_PROTOCOL(dcb, MySQLProtocol); CHK_PROTOCOL(protocol); - if(protocol->protocol_auth_state == MYSQL_AUTH_SSL_RECV) + + /** Let the OpenSSL API do the reading from the socket */ + if(protocol->protocol_auth_state == MYSQL_AUTH_SSL_REQ) { - goto do_auth; + if(do_ssl_accept(protocol) == 1) + { + spinlock_acquire(&protocol->protocol_lock); + protocol->protocol_auth_state = MYSQL_AUTH_SSL_EXCHANGE_DONE; + protocol->use_ssl = true; + spinlock_release(&protocol->protocol_lock); + + spinlock_acquire(&dcb->authlock); + //dcb->func.read = gw_read_client_event_SSL; + //dcb->func.write = gw_MySQLWrite_client_SSL; + //dcb->func.write_ready = gw_write_client_event_SSL; + spinlock_release(&dcb->authlock); + } + goto return_rc; } - rc = dcb_read(dcb, &read_buffer); - - + + + if(protocol->use_ssl) + { + rc = dcb_read_SSL(dcb,protocol->ssl, &read_buffer); + } + else + { + rc = dcb_read(dcb, &read_buffer); + } + if (rc < 0) { dcb_close(dcb); @@ -811,7 +868,7 @@ int gw_read_client_event( } } - do_auth: + /** * Now there should be at least one complete mysql packet in read_buffer. */ @@ -823,7 +880,7 @@ int gw_read_client_event( auth_val = gw_mysql_do_authentication(dcb, read_buffer); - if(protocol->protocol_auth_state == MYSQL_AUTH_SSL_RECV) + if(protocol->protocol_auth_state == MYSQL_AUTH_SSL_REQ) break; if (auth_val == 0) @@ -919,12 +976,9 @@ int gw_read_client_event( } break; - case MYSQL_AUTH_SSL_RECV: + case MYSQL_AUTH_SSL_EXCHANGE_DONE: { - if(do_ssl_accept(protocol) == 1) - { - protocol->protocol_auth_state = MYSQL_AUTH_SSL_EXCHANGE_DONE; - } + protocol->protocol_auth_state = MYSQL_AUTH_SSL_EXCHANGE_DONE; } break; @@ -1047,6 +1101,64 @@ return_rc: return rc; } + +/////////////////////////////////////////////// +// client write event to Client triggered by EPOLLOUT +////////////////////////////////////////////// +/** + * @node Client's fd became writable, and EPOLLOUT event + * arrived. As a consequence, client input buffer (writeq) is flushed. + * + * Parameters: + * @param dcb - in, use + * client dcb + * + * @return constantly 1 + * + * + * @details (write detailed description here) + * + */ +int gw_write_client_event(DCB *dcb) +{ + MySQLProtocol *protocol = NULL; + + CHK_DCB(dcb); + + ss_dassert(dcb->state != DCB_STATE_DISCONNECTED); + + if (dcb == NULL) { + goto return_1; + } + + if (dcb->state == DCB_STATE_DISCONNECTED) { + goto return_1; + } + + if (dcb->protocol == NULL) { + goto return_1; + } + protocol = (MySQLProtocol *)dcb->protocol; + CHK_PROTOCOL(protocol); + + if (protocol->protocol_auth_state == MYSQL_IDLE) + { + dcb_drain_writeq(dcb); + goto return_1; + } + +return_1: +#if defined(SS_DEBUG) + if (dcb->state == DCB_STATE_POLLING || + dcb->state == DCB_STATE_NOPOLLING || + dcb->state == DCB_STATE_ZOMBIE) + { + CHK_PROTOCOL(protocol); + } +#endif + return 1; +} + /////////////////////////////////////////////// // client write event to Client triggered by EPOLLOUT ////////////////////////////////////////////// @@ -1064,7 +1176,7 @@ return_rc: * @details (write detailed description here) * */ -int gw_write_client_event(DCB *dcb) +int gw_write_client_event_SSL(DCB *dcb) { MySQLProtocol *protocol = NULL; @@ -1088,7 +1200,7 @@ int gw_write_client_event(DCB *dcb) if (protocol->protocol_auth_state == MYSQL_IDLE) { - dcb_drain_writeq(dcb); + dcb_drain_writeq_SSL(dcb,protocol->ssl); goto return_1; } @@ -1776,16 +1888,17 @@ int do_ssl_accept(MySQLProtocol* protocol) { case 0: errnum = SSL_get_error(protocol->ssl,rval); - ERR_error_string(errnum,errbuf); - skygw_log_write_flush(LOGFILE_ERROR,"SSL_accept: %s",errbuf); - ERR_print_errors_fp(stdout); - ERR_error_string(errnum,errbuf); - printf("%s\n",errbuf); - fflush(stdout); + skygw_log_write_flush(LT,"SSL_accept ongoing for %s@%s", + protocol->owner_dcb->user, + protocol->owner_dcb->remote); break; case 1: protocol->protocol_auth_state = MYSQL_AUTH_SSL_EXCHANGE_DONE; rval = 1; + protocol->use_ssl = true; + skygw_log_write_flush(LT,"SSL_accept done for %s@%s", + protocol->owner_dcb->user, + protocol->owner_dcb->remote); break; default: @@ -1795,14 +1908,18 @@ int do_ssl_accept(MySQLProtocol* protocol) /** Not all of the data has been read. Go back to the poll queue and wait for more.*/ rval = 0; + protocol->protocol_auth_state = MYSQL_AUTH_SSL_REQ; + skygw_log_write_flush(LT,"SSL_accept partially done for %s@%s", + protocol->owner_dcb->user, + protocol->owner_dcb->remote); } else { - ERR_print_errors_fp(stdout); - ERR_error_string(errnum,errbuf); - printf("%s\n",errbuf); - fflush(stdout); - skygw_log_write_flush(LOGFILE_ERROR,"Error: Fatal error in SSL_accept: %s",errbuf); + skygw_log_write_flush(LE, + "Error: Fatal error in SSL_accept for %s@%s: %s", + protocol->owner_dcb->user, + protocol->owner_dcb->remote, + ERR_error_string(errnum,NULL)); protocol->protocol_auth_state = MYSQL_AUTH_SSL_EXCHANGE_ERR; rval = -1; } diff --git a/server/modules/protocol/mysql_common.c b/server/modules/protocol/mysql_common.c index ffd72c034..654d24692 100644 --- a/server/modules/protocol/mysql_common.c +++ b/server/modules/protocol/mysql_common.c @@ -137,7 +137,10 @@ void mysql_protocol_done ( goto retblock; } scmd = p->protocol_cmd_history; - + if(p->ssl) + { + SSL_free(p->ssl); + } while (scmd != NULL) { scmd2 = scmd->scom_next;