From ee54310a73b169ff01f82063a0fa84117aaa0787 Mon Sep 17 00:00:00 2001 From: MassimilianoPinto Date: Mon, 13 Oct 2014 09:50:55 +0200 Subject: [PATCH 01/31] Implementation of db auth Implementation of db auth --- server/core/dbusers.c | 457 ++++++++++++++++++++---- server/core/test/CMakeLists.txt | 27 +- server/core/test/test_mysql_users.c | 175 ++++++++- server/include/dbusers.h | 6 +- server/modules/protocol/mysql_backend.c | 46 +-- server/modules/protocol/mysql_client.c | 298 +++++++-------- server/modules/protocol/mysql_common.c | 239 ++++++++----- 7 files changed, 856 insertions(+), 392 deletions(-) diff --git a/server/core/dbusers.c b/server/core/dbusers.c index c65aafdc9..ff1c78c44 100644 --- a/server/core/dbusers.c +++ b/server/core/dbusers.c @@ -28,6 +28,9 @@ * 06/02/2014 Massimiliano Pinto Mysql user root selected based on configuration flag * 26/02/2014 Massimiliano Pinto Addd: replace_mysql_users() routine may replace users' table based on a checksum * 28/02/2014 Massimiliano Pinto Added Mysql user@host authentication + * 29/09/2014 Massimiliano Pinto Added Mysql user@host authentication with wildcard in IPv4 hosts: + * x.y.z.%, x.y.%.%, x.%.%.% + * 03/10/14 Massimiliano Pinto Added netmask to user@host authentication for wildcard in IPv4 hosts * * @endverbatim */ @@ -45,18 +48,25 @@ #include #define USERS_QUERY_NO_ROOT " AND user NOT IN ('root')" -#define LOAD_MYSQL_USERS_QUERY "SELECT user, host, password, concat(user,host,password) AS userdata FROM mysql.user WHERE user IS NOT NULL AND user <> ''" +#define LOAD_MYSQL_USERS_QUERY "SELECT user, host, password, concat(user,host,password,Select_priv) AS userdata, Select_priv AS anydb FROM mysql.user WHERE user IS NOT NULL AND user <> ''" #define MYSQL_USERS_COUNT "SELECT COUNT(1) AS nusers FROM mysql.user" +#define MYSQL_USERS_WITH_DB_ORDER " ORDER BY host DESC" +#define LOAD_MYSQL_USERS_WITH_DB_QUERY "SELECT user.user AS user,user.host AS host,user.password AS password,concat(user.user,user.host,user.password,user.Select_priv) AS userdata, user.Select_priv AS anydb,db.db AS db FROM mysql.user LEFT JOIN mysql.db ON user.user=db.user AND user.host=db.host WHERE user.user IS NOT NULL AND user.user <> ''" MYSQL_USERS_WITH_DB_ORDER + +#define LOAD_MYSQL_USERS_WITH_DB_QUERY_NO_ROOT "SELECT * FROM (" LOAD_MYSQL_USERS_WITH_DB_QUERY ") AS t1 WHERE user NOT IN ('root')" MYSQL_USERS_WITH_DB_ORDER + extern int lm_enabled_logfiles_bitmask; -static int getUsers(SERVICE *service, struct users *users); +static int getUsers(SERVICE *service, USERS *users); static int uh_cmpfun( void* v1, void* v2); static void *uh_keydup(void* key); static void uh_keyfree( void* key); static int uh_hfun( void* key); -char *mysql_users_fetch(USERS *users, MYSQL_USER_HOST *key); +void *mysql_users_fetch(USERS *users, MYSQL_USER_HOST *key); char *mysql_format_user_entry(void *data); +int add_mysql_users_with_host_ipv4(USERS *users, char *user, char *host, char *passwd, char *anydb, char *db); +static int getDatabases(SERVICE *); /** * Load the user/passwd form mysql.user table into the service users' hashtable @@ -71,6 +81,10 @@ load_mysql_users(SERVICE *service) return getUsers(service, service->users); } +int mysql_users_load_dbs(SERVICE *service) { + return getDatabases(service); +} + /** * Reload the user/passwd form mysql.user table into the service users' hashtable * environment. @@ -82,7 +96,7 @@ int reload_mysql_users(SERVICE *service) { int i; -struct users *newusers, *oldusers; +USERS *newusers, *oldusers; if ((newusers = mysql_users_alloc()) == NULL) return 0; @@ -108,7 +122,7 @@ int replace_mysql_users(SERVICE *service) { int i; -struct users *newusers, *oldusers; +USERS *newusers, *oldusers; if ((newusers = mysql_users_alloc()) == NULL) return -1; @@ -148,6 +162,108 @@ struct users *newusers, *oldusers; return i; } + +/** + * Add a new MySQL user with host, password and netmask into the service users table + * + * The netmask values are: + * 0 for any, 32 for single IPv4 + * 24 for a class C from a.b.c.%, 16 for a Class B from a.b.%.% and 8 for a Class A from a.%.%.% + * + * @param users The users table + * @param user The user name + * @param host The host to add, with possible wildcards + * @param passwd The sha1(sha1(passoword)) to add + * @return 1 on success, 0 on failure + */ + +int add_mysql_users_with_host_ipv4(USERS *users, char *user, char *host, char *passwd, char *anydb, char *db) { + struct sockaddr_in serv_addr; + MYSQL_USER_HOST key; + char ret_ip[INET_ADDRSTRLEN + 1]=""; + int found_range=0; + int found_any=0; + int ret = 0; + + fprintf(stderr, "Current %s@%s has anydb access %s OR specific db %s\n", user, host, anydb, db); + + /* prepare the user@host data struct */ + memset(&serv_addr, 0, sizeof(serv_addr)); + memset(&key, '\0', sizeof(key)); + + /* set user */ + key.user = strdup(user); + + if(key.user == NULL) { + return ret; + } + + /* for anydb == Y key.resource is '\0' as set by memset */ + if (strcmp(anydb, "N") == 0) { + if (db) + key.resource = strdup(db); + else + key.resource = NULL; + } else { + key.resource = strdup(""); + } + + /* handle ANY, Class C,B,A */ + + /* ANY */ + if (strcmp(host, "%") == 0) { + strcpy(ret_ip, "0.0.0.0"); + found_any = 1; + } else { + char *tmp; + strcpy(ret_ip, host); + tmp = ret_ip+strlen(ret_ip)-1; + + /* start from Class C */ + while(*tmp) { + if (*tmp == '%') { + /* set only the last IPv4 byte to 1 + * avoiding setipadress() failure + * for Class C address + */ + found_range++; + if (found_range == 1) + *tmp = '1'; + else + *tmp = '0'; + } + tmp--; + } + } + + /* fill IPv4 data struct */ + if (setipaddress(&serv_addr.sin_addr, ret_ip)) { + + /* copy IPv4 data into key.ipv4 */ + memcpy(&key.ipv4, &serv_addr, sizeof(serv_addr)); + + if (found_range) { + /* let's zero the last IP byte: a.b.c.0 we set above to 1*/ + key.ipv4.sin_addr.s_addr &= 0x00FFFFFF; + key.netmask = 32 - (found_range * 8); + } else { + key.netmask = 32 - (found_any * 32); + } + + /* add user@host as key and passwd as value in the MySQL users hash table */ + if (mysql_users_add(users, &key, passwd)) + ret = 1; + if (ret == 1) + fprintf(stderr, "Added user %s@%i with db [%s]\n", key.user, key.ipv4.sin_addr.s_addr, key.resource); + } + + free(key.user); + if (key.resource) + free(key.resource); + + return ret; +} + /** * Load the user/passwd form mysql.user table into the service users' hashtable * environment. @@ -157,7 +273,7 @@ struct users *newusers, *oldusers; * @return -1 on any error or the number of users inserted (0 means no users at all) */ static int -getUsers(SERVICE *service, struct users *users) +getDatabases(SERVICE *service) { MYSQL *con = NULL; MYSQL_ROW row; @@ -171,17 +287,188 @@ getUsers(SERVICE *service, struct users *users) char *users_query; unsigned char hash[SHA_DIGEST_LENGTH]=""; char *users_data = NULL; + int ndbs = 0; + /* last byte is for Select_priv=Y|N */ + int users_data_row_len = MYSQL_USER_MAXLEN + MYSQL_HOST_MAXLEN + MYSQL_PASSWORD_LEN + sizeof(char); + int i = 0; + + serviceGetUser(service, &service_user, &service_passwd); + if (service_user == NULL || service_passwd == NULL) + return -1; + + con = mysql_init(NULL); + + if (con == NULL) { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : mysql_init: %s", + mysql_error(con)))); + return -1; + } + + if (mysql_options(con, MYSQL_OPT_USE_REMOTE_CONNECTION, NULL)) { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : failed to set external connection. " + "It is needed for backend server connections. " + "Exiting."))); + return -1; + } + /** + * Attempt to connect to one of the databases database or until we run + * out of databases + * to try + */ + server = service->databases; + dpwd = decryptPassword(service_passwd); + while (server != NULL && (mysql_real_connect(con, + server->name, + service_user, + dpwd, + NULL, + server->port, + NULL, + 0) == NULL)) + { + server = server->nextdb; + } + free(dpwd); + + if (server == NULL) + { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Unable to get user data from backend database " + "for service %s. Missing server information.", + service->name))); + mysql_close(con); + return -1; + } + + if (mysql_query(con, "select count(1) from information_schema.SCHEMATA")) { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Loading database names for service %s encountered " + "error: %s.", + service->name, + mysql_error(con)))); + mysql_close(con); + return -1; + } + result = mysql_store_result(con); + + if (result == NULL) { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Loading database names for service %s encountered " + "error: %s.", + service->name, + mysql_error(con)))); + mysql_close(con); + return -1; + } + num_fields = mysql_num_fields(result); + row = mysql_fetch_row(result); + + ndbs = atoi(row[0]); + + mysql_free_result(result); + + fprintf(stderr, "Found %i schemas\n", ndbs); + + if (!ndbs) { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Counting users for service %s returned 0", + service->name))); + mysql_close(con); + return -1; + } + + service->resources = (char **) calloc(ndbs+1, sizeof(char *)); + for (i = 0; i < ndbs; i++) { + service->resources[i] = NULL; + } + + service->resources[i] = NULL; + + if (mysql_query(con, "SHOW DATABASES")) { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Loading database names for service %s encountered " + "error: %s.", + service->name, + mysql_error(con)))); + mysql_close(con); + return -1; + } + + result = mysql_store_result(con); + + if (result == NULL) { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Loading database names for service %s encountered " + "error: %s.", + service->name, + mysql_error(con)))); + mysql_close(con); + return -1; + } + num_fields = mysql_num_fields(result); + + i = 0; + while ((row = mysql_fetch_row(result))) { + service->resources[i] = strndup(row[0], MYSQL_DATABASE_MAXLEN); + fprintf(stderr, "Found a Database[%i]:[%s]\n", i, service->resources[i]); + + i++; + } + + return ndbs; +} + +/** + * Load the user/passwd form mysql.user table into the service users' hashtable + * environment. + * + * @param service The current service + * @param users The users table into which to load the users + * @return -1 on any error or the number of users inserted (0 means no users at all) + */ +static int +getUsers(SERVICE *service, USERS *users) +{ + MYSQL *con = NULL; + MYSQL_ROW row; + MYSQL_RES *result = NULL; + int num_fields = 0; + char *service_user = NULL; + char *service_passwd = NULL; + char *dpwd; + int total_users = 0; + SERVER *server; + char *users_query; + char *dbs_query; + unsigned char hash[SHA_DIGEST_LENGTH]=""; + char *users_data = NULL; int nusers = 0; - int users_data_row_len = MYSQL_USER_MAXLEN + MYSQL_HOST_MAXLEN + MYSQL_PASSWORD_LEN; - struct sockaddr_in serv_addr; - MYSQL_USER_HOST key; + /* last byte is for Select_priv=Y|N */ + int users_data_row_len = MYSQL_USER_MAXLEN + MYSQL_HOST_MAXLEN + MYSQL_PASSWORD_LEN + sizeof(char); /* enable_root for MySQL protocol module means load the root user credentials from backend databases */ +/* if(service->enable_root) { users_query = LOAD_MYSQL_USERS_QUERY " ORDER BY HOST DESC"; } else { users_query = LOAD_MYSQL_USERS_QUERY USERS_QUERY_NO_ROOT " ORDER BY HOST DESC"; } +*/ + if(service->enable_root) { + users_query = LOAD_MYSQL_USERS_WITH_DB_QUERY; + } else { + users_query = LOAD_MYSQL_USERS_WITH_DB_QUERY_NO_ROOT; + } serviceGetUser(service, &service_user, &service_passwd); if (service_user == NULL || service_passwd == NULL) @@ -306,68 +593,31 @@ getUsers(SERVICE *service, struct users *users) while ((row = mysql_fetch_row(result))) { /** - * Four fields should be returned. - * user and passwd+1 (escaping the first byte that is '*') are - * added to hashtable. + * Six fields should be returned. + * user,host,passwd,concat(),anydb,db + * passwd+1 (escaping the first byte that is '*') */ - char ret_ip[INET_ADDRSTRLEN + 1]=""; - const char *rc; + int rc = 0; - /* prepare the user@host data struct */ - memset(&serv_addr, 0, sizeof(serv_addr)); - memset(&key, 0, sizeof(key)); + rc = add_mysql_users_with_host_ipv4(users, row[0], row[1], strlen(row[2]) ? row[2]+1 : row[2], row[4], row[5]); - /* if host == '%', 0 is passed */ - if (setipaddress(&serv_addr.sin_addr, strcmp(row[1], "%") ? row[1] : "0.0.0.0")) { + if (rc == 1) { + LOGIF(LD, (skygw_log_write_flush( + LOGFILE_DEBUG, + "%lu [mysql_users_add()] Added user %s@%s", + pthread_self(), + row[0], + row[1]))); - key.user = strdup(row[0]); - - if(key.user == NULL) { - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "%lu [getUsers()] strdup() failed for user %s", - pthread_self(), - row[0]))); - - continue; - } - - memcpy(&key.ipv4, &serv_addr, sizeof(serv_addr)); - - rc = inet_ntop(AF_INET, &(serv_addr).sin_addr, ret_ip, INET_ADDRSTRLEN); - - /* add user@host as key and passwd as value in the MySQL users hash table */ - if (mysql_users_add(users, &key, strlen(row[2]) ? row[2]+1 : row[2])) { - LOGIF(LD, (skygw_log_write_flush( - LOGFILE_DEBUG, - "%lu [mysql_users_add()] Added user %s@%s(%s)", - pthread_self(), - row[0], - row[1], - rc == NULL ? "NULL" : ret_ip))); - - /* Append data in the memory area for SHA1 digest */ - strncat(users_data, row[3], users_data_row_len); - - total_users++; - } else { - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "%lu [mysql_users_add()] Failed adding user %s@%s(%s)", - pthread_self(), - row[0], - row[1], - rc == NULL ? "NULL" : ret_ip))); - } - - free(key.user); + /* Append data in the memory area for SHA1 digest */ + strncat(users_data, row[3], users_data_row_len); + total_users++; } else { - /* setipaddress() failed, skip user add and log this*/ LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, - "%lu [getUsers()] setipaddress failed: user %s@%s not added", + "%lu [mysql_users_add()] Failed adding user %s@%s", pthread_self(), row[0], row[1]))); @@ -445,11 +695,23 @@ int add; * @param key The key with user@host * @return The authentication data or NULL on error */ -char *mysql_users_fetch(USERS *users, MYSQL_USER_HOST *key) { +void *mysql_users_fetch(USERS *users, MYSQL_USER_HOST *key) { + HASHENTRIES *matched; + MYSQL_USER_HOST *entry; if (key == NULL) return NULL; + atomic_add(&users->stats.n_fetches, 1); - return hashtable_fetch(users->data, key); + + fprintf(stderr, "-- fetching %s@%i db=[%s]\n", key->user, key->ipv4.sin_addr.s_addr, key->resource); + + matched = (HASHENTRIES *)hashtable_fetch_key(users->data, key); + if (matched) { + entry = (MYSQL_USER_HOST *)matched->key; + fprintf(stderr, "+++++++ Hash match for %s, db %s\n", entry->user, entry->resource); + return matched->value; + } else + return NULL; } /** @@ -475,7 +737,7 @@ static int uh_hfun( void* key) { * Currently only IPv4 addresses are supported * * @param key1 The key value, i.e. username@host (IPv4) - * @param key1 The key value, i.e. username@host (IPv4) + * @param key2 The key value, i.e. username@host (IPv4) * @return The compare value */ @@ -486,8 +748,28 @@ static int uh_cmpfun( void* v1, void* v2) { if (v1 == NULL || v2 == NULL || hu1 == NULL || hu2 == NULL || hu1->user == NULL || hu2->user == NULL) return 0; - if (strcmp(hu1->user, hu2->user) == 0 && (hu1->ipv4.sin_addr.s_addr == hu2->ipv4.sin_addr.s_addr)) { - return 0; + if (strcmp(hu1->user, hu2->user) == 0 && (hu1->ipv4.sin_addr.s_addr == hu2->ipv4.sin_addr.s_addr) && (hu1->netmask >= hu2->netmask)) { + + /* if no database name was passed, auth is ok */ + if (hu1->resource == NULL || (hu1->resource && !strlen(hu1->resource))) { + return 0; + } else { + /* (1) check for no database grants at all and deny auth*/ + if (hu2->resource == NULL) { + return 1; + } + /* (2) check for ANY database grant and allow auth*/ + if (!strlen(hu2->resource)) { + return 0; + } + /* (3) check for database name specific grant and allow auth*/ + if (hu1->resource && hu2->resource && strcmp(hu1->resource,hu2->resource) == 0) { + return 0; + } + + /* no matches, deny auth */ + return 1; + } } else { return 1; } @@ -513,6 +795,10 @@ static void *uh_keydup(void* key) { return NULL; memcpy(&rval->ipv4, ¤t_key->ipv4, sizeof(struct sockaddr_in)); + memcpy(&rval->netmask, ¤t_key->netmask, sizeof(int)); + + if (current_key->resource) + rval->resource = strdup(current_key->resource); return (void *) rval; } @@ -531,6 +817,9 @@ static void uh_keyfree( void* key) { if (current_key && current_key->user) free(current_key->user); + if (current_key && current_key->resource) + free(current_key->resource); + free(key); } @@ -546,7 +835,7 @@ char *mysql_format_user_entry(void *data) MYSQL_USER_HOST *entry; char *mysql_user; /* the returned user string is "USER" + "@" + "HOST" + '\0' */ - int mysql_user_len = MYSQL_USER_MAXLEN + 1 + INET_ADDRSTRLEN + 1; + int mysql_user_len = MYSQL_USER_MAXLEN + 1 + INET_ADDRSTRLEN + 10 + MYSQL_USER_MAXLEN + 1; if (data == NULL) return NULL; @@ -560,14 +849,38 @@ char *mysql_format_user_entry(void *data) if (mysql_user == NULL) return NULL; - - if (entry->ipv4.sin_addr.s_addr == INADDR_ANY) { - snprintf(mysql_user, mysql_user_len, "%s@%%", entry->user); - } else { + + /* format user@host based on wildcards */ + if (entry->ipv4.sin_addr.s_addr == INADDR_ANY && entry->netmask == 0) { + snprintf(mysql_user, mysql_user_len-1, "%s@%%", entry->user); + } else if ( (entry->ipv4.sin_addr.s_addr & 0xFF000000) == 0 && entry->netmask == 24) { + snprintf(mysql_user, mysql_user_len-1, "%s@%i.%i.%i.%%", entry->user, entry->ipv4.sin_addr.s_addr & 0x000000FF, (entry->ipv4.sin_addr.s_addr & 0x0000FF00) / (256), (entry->ipv4.sin_addr.s_addr & 0x00FF0000) / (256 * 256)); + } else if ( (entry->ipv4.sin_addr.s_addr & 0xFFFF0000) == 0 && entry->netmask == 16) { + snprintf(mysql_user, mysql_user_len-1, "%s@%i.%i.%%.%%", entry->user, entry->ipv4.sin_addr.s_addr & 0x000000FF, (entry->ipv4.sin_addr.s_addr & 0x0000FF00) / (256)); + } else if ( (entry->ipv4.sin_addr.s_addr & 0xFFFFFF00) == 0 && entry->netmask == 8) { + snprintf(mysql_user, mysql_user_len-1, "%s@%i.%%.%%.%%", entry->user, entry->ipv4.sin_addr.s_addr & 0x000000FF); + } else if (entry->netmask == 32) { strncpy(mysql_user, entry->user, MYSQL_USER_MAXLEN); strcat(mysql_user, "@"); inet_ntop(AF_INET, &(entry->ipv4).sin_addr, mysql_user+strlen(mysql_user), INET_ADDRSTRLEN); + } else { + snprintf(mysql_user, MYSQL_USER_MAXLEN-5, "Err: %s", entry->user); + strcat(mysql_user, "@"); + inet_ntop(AF_INET, &(entry->ipv4).sin_addr, mysql_user+strlen(mysql_user), INET_ADDRSTRLEN); + + } + + if (entry->resource) { + if (strlen(entry->resource)) { + strcat(mysql_user, " db: "); + strcat(mysql_user, entry->resource); + } else { + strcat(mysql_user, " db: ANY"); + } + } else { + strcat(mysql_user, " no db"); } return mysql_user; } + diff --git a/server/core/test/CMakeLists.txt b/server/core/test/CMakeLists.txt index 14a80cd36..fcbd343e2 100644 --- a/server/core/test/CMakeLists.txt +++ b/server/core/test/CMakeLists.txt @@ -1,37 +1,16 @@ +add_executable(test_mysql_users test_mysql_users.c) add_executable(test_hash testhash.c) -add_executable(test_hint testhint.c) add_executable(test_spinlock testspinlock.c) add_executable(test_filter testfilter.c) -add_executable(test_buffer testbuffer.c) -add_executable(test_dcb testdcb.c) -add_executable(test_modutil testmodutil.c) -add_executable(test_poll testpoll.c) -add_executable(test_service testservice.c) -add_executable(test_server testserver.c) -add_executable(test_users testusers.c) add_executable(test_adminusers testadminusers.c) +target_link_libraries(test_mysql_users fullcore MySQLClient) target_link_libraries(test_hash fullcore) -target_link_libraries(test_hint fullcore) target_link_libraries(test_spinlock fullcore) target_link_libraries(test_filter fullcore) -target_link_libraries(test_buffer fullcore) -target_link_libraries(test_dcb fullcore) -target_link_libraries(test_modutil fullcore) -target_link_libraries(test_poll fullcore) -target_link_libraries(test_service fullcore) -target_link_libraries(test_server fullcore) -target_link_libraries(test_users fullcore) target_link_libraries(test_adminusers fullcore) +add_test(testMySQLUsers test_mysql_users) add_test(TestHash test_hash) -add_test(TestHint test_hint) add_test(TestSpinlock test_spinlock) add_test(TestFilter test_filter) -add_test(TestBuffer test_buffer) -add_test(TestDCB test_dcb) -add_test(TestModutil test_modutil) -add_test(TestPoll test_poll) -add_test(TestService test_service) -add_test(TestServer test_server) -add_test(TestUsers test_users) add_test(TestAdminUsers test_adminusers) diff --git a/server/core/test/test_mysql_users.c b/server/core/test/test_mysql_users.c index 7dc789d83..73ab57d66 100644 --- a/server/core/test/test_mysql_users.c +++ b/server/core/test/test_mysql_users.c @@ -24,6 +24,7 @@ * Date Who Description * 14/02/2014 Massimiliano Pinto Initial implementation * 17/02/2014 Massimiliano Pinto Added check ipv4 + * 03/10/2014 Massimiliano Pinto Added check for wildcard hosts * * @endverbatim */ @@ -39,6 +40,7 @@ #include #include #include +#include #include @@ -51,6 +53,7 @@ int set_and_get_single_mysql_users_ipv4(char *username, unsigned long ipv4, char USERS *mysql_users; char ret_ip[200]=""; char *fetch_data; + char *db=""; unsigned long fix_ipv4; @@ -69,6 +72,7 @@ int set_and_get_single_mysql_users_ipv4(char *username, unsigned long ipv4, char key.user = username; memcpy(&key.ipv4, &serv_addr, sizeof(serv_addr)); + key.resource = db; inet_ntop(AF_INET, &(serv_addr).sin_addr, ret_ip, INET_ADDRSTRLEN); @@ -85,6 +89,7 @@ int set_and_get_single_mysql_users_ipv4(char *username, unsigned long ipv4, char find_key.user = username; memcpy(&(serv_addr).sin_addr.s_addr, &ipv4, sizeof(ipv4)); + find_key.resource = db; memcpy(&find_key.ipv4, &serv_addr, sizeof(serv_addr)); @@ -101,10 +106,10 @@ int set_and_get_single_mysql_users_ipv4(char *username, unsigned long ipv4, char int set_and_get_single_mysql_users(char *username, char *hostname, char *password) { struct sockaddr_in serv_addr; MYSQL_USER_HOST key; - MYSQL_USER_HOST find_key; USERS *mysql_users; char ret_ip[200]=""; char *fetch_data; + char *db=""; mysql_users = mysql_users_alloc(); /* prepare the user@host data struct */ @@ -121,6 +126,7 @@ int set_and_get_single_mysql_users(char *username, char *hostname, char *passwor key.user = username; memcpy(&key.ipv4, &serv_addr, sizeof(serv_addr)); + key.resource = db; inet_ntop(AF_INET, &(serv_addr).sin_addr, ret_ip, INET_ADDRSTRLEN); @@ -133,7 +139,6 @@ int set_and_get_single_mysql_users(char *username, char *hostname, char *passwor } memset(&serv_addr, 0, sizeof(serv_addr)); - memset(&find_key, 0, sizeof(key)); if (hostname) if(!setipaddress(&serv_addr.sin_addr, hostname)) { @@ -142,6 +147,7 @@ int set_and_get_single_mysql_users(char *username, char *hostname, char *passwor } key.user = username; memcpy(&key.ipv4, &serv_addr, sizeof(serv_addr)); + key.resource = db; fetch_data = mysql_users_fetch(mysql_users, &key); @@ -153,6 +159,81 @@ int set_and_get_single_mysql_users(char *username, char *hostname, char *passwor return 0; } +int set_and_get_mysql_users_wildcards(char *username, char *hostname, char *password, char *from, char *anydb, char *db, char *db_from) { + USERS *mysql_users; + int ret; + struct sockaddr_in client_addr; + DCB *dcb; + SERVICE *service; + MYSQL_session data; + + dcb = dcb_alloc(DCB_ROLE_INTERNAL); + + if (dcb == NULL) { + fprintf(stderr, "dcb_alloc() failed\n"); + return 1; + } + if ((service = (SERVICE *)calloc(1, sizeof(SERVICE))) == NULL) { + fprintf(stderr, "service_alloc() failed\n"); + return 1; + } + + memset(&client_addr, 0, sizeof(client_addr)); + + if (hostname) { + if(!setipaddress(&client_addr.sin_addr, from)) { + fprintf(stderr, "setipaddress failed for host [%s]\n", from); + return 1; + } + } + + /* client IPv4 in raw data*/ + memcpy(&dcb->ipv4, (struct sockaddr_in *)&client_addr, sizeof(struct sockaddr_in)); + + dcb->service = service; + + mysql_users = mysql_users_alloc(); + + service->users = mysql_users; + + if (db_from != NULL) + strcpy(data.db, db_from); + else + strcpy(data.db, ""); + + dcb->data = &data; + + // the routine returns 1 on success + if (anydb) { + if (strcmp(anydb, "N") == 0) { + ret = add_mysql_users_with_host_ipv4(mysql_users, username, hostname, password, anydb, db); + } else if (strcmp(anydb, "Y") == 0) { + ret = add_mysql_users_with_host_ipv4(mysql_users, username, hostname, password, "Y", ""); + } else { + ret = add_mysql_users_with_host_ipv4(mysql_users, username, hostname, password, "N", NULL); + } + } else { + ret = add_mysql_users_with_host_ipv4(mysql_users, username, hostname, password, "N", NULL); + } + + + if (!ret) { + fprintf(stderr, "add_mysql_users_with_host_ipv4 (%s@%s, %s) FAILED\n", username, hostname, password); + return 1; + } else { + char db_passwd[100]=""; + + dcb->remote=strdup(from); + //fprintf(stderr, "add_mysql_users_with_host_ipv4 passed(%s@%s, %s) OK\n", username, hostname, password); + + fprintf(stderr, "Checking '%s' @ '%s' against (%s@%s)\n", username, from, username, hostname); + + // returns 0 on success + ret = gw_find_mysql_user_password_sha1(username, db_passwd, dcb); + } + + return ret; +} int main() { int ret; @@ -166,6 +247,7 @@ int main() { fprintf(stderr, "%s\n", asctime(localtime(&t))); fprintf(stderr, ">>> Started MySQL load, set & get users@host\n"); + ret = set_and_get_single_mysql_users("pippo", "localhost", "xyz"); assert(ret == 0); ret = set_and_get_single_mysql_users("pippo", "127.0.0.2", "xyz"); @@ -193,15 +275,102 @@ int main() { char user[129] = ""; snprintf(user, 128, "user_%i", k); ret = set_and_get_single_mysql_users_ipv4(user, i, "JJcd"); + assert(ret == 0); k++; } + ret = set_and_get_mysql_users_wildcards("pippo", "%", "one", "127.0.0.1", NULL, NULL, NULL); + if (ret) fprintf(stderr, "\t-- Expecting no match\n"); + assert(ret == 1); + + ret = set_and_get_mysql_users_wildcards("pippo", "%", "", "127.0.0.1", NULL, NULL, NULL); + if (ret) fprintf(stderr, "\t-- Expecting no match\n"); + assert(ret == 1); + + ret = set_and_get_mysql_users_wildcards("pippo", "%", "two", "192.168.2.2", NULL, NULL, NULL); + if (!ret) fprintf(stderr, "\t-- Expecting ok\n"); + assert(ret == 0); + + ret = set_and_get_mysql_users_wildcards("pippo", "192.168.1.%", "foo", "192.168.2.2", NULL, NULL, NULL); + if (ret) fprintf(stderr, "\t-- Expecting no match\n"); + assert(ret == 1); + + ret = set_and_get_mysql_users_wildcards("pippo", "192.168.%.%", "foo", "192.168.2.2", NULL, NULL, NULL); + if (!ret) fprintf(stderr, "\t-- Expecting ok\n"); + assert(ret == 0); + + ret = set_and_get_mysql_users_wildcards("pippo", "192.%.%.%", "foo", "192.68.0.2", NULL, NULL, NULL); + if (!ret) fprintf(stderr, "\t-- Expecting ok\n"); + assert(ret == 0); + + fprintf(stderr, "Adding pippo, 192.%%.%%.%%, foo, 192.0.0.2, Y, NULL, cossa\n"); + ret = set_and_get_mysql_users_wildcards("pippo", "192.%.%.%", "foo", "192.0.0.2", "Y", NULL, "cossa"); + if (!ret) fprintf(stderr, "\t-- Expecting ok\n"); + assert(ret == 0); + + fprintf(stderr, "Adding pippo, 192.%%.%%.%%, foo, 192.0.0.2, N, NULL, ragione\n"); + ret = set_and_get_mysql_users_wildcards("pippo", "192.%.%.%", "foo", "192.0.0.2", "N", NULL, "ragione"); + if (!ret) fprintf(stderr, "\t-- Expecting no match\n"); + assert(ret == 1); + + ret = set_and_get_mysql_users_wildcards("pippo", "192.0.%.%", "foo", "192.2.0.2", NULL, NULL, NULL); + if (ret) fprintf(stderr, "\t-- Expecting no match\n"); + assert(ret == 1); + + ret = set_and_get_mysql_users_wildcards("pippo", "192.0.0.1", "foo", "192.0.0.2", NULL, NULL, NULL); + if (ret) fprintf(stderr, "\t-- Expecting no match\n"); + assert(ret == 1); + + ret = set_and_get_mysql_users_wildcards("pippo", "192.0.%.%", "foo", "192.1.0.2", NULL, NULL, NULL); + if (ret) fprintf(stderr, "\t-- Expecting no match\n"); + assert(ret == 1); + + ret = set_and_get_mysql_users_wildcards("pippo", "192.0.0.%", "foo", "192.3.2.1", NULL, NULL, NULL); + if (ret) fprintf(stderr, "\t-- Expecting no match\n"); + assert(ret == 1); + + ret = set_and_get_mysql_users_wildcards("pippo", "192.0.%.%", "foo", "192.3.2.1", NULL, NULL, NULL); + if (ret) fprintf(stderr, "\t-- Expecting no match\n"); + assert(ret == 1); + + ret = set_and_get_mysql_users_wildcards("pippo", "192.%.%.%", "foo", "192.254.254.245", "N", "matto", "matto"); + if (!ret) fprintf(stderr, "\t-- Expecting ok\n"); + assert(ret == 0); + + ret = set_and_get_mysql_users_wildcards("pippo", "192.%.%.%", "foo", "192.254.254.245", "N", "matto", "fatto"); + if (!ret) fprintf(stderr, "\t-- Expecting no match\n"); + assert(ret == 1); + + ret = set_and_get_mysql_users_wildcards("pippo", "192.%.%.%", "foo", "192.254.254.245", "Y", "matto", "fatto"); + if (!ret) fprintf(stderr, "\t-- Expecting ok\n"); + assert(ret == 0); + + ret = set_and_get_mysql_users_wildcards("pippo", "192.%.%.%", "foo", "192.254.254.245", "Y", "", "fto"); + if (!ret) fprintf(stderr, "\t-- Expecting ok\n"); + assert(ret == 0); + + ret = set_and_get_mysql_users_wildcards("pippo", "192.%.%.%", "foo", "192.254.254.245", "Y", NULL, "grewao"); + if (!ret) fprintf(stderr, "\t-- Expecting ok\n"); + assert(ret == 0); + + ret = set_and_get_mysql_users_wildcards("pippo", "192.%.%.%", "foo", "192.254.254.242", NULL, NULL, NULL); + if (!ret) fprintf(stderr, "\t-- Expecting ok\n"); + assert(ret == 0); + + ret = set_and_get_mysql_users_wildcards("riccio", "192.0.0.%", "foo", "192.134.0.2", NULL, NULL, NULL); + if (ret) fprintf(stderr, "\t-- Expecting no match\n"); + assert(ret == 1); + + ret = set_and_get_mysql_users_wildcards("pippo", "192.%.%.%", "foo", "192.254.254.245", "Y", NULL, NULL); + if (!ret) fprintf(stderr, "\t-- Expecting ok\n"); + assert(ret == 0); + fprintf(stderr, "----------------\n"); fprintf(stderr, "<<< Test completed\n"); time(&t); fprintf(stderr, "%s\n", asctime(localtime(&t))); - return ret; + return 0; } diff --git a/server/include/dbusers.h b/server/include/dbusers.h index f9f4a50ab..3397a7a6a 100644 --- a/server/include/dbusers.h +++ b/server/include/dbusers.h @@ -32,6 +32,7 @@ * 25/06/13 Mark Riddoch Initial implementation * 25/02/13 Massimiliano Pinto Added users table refresh rate default values * 28/02/14 Massimiliano Pinto Added MySQL user and host data structure + * 03/10/14 Massimiliano Pinto Added netmask to MySQL user and host data structure * * @endverbatim */ @@ -52,12 +53,15 @@ typedef struct mysql_user_host_key { char *user; struct sockaddr_in ipv4; + int netmask; + char *resource; } MYSQL_USER_HOST; extern int load_mysql_users(SERVICE *service); extern int reload_mysql_users(SERVICE *service); extern int mysql_users_add(USERS *users, MYSQL_USER_HOST *key, char *auth); +extern int add_mysql_users_with_host_ipv4(USERS *users, char *user, char *host, char *passwd, char *anydb, char *db); extern USERS *mysql_users_alloc(); -extern char *mysql_users_fetch(USERS *users, MYSQL_USER_HOST *key); +extern void *mysql_users_fetch(USERS *users, MYSQL_USER_HOST *key); extern int replace_mysql_users(SERVICE *service); #endif diff --git a/server/modules/protocol/mysql_backend.c b/server/modules/protocol/mysql_backend.c index 049455686..2772e8e7b 100644 --- a/server/modules/protocol/mysql_backend.c +++ b/server/modules/protocol/mysql_backend.c @@ -1,5 +1,5 @@ /* - * This file is distributed as part of the MariaDB Corporation MaxScale. It is free + * This file is distributed as part of the SkySQL Gateway. It is free * software: you can redistribute it and/or modify it under the terms of the * GNU General Public License as published by the Free Software Foundation, * version 2. @@ -13,7 +13,7 @@ * this program; if not, write to the Free Software Foundation, Inc., 51 * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * - * Copyright MariaDB Corporation Ab 2013-2014 + * Copyright SkySQL Ab 2013 */ #include "mysql_client_server_protocol.h" @@ -27,7 +27,7 @@ * Revision History * Date Who Description * 14/06/2013 Mark Riddoch Initial version - * 17/06/2013 Massimiliano Pinto Added MaxScale To Backends routines + * 17/06/2013 Massimiliano Pinto Added Gateway To Backends routines * 27/06/2013 Vilho Raatikka Added skygw_log_write command as an example * and necessary headers. * 01/07/2013 Massimiliano Pinto Put Log Manager example code behind SS_DEBUG macros. @@ -199,7 +199,6 @@ static int gw_read_backend_event(DCB *dcb) { if (backend_protocol->protocol_auth_state == MYSQL_CONNECTED) { - /** Read cached backend handshake */ if (gw_read_backend_handshake(backend_protocol) != 0) { backend_protocol->protocol_auth_state = MYSQL_AUTH_FAILED; @@ -210,13 +209,11 @@ static int gw_read_backend_event(DCB *dcb) { "state = MYSQL_AUTH_FAILED.", pthread_self(), backend_protocol->owner_dcb->fd))); + } - else + else { - /** - * Decode password and send the auth credentials - * to backend. - */ + /* handshake decoded, send the auth credentials */ if (gw_send_authentication_to_backend( current_session->db, current_session->user, @@ -230,16 +227,16 @@ static int gw_read_backend_event(DCB *dcb) { "gw_send_authentication_to_backend " "fd %d, state = MYSQL_AUTH_FAILED.", pthread_self(), - backend_protocol->owner_dcb->fd))); - } - else + backend_protocol->owner_dcb->fd))); + } + else { backend_protocol->protocol_auth_state = MYSQL_AUTH_RECV; } } } spinlock_release(&dcb->authlock); - } /*< backend_protocol->protocol_auth_state == MYSQL_CONNECTED */ + } /* * Now: * -- check the authentication reply from backend @@ -269,10 +266,9 @@ static int gw_read_backend_event(DCB *dcb) { router_instance = session->service->router_instance; rsession = session->router_session; - if (backend_protocol->protocol_auth_state == MYSQL_AUTH_RECV) - { - /** - * Read backed's reply to authentication message + if (backend_protocol->protocol_auth_state == MYSQL_AUTH_RECV) { + /*< + * Read backed auth reply */ receive_rc = gw_receive_backend_auth(backend_protocol); @@ -287,6 +283,7 @@ static int gw_read_backend_event(DCB *dcb) { "fd %d, state = MYSQL_AUTH_FAILED.", pthread_self(), backend_protocol->owner_dcb->fd))); + LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, @@ -365,8 +362,8 @@ static int gw_read_backend_event(DCB *dcb) { 0, "Authentication with backend failed. " "Session will be closed."); - - router->handleError(router_instance, + + router->handleError(router_instance, rsession, errbuf, dcb, @@ -374,6 +371,7 @@ static int gw_read_backend_event(DCB *dcb) { &succp); ss_dassert(!succp); + LOGIF(LD, (skygw_log_write( LOGFILE_DEBUG, "%lu [gw_read_backend_event] " @@ -414,7 +412,7 @@ static int gw_read_backend_event(DCB *dcb) { } } } /* MYSQL_AUTH_RECV || MYSQL_AUTH_FAILED */ - + spinlock_release(&dcb->authlock); } /* MYSQL_AUTH_RECV || MYSQL_AUTH_FAILED */ @@ -880,10 +878,6 @@ static int gw_create_backend_connection( goto return_fd; } - /** Copy client flags to backend protocol */ - protocol->client_capabilities = - ((MySQLProtocol *)(backend_dcb->session->client->protocol))->client_capabilities; - /*< if succeed, fd > 0, -1 otherwise */ rv = gw_do_connect_to_backend(server->name, server->port, &fd); /*< Assign protocol with backend_dcb */ @@ -1054,10 +1048,6 @@ gw_backend_close(DCB *dcb) mysql_protocol_done(dcb); - /** - * If session->state is set to STOPPING the client and the session must - * be closed too. - */ if (session != NULL && session->state == SESSION_STATE_STOPPING) { client_dcb = session->client; diff --git a/server/modules/protocol/mysql_client.c b/server/modules/protocol/mysql_client.c index 06f83f3e1..974ad37e6 100644 --- a/server/modules/protocol/mysql_client.c +++ b/server/modules/protocol/mysql_client.c @@ -1,5 +1,5 @@ /* - * This file is distributed as part of the MariaDB Corporation MaxScale. It is free + * This file is distributed as part of the SkySQL Gateway. It is free * software: you can redistribute it and/or modify it under the terms of the * GNU General Public License as published by the Free Software Foundation, * version 2. @@ -13,7 +13,7 @@ * this program; if not, write to the Free Software Foundation, Inc., 51 * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * - * Copyright MariaDB Corporation Ab 2013-2014 + * Copyright SkySQL Ab 2013 */ /** @@ -25,7 +25,7 @@ * Revision History * Date Who Description * 14/06/2013 Mark Riddoch Initial version - * 17/06/2013 Massimiliano Pinto Added Client To MaxScale routines + * 17/06/2013 Massimiliano Pinto Added Client To Gateway routines * 24/06/2013 Massimiliano Pinto Added: fetch passwords from service users' hashtable * 02/09/2013 Massimiliano Pinto Added: session refcount * 16/12/2013 Massimiliano Pinto Added: client closed socket detection with recv(..., MSG_PEEK) @@ -68,8 +68,6 @@ 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. @@ -374,9 +372,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 If succeed, otherwise non-zero value + * @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 * */ @@ -434,10 +432,12 @@ static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue) { &protocol->client_capabilities); */ - username = get_username_from_auth(username, client_auth_packet); - - if (username == NULL) - { + /* 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)) { 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 */ @@ -485,86 +485,33 @@ static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue) { if (auth_token) free(auth_token); + + if (database) { + int i = 0; + int db_exists = 0; + while(dcb->service->resources[i]) { + if (strncmp(database, dcb->service->resources[i], MYSQL_DATABASE_MAXLEN) == 0) { + db_exists = 1; + } + + i++; + } + + if (!db_exists) { + auth_ret = 2; + } + } + if (auth_ret == 0) { 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 memory allocation - * failed or if username was empty. - */ -static char* get_username_from_auth( - char* ptr, - uint8_t* data) -{ - char* first_letter; - char* rval; - - 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+1)) == NULL) - { - goto retblock; - } - } - else - { - rval = ptr; - } - snprintf(rval, MYSQL_USER_MAXLEN+1, "%s", first_letter); - -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 + * Write function for client DCB: writes data from Gateway to Client * * @param dcb The DCB of the client * @param queue Queue of buffers to write @@ -663,83 +610,85 @@ int gw_read_client_event( case MYSQL_AUTH_SENT: { - int auth_val; - + int auth_val = -1; + 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); - - 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); + 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); - 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); - } + 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 */ + if (auth_val == 2) { + mysql_send_auth_error( + dcb, + 2, + 0, + "Database not existent"); + } else { + mysql_send_auth_error( + dcb, + 2, + 0, + "Authorization failed"); + } + + dcb_close(dcb); + } + } break; case MYSQL_IDLE: @@ -877,12 +826,9 @@ int gw_read_client_event( } /** succeed */ - if (rc) - { + if (rc) { rc = 0; /**< here '0' means success */ - } - else - { + } else { GWBUF* errbuf; bool succp; @@ -1439,12 +1385,20 @@ gw_client_close(DCB *dcb) CHK_SESSION(session); spinlock_acquire(&session->ses_lock); - if (session->state != SESSION_STATE_STOPPING) + if (session->state == SESSION_STATE_STOPPING) { - session->state = SESSION_STATE_STOPPING; - } - spinlock_release(&session->ses_lock); - + /** + * Session is already getting closed so avoid + * redundant calls + */ + spinlock_release(&session->ses_lock); + return 1; + } + else + { + session->state = SESSION_STATE_STOPPING; + spinlock_release(&session->ses_lock); + } router = session->service->router; router_instance = session->service->router_instance; rsession = session->router_session; diff --git a/server/modules/protocol/mysql_common.c b/server/modules/protocol/mysql_common.c index 684829fdf..aeb40a1a1 100644 --- a/server/modules/protocol/mysql_common.c +++ b/server/modules/protocol/mysql_common.c @@ -31,6 +31,9 @@ * localhost entry should be added for the selected user in the backends. * Setting to 1 allow localhost (127.0.0.1 or socket) to match the any host grant via * user@% + * 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. * */ @@ -463,7 +466,6 @@ int gw_receive_backend_auth( bufstr))); free(bufstr); - free(err); rc = -1; } else @@ -538,9 +540,9 @@ int gw_receive_backend_auth( * @return 0 on success, 1 on failure */ int gw_send_authentication_to_backend( - char *dbname, - char *user, - uint8_t *passwd, + char *dbname, + char *user, + uint8_t *passwd, MySQLProtocol *conn) { int compress = 0; @@ -550,8 +552,8 @@ int gw_send_authentication_to_backend( long bytes; uint8_t client_scramble[GW_MYSQL_SCRAMBLE_SIZE]; uint8_t client_capabilities[4]; - uint32_t server_capabilities = 0; - uint32_t final_capabilities = 0; + uint32_t server_capabilities; + uint32_t final_capabilities; char dbpass[MYSQL_USER_MAXLEN + 1]=""; GWBUF *buffer; DCB *dcb; @@ -566,12 +568,17 @@ int gw_send_authentication_to_backend( curr_passwd = passwd; dcb = conn->owner_dcb; + + // Zero the vars + memset(&server_capabilities, '\0', sizeof(server_capabilities)); + memset(&final_capabilities, '\0', sizeof(final_capabilities)); + final_capabilities = gw_mysql_get_byte4((uint8_t *)&server_capabilities); - /** Copy client's flags to backend */ - final_capabilities |= conn->client_capabilities;; + final_capabilities |= GW_MYSQL_CAPABILITIES_PROTOCOL_41; + final_capabilities |= GW_MYSQL_CAPABILITIES_CLIENT; - if (compress) { + if (compress) { final_capabilities |= GW_MYSQL_CAPABILITIES_COMPRESS; #ifdef DEBUG_MYSQL_CONN fprintf(stderr, ">>>> Backend Connection with compression\n"); @@ -1026,24 +1033,19 @@ int mysql_send_custom_error ( * @param passwd The SHA1(real_password): Note real_password is unknown * @return 1 on success, 0 on failure */ -int gw_send_change_user_to_backend( - char *dbname, - char *user, - uint8_t *passwd, - MySQLProtocol *conn) -{ - int compress = 0; - int rv; - uint8_t *payload = NULL; - uint8_t *payload_start = NULL; - long bytes; - uint8_t client_scramble[GW_MYSQL_SCRAMBLE_SIZE]; - uint8_t client_capabilities[4]; - uint32_t server_capabilities = 0; - uint32_t final_capabilities = 0; - char dbpass[MYSQL_USER_MAXLEN + 1]=""; - GWBUF *buffer; - DCB *dcb; +int gw_send_change_user_to_backend(char *dbname, char *user, uint8_t *passwd, MySQLProtocol *conn) { + int compress = 0; + int rv; + uint8_t *payload = NULL; + uint8_t *payload_start = NULL; + long bytes; + uint8_t client_scramble[GW_MYSQL_SCRAMBLE_SIZE]; + uint8_t client_capabilities[4]; + uint32_t server_capabilities; + uint32_t final_capabilities; + char dbpass[MYSQL_USER_MAXLEN + 1]=""; + GWBUF *buffer; + DCB *dcb; char *curr_db = NULL; uint8_t *curr_passwd = NULL; @@ -1056,10 +1058,14 @@ int gw_send_change_user_to_backend( dcb = conn->owner_dcb; - final_capabilities = gw_mysql_get_byte4((uint8_t *)&server_capabilities); + // Zero the vars + memset(&server_capabilities, '\0', sizeof(server_capabilities)); + memset(&final_capabilities, '\0', sizeof(final_capabilities)); - /** Copy client's flags to backend */ - final_capabilities |= conn->client_capabilities;; + final_capabilities = gw_mysql_get_byte4((uint8_t *)&server_capabilities); + + final_capabilities |= GW_MYSQL_CAPABILITIES_PROTOCOL_41; + final_capabilities |= GW_MYSQL_CAPABILITIES_CLIENT; if (compress) { final_capabilities |= GW_MYSQL_CAPABILITIES_COMPRESS; @@ -1310,7 +1316,7 @@ int gw_check_mysql_scramble_data(DCB *dcb, uint8_t *token, unsigned int token_le /** * gw_find_mysql_user_password_sha1 * - * The routine fetches look for an user int he MaxScale users' table + * The routine fetches look for an user int the MaxScale users' table * The users' table is dcb->service->users or a different one specified with void *repository * * If found the HEX password, representing sha1(sha1(password)), is converted in binary data and @@ -1328,12 +1334,18 @@ int gw_find_mysql_user_password_sha1(char *username, uint8_t *gateway_password, struct sockaddr_in *client; char *user_password = NULL; MYSQL_USER_HOST key; + MYSQL_session *client_data = NULL; + + client_data = dcb->data; service = (SERVICE *) dcb->service; client = (struct sockaddr_in *) &dcb->ipv4; key.user = username; + key.resource = client_data->db; + memcpy(&key.ipv4, client, sizeof(struct sockaddr_in)); + key.netmask = 32; LOGIF(LD, (skygw_log_write_flush( @@ -1343,73 +1355,118 @@ int gw_find_mysql_user_password_sha1(char *username, uint8_t *gateway_password, key.user, dcb->remote))); - /* look for user@current_host now */ + /* look for user@current_ipv4 now */ user_password = mysql_users_fetch(service->users, &key); if (!user_password) { - /* The user is not authenticated @ current host */ + /* The user is not authenticated @ current IPv4 */ - /* 1) Check for localhost first. - * The check for localhost is 127.0.0.1 (IPv4 only) - */ - - if ((key.ipv4.sin_addr.s_addr == 0x0100007F) && !dcb->service->localhost_match_wildcard_host) { - /* Skip the wildcard check and return 1 */ - LOGIF(LE, - (skygw_log_write_flush( - LOGFILE_ERROR, - "Error : user %s@%s not found, try set " - "'localhost_match_wildcard_host=1' in " - "service definition of the configuration " - "file.", - key.user, - dcb->remote))); - - return 1; - } - - /* 2) Continue and check for wildcard host, user@% - * Return 1 if no match - */ - - memset(&key.ipv4, 0, sizeof(struct sockaddr_in)); - - LOGIF(LD, - (skygw_log_write_flush( - LOGFILE_DEBUG, - "%lu [MySQL Client Auth], checking user [%s@%s] with wildcard host [%%]", - pthread_self(), - key.user, - dcb->remote))); - - user_password = mysql_users_fetch(service->users, &key); - - if (!user_password) { - /* the user@% was not found. - * Return 1 + while (1) { + /* + * (1) Check for localhost first: 127.0.0.1 (IPv4 only) */ + + if ((key.ipv4.sin_addr.s_addr == 0x0100007F) && !dcb->service->localhost_match_wildcard_host) { + /* Skip the wildcard check and return 1 */ + LOGIF(LE, + (skygw_log_write_flush( + LOGFILE_ERROR, + "%lu [MySQL Client Auth], user [%s@%s] not found, please try with 'localhost_match_wildcard_host=1' in service definition", + pthread_self(), + key.user, + dcb->remote))); + + break; + } + + /* + * (2) check for possible IPv4 class C,B,A networks + */ + + /* Class C check */ + key.ipv4.sin_addr.s_addr &= 0x00FFFFFF; + key.netmask -= 8; + + user_password = mysql_users_fetch(service->users, &key); + + if (user_password) { + break; + } + + /* Class B check */ + key.ipv4.sin_addr.s_addr &= 0x0000FFFF; + key.netmask -= 8; + + user_password = mysql_users_fetch(service->users, &key); + + if (user_password) { + break; + } + + /* Class A check */ + key.ipv4.sin_addr.s_addr &= 0x000000FF; + key.netmask -= 8; + + user_password = mysql_users_fetch(service->users, &key); + + if (user_password) { + break; + } + + /* + * (3) Continue check for wildcard host, user@% + */ + + memset(&key.ipv4, 0, sizeof(struct sockaddr_in)); + key.netmask = 0; + LOGIF(LD, (skygw_log_write_flush( LOGFILE_DEBUG, - "%lu [MySQL Client Auth], user [%s@%s] not existent", + "%lu [MySQL Client Auth], checking user [%s@%s] with wildcard host [%%]", pthread_self(), key.user, dcb->remote))); - return 1; + + user_password = mysql_users_fetch(service->users, &key); + + if (!user_password) { + /* + * the user@% has not been found. + */ + + LOGIF(LD, + (skygw_log_write_flush( + LOGFILE_DEBUG, + "%lu [MySQL Client Auth], user [%s@%s] not existent", + pthread_self(), + key.user, + dcb->remote))); + break; + } + + break; } } - /* user@host found: now check the password - * - * Convert the hex data (40 bytes) to binary (20 bytes). - * The gateway_password represents the SHA1(SHA1(real_password)). - * Please note: the real_password is unknown and SHA1(real_password) is unknown as well - */ + /* If user@host has been found we get the the password in binary format*/ + if (user_password) { + /* + * Convert the hex data (40 bytes) to binary (20 bytes). + * The gateway_password represents the SHA1(SHA1(real_password)). + * Please note: the real_password is unknown and SHA1(real_password) is unknown as well + */ + //char *the_password = user_password->value; + //MYSQL_USER_HOST *matched_key = (MYSQL_USER_HOST *)user_password->key; - if (strlen(user_password)) - gw_hex2bin(gateway_password, user_password, SHA_DIGEST_LENGTH * 2); - return 0; + if (strlen(user_password)) + gw_hex2bin(gateway_password, user_password, SHA_DIGEST_LENGTH * 2); + + return 0; + } else { + return 1; + } } /** @@ -1457,7 +1514,7 @@ mysql_send_auth_error ( } mysql_errno = 1045; mysql_error_msg = "Access denied!"; - mysql_state = "28000"; + mysql_state = "2800"; field_count = 0xff; gw_mysql_set_byte2(mysql_err, mysql_errno); @@ -1652,11 +1709,9 @@ void protocol_archive_srv_command( s1 = &p->protocol_command; - LOGIF(LD, (skygw_log_write( - LOGFILE_DEBUG, - "%lu [protocol_archive_srv_command] Move command %s from fd %d " - "to command history.", - pthread_self(), + LOGIF(LT, (skygw_log_write( + LOGFILE_TRACE, + "Move command %s from fd %d to command history.", STRPACKETTYPE(s1->scom_cmd), p->owner_dcb->fd))); @@ -1728,8 +1783,8 @@ void protocol_add_srv_command( p->protocol_command.scom_next = server_command_init(NULL, cmd); } - LOGIF(LD, (skygw_log_write( - LOGFILE_DEBUG, + LOGIF(LT, (skygw_log_write( + LOGFILE_TRACE, "Added command %s to fd %d.", STRPACKETTYPE(cmd), p->owner_dcb->fd))); @@ -1739,8 +1794,8 @@ void protocol_add_srv_command( while (c != NULL && c->scom_cmd != MYSQL_COM_UNDEFINED) { - LOGIF(LD, (skygw_log_write( - LOGFILE_DEBUG, + LOGIF(LT, (skygw_log_write( + LOGFILE_TRACE, "fd %d : %d %s", p->owner_dcb->fd, c->scom_cmd, From d829c323b9cab7fcefc527c86f097623a0c0c549 Mon Sep 17 00:00:00 2001 From: MassimilianoPinto Date: Mon, 13 Oct 2014 09:55:42 +0200 Subject: [PATCH 02/31] Header history update Header history update --- server/core/dbusers.c | 1 + server/include/dbusers.h | 1 + 2 files changed, 2 insertions(+) diff --git a/server/core/dbusers.c b/server/core/dbusers.c index ff1c78c44..2a2ad3ae2 100644 --- a/server/core/dbusers.c +++ b/server/core/dbusers.c @@ -31,6 +31,7 @@ * 29/09/2014 Massimiliano Pinto Added Mysql user@host authentication with wildcard in IPv4 hosts: * x.y.z.%, x.y.%.%, x.%.%.% * 03/10/14 Massimiliano Pinto Added netmask to user@host authentication for wildcard in IPv4 hosts + * 13/10/14 Massimiliano Pinto Added (user@host)@db authentication * * @endverbatim */ diff --git a/server/include/dbusers.h b/server/include/dbusers.h index 3397a7a6a..57b205333 100644 --- a/server/include/dbusers.h +++ b/server/include/dbusers.h @@ -33,6 +33,7 @@ * 25/02/13 Massimiliano Pinto Added users table refresh rate default values * 28/02/14 Massimiliano Pinto Added MySQL user and host data structure * 03/10/14 Massimiliano Pinto Added netmask to MySQL user and host data structure + * 13/10/14 Massimiliano Pinto Added resource to MySQL user and host data structure * * @endverbatim */ From e22f7da0919727a6254a7cf8b41980665b76e35f Mon Sep 17 00:00:00 2001 From: MassimilianoPinto Date: Mon, 13 Oct 2014 10:00:15 +0200 Subject: [PATCH 03/31] Added test_mysql_users into tests Added test_mysql_users into tests --- server/core/test/CMakeLists.txt | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/server/core/test/CMakeLists.txt b/server/core/test/CMakeLists.txt index fcbd343e2..13e14cba4 100644 --- a/server/core/test/CMakeLists.txt +++ b/server/core/test/CMakeLists.txt @@ -1,16 +1,40 @@ -add_executable(test_mysql_users test_mysql_users.c) add_executable(test_hash testhash.c) +add_executable(test_hint testhint.c) add_executable(test_spinlock testspinlock.c) add_executable(test_filter testfilter.c) +add_executable(test_buffer testbuffer.c) +add_executable(test_dcb testdcb.c) +add_executable(test_modutil testmodutil.c) +add_executable(test_poll testpoll.c) +add_executable(test_service testservice.c) +add_executable(test_server testserver.c) +add_executable(test_users testusers.c) +add_executable(test_mysql_users test_mysql_users.c) add_executable(test_adminusers testadminusers.c) -target_link_libraries(test_mysql_users fullcore MySQLClient) target_link_libraries(test_hash fullcore) +target_link_libraries(test_hint fullcore) target_link_libraries(test_spinlock fullcore) target_link_libraries(test_filter fullcore) +target_link_libraries(test_buffer fullcore) +target_link_libraries(test_dcb fullcore) +target_link_libraries(test_modutil fullcore) +target_link_libraries(test_poll fullcore) +target_link_libraries(test_service fullcore) +target_link_libraries(test_server fullcore) +target_link_libraries(test_users fullcore) +target_link_libraries(test_mysql_users fullcore MySQLClient) target_link_libraries(test_adminusers fullcore) -add_test(testMySQLUsers test_mysql_users) add_test(TestHash test_hash) +add_test(TestHint test_hint) add_test(TestSpinlock test_spinlock) add_test(TestFilter test_filter) +add_test(TestBuffer test_buffer) +add_test(TestDCB test_dcb) +add_test(TestModutil test_modutil) +add_test(TestPoll test_poll) +add_test(TestService test_service) +add_test(TestServer test_server) +add_test(TestUsers test_users) +add_test(testMySQLUsers test_mysql_users) add_test(TestAdminUsers test_adminusers) From 273a445045900e8ecde320f6eea3c83c1c1d6f05 Mon Sep 17 00:00:00 2001 From: MassimilianoPinto Date: Mon, 13 Oct 2014 10:47:27 +0200 Subject: [PATCH 04/31] Code cleanup Code cleanup --- server/modules/protocol/mysql_backend.c | 46 ++-- server/modules/protocol/mysql_client.c | 293 +++++++++++++++--------- server/modules/protocol/mysql_common.c | 121 +++++----- 3 files changed, 269 insertions(+), 191 deletions(-) diff --git a/server/modules/protocol/mysql_backend.c b/server/modules/protocol/mysql_backend.c index 2772e8e7b..049455686 100644 --- a/server/modules/protocol/mysql_backend.c +++ b/server/modules/protocol/mysql_backend.c @@ -1,5 +1,5 @@ /* - * This file is distributed as part of the SkySQL Gateway. It is free + * This file is distributed as part of the MariaDB Corporation MaxScale. It is free * software: you can redistribute it and/or modify it under the terms of the * GNU General Public License as published by the Free Software Foundation, * version 2. @@ -13,7 +13,7 @@ * this program; if not, write to the Free Software Foundation, Inc., 51 * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * - * Copyright SkySQL Ab 2013 + * Copyright MariaDB Corporation Ab 2013-2014 */ #include "mysql_client_server_protocol.h" @@ -27,7 +27,7 @@ * Revision History * Date Who Description * 14/06/2013 Mark Riddoch Initial version - * 17/06/2013 Massimiliano Pinto Added Gateway To Backends routines + * 17/06/2013 Massimiliano Pinto Added MaxScale To Backends routines * 27/06/2013 Vilho Raatikka Added skygw_log_write command as an example * and necessary headers. * 01/07/2013 Massimiliano Pinto Put Log Manager example code behind SS_DEBUG macros. @@ -199,6 +199,7 @@ static int gw_read_backend_event(DCB *dcb) { if (backend_protocol->protocol_auth_state == MYSQL_CONNECTED) { + /** Read cached backend handshake */ if (gw_read_backend_handshake(backend_protocol) != 0) { backend_protocol->protocol_auth_state = MYSQL_AUTH_FAILED; @@ -209,11 +210,13 @@ static int gw_read_backend_event(DCB *dcb) { "state = MYSQL_AUTH_FAILED.", pthread_self(), backend_protocol->owner_dcb->fd))); - } - else + else { - /* handshake decoded, send the auth credentials */ + /** + * Decode password and send the auth credentials + * to backend. + */ if (gw_send_authentication_to_backend( current_session->db, current_session->user, @@ -227,16 +230,16 @@ static int gw_read_backend_event(DCB *dcb) { "gw_send_authentication_to_backend " "fd %d, state = MYSQL_AUTH_FAILED.", pthread_self(), - backend_protocol->owner_dcb->fd))); - } - else + backend_protocol->owner_dcb->fd))); + } + else { backend_protocol->protocol_auth_state = MYSQL_AUTH_RECV; } } } spinlock_release(&dcb->authlock); - } + } /*< backend_protocol->protocol_auth_state == MYSQL_CONNECTED */ /* * Now: * -- check the authentication reply from backend @@ -266,9 +269,10 @@ static int gw_read_backend_event(DCB *dcb) { router_instance = session->service->router_instance; rsession = session->router_session; - if (backend_protocol->protocol_auth_state == MYSQL_AUTH_RECV) { - /*< - * Read backed auth reply + if (backend_protocol->protocol_auth_state == MYSQL_AUTH_RECV) + { + /** + * Read backed's reply to authentication message */ receive_rc = gw_receive_backend_auth(backend_protocol); @@ -283,7 +287,6 @@ static int gw_read_backend_event(DCB *dcb) { "fd %d, state = MYSQL_AUTH_FAILED.", pthread_self(), backend_protocol->owner_dcb->fd))); - LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, @@ -362,8 +365,8 @@ static int gw_read_backend_event(DCB *dcb) { 0, "Authentication with backend failed. " "Session will be closed."); - - router->handleError(router_instance, + + router->handleError(router_instance, rsession, errbuf, dcb, @@ -371,7 +374,6 @@ static int gw_read_backend_event(DCB *dcb) { &succp); ss_dassert(!succp); - LOGIF(LD, (skygw_log_write( LOGFILE_DEBUG, "%lu [gw_read_backend_event] " @@ -412,7 +414,7 @@ static int gw_read_backend_event(DCB *dcb) { } } } /* MYSQL_AUTH_RECV || MYSQL_AUTH_FAILED */ - + spinlock_release(&dcb->authlock); } /* MYSQL_AUTH_RECV || MYSQL_AUTH_FAILED */ @@ -878,6 +880,10 @@ static int gw_create_backend_connection( goto return_fd; } + /** Copy client flags to backend protocol */ + protocol->client_capabilities = + ((MySQLProtocol *)(backend_dcb->session->client->protocol))->client_capabilities; + /*< if succeed, fd > 0, -1 otherwise */ rv = gw_do_connect_to_backend(server->name, server->port, &fd); /*< Assign protocol with backend_dcb */ @@ -1048,6 +1054,10 @@ gw_backend_close(DCB *dcb) mysql_protocol_done(dcb); + /** + * If session->state is set to STOPPING the client and the session must + * be closed too. + */ if (session != NULL && session->state == SESSION_STATE_STOPPING) { client_dcb = session->client; diff --git a/server/modules/protocol/mysql_client.c b/server/modules/protocol/mysql_client.c index 974ad37e6..9e51639da 100644 --- a/server/modules/protocol/mysql_client.c +++ b/server/modules/protocol/mysql_client.c @@ -1,5 +1,5 @@ /* - * This file is distributed as part of the SkySQL Gateway. It is free + * This file is distributed as part of the MariaDB Corporation MaxScale. It is free * software: you can redistribute it and/or modify it under the terms of the * GNU General Public License as published by the Free Software Foundation, * version 2. @@ -13,7 +13,7 @@ * this program; if not, write to the Free Software Foundation, Inc., 51 * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * - * Copyright SkySQL Ab 2013 + * Copyright MariaDB Corporation Ab 2013-2014 */ /** @@ -25,7 +25,7 @@ * Revision History * Date Who Description * 14/06/2013 Mark Riddoch Initial version - * 17/06/2013 Massimiliano Pinto Added Client To Gateway routines + * 17/06/2013 Massimiliano Pinto Added Client To MaxScale routines * 24/06/2013 Massimiliano Pinto Added: fetch passwords from service users' hashtable * 02/09/2013 Massimiliano Pinto Added: session refcount * 16/12/2013 Massimiliano Pinto Added: client closed socket detection with recv(..., MSG_PEEK) @@ -35,6 +35,7 @@ * 11/03/2014 Massimiliano Pinto Added: Unix socket support * 07/05/2014 Massimiliano Pinto Added: specific version string in server handshake * 09/09/2014 Massimiliano Pinto Added: 777 permission for socket path + * 13/10/2014 Massimiliano Pinto Added: dbname authentication check * */ #include @@ -68,6 +69,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 +375,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 +435,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 +449,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 */ @@ -485,19 +486,18 @@ static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue) { if (auth_token) free(auth_token); - if (database) { int i = 0; int db_exists = 0; while(dcb->service->resources[i]) { if (strncmp(database, dcb->service->resources[i], MYSQL_DATABASE_MAXLEN) == 0) { db_exists = 1; - } - - i++; } - if (!db_exists) { + i++; + } + + if (!db_exists) { auth_ret = 2; } } @@ -506,12 +506,82 @@ static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue) { { dcb->user = strdup(client_data->user); } - + return auth_ret; } /** - * Write function for client DCB: writes data from Gateway to Client + * 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 memory allocation + * failed or if username was empty. + */ +static char* get_username_from_auth( + char* ptr, + uint8_t* data) +{ + char* first_letter; + char* rval; + + 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+1)) == NULL) + { + goto retblock; + } + } + else + { + rval = ptr; + } + snprintf(rval, MYSQL_USER_MAXLEN+1, "%s", first_letter); + +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 * * @param dcb The DCB of the client * @param queue Queue of buffers to write @@ -610,85 +680,91 @@ 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); - - 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 */ + + 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); + if (auth_val == 2) { - mysql_send_auth_error( - dcb, - 2, - 0, - "Database not existent"); + mysql_send_auth_error( + dcb, + 2, + 0, + "Database not existent"); } else { - mysql_send_auth_error( - dcb, - 2, - 0, - "Authorization failed"); + /** Send error 1045 to client */ + mysql_send_auth_error( + dcb, + 2, + 0, + fail_str); } - 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: @@ -826,9 +902,12 @@ int gw_read_client_event( } /** succeed */ - if (rc) { + if (rc) + { rc = 0; /**< here '0' means success */ - } else { + } + else + { GWBUF* errbuf; bool succp; @@ -1385,20 +1464,12 @@ gw_client_close(DCB *dcb) CHK_SESSION(session); spinlock_acquire(&session->ses_lock); - if (session->state == SESSION_STATE_STOPPING) + if (session->state != SESSION_STATE_STOPPING) { - /** - * Session is already getting closed so avoid - * redundant calls - */ - spinlock_release(&session->ses_lock); - return 1; - } - else - { - session->state = SESSION_STATE_STOPPING; - spinlock_release(&session->ses_lock); - } + session->state = SESSION_STATE_STOPPING; + } + spinlock_release(&session->ses_lock); + router = session->service->router; router_instance = session->service->router_instance; rsession = session->router_session; diff --git a/server/modules/protocol/mysql_common.c b/server/modules/protocol/mysql_common.c index aeb40a1a1..261765c52 100644 --- a/server/modules/protocol/mysql_common.c +++ b/server/modules/protocol/mysql_common.c @@ -32,7 +32,7 @@ * Setting to 1 allow localhost (127.0.0.1 or socket) to match the any host grant via * user@% * 29/09/2014 Massimiliano Pinto Added Mysql user@host authentication with wildcard in IPv4 hosts: - * x.y.z.%, x.y.%.%, x.%.%.% + * x.y.z.%, x.y.%.%, x.%.%.% * 03/10/2014 Massimiliano Pinto Added netmask for wildcard in IPv4 hosts. * */ @@ -466,6 +466,7 @@ int gw_receive_backend_auth( bufstr))); free(bufstr); + free(err); rc = -1; } else @@ -540,9 +541,9 @@ int gw_receive_backend_auth( * @return 0 on success, 1 on failure */ int gw_send_authentication_to_backend( - char *dbname, - char *user, - uint8_t *passwd, + char *dbname, + char *user, + uint8_t *passwd, MySQLProtocol *conn) { int compress = 0; @@ -552,8 +553,8 @@ int gw_send_authentication_to_backend( long bytes; uint8_t client_scramble[GW_MYSQL_SCRAMBLE_SIZE]; uint8_t client_capabilities[4]; - uint32_t server_capabilities; - uint32_t final_capabilities; + uint32_t server_capabilities = 0; + uint32_t final_capabilities = 0; char dbpass[MYSQL_USER_MAXLEN + 1]=""; GWBUF *buffer; DCB *dcb; @@ -568,17 +569,12 @@ int gw_send_authentication_to_backend( curr_passwd = passwd; dcb = conn->owner_dcb; - - // Zero the vars - memset(&server_capabilities, '\0', sizeof(server_capabilities)); - memset(&final_capabilities, '\0', sizeof(final_capabilities)); - final_capabilities = gw_mysql_get_byte4((uint8_t *)&server_capabilities); - final_capabilities |= GW_MYSQL_CAPABILITIES_PROTOCOL_41; - final_capabilities |= GW_MYSQL_CAPABILITIES_CLIENT; + /** Copy client's flags to backend */ + final_capabilities |= conn->client_capabilities;; - if (compress) { + if (compress) { final_capabilities |= GW_MYSQL_CAPABILITIES_COMPRESS; #ifdef DEBUG_MYSQL_CONN fprintf(stderr, ">>>> Backend Connection with compression\n"); @@ -1033,19 +1029,24 @@ int mysql_send_custom_error ( * @param passwd The SHA1(real_password): Note real_password is unknown * @return 1 on success, 0 on failure */ -int gw_send_change_user_to_backend(char *dbname, char *user, uint8_t *passwd, MySQLProtocol *conn) { - int compress = 0; - int rv; - uint8_t *payload = NULL; - uint8_t *payload_start = NULL; - long bytes; - uint8_t client_scramble[GW_MYSQL_SCRAMBLE_SIZE]; - uint8_t client_capabilities[4]; - uint32_t server_capabilities; - uint32_t final_capabilities; - char dbpass[MYSQL_USER_MAXLEN + 1]=""; - GWBUF *buffer; - DCB *dcb; +int gw_send_change_user_to_backend( + char *dbname, + char *user, + uint8_t *passwd, + MySQLProtocol *conn) +{ + int compress = 0; + int rv; + uint8_t *payload = NULL; + uint8_t *payload_start = NULL; + long bytes; + uint8_t client_scramble[GW_MYSQL_SCRAMBLE_SIZE]; + uint8_t client_capabilities[4]; + uint32_t server_capabilities = 0; + uint32_t final_capabilities = 0; + char dbpass[MYSQL_USER_MAXLEN + 1]=""; + GWBUF *buffer; + DCB *dcb; char *curr_db = NULL; uint8_t *curr_passwd = NULL; @@ -1058,14 +1059,10 @@ int gw_send_change_user_to_backend(char *dbname, char *user, uint8_t *passwd, My dcb = conn->owner_dcb; - // Zero the vars - memset(&server_capabilities, '\0', sizeof(server_capabilities)); - memset(&final_capabilities, '\0', sizeof(final_capabilities)); + final_capabilities = gw_mysql_get_byte4((uint8_t *)&server_capabilities); - final_capabilities = gw_mysql_get_byte4((uint8_t *)&server_capabilities); - - final_capabilities |= GW_MYSQL_CAPABILITIES_PROTOCOL_41; - final_capabilities |= GW_MYSQL_CAPABILITIES_CLIENT; + /** Copy client's flags to backend */ + final_capabilities |= conn->client_capabilities;; if (compress) { final_capabilities |= GW_MYSQL_CAPABILITIES_COMPRESS; @@ -1316,7 +1313,7 @@ int gw_check_mysql_scramble_data(DCB *dcb, uint8_t *token, unsigned int token_le /** * gw_find_mysql_user_password_sha1 * - * The routine fetches look for an user int the MaxScale users' table + * The routine fetches look for an user in the MaxScale users' table * The users' table is dcb->service->users or a different one specified with void *repository * * If found the HEX password, representing sha1(sha1(password)), is converted in binary data and @@ -1334,18 +1331,16 @@ int gw_find_mysql_user_password_sha1(char *username, uint8_t *gateway_password, struct sockaddr_in *client; char *user_password = NULL; MYSQL_USER_HOST key; - MYSQL_session *client_data = NULL; - - client_data = dcb->data; + MYSQL_session *client_data = NULL; + client_data = (MYSQL_session *) dcb->data; service = (SERVICE *) dcb->service; client = (struct sockaddr_in *) &dcb->ipv4; key.user = username; - key.resource = client_data->db; - memcpy(&key.ipv4, client, sizeof(struct sockaddr_in)); key.netmask = 32; + key.resource = client_data->db; LOGIF(LD, (skygw_log_write_flush( @@ -1355,11 +1350,11 @@ int gw_find_mysql_user_password_sha1(char *username, uint8_t *gateway_password, key.user, dcb->remote))); - /* look for user@current_ipv4 now */ + /* look for user@current_host now */ user_password = mysql_users_fetch(service->users, &key); if (!user_password) { - /* The user is not authenticated @ current IPv4 */ + /* The user is not authenticated @ current host */ while (1) { /* @@ -1371,14 +1366,16 @@ int gw_find_mysql_user_password_sha1(char *username, uint8_t *gateway_password, LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, - "%lu [MySQL Client Auth], user [%s@%s] not found, please try with 'localhost_match_wildcard_host=1' in service definition", - pthread_self(), + "Error : user %s@%s not found, try set " + "'localhost_match_wildcard_host=1' in " + "service definition of the configuration " + "file.", key.user, dcb->remote))); break; } - + /* * (2) check for possible IPv4 class C,B,A networks */ @@ -1388,7 +1385,7 @@ int gw_find_mysql_user_password_sha1(char *username, uint8_t *gateway_password, key.netmask -= 8; user_password = mysql_users_fetch(service->users, &key); - + if (user_password) { break; } @@ -1402,7 +1399,7 @@ int gw_find_mysql_user_password_sha1(char *username, uint8_t *gateway_password, if (user_password) { break; } - + /* Class A check */ key.ipv4.sin_addr.s_addr &= 0x000000FF; key.netmask -= 8; @@ -1432,7 +1429,7 @@ int gw_find_mysql_user_password_sha1(char *username, uint8_t *gateway_password, if (!user_password) { /* - * the user@% has not been found. + * user@% not found. */ LOGIF(LD, @@ -1456,14 +1453,12 @@ int gw_find_mysql_user_password_sha1(char *username, uint8_t *gateway_password, * The gateway_password represents the SHA1(SHA1(real_password)). * Please note: the real_password is unknown and SHA1(real_password) is unknown as well */ - //char *the_password = user_password->value; - //MYSQL_USER_HOST *matched_key = (MYSQL_USER_HOST *)user_password->key; + + if (strlen(user_password)) + gw_hex2bin(gateway_password, user_password, SHA_DIGEST_LENGTH * 2); - if (strlen(user_password)) - gw_hex2bin(gateway_password, user_password, SHA_DIGEST_LENGTH * 2); - - return 0; + return 0; } else { return 1; } @@ -1514,7 +1509,7 @@ mysql_send_auth_error ( } mysql_errno = 1045; mysql_error_msg = "Access denied!"; - mysql_state = "2800"; + mysql_state = "28000"; field_count = 0xff; gw_mysql_set_byte2(mysql_err, mysql_errno); @@ -1709,9 +1704,11 @@ void protocol_archive_srv_command( s1 = &p->protocol_command; - LOGIF(LT, (skygw_log_write( - LOGFILE_TRACE, - "Move command %s from fd %d to command history.", + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, + "%lu [protocol_archive_srv_command] Move command %s from fd %d " + "to command history.", + pthread_self(), STRPACKETTYPE(s1->scom_cmd), p->owner_dcb->fd))); @@ -1783,8 +1780,8 @@ void protocol_add_srv_command( p->protocol_command.scom_next = server_command_init(NULL, cmd); } - LOGIF(LT, (skygw_log_write( - LOGFILE_TRACE, + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, "Added command %s to fd %d.", STRPACKETTYPE(cmd), p->owner_dcb->fd))); @@ -1794,8 +1791,8 @@ void protocol_add_srv_command( while (c != NULL && c->scom_cmd != MYSQL_COM_UNDEFINED) { - LOGIF(LT, (skygw_log_write( - LOGFILE_TRACE, + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, "fd %d : %d %s", p->owner_dcb->fd, c->scom_cmd, From 6813a0381dbc3d859fbad897a40c0a0f35e157d6 Mon Sep 17 00:00:00 2001 From: MassimilianoPinto Date: Mon, 13 Oct 2014 10:52:34 +0200 Subject: [PATCH 05/31] fprintf removed fprintf removed --- server/core/dbusers.c | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/server/core/dbusers.c b/server/core/dbusers.c index 2a2ad3ae2..e0827496a 100644 --- a/server/core/dbusers.c +++ b/server/core/dbusers.c @@ -697,22 +697,13 @@ int add; * @return The authentication data or NULL on error */ void *mysql_users_fetch(USERS *users, MYSQL_USER_HOST *key) { - HASHENTRIES *matched; MYSQL_USER_HOST *entry; if (key == NULL) return NULL; atomic_add(&users->stats.n_fetches, 1); - fprintf(stderr, "-- fetching %s@%i db=[%s]\n", key->user, key->ipv4.sin_addr.s_addr, key->resource); - - matched = (HASHENTRIES *)hashtable_fetch_key(users->data, key); - if (matched) { - entry = (MYSQL_USER_HOST *)matched->key; - fprintf(stderr, "+++++++ Hash match for %s, db %s\n", entry->user, entry->resource); - return matched->value; - } else - return NULL; + return hashtable_fetch(users->data, key); } /** From b15ebfe14ac6d39f4db7645830b2f90fbb2512c3 Mon Sep 17 00:00:00 2001 From: MassimilianoPinto Date: Mon, 13 Oct 2014 10:53:52 +0200 Subject: [PATCH 06/31] Extra line removed Extra line removed --- server/core/test/CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/server/core/test/CMakeLists.txt b/server/core/test/CMakeLists.txt index 13e14cba4..5ec3a834f 100644 --- a/server/core/test/CMakeLists.txt +++ b/server/core/test/CMakeLists.txt @@ -37,4 +37,3 @@ add_test(TestServer test_server) add_test(TestUsers test_users) add_test(testMySQLUsers test_mysql_users) add_test(TestAdminUsers test_adminusers) - From d645088645f0b1fd128e97049b7cdeca84f924df Mon Sep 17 00:00:00 2001 From: MassimilianoPinto Date: Mon, 13 Oct 2014 18:18:30 +0200 Subject: [PATCH 07/31] Added resources pointer to service Added resources pointer to service --- server/core/service.c | 1 + server/include/service.h | 1 + 2 files changed, 2 insertions(+) diff --git a/server/core/service.c b/server/core/service.c index bf32846ce..3013c41bc 100644 --- a/server/core/service.c +++ b/server/core/service.c @@ -137,6 +137,7 @@ SERVICE *service; service->filters = NULL; service->n_filters = 0; service->weightby = 0; + service->resources = NULL; spinlock_init(&service->spin); spinlock_init(&service->users_table_spin); memset(&service->rate_limit, 0, sizeof(SERVICE_REFRESH_RATE)); diff --git a/server/include/service.h b/server/include/service.h index 659e422aa..ef9b73cf9 100644 --- a/server/include/service.h +++ b/server/include/service.h @@ -124,6 +124,7 @@ typedef struct service { struct users *users; /**< The user data for this service */ int enable_root; /**< Allow root user access */ int localhost_match_wildcard_host; /**< Match localhost against wildcard */ + char **resources; /** resource list, todo: use hashtables */ CONFIG_PARAMETER* svc_config_param; /*< list of config params and values */ int svc_config_version; /*< Version number of configuration */ From f2362c3359cfc7001bc65fb0d4f3994ce54792cd Mon Sep 17 00:00:00 2001 From: MassimilianoPinto Date: Thu, 16 Oct 2014 12:50:14 +0200 Subject: [PATCH 08/31] Auth error message fix for using password: YES,NO MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Auth error message fix for using password: YES,NO This applies to a “not found user” trying to authenticate with or without password --- server/modules/protocol/mysql_common.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/server/modules/protocol/mysql_common.c b/server/modules/protocol/mysql_common.c index a81c7e983..9e1469878 100644 --- a/server/modules/protocol/mysql_common.c +++ b/server/modules/protocol/mysql_common.c @@ -1236,6 +1236,12 @@ int gw_check_mysql_scramble_data(DCB *dcb, uint8_t *token, unsigned int token_le ret_val = gw_find_mysql_user_password_sha1(username, password, dcb); if (ret_val) { + /* if password was sent, fill stage1_hash with at least 1 byte in order + * to create rigth error message: (using password: YES|NO) + */ + if (token_len) + memcpy(stage1_hash, (char *)"_", 1); + return 1; } From f66dc2b025f02abb607ec56716d386d9ddab06e0 Mon Sep 17 00:00:00 2001 From: MassimilianoPinto Date: Thu, 16 Oct 2014 13:00:23 +0200 Subject: [PATCH 09/31] Removed compilation error on mysqlusers Removed compilation error on mysqlusers --- server/core/test/test_mysql_users.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/server/core/test/test_mysql_users.c b/server/core/test/test_mysql_users.c index 565cc02ca..7f72496cc 100644 --- a/server/core/test/test_mysql_users.c +++ b/server/core/test/test_mysql_users.c @@ -347,7 +347,7 @@ int main() { if (ret) fprintf(stderr, "\t-- Expecting no match\n"); assert(ret == 1); - ret = set_and_get_mysql_users_wildcards("pippo", "192.0.%.%", "foo", "192.3.2.1", NULL, NULL, NULL); + ret = set_and_get_mysql_users_wildcards("pippo", "192.0.%.%", "foo", "192.3.2.1", "Y", NULL, NULL); if (ret) fprintf(stderr, "\t-- Expecting no match\n"); assert(ret == 1); @@ -383,9 +383,6 @@ int main() { if (!ret) fprintf(stderr, "\t-- Expecting ok\n"); assert(ret == 0); - ret = set_and_get_mysql_users_wildcards("pippo", "192.0.0.%", "1234567890123456789012345678901", "192.3.2.1"); - if (ret) fprintf(stderr, "\t-- Expecting no match\n"); - assert(ret == 1); fprintf(stderr, "----------------\n"); From a437d089bba239e20eb8456463e0bcee549a99e1 Mon Sep 17 00:00:00 2001 From: MassimilianoPinto Date: Thu, 16 Oct 2014 17:49:57 +0200 Subject: [PATCH 10/31] database names are loaded database names are loaded --- server/core/service.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/server/core/service.c b/server/core/service.c index 3013c41bc..d8472818f 100644 --- a/server/core/service.c +++ b/server/core/service.c @@ -199,6 +199,8 @@ GWPROTOCOL *funcs; } if (strcmp(port->protocol, "MySQLClient") == 0) { int loaded; + int dbnames_loaded; + /* Allocate specific data for MySQL users */ service->users = mysql_users_alloc(); loaded = load_mysql_users(service); @@ -213,6 +215,14 @@ GWPROTOCOL *funcs; LOGFILE_MESSAGE, "Loaded %d MySQL Users.", loaded))); + + /* load all mysql database names */ + dbnames_loaded = mysql_users_load_dbs(service); + + LOGIF(LM, (skygw_log_write( + LOGFILE_MESSAGE, + "Loaded %d MySQL Databases.", + dbnames_loaded))); } else { /* Generic users table */ service->users = users_alloc(); From a82d18f51787848c5d0c173a4f32ae943d06a891 Mon Sep 17 00:00:00 2001 From: MassimilianoPinto Date: Thu, 16 Oct 2014 18:11:29 +0200 Subject: [PATCH 11/31] get wildcard loop tidy up get wildcard loop tidy up --- server/core/dbusers.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/core/dbusers.c b/server/core/dbusers.c index 8b84bf5f3..685c7aa39 100644 --- a/server/core/dbusers.c +++ b/server/core/dbusers.c @@ -223,7 +223,7 @@ int add_mysql_users_with_host_ipv4(USERS *users, char *user, char *host, char *p tmp = ret_ip+strlen(ret_ip)-1; /* start from Class C */ - while(*tmp) { + while(tmp > ret_ip) { if (*tmp == '%') { /* set only the last IPv4 byte to 1 * avoiding setipadress() failure From 2dfa01fccc43337d328893cd7115e383dd939cf2 Mon Sep 17 00:00:00 2001 From: MassimilianoPinto Date: Thu, 16 Oct 2014 19:17:15 +0200 Subject: [PATCH 12/31] MySQL Auth with dbname check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MySQL Auth with dbname check Unknown database ‘xxxxx’ is returned to client Proper error number is still missing and also com_change_user need to be fixed --- server/core/dbusers.c | 17 ++++--- server/core/service.c | 6 +-- server/core/test/test_mysql_users.c | 67 +++++++++++++++++--------- server/include/dbusers.h | 2 +- server/modules/protocol/mysql_client.c | 16 ++++-- 5 files changed, 69 insertions(+), 39 deletions(-) diff --git a/server/core/dbusers.c b/server/core/dbusers.c index 685c7aa39..a355e85f0 100644 --- a/server/core/dbusers.c +++ b/server/core/dbusers.c @@ -64,7 +64,7 @@ static int uh_cmpfun( void* v1, void* v2); static void *uh_keydup(void* key); static void uh_keyfree( void* key); static int uh_hfun( void* key); -void *mysql_users_fetch(USERS *users, MYSQL_USER_HOST *key); +char *mysql_users_fetch(USERS *users, MYSQL_USER_HOST *key); char *mysql_format_user_entry(void *data); int add_mysql_users_with_host_ipv4(USERS *users, char *user, char *host, char *passwd, char *anydb, char *db); static int getDatabases(SERVICE *); @@ -188,7 +188,11 @@ int add_mysql_users_with_host_ipv4(USERS *users, char *user, char *host, char *p int found_any=0; int ret = 0; - fprintf(stderr, "Current %s@%s has anydb access %s OR specific db %s\n", user, host, anydb, db); + //fprintf(stderr, "Current %s@%s has anydb access %s OR specific db %s\n", user, host, anydb, db); + + if (users == NULL || user == NULL || host == NULL) { + return ret; + } /* prepare the user@host data struct */ memset(&serv_addr, 0, sizeof(serv_addr)); @@ -219,10 +223,11 @@ int add_mysql_users_with_host_ipv4(USERS *users, char *user, char *host, char *p found_any = 1; } else { char *tmp; - strcpy(ret_ip, host); + strncpy(ret_ip, host, INET_ADDRSTRLEN); tmp = ret_ip+strlen(ret_ip)-1; /* start from Class C */ + while(tmp > ret_ip) { if (*tmp == '%') { /* set only the last IPv4 byte to 1 @@ -254,10 +259,10 @@ int add_mysql_users_with_host_ipv4(USERS *users, char *user, char *host, char *p } /* add user@host as key and passwd as value in the MySQL users hash table */ - if (mysql_users_add(users, &key, passwd)) + if (mysql_users_add(users, &key, passwd)) { ret = 1; - if (ret == 1) fprintf(stderr, "Added user %s@%i with db [%s]\n", key.user, key.ipv4.sin_addr.s_addr, key.resource); + } } free(key.user); @@ -698,7 +703,7 @@ int add; * @param key The key with user@host * @return The authentication data or NULL on error */ -void *mysql_users_fetch(USERS *users, MYSQL_USER_HOST *key) { +char *mysql_users_fetch(USERS *users, MYSQL_USER_HOST *key) { MYSQL_USER_HOST *entry; if (key == NULL) return NULL; diff --git a/server/core/service.c b/server/core/service.c index d8472818f..e2cb8dd9d 100644 --- a/server/core/service.c +++ b/server/core/service.c @@ -220,9 +220,9 @@ GWPROTOCOL *funcs; dbnames_loaded = mysql_users_load_dbs(service); LOGIF(LM, (skygw_log_write( - LOGFILE_MESSAGE, - "Loaded %d MySQL Databases.", - dbnames_loaded))); + LOGFILE_MESSAGE, + "Loaded %d MySQL Databases.", + dbnames_loaded))); } else { /* Generic users table */ service->users = users_alloc(); diff --git a/server/core/test/test_mysql_users.c b/server/core/test/test_mysql_users.c index 7f72496cc..e2d15a960 100644 --- a/server/core/test/test_mysql_users.c +++ b/server/core/test/test_mysql_users.c @@ -54,9 +54,23 @@ int set_and_get_single_mysql_users_ipv4(char *username, unsigned long ipv4, char char ret_ip[200]=""; char *fetch_data; char *db=""; + DCB *dcb; + SERVICE *service; unsigned long fix_ipv4; + dcb = dcb_alloc(DCB_ROLE_INTERNAL); + + if (dcb == NULL) { + fprintf(stderr, "dcb_alloc() failed\n"); + return 1; + } + if ((service = (SERVICE *)calloc(1, sizeof(SERVICE))) == NULL) { + fprintf(stderr, "service_alloc() failed\n"); + dcb_free(dcb); + return 1; + } + if (ipv4 > UINT_MAX) { fix_ipv4 = UINT_MAX; } else { @@ -82,6 +96,8 @@ int set_and_get_single_mysql_users_ipv4(char *username, unsigned long ipv4, char if (!mysql_users_add(mysql_users, &key, password)) { fprintf(stderr, "Failed adding %s@%s(%lu)\n", username, ret_ip, fix_ipv4); users_free(mysql_users); + free(service); + dcb_free(dcb); return 1; } @@ -97,6 +113,8 @@ int set_and_get_single_mysql_users_ipv4(char *username, unsigned long ipv4, char fetch_data = mysql_users_fetch(mysql_users, &find_key); users_free(mysql_users); + free(service); + dcb_free(dcb); if (!fetch_data) return 1; @@ -166,22 +184,23 @@ int set_and_get_single_mysql_users(char *username, char *hostname, char *passwor int set_and_get_mysql_users_wildcards(char *username, char *hostname, char *password, char *from, char *anydb, char *db, char *db_from) { USERS *mysql_users; - int ret; + int ret = -1; + int rc = -1; struct sockaddr_in client_addr; DCB *dcb; SERVICE *service; - MYSQL_session data; + MYSQL_session *data; dcb = dcb_alloc(DCB_ROLE_INTERNAL); if (dcb == NULL) { fprintf(stderr, "dcb_alloc() failed\n"); - return 1; + return ret; } if ((service = (SERVICE *)calloc(1, sizeof(SERVICE))) == NULL) { fprintf(stderr, "service_alloc() failed\n"); dcb_free(dcb); - return 1; + return ret; } memset(&client_addr, 0, sizeof(client_addr)); @@ -191,10 +210,18 @@ int set_and_get_mysql_users_wildcards(char *username, char *hostname, char *pass fprintf(stderr, "setipaddress failed for host [%s]\n", from); free(service); dcb_free(dcb); - return 1; + return ret; } } + if ((data = (MYSQL_session *) calloc(1, sizeof(MYSQL_session))) == NULL) { + fprintf(stderr, "MYSQL_session alloc failed\n"); + free(service); + dcb_free(dcb); + return ret; + } + + /* client IPv4 in raw data*/ memcpy(&dcb->ipv4, (struct sockaddr_in *)&client_addr, sizeof(struct sockaddr_in)); @@ -205,14 +232,15 @@ int set_and_get_mysql_users_wildcards(char *username, char *hostname, char *pass service->users = mysql_users; if (db_from != NULL) - strcpy(data.db, db_from); + strcpy(data->db, db_from); else - strcpy(data.db, ""); + strcpy(data->db, ""); - dcb->data = &data; + /* freed by dcb_free(dcb) */ + dcb->data = data; // the routine returns 1 on success - if (anydb) { + if (anydb != NULL) { if (strcmp(anydb, "N") == 0) { ret = add_mysql_users_with_host_ipv4(mysql_users, username, hostname, password, anydb, db); } else if (strcmp(anydb, "Y") == 0) { @@ -223,25 +251,19 @@ int set_and_get_mysql_users_wildcards(char *username, char *hostname, char *pass } else { ret = add_mysql_users_with_host_ipv4(mysql_users, username, hostname, password, "N", NULL); } - - - if (!ret) { + + if (ret == 0) { fprintf(stderr, "add_mysql_users_with_host_ipv4 (%s@%s, %s) FAILED\n", username, hostname, password); - users_free(mysql_users); - free(service); - dcb_free(dcb); - - return 1; } else { - char db_passwd[100]=""; + unsigned char db_passwd[100]=""; dcb->remote=strdup(from); //fprintf(stderr, "add_mysql_users_with_host_ipv4 passed(%s@%s, %s) OK\n", username, hostname, password); - fprintf(stderr, "Checking '%s' @ '%s' against (%s@%s)\n", username, from, username, hostname); + //fprintf(stderr, "Checking '%s' @ '%s' against (%s@%s)\n", username, from, username, hostname); // returns 0 on success - ret = gw_find_mysql_user_password_sha1(username, db_passwd, dcb); + ret = gw_find_mysql_user_password_sha1(username, db_passwd, dcb); } users_free(mysql_users); @@ -309,7 +331,7 @@ int main() { if (!ret) fprintf(stderr, "\t-- Expecting ok\n"); assert(ret == 0); - ret = set_and_get_mysql_users_wildcards("pippo", "192.168.1.%", "foo", "192.168.2.2", NULL, NULL, NULL); + ret = set_and_get_mysql_users_wildcards("pippo", "192.168.4.%", "ffoo", "192.168.2.2", NULL, NULL, NULL); if (ret) fprintf(stderr, "\t-- Expecting no match\n"); assert(ret == 1); @@ -321,7 +343,6 @@ int main() { if (!ret) fprintf(stderr, "\t-- Expecting ok\n"); assert(ret == 0); - fprintf(stderr, "Adding pippo, 192.%%.%%.%%, foo, 192.0.0.2, Y, NULL, cossa\n"); ret = set_and_get_mysql_users_wildcards("pippo", "192.%.%.%", "foo", "192.0.0.2", "Y", NULL, "cossa"); if (!ret) fprintf(stderr, "\t-- Expecting ok\n"); assert(ret == 0); @@ -383,8 +404,6 @@ int main() { if (!ret) fprintf(stderr, "\t-- Expecting ok\n"); assert(ret == 0); - - fprintf(stderr, "----------------\n"); fprintf(stderr, "<<< Test completed\n"); diff --git a/server/include/dbusers.h b/server/include/dbusers.h index 57b205333..7b7fb03dd 100644 --- a/server/include/dbusers.h +++ b/server/include/dbusers.h @@ -63,6 +63,6 @@ extern int reload_mysql_users(SERVICE *service); extern int mysql_users_add(USERS *users, MYSQL_USER_HOST *key, char *auth); extern int add_mysql_users_with_host_ipv4(USERS *users, char *user, char *host, char *passwd, char *anydb, char *db); extern USERS *mysql_users_alloc(); -extern void *mysql_users_fetch(USERS *users, MYSQL_USER_HOST *key); +extern char *mysql_users_fetch(USERS *users, MYSQL_USER_HOST *key); extern int replace_mysql_users(SERVICE *service); #endif diff --git a/server/modules/protocol/mysql_client.c b/server/modules/protocol/mysql_client.c index 43baf02f0..54d577d4d 100644 --- a/server/modules/protocol/mysql_client.c +++ b/server/modules/protocol/mysql_client.c @@ -497,7 +497,7 @@ static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue) { i++; } - if (!db_exists) { + if (!db_exists && auth_ret == 0) { auth_ret = 2; } } @@ -735,18 +735,23 @@ int gw_read_client_event( 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); if (auth_val == 2) { + char *dberr; + dberr= calloc(1, 100); + sprintf(dberr, "Unknown database '%s'", (char*)((MYSQL_session *)dcb->data)->db); + mysql_send_auth_error( dcb, 2, 0, - "Database not existent"); + dberr); + free(dberr); } else { /** Send error 1045 to client */ + fail_str = create_auth_fail_str(read_buffer, + dcb->remote, + (char*)((MYSQL_session *)dcb->data)->client_sha1); mysql_send_auth_error( dcb, 2, @@ -761,6 +766,7 @@ int gw_read_client_event( "state = MYSQL_AUTH_FAILED.", protocol->owner_dcb->fd, pthread_self()))); + free(fail_str); dcb_close(dcb); } From b8f590e67ffa7b3512d1f515eb337df4f45e8de2 Mon Sep 17 00:00:00 2001 From: MassimilianoPinto Date: Fri, 17 Oct 2014 19:02:19 +0200 Subject: [PATCH 13/31] Added database errmsg to change_user Added database errmsg to change_user --- server/modules/protocol/mysql_backend.c | 37 +++++++++--- server/modules/protocol/mysql_client.c | 71 ++++++---------------- server/modules/protocol/mysql_common.c | 79 +++++++++++++++++++++++-- 3 files changed, 119 insertions(+), 68 deletions(-) diff --git a/server/modules/protocol/mysql_backend.c b/server/modules/protocol/mysql_backend.c index 049455686..4dab7a81e 100644 --- a/server/modules/protocol/mysql_backend.c +++ b/server/modules/protocol/mysql_backend.c @@ -66,7 +66,7 @@ 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); #if defined(NOT_USED) @@ -1191,6 +1191,7 @@ static int gw_change_user( uint8_t *auth_token = NULL; int rv = -1; int auth_ret = 1; + int db_exists = 0; current_session = (MYSQL_session *)in_session->client->data; backend_protocol = backend->protocol; @@ -1217,6 +1218,10 @@ static int gw_change_user( memcpy(auth_token, client_auth_packet, auth_token_len); client_auth_packet += auth_token_len; } + + // get db name + strcpy(database, (char *)client_auth_packet); + // 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); @@ -1233,17 +1238,33 @@ static int gw_change_user( if (auth_token) free(auth_token); - if (auth_ret != 0) { - /*< vraa : errorHandle */ + if (strlen(database)) { + int i = 0; + while(backend->session->client->service->resources[i]) { + if (strncmp(database, backend->session->client->service->resources[i], MYSQL_DATABASE_MAXLEN) == 0) { + db_exists = 1; + } - // send the error packet - mysql_send_auth_error(backend->session->client, 1, 0, "Authorization failed on change_user"); + i++; + } + + if (!db_exists && auth_ret == 0) { + auth_ret = 2; + } + } + + if (auth_ret != 0) { + + char *message = create_auth_failed_msg(queue, "ipaddr", client_sha1, auth_ret); + /* send the error packet */ + mysql_send_auth_error(backend->session->client, 1, 0, message); + fprintf(stderr, "ERROR change user for [%s] to [%s]\n", username, database); + //mysql_send_auth_error(backend->session->client, 1, 0, "Authorization failed on change_user"); + + free(message); rv = 1; } else { - // get db name - strcpy(database, (char *)client_auth_packet); - rv = gw_send_change_user_to_backend(database, username, client_sha1, backend_protocol); /*< diff --git a/server/modules/protocol/mysql_client.c b/server/modules/protocol/mysql_client.c index 54d577d4d..ec6ad8d72 100644 --- a/server/modules/protocol/mysql_client.c +++ b/server/modules/protocol/mysql_client.c @@ -70,7 +70,7 @@ 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); +extern char* get_username_from_auth(char* ptr, uint8_t* data); /* * The "module object" for the mysqld client protocol module. @@ -394,6 +394,7 @@ 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); @@ -447,11 +448,15 @@ static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue) { client_auth_packet + 4 + 4 + 4 + 1 + 23 + strlen(username) + 1, 1); + /* + * Note: some clients may pass empty database, connect_with_db !=0 but database ="" + */ 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); + fprintf(stderr, "---- Database name passed [%s], flag [%i]\n", database, connect_with_db); } /* allocate memory for token only if auth_token_len > 0 */ @@ -482,22 +487,23 @@ static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue) { } } + fprintf(stderr, "--- Authentication reply is [%i]\n", auth_ret); + /* let's free the auth_token now */ if (auth_token) free(auth_token); - if (database) { + if (database && strlen(database)) { int i = 0; - int db_exists = 0; while(dcb->service->resources[i]) { if (strncmp(database, dcb->service->resources[i], MYSQL_DATABASE_MAXLEN) == 0) { db_exists = 1; + } + + i++; } - i++; - } - - if (!db_exists && auth_ret == 0) { + if (!db_exists && auth_ret == 0) { auth_ret = 2; } } @@ -510,50 +516,6 @@ static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue) { 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 memory allocation - * failed or if username was empty. - */ -static char* get_username_from_auth( - char* ptr, - uint8_t* data) -{ - char* first_letter; - char* rval; - - 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+1)) == NULL) - { - goto retblock; - } - } - else - { - rval = ptr; - } - snprintf(rval, MYSQL_USER_MAXLEN+1, "%s", first_letter); - -retblock: - - return rval; -} - - static char* create_auth_fail_str( GWBUF* readbuf, char* hostaddr, @@ -757,6 +719,7 @@ int gw_read_client_event( 2, 0, fail_str); + free(fail_str); } LOGIF(LD, (skygw_log_write( @@ -767,7 +730,6 @@ int gw_read_client_event( protocol->owner_dcb->fd, pthread_self()))); - free(fail_str); dcb_close(dcb); } read_buffer = gwbuf_consume(read_buffer, nbytes_read); @@ -779,8 +741,9 @@ int gw_read_client_event( uint8_t cap = 0; uint8_t* payload = NULL; bool stmt_input; /*< router input type */ - - ss_dassert(nbytes_read >= 5); + + fprintf(stderr, "NBYTES %i\n", nbytes_read); + //ss_dassert(nbytes_read >= 5); session = dcb->session; ss_dassert( session!= NULL); diff --git a/server/modules/protocol/mysql_common.c b/server/modules/protocol/mysql_common.c index 9e1469878..da0e2ab93 100644 --- a/server/modules/protocol/mysql_common.c +++ b/server/modules/protocol/mysql_common.c @@ -49,6 +49,7 @@ extern int gw_read_backend_event(DCB* dcb); extern int gw_write_backend_event(DCB *dcb); extern int gw_MySQLWrite_backend(DCB *dcb, GWBUF *queue); extern int gw_error_backend_event(DCB *dcb); +char* get_username_from_auth(char* ptr, uint8_t* data); static server_command_t* server_command_init(server_command_t* srvcmd, mysql_server_cmd_t cmd); @@ -1117,15 +1118,15 @@ int gw_send_change_user_to_backend( if (curr_passwd != NULL) { bytes += GW_MYSQL_SCRAMBLE_SIZE; - bytes++; - } else { - bytes++; - } + } + // the NULL + bytes++; if (curr_db != NULL) { bytes += strlen(curr_db); - bytes++; } + // the NULL + bytes++; // the charset bytes += 2; @@ -1177,7 +1178,10 @@ int gw_send_change_user_to_backend( memcpy(payload, curr_db, strlen(curr_db)); payload += strlen(curr_db); payload++; - } + } else { + // skip the NULL + payload++; + } // set the charset, 2 bytes!!!! *payload = '\x08'; @@ -1979,3 +1983,66 @@ void protocol_set_response_status ( spinlock_release(&p->protocol_lock); } +char* create_auth_failed_msg( + GWBUF* readbuf, + char* hostaddr, + uint8_t* sha1, int dbmatch) +{ + 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); + + if (errstr != NULL) + { + sprintf(errstr, ferrstr, uname, hostaddr, (*sha1 == '\0' ? "NO" : "YES")); + strcat(errstr, " to database 'database'"); + } + + return errstr; +} + +/** + * 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 memory allocation + * failed or if username was empty. + */ +char* get_username_from_auth( + char* ptr, + uint8_t* data) +{ + char* first_letter; + char* rval; + + 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+1)) == NULL) + { + goto retblock; + } + } + else + { + rval = ptr; + } + snprintf(rval, MYSQL_USER_MAXLEN+1, "%s", first_letter); + +retblock: + + return rval; +} From 220e1d95054e60fc0928cc4ddb1b635b127743bb Mon Sep 17 00:00:00 2001 From: MassimilianoPinto Date: Mon, 20 Oct 2014 10:26:00 +0200 Subject: [PATCH 14/31] local authentication with db name for change_user local authentication with db name for change_user --- server/modules/protocol/mysql_backend.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/server/modules/protocol/mysql_backend.c b/server/modules/protocol/mysql_backend.c index 4dab7a81e..75334ae23 100644 --- a/server/modules/protocol/mysql_backend.c +++ b/server/modules/protocol/mysql_backend.c @@ -1185,6 +1185,7 @@ static int gw_change_user( MySQLProtocol *client_protocol = NULL; char username[MYSQL_USER_MAXLEN+1]=""; char database[MYSQL_DATABASE_MAXLEN+1]=""; + char current_database[MYSQL_DATABASE_MAXLEN+1]=""; uint8_t client_sha1[MYSQL_SCRAMBLE_LEN]=""; uint8_t *client_auth_packet = GWBUF_DATA(queue); unsigned int auth_token_len = 0; @@ -1219,8 +1220,13 @@ static int gw_change_user( client_auth_packet += auth_token_len; } - // get db name + /* save current_database name */ + strcpy(current_database, current_session->db); + + /* get new database name */ strcpy(database, (char *)client_auth_packet); + /* set it to current dtabase */ + strcpy(current_session->db, database); // decode the token and check the password // Note: if auth_token_len == 0 && auth_token == NULL, user is without password @@ -1259,11 +1265,13 @@ static int gw_change_user( /* send the error packet */ mysql_send_auth_error(backend->session->client, 1, 0, message); fprintf(stderr, "ERROR change user for [%s] to [%s]\n", username, database); - //mysql_send_auth_error(backend->session->client, 1, 0, "Authorization failed on change_user"); free(message); - rv = 1; + /* copy back current datbase to client session */ + strcpy(current_session->db, current_database); + + rv = 1; } else { rv = gw_send_change_user_to_backend(database, username, client_sha1, backend_protocol); @@ -1275,6 +1283,9 @@ static int gw_change_user( memcpy(current_session->client_sha1, client_sha1, sizeof(current_session->client_sha1)); } gwbuf_free(queue); + + fprintf(stderr, "--- After change_user curren client dcb DB is [%s]\n", current_session->db); + return rv; } From 3cdb1dc2ae06d381869c873ed6e3069448cf3263 Mon Sep 17 00:00:00 2001 From: MassimilianoPinto Date: Mon, 20 Oct 2014 19:26:13 +0200 Subject: [PATCH 15/31] Added db names as hashtable Added db names as hashtable --- server/core/dbusers.c | 288 +++++++++++++++++------- server/core/service.c | 19 +- server/include/service.h | 4 +- server/modules/protocol/mysql_backend.c | 8 +- server/modules/protocol/mysql_client.c | 19 +- 5 files changed, 239 insertions(+), 99 deletions(-) diff --git a/server/core/dbusers.c b/server/core/dbusers.c index a355e85f0..0e6ffc0c6 100644 --- a/server/core/dbusers.c +++ b/server/core/dbusers.c @@ -68,6 +68,7 @@ char *mysql_users_fetch(USERS *users, MYSQL_USER_HOST *key); char *mysql_format_user_entry(void *data); int add_mysql_users_with_host_ipv4(USERS *users, char *user, char *host, char *passwd, char *anydb, char *db); static int getDatabases(SERVICE *); +HASHTABLE *resource_alloc(); /** * Load the user/passwd form mysql.user table into the service users' hashtable @@ -188,8 +189,6 @@ int add_mysql_users_with_host_ipv4(USERS *users, char *user, char *host, char *p int found_any=0; int ret = 0; - //fprintf(stderr, "Current %s@%s has anydb access %s OR specific db %s\n", user, host, anydb, db); - if (users == NULL || user == NULL || host == NULL) { return ret; } @@ -206,13 +205,18 @@ int add_mysql_users_with_host_ipv4(USERS *users, char *user, char *host, char *p } /* for anydb == Y key.resource is '\0' as set by memset */ - if (strcmp(anydb, "N") == 0) { - if (db) - key.resource = strdup(db); - else - key.resource = NULL; - } else { - key.resource = strdup(""); + if (anydb == NULL) { + key.resource = NULL; + } + else { + if (strcmp(anydb, "N") == 0) { + if (db) + key.resource = strdup(db); + else + key.resource = NULL; + } else { + key.resource = strdup(""); + } } /* handle ANY, Class C,B,A */ @@ -286,20 +290,15 @@ getDatabases(SERVICE *service) MYSQL *con = NULL; MYSQL_ROW row; MYSQL_RES *result = NULL; - int num_fields = 0; char *service_user = NULL; char *service_passwd = NULL; char *dpwd; - int total_users = 0; SERVER *server; - char *users_query; - unsigned char hash[SHA_DIGEST_LENGTH]=""; - char *users_data = NULL; int ndbs = 0; - /* last byte is for Select_priv=Y|N */ - int users_data_row_len = MYSQL_USER_MAXLEN + MYSQL_HOST_MAXLEN + MYSQL_PASSWORD_LEN + sizeof(char); int i = 0; + char *get_showdbs_priv_query="SELECT * FROM ( (SELECT COUNT(1) AS ndbs FROM INFORMATION_SCHEMA.SCHEMATA) AS tbl1, (SELECT GRANTEE,PRIVILEGE_TYPE from INFORMATION_SCHEMA.USER_PRIVILEGES WHERE privilege_type='SHOW DATABASES' AND REPLACE(GRANTEE, \"\'\",\"\")=CURRENT_USER()) AS tbl2)"; + serviceGetUser(service, &service_user, &service_passwd); if (service_user == NULL || service_passwd == NULL) return -1; @@ -346,14 +345,14 @@ getDatabases(SERVICE *service) { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, - "Error : Unable to get user data from backend database " + "Error : Unable to load db names from backend database " "for service %s. Missing server information.", service->name))); mysql_close(con); return -1; } - if (mysql_query(con, "select count(1) from information_schema.SCHEMATA")) { + if (mysql_query(con, get_showdbs_priv_query)) { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Loading database names for service %s encountered " @@ -363,6 +362,7 @@ getDatabases(SERVICE *service) mysql_close(con); return -1; } + result = mysql_store_result(con); if (result == NULL) { @@ -375,30 +375,33 @@ getDatabases(SERVICE *service) mysql_close(con); return -1; } - num_fields = mysql_num_fields(result); + + /* Result has only one row */ row = mysql_fetch_row(result); - ndbs = atoi(row[0]); + if (row) { + ndbs = atoi(row[0]); + } else { + ndbs = 0; + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Warning: Loading DB names for service [%s] returned 0 rows." + " SHOW DATABASES grant to user [%s] is required for MaxScale DB Name Authentication", + service->name, + service_user))); + } + + /* free resut set */ mysql_free_result(result); - fprintf(stderr, "Found %i schemas\n", ndbs); - if (!ndbs) { - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "Error : Counting users for service %s returned 0", - service->name))); + /* return if no db names are available */ mysql_close(con); - return -1; + + return 0; } - service->resources = (char **) calloc(ndbs+1, sizeof(char *)); - for (i = 0; i < ndbs; i++) { - service->resources[i] = NULL; - } - - service->resources[i] = NULL; if (mysql_query(con, "SHOW DATABASES")) { LOGIF(LE, (skygw_log_write_flush( @@ -407,6 +410,7 @@ getDatabases(SERVICE *service) "error: %s.", service->name, mysql_error(con)))); + mysql_close(con); return -1; } @@ -420,19 +424,33 @@ getDatabases(SERVICE *service) "error: %s.", service->name, mysql_error(con)))); + + /* cleanup the hashtable */ mysql_close(con); return -1; } - num_fields = mysql_num_fields(result); + /* Now populate service->resources hashatable with db names */ + //service->resources = (char **) calloc(ndbs+1, sizeof(char *)); + service->resources = resource_alloc(); + + // if calloc fails, return -1 + + //for (i = 0; i < ndbs; i++) { + // service->resources[i] = NULL; + //} i = 0; while ((row = mysql_fetch_row(result))) { - service->resources[i] = strndup(row[0], MYSQL_DATABASE_MAXLEN); - fprintf(stderr, "Found a Database[%i]:[%s]\n", i, service->resources[i]); + //service->resources[i] = strndup(row[0], MYSQL_DATABASE_MAXLEN); + fprintf(stderr, "Found a Database[%i]:[%s]\n", i, row[0]); + resource_add(service->resources, row[0], ""); i++; } + mysql_free_result(result); + mysql_close(con); + return ndbs; } @@ -450,38 +468,33 @@ getUsers(SERVICE *service, USERS *users) MYSQL *con = NULL; MYSQL_ROW row; MYSQL_RES *result = NULL; - int num_fields = 0; char *service_user = NULL; char *service_passwd = NULL; char *dpwd; int total_users = 0; SERVER *server; char *users_query; - char *dbs_query; unsigned char hash[SHA_DIGEST_LENGTH]=""; char *users_data = NULL; int nusers = 0; /* last byte is for Select_priv=Y|N */ int users_data_row_len = MYSQL_USER_MAXLEN + MYSQL_HOST_MAXLEN + MYSQL_PASSWORD_LEN + sizeof(char); + int dbnames_loaded = 0; - /* enable_root for MySQL protocol module means load the root user credentials from backend databases */ -/* - if(service->enable_root) { - users_query = LOAD_MYSQL_USERS_QUERY " ORDER BY HOST DESC"; - } else { - users_query = LOAD_MYSQL_USERS_QUERY USERS_QUERY_NO_ROOT " ORDER BY HOST DESC"; - } -*/ - if(service->enable_root) { - users_query = LOAD_MYSQL_USERS_WITH_DB_QUERY; - } else { - users_query = LOAD_MYSQL_USERS_WITH_DB_QUERY_NO_ROOT; - } - + serviceGetUser(service, &service_user, &service_passwd); + if (service_user == NULL || service_passwd == NULL) return -1; + /* load all mysql database names */ + dbnames_loaded = mysql_users_load_dbs(service); + + LOGIF(LM, (skygw_log_write( + LOGFILE_MESSAGE, + "Loaded %d MySQL Database Names.", + dbnames_loaded))); + con = mysql_init(NULL); if (con == NULL) { @@ -507,6 +520,7 @@ getUsers(SERVICE *service, USERS *users) */ server = service->databases; dpwd = decryptPassword(service_passwd); + while (server != NULL && (mysql_real_connect(con, server->name, service_user, @@ -525,7 +539,7 @@ getUsers(SERVICE *service, USERS *users) LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Unable to get user data from backend database " - "for service %s. Missing server information.", + "for service [%s]. Missing server information.", service->name))); mysql_close(con); return -1; @@ -534,8 +548,8 @@ getUsers(SERVICE *service, USERS *users) if (mysql_query(con, MYSQL_USERS_COUNT)) { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, - "Error : Loading users for service %s encountered " - "error: %s.", + "Error : Loading users for service [%s] encountered " + "error: [%s].", service->name, mysql_error(con)))); mysql_close(con); @@ -546,14 +560,14 @@ getUsers(SERVICE *service, USERS *users) if (result == NULL) { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, - "Error : Loading users for service %s encountered " - "error: %s.", + "Error : Loading users for service [%s] encountered " + "error: [%s].", service->name, mysql_error(con)))); mysql_close(con); return -1; } - num_fields = mysql_num_fields(result); + row = mysql_fetch_row(result); nusers = atoi(row[0]); @@ -569,15 +583,68 @@ getUsers(SERVICE *service, USERS *users) return -1; } + fprintf(stderr, "---> result from dbnames_loaded = [%i]\n", dbnames_loaded); + + /* enable_root for MySQL protocol module means load the root user credentials from backend databases */ + if (dbnames_loaded > 0) { + fprintf(stderr, "Loading dbnames_loaded = [%i]\n", dbnames_loaded); + if(service->enable_root) { + users_query = LOAD_MYSQL_USERS_WITH_DB_QUERY; + } else { + users_query = LOAD_MYSQL_USERS_WITH_DB_QUERY_NO_ROOT; + } + } else { + if(service->enable_root) { + users_query = LOAD_MYSQL_USERS_QUERY " ORDER BY HOST DESC"; + } else { + users_query = LOAD_MYSQL_USERS_QUERY USERS_QUERY_NO_ROOT " ORDER BY HOST DESC"; + } + } + + /* if there is a Table access denied, try first removing dbanme from user selection */ if (mysql_query(con, users_query)) { - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "Error : Loading users for service %s encountered " - "error: %s.", - service->name, - mysql_error(con)))); - mysql_close(con); - return -1; + /* if not ER_TABLEACCESS_DENIED_ERROR) exit */ + if (1142 != mysql_errno(con)) { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Loading users with dbnames for service [%s] encountered " + "error: [%s], MySQL errno %i", + service->name, + mysql_error(con), + mysql_errno(con)))); + + mysql_close(con); + + return -1; + } else { + /* ER_TABLEACCESS_DENIED_ERROR, try mysql.user only query without DB names */ + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Warning: Loading DB grants failed: GRANT is required on [mysql.db] to user [%s]. Try loading DB users for service [%s] without DB name MaxScale Authentication", service_user, service->name))); + + if(service->enable_root) { + users_query = LOAD_MYSQL_USERS_QUERY " ORDER BY HOST DESC"; + } else { + users_query = LOAD_MYSQL_USERS_QUERY USERS_QUERY_NO_ROOT " ORDER BY HOST DESC"; + } + + if (mysql_query(con, users_query)) { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Loading users for service [%s] encountered " + "error: [%s], code %i", + service->name, + mysql_error(con), + mysql_errno(con)))); + + mysql_close(con); + + return -1; + } + + /* users loaded but without dbnames */ + dbnames_loaded = 0; + } } result = mysql_store_result(con); @@ -589,30 +656,39 @@ getUsers(SERVICE *service, USERS *users) "error: %s.", service->name, mysql_error(con)))); + + mysql_free_result(result); mysql_close(con); + return -1; } - num_fields = mysql_num_fields(result); - + users_data = (char *)calloc(nusers, (users_data_row_len * sizeof(char)) + 1); - if(users_data == NULL) - return -1; + if(users_data == NULL) { + mysql_free_result(result); + mysql_close(con); + return -1; + } + while ((row = mysql_fetch_row(result))) { /** - * Six fields should be returned. + * Up to six fields could be returned. * user,host,passwd,concat(),anydb,db * passwd+1 (escaping the first byte that is '*') */ int rc = 0; - rc = add_mysql_users_with_host_ipv4(users, row[0], row[1], strlen(row[2]) ? row[2]+1 : row[2], row[4], row[5]); + if (dbnames_loaded) + rc = add_mysql_users_with_host_ipv4(users, row[0], row[1], strlen(row[2]) ? row[2]+1 : row[2], row[4], row[5]); + else + rc = add_mysql_users_with_host_ipv4(users, row[0], row[1], strlen(row[2]) ? row[2]+1 : row[2], "Y", NULL); if (rc == 1) { - LOGIF(LD, (skygw_log_write_flush( - LOGFILE_DEBUG, + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, "%lu [mysql_users_add()] Added user %s@%s", pthread_self(), row[0], @@ -704,12 +780,9 @@ int add; * @return The authentication data or NULL on error */ char *mysql_users_fetch(USERS *users, MYSQL_USER_HOST *key) { - MYSQL_USER_HOST *entry; if (key == NULL) return NULL; - atomic_add(&users->stats.n_fetches, 1); - return hashtable_fetch(users->data, key); } @@ -753,15 +826,15 @@ static int uh_cmpfun( void* v1, void* v2) { if (hu1->resource == NULL || (hu1->resource && !strlen(hu1->resource))) { return 0; } else { - /* (1) check for no database grants at all and deny auth*/ + /* (1) check for no database grants at all and deny auth */ if (hu2->resource == NULL) { return 1; } - /* (2) check for ANY database grant and allow auth*/ + /* (2) check for ANY database grant and allow auth */ if (!strlen(hu2->resource)) { return 0; } - /* (3) check for database name specific grant and allow auth*/ + /* (3) check for database name specific grant and allow auth */ if (hu1->resource && hu2->resource && strcmp(hu1->resource,hu2->resource) == 0) { return 0; } @@ -877,9 +950,62 @@ char *mysql_format_user_entry(void *data) strcat(mysql_user, " db: ANY"); } } else { - strcat(mysql_user, " no db"); + //strcat(mysql_user, " no db"); } return mysql_user; } +int +resource_hash(char *key) +{ + return (*key + *(key + 1)); +} + +void +resource_free(HASHTABLE *resources) +{ + hashtable_free(resources); +} + +HASHTABLE * +resource_alloc() +{ +HASHTABLE *resources; + + if ((resources = hashtable_alloc(10, resource_hash, strcmp)) == NULL) + { + return NULL; + } + + hashtable_memory_fns(resources, (HASHMEMORYFN)strdup, (HASHMEMORYFN)strdup, (HASHMEMORYFN)free, (HASHMEMORYFN)free); + + return resources; +} + +int +resource_add(HASHTABLE *resources, char *key, char *value) +{ + return hashtable_add(resources, key, value); +} + +int +resource_delete(HASHTABLE *resources, char *key) +{ + return hashtable_delete(resources, key); +} + +char +*resource_fetch(HASHTABLE *resources, char *key) +{ + return hashtable_fetch(resources, key); +} + +int +resource_update(HASHTABLE *resources, char *key, char *value) +{ + if (hashtable_delete(resources, key) == 0) + return 0; + return hashtable_add(resources, key, value); +} + diff --git a/server/core/service.c b/server/core/service.c index e2cb8dd9d..dca217d87 100644 --- a/server/core/service.c +++ b/server/core/service.c @@ -199,11 +199,14 @@ GWPROTOCOL *funcs; } if (strcmp(port->protocol, "MySQLClient") == 0) { int loaded; - int dbnames_loaded; - /* Allocate specific data for MySQL users */ + /* + * Allocate specific data for MySQL users + * including hosts and db names + */ service->users = mysql_users_alloc(); loaded = load_mysql_users(service); + /* At service start last update is set to USERS_REFRESH_TIME seconds earlier. * This way MaxScale could try reloading users' just after startup */ @@ -211,18 +214,10 @@ GWPROTOCOL *funcs; service->rate_limit.last=time(NULL) - USERS_REFRESH_TIME; service->rate_limit.nloads=1; - LOGIF(LM, (skygw_log_write( - LOGFILE_MESSAGE, - "Loaded %d MySQL Users.", - loaded))); - - /* load all mysql database names */ - dbnames_loaded = mysql_users_load_dbs(service); - LOGIF(LM, (skygw_log_write( LOGFILE_MESSAGE, - "Loaded %d MySQL Databases.", - dbnames_loaded))); + "Loaded %d MySQL Users.", + loaded))); } else { /* Generic users table */ service->users = users_alloc(); diff --git a/server/include/service.h b/server/include/service.h index ef9b73cf9..f68cb0774 100644 --- a/server/include/service.h +++ b/server/include/service.h @@ -23,6 +23,7 @@ #include #include #include +#include #include "config.h" /** @@ -45,6 +46,7 @@ * 29/05/14 Mark Riddoch Filter API mechanism * 26/06/14 Mark Riddoch Added WeightBy support * 09/09/14 Massimiliano Pinto Added service option for localhost authentication + * 09/10/14 Massimiliano Pinto Added service resources via hashtable * * @endverbatim */ @@ -124,7 +126,7 @@ typedef struct service { struct users *users; /**< The user data for this service */ int enable_root; /**< Allow root user access */ int localhost_match_wildcard_host; /**< Match localhost against wildcard */ - char **resources; /** resource list, todo: use hashtables */ + HASHTABLE *resources; /**< hastable for service resources, i.e. database names */ CONFIG_PARAMETER* svc_config_param; /*< list of config params and values */ int svc_config_version; /*< Version number of configuration */ diff --git a/server/modules/protocol/mysql_backend.c b/server/modules/protocol/mysql_backend.c index 75334ae23..2b6dd4d33 100644 --- a/server/modules/protocol/mysql_backend.c +++ b/server/modules/protocol/mysql_backend.c @@ -1246,6 +1246,12 @@ static int gw_change_user( if (strlen(database)) { int i = 0; + if (backend->session->client->service->resources) { + if (hashtable_fetch(backend->session->client->service->resources, database)) { + db_exists = 1; + } + } +/* while(backend->session->client->service->resources[i]) { if (strncmp(database, backend->session->client->service->resources[i], MYSQL_DATABASE_MAXLEN) == 0) { db_exists = 1; @@ -1253,7 +1259,7 @@ static int gw_change_user( i++; } - +*/ if (!db_exists && auth_ret == 0) { auth_ret = 2; } diff --git a/server/modules/protocol/mysql_client.c b/server/modules/protocol/mysql_client.c index ec6ad8d72..f61dca993 100644 --- a/server/modules/protocol/mysql_client.c +++ b/server/modules/protocol/mysql_client.c @@ -495,12 +495,23 @@ static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue) { if (database && strlen(database)) { int i = 0; - while(dcb->service->resources[i]) { - if (strncmp(database, dcb->service->resources[i], MYSQL_DATABASE_MAXLEN) == 0) { - db_exists = 1; + if (dcb->service->resources) { + if (hashtable_fetch(dcb->service->resources, database)) { + db_exists = 1; } + /* fetch dbname */ +/* + while(dcb->service->resources[i]) { + if (strncmp(database, dcb->service->resources[i], MYSQL_DATABASE_MAXLEN) == 0) { + db_exists = 1; + } - i++; + i++; + } +*/ + } else { + /* if database names are not loaded we take dbname as existent */ + db_exists = 1; } if (!db_exists && auth_ret == 0) { From a1f621da306e205a01cbc7d849de14195a87967c Mon Sep 17 00:00:00 2001 From: MassimilianoPinto Date: Tue, 21 Oct 2014 16:46:52 +0200 Subject: [PATCH 16/31] MySQL authentication with db name MySQL authentication with db name --- server/core/dbusers.c | 185 +++++++++++-------------- server/modules/protocol/mysql_client.c | 32 ++--- 2 files changed, 93 insertions(+), 124 deletions(-) diff --git a/server/core/dbusers.c b/server/core/dbusers.c index 0e6ffc0c6..219098409 100644 --- a/server/core/dbusers.c +++ b/server/core/dbusers.c @@ -53,7 +53,7 @@ #define MYSQL_USERS_COUNT "SELECT COUNT(1) AS nusers FROM mysql.user" #define MYSQL_USERS_WITH_DB_ORDER " ORDER BY host DESC" -#define LOAD_MYSQL_USERS_WITH_DB_QUERY "SELECT user.user AS user,user.host AS host,user.password AS password,concat(user.user,user.host,user.password,user.Select_priv) AS userdata, user.Select_priv AS anydb,db.db AS db FROM mysql.user LEFT JOIN mysql.db ON user.user=db.user AND user.host=db.host WHERE user.user IS NOT NULL AND user.user <> ''" MYSQL_USERS_WITH_DB_ORDER +#define LOAD_MYSQL_USERS_WITH_DB_QUERY "SELECT user.user AS user,user.host AS host,user.password AS password,concat(user.user,user.host,user.password,user.Select_priv,IFNULL(db,'')) AS userdata, user.Select_priv AS anydb,db.db AS db FROM mysql.user LEFT JOIN mysql.db ON user.user=db.user AND user.host=db.host WHERE user.user IS NOT NULL AND user.user <> ''" MYSQL_USERS_WITH_DB_ORDER #define LOAD_MYSQL_USERS_WITH_DB_QUERY_NO_ROOT "SELECT * FROM (" LOAD_MYSQL_USERS_WITH_DB_QUERY ") AS t1 WHERE user NOT IN ('root')" MYSQL_USERS_WITH_DB_ORDER @@ -67,7 +67,7 @@ static int uh_hfun( void* key); char *mysql_users_fetch(USERS *users, MYSQL_USER_HOST *key); char *mysql_format_user_entry(void *data); int add_mysql_users_with_host_ipv4(USERS *users, char *user, char *host, char *passwd, char *anydb, char *db); -static int getDatabases(SERVICE *); +static int getDatabases(SERVICE *, MYSQL *); HASHTABLE *resource_alloc(); /** @@ -83,8 +83,8 @@ load_mysql_users(SERVICE *service) return getUsers(service, service->users); } -int mysql_users_load_dbs(SERVICE *service) { - return getDatabases(service); +int mysql_users_load_dbs(SERVICE *service, MYSQL *con) { + return getDatabases(service, con); } /** @@ -207,10 +207,9 @@ int add_mysql_users_with_host_ipv4(USERS *users, char *user, char *host, char *p /* for anydb == Y key.resource is '\0' as set by memset */ if (anydb == NULL) { key.resource = NULL; - } - else { + } else { if (strcmp(anydb, "N") == 0) { - if (db) + if (db != NULL) key.resource = strdup(db); else key.resource = NULL; @@ -265,7 +264,7 @@ int add_mysql_users_with_host_ipv4(USERS *users, char *user, char *host, char *p /* add user@host as key and passwd as value in the MySQL users hash table */ if (mysql_users_add(users, &key, passwd)) { ret = 1; - fprintf(stderr, "Added user %s@%i with db [%s]\n", key.user, key.ipv4.sin_addr.s_addr, key.resource); + fprintf(stderr, "Added user %s@%i with db [%s]\n", key.user, key.ipv4.sin_addr.s_addr, key.resource == NULL ? "NULL" : key.resource); } } @@ -285,15 +284,12 @@ int add_mysql_users_with_host_ipv4(USERS *users, char *user, char *host, char *p * @return -1 on any error or the number of users inserted (0 means no users at all) */ static int -getDatabases(SERVICE *service) +getDatabases(SERVICE *service, MYSQL *con) { - MYSQL *con = NULL; MYSQL_ROW row; MYSQL_RES *result = NULL; char *service_user = NULL; char *service_passwd = NULL; - char *dpwd; - SERVER *server; int ndbs = 0; int i = 0; @@ -303,55 +299,6 @@ getDatabases(SERVICE *service) if (service_user == NULL || service_passwd == NULL) return -1; - con = mysql_init(NULL); - - if (con == NULL) { - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "Error : mysql_init: %s", - mysql_error(con)))); - return -1; - } - - if (mysql_options(con, MYSQL_OPT_USE_REMOTE_CONNECTION, NULL)) { - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "Error : failed to set external connection. " - "It is needed for backend server connections. " - "Exiting."))); - return -1; - } - /** - * Attempt to connect to one of the databases database or until we run - * out of databases - * to try - */ - server = service->databases; - dpwd = decryptPassword(service_passwd); - while (server != NULL && (mysql_real_connect(con, - server->name, - service_user, - dpwd, - NULL, - server->port, - NULL, - 0) == NULL)) - { - server = server->nextdb; - } - free(dpwd); - - if (server == NULL) - { - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "Error : Unable to load db names from backend database " - "for service %s. Missing server information.", - service->name))); - mysql_close(con); - return -1; - } - if (mysql_query(con, get_showdbs_priv_query)) { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, @@ -359,7 +306,6 @@ getDatabases(SERVICE *service) "error: %s.", service->name, mysql_error(con)))); - mysql_close(con); return -1; } @@ -372,7 +318,6 @@ getDatabases(SERVICE *service) "error: %s.", service->name, mysql_error(con)))); - mysql_close(con); return -1; } @@ -397,12 +342,9 @@ getDatabases(SERVICE *service) if (!ndbs) { /* return if no db names are available */ - mysql_close(con); - return 0; } - if (mysql_query(con, "SHOW DATABASES")) { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, @@ -411,7 +353,6 @@ getDatabases(SERVICE *service) service->name, mysql_error(con)))); - mysql_close(con); return -1; } @@ -425,31 +366,20 @@ getDatabases(SERVICE *service) service->name, mysql_error(con)))); - /* cleanup the hashtable */ - mysql_close(con); return -1; } /* Now populate service->resources hashatable with db names */ - //service->resources = (char **) calloc(ndbs+1, sizeof(char *)); service->resources = resource_alloc(); - // if calloc fails, return -1 - - //for (i = 0; i < ndbs; i++) { - // service->resources[i] = NULL; - //} i = 0; while ((row = mysql_fetch_row(result))) { - //service->resources[i] = strndup(row[0], MYSQL_DATABASE_MAXLEN); - fprintf(stderr, "Found a Database[%i]:[%s]\n", i, row[0]); resource_add(service->resources, row[0], ""); - + fprintf(stderr, "Found a Database[%i]:[%s]\n", i, row[0]); i++; } mysql_free_result(result); - mysql_close(con); return ndbs; } @@ -477,9 +407,9 @@ getUsers(SERVICE *service, USERS *users) unsigned char hash[SHA_DIGEST_LENGTH]=""; char *users_data = NULL; int nusers = 0; - /* last byte is for Select_priv=Y|N */ - int users_data_row_len = MYSQL_USER_MAXLEN + MYSQL_HOST_MAXLEN + MYSQL_PASSWORD_LEN + sizeof(char); - int dbnames_loaded = 0; + int users_data_row_len = MYSQL_USER_MAXLEN + MYSQL_HOST_MAXLEN + MYSQL_PASSWORD_LEN + sizeof(char) + MYSQL_DATABASE_MAXLEN; + int dbnames = 0; + int db_grants = 0; serviceGetUser(service, &service_user, &service_passwd); @@ -487,14 +417,6 @@ getUsers(SERVICE *service, USERS *users) if (service_user == NULL || service_passwd == NULL) return -1; - /* load all mysql database names */ - dbnames_loaded = mysql_users_load_dbs(service); - - LOGIF(LM, (skygw_log_write( - LOGFILE_MESSAGE, - "Loaded %d MySQL Database Names.", - dbnames_loaded))); - con = mysql_init(NULL); if (con == NULL) { @@ -545,6 +467,15 @@ getUsers(SERVICE *service, USERS *users) return -1; } + /* load all mysql database names */ + dbnames = mysql_users_load_dbs(service, con); + + LOGIF(LM, (skygw_log_write( + LOGFILE_MESSAGE, + "Loaded %d MySQL Database Names.", + dbnames))); + + /* Load users */ if (mysql_query(con, MYSQL_USERS_COUNT)) { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, @@ -583,17 +514,16 @@ getUsers(SERVICE *service, USERS *users) return -1; } - fprintf(stderr, "---> result from dbnames_loaded = [%i]\n", dbnames_loaded); - /* enable_root for MySQL protocol module means load the root user credentials from backend databases */ - if (dbnames_loaded > 0) { - fprintf(stderr, "Loading dbnames_loaded = [%i]\n", dbnames_loaded); + if (dbnames > 0) { + /* check for root user select */ if(service->enable_root) { users_query = LOAD_MYSQL_USERS_WITH_DB_QUERY; } else { users_query = LOAD_MYSQL_USERS_WITH_DB_QUERY_NO_ROOT; } } else { + /* check for root user select */ if(service->enable_root) { users_query = LOAD_MYSQL_USERS_QUERY " ORDER BY HOST DESC"; } else { @@ -601,10 +531,17 @@ getUsers(SERVICE *service, USERS *users) } } - /* if there is a Table access denied, try first removing dbanme from user selection */ + /* send the query that fetches users and db grants */ if (mysql_query(con, users_query)) { - /* if not ER_TABLEACCESS_DENIED_ERROR) exit */ + /* + * An error occurred executing the query + * + * Check mysql_errno() against ER_TABLEACCESS_DENIED_ERROR) + */ + if (1142 != mysql_errno(con)) { + /* This is an error we cannot handle, return */ + LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Error : Loading users with dbnames for service [%s] encountered " @@ -617,11 +554,16 @@ getUsers(SERVICE *service, USERS *users) return -1; } else { - /* ER_TABLEACCESS_DENIED_ERROR, try mysql.user only query without DB names */ + /* + * We have got ER_TABLEACCESS_DENIED_ERROR + * try loading users from mysql.user without DB names. + */ + LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, "Warning: Loading DB grants failed: GRANT is required on [mysql.db] to user [%s]. Try loading DB users for service [%s] without DB name MaxScale Authentication", service_user, service->name))); - + + /* check for root user select */ if(service->enable_root) { users_query = LOAD_MYSQL_USERS_QUERY " ORDER BY HOST DESC"; } else { @@ -642,9 +584,29 @@ getUsers(SERVICE *service, USERS *users) return -1; } - /* users loaded but without dbnames */ - dbnames_loaded = 0; + // LOG IT + /* users successfully loaded but without db grants */ } + } else { + /* + * users successfully loaded. + * If dbnames > 0 the query loaded also db grants. + */ + + if (dbnames > 0) { + // LOG IT + db_grants = 1; + } + } + + /* + * If database specific grants were not loaded + * we need to delete database names hashtable, if loaded + */ + + if (db_grants == 0 && dbnames > 0) { + // LOG IT + resource_free(service->resources); } result = mysql_store_result(con); @@ -680,11 +642,20 @@ getUsers(SERVICE *service, USERS *users) */ int rc = 0; + char *password; + if (row[2] != NULL) { + if (strlen(row[2]) > 1) + password = row[2] +1; + else + password = row[2]; + } else { + password = strdup(""); + } - if (dbnames_loaded) - rc = add_mysql_users_with_host_ipv4(users, row[0], row[1], strlen(row[2]) ? row[2]+1 : row[2], row[4], row[5]); + if (db_grants > 0) + rc = add_mysql_users_with_host_ipv4(users, row[0], row[1], password, row[4], row[5]); else - rc = add_mysql_users_with_host_ipv4(users, row[0], row[1], strlen(row[2]) ? row[2]+1 : row[2], "Y", NULL); + rc = add_mysql_users_with_host_ipv4(users, row[0], row[1], password, "Y", NULL); if (rc == 1) { LOGIF(LE, (skygw_log_write_flush( @@ -817,9 +788,15 @@ static int uh_cmpfun( void* v1, void* v2) { MYSQL_USER_HOST *hu1 = (MYSQL_USER_HOST *) v1; MYSQL_USER_HOST *hu2 = (MYSQL_USER_HOST *) v2; - if (v1 == NULL || v2 == NULL || hu1 == NULL || hu2 == NULL || hu1->user == NULL || hu2->user == NULL) + if (v1 == NULL || v2 == NULL) return 0; + if (hu1 == NULL || hu2 == NULL) + return 0; + + if (hu1->user == NULL || hu2->user == NULL) + return 0; + if (strcmp(hu1->user, hu2->user) == 0 && (hu1->ipv4.sin_addr.s_addr == hu2->ipv4.sin_addr.s_addr) && (hu1->netmask >= hu2->netmask)) { /* if no database name was passed, auth is ok */ diff --git a/server/modules/protocol/mysql_client.c b/server/modules/protocol/mysql_client.c index f61dca993..2df4ae9ea 100644 --- a/server/modules/protocol/mysql_client.c +++ b/server/modules/protocol/mysql_client.c @@ -456,7 +456,6 @@ static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue) { strncpy(database, (char *)(client_auth_packet + 4 + 4 + 4 + 1 + 23 + strlen(username) + 1 + 1 + auth_token_len), MYSQL_DATABASE_MAXLEN); - fprintf(stderr, "---- Database name passed [%s], flag [%i]\n", database, connect_with_db); } /* allocate memory for token only if auth_token_len > 0 */ @@ -487,36 +486,30 @@ static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue) { } } - fprintf(stderr, "--- Authentication reply is [%i]\n", auth_ret); - /* let's free the auth_token now */ if (auth_token) free(auth_token); if (database && strlen(database)) { - int i = 0; + /* if database names are loaded we can check if db name exists */ if (dcb->service->resources) { if (hashtable_fetch(dcb->service->resources, database)) { - db_exists = 1; + db_exists = 1; + } else { + db_exists = 0; } - /* fetch dbname */ -/* - while(dcb->service->resources[i]) { - if (strncmp(database, dcb->service->resources[i], MYSQL_DATABASE_MAXLEN) == 0) { - db_exists = 1; - } - - i++; - } -*/ } else { - /* if database names are not loaded we take dbname as existent */ - db_exists = 1; + /* if database names are not loaded we don't allow connection */ + db_exists = -1; } - if (!db_exists && auth_ret == 0) { + if (db_exists == 0 && auth_ret == 0) { auth_ret = 2; } + + if (db_exists < 0 && auth_ret == 0) { + auth_ret = 1; + } } if (auth_ret == 0) @@ -753,8 +746,7 @@ int gw_read_client_event( uint8_t* payload = NULL; bool stmt_input; /*< router input type */ - fprintf(stderr, "NBYTES %i\n", nbytes_read); - //ss_dassert(nbytes_read >= 5); + ss_dassert(nbytes_read >= 5); session = dcb->session; ss_dassert( session!= NULL); From 75001fa90b67ed2bd1cd70d702f207aa41ef15ab Mon Sep 17 00:00:00 2001 From: MassimilianoPinto Date: Wed, 22 Oct 2014 12:51:44 +0200 Subject: [PATCH 17/31] added dbnames refresh on auth failure added dbnames refresh on auth failure. DB authentication will take place only if both dbnames and db grants are loaded --- server/core/dbusers.c | 147 ++++++++++++++++++++++-------------------- 1 file changed, 76 insertions(+), 71 deletions(-) diff --git a/server/core/dbusers.c b/server/core/dbusers.c index d99fc5464..fcdc0efe6 100644 --- a/server/core/dbusers.c +++ b/server/core/dbusers.c @@ -57,6 +57,8 @@ #define LOAD_MYSQL_USERS_WITH_DB_QUERY_NO_ROOT "SELECT * FROM (" LOAD_MYSQL_USERS_WITH_DB_QUERY ") AS t1 WHERE user NOT IN ('root')" MYSQL_USERS_WITH_DB_ORDER +#define LOAD_MYSQL_DATABASE_NAMES "SELECT * FROM ( (SELECT COUNT(1) AS ndbs FROM INFORMATION_SCHEMA.SCHEMATA) AS tbl1, (SELECT GRANTEE,PRIVILEGE_TYPE from INFORMATION_SCHEMA.USER_PRIVILEGES WHERE privilege_type='SHOW DATABASES' AND REPLACE(GRANTEE, \"\'\",\"\")=CURRENT_USER()) AS tbl2)" + extern int lm_enabled_logfiles_bitmask; static int getUsers(SERVICE *service, USERS *users); @@ -69,6 +71,10 @@ char *mysql_format_user_entry(void *data); int add_mysql_users_with_host_ipv4(USERS *users, char *user, char *host, char *passwd, char *anydb, char *db); static int getDatabases(SERVICE *, MYSQL *); HASHTABLE *resource_alloc(); +void resource_free(HASHTABLE *resource); +void *resource_fetch(HASHTABLE *, char *); +int resource_add(HASHTABLE *, char *, char *); +int resource_hash(char *); /** * Load the user/passwd form mysql.user table into the service users' hashtable @@ -83,10 +89,6 @@ load_mysql_users(SERVICE *service) return getUsers(service, service->users); } -int mysql_users_load_dbs(SERVICE *service, MYSQL *con) { - return getDatabases(service, con); -} - /** * Reload the user/passwd form mysql.user table into the service users' hashtable * environment. @@ -99,15 +101,26 @@ reload_mysql_users(SERVICE *service) { int i; USERS *newusers, *oldusers; +HASHTABLE *oldresources; if ((newusers = mysql_users_alloc()) == NULL) return 0; + + oldresources = service->resources; + i = getUsers(service, newusers); + spinlock_acquire(&service->spin); oldusers = service->users; + service->users = newusers; + spinlock_release(&service->spin); + + /* free the old table */ users_free(oldusers); + /* free old resources */ + resource_free(oldresources); return i; } @@ -125,14 +138,20 @@ replace_mysql_users(SERVICE *service) { int i; USERS *newusers, *oldusers; +HASHTABLE *oldresources; if ((newusers = mysql_users_alloc()) == NULL) return -1; + oldresources = service->resources; + + /* load db users ad db grants */ i = getUsers(service, newusers); if (i <= 0) { users_free(newusers); + /* restore resources */ + service->resources = oldresources; return i; } @@ -146,6 +165,7 @@ USERS *newusers, *oldusers; LOGFILE_DEBUG, "%lu [replace_mysql_users] users' tables not switched, checksum is the same", pthread_self()))); + /* free the new table */ users_free(newusers); i = 0; @@ -158,10 +178,15 @@ USERS *newusers, *oldusers; service->users = newusers; } + /* free old resources */ + resource_free(oldresources); + spinlock_release(&service->spin); - if (i) + if (i) { + /* free the old table */ users_free(oldusers); + } return i; } @@ -230,6 +255,7 @@ int add_mysql_users_with_host_ipv4(USERS *users, char *user, char *host, char *p tmp = ret_ip+strlen(ret_ip)-1; /* start from Class C */ + while(tmp > ret_ip) { if (*tmp == '%') { /* set only the last IPv4 byte to 1 @@ -263,7 +289,6 @@ int add_mysql_users_with_host_ipv4(USERS *users, char *user, char *host, char *p /* add user@host as key and passwd as value in the MySQL users hash table */ if (mysql_users_add(users, &key, passwd)) { ret = 1; - fprintf(stderr, "Added user %s@%i with db [%s]\n", key.user, key.ipv4.sin_addr.s_addr, key.resource == NULL ? "NULL" : key.resource); } } @@ -275,7 +300,7 @@ int add_mysql_users_with_host_ipv4(USERS *users, char *user, char *host, char *p } /** - * Load the user/passwd form mysql.user table into the service users' hashtable + * Load the database specific grants from mysql.db table into the service resources hashtable * environment. * * @param service The current service @@ -290,9 +315,8 @@ getDatabases(SERVICE *service, MYSQL *con) char *service_user = NULL; char *service_passwd = NULL; int ndbs = 0; - int i = 0; - char *get_showdbs_priv_query="SELECT * FROM ( (SELECT COUNT(1) AS ndbs FROM INFORMATION_SCHEMA.SCHEMATA) AS tbl1, (SELECT GRANTEE,PRIVILEGE_TYPE from INFORMATION_SCHEMA.USER_PRIVILEGES WHERE privilege_type='SHOW DATABASES' AND REPLACE(GRANTEE, \"\'\",\"\")=CURRENT_USER()) AS tbl2)"; + char *get_showdbs_priv_query = LOAD_MYSQL_DATABASE_NAMES; serviceGetUser(service, &service_user, &service_passwd); if (service_user == NULL || service_passwd == NULL) @@ -371,11 +395,9 @@ getDatabases(SERVICE *service, MYSQL *con) /* Now populate service->resources hashatable with db names */ service->resources = resource_alloc(); - i = 0; + /* insert key and value "" */ while ((row = mysql_fetch_row(result))) { resource_add(service->resources, row[0], ""); - fprintf(stderr, "Found a Database[%i]:[%s]\n", i, row[0]); - i++; } mysql_free_result(result); @@ -466,15 +488,7 @@ getUsers(SERVICE *service, USERS *users) return -1; } - /* load all mysql database names */ - dbnames = mysql_users_load_dbs(service, con); - - LOGIF(LM, (skygw_log_write( - LOGFILE_MESSAGE, - "Loaded %d MySQL Database Names.", - dbnames))); - - /* Load users */ + /* count users */ if (mysql_query(con, MYSQL_USERS_COUNT)) { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, @@ -513,24 +527,14 @@ getUsers(SERVICE *service, USERS *users) return -1; } - /* enable_root for MySQL protocol module means load the root user credentials from backend databases */ - if (dbnames > 0) { - /* check for root user select */ - if(service->enable_root) { - users_query = LOAD_MYSQL_USERS_WITH_DB_QUERY; - } else { - users_query = LOAD_MYSQL_USERS_WITH_DB_QUERY_NO_ROOT; - } + if(service->enable_root) { + /* enable_root for MySQL protocol module means load the root user credentials from backend databases */ + users_query = LOAD_MYSQL_USERS_WITH_DB_QUERY; } else { - /* check for root user select */ - if(service->enable_root) { - users_query = LOAD_MYSQL_USERS_QUERY " ORDER BY HOST DESC"; - } else { - users_query = LOAD_MYSQL_USERS_QUERY USERS_QUERY_NO_ROOT " ORDER BY HOST DESC"; - } + users_query = LOAD_MYSQL_USERS_WITH_DB_QUERY_NO_ROOT; } - /* send the query that fetches users and db grants */ + /* send first the query that fetches users and db grants */ if (mysql_query(con, users_query)) { /* * An error occurred executing the query @@ -588,24 +592,10 @@ getUsers(SERVICE *service, USERS *users) } } else { /* - * users successfully loaded. - * If dbnames > 0 the query loaded also db grants. + * users successfully loaded with db grants. */ - if (dbnames > 0) { - // LOG IT - db_grants = 1; - } - } - - /* - * If database specific grants were not loaded - * we need to delete database names hashtable, if loaded - */ - - if (db_grants == 0 && dbnames > 0) { - // LOG IT - resource_free(service->resources); + db_grants = 1; } result = mysql_store_result(con); @@ -632,7 +622,19 @@ getUsers(SERVICE *service, USERS *users) return -1; } - + + if (db_grants) { + /* load all mysql database names */ + dbnames = getDatabases(service, con); + + LOGIF(LM, (skygw_log_write( + LOGFILE_MESSAGE, + "Loaded %d MySQL Database Names.", + dbnames))); + } else { + service->resources = NULL; + } + while ((row = mysql_fetch_row(result))) { /** * Up to six fields could be returned. @@ -926,24 +928,35 @@ char *mysql_format_user_entry(void *data) strcat(mysql_user, " db: ANY"); } } else { - //strcat(mysql_user, " no db"); + strcat(mysql_user, " no db"); } return mysql_user; } +/* + * + */ int resource_hash(char *key) { return (*key + *(key + 1)); } +/* + * + */ void resource_free(HASHTABLE *resources) { - hashtable_free(resources); + if (resources) { + hashtable_free(resources); + } } +/* + * + */ HASHTABLE * resource_alloc() { @@ -959,29 +972,21 @@ HASHTABLE *resources; return resources; } +/* + * + */ int resource_add(HASHTABLE *resources, char *key, char *value) { return hashtable_add(resources, key, value); } -int -resource_delete(HASHTABLE *resources, char *key) -{ - return hashtable_delete(resources, key); -} - -char -*resource_fetch(HASHTABLE *resources, char *key) +/* + * + */ +void * +resource_fetch(HASHTABLE *resources, char *key) { return hashtable_fetch(resources, key); } -int -resource_update(HASHTABLE *resources, char *key, char *value) -{ - if (hashtable_delete(resources, key) == 0) - return 0; - return hashtable_add(resources, key, value); -} - From 6dae7a3c019596e21953f6f653bfae1003c621e4 Mon Sep 17 00:00:00 2001 From: MassimilianoPinto Date: Wed, 22 Oct 2014 17:45:17 +0200 Subject: [PATCH 18/31] Added logging for dbnames Added logging for dbnames --- server/core/dbusers.c | 49 +++++++++++++++++++++++++++++++++---------- 1 file changed, 38 insertions(+), 11 deletions(-) diff --git a/server/core/dbusers.c b/server/core/dbusers.c index fcdc0efe6..58f4b9208 100644 --- a/server/core/dbusers.c +++ b/server/core/dbusers.c @@ -564,7 +564,7 @@ getUsers(SERVICE *service, USERS *users) LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, - "Warning: Loading DB grants failed: GRANT is required on [mysql.db] to user [%s]. Try loading DB users for service [%s] without DB name MaxScale Authentication", service_user, service->name))); + "Error: Loading DB grants failed: GRANT is required on [mysql.db] to user [%s]. Try loading DB users for service [%s] without DB name MaxScale Authentication", service_user, service->name))); /* check for root user select */ if(service->enable_root) { @@ -587,8 +587,13 @@ getUsers(SERVICE *service, USERS *users) return -1; } - // LOG IT /* users successfully loaded but without db grants */ + + LOGIF(LM, (skygw_log_write_flush( + LOGFILE_MESSAGE, + "Loading users from [mysql.user] without DB grants from [mysql.db] for service [%s]." + " MaxScale Authentication with DBname on connect will not work", + service->name))); } } else { /* @@ -629,8 +634,9 @@ getUsers(SERVICE *service, USERS *users) LOGIF(LM, (skygw_log_write( LOGFILE_MESSAGE, - "Loaded %d MySQL Database Names.", - dbnames))); + "Loaded %d MySQL Database Names for service [%s]", + dbnames, + service->name))); } else { service->resources = NULL; } @@ -653,18 +659,39 @@ getUsers(SERVICE *service, USERS *users) password = strdup(""); } - if (db_grants > 0) + if (db_grants) rc = add_mysql_users_with_host_ipv4(users, row[0], row[1], password, row[4], row[5]); else rc = add_mysql_users_with_host_ipv4(users, row[0], row[1], password, "Y", NULL); if (rc == 1) { - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "%lu [mysql_users_add()] Added user %s@%s", - pthread_self(), - row[0], - row[1]))); + if (db_grants) { + char *dbgrant=NULL; + if (row[4] != NULL) { + if (strcmp(row[4], "Y")) + dbgrant = "ANY"; + else { + if (row[5]) + dbgrant = row[5]; + } + } + + /* Log the user being added with its db grants */ + LOGIF(LD, (skygw_log_write_flush( + LOGFILE_DEBUG, + "%lu [mysql_users_add()] Added user %s@%s with DB grants on [%s]", + pthread_self(), + row[0], + row[1], dbgrant != NULL ? dbgrant : "no db"))); + } else { + /* Log the user being added (without db grants) */ + LOGIF(LD, (skygw_log_write_flush( + LOGFILE_DEBUG, + "%lu [mysql_users_add()] Added user %s@%s", + pthread_self(), + row[0], + row[1]))); + } /* Append data in the memory area for SHA1 digest */ strncat(users_data, row[3], users_data_row_len); From 8dd20a10e6b7bd409270098cece514a2eee83a24 Mon Sep 17 00:00:00 2001 From: MassimilianoPinto Date: Wed, 22 Oct 2014 17:56:40 +0200 Subject: [PATCH 19/31] reload users with db auth failed reload users with db auth failed --- server/modules/protocol/mysql_client.c | 58 +++++++++++++++++++------- 1 file changed, 42 insertions(+), 16 deletions(-) diff --git a/server/modules/protocol/mysql_client.c b/server/modules/protocol/mysql_client.c index 2df4ae9ea..ae743374d 100644 --- a/server/modules/protocol/mysql_client.c +++ b/server/modules/protocol/mysql_client.c @@ -477,29 +477,18 @@ static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue) { username, stage1_hash); - /* On failed auth try to load users' table from backend database */ - if (auth_ret != 0) { - if (!service_refresh_users(dcb->service)) { - /* Try authentication again with new repository data */ - /* Note: if no auth client authentication will fail */ - auth_ret = gw_check_mysql_scramble_data(dcb, auth_token, auth_token_len, protocol->scramble, sizeof(protocol->scramble), username, stage1_hash); - } - } - - /* let's free the auth_token now */ - if (auth_token) - free(auth_token); + /* check for dabase name and possible match in resource hashtable */ if (database && strlen(database)) { /* if database names are loaded we can check if db name exists */ - if (dcb->service->resources) { + if (dcb->service->resources != NULL) { if (hashtable_fetch(dcb->service->resources, database)) { db_exists = 1; } else { db_exists = 0; } } else { - /* if database names are not loaded we don't allow connection */ + /* if database names are not loaded we don't allow connection with db name*/ db_exists = -1; } @@ -512,10 +501,47 @@ static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue) { } } - if (auth_ret == 0) - { + /* On failed auth try to load users' table from backend database */ + if (auth_ret != 0) { + if (!service_refresh_users(dcb->service)) { + /* Try authentication again with new repository data */ + /* Note: if no auth client authentication will fail */ + auth_ret = gw_check_mysql_scramble_data(dcb, auth_token, auth_token_len, protocol->scramble, sizeof(protocol->scramble), username, stage1_hash); + } + } + + /* Do again the database check */ + /* check for dabase name and possible match in resource hashtable */ + if (database && strlen(database)) { + /* if database names are loaded we can check if db name exists */ + if (dcb->service->resources != NULL) { + if (hashtable_fetch(dcb->service->resources, database)) { + db_exists = 1; + } else { + db_exists = 0; + } + } else { + /* if database names are not loaded we don't allow connection with db name*/ + db_exists = -1; + } + + if (db_exists == 0 && auth_ret == 0) { + auth_ret = 2; + } + + if (db_exists < 0 && auth_ret == 0) { + auth_ret = 1; + } + } + + if (auth_ret == 0) { dcb->user = strdup(client_data->user); } + + /* let's free the auth_token now */ + if (auth_token) { + free(auth_token); + } return auth_ret; } From cd1ef3722a5c7cf3fbd85b6786ba4d0f4a008315 Mon Sep 17 00:00:00 2001 From: MassimilianoPinto Date: Wed, 22 Oct 2014 18:18:54 +0200 Subject: [PATCH 20/31] db grant log while adding user db grant log while adding user --- server/core/dbusers.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/server/core/dbusers.c b/server/core/dbusers.c index 58f4b9208..4e9a9e954 100644 --- a/server/core/dbusers.c +++ b/server/core/dbusers.c @@ -666,23 +666,27 @@ getUsers(SERVICE *service, USERS *users) if (rc == 1) { if (db_grants) { - char *dbgrant=NULL; + char dbgrant[MYSQL_DATABASE_MAXLEN + 1]=""; if (row[4] != NULL) { if (strcmp(row[4], "Y")) - dbgrant = "ANY"; + strcpy(dbgrant, "ANY"); else { if (row[5]) - dbgrant = row[5]; + strncpy(dbgrant, row[5], MYSQL_DATABASE_MAXLEN); } } + if (!strlen(dbgrant)) + strcpy(dbgrant, "no db"); + /* Log the user being added with its db grants */ LOGIF(LD, (skygw_log_write_flush( LOGFILE_DEBUG, "%lu [mysql_users_add()] Added user %s@%s with DB grants on [%s]", pthread_self(), row[0], - row[1], dbgrant != NULL ? dbgrant : "no db"))); + row[1], + dbgrant))); } else { /* Log the user being added (without db grants) */ LOGIF(LD, (skygw_log_write_flush( From 78227bbe30a620a66ad0223b9e6630ec3d8817bc Mon Sep 17 00:00:00 2001 From: MassimilianoPinto Date: Thu, 23 Oct 2014 17:40:14 +0200 Subject: [PATCH 21/31] Added routine documentation Added routine documentation --- server/core/dbusers.c | 24 ++++++++++++++++++++---- server/core/service.c | 4 ++-- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/server/core/dbusers.c b/server/core/dbusers.c index 4e9a9e954..2aa760996 100644 --- a/server/core/dbusers.c +++ b/server/core/dbusers.c @@ -966,7 +966,10 @@ char *mysql_format_user_entry(void *data) } /* + * The hash function we use for storing MySQL database names. * + * @param key The key value + * @return The hash key */ int resource_hash(char *key) @@ -974,8 +977,10 @@ resource_hash(char *key) return (*key + *(key + 1)); } -/* +/** + * Remove the resources table * + * @param resources The resources table to remove */ void resource_free(HASHTABLE *resources) @@ -985,8 +990,10 @@ resource_free(HASHTABLE *resources) } } -/* +/** + * Allocate a MySQL database names table * + * @return The database names table */ HASHTABLE * resource_alloc() @@ -1003,8 +1010,13 @@ HASHTABLE *resources; return resources; } -/* +/** + * Add a new MySQL database name to the resources table. The resource name must be unique * + * @param resources The resources table + * @param key The resource name + * @param value The value for resource (not used) + * @return The number of resources dded to the table */ int resource_add(HASHTABLE *resources, char *key, char *value) @@ -1012,8 +1024,12 @@ resource_add(HASHTABLE *resources, char *key, char *value) return hashtable_add(resources, key, value); } -/* +/** + * Fetch a particular database name from the resources table * + * @param resources The MySQL database names table + * @param key The database name to fetch + * @return The database esists or NULL if not found */ void * resource_fetch(HASHTABLE *resources, char *key) diff --git a/server/core/service.c b/server/core/service.c index dca217d87..9a5384b2c 100644 --- a/server/core/service.c +++ b/server/core/service.c @@ -216,8 +216,8 @@ GWPROTOCOL *funcs; LOGIF(LM, (skygw_log_write( LOGFILE_MESSAGE, - "Loaded %d MySQL Users.", - loaded))); + "Loaded %d MySQL Users for service [%s].", + loaded, service->name))); } else { /* Generic users table */ service->users = users_alloc(); From 1d380952292efc27bbf0340e813399ab780f6660 Mon Sep 17 00:00:00 2001 From: MassimilianoPinto Date: Thu, 23 Oct 2014 17:45:39 +0200 Subject: [PATCH 22/31] Added history Added history --- server/core/service.c | 1 + 1 file changed, 1 insertion(+) diff --git a/server/core/service.c b/server/core/service.c index 9a5384b2c..52796910c 100644 --- a/server/core/service.c +++ b/server/core/service.c @@ -32,6 +32,7 @@ * 23/05/14 Mark Riddoch Addition of service validation call * 29/05/14 Mark Riddoch Filter API implementation * 09/09/14 Massimiliano Pinto Added service option for localhost authentication + * 13/10/14 Massimiliano Pinto Added hashtable for resources (i.e database names for MySQL services) * * @endverbatim */ From ac2811394e29adbd38f2d0ff40ef9a4c53c3e77c Mon Sep 17 00:00:00 2001 From: MassimilianoPinto Date: Thu, 23 Oct 2014 17:49:59 +0200 Subject: [PATCH 23/31] Removed compile warning for setipaddress Removed compile warning for setipaddress --- server/include/gw.h | 1 + 1 file changed, 1 insertion(+) diff --git a/server/include/gw.h b/server/include/gw.h index 14adfb1a0..23d949ed0 100644 --- a/server/include/gw.h +++ b/server/include/gw.h @@ -70,3 +70,4 @@ int gw_write( size_t nbytes); int gw_getsockerrno(int fd); int parse_bindconfig(char *, unsigned short, struct sockaddr_in *); +int setipaddress(struct in_addr *, char *); From a834b4eeb0058563cda5b64af4a7d4d151411570 Mon Sep 17 00:00:00 2001 From: MassimilianoPinto Date: Thu, 23 Oct 2014 18:31:02 +0200 Subject: [PATCH 24/31] Users correctly counted (with or without dbgrants) Users correctly counted (with or without dbgrants) --- server/core/dbusers.c | 84 ++++++++++++++++++++++++++++--------------- 1 file changed, 56 insertions(+), 28 deletions(-) diff --git a/server/core/dbusers.c b/server/core/dbusers.c index 2aa760996..b859ea29f 100644 --- a/server/core/dbusers.c +++ b/server/core/dbusers.c @@ -55,6 +55,8 @@ #define MYSQL_USERS_WITH_DB_ORDER " ORDER BY host DESC" #define LOAD_MYSQL_USERS_WITH_DB_QUERY "SELECT user.user AS user,user.host AS host,user.password AS password,concat(user.user,user.host,user.password,user.Select_priv,IFNULL(db,'')) AS userdata, user.Select_priv AS anydb,db.db AS db FROM mysql.user LEFT JOIN mysql.db ON user.user=db.user AND user.host=db.host WHERE user.user IS NOT NULL AND user.user <> ''" MYSQL_USERS_WITH_DB_ORDER +#define MYSQL_USERS_WITH_DB_COUNT "SELECT COUNT(1) AS nusers_db FROM (" LOAD_MYSQL_USERS_WITH_DB_QUERY ") AS tbl_count" + #define LOAD_MYSQL_USERS_WITH_DB_QUERY_NO_ROOT "SELECT * FROM (" LOAD_MYSQL_USERS_WITH_DB_QUERY ") AS t1 WHERE user NOT IN ('root')" MYSQL_USERS_WITH_DB_ORDER #define LOAD_MYSQL_DATABASE_NAMES "SELECT * FROM ( (SELECT COUNT(1) AS ndbs FROM INFORMATION_SCHEMA.SCHEMATA) AS tbl1, (SELECT GRANTEE,PRIVILEGE_TYPE from INFORMATION_SCHEMA.USER_PRIVILEGES WHERE privilege_type='SHOW DATABASES' AND REPLACE(GRANTEE, \"\'\",\"\")=CURRENT_USER()) AS tbl2)" @@ -416,22 +418,21 @@ getDatabases(SERVICE *service, MYSQL *con) static int getUsers(SERVICE *service, USERS *users) { - MYSQL *con = NULL; - MYSQL_ROW row; - MYSQL_RES *result = NULL; - char *service_user = NULL; - char *service_passwd = NULL; - char *dpwd; - int total_users = 0; - SERVER *server; - char *users_query; - unsigned char hash[SHA_DIGEST_LENGTH]=""; - char *users_data = NULL; - int nusers = 0; - int users_data_row_len = MYSQL_USER_MAXLEN + MYSQL_HOST_MAXLEN + MYSQL_PASSWORD_LEN + sizeof(char) + MYSQL_DATABASE_MAXLEN; - int dbnames = 0; - int db_grants = 0; - + MYSQL *con = NULL; + MYSQL_ROW row; + MYSQL_RES *result = NULL; + char *service_user = NULL; + char *service_passwd = NULL; + char *dpwd; + int total_users = 0; + SERVER *server; + char *users_query; + unsigned char hash[SHA_DIGEST_LENGTH]=""; + char *users_data = NULL; + int nusers = 0; + int users_data_row_len = MYSQL_USER_MAXLEN + MYSQL_HOST_MAXLEN + MYSQL_PASSWORD_LEN + sizeof(char) + MYSQL_DATABASE_MAXLEN; + int dbnames = 0; + int db_grants = 0; serviceGetUser(service, &service_user, &service_passwd); @@ -489,16 +490,37 @@ getUsers(SERVICE *service, USERS *users) } /* count users */ - if (mysql_query(con, MYSQL_USERS_COUNT)) { - LOGIF(LE, (skygw_log_write_flush( - LOGFILE_ERROR, - "Error : Loading users for service [%s] encountered " - "error: [%s].", - service->name, - mysql_error(con)))); - mysql_close(con); - return -1; + + /* start with users and db grants for users */ + if (mysql_query(con, MYSQL_USERS_WITH_DB_COUNT)) { + if (1142 != mysql_errno(con)) { + /* This is an error we cannot handle, return */ + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Loading users for service [%s] encountered " + "error: [%s].", + service->name, + mysql_error(con)))); + mysql_close(con); + return -1; + } else { + /* + * We have got ER_TABLEACCESS_DENIED_ERROR + * try counting users from mysql.user without DB names. + */ + if (mysql_query(con, MYSQL_USERS_COUNT)) { + LOGIF(LE, (skygw_log_write_flush( + LOGFILE_ERROR, + "Error : Loading users for service [%s] encountered " + "error: [%s].", + service->name, + mysql_error(con)))); + mysql_close(con); + return -1; + } + } } + result = mysql_store_result(con); if (result == NULL) { @@ -641,7 +663,8 @@ getUsers(SERVICE *service, USERS *users) service->resources = NULL; } - while ((row = mysql_fetch_row(result))) { + while ((row = mysql_fetch_row(result))) { + /** * Up to six fields could be returned. * user,host,passwd,concat(),anydb,db @@ -659,6 +682,10 @@ getUsers(SERVICE *service, USERS *users) password = strdup(""); } + /* + * add user@host and DB global priv and specificsa grant (if possible) + */ + if (db_grants) rc = add_mysql_users_with_host_ipv4(users, row[0], row[1], password, row[4], row[5]); else @@ -704,10 +731,11 @@ getUsers(SERVICE *service, USERS *users) } else { LOGIF(LE, (skygw_log_write_flush( LOGFILE_ERROR, - "%lu [mysql_users_add()] Failed adding user %s@%s", + "%lu [mysql_users_add()] Failed adding user %s@%si for service [%s]", pthread_self(), row[0], - row[1]))); + row[1], + service->name))); } } From 35996a40cbc40d9143fa49d0524b422fd8eb2f25 Mon Sep 17 00:00:00 2001 From: MassimilianoPinto Date: Thu, 23 Oct 2014 19:36:25 +0200 Subject: [PATCH 25/31] Reply messages for failed db authentication Reply messages for failed db authentication --- server/core/modutil.c | 106 ++++++++++++++++++++++++ server/modules/protocol/mysql_backend.c | 45 ++++------ server/modules/protocol/mysql_client.c | 87 +++++++------------ server/modules/protocol/mysql_common.c | 29 +++++++ 4 files changed, 182 insertions(+), 85 deletions(-) diff --git a/server/core/modutil.c b/server/core/modutil.c index cb4ed0a28..b95c0b019 100644 --- a/server/core/modutil.c +++ b/server/core/modutil.c @@ -226,3 +226,109 @@ modutil_get_query(GWBUF *buf) retblock: return query_str; } + + +/* create mysql response packet */ + +GWBUF* modutil_create_mysql_packet( + int packet_number, + int affected_rows, + int merrno, + 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; + + mysql_errno = (unsigned int)merrno; + mysql_error_msg = msg; + mysql_state = statemsg; + + field_count = 0xff; + + gw_mysql_set_byte2(mysql_err, mysql_errno); + + mysql_statemsg[0]='#'; + memcpy(mysql_statemsg+1, mysql_state, 5); + + if (msg != NULL) { + mysql_error_msg = msg; + } + mysql_payload_size = sizeof(field_count) + + sizeof(mysql_err) + + sizeof(mysql_statemsg) + + strlen(mysql_error_msg); + + /** allocate memory for packet header + payload */ + errbuf = gwbuf_alloc(sizeof(mysql_packet_header) + mysql_payload_size); + ss_dassert(errbuf != NULL); + + if (errbuf == NULL) + { + return 0; + } + outbuf = GWBUF_DATA(errbuf); + + /** write packet header and 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 error message */ + memcpy(mysql_payload, mysql_error_msg, strlen(mysql_error_msg)); + + return errbuf; +} + +/** + * 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 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 + * + */ +int modutil_send_mysql_packet ( + DCB *dcb, + int packet_number, + int in_affected_rows, + const char *mysql_message) +{ + GWBUF* buf; + + buf = modutil_create_mysql_packet(packet_number, in_affected_rows, 1049, "42000", mysql_message); + + return dcb->func.write(dcb, buf); +} + diff --git a/server/modules/protocol/mysql_backend.c b/server/modules/protocol/mysql_backend.c index 2b6dd4d33..074c9c82c 100644 --- a/server/modules/protocol/mysql_backend.c +++ b/server/modules/protocol/mysql_backend.c @@ -1192,7 +1192,6 @@ static int gw_change_user( uint8_t *auth_token = NULL; int rv = -1; int auth_ret = 1; - int db_exists = 0; current_session = (MYSQL_session *)in_session->client->data; backend_protocol = backend->protocol; @@ -1220,13 +1219,14 @@ static int gw_change_user( client_auth_packet += auth_token_len; } + /* get new database name */ + strcpy(database, (char *)client_auth_packet); + /* save current_database name */ strcpy(current_database, current_session->db); - /* get new database name */ - strcpy(database, (char *)client_auth_packet); - /* set it to current dtabase */ - strcpy(current_session->db, database); + /* empty database name in dcb */ + strcpy(current_session->db, ""); // decode the token and check the password // Note: if auth_token_len == 0 && auth_token == NULL, user is without password @@ -1240,45 +1240,30 @@ 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 if (auth_token) free(auth_token); - if (strlen(database)) { - int i = 0; - if (backend->session->client->service->resources) { - if (hashtable_fetch(backend->session->client->service->resources, database)) { - db_exists = 1; - } - } -/* - while(backend->session->client->service->resources[i]) { - if (strncmp(database, backend->session->client->service->resources[i], MYSQL_DATABASE_MAXLEN) == 0) { - db_exists = 1; - } - - i++; - } -*/ - if (!db_exists && auth_ret == 0) { - auth_ret = 2; - } - } - if (auth_ret != 0) { + char *message; + + message = calloc(1,100); + strcpy(message, "change user authentication failed"); - char *message = create_auth_failed_msg(queue, "ipaddr", client_sha1, auth_ret); /* 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); free(message); - /* copy back current datbase to client session */ - strcpy(current_session->db, current_database); - 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); /*< diff --git a/server/modules/protocol/mysql_client.c b/server/modules/protocol/mysql_client.c index ae743374d..bcca6a1e1 100644 --- a/server/modules/protocol/mysql_client.c +++ b/server/modules/protocol/mysql_client.c @@ -44,6 +44,7 @@ #include #include #include +#include MODULE_INFO info = { MODULE_API_PROTOCOL, @@ -69,8 +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* sha1); +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 *); /* * The "module object" for the mysqld client protocol module. @@ -466,7 +468,8 @@ static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue) { auth_token_len); } - /* decode the token and check the password + /* + * Decode the token and check the password * Note: if auth_token_len == 0 && auth_token == NULL, user is without password */ @@ -477,29 +480,8 @@ static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue) { username, stage1_hash); - - /* check for dabase name and possible match in resource hashtable */ - if (database && strlen(database)) { - /* if database names are loaded we can check if db name exists */ - if (dcb->service->resources != NULL) { - if (hashtable_fetch(dcb->service->resources, database)) { - db_exists = 1; - } else { - db_exists = 0; - } - } else { - /* if database names are not loaded we don't allow connection with db name*/ - db_exists = -1; - } - - if (db_exists == 0 && auth_ret == 0) { - auth_ret = 2; - } - - if (db_exists < 0 && auth_ret == 0) { - auth_ret = 1; - } - } + /* check for database name match in resource hashtable */ + auth_ret = check_db_name_after_auth(dcb, database, auth_ret); /* On failed auth try to load users' table from backend database */ if (auth_ret != 0) { @@ -511,29 +493,9 @@ static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue) { } /* Do again the database check */ - /* check for dabase name and possible match in resource hashtable */ - if (database && strlen(database)) { - /* if database names are loaded we can check if db name exists */ - if (dcb->service->resources != NULL) { - if (hashtable_fetch(dcb->service->resources, database)) { - db_exists = 1; - } else { - db_exists = 0; - } - } else { - /* if database names are not loaded we don't allow connection with db name*/ - db_exists = -1; - } - - if (db_exists == 0 && auth_ret == 0) { - auth_ret = 2; - } - - if (db_exists < 0 && auth_ret == 0) { - auth_ret = 1; - } - } + auth_ret = check_db_name_after_auth(dcb, database, auth_ret); + /* on succesful auth set user into dcb field */ if (auth_ret == 0) { dcb->user = strdup(client_data->user); } @@ -549,11 +511,23 @@ static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue) { static char* create_auth_fail_str( GWBUF* readbuf, char* hostaddr, - char* sha1) + char* sha1, + char* db) { char* errstr; char* uname; - const char* ferrstr = "Access denied for user '%s'@'%s' (using password: %s)"; + 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) { @@ -561,12 +535,14 @@ static char* create_auth_fail_str( goto retblock; } /** -4 comes from 2X'%s' minus terminating char */ - errstr = (char *)malloc(strlen(uname)+strlen(ferrstr)+strlen(hostaddr)+strlen("YES")-6+1); + errstr = (char *)malloc(strlen(uname)+strlen(ferrstr)+strlen(hostaddr)+strlen("YES")-6 + db_len + 1); - if (errstr != NULL) + if (errstr != NULL && db_len>0) { - sprintf(errstr, ferrstr, uname, hostaddr, (*sha1 == '\0' ? "NO" : "YES")); + sprintf(errstr, ferrstr, uname, hostaddr, (*sha1 == '\0' ? "NO" : "YES"), db); } + else + sprintf(errstr, ferrstr, uname, hostaddr, (*sha1 == '\0' ? "NO" : "YES")); free(uname); retblock: @@ -733,17 +709,19 @@ int gw_read_client_event( dberr= calloc(1, 100); sprintf(dberr, "Unknown database '%s'", (char*)((MYSQL_session *)dcb->data)->db); - mysql_send_auth_error( + modutil_send_mysql_packet( + //mysql_send_auth_error( dcb, 2, 0, dberr); + free(dberr); } else { /** Send error 1045 to client */ fail_str = create_auth_fail_str(read_buffer, dcb->remote, - (char*)((MYSQL_session *)dcb->data)->client_sha1); + (char*)((MYSQL_session *)dcb->data)->client_sha1, (char*)((MYSQL_session *)dcb->data)->db); mysql_send_auth_error( dcb, 2, @@ -1640,4 +1618,3 @@ return_str: } #endif - diff --git a/server/modules/protocol/mysql_common.c b/server/modules/protocol/mysql_common.c index ba453b166..c294bc7ba 100644 --- a/server/modules/protocol/mysql_common.c +++ b/server/modules/protocol/mysql_common.c @@ -2046,3 +2046,32 @@ retblock: return rval; } + +int check_db_name_after_auth(DCB *dcb, char *database, int auth_ret) { + int db_exists = -1; + + /* check for dabase name and possible match in resource hashtable */ + if (database && strlen(database)) { + /* if database names are loaded we can check if db name exists */ + if (dcb->service->resources != NULL) { + if (hashtable_fetch(dcb->service->resources, database)) { + db_exists = 1; + } else { + db_exists = 0; + } + } else { + /* if database names are not loaded we don't allow connection with db name*/ + db_exists = -1; + } + + if (db_exists == 0 && auth_ret == 0) { + auth_ret = 2; + } + + if (db_exists < 0 && auth_ret == 0) { + auth_ret = 1; + } + } + + return auth_ret; +} From 970511a275cb74161ec916d2044b260f9f6779b3 Mon Sep 17 00:00:00 2001 From: MassimilianoPinto Date: Fri, 24 Oct 2014 12:20:50 +0200 Subject: [PATCH 26/31] Update for message errors in DB authentication Update for message errors in DB authentication --- server/core/dbusers.c | 2 +- server/core/modutil.c | 66 +++++++++++---------- server/include/modutil.h | 11 ++-- server/modules/protocol/mysql_backend.c | 58 ++++++++++++------- server/modules/protocol/mysql_client.c | 77 +++++-------------------- server/modules/protocol/mysql_common.c | 50 +++++++++++++++- 6 files changed, 142 insertions(+), 122 deletions(-) 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; +} From c207b8b990200965930c3383fb25a608c38ac41f Mon Sep 17 00:00:00 2001 From: MassimilianoPinto Date: Fri, 24 Oct 2014 12:33:38 +0200 Subject: [PATCH 27/31] Added routines comment Added routines comment --- server/core/modutil.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/server/core/modutil.c b/server/core/modutil.c index 59e7a1419..e5b557cc3 100644 --- a/server/core/modutil.c +++ b/server/core/modutil.c @@ -231,9 +231,16 @@ retblock: /** -* create mysql response packet */ + * create a GWBUFF with a MySQL ERR 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 The allocated GWBUF or NULL on failure */ -GWBUF* modutil_create_mysql_packet( +GWBUF *modutil_create_mysql_err_msg( int packet_number, int affected_rows, int merrno, @@ -276,9 +283,8 @@ GWBUF* modutil_create_mysql_packet( ss_dassert(errbuf != NULL); if (errbuf == NULL) - { - return 0; - } + return NULL; + outbuf = GWBUF_DATA(errbuf); /** write packet header and packet number */ From 9eb0e3088b36ffe65b15a5af27babfac861c3ad7 Mon Sep 17 00:00:00 2001 From: MassimilianoPinto Date: Fri, 24 Oct 2014 16:00:55 +0200 Subject: [PATCH 28/31] Updated debug log messages --- server/core/dbusers.c | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/server/core/dbusers.c b/server/core/dbusers.c index e8fe2b46a..784c4fe84 100644 --- a/server/core/dbusers.c +++ b/server/core/dbusers.c @@ -321,6 +321,7 @@ getDatabases(SERVICE *service, MYSQL *con) char *get_showdbs_priv_query = LOAD_MYSQL_DATABASE_NAMES; serviceGetUser(service, &service_user, &service_passwd); + if (service_user == NULL || service_passwd == NULL) return -1; @@ -650,7 +651,7 @@ getUsers(SERVICE *service, USERS *users) return -1; } - if (db_grants) { + if (db_grants) { /* load all mysql database names */ dbnames = getDatabases(service, con); @@ -658,8 +659,7 @@ getUsers(SERVICE *service, USERS *users) LOGFILE_MESSAGE, "Loaded %d MySQL Database Names for service [%s]", dbnames, - service->name))); - } else { + else { service->resources = NULL; } @@ -672,24 +672,25 @@ getUsers(SERVICE *service, USERS *users) */ int rc = 0; - char *password; + char *password = NULL; if (row[2] != NULL) { if (strlen(row[2]) > 1) password = row[2] +1; else password = row[2]; - } else { - password = strdup(""); } /* * add user@host and DB global priv and specificsa grant (if possible) */ - if (db_grants) + if (db_grants) { + /* we have dbgrants, store them */ rc = add_mysql_users_with_host_ipv4(users, row[0], row[1], password, row[4], row[5]); - else + } else { + /* we don't have dbgrants, simply set ANY DB for the user */ rc = add_mysql_users_with_host_ipv4(users, row[0], row[1], password, "Y", NULL); + } if (rc == 1) { if (db_grants) { @@ -708,20 +709,20 @@ getUsers(SERVICE *service, USERS *users) /* Log the user being added with its db grants */ LOGIF(LD, (skygw_log_write_flush( - LOGFILE_DEBUG, - "%lu [mysql_users_add()] Added user %s@%s with DB grants on [%s]", - pthread_self(), - row[0], - row[1], - dbgrant))); + LOGFILE_DEBUG, + "%lu [mysql_users_add()] Added user %s@%s with DB grants on [%s]", + pthread_self(), + row[0], + row[1], + dbgrant))); } else { /* Log the user being added (without db grants) */ LOGIF(LD, (skygw_log_write_flush( LOGFILE_DEBUG, - "%lu [mysql_users_add()] Added user %s@%s", - pthread_self(), - row[0], - row[1]))); + "%lu [mysql_users_add()] Added user %s@%s", + pthread_self(), + row[0], + row[1]))); } /* Append data in the memory area for SHA1 digest */ From 9541f6529c904c0b0ec42cb82e5ad2deedf9bad0 Mon Sep 17 00:00:00 2001 From: MassimilianoPinto Date: Fri, 24 Oct 2014 16:04:47 +0200 Subject: [PATCH 29/31] Added missing statement and bracket Added missing statement and bracket --- server/core/dbusers.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/core/dbusers.c b/server/core/dbusers.c index 784c4fe84..8bdee800a 100644 --- a/server/core/dbusers.c +++ b/server/core/dbusers.c @@ -659,7 +659,8 @@ getUsers(SERVICE *service, USERS *users) LOGFILE_MESSAGE, "Loaded %d MySQL Database Names for service [%s]", dbnames, - else { + service->name))); + } else { service->resources = NULL; } From 83257aad5563edf4175d6518a4656f6e6dc34aec Mon Sep 17 00:00:00 2001 From: MassimilianoPinto Date: Fri, 24 Oct 2014 16:49:55 +0200 Subject: [PATCH 30/31] MySQL DB authentication tests MySQL DB authentication tests update --- server/core/test/test_mysql_users.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/server/core/test/test_mysql_users.c b/server/core/test/test_mysql_users.c index e2d15a960..090d0d3c0 100644 --- a/server/core/test/test_mysql_users.c +++ b/server/core/test/test_mysql_users.c @@ -185,7 +185,6 @@ int set_and_get_single_mysql_users(char *username, char *hostname, char *passwor int set_and_get_mysql_users_wildcards(char *username, char *hostname, char *password, char *from, char *anydb, char *db, char *db_from) { USERS *mysql_users; int ret = -1; - int rc = -1; struct sockaddr_in client_addr; DCB *dcb; SERVICE *service; @@ -258,9 +257,6 @@ int set_and_get_mysql_users_wildcards(char *username, char *hostname, char *pass unsigned char db_passwd[100]=""; dcb->remote=strdup(from); - //fprintf(stderr, "add_mysql_users_with_host_ipv4 passed(%s@%s, %s) OK\n", username, hostname, password); - - //fprintf(stderr, "Checking '%s' @ '%s' against (%s@%s)\n", username, from, username, hostname); // returns 0 on success ret = gw_find_mysql_user_password_sha1(username, db_passwd, dcb); From 7b5d2df7a6b1f87ea009747b3143a7fdb4f9f87f Mon Sep 17 00:00:00 2001 From: MassimilianoPinto Date: Fri, 24 Oct 2014 17:21:07 +0200 Subject: [PATCH 31/31] mysql_send_custom_error returns write operation mysql_send_custom_error returns write operation --- server/modules/protocol/mysql_common.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/server/modules/protocol/mysql_common.c b/server/modules/protocol/mysql_common.c index 3acc91243..ab1fab315 100644 --- a/server/modules/protocol/mysql_common.c +++ b/server/modules/protocol/mysql_common.c @@ -1004,7 +1004,7 @@ GWBUF* mysql_create_custom_error( * @param packet_number * @param in_affected_rows * @param mysql_message - * @return packet length + * @return 1 Non-zero if data was sent * */ int mysql_send_custom_error ( @@ -1017,9 +1017,7 @@ int mysql_send_custom_error ( buf = mysql_create_custom_error(packet_number, in_affected_rows, mysql_message); - dcb->func.write(dcb, buf); - - return GWBUF_LENGTH(buf); + return dcb->func.write(dcb, buf); } /**