From 740a001d019e2c29e0e164692bcd5becdb4b655b Mon Sep 17 00:00:00 2001 From: Massimiliano Pinto Date: Fri, 5 Jul 2013 09:38:52 +0200 Subject: [PATCH] moved mysql_send_custom_error to mysql_common.c The failed backend auth now forces the closeSession and also sets thre router session = NULL This will be changed introducing another state in the session struct --- modules/protocol/mysql_backend.c | 54 ++++++++++++++++---- modules/protocol/mysql_client.c | 85 +++++++++++++++---------------- modules/protocol/mysql_common.c | 86 ++++++++++++++++++++++++++++++-- 3 files changed, 164 insertions(+), 61 deletions(-) diff --git a/modules/protocol/mysql_backend.c b/modules/protocol/mysql_backend.c index 8c276779b..252985913 100644 --- a/modules/protocol/mysql_backend.c +++ b/modules/protocol/mysql_backend.c @@ -122,7 +122,7 @@ static int gw_read_backend_event(DCB *dcb) { backend_protocol = (MySQLProtocol *) dcb->protocol; current_session = (MYSQL_session *)dcb->session->data; - //fprintf(stderr, ">>> backend EPOLLIN from %i, protocol state [%s]\n", dcb->fd, gw_mysql_protocol_state2string(backend_protocol->state)); + fprintf(stderr, ">>> backend EPOLLIN from %i, protocol state [%s]\n", dcb->fd, gw_mysql_protocol_state2string(backend_protocol->state)); // backend is connected: read server handshake and write auth request and return if (backend_protocol->state == MYSQL_CONNECTED) { @@ -135,15 +135,36 @@ static int gw_read_backend_event(DCB *dcb) { // ready to check the authentication reply if (backend_protocol->state == MYSQL_AUTH_RECV) { + ROUTER_OBJECT *router = NULL; + ROUTER *router_instance = NULL; + void *rsession = NULL; int rv = -1; + SESSION *session = dcb->session; + + if (session) { + router = session->service->router; + router_instance = session->service->router_instance; + rsession = session->router_session; + } + rv = gw_receive_backend_auth(backend_protocol); switch (rv) { case MYSQL_FAILED_AUTHENTICATION: + fprintf(stderr, ">>>> Backend Auth failed for %i\n", dcb->fd); + backend_protocol->state = MYSQL_AUTH_FAILED; - // this will close the opened backend socket - dcb_close(dcb); + /* send an error to the client */ + mysql_send_custom_error(dcb->session->client, 1, 0, "Connection to backend lost right now"); + + /* close the active session */ + router->closeSession(router_instance, rsession); + + /* force the router_session to NULL + * Later we will implement a proper status for the session + */ + session->router_session = NULL; return 1; @@ -152,7 +173,7 @@ static int gw_read_backend_event(DCB *dcb) { backend_protocol->state = MYSQL_IDLE; - // check the delay queue + /* check the delay queue and flush the data */ if(dcb->delayq) { backend_write_delayqueue(dcb); spinlock_release(&dcb->authlock); @@ -163,20 +184,20 @@ static int gw_read_backend_event(DCB *dcb) { return 1; default: - // no other authentication state here right now, so just return + /* no other authentication state here right now, so just return */ return 0; } } - // reading MySQL command output from backend and writing to the client + /* reading MySQL command output from backend and writing to the client */ if ((client_protocol->state == MYSQL_WAITING_RESULT) || (client_protocol->state == MYSQL_IDLE)) { GWBUF *head = NULL; - // read data + /* read available backend data */ dcb_read(dcb, &head); - // write the gwbuffer to client + /* and write the gwbuffer to client */ dcb->session->client->func.write(dcb->session->client, head); return 1; @@ -194,7 +215,12 @@ static int gw_read_backend_event(DCB *dcb) { static int gw_write_backend_event(DCB *dcb) { MySQLProtocol *backend_protocol = dcb->protocol; - //fprintf(stderr, ">>> backend EPOLLOUT %i, protocol state [%s]\n", backend_protocol->fd, gw_mysql_protocol_state2string(backend_protocol->state)); + fprintf(stderr, ">>> backend EPOLLOUT %i, protocol state [%s]\n", backend_protocol->fd, gw_mysql_protocol_state2string(backend_protocol->state)); + + if (backend_protocol->state == MYSQL_AUTH_FAILED) { + fprintf(stderr, ">>> Backend epollout auth failed, EXIT\n"); + return 0; + } // spinlock_acquire(&dcb->connectlock); @@ -224,11 +250,17 @@ gw_MySQLWrite_backend(DCB *dcb, GWBUF *queue) { MySQLProtocol *backend_protocol = dcb->protocol; + if (backend_protocol->state == MYSQL_AUTH_FAILED) { + fprintf(stderr, ">>> backend %i auth failed, EXIT\n", dcb->fd); + dcb_close(dcb); + return 0; + } + spinlock_acquire(&dcb->authlock); // put incoming data to the delay queue unless backend is connected with auth ok if (backend_protocol->state != MYSQL_IDLE) { - fprintf(stderr, ">>> Writing in the backend %i delay queue\n", dcb->fd); + //fprintf(stderr, ">>> Writing in the backend %i delay queue\n", dcb->fd); backend_set_delayqueue(dcb, queue); spinlock_release(&dcb->authlock); @@ -315,7 +347,7 @@ static int gw_create_backend_connection(DCB *backend, SERVER *server, SESSION *s break; } - fprintf(stderr, "--> Backend conn added [%i], in the client session [%i]\n", backend->fd, session->client->fd); + fprintf(stderr, ">>> Backend [%s:%i] added [%i], in the client session [%i]\n", server->name, server->port, backend->fd, session->client->fd); backend->state = DCB_STATE_POLLING; diff --git a/modules/protocol/mysql_client.c b/modules/protocol/mysql_client.c index b3da2b733..568ffffdb 100644 --- a/modules/protocol/mysql_client.c +++ b/modules/protocol/mysql_client.c @@ -180,7 +180,7 @@ mysql_send_ok(DCB *dcb, int packet_number, int in_affected_rows, const char* mys } /** - * mysql_send_auth_error + * mysql_send_custom_error * * Send a MySQL protocol Generic ERR message, to the dcb * Note the errno and state are still fixed now @@ -542,8 +542,6 @@ static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue) { fprintf(stderr, "<<< Client is NOT connected with db\n"); } - fprintf(stderr, "HERE auth token len is %i\n", auth_token_len); - // allocate memory for token only if auth_token_len > 0 if (auth_token_len) { auth_token = (uint8_t *)malloc(auth_token_len); @@ -558,10 +556,8 @@ static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue) { if (auth_token) free(auth_token); - if (auth_ret == 0) { - fprintf(stderr, "<<< CLIENT AUTH is OK\n"); - } else { - fprintf(stderr, "<<< CLIENT AUTH FAILED\n"); + if (auth_ret != 0) { + fprintf(stderr, "<<< CLIENT AUTH FAILEDi for user [%s]\n", username); } return auth_ret; @@ -586,7 +582,6 @@ static int gw_find_mysql_user_password_sha1(char *username, uint8_t *gateway_pas fprintf(stderr, ">>> MYSQL user NOT FOUND: %s\n", username); return 1; } - fprintf(stderr, ">>> MYSQL user FOUND !!!!: [%s]:[%s]\n", username, user_password); // convert hex data (40 bytes) to binary (20 bytes) // gateway_password represents the SHA1(SHA1(real_password)) @@ -618,16 +613,14 @@ static int gw_check_mysql_scramble_data(DCB *dcb, uint8_t *token, unsigned int t if (ret_val) { fprintf(stderr, "<<<< User [%s] was not found\n", username); return 1; - } else { - fprintf(stderr, "<<<< User [%s] OK\n", username); } if (token && token_len) { - fprintf(stderr, ">>> continue with auth\n"); // convert in hex format: this is the content of mysql.user table, field password without the '*' prefix - // an it is 40 bytes long + // 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)) { fprintf(stderr, ">>> continue WITHOUT auth, no password\n"); return 0; @@ -673,14 +666,12 @@ static int gw_check_mysql_scramble_data(DCB *dcb, uint8_t *token, unsigned int t gw_sha1_str(step2, SHA_DIGEST_LENGTH, check_hash); - fprintf(stderr, "<<<< Client_SHA1 [%20s]\n", stage1_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); + fprintf(stderr, "The CLIENT hex(SHA1(SHA1(password))) for \"%s\" is [%s]", username, inpass); } #endif @@ -865,6 +856,8 @@ int gw_read_client_event(DCB* dcb) { int ret = -1; session = dcb->session; + + // get the backend session, if available if (session) { router = session->service->router; router_instance = session->service->router_instance; @@ -877,65 +870,66 @@ int gw_read_client_event(DCB* dcb) { if ((ret = gw_read_gwbuff(dcb, &gw_buffer, b)) != 0) return ret; - // Now assuming in the first buffer there is the information form mysql command + /* Now, we are assuming in the first buffer there is the information form mysql command */ - // following code is only for debug now queue = gw_buffer; len = GWBUF_LENGTH(queue); ptr_buff = GWBUF_DATA(queue); - // get mysql commang + /* get mysql commang at fourth byte */ if (ptr_buff) mysql_command = ptr_buff[4]; if (mysql_command == '\x03') { - /// this is a query !!!! - //fprintf(stderr, "<<< MySQL Query from Client %i bytes: [%s]\n", len, ptr_buff+5); - //else - //fprintf(stderr, "<<< Reading from Client %i bytes: [%s]\n", len, ptr_buff); + /// this is a standard MySQL query !!!! } - + /** + * Routing Client input to Backend + */ + + /* Do not route the query without session! */ if(!rsession) { - if (mysql_command == '\x01') { - fprintf(stderr, "COM_QUIT received with no connected backends\n"); + /* COM_QUIT handling */ + fprintf(stderr, "COM_QUIT received with no connected backends from %i\n", dcb->fd); (dcb->func).close(dcb); + return 1; + } else { + /* Send a custom error as MySQL command reply */ + mysql_send_custom_error(dcb, 1, 0, "Connection to backend lost"); + + protocol->state = MYSQL_IDLE; + return 1; } - mysql_send_custom_error(dcb, 1, 0, "Connection to backend lost"); - protocol->state = MYSQL_IDLE; - - break; } - - /////////////////////////// - // Handling the COM_QUIT - ////////////////////////// - if (mysql_command == '\x01') { - fprintf(stderr, "COM_QUIT received\n"); - // uncomment the following lines for closing - // client and backend conns - // dcb still to be freed + + /* We can route the query */ - // this will propagate COM_QUIT to backends + /* COM_QUIT handling */ + if (mysql_command == '\x01') { + fprintf(stderr, "COM_QUIT received for %i\n", dcb->fd); + + /* this will propagate COM_QUIT to backend(s) */ + fprintf(stderr, "<<< Routing the COM_QUIT ...\n"); router->routeQuery(router_instance, rsession, queue); - // close client + + /* close client connection */ (dcb->func).close(dcb); - // call errors, it will be removed after tests - //(dcb->func).error(dcb); return 1; } + /* MySQL Command Routing */ + protocol->state = MYSQL_ROUTING; - /////////////////////////////////////// - // writing in the backend buffer queue, via routeQuery - /////////////////////////////////////// + /* writing in the backend buffer queue, via routeQuery */ + fprintf(stderr, "<<< Routing the Query ...\n"); router->routeQuery(router_instance, rsession, queue); protocol->state = MYSQL_WAITING_RESULT; @@ -1085,7 +1079,6 @@ int gw_MySQLAccept(DCB *listener) { if (c_sock == -1) { if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) { - fprintf(stderr, ">>>> NO MORE conns for MySQL Listener: errno is %i for %i\n", errno, listener->fd); /* We have processed all incoming connections. */ break; } else { diff --git a/modules/protocol/mysql_common.c b/modules/protocol/mysql_common.c index 954a0fc28..06b2bdca1 100644 --- a/modules/protocol/mysql_common.c +++ b/modules/protocol/mysql_common.c @@ -22,9 +22,7 @@ * Revision History * Date Who Description * 17/06/2013 Massimiliano Pinto Common MySQL protocol routines - * 02/07/2013 Massimiliano Pinto MySQL connect asynchronous phases - * 04/07/2013 Massimiliano Pinto MySQL connect routine supports EAGAIN - * Added gw_mysql_protocol_state2string for printing MySQL the protocol status + * 02/06/2013 Massimiliano Pinto MySQL connect asynchronous phases */ #include "mysql_client_server_protocol.h" @@ -464,7 +462,7 @@ int gw_do_connect_to_backend(char *host, int port, MySQLProtocol *conn) { if ((rv = connect(so, (struct sockaddr *)&serv_addr, sizeof(serv_addr))) < 0) { // If connection is not yet completed just return 1 if (errno == EINPROGRESS) { - fprintf(stderr, ">>> Connection not yet completed for backend server [%s:%i]: errno %i, %s: RV = [%i]\n", host, port, errno, strerror(errno), rv); + fprintf(stderr, ">>> Connection is not yet completed for backend server [%s:%i]: errno %i, %s: RV = [%i]\n", host, port, errno, strerror(errno), rv); return 1; } else { // this is a real error @@ -510,4 +508,84 @@ gw_mysql_protocol_state2string (int state) { 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 +gw_backend_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; + + 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; +} /////