Use SQLite3 based authentication
The user data is now stored inside a SQLite3 database. By storing the data inside a database, we remove the restriction that the previous hashtable based implementation had.
This commit is contained in:
@ -25,3 +25,6 @@
|
|||||||
#if SQLITE_VERSION_NUMBER < 3007014
|
#if SQLITE_VERSION_NUMBER < 3007014
|
||||||
#define sqlite3_close_v2 sqlite3_close
|
#define sqlite3_close_v2 sqlite3_close
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/** Default timeout is one minute */
|
||||||
|
#define MXS_SQLITE_BUSY_TIMEOUT 60000
|
||||||
|
@ -20,11 +20,9 @@
|
|||||||
#include <maxscale/protocol/mysql.h>
|
#include <maxscale/protocol/mysql.h>
|
||||||
#include <maxscale/secrets.h>
|
#include <maxscale/secrets.h>
|
||||||
#include <maxscale/mysql_utils.h>
|
#include <maxscale/mysql_utils.h>
|
||||||
|
#include <maxscale/sqlite3.h>
|
||||||
#include "../gssapi_auth.h"
|
#include "../gssapi_auth.h"
|
||||||
|
|
||||||
/** Default timeout is one minute */
|
|
||||||
#define MXS_SQLITE_BUSY_TIMEOUT 60000
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MySQL queries for retrieving the list of users
|
* MySQL queries for retrieving the list of users
|
||||||
*/
|
*/
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
add_library(MySQLAuth SHARED mysql_auth.c dbusers.c)
|
add_library(MySQLAuth SHARED mysql_auth.c dbusers.c)
|
||||||
target_link_libraries(MySQLAuth maxscale-common MySQLCommon)
|
target_link_libraries(MySQLAuth maxscale-common MySQLCommon sqlite3)
|
||||||
set_target_properties(MySQLAuth PROPERTIES VERSION "1.0.0")
|
set_target_properties(MySQLAuth PROPERTIES VERSION "1.0.0")
|
||||||
install_module(MySQLAuth core)
|
install_module(MySQLAuth core)
|
||||||
|
@ -40,6 +40,7 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
#include <mysql.h>
|
#include <mysql.h>
|
||||||
|
#include <netdb.h>
|
||||||
|
|
||||||
#include <maxscale/dcb.h>
|
#include <maxscale/dcb.h>
|
||||||
#include <maxscale/service.h>
|
#include <maxscale/service.h>
|
||||||
@ -121,6 +122,15 @@
|
|||||||
MaxScale authentication will proceed without including database permissions. \
|
MaxScale authentication will proceed without including database permissions. \
|
||||||
See earlier error messages for user '%s' for more information."
|
See earlier error messages for user '%s' for more information."
|
||||||
|
|
||||||
|
#define NEW_LOAD_DBUSERS_QUERY "SELECT u.user, u.host, d.db, u.select_priv, u.%s \
|
||||||
|
FROM mysql.user AS u LEFT JOIN mysql.db AS d \
|
||||||
|
ON (u.user = d.user AND u.host = d.host) %s \
|
||||||
|
UNION \
|
||||||
|
SELECT u.user, u.host, t.db, u.select_priv, u.%s \
|
||||||
|
FROM mysql.user AS u LEFT JOIN mysql.tables_priv AS t \
|
||||||
|
ON (u.user = t.user AND u.host = t.host) %s\
|
||||||
|
ORDER BY user"
|
||||||
|
|
||||||
static int add_databases(SERV_LISTENER *listener, MYSQL *con);
|
static int add_databases(SERV_LISTENER *listener, MYSQL *con);
|
||||||
static int add_wildcard_users(USERS *users, char* name, char* host,
|
static int add_wildcard_users(USERS *users, char* name, char* host,
|
||||||
char* password, char* anydb, char* db, HASHTABLE* hash);
|
char* password, char* anydb, char* db, HASHTABLE* hash);
|
||||||
@ -215,6 +225,22 @@ static char* get_usercount_query(const char* server_version, bool include_root,
|
|||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static char* get_new_users_query(const char *server_version, bool include_root)
|
||||||
|
{
|
||||||
|
const char* password = strstr(server_version, "5.7.") ? MYSQL57_PASSWORD : MYSQL_PASSWORD;
|
||||||
|
const char *with_root = include_root ? "user.user NOT IN ('root')" : "";
|
||||||
|
|
||||||
|
size_t n_bytes = snprintf(NULL, 0, NEW_LOAD_DBUSERS_QUERY, password, with_root, password, with_root);
|
||||||
|
char *rval = MXS_MALLOC(n_bytes + 1);
|
||||||
|
|
||||||
|
if (rval)
|
||||||
|
{
|
||||||
|
snprintf(rval, n_bytes + 1, NEW_LOAD_DBUSERS_QUERY, password, with_root, password, with_root);
|
||||||
|
}
|
||||||
|
|
||||||
|
return rval;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the IP address of the user matches the one in the grant. This assumes
|
* Check if the IP address of the user matches the one in the grant. This assumes
|
||||||
* that the grant has one or more single-character wildcards in it.
|
* that the grant has one or more single-character wildcards in it.
|
||||||
@ -1172,6 +1198,207 @@ cleanup:
|
|||||||
return total_users;
|
return total_users;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool check_password(const char *output,
|
||||||
|
uint8_t *token, size_t token_len,
|
||||||
|
uint8_t *scramble, size_t scramble_len)
|
||||||
|
{
|
||||||
|
uint8_t stored_token[SHA_DIGEST_LENGTH] = {};
|
||||||
|
size_t stored_token_len = sizeof(stored_token);
|
||||||
|
|
||||||
|
if (*output)
|
||||||
|
{
|
||||||
|
/** Convert the hexadecimal string to binary */
|
||||||
|
gw_hex2bin(stored_token, output, strlen(output));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The client authentication token is made up of:
|
||||||
|
*
|
||||||
|
* XOR( SHA1(real_password), SHA1( CONCAT( scramble, <value of mysql.user.password> ) ) )
|
||||||
|
*
|
||||||
|
* Since we know the scramble and the value stored in mysql.user.password,
|
||||||
|
* we can extract the SHA1 of the real password by doing a XOR of the client
|
||||||
|
* authentication token with the SHA1 of the scramble concatenated with the
|
||||||
|
* value of mysql.user.password.
|
||||||
|
*
|
||||||
|
* Once we have the SHA1 of the original password, we can create the SHA1
|
||||||
|
* of this hash and compare the value with the one stored in the backend
|
||||||
|
* database. If the values match, the user has sent the right password.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** First, calculate the SHA1 of the scramble and the hash stored in the database */
|
||||||
|
uint8_t step1[SHA_DIGEST_LENGTH];
|
||||||
|
gw_sha1_2_str(scramble, scramble_len, stored_token, stored_token_len, step1);
|
||||||
|
|
||||||
|
/** Next, extract the SHA1 of the real password by XOR'ing it with
|
||||||
|
* the output of the previous calculation */
|
||||||
|
uint8_t step2[SHA_DIGEST_LENGTH];
|
||||||
|
gw_str_xor(step2, token, step1, token_len);
|
||||||
|
|
||||||
|
/** Finally, calculate the SHA1 of the hashed real password */
|
||||||
|
uint8_t final_step[SHA_DIGEST_LENGTH];
|
||||||
|
gw_sha1_str(step2, SHA_DIGEST_LENGTH, final_step);
|
||||||
|
|
||||||
|
/** If the two values match, the client has sent the correct password */
|
||||||
|
return memcmp(final_step, stored_token, stored_token_len) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Used to detect empty result sets */
|
||||||
|
struct user_query_result
|
||||||
|
{
|
||||||
|
bool ok;
|
||||||
|
char output[SHA_DIGEST_LENGTH * 2 + 1];
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @brief Callback for sqlite3_exec() */
|
||||||
|
static int auth_cb(void *data, int columns, char** rows, char** row_names)
|
||||||
|
{
|
||||||
|
struct user_query_result *res = (struct user_query_result*)data;
|
||||||
|
strcpy(res->output, rows[0] ? rows[0] : "");
|
||||||
|
res->ok = true;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Verify the user has access to the database
|
||||||
|
*
|
||||||
|
* @param auth Authenticator session
|
||||||
|
* @param dcb Client DCB
|
||||||
|
* @param session MySQL session
|
||||||
|
* @param pw Client password
|
||||||
|
*
|
||||||
|
* @return True if the user has access to the database
|
||||||
|
*/
|
||||||
|
bool validate_mysql_user(sqlite3 *handle, DCB *dcb, MYSQL_session *session)
|
||||||
|
{
|
||||||
|
size_t len = sizeof(mysqlauth_validation_query) + strlen(session->user) * 2 +
|
||||||
|
strlen(session->db) * 2 + MYSQL_HOST_MAXLEN + session->auth_token_len * 4 + 1;
|
||||||
|
char sql[len + 1];
|
||||||
|
bool rval = false;
|
||||||
|
char *err;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try authentication twice; first time with the current users, second
|
||||||
|
* time with fresh users
|
||||||
|
*/
|
||||||
|
for (int i = 0; i < 2 && !rval; i++)
|
||||||
|
{
|
||||||
|
sprintf(sql, mysqlauth_validation_query, session->user, dcb->remote,
|
||||||
|
session->db, session->db);
|
||||||
|
|
||||||
|
struct user_query_result res = {};
|
||||||
|
|
||||||
|
if (sqlite3_exec(handle, sql, auth_cb, &res, &err) != SQLITE_OK)
|
||||||
|
{
|
||||||
|
MXS_ERROR("Failed to execute auth query: %s", err);
|
||||||
|
sqlite3_free(err);
|
||||||
|
rval = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!res.ok)
|
||||||
|
{
|
||||||
|
/** Try authentication with the hostname */
|
||||||
|
char client_hostname[MYSQL_HOST_MAXLEN];
|
||||||
|
wildcard_domain_match(dcb->remote, client_hostname);
|
||||||
|
sprintf(sql, mysqlauth_validation_query, session->user, client_hostname,
|
||||||
|
session->db, session->db);
|
||||||
|
|
||||||
|
if (sqlite3_exec(handle, sql, auth_cb, &res, &err) != SQLITE_OK)
|
||||||
|
{
|
||||||
|
MXS_ERROR("Failed to execute auth query: %s", err);
|
||||||
|
sqlite3_free(err);
|
||||||
|
rval = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res.ok)
|
||||||
|
{
|
||||||
|
/** Found a matching row */
|
||||||
|
MySQLProtocol *proto = (MySQLProtocol*)dcb->protocol;
|
||||||
|
rval = check_password(res.output, session->auth_token, session->auth_token_len,
|
||||||
|
proto->scramble, sizeof(proto->scramble));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!rval && i == 0)
|
||||||
|
{
|
||||||
|
service_refresh_users(dcb->service);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rval;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Delete all users
|
||||||
|
*
|
||||||
|
* @param handle SQLite handle
|
||||||
|
*/
|
||||||
|
static void delete_mysql_users(sqlite3 *handle)
|
||||||
|
{
|
||||||
|
char *err;
|
||||||
|
|
||||||
|
if (sqlite3_exec(handle, delete_query, NULL, NULL, &err) != SQLITE_OK)
|
||||||
|
{
|
||||||
|
MXS_ERROR("Failed to delete old users: %s", err);
|
||||||
|
sqlite3_free(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Add new MySQL user to the internal user database
|
||||||
|
*
|
||||||
|
* @param handle Database handle
|
||||||
|
* @param user Username
|
||||||
|
* @param host Host
|
||||||
|
* @param db Database
|
||||||
|
* @param anydb Global access to databases
|
||||||
|
*/
|
||||||
|
static void add_mysql_user(sqlite3 *handle, const char *user, const char *host,
|
||||||
|
const char *db, bool anydb, const char *pw)
|
||||||
|
{
|
||||||
|
size_t dblen = db && *db ? strlen(db) + 2 : sizeof(null_token); /** +2 for single quotes */
|
||||||
|
char dbstr[dblen + 1];
|
||||||
|
|
||||||
|
if (db && *db)
|
||||||
|
{
|
||||||
|
sprintf(dbstr, "'%s'", db);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
strcpy(dbstr, null_token);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t pwlen = pw && *pw ? strlen(pw) + 2 : sizeof(null_token); /** +2 for single quotes */
|
||||||
|
char pwstr[pwlen + 1];
|
||||||
|
|
||||||
|
if (pw && *pw)
|
||||||
|
{
|
||||||
|
if (*pw == '*')
|
||||||
|
{
|
||||||
|
pw++;
|
||||||
|
}
|
||||||
|
sprintf(pwstr, "'%s'", pw);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
strcpy(pwstr, null_token);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t len = sizeof(insert_sql_pattern) + strlen(user) + strlen(host) + dblen + pwlen + 1;
|
||||||
|
|
||||||
|
char insert_sql[len + 1];
|
||||||
|
sprintf(insert_sql, insert_sql_pattern, user, host, dbstr, anydb ? "1" : "0", pwstr);
|
||||||
|
|
||||||
|
char *err;
|
||||||
|
if (sqlite3_exec(handle, insert_sql, NULL, NULL, &err) != SQLITE_OK)
|
||||||
|
{
|
||||||
|
MXS_ERROR("Failed to insert user: %s", err);
|
||||||
|
sqlite3_free(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
MXS_INFO("Added user: %s", insert_sql);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load the user/passwd form mysql.user table into the service users' hashtable
|
* Load the user/passwd form mysql.user table into the service users' hashtable
|
||||||
* environment.
|
* environment.
|
||||||
@ -1618,6 +1845,37 @@ get_users(SERV_LISTENER *listener, USERS *users)
|
|||||||
|
|
||||||
MXS_FREE(users_data);
|
MXS_FREE(users_data);
|
||||||
mysql_free_result(result);
|
mysql_free_result(result);
|
||||||
|
|
||||||
|
/** Testing new users query */
|
||||||
|
char *query = get_new_users_query(server->server->server_string, service->enable_root);
|
||||||
|
|
||||||
|
if (query)
|
||||||
|
{
|
||||||
|
if (mysql_query(con, query) == 0)
|
||||||
|
{
|
||||||
|
MYSQL_AUTH *instance = (MYSQL_AUTH*)listener->auth_instance;
|
||||||
|
delete_mysql_users(instance->handle);
|
||||||
|
|
||||||
|
if ((result = mysql_store_result(con)))
|
||||||
|
{
|
||||||
|
while ((row = mysql_fetch_row(result)))
|
||||||
|
{
|
||||||
|
add_mysql_user(instance->handle, row[0], row[1], row[2],
|
||||||
|
row[3] && strcmp(row[3], "Y") == 0,
|
||||||
|
row[4]);
|
||||||
|
}
|
||||||
|
|
||||||
|
mysql_free_result(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
MXS_ERROR("Failed to load users: %s", mysql_error(con));
|
||||||
|
}
|
||||||
|
|
||||||
|
MXS_FREE(query);
|
||||||
|
}
|
||||||
|
|
||||||
mysql_close(con);
|
mysql_close(con);
|
||||||
|
|
||||||
return total_users;
|
return total_users;
|
||||||
@ -2753,3 +3011,53 @@ static void merge_netmask(char *host)
|
|||||||
"Merge incomplete: %s", host);
|
"Merge incomplete: %s", host);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check if an ip matches a wildcard hostname.
|
||||||
|
*
|
||||||
|
* One of the parameters should be an IP-address without wildcards, the other a
|
||||||
|
* hostname with wildcards. The hostname corresponding to the ip-address will be
|
||||||
|
* looked up and compared to the hostname with wildcard(s). Any error in the
|
||||||
|
* parameters or looking up the hostname will result in a false match.
|
||||||
|
*
|
||||||
|
* @param ip-address or a hostname with wildcard(s)
|
||||||
|
* @param ip-address or a hostname with wildcard(s)
|
||||||
|
* @return True if the host represented by the IP matches the wildcard string
|
||||||
|
*/
|
||||||
|
static bool wildcard_domain_match(const char *ip_address, char *client_hostname)
|
||||||
|
{
|
||||||
|
/* Looks like the parameters are valid. First, convert the client IP string
|
||||||
|
* to binary form. This is somewhat silly, since just a while ago we had the
|
||||||
|
* binary address but had to zero it. dbusers.c should be refactored to fix this.
|
||||||
|
*/
|
||||||
|
struct sockaddr_in bin_address;
|
||||||
|
bin_address.sin_family = AF_INET;
|
||||||
|
if (inet_pton(bin_address.sin_family, ip_address, &(bin_address.sin_addr)) != 1)
|
||||||
|
{
|
||||||
|
MXS_ERROR("Could not convert to binary ip-address: '%s'.", ip_address);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Try to lookup the domain name of the given IP-address. This is a slow
|
||||||
|
* i/o-operation, which will stall the entire thread. TODO: cache results
|
||||||
|
* if this feature is used often.
|
||||||
|
*/
|
||||||
|
MXS_DEBUG("Resolving '%s'", ip_address);
|
||||||
|
int lookup_result = getnameinfo((struct sockaddr*)&bin_address,
|
||||||
|
sizeof(struct sockaddr_in),
|
||||||
|
client_hostname, sizeof(client_hostname),
|
||||||
|
NULL, 0, // No need for the port
|
||||||
|
NI_NAMEREQD); // Text address only
|
||||||
|
|
||||||
|
if (lookup_result != 0)
|
||||||
|
{
|
||||||
|
MXS_ERROR("Client hostname lookup failed, getnameinfo() returned: '%s'.",
|
||||||
|
gai_strerror(lookup_result));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
MXS_DEBUG("IP-lookup success, hostname is: '%s'", client_hostname);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
@ -41,6 +41,8 @@ static bool mysql_auth_is_client_ssl_capable(DCB *dcb);
|
|||||||
static int mysql_auth_authenticate(DCB *dcb);
|
static int mysql_auth_authenticate(DCB *dcb);
|
||||||
static void mysql_auth_free_client_data(DCB *dcb);
|
static void mysql_auth_free_client_data(DCB *dcb);
|
||||||
static int mysql_auth_load_users(SERV_LISTENER *port);
|
static int mysql_auth_load_users(SERV_LISTENER *port);
|
||||||
|
static void *mysql_auth_create(void *instance);
|
||||||
|
static void mysql_auth_destroy(void *data);
|
||||||
|
|
||||||
static int combined_auth_check(
|
static int combined_auth_check(
|
||||||
DCB *dcb,
|
DCB *dcb,
|
||||||
@ -72,12 +74,12 @@ MXS_MODULE* MXS_CREATE_MODULE()
|
|||||||
static MXS_AUTHENTICATOR MyObject =
|
static MXS_AUTHENTICATOR MyObject =
|
||||||
{
|
{
|
||||||
mysql_auth_init, /* Initialize the authenticator */
|
mysql_auth_init, /* Initialize the authenticator */
|
||||||
NULL, /* No create entry point */
|
mysql_auth_create, /* Create entry point */
|
||||||
mysql_auth_set_protocol_data, /* Extract data into structure */
|
mysql_auth_set_protocol_data, /* Extract data into structure */
|
||||||
mysql_auth_is_client_ssl_capable, /* Check if client supports SSL */
|
mysql_auth_is_client_ssl_capable, /* Check if client supports SSL */
|
||||||
mysql_auth_authenticate, /* Authenticate user credentials */
|
mysql_auth_authenticate, /* Authenticate user credentials */
|
||||||
mysql_auth_free_client_data, /* Free the client data held in DCB */
|
mysql_auth_free_client_data, /* Free the client data held in DCB */
|
||||||
NULL, /* No destroy entry point */
|
mysql_auth_destroy, /* Destroy entry point */
|
||||||
mysql_auth_load_users, /* Load users from backend databases */
|
mysql_auth_load_users, /* Load users from backend databases */
|
||||||
mysql_auth_reauthenticate /* Handle COM_CHANGE_USER */
|
mysql_auth_reauthenticate /* Handle COM_CHANGE_USER */
|
||||||
};
|
};
|
||||||
@ -117,6 +119,24 @@ static void* mysql_auth_init(char **options)
|
|||||||
instance->inject_service_user = true;
|
instance->inject_service_user = true;
|
||||||
instance->skip_auth = false;
|
instance->skip_auth = false;
|
||||||
|
|
||||||
|
if (sqlite3_open_v2(MYSQLAUTH_DATABASE_NAME, &instance->handle, db_flags, NULL) != SQLITE_OK)
|
||||||
|
{
|
||||||
|
MXS_ERROR("Failed to open SQLite3 handle.");
|
||||||
|
MXS_FREE(instance);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *err;
|
||||||
|
|
||||||
|
if (sqlite3_exec(instance->handle, create_sql, NULL, NULL, &err) != SQLITE_OK)
|
||||||
|
{
|
||||||
|
MXS_ERROR("Failed to create database: %s", err);
|
||||||
|
sqlite3_free(err);
|
||||||
|
sqlite3_close_v2(instance->handle);
|
||||||
|
MXS_FREE(instance);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = 0; options[i]; i++)
|
for (int i = 0; options[i]; i++)
|
||||||
{
|
{
|
||||||
char *value = strchr(options[i], '=');
|
char *value = strchr(options[i], '=');
|
||||||
@ -165,6 +185,37 @@ static void* mysql_auth_init(char **options)
|
|||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void* mysql_auth_create(void *instance)
|
||||||
|
{
|
||||||
|
mysql_auth_t *rval = MXS_MALLOC(sizeof(*rval));
|
||||||
|
|
||||||
|
if (rval)
|
||||||
|
{
|
||||||
|
if (sqlite3_open_v2(MYSQLAUTH_DATABASE_NAME, &rval->handle, db_flags, NULL) == SQLITE_OK)
|
||||||
|
{
|
||||||
|
sqlite3_busy_timeout(rval->handle, MXS_SQLITE_BUSY_TIMEOUT);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
MXS_ERROR("Failed to open SQLite3 handle.");
|
||||||
|
MXS_FREE(rval);
|
||||||
|
rval = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rval;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mysql_auth_destroy(void *data)
|
||||||
|
{
|
||||||
|
mysql_auth_t *auth = (mysql_auth_t*)data;
|
||||||
|
if (auth)
|
||||||
|
{
|
||||||
|
sqlite3_close_v2(auth->handle);
|
||||||
|
MXS_FREE(auth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Authenticates a MySQL user who is a client to MaxScale.
|
* @brief Authenticates a MySQL user who is a client to MaxScale.
|
||||||
*
|
*
|
||||||
@ -183,7 +234,7 @@ mysql_auth_authenticate(DCB *dcb)
|
|||||||
{
|
{
|
||||||
MySQLProtocol *protocol = DCB_PROTOCOL(dcb, MySQLProtocol);
|
MySQLProtocol *protocol = DCB_PROTOCOL(dcb, MySQLProtocol);
|
||||||
MYSQL_session *client_data = (MYSQL_session *)dcb->data;
|
MYSQL_session *client_data = (MYSQL_session *)dcb->data;
|
||||||
int auth_ret;
|
int auth_ret = MXS_AUTH_FAILED;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* We record the SSL status before and after the authentication. This allows
|
* We record the SSL status before and after the authentication. This allows
|
||||||
@ -199,24 +250,16 @@ mysql_auth_authenticate(DCB *dcb)
|
|||||||
{
|
{
|
||||||
auth_ret = (SSL_ERROR_CLIENT_NOT_SSL == ssl_ret) ? MXS_AUTH_FAILED_SSL : MXS_AUTH_FAILED;
|
auth_ret = (SSL_ERROR_CLIENT_NOT_SSL == ssl_ret) ? MXS_AUTH_FAILED_SSL : MXS_AUTH_FAILED;
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (!health_after)
|
else if (!health_after)
|
||||||
{
|
{
|
||||||
auth_ret = MXS_AUTH_SSL_INCOMPLETE;
|
auth_ret = MXS_AUTH_SSL_INCOMPLETE;
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (!health_before && health_after)
|
else if (!health_before && health_after)
|
||||||
{
|
{
|
||||||
auth_ret = MXS_AUTH_SSL_INCOMPLETE;
|
auth_ret = MXS_AUTH_SSL_INCOMPLETE;
|
||||||
poll_add_epollin_event_to_dcb(dcb, NULL);
|
poll_add_epollin_event_to_dcb(dcb, NULL);
|
||||||
}
|
}
|
||||||
|
else if (*client_data->user)
|
||||||
else if (0 == strlen(client_data->user))
|
|
||||||
{
|
|
||||||
auth_ret = MXS_AUTH_FAILED;
|
|
||||||
}
|
|
||||||
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
MXS_DEBUG("Receiving connection from '%s' to database '%s'.",
|
MXS_DEBUG("Receiving connection from '%s' to database '%s'.",
|
||||||
client_data->user, client_data->db);
|
client_data->user, client_data->db);
|
||||||
@ -226,17 +269,15 @@ mysql_auth_authenticate(DCB *dcb)
|
|||||||
|
|
||||||
MYSQL_AUTH *instance = (MYSQL_AUTH*)dcb->listener->auth_instance;
|
MYSQL_AUTH *instance = (MYSQL_AUTH*)dcb->listener->auth_instance;
|
||||||
|
|
||||||
/* On failed authentication try to load user table from backend database */
|
bool is_ok = validate_mysql_user(instance->handle, dcb, client_data);
|
||||||
/* Success for service_refresh_users returns 0 */
|
|
||||||
if (MXS_AUTH_SUCCEEDED != auth_ret && !instance->skip_auth &&
|
if (!is_ok && !instance->skip_auth && service_refresh_users(dcb->service) == 0)
|
||||||
0 == service_refresh_users(dcb->service))
|
|
||||||
{
|
{
|
||||||
auth_ret = combined_auth_check(dcb, client_data->auth_token, client_data->auth_token_len, protocol,
|
is_ok = validate_mysql_user(instance->handle, dcb, client_data);
|
||||||
client_data->user, client_data->client_sha1, client_data->db);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* on successful authentication, set user into dcb field */
|
/* on successful authentication, set user into dcb field */
|
||||||
if (MXS_AUTH_SUCCEEDED == auth_ret || instance->skip_auth)
|
if (is_ok || instance->skip_auth)
|
||||||
{
|
{
|
||||||
auth_ret = MXS_AUTH_SUCCEEDED;
|
auth_ret = MXS_AUTH_SUCCEEDED;
|
||||||
dcb->user = MXS_STRDUP_A(client_data->user);
|
dcb->user = MXS_STRDUP_A(client_data->user);
|
||||||
|
@ -41,7 +41,7 @@ MXS_BEGIN_DECLS
|
|||||||
static const char DBUSERS_DIR[] = "cache";
|
static const char DBUSERS_DIR[] = "cache";
|
||||||
static const char DBUSERS_FILE[] = "dbusers";
|
static const char DBUSERS_FILE[] = "dbusers";
|
||||||
|
|
||||||
#define MYSQLAUTH_DATABASE_NAME "file:mysqlauth.db"
|
#define MYSQLAUTH_DATABASE_NAME "file:mysqlauth.db?mode=memory&cache=shared"
|
||||||
|
|
||||||
/** The table name where we store the users */
|
/** The table name where we store the users */
|
||||||
#define MYSQLAUTH_TABLE_NAME "mysqlauth_users"
|
#define MYSQLAUTH_TABLE_NAME "mysqlauth_users"
|
||||||
@ -52,10 +52,10 @@ static const char create_sql[] =
|
|||||||
"(user varchar(255), host varchar(255), db varchar(255), anydb boolean, password text)";
|
"(user varchar(255), host varchar(255), db varchar(255), anydb boolean, password text)";
|
||||||
|
|
||||||
/** The query that is executed when a user is authenticated */
|
/** The query that is executed when a user is authenticated */
|
||||||
static const char mysqlauth_auth_query[] =
|
static const char mysqlauth_validation_query[] =
|
||||||
"SELECT * FROM " MYSQLAUTH_TABLE_NAME
|
"SELECT password FROM " MYSQLAUTH_TABLE_NAME
|
||||||
" WHERE user = '%s' AND '%s' LIKE host AND (anydb = '1' OR '%s' = '' OR '%s' LIKE db)"
|
" WHERE user = '%s' AND '%s' LIKE host AND (anydb = '1' OR '%s' = '' OR '%s' LIKE db)"
|
||||||
" AND ('%s' = '%s') LIMIT 1";
|
" LIMIT 1";
|
||||||
|
|
||||||
/** Delete query used to clean up the database before loading new users */
|
/** Delete query used to clean up the database before loading new users */
|
||||||
static const char delete_query[] = "DELETE FROM " MYSQLAUTH_TABLE_NAME;
|
static const char delete_query[] = "DELETE FROM " MYSQLAUTH_TABLE_NAME;
|
||||||
@ -121,5 +121,6 @@ int gw_find_mysql_user_password_sha1(
|
|||||||
const char *username,
|
const char *username,
|
||||||
uint8_t *gateway_password,
|
uint8_t *gateway_password,
|
||||||
DCB *dcb);
|
DCB *dcb);
|
||||||
|
bool validate_mysql_user(sqlite3 *handle, DCB *dcb, MYSQL_session *session);
|
||||||
|
|
||||||
MXS_END_DECLS
|
MXS_END_DECLS
|
||||||
|
Reference in New Issue
Block a user