MySQL authentication with user@host

MySQL authentication with user@host: user, host and pass word are
loaded from backend servers.
Host is currently handled as IPv4 address
This commit is contained in:
MassimilianoPinto
2014-02-14 15:56:48 +01:00
parent dd5c287339
commit 6a12e99001
6 changed files with 364 additions and 29 deletions

View File

@ -26,6 +26,7 @@
* 24/06/2013 Massimiliano Pinto Initial implementation
* 08/08/2013 Massimiliano Pinto Fixed bug for invalid memory access in row[1]+1 when row[1] is ""
* 06/02/2014 Massimiliano Pinto Mysql user root selected based on configuration flag
* 07/02/2014 Massimiliano Pinto Added Mysql user@host authentication
*
* @endverbatim
*/
@ -39,13 +40,20 @@
#include <skygw_utils.h>
#include <log_manager.h>
#include <secrets.h>
#include <dbusers.h>
#define USERS_QUERY_NO_ROOT " WHERE user NOT IN ('root')"
#define LOAD_MYSQL_USERS_QUERY "SELECT user, password FROM mysql.user"
#define USERS_QUERY_NO_ROOT " AND user NOT IN ('root')"
#define LOAD_MYSQL_USERS_QUERY "SELECT user, host, password FROM mysql.user WHERE user IS NOT NULL AND user <> ''"
extern int lm_enabled_logfiles_bitmask;
static int getUsers(SERVICE *service, struct 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);
char *mysql_format_user_entry(void *data);
/**
* Load the user/passwd form mysql.user table into the service users' hashtable
@ -73,7 +81,7 @@ reload_mysql_users(SERVICE *service)
int i;
struct users *newusers, *oldusers;
if ((newusers = users_alloc()) == NULL)
if ((newusers = mysql_users_alloc()) == NULL)
return 0;
i = getUsers(service, newusers);
spinlock_acquire(&service->spin);
@ -105,14 +113,19 @@ getUsers(SERVICE *service, struct users *users)
char *dpwd;
int total_users = 0;
SERVER *server;
char *users_query;
struct sockaddr_in serv_addr;
MYSQL_USER_HOST key;
char *users_query;
/* 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_QUERY;
else
users_query = LOAD_MYSQL_USERS_QUERY USERS_QUERY_NO_ROOT;
serviceGetUser(service, &service_user, &service_passwd);
/** multi-thread environment requires that thread init succeeds. */
if (mysql_thread_init()) {
LOGIF(LE, (skygw_log_write_flush(
@ -158,6 +171,7 @@ getUsers(SERVICE *service, struct users *users)
server = server->nextdb;
}
free(dpwd);
if (server == NULL)
{
LOGIF(LE, (skygw_log_write_flush(
@ -193,17 +207,260 @@ getUsers(SERVICE *service, struct users *users)
}
num_fields = mysql_num_fields(result);
while ((row = mysql_fetch_row(result))) {
while ((row = mysql_fetch_row(result))) {
char ret_ip[INET_ADDRSTRLEN + 1]="";
const char *rc;
/**
* Two fields should be returned.
* user and passwd+1 (escaping the first byte that is '*') are
* added to hashtable.
*/
users_add(users, row[0], strlen(row[1]) ? row[1]+1 : row[1]);
total_users++;
/* prepare the user@host data struct */
memset(&serv_addr, 0, sizeof(serv_addr));
memset(&key, 0, sizeof(key));
/* if host == '%', 0 is passed */
if (setipaddress(&serv_addr.sin_addr, strcmp(row[1], "%") ? row[1] : "0.0.0.0")) {
key.user = strdup(row[0]);
if(key.user == NULL) {
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"%lu [getUsers()] strdup() failed for user %s\n",
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)\n",
pthread_self(),
row[0],
row[1],
rc == NULL ? "NULL" : ret_ip)));
total_users++;
} else {
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"%lu [mysql_users_add()] Failed adding user user %s@%s(%s)\n",
pthread_self(),
row[0],
row[1],
rc == NULL ? "NULL" : ret_ip)));
continue;
}
} else {
/* setipaddress() failed, skip user add and log this*/
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"%lu [getUsers()] setipaddress failed: user NOT added %s@%s\n",
pthread_self(),
row[0],
row[1])));
}
}
mysql_free_result(result);
mysql_close(con);
mysql_thread_end();
return total_users;
}
/**
* Allocate a new MySQL users table for mysql specific users@host as key
*
* @return The users table
*/
USERS *
mysql_users_alloc()
{
USERS *rval;
if ((rval = calloc(1, sizeof(USERS))) == NULL)
return NULL;
if ((rval->data = hashtable_alloc(52, uh_hfun, uh_cmpfun)) == NULL) {
free(rval);
return NULL;
}
/* set the MySQL user@host print routine for the debug interface */
rval->usersCustomUserFormat = mysql_format_user_entry;
/* the key is handled by uh_keydup/uh_keyfree.
* the value is a (char *): it's handled by strdup/free
*/
hashtable_memory_fns(rval->data, (HASHMEMORYFN)uh_keydup, (HASHMEMORYFN) strdup, (HASHMEMORYFN)uh_keyfree, (HASHMEMORYFN)free);
return rval;
}
/**
* Add a new MySQL user to the user table. The user name must be unique
*
* @param users The users table
* @param user The user name
* @param auth The authentication data
* @return The number of users added to the table
*/
int
mysql_users_add(USERS *users, MYSQL_USER_HOST *key, char *auth)
{
int add;
if (key == NULL || key->user == NULL)
return 0;
atomic_add(&users->stats.n_adds, 1);
add = hashtable_add(users->data, key, auth);
atomic_add(&users->stats.n_entries, add);
return add;
}
/**
* Fetch the authentication data for a particular user from the users table
*
* @param users The MySQL users table
* @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) {
if (key == NULL)
return NULL;
atomic_add(&users->stats.n_fetches, 1);
return hashtable_fetch(users->data, key);
}
/**
* The hash function we use for storing MySQL users as: users@hosts.
* Currently only IPv4 addresses are supported
*
* @param key The key value, i.e. username@host (IPv4)
* @return The hash key
*/
static int uh_hfun( void* key) {
MYSQL_USER_HOST *hu = (MYSQL_USER_HOST *) key;
if (key == NULL || hu == NULL || hu->user == NULL) {
return 0;
} else {
return (*hu->user + *(hu->user + 1) + (unsigned int) (hu->ipv4.sin_addr.s_addr & 0xFF000000 / (256 * 256 * 256)));
}
}
/**
* The compare function we use for compare MySQL users as: users@hosts.
* 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)
* @return The compare value
*/
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)
return 0;
if (strcmp(hu1->user, hu2->user) == 0 && (hu1->ipv4.sin_addr.s_addr == hu2->ipv4.sin_addr.s_addr)) {
return 0;
} else {
return 1;
}
}
/**
*The key dup function we use for duplicate the users@hosts.
*
* @param key The key value, i.e. username@host ip4/ip6 data
*/
static void *uh_keydup(void* key) {
MYSQL_USER_HOST *rval = (MYSQL_USER_HOST *) calloc(1, sizeof(MYSQL_USER_HOST));
MYSQL_USER_HOST *current_key = (MYSQL_USER_HOST *)key;
if (key == NULL || rval == NULL || current_key == NULL || current_key->user == NULL) {
return NULL;
}
rval->user = strdup(current_key->user);
if (rval->user == NULL)
return NULL;
memcpy(&rval->ipv4, &current_key->ipv4, sizeof(struct sockaddr_in));
return (void *) rval;
}
/**
* The key free function we use for freeing the users@hosts data
*
* @param key The key value, i.e. username@host ip4 data
*/
static void uh_keyfree( void* key) {
MYSQL_USER_HOST *current_key = (MYSQL_USER_HOST *)key;
if (key == NULL)
return;
if (current_key && current_key->user)
free(current_key->user);
free(key);
}
/**
* Format the mysql user as user@host
* The returned memory must be freed by the caller
*
* @param data Input data
* @return the MySQL user@host
*/
char *mysql_format_user_entry(void *data)
{
MYSQL_USER_HOST *entry;
char *mysql_user;
/* the returned user string is "USER@HOST" */
int mysql_user_len = 128 + 1 + INET_ADDRSTRLEN + 1;
if (data == NULL)
return NULL;
entry = (MYSQL_USER_HOST *) data;
if (entry == NULL)
return NULL;
mysql_user = (char *) calloc(mysql_user_len, sizeof(char));
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 {
snprintf(mysql_user, 128, entry->user);
strcat(mysql_user, "@");
inet_ntop(AF_INET, &(entry->ipv4).sin_addr, mysql_user+strlen(mysql_user), INET_ADDRSTRLEN);
}
return mysql_user;
}

View File

@ -26,6 +26,8 @@
* 18/06/13 Mark Riddoch Initial implementation
* 24/06/13 Massimiliano Pinto Added: Loading users from mysql backend in serviceStart
* 06/02/14 Massimiliano Pinto Added: serviceEnableRootUser routine
* 14/02/14 Massimiliano Pinto users_alloc moved from service_alloc to serviceStartPort (generic hashable for services)
*
* @endverbatim
*/
#include <stdio.h>
@ -78,7 +80,6 @@ SERVICE *service;
service->state = SERVICE_STATE_ALLOC;
service->credentials.name = NULL;
service->credentials.authdata = NULL;
service->users = users_alloc();
service->enable_root = 0;
service->routerOptions = NULL;
service->databases = NULL;
@ -113,11 +114,17 @@ GWPROTOCOL *funcs;
return 0;
}
if (strcmp(port->protocol, "MySQLClient") == 0) {
int loaded = load_mysql_users(service);
int loaded;
/* Allocate specific data for MySQL users */
service->users = mysql_users_alloc();
loaded = load_mysql_users(service);
LOGIF(LM, (skygw_log_write(
LOGFILE_MESSAGE,
"Loaded %d MySQL Users.",
loaded)));
} else {
/* Generic users table */
service->users = users_alloc();
}
if ((funcs =

View File

@ -177,7 +177,8 @@ void
dcb_usersPrint(DCB *dcb, USERS *users)
{
HASHITERATOR *iter;
char *sep, *user;
char *sep;
void *user;
dcb_printf(dcb, "Users table data\n");
dcb_hashtable_stats(dcb, users->data);
@ -185,11 +186,26 @@ char *sep, *user;
{
dcb_printf(dcb, "User names: ");
sep = "";
while ((user = hashtable_next(iter)) != NULL)
{
dcb_printf(dcb, "%s%s", sep, user);
sep = ", ";
if (users->usersCustomUserFormat != NULL) {
while ((user = hashtable_next(iter)) != NULL)
{
char *custom_user;
custom_user = users->usersCustomUserFormat(user);
if (custom_user) {
dcb_printf(dcb, "%s%s", sep, custom_user);
free(custom_user);
sep = ", ";
}
}
} else {
while ((user = hashtable_next(iter)) != NULL)
{
dcb_printf(dcb, "%s%s", sep, (char *)user);
sep = ", ";
}
}
dcb_printf(dcb, "\n");
hashtable_iterator_free(iter);
}

View File

@ -18,6 +18,8 @@
* Copyright SkySQL Ab 2013
*/
#include <service.h>
#include <gw.h>
/**
* @file dbusers.h Extarct user information form the backend database
@ -25,12 +27,24 @@
* @verbatim
* Revision History
*
* Date Who Description
* 25/06/13 Mark Riddoch Initial implementation
* Date Who Description
* 25/06/13 Mark Riddoch Initial implementation
* 07/02/14 Massimiliano Pinto Added MySQL user and host data structure
*
* @endverbatim
*/
/**
* MySQL user and host data structure
*/
typedef struct mysql_user_host_key {
char *user;
struct sockaddr_in ipv4;
} 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 USERS *mysql_users_alloc();
extern char *mysql_users_fetch(USERS *users, MYSQL_USER_HOST *key);
#endif

View File

@ -27,8 +27,9 @@
* @verbatim
* Revision History
*
* Date Who Description
* 23/06/13 Mark Riddoch Initial implementation
* Date Who Description
* 23/06/13 Mark Riddoch Initial implementation
* 14/02/14 Massimiliano Pinto Added usersCustomUserFormat, optional username format routine
*
* @endverbatim
*/
@ -48,8 +49,9 @@ typedef struct {
* for the authentication implementation within the gateway.
*/
typedef struct users {
HASHTABLE *data; /**< The hashtable containing the actual data */
USERS_STATS stats; /**< The statistics for the users table */
HASHTABLE *data; /**< The hashtable containing the actual data */
char *(*usersCustomUserFormat)(void *); /**< Optional username format routine */
USERS_STATS stats; /**< The statistics for the users table */
} USERS;
extern USERS *users_alloc(); /**< Allocate a users table */

View File

@ -25,6 +25,7 @@
* 02/06/2013 Massimiliano Pinto MySQL connect asynchronous phases
* 04/09/2013 Massimiliano Pinto Added dcb NULL assert in mysql_send_custom_error
* 12/09/2013 Massimiliano Pinto Added checks in gw_decode_mysql_server_handshake and gw_read_backend_handshake
* 10/02/2014 Massimiliano Pinto Added MySQL Authentication with user@host
*
*/
@ -1099,15 +1100,53 @@ int gw_check_mysql_scramble_data(DCB *dcb, uint8_t *token, unsigned int token_le
int gw_find_mysql_user_password_sha1(char *username, uint8_t *gateway_password, void *repository) {
SERVICE *service = NULL;
struct sockaddr_in *client;
char *user_password = NULL;
DCB *dcb = (DCB *)repository;
MYSQL_USER_HOST key;
service = (SERVICE *) ((DCB *)repository)->service;
service = (SERVICE *) ((DCB *)repository)->service;
client = (struct sockaddr_in *) &dcb->ipv4;
user_password = (char *)users_fetch(service->users, username);
key.user = username;
memcpy(&key.ipv4, client, sizeof(struct sockaddr_in));
LOGIF(LD,
(skygw_log_write_flush(
LOGFILE_DEBUG,
"%lu [MySQL Client Auth], checking user [%s@%s]",
pthread_self(),
key.user,
dcb->remote)));
/* lookk for user@current_host now */
user_password = mysql_users_fetch(service->users, &key);
if (!user_password) {
return 1;
}
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)));
/* look for wildcard user@%, and then fail if no match */
user_password = mysql_users_fetch(service->users, &key);
if (!user_password) {
LOGIF(LD,
(skygw_log_write_flush(
LOGFILE_DEBUG,
"%lu [MySQL Client Auth], user [%s@%s] not existent",
pthread_self(),
key.user,
dcb->remote)));
return 1;
}
}
/*<
* Convert now the hex data (40 bytes) to binary (20 bytes).