diff --git a/server/core/dbusers.c b/server/core/dbusers.c index b859ea29f..e8fe2b46a 100644 --- a/server/core/dbusers.c +++ b/server/core/dbusers.c @@ -731,7 +731,7 @@ getUsers(SERVICE *service, USERS *users) } else { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, - "%lu [mysql_users_add()] Failed adding user %s@%si for service [%s]", + "%lu [mysql_users_add()] Failed adding user %s@%s for service [%s]", pthread_self(), row[0], row[1], diff --git a/server/core/modutil.c b/server/core/modutil.c index 4c6ded8a2..59e7a1419 100644 --- a/server/core/modutil.c +++ b/server/core/modutil.c @@ -22,8 +22,9 @@ * @verbatim * Revision History * - * Date Who Description - * 04/06/14 Mark Riddoch Initial implementation + * Date Who Description + * 04/06/14 Mark Riddoch Initial implementation + * 24/10/14 Massimiliano Pinto Added modutil_send_mysql_err_packet, modutil_create_mysql_err_msg * * @endverbatim */ @@ -229,27 +230,27 @@ retblock: } -/* create mysql response packet */ - +/** +* create mysql response packet */ +*/ GWBUF* modutil_create_mysql_packet( int packet_number, int affected_rows, int merrno, - char *statemsg, + const char *statemsg, const char * msg) { - 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* errbuf = NULL; + 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 *errbuf = NULL; mysql_errno = (unsigned int)merrno; mysql_error_msg = msg; @@ -270,7 +271,7 @@ GWBUF* modutil_create_mysql_packet( sizeof(mysql_statemsg) + strlen(mysql_error_msg); - /** allocate memory for packet header + payload */ + /* allocate memory for packet header + payload */ errbuf = gwbuf_alloc(sizeof(mysql_packet_header) + mysql_payload_size); ss_dassert(errbuf != NULL); @@ -308,27 +309,30 @@ GWBUF* modutil_create_mysql_packet( } /** - * mysql_send_custom_error + * modutil_send_mysql_err_packet * * Send a MySQL protocol Generic ERR message, to the dcb - * Note the errno and state are still fixed now * - * @param dcb Owner_Dcb Control Block for the connection to which the OK is sent - * @param packet_number - * @param in_affected_rows - * @param mysql_message - * @return packet length + * @param dcb The DCB to send the packet + * @param packet_number MySQL protocol sequence number in the packet + * @param in_affected_rows MySQL affected rows + * @param mysql_errno The MySQL errno + * @param sqlstate_msg The MySQL State Message + * @param mysql_message The Error Message + * @return 0 for successful dcb write or 1 on failure * */ -int modutil_send_mysql_packet ( - DCB *dcb, - int packet_number, - int in_affected_rows, - const char *mysql_message) +int modutil_send_mysql_err_packet ( + DCB *dcb, + int packet_number, + int in_affected_rows, + int mysql_errno, + const char *sqlstate_msg, + const char *mysql_message) { GWBUF* buf; - buf = modutil_create_mysql_packet(packet_number, in_affected_rows, 1049, "42000", mysql_message); + buf = modutil_create_mysql_err_msg(packet_number, in_affected_rows, mysql_errno, sqlstate_msg, mysql_message); return dcb->func.write(dcb, buf); } diff --git a/server/include/modutil.h b/server/include/modutil.h index 3935e8e80..224e5f431 100644 --- a/server/include/modutil.h +++ b/server/include/modutil.h @@ -24,18 +24,21 @@ * @verbatim * Revision History * - * Date Who Description - * 04/06/14 Mark Riddoch Initial implementation - * 24/06/14 Mark Riddoch Add modutil_MySQL_Query to enable multipacket queries + * Date Who Description + * 04/06/14 Mark Riddoch Initial implementation + * 24/06/14 Mark Riddoch Add modutil_MySQL_Query to enable multipacket queries + * 24/10/14 Massimiliano Pinto Add modutil_send_mysql_err_packet to send a mysql ERR_Packet * * @endverbatim */ #include +#include extern int modutil_is_SQL(GWBUF *); extern int modutil_extract_SQL(GWBUF *, char **, int *); extern int modutil_MySQL_Query(GWBUF *, char **, int *, int *); extern GWBUF *modutil_replace_SQL(GWBUF *, char *); -char* modutil_get_query(GWBUF* buf); +extern char *modutil_get_query(GWBUF* buf); +extern int modutil_send_mysql_err_packet(DCB *, int, int, int, const char *, const char *); #endif diff --git a/server/modules/protocol/mysql_backend.c b/server/modules/protocol/mysql_backend.c index 074c9c82c..a72a6fad4 100644 --- a/server/modules/protocol/mysql_backend.c +++ b/server/modules/protocol/mysql_backend.c @@ -20,6 +20,8 @@ #include #include #include +#include + /* * MySQL Protocol module for handling the protocol between the gateway * and the backend MySQL database. @@ -41,6 +43,7 @@ * 04/09/2013 Massimiliano Pinto Added dcb->session and dcb->session->client checks for NULL * 12/09/2013 Massimiliano Pinto Added checks in gw_read_backend_event() for gw_read_backend_handshake * 27/09/2013 Massimiliano Pinto Changed in gw_read_backend_event the check for dcb_read(), now is if rc < 0 + * 24/10/2014 Massimiliano Pinto Added Mysql user@host @db authentication support * */ #include @@ -66,7 +69,8 @@ static int backend_write_delayqueue(DCB *dcb); static void backend_set_delayqueue(DCB *dcb, GWBUF *queue); static int gw_change_user(DCB *backend_dcb, SERVER *server, SESSION *in_session, GWBUF *queue); static GWBUF* process_response_data (DCB* dcb, GWBUF* readbuf, int nbytes_to_process); -extern char* create_auth_failed_msg( GWBUF* readbuf, char* hostaddr, uint8_t* sha1, int dbmatch); +extern char* create_auth_failed_msg( GWBUF* readbuf, char* hostaddr, uint8_t* sha1); +extern char* create_auth_fail_str(char *username, char *hostaddr, char *sha1, char *db); #if defined(NOT_USED) @@ -1171,9 +1175,16 @@ static int backend_write_delayqueue(DCB *dcb) return rc; } - - +/** + * This routine handles the COM_CHANGE_USER command + * + * @param dcb The current backend DCB + * @param server The backend server pointer + * @param in_session The current session data (MYSQL_session) + * @param queue The GWBUF containing the COM_CHANGE_USER receveid + * @return 0 on success and 1 on failure + */ static int gw_change_user( DCB *backend, SERVER *server, @@ -1197,18 +1208,18 @@ static int gw_change_user( backend_protocol = backend->protocol; client_protocol = in_session->client->protocol; - // now get the user, after 4 bytes header and 1 byte command + /* now get the user, after 4 bytes header and 1 byte command */ client_auth_packet += 5; strcpy(username, (char *)client_auth_packet); client_auth_packet += strlen(username) + 1; - // get the auth token len + /* get the auth token len */ memcpy(&auth_token_len, client_auth_packet, 1); ss_dassert(auth_token_len >= 0); client_auth_packet++; - // allocate memory for token only if auth_token_len > 0 + /* allocate memory for token only if auth_token_len > 0 */ if (auth_token_len > 0) { auth_token = (uint8_t *)malloc(auth_token_len); ss_dassert(auth_token != NULL); @@ -1225,11 +1236,16 @@ static int gw_change_user( /* save current_database name */ strcpy(current_database, current_session->db); - /* empty database name in dcb */ + /* + * Now clear database name in dcb as we don't do local authentication on db name for change user. + * Local authentication only for user@host and if successful the database name change is sent to backend. + */ strcpy(current_session->db, ""); - // decode the token and check the password - // Note: if auth_token_len == 0 && auth_token == NULL, user is without password + /* + * decode the token and check the password. + * Note: if auth_token_len == 0 && auth_token == NULL, user is without password + */ auth_ret = gw_check_mysql_scramble_data(backend->session->client, auth_token, auth_token_len, client_protocol->scramble, sizeof(client_protocol->scramble), username, client_sha1); if (auth_ret != 0) { @@ -1243,30 +1259,34 @@ static int gw_change_user( /* copy back current datbase to client session */ strcpy(current_session->db, current_database); - // let's free the auth_token now + /* let's free the auth_token now */ if (auth_token) free(auth_token); if (auth_ret != 0) { - char *message; + char *password_set = NULL; + char *message = NULL; - message = calloc(1,100); - strcpy(message, "change user authentication failed"); + if (auth_token_len > 0) + password_set = (char *)client_sha1; + else + password_set = ""; + message=create_auth_fail_str(username, + backend->session->client->remote, + password_set, + ""); /* send the error packet */ - //modutil_send_mysql_packet(backend->session->client, 1, 0, message); - mysql_send_auth_error(backend->session->client, 1, 0, message); - fprintf(stderr, "ERROR change user for [%s] to [%s]\n", username, database); + modutil_send_mysql_err_packet(backend->session->client, 1, 0, 1045, "28000", message); free(message); rv = 1; } else { - fprintf(stderr, "going to backend change_user for db [%s]\n", database); rv = gw_send_change_user_to_backend(database, username, client_sha1, backend_protocol); - /*< + /* * Now copy new data into user session */ strcpy(current_session->user, username); @@ -1275,8 +1295,6 @@ static int gw_change_user( } gwbuf_free(queue); - fprintf(stderr, "--- After change_user curren client dcb DB is [%s]\n", current_session->db); - return rv; } diff --git a/server/modules/protocol/mysql_client.c b/server/modules/protocol/mysql_client.c index bcca6a1e1..781dc96ce 100644 --- a/server/modules/protocol/mysql_client.c +++ b/server/modules/protocol/mysql_client.c @@ -70,9 +70,9 @@ 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* sha1i, char *db); extern char* get_username_from_auth(char* ptr, uint8_t* data); -extern int modutil_send_mysql_packet(DCB *, int, int, const char *); +extern int check_db_name_after_auth(DCB *, char *, int); +extern char* create_auth_fail_str(char *username, char *hostaddr, char *sha1, char *db); /* * The "module object" for the mysqld client protocol module. @@ -396,7 +396,6 @@ static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue) { uint8_t *stage1_hash = NULL; int auth_ret = -1; MYSQL_session *client_data = NULL; - int db_exists = 0; CHK_DCB(dcb); @@ -508,47 +507,6 @@ static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue) { return auth_ret; } -static char* create_auth_fail_str( - GWBUF* readbuf, - char* hostaddr, - char* sha1, - char* db) -{ - char* errstr; - char* uname; - const char* ferrstr; - int db_len; - - if (db != NULL) - db_len = strlen(db); - else - db_len = 0; - - if (db_len>0) - ferrstr = "Access denied for user '%s'@'%s' (using password: %s) to database '%s'"; - else - 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 + db_len + 1); - - if (errstr != NULL && db_len>0) - { - sprintf(errstr, ferrstr, uname, hostaddr, (*sha1 == '\0' ? "NO" : "YES"), db); - } - else - sprintf(errstr, ferrstr, uname, hostaddr, (*sha1 == '\0' ? "NO" : "YES")); - free(uname); - -retblock: - return errstr; -} - /** * Write function for client DCB: writes data from MaxScale to Client * @@ -700,35 +658,28 @@ int gw_read_client_event( } else { - char* fail_str; + char* fail_str = NULL; protocol->protocol_auth_state = MYSQL_AUTH_FAILED; if (auth_val == 2) { - char *dberr; - dberr= calloc(1, 100); - sprintf(dberr, "Unknown database '%s'", (char*)((MYSQL_session *)dcb->data)->db); + /** Send error 1049 to client */ + int message_len = 25 + MYSQL_DATABASE_MAXLEN; - modutil_send_mysql_packet( - //mysql_send_auth_error( - dcb, - 2, - 0, - dberr); + fail_str = calloc(1, message_len+1); + snprintf(fail_str, message_len, "Unknown database '%s'", (char*)((MYSQL_session *)dcb->data)->db); - free(dberr); + modutil_send_mysql_err_packet(dcb, 2, 0, 1049, "42000", fail_str); } else { /** Send error 1045 to client */ - fail_str = create_auth_fail_str(read_buffer, + 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); - mysql_send_auth_error( - dcb, - 2, - 0, - fail_str); - free(fail_str); + (char*)((MYSQL_session *)dcb->data)->client_sha1, + (char*)((MYSQL_session *)dcb->data)->db); + modutil_send_mysql_err_packet(dcb, 2, 0, 1045, "28000", fail_str); } + if (fail_str) + free(fail_str); LOGIF(LD, (skygw_log_write( LOGFILE_DEBUG, diff --git a/server/modules/protocol/mysql_common.c b/server/modules/protocol/mysql_common.c index c294bc7ba..3acc91243 100644 --- a/server/modules/protocol/mysql_common.c +++ b/server/modules/protocol/mysql_common.c @@ -34,6 +34,7 @@ * 29/09/2014 Massimiliano Pinto Added Mysql user@host authentication with wildcard in IPv4 hosts: * x.y.z.%, x.y.%.%, x.%.%.% * 03/10/2014 Massimiliano Pinto Added netmask for wildcard in IPv4 hosts. + * 24/10/2014 Massimiliano Pinto Added Mysql user@host @db authentication support * */ @@ -1986,19 +1987,18 @@ void protocol_set_response_status ( char* create_auth_failed_msg( GWBUF* readbuf, char* hostaddr, - uint8_t* sha1, int dbmatch) + uint8_t* sha1) { char* errstr; char* uname=(char *)GWBUF_DATA(readbuf) + 5; const char* ferrstr = "Access denied for user '%s'@'%s' (using password: %s)"; /** -4 comes from 2X'%s' minus terminating char */ - errstr = (char *)malloc(strlen(uname)+strlen(ferrstr)+strlen(hostaddr)+strlen("YES")-6+1 + strlen(" to database ") + strlen("''") + strlen("datbase") +1); + 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")); - strcat(errstr, " to database 'database'"); } return errstr; @@ -2007,6 +2007,8 @@ char* create_auth_failed_msg( /** * Read username from MySQL authentication packet. * + * Only for client to server packet, COM_CHANGE_USER packet has different format. + * * @param ptr address where to write the result or NULL if memory * is allocated here. * @param data Address of MySQL packet. @@ -2075,3 +2077,45 @@ int check_db_name_after_auth(DCB *dcb, char *database, int auth_ret) { return auth_ret; } + +/** + * Create a message error string to send via MySQL ERR packet. + * + * @param username the MySQL user + * @param hostaddr the client IP + * @param sha1 authentication scramble data + * @param db the MySQL db to connect to + * + * @return Pointer to the allocated string or NULL on failure + */ +char *create_auth_fail_str( + char *username, + char *hostaddr, + char *sha1, + char *db) +{ + char* errstr; + const char* ferrstr; + int db_len; + + if (db != NULL) + db_len = strlen(db); + else + db_len = 0; + + if (db_len>0) + ferrstr = "Access denied for user '%s'@'%s' (using password: %s) to database '%s'"; + else + ferrstr = "Access denied for user '%s'@'%s' (using password: %s)"; + + errstr = (char *)malloc(strlen(username)+strlen(ferrstr)+strlen(hostaddr)+strlen("YES")-6 + db_len + ((db_len > 0) ? (strlen(" to database ") +2) : 0) + 1); + + if (errstr != NULL) { + if (db_len>0) + sprintf(errstr, ferrstr, username, hostaddr, (*sha1 == '\0' ? "NO" : "YES"), db); + else + sprintf(errstr, ferrstr, username, hostaddr, (*sha1 == '\0' ? "NO" : "YES")); + } + + return errstr; +}