From 4f03c53bdfdeadceb9a65ad9b7f9ab0d711c884e Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Thu, 9 Mar 2017 10:04:59 +0200 Subject: [PATCH 01/38] Update 2.1.1 release notes --- Documentation/Release-Notes/MaxScale-2.1.1-Release-Notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/Release-Notes/MaxScale-2.1.1-Release-Notes.md b/Documentation/Release-Notes/MaxScale-2.1.1-Release-Notes.md index 39d65b1a6..423752daa 100644 --- a/Documentation/Release-Notes/MaxScale-2.1.1-Release-Notes.md +++ b/Documentation/Release-Notes/MaxScale-2.1.1-Release-Notes.md @@ -74,6 +74,7 @@ NOTE This feature was available already in _2.1.0_. [Here is a list of bugs fixed since the release of MaxScale 2.1.0.](https://jira.mariadb.org/issues/?jql=project%20%3D%20MXS%20AND%20issuetype%20%3D%20Bug%20AND%20resolution%20in%20(Fixed%2C%20Done)%20AND%20fixVersion%20%3D%202.1.1%20AND%20fixVersion%20NOT%20IN%20(2.1.0)) +* [MXS-1178](https://jira.mariadb.org/browse/MXS-1178) master_accept_reads doesn't work with detect_replication_lag * [MXS-1165](https://jira.mariadb.org/browse/MXS-1165) MaxInfo eat too much memory when getting list of session and client. * [MXS-1143](https://jira.mariadb.org/browse/MXS-1143) Add support for new MariaDB 10.2 flags * [MXS-1130](https://jira.mariadb.org/browse/MXS-1130) Unexpected length encoding 'ff' encountered From 6da8cfe97e48dbd313d4f6282c46ed3cd1aa2ace Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Fri, 10 Mar 2017 09:11:17 +0200 Subject: [PATCH 02/38] Fix assignment of master status on failed servers When the real root master server went down, it still received the master status bit due to how the replication tree was built. The bit should only be set for servers that are running. Also fixed a false state change event when the master status bit was manually cleared from the downed root master server. --- server/core/monitor.c | 9 ++++++--- server/modules/monitor/mysqlmon/mysql_mon.c | 7 +++++-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/server/core/monitor.c b/server/core/monitor.c index d2d392e03..1f34f3809 100644 --- a/server/core/monitor.c +++ b/server/core/monitor.c @@ -998,10 +998,13 @@ bool mon_status_changed(MXS_MONITOR_SERVERS* mon_srv) unsigned int new_status = mon_srv->server->status & all_server_bits; /** - * The state has changed if the relevant state bits are not the same and - * the server is not going into maintenance or coming out of it + * The state has changed if the relevant state bits are not the same, + * the server is either running, stopping or starting and the server is + * not going into maintenance or coming out of it */ - if (old_status != new_status && ((old_status | new_status) & SERVER_MAINT) == 0) + if (old_status != new_status && + ((old_status | new_status) & SERVER_MAINT) == 0 && + ((old_status | new_status) & SERVER_RUNNING) == SERVER_RUNNING) { rval = true; } diff --git a/server/modules/monitor/mysqlmon/mysql_mon.c b/server/modules/monitor/mysqlmon/mysql_mon.c index 4e429be09..e4e5f26b3 100644 --- a/server/modules/monitor/mysqlmon/mysql_mon.c +++ b/server/modules/monitor/mysqlmon/mysql_mon.c @@ -1777,8 +1777,11 @@ static MXS_MONITOR_SERVERS *get_replication_tree(MXS_MONITOR *mon, int num_serve master->server->unique_name); ss_dassert(info); - /** Only set the Master status if read_only is disabled */ - monitor_set_pending_status(master, info->read_only ? SERVER_SLAVE : SERVER_MASTER); + if (SERVER_IS_RUNNING(master->server)) + { + /** Only set the Master status if read_only is disabled */ + monitor_set_pending_status(master, info->read_only ? SERVER_SLAVE : SERVER_MASTER); + } handle->master = master; } From d4a06c61de2fe63d56201e9074b5a15058b78701 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Sat, 28 Jan 2017 11:23:49 +0200 Subject: [PATCH 03/38] Move reauthentication to authenticators Currently the only situation where a user needs to be authenticated after the initial authentication is when a COM_CHANGE_USER is being executed. This was previously handled by directly calling a function in the MySQLAuth authenticator. The new entry in the API of the authenticators is very specific to MySQL and should be reviewed once other protocols are added. --- include/maxscale/authenticator.h | 6 + .../modules/authenticator/MySQLAuth/dbusers.c | 4 +- .../modules/authenticator/MySQLAuth/dbusers.h | 69 ---------- .../authenticator/MySQLAuth/mysql_auth.c | 32 +++-- .../authenticator/MySQLAuth/mysql_auth.h | 125 ++++++++++++++++++ server/modules/include/mysql_auth.h | 50 ------- .../MySQL/MySQLBackend/mysql_backend.c | 34 ++--- .../protocol/MySQL/MySQLClient/mysql_client.c | 1 - 8 files changed, 169 insertions(+), 152 deletions(-) delete mode 100644 server/modules/authenticator/MySQLAuth/dbusers.h create mode 100644 server/modules/authenticator/MySQLAuth/mysql_auth.h delete mode 100644 server/modules/include/mysql_auth.h diff --git a/include/maxscale/authenticator.h b/include/maxscale/authenticator.h index 43d561675..0d8019ffd 100644 --- a/include/maxscale/authenticator.h +++ b/include/maxscale/authenticator.h @@ -76,6 +76,12 @@ typedef struct mxs_authenticator void (*free)(struct dcb *); void (*destroy)(void *); int (*loadusers)(struct servlistener *); + + /** This entry point was added to avoid calling authenticator functions + * directly when a COM_CHANGE_USER command is executed. */ + int (*reauthenticate)(struct dcb *, const char *user, + uint8_t *token, size_t token_len, + uint8_t *scramble, size_t scramble_len); } MXS_AUTHENTICATOR; /** Return values for extract and authenticate entry points */ diff --git a/server/modules/authenticator/MySQLAuth/dbusers.c b/server/modules/authenticator/MySQLAuth/dbusers.c index bf0f886f0..f79f5167f 100644 --- a/server/modules/authenticator/MySQLAuth/dbusers.c +++ b/server/modules/authenticator/MySQLAuth/dbusers.c @@ -35,7 +35,7 @@ * @endverbatim */ -#define MXS_MODULE_NAME "MySQLAuth" +#include "mysql_auth.h" #include #include @@ -44,12 +44,10 @@ #include #include #include -#include "dbusers.h" #include #include #include #include -#include #include #include diff --git a/server/modules/authenticator/MySQLAuth/dbusers.h b/server/modules/authenticator/MySQLAuth/dbusers.h deleted file mode 100644 index a770fbae4..000000000 --- a/server/modules/authenticator/MySQLAuth/dbusers.h +++ /dev/null @@ -1,69 +0,0 @@ -#pragma once -#ifndef _MAXSCALE_DBUSERS_H -#define _MAXSCALE_DBUSERS_H -/* - * Copyright (c) 2016 MariaDB Corporation Ab - * - * Use of this software is governed by the Business Source License included - * in the LICENSE.TXT file and at www.mariadb.com/bsl11. - * - * Change Date: 2019-07-01 - * - * On the date above, in accordance with the Business Source License, use - * of this software will be governed by version 2 or later of the General - * Public License. - */ - -/** - * @file dbusers.h Extarct user information form the backend database - * - * @verbatim - * Revision History - * - * Date Who Description - * 25/06/13 Mark Riddoch Initial implementation - * 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 - */ - -#include -#include -#include -#include - -MXS_BEGIN_DECLS - - -/** Cache directory and file names */ -static const char DBUSERS_DIR[] = "cache"; -static const char DBUSERS_FILE[] = "dbusers"; - -/** - * MySQL user and host data structure - */ -typedef struct mysql_user_host_key -{ - char *user; - struct sockaddr_in ipv4; - int netmask; - char *resource; - char hostname[MYSQL_HOST_MAXLEN + 1]; -} MYSQL_USER_HOST; - -extern int add_mysql_users_with_host_ipv4(USERS *users, const char *user, const char *host, - char *passwd, const char *anydb, const char *db); -extern bool check_service_permissions(SERVICE* service); -extern int dbusers_load(USERS *, const char *filename); -extern int dbusers_save(USERS *, const char *filename); -extern int mysql_users_add(USERS *users, MYSQL_USER_HOST *key, char *auth); -extern USERS *mysql_users_alloc(); -extern char *mysql_users_fetch(USERS *users, MYSQL_USER_HOST *key); -extern int replace_mysql_users(SERV_LISTENER *listener); - -MXS_END_DECLS - -#endif diff --git a/server/modules/authenticator/MySQLAuth/mysql_auth.c b/server/modules/authenticator/MySQLAuth/mysql_auth.c index 9d4556cbb..30593c18b 100644 --- a/server/modules/authenticator/MySQLAuth/mysql_auth.c +++ b/server/modules/authenticator/MySQLAuth/mysql_auth.c @@ -25,25 +25,16 @@ * @endverbatim */ -#define MXS_MODULE_NAME "MySQLAuth" +#include "mysql_auth.h" -#include #include #include #include #include -#include "dbusers.h" #include #include #include -typedef struct mysql_auth -{ - 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 */ -} MYSQL_AUTH; - static void* mysql_auth_init(char **options); static int mysql_auth_set_protocol_data(DCB *dcb, GWBUF *buf); static bool mysql_auth_is_client_ssl_capable(DCB *dcb); @@ -65,6 +56,9 @@ static int mysql_auth_set_client_data( MySQLProtocol *protocol, GWBUF *buffer); +int mysql_auth_reauthenticate(DCB *dcb, const char *user, + uint8_t *token, size_t token_len, + uint8_t *scramble, size_t scramble_len); /** * The module entry point routine. It is this routine that * must populate the structure that is referred to as the @@ -84,7 +78,8 @@ MXS_MODULE* MXS_CREATE_MODULE() mysql_auth_authenticate, /* Authenticate user credentials */ mysql_auth_free_client_data, /* Free the client data held in DCB */ NULL, /* No 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 */ }; static MXS_MODULE info = @@ -437,7 +432,7 @@ mysql_auth_is_client_ssl_capable(DCB *dcb) * @return 1 if user is not found or 0 if the user exists * */ -int gw_find_mysql_user_password_sha1(char *username, uint8_t *gateway_password, DCB *dcb) +int gw_find_mysql_user_password_sha1(const char *username, uint8_t *gateway_password, DCB *dcb) { MYSQL_session *client_data = (MYSQL_session *) dcb->data; SERVICE *service = (SERVICE *) dcb->service; @@ -445,7 +440,7 @@ int gw_find_mysql_user_password_sha1(char *username, uint8_t *gateway_password, struct sockaddr_in *client = (struct sockaddr_in *) &dcb->ipv4; MYSQL_USER_HOST key = {}; - key.user = username; + key.user = (char*)username; memcpy(&key.ipv4, client, sizeof(struct sockaddr_in)); key.netmask = 32; key.resource = client_data->db; @@ -603,7 +598,7 @@ gw_check_mysql_scramble_data(DCB *dcb, unsigned int token_len, uint8_t *mxs_scramble, unsigned int scramble_len, - char *username, + const char *username, uint8_t *stage1_hash) { uint8_t step1[GW_MYSQL_SCRAMBLE_SIZE] = ""; @@ -946,3 +941,12 @@ static int mysql_auth_load_users(SERV_LISTENER *port) return rc; } + +int mysql_auth_reauthenticate(DCB *dcb, const char *user, + uint8_t *token, size_t token_len, + uint8_t *scramble, size_t scramble_len) +{ + MYSQL_session *client_data = (MYSQL_session *)dcb->data; + return gw_check_mysql_scramble_data(dcb, token, token_len, scramble, scramble_len, + user, client_data->client_sha1); +} diff --git a/server/modules/authenticator/MySQLAuth/mysql_auth.h b/server/modules/authenticator/MySQLAuth/mysql_auth.h new file mode 100644 index 000000000..42e27f7f1 --- /dev/null +++ b/server/modules/authenticator/MySQLAuth/mysql_auth.h @@ -0,0 +1,125 @@ +#pragma once +/* + * Copyright (c) 2016 MariaDB Corporation Ab + * + * Use of this software is governed by the Business Source License included + * in the LICENSE.TXT file and at www.mariadb.com/bsl. + * + * Change Date: 2019-07-01 + * + * On the date above, in accordance with the Business Source License, use + * of this software will be governed by version 2 or later of the General + * Public License. + */ + +/* + * @verbatim + * Revision History + * + * Date Who Description + * 02/02/2016 Martin Brampton Initial implementation + * + * @endverbatim + */ + +#define MXS_MODULE_NAME "MySQLAuth" + +#include + +#include +#include + +#include +#include +#include +#include +#include + +MXS_BEGIN_DECLS + +/** Cache directory and file names */ +static const char DBUSERS_DIR[] = "cache"; +static const char DBUSERS_FILE[] = "dbusers"; + +#define MYSQLAUTH_DATABASE_NAME "file:mysqlauth.db" + +/** The table name where we store the users */ +#define MYSQLAUTH_TABLE_NAME "mysqlauth_users" + +/** CREATE TABLE statement for the in-memory table */ +static const char create_sql[] = + "CREATE TABLE IF NOT EXISTS " MYSQLAUTH_TABLE_NAME + "(user varchar(255), host varchar(255), db varchar(255), anydb boolean, password text)"; + +/** The query that is executed when a user is authenticated */ +static const char mysqlauth_auth_query[] = + "SELECT * FROM " MYSQLAUTH_TABLE_NAME + " WHERE user = '%s' AND '%s' LIKE host AND (anydb = '1' OR '%s' = '' OR '%s' LIKE db)" + " AND ('%s' = '%s') LIMIT 1"; + +/** Delete query used to clean up the database before loading new users */ +static const char delete_query[] = "DELETE FROM " MYSQLAUTH_TABLE_NAME; + +/** The insert query template which adds users to the mysqlauth_users table */ +static const char insert_sql_pattern[] = + "INSERT INTO " MYSQLAUTH_TABLE_NAME " VALUES ('%s', '%s', %s, %s, %s)"; + +/** Used for NULL value creation in the INSERT query */ +static const char null_token[] = "NULL"; + +/** Flags for sqlite3_open_v2() */ +static int db_flags = SQLITE_OPEN_READWRITE | + SQLITE_OPEN_CREATE | + SQLITE_OPEN_URI | + SQLITE_OPEN_SHAREDCACHE; + +typedef struct mysql_auth +{ + sqlite3 *handle; /**< 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 */ +} MYSQL_AUTH; + +/** Common structure for both backend and client authenticators */ +typedef struct gssapi_auth +{ + sqlite3 *handle; /**< SQLite3 database handle */ +} mysql_auth_t; + +/** + * MySQL user and host data structure + */ +typedef struct mysql_user_host_key +{ + char *user; + struct sockaddr_in ipv4; + int netmask; + char *resource; + char hostname[MYSQL_HOST_MAXLEN + 1]; +} MYSQL_USER_HOST; + +extern int add_mysql_users_with_host_ipv4(USERS *users, const char *user, const char *host, + char *passwd, const char *anydb, const char *db); +extern bool check_service_permissions(SERVICE* service); +extern int dbusers_load(USERS *, const char *filename); +extern int dbusers_save(USERS *, const char *filename); +extern int mysql_users_add(USERS *users, MYSQL_USER_HOST *key, char *auth); +extern USERS *mysql_users_alloc(); +extern char *mysql_users_fetch(USERS *users, MYSQL_USER_HOST *key); +extern int replace_mysql_users(SERV_LISTENER *listener); + +int gw_check_mysql_scramble_data(DCB *dcb, + uint8_t *token, + unsigned int token_len, + uint8_t *scramble, + unsigned int scramble_len, + const char *username, + uint8_t *stage1_hash); +int check_db_name_after_auth(DCB *dcb, char *database, int auth_ret); +int gw_find_mysql_user_password_sha1( + const char *username, + uint8_t *gateway_password, + DCB *dcb); + +MXS_END_DECLS diff --git a/server/modules/include/mysql_auth.h b/server/modules/include/mysql_auth.h deleted file mode 100644 index 2801b437a..000000000 --- a/server/modules/include/mysql_auth.h +++ /dev/null @@ -1,50 +0,0 @@ -#pragma once -#ifndef _MYSQL_AUTH_H -#define _MYSQL_AUTH_H -/* - * Copyright (c) 2016 MariaDB Corporation Ab - * - * Use of this software is governed by the Business Source License included - * in the LICENSE.TXT file and at www.mariadb.com/bsl11. - * - * Change Date: 2019-07-01 - * - * On the date above, in accordance with the Business Source License, use - * of this software will be governed by version 2 or later of the General - * Public License. - */ - -/* - * @verbatim - * Revision History - * - * Date Who Description - * 02/02/2016 Martin Brampton Initial implementation - * - * @endverbatim - */ - -#include -#include -#include -#include -#include - -MXS_BEGIN_DECLS - -int gw_check_mysql_scramble_data(DCB *dcb, - uint8_t *token, - unsigned int token_len, - uint8_t *scramble, - unsigned int scramble_len, - char *username, - uint8_t *stage1_hash); -int check_db_name_after_auth(DCB *dcb, char *database, int auth_ret); -int gw_find_mysql_user_password_sha1( - char *username, - uint8_t *gateway_password, - DCB *dcb); - -MXS_END_DECLS - -#endif /** _MYSQL_AUTH_H */ diff --git a/server/modules/protocol/MySQL/MySQLBackend/mysql_backend.c b/server/modules/protocol/MySQL/MySQLBackend/mysql_backend.c index a664604af..f4cfcd588 100644 --- a/server/modules/protocol/MySQL/MySQLBackend/mysql_backend.c +++ b/server/modules/protocol/MySQL/MySQLBackend/mysql_backend.c @@ -23,7 +23,6 @@ #include #include #include -#include /* * MySQL Protocol module for handling the protocol between the gateway @@ -1526,11 +1525,20 @@ static int gw_change_user(DCB *backend, * 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_dcb, + DCB *dcb = backend->session->client_dcb; + + if (dcb->authfunc.reauthenticate == NULL) + { + /** Authenticator does not support reauthentication */ + rv = 0; + goto retblock; + } + + auth_ret = dcb->authfunc.reauthenticate(dcb, username, auth_token, auth_token_len, client_protocol->scramble, - sizeof(client_protocol->scramble), - username, client_sha1); + sizeof(client_protocol->scramble)); + strcpy(current_session->db, current_database); if (auth_ret != 0) @@ -1540,21 +1548,17 @@ static int gw_change_user(DCB *backend, /* Try authentication again with new repository data */ /* Note: if no auth client authentication will fail */ *current_session->db = 0; - auth_ret = gw_check_mysql_scramble_data( - backend->session->client_dcb, - auth_token, auth_token_len, - client_protocol->scramble, - sizeof(client_protocol->scramble), - username, client_sha1); + + auth_ret = dcb->authfunc.reauthenticate(dcb, username, + auth_token, auth_token_len, + client_protocol->scramble, + sizeof(client_protocol->scramble)); + strcpy(current_session->db, current_database); } } - /* let's free the auth_token now */ - if (auth_token) - { - MXS_FREE(auth_token); - } + MXS_FREE(auth_token); if (auth_ret != 0) { diff --git a/server/modules/protocol/MySQL/MySQLClient/mysql_client.c b/server/modules/protocol/MySQL/MySQLClient/mysql_client.c index cbfd09a73..bb13ceaf1 100644 --- a/server/modules/protocol/MySQL/MySQLClient/mysql_client.c +++ b/server/modules/protocol/MySQL/MySQLClient/mysql_client.c @@ -52,7 +52,6 @@ #include #include #include -#include #include #include #include From 041c0f1f2d19fd1680581fd8de50261e6573f88d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Sat, 28 Jan 2017 12:58:24 +0200 Subject: [PATCH 04/38] 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. --- include/maxscale/sqlite3.h | 3 + .../GSSAPI/GSSAPIAuth/gssapi_auth.c | 4 +- .../authenticator/MySQLAuth/CMakeLists.txt | 2 +- .../modules/authenticator/MySQLAuth/dbusers.c | 308 ++++++++++++++++++ .../authenticator/MySQLAuth/mysql_auth.c | 79 +++-- .../authenticator/MySQLAuth/mysql_auth.h | 9 +- 6 files changed, 378 insertions(+), 27 deletions(-) diff --git a/include/maxscale/sqlite3.h b/include/maxscale/sqlite3.h index 257c00646..e7a9bcfa5 100644 --- a/include/maxscale/sqlite3.h +++ b/include/maxscale/sqlite3.h @@ -25,3 +25,6 @@ #if SQLITE_VERSION_NUMBER < 3007014 #define sqlite3_close_v2 sqlite3_close #endif + +/** Default timeout is one minute */ +#define MXS_SQLITE_BUSY_TIMEOUT 60000 diff --git a/server/modules/authenticator/GSSAPI/GSSAPIAuth/gssapi_auth.c b/server/modules/authenticator/GSSAPI/GSSAPIAuth/gssapi_auth.c index fb31d0c70..e925b658c 100644 --- a/server/modules/authenticator/GSSAPI/GSSAPIAuth/gssapi_auth.c +++ b/server/modules/authenticator/GSSAPI/GSSAPIAuth/gssapi_auth.c @@ -20,11 +20,9 @@ #include #include #include +#include #include "../gssapi_auth.h" -/** Default timeout is one minute */ -#define MXS_SQLITE_BUSY_TIMEOUT 60000 - /** * MySQL queries for retrieving the list of users */ diff --git a/server/modules/authenticator/MySQLAuth/CMakeLists.txt b/server/modules/authenticator/MySQLAuth/CMakeLists.txt index 9bbcd952a..4f26199c6 100644 --- a/server/modules/authenticator/MySQLAuth/CMakeLists.txt +++ b/server/modules/authenticator/MySQLAuth/CMakeLists.txt @@ -1,4 +1,4 @@ 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") install_module(MySQLAuth core) diff --git a/server/modules/authenticator/MySQLAuth/dbusers.c b/server/modules/authenticator/MySQLAuth/dbusers.c index f79f5167f..71832fb76 100644 --- a/server/modules/authenticator/MySQLAuth/dbusers.c +++ b/server/modules/authenticator/MySQLAuth/dbusers.c @@ -40,6 +40,7 @@ #include #include #include +#include #include #include @@ -121,6 +122,15 @@ MaxScale authentication will proceed without including database permissions. \ 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_wildcard_users(USERS *users, char* name, char* host, 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; } +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 * that the grant has one or more single-character wildcards in it. @@ -1172,6 +1198,207 @@ cleanup: 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, ) ) ) + * + * 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 * environment. @@ -1618,6 +1845,37 @@ get_users(SERV_LISTENER *listener, USERS *users) MXS_FREE(users_data); 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); return total_users; @@ -2753,3 +3011,53 @@ static void merge_netmask(char *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; +} diff --git a/server/modules/authenticator/MySQLAuth/mysql_auth.c b/server/modules/authenticator/MySQLAuth/mysql_auth.c index 30593c18b..aadcf6330 100644 --- a/server/modules/authenticator/MySQLAuth/mysql_auth.c +++ b/server/modules/authenticator/MySQLAuth/mysql_auth.c @@ -41,6 +41,8 @@ static bool mysql_auth_is_client_ssl_capable(DCB *dcb); static int mysql_auth_authenticate(DCB *dcb); static void mysql_auth_free_client_data(DCB *dcb); 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( DCB *dcb, @@ -72,12 +74,12 @@ MXS_MODULE* MXS_CREATE_MODULE() static MXS_AUTHENTICATOR MyObject = { 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_is_client_ssl_capable, /* Check if client supports SSL */ mysql_auth_authenticate, /* Authenticate user credentials */ 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_reauthenticate /* Handle COM_CHANGE_USER */ }; @@ -117,6 +119,24 @@ static void* mysql_auth_init(char **options) instance->inject_service_user = true; 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++) { char *value = strchr(options[i], '='); @@ -165,6 +185,37 @@ static void* mysql_auth_init(char **options) 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. * @@ -183,7 +234,7 @@ mysql_auth_authenticate(DCB *dcb) { MySQLProtocol *protocol = DCB_PROTOCOL(dcb, MySQLProtocol); 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 @@ -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; } - else if (!health_after) { auth_ret = MXS_AUTH_SSL_INCOMPLETE; } - else if (!health_before && health_after) { auth_ret = MXS_AUTH_SSL_INCOMPLETE; poll_add_epollin_event_to_dcb(dcb, NULL); } - - else if (0 == strlen(client_data->user)) - { - auth_ret = MXS_AUTH_FAILED; - } - - else + else if (*client_data->user) { MXS_DEBUG("Receiving connection from '%s' to database '%s'.", client_data->user, client_data->db); @@ -226,17 +269,15 @@ mysql_auth_authenticate(DCB *dcb) MYSQL_AUTH *instance = (MYSQL_AUTH*)dcb->listener->auth_instance; - /* On failed authentication try to load user table from backend database */ - /* Success for service_refresh_users returns 0 */ - if (MXS_AUTH_SUCCEEDED != auth_ret && !instance->skip_auth && - 0 == service_refresh_users(dcb->service)) + bool is_ok = validate_mysql_user(instance->handle, dcb, client_data); + + if (!is_ok && !instance->skip_auth && service_refresh_users(dcb->service) == 0) { - auth_ret = combined_auth_check(dcb, client_data->auth_token, client_data->auth_token_len, protocol, - client_data->user, client_data->client_sha1, client_data->db); + is_ok = validate_mysql_user(instance->handle, dcb, client_data); } /* 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; dcb->user = MXS_STRDUP_A(client_data->user); diff --git a/server/modules/authenticator/MySQLAuth/mysql_auth.h b/server/modules/authenticator/MySQLAuth/mysql_auth.h index 42e27f7f1..d9a037a5d 100644 --- a/server/modules/authenticator/MySQLAuth/mysql_auth.h +++ b/server/modules/authenticator/MySQLAuth/mysql_auth.h @@ -41,7 +41,7 @@ MXS_BEGIN_DECLS static const char DBUSERS_DIR[] = "cache"; 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 */ #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)"; /** The query that is executed when a user is authenticated */ -static const char mysqlauth_auth_query[] = - "SELECT * FROM " MYSQLAUTH_TABLE_NAME +static const char mysqlauth_validation_query[] = + "SELECT password FROM " MYSQLAUTH_TABLE_NAME " 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 */ static const char delete_query[] = "DELETE FROM " MYSQLAUTH_TABLE_NAME; @@ -121,5 +121,6 @@ int gw_find_mysql_user_password_sha1( const char *username, uint8_t *gateway_password, DCB *dcb); +bool validate_mysql_user(sqlite3 *handle, DCB *dcb, MYSQL_session *session); MXS_END_DECLS From 2f4df0c21e9287a7ff7d01c4814a668276638e8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Sat, 28 Jan 2017 20:48:17 +0200 Subject: [PATCH 05/38] Store databases in the SQLite database The databases are now also stored in the sqlite database. This allows the `resource` member of the USERS struct to be removed in the future. --- .../modules/authenticator/MySQLAuth/dbusers.c | 136 ++++++++++++------ .../authenticator/MySQLAuth/mysql_auth.c | 6 +- .../authenticator/MySQLAuth/mysql_auth.h | 38 +++-- 3 files changed, 125 insertions(+), 55 deletions(-) diff --git a/server/modules/authenticator/MySQLAuth/dbusers.c b/server/modules/authenticator/MySQLAuth/dbusers.c index 71832fb76..5c3149af6 100644 --- a/server/modules/authenticator/MySQLAuth/dbusers.c +++ b/server/modules/authenticator/MySQLAuth/dbusers.c @@ -1243,6 +1243,32 @@ static bool check_password(const char *output, return memcmp(final_step, stored_token, stored_token_len) == 0; } +static int database_cb(void *data, int columns, char** rows, char** row_names) +{ + bool *rval = (bool*)data; + *rval = true; + return 0; +} + +static bool check_database(sqlite3 *handle, const char *database) +{ + size_t len = sizeof(mysqlauth_validate_database_query) + strlen(database) + 1; + char sql[len]; + + sprintf(sql, mysqlauth_validate_database_query, database); + bool rval = false; + char *err; + + if (sqlite3_exec(handle, sql, database_cb, &rval, &err) != SQLITE_OK) + { + MXS_ERROR("Failed to execute auth query: %s", err); + sqlite3_free(err); + rval = false; + } + + return rval; +} + /** Used to detect empty result sets */ struct user_query_result { @@ -1271,57 +1297,51 @@ static int auth_cb(void *data, int columns, char** rows, char** row_names) */ bool validate_mysql_user(sqlite3 *handle, DCB *dcb, MYSQL_session *session) { - size_t len = sizeof(mysqlauth_validation_query) + strlen(session->user) * 2 + + 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]; 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); + sprintf(sql, mysqlauth_validate_user_query, session->user, dcb->remote, + session->db, session->db); - struct user_query_result res = {}; + 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); + } + + if (!res.ok) + { + /** + * Try authentication with the hostname instead of the IP. We do this only + * as a last resort so we avoid the high cost of the DNS lookup. + */ + char client_hostname[MYSQL_HOST_MAXLEN]; + wildcard_domain_match(dcb->remote, client_hostname); + sprintf(sql, mysqlauth_validate_user_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) + if (res.ok) + { + /** Found a matching row */ + MySQLProtocol *proto = (MySQLProtocol*)dcb->protocol; + + if (check_password(res.output, session->auth_token, session->auth_token_len, + proto->scramble, sizeof(proto->scramble))) { - /** 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); + /** Password is OK, check that the database exists */ + rval = check_database(handle, session->db); } } @@ -1337,7 +1357,8 @@ static void delete_mysql_users(sqlite3 *handle) { char *err; - if (sqlite3_exec(handle, delete_query, NULL, NULL, &err) != SQLITE_OK) + if (sqlite3_exec(handle, delete_users_query, NULL, NULL, &err) != SQLITE_OK || + sqlite3_exec(handle, delete_databases_query, NULL, NULL, &err) != SQLITE_OK) { MXS_ERROR("Failed to delete old users: %s", err); sqlite3_free(err); @@ -1384,10 +1405,10 @@ static void add_mysql_user(sqlite3 *handle, const char *user, const char *host, strcpy(pwstr, null_token); } - size_t len = sizeof(insert_sql_pattern) + strlen(user) + strlen(host) + dblen + pwlen + 1; + size_t len = sizeof(insert_user_query) + 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); + sprintf(insert_sql, insert_user_query, user, host, dbstr, anydb ? "1" : "0", pwstr); char *err; if (sqlite3_exec(handle, insert_sql, NULL, NULL, &err) != SQLITE_OK) @@ -1399,6 +1420,21 @@ static void add_mysql_user(sqlite3 *handle, const char *user, const char *host, MXS_INFO("Added user: %s", insert_sql); } +static void add_database(sqlite3 *handle, const char *db) +{ + size_t len = sizeof(insert_database_query) + strlen(db) + 1; + char insert_sql[len + 1]; + + sprintf(insert_sql, insert_database_query, db); + + char *err; + if (sqlite3_exec(handle, insert_sql, NULL, NULL, &err) != SQLITE_OK) + { + MXS_ERROR("Failed to insert database: %s", err); + sqlite3_free(err); + } +} + /** * Load the user/passwd form mysql.user table into the service users' hashtable * environment. @@ -1848,12 +1884,12 @@ get_users(SERV_LISTENER *listener, USERS *users) /** Testing new users query */ char *query = get_new_users_query(server->server->server_string, service->enable_root); + MYSQL_AUTH *instance = (MYSQL_AUTH*)listener->auth_instance; 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))) @@ -1876,6 +1912,24 @@ get_users(SERV_LISTENER *listener, USERS *users) MXS_FREE(query); } + /** Load the list of databases */ + if (mysql_query(con, "SHOW DATABASES") == 0) + { + if ((result = mysql_store_result(con))) + { + while ((row = mysql_fetch_row(result))) + { + add_database(instance->handle, row[0]); + } + + mysql_free_result(result); + } + } + else + { + MXS_ERROR("Failed to load list of databases: %s", mysql_error(con)); + } + mysql_close(con); return total_users; diff --git a/server/modules/authenticator/MySQLAuth/mysql_auth.c b/server/modules/authenticator/MySQLAuth/mysql_auth.c index aadcf6330..9416ada49 100644 --- a/server/modules/authenticator/MySQLAuth/mysql_auth.c +++ b/server/modules/authenticator/MySQLAuth/mysql_auth.c @@ -128,7 +128,8 @@ static void* mysql_auth_init(char **options) char *err; - if (sqlite3_exec(instance->handle, create_sql, NULL, NULL, &err) != SQLITE_OK) + if (sqlite3_exec(instance->handle, users_create_sql, NULL, NULL, &err) != SQLITE_OK || + sqlite3_exec(instance->handle, databases_create_sql, NULL, NULL, &err) != SQLITE_OK) { MXS_ERROR("Failed to create database: %s", err); sqlite3_free(err); @@ -264,9 +265,6 @@ mysql_auth_authenticate(DCB *dcb) MXS_DEBUG("Receiving connection from '%s' to database '%s'.", client_data->user, client_data->db); - auth_ret = combined_auth_check(dcb, client_data->auth_token, client_data->auth_token_len, - protocol, client_data->user, client_data->client_sha1, client_data->db); - MYSQL_AUTH *instance = (MYSQL_AUTH*)dcb->listener->auth_instance; bool is_ok = validate_mysql_user(instance->handle, dcb, client_data); diff --git a/server/modules/authenticator/MySQLAuth/mysql_auth.h b/server/modules/authenticator/MySQLAuth/mysql_auth.h index d9a037a5d..96348e4c0 100644 --- a/server/modules/authenticator/MySQLAuth/mysql_auth.h +++ b/server/modules/authenticator/MySQLAuth/mysql_auth.h @@ -44,25 +44,43 @@ static const char DBUSERS_FILE[] = "dbusers"; #define MYSQLAUTH_DATABASE_NAME "file:mysqlauth.db?mode=memory&cache=shared" /** The table name where we store the users */ -#define MYSQLAUTH_TABLE_NAME "mysqlauth_users" +#define MYSQLAUTH_USERS_TABLE_NAME "mysqlauth_users" -/** CREATE TABLE statement for the in-memory table */ -static const char create_sql[] = - "CREATE TABLE IF NOT EXISTS " MYSQLAUTH_TABLE_NAME +/** The table name where we store the users */ +#define MYSQLAUTH_DATABASES_TABLE_NAME "mysqlauth_databases" + +/** CREATE TABLE statement for the in-memory users table */ +static const char users_create_sql[] = + "CREATE TABLE IF NOT EXISTS " MYSQLAUTH_USERS_TABLE_NAME "(user varchar(255), host varchar(255), db varchar(255), anydb boolean, password text)"; -/** The query that is executed when a user is authenticated */ -static const char mysqlauth_validation_query[] = - "SELECT password FROM " MYSQLAUTH_TABLE_NAME +/** CREATE TABLE statement for the in-memory databases table */ +static const char databases_create_sql[] = + "CREATE TABLE IF NOT EXISTS " MYSQLAUTH_DATABASES_TABLE_NAME "(db varchar(255))"; + +/** Query that checks if there's a grant for the user being authenticated */ +static const char mysqlauth_validate_user_query[] = + "SELECT password FROM " MYSQLAUTH_USERS_TABLE_NAME " WHERE user = '%s' AND '%s' LIKE host AND (anydb = '1' OR '%s' = '' OR '%s' LIKE db)" " LIMIT 1"; +/** Query that checks that the database exists */ +static const char mysqlauth_validate_database_query[] = + "SELECT * FROM " MYSQLAUTH_DATABASES_TABLE_NAME " WHERE db = '%s' LIMIT 1"; + /** 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_users_query[] = "DELETE FROM " MYSQLAUTH_USERS_TABLE_NAME; + +/** Delete query used to clean up the database before loading new users */ +static const char delete_databases_query[] = "DELETE FROM " MYSQLAUTH_DATABASES_TABLE_NAME; /** The insert query template which adds users to the mysqlauth_users table */ -static const char insert_sql_pattern[] = - "INSERT INTO " MYSQLAUTH_TABLE_NAME " VALUES ('%s', '%s', %s, %s, %s)"; +static const char insert_user_query[] = + "INSERT INTO " MYSQLAUTH_USERS_TABLE_NAME " VALUES ('%s', '%s', %s, %s, %s)"; + +/** The insert query template which adds the databases to the table */ +static const char insert_database_query[] = + "INSERT INTO " MYSQLAUTH_DATABASES_TABLE_NAME " VALUES ('%s')"; /** Used for NULL value creation in the INSERT query */ static const char null_token[] = "NULL"; From 62763e2505c70885826d175f1b33c8942edd434a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Sat, 28 Jan 2017 22:04:26 +0200 Subject: [PATCH 06/38] Store SQLite tables on disk The SQLite database users are cached on disk. This allows the binlogrouter to authenticate users without a connection to the master server. --- .../modules/authenticator/MySQLAuth/dbusers.c | 126 ++++++++++++++---- .../authenticator/MySQLAuth/mysql_auth.c | 9 +- .../authenticator/MySQLAuth/mysql_auth.h | 19 ++- 3 files changed, 120 insertions(+), 34 deletions(-) diff --git a/server/modules/authenticator/MySQLAuth/dbusers.c b/server/modules/authenticator/MySQLAuth/dbusers.c index 5c3149af6..4bd3cb4a4 100644 --- a/server/modules/authenticator/MySQLAuth/dbusers.c +++ b/server/modules/authenticator/MySQLAuth/dbusers.c @@ -128,8 +128,7 @@ See earlier error messages for user '%s' for more information." 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" + ON (u.user = t.user AND u.host = t.host) %s" static int add_databases(SERV_LISTENER *listener, MYSQL *con); static int add_wildcard_users(USERS *users, char* name, char* host, @@ -228,7 +227,7 @@ static char* get_usercount_query(const char* server_version, bool include_root, 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')" : ""; + const char *with_root = include_root ? "WHERE u.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); @@ -1374,8 +1373,8 @@ static void delete_mysql_users(sqlite3 *handle) * @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) +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]; @@ -2688,30 +2687,106 @@ dbusers_valueread(int fd) return (void *) value; } -/** - * Save the dbusers data to a hashtable file - * - * @param users The hashtable that stores the user data - * @param filename The filename to save the data in - * @return The number of entries saved - */ -int -dbusers_save(USERS *users, const char *filename) +int dump_user_cb(void *data, int fields, char **row, char **field_names) { - return hashtable_save(users->data, filename, dbusers_keywrite, dbusers_valuewrite); + sqlite3 *handle = (sqlite3*)data; + add_mysql_user(handle, row[0], row[1], row[2], row[3] && strcmp(row[3], "1"), row[4]); + return 0; +} + +int dump_database_cb(void *data, int fields, char **row, char **field_names) +{ + sqlite3 *handle = (sqlite3*)data; + add_database(handle, row[0]); + return 0; +} + +static bool transfer_table_contents(sqlite3 *src, sqlite3 *dest) +{ + bool rval = true; + char *err; + + /** Make sure the tables exist in both databases */ + if (sqlite3_exec(src, users_create_sql, NULL, NULL, &err) != SQLITE_OK || + sqlite3_exec(dest, users_create_sql, NULL, NULL, &err) != SQLITE_OK || + sqlite3_exec(src, databases_create_sql, NULL, NULL, &err) != SQLITE_OK || + sqlite3_exec(dest, databases_create_sql, NULL, NULL, &err) != SQLITE_OK) + { + MXS_ERROR("Failed to create tables: %s", err); + sqlite3_free(err); + rval = false; + } + + if (sqlite3_exec(dest, "BEGIN", NULL, NULL, &err) != SQLITE_OK) + { + MXS_ERROR("Failed to start transaction: %s", err); + sqlite3_free(err); + rval = false; + } + + /** Replace the data */ + if (sqlite3_exec(src, dump_users_query, dump_user_cb, dest, &err) != SQLITE_OK || + sqlite3_exec(src, dump_databases_query, dump_database_cb, dest, &err) != SQLITE_OK) + { + MXS_ERROR("Failed to load database contents: %s", err); + sqlite3_free(err); + rval = false; + } + + if (sqlite3_exec(dest, "COMMIT", NULL, NULL, &err) != SQLITE_OK) + { + MXS_ERROR("Failed to commit transaction: %s", err); + sqlite3_free(err); + rval = false; + } + + return rval; } /** - * Load the dbusers data from a saved hashtable file + * Load users from persisted database * - * @param users The hashtable that stores the user data - * @param filename The filename to laod the data from - * @return The number of entries loaded + * @param dest Open SQLite handle where contents are loaded + * + * @return True on success */ -int -dbusers_load(USERS *users, const char *filename) +bool dbusers_load(sqlite3 *dest, const char *filename) { - return hashtable_load(users->data, filename, dbusers_keyread, dbusers_valueread); + sqlite3 *src; + + if (sqlite3_open_v2(filename, &src, db_flags, NULL) != SQLITE_OK) + { + MXS_ERROR("Failed to open persisted SQLite3 database."); + return false; + } + + bool rval = transfer_table_contents(src, dest); + sqlite3_close_v2(src); + + return rval; +} + +/** + * Save users to persisted database + * + * @param dest Open SQLite handle where contents are stored + * + * @return True on success + */ +bool dbusers_save(sqlite3 *src, const char *filename) +{ + sqlite3 *dest; + + if (sqlite3_open_v2(filename, &dest, db_flags, NULL) != SQLITE_OK) + { + MXS_ERROR("Failed to open persisted SQLite3 database."); + return -1; + } + + bool rval = transfer_table_contents(src, dest); + sqlite3_close_v2(dest); + + return rval; } /** @@ -2861,11 +2936,12 @@ static bool check_server_permissions(SERVICE *service, SERVER* server, server_set_version_string(server, server_string); } - char query[MAX_QUERY_STR_LEN]; + const char *template = "SELECT user, host, %s, Select_priv FROM mysql.user limit 1"; const char* query_pw = strstr(server->server_string, "5.7.") ? - MYSQL57_PASSWORD : MYSQL_PASSWORD; + MYSQL57_PASSWORD : MYSQL_PASSWORD; + char query[strlen(template) + strlen(query_pw) + 1]; bool rval = true; - snprintf(query, sizeof(query), "SELECT user, host, %s, Select_priv FROM mysql.user limit 1", query_pw); + sprintf(query, template, query_pw); if (mysql_query(mysql, query) != 0) { diff --git a/server/modules/authenticator/MySQLAuth/mysql_auth.c b/server/modules/authenticator/MySQLAuth/mysql_auth.c index 9416ada49..c186d1ba3 100644 --- a/server/modules/authenticator/MySQLAuth/mysql_auth.c +++ b/server/modules/authenticator/MySQLAuth/mysql_auth.c @@ -878,8 +878,9 @@ static bool add_service_user(SERV_LISTENER *port) if (newpw) { - add_mysql_users_with_host_ipv4(port->users, user, "%", newpw, "Y", ""); - add_mysql_users_with_host_ipv4(port->users, user, "localhost", newpw, "Y", ""); + MYSQL_AUTH *inst = (MYSQL_AUTH*)port->auth_instance; + add_mysql_user(inst->handle, user, "%", newpw, "Y", ""); + add_mysql_user(inst->handle, user, "localhost", newpw, "Y", ""); MXS_FREE(newpw); rval = true; } @@ -937,7 +938,7 @@ static int mysql_auth_load_users(SERV_LISTENER *port) strcat(path, DBUSERS_FILE); - if ((loaded = dbusers_load(port->users, path)) == -1) + if (!dbusers_load(instance->handle, path)) { MXS_ERROR("[%s] Failed to load cached users from '%s'.", service->name, path); rc = MXS_AUTH_LOADUSERS_ERROR; @@ -963,7 +964,7 @@ static int mysql_auth_load_users(SERV_LISTENER *port) if (mxs_mkdir_all(path, 0777)) { strcat(path, DBUSERS_FILE); - dbusers_save(port->users, path); + dbusers_save(instance->handle, path); MXS_INFO("[%s] Storing cached credential information at '%s'.", service->name, path); } } diff --git a/server/modules/authenticator/MySQLAuth/mysql_auth.h b/server/modules/authenticator/MySQLAuth/mysql_auth.h index 96348e4c0..1bcf70aa9 100644 --- a/server/modules/authenticator/MySQLAuth/mysql_auth.h +++ b/server/modules/authenticator/MySQLAuth/mysql_auth.h @@ -39,7 +39,7 @@ MXS_BEGIN_DECLS /** Cache directory and file names */ static const char DBUSERS_DIR[] = "cache"; -static const char DBUSERS_FILE[] = "dbusers"; +static const char DBUSERS_FILE[] = "dbusers.db"; #define MYSQLAUTH_DATABASE_NAME "file:mysqlauth.db?mode=memory&cache=shared" @@ -76,11 +76,17 @@ static const char delete_databases_query[] = "DELETE FROM " MYSQLAUTH_DATABASES_ /** The insert query template which adds users to the mysqlauth_users table */ static const char insert_user_query[] = - "INSERT INTO " MYSQLAUTH_USERS_TABLE_NAME " VALUES ('%s', '%s', %s, %s, %s)"; + "INSERT OR REPLACE INTO " MYSQLAUTH_USERS_TABLE_NAME " VALUES ('%s', '%s', %s, %s, %s)"; /** The insert query template which adds the databases to the table */ static const char insert_database_query[] = - "INSERT INTO " MYSQLAUTH_DATABASES_TABLE_NAME " VALUES ('%s')"; + "INSERT OR REPLACE INTO " MYSQLAUTH_DATABASES_TABLE_NAME " VALUES ('%s')"; + +static const char dump_users_query[] = + "SELECT user, host, db, anydb, password FROM " MYSQLAUTH_USERS_TABLE_NAME; + +static const char dump_databases_query[] = + "SELECT db FROM " MYSQLAUTH_DATABASES_TABLE_NAME; /** Used for NULL value creation in the INSERT query */ static const char null_token[] = "NULL"; @@ -117,11 +123,14 @@ typedef struct mysql_user_host_key char hostname[MYSQL_HOST_MAXLEN + 1]; } MYSQL_USER_HOST; +void add_mysql_user(sqlite3 *handle, const char *user, const char *host, + const char *db, bool anydb, const char *pw); + extern int add_mysql_users_with_host_ipv4(USERS *users, const char *user, const char *host, char *passwd, const char *anydb, const char *db); extern bool check_service_permissions(SERVICE* service); -extern int dbusers_load(USERS *, const char *filename); -extern int dbusers_save(USERS *, const char *filename); +extern bool dbusers_load(sqlite3 *handle, const char *filename); +extern bool dbusers_save(sqlite3 *src, const char *filename); extern int mysql_users_add(USERS *users, MYSQL_USER_HOST *key, char *auth); extern USERS *mysql_users_alloc(); extern char *mysql_users_fetch(USERS *users, MYSQL_USER_HOST *key); From 48d70fa4a86a52e9ecdf2a0ed6f871c37e09e7bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Sat, 28 Jan 2017 23:27:06 +0200 Subject: [PATCH 07/38] Replace get_users implementation with new version The get_users function now combines the functionality of the old get_users and get_all_users. This removes large parts of similar code. Removed the listener resources as MySQLAuth was the only one that used it. --- include/maxscale/listener.h | 1 - server/core/listener.c | 5 - .../modules/authenticator/MySQLAuth/dbusers.c | 1559 ++--------------- .../authenticator/MySQLAuth/mysql_auth.c | 96 - 4 files changed, 192 insertions(+), 1469 deletions(-) diff --git a/include/maxscale/listener.h b/include/maxscale/listener.h index a41c9c282..9c2c67c71 100644 --- a/include/maxscale/listener.h +++ b/include/maxscale/listener.h @@ -44,7 +44,6 @@ typedef struct servlistener SSL_LISTENER *ssl; /**< Structure of SSL data or NULL */ struct dcb *listener; /**< The DCB for the listener */ struct users *users; /**< The user data for this listener */ - HASHTABLE *resources; /**< hastable for listener resources, i.e. database names */ struct service* service; /**< The service which used by this listener */ SPINLOCK lock; struct servlistener *next; /**< Next service protocol */ diff --git a/server/core/listener.c b/server/core/listener.c index f67fff302..6fdf71eff 100644 --- a/server/core/listener.c +++ b/server/core/listener.c @@ -129,7 +129,6 @@ listener_alloc(struct service* service, const char* name, const char *protocol, proto->auth_options = my_auth_options; proto->ssl = ssl; proto->users = NULL; - proto->resources = NULL; proto->next = NULL; proto->auth_instance = auth_instance; spinlock_init(&proto->lock); @@ -146,10 +145,6 @@ void listener_free(SERV_LISTENER* listener) { if (listener) { - if (listener->resources) - { - hashtable_free(listener->resources); - } if (listener->users) { users_free(listener->users); diff --git a/server/modules/authenticator/MySQLAuth/dbusers.c b/server/modules/authenticator/MySQLAuth/dbusers.c index 4bd3cb4a4..189e1a646 100644 --- a/server/modules/authenticator/MySQLAuth/dbusers.c +++ b/server/modules/authenticator/MySQLAuth/dbusers.c @@ -12,27 +12,7 @@ */ /** - * @file dbusers.c - Loading MySQL users from a MySQL backend server, this needs - * libmysqlclient.so and header files - * - * @verbatim - * Revision History - * - * Date Who Description - * 24/06/2013 Massimiliano Pinto Initial implementation - * 08/08/2013 Massimiliano Pinto Fixed bug for invalid memory access in row[1]+1 when row[1] is "" - * 06/02/2014 Massimiliano Pinto Mysql user root selected based on configuration flag - * 26/02/2014 Massimiliano Pinto Addd: replace_mysql_users() routine may replace users' table - * based on a checksum - * 28/02/2014 Massimiliano Pinto Added Mysql user@host authentication - * 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 - * 04/12/14 Massimiliano Pinto Added support for IPv$ wildcard hosts: a.%, a.%.% and a.b.% - * 25/05/16 Massimiliano Pinto Removed log message for duplicate entry while adding an user - * - * @endverbatim + * Loading MySQL users from a MySQL backend server */ #include "mysql_auth.h" @@ -55,73 +35,12 @@ /** Don't include the root user */ #define USERS_QUERY_NO_ROOT " AND user.user NOT IN ('root')" -/** User count without databases */ -#define MYSQL_USERS_COUNT "SELECT COUNT(1) AS nusers FROM mysql.user" - /** Normal password column name */ #define MYSQL_PASSWORD "password" /** MySQL 5.7 password column name */ #define MYSQL57_PASSWORD "authentication_string" -/** - * Query template which resolves user grants and access to databases at the table level - * - * The first two parameters for this template should be the 'password' column name. - * The third parameter is either an empty string the the contents of USERS_QUERY_NO_ROOT - * if the root user is not included. These three parameters are then repeated in the same - * order for the remaining parameters. For an example on how it is used see get_users_db_query() - */ -#define MYSQL_USERS_DB_QUERY_TEMPLATE \ - "SELECT DISTINCT \ - user.user AS user, \ - user.host AS host, \ - user.%s AS password, \ - concat(user.user,user.host,user.%s, \ - user.Select_priv, COALESCE(db.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 <> '' %s \ - UNION \ - SELECT DISTINCT \ - user.user AS user, \ - user.host AS host, \ - user.%s AS password, \ - concat(user.user,user.host,user.%s, \ - user.Select_priv, COALESCE(tp.db, '')) AS userdata, \ - user.Select_priv AS anydb, \ - tp.db as db FROM \ - mysql.tables_priv AS tp LEFT JOIN \ - mysql.user ON user.user=tp.user AND user.host=tp.host \ - WHERE user.user IS NOT NULL AND user.user <> '' %s" - -#define MYSQL_USERS_QUERY_TEMPLATE "SELECT \ - user, host, %s, concat(user, host, %s, Select_priv) AS userdata, \ - Select_priv AS anydb FROM mysql.user WHERE user.user IS NOT NULL AND user.user <> ''" - -/** User count query split into two parts. This way the actual query used to - * fetch the users can be inserted as a subquery between the START and END - * portions of them. */ -#define MYSQL_USERS_COUNT_TEMPLATE_START "SELECT COUNT(1) AS nusers_db FROM (" -#define MYSQL_USERS_COUNT_TEMPLATE_END ") AS tbl_count" - -/** The maximum possible length of the query */ -#define MAX_QUERY_STR_LEN strlen(MYSQL_USERS_COUNT_TEMPLATE_START MYSQL_USERS_COUNT_TEMPLATE_END \ - MYSQL_USERS_DB_QUERY_TEMPLATE) + strlen(USERS_QUERY_NO_ROOT) * 2 + strlen(MYSQL57_PASSWORD) * 4 + 1 - -#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)" - -#define ERROR_NO_SHOW_DATABASES "%s: Unable to load database grant information, \ -MaxScale authentication will proceed without including database permissions. \ -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 \ @@ -159,71 +78,6 @@ static void uh_keyfree(MYSQL_USER_HOST* key); static int wildcard_db_grant(char* str); static void merge_netmask(char *host); -/** - * Get the user data query with databases - * - * @param server_version Server version string - * @param include_root Include root user - * @param buffer Destination where the query is written. Must be at least - * MAX_QUERY_STR_LEN bytes long - * @return Users query with databases included - */ -static char* get_users_db_query(const char* server_version, bool include_root, char* buffer) -{ - const char* password = strstr(server_version, "5.7.") ? - MYSQL57_PASSWORD : MYSQL_PASSWORD; - - int nchars = snprintf(buffer, MAX_QUERY_STR_LEN, MYSQL_USERS_DB_QUERY_TEMPLATE, - password, password, include_root ? "" : USERS_QUERY_NO_ROOT, - password, password, include_root ? "" : USERS_QUERY_NO_ROOT); - ss_dassert(nchars < MAX_QUERY_STR_LEN); - (void) nchars; - return buffer; -} - -/** - * Get the user data query - * - * @param server_version Server version string - * @param include_root Include root user - * @param buffer Destination where the query is written. Must be at least - * MAX_QUERY_STR_LEN bytes long - * @return Users query - */ -static char* get_users_query(const char* server_version, bool include_root, char* buffer) -{ - const char* password = strstr(server_version, "5.7.") ? - MYSQL57_PASSWORD : MYSQL_PASSWORD; - - int nchars = snprintf(buffer, MAX_QUERY_STR_LEN, MYSQL_USERS_QUERY_TEMPLATE "%s", - password, password, include_root ? "" : USERS_QUERY_NO_ROOT); - ss_dassert(nchars < MAX_QUERY_STR_LEN); - (void) nchars; - return buffer; -} - -/** - * Get the user count query - * - * @param server_version Server version string - * @param buffer Destination where the query is written. Must be at least - * MAX_QUERY_STR_LEN bytes long - * @return User count query - * */ -static char* get_usercount_query(const char* server_version, bool include_root, char* buffer) -{ - const char* password = strstr(server_version, "5.7.") ? - MYSQL57_PASSWORD : MYSQL_PASSWORD; - - int nchars = snprintf(buffer, MAX_QUERY_STR_LEN, MYSQL_USERS_COUNT_TEMPLATE_START - MYSQL_USERS_DB_QUERY_TEMPLATE MYSQL_USERS_COUNT_TEMPLATE_END, - password, password, include_root ? "" : USERS_QUERY_NO_ROOT, - password, password, include_root ? "" : USERS_QUERY_NO_ROOT); - ss_dassert(nchars < MAX_QUERY_STR_LEN); - (void) nchars; - 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; @@ -281,9 +135,6 @@ replace_mysql_users(SERV_LISTENER *listener) spinlock_acquire(&listener->lock); - /** TODO: Make the listener resource a part of the USERS struct */ - HASHTABLE *oldresources = listener->resources; - /* load users and grants from the backend database */ int i = get_users(listener, newusers); @@ -294,7 +145,6 @@ replace_mysql_users(SERV_LISTENER *listener) { /* Restore old users and resources */ users_free(newusers); - listener->resources = oldresources; } else { @@ -314,9 +164,6 @@ replace_mysql_users(SERV_LISTENER *listener) spinlock_release(&listener->lock); - /* free old resources */ - resource_free(oldresources); - if (oldusers) { /* free the old table */ @@ -498,708 +345,8 @@ int add_mysql_users_with_host_ipv4(USERS *users, const char *user, const char *h return ret; } -/** - * Add 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 -add_databases(SERV_LISTENER *listener, MYSQL *con) -{ - SERVICE *service = listener->service; - 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)) - { - MXS_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) - { - MXS_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; - - MXS_ERROR("Failed to retrieve database names: %s", mysql_error(con)); - MXS_ERROR(ERROR_NO_SHOW_DATABASES, 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")) - { - MXS_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) - { - MXS_ERROR("Loading database names for service %s encountered " - "error: %s.", - service->name, - mysql_error(con)); - - return -1; - } - - /* insert key and value "" */ - while ((row = mysql_fetch_row(result))) - { - if (resource_add(listener->resources, row[0], "")) - { - MXS_DEBUG("%s: Adding database %s to the resouce hash.", service->name, row[0]); - } - } - - mysql_free_result(result); - - return ndbs; -} - -/** - * 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 -get_databases(SERV_LISTENER *listener, MYSQL *con) -{ - SERVICE *service = listener->service; - 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)) - { - MXS_ERROR("Loading database names for service %s encountered " - "error when querying database privileges: %s.", - service->name, - mysql_error(con)); - return -1; - } - - result = mysql_store_result(con); - - if (result == NULL) - { - MXS_ERROR("Loading database names for service %s encountered " - "error when storing result set of database privilege query: %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; - - MXS_ERROR("Failed to retrieve database names: %s", mysql_error(con)); - MXS_ERROR(ERROR_NO_SHOW_DATABASES, 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")) - { - MXS_ERROR("Loading database names for service %s encountered " - "error when executing SHOW DATABASES query: %s.", - service->name, - mysql_error(con)); - - return -1; - } - - result = mysql_store_result(con); - - if (result == NULL) - { - MXS_ERROR("Loading database names for service %s encountered " - "error when storing the result set: %s.", - service->name, - mysql_error(con)); - - return -1; - } - - /* Now populate service->resources hashatable with db names */ - listener->resources = resource_alloc(); - - /* insert key and value "" */ - while ((row = mysql_fetch_row(result))) - { - MXS_DEBUG("%s: Adding database %s to the resouce hash.", service->name, row[0]); - resource_add(listener->resources, row[0], ""); - } - - mysql_free_result(result); - - return ndbs; -} - -/** - * Load the user/passwd from mysql.user table into the service users' hashtable - * environment from all the backend servers. - * - * @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 - */ -static int -get_all_users(SERV_LISTENER *listener, USERS *users) -{ - SERVICE *service = listener->service; - MYSQL *con = NULL; - MYSQL_ROW row; - MYSQL_RES *result = NULL; - char *service_user = NULL; - char *service_passwd = NULL; - char *dpwd = NULL; - int total_users = 0; - SERVER_REF *server; - const char *userquery; - char *tmp; - unsigned char hash[SHA_DIGEST_LENGTH] = ""; - char *users_data = NULL; - char *final_data = NULL; - char dbnm[MYSQL_DATABASE_MAXLEN + 1]; - int nusers = -1; - 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; - bool anon_user = false; - - if (serviceGetUser(service, &service_user, &service_passwd) == 0) - { - ss_dassert(service_passwd == NULL || service_user == NULL); - return -1; - } - - if (service->svc_do_shutdown) - { - return -1; - } - - dpwd = decrypt_password(service_passwd); - final_data = (char*) MXS_MALLOC(sizeof(char)); - MXS_ABORT_IF_NULL(final_data); - *final_data = '\0'; - - /** - * Attempt to connect to one of the databases database or until we run - * out of databases - * to try - */ - server = service->dbref; - - if (server == NULL) - { - goto cleanup; - } - - listener->resources = resource_alloc(); - - while (server != NULL) - { - while (!service->svc_do_shutdown && server != NULL) - { - con = gw_mysql_init(); - if (con) - { - if (mxs_mysql_real_connect(con, server->server, service_user, dpwd) == NULL) - { - MXS_ERROR("Failure loading users data from backend " - "[%s:%i] for service [%s]. MySQL error %i, %s", - server->server->name, server->server->port, - service->name, mysql_errno(con), mysql_error(con)); - mysql_close(con); - } - else - { - /** Successfully connected to a server */ - break; - } - } - else - { - server = NULL; - break; - } - - server = server->next; - } - - if (server == NULL) - { - MXS_ERROR("Unable to get user data from backend database " - "for service [%s]. Missing server information.", - service->name); - goto cleanup; - } - - add_databases(listener, con); - mysql_close(con); - server = server->next; - } - - server = service->dbref; - - while (server != NULL) - { - while (!service->svc_do_shutdown && server != NULL) - { - con = gw_mysql_init(); - if (con) - { - if (mxs_mysql_real_connect(con, server->server, service_user, dpwd) == NULL) - { - MXS_ERROR("Failure loading users data from backend " - "[%s:%i] for service [%s]. MySQL error %i, %s", - server->server->name, server->server->port, - service->name, mysql_errno(con), mysql_error(con)); - mysql_close(con); - } - else - { - /** Successfully connected to a server */ - break; - } - } - else - { - server = NULL; - break; - } - - server = server->next; - } - - if (server == NULL) - { - MXS_ERROR("Unable to get user data from backend database " - "for service [%s]. Missing server information.", - service->name); - goto cleanup; - } - - if (server->server->server_string == NULL) - { - const char *server_string = mysql_get_server_info(con); - if (!server_set_version_string(server->server, server_string)) - { - mysql_close(con); - goto cleanup; - } - } - - char querybuffer[MAX_QUERY_STR_LEN]; - /** Count users. Start with users and db grants for users */ - const char *usercount = get_usercount_query(server->server->server_string, - service->enable_root, querybuffer); - if (mysql_query(con, usercount)) - { - if (mysql_errno(con) != ER_TABLEACCESS_DENIED_ERROR) - { - /* This is an error we cannot handle, return */ - MXS_ERROR("Loading users for service [%s] encountered error: [%s].", - service->name, - mysql_error(con)); - mysql_close(con); - goto cleanup; - } - else - { - /* - * We have got ER_TABLEACCESS_DENIED_ERROR - * try counting users from mysql.user without DB names. - */ - if (mysql_query(con, MYSQL_USERS_COUNT)) - { - MXS_ERROR("Loading users for service [%s] encountered error: [%s].", - service->name, - mysql_error(con)); - mysql_close(con); - goto cleanup; - } - } - } - - result = mysql_store_result(con); - - if (result == NULL) - { - MXS_ERROR("Loading users for service [%s] encountered error: [%s].", - service->name, - mysql_error(con)); - mysql_close(con); - goto cleanup; - } - - row = mysql_fetch_row(result); - - nusers = atoi(row[0]); - - mysql_free_result(result); - - if (!nusers) - { - MXS_ERROR("Counting users for service %s returned 0.", service->name); - mysql_close(con); - goto cleanup; - } - - userquery = get_users_db_query(server->server->server_string, - service->enable_root, querybuffer); - - /* send first the query that fetches users and db grants */ - if (mysql_query(con, userquery)) - { - /* - * 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 */ - - MXS_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); - - goto cleanup; - } - else - { - /* - * We have got ER_TABLEACCESS_DENIED_ERROR - * try loading users from mysql.user without DB names. - */ - - MXS_ERROR("Failed to retrieve users: %s", mysql_error(con)); - MXS_ERROR(ERROR_NO_SHOW_DATABASES, service->name, service_user); - - userquery = get_users_query(server->server->server_string, - service->enable_root, querybuffer); - - if (mysql_query(con, userquery)) - { - MXS_ERROR("Loading users for service [%s] encountered " - "error: [%s], code %i", - service->name, - mysql_error(con), - mysql_errno(con)); - - mysql_close(con); - - goto cleanup; - } - - /* users successfully loaded but without db grants */ - - MXS_NOTICE("Loading users from [mysql.user] without access to [mysql.db] for " - "service [%s]. MaxScale Authentication with DBname on connect " - "will not consider database grants.", - service->name); - } - } - else - { - /* - * users successfully loaded with db grants. - */ - MXS_DEBUG("[%s] Loading users with db grants.", service->name); - db_grants = 1; - } - - result = mysql_store_result(con); - - if (result == NULL) - { - MXS_ERROR("Loading users for service %s encountered error: %s.", - service->name, - mysql_error(con)); - - mysql_free_result(result); - mysql_close(con); - - goto cleanup; - } - - users_data = (char *) MXS_CALLOC(nusers, (users_data_row_len * sizeof(char)) + 1); - - if (users_data == NULL) - { - mysql_free_result(result); - mysql_close(con); - - goto cleanup; - } - - while ((row = mysql_fetch_row(result))) - { - - /** - * 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 the username is empty, the backend server still has anonymous - * user in it. This will mean that localhost addresses do not match - * the wildcard host '%' */ - if (strlen(row[0]) == 0) - { - anon_user = true; - continue; - } - - if (row[2] != NULL) - { - /* detect mysql_old_password (pre 4.1 protocol) */ - if (strlen(row[2]) == 16) - { - MXS_ERROR("%s: The user %s@%s has on old password in the " - "backend database. MaxScale does not support these " - "old passwords. This user will not be able to connect " - "via MaxScale. Update the users password to correct " - "this.", - service->name, - row[0], - row[1]); - continue; - } - - if (strlen(row[2]) > 1) - { - password = row[2] + 1; - } - else - { - password = row[2]; - } - } - - /* - * add user@host and DB global priv and specificsa grant (if possible) - */ - bool havedb = false; - - if (db_grants) - { - /* we have dbgrants, store them */ - if (row[5]) - { - unsigned long *rowlen = mysql_fetch_lengths(result); - memcpy(dbnm, row[5], rowlen[5]); - memset(dbnm + rowlen[5], 0, 1); - havedb = true; - if (service->strip_db_esc) - { - strip_escape_chars(dbnm); - MXS_DEBUG("[%s]: %s -> %s", - service->name, - row[5], - dbnm); - } - } - - rc = add_mysql_users_with_host_ipv4(users, row[0], row[1], - password, row[4], - havedb ? dbnm : NULL); - - MXS_DEBUG("%s: Adding user:%s host:%s anydb:%s db:%s.", - service->name, row[0], row[1], row[4], - havedb ? dbnm : NULL); - } - 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) - { - if (db_grants) - { - char dbgrant[MYSQL_DATABASE_MAXLEN + 1] = ""; - if (row[4] != NULL) - { - if (strcmp(row[4], "Y") == 0) - { - strcpy(dbgrant, "ANY"); - } - else if (row[5]) - { - strncpy(dbgrant, row[5], MYSQL_DATABASE_MAXLEN); - dbgrant[MYSQL_DATABASE_MAXLEN] = 0; - } - } - - if (!strlen(dbgrant)) - { - strcpy(dbgrant, "no db"); - } - - /* Log the user being added with its db grants */ - MXS_INFO("%s: User %s@%s for database %s added to service user table.", - service->name, row[0], row[1], dbgrant); - } - else - { - /* Log the user being added (without db grants) */ - MXS_INFO("%s: User %s@%s added to service user table.", - service->name, row[0], row[1]); - } - - /* Append data in the memory area for SHA1 digest */ - strncat(users_data, row[3], users_data_row_len); - total_users++; - } - else - { - /** Log errors and not the duplicate user */ - if (service->log_auth_warnings && rc != -1) - { - MXS_WARNING("Failed to add user %s@%s for service [%s]." - " This user will be unavailable via MaxScale.", - row[0], row[1], service->name); - } - } - } - - mysql_free_result(result); - mysql_close(con); - - if ((tmp = MXS_REALLOC(final_data, (strlen(final_data) + strlen(users_data) - + 1) * sizeof(char))) == NULL) - { - MXS_FREE(users_data); - goto cleanup; - } - - final_data = tmp; - - strcat(final_data, users_data); - MXS_FREE(users_data); - - if (service->users_from_all) - { - server = server->next; - } - else - { - server = NULL; - } - } - - /* compute SHA1 digest for users' data */ - SHA1((const unsigned char *) final_data, strlen(final_data), hash); - - memcpy(users->cksum, hash, SHA_DIGEST_LENGTH); - - /** Set the parameter if it is not configured by the user */ - if (service->localhost_match_wildcard_host == SERVICE_PARAM_UNINIT) - { - service->localhost_match_wildcard_host = anon_user ? 0 : 1; - } -cleanup: - - MXS_FREE(dpwd); - MXS_FREE(final_data); - - return total_users; -} - -static bool check_password(const char *output, - uint8_t *token, size_t token_len, - uint8_t *scramble, size_t scramble_len) +static bool check_password(const char *output, uint8_t *token, size_t token_len, + uint8_t *scramble, size_t scramble_len, uint8_t *phase2_scramble) { uint8_t stored_token[SHA_DIGEST_LENGTH] = {}; size_t stored_token_len = sizeof(stored_token); @@ -1234,6 +381,10 @@ static bool check_password(const char *output, uint8_t step2[SHA_DIGEST_LENGTH]; gw_str_xor(step2, token, step1, token_len); + /** The phase 2 scramble needs to be copied to the shared data structure as it + * is required when the backend authentication is done. */ + memcpy(phase2_scramble, step2, SHA_DIGEST_LENGTH); + /** 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); @@ -1242,6 +393,7 @@ static bool check_password(const char *output, return memcmp(final_step, stored_token, stored_token_len) == 0; } +/** Callback for check_database() */ static int database_cb(void *data, int columns, char** rows, char** row_names) { bool *rval = (bool*)data; @@ -1251,18 +403,24 @@ static int database_cb(void *data, int columns, char** rows, char** row_names) static bool check_database(sqlite3 *handle, const char *database) { - size_t len = sizeof(mysqlauth_validate_database_query) + strlen(database) + 1; - char sql[len]; + bool rval = true; - sprintf(sql, mysqlauth_validate_database_query, database); - bool rval = false; - char *err; - - if (sqlite3_exec(handle, sql, database_cb, &rval, &err) != SQLITE_OK) + if (*database) { - MXS_ERROR("Failed to execute auth query: %s", err); - sqlite3_free(err); rval = false; + size_t len = sizeof(mysqlauth_validate_database_query) + strlen(database) + 1; + char sql[len]; + + sprintf(sql, mysqlauth_validate_database_query, database); + + char *err; + + if (sqlite3_exec(handle, sql, database_cb, &rval, &err) != SQLITE_OK) + { + MXS_ERROR("Failed to execute auth query: %s", err); + sqlite3_free(err); + rval = false; + } } return rval; @@ -1333,11 +491,18 @@ bool validate_mysql_user(sqlite3 *handle, DCB *dcb, MYSQL_session *session) if (res.ok) { + if (session->auth_token_len) + { + /** If authentication fails, this will trigger the right + * error message with `Using password : YES` */ + session->client_sha1[0] = '_'; + } + /** Found a matching row */ MySQLProtocol *proto = (MySQLProtocol*)dcb->protocol; if (check_password(res.output, session->auth_token, session->auth_token_len, - proto->scramble, sizeof(proto->scramble))) + proto->scramble, sizeof(proto->scramble), session->client_sha1)) { /** Password is OK, check that the database exists */ rval = check_database(handle, session->db); @@ -1434,506 +599,6 @@ static void add_database(sqlite3 *handle, const char *db) } } -/** - * Load the user/passwd form mysql.user table into the service users' 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 - */ -static int -get_users(SERV_LISTENER *listener, USERS *users) -{ - SERVICE *service = listener->service; - 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_REF *server; - const char *userquery; - 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 db_grants = 0; - char dbnm[MYSQL_DATABASE_MAXLEN + 1]; - bool anon_user = false; - - if (serviceGetUser(service, &service_user, &service_passwd) == 0) - { - ss_dassert(service_passwd == NULL || service_user == NULL); - return -1; - } - - if (service->users_from_all) - { - return get_all_users(listener, users); - } - - con = gw_mysql_init(); - - if (!con) - { - return -1; - } - - /** - * Attempt to connect to one of the databases database or until we run - * out of databases - * to try - */ - server = service->dbref; - dpwd = decrypt_password(service_passwd); - - /* Select a server with Master bit, if available */ - while (server != NULL && !(server->server->status & SERVER_MASTER)) - { - server = server->next; - } - - if (service->svc_do_shutdown) - { - MXS_FREE(dpwd); - mysql_close(con); - return -1; - } - - /* Try loading data from master server */ - if (server != NULL && - (mxs_mysql_real_connect(con, server->server, service_user, dpwd) != NULL)) - { - MXS_DEBUG("Loading data from backend database with " - "Master role [%s:%i] for service [%s]", - server->server->name, - server->server->port, - service->name); - } - else - { - mysql_close(con); - /* load data from other servers via loop */ - server = service->dbref; - - while (!service->svc_do_shutdown && server != NULL) - { - con = gw_mysql_init(); - if (con) - { - if (mxs_mysql_real_connect(con, server->server, service_user, dpwd) == NULL) - { - MXS_ERROR("Failure loading users data from backend " - "[%s:%i] for service [%s]. MySQL error %i, %s", - server->server->name, server->server->port, - service->name, mysql_errno(con), mysql_error(con)); - mysql_close(con); - } - else - { - /** Successfully connected to a server */ - break; - } - } - else - { - server = NULL; - break; - } - - server = server->next; - } - - if (service->svc_do_shutdown) - { - MXS_FREE(dpwd); - mysql_close(con); - return -1; - } - - if (server != NULL) - { - MXS_DEBUG("Loading data from backend database [%s:%i] for service [%s]", - server->server->name, server->server->port, service->name); - } - } - - MXS_FREE(dpwd); - - if (server == NULL) - { - MXS_ERROR("Unable to get user data from backend database for service [%s]." - " Failed to connect to any of the backend databases.", service->name); - return -1; - } - - if (server->server->server_string == NULL) - { - const char *server_string = mysql_get_server_info(con); - if (!server_set_version_string(server->server, server_string)) - { - mysql_close(con); - return -1; - } - } - - char querybuffer[MAX_QUERY_STR_LEN]; - const char *usercount = get_usercount_query(server->server->server_string, - service->enable_root, querybuffer); - /** Count users. Start with users and db grants for users */ - if (mysql_query(con, usercount)) - { - if (mysql_errno(con) != ER_TABLEACCESS_DENIED_ERROR) - { - /* This is an error we cannot handle, return */ - MXS_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)) - { - MXS_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) - { - MXS_ERROR("Loading users for service [%s] encountered error: [%s].", - service->name, mysql_error(con)); - mysql_close(con); - return -1; - } - - row = mysql_fetch_row(result); - - nusers = atoi(row[0]); - - mysql_free_result(result); - - if (!nusers) - { - MXS_ERROR("Counting users for service %s returned 0.", service->name); - mysql_close(con); - return -1; - } - - userquery = get_users_db_query(server->server->server_string, - service->enable_root, querybuffer); - /* send first the query that fetches users and db grants */ - if (mysql_query(con, userquery)) - { - /* - * 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 */ - - MXS_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. - */ - MXS_ERROR("Failed to retrieve users: %s", mysql_error(con)); - MXS_ERROR(ERROR_NO_SHOW_DATABASES, service->name, service_user); - - userquery = get_users_query(server->server->server_string, - service->enable_root, querybuffer); - - if (mysql_query(con, userquery)) - { - MXS_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 */ - - MXS_NOTICE("Loading users from [mysql.user] without access to [mysql.db] for " - "service [%s]. MaxScale Authentication with DBname on connect " - "will not consider database grants.", service->name); - } - } - else - { - /** Users successfully loaded with database grants */ - db_grants = 1; - } - - result = mysql_store_result(con); - - if (result == NULL) - { - MXS_ERROR("Loading users for service %s encountered error: %s.", - service->name, mysql_error(con)); - - mysql_free_result(result); - mysql_close(con); - return -1; - } - - users_data = (char *) MXS_CALLOC(nusers, (users_data_row_len * sizeof(char)) + 1); - - if (users_data == NULL) - { - mysql_free_result(result); - mysql_close(con); - return -1; - } - - if (db_grants) - { - /* load all mysql database names */ - ss_debug(int dbnames = ) get_databases(listener, con); - MXS_DEBUG("Loaded %d MySQL Database Names for service [%s]", - dbnames, service->name); - } - else - { - listener->resources = NULL; - } - - while ((row = mysql_fetch_row(result))) - { - - /** - * 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 the username is empty, the backend server still has anonymous - * user in it. This will mean that localhost addresses do not match - * the wildcard host '%' */ - if (strlen(row[0]) == 0) - { - anon_user = true; - continue; - } - - if (row[2] != NULL) - { - /* detect mysql_old_password (pre 4.1 protocol) */ - if (strlen(row[2]) == 16) - { - MXS_ERROR("%s: The user %s@%s has on old password in the " - "backend database. MaxScale does not support these " - "old passwords. This user will not be able to connect " - "via MaxScale. Update the users password to correct " - "this.", service->name, row[0], row[1]); - continue; - } - - if (strlen(row[2]) > 1) - { - password = row[2] + 1; - } - else - { - password = row[2]; - } - } - - /* - * add user@host and DB global priv and specificsa grant (if possible) - */ - if (db_grants) - { - bool havedb = false; - /* we have dbgrants, store them */ - if (row[5]) - { - unsigned long *rowlen = mysql_fetch_lengths(result); - memcpy(dbnm, row[5], rowlen[5]); - memset(dbnm + rowlen[5], 0, 1); - havedb = true; - if (service->strip_db_esc) - { - strip_escape_chars(dbnm); - MXS_DEBUG("[%s]: %s -> %s", service->name, row[5], dbnm); - } - } - - if (havedb && wildcard_db_grant(row[5])) - { - /** Use ANYDB for wildcard grants */ - rc = add_mysql_users_with_host_ipv4(users, row[0], row[1], - password, "Y", NULL); - } - else - { - rc = add_mysql_users_with_host_ipv4(users, row[0], row[1], - password, row[4], - havedb ? dbnm : NULL); - } - - } - 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) - { - if (db_grants) - { - char dbgrant[MYSQL_DATABASE_MAXLEN + 1] = ""; - if (row[4] != NULL) - { - if (strcmp(row[4], "Y") == 0) - { - strcpy(dbgrant, "ANY"); - } - else if (row[5]) - { - strncpy(dbgrant, row[5], MYSQL_DATABASE_MAXLEN); - dbgrant[MYSQL_DATABASE_MAXLEN] = 0; - } - } - - if (!strlen(dbgrant)) - { - strcpy(dbgrant, "no db"); - } - - /* Log the user being added with its db grants */ - MXS_INFO("%s: User %s@%s for database %s added to " - "service user table.", - service->name, - row[0], - row[1], - dbgrant); - } - else - { - /* Log the user being added (without db grants) */ - MXS_INFO("%s: User %s@%s added to service user table.", - service->name, - row[0], - row[1]); - } - - /* Append data in the memory area for SHA1 digest */ - strncat(users_data, row[3], users_data_row_len); - total_users++; - } - else - { - /** Log errors and not the duplicate user */ - if (service->log_auth_warnings && rc != -1) - { - MXS_WARNING("Failed to add user %s@%s for" - " service [%s]. This user will be unavailable" - " via MaxScale.", row[0], row[1], service->name); - } - } - } - - /* compute SHA1 digest for users' data */ - SHA1((const unsigned char *) users_data, strlen(users_data), hash); - - memcpy(users->cksum, hash, SHA_DIGEST_LENGTH); - - /** Set the parameter if it is not configured by the user */ - if (service->localhost_match_wildcard_host == SERVICE_PARAM_UNINIT) - { - service->localhost_match_wildcard_host = anon_user ? 0 : 1; - } - - MXS_FREE(users_data); - mysql_free_result(result); - - /** Testing new users query */ - char *query = get_new_users_query(server->server->server_string, service->enable_root); - MYSQL_AUTH *instance = (MYSQL_AUTH*)listener->auth_instance; - - if (query) - { - if (mysql_query(con, query) == 0) - { - 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); - } - - /** Load the list of databases */ - if (mysql_query(con, "SHOW DATABASES") == 0) - { - if ((result = mysql_store_result(con))) - { - while ((row = mysql_fetch_row(result))) - { - add_database(instance->handle, row[0]); - } - - mysql_free_result(result); - } - } - else - { - MXS_ERROR("Failed to load list of databases: %s", mysql_error(con)); - } - - mysql_close(con); - - return total_users; -} - /** * Allocate a new MySQL users table for mysql specific users@host as key * @@ -2938,7 +1603,7 @@ static bool check_server_permissions(SERVICE *service, SERVER* server, const char *template = "SELECT user, host, %s, Select_priv FROM mysql.user limit 1"; const char* query_pw = strstr(server->server_string, "5.7.") ? - MYSQL57_PASSWORD : MYSQL_PASSWORD; + MYSQL57_PASSWORD : MYSQL_PASSWORD; char query[strlen(template) + strlen(query_pw) + 1]; bool rval = true; sprintf(query, template, query_pw); @@ -3191,3 +1856,163 @@ static bool wildcard_domain_match(const char *ip_address, char *client_hostname) return false; } + +int get_users_from_server(MYSQL *con, SERVER_REF *server, SERVICE *service, SERV_LISTENER *listener) +{ + + if (server->server->server_string == NULL) + { + const char *server_string = mysql_get_server_info(con); + if (!server_set_version_string(server->server, server_string)) + { + return -1; + } + } + + /** Testing new users query */ + char *query = get_new_users_query(server->server->server_string, service->enable_root); + MYSQL_AUTH *instance = (MYSQL_AUTH*)listener->auth_instance; + bool anon_user = false; + int users = 0; + + if (query) + { + if (mysql_query(con, query) == 0) + { + MYSQL_RES *result = mysql_store_result(con); + + if (result) + { + MYSQL_ROW row; + while ((row = mysql_fetch_row(result))) + { + if (service->strip_db_esc) + { + strip_escape_chars(row[2]); + } + + add_mysql_user(instance->handle, row[0], row[1], row[2], + row[3] && strcmp(row[3], "Y") == 0, row[4]); + users++; + + if (row[0] && *row[0] == '\0') + { + /** Empty username is used for the anonymous user. This means + that localhost does not match wildcard host. */ + anon_user = true; + } + } + + mysql_free_result(result); + } + } + else + { + MXS_ERROR("Failed to load users: %s", mysql_error(con)); + } + + MXS_FREE(query); + } + + /** Set the parameter if it is not configured by the user */ + if (service->localhost_match_wildcard_host == SERVICE_PARAM_UNINIT) + { + service->localhost_match_wildcard_host = anon_user ? 0 : 1; + } + + /** Load the list of databases */ + if (mysql_query(con, "SHOW DATABASES") == 0) + { + MYSQL_RES *result = mysql_store_result(con); + if (result) + { + MYSQL_ROW row; + while ((row = mysql_fetch_row(result))) + { + add_database(instance->handle, row[0]); + } + + mysql_free_result(result); + } + } + else + { + MXS_ERROR("Failed to load list of databases: %s", mysql_error(con)); + } + + return users; +} + +/** + * Load the user/passwd form mysql.user table into the service users' 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 + */ +static int get_users(SERV_LISTENER *listener, USERS *users) +{ + MYSQL *con = gw_mysql_init(); + char *service_user = NULL; + char *service_passwd = NULL; + SERVICE *service = listener->service; + + if (con == NULL || serviceGetUser(service, &service_user, &service_passwd) == 0) + { + return -1; + } + + char *dpwd = decrypt_password(service_passwd); + + if (dpwd == NULL) + { + return -1; + } + + SERVER_REF *server = service->dbref; + int total_users = -1; + + for (server = service->dbref; !service->svc_do_shutdown && server; server = server->next) + { + con = gw_mysql_init(); + if (con) + { + if (mxs_mysql_real_connect(con, server->server, service_user, dpwd) == NULL) + { + MXS_ERROR("Failure loading users data from backend " + "[%s:%i] for service [%s]. MySQL error %i, %s", + server->server->name, server->server->port, + service->name, mysql_errno(con), mysql_error(con)); + mysql_close(con); + } + else + { + /** Successfully connected to a server */ + int users = get_users_from_server(con, server, service, listener); + + if (users > total_users) + { + total_users = users; + } + + mysql_close(con); + + if (!service->users_from_all) + { + break; + } + } + } + } + + MXS_FREE(dpwd); + + if (server == NULL) + { + MXS_ERROR("Unable to get user data from backend database for service [%s]." + " Failed to connect to any of the backend databases.", service->name); + } + + return total_users; +} diff --git a/server/modules/authenticator/MySQLAuth/mysql_auth.c b/server/modules/authenticator/MySQLAuth/mysql_auth.c index c186d1ba3..8a991f3fc 100644 --- a/server/modules/authenticator/MySQLAuth/mysql_auth.c +++ b/server/modules/authenticator/MySQLAuth/mysql_auth.c @@ -742,102 +742,6 @@ gw_check_mysql_scramble_data(DCB *dcb, return (0 == memcmp(password, check_hash, SHA_DIGEST_LENGTH)) ? MXS_AUTH_SUCCEEDED : MXS_AUTH_FAILED; } - -/** - * @brief If the client connection specifies a database, check existence - * - * The client can specify a default database, but if so, it must be one - * that exists. This function is chained from the previous one, and will - * amend the given return code if it is previously showing success. - * - * @param dcb Request handler DCB connected to the client - * @param database A string containing the database name - * @param auth_ret The authentication status prior to calling this function. - * @return Authentication status - * @note Authentication status codes are defined in maxscale/protocol/mysql.h - */ -int -check_db_name_after_auth(DCB *dcb, char *database, int auth_ret) -{ - int db_exists = -1; - - /* check for database 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->listener->resources != NULL) - { - if (hashtable_fetch(dcb->listener->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 == MXS_AUTH_SUCCEEDED) - { - auth_ret = MXS_AUTH_FAILED_DB; - } - - if (db_exists < 0 && auth_ret == MXS_AUTH_SUCCEEDED) - { - auth_ret = MXS_AUTH_FAILED; - } - } - - return auth_ret; -} - -/** - * @brief Function to easily call authentication and database checks. - * - * The two functions are called one after the other, with the return from - * the first passed to the second. For convenience and clarity this function - * combines the calls. - * - * @param dcb Request handler DCB connected to the client - * @param auth_token A string of bytes containing the authentication token - * @param auth_token_len An integer, the length of the preceding parameter - * @param protocol The protocol structure for the connection - * @param username String containing username - * @param stage1_hash A password hash for authentication - * @param database A string containing the name for the default database - * @return Authentication status - * @note Authentication status codes are defined in maxscale/protocol/mysql.h - */ -static int combined_auth_check( - DCB *dcb, - uint8_t *auth_token, - size_t auth_token_len, - MySQLProtocol *protocol, - char *username, - uint8_t *stage1_hash, - char *database -) -{ - int auth_ret; - - auth_ret = gw_check_mysql_scramble_data(dcb, - auth_token, - auth_token_len, - protocol->scramble, - sizeof(protocol->scramble), - username, - stage1_hash); - - /* check for database name match in resource hashtable */ - auth_ret = check_db_name_after_auth(dcb, database, auth_ret); - return auth_ret; -} - /** * @brief Free the client data pointed to by the passed DCB. * From b376d9043a144cbc797f8b6953be250b91544fe3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Mon, 30 Jan 2017 12:23:06 +0200 Subject: [PATCH 08/38] Use new authentication for reauthentication This fixes the reauthentication of users that was missing from the new implementation. Now COM_CHANGE_USER should work properly. --- include/maxscale/authenticator.h | 5 +-- .../modules/authenticator/MySQLAuth/dbusers.c | 18 +++++------ .../authenticator/MySQLAuth/mysql_auth.c | 31 +++++++++++++++---- .../authenticator/MySQLAuth/mysql_auth.h | 3 +- .../MySQL/MySQLBackend/mysql_backend.c | 6 ++-- 5 files changed, 43 insertions(+), 20 deletions(-) diff --git a/include/maxscale/authenticator.h b/include/maxscale/authenticator.h index 0d8019ffd..d941adc35 100644 --- a/include/maxscale/authenticator.h +++ b/include/maxscale/authenticator.h @@ -80,8 +80,9 @@ typedef struct mxs_authenticator /** This entry point was added to avoid calling authenticator functions * directly when a COM_CHANGE_USER command is executed. */ int (*reauthenticate)(struct dcb *, const char *user, - uint8_t *token, size_t token_len, - uint8_t *scramble, size_t scramble_len); + uint8_t *token, size_t token_len, /**< Client auth token */ + uint8_t *scramble, size_t scramble_len, /**< Scramble sent by MaxScale to client */ + uint8_t *output, size_t output_len); /**< Hashed client password used by backend protocols */ } MXS_AUTHENTICATOR; /** Return values for extract and authenticate entry points */ diff --git a/server/modules/authenticator/MySQLAuth/dbusers.c b/server/modules/authenticator/MySQLAuth/dbusers.c index 189e1a646..42e228730 100644 --- a/server/modules/authenticator/MySQLAuth/dbusers.c +++ b/server/modules/authenticator/MySQLAuth/dbusers.c @@ -445,14 +445,16 @@ static int auth_cb(void *data, int columns, char** rows, char** row_names) /** * @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 + * @param handle SQLite handle to MySQLAuth user database + * @param dcb Client DCB + * @param session Shared MySQL session + * @param scramble The scramble sent to the client in the initial handshake + * @param scramble_len Length of @c scramble * * @return True if the user has access to the database */ -bool validate_mysql_user(sqlite3 *handle, DCB *dcb, MYSQL_session *session) +bool validate_mysql_user(sqlite3 *handle, DCB *dcb, MYSQL_session *session, + uint8_t *scramble, size_t scramble_len) { 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; @@ -491,6 +493,7 @@ bool validate_mysql_user(sqlite3 *handle, DCB *dcb, MYSQL_session *session) if (res.ok) { + /** Found a matching row */ if (session->auth_token_len) { /** If authentication fails, this will trigger the right @@ -498,11 +501,8 @@ bool validate_mysql_user(sqlite3 *handle, DCB *dcb, MYSQL_session *session) session->client_sha1[0] = '_'; } - /** Found a matching row */ - MySQLProtocol *proto = (MySQLProtocol*)dcb->protocol; - if (check_password(res.output, session->auth_token, session->auth_token_len, - proto->scramble, sizeof(proto->scramble), session->client_sha1)) + scramble, scramble_len, session->client_sha1)) { /** Password is OK, check that the database exists */ rval = check_database(handle, session->db); diff --git a/server/modules/authenticator/MySQLAuth/mysql_auth.c b/server/modules/authenticator/MySQLAuth/mysql_auth.c index 8a991f3fc..6d45d0633 100644 --- a/server/modules/authenticator/MySQLAuth/mysql_auth.c +++ b/server/modules/authenticator/MySQLAuth/mysql_auth.c @@ -60,7 +60,8 @@ static int mysql_auth_set_client_data( int mysql_auth_reauthenticate(DCB *dcb, const char *user, uint8_t *token, size_t token_len, - uint8_t *scramble, size_t scramble_len); + uint8_t *scramble, size_t scramble_len, + uint8_t *output_token, size_t output_token_len); /** * The module entry point routine. It is this routine that * must populate the structure that is referred to as the @@ -267,11 +268,13 @@ mysql_auth_authenticate(DCB *dcb) MYSQL_AUTH *instance = (MYSQL_AUTH*)dcb->listener->auth_instance; - bool is_ok = validate_mysql_user(instance->handle, dcb, client_data); + bool is_ok = validate_mysql_user(instance->handle, dcb, client_data, + protocol->scramble, sizeof(protocol->scramble)); if (!is_ok && !instance->skip_auth && service_refresh_users(dcb->service) == 0) { - is_ok = validate_mysql_user(instance->handle, dcb, client_data); + is_ok = validate_mysql_user(instance->handle, dcb, client_data, + protocol->scramble, sizeof(protocol->scramble)); } /* on successful authentication, set user into dcb field */ @@ -888,9 +891,25 @@ static int mysql_auth_load_users(SERV_LISTENER *port) int mysql_auth_reauthenticate(DCB *dcb, const char *user, uint8_t *token, size_t token_len, - uint8_t *scramble, size_t scramble_len) + uint8_t *scramble, size_t scramble_len, + uint8_t *output_token, size_t output_token_len) { MYSQL_session *client_data = (MYSQL_session *)dcb->data; - return gw_check_mysql_scramble_data(dcb, token, token_len, scramble, scramble_len, - user, client_data->client_sha1); + MYSQL_session temp; + int rval = 1; + + memcpy(&temp, client_data, sizeof(*client_data)); + strcpy(temp.user, user); + temp.auth_token = token; + temp.auth_token_len = token_len; + + MYSQL_AUTH *instance = (MYSQL_AUTH*)dcb->listener->auth_instance; + + if (validate_mysql_user(instance->handle, dcb, &temp, scramble, scramble_len)) + { + memcpy(output_token, temp.client_sha1, output_token_len); + rval = 0; + } + + return rval; } diff --git a/server/modules/authenticator/MySQLAuth/mysql_auth.h b/server/modules/authenticator/MySQLAuth/mysql_auth.h index 1bcf70aa9..8e6713f5a 100644 --- a/server/modules/authenticator/MySQLAuth/mysql_auth.h +++ b/server/modules/authenticator/MySQLAuth/mysql_auth.h @@ -148,6 +148,7 @@ int gw_find_mysql_user_password_sha1( const char *username, uint8_t *gateway_password, DCB *dcb); -bool validate_mysql_user(sqlite3 *handle, DCB *dcb, MYSQL_session *session); +bool validate_mysql_user(sqlite3 *handle, DCB *dcb, MYSQL_session *session, + uint8_t *scramble, size_t scramble_len); MXS_END_DECLS diff --git a/server/modules/protocol/MySQL/MySQLBackend/mysql_backend.c b/server/modules/protocol/MySQL/MySQLBackend/mysql_backend.c index f4cfcd588..bedd8eb0e 100644 --- a/server/modules/protocol/MySQL/MySQLBackend/mysql_backend.c +++ b/server/modules/protocol/MySQL/MySQLBackend/mysql_backend.c @@ -1537,7 +1537,8 @@ static int gw_change_user(DCB *backend, auth_ret = dcb->authfunc.reauthenticate(dcb, username, auth_token, auth_token_len, client_protocol->scramble, - sizeof(client_protocol->scramble)); + sizeof(client_protocol->scramble), + client_sha1, sizeof(client_sha1)); strcpy(current_session->db, current_database); @@ -1552,7 +1553,8 @@ static int gw_change_user(DCB *backend, auth_ret = dcb->authfunc.reauthenticate(dcb, username, auth_token, auth_token_len, client_protocol->scramble, - sizeof(client_protocol->scramble)); + sizeof(client_protocol->scramble), + client_sha1, sizeof(client_sha1)); strcpy(current_session->db, current_database); } From ba16b8ca1c41a1797a8307cde134703b3417c38e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Mon, 30 Jan 2017 12:48:21 +0200 Subject: [PATCH 09/38] Fix memory leak in dbusers.c The MySQL connection was created twice. --- server/modules/authenticator/MySQLAuth/dbusers.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/server/modules/authenticator/MySQLAuth/dbusers.c b/server/modules/authenticator/MySQLAuth/dbusers.c index 42e228730..7ae3717a3 100644 --- a/server/modules/authenticator/MySQLAuth/dbusers.c +++ b/server/modules/authenticator/MySQLAuth/dbusers.c @@ -1953,12 +1953,11 @@ int get_users_from_server(MYSQL *con, SERVER_REF *server, SERVICE *service, SERV */ static int get_users(SERV_LISTENER *listener, USERS *users) { - MYSQL *con = gw_mysql_init(); char *service_user = NULL; char *service_passwd = NULL; SERVICE *service = listener->service; - if (con == NULL || serviceGetUser(service, &service_user, &service_passwd) == 0) + if (serviceGetUser(service, &service_user, &service_passwd) == 0) { return -1; } @@ -1975,7 +1974,7 @@ static int get_users(SERV_LISTENER *listener, USERS *users) for (server = service->dbref; !service->svc_do_shutdown && server; server = server->next) { - con = gw_mysql_init(); + MYSQL *con = gw_mysql_init(); if (con) { if (mxs_mysql_real_connect(con, server->server, service_user, dpwd) == NULL) From b206300975cd1f598bffc4d7ba5de7e19b9deccb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Mon, 30 Jan 2017 12:53:54 +0200 Subject: [PATCH 10/38] Exact hostnames grants take precedence over wildcard grants MariaDB and others match grants first by exact hostname/IP and then by wildcard. If there are no exact matches, the wildcard grant should be picked. This can be tested by having different passwords for localhost and remote address. The SQLite based authentication should first check for an exact match and then only after that should it try to match the hostname to a wildcard grant. --- server/modules/authenticator/MySQLAuth/dbusers.c | 4 ++-- server/modules/authenticator/MySQLAuth/mysql_auth.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/server/modules/authenticator/MySQLAuth/dbusers.c b/server/modules/authenticator/MySQLAuth/dbusers.c index 7ae3717a3..b8b05b219 100644 --- a/server/modules/authenticator/MySQLAuth/dbusers.c +++ b/server/modules/authenticator/MySQLAuth/dbusers.c @@ -463,7 +463,7 @@ bool validate_mysql_user(sqlite3 *handle, DCB *dcb, MYSQL_session *session, char *err; sprintf(sql, mysqlauth_validate_user_query, session->user, dcb->remote, - session->db, session->db); + dcb->remote, session->db, session->db); struct user_query_result res = {}; @@ -482,7 +482,7 @@ bool validate_mysql_user(sqlite3 *handle, DCB *dcb, MYSQL_session *session, char client_hostname[MYSQL_HOST_MAXLEN]; wildcard_domain_match(dcb->remote, client_hostname); sprintf(sql, mysqlauth_validate_user_query, session->user, client_hostname, - session->db, session->db); + client_hostname, session->db, session->db); if (sqlite3_exec(handle, sql, auth_cb, &res, &err) != SQLITE_OK) { diff --git a/server/modules/authenticator/MySQLAuth/mysql_auth.h b/server/modules/authenticator/MySQLAuth/mysql_auth.h index 8e6713f5a..6036a7ea1 100644 --- a/server/modules/authenticator/MySQLAuth/mysql_auth.h +++ b/server/modules/authenticator/MySQLAuth/mysql_auth.h @@ -61,7 +61,7 @@ static const char databases_create_sql[] = /** Query that checks if there's a grant for the user being authenticated */ static const char mysqlauth_validate_user_query[] = "SELECT password FROM " MYSQLAUTH_USERS_TABLE_NAME - " WHERE user = '%s' AND '%s' LIKE host AND (anydb = '1' OR '%s' = '' OR '%s' LIKE db)" + " WHERE user = '%s' AND ( '%s' = host OR '%s' LIKE host) AND (anydb = '1' OR '%s' = '' OR '%s' LIKE db)" " LIMIT 1"; /** Query that checks that the database exists */ From 04899f3a3e8ade84252c69bdf24f8279527bd9d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Mon, 30 Jan 2017 13:16:48 +0200 Subject: [PATCH 11/38] Remove unused code from MySQLAuth Removed the old implementation of MySQL authentication. The user printing functionality still expects a hashtable which should be fixed. --- .../modules/authenticator/MySQLAuth/dbusers.c | 1040 +---------------- .../authenticator/MySQLAuth/mysql_auth.c | 288 ----- .../authenticator/MySQLAuth/mysql_auth.h | 81 +- 3 files changed, 79 insertions(+), 1330 deletions(-) diff --git a/server/modules/authenticator/MySQLAuth/dbusers.c b/server/modules/authenticator/MySQLAuth/dbusers.c index b8b05b219..06fcb1b7f 100644 --- a/server/modules/authenticator/MySQLAuth/dbusers.c +++ b/server/modules/authenticator/MySQLAuth/dbusers.c @@ -49,34 +49,12 @@ FROM mysql.user AS u LEFT JOIN mysql.tables_priv AS t \ ON (u.user = t.user AND u.host = t.host) %s" -static int add_databases(SERV_LISTENER *listener, MYSQL *con); -static int add_wildcard_users(USERS *users, char* name, char* host, - char* password, char* anydb, char* db, HASHTABLE* hash); -static void *dbusers_keyread(int fd); -static int dbusers_keywrite(int fd, void *key); -static void *dbusers_valueread(int fd); -static int dbusers_valuewrite(int fd, void *value); -static int get_all_users(SERV_LISTENER *listener, USERS *users); -static int get_databases(SERV_LISTENER *listener, MYSQL *users); static int get_users(SERV_LISTENER *listener, USERS *users); static MYSQL *gw_mysql_init(void); static int gw_mysql_set_timeouts(MYSQL* handle); -static bool host_has_singlechar_wildcard(const char *host); -static bool host_matches_singlechar_wildcard(const char* user, const char* wild); -static bool is_ipaddress(const char* host); static char *mysql_format_user_entry(void *data); -static char *mysql_format_user_entry(void *data); -static int normalize_hostname(const char *input_host, char *output_host); -static int resource_add(HASHTABLE *, char *, char *); -static HASHTABLE *resource_alloc(); -static void *resource_fetch(HASHTABLE *, char *); -static void resource_free(HASHTABLE *resource); -static int uh_cmpfun(const void* v1, const void* v2); -static int uh_hfun(const void* key); -static MYSQL_USER_HOST *uh_keydup(const MYSQL_USER_HOST* key); -static void uh_keyfree(MYSQL_USER_HOST* key); -static int wildcard_db_grant(char* str); -static void merge_netmask(char *host); +static bool get_hostname(const char *ip_address, char *client_hostname); +USERS* mysql_users_alloc(); static char* get_new_users_query(const char *server_version, bool include_root) { @@ -94,37 +72,7 @@ static char* get_new_users_query(const char *server_version, bool include_root) return rval; } -/** - * 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. - * @param userhost User host address - * @param wildcardhost Host address in the grant - * @return True if the host address matches - */ -static bool host_matches_singlechar_wildcard(const char* user, const char* wild) -{ - while (*user != '\0' && *wild != '\0') - { - if (*user != *wild && *wild != '_') - { - return false; - } - user++; - wild++; - } - return true; -} - -/** - * Replace the user/passwd form mysql.user table into the service users' hashtable - * environment. - * The replacement is succesful only if the users' table checksums differ - * - * @param service The current service - * @return -1 on any error or the number of users inserted (0 means no users at all) - */ -int -replace_mysql_users(SERV_LISTENER *listener) +int replace_mysql_users(SERV_LISTENER *listener) { USERS *newusers = mysql_users_alloc(); @@ -173,178 +121,6 @@ replace_mysql_users(SERV_LISTENER *listener) return i; } -/** - * Check if the IP address is a valid MySQL IP address. The IP address can contain - * single or multi-character wildcards as used by MySQL. - * @param host IP address to check - * @return True if the address is a valid, MySQL type IP address - */ -static bool is_ipaddress(const char* host) -{ - while (*host != '\0') - { - if (!isdigit(*host) && *host != '.' && *host != '_' && *host != '%') - { - return false; - } - host++; - } - return true; -} - -/** - * Check if an IP address has single-character wildcards. A single-character - * wildcard is represented by an underscore in the MySQL hostnames. - * @param host Hostname to check - * @return True if the hostname is a valid IP address with a single character wildcard - */ -static bool host_has_singlechar_wildcard(const char *host) -{ - const char* chrptr = host; - bool retval = false; - - while (*chrptr != '\0') - { - if (!isdigit(*chrptr) && *chrptr != '.') - { - if (*chrptr == '_') - { - retval = true; - } - else - { - return false; - } - } - chrptr++; - } - return retval; -} - -/** - * Add a new MySQL user with host, password and netmask into the service users table - * - * The netmask values are: - * 0 for any, 32 for single IPv4 - * 24 for a class C from a.b.c.%, 16 for a Class B from a.b.%.% and 8 for a Class A from a.%.%.% - * - * @param users The users table - * @param user The user name - * @param host The host to add, with possible wildcards - * @param passwd The sha1(sha1(passoword)) to add - * @return 1 on success, 0 on failure and -1 on duplicate user - */ - -int add_mysql_users_with_host_ipv4(USERS *users, const char *user, const char *host, - char *passwd, const char *anydb, const char *db) -{ - struct sockaddr_in serv_addr; - MYSQL_USER_HOST key; - char ret_ip[400] = ""; - 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)); - - /* set user */ - key.user = MXS_STRDUP(user); - - if (key.user == NULL) - { - 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 = MXS_STRDUP(db); - MXS_ABORT_IF_NULL(key.resource); - } - else - { - key.resource = NULL; - } - } - else - { - key.resource = MXS_STRDUP(""); - MXS_ABORT_IF_NULL(key.resource); - } - } - - /* handle ANY, Class C,B,A */ - - /* ANY */ - if (strcmp(host, "%") == 0) - { - strcpy(ret_ip, "0.0.0.0"); - key.netmask = 0; - } - else if ((strnlen(host, MYSQL_HOST_MAXLEN + 1) <= MYSQL_HOST_MAXLEN) && - /** The host is an ip-address and has a '_'-wildcard but not '%' - * (combination of both is invalid). */ - (is_ipaddress(host) && host_has_singlechar_wildcard(host))) - { - strcpy(key.hostname, host); - strcpy(ret_ip, "0.0.0.0"); - key.netmask = 0; - } - else - { - /* hostname without % wildcards has netmask = 32 */ - key.netmask = normalize_hostname(host, ret_ip); - - if (key.netmask == -1) - { - MXS_ERROR("strdup() failed in normalize_hostname for %s@%s", user, host); - } - } - - /* fill IPv4 data struct */ - if (setipaddress(&serv_addr.sin_addr, ret_ip) && strlen(ret_ip)) - { - - /* copy IPv4 data into key.ipv4 */ - memcpy(&key.ipv4, &serv_addr, sizeof(serv_addr)); - - /* if netmask < 32 there are % wildcards */ - if (key.netmask < 32) - { - /* let's zero the last IP byte: a.b.c.0 we may have set above to 1*/ - key.ipv4.sin_addr.s_addr &= 0x00FFFFFF; - } - - /* add user@host as key and passwd as value in the MySQL users hash table */ - if (mysql_users_add(users, &key, passwd)) - { - ret = 1; - } - else if (key.user) - { - ret = -1; - } - } - - MXS_FREE(key.user); - MXS_FREE(key.resource); - - return ret; -} - static bool check_password(const char *output, uint8_t *token, size_t token_len, uint8_t *scramble, size_t scramble_len, uint8_t *phase2_scramble) { @@ -442,17 +218,6 @@ static int auth_cb(void *data, int columns, char** rows, char** row_names) return 0; } -/** - * @brief Verify the user has access to the database - * - * @param handle SQLite handle to MySQLAuth user database - * @param dcb Client DCB - * @param session Shared MySQL session - * @param scramble The scramble sent to the client in the initial handshake - * @param scramble_len Length of @c scramble - * - * @return True if the user has access to the database - */ bool validate_mysql_user(sqlite3 *handle, DCB *dcb, MYSQL_session *session, uint8_t *scramble, size_t scramble_len) { @@ -480,7 +245,7 @@ bool validate_mysql_user(sqlite3 *handle, DCB *dcb, MYSQL_session *session, * as a last resort so we avoid the high cost of the DNS lookup. */ char client_hostname[MYSQL_HOST_MAXLEN]; - wildcard_domain_match(dcb->remote, client_hostname); + get_hostname(dcb->remote, client_hostname); sprintf(sql, mysqlauth_validate_user_query, session->user, client_hostname, client_hostname, session->db, session->db); @@ -529,15 +294,6 @@ static void delete_mysql_users(sqlite3 *handle) } } -/** - * @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 - */ void add_mysql_user(sqlite3 *handle, const char *user, const char *host, const char *db, bool anydb, const char *pw) { @@ -604,8 +360,7 @@ static void add_database(sqlite3 *handle, const char *db) * * @return The users table */ -USERS * -mysql_users_alloc() +USERS* mysql_users_alloc() { USERS *rval; @@ -614,8 +369,12 @@ mysql_users_alloc() return NULL; } - if ((rval->data = hashtable_alloc(USERS_HASHTABLE_DEFAULT_SIZE, uh_hfun, - uh_cmpfun)) == NULL) + // TODO: Refactor the `show dbusers` functionality + /** This prevents a crash when dbusers are queried through maxadmin */ + rval->data = hashtable_alloc(USERS_HASHTABLE_DEFAULT_SIZE, + hashtable_item_strhash, hashtable_item_strcmp); + + if (rval->data == NULL) { MXS_FREE(rval); return NULL; @@ -624,236 +383,9 @@ mysql_users_alloc() /* set the MySQL user@host print routine for the debug interface */ rval->usersCustomUserFormat = mysql_format_user_entry; - /* the key is handled by uh_keydup/uh_keyfree. - * the value is a (char *): it's handled by strdup/free - */ - hashtable_memory_fns(rval->data, - (HASHCOPYFN) uh_keydup, hashtable_item_strdup, - (HASHFREEFN) uh_keyfree, hashtable_item_free); - return rval; } -/** - * Add a new MySQL user to the user table. The user name must be unique - * - * @param users The users table - * @param user The user name - * @param auth The authentication data - * @return The number of users added to the table - */ -int -mysql_users_add(USERS *users, MYSQL_USER_HOST *key, char *auth) -{ - int add; - - if (key == NULL || key->user == NULL) - { - return 0; - } - - atomic_add(&users->stats.n_adds, 1); - add = hashtable_add(users->data, key, auth); - atomic_add(&users->stats.n_entries, add); - - return add; -} - -/** - * Fetch the authentication data for a particular user from the users table - * - * @param users The MySQL users table - * @param key The key with user@host - * @return The authentication data or NULL on error - */ -char *mysql_users_fetch(USERS *users, MYSQL_USER_HOST *key) -{ - if (key == NULL) - { - return NULL; - } - atomic_add(&users->stats.n_fetches, 1); - return hashtable_fetch(users->data, key); -} - -/** - * The hash function we use for storing MySQL users as: users@hosts. - * Currently only IPv4 addresses are supported - * - * @param key The key value, i.e. username@host (IPv4) - * @return The hash key - */ - -static int uh_hfun(const void* key) -{ - const MYSQL_USER_HOST *hu = (const MYSQL_USER_HOST *) key; - - if (key == NULL || hu == NULL || hu->user == NULL) - { - return 0; - } - else - { - return (*hu->user + * (hu->user + 1) + - (unsigned int) (hu->ipv4.sin_addr.s_addr & 0xFF000000 / (256 * 256 * 256))); - } -} - -/** - * The compare function we use for compare MySQL users as: users@hosts. - * Currently only IPv4 addresses are supported - * - * @param key1 The key value, i.e. username@host (IPv4) - * @param key2 The key value, i.e. username@host (IPv4) - * @return The compare value - */ - -static int uh_cmpfun(const void* v1, const void* v2) -{ - const MYSQL_USER_HOST *hu1 = (const MYSQL_USER_HOST *) v1; - const MYSQL_USER_HOST *hu2 = (const MYSQL_USER_HOST *) v2; - - if (v1 == NULL || v2 == NULL) - { - return 0; - } - - if (hu1->user == NULL || hu2->user == NULL) - { - return 0; - } - - /** If the stored user has the unmodified address stored, that means we were not able - * to resolve it at the time we loaded the users. We need to check if the - * address contains wildcards and if the user's address matches that. */ - - const bool wildcard_host = strlen(hu2->hostname) > 0 && strlen(hu1->hostname) > 0; - - if ((strcmp(hu1->user, hu2->user) == 0) && - /** Check for wildcard hostnames */ - ((wildcard_host && host_matches_singlechar_wildcard(hu1->hostname, hu2->hostname)) || - /** If no wildcard hostname is stored, check for network address. */ - (!wildcard_host && (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; - } - - if (hu2->resource && strlen(hu2->resource) && strchr(hu2->resource, '%') != NULL) - { - regex_t re; - char db[MYSQL_DATABASE_MAXLEN * 2 + 1]; - strcpy(db, hu2->resource); - int len = strlen(db); - char* ptr = strrchr(db, '%'); - - if (ptr == NULL) - { - return 1; - } - - while (ptr) - { - memmove(ptr + 1, ptr, (len - (ptr - db)) + 1); - *ptr = '.'; - *(ptr + 1) = '*'; - len = strlen(db); - ptr = strrchr(db, '%'); - } - - if ((regcomp(&re, db, REG_ICASE | REG_NOSUB))) - { - return 1; - } - - if (regexec(&re, hu1->resource, 0, NULL, 0) == 0) - { - regfree(&re); - return 0; - } - regfree(&re); - } - - /* no matches, deny auth */ - return 1; - } - } - else - { - return 1; - } -} - -/** - *The key dup function we use for duplicate the users@hosts. - * - * @param key The key value, i.e. username@host ip4/ip6 data - */ - -static MYSQL_USER_HOST *uh_keydup(const MYSQL_USER_HOST* key) -{ - if ((key == NULL) || (key->user == NULL)) - { - return NULL; - } - - MYSQL_USER_HOST *rval = (MYSQL_USER_HOST *) MXS_CALLOC(1, sizeof(MYSQL_USER_HOST)); - char* user = MXS_STRDUP(key->user); - char* resource = key->resource ? MXS_STRDUP(key->resource) : NULL; - - if (!user || !rval || (key->resource && !resource)) - { - MXS_FREE(rval); - MXS_FREE(user); - MXS_FREE(resource); - return NULL; - } - - rval->user = user; - rval->ipv4 = key->ipv4; - rval->netmask = key->netmask; - rval->resource = resource; - strcpy(rval->hostname, key->hostname); - - return (void *) rval; -} - -/** - * The key free function we use for freeing the users@hosts data - * - * @param key The key value, i.e. username@host ip4 data - */ -static void uh_keyfree(MYSQL_USER_HOST* key) -{ - if (key) - { - MXS_FREE(key->user); - MXS_FREE(key->resource); - MXS_FREE(key); - } -} - /** * Format the mysql user as user@host * The returned memory must be freed by the caller @@ -925,165 +457,6 @@ static char *mysql_format_user_entry(void *data) return mysql_user; } -/** - * Remove the resources table - * - * @param resources The resources table to remove - */ -static void -resource_free(HASHTABLE *resources) -{ - if (resources) - { - hashtable_free(resources); - } -} - -/** - * Allocate a MySQL database names table - * - * @return The database names table - */ -static HASHTABLE * -resource_alloc() -{ - HASHTABLE *resources; - - if ((resources = hashtable_alloc(10, hashtable_item_strhash, hashtable_item_strcmp)) == NULL) - { - return NULL; - } - - hashtable_memory_fns(resources, - hashtable_item_strdup, hashtable_item_strdup, - hashtable_item_free, hashtable_item_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 - */ -static 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 - */ -static void * -resource_fetch(HASHTABLE *resources, char *key) -{ - return hashtable_fetch(resources, key); -} - -/** - * Normalize hostname with % wildcards to a valid IP string. - * - * Valid input values: - * a.b.c.d, a.b.c.%, a.b.%.%, a.%.%.% - * Short formats a.% and a.%.% are both converted to a.%.%.% - * Short format a.b.% is converted to a.b.%.% - * - * Last host byte is set to 1, avoiding setipadress() failure - * - * @param input_host The hostname with possible % wildcards - * @param output_host The normalized hostname (buffer must be preallocated) - * @return The calculated netmask or -1 on failure - */ -static int normalize_hostname(const char *input_host, char *output_host) -{ - int netmask, bytes, bits = 0, found_wildcard = 0; - char *p, *lasts, *tmp; - int useorig = 0; - - output_host[0] = 0; - bytes = 0; - - tmp = MXS_STRDUP(input_host); - - if (tmp == NULL) - { - return -1; - } - /* Handle hosts with netmasks (e.g. "123.321.123.0/255.255.255.0") by - * replacing the zeros with '%'. - */ - merge_netmask(tmp); - - p = strtok_r(tmp, ".", &lasts); - while (p != NULL) - { - - if (strcmp(p, "%")) - { - if (!isdigit(*p)) - { - useorig = 1; - } - - strcat(output_host, p); - bits += 8; - } - else if (bytes == 3) - { - found_wildcard = 1; - strcat(output_host, "1"); - } - else - { - found_wildcard = 1; - strcat(output_host, "0"); - } - bytes++; - p = strtok_r(NULL, ".", &lasts); - if (p) - { - strcat(output_host, "."); - } - } - if (found_wildcard) - { - netmask = bits; - while (bytes++ < 4) - { - if (bytes == 4) - { - strcat(output_host, ".1"); - } - else - { - strcat(output_host, ".0"); - } - } - } - else - { - netmask = 32; - } - - if (useorig == 1) - { - netmask = 32; - strcpy(output_host, input_host); - } - - MXS_FREE(tmp); - - return netmask; -} - /** * Returns a MYSQL object suitably configured. * @@ -1166,192 +539,6 @@ retblock: return rc; } -/* - * Serialise a key for the dbusers hashtable to a file - * - * @param fd File descriptor to write to - * @param key The key to write - * @return 0 on error, 1 if the key was written - */ -static int -dbusers_keywrite(int fd, void *key) -{ - MYSQL_USER_HOST *dbkey = (MYSQL_USER_HOST *) key; - int tmp; - - tmp = strlen(dbkey->user); - if (write(fd, &tmp, sizeof(tmp)) != sizeof(tmp)) - { - return 0; - } - if (write(fd, dbkey->user, tmp) != tmp) - { - return 0; - } - if (write(fd, &dbkey->ipv4, sizeof(dbkey->ipv4)) != sizeof(dbkey->ipv4)) - { - return 0; - } - if (write(fd, &dbkey->netmask, sizeof(dbkey->netmask)) != sizeof(dbkey->netmask)) - { - return 0; - } - if (dbkey->resource) - { - tmp = strlen(dbkey->resource); - if (write(fd, &tmp, sizeof(tmp)) != sizeof(tmp)) - { - return 0; - } - if (write(fd, dbkey->resource, tmp) != tmp) - { - return 0; - } - } - else // NULL is valid, so represent with a length of -1 - { - tmp = -1; - if (write(fd, &tmp, sizeof(tmp)) != sizeof(tmp)) - { - return 0; - } - } - return 1; -} - -/** - * Serialise a value for the dbusers hashtable to a file - * - * @param fd File descriptor to write to - * @param value The value to write - * @return 0 on error, 1 if the value was written - */ -static int -dbusers_valuewrite(int fd, void *value) -{ - int tmp; - - tmp = strlen(value); - if (write(fd, &tmp, sizeof(tmp)) != sizeof(tmp)) - { - return 0; - } - if (write(fd, value, tmp) != tmp) - { - return 0; - } - return 1; -} - -/** - * Unserialise a key for the dbusers hashtable from a file - * - * @param fd File descriptor to read from - * @return Pointer to the new key or NULL on error - */ -static void * -dbusers_keyread(int fd) -{ - MYSQL_USER_HOST *dbkey; - - if ((dbkey = (MYSQL_USER_HOST *) MXS_MALLOC(sizeof(MYSQL_USER_HOST))) == NULL) - { - return NULL; - } - - *dbkey->hostname = '\0'; - - int user_size; - if (read(fd, &user_size, sizeof(user_size)) != sizeof(user_size)) - { - MXS_FREE(dbkey); - return NULL; - } - if ((dbkey->user = (char *) MXS_MALLOC(user_size + 1)) == NULL) - { - MXS_FREE(dbkey); - return NULL; - } - if (read(fd, dbkey->user, user_size) != user_size) - { - MXS_FREE(dbkey->user); - MXS_FREE(dbkey); - return NULL; - } - dbkey->user[user_size] = 0; // NULL Terminate - if (read(fd, &dbkey->ipv4, sizeof(dbkey->ipv4)) != sizeof(dbkey->ipv4)) - { - MXS_FREE(dbkey->user); - MXS_FREE(dbkey); - return NULL; - } - if (read(fd, &dbkey->netmask, sizeof(dbkey->netmask)) != sizeof(dbkey->netmask)) - { - MXS_FREE(dbkey->user); - MXS_FREE(dbkey); - return NULL; - } - - int res_size; - if (read(fd, &res_size, sizeof(res_size)) != sizeof(res_size)) - { - MXS_FREE(dbkey->user); - MXS_FREE(dbkey); - return NULL; - } - else if (res_size != -1) - { - if ((dbkey->resource = (char *) MXS_MALLOC(res_size + 1)) == NULL) - { - MXS_FREE(dbkey->user); - MXS_FREE(dbkey); - return NULL; - } - if (read(fd, dbkey->resource, res_size) != res_size) - { - MXS_FREE(dbkey->resource); - MXS_FREE(dbkey->user); - MXS_FREE(dbkey); - return NULL; - } - dbkey->resource[res_size] = 0; // NULL Terminate - } - else // NULL is valid, so represent with a length of -1 - { - dbkey->resource = NULL; - } - return (void *) dbkey; -} - -/** - * Unserialise a value for the dbusers hashtable from a file - * - * @param fd File descriptor to read from - * @return Return the new value data or NULL on error - */ -static void * -dbusers_valueread(int fd) -{ - char *value; - int tmp; - - if (read(fd, &tmp, sizeof(tmp)) != sizeof(tmp)) - { - return NULL; - } - if ((value = (char *) MXS_MALLOC(tmp + 1)) == NULL) - { - return NULL; - } - if (read(fd, value, tmp) != tmp) - { - MXS_FREE(value); - return NULL; - } - value[tmp] = 0; - return (void *) value; -} - int dump_user_cb(void *data, int fields, char **row, char **field_names) { sqlite3 *handle = (sqlite3*)data; @@ -1408,13 +595,6 @@ static bool transfer_table_contents(sqlite3 *src, sqlite3 *dest) return rval; } -/** - * Load users from persisted database - * - * @param dest Open SQLite handle where contents are loaded - * - * @return True on success - */ bool dbusers_load(sqlite3 *dest, const char *filename) { sqlite3 *src; @@ -1431,13 +611,6 @@ bool dbusers_load(sqlite3 *dest, const char *filename) return rval; } -/** - * Save users to persisted database - * - * @param dest Open SQLite handle where contents are stored - * - * @return True on success - */ bool dbusers_save(sqlite3 *src, const char *filename) { sqlite3 *dest; @@ -1454,105 +627,6 @@ bool dbusers_save(sqlite3 *src, const char *filename) return rval; } -/** - * Check if the database name contains a wildcard character - * @param str Database grant - * @return 1 if the name contains the '%' wildcard character, 0 if it does not - */ -static int wildcard_db_grant(char* str) -{ - char* ptr = str; - - while (ptr && *ptr != '\0') - { - if (*ptr == '%') - { - return 1; - } - ptr++; - } - - return 0; -} - -/** - * - * @param users Pointer to USERS struct - * @param name Username of the client - * @param host Host address of the client - * @param password Client password - * @param anydb If the user has access to all databases - * @param db Database, in wildcard form - * @param hash Hashtable with all database names - * @return number of unique grants generated from wildcard database name - */ -static int add_wildcard_users(USERS *users, char* name, char* host, char* password, - char* anydb, char* db, HASHTABLE* hash) -{ - HASHITERATOR* iter; - HASHTABLE* ht = hash; - char *restr, *ptr, *value; - int len, err, rval = 0; - char errbuf[1024]; - regex_t re; - - if (db == NULL || hash == NULL) - { - return 0; - } - - if ((restr = MXS_MALLOC(sizeof(char) * strlen(db) * 2)) == NULL) - { - return 0; - } - - strcpy(restr, db); - - len = strlen(restr); - ptr = strchr(restr, '%'); - - if (ptr == NULL) - { - MXS_FREE(restr); - return 0; - } - - while (ptr) - { - memmove(ptr + 1, ptr, (len - (ptr - restr)) + 1); - *ptr++ = '.'; - *ptr = '*'; - len = strlen(restr); - ptr = strchr(restr, '%'); - } - - if ((err = regcomp(&re, restr, REG_ICASE | REG_NOSUB))) - { - regerror(err, &re, errbuf, 1024); - MXS_ERROR("Failed to compile regex when resolving wildcard database grants: %s", - errbuf); - MXS_FREE(restr); - return 0; - } - - iter = hashtable_iterator(ht); - - while (iter && (value = hashtable_next(iter))) - { - if (regexec(&re, value, 0, NULL, 0) == 0) - { - rval += add_mysql_users_with_host_ipv4(users, name, host, password, - anydb, value); - } - } - - hashtable_iterator_free(iter); - regfree(&re); - MXS_FREE(restr); - - return rval; -} - /** * @brief Check service permissions on one server * @@ -1699,17 +773,6 @@ static bool check_server_permissions(SERVICE *service, SERVER* server, return rval; } -/** - * @brief Check if the service user has all required permissions to operate properly. - * - * This checks for SELECT permissions on mysql.user, mysql.db and mysql.tables_priv - * tables and for SHOW DATABASES permissions. If permissions are not adequate, - * an error message is logged and the service is not started. - * - * @param service Service to inspect - * @return True if service permissions are correct on at least one server, false - * if permissions are missing or if an error occurred. - */ bool check_service_permissions(SERVICE* service) { if (is_internal_service(service->routerModule) || @@ -1745,81 +808,16 @@ bool check_service_permissions(SERVICE* service) } /** - * If the hostname is of form a.b.c.d/e.f.g.h where e-h is 255 or 0, replace - * the zeros in the first part with '%' and remove the second part. This does - * not yet support netmasks completely, but should be sufficient for most - * situations. In case of error, the hostname may end in an invalid state, which - * will cause an error later on. + * @brief Get client hostname * - * @param host The hostname, which is modified in-place. If merging is unsuccessful, - * it may end up garbled. + * Queries the DNS server for the client's hostname. + * + * @param ip_address Client IP address + * @param client_hostname Output buffer for hostname + * + * @return True if the hostname query was successful */ -static void merge_netmask(char *host) -{ - char *delimiter_loc = strchr(host, '/'); - if (delimiter_loc == NULL) - { - return; // Nothing to do - } - /* If anything goes wrong, we put the '/' back in to ensure the hostname - * cannot be used. - */ - *delimiter_loc = '\0'; - - char *ip_token_loc = host; - char *mask_token_loc = delimiter_loc + 1; // This is at minimum a \0 - - while (ip_token_loc && mask_token_loc) - { - if (strncmp(mask_token_loc, "255", 3) == 0) - { - // Skip - } - else if (*mask_token_loc == '0' && *ip_token_loc == '0') - { - *ip_token_loc = '%'; - } - else - { - /* Any other combination is considered invalid. This may leave the - * hostname in a partially modified state. - * TODO: handle more cases - */ - *delimiter_loc = '/'; - MXS_ERROR("Unrecognized IP-bytes in host/mask-combination. " - "Merge incomplete: %s", host); - return; - } - - ip_token_loc = strchr(ip_token_loc, '.'); - mask_token_loc = strchr(mask_token_loc, '.'); - if (ip_token_loc && mask_token_loc) - { - ip_token_loc++; - mask_token_loc++; - } - } - if (ip_token_loc || mask_token_loc) - { - *delimiter_loc = '/'; - MXS_ERROR("Unequal number of IP-bytes in host/mask-combination. " - "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) +static bool get_hostname(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 diff --git a/server/modules/authenticator/MySQLAuth/mysql_auth.c b/server/modules/authenticator/MySQLAuth/mysql_auth.c index 6d45d0633..554b4ff80 100644 --- a/server/modules/authenticator/MySQLAuth/mysql_auth.c +++ b/server/modules/authenticator/MySQLAuth/mysql_auth.c @@ -328,7 +328,6 @@ mysql_auth_authenticate(DCB *dcb) static int mysql_auth_set_protocol_data(DCB *dcb, GWBUF *buf) { - uint8_t *client_auth_packet = GWBUF_DATA(buf); MySQLProtocol *protocol = NULL; MYSQL_session *client_data = NULL; int client_auth_packet_size = 0; @@ -458,293 +457,6 @@ mysql_auth_is_client_ssl_capable(DCB *dcb) return (protocol->client_capabilities & (int)GW_MYSQL_CAPABILITIES_SSL) ? true : false; } -/** - * gw_find_mysql_user_password_sha1 - * - * The routine fetches an user from the MaxScale users' table - * The users' table is dcb->listener->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 - * - * @param username The user to look for - * @param gateway_password The related SHA1(SHA1(password)), the pointer must be preallocated - * @param dcb Current DCB - * @return 1 if user is not found or 0 if the user exists - * - */ -int gw_find_mysql_user_password_sha1(const char *username, uint8_t *gateway_password, DCB *dcb) -{ - MYSQL_session *client_data = (MYSQL_session *) dcb->data; - SERVICE *service = (SERVICE *) dcb->service; - SERV_LISTENER *listener = dcb->listener; - struct sockaddr_in *client = (struct sockaddr_in *) &dcb->ipv4; - - MYSQL_USER_HOST key = {}; - key.user = (char*)username; - memcpy(&key.ipv4, client, sizeof(struct sockaddr_in)); - key.netmask = 32; - key.resource = client_data->db; - - if (strlen(dcb->remote) < MYSQL_HOST_MAXLEN) - { - strcpy(key.hostname, dcb->remote); - } - - MXS_DEBUG("%lu [MySQL Client Auth], checking user [%s@%s]%s%s", - pthread_self(), - key.user, - dcb->remote, - key.resource != NULL ? " db: " : "", - key.resource != NULL ? key.resource : ""); - - /* look for user@current_ipv4 now */ - char *user_password = mysql_users_fetch(listener->users, &key); - - if (!user_password) - { - /* The user is not authenticated @ current IPv4 */ - - while (1) - { - /* - * (1) Check for localhost first: 127.0.0.1 (IPv4 only) - */ - - if ((key.ipv4.sin_addr.s_addr == 0x0100007F) && - !dcb->service->localhost_match_wildcard_host) - { - /* Skip the wildcard check and return 1 */ - break; - } - - /* - * (2) check for possible IPv4 class C,B,A networks - */ - - /* Class C check */ - key.ipv4.sin_addr.s_addr &= 0x00FFFFFF; - key.netmask -= 8; - - user_password = mysql_users_fetch(listener->users, &key); - - if (user_password) - { - break; - } - - /* Class B check */ - key.ipv4.sin_addr.s_addr &= 0x0000FFFF; - key.netmask -= 8; - - user_password = mysql_users_fetch(listener->users, &key); - - if (user_password) - { - break; - } - - /* Class A check */ - key.ipv4.sin_addr.s_addr &= 0x000000FF; - key.netmask -= 8; - - user_password = mysql_users_fetch(listener->users, &key); - - if (user_password) - { - break; - } - - /* - * (3) Continue check for wildcard host, user@% - */ - - memset(&key.ipv4, 0, sizeof(struct sockaddr_in)); - key.netmask = 0; - - MXS_DEBUG("%lu [MySQL Client Auth], checking user [%s@%s] with " - "wildcard host [%%]", - pthread_self(), - key.user, - dcb->remote); - - user_password = mysql_users_fetch(listener->users, &key); - - if (user_password) - { - break; - } - - if (!user_password) - { - /* - * user@% not found. - */ - - MXS_DEBUG("%lu [MySQL Client Auth], user [%s@%s] not existent", - pthread_self(), - key.user, - dcb->remote); - - MXS_INFO("Authentication Failed: user [%s@%s] not found.", - key.user, - dcb->remote); - break; - } - - } - } - - /* If user@host has been found we get the the password in binary format*/ - if (user_password) - { - /* - * Convert the hex data (40 bytes) to binary (20 bytes). - * The gateway_password represents the SHA1(SHA1(real_password)). - * Please note: the real_password is unknown and SHA1(real_password) is unknown as well - */ - int passwd_len = strlen(user_password); - if (passwd_len) - { - passwd_len = (passwd_len <= (SHA_DIGEST_LENGTH * 2)) ? passwd_len : (SHA_DIGEST_LENGTH * 2); - gw_hex2bin(gateway_password, user_password, passwd_len); - } - - return 0; - } - else - { - return 1; - } -} - -/** - * - * @brief Check authentication token received against stage1_hash and scramble - * - * @param dcb The current dcb - * @param token The token sent by the client in the authentication request - * @param token_len The token size in bytes - * @param scramble The scramble data sent by the server during handshake - * @param scramble_len The scramble size in bytes - * @param username The current username in the authentication request - * @param stage1_hash The SHA1(candidate_password) decoded by this routine - * @return Authentication status - * @note Authentication status codes are defined in maxscale/protocol/mysql.h - * - */ -int -gw_check_mysql_scramble_data(DCB *dcb, - uint8_t *token, - unsigned int token_len, - uint8_t *mxs_scramble, - unsigned int scramble_len, - const char *username, - uint8_t *stage1_hash) -{ - uint8_t step1[GW_MYSQL_SCRAMBLE_SIZE] = ""; - uint8_t step2[GW_MYSQL_SCRAMBLE_SIZE + 1] = ""; - uint8_t check_hash[GW_MYSQL_SCRAMBLE_SIZE] = ""; - char hex_double_sha1[2 * GW_MYSQL_SCRAMBLE_SIZE + 1] = ""; - uint8_t password[GW_MYSQL_SCRAMBLE_SIZE] = ""; - /* The following can be compared using memcmp to detect a null password */ - uint8_t null_client_sha1[MYSQL_SCRAMBLE_LEN] = ""; - - - if ((username == NULL) || (mxs_scramble == NULL) || (stage1_hash == NULL)) - { - return MXS_AUTH_FAILED; - } - - /*< - * get the user's password from repository in SHA1(SHA1(real_password)); - * please note 'real_password' is unknown! - */ - - if (gw_find_mysql_user_password_sha1(username, password, dcb)) - { - /* if password was sent, fill stage1_hash with at least 1 byte in order - * to create right error message: (using password: YES|NO) - */ - if (token_len) - { - memcpy(stage1_hash, (char *)"_", 1); - } - - return MXS_AUTH_FAILED; - } - - if (token && token_len) - { - /*< - * convert in hex format: this is the content of mysql.user table. - * The field password is without the '*' prefix and it is 40 bytes long - */ - - gw_bin2hex(hex_double_sha1, password, SHA_DIGEST_LENGTH); - } - else - { - /* check if the password is not set in the user table */ - return memcmp(password, null_client_sha1, MYSQL_SCRAMBLE_LEN) ? - MXS_AUTH_FAILED : MXS_AUTH_SUCCEEDED; - } - - /*< - * Auth check in 3 steps - * - * Note: token = XOR (SHA1(real_password), SHA1(CONCAT(scramble, SHA1(SHA1(real_password))))) - * the client sends token - * - * Now, server side: - * - * - * step 1: compute the STEP1 = SHA1(CONCAT(scramble, gateway_password)) - * the result in step1 is SHA_DIGEST_LENGTH long - */ - - gw_sha1_2_str(mxs_scramble, scramble_len, password, SHA_DIGEST_LENGTH, step1); - - /*< - * step2: STEP2 = XOR(token, STEP1) - * - * token is transmitted form client and it's based on the handshake scramble and SHA1(real_passowrd) - * step1 has been computed in the previous step - * the result STEP2 is SHA1(the_password_to_check) and is SHA_DIGEST_LENGTH long - */ - - gw_str_xor(step2, token, step1, token_len); - - /*< - * copy the stage1_hash back to the caller - * stage1_hash will be used for backend authentication - */ - - memcpy(stage1_hash, step2, SHA_DIGEST_LENGTH); - - /*< - * step 3: prepare the check_hash - * - * compute the SHA1(STEP2) that is SHA1(SHA1(the_password_to_check)), and is SHA_DIGEST_LENGTH long - */ - - gw_sha1_str(step2, SHA_DIGEST_LENGTH, check_hash); - - -#ifdef GW_DEBUG_CLIENT_AUTH - { - char inpass[128] = ""; - gw_bin2hex(inpass, check_hash, SHA_DIGEST_LENGTH); - - fprintf(stderr, "The CLIENT hex(SHA1(SHA1(password))) for \"%s\" is [%s]", username, inpass); - } -#endif - - /* now compare SHA1(SHA1(gateway_password)) and check_hash: return 0 is MYSQL_AUTH_OK */ - return (0 == memcmp(password, check_hash, SHA_DIGEST_LENGTH)) ? - MXS_AUTH_SUCCEEDED : MXS_AUTH_FAILED; -} /** * @brief Free the client data pointed to by the passed DCB. * diff --git a/server/modules/authenticator/MySQLAuth/mysql_auth.h b/server/modules/authenticator/MySQLAuth/mysql_auth.h index 6036a7ea1..2c5982136 100644 --- a/server/modules/authenticator/MySQLAuth/mysql_auth.h +++ b/server/modules/authenticator/MySQLAuth/mysql_auth.h @@ -123,31 +123,70 @@ typedef struct mysql_user_host_key char hostname[MYSQL_HOST_MAXLEN + 1]; } MYSQL_USER_HOST; +/** + * @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 + */ void add_mysql_user(sqlite3 *handle, const char *user, const char *host, const char *db, bool anydb, const char *pw); -extern int add_mysql_users_with_host_ipv4(USERS *users, const char *user, const char *host, - char *passwd, const char *anydb, const char *db); -extern bool check_service_permissions(SERVICE* service); -extern bool dbusers_load(sqlite3 *handle, const char *filename); -extern bool dbusers_save(sqlite3 *src, const char *filename); -extern int mysql_users_add(USERS *users, MYSQL_USER_HOST *key, char *auth); -extern USERS *mysql_users_alloc(); -extern char *mysql_users_fetch(USERS *users, MYSQL_USER_HOST *key); -extern int replace_mysql_users(SERV_LISTENER *listener); +/** + * @brief Check if the service user has all required permissions to operate properly. + * + * This checks for SELECT permissions on mysql.user, mysql.db and mysql.tables_priv + * tables and for SHOW DATABASES permissions. If permissions are not adequate, + * an error message is logged and the service is not started. + * + * @param service Service to inspect + * + * @return True if service permissions are correct on at least one server, false + * if permissions are missing or if an error occurred. + */ +bool check_service_permissions(SERVICE* service); -int gw_check_mysql_scramble_data(DCB *dcb, - uint8_t *token, - unsigned int token_len, - uint8_t *scramble, - unsigned int scramble_len, - const char *username, - uint8_t *stage1_hash); -int check_db_name_after_auth(DCB *dcb, char *database, int auth_ret); -int gw_find_mysql_user_password_sha1( - const char *username, - uint8_t *gateway_password, - DCB *dcb); +/** + * Load users from persisted database + * + * @param dest Open SQLite handle where contents are loaded + * + * @return True on success + */ +bool dbusers_load(sqlite3 *handle, const char *filename); + +/** + * Save users to persisted database + * + * @param dest Open SQLite handle where contents are stored + * + * @return True on success + */ +bool dbusers_save(sqlite3 *src, const char *filename); + +/** + * Reload and replace the currently loaded database users + * + * @param service The current service + * + * @return -1 on any error or the number of users inserted (0 means no users at all) + */ +int replace_mysql_users(SERV_LISTENER *listener); + +/** + * @brief Verify the user has access to the database + * + * @param handle SQLite handle to MySQLAuth user database + * @param dcb Client DCB + * @param session Shared MySQL session + * @param scramble The scramble sent to the client in the initial handshake + * @param scramble_len Length of @c scramble + * + * @return True if the user has access to the database + */ bool validate_mysql_user(sqlite3 *handle, DCB *dcb, MYSQL_session *session, uint8_t *scramble, size_t scramble_len); From 0488f8a052d9a89675bcaa1e6252b4fa126ae764 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Mon, 30 Jan 2017 16:09:00 +0200 Subject: [PATCH 12/38] Fix enable_root_user The option was inverted. --- server/modules/authenticator/MySQLAuth/dbusers.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/modules/authenticator/MySQLAuth/dbusers.c b/server/modules/authenticator/MySQLAuth/dbusers.c index 06fcb1b7f..d364efe21 100644 --- a/server/modules/authenticator/MySQLAuth/dbusers.c +++ b/server/modules/authenticator/MySQLAuth/dbusers.c @@ -59,7 +59,7 @@ USERS* mysql_users_alloc(); 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 ? "WHERE u.user NOT IN ('root')" : ""; + const char *with_root = include_root ? "" : "WHERE u.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); From 5494b262d31f26485e1e898f26810a71d85206af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Mon, 30 Jan 2017 16:09:31 +0200 Subject: [PATCH 13/38] Reorder SQL statements when persisting users The statements are now executed in a more controlled order and a failure of one will skip the others. The transaction is also rolled back if it fails. --- .../modules/authenticator/MySQLAuth/dbusers.c | 55 +++++++++++++------ 1 file changed, 37 insertions(+), 18 deletions(-) diff --git a/server/modules/authenticator/MySQLAuth/dbusers.c b/server/modules/authenticator/MySQLAuth/dbusers.c index d364efe21..9aef63431 100644 --- a/server/modules/authenticator/MySQLAuth/dbusers.c +++ b/server/modules/authenticator/MySQLAuth/dbusers.c @@ -282,8 +282,9 @@ bool validate_mysql_user(sqlite3 *handle, DCB *dcb, MYSQL_session *session, * * @param handle SQLite handle */ -static void delete_mysql_users(sqlite3 *handle) +static bool delete_mysql_users(sqlite3 *handle) { + bool rval = true; char *err; if (sqlite3_exec(handle, delete_users_query, NULL, NULL, &err) != SQLITE_OK || @@ -291,7 +292,10 @@ static void delete_mysql_users(sqlite3 *handle) { MXS_ERROR("Failed to delete old users: %s", err); sqlite3_free(err); + rval = false; } + + return rval; } void add_mysql_user(sqlite3 *handle, const char *user, const char *host, @@ -568,30 +572,45 @@ static bool transfer_table_contents(sqlite3 *src, sqlite3 *dest) sqlite3_free(err); rval = false; } - - if (sqlite3_exec(dest, "BEGIN", NULL, NULL, &err) != SQLITE_OK) + else if (sqlite3_exec(dest, "BEGIN", NULL, NULL, &err) != SQLITE_OK) { MXS_ERROR("Failed to start transaction: %s", err); sqlite3_free(err); rval = false; } - - /** Replace the data */ - if (sqlite3_exec(src, dump_users_query, dump_user_cb, dest, &err) != SQLITE_OK || - sqlite3_exec(src, dump_databases_query, dump_database_cb, dest, &err) != SQLITE_OK) + else { - MXS_ERROR("Failed to load database contents: %s", err); - sqlite3_free(err); - rval = false; + /** Transaction is open */ + if (!delete_mysql_users(dest)) + { + rval = false; + } + else if (sqlite3_exec(src, dump_users_query, dump_user_cb, dest, &err) != SQLITE_OK || + sqlite3_exec(src, dump_databases_query, dump_database_cb, dest, &err) != SQLITE_OK) + { + MXS_ERROR("Failed to load database contents: %s", err); + sqlite3_free(err); + rval = false; + } + if (rval) + { + if (sqlite3_exec(dest, "COMMIT", NULL, NULL, &err) != SQLITE_OK) + { + MXS_ERROR("Failed to commit transaction: %s", err); + sqlite3_free(err); + rval = false; + } + } + else + { + if (sqlite3_exec(dest, "ROLLBACK", NULL, NULL, &err) != SQLITE_OK) + { + MXS_ERROR("Failed to rollback transaction: %s", err); + sqlite3_free(err); + rval = false; + } + } } - - if (sqlite3_exec(dest, "COMMIT", NULL, NULL, &err) != SQLITE_OK) - { - MXS_ERROR("Failed to commit transaction: %s", err); - sqlite3_free(err); - rval = false; - } - return rval; } From b2c018a986d7d04872d35bfac970a8c9f0991554 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Mon, 30 Jan 2017 16:16:45 +0200 Subject: [PATCH 14/38] Don't users with a pre-4.1 password The old error message and behavior was lost with the new implementation. --- server/modules/authenticator/MySQLAuth/dbusers.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/server/modules/authenticator/MySQLAuth/dbusers.c b/server/modules/authenticator/MySQLAuth/dbusers.c index 9aef63431..8a85027fc 100644 --- a/server/modules/authenticator/MySQLAuth/dbusers.c +++ b/server/modules/authenticator/MySQLAuth/dbusers.c @@ -318,7 +318,16 @@ void add_mysql_user(sqlite3 *handle, const char *user, const char *host, if (pw && *pw) { - if (*pw == '*') + if (strlen(pw) == 16) + { + MXS_ERROR("The user %s@%s has on old password in the " + "backend database. MaxScale does not support these " + "old passwords. This user will not be able to connect " + "via MaxScale. Update the users password to correct " + "this.", user, host); + return; + } + else if (*pw == '*') { pw++; } From a3a2a24c97fb0b81f2dc2ce8b6034d3c7f244b36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Mon, 30 Jan 2017 17:01:16 +0200 Subject: [PATCH 15/38] Return correct value for failed db authentication The unknown database error was never triggered as all authentication errors returned MXS_AUTH_FAILED. --- .../modules/authenticator/MySQLAuth/dbusers.c | 27 ++++++++++++------- .../authenticator/MySQLAuth/mysql_auth.c | 17 +++++++----- .../authenticator/MySQLAuth/mysql_auth.h | 5 ++-- 3 files changed, 30 insertions(+), 19 deletions(-) diff --git a/server/modules/authenticator/MySQLAuth/dbusers.c b/server/modules/authenticator/MySQLAuth/dbusers.c index 8a85027fc..20caf18a6 100644 --- a/server/modules/authenticator/MySQLAuth/dbusers.c +++ b/server/modules/authenticator/MySQLAuth/dbusers.c @@ -218,13 +218,13 @@ static int auth_cb(void *data, int columns, char** rows, char** row_names) return 0; } -bool validate_mysql_user(sqlite3 *handle, DCB *dcb, MYSQL_session *session, - uint8_t *scramble, size_t scramble_len) +int validate_mysql_user(sqlite3 *handle, DCB *dcb, MYSQL_session *session, + uint8_t *scramble, size_t scramble_len) { 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]; - bool rval = false; + int rval = MXS_AUTH_FAILED; char *err; sprintf(sql, mysqlauth_validate_user_query, session->user, dcb->remote, @@ -259,18 +259,25 @@ bool validate_mysql_user(sqlite3 *handle, DCB *dcb, MYSQL_session *session, if (res.ok) { /** Found a matching row */ - if (session->auth_token_len) - { - /** If authentication fails, this will trigger the right - * error message with `Using password : YES` */ - session->client_sha1[0] = '_'; - } if (check_password(res.output, session->auth_token, session->auth_token_len, scramble, scramble_len, session->client_sha1)) { /** Password is OK, check that the database exists */ - rval = check_database(handle, session->db); + if (check_database(handle, session->db)) + { + rval = MXS_AUTH_SUCCEEDED; + } + else + { + rval = MXS_AUTH_FAILED_DB; + } + } + else if (session->auth_token_len) + { + /** If authentication fails, this will trigger the right + * error message with `Using password : YES` */ + session->client_sha1[0] = '_'; } } diff --git a/server/modules/authenticator/MySQLAuth/mysql_auth.c b/server/modules/authenticator/MySQLAuth/mysql_auth.c index 554b4ff80..452a72d4f 100644 --- a/server/modules/authenticator/MySQLAuth/mysql_auth.c +++ b/server/modules/authenticator/MySQLAuth/mysql_auth.c @@ -268,17 +268,19 @@ mysql_auth_authenticate(DCB *dcb) MYSQL_AUTH *instance = (MYSQL_AUTH*)dcb->listener->auth_instance; - bool is_ok = validate_mysql_user(instance->handle, dcb, client_data, - protocol->scramble, sizeof(protocol->scramble)); + auth_ret = validate_mysql_user(instance->handle, dcb, client_data, + protocol->scramble, sizeof(protocol->scramble)); - if (!is_ok && !instance->skip_auth && service_refresh_users(dcb->service) == 0) + if (auth_ret != MXS_AUTH_SUCCEEDED && + !instance->skip_auth && + service_refresh_users(dcb->service) == 0) { - is_ok = validate_mysql_user(instance->handle, dcb, client_data, - protocol->scramble, sizeof(protocol->scramble)); + auth_ret = validate_mysql_user(instance->handle, dcb, client_data, + protocol->scramble, sizeof(protocol->scramble)); } /* on successful authentication, set user into dcb field */ - if (is_ok || instance->skip_auth) + if (auth_ret == MXS_AUTH_SUCCEEDED || instance->skip_auth) { auth_ret = MXS_AUTH_SUCCEEDED; dcb->user = MXS_STRDUP_A(client_data->user); @@ -616,8 +618,9 @@ int mysql_auth_reauthenticate(DCB *dcb, const char *user, temp.auth_token_len = token_len; MYSQL_AUTH *instance = (MYSQL_AUTH*)dcb->listener->auth_instance; + int rc = validate_mysql_user(instance->handle, dcb, &temp, scramble, scramble_len); - if (validate_mysql_user(instance->handle, dcb, &temp, scramble, scramble_len)) + if (rc == MXS_AUTH_SUCCEEDED) { memcpy(output_token, temp.client_sha1, output_token_len); rval = 0; diff --git a/server/modules/authenticator/MySQLAuth/mysql_auth.h b/server/modules/authenticator/MySQLAuth/mysql_auth.h index 2c5982136..328ee8d87 100644 --- a/server/modules/authenticator/MySQLAuth/mysql_auth.h +++ b/server/modules/authenticator/MySQLAuth/mysql_auth.h @@ -29,6 +29,7 @@ #include #include +#include #include #include #include @@ -185,9 +186,9 @@ int replace_mysql_users(SERV_LISTENER *listener); * @param scramble The scramble sent to the client in the initial handshake * @param scramble_len Length of @c scramble * - * @return True if the user has access to the database + * @return MXS_AUTH_SUCCEEDED if the user has access to the database */ -bool validate_mysql_user(sqlite3 *handle, DCB *dcb, MYSQL_session *session, +int validate_mysql_user(sqlite3 *handle, DCB *dcb, MYSQL_session *session, uint8_t *scramble, size_t scramble_len); MXS_END_DECLS From 2dd79e2e0ce95a7f0ced9b00f22be69189f85bd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Fri, 3 Feb 2017 08:10:03 +0200 Subject: [PATCH 16/38] Make sure user cache directory exists The cache directory needs to be created by the authenticator itself. --- .../authenticator/MySQLAuth/mysql_auth.c | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/server/modules/authenticator/MySQLAuth/mysql_auth.c b/server/modules/authenticator/MySQLAuth/mysql_auth.c index 452a72d4f..02b7ebabd 100644 --- a/server/modules/authenticator/MySQLAuth/mysql_auth.c +++ b/server/modules/authenticator/MySQLAuth/mysql_auth.c @@ -557,16 +557,19 @@ static int mysql_auth_load_users(SERV_LISTENER *port) MXS_ERROR("[%s] Unable to load users for listener %s listening at %s:%d.", service->name, port->name, port->address ? port->address : "0.0.0.0", port->port); - strcat(path, DBUSERS_FILE); + if (mxs_mkdir_all(path, S_IRWXU)) + { + strcat(path, DBUSERS_FILE); - if (!dbusers_load(instance->handle, path)) - { - MXS_ERROR("[%s] Failed to load cached users from '%s'.", service->name, path); - rc = MXS_AUTH_LOADUSERS_ERROR; - } - else - { - MXS_WARNING("[%s] Using cached credential information from '%s'.", service->name, path); + if (!dbusers_load(instance->handle, path)) + { + MXS_ERROR("[%s] Failed to load cached users from '%s'.", service->name, path); + rc = MXS_AUTH_LOADUSERS_ERROR; + } + else + { + MXS_WARNING("[%s] Using cached credential information from '%s'.", service->name, path); + } } if (instance->inject_service_user) From f225b29756cef2619990068f2873fba177423ef1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Fri, 3 Feb 2017 08:52:52 +0200 Subject: [PATCH 17/38] Fix injection of service user The parameters that were given to the user creation function were in the wrong order. --- server/modules/authenticator/MySQLAuth/mysql_auth.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/modules/authenticator/MySQLAuth/mysql_auth.c b/server/modules/authenticator/MySQLAuth/mysql_auth.c index 02b7ebabd..26bc6be5f 100644 --- a/server/modules/authenticator/MySQLAuth/mysql_auth.c +++ b/server/modules/authenticator/MySQLAuth/mysql_auth.c @@ -500,8 +500,8 @@ static bool add_service_user(SERV_LISTENER *port) if (newpw) { MYSQL_AUTH *inst = (MYSQL_AUTH*)port->auth_instance; - add_mysql_user(inst->handle, user, "%", newpw, "Y", ""); - add_mysql_user(inst->handle, user, "localhost", newpw, "Y", ""); + add_mysql_user(inst->handle, user, "%", "", "Y", newpw); + add_mysql_user(inst->handle, user, "localhost", "", "Y", newpw); MXS_FREE(newpw); rval = true; } From b796967df84287ed1da88579baa301bccbd97b4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Sat, 4 Feb 2017 02:46:48 +0200 Subject: [PATCH 18/38] Add diagnostic entry point to authenticators The authenticators should have a similar way to print diagnostic information as filter and routers do. This allows the authenticators to print the users in their own format. In the future, all the diagnostic entry points should be changed so that they return a structure that contains the information in a standard form. This information can then be formatted in different ways by other modules. --- include/maxscale/authenticator.h | 6 + include/maxscale/service.h | 9 ++ include/maxscale/users.h | 94 +++++++++-- server/core/service.c | 11 ++ server/core/users.c | 141 ++++------------ .../CDCPlainAuth/cdc_plain_auth.c | 32 +--- .../GSSAPI/GSSAPIAuth/gssapi_auth.c | 4 +- .../GSSAPIBackendAuth/gssapi_backend_auth.c | 4 +- .../authenticator/HTTPAuth/http_auth.c | 4 +- .../MaxAdminAuth/max_admin_auth.c | 4 +- .../modules/authenticator/MySQLAuth/dbusers.c | 150 +----------------- .../authenticator/MySQLAuth/mysql_auth.c | 28 ++++ .../MySQLBackendAuth/mysql_backend_auth.c | 4 +- .../NullAuthAllow/null_auth_allow.c | 4 +- .../NullAuthDeny/null_auth_deny.c | 4 +- server/modules/routing/debugcli/debugcmd.c | 11 +- 16 files changed, 196 insertions(+), 314 deletions(-) diff --git a/include/maxscale/authenticator.h b/include/maxscale/authenticator.h index d941adc35..94346c909 100644 --- a/include/maxscale/authenticator.h +++ b/include/maxscale/authenticator.h @@ -60,6 +60,11 @@ struct servlistener; * entry point. * * loadusers Load or update authenticator user data + * + * diagnostic Print diagnostic output to a DCB + * + * reauthenticate Reauthenticate a user + * * @endverbatim * * This forms the "module object" for authenticator modules within the gateway. @@ -76,6 +81,7 @@ typedef struct mxs_authenticator void (*free)(struct dcb *); void (*destroy)(void *); int (*loadusers)(struct servlistener *); + void (*diagnostic)(struct dcb*, struct servlistener *); /** This entry point was added to avoid calling authenticator functions * directly when a COM_CHANGE_USER command is executed. */ diff --git a/include/maxscale/service.h b/include/maxscale/service.h index 0672524a0..5d9da9f03 100644 --- a/include/maxscale/service.h +++ b/include/maxscale/service.h @@ -258,6 +258,15 @@ int service_refresh_users(SERVICE *service); /** * Diagnostics */ + +/** + * @brief Print service authenticator diagnostics + * + * @param dcb DCB to print to + * @param service The service to diagnose + */ +void service_print_users(DCB *, const SERVICE *); + void dprintAllServices(DCB *dcb); void dprintService(DCB *dcb, SERVICE *service); void dListServices(DCB *dcb); diff --git a/include/maxscale/users.h b/include/maxscale/users.h index a1c239f81..cf674c8e3 100644 --- a/include/maxscale/users.h +++ b/include/maxscale/users.h @@ -40,27 +40,91 @@ typedef struct } USERS_STATS; /** - * The user table, this contains the username and authentication data required - * for the authentication implementation within the gateway. + * A generic user table containing the username and authentication data */ typedef struct users { HASHTABLE *data; /**< The hashtable containing the actual data */ - char *(*usersCustomUserFormat)(void *); /**< Optional username format routine */ USERS_STATS stats; /**< The statistics for the users table */ - unsigned char cksum[SHA_DIGEST_LENGTH]; /**< The users' table ckecksum */ } USERS; -extern USERS *users_alloc(); /**< Allocate a users table */ -extern void users_free(USERS *); /**< Free a users table */ -extern int users_add(USERS *, const char *, const char *); /**< Add a user to the users table */ -extern int users_delete(USERS *, const char *); /**< Delete a user from the users table */ -extern const char *users_fetch(USERS *, const char *); /**< Fetch the authentication data for a user*/ -extern int users_update(USERS *, const char *, const char *); /**< Change the password data for a user in - the users table */ -extern int users_default_loadusers(SERV_LISTENER *port); /**< A generic implementation of the - authenticator loadusers entry point */ -extern void usersPrint(const USERS *); /**< Print data about the users loaded */ -extern void dcb_usersPrint(DCB *, const SERVICE *); /**< Print data about the users loaded */ + +/** + * Allocate a new users table + * + * @return The users table + */ +USERS *users_alloc(); + +/** + * Remove the users table + * + * @param users The users table to remove + */ +void users_free(USERS *); + +/** + * Add a new user to the user table. The user name must be unique + * + * @param users The users table + * @param user The user name + * @param auth The authentication data + * @return The number of users added to the table + */ +int users_add(USERS *, const char *, const char *); + +/** + * Delete a user from the user table. + * + * @param users The users table + * @param user The user name + * @return The number of users deleted from the table + */ +int users_delete(USERS *, const char *); + +/** + * Fetch the authentication data for a particular user from the users table + * + * @param users The users table + * @param user The user name + * @return The authentication data or NULL on error + */ +const char *users_fetch(USERS *, const char *); + +/** + * Change the password data associated with a user in the users + * table. + * + * @param users The users table + * @param user The user name + * @param auth The new authentication details + * @return Number of users updated + */ +int users_update(USERS *, const char *, const char *); + +/** + * @brief Default user loading function + * + * A generic key-value user table is allocated for the service. + * + * @param port Listener configuration + * @return Always AUTH_LOADUSERS_OK + */ +int users_default_loadusers(SERV_LISTENER *port); + +/** + * @brief Default authenticator diagnostic function + * + * @param dcb DCB where data is printed + * @param port Port whose data is to be printed + */ +void users_default_diagnostic(DCB *dcb, SERV_LISTENER *port); + +/** + * Print details of the users storage mechanism + * + * @param users The users table + */ +void usersPrint(const USERS *); MXS_END_DECLS diff --git a/server/core/service.c b/server/core/service.c index 9b7dbacb9..26e1e71a4 100644 --- a/server/core/service.c +++ b/server/core/service.c @@ -2285,3 +2285,14 @@ bool service_serialize_servers(const SERVICE *service) return rval; } + +void service_print_users(DCB *dcb, const SERVICE *service) +{ + for (SERV_LISTENER *port = service->ports; port; port = port->next) + { + if (port->listener && port->listener->authfunc.diagnostic) + { + port->listener->authfunc.diagnostic(dcb, port); + } + } +} diff --git a/server/core/users.c b/server/core/users.c index ba4cfda03..975f12728 100644 --- a/server/core/users.c +++ b/server/core/users.c @@ -33,13 +33,7 @@ * @endverbatim */ -/** - * Allocate a new users table - * - * @return The users table - */ -USERS * -users_alloc() +USERS *users_alloc() { USERS *rval; @@ -63,13 +57,7 @@ users_alloc() return rval; } -/** - * Remove the users table - * - * @param users The users table to remove - */ -void -users_free(USERS *users) +void users_free(USERS *users) { if (users) { @@ -78,16 +66,7 @@ users_free(USERS *users) } } -/** - * Add a new user to the user table. The user name must be unique - * - * @param users The users table - * @param user The user name - * @param auth The authentication data - * @return The number of users added to the table - */ -int -users_add(USERS *users, const char *user, const char *auth) +int users_add(USERS *users, const char *user, const char *auth) { int add; @@ -97,15 +76,7 @@ users_add(USERS *users, const char *user, const char *auth) return add; } -/** - * Delete a user from the user table. - * - * @param users The users table - * @param user The user name - * @return The number of users deleted from the table - */ -int -users_delete(USERS *users, const char *user) +int users_delete(USERS *users, const char *user) { int del; @@ -115,32 +86,14 @@ users_delete(USERS *users, const char *user) return del; } -/** - * Fetch the authentication data for a particular user from the users table - * - * @param users The users table - * @param user The user name - * @return The authentication data or NULL on error - */ -const char -*users_fetch(USERS *users, const char *user) +const char *users_fetch(USERS *users, const char *user) { atomic_add(&users->stats.n_fetches, 1); // TODO: Returning data from the hashtable is not threadsafe. return hashtable_fetch(users->data, (char*)user); } -/** - * Change the password data associated with a user in the users - * table. - * - * @param users The users table - * @param user The user name - * @param auth The new authentication details - * @return Number of users updated - */ -int -users_update(USERS *users, const char *user, const char *auth) +int users_update(USERS *users, const char *user, const char *auth) { if (hashtable_delete(users->data, (char*)user) == 0) { @@ -149,81 +102,41 @@ users_update(USERS *users, const char *user, const char *auth) return hashtable_add(users->data, (char*)user, (char*)auth); } -/** - * Print details of the users storage mechanism - * - * @param users The users table - */ -void -usersPrint(const USERS *users) + +void usersPrint(const USERS *users) { printf("Users table data\n"); hashtable_stats(users->data); } -/** - * Print details of the users storage mechanism to a DCB - * - * @param dcb DCB to print to - * @param users The users table - */ -void -dcb_usersPrint(DCB *dcb, const SERVICE *service) +void users_default_diagnostic(DCB *dcb, SERV_LISTENER *port) { - for (SERV_LISTENER *port = service->ports; port; port = port->next) + if (port->users && port->users->data) { - if (port->users && port->users->data) - { - HASHITERATOR *iter = hashtable_iterator(port->users->data); + HASHITERATOR *iter = hashtable_iterator(port->users->data); - if (iter) + if (iter) + { + dcb_printf(dcb, "User names: "); + char *sep = ""; + void *user; + + while ((user = hashtable_next(iter)) != NULL) { - dcb_printf(dcb, "User names: "); - char *sep = ""; - void *user; - - if (port->users->usersCustomUserFormat != NULL) - { - while ((user = hashtable_next(iter)) != NULL) - { - char *custom_user = port->users->usersCustomUserFormat(user); - if (custom_user) - { - dcb_printf(dcb, "%s%s", sep, custom_user); - MXS_FREE(custom_user); - sep = ", "; - } - } - } - else - { - while ((user = hashtable_next(iter)) != NULL) - { - dcb_printf(dcb, "%s%s", sep, (char *)user); - sep = ", "; - } - } - - hashtable_iterator_free(iter); + dcb_printf(dcb, "%s%s", sep, (char *)user); + sep = ", "; } - } - else - { - dcb_printf(dcb, "Users table is empty\n"); + + dcb_printf(dcb, "\n"); + hashtable_iterator_free(iter); } } - - dcb_printf(dcb, "\n"); + else + { + dcb_printf(dcb, "Users table is empty\n"); + } } -/** - * @brief Default user loading function - * - * A generic key-value user table is allocated for the service. - * - * @param port Listener configuration - * @return Always AUTH_LOADUSERS_OK - */ int users_default_loadusers(SERV_LISTENER *port) { users_free(port->users); diff --git a/server/modules/authenticator/CDCPlainAuth/cdc_plain_auth.c b/server/modules/authenticator/CDCPlainAuth/cdc_plain_auth.c index 835d639ca..327062fb5 100644 --- a/server/modules/authenticator/CDCPlainAuth/cdc_plain_auth.c +++ b/server/modules/authenticator/CDCPlainAuth/cdc_plain_auth.c @@ -167,7 +167,9 @@ MXS_MODULE* MXS_CREATE_MODULE() cdc_auth_authenticate, /* Authenticate user credentials */ cdc_auth_free_client_data, /* Free the client data held in DCB */ NULL, /* No destroy entry point */ - cdc_replace_users /* Load CDC users */ + cdc_replace_users, /* Load CDC users */ + users_default_diagnostic, /* Default diagnostic */ + NULL /* No user reauthentication */ }; static MXS_MODULE info = @@ -484,11 +486,6 @@ cdc_read_users(USERS *users, char *usersfile) char *user_passwd; /* user maxlen ':' password hash '\n' '\0' */ char read_buffer[CDC_USER_MAXLEN + 1 + SHA_DIGEST_LENGTH + 1 + 1]; - char *all_users_data = NULL; - struct stat statb; - int fd; - int filelen = 0; - unsigned char hash[SHA_DIGEST_LENGTH] = ""; int max_line_size = sizeof(read_buffer) - 1; @@ -497,27 +494,11 @@ cdc_read_users(USERS *users, char *usersfile) return -1; } - fd = fileno(fp); - - if (fstat(fd, &statb) == 0) - { - filelen = statb.st_size; - } - - if ((all_users_data = MXS_MALLOC(filelen + 1)) == NULL) - { - return -1; - } - - *all_users_data = '\0'; - while (!feof(fp)) { if (fgets(read_buffer, max_line_size, fp) != NULL) { char *tmp_ptr = read_buffer; - /* append data for hash */ - strcat(all_users_data, read_buffer); if ((tmp_ptr = strchr(read_buffer, ':')) != NULL) { @@ -537,13 +518,6 @@ cdc_read_users(USERS *users, char *usersfile) } } - /* compute SHA1 digest for users' data */ - SHA1((const unsigned char *) all_users_data, strlen(all_users_data), hash); - - memcpy(users->cksum, hash, SHA_DIGEST_LENGTH); - - MXS_FREE(all_users_data); - fclose(fp); return loaded; diff --git a/server/modules/authenticator/GSSAPI/GSSAPIAuth/gssapi_auth.c b/server/modules/authenticator/GSSAPI/GSSAPIAuth/gssapi_auth.c index e925b658c..d3407a2a4 100644 --- a/server/modules/authenticator/GSSAPI/GSSAPIAuth/gssapi_auth.c +++ b/server/modules/authenticator/GSSAPI/GSSAPIAuth/gssapi_auth.c @@ -664,7 +664,9 @@ MXS_MODULE* MXS_CREATE_MODULE() gssapi_auth_authenticate, /* Authenticate user credentials */ gssapi_auth_free_data, /* Free the client data held in DCB */ gssapi_auth_free, /* Free authenticator data */ - gssapi_auth_load_users /* Load database users */ + gssapi_auth_load_users, /* Load database users */ + users_default_diagnostic, /* Default user diagnostic */ + NULL /* No user reauthentication */ }; static MXS_MODULE info = diff --git a/server/modules/authenticator/GSSAPI/GSSAPIBackendAuth/gssapi_backend_auth.c b/server/modules/authenticator/GSSAPI/GSSAPIBackendAuth/gssapi_backend_auth.c index aaa31d08d..a41d0304d 100644 --- a/server/modules/authenticator/GSSAPI/GSSAPIBackendAuth/gssapi_backend_auth.c +++ b/server/modules/authenticator/GSSAPI/GSSAPIBackendAuth/gssapi_backend_auth.c @@ -276,7 +276,9 @@ MXS_MODULE* MXS_CREATE_MODULE() gssapi_backend_auth_authenticate, /* Authenticate user credentials */ NULL, /* Client plugin will free shared data */ gssapi_backend_auth_free, /* Free authenticator data */ - NULL /* Load users from backend databases */ + NULL, /* Load users from backend databases */ + NULL, /* No diagnostic */ + NULL /* No user reauthentication */ }; static MXS_MODULE info = diff --git a/server/modules/authenticator/HTTPAuth/http_auth.c b/server/modules/authenticator/HTTPAuth/http_auth.c index dba38b4dc..bf82897ee 100644 --- a/server/modules/authenticator/HTTPAuth/http_auth.c +++ b/server/modules/authenticator/HTTPAuth/http_auth.c @@ -66,7 +66,9 @@ MXS_MODULE* MXS_CREATE_MODULE() http_auth_authenticate, /* Authenticate user credentials */ http_auth_free_client_data, /* Free the client data held in DCB */ NULL, /* No destroy entry point */ - users_default_loadusers /* Load generic users */ + users_default_loadusers, /* Load generic users */ + users_default_diagnostic, /* Default user diagnostic */ + NULL /* No user reauthentication */ }; static MXS_MODULE info = diff --git a/server/modules/authenticator/MaxAdminAuth/max_admin_auth.c b/server/modules/authenticator/MaxAdminAuth/max_admin_auth.c index 8ac08a8e4..f7b86122b 100644 --- a/server/modules/authenticator/MaxAdminAuth/max_admin_auth.c +++ b/server/modules/authenticator/MaxAdminAuth/max_admin_auth.c @@ -60,7 +60,9 @@ MXS_MODULE* MXS_CREATE_MODULE() max_admin_auth_authenticate, /* Authenticate user credentials */ max_admin_auth_free_client_data, /* Free the client data held in DCB */ NULL, /* No destroy entry point */ - users_default_loadusers /* Load generic users */ + users_default_loadusers, /* Load generic users */ + users_default_diagnostic, /* Default user diagnostic */ + NULL /* No user reauthentication */ }; static MXS_MODULE info = diff --git a/server/modules/authenticator/MySQLAuth/dbusers.c b/server/modules/authenticator/MySQLAuth/dbusers.c index 20caf18a6..87055a705 100644 --- a/server/modules/authenticator/MySQLAuth/dbusers.c +++ b/server/modules/authenticator/MySQLAuth/dbusers.c @@ -49,12 +49,11 @@ FROM mysql.user AS u LEFT JOIN mysql.tables_priv AS t \ ON (u.user = t.user AND u.host = t.host) %s" -static int get_users(SERV_LISTENER *listener, USERS *users); +static int get_users(SERV_LISTENER *listener); static MYSQL *gw_mysql_init(void); static int gw_mysql_set_timeouts(MYSQL* handle); static char *mysql_format_user_entry(void *data); static bool get_hostname(const char *ip_address, char *client_hostname); -USERS* mysql_users_alloc(); static char* get_new_users_query(const char *server_version, bool include_root) { @@ -74,50 +73,9 @@ static char* get_new_users_query(const char *server_version, bool include_root) int replace_mysql_users(SERV_LISTENER *listener) { - USERS *newusers = mysql_users_alloc(); - - if (newusers == NULL) - { - return -1; - } - spinlock_acquire(&listener->lock); - - /* load users and grants from the backend database */ - int i = get_users(listener, newusers); - - if (i <= 0) - { - /** Failed to load users */ - if (listener->users) - { - /* Restore old users and resources */ - users_free(newusers); - } - else - { - /* No users allocated, use the empty new one */ - listener->users = newusers; - } - spinlock_release(&listener->lock); - return i; - } - - /** TODO: Figure out a way to create a checksum function in the backend server - * so that we can avoid querying the complete list of users every time we - * need to refresh the users */ - MXS_DEBUG("%lu [replace_mysql_users] users' tables replaced", pthread_self()); - USERS *oldusers = listener->users; - listener->users = newusers; - + int i = get_users(listener); spinlock_release(&listener->lock); - - if (oldusers) - { - /* free the old table */ - users_free(oldusers); - } - return i; } @@ -375,108 +333,6 @@ static void add_database(sqlite3 *handle, const char *db) } } -/** - * Allocate a new MySQL users table for mysql specific users@host as key - * - * @return The users table - */ -USERS* mysql_users_alloc() -{ - USERS *rval; - - if ((rval = MXS_CALLOC(1, sizeof(USERS))) == NULL) - { - return NULL; - } - - // TODO: Refactor the `show dbusers` functionality - /** This prevents a crash when dbusers are queried through maxadmin */ - rval->data = hashtable_alloc(USERS_HASHTABLE_DEFAULT_SIZE, - hashtable_item_strhash, hashtable_item_strcmp); - - if (rval->data == NULL) - { - MXS_FREE(rval); - return NULL; - } - - /* set the MySQL user@host print routine for the debug interface */ - rval->usersCustomUserFormat = mysql_format_user_entry; - - return rval; -} - -/** - * Format the mysql user as user@host - * The returned memory must be freed by the caller - * - * @param data Input data - * @return the MySQL user@host - */ -static 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 + 10 + - MYSQL_USER_MAXLEN + 1; - - if (data == NULL) - { - return NULL; - } - - entry = (MYSQL_USER_HOST *) data; - - mysql_user = (char *) MXS_CALLOC(mysql_user_len, sizeof(char)); - - 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); - } - else if ((entry->ipv4.sin_addr.s_addr & 0xFF000000) == 0 && entry->netmask == 24) - { - snprintf(mysql_user, mysql_user_len - 1, "%s@%i.%i.%i.%%", entry->user, - entry->ipv4.sin_addr.s_addr & 0x000000FF, - (entry->ipv4.sin_addr.s_addr & 0x0000FF00) / (256), - (entry->ipv4.sin_addr.s_addr & 0x00FF0000) / (256 * 256)); - } - else if ((entry->ipv4.sin_addr.s_addr & 0xFFFF0000) == 0 && entry->netmask == 16) - { - snprintf(mysql_user, mysql_user_len - 1, "%s@%i.%i.%%.%%", entry->user, - entry->ipv4.sin_addr.s_addr & 0x000000FF, - (entry->ipv4.sin_addr.s_addr & 0x0000FF00) / (256)); - } - else if ((entry->ipv4.sin_addr.s_addr & 0xFFFFFF00) == 0 && entry->netmask == 8) - { - snprintf(mysql_user, mysql_user_len - 1, "%s@%i.%%.%%.%%", entry->user, - entry->ipv4.sin_addr.s_addr & 0x000000FF); - } - else if (entry->netmask == 32) - { - strcpy(mysql_user, entry->user); - 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 - 5, "Err: %s", entry->user); - strcat(mysql_user, "@"); - inet_ntop(AF_INET, &(entry->ipv4).sin_addr, mysql_user + strlen(mysql_user), - INET_ADDRSTRLEN); - } - - return mysql_user; -} - /** * Returns a MYSQL object suitably configured. * @@ -984,7 +840,7 @@ int get_users_from_server(MYSQL *con, SERVER_REF *server, SERVICE *service, SERV * @param users The users table into which to load the users * @return -1 on any error or the number of users inserted */ -static int get_users(SERV_LISTENER *listener, USERS *users) +static int get_users(SERV_LISTENER *listener) { char *service_user = NULL; char *service_passwd = NULL; diff --git a/server/modules/authenticator/MySQLAuth/mysql_auth.c b/server/modules/authenticator/MySQLAuth/mysql_auth.c index 26bc6be5f..657738bd1 100644 --- a/server/modules/authenticator/MySQLAuth/mysql_auth.c +++ b/server/modules/authenticator/MySQLAuth/mysql_auth.c @@ -58,6 +58,8 @@ static int mysql_auth_set_client_data( MySQLProtocol *protocol, GWBUF *buffer); +void mysql_auth_diagnostic(DCB *dcb, SERV_LISTENER *port); + int mysql_auth_reauthenticate(DCB *dcb, const char *user, uint8_t *token, size_t token_len, uint8_t *scramble, size_t scramble_len, @@ -82,6 +84,7 @@ MXS_MODULE* MXS_CREATE_MODULE() mysql_auth_free_client_data, /* Free the client data held in DCB */ mysql_auth_destroy, /* Destroy entry point */ mysql_auth_load_users, /* Load users from backend databases */ + mysql_auth_diagnostic, mysql_auth_reauthenticate /* Handle COM_CHANGE_USER */ }; @@ -630,4 +633,29 @@ int mysql_auth_reauthenticate(DCB *dcb, const char *user, } return rval; + +} + +int diag_cb(void *data, int columns, char **row, char **field_names) +{ + DCB *dcb = (DCB*)data; + dcb_printf(dcb, "%s@%s ", row[0], row[1]); + return 0; +} + +void mysql_auth_diagnostic(DCB *dcb, SERV_LISTENER *port) +{ + dcb_printf(dcb, "User names: "); + + MYSQL_AUTH *instance = (MYSQL_AUTH*)port->auth_instance; + char *err; + + if (sqlite3_exec(instance->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); + MXS_ERROR("Failed to print users: %s", err); + sqlite3_free(err); + } + dcb_printf(dcb, "\n"); } diff --git a/server/modules/authenticator/MySQLBackendAuth/mysql_backend_auth.c b/server/modules/authenticator/MySQLBackendAuth/mysql_backend_auth.c index 377c0d539..3a260be9b 100644 --- a/server/modules/authenticator/MySQLBackendAuth/mysql_backend_auth.c +++ b/server/modules/authenticator/MySQLBackendAuth/mysql_backend_auth.c @@ -169,7 +169,9 @@ MXS_MODULE* MXS_CREATE_MODULE() auth_backend_authenticate, /* Authenticate user credentials */ NULL, /* The shared data is freed by the client DCB */ auth_backend_destroy, /* Destroy authenticator */ - NULL /* We don't need to load users */ + NULL, /* We don't need to load users */ + NULL, /* No diagnostic */ + NULL /* No user reauthentication */ }; static MXS_MODULE info = diff --git a/server/modules/authenticator/NullAuthAllow/null_auth_allow.c b/server/modules/authenticator/NullAuthAllow/null_auth_allow.c index 5ab52b49b..4c42be90c 100644 --- a/server/modules/authenticator/NullAuthAllow/null_auth_allow.c +++ b/server/modules/authenticator/NullAuthAllow/null_auth_allow.c @@ -62,7 +62,9 @@ MXS_MODULE* MXS_CREATE_MODULE() null_auth_authenticate, /* Authenticate user credentials */ null_auth_free_client_data, /* Free the client data held in DCB */ NULL, /* No destroy entry point */ - users_default_loadusers /* Load generic users */ + users_default_loadusers, /* Load generic users */ + NULL, /* No diagnostic */ + NULL /* No user reauthentication */ }; static MXS_MODULE info = diff --git a/server/modules/authenticator/NullAuthDeny/null_auth_deny.c b/server/modules/authenticator/NullAuthDeny/null_auth_deny.c index cf719c617..183f9a969 100644 --- a/server/modules/authenticator/NullAuthDeny/null_auth_deny.c +++ b/server/modules/authenticator/NullAuthDeny/null_auth_deny.c @@ -59,7 +59,9 @@ MXS_MODULE* MXS_CREATE_MODULE() null_auth_authenticate, /* Authenticate user credentials */ null_auth_free_client_data, /* Free the client data held in DCB */ NULL, /* No destroy entry point */ - users_default_loadusers /* Load generic users */ + users_default_loadusers, /* Load generic users */ + NULL, /* No diagnostic */ + NULL /* No user reauthentication */ }; static MXS_MODULE info = diff --git a/server/modules/routing/debugcli/debugcmd.c b/server/modules/routing/debugcli/debugcmd.c index 48d6e1a2f..e200e5aba 100644 --- a/server/modules/routing/debugcli/debugcmd.c +++ b/server/modules/routing/debugcli/debugcmd.c @@ -141,12 +141,19 @@ struct subcommand showoptions[] = {0} }, { - "dbusers", 1, 1, dcb_usersPrint, - "Show user statistics", + "dbusers", 1, 1, service_print_users, + "[deprecated] Show user statistics", "Show statistics and user names for a service's user table.\n" "\t\tExample : show dbusers ", {ARG_TYPE_SERVICE, 0, 0} }, + { + "authenticators", 1, 1, service_print_users, + "Show authenticator diagnostics", + "Show authenticator diagnostics for a service.\n" + "\t\tExample : show authenticators ", + {ARG_TYPE_SERVICE, 0, 0} + }, { "epoll", 0, 0, dprintPollStats, "Show the poll statistics", From adb9b5049be4fc18a7e191f8b7d9b6b19da85fd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Sat, 4 Feb 2017 02:49:11 +0200 Subject: [PATCH 19/38] Fix user cache directory permissions The user cache directory is only used by the maxscale user so only the maxscale user should have access to it. --- server/modules/authenticator/MySQLAuth/mysql_auth.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/modules/authenticator/MySQLAuth/mysql_auth.c b/server/modules/authenticator/MySQLAuth/mysql_auth.c index 657738bd1..ca1517db9 100644 --- a/server/modules/authenticator/MySQLAuth/mysql_auth.c +++ b/server/modules/authenticator/MySQLAuth/mysql_auth.c @@ -588,7 +588,7 @@ static int mysql_auth_load_users(SERV_LISTENER *port) else { /* Users loaded successfully, save authentication data to file cache */ - if (mxs_mkdir_all(path, 0777)) + if (mxs_mkdir_all(path, S_IRWXU)) { strcat(path, DBUSERS_FILE); dbusers_save(instance->handle, path); From 71b3ee70a4735e81c5e18c3ed867ad5ea27b9dbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Mon, 13 Feb 2017 12:59:06 +0200 Subject: [PATCH 20/38] Fix empty password detection If a user was defined without a password, the authentication would fail. --- server/modules/authenticator/MySQLAuth/dbusers.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/server/modules/authenticator/MySQLAuth/dbusers.c b/server/modules/authenticator/MySQLAuth/dbusers.c index 87055a705..f52b1f539 100644 --- a/server/modules/authenticator/MySQLAuth/dbusers.c +++ b/server/modules/authenticator/MySQLAuth/dbusers.c @@ -160,6 +160,11 @@ static bool check_database(sqlite3 *handle, const char *database) return rval; } +static bool no_password_required(const char *result, size_t tok_len) +{ + return *result == '\0' && tok_len == 0; +} + /** Used to detect empty result sets */ struct user_query_result { @@ -218,7 +223,8 @@ int validate_mysql_user(sqlite3 *handle, DCB *dcb, MYSQL_session *session, { /** Found a matching row */ - if (check_password(res.output, session->auth_token, session->auth_token_len, + if (no_password_required(res.output, session->auth_token_len) || + check_password(res.output, session->auth_token, session->auth_token_len, scramble, scramble_len, session->client_sha1)) { /** Password is OK, check that the database exists */ From 405b944abbcbbd20b7779b38a44c386d320a22f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Fri, 10 Feb 2017 16:14:06 +0200 Subject: [PATCH 21/38] Use on-disk database for MySQLAuth users The SQLite database is now always created on disk. This will remove the need to dump the database users from the in-memory database to the persisted on-disk database. This change will also make the authentication compatible with older SQLite implementations which lack the URI-based database strings found in newer versions. --- .../authenticator/MySQLAuth/CMakeLists.txt | 12 +- .../modules/authenticator/MySQLAuth/dbusers.c | 132 ++++------------ .../authenticator/MySQLAuth/mysql_auth.c | 148 ++++++++++-------- .../authenticator/MySQLAuth/mysql_auth.h | 6 +- 4 files changed, 122 insertions(+), 176 deletions(-) diff --git a/server/modules/authenticator/MySQLAuth/CMakeLists.txt b/server/modules/authenticator/MySQLAuth/CMakeLists.txt index 4f26199c6..9828e34b6 100644 --- a/server/modules/authenticator/MySQLAuth/CMakeLists.txt +++ b/server/modules/authenticator/MySQLAuth/CMakeLists.txt @@ -1,4 +1,8 @@ -add_library(MySQLAuth SHARED mysql_auth.c dbusers.c) -target_link_libraries(MySQLAuth maxscale-common MySQLCommon sqlite3) -set_target_properties(MySQLAuth PROPERTIES VERSION "1.0.0") -install_module(MySQLAuth core) +if(SQLITE_VERSION VERSION_LESS 3.3) + message(FATAL_ERROR "SQLite version 3.3 or higher is required") +else() + add_library(MySQLAuth SHARED mysql_auth.c dbusers.c) + target_link_libraries(MySQLAuth maxscale-common MySQLCommon sqlite3) + set_target_properties(MySQLAuth PROPERTIES VERSION "1.0.0") + install_module(MySQLAuth core) +endif() diff --git a/server/modules/authenticator/MySQLAuth/dbusers.c b/server/modules/authenticator/MySQLAuth/dbusers.c index f52b1f539..b8f505809 100644 --- a/server/modules/authenticator/MySQLAuth/dbusers.c +++ b/server/modules/authenticator/MySQLAuth/dbusers.c @@ -421,109 +421,6 @@ retblock: return rc; } -int dump_user_cb(void *data, int fields, char **row, char **field_names) -{ - sqlite3 *handle = (sqlite3*)data; - add_mysql_user(handle, row[0], row[1], row[2], row[3] && strcmp(row[3], "1"), row[4]); - return 0; -} - -int dump_database_cb(void *data, int fields, char **row, char **field_names) -{ - sqlite3 *handle = (sqlite3*)data; - add_database(handle, row[0]); - return 0; -} - -static bool transfer_table_contents(sqlite3 *src, sqlite3 *dest) -{ - bool rval = true; - char *err; - - /** Make sure the tables exist in both databases */ - if (sqlite3_exec(src, users_create_sql, NULL, NULL, &err) != SQLITE_OK || - sqlite3_exec(dest, users_create_sql, NULL, NULL, &err) != SQLITE_OK || - sqlite3_exec(src, databases_create_sql, NULL, NULL, &err) != SQLITE_OK || - sqlite3_exec(dest, databases_create_sql, NULL, NULL, &err) != SQLITE_OK) - { - MXS_ERROR("Failed to create tables: %s", err); - sqlite3_free(err); - rval = false; - } - else if (sqlite3_exec(dest, "BEGIN", NULL, NULL, &err) != SQLITE_OK) - { - MXS_ERROR("Failed to start transaction: %s", err); - sqlite3_free(err); - rval = false; - } - else - { - /** Transaction is open */ - if (!delete_mysql_users(dest)) - { - rval = false; - } - else if (sqlite3_exec(src, dump_users_query, dump_user_cb, dest, &err) != SQLITE_OK || - sqlite3_exec(src, dump_databases_query, dump_database_cb, dest, &err) != SQLITE_OK) - { - MXS_ERROR("Failed to load database contents: %s", err); - sqlite3_free(err); - rval = false; - } - if (rval) - { - if (sqlite3_exec(dest, "COMMIT", NULL, NULL, &err) != SQLITE_OK) - { - MXS_ERROR("Failed to commit transaction: %s", err); - sqlite3_free(err); - rval = false; - } - } - else - { - if (sqlite3_exec(dest, "ROLLBACK", NULL, NULL, &err) != SQLITE_OK) - { - MXS_ERROR("Failed to rollback transaction: %s", err); - sqlite3_free(err); - rval = false; - } - } - } - return rval; -} - -bool dbusers_load(sqlite3 *dest, const char *filename) -{ - sqlite3 *src; - - if (sqlite3_open_v2(filename, &src, db_flags, NULL) != SQLITE_OK) - { - MXS_ERROR("Failed to open persisted SQLite3 database."); - return false; - } - - bool rval = transfer_table_contents(src, dest); - sqlite3_close_v2(src); - - return rval; -} - -bool dbusers_save(sqlite3 *src, const char *filename) -{ - sqlite3 *dest; - - if (sqlite3_open_v2(filename, &dest, db_flags, NULL) != SQLITE_OK) - { - MXS_ERROR("Failed to open persisted SQLite3 database."); - return -1; - } - - bool rval = transfer_table_contents(src, dest); - sqlite3_close_v2(dest); - - return rval; -} - /** * @brief Check service permissions on one server * @@ -752,9 +649,28 @@ static bool get_hostname(const char *ip_address, char *client_hostname) return false; } +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, SERVICE *service, SERV_LISTENER *listener) { - if (server->server->server_string == NULL) { const char *server_string = mysql_get_server_info(con); @@ -764,7 +680,6 @@ int get_users_from_server(MYSQL *con, SERVER_REF *server, SERVICE *service, SERV } } - /** Testing new users query */ char *query = get_new_users_query(server->server->server_string, service->enable_root); MYSQL_AUTH *instance = (MYSQL_AUTH*)listener->auth_instance; bool anon_user = false; @@ -778,7 +693,12 @@ int get_users_from_server(MYSQL *con, SERVER_REF *server, SERVICE *service, SERV if (result) { + start_sqlite_transaction(instance->handle); + + /** Delete the old users */ + delete_mysql_users(instance->handle); MYSQL_ROW row; + while ((row = mysql_fetch_row(result))) { if (service->strip_db_esc) @@ -798,6 +718,8 @@ int get_users_from_server(MYSQL *con, SERVER_REF *server, SERVICE *service, SERV } } + commit_sqlite_transaction(instance->handle); + mysql_free_result(result); } } diff --git a/server/modules/authenticator/MySQLAuth/mysql_auth.c b/server/modules/authenticator/MySQLAuth/mysql_auth.c index ca1517db9..a29487419 100644 --- a/server/modules/authenticator/MySQLAuth/mysql_auth.c +++ b/server/modules/authenticator/MySQLAuth/mysql_auth.c @@ -106,6 +106,66 @@ MXS_MODULE* MXS_CREATE_MODULE() return &info; } +static void get_database_path(SERV_LISTENER *port, char *dest) +{ + MYSQL_AUTH *instance = port->auth_instance; + SERVICE *service = port->service; + + if (instance->cache_dir) + { + snprintf(dest, sizeof(dest) - sizeof(DBUSERS_FILE) - 1, "%s/", instance->cache_dir); + } + else + { + sprintf(dest, "%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) + { + MXS_ERROR("Failed to open SQLite3 handle."); + return false; + } + + char *err; + + if (sqlite3_exec(*handle, users_create_sql, NULL, NULL, &err) != SQLITE_OK || + sqlite3_exec(*handle, databases_create_sql, NULL, NULL, &err) != SQLITE_OK || + sqlite3_exec(*handle, pragma_sql, NULL, NULL, &err) != SQLITE_OK) + { + MXS_ERROR("Failed to create database: %s", err); + sqlite3_free(err); + sqlite3_close_v2(*handle); + return false; + } + + return true; +} + +static bool open_client_database(const char *path, sqlite3 **handle) +{ + bool rval = false; + + if (sqlite3_open_v2(path, handle, db_flags, NULL) == SQLITE_OK) + { + sqlite3_busy_timeout(*handle, MXS_SQLITE_BUSY_TIMEOUT); + rval = true; + } + else + { + MXS_ERROR("Failed to open SQLite3 handle."); + } + + return rval; +} + /** * @brief Initialize the authenticator instance * @@ -122,25 +182,7 @@ static void* mysql_auth_init(char **options) instance->cache_dir = NULL; instance->inject_service_user = true; 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, users_create_sql, NULL, NULL, &err) != SQLITE_OK || - sqlite3_exec(instance->handle, databases_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; - } + instance->handle = NULL; for (int i = 0; options[i]; i++) { @@ -196,16 +238,7 @@ static void* mysql_auth_create(void *instance) 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; - } + rval->handle = NULL; } return rval; @@ -336,6 +369,18 @@ mysql_auth_set_protocol_data(DCB *dcb, GWBUF *buf) MySQLProtocol *protocol = NULL; MYSQL_session *client_data = NULL; int client_auth_packet_size = 0; + mysql_auth_t *auth_ses = (mysql_auth_t*)dcb->authenticator_data; + + if (auth_ses->handle == NULL) + { + char path[PATH_MAX]; + get_database_path(dcb->listener, path); + + if (!open_client_database(path, &auth_ses->handle)) + { + return MXS_AUTH_FAILED; + } + } protocol = DCB_PROTOCOL(dcb, MySQLProtocol); CHK_PROTOCOL(protocol); @@ -543,38 +588,23 @@ static int mysql_auth_load_users(SERV_LISTENER *port) return MXS_AUTH_LOADUSERS_FATAL; } - int loaded = replace_mysql_users(port); - char path[PATH_MAX]; + if (instance->handle == NULL) + { + char path[PATH_MAX]; + get_database_path(port, path); + if (!open_instance_database(path, &instance->handle)) + { + return MXS_AUTH_LOADUSERS_FATAL; + } + } - if (instance->cache_dir) - { - snprintf(path, sizeof(path) - sizeof(DBUSERS_FILE) - 1, "%s/", instance->cache_dir); - } - else - { - sprintf(path, "%s/%s/%s/%s/", get_cachedir(), service->name, port->name, DBUSERS_DIR); - } + int loaded = replace_mysql_users(port); if (loaded < 0) { MXS_ERROR("[%s] Unable to load users for listener %s listening at %s:%d.", service->name, port->name, port->address ? port->address : "0.0.0.0", port->port); - if (mxs_mkdir_all(path, S_IRWXU)) - { - strcat(path, DBUSERS_FILE); - - if (!dbusers_load(instance->handle, path)) - { - MXS_ERROR("[%s] Failed to load cached users from '%s'.", service->name, path); - rc = MXS_AUTH_LOADUSERS_ERROR; - } - else - { - MXS_WARNING("[%s] Using cached credential information from '%s'.", service->name, path); - } - } - if (instance->inject_service_user) { /** Inject the service user as a 'backup' user that's available @@ -585,16 +615,6 @@ static int mysql_auth_load_users(SERV_LISTENER *port) } } } - else - { - /* Users loaded successfully, save authentication data to file cache */ - if (mxs_mkdir_all(path, S_IRWXU)) - { - strcat(path, DBUSERS_FILE); - dbusers_save(instance->handle, path); - MXS_INFO("[%s] Storing cached credential information at '%s'.", service->name, path); - } - } if (loaded == 0) { diff --git a/server/modules/authenticator/MySQLAuth/mysql_auth.h b/server/modules/authenticator/MySQLAuth/mysql_auth.h index 328ee8d87..f3f2c9504 100644 --- a/server/modules/authenticator/MySQLAuth/mysql_auth.h +++ b/server/modules/authenticator/MySQLAuth/mysql_auth.h @@ -42,8 +42,6 @@ MXS_BEGIN_DECLS static const char DBUSERS_DIR[] = "cache"; static const char DBUSERS_FILE[] = "dbusers.db"; -#define MYSQLAUTH_DATABASE_NAME "file:mysqlauth.db?mode=memory&cache=shared" - /** The table name where we store the users */ #define MYSQLAUTH_USERS_TABLE_NAME "mysqlauth_users" @@ -59,6 +57,9 @@ static const char users_create_sql[] = static const char databases_create_sql[] = "CREATE TABLE IF NOT EXISTS " MYSQLAUTH_DATABASES_TABLE_NAME "(db varchar(255))"; +/** PRAGMA configuration options for SQLite */ +static const char pragma_sql[] = "PRAGMA JOURNAL_MODE=MEMORY"; + /** Query that checks if there's a grant for the user being authenticated */ static const char mysqlauth_validate_user_query[] = "SELECT password FROM " MYSQLAUTH_USERS_TABLE_NAME @@ -95,7 +96,6 @@ static const char null_token[] = "NULL"; /** Flags for sqlite3_open_v2() */ static int db_flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | - SQLITE_OPEN_URI | SQLITE_OPEN_SHAREDCACHE; typedef struct mysql_auth From 82247f91439fdf691aa0bc612a0160e37b11d2bc Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Mon, 13 Feb 2017 16:13:26 +0200 Subject: [PATCH 22/38] Add mxs_strerror Thread-safe version of strerror; thread local buffer used for storing the message. The performance penalty of a thread local buffer is not likely to be significant, since this is only called in an error situation that anyway is likely to interrupt the normal processing. --- include/maxscale/log_manager.h | 17 +++++++++++++++++ server/core/log_manager.cc | 8 ++++++++ 2 files changed, 25 insertions(+) diff --git a/include/maxscale/log_manager.h b/include/maxscale/log_manager.h index a3a2ea896..329155dc7 100644 --- a/include/maxscale/log_manager.h +++ b/include/maxscale/log_manager.h @@ -196,4 +196,21 @@ enum trailing NULL. If longer, it will be cut. */ }; +/** + * Return a thread specific pointer to a string describing the error + * code passed as argument. The string is obtained using strerror_r. + * + * @param error One of the errno error codes. + * + * @return Thread specific pointer to string describing the error code. + * + * @attention The function is thread safe, but not re-entrant. That is, + * calling it twice with different error codes between two sequence points + * will not work. E.g: + * + * printf("EINVAL = %s, EACCESS = %s", + * mxs_strerror(EINVAL), mxs_strerror(EACCESS)); + */ +const char* mxs_strerror(int error); + MXS_END_DECLS diff --git a/server/core/log_manager.cc b/server/core/log_manager.cc index 053444db5..b106c4fba 100644 --- a/server/core/log_manager.cc +++ b/server/core/log_manager.cc @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -3011,3 +3012,10 @@ int mxs_log_message(int priority, return err; } + +const char* mxs_strerror(int error) +{ + static thread_local char errbuf[MXS_STRERROR_BUFLEN]; + + return strerror_r(error, errbuf, sizeof(errbuf)); +} From 37dd561470777cd5105799f4354a50076e298e85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Mon, 6 Mar 2017 10:20:45 +0200 Subject: [PATCH 23/38] Add support for IPv6 Both the listeners and servers now support IPv6 addresses. The namedserverfilter does not yet use the new structures and needs to be fixed in a following commit. --- include/maxscale/dcb.h | 12 +- include/maxscale/utils.h | 24 +++- server/core/config.c | 1 - server/core/dcb.c | 65 +++++++--- server/core/service.c | 4 +- server/core/utils.c | 114 ++++++------------ .../authenticator/MySQLAuth/mysql_auth.c | 10 +- .../namedserverfilter/namedserverfilter.c | 11 +- .../MySQL/MySQLBackend/mysql_backend.c | 62 +++++----- server/modules/routing/avrorouter/avro.c | 2 +- server/modules/routing/binlogrouter/blr.c | 4 +- .../modules/routing/binlogrouter/blr_master.c | 4 +- .../modules/routing/binlogrouter/blr_slave.c | 24 ++-- 13 files changed, 167 insertions(+), 170 deletions(-) diff --git a/include/maxscale/dcb.h b/include/maxscale/dcb.h index 994508011..a65cf3840 100644 --- a/include/maxscale/dcb.h +++ b/include/maxscale/dcb.h @@ -197,7 +197,7 @@ typedef struct dcb int flags; /**< DCB flags */ char *remote; /**< Address of remote end */ char *user; /**< User name for connection */ - struct sockaddr_in ipv4; /**< remote end IPv4 address */ + struct sockaddr_storage ip; /**< remote IPv4/IPv6 address */ char *protoname; /**< Name of the protocol */ void *protocol; /**< The protocol specific state */ size_t protocol_packet_length; /**< How long the protocol specific packet is */ @@ -240,7 +240,7 @@ typedef struct dcb } DCB; #define DCB_INIT {.dcb_chk_top = CHK_NUM_DCB, \ - .evq = DCBEVENTQ_INIT, .ipv4 = {0}, .func = {0}, .authfunc = {0}, \ + .evq = DCBEVENTQ_INIT, .ip = {0}, .func = {0}, .authfunc = {0}, \ .stats = {0}, .memdata = DCBMM_INIT, \ .fd = DCBFD_CLOSED, .stats = DCBSTATS_INIT, .ssl_state = SSL_HANDSHAKE_UNKNOWN, \ .state = DCB_STATE_ALLOC, .dcb_chk_tail = CHK_NUM_DCB, \ @@ -343,6 +343,14 @@ void dcb_process_idle_sessions(int thr); */ bool dcb_foreach(bool (*func)(DCB *, void *), void *data); +/** + * @brief Return the port number this DCB is connected to + * + * @param dcb DCB to inspect + * @return Port number the DCB is connected to or -1 if information is not available + */ +int dcb_get_port(const DCB *dcb); + /** * DCB flags values */ diff --git a/include/maxscale/utils.h b/include/maxscale/utils.h index 9b9d65070..ca32996ec 100644 --- a/include/maxscale/utils.h +++ b/include/maxscale/utils.h @@ -38,10 +38,28 @@ MXS_BEGIN_DECLS bool utils_init(); /*< Call this first before using any other function */ void utils_end(); -int setnonblocking(int fd); -int parse_bindconfig(const char *, struct sockaddr_in *); -int setipaddress(struct in_addr *, char *); +/** + * Parse the bind configuration data. + * + * The configuration is passed as string in the `address|port` format. + * + * @param config The bind address and port separated by a '|' + * @param addr The sockaddr_in6 in which the data is written + * @return 1 on success, 0 on failure + */ +int parse_bindconfig(const char *, struct sockaddr_in6 *, int *); + +/** + * @brief Create a network socket and a socket configuration + * + * @param dest Pointer to a struct sockaddr_storage where the configuration is stored + * @param host The target host for which the socket is created + * + * @return The opened socket or -1 on failure + */int create_network_socket(struct sockaddr_storage *, char *); + +int setnonblocking(int fd); char *gw_strend(register const char *s); static char gw_randomchar(); int gw_generate_random_str(char *output, int len); diff --git a/server/core/config.c b/server/core/config.c index 433a4734c..932ef3bd2 100644 --- a/server/core/config.c +++ b/server/core/config.c @@ -83,7 +83,6 @@ typedef struct duplicate_context static bool duplicate_context_init(DUPLICATE_CONTEXT* context); static void duplicate_context_finish(DUPLICATE_CONTEXT* context); -extern int setipaddress(struct in_addr *, char *); static bool process_config_context(CONFIG_CONTEXT *); static bool process_config_update(CONFIG_CONTEXT *); static char *config_get_value(MXS_CONFIG_PARAMETER *, const char *); diff --git a/server/core/dcb.c b/server/core/dcb.c index 5c40e8f9d..dd4d20e9f 100644 --- a/server/core/dcb.c +++ b/server/core/dcb.c @@ -2865,25 +2865,30 @@ dcb_accept(DCB *listener) if (client_conn.ss_family == AF_UNIX) { // client address + // Should this be `localhost` like it is in the MariaDB server? client_dcb->remote = MXS_STRDUP_A("localhost_from_socket"); - // set localhost IP for user authentication - (client_dcb->ipv4).sin_addr.s_addr = 0x0100007F; } else { - /* client IPv4 in raw data*/ - memcpy(&client_dcb->ipv4, - (struct sockaddr_in *)&client_conn, - sizeof(struct sockaddr_in)); - /* client IPv4 in string representation */ - client_dcb->remote = (char *)MXS_CALLOC(INET_ADDRSTRLEN + 1, sizeof(char)); + /* client IP in raw data*/ + memcpy(&client_dcb->ip, &client_conn, sizeof(client_conn)); + /* client IP in string representation */ + client_dcb->remote = (char *)MXS_CALLOC(INET6_ADDRSTRLEN + 1, sizeof(char)); - if (client_dcb->remote != NULL) + if (client_dcb->remote) { - inet_ntop(AF_INET, - &(client_dcb->ipv4).sin_addr, - client_dcb->remote, - INET_ADDRSTRLEN); + void *ptr; + if (client_dcb->ip.ss_family == AF_INET) + { + ptr = &((struct sockaddr_in*)&client_dcb->ip)->sin_addr; + } + else + { + ptr = &((struct sockaddr_in6*)&client_dcb->ip)->sin6_addr; + } + + inet_ntop(client_dcb->ip.ss_family, ptr, + client_dcb->remote, INET6_ADDRSTRLEN); } } memcpy(&client_dcb->func, protocol_funcs, sizeof(MXS_PROTOCOL)); @@ -3077,7 +3082,7 @@ dcb_listen(DCB *listener, const char *config, const char *protocol_name) return -1; } - MXS_NOTICE("Listening connections at %s with protocol %s", config, protocol_name); + MXS_NOTICE("Listening for connections at %s with protocol %s", config, protocol_name); // assign listener_socket to dcb listener->fd = listener_socket; @@ -3105,18 +3110,18 @@ static int dcb_listen_create_socket_inet(const char *config_bind) { int listener_socket; - struct sockaddr_in server_address; + struct sockaddr_in6 server_address = {}; int one = 1; + int sock_type = 0; - memset(&server_address, 0, sizeof(server_address)); - if (!parse_bindconfig(config_bind, &server_address)) + if (!parse_bindconfig(config_bind, &server_address, &sock_type)) { MXS_ERROR("Error in parse_bindconfig for [%s]", config_bind); return -1; } /** Create the TCP socket */ - if ((listener_socket = socket(AF_INET, SOCK_STREAM, 0)) < 0) + if ((listener_socket = socket(sock_type, SOCK_STREAM, 0)) < 0) { char errbuf[MXS_STRERROR_BUFLEN]; MXS_ERROR("Can't create socket: %i, %s", @@ -3140,7 +3145,7 @@ dcb_listen_create_socket_inet(const char *config_bind) return -1; } - if (bind(listener_socket, (struct sockaddr *) &server_address, sizeof(server_address)) < 0) + if (bind(listener_socket, (struct sockaddr*)&server_address, sizeof(server_address)) < 0) { char errbuf[MXS_STRERROR_BUFLEN]; MXS_ERROR("Failed to bind on '%s': %i, %s", @@ -3460,3 +3465,25 @@ bool dcb_foreach(bool(*func)(DCB *, void *), void *data) return more; } + +int dcb_get_port(const DCB *dcb) +{ + int rval = -1; + + if (dcb->ip.ss_family == AF_INET) + { + struct sockaddr_in* ip = (struct sockaddr_in*)&dcb->ip; + rval = ntohs(ip->sin_port); + } + else if (dcb->ip.ss_family == AF_INET6) + { + struct sockaddr_in6* ip = (struct sockaddr_in6*)&dcb->ip; + rval = ntohs(ip->sin6_port); + } + else + { + ss_dassert(dcb->ip.ss_family == AF_UNIX); + } + + return rval; +} diff --git a/server/core/service.c b/server/core/service.c index 26e1e71a4..6d3b7a7bf 100644 --- a/server/core/service.c +++ b/server/core/service.c @@ -307,11 +307,11 @@ serviceStartPort(SERVICE *service, SERV_LISTENER *port) if (port->address) { - sprintf(config_bind, "%s:%d", port->address, port->port); + sprintf(config_bind, "%s|%d", port->address, port->port); } else { - sprintf(config_bind, "0.0.0.0:%d", port->port); + sprintf(config_bind, "0.0.0.0|%d", port->port); } /** Load the authentication users before before starting the listener */ diff --git a/server/core/utils.c b/server/core/utils.c index 1c60ca56c..fa3f73ba6 100644 --- a/server/core/utils.c +++ b/server/core/utils.c @@ -895,113 +895,70 @@ void utils_end() SPINLOCK tmplock = SPINLOCK_INIT; -/* - * Set IP address in socket structure in_addr - * - * @param a Pointer to a struct in_addr into which the address is written - * @param p The hostname to lookup - * @return 1 on success, 0 on failure - */ -int -setipaddress(struct in_addr *a, char *p) +int create_network_socket(struct sockaddr_storage *dest, char *host) { #ifdef __USE_POSIX - struct addrinfo *ai = NULL, hint; - int rc; - struct sockaddr_in *res_addr; - memset(&hint, 0, sizeof (hint)); - + struct addrinfo *ai = NULL, hint = {}; + int so, rc; hint.ai_socktype = SOCK_STREAM; + hint.ai_family = AF_UNSPEC; - /* - * This is for the listening socket, matching INADDR_ANY only for now. - * For future specific addresses bind, a dedicated routine woulbd be better - */ - - if (strcmp(p, "0.0.0.0") == 0) + if (strcmp(host, "0.0.0.0") == 0 || strcmp(host, "::") == 0) { + /** All interfaces */ hint.ai_flags = AI_PASSIVE; - hint.ai_family = AF_UNSPEC; - if ((rc = getaddrinfo(p, NULL, &hint, &ai)) != 0) + if ((rc = getaddrinfo(host, NULL, &hint, &ai)) != 0) { MXS_ERROR("Failed to obtain address for host %s, %s", - p, + host, gai_strerror(rc)); - return 0; + return -1; } } else { - hint.ai_flags = AI_CANONNAME; - hint.ai_family = AF_INET; - - if ((rc = getaddrinfo(p, NULL, &hint, &ai)) != 0) + hint.ai_flags = AI_ALL; + if ((rc = getaddrinfo(host, NULL, &hint, &ai)) != 0) { MXS_ERROR("Failed to obtain address for host %s, %s", - p, + host, gai_strerror(rc)); - return 0; + return -1; } } /* take the first one */ if (ai != NULL) { - res_addr = (struct sockaddr_in *)(ai->ai_addr); - memcpy(a, &res_addr->sin_addr, sizeof(struct in_addr)); + so = socket(ai->ai_family, SOCK_STREAM, 0); - freeaddrinfo(ai); - - return 1; - } -#else - struct hostent *h; - - spinlock_acquire(&tmplock); - h = gethostbyname(p); - spinlock_release(&tmplock); - - if (h == NULL) - { - if ((a->s_addr = inet_addr(p)) == -1) + if (so < 0) { - MXS_ERROR("gethostbyname failed for [%s]", p); - - return 0; + char errbuf[MXS_STRERROR_BUFLEN]; + MXS_ERROR("Socket creation failed: %d, %s.", + errno, strerror_r(errno, errbuf, sizeof(errbuf))); + } + else + { + memcpy(dest, ai->ai_addr, ai->ai_addrlen); + freeaddrinfo(ai); } } - else - { - /* take the first one */ - memcpy(a, h->h_addr, h->h_length); - - return 1; - } +#else +#error Only the POSIX networking interface is supported #endif - return 0; + return so; } - -/** - * Parse the bind config data. This is passed in a string as address:port. - * - * The address may be either a . separated IP address or a hostname to - * lookup. The address 0.0.0.0 is the wildcard address for SOCKADR_ANY. - * The ':' and port are required. - * - * @param config The bind address and port separated by a ':' - * @param addr The sockaddr_in in which the data is written - * @return 0 on failure - */ -int -parse_bindconfig(const char *config, struct sockaddr_in *addr) +int parse_bindconfig(const char *config, struct sockaddr_in6 *addr, int *sock_type) { char buf[strlen(config) + 1]; strcpy(buf, config); + *sock_type = strchr(buf, '.') ? AF_INET : AF_INET6; - char *port = strrchr(buf, ':'); + char *port = strrchr(buf, '|'); short pnum; if (port) { @@ -1014,19 +971,20 @@ parse_bindconfig(const char *config, struct sockaddr_in *addr) return 0; } - if (!strcmp(buf, "0.0.0.0")) + if (!strcmp(buf, "0.0.0.0") || !strcmp(buf, "::")) { - addr->sin_addr.s_addr = htonl(INADDR_ANY); + *sock_type = AF_INET6; + addr->sin6_addr = in6addr_any; } else { - if (!inet_aton(buf, &addr->sin_addr)) + if (inet_pton(*sock_type, buf, &addr->sin6_addr) < 1) { struct hostent *hp = gethostbyname(buf); if (hp) { - bcopy(hp->h_addr, &(addr->sin_addr.s_addr), hp->h_length); + inet_pton(*sock_type, hp->h_addr_list[0], &addr->sin6_addr); } else { @@ -1036,8 +994,8 @@ parse_bindconfig(const char *config, struct sockaddr_in *addr) } } - addr->sin_family = AF_INET; - addr->sin_port = htons(pnum); + addr->sin6_family = *sock_type; + addr->sin6_port = htons(pnum); return 1; } diff --git a/server/modules/authenticator/MySQLAuth/mysql_auth.c b/server/modules/authenticator/MySQLAuth/mysql_auth.c index a29487419..41953c377 100644 --- a/server/modules/authenticator/MySQLAuth/mysql_auth.c +++ b/server/modules/authenticator/MySQLAuth/mysql_auth.c @@ -325,15 +325,7 @@ mysql_auth_authenticate(DCB *dcb) else if (dcb->service->log_auth_warnings) { MXS_WARNING("%s: login attempt for user '%s'@%s:%d, authentication failed.", - dcb->service->name, client_data->user, dcb->remote, ntohs(dcb->ipv4.sin_port)); - if (dcb->ipv4.sin_addr.s_addr == 0x0100007F && - !dcb->service->localhost_match_wildcard_host) - { - MXS_NOTICE("If you have a wildcard grant that covers" - " this address, try adding " - "'localhost_match_wildcard_host=true' for " - "service '%s'. ", dcb->service->name); - } + dcb->service->name, client_data->user, dcb->remote, dcb_get_port(dcb)); } /* let's free the auth_token now */ diff --git a/server/modules/filter/namedserverfilter/namedserverfilter.c b/server/modules/filter/namedserverfilter/namedserverfilter.c index 6d2f6e864..46be8e27f 100644 --- a/server/modules/filter/namedserverfilter/namedserverfilter.c +++ b/server/modules/filter/namedserverfilter/namedserverfilter.c @@ -74,7 +74,7 @@ typedef struct static bool validate_ip_address(const char *); static int check_source_host(REGEXHINT_INSTANCE *, const char *, - const struct sockaddr_in *); + const struct sockaddr_storage *); static REGEXHINT_SOURCE_HOST *set_source_address(const char *); static void free_instance(REGEXHINT_INSTANCE *); @@ -237,7 +237,7 @@ newSession(MXS_FILTER *instance, MXS_SESSION *session) { my_session->active = check_source_host(my_instance, remote, - &session->client_dcb->ipv4); + &session->client_dcb->ip); } /* Check client user against 'user' option */ @@ -446,12 +446,12 @@ static bool validate_ip_address(const char *host) */ static int check_source_host(REGEXHINT_INSTANCE *instance, const char *remote, - const struct sockaddr_in *ipv4) + const struct sockaddr_storage *ip) { int ret = 0; struct sockaddr_in check_ipv4; - memcpy(&check_ipv4, ipv4, sizeof(check_ipv4)); + memcpy(&check_ipv4, ip, sizeof(check_ipv4)); switch (instance->source->netmask) { @@ -562,7 +562,8 @@ static REGEXHINT_SOURCE_HOST *set_source_address(const char *input_host) source_host->netmask = netmask; /* fill IPv4 data struct */ - if (setipaddress(&source_host->ipv4.sin_addr, format_host) && strlen(format_host)) + //TODO: Fix this + if (false /* setipaddress(&source_host->ipv4.sin_addr, format_host) && strlen(format_host)*/) { /* if netmask < 32 there are % wildcards */ diff --git a/server/modules/protocol/MySQL/MySQLBackend/mysql_backend.c b/server/modules/protocol/MySQL/MySQLBackend/mysql_backend.c index bedd8eb0e..907156ec9 100644 --- a/server/modules/protocol/MySQL/MySQLBackend/mysql_backend.c +++ b/server/modules/protocol/MySQL/MySQLBackend/mysql_backend.c @@ -271,34 +271,35 @@ return_fd: * backend server. In failure, fd == -1 and socket is closed. * */ -static int -gw_do_connect_to_backend(char *host, int port, int *fd) +static int gw_do_connect_to_backend(char *host, int port, int *fd) { - struct sockaddr_in serv_addr; - int rv; - int so = 0; + struct sockaddr_storage serv_addr = {}; + int rv = -1; int bufsize; - memset(&serv_addr, 0, sizeof serv_addr); - serv_addr.sin_family = (int)AF_INET; - so = socket((int)AF_INET, (int)SOCK_STREAM, 0); + /* prepare for connect */ + int so = create_network_socket(&serv_addr, host); if (so < 0) { char errbuf[MXS_STRERROR_BUFLEN]; - MXS_ERROR("Establishing connection to backend server " - "%s:%d failed.\n\t\t Socket creation failed " - "due %d, %s.", - host, - port, - errno, - strerror_r(errno, errbuf, sizeof(errbuf))); - rv = -1; - goto return_rv; + MXS_ERROR("Establishing connection to backend server %s:%d failed.", host, port); + return rv; } - /* prepare for connect */ - setipaddress(&serv_addr.sin_addr, host); - serv_addr.sin_port = htons(port); + + /** Configure the destination port */ + if (serv_addr.ss_family == AF_INET) + { + struct sockaddr_in *ip = (struct sockaddr_in*)&serv_addr; + ip->sin_port = htons(port); + } + else + { + ss_dassert(serv_addr.ss_family == AF_INET6); + struct sockaddr_in6 *ip = (struct sockaddr_in6*)&serv_addr; + ip->sin6_port = htons(port); + } + bufsize = MXS_BACKEND_SO_SNDBUF; if (setsockopt(so, SOL_SOCKET, SO_SNDBUF, &bufsize, sizeof(bufsize)) != 0) @@ -311,10 +312,9 @@ gw_do_connect_to_backend(char *host, int port, int *fd) port, errno, strerror_r(errno, errbuf, sizeof(errbuf))); - rv = -1; /** Close socket */ close_socket(so); - goto return_rv; + return rv; } bufsize = MXS_BACKEND_SO_RCVBUF; @@ -328,10 +328,9 @@ gw_do_connect_to_backend(char *host, int port, int *fd) port, errno, strerror_r(errno, errbuf, sizeof(errbuf))); - rv = -1; /** Close socket */ close_socket(so); - goto return_rv; + return rv; } int one = 1; @@ -345,10 +344,9 @@ gw_do_connect_to_backend(char *host, int port, int *fd) port, errno, strerror_r(errno, errbuf, sizeof(errbuf))); - rv = -1; /** Close socket */ close_socket(so); - goto return_rv; + return rv; } /* set socket to as non-blocking here */ @@ -364,23 +362,19 @@ gw_do_connect_to_backend(char *host, int port, int *fd) else { char errbuf[MXS_STRERROR_BUFLEN]; - MXS_ERROR("Failed to connect backend server %s:%d, " - "due %d, %s.", - host, - port, - errno, - strerror_r(errno, errbuf, sizeof(errbuf))); + MXS_ERROR("Failed to connect backend server %s:%d due to: %d, %s.", + host, port, errno, strerror_r(errno, errbuf, sizeof(errbuf))); /** Close socket */ close_socket(so); - goto return_rv; + return rv; } } + *fd = so; MXS_DEBUG("%lu [gw_do_connect_to_backend] Connected to backend server " "%s:%d, fd %d.", pthread_self(), host, port, so); -return_rv: return rv; } diff --git a/server/modules/routing/avrorouter/avro.c b/server/modules/routing/avrorouter/avro.c index f9ca49f74..20cc5a9b1 100644 --- a/server/modules/routing/avrorouter/avro.c +++ b/server/modules/routing/avrorouter/avro.c @@ -893,7 +893,7 @@ diagnostics(MXS_ROUTER *router, DCB *dcb) dcb_printf(dcb, "\t\tClient UUID: %s\n", session->uuid); dcb_printf(dcb, "\t\tClient_host_port: %s:%d\n", - session->dcb->remote, ntohs((session->dcb->ipv4).sin_port)); + session->dcb->remote, dcb_get_port(session->dcb)); dcb_printf(dcb, "\t\tUsername: %s\n", session->dcb->user); dcb_printf(dcb, "\t\tClient DCB: %p\n", session->dcb); dcb_printf(dcb, "\t\tClient protocol: %s\n", diff --git a/server/modules/routing/binlogrouter/blr.c b/server/modules/routing/binlogrouter/blr.c index 67d3ef9bb..ab6ea16b0 100644 --- a/server/modules/routing/binlogrouter/blr.c +++ b/server/modules/routing/binlogrouter/blr.c @@ -1132,7 +1132,7 @@ closeSession(MXS_ROUTER *instance, MXS_ROUTER_SESSION *router_session) MXS_NOTICE("%s: Slave %s:%d, server id %d, disconnected after %ld seconds. " "%d SQL commands, %d events sent (%lu bytes), binlog '%s', " "last position %lu", - router->service->name, slave->dcb->remote, ntohs((slave->dcb->ipv4).sin_port), + router->service->name, slave->dcb->remote, dcb_get_port(slave->dcb), slave->serverid, time(0) - slave->connect_time, slave->stats.n_queries, @@ -1533,7 +1533,7 @@ diagnostics(MXS_ROUTER *router, DCB *dcb) } dcb_printf(dcb, "\t\tSlave_host_port: %s:%d\n", - session->dcb->remote, ntohs((session->dcb->ipv4).sin_port)); + session->dcb->remote, dcb_get_port(session->dcb)); dcb_printf(dcb, "\t\tUsername: %s\n", session->dcb->user); diff --git a/server/modules/routing/binlogrouter/blr_master.c b/server/modules/routing/binlogrouter/blr_master.c index 7febbfd24..65aa0e5cb 100644 --- a/server/modules/routing/binlogrouter/blr_master.c +++ b/server/modules/routing/binlogrouter/blr_master.c @@ -2347,7 +2347,7 @@ bool blr_send_event(blr_thread_role_t role, "the event has already been sent by thread %lu in the role of %s. " "%u bytes buffered for writing in DCB %p. %lu events received from master.", slave->dcb->remote, - ntohs((slave->dcb->ipv4).sin_port), + dcb_get_port(slave->dcb), slave->serverid, binlog_name, binlog_pos, @@ -2410,7 +2410,7 @@ bool blr_send_event(blr_thread_role_t role, { MXS_ERROR("Failed to send an event of %u bytes to slave at %s:%d.", hdr->event_size, slave->dcb->remote, - ntohs(slave->dcb->ipv4.sin_port)); + dcb_get_port(slave->dcb)); } return rval; } diff --git a/server/modules/routing/binlogrouter/blr_slave.c b/server/modules/routing/binlogrouter/blr_slave.c index 24a210cb5..696cf557c 100644 --- a/server/modules/routing/binlogrouter/blr_slave.c +++ b/server/modules/routing/binlogrouter/blr_slave.c @@ -2055,7 +2055,7 @@ blr_slave_binlog_dump(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, GWBUF *queue "Latest safe position %lu, end of binlog file %lu", router->service->name, slave->dcb->remote, - ntohs((slave->dcb->ipv4).sin_port), + dcb_get_port(slave->dcb), slave->serverid, slave->binlogfile, (unsigned long)slave->binlog_pos, @@ -2176,7 +2176,7 @@ blr_slave_binlog_dump(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, GWBUF *queue MXS_NOTICE("%s: Slave %s:%d, server id %d requested binlog file %s from position %lu", router->service->name, slave->dcb->remote, - ntohs((slave->dcb->ipv4).sin_port), + dcb_get_port(slave->dcb), slave->serverid, slave->binlogfile, (unsigned long)slave->binlog_pos); @@ -2340,7 +2340,7 @@ blr_slave_catchup(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, bool large) } MXS_ERROR("Slave %s:%i, server-id %d, binlog '%s': blr_slave_catchup " "failed to open binlog file", - slave->dcb->remote, ntohs((slave->dcb->ipv4).sin_port), slave->serverid, + slave->dcb->remote, dcb_get_port(slave->dcb), slave->serverid, slave->binlogfile); slave->cstate &= ~CS_BUSY; @@ -2464,7 +2464,7 @@ blr_slave_catchup(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, bool large) MXS_ERROR("Slave %s:%i, server-id %d, binlog '%s': blr_slave_catchup " "failed to open binlog file in rotate event", slave->dcb->remote, - ntohs((slave->dcb->ipv4).sin_port), + dcb_get_port(slave->dcb), slave->serverid, slave->binlogfile); @@ -2503,7 +2503,7 @@ blr_slave_catchup(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, bool large) MXS_WARNING("Slave %s:%i, server-id %d, binlog '%s, position %u: " "Slave-thread could not send event to slave, closing connection.", slave->dcb->remote, - ntohs((slave->dcb->ipv4).sin_port), + dcb_get_port(slave->dcb), slave->serverid, binlog_name, binlog_pos); @@ -2538,7 +2538,7 @@ blr_slave_catchup(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, bool large) MXS_ERROR("%s Slave %s:%i, server-id %d, binlog '%s', %s", router->service->name, slave->dcb->remote, - ntohs((slave->dcb->ipv4).sin_port), + dcb_get_port(slave->dcb), slave->serverid, slave->binlogfile, read_errmsg); @@ -2549,7 +2549,7 @@ blr_slave_catchup(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, bool large) MXS_ERROR("%s Slave %s:%i, server-id %d, binlog '%s', %s", router->service->name, slave->dcb->remote, - ntohs((slave->dcb->ipv4).sin_port), + dcb_get_port(slave->dcb), slave->serverid, slave->binlogfile, read_errmsg); @@ -2571,7 +2571,7 @@ blr_slave_catchup(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, bool large) MXS_ERROR("%s Slave %s:%i, server-id %d, binlog '%s', %s", router->service->name, slave->dcb->remote, - ntohs((slave->dcb->ipv4).sin_port), + dcb_get_port(slave->dcb), slave->serverid, slave->binlogfile, read_errmsg); @@ -2601,7 +2601,7 @@ blr_slave_catchup(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, bool large) "current committed transaction event being sent: %lu, %s", router->service->name, slave->dcb->remote, - ntohs((slave->dcb->ipv4).sin_port), + dcb_get_port(slave->dcb), slave->serverid, slave->binlogfile, slave->stats.n_events - events_before, @@ -2676,7 +2676,7 @@ blr_slave_catchup(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, bool large) "previous failure of the master.", router->service->name, slave->dcb->remote, - ntohs((slave->dcb->ipv4).sin_port), + dcb_get_port(slave->dcb), slave->serverid, slave->binlogfile, (unsigned long)slave->binlog_pos, router->binlog_name, router->binlog_position); @@ -2921,7 +2921,7 @@ blr_slave_read_fde(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave) { MXS_ERROR("Slave %s:%i, server-id %d, binlog '%s', blr_read_binlog failure: %s", slave->dcb->remote, - ntohs((slave->dcb->ipv4).sin_port), + dcb_get_port(slave->dcb), slave->serverid, slave->binlogfile, err_msg); @@ -5882,7 +5882,7 @@ blr_slave_read_ste(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, uint32_t fde_en { MXS_ERROR("Slave %s:%i, server-id %d, binlog '%s', blr_read_binlog failure: %s", slave->dcb->remote, - ntohs((slave->dcb->ipv4).sin_port), + dcb_get_port(slave->dcb), slave->serverid, slave->binlogfile, err_msg); From 66ba7f3c80ad9d4f518411bb15092ad2531753f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Mon, 6 Mar 2017 12:24:12 +0200 Subject: [PATCH 24/38] Simplify network socket creation code The socket creation code in mysql_backend.c wasn't MySQL specific and it could be used for all non-blocking network connections. Thus, it makes sense to move it to a common file where other protocol modules can use it. The address resolution code now uses `getaddrinfo` to resolve all addresses instead of manually handling wildcard hosts. This allows the same code to be used for all addresses. --- include/maxscale/utils.h | 10 +- server/core/dcb.c | 17 +-- server/core/utils.c | 131 +++++++++++------- .../MySQL/MySQLBackend/mysql_backend.c | 86 +----------- 4 files changed, 98 insertions(+), 146 deletions(-) diff --git a/include/maxscale/utils.h b/include/maxscale/utils.h index ca32996ec..5aaabd729 100644 --- a/include/maxscale/utils.h +++ b/include/maxscale/utils.h @@ -44,10 +44,10 @@ void utils_end(); * The configuration is passed as string in the `address|port` format. * * @param config The bind address and port separated by a '|' - * @param addr The sockaddr_in6 in which the data is written - * @return 1 on success, 0 on failure + * @param addr The struct sockaddr_storage in which the data is written + * @return True on success, false on failure */ -int parse_bindconfig(const char *, struct sockaddr_in6 *, int *); +bool parse_bindconfig(const char *config, struct sockaddr_storage *addr); /** @@ -55,9 +55,11 @@ int parse_bindconfig(const char *, struct sockaddr_in6 *, int *); * * @param dest Pointer to a struct sockaddr_storage where the configuration is stored * @param host The target host for which the socket is created + * @param port The target port on the host * * @return The opened socket or -1 on failure - */int create_network_socket(struct sockaddr_storage *, char *); + */ +int open_network_socket(struct sockaddr_storage *dest, char *host, uint16_t port); int setnonblocking(int fd); char *gw_strend(register const char *s); diff --git a/server/core/dcb.c b/server/core/dcb.c index dd4d20e9f..e83ba0f3e 100644 --- a/server/core/dcb.c +++ b/server/core/dcb.c @@ -3109,27 +3109,28 @@ dcb_listen(DCB *listener, const char *config, const char *protocol_name) static int dcb_listen_create_socket_inet(const char *config_bind) { - int listener_socket; - struct sockaddr_in6 server_address = {}; - int one = 1; - int sock_type = 0; + struct sockaddr_storage server_address = {}; - if (!parse_bindconfig(config_bind, &server_address, &sock_type)) + if (!parse_bindconfig(config_bind, &server_address)) { MXS_ERROR("Error in parse_bindconfig for [%s]", config_bind); return -1; } + /** TODO: Move everything before the `bind` call to utils.c */ + /** Create the TCP socket */ - if ((listener_socket = socket(sock_type, SOCK_STREAM, 0)) < 0) + int listener_socket = socket(server_address.ss_family, SOCK_STREAM, 0); + + if (listener_socket < 0) { char errbuf[MXS_STRERROR_BUFLEN]; - MXS_ERROR("Can't create socket: %i, %s", - errno, + MXS_ERROR("Can't create socket: %d, %s", errno, strerror_r(errno, errbuf, sizeof(errbuf))); return -1; } + int one = 1; // socket options if (dcb_set_socket_option(listener_socket, SOL_SOCKET, SO_REUSEADDR, (char *) &one, sizeof(one)) != 0 || dcb_set_socket_option(listener_socket, IPPROTO_TCP, TCP_NODELAY, (char *) &one, sizeof(one)) != 0) diff --git a/server/core/utils.c b/server/core/utils.c index fa3f73ba6..f95f4fc7b 100644 --- a/server/core/utils.c +++ b/server/core/utils.c @@ -37,12 +37,13 @@ #include #include #include - +#include #include #include #include #include +#include #include #include #include @@ -895,42 +896,62 @@ void utils_end() SPINLOCK tmplock = SPINLOCK_INIT; -int create_network_socket(struct sockaddr_storage *dest, char *host) +static bool configure_socket(int so) +{ + int sndbufsize = MXS_BACKEND_SO_SNDBUF; + int rcvbufsize = MXS_BACKEND_SO_RCVBUF; + int one = 1; + + if (setsockopt(so, SOL_SOCKET, SO_SNDBUF, &sndbufsize, sizeof(sndbufsize)) != 0 || + setsockopt(so, SOL_SOCKET, SO_RCVBUF, &rcvbufsize, sizeof(rcvbufsize)) != 0 || + setsockopt(so, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one)) != 0) + { + char errbuf[MXS_STRERROR_BUFLEN]; + MXS_ERROR("Failed to set socket option: %d, %s.", + errno, strerror_r(errno, errbuf, sizeof(errbuf))); + return false; + } + + return setnonblocking(so) == 0; +} + +static void set_port(struct sockaddr_storage *addr, uint16_t port) +{ + if (addr->ss_family == AF_INET) + { + struct sockaddr_in *ip = (struct sockaddr_in*)addr; + ip->sin_port = htons(port); + } + else if (addr->ss_family == AF_INET6) + { + struct sockaddr_in6 *ip = (struct sockaddr_in6*)addr; + ip->sin6_port = htons(port); + } + else + { + MXS_ERROR("Unknown address family: %d", (int)addr->ss_family); + ss_dassert(false); + } +} + +int open_network_socket(struct sockaddr_storage *dest, char *host, uint16_t port) { #ifdef __USE_POSIX struct addrinfo *ai = NULL, hint = {}; int so, rc; hint.ai_socktype = SOCK_STREAM; hint.ai_family = AF_UNSPEC; + hint.ai_flags = AI_ALL; - if (strcmp(host, "0.0.0.0") == 0 || strcmp(host, "::") == 0) + if ((rc = getaddrinfo(host, NULL, &hint, &ai)) != 0) { - /** All interfaces */ - hint.ai_flags = AI_PASSIVE; - if ((rc = getaddrinfo(host, NULL, &hint, &ai)) != 0) - { - MXS_ERROR("Failed to obtain address for host %s, %s", - host, - gai_strerror(rc)); - - return -1; - } - } - else - { - hint.ai_flags = AI_ALL; - if ((rc = getaddrinfo(host, NULL, &hint, &ai)) != 0) - { - MXS_ERROR("Failed to obtain address for host %s, %s", - host, - gai_strerror(rc)); - - return -1; - } + MXS_ERROR("Failed to obtain address for host %s, %s", + host, gai_strerror(rc)); + return -1; } - /* take the first one */ - if (ai != NULL) + /* Take the first one */ + if (ai) { so = socket(ai->ai_family, SOCK_STREAM, 0); @@ -943,23 +964,33 @@ int create_network_socket(struct sockaddr_storage *dest, char *host) else { memcpy(dest, ai->ai_addr, ai->ai_addrlen); - freeaddrinfo(ai); + set_port(dest, port); + + if (!configure_socket(so)) + { + close(so); + so = -1; + } } + + freeaddrinfo(ai); } + #else #error Only the POSIX networking interface is supported #endif + return so; } -int parse_bindconfig(const char *config, struct sockaddr_in6 *addr, int *sock_type) +bool parse_bindconfig(const char *config, struct sockaddr_storage *addr) { char buf[strlen(config) + 1]; strcpy(buf, config); - *sock_type = strchr(buf, '.') ? AF_INET : AF_INET6; char *port = strrchr(buf, '|'); short pnum; + if (port) { *port = 0; @@ -968,35 +999,35 @@ int parse_bindconfig(const char *config, struct sockaddr_in6 *addr, int *sock_ty } else { + ss_dassert(false); return 0; } - if (!strcmp(buf, "0.0.0.0") || !strcmp(buf, "::")) + struct addrinfo *ai = NULL, hint = {}; + hint.ai_flags = AI_ALL; + hint.ai_family = AF_UNSPEC; + int rc = getaddrinfo(buf, NULL, &hint, &ai); + + if (rc == 0) { - *sock_type = AF_INET6; - addr->sin6_addr = in6addr_any; + if (ai) + { + memcpy(addr, ai->ai_addr, ai->ai_addrlen); + set_port(addr, pnum); + freeaddrinfo(ai); + } + else + { + MXS_ERROR("Failed to find valid network address for '%s'.", config); + rc = -1; + } } else { - if (inet_pton(*sock_type, buf, &addr->sin6_addr) < 1) - { - struct hostent *hp = gethostbyname(buf); - - if (hp) - { - inet_pton(*sock_type, hp->h_addr_list[0], &addr->sin6_addr); - } - else - { - MXS_ERROR("Failed to lookup host '%s'.", buf); - return 0; - } - } + MXS_ERROR("Failed to resolve network address for '%s': %s", config, gai_strerror(rc)); } - addr->sin6_family = *sock_type; - addr->sin6_port = htons(pnum); - return 1; + return rc == 0; } /** diff --git a/server/modules/protocol/MySQL/MySQLBackend/mysql_backend.c b/server/modules/protocol/MySQL/MySQLBackend/mysql_backend.c index 907156ec9..a15d3ab3e 100644 --- a/server/modules/protocol/MySQL/MySQLBackend/mysql_backend.c +++ b/server/modules/protocol/MySQL/MySQLBackend/mysql_backend.c @@ -18,7 +18,6 @@ #include #include #include -#include #include #include #include @@ -275,82 +274,16 @@ static int gw_do_connect_to_backend(char *host, int port, int *fd) { struct sockaddr_storage serv_addr = {}; int rv = -1; - int bufsize; /* prepare for connect */ - int so = create_network_socket(&serv_addr, host); + int so = open_network_socket(&serv_addr, host, port); if (so < 0) { - char errbuf[MXS_STRERROR_BUFLEN]; MXS_ERROR("Establishing connection to backend server %s:%d failed.", host, port); return rv; } - /** Configure the destination port */ - if (serv_addr.ss_family == AF_INET) - { - struct sockaddr_in *ip = (struct sockaddr_in*)&serv_addr; - ip->sin_port = htons(port); - } - else - { - ss_dassert(serv_addr.ss_family == AF_INET6); - struct sockaddr_in6 *ip = (struct sockaddr_in6*)&serv_addr; - ip->sin6_port = htons(port); - } - - bufsize = MXS_BACKEND_SO_SNDBUF; - - if (setsockopt(so, SOL_SOCKET, SO_SNDBUF, &bufsize, sizeof(bufsize)) != 0) - { - char errbuf[MXS_STRERROR_BUFLEN]; - MXS_ERROR("Failed to set socket options " - "%s:%d failed.\n\t\t Socket configuration failed " - "due %d, %s.", - host, - port, - errno, - strerror_r(errno, errbuf, sizeof(errbuf))); - /** Close socket */ - close_socket(so); - return rv; - } - bufsize = MXS_BACKEND_SO_RCVBUF; - - if (setsockopt(so, SOL_SOCKET, SO_RCVBUF, &bufsize, sizeof(bufsize)) != 0) - { - char errbuf[MXS_STRERROR_BUFLEN]; - MXS_ERROR("Failed to set socket options " - "%s:%d failed.\n\t\t Socket configuration failed " - "due %d, %s.", - host, - port, - errno, - strerror_r(errno, errbuf, sizeof(errbuf))); - /** Close socket */ - close_socket(so); - return rv; - } - - int one = 1; - if (setsockopt(so, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one)) != 0) - { - char errbuf[MXS_STRERROR_BUFLEN]; - MXS_ERROR("Failed to set socket options " - "%s:%d failed.\n\t\t Socket configuration failed " - "due %d, %s.", - host, - port, - errno, - strerror_r(errno, errbuf, sizeof(errbuf))); - /** Close socket */ - close_socket(so); - return rv; - } - - /* set socket to as non-blocking here */ - setnonblocking(so); rv = connect(so, (struct sockaddr *)&serv_addr, sizeof(serv_addr)); if (rv != 0) @@ -365,7 +298,7 @@ static int gw_do_connect_to_backend(char *host, int port, int *fd) MXS_ERROR("Failed to connect backend server %s:%d due to: %d, %s.", host, port, errno, strerror_r(errno, errbuf, sizeof(errbuf))); /** Close socket */ - close_socket(so); + close(so); return rv; } } @@ -1817,21 +1750,6 @@ static bool sescmd_response_complete(DCB* dcb) return succp; } -static void inline -close_socket(int sock) -{ - /*< Close newly created socket. */ - if (close(sock) != 0) - { - char errbuf[MXS_STRERROR_BUFLEN]; - MXS_ERROR("Failed to close socket %d due %d, %s.", - sock, - errno, - strerror_r(errno, errbuf, sizeof(errbuf))); - } - -} - /** * Create COM_CHANGE_USER packet and store it to GWBUF * From 894679d611cfb50153588f7d70378ed0a4537909 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Mon, 6 Mar 2017 14:00:06 +0200 Subject: [PATCH 25/38] Fix hostname resolution in MySQLAuth The hostname resolution was broken by the move to IPv6. --- .../modules/authenticator/MySQLAuth/dbusers.c | 34 +++++++++---------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/server/modules/authenticator/MySQLAuth/dbusers.c b/server/modules/authenticator/MySQLAuth/dbusers.c index b8f505809..01d7e3577 100644 --- a/server/modules/authenticator/MySQLAuth/dbusers.c +++ b/server/modules/authenticator/MySQLAuth/dbusers.c @@ -53,7 +53,7 @@ static int get_users(SERV_LISTENER *listener); static MYSQL *gw_mysql_init(void); static int gw_mysql_set_timeouts(MYSQL* handle); static char *mysql_format_user_entry(void *data); -static bool get_hostname(const char *ip_address, char *client_hostname); +static bool get_hostname(DCB *dcb, char *client_hostname, size_t size); static char* get_new_users_query(const char *server_version, bool include_root) { @@ -208,7 +208,8 @@ int validate_mysql_user(sqlite3 *handle, DCB *dcb, MYSQL_session *session, * as a last resort so we avoid the high cost of the DNS lookup. */ char client_hostname[MYSQL_HOST_MAXLEN]; - get_hostname(dcb->remote, client_hostname); + get_hostname(dcb, client_hostname, sizeof(client_hostname) - 1); + sprintf(sql, mysqlauth_validate_user_query, session->user, client_hostname, client_hostname, session->db, session->db); @@ -611,30 +612,27 @@ bool check_service_permissions(SERVICE* service) * * @return True if the hostname query was successful */ -static bool get_hostname(const char *ip_address, char *client_hostname) +static bool get_hostname(DCB *dcb, char *client_hostname, size_t size) { - /* 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) + struct addrinfo *ai = NULL, hint = {}; + hint.ai_flags = AI_ALL; + int rc; + + if ((rc = getaddrinfo(dcb->remote, NULL, &hint, &ai)) != 0) { - MXS_ERROR("Could not convert to binary ip-address: '%s'.", ip_address); + MXS_ERROR("Failed to obtain address for host %s, %s", + dcb->remote, gai_strerror(rc)); 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), + * if this feature is used often. */ + int lookup_result = getnameinfo(ai->ai_addr, ai->ai_addrlen, + client_hostname, size, NULL, 0, // No need for the port NI_NAMEREQD); // Text address only + freeaddrinfo(ai); if (lookup_result != 0) { @@ -646,7 +644,7 @@ static bool get_hostname(const char *ip_address, char *client_hostname) MXS_DEBUG("IP-lookup success, hostname is: '%s'", client_hostname); } - return false; + return lookup_result == 0; } void start_sqlite_transaction(sqlite3 *handle) From d88d02ee95fd032a51295511505c152a33ae7518 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Mon, 6 Mar 2017 14:12:57 +0200 Subject: [PATCH 26/38] Fix namedserverfilter `source` parameter The `source` parameter was broken by the IPv6 change. Now the filter no longer uses functions from the core to resolve the network addresses. --- .../namedserverfilter/namedserverfilter.c | 46 +++++++++++-------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/server/modules/filter/namedserverfilter/namedserverfilter.c b/server/modules/filter/namedserverfilter/namedserverfilter.c index 46be8e27f..b8d18c200 100644 --- a/server/modules/filter/namedserverfilter/namedserverfilter.c +++ b/server/modules/filter/namedserverfilter/namedserverfilter.c @@ -24,6 +24,7 @@ #include #include #include +#include /** * @file namedserverfilter.c - a very simple regular expression based filter @@ -54,7 +55,7 @@ static uint64_t getCapabilities(MXS_FILTER* instance); typedef struct source_host { - const char *address; + char *address; struct sockaddr_in ipv4; int netmask; } REGEXHINT_SOURCE_HOST; @@ -170,7 +171,6 @@ createInstance(const char *name, char **options, MXS_CONFIG_PARAMETER *params) if (my_instance) { - REGEXHINT_SOURCE_HOST *source = NULL; const char *cfg_param = config_get_string(params, "source"); if (*cfg_param) { @@ -506,7 +506,6 @@ static REGEXHINT_SOURCE_HOST *set_source_address(const char *input_host) { int netmask = 32; int bytes = 0; - struct sockaddr_in serv_addr; REGEXHINT_SOURCE_HOST *source_host = MXS_CALLOC(1, sizeof(REGEXHINT_SOURCE_HOST)); if (!input_host || !source_host) @@ -514,17 +513,15 @@ static REGEXHINT_SOURCE_HOST *set_source_address(const char *input_host) return NULL; } - if(!validate_ip_address(input_host)) + if (!validate_ip_address(input_host)) { - MXS_WARNING("The given 'source' parameter source=%s" - " is not a valid IP address: it will not be used.", - input_host); - - source_host->address = NULL; - return source_host; + MXS_WARNING("The given 'source' parameter '%s' is not a valid " + "IPv4 address.", input_host); + MXS_FREE(source_host); + return NULL; } - source_host->address = input_host; + source_host->address = MXS_STRDUP_A(input_host); /* If no wildcards don't check it, set netmask to 32 and return */ if (!strchr(input_host, '%')) @@ -561,10 +558,15 @@ static REGEXHINT_SOURCE_HOST *set_source_address(const char *input_host) *out ='\0'; source_host->netmask = netmask; + struct addrinfo *ai = NULL, hint = {}; + hint.ai_flags = AI_ADDRCONFIG | AI_V4MAPPED; + int rc = getaddrinfo(input_host, NULL, &hint, &ai); + /* fill IPv4 data struct */ - //TODO: Fix this - if (false /* setipaddress(&source_host->ipv4.sin_addr, format_host) && strlen(format_host)*/) + if (rc == 0) { + ss_dassert(ai->ai_family == AF_INET); + memcpy(&source_host->ipv4, ai->ai_addr, ai->ai_addrlen); /* if netmask < 32 there are % wildcards */ if (source_host->netmask < 32) @@ -573,16 +575,16 @@ static REGEXHINT_SOURCE_HOST *set_source_address(const char *input_host) source_host->ipv4.sin_addr.s_addr &= 0x00FFFFFF; } - MXS_INFO("Input %s is valid with netmask %d\n", - source_host->address, - source_host->netmask); + MXS_INFO("Input %s is valid with netmask %d", source_host->address, source_host->netmask); + freeaddrinfo(ai); } else { - MXS_WARNING("Found invalid IP address for parameter 'source=%s'," - " it will not be used.", - input_host); - source_host->address = NULL; + MXS_WARNING("Found invalid IP address for parameter 'source=%s': %s", + input_host, gai_strerror(rc)); + MXS_FREE(source_host->address); + MXS_FREE(source_host); + return NULL; } return (REGEXHINT_SOURCE_HOST *)source_host; @@ -604,5 +606,9 @@ static void free_instance(REGEXHINT_INSTANCE *instance) MXS_FREE(instance->server); MXS_FREE(instance->source); MXS_FREE(instance->user); + if (instance->source) + { + MXS_FREE(instance->source->address); + } MXS_FREE(instance); } From ea55667096ec9faf6aff0c120df0103f60d3dd72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Mon, 6 Mar 2017 16:03:42 +0200 Subject: [PATCH 27/38] Add back the missing netmask handling code The netmask code was mistakenly removed when the authentication was moved to the SQLite based system. --- .../modules/authenticator/MySQLAuth/dbusers.c | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/server/modules/authenticator/MySQLAuth/dbusers.c b/server/modules/authenticator/MySQLAuth/dbusers.c index 01d7e3577..73425e4de 100644 --- a/server/modules/authenticator/MySQLAuth/dbusers.c +++ b/server/modules/authenticator/MySQLAuth/dbusers.c @@ -270,6 +270,69 @@ static bool delete_mysql_users(sqlite3 *handle) return rval; } +/** + * If the hostname is of form a.b.c.d/e.f.g.h where e-h is 255 or 0, replace + * the zeros in the first part with '%' and remove the second part. This does + * not yet support netmasks completely, but should be sufficient for most + * situations. In case of error, the hostname may end in an invalid state, which + * will cause an error later on. + * + * @param host The hostname, which is modified in-place. If merging is unsuccessful, + * it may end up garbled. + */ +static void merge_netmask(char *host) +{ + char *delimiter_loc = strchr(host, '/'); + if (delimiter_loc == NULL) + { + return; // Nothing to do + } + /* If anything goes wrong, we put the '/' back in to ensure the hostname + * cannot be used. + */ + *delimiter_loc = '\0'; + + char *ip_token_loc = host; + char *mask_token_loc = delimiter_loc + 1; // This is at minimum a \0 + + while (ip_token_loc && mask_token_loc) + { + if (strncmp(mask_token_loc, "255", 3) == 0) + { + // Skip + } + else if (*mask_token_loc == '0' && *ip_token_loc == '0') + { + *ip_token_loc = '%'; + } + else + { + /* Any other combination is considered invalid. This may leave the + * hostname in a partially modified state. + * TODO: handle more cases + */ + *delimiter_loc = '/'; + MXS_ERROR("Unrecognized IP-bytes in host/mask-combination. " + "Merge incomplete: %s", host); + return; + } + + ip_token_loc = strchr(ip_token_loc, '.'); + mask_token_loc = strchr(mask_token_loc, '.'); + if (ip_token_loc && mask_token_loc) + { + ip_token_loc++; + mask_token_loc++; + } + } + if (ip_token_loc || mask_token_loc) + { + *delimiter_loc = '/'; + MXS_ERROR("Unequal number of IP-bytes in host/mask-combination. " + "Merge incomplete: %s", host); + } +} + void add_mysql_user(sqlite3 *handle, const char *user, const char *host, const char *db, bool anydb, const char *pw) { @@ -704,6 +767,11 @@ int get_users_from_server(MYSQL *con, SERVER_REF *server, SERVICE *service, SERV strip_escape_chars(row[2]); } + if (strchr(row[1], '/')) + { + merge_netmask(row[1]); + } + add_mysql_user(instance->handle, row[0], row[1], row[2], row[3] && strcmp(row[3], "Y") == 0, row[4]); users++; From 7bc47fd5a4239dc82cf5fb6391921279368ff072 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Mon, 6 Mar 2017 16:04:57 +0200 Subject: [PATCH 28/38] Return the `localhost_match_wildcard_host` warning When a local address is used which could match the wildcard host, a warning is again logged. --- .../authenticator/MySQLAuth/mysql_auth.c | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/server/modules/authenticator/MySQLAuth/mysql_auth.c b/server/modules/authenticator/MySQLAuth/mysql_auth.c index 41953c377..b052b0eaf 100644 --- a/server/modules/authenticator/MySQLAuth/mysql_auth.c +++ b/server/modules/authenticator/MySQLAuth/mysql_auth.c @@ -254,6 +254,30 @@ static void mysql_auth_destroy(void *data) } } +static bool is_localhost_address(struct sockaddr_storage *addr) +{ + bool rval = false; + + if (addr->ss_family == AF_INET) + { + struct sockaddr_in *ip = (struct sockaddr_in*)addr; + if (ip->sin_addr.s_addr == INADDR_LOOPBACK) + { + rval = true; + } + } + else if (addr->ss_family == AF_INET6) + { + struct sockaddr_in6 *ip = (struct sockaddr_in6*)addr; + if (memcmp(&ip->sin6_addr, &in6addr_loopback, sizeof(ip->sin6_addr)) == 0) + { + rval = true; + } + } + + return rval; +} + /** * @brief Authenticates a MySQL user who is a client to MaxScale. * @@ -326,6 +350,14 @@ mysql_auth_authenticate(DCB *dcb) { MXS_WARNING("%s: login attempt for user '%s'@%s:%d, authentication failed.", dcb->service->name, client_data->user, dcb->remote, dcb_get_port(dcb)); + + if (is_localhost_address(&dcb->ip) && + !dcb->service->localhost_match_wildcard_host) + { + MXS_NOTICE("If you have a wildcard grant that covers this address, " + "try adding 'localhost_match_wildcard_host=true' for " + "service '%s'. ", dcb->service->name); + } } /* let's free the auth_token now */ From e8ef70140940764339fdf72a7bcd1c6c16391b38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Tue, 7 Mar 2017 10:59:02 +0200 Subject: [PATCH 29/38] Combine socket creation code The client connection and the server listener sockets used largely similar code. Combining them allows for simpler protocol code. Cleaned up parts of the DCB listener creation and moved the parsing of the network binding configuration to a higher level. --- include/maxscale/utils.h | 36 ++-- server/core/dcb.c | 159 +++++++----------- server/core/utils.c | 88 +++------- .../MySQL/MySQLBackend/mysql_backend.c | 11 +- 4 files changed, 109 insertions(+), 185 deletions(-) diff --git a/include/maxscale/utils.h b/include/maxscale/utils.h index 5aaabd729..cf22d1382 100644 --- a/include/maxscale/utils.h +++ b/include/maxscale/utils.h @@ -35,31 +35,39 @@ MXS_BEGIN_DECLS */ #define MXS_PTR(a, b) (((uint8_t*)(a)) + (b)) +/** The type of the socket */ +enum mxs_socket_type +{ + MXS_SOCKET_LISTENER, /**< */ + MXS_SOCKET_NETWORK, +}; + bool utils_init(); /*< Call this first before using any other function */ void utils_end(); -/** - * Parse the bind configuration data. - * - * The configuration is passed as string in the `address|port` format. - * - * @param config The bind address and port separated by a '|' - * @param addr The struct sockaddr_storage in which the data is written - * @return True on success, false on failure - */ -bool parse_bindconfig(const char *config, struct sockaddr_storage *addr); - - /** * @brief Create a network socket and a socket configuration * - * @param dest Pointer to a struct sockaddr_storage where the configuration is stored + * This helper function can be used to open both listener socket and network + * connection sockets. For listener sockets, the @c host and @c port parameters + * tell where the socket will bind to. For network sockets, the parameters tell + * where the connection is created. + * + * After calling this function, the only thing that needs to be done is to + * give @c addr and the return value of this function as the parameters to + * either bind() (for listeners) or connect() (for outbound network connections). + * + * @param type Type of the socket, either MXS_SOCKET_LISTENER for a listener + * socket or MXS_SOCKET_NETWORK for a network connection socket + * @param addr Pointer to a struct sockaddr_storage where the socket + * configuration is stored * @param host The target host for which the socket is created * @param port The target port on the host * * @return The opened socket or -1 on failure */ -int open_network_socket(struct sockaddr_storage *dest, char *host, uint16_t port); +int open_network_socket(enum mxs_socket_type type, struct sockaddr_storage *addr, + const char *host, uint16_t port); int setnonblocking(int fd); char *gw_strend(register const char *s); diff --git a/server/core/dcb.c b/server/core/dcb.c index e83ba0f3e..3f364751e 100644 --- a/server/core/dcb.c +++ b/server/core/dcb.c @@ -153,8 +153,8 @@ static int gw_write(DCB *dcb, GWBUF *writeq, bool *stop_writing); static int gw_write_SSL(DCB *dcb, GWBUF *writeq, bool *stop_writing); static int dcb_log_errors_SSL (DCB *dcb, const char *called_by, int ret); static int dcb_accept_one_connection(DCB *listener, struct sockaddr *client_conn); -static int dcb_listen_create_socket_inet(const char *config_bind); -static int dcb_listen_create_socket_unix(const char *config_bind); +static int dcb_listen_create_socket_inet(const char *host, uint16_t port); +static int dcb_listen_create_socket_unix(const char *path); static int dcb_set_socket_option(int sockfd, int level, int optname, void *optval, socklen_t optlen); static void dcb_add_to_all_list(DCB *dcb); static DCB *dcb_find_free(); @@ -3044,22 +3044,38 @@ dcb_accept_one_connection(DCB *listener, struct sockaddr *client_conn) * @param protocol_name Name of protocol that is listening * @return 0 if new listener created successfully, otherwise -1 */ -int -dcb_listen(DCB *listener, const char *config, const char *protocol_name) +int dcb_listen(DCB *listener, const char *config, const char *protocol_name) { - int listener_socket; + char host[strlen(config) + 1]; + strcpy(host, config); + char *port_str = strrchr(host, '|'); + uint16_t port = 0; - listener->fd = -1; - if (strchr(config, '/')) + if (port_str) { - listener_socket = dcb_listen_create_socket_unix(config); + *port_str++ = 0; + port = atoi(port_str); + } + + int listener_socket = -1; + + if (strchr(host, '/')) + { + listener_socket = dcb_listen_create_socket_unix(host); + } + else if (port > 0) + { + listener_socket = dcb_listen_create_socket_inet(host, port); } else { - listener_socket = dcb_listen_create_socket_inet(config); + // We don't have a socket path or a network port + ss_dassert(false); } + if (listener_socket < 0) { + ss_dassert(listener_socket == -1); return -1; } @@ -3072,12 +3088,8 @@ dcb_listen(DCB *listener, const char *config, const char *protocol_name) */ if (listen(listener_socket, INT_MAX) != 0) { - char errbuf[MXS_STRERROR_BUFLEN]; MXS_ERROR("Failed to start listening on '%s' with protocol '%s': %d, %s", - config, - protocol_name, - errno, - strerror_r(errno, errbuf, sizeof(errbuf))); + config, protocol_name, errno, mxs_strerror(errno)); close(listener_socket); return -1; } @@ -3098,108 +3110,59 @@ dcb_listen(DCB *listener, const char *config, const char *protocol_name) } /** - * @brief Create a listening socket, TCP + * @brief Create a network listener socket * - * Parse the configuration provided and if valid create a socket. - * Set options, set non-blocking and bind to the socket. - * - * @param config_bind The configuration information - * @return socket if successful, -1 otherwise + * @param host The network address to listen on + * @param port The port to listen on + * @return The opened socket or -1 on error */ -static int -dcb_listen_create_socket_inet(const char *config_bind) +static int dcb_listen_create_socket_inet(const char *host, uint16_t port) { struct sockaddr_storage server_address = {}; + int listener_socket = open_network_socket(MXS_SOCKET_LISTENER, &server_address, host, port); - if (!parse_bindconfig(config_bind, &server_address)) + if (listener_socket != -1) { - MXS_ERROR("Error in parse_bindconfig for [%s]", config_bind); - return -1; + if (bind(listener_socket, (struct sockaddr*)&server_address, sizeof(server_address)) < 0) + { + MXS_ERROR("Failed to bind on '%s:%u': %d, %s", + host, port, errno, mxs_strerror(errno)); + close(listener_socket); + listener_socket = -1; + } } - /** TODO: Move everything before the `bind` call to utils.c */ - - /** Create the TCP socket */ - int listener_socket = socket(server_address.ss_family, SOCK_STREAM, 0); - - if (listener_socket < 0) - { - char errbuf[MXS_STRERROR_BUFLEN]; - MXS_ERROR("Can't create socket: %d, %s", errno, - strerror_r(errno, errbuf, sizeof(errbuf))); - return -1; - } - - int one = 1; - // socket options - if (dcb_set_socket_option(listener_socket, SOL_SOCKET, SO_REUSEADDR, (char *) &one, sizeof(one)) != 0 || - dcb_set_socket_option(listener_socket, IPPROTO_TCP, TCP_NODELAY, (char *) &one, sizeof(one)) != 0) - { - return -1; - } - - // set NONBLOCKING mode - if (setnonblocking(listener_socket) != 0) - { - MXS_ERROR("Failed to set socket to non-blocking mode."); - close(listener_socket); - return -1; - } - - if (bind(listener_socket, (struct sockaddr*)&server_address, sizeof(server_address)) < 0) - { - char errbuf[MXS_STRERROR_BUFLEN]; - MXS_ERROR("Failed to bind on '%s': %i, %s", - config_bind, - errno, - strerror_r(errno, errbuf, sizeof(errbuf))); - close(listener_socket); - return -1; - } return listener_socket; } /** - * @brief Create a listening socket, Unix + * @brief Create a Unix domain socket * - * Parse the configuration provided and if valid create a socket. - * Set options, set non-blocking and bind to the socket. - * - * @param config_bind The configuration information - * @return socket if successful, -1 otherwise + * @param path The socket path + * @return The opened socket or -1 on error */ -static int -dcb_listen_create_socket_unix(const char *config_bind) +static int dcb_listen_create_socket_unix(const char *path) { int listener_socket; struct sockaddr_un local_addr; int one = 1; - char *tmp = strrchr(config_bind, ':'); - if (tmp) - { - *tmp = '\0'; - } - - if (strlen(config_bind) > sizeof(local_addr.sun_path) - 1) + if (strlen(path) > sizeof(local_addr.sun_path) - 1) { MXS_ERROR("The path %s specified for the UNIX domain socket is too long. " - "The maximum length is %lu.", config_bind, sizeof(local_addr.sun_path) - 1); + "The maximum length is %lu.", path, sizeof(local_addr.sun_path) - 1); return -1; } // UNIX socket create if ((listener_socket = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { - char errbuf[MXS_STRERROR_BUFLEN]; - MXS_ERROR("Can't create UNIX socket: %i, %s", - errno, - strerror_r(errno, errbuf, sizeof(errbuf))); + MXS_ERROR("Can't create UNIX socket: %d, %s", errno, mxs_strerror(errno)); return -1; } // socket options - if (dcb_set_socket_option(listener_socket, SOL_SOCKET, SO_REUSEADDR, (char *) &one, sizeof(one)) != 0) + if (dcb_set_socket_option(listener_socket, SOL_SOCKET, SO_REUSEADDR, (char *)&one, sizeof(one)) != 0) { return -1; } @@ -3214,36 +3177,30 @@ dcb_listen_create_socket_unix(const char *config_bind) memset(&local_addr, 0, sizeof(local_addr)); local_addr.sun_family = AF_UNIX; - strcpy(local_addr.sun_path, config_bind); + strcpy(local_addr.sun_path, path); - if ((-1 == unlink(config_bind)) && (errno != ENOENT)) + if ((-1 == unlink(path)) && (errno != ENOENT)) { - char errbuf[MXS_STRERROR_BUFLEN]; MXS_ERROR("Failed to unlink Unix Socket %s: %d %s", - config_bind, errno, strerror_r(errno, errbuf, sizeof(errbuf))); + path, errno, mxs_strerror(errno)); } /* Bind the socket to the Unix domain socket */ - if (bind(listener_socket, (struct sockaddr *) &local_addr, sizeof(local_addr)) < 0) + if (bind(listener_socket, (struct sockaddr *)&local_addr, sizeof(local_addr)) < 0) { - char errbuf[MXS_STRERROR_BUFLEN]; - MXS_ERROR("Failed to bind to UNIX Domain socket '%s': %i, %s", - config_bind, - errno, - strerror_r(errno, errbuf, sizeof(errbuf))); + MXS_ERROR("Failed to bind to UNIX Domain socket '%s': %d, %s", + path, errno, mxs_strerror(errno)); close(listener_socket); return -1; } /* set permission for all users */ - if (chmod(config_bind, 0777) < 0) + if (chmod(path, 0777) < 0) { - char errbuf[MXS_STRERROR_BUFLEN]; - MXS_ERROR("Failed to change permissions on UNIX Domain socket '%s': %i, %s", - config_bind, - errno, - strerror_r(errno, errbuf, sizeof(errbuf))); + MXS_ERROR("Failed to change permissions on UNIX Domain socket '%s': %d, %s", + path, errno, mxs_strerror(errno)); } + return listener_socket; } diff --git a/server/core/utils.c b/server/core/utils.c index f95f4fc7b..2c6eb5748 100644 --- a/server/core/utils.c +++ b/server/core/utils.c @@ -896,7 +896,7 @@ void utils_end() SPINLOCK tmplock = SPINLOCK_INIT; -static bool configure_socket(int so) +static bool configure_network_socket(int so) { int sndbufsize = MXS_BACKEND_SO_SNDBUF; int rcvbufsize = MXS_BACKEND_SO_RCVBUF; @@ -906,9 +906,21 @@ static bool configure_socket(int so) setsockopt(so, SOL_SOCKET, SO_RCVBUF, &rcvbufsize, sizeof(rcvbufsize)) != 0 || setsockopt(so, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one)) != 0) { - char errbuf[MXS_STRERROR_BUFLEN]; - MXS_ERROR("Failed to set socket option: %d, %s.", - errno, strerror_r(errno, errbuf, sizeof(errbuf))); + MXS_ERROR("Failed to set socket option: %d, %s.", errno, mxs_strerror(errno)); + return false; + } + + return setnonblocking(so) == 0; +} + +static bool configure_listener_socket(int so) +{ + int one = 1; + + if (setsockopt(so, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) != 0 || + setsockopt(so, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one)) != 0) + { + MXS_ERROR("Failed to set socket option: %d, %s.", errno, mxs_strerror(errno)); return false; } @@ -934,8 +946,9 @@ static void set_port(struct sockaddr_storage *addr, uint16_t port) } } -int open_network_socket(struct sockaddr_storage *dest, char *host, uint16_t port) +int open_network_socket(enum mxs_socket_type type, struct sockaddr_storage *addr, const char *host, uint16_t port) { + ss_dassert(type == MXS_SOCKET_NETWORK || type == MXS_SOCKET_LISTENER); #ifdef __USE_POSIX struct addrinfo *ai = NULL, hint = {}; int so, rc; @@ -945,28 +958,24 @@ int open_network_socket(struct sockaddr_storage *dest, char *host, uint16_t port if ((rc = getaddrinfo(host, NULL, &hint, &ai)) != 0) { - MXS_ERROR("Failed to obtain address for host %s, %s", - host, gai_strerror(rc)); + MXS_ERROR("Failed to obtain address for host %s: %s", host, gai_strerror(rc)); return -1; } /* Take the first one */ if (ai) { - so = socket(ai->ai_family, SOCK_STREAM, 0); - - if (so < 0) + if ((so = socket(ai->ai_family, SOCK_STREAM, 0)) == -1) { - char errbuf[MXS_STRERROR_BUFLEN]; - MXS_ERROR("Socket creation failed: %d, %s.", - errno, strerror_r(errno, errbuf, sizeof(errbuf))); + MXS_ERROR("Socket creation failed: %d, %s.", errno, mxs_strerror(errno)); } else { - memcpy(dest, ai->ai_addr, ai->ai_addrlen); - set_port(dest, port); + memcpy(addr, ai->ai_addr, ai->ai_addrlen); + set_port(addr, port); - if (!configure_socket(so)) + if ((type == MXS_SOCKET_NETWORK && !configure_network_socket(so)) || + (type == MXS_SOCKET_LISTENER && !configure_listener_socket(so))) { close(so); so = -1; @@ -983,53 +992,6 @@ int open_network_socket(struct sockaddr_storage *dest, char *host, uint16_t port return so; } -bool parse_bindconfig(const char *config, struct sockaddr_storage *addr) -{ - char buf[strlen(config) + 1]; - strcpy(buf, config); - - char *port = strrchr(buf, '|'); - short pnum; - - if (port) - { - *port = 0; - port++; - pnum = atoi(port); - } - else - { - ss_dassert(false); - return 0; - } - - struct addrinfo *ai = NULL, hint = {}; - hint.ai_flags = AI_ALL; - hint.ai_family = AF_UNSPEC; - int rc = getaddrinfo(buf, NULL, &hint, &ai); - - if (rc == 0) - { - if (ai) - { - memcpy(addr, ai->ai_addr, ai->ai_addrlen); - set_port(addr, pnum); - freeaddrinfo(ai); - } - else - { - MXS_ERROR("Failed to find valid network address for '%s'.", config); - rc = -1; - } - } - else - { - MXS_ERROR("Failed to resolve network address for '%s': %s", config, gai_strerror(rc)); - } - - return rc == 0; -} - /** * Return the number of processors available. * @return Number of processors or 1 if the required definition of _SC_NPROCESSORS_CONF diff --git a/server/modules/protocol/MySQL/MySQLBackend/mysql_backend.c b/server/modules/protocol/MySQL/MySQLBackend/mysql_backend.c index a15d3ab3e..782819d45 100644 --- a/server/modules/protocol/MySQL/MySQLBackend/mysql_backend.c +++ b/server/modules/protocol/MySQL/MySQLBackend/mysql_backend.c @@ -276,9 +276,9 @@ static int gw_do_connect_to_backend(char *host, int port, int *fd) int rv = -1; /* prepare for connect */ - int so = open_network_socket(&serv_addr, host, port); + int so = open_network_socket(MXS_SOCKET_NETWORK, &serv_addr, host, port); - if (so < 0) + if (so == -1) { MXS_ERROR("Establishing connection to backend server %s:%d failed.", host, port); return rv; @@ -294,10 +294,8 @@ static int gw_do_connect_to_backend(char *host, int port, int *fd) } else { - char errbuf[MXS_STRERROR_BUFLEN]; MXS_ERROR("Failed to connect backend server %s:%d due to: %d, %s.", - host, port, errno, strerror_r(errno, errbuf, sizeof(errbuf))); - /** Close socket */ + host, port, errno, mxs_strerror(errno)); close(so); return rv; } @@ -305,8 +303,7 @@ static int gw_do_connect_to_backend(char *host, int port, int *fd) *fd = so; MXS_DEBUG("%lu [gw_do_connect_to_backend] Connected to backend server " - "%s:%d, fd %d.", - pthread_self(), host, port, so); + "%s:%d, fd %d.", pthread_self(), host, port, so); return rv; From 086650bb4d28ac3504ca7713efebcf3be2263b33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Wed, 8 Mar 2017 16:31:58 +0200 Subject: [PATCH 30/38] Fix loading of users from multiple servers The users were deleted before each individual server was queried. This caused authentication to fail if the authentication data was loaded from multiple servers. --- server/modules/authenticator/MySQLAuth/dbusers.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/server/modules/authenticator/MySQLAuth/dbusers.c b/server/modules/authenticator/MySQLAuth/dbusers.c index 73425e4de..5dd31b124 100644 --- a/server/modules/authenticator/MySQLAuth/dbusers.c +++ b/server/modules/authenticator/MySQLAuth/dbusers.c @@ -756,8 +756,6 @@ int get_users_from_server(MYSQL *con, SERVER_REF *server, SERVICE *service, SERV { start_sqlite_transaction(instance->handle); - /** Delete the old users */ - delete_mysql_users(instance->handle); MYSQL_ROW row; while ((row = mysql_fetch_row(result))) @@ -852,6 +850,10 @@ static int get_users(SERV_LISTENER *listener) return -1; } + /** Delete the old users */ + MYSQL_AUTH *instance = (MYSQL_AUTH*)listener->auth_instance; + delete_mysql_users(instance->handle); + SERVER_REF *server = service->dbref; int total_users = -1; From 9c325104b7197d7ed753d46c32cea1ee303ebf2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Thu, 9 Mar 2017 14:52:21 +0200 Subject: [PATCH 31/38] Use correct size for snprintf The mysql authenticator used the size of the pointer for the snprintf size parameter instead of the actual size of the destination buffer. --- server/modules/authenticator/MySQLAuth/mysql_auth.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/server/modules/authenticator/MySQLAuth/mysql_auth.c b/server/modules/authenticator/MySQLAuth/mysql_auth.c index b052b0eaf..62793d167 100644 --- a/server/modules/authenticator/MySQLAuth/mysql_auth.c +++ b/server/modules/authenticator/MySQLAuth/mysql_auth.c @@ -106,18 +106,19 @@ MXS_MODULE* MXS_CREATE_MODULE() return &info; } -static void get_database_path(SERV_LISTENER *port, char *dest) +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, sizeof(dest) - sizeof(DBUSERS_FILE) - 1, "%s/", instance->cache_dir); + snprintf(dest, size, "%s/", instance->cache_dir); } else { - sprintf(dest, "%s/%s/%s/%s/", get_cachedir(), service->name, port->name, DBUSERS_DIR); + snprintf(dest, size, "%s/%s/%s/%s/", get_cachedir(), service->name, port->name, DBUSERS_DIR); } if (mxs_mkdir_all(dest, S_IRWXU)) @@ -398,7 +399,7 @@ mysql_auth_set_protocol_data(DCB *dcb, GWBUF *buf) if (auth_ses->handle == NULL) { char path[PATH_MAX]; - get_database_path(dcb->listener, path); + get_database_path(dcb->listener, path, sizeof(path)); if (!open_client_database(path, &auth_ses->handle)) { @@ -615,7 +616,7 @@ static int mysql_auth_load_users(SERV_LISTENER *port) if (instance->handle == NULL) { char path[PATH_MAX]; - get_database_path(port, path); + get_database_path(port, path, sizeof(path)); if (!open_instance_database(path, &instance->handle)) { return MXS_AUTH_LOADUSERS_FATAL; From 4d561c5f6a60cfc054d79d2ae909769c88afabe0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Mon, 13 Mar 2017 18:56:23 +0200 Subject: [PATCH 32/38] Detect password usage based on token length The client protocol module can resolve whether a password was used based on the information the authenticators gather before authentication is done. It uses the authentication token length as the basis on which it makes the decision. --- include/maxscale/protocol/mysql.h | 2 +- .../modules/authenticator/MySQLAuth/dbusers.c | 6 -- .../MySQL/MySQLBackend/mysql_backend.c | 2 +- .../protocol/MySQL/MySQLClient/mysql_client.c | 68 +++++++------------ server/modules/protocol/MySQL/mysql_common.c | 15 ++-- 5 files changed, 34 insertions(+), 59 deletions(-) diff --git a/include/maxscale/protocol/mysql.h b/include/maxscale/protocol/mysql.h index 14f959add..e0d43b322 100644 --- a/include/maxscale/protocol/mysql.h +++ b/include/maxscale/protocol/mysql.h @@ -407,7 +407,7 @@ bool protocol_get_response_status (MySQLProtocol* p, int* npackets, ssize_t* nby void protocol_set_response_status (MySQLProtocol* p, int npackets, ssize_t nbytes); void protocol_archive_srv_command(MySQLProtocol* p); -char* create_auth_fail_str(char *username, char *hostaddr, char *sha1, char *db, int); +char* create_auth_fail_str(char *username, char *hostaddr, bool password, char *db, int); void init_response_status ( GWBUF* buf, diff --git a/server/modules/authenticator/MySQLAuth/dbusers.c b/server/modules/authenticator/MySQLAuth/dbusers.c index 5dd31b124..4ed39d91b 100644 --- a/server/modules/authenticator/MySQLAuth/dbusers.c +++ b/server/modules/authenticator/MySQLAuth/dbusers.c @@ -238,12 +238,6 @@ int validate_mysql_user(sqlite3 *handle, DCB *dcb, MYSQL_session *session, rval = MXS_AUTH_FAILED_DB; } } - else if (session->auth_token_len) - { - /** If authentication fails, this will trigger the right - * error message with `Using password : YES` */ - session->client_sha1[0] = '_'; - } } return rval; diff --git a/server/modules/protocol/MySQL/MySQLBackend/mysql_backend.c b/server/modules/protocol/MySQL/MySQLBackend/mysql_backend.c index 782819d45..e62ecab09 100644 --- a/server/modules/protocol/MySQL/MySQLBackend/mysql_backend.c +++ b/server/modules/protocol/MySQL/MySQLBackend/mysql_backend.c @@ -1510,7 +1510,7 @@ static int gw_change_user(DCB *backend, message = create_auth_fail_str(username, backend->session->client_dcb->remote, password_set, - "", + false, auth_ret); if (message == NULL) { diff --git a/server/modules/protocol/MySQL/MySQLClient/mysql_client.c b/server/modules/protocol/MySQL/MySQLClient/mysql_client.c index bb13ceaf1..526c269fb 100644 --- a/server/modules/protocol/MySQL/MySQLClient/mysql_client.c +++ b/server/modules/protocol/MySQL/MySQLClient/mysql_client.c @@ -83,7 +83,6 @@ static void mysql_client_auth_error_handling(DCB *dcb, int auth_val, int packet_ static int gw_read_do_authentication(DCB *dcb, GWBUF *read_buffer, int nbytes_read); static int gw_read_normal_data(DCB *dcb, GWBUF *read_buffer, int nbytes_read); static int gw_read_finish_processing(DCB *dcb, GWBUF *read_buffer, uint64_t capabilities); -extern char* create_auth_fail_str(char *username, char *hostaddr, char *sha1, char *db, int); static bool ensure_complete_packet(DCB *dcb, GWBUF **read_buffer, int nbytes_read); static void gw_process_one_new_client(DCB *client_dcb); @@ -1072,86 +1071,67 @@ mysql_client_auth_error_handling(DCB *dcb, int auth_val, int packet_number) { int message_len; char *fail_str = NULL; + MYSQL_session *session = (MYSQL_session*)dcb->data; switch (auth_val) { case MXS_AUTH_NO_SESSION: - MXS_DEBUG("%lu [gw_read_client_event] session " - "creation failed. fd %d, " - "state = MYSQL_AUTH_NO_SESSION.", - pthread_self(), - dcb->fd); + MXS_DEBUG("%lu [gw_read_client_event] session creation failed. fd %d, " + "state = MYSQL_AUTH_NO_SESSION.", pthread_self(), dcb->fd); /** Send ERR 1045 to client */ - mysql_send_auth_error(dcb, - packet_number, - 0, - "failed to create new session"); + mysql_send_auth_error(dcb, packet_number, 0, "failed to create new session"); break; + case MXS_AUTH_FAILED_DB: - MXS_DEBUG("%lu [gw_read_client_event] database " - "specified was not valid. fd %d, " - "state = MYSQL_FAILED_AUTH_DB.", - pthread_self(), - dcb->fd); + MXS_DEBUG("%lu [gw_read_client_event] database specified was not valid. fd %d, " + "state = MYSQL_FAILED_AUTH_DB.", pthread_self(), dcb->fd); /** Send error 1049 to client */ message_len = 25 + MYSQL_DATABASE_MAXLEN; fail_str = MXS_CALLOC(1, message_len + 1); MXS_ABORT_IF_NULL(fail_str); - snprintf(fail_str, message_len, "Unknown database '%s'", - (char*)((MYSQL_session *)dcb->data)->db); + snprintf(fail_str, message_len, "Unknown database '%s'", session->db); modutil_send_mysql_err_packet(dcb, packet_number, 0, 1049, "42000", fail_str); break; + case MXS_AUTH_FAILED_SSL: MXS_DEBUG("%lu [gw_read_client_event] client is " "not SSL capable for SSL listener. fd %d, " - "state = MYSQL_FAILED_AUTH_SSL.", - pthread_self(), - dcb->fd); + "state = MYSQL_FAILED_AUTH_SSL.", pthread_self(), dcb->fd); /** Send ERR 1045 to client */ - mysql_send_auth_error(dcb, - packet_number, - 0, - "Access without SSL denied"); + mysql_send_auth_error(dcb, packet_number, 0, "Access without SSL denied"); break; + case MXS_AUTH_SSL_INCOMPLETE: MXS_DEBUG("%lu [gw_read_client_event] unable to " "complete SSL authentication. fd %d, " - "state = MYSQL_AUTH_SSL_INCOMPLETE.", - pthread_self(), - dcb->fd); + "state = MYSQL_AUTH_SSL_INCOMPLETE.", pthread_self(), dcb->fd); /** Send ERR 1045 to client */ - mysql_send_auth_error(dcb, - packet_number, - 0, + mysql_send_auth_error(dcb, packet_number, 0, "failed to complete SSL authentication"); break; + case MXS_AUTH_FAILED: MXS_DEBUG("%lu [gw_read_client_event] authentication failed. fd %d, " - "state = MYSQL_FAILED_AUTH.", - pthread_self(), - dcb->fd); + "state = MYSQL_FAILED_AUTH.", pthread_self(), dcb->fd); /** 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, - (char*)((MYSQL_session *)dcb->data)->db, auth_val); + fail_str = create_auth_fail_str(session->user, dcb->remote, + session->auth_token_len > 0, + session->db, auth_val); modutil_send_mysql_err_packet(dcb, packet_number, 0, 1045, "28000", fail_str); break; + default: MXS_DEBUG("%lu [gw_read_client_event] authentication failed. fd %d, " - "state unrecognized.", - pthread_self(), - dcb->fd); + "state unrecognized.", pthread_self(), dcb->fd); /** 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, - (char*)((MYSQL_session *)dcb->data)->db, auth_val); + fail_str = create_auth_fail_str(session->user, dcb->remote, + session->auth_token_len > 0, + session->db, auth_val); modutil_send_mysql_err_packet(dcb, packet_number, 0, 1045, "28000", fail_str); } MXS_FREE(fail_str); diff --git a/server/modules/protocol/MySQL/mysql_common.c b/server/modules/protocol/MySQL/mysql_common.c index 8103ff9aa..a2db80fd4 100644 --- a/server/modules/protocol/MySQL/mysql_common.c +++ b/server/modules/protocol/MySQL/mysql_common.c @@ -925,16 +925,17 @@ char* create_auth_failed_msg(GWBUF*readbuf, /** * 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 + * @param username The MySQL user + * @param hostaddr The client IP + * @param password If client provided a password + * @param db The default database the client requested + * @param errcode Authentication error code * * @return Pointer to the allocated string or NULL on failure */ char *create_auth_fail_str(char *username, char *hostaddr, - char *sha1, + bool password, char *db, int errcode) { @@ -974,7 +975,7 @@ char *create_auth_fail_str(char *username, if (db_len > 0) { - sprintf(errstr, ferrstr, username, hostaddr, (*sha1 == '\0' ? "NO" : "YES"), db); + sprintf(errstr, ferrstr, username, hostaddr, password ? "YES": "NO", db); } else if (errcode == MXS_AUTH_FAILED_SSL) { @@ -982,7 +983,7 @@ char *create_auth_fail_str(char *username, } else { - sprintf(errstr, ferrstr, username, hostaddr, (*sha1 == '\0' ? "NO" : "YES")); + sprintf(errstr, ferrstr, username, hostaddr, password ? "YES" : "NO"); } retblock: From fbcd25342cd611c9cc9c1a5ec679ac65a78492ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Tue, 14 Mar 2017 10:11:57 +0200 Subject: [PATCH 33/38] Add 2.1.2 release notes Created release notes for 2.1.2 and added the new features into it. Also updated the MySQLAuth limitations in the limitations document. --- Documentation/About/Limitations.md | 7 --- .../MaxScale-2.1.2-Release-Notes.md | 52 +++++++++++++++++++ 2 files changed, 52 insertions(+), 7 deletions(-) create mode 100644 Documentation/Release-Notes/MaxScale-2.1.2-Release-Notes.md diff --git a/Documentation/About/Limitations.md b/Documentation/About/Limitations.md index b42138149..afeaf9aec 100644 --- a/Documentation/About/Limitations.md +++ b/Documentation/About/Limitations.md @@ -20,13 +20,6 @@ different backend authentication module is not supported. ### Limitations in the MySQL authenticator (MySQLAuth) -* MariaDB MaxScale supports authentication that uses wildcard matching in -hostnames in the `mysql.user` table of the backend database. For IP address -entries either `%` or `_`-wildcards are accepted, they should not be mixed in -the same entry. For text addresses both wildcards can be mixed. - -* Wildcards in text-form hostnames are not supported. - * MySQL old style passwords are not supported. MySQL versions 4.1 and newer use a new authentication protocol which does not support pre-4.1 style passwords. diff --git a/Documentation/Release-Notes/MaxScale-2.1.2-Release-Notes.md b/Documentation/Release-Notes/MaxScale-2.1.2-Release-Notes.md new file mode 100644 index 000000000..b3712049d --- /dev/null +++ b/Documentation/Release-Notes/MaxScale-2.1.2-Release-Notes.md @@ -0,0 +1,52 @@ +# MariaDB MaxScale 2.1.2 Release Notes + +Release 2.1.2 is a Beta release. + +This document describes the changes in release 2.1.2, when compared to +release [2.1.1](MaxScale-2.1.1-Release-Notes.md). + +If you are upgrading from release 2.0, please also read the following +release notes: +[2.1.1](./MaxScale-2.1.1-Release-Notes.md) +[2.1.0](./MaxScale-2.1.0-Release-Notes.md) + +For any problems you encounter, please consider submitting a bug +report at [Jira](https://jira.mariadb.org). + +## Changed Features + +### Improved Wildcard Matching + +The MySQLAuth module now supports all types of wildcards for both IP addresses +as well as hostnames. + +## New Features + +### IPv6 Support + +MaxScale now supports IPv6 connections on both the client and backend side as +well as being able to listen on IPv6 addresses. + +## Bug fixes + +[Here is a list of bugs fixed since the release of MaxScale 2.1.1.](https://jira.mariadb.org/issues/?jql=project%20%3D%20MXS%20AND%20issuetype%20%3D%20Bug%20AND%20resolution%20in%20(Fixed%2C%20Done)%20AND%20fixVersion%20%3D%202.1.2%20AND%20fixVersion%20NOT%20IN%20(2.1.1)) + +## Known Issues and Limitations + +There are some limitations and known issues within this version of MaxScale. +For more information, please refer to the [Limitations](../About/Limitations.md) document. + +## Packaging + +RPM and Debian packages are provided for the Linux distributions supported +by MariaDB Enterprise. + +Packages can be downloaded [here](https://mariadb.com/resources/downloads). + +## Source Code + +The source code of MaxScale is tagged at GitHub with a tag, which is identical +with the version of MaxScale. For instance, the tag of version X.Y.Z of MaxScale +is X.Y.Z. Further, *master* always refers to the latest released non-beta version. + +The source code is available [here](https://github.com/mariadb-corporation/MaxScale). From 573615889b5d3a9657d02850a009c724c3ecf9d0 Mon Sep 17 00:00:00 2001 From: MassimilianoPinto Date: Tue, 14 Mar 2017 09:28:04 +0100 Subject: [PATCH 34/38] Binlog Server doesn't ask for any maxwell query if the option is not set MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If ‘maxwell-compatibility’ option is not set, no SET/SELECT are sent to master during slave registration phase --- server/modules/routing/binlogrouter/blr.h | 16 ++-- .../modules/routing/binlogrouter/blr_master.c | 93 +++++++++++++------ 2 files changed, 74 insertions(+), 35 deletions(-) diff --git a/server/modules/routing/binlogrouter/blr.h b/server/modules/routing/binlogrouter/blr.h index 0b840727c..e9b0d8315 100644 --- a/server/modules/routing/binlogrouter/blr.h +++ b/server/modules/routing/binlogrouter/blr.h @@ -703,14 +703,15 @@ typedef struct binlog_pos_fix #define BLRM_SERVER_VARS 0x0016 #define BLRM_BINLOG_VARS 0x0017 #define BLRM_LOWER_CASE_TABLES 0x0018 -#define BLRM_REGISTER 0x0019 -#define BLRM_CHECK_SEMISYNC 0x001A -#define BLRM_REQUEST_SEMISYNC 0x001B -#define BLRM_REQUEST_BINLOGDUMP 0x001C -#define BLRM_BINLOGDUMP 0x001D -#define BLRM_SLAVE_STOPPED 0x001E +#define BLRM_REGISTER_READY 0x0019 +#define BLRM_REGISTER 0x001A +#define BLRM_CHECK_SEMISYNC 0x001B +#define BLRM_REQUEST_SEMISYNC 0x001C +#define BLRM_REQUEST_BINLOGDUMP 0x001D +#define BLRM_BINLOGDUMP 0x001E +#define BLRM_SLAVE_STOPPED 0x001F -#define BLRM_MAXSTATE 0x001E +#define BLRM_MAXSTATE 0x001F static char *blrm_states[] = { @@ -739,6 +740,7 @@ static char *blrm_states[] = "Query server variables", "Query binlog variables", "Query @@lower_case_table_names", + "Ready to Register", "Register slave", "Semi-Sync Support retrivial", "Request Semi-Sync Replication", diff --git a/server/modules/routing/binlogrouter/blr_master.c b/server/modules/routing/binlogrouter/blr_master.c index 1d6e7969c..f5dbb43d4 100644 --- a/server/modules/routing/binlogrouter/blr_master.c +++ b/server/modules/routing/binlogrouter/blr_master.c @@ -722,43 +722,80 @@ blr_master_response(ROUTER_INSTANCE *router, GWBUF *buf) router->saved_master.map = buf; blr_cache_response(router, "map", buf); - // Query for Server Variables - buf = blr_make_query(router->master, MYSQL_CONNECTOR_SERVER_VARS_QUERY); - router->master_state = BLRM_SERVER_VARS; - router->master->func.write(router->master, buf); - break; + if (router->maxwell_compat) + { + // Query for Server Variables + buf = blr_make_query(router->master, MYSQL_CONNECTOR_SERVER_VARS_QUERY); + router->master_state = BLRM_SERVER_VARS; + router->master->func.write(router->master, buf); + break; + } + else + { + // Continue: ready for the registration, nothing to write/read + router->master_state = BLRM_REGISTER_READY; + } case BLRM_SERVER_VARS: - if (router->saved_master.server_vars) + /** + * This branch could be reached as fallthrough from BLRM_MAP + * with new state BLRM_REGISTER_READY + * Go ahead if maxwell_compat is not set + */ + if (router->maxwell_compat) { - GWBUF_CONSUME_ALL(router->saved_master.server_vars); - } - router->saved_master.server_vars = buf; - blr_cache_response(router, "server_vars", buf); + if (router->saved_master.server_vars) + { + GWBUF_CONSUME_ALL(router->saved_master.server_vars); + } + router->saved_master.server_vars = buf; + blr_cache_response(router, "server_vars", buf); - buf = blr_make_query(router->master, "SELECT IF(@@global.log_bin, 'ON', 'OFF'), @@global.binlog_format, @@global.binlog_row_image"); - router->master_state = BLRM_BINLOG_VARS; - router->master->func.write(router->master, buf); - break; + buf = blr_make_query(router->master, + "SELECT IF(@@global.log_bin, 'ON', 'OFF'), " + "@@global.binlog_format, @@global.binlog_row_image"); + router->master_state = BLRM_BINLOG_VARS; + router->master->func.write(router->master, buf); + break; + } case BLRM_BINLOG_VARS: - if (router->saved_master.binlog_vars) + /** + * This branch could be reached as fallthrough from BLRM_MAP + * with new state BLRM_REGISTER_READY. + * Go ahead if maxwell_compat is not set + */ + if (router->maxwell_compat) { - GWBUF_CONSUME_ALL(router->saved_master.binlog_vars); - } - router->saved_master.binlog_vars = buf; - blr_cache_response(router, "binlog_vars", buf); + if (router->saved_master.binlog_vars) + { + GWBUF_CONSUME_ALL(router->saved_master.binlog_vars); + } + router->saved_master.binlog_vars = buf; + blr_cache_response(router, "binlog_vars", buf); - buf = blr_make_query(router->master, "select @@lower_case_table_names"); - router->master_state = BLRM_LOWER_CASE_TABLES; - router->master->func.write(router->master, buf); - break; + buf = blr_make_query(router->master, "select @@lower_case_table_names"); + router->master_state = BLRM_LOWER_CASE_TABLES; + router->master->func.write(router->master, buf); + break; + } case BLRM_LOWER_CASE_TABLES: - if (router->saved_master.lower_case_tables) + /** + * This branch could be reached as fallthrough from BLRM_MAP + * with new state BLRM_REGISTER_READY. + * Go ahead if maxwell_compat is not set + */ + if (router->maxwell_compat) { - GWBUF_CONSUME_ALL(router->saved_master.lower_case_tables); + if (router->saved_master.lower_case_tables) + { + GWBUF_CONSUME_ALL(router->saved_master.lower_case_tables); + } + router->saved_master.lower_case_tables = buf; + blr_cache_response(router, "lower_case_tables", buf); + router->master_state = BLRM_REGISTER_READY; + // Continue: ready for the registration, nothing to write/read } - router->saved_master.lower_case_tables = buf; - blr_cache_response(router, "lower_case_tables", buf); - + case BLRM_REGISTER_READY: + // Prepare registration buf = blr_make_registration(router); router->master_state = BLRM_REGISTER; router->master->func.write(router->master, buf); From 99526760523497af1251a6db2788a9ebff3ed02c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Mon, 13 Mar 2017 18:10:58 +0200 Subject: [PATCH 35/38] Move UNIX domain socket creation to utils.c The network socket code is now completely inside utils.c. --- include/maxscale/utils.h | 17 +++++++++++ server/core/dcb.c | 66 +++------------------------------------- server/core/utils.c | 52 +++++++++++++++++++++++++++++++ 3 files changed, 74 insertions(+), 61 deletions(-) diff --git a/include/maxscale/utils.h b/include/maxscale/utils.h index cf22d1382..6f34e47aa 100644 --- a/include/maxscale/utils.h +++ b/include/maxscale/utils.h @@ -69,6 +69,23 @@ void utils_end(); int open_network_socket(enum mxs_socket_type type, struct sockaddr_storage *addr, const char *host, uint16_t port); +/** + * @brief Create a UNIX domain socket + * + * This opens and prepares a UNIX domain socket for use. The @c addr parameter + * can be given to the bind() function to bind the socket. + * + * @param type Type of the socket, either MXS_SOCKET_LISTENER for a listener + * socket or MXS_SOCKET_NETWORK for a network connection socket + * @param addr Pointer to a struct sockaddr_un where the socket configuration + * is stored + * @param path Path to the socket + * + * @return The opened socket or -1 on failure + */ +int open_unix_socket(enum mxs_socket_type type, struct sockaddr_un *addr, + const char *path); + int setnonblocking(int fd); char *gw_strend(register const char *s); static char gw_randomchar(); diff --git a/server/core/dcb.c b/server/core/dcb.c index 94342526f..dfb745f2e 100644 --- a/server/core/dcb.c +++ b/server/core/dcb.c @@ -2964,20 +2964,7 @@ int dcb_listen(DCB *listener, const char *config, const char *protocol_name) static int dcb_listen_create_socket_inet(const char *host, uint16_t port) { struct sockaddr_storage server_address = {}; - int listener_socket = open_network_socket(MXS_SOCKET_LISTENER, &server_address, host, port); - - if (listener_socket != -1) - { - if (bind(listener_socket, (struct sockaddr*)&server_address, sizeof(server_address)) < 0) - { - MXS_ERROR("Failed to bind on '%s:%u': %d, %s", - host, port, errno, mxs_strerror(errno)); - close(listener_socket); - listener_socket = -1; - } - } - - return listener_socket; + return open_network_socket(MXS_SOCKET_LISTENER, &server_address, host, port); } /** @@ -2988,59 +2975,16 @@ static int dcb_listen_create_socket_inet(const char *host, uint16_t port) */ static int dcb_listen_create_socket_unix(const char *path) { - int listener_socket; - struct sockaddr_un local_addr; - int one = 1; - - if (strlen(path) > sizeof(local_addr.sun_path) - 1) - { - MXS_ERROR("The path %s specified for the UNIX domain socket is too long. " - "The maximum length is %lu.", path, sizeof(local_addr.sun_path) - 1); - return -1; - } - - // UNIX socket create - if ((listener_socket = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) - { - MXS_ERROR("Can't create UNIX socket: %d, %s", errno, mxs_strerror(errno)); - return -1; - } - - // socket options - if (dcb_set_socket_option(listener_socket, SOL_SOCKET, SO_REUSEADDR, (char *)&one, sizeof(one)) != 0) - { - return -1; - } - - // set NONBLOCKING mode - if (setnonblocking(listener_socket) != 0) - { - MXS_ERROR("Failed to set socket to non-blocking mode."); - close(listener_socket); - return -1; - } - - memset(&local_addr, 0, sizeof(local_addr)); - local_addr.sun_family = AF_UNIX; - strcpy(local_addr.sun_path, path); - - if ((-1 == unlink(path)) && (errno != ENOENT)) + if (unlink(path) == -1 && errno != ENOENT) { MXS_ERROR("Failed to unlink Unix Socket %s: %d %s", path, errno, mxs_strerror(errno)); } - /* Bind the socket to the Unix domain socket */ - if (bind(listener_socket, (struct sockaddr *)&local_addr, sizeof(local_addr)) < 0) - { - MXS_ERROR("Failed to bind to UNIX Domain socket '%s': %d, %s", - path, errno, mxs_strerror(errno)); - close(listener_socket); - return -1; - } + struct sockaddr_un local_addr; + int listener_socket = open_unix_socket(MXS_SOCKET_LISTENER, &local_addr, path); - /* set permission for all users */ - if (chmod(path, 0777) < 0) + if (listener_socket >= 0 && chmod(path, 0777) < 0) { MXS_ERROR("Failed to change permissions on UNIX Domain socket '%s': %d, %s", path, errno, mxs_strerror(errno)); diff --git a/server/core/utils.c b/server/core/utils.c index 3814b8128..4ad3556ff 100644 --- a/server/core/utils.c +++ b/server/core/utils.c @@ -37,6 +37,7 @@ #include #include #include +#include #include #include @@ -974,6 +975,13 @@ int open_network_socket(enum mxs_socket_type type, struct sockaddr_storage *addr close(so); so = -1; } + else if (type == MXS_SOCKET_LISTENER && bind(so, (struct sockaddr*)addr, sizeof(*addr)) < 0) + { + MXS_ERROR("Failed to bind on '%s:%u': %d, %s", + host, port, errno, mxs_strerror(errno)); + close(so); + so = -1; + } } freeaddrinfo(ai); @@ -986,6 +994,50 @@ int open_network_socket(enum mxs_socket_type type, struct sockaddr_storage *addr return so; } +static bool configure_unix_socket(int so) +{ + int one = 1; + + if (setsockopt(so, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) != 0) + { + MXS_ERROR("Failed to set socket option: %d, %s.", errno, mxs_strerror(errno)); + return false; + } + + return setnonblocking(so) == 0; +} + +int open_unix_socket(enum mxs_socket_type type, struct sockaddr_un *addr, const char *path) +{ + int fd = -1; + + if (strlen(path) > sizeof(addr->sun_path) - 1) + { + MXS_ERROR("The path %s specified for the UNIX domain socket is too long. " + "The maximum length is %lu.", path, sizeof(addr->sun_path) - 1); + } + else if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) + { + MXS_ERROR("Can't create UNIX socket: %d, %s", errno, mxs_strerror(errno)); + } + else if (configure_unix_socket(fd)) + { + addr->sun_family = AF_UNIX; + strcpy(addr->sun_path, path); + + /* Bind the socket to the Unix domain socket */ + if (type == MXS_SOCKET_LISTENER && bind(fd, (struct sockaddr *)addr, sizeof(*addr)) < 0) + { + MXS_ERROR("Failed to bind to UNIX Domain socket '%s': %d, %s", + path, errno, mxs_strerror(errno)); + close(fd); + fd = -1; + } + } + + return fd; +} + /** * Return the number of processors available. * @return Number of processors or 1 if the required definition of _SC_NPROCESSORS_CONF From 29be8436e5b5ec2c6f9578c0488d59e2619515e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Mon, 13 Mar 2017 22:31:58 +0200 Subject: [PATCH 36/38] Remove unused readconnroute code The state change code was not used. --- .../routing/readconnroute/readconnroute.c | 47 ------------------- 1 file changed, 47 deletions(-) diff --git a/server/modules/routing/readconnroute/readconnroute.c b/server/modules/routing/readconnroute/readconnroute.c index a1564c18f..0b3f0f4fb 100644 --- a/server/modules/routing/readconnroute/readconnroute.c +++ b/server/modules/routing/readconnroute/readconnroute.c @@ -102,7 +102,6 @@ static uint64_t getCapabilities(MXS_ROUTER* instance); static bool rses_begin_locked_router_action(ROUTER_CLIENT_SES* rses); static void rses_end_locked_router_action(ROUTER_CLIENT_SES* rses); static SERVER_REF *get_root_master(SERVER_REF *servers); -static int handle_state_switch(DCB* dcb, DCB_REASON reason, void * routersession); /** * The module entry point routine. It is this routine that @@ -810,49 +809,3 @@ static SERVER_REF *get_root_master(SERVER_REF *servers) } return master_host; } - -static int handle_state_switch(DCB* dcb, DCB_REASON reason, void * routersession) -{ - ss_dassert(dcb != NULL); - MXS_SESSION* session = dcb->session; - ROUTER_CLIENT_SES* rses = (ROUTER_CLIENT_SES*) routersession; - SERVICE* service = session->service; - MXS_ROUTER* router = (MXS_ROUTER *) service->router; - - if (NULL == dcb->session->router_session && DCB_REASON_ERROR != reason) - { - /* - * We cannot handle a DCB that does not have a router session, - * except in the case where error processing is invoked. - */ - return 0; - } - switch (reason) - { - case DCB_REASON_CLOSE: - dcb->func.close(dcb); - break; - case DCB_REASON_DRAINED: - /** Do we need to do anything? */ - break; - case DCB_REASON_HIGH_WATER: - /** Do we need to do anything? */ - break; - case DCB_REASON_LOW_WATER: - /** Do we need to do anything? */ - break; - case DCB_REASON_ERROR: - dcb->func.error(dcb); - break; - case DCB_REASON_HUP: - dcb->func.hangup(dcb); - break; - case DCB_REASON_NOT_RESPONDING: - dcb->func.hangup(dcb); - break; - default: - break; - } - - return 0; -} From 7b3c287ac3af4002f55ac7486e9db9fb958660b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Mon, 13 Mar 2017 22:56:18 +0200 Subject: [PATCH 37/38] Close sessions in MaxScale core The core now provides a simple function to close a session. This removes the need for the modules to directly call the API entry points when the session should be closed. It is also in line with the style that other objects, namely the DCBs, use. This makes the new session_close very similar to dcb_close. --- include/maxscale/session.h | 11 +++++ server/core/session.c | 17 +++++++ server/modules/filter/tee/tee.c | 17 +------ .../MySQL/MySQLBackend/mysql_backend.c | 47 +++++++----------- .../protocol/MySQL/MySQLClient/mysql_client.c | 48 ++----------------- 5 files changed, 50 insertions(+), 90 deletions(-) diff --git a/include/maxscale/session.h b/include/maxscale/session.h index a85b9292a..92d38002a 100644 --- a/include/maxscale/session.h +++ b/include/maxscale/session.h @@ -324,6 +324,17 @@ static inline bool session_set_autocommit(MXS_SESSION* ses, bool autocommit) */ MXS_SESSION* session_get_by_id(int id); +/** + * @brief Close a session + * + * Calling this function will start the session shutdown process. The shutdown + * closes all related backend DCBs by calling the closeSession entry point + * of the router session. + * + * @param session The session to close + */ +void session_close(MXS_SESSION *session); + /** * @brief Release a session reference * diff --git a/server/core/session.c b/server/core/session.c index fff7f9832..d286f2507 100644 --- a/server/core/session.c +++ b/server/core/session.c @@ -303,6 +303,23 @@ session_simple_free(MXS_SESSION *session, DCB *dcb) session_final_free(session); } +void session_close(MXS_SESSION *session) +{ + if (!session->ses_is_child && session->router_session) + { + if (session->state != SESSION_STATE_STOPPING) + { + session->state = SESSION_STATE_STOPPING; + } + + MXS_ROUTER_OBJECT* router = session->service->router; + void* router_instance = session->service->router_instance; + + /** Close router session and all its connections */ + router->closeSession(router_instance, session->router_session); + } +} + /** * Deallocate the specified session * diff --git a/server/modules/filter/tee/tee.c b/server/modules/filter/tee/tee.c index 768c6b88a..da8a961da 100644 --- a/server/modules/filter/tee/tee.c +++ b/server/modules/filter/tee/tee.c @@ -536,22 +536,9 @@ closeSession(MXS_FILTER *instance, MXS_FILTER_SESSION *session) if ((bsession = my_session->branch_session) != NULL) { CHK_SESSION(bsession); - - if (bsession->state != SESSION_STATE_STOPPING) - { - bsession->state = SESSION_STATE_STOPPING; - } - router = bsession->service->router; - router_instance = bsession->service->router_instance; - rsession = bsession->router_session; - - /** Close router session and all its connections */ - router->closeSession(router_instance, rsession); + bsession->ses_is_child = false; + session_close(bsession); } - /* No need to free the session, this is done as - * a side effect of closing the client DCB of the - * session. - */ if (my_session->waiting[PARENT]) { diff --git a/server/modules/protocol/MySQL/MySQLBackend/mysql_backend.c b/server/modules/protocol/MySQL/MySQLBackend/mysql_backend.c index bed3034f8..56ccb4a75 100644 --- a/server/modules/protocol/MySQL/MySQLBackend/mysql_backend.c +++ b/server/modules/protocol/MySQL/MySQLBackend/mysql_backend.c @@ -1112,46 +1112,31 @@ static int gw_backend_hangup(DCB *dcb) */ static int gw_backend_close(DCB *dcb) { - MXS_SESSION* session; - GWBUF* quitbuf; - CHK_DCB(dcb); - session = dcb->session; - - MXS_DEBUG("%lu [gw_backend_close]", pthread_self()); - - quitbuf = mysql_create_com_quit(NULL, 0); - gwbuf_set_type(quitbuf, GWBUF_TYPE_MYSQL); + ss_dassert(dcb->session); /** Send COM_QUIT to the backend being closed */ + GWBUF* quitbuf = mysql_create_com_quit(NULL, 0); + gwbuf_set_type(quitbuf, GWBUF_TYPE_MYSQL); mysql_send_com_quit(dcb, 0, quitbuf); + /** Free protocol data */ mysql_protocol_done(dcb); - if (session) - { - CHK_SESSION(session); - /** - * The lock is needed only to protect the read of session->state and - * session->client_dcb values. Client's state may change by other thread - * but client's close and adding client's DCB to zombies list is executed - * only if client's DCB's state does _not_ change in parallel. - */ + MXS_SESSION* session = dcb->session; + CHK_SESSION(session); - /** - * If session->state is STOPPING, start closing client session. - * Otherwise only this backend connection is closed. - */ - if (session->state == SESSION_STATE_STOPPING && - session->client_dcb != NULL) - { - if (session->client_dcb->state == DCB_STATE_POLLING) - { - /** Close client DCB */ - dcb_close(session->client_dcb); - } - } + /** + * If session state is SESSION_STATE_STOPPING, start closing client session. + * Otherwise only this backend connection is closed. + */ + if (session->client_dcb && + session->state == SESSION_STATE_STOPPING && + session->client_dcb->state == DCB_STATE_POLLING) + { + dcb_close(session->client_dcb); } + return 1; } diff --git a/server/modules/protocol/MySQL/MySQLClient/mysql_client.c b/server/modules/protocol/MySQL/MySQLClient/mysql_client.c index a9aaff2ab..2f3888c27 100644 --- a/server/modules/protocol/MySQL/MySQLClient/mysql_client.c +++ b/server/modules/protocol/MySQL/MySQLClient/mysql_client.c @@ -1347,52 +1347,12 @@ retblock: return 1; } -static int -gw_client_close(DCB *dcb) +static int gw_client_close(DCB *dcb) { - MXS_SESSION* session; - MXS_ROUTER_OBJECT* router; - void* router_instance; -#if defined(SS_DEBUG) - MySQLProtocol* protocol = (MySQLProtocol *)dcb->protocol; - - if (dcb->state == DCB_STATE_POLLING || - dcb->state == DCB_STATE_NOPOLLING || - dcb->state == DCB_STATE_ZOMBIE) - { - if (!DCB_IS_CLONE(dcb)) - { - CHK_PROTOCOL(protocol); - } - } -#endif - MXS_DEBUG("%lu [gw_client_close]", pthread_self()); + CHK_DCB(dcb); + ss_dassert(dcb->protocol); mysql_protocol_done(dcb); - session = dcb->session; - /** - * session may be NULL if session_alloc failed. - * In that case, router session wasn't created. - */ - if (session != NULL && SESSION_STATE_DUMMY != session->state) - { - CHK_SESSION(session); - - if (session->state != SESSION_STATE_STOPPING) - { - session->state = SESSION_STATE_STOPPING; - } - router_instance = session->service->router_instance; - router = session->service->router; - /** - * If router session is being created concurrently router - * session might be NULL and it shouldn't be closed. - */ - if (session->router_session != NULL) - { - /** Close router session and all its connections */ - router->closeSession(router_instance, session->router_session); - } - } + session_close(dcb->session); return 1; } From dd82c46596ad772573b496f4bd873646ade15feb Mon Sep 17 00:00:00 2001 From: Esa Korhonen Date: Tue, 14 Mar 2017 10:40:36 +0200 Subject: [PATCH 38/38] Remove two checks that always return false At the point this function is called, the servers and services are not yet created. --- server/core/config.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/server/core/config.c b/server/core/config.c index b739668ba..9913f2958 100644 --- a/server/core/config.c +++ b/server/core/config.c @@ -3400,16 +3400,14 @@ bool config_param_is_valid(const MXS_MODULE_PARAM *params, const char *key, break; case MXS_MODULE_PARAM_SERVICE: - if ((context && config_contains_type(context, value, "service")) || - service_find(value)) + if (context && config_contains_type(context, value, "service")) { valid = true; } break; case MXS_MODULE_PARAM_SERVER: - if ((context && config_contains_type(context, value, "server")) || - server_find_by_unique_name(value)) + if (context && config_contains_type(context, value, "server")) { valid = true; }