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:
@ -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, ¤t_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;
|
||||
}
|
||||
|
||||
@ -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 =
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 */
|
||||
|
||||
@ -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).
|
||||
|
||||
Reference in New Issue
Block a user