diff --git a/Documentation/Release-Notes/MaxScale-2.1.9-Release-Notes.md b/Documentation/Release-Notes/MaxScale-2.1.9-Release-Notes.md index d3d9bdf85..b2c4bb96f 100644 --- a/Documentation/Release-Notes/MaxScale-2.1.9-Release-Notes.md +++ b/Documentation/Release-Notes/MaxScale-2.1.9-Release-Notes.md @@ -1,4 +1,4 @@ -# MariaDB MaxScale 2.1.9 Release Notes +# MariaDB MaxScale 2.1.9 Release Notes -- 2017-09-25 Release 2.1.9 is a GA release. diff --git a/VERSION21.cmake b/VERSION21.cmake index 361a61bcc..8ebe7a010 100644 --- a/VERSION21.cmake +++ b/VERSION21.cmake @@ -5,7 +5,7 @@ set(MAXSCALE_VERSION_MAJOR "2" CACHE STRING "Major version") set(MAXSCALE_VERSION_MINOR "1" CACHE STRING "Minor version") -set(MAXSCALE_VERSION_PATCH "9" CACHE STRING "Patch version") +set(MAXSCALE_VERSION_PATCH "10" CACHE STRING "Patch version") # This should only be incremented if a package is rebuilt set(MAXSCALE_BUILD_NUMBER 1 CACHE STRING "Release number") diff --git a/include/maxscale/utils.h b/include/maxscale/utils.h index c49eab1bb..4e0036a06 100644 --- a/include/maxscale/utils.h +++ b/include/maxscale/utils.h @@ -100,8 +100,41 @@ void gw_sha1_2_str(const uint8_t *in, int in_len, const uint8_t *in2, int in2_le int gw_getsockerrno(int fd); char *create_hex_sha1_sha1_passwd(char *passwd); -/** String formatting functions */ +/** + * Trim leading whitespace from a string. + * + * @param str String to trim. + * @return @c str + * + * @note If there is leading whitespace, the string is moved so that + * the returned pointer is always the same as the one given as + * argument. + */ +char* trim_leading(char* str); + +/** + * Trim trailing whitespace from a string. + * + * @param str String to trim. + * @return @c str + * + * @note The returned pointer is always the same the one given as + * argument. + */ +char* trim_trailing(char* str); + +/** + * Trim leading and trailing whitespace from a string. + * + * @param str String to trim. + * @return @c str + * + * @note If there is leading whitespace, the string is moved so that + * the returned pointer is always the same the one given as + * argument. + */ char* trim(char *str); + void replace_whitespace(char* str); char* squeeze_whitespace(char* str); bool strip_escape_chars(char*); diff --git a/maxscale-system-test/CMakeLists.txt b/maxscale-system-test/CMakeLists.txt index 914994d4a..2d443e18a 100644 --- a/maxscale-system-test/CMakeLists.txt +++ b/maxscale-system-test/CMakeLists.txt @@ -490,6 +490,10 @@ add_test_executable(mxs1418.cpp mxs1418 replication LABELS maxscale REPL_BACKEND # https://jira.mariadb.org/browse/MXS-1295 add_test_executable(mxs1295_sp_call.cpp mxs1295_sp_call mxs1295 LABELS maxscale REPL_BACKEND) +# MXS-1451: Password is not stored with skip_authentication=true +# https://jira.mariadb.org/browse/MXS-1451 +add_test_executable(mxs1451_skip_auth.cpp mxs1451_skip_auth mxs1451_skip_auth LABELS maxscale REPL_BACKEND) + # 'namedserverfilter' test add_test_executable(namedserverfilter.cpp namedserverfilter namedserverfilter LABELS namedserverfilter LIGHT REPL_BACKEND) diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.mxs1451_skip_auth b/maxscale-system-test/cnf/maxscale.cnf.template.mxs1451_skip_auth new file mode 100644 index 000000000..43df64165 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.mxs1451_skip_auth @@ -0,0 +1,89 @@ +[maxscale] +threads=###threads### + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +monitor_interval=1000 + +[RW Split Router] +type=service +router=readwritesplit +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options=slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +authenticator_options=skip_authentication=true + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 +authenticator_options=skip_authentication=true + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 +authenticator_options=skip_authentication=true + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend diff --git a/maxscale-system-test/mxs1451_skip_auth.cpp b/maxscale-system-test/mxs1451_skip_auth.cpp new file mode 100644 index 000000000..972873ac9 --- /dev/null +++ b/maxscale-system-test/mxs1451_skip_auth.cpp @@ -0,0 +1,62 @@ +/** + * MXS-1451: Password is not stored with skip_authentication=true + * + * Check that connection through MaxScale work even if authentication is disabled + */ + +#include "testconnections.h" + +int main(int argc, char *argv[]) +{ + TestConnections test(argc, argv); + + test.set_timeout(60); + test.tprintf("Creating users"); + test.repl->connect(); + execute_query(test.repl->nodes[0], "CREATE USER 'auth_test'@'%s' IDENTIFIED BY 'test'", test.maxscale_ip()); + execute_query(test.repl->nodes[0], "GRANT ALL ON *.* to 'auth_test'@'%s'", test.maxscale_ip()); + execute_query(test.repl->nodes[0], "CREATE USER 'auth_test_nopw'@'%s'", test.maxscale_ip()); + execute_query(test.repl->nodes[0], "GRANT ALL ON *.* to 'auth_test_nopw'@'%s'", test.maxscale_ip()); + test.repl->sync_slaves(); + test.repl->close_connections(); + + test.tprintf("Trying to connect through MaxScale"); + + test.set_timeout(60); + test.tprintf("... with correct credentials"); + MYSQL* conn = open_conn_db(test.rwsplit_port, test.maxscale_ip(), "test", "auth_test", "test", false); + test.try_query(conn, "SHOW DATABASES"); + mysql_close(conn); + + test.set_timeout(60); + test.tprintf("... without a password"); + conn = open_conn_db(test.rwsplit_port, test.maxscale_ip(), "test", "auth_test_nopw", "", false); + test.try_query(conn, "SHOW DATABASES"); + mysql_close(conn); + + test.set_timeout(60); + test.tprintf("... with wrong password"); + conn = open_conn_db(test.rwsplit_port, test.maxscale_ip(), "test", "auth_test", "wrong_password", false); + test.add_result(mysql_errno(conn) == 0, "Connection with wrong password should fail"); + mysql_close(conn); + + test.set_timeout(60); + test.tprintf("... with a password for user without a password"); + conn = open_conn_db(test.rwsplit_port, test.maxscale_ip(), "test", "auth_test_nopw", "test", false); + test.add_result(mysql_errno(conn) == 0, "Connection with wrong password to user without a password should fail"); + mysql_close(conn); + + test.tprintf("... with bad credentials"); + conn = open_conn_db(test.rwsplit_port, test.maxscale_ip(), "test", "wrong_user", "wrong_password", false); + test.add_result(mysql_errno(conn) == 0, "Connection with bad credentials should fail"); + mysql_close(conn); + + test.set_timeout(60); + test.tprintf("Dropping users"); + test.repl->connect(); + execute_query(test.repl->nodes[0], "DROP USER 'auth_test'@'%s'", test.maxscale_ip()); + execute_query(test.repl->nodes[0], "DROP USER 'auth_test_nopw'@'%s'", test.maxscale_ip()); + test.repl->close_connections(); + + return test.global_result; +} diff --git a/server/core/test/CMakeLists.txt b/server/core/test/CMakeLists.txt index 0696b650d..dd9d2e539 100644 --- a/server/core/test/CMakeLists.txt +++ b/server/core/test/CMakeLists.txt @@ -18,6 +18,7 @@ add_executable(test_spinlock testspinlock.cc) add_executable(test_trxcompare testtrxcompare.cc ../../../query_classifier/test/testreader.cc) add_executable(test_trxtracking testtrxtracking.cc) add_executable(test_users testusers.cc) +add_executable(test_utils testutils.cc) add_executable(testmaxscalepcre2 testmaxscalepcre2.cc) add_executable(testmodulecmd testmodulecmd.cc) add_executable(testconfig testconfig.cc) @@ -44,6 +45,7 @@ target_link_libraries(test_spinlock maxscale-common) target_link_libraries(test_trxcompare maxscale-common) target_link_libraries(test_trxtracking maxscale-common) target_link_libraries(test_users maxscale-common) +target_link_libraries(test_utils maxscale-common) target_link_libraries(testmaxscalepcre2 maxscale-common) target_link_libraries(testmodulecmd maxscale-common) target_link_libraries(testconfig maxscale-common) @@ -70,6 +72,7 @@ add_test(TestServer test_server) add_test(TestService test_service) add_test(TestSpinlock test_spinlock) add_test(TestUsers test_users) +add_test(TestUtils test_utils) add_test(TestModulecmd testmodulecmd) add_test(TestConfig testconfig) add_test(TestTrxTracking test_trxtracking) diff --git a/server/core/test/testutils.cc b/server/core/test/testutils.cc new file mode 100644 index 000000000..6dcb3bb53 --- /dev/null +++ b/server/core/test/testutils.cc @@ -0,0 +1,130 @@ +/* + * 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. + */ + +#include +#include +#include + +using std::cout; +using std::endl; + +namespace +{ + +#define TRIM_TCE(zFrom, zTo) { zFrom, zTo } + +struct TRIM_TEST_CASE +{ + const char* zFrom; + const char* zTo; +}; + +TRIM_TEST_CASE trim_testcases[] = +{ + TRIM_TCE("", ""), + TRIM_TCE("a", "a"), + TRIM_TCE(" a", "a"), + TRIM_TCE("a ", "a"), + TRIM_TCE(" a ", "a"), + TRIM_TCE(" a", "a"), + TRIM_TCE("a ", "a"), + TRIM_TCE(" a ", "a"), + TRIM_TCE(" a b ", "a b"), +}; + +const int n_trim_testcases = sizeof(trim_testcases) / sizeof(trim_testcases[0]); + +TRIM_TEST_CASE trim_leading_testcases[] = +{ + TRIM_TCE("", ""), + TRIM_TCE("a", "a"), + TRIM_TCE(" a", "a"), + TRIM_TCE("a ", "a "), + TRIM_TCE(" a ", "a "), + TRIM_TCE(" a", "a"), + TRIM_TCE("a ", "a "), + TRIM_TCE(" a ", "a "), + TRIM_TCE(" a b ", "a b "), +}; + +const int n_trim_leading_testcases = sizeof(trim_leading_testcases) / sizeof(trim_leading_testcases[0]); + +TRIM_TEST_CASE trim_trailing_testcases[] = +{ + TRIM_TCE("", ""), + TRIM_TCE("a", "a"), + TRIM_TCE(" a", " a"), + TRIM_TCE("a ", "a"), + TRIM_TCE(" a ", " a"), + TRIM_TCE(" a", " a"), + TRIM_TCE("a ", "a"), + TRIM_TCE(" a ", " a"), + TRIM_TCE(" a b ", " a b"), +}; + +const int n_trim_trailing_testcases = sizeof(trim_trailing_testcases) / sizeof(trim_trailing_testcases[0]); + + +int test(TRIM_TEST_CASE* pTest_cases, int n_test_cases, char* (*p)(char*)) +{ + int rv = 0; + + for (int i = 0; i < n_test_cases; ++i) + { + const char* zFrom = pTest_cases[i].zFrom; + const char* zTo = pTest_cases[i].zTo; + + char copy[strlen(zFrom) + 1]; + strcpy(copy, zFrom); + + char* z = p(copy); + + if (strcmp(z, zTo) != 0) + { + ++rv; + } + } + + return rv; +} + +int test_trim() +{ + cout << "trim()" << endl; + return test(trim_testcases, n_trim_testcases, trim); +} + +int test_trim_leading() +{ + cout << "trim_leading()" << endl; + return test(trim_leading_testcases, n_trim_leading_testcases, trim_leading); +} + +int test_trim_trailing() +{ + cout << "trim_trailing()" << endl; + return test(trim_trailing_testcases, n_trim_trailing_testcases, trim_trailing); +} + +} + +int main(int argc, char* argv[]) +{ + int rv = 0; + + rv += test_trim(); + rv += test_trim_leading(); + rv += test_trim_trailing(); + + return rv; +} diff --git a/server/core/utils.cc b/server/core/utils.cc index 6b96c5ff0..11628db70 100644 --- a/server/core/utils.cc +++ b/server/core/utils.cc @@ -452,13 +452,24 @@ bool mxs_mkdir_all(const char *path, int mask) return mkdir_all_internal(local_path, (mode_t)mask); } -/** - * Trim leading and trailing whitespace from a string - * - * @param str String to trim - * @return Trimmed string - */ -char* trim(char *str) +char* trim_leading(char* str) +{ + char* ptr = str; + + while (isspace(*ptr)) + { + ptr++; + } + + if (ptr != str) + { + memmove(str, ptr, strlen(ptr) + 1); + } + + return str; +} + +char* trim_trailing(char* str) { char* ptr = strchr(str, '\0') - 1; @@ -472,21 +483,14 @@ char* trim(char *str) *(ptr + 1) = '\0'; } - ptr = str; - - while (isspace(*ptr)) - { - ptr++; - } - - if (ptr != str) - { - memmove(str, ptr, strlen(ptr) + 1); - } - return str; } +char* trim(char *str) +{ + return trim_leading(trim_trailing(str)); +} + /** * @brief Replace whitespace with hyphens * diff --git a/server/modules/authenticator/MySQLAuth/dbusers.c b/server/modules/authenticator/MySQLAuth/dbusers.c index 2dba1f99a..76ad638bf 100644 --- a/server/modules/authenticator/MySQLAuth/dbusers.c +++ b/server/modules/authenticator/MySQLAuth/dbusers.c @@ -182,17 +182,25 @@ static int auth_cb(void *data, int columns, char** rows, char** row_names) return 0; } -int validate_mysql_user(sqlite3 *handle, DCB *dcb, MYSQL_session *session, +int validate_mysql_user(MYSQL_AUTH* instance, DCB *dcb, MYSQL_session *session, uint8_t *scramble, size_t scramble_len) { + sqlite3 *handle = instance->handle; 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]; int rval = MXS_AUTH_FAILED; char *err; - sprintf(sql, mysqlauth_validate_user_query, session->user, dcb->remote, - dcb->remote, session->db, session->db); + if (instance->skip_auth) + { + sprintf(sql, mysqlauth_skip_auth_query, session->user, session->db, session->db); + } + else + { + sprintf(sql, mysqlauth_validate_user_query, session->user, dcb->remote, + dcb->remote, session->db, session->db); + } struct user_query_result res = {}; diff --git a/server/modules/authenticator/MySQLAuth/mysql_auth.c b/server/modules/authenticator/MySQLAuth/mysql_auth.c index e56714d14..6ade98c73 100644 --- a/server/modules/authenticator/MySQLAuth/mysql_auth.c +++ b/server/modules/authenticator/MySQLAuth/mysql_auth.c @@ -304,19 +304,18 @@ mysql_auth_authenticate(DCB *dcb) MYSQL_AUTH *instance = (MYSQL_AUTH*)dcb->listener->auth_instance; MySQLProtocol *protocol = DCB_PROTOCOL(dcb, MySQLProtocol); - auth_ret = validate_mysql_user(instance->handle, dcb, client_data, + auth_ret = validate_mysql_user(instance, dcb, client_data, protocol->scramble, sizeof(protocol->scramble)); if (auth_ret != MXS_AUTH_SUCCEEDED && - !instance->skip_auth && service_refresh_users(dcb->service) == 0) { - auth_ret = validate_mysql_user(instance->handle, dcb, client_data, + auth_ret = validate_mysql_user(instance, dcb, client_data, protocol->scramble, sizeof(protocol->scramble)); } /* on successful authentication, set user into dcb field */ - if (auth_ret == MXS_AUTH_SUCCEEDED || instance->skip_auth) + if (auth_ret == MXS_AUTH_SUCCEEDED) { auth_ret = MXS_AUTH_SUCCEEDED; dcb->user = MXS_STRDUP_A(client_data->user); @@ -638,7 +637,7 @@ 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); + int rc = validate_mysql_user(instance, dcb, &temp, scramble, scramble_len); if (rc == MXS_AUTH_SUCCEEDED) { diff --git a/server/modules/authenticator/MySQLAuth/mysql_auth.h b/server/modules/authenticator/MySQLAuth/mysql_auth.h index b453704c2..d19a45e74 100644 --- a/server/modules/authenticator/MySQLAuth/mysql_auth.h +++ b/server/modules/authenticator/MySQLAuth/mysql_auth.h @@ -66,6 +66,12 @@ static const char mysqlauth_validate_user_query[] = " WHERE user = '%s' AND ( '%s' = host OR '%s' LIKE host) AND (anydb = '1' OR '%s' = '' OR '%s' LIKE db)" " LIMIT 1"; +/** Query that only checks if there's a matching user */ +static const char mysqlauth_skip_auth_query[] = + "SELECT password FROM " MYSQLAUTH_USERS_TABLE_NAME + " WHERE user = '%s' 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"; @@ -181,7 +187,7 @@ int replace_mysql_users(SERV_LISTENER *listener, bool skip_local); /** * @brief Verify the user has access to the database * - * @param handle SQLite handle to MySQLAuth user database + * @param instance MySQLAuth instance * @param dcb Client DCB * @param session Shared MySQL session * @param scramble The scramble sent to the client in the initial handshake @@ -189,7 +195,7 @@ int replace_mysql_users(SERV_LISTENER *listener, bool skip_local); * * @return MXS_AUTH_SUCCEEDED if the user has access to the database */ -int validate_mysql_user(sqlite3 *handle, DCB *dcb, MYSQL_session *session, - uint8_t *scramble, size_t scramble_len); +int validate_mysql_user(MYSQL_AUTH* instance, DCB *dcb, MYSQL_session *session, + uint8_t *scramble, size_t scramble_len); MXS_END_DECLS diff --git a/server/modules/routing/debugcli/debugcmd.c b/server/modules/routing/debugcli/debugcmd.c index 88a63cb4d..988a790de 100644 --- a/server/modules/routing/debugcli/debugcmd.c +++ b/server/modules/routing/debugcli/debugcmd.c @@ -27,33 +27,35 @@ */ #include +#include #include #include #include -#include -#include #include +#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include -#include +#include +#include +#include +#include +#include +#include #include -#include -#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include + #include "../../../core/maxscale/config_runtime.h" #include "../../../core/maxscale/maxscale.h" #include "../../../core/maxscale/modules.h" @@ -1906,7 +1908,7 @@ execute_cmd(CLI_SESSION *cli) bool in_space = false; int nskip = 0; - args[0] = cli->cmdbuf; + args[0] = trim_leading(cli->cmdbuf); ptr = args[0]; lptr = ptr; i = 1;