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,