diff --git a/server/modules/authenticator/MySQLAuth/dbusers.c b/server/modules/authenticator/MySQLAuth/dbusers.c index 978375513..2ba99e7dd 100644 --- a/server/modules/authenticator/MySQLAuth/dbusers.c +++ b/server/modules/authenticator/MySQLAuth/dbusers.c @@ -74,9 +74,7 @@ static char* get_new_users_query(const char *server_version, bool include_root) int replace_mysql_users(SERV_LISTENER *listener, bool skip_local) { - spinlock_acquire(&listener->lock); int i = get_users(listener, skip_local); - spinlock_release(&listener->lock); return i; } @@ -185,7 +183,7 @@ static int auth_cb(void *data, int columns, char** rows, char** row_names) int validate_mysql_user(MYSQL_AUTH* instance, DCB *dcb, MYSQL_session *session, uint8_t *scramble, size_t scramble_len) { - sqlite3 *handle = instance->handle; + sqlite3 *handle = get_handle(instance); size_t len = sizeof(mysqlauth_validate_user_query) + strlen(session->user) * 2 + strlen(session->db) * 2 + MYSQL_HOST_MAXLEN + session->auth_token_len * 4 + 1; char sql[len + 1]; @@ -713,26 +711,6 @@ static bool get_hostname(DCB *dcb, char *client_hostname, size_t size) return lookup_result == 0; } -void start_sqlite_transaction(sqlite3 *handle) -{ - char *err; - if (sqlite3_exec(handle, "BEGIN", NULL, NULL, &err) != SQLITE_OK) - { - MXS_ERROR("Failed to start transaction: %s", err); - sqlite3_free(err); - } -} - -void commit_sqlite_transaction(sqlite3 *handle) -{ - char *err; - if (sqlite3_exec(handle, "COMMIT", NULL, NULL, &err) != SQLITE_OK) - { - MXS_ERROR("Failed to commit transaction: %s", err); - sqlite3_free(err); - } -} - int get_users_from_server(MYSQL *con, SERVER_REF *server_ref, SERVICE *service, SERV_LISTENER *listener) { if (server_ref->server->version_string[0] == 0) @@ -742,6 +720,7 @@ int get_users_from_server(MYSQL *con, SERVER_REF *server_ref, SERVICE *service, char *query = get_new_users_query(server_ref->server->version_string, service->enable_root); MYSQL_AUTH *instance = (MYSQL_AUTH*)listener->auth_instance; + sqlite3* handle = get_handle(instance); bool anon_user = false; int users = 0; @@ -753,8 +732,6 @@ int get_users_from_server(MYSQL *con, SERVER_REF *server_ref, SERVICE *service, if (result) { - start_sqlite_transaction(instance->handle); - MYSQL_ROW row; while ((row = mysql_fetch_row(result))) @@ -769,7 +746,7 @@ int get_users_from_server(MYSQL *con, SERVER_REF *server_ref, SERVICE *service, merge_netmask(row[1]); } - add_mysql_user(instance->handle, row[0], row[1], row[2], + add_mysql_user(handle, row[0], row[1], row[2], row[3] && strcmp(row[3], "Y") == 0, row[4]); users++; @@ -781,8 +758,6 @@ int get_users_from_server(MYSQL *con, SERVER_REF *server_ref, SERVICE *service, } } - commit_sqlite_transaction(instance->handle); - mysql_free_result(result); } } @@ -809,7 +784,7 @@ int get_users_from_server(MYSQL *con, SERVER_REF *server_ref, SERVICE *service, MYSQL_ROW row; while ((row = mysql_fetch_row(result))) { - add_database(instance->handle, row[0]); + add_database(handle, row[0]); } mysql_free_result(result); @@ -851,7 +826,8 @@ static int get_users(SERV_LISTENER *listener, bool skip_local) /** Delete the old users */ MYSQL_AUTH *instance = (MYSQL_AUTH*)listener->auth_instance; - delete_mysql_users(instance->handle); + sqlite3* handle = get_handle(instance); + delete_mysql_users(handle); SERVER_REF *server = service->dbref; int total_users = -1; diff --git a/server/modules/authenticator/MySQLAuth/mysql_auth.c b/server/modules/authenticator/MySQLAuth/mysql_auth.c index 3507e8eea..05d946496 100644 --- a/server/modules/authenticator/MySQLAuth/mysql_auth.c +++ b/server/modules/authenticator/MySQLAuth/mysql_auth.c @@ -34,6 +34,7 @@ #include #include #include +#include static void* mysql_auth_init(char **options); static bool mysql_auth_set_protocol_data(DCB *dcb, GWBUF *buf); @@ -109,27 +110,6 @@ MXS_MODULE* MXS_CREATE_MODULE() return &info; } -static void get_database_path(SERV_LISTENER *port, char *dest, size_t size) -{ - MYSQL_AUTH *instance = port->auth_instance; - SERVICE *service = port->service; - ss_dassert(size - sizeof(DBUSERS_FILE) - 1 >= 0); - - if (instance->cache_dir) - { - snprintf(dest, size, "%s/", instance->cache_dir); - } - else - { - snprintf(dest, size, "%s/%s/%s/%s/", get_cachedir(), service->name, port->name, DBUSERS_DIR); - } - - if (mxs_mkdir_all(dest, S_IRWXU)) - { - strcat(dest, DBUSERS_FILE); - } -} - static bool open_instance_database(const char *path, sqlite3 **handle) { if (sqlite3_open_v2(path, handle, db_flags, NULL) != SQLITE_OK) @@ -153,21 +133,31 @@ static bool open_instance_database(const char *path, sqlite3 **handle) return true; } -static bool open_client_database(const char *path, sqlite3 **handle) +sqlite3* get_handle(MYSQL_AUTH* instance) { - bool rval = false; + int i = mxs_worker_get_current_id(); + ss_dassert(i >= 0); - if (sqlite3_open_v2(path, handle, db_flags, NULL) == SQLITE_OK) + if (instance->handles[i] == NULL) { - sqlite3_busy_timeout(*handle, MXS_SQLITE_BUSY_TIMEOUT); - rval = true; - } - else - { - MXS_ERROR("Failed to open SQLite3 handle."); + ss_debug(bool rval = )open_instance_database(":memory", &instance->handles[i]); + ss_dassert(rval); } - return rval; + return instance->handles[i]; +} + +/** + * @brief Check if service permissions should be checked + * + * @param instance Authenticator instance + * + * @return True if permissions should be checked + */ +static bool should_check_permissions(MYSQL_AUTH* instance) +{ + // Only check permissions when the users are loaded for the first time. + return instance->check_permissions; } /** @@ -180,13 +170,13 @@ static void* mysql_auth_init(char **options) { MYSQL_AUTH *instance = MXS_MALLOC(sizeof(*instance)); - if (instance) + if (instance && (instance->handles = MXS_CALLOC(config_threadcount(), sizeof(sqlite3*)))) { bool error = false; instance->cache_dir = NULL; instance->inject_service_user = true; instance->skip_auth = false; - instance->handle = NULL; + instance->check_permissions = true; for (int i = 0; options[i]; i++) { @@ -228,10 +218,16 @@ static void* mysql_auth_init(char **options) if (error) { MXS_FREE(instance->cache_dir); + MXS_FREE(instance->handles); MXS_FREE(instance); instance = NULL; } } + else if (instance) + { + MXS_FREE(instance); + instance = NULL; + } return instance; } @@ -508,8 +504,9 @@ static bool add_service_user(SERV_LISTENER *port) if (newpw) { MYSQL_AUTH *inst = (MYSQL_AUTH*)port->auth_instance; - add_mysql_user(inst->handle, user, "%", "", "Y", newpw); - add_mysql_user(inst->handle, user, "localhost", "", "Y", newpw); + sqlite3* handle = get_handle(inst); + add_mysql_user(handle, user, "%", "", "Y", newpw); + add_mysql_user(handle, user, "localhost", "", "Y", newpw); MXS_FREE(newpw); rval = true; } @@ -542,21 +539,21 @@ static int mysql_auth_load_users(SERV_LISTENER *port) int rc = MXS_AUTH_LOADUSERS_OK; SERVICE *service = port->listener->service; MYSQL_AUTH *instance = (MYSQL_AUTH*)port->auth_instance; - bool skip_local = false; + bool first_load = false; - if (instance->handle == NULL) + if (should_check_permissions(instance)) { - skip_local = true; - char path[PATH_MAX]; - get_database_path(port, path, sizeof(path)); - if (!check_service_permissions(port->service) || - !open_instance_database(path, &instance->handle)) + if (!check_service_permissions(port->service)) { return MXS_AUTH_LOADUSERS_FATAL; } + + // Permissions are OK, no need to check them again + instance->check_permissions = false; + first_load = true; } - int loaded = replace_mysql_users(port, skip_local); + int loaded = replace_mysql_users(port, first_load); bool injected = false; if (loaded <= 0) @@ -588,12 +585,12 @@ static int mysql_auth_load_users(SERV_LISTENER *port) "Enabling service credentials for authentication until " "database users have been successfully loaded.", service->name); } - else if (loaded == 0 && !skip_local) + else if (loaded == 0 && !first_load) { MXS_WARNING("[%s]: failed to load any user information. Authentication" " will probably fail as a result.", service->name); } - else if (loaded > 0) + else if (loaded > 0 && first_load) { MXS_NOTICE("[%s] Loaded %d MySQL users for listener %s.", service->name, loaded, port->name); } @@ -640,9 +637,10 @@ void mysql_auth_diagnostic(DCB *dcb, SERV_LISTENER *port) dcb_printf(dcb, "User names: "); MYSQL_AUTH *instance = (MYSQL_AUTH*)port->auth_instance; + sqlite3* handle = get_handle(instance); char *err; - if (sqlite3_exec(instance->handle, "SELECT user, host FROM " MYSQLAUTH_USERS_TABLE_NAME, + if (sqlite3_exec(handle, "SELECT user, host FROM " MYSQLAUTH_USERS_TABLE_NAME, diag_cb, dcb, &err) != SQLITE_OK) { dcb_printf(dcb, "Failed to print users: %s\n", err); @@ -669,8 +667,9 @@ json_t* mysql_auth_diagnostic_json(const SERV_LISTENER *port) MYSQL_AUTH *instance = (MYSQL_AUTH*)port->auth_instance; char *err; + sqlite3* handle = get_handle(instance); - if (sqlite3_exec(instance->handle, "SELECT user, host FROM " MYSQLAUTH_USERS_TABLE_NAME, + if (sqlite3_exec(handle, "SELECT user, host FROM " MYSQLAUTH_USERS_TABLE_NAME, diag_cb, rval, &err) != SQLITE_OK) { MXS_ERROR("Failed to print users: %s", err); diff --git a/server/modules/authenticator/MySQLAuth/mysql_auth.h b/server/modules/authenticator/MySQLAuth/mysql_auth.h index 5142a3cac..ae81ccaf9 100644 --- a/server/modules/authenticator/MySQLAuth/mysql_auth.h +++ b/server/modules/authenticator/MySQLAuth/mysql_auth.h @@ -102,14 +102,15 @@ static const char null_token[] = "NULL"; /** Flags for sqlite3_open_v2() */ static int db_flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | - SQLITE_OPEN_SHAREDCACHE; + SQLITE_OPEN_NOMUTEX; typedef struct mysql_auth { - sqlite3 *handle; /**< SQLite3 database handle */ + sqlite3 **handles; /**< SQLite3 database handle */ char *cache_dir; /**< Custom cache directory location */ bool inject_service_user; /**< Inject the service user into the list of users */ bool skip_auth; /**< Authentication will always be successful */ + bool check_permissions; } MYSQL_AUTH; /** @@ -124,6 +125,15 @@ typedef struct mysql_user_host_key char hostname[MYSQL_HOST_MAXLEN + 1]; } MYSQL_USER_HOST; +/** + * @brief Get the thread-specific SQLite handle + * + * @param instance Authenticator instance + * + * @return The thread-specific handle + */ +sqlite3* get_handle(MYSQL_AUTH* instance); + /** * @brief Add new MySQL user to the internal user database *