This commit is contained in:
VilhoRaatikka 2014-10-24 18:34:55 +03:00
commit 18ec838ba7
12 changed files with 1009 additions and 236 deletions

View File

@ -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
*/
@ -48,9 +49,18 @@
#include <mysql_client_server_protocol.h>
#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,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)"
extern int lm_enabled_logfiles_bitmask;
static int getUsers(SERVICE *service, USERS *users);
@ -60,7 +70,13 @@ 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);
int add_mysql_users_with_host_ipv4(USERS *users, char *user, char *host, char *passwd);
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
@ -87,15 +103,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;
}
@ -113,14 +140,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;
}
@ -134,6 +167,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;
@ -146,10 +180,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;
}
@ -169,7 +208,7 @@ USERS *newusers, *oldusers;
* @return 1 on success, 0 on failure
*/
int add_mysql_users_with_host_ipv4(USERS *users, char *user, char *host, char *passwd) {
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]="";
@ -177,9 +216,13 @@ int add_mysql_users_with_host_ipv4(USERS *users, char *user, char *host, char *p
int found_any=0;
int ret = 0;
if (users == NULL || user == NULL || host == NULL) {
return ret;
}
/* prepare the user@host data struct */
memset(&serv_addr, 0, sizeof(serv_addr));
memset(&key, 0, sizeof(key));
memset(&key, '\0', sizeof(key));
/* set user */
key.user = strdup(user);
@ -188,6 +231,20 @@ int add_mysql_users_with_host_ipv4(USERS *users, char *user, char *host, char *p
return ret;
}
/* for anydb == Y key.resource is '\0' as set by memset */
if (anydb == NULL) {
key.resource = NULL;
} else {
if (strcmp(anydb, "N") == 0) {
if (db != NULL)
key.resource = strdup(db);
else
key.resource = NULL;
} else {
key.resource = strdup("");
}
}
/* handle ANY, Class C,B,A */
/* ANY */
@ -196,10 +253,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
@ -231,15 +289,125 @@ 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;
}
}
free(key.user);
if (key.resource)
free(key.resource);
return ret;
}
/**
* Load the database specific grants from mysql.db table into the service resources 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
getDatabases(SERVICE *service, MYSQL *con)
{
MYSQL_ROW row;
MYSQL_RES *result = NULL;
char *service_user = NULL;
char *service_passwd = NULL;
int ndbs = 0;
char *get_showdbs_priv_query = LOAD_MYSQL_DATABASE_NAMES;
serviceGetUser(service, &service_user, &service_passwd);
if (service_user == NULL || service_passwd == NULL)
return -1;
if (mysql_query(con, get_showdbs_priv_query)) {
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"Error : Loading database names for service %s encountered "
"error: %s.",
service->name,
mysql_error(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))));
return -1;
}
/* Result has only one row */
row = mysql_fetch_row(result);
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);
if (!ndbs) {
/* return if no db names are available */
return 0;
}
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))));
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))));
return -1;
}
/* Now populate service->resources hashatable with db names */
service->resources = resource_alloc();
/* insert key and value "" */
while ((row = mysql_fetch_row(result))) {
resource_add(service->resources, row[0], "");
}
mysql_free_result(result);
return ndbs;
}
/**
* Load the user/passwd form mysql.user table into the service users' hashtable
* environment.
@ -251,29 +419,24 @@ int add_mysql_users_with_host_ipv4(USERS *users, char *user, char *host, char *p
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;
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;
/* 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";
}
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);
if (service_user == NULL || service_passwd == NULL)
return -1;
@ -302,6 +465,7 @@ getUsers(SERVICE *service, USERS *users)
*/
server = service->databases;
dpwd = decryptPassword(service_passwd);
while (server != NULL && (mysql_real_connect(con,
server->name,
service_user,
@ -320,35 +484,57 @@ 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;
}
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;
/* count users */
/* 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) {
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]);
@ -364,15 +550,80 @@ getUsers(SERVICE *service, USERS *users)
return -1;
}
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 {
users_query = LOAD_MYSQL_USERS_WITH_DB_QUERY_NO_ROOT;
}
/* send first the query that fetches users and db grants */
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;
/*
* 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 "
"error: [%s], MySQL errno %i",
service->name,
mysql_error(con),
mysql_errno(con))));
mysql_close(con);
return -1;
} else {
/*
* We have got ER_TABLEACCESS_DENIED_ERROR
* try loading users from mysql.user without DB names.
*/
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"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) {
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 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 {
/*
* users successfully loaded with db grants.
*/
db_grants = 1;
}
result = mysql_store_result(con);
@ -384,34 +635,96 @@ 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;
}
if (db_grants) {
/* load all mysql database names */
dbnames = getDatabases(service, con);
LOGIF(LM, (skygw_log_write(
LOGFILE_MESSAGE,
"Loaded %d MySQL Database Names for service [%s]",
dbnames,
service->name)));
} else {
service->resources = NULL;
}
while ((row = mysql_fetch_row(result))) {
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.
* Up to six fields could be returned.
* user,host,passwd,concat(),anydb,db
* passwd+1 (escaping the first byte that is '*')
*/
int rc = 0;
char *password = NULL;
if (row[2] != NULL) {
if (strlen(row[2]) > 1)
password = row[2] +1;
else
password = row[2];
}
rc = add_mysql_users_with_host_ipv4(users, row[0], row[1], strlen(row[2]) ? row[2]+1 : row[2]);
/*
* add user@host and DB global priv and specificsa grant (if possible)
*/
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 {
/* 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) {
LOGIF(LD, (skygw_log_write_flush(
LOGFILE_DEBUG,
"%lu [mysql_users_add()] Added user %s@%s",
pthread_self(),
row[0],
row[1])));
if (db_grants) {
char dbgrant[MYSQL_DATABASE_MAXLEN + 1]="";
if (row[4] != NULL) {
if (strcmp(row[4], "Y"))
strcpy(dbgrant, "ANY");
else {
if (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)));
} 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);
@ -420,10 +733,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@%s for service [%s]",
pthread_self(),
row[0],
row[1])));
row[1],
service->name)));
}
}
@ -502,7 +816,7 @@ 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);
return hashtable_fetch(users->data, key);
}
/**
@ -536,11 +850,37 @@ 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 (strcmp(hu1->user, hu2->user) == 0 && (hu1->ipv4.sin_addr.s_addr == hu2->ipv4.sin_addr.s_addr) && (hu1->netmask >= hu2->netmask)) {
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 */
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;
}
@ -568,6 +908,9 @@ static void *uh_keydup(void* key) {
memcpy(&rval->ipv4, &current_key->ipv4, sizeof(struct sockaddr_in));
memcpy(&rval->netmask, &current_key->netmask, sizeof(int));
if (current_key->resource)
rval->resource = strdup(current_key->resource);
return (void *) rval;
}
@ -585,6 +928,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);
}
@ -600,7 +946,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;
@ -614,6 +960,8 @@ char *mysql_format_user_entry(void *data)
if (mysql_user == NULL)
return NULL;
/* 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);
@ -628,12 +976,94 @@ char *mysql_format_user_entry(void *data)
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-6, "warn: %s", entry->user);
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;
}
/*
* The hash function we use for storing MySQL database names.
*
* @param key The key value
* @return The hash key
*/
int
resource_hash(char *key)
{
return (*key + *(key + 1));
}
/**
* Remove the resources table
*
* @param resources The resources table to remove
*/
void
resource_free(HASHTABLE *resources)
{
if (resources) {
hashtable_free(resources);
}
}
/**
* Allocate a MySQL database names table
*
* @return The database names table
*/
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;
}
/**
* 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)
{
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)
{
return hashtable_fetch(resources, key);
}

View File

@ -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
*/
@ -227,3 +228,118 @@ modutil_get_query(GWBUF *buf)
retblock:
return query_str;
}
/**
* 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_err_msg(
int packet_number,
int affected_rows,
int merrno,
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;
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 NULL;
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;
}
/**
* modutil_send_mysql_err_packet
*
* Send a MySQL protocol Generic ERR message, to the dcb
*
* @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_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_err_msg(packet_number, in_affected_rows, mysql_errno, sqlstate_msg, mysql_message);
return dcb->func.write(dcb, buf);
}

View File

@ -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
*/
@ -137,6 +138,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));
@ -198,9 +200,14 @@ GWPROTOCOL *funcs;
}
if (strcmp(port->protocol, "MySQLClient") == 0) {
int 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
*/
@ -209,9 +216,9 @@ GWPROTOCOL *funcs;
service->rate_limit.nloads=1;
LOGIF(LM, (skygw_log_write(
LOGFILE_MESSAGE,
"Loaded %d MySQL Users.",
loaded)));
LOGFILE_MESSAGE,
"Loaded %d MySQL Users for service [%s].",
loaded, service->name)));
} else {
/* Generic users table */
service->users = users_alloc();

View File

@ -37,4 +37,3 @@ add_test(TestService test_service)
add_test(TestServer test_server)
add_test(TestUsers test_users)
add_test(TestAdminUsers test_adminusers)

View File

@ -40,6 +40,7 @@
#include <log_manager.h>
#include <secrets.h>
#include <dbusers.h>
#include <mysql_client_server_protocol.h>
#include <arpa/inet.h>
@ -52,9 +53,24 @@ 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="";
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 {
@ -70,6 +86,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);
@ -79,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;
}
@ -87,12 +106,15 @@ 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));
fetch_data = mysql_users_fetch(mysql_users, &find_key);
users_free(mysql_users);
free(service);
dcb_free(dcb);
if (!fetch_data)
return 1;
@ -103,10 +125,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();
@ -125,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;
inet_ntop(AF_INET, &(serv_addr).sin_addr, ret_ip, INET_ADDRSTRLEN);
@ -138,7 +161,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)) {
@ -148,6 +170,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);
@ -159,23 +182,24 @@ 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) {
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;
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;
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));
@ -185,10 +209,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));
@ -198,26 +230,36 @@ 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);
else
strcpy(data->db, "");
/* freed by dcb_free(dcb) */
dcb->data = data;
// the routine returns 1 on success
ret = add_mysql_users_with_host_ipv4(mysql_users, username, hostname, password);
if (!ret) {
fprintf(stderr, "add_mysql_users_with_host_ipv4 passed(%s@%s, %s) FAILED\n", username, hostname, password);
users_free(mysql_users);
free(service);
dcb_free(dcb);
return 1;
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) {
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 {
char db_passwd[100]="";
ret = add_mysql_users_with_host_ipv4(mysql_users, username, hostname, password, "N", NULL);
}
if (ret == 0) {
fprintf(stderr, "add_mysql_users_with_host_ipv4 (%s@%s, %s) FAILED\n", username, hostname, password);
} else {
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);
ret = gw_find_mysql_user_password_sha1(username, db_passwd, dcb);
}
users_free(mysql_users);
@ -239,6 +281,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");
@ -272,62 +315,91 @@ int main() {
k++;
}
ret = set_and_get_mysql_users_wildcards("pippo", "%", "one", "127.0.0.1");
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");
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");
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");
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);
ret = set_and_get_mysql_users_wildcards("pippo", "192.168.%.%", "foo", "192.168.2.2");
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");
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);
ret = set_and_get_mysql_users_wildcards("pippo", "192.%.%.%", "foo", "192.0.0.2");
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);
ret = set_and_get_mysql_users_wildcards("pippo", "192.0.%.%", "foo", "192.2.0.2");
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");
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");
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.%", "y78764o", "192.3.2.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.%.%", "1234567890123456789012345678901234567890", "192.3.2.1");
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);
ret = set_and_get_mysql_users_wildcards("pippo", "192.%.%.%", "1234567890123456789012345678901234567890f8__uuo5", "192.3.2.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.0.0.%", "fo887778o", "192.134.0.2");
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.%.%.%", "12345678901234567890123456789012345678901234", "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");

View File

@ -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
*/
@ -54,12 +55,13 @@ 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);
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 int replace_mysql_users(SERVICE *service);

View File

@ -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 *);

View File

@ -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 <buffer.h>
#include <dcb.h>
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

View File

@ -23,6 +23,7 @@
#include <dcb.h>
#include <server.h>
#include <filter.h>
#include <hashtable.h>
#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,6 +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 */
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 */

View File

@ -20,6 +20,8 @@
#include <skygw_types.h>
#include <skygw_utils.h>
#include <log_manager.h>
#include <modutil.h>
/*
* 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 <modinfo.h>
@ -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);
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,
@ -1185,6 +1196,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;
@ -1196,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);
@ -1217,8 +1229,23 @@ static int gw_change_user(
memcpy(auth_token, client_auth_packet, auth_token_len);
client_auth_packet += auth_token_len;
}
// decode the token and check the password
// Note: if auth_token_len == 0 && auth_token == NULL, user is without password
/* get new database name */
strcpy(database, (char *)client_auth_packet);
/* save current_database name */
strcpy(current_database, current_session->db);
/*
* 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
*/
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) {
@ -1229,24 +1256,37 @@ static int gw_change_user(
}
}
// let's free the auth_token now
/* 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 (auth_ret != 0) {
/*< vraa : errorHandle */
char *password_set = NULL;
char *message = NULL;
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_err_packet(backend->session->client, 1, 0, 1045, "28000", message);
free(message);
// send the error packet
mysql_send_auth_error(backend->session->client, 1, 0, "Authorization failed on change_user");
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);
/*<
/*
* Now copy new data into user session
*/
strcpy(current_session->user, username);
@ -1254,6 +1294,7 @@ static int gw_change_user(
memcpy(current_session->client_sha1, client_sha1, sizeof(current_session->client_sha1));
}
gwbuf_free(queue);
return rv;
}

View File

@ -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 <skygw_utils.h>
@ -43,6 +44,7 @@
#include <gw.h>
#include <modinfo.h>
#include <sys/stat.h>
#include <modutil.h>
MODULE_INFO info = {
MODULE_API_PROTOCOL,
@ -68,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* get_username_from_auth(char* ptr, uint8_t* data);
extern char* get_username_from_auth(char* ptr, uint8_t* data);
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.
@ -446,6 +449,9 @@ 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,
@ -461,7 +467,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
*/
@ -472,6 +479,9 @@ static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue) {
username,
stage1_hash);
/* 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) {
if (!service_refresh_users(dcb->service)) {
@ -481,89 +491,22 @@ static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue) {
}
}
/* let's free the auth_token now */
if (auth_token)
free(auth_token);
/* Do again the database check */
auth_ret = check_db_name_after_auth(dcb, database, auth_ret);
if (auth_ret == 0)
{
/* on succesful auth set user into dcb field */
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;
}
/**
* 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"));
}
free(uname);
retblock:
return errstr;
}
/**
* Write function for client DCB: writes data from MaxScale to Client
*
@ -715,19 +658,28 @@ int gw_read_client_event(
}
else
{
char* fail_str;
char* fail_str = NULL;
protocol->protocol_auth_state = MYSQL_AUTH_FAILED;
fail_str = create_auth_fail_str(read_buffer,
if (auth_val == 2) {
/** Send error 1049 to client */
int message_len = 25 + MYSQL_DATABASE_MAXLEN;
fail_str = calloc(1, message_len+1);
snprintf(fail_str, message_len, "Unknown database '%s'", (char*)((MYSQL_session *)dcb->data)->db);
modutil_send_mysql_err_packet(dcb, 2, 0, 1049, "42000", fail_str);
} else {
/** Send error 1045 to client */
fail_str = create_auth_fail_str((char *)((MYSQL_session *)dcb->data)->user,
dcb->remote,
(char*)((MYSQL_session *)dcb->data)->client_sha1);
/** Send error 1045 to client */
mysql_send_auth_error(
dcb,
2,
0,
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,
@ -736,7 +688,7 @@ int gw_read_client_event(
"state = MYSQL_AUTH_FAILED.",
protocol->owner_dcb->fd,
pthread_self())));
free(fail_str);
dcb_close(dcb);
}
read_buffer = gwbuf_consume(read_buffer, nbytes_read);
@ -748,7 +700,7 @@ int gw_read_client_event(
uint8_t cap = 0;
uint8_t* payload = NULL;
bool stmt_input; /*< router input type */
ss_dassert(nbytes_read >= 5);
session = dcb->session;
@ -1617,4 +1569,3 @@ return_str:
}
#endif

View File

@ -32,8 +32,9 @@
* 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.
* 24/10/2014 Massimiliano Pinto Added Mysql user@host @db authentication support
*
*/
@ -49,6 +50,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);
@ -1002,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 (
@ -1015,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);
}
/**
@ -1177,7 +1177,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';
@ -1236,6 +1239,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;
}
@ -1313,8 +1322,9 @@ 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 an user from the MaxScale users' table
* The users' table is dcb->service->users or a different one specified with void *repository
* The user lookup uses username,host and db name (if passed in connection or change user)
*
* If found the HEX password, representing sha1(sha1(password)), is converted in binary data and
* copied into gateway_password
@ -1331,13 +1341,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 = (MYSQL_session *) dcb->data;
service = (SERVICE *) dcb->service;
client = (struct sockaddr_in *) &dcb->ipv4;
key.user = username;
memcpy(&key.ipv4, client, sizeof(struct sockaddr_in));
key.netmask = 32;
key.resource = client_data->db;
LOGIF(LD,
(skygw_log_write_flush(
@ -1382,7 +1395,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;
}
@ -1396,7 +1409,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;
@ -1426,7 +1439,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,
@ -1969,3 +1982,138 @@ void protocol_set_response_status (
spinlock_release(&p->protocol_lock);
}
char* create_auth_failed_msg(
GWBUF* readbuf,
char* hostaddr,
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);
if (errstr != NULL)
{
sprintf(errstr, ferrstr, uname, hostaddr, (*sha1 == '\0' ? "NO" : "YES"));
}
return errstr;
}
/**
* 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.
*
* @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;
}
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;
}
/**
* 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;
}