diff --git a/server/modules/protocol/mysql_client.c b/server/modules/protocol/mysql_client.c index 9372314d8..dfcdd1334 100644 --- a/server/modules/protocol/mysql_client.c +++ b/server/modules/protocol/mysql_client.c @@ -68,6 +68,8 @@ int mysql_send_ok(DCB *dcb, int packet_number, int in_affected_rows, const char* int MySQLSendHandshake(DCB* dcb); static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue); static int route_by_statement(SESSION *, GWBUF **); +static char* create_auth_fail_str(GWBUF* readbuf, char* hostaddr, char* sha1); +static char* get_username_from_auth(char* ptr, uint8_t* data); /* * The "module object" for the mysqld client protocol module. @@ -372,9 +374,9 @@ MySQLSendHandshake(DCB* dcb) * The useful data: user, db, client_sha1 are copied into the MYSQL_session * dcb->session->data * client_capabilitiesa are copied into the dcb->protocol * - * @param dcb Descriptor Control Block of the client - * @param queue The GWBUF with data from client - * @return 0 for Authentication ok, !=0 for failed autht + * @param dcb Descriptor Control Block of the client + * @param queue The GWBUF with data from client + * @return 0 If succeed, otherwise non-zero value * */ @@ -432,12 +434,10 @@ static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue) { &protocol->client_capabilities); */ - /* now get the user */ - strncpy(username, (char *)(client_auth_packet + 4 + 4 + 4 + 1 + 23), MYSQL_USER_MAXLEN); - - - /* the empty username field is not allowed */ - if (!strlen(username)) { + username = get_username_from_auth(username, client_auth_packet); + + if (username == NULL) + { return 1; } @@ -448,9 +448,9 @@ static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue) { if (connect_with_db) { database = client_data->db; - strncpy(database, - (char *)(client_auth_packet + 4 + 4 + 4 + 1 + 23 + strlen(username) + - 1 + 1 + auth_token_len), MYSQL_DATABASE_MAXLEN); + strncpy(database, + (char *)(client_auth_packet + 4 + 4 + 4 + 1 + 23 + strlen(username) + + 1 + 1 + auth_token_len), MYSQL_DATABASE_MAXLEN); } /* allocate memory for token only if auth_token_len > 0 */ @@ -489,10 +489,88 @@ static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue) { { dcb->user = strdup(client_data->user); } - + return auth_ret; } +/** + * Read username from MySQL authentication packet. + * + * @param ptr address where to write the result or NULL if memory + * is allocated here. + * @param data Address of MySQL packet. + * + * @return Pointer to a copy of the username. NULL if packet was of wrong + * type, if memory allocation failed or if username was empty. + */ +static char* get_username_from_auth( + char* ptr, + uint8_t* data) +{ + uint8_t packet_type; + char* first_letter; + char* rval; + + packet_type = data[4]; + + if (packet_type != MYSQL_COM_CREATE_DB) + { + rval = NULL; + goto retblock; + } + first_letter = (char *)(data + 4 + 4 + 4 + 1 + 23); + + if (first_letter == '\0') + { + rval = NULL; + goto retblock; + } + + if (ptr == NULL) + { + if ((rval = (char *)malloc(MYSQL_USER_MAXLEN)) == NULL) + { + goto retblock; + } + } + else + { + rval = ptr; + } + strncpy(rval, first_letter, MYSQL_USER_MAXLEN); + +retblock: + + return rval; +} + + +static char* create_auth_fail_str( + GWBUF* readbuf, + char* hostaddr, + char* sha1) +{ + char* errstr; + char* uname; + const char* ferrstr = "Access denied for user '%s'@'%s' (using password: %s)"; + + if ( (uname = get_username_from_auth(NULL, (uint8_t *)GWBUF_DATA(readbuf))) == NULL) + { + errstr = NULL; + goto retblock; + } + /** -4 comes from 2X'%s' minus terminating char */ + errstr = (char *)malloc(strlen(uname)+strlen(ferrstr)+strlen(hostaddr)+strlen("YES")-6+1); + + if (errstr != NULL) + { + sprintf(errstr, ferrstr, uname, hostaddr, (*sha1 == '\0' ? "NO" : "YES")); + } + +retblock: + return errstr; +} + /** * Write function for client DCB: writes data from MaxScale to Client * @@ -593,77 +671,83 @@ int gw_read_client_event( case MYSQL_AUTH_SENT: { - int auth_val = -1; - + int auth_val; + auth_val = gw_mysql_do_authentication(dcb, read_buffer); - read_buffer = gwbuf_consume(read_buffer, nbytes_read); - ss_dassert(read_buffer == NULL || GWBUF_EMPTY(read_buffer)); - - if (auth_val == 0) - { - SESSION *session = NULL; - 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); + + 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); + + protocol->protocol_auth_state = MYSQL_IDLE; + /** + * Send an AUTH_OK packet to the client, + * packet sequence is # 2 + */ + mysql_send_ok(dcb, 2, 0, NULL); + } + else + { + protocol->protocol_auth_state = MYSQL_AUTH_FAILED; + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, + "%lu [gw_read_client_event] session " + "creation failed. fd %d, " + "state = MYSQL_AUTH_FAILED.", + protocol->owner_dcb->fd, + pthread_self()))); + + /** Send ERR 1045 to client */ + mysql_send_auth_error( + dcb, + 2, + 0, + "failed to create new session"); + + dcb_close(dcb); + } + } + else + { + char* fail_str; + + protocol->protocol_auth_state = MYSQL_AUTH_FAILED; + fail_str = create_auth_fail_str(read_buffer, + dcb->remote, + (char*)((MYSQL_session *)dcb->data)->client_sha1); + + /** Send error 1045 to client */ + mysql_send_auth_error( + dcb, + 2, + 0, + fail_str); - protocol->protocol_auth_state = MYSQL_IDLE; - /** - * Send an AUTH_OK packet to the client, - * packet sequence is # 2 - */ - mysql_send_ok(dcb, 2, 0, NULL); - } - else - { - protocol->protocol_auth_state = MYSQL_AUTH_FAILED; - LOGIF(LD, (skygw_log_write( - LOGFILE_DEBUG, - "%lu [gw_read_client_event] session " - "creation failed. fd %d, " - "state = MYSQL_AUTH_FAILED.", - protocol->owner_dcb->fd, - pthread_self()))); - - /** Send ERR 1045 to client */ - mysql_send_auth_error( - dcb, - 2, - 0, - "failed to create new session"); - - dcb_close(dcb); - } - } - else - { - protocol->protocol_auth_state = MYSQL_AUTH_FAILED; - LOGIF(LD, (skygw_log_write( - LOGFILE_DEBUG, - "%lu [gw_read_client_event] after " - "gw_mysql_do_authentication, fd %d, " - "state = MYSQL_AUTH_FAILED.", - protocol->owner_dcb->fd, - pthread_self()))); - - /** Send ERR 1045 to client */ - mysql_send_auth_error( - dcb, - 2, - 0, - "Access denied"); - - dcb_close(dcb); - } - } + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, + "%lu [gw_read_client_event] after " + "gw_mysql_do_authentication, fd %d, " + "state = MYSQL_AUTH_FAILED.", + protocol->owner_dcb->fd, + pthread_self()))); + free(fail_str); + dcb_close(dcb); + } + read_buffer = gwbuf_consume(read_buffer, nbytes_read); + } break; case MYSQL_IDLE: