Merge branch 'develop' of https://github.com/mariadb-corporation/MaxScale into develop
This commit is contained in:
commit
18ec838ba7
@ -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, ¤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;
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
@ -37,4 +37,3 @@ add_test(TestService test_service)
|
||||
add_test(TestServer test_server)
|
||||
add_test(TestUsers test_users)
|
||||
add_test(TestAdminUsers test_adminusers)
|
||||
|
||||
|
@ -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");
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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 *);
|
||||
|
@ -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
|
||||
|
@ -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 */
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user