Merge branch '2.2' into 2.3

This commit is contained in:
Markus Mäkelä
2018-10-30 13:25:38 +02:00
9 changed files with 226 additions and 103 deletions

View File

@ -1084,4 +1084,10 @@ add_test_executable(mxs2043_select_for_update.cpp mxs2043_select_for_update repl
# MXS-2054: Hybrid clusters
add_test_executable(mxs2054_hybrid_cluster.cpp mxs2054_hybrid_cluster mxs2054_hybrid_cluster LABELS REPL_BACKEND)
# MXS-2111: mysql.user sometimes has SHA1 in authentication_string instead of password
add_test_executable(mxs2111_auth_string.cpp mxs2111_auth_string replication LABELS REPL_BACKEND)
# MXS-2115: Automatic version_string detection
add_test_executable(mxs2115_version_string.cpp mxs2115_version_string replication LABELS REPL_BACKEND)
configure_file(templates.h.in ${CMAKE_CURRENT_BINARY_DIR}/templates.h @ONLY)

View File

@ -0,0 +1,32 @@
/**
* MXS-2111: The password is stored in `authentication_string` instead of `password` due to MDEV-16774
*/
#include "testconnections.h"
int main(int argc, char **argv)
{
TestConnections::require_repl_version("10.2.0");
TestConnections test(argc, argv);
auto batch = [&](std::vector<std::string> queries){
test.maxscales->connect();
for (const auto& a: queries)
{
test.try_query(test.maxscales->conn_rwsplit[0], "%s", a.c_str());
}
test.maxscales->disconnect();
};
batch({"CREATE USER 'test' IDENTIFIED BY 'test'",
"GRANT SELECT ON *.* TO test",
"SET PASSWORD FOR 'test' = PASSWORD('test')"});
MYSQL* conn = open_conn(test.maxscales->rwsplit_port[0], test.maxscales->IP[0], "test", "test");
test.try_query(conn, "SELECT 1");
mysql_close(conn);
batch({"DROP USER 'test'"});
return test.global_result;
}

View File

@ -0,0 +1,21 @@
/**
* MXS-2115: Automatic version string detection doesn't work
*
* When servers are available, the backend server and Maxscale should return the
* same version string.
*/
#include "testconnections.h"
int main(int argc, char **argv)
{
TestConnections test(argc, argv);
test.repl->connect();
test.maxscales->connect();
std::string direct = mysql_get_server_info(test.repl->nodes[0]);
std::string mxs = mysql_get_server_info(test.maxscales->conn_rwsplit[0]);
test.expect(direct == mxs, "MaxScale sends wrong version: %s != %s", direct.c_str(), mxs.c_str());
return test.global_result;
}

View File

@ -15,12 +15,14 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <crypt.h>
#include <sys/stat.h>
#include <string>
#include <maxscale/alloc.h>
#include <maxscale/users.h>
#include <maxscale/adminusers.h>
#include <maxscale/log.h>
@ -60,8 +62,6 @@ void admin_users_init()
static bool admin_dump_users(USERS* users, const char* fname)
{
char path[PATH_MAX];
if (access(get_datadir(), F_OK) != 0)
{
if (mkdir(get_datadir(), S_IRWXU) != 0 && errno != EEXIST)
@ -74,17 +74,40 @@ static bool admin_dump_users(USERS* users, const char* fname)
}
}
snprintf(path, sizeof(path), "%s/%s", get_datadir(), fname);
json_t* json = users_to_json(users);
bool rval = true;
bool rval = false;
std::string path = std::string(get_datadir()) + "/" + fname;
std::string tmppath = path + ".tmp";
if (json_dump_file(json, path, 0) == -1)
int fd = open(tmppath.c_str(), O_CREAT | O_WRONLY | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
if (fd == -1)
{
MXS_ERROR("Failed to dump admin users to file");
rval = false;
MXS_ERROR("Failed to create '%s': %d, %s", tmppath.c_str(), errno, mxs_strerror(errno));
}
else
{
json_t* json = users_to_json(users);
char* str = json_dumps(json, 0);
json_decref(json);
json_decref(json);
if (write(fd, str, strlen(str)) == -1)
{
MXS_ERROR("Failed to dump admin users to '%s': %d, %s",
tmppath.c_str(), errno, mxs_strerror(errno));
}
else if (rename(tmppath.c_str(), path.c_str()) == -1)
{
MXS_ERROR("Failed to rename to '%s': %d, %s",
path.c_str(), errno, mxs_strerror(errno));
}
else
{
rval = true;
}
MXS_FREE(str);
close(fd);
}
return rval;
}

View File

@ -4055,21 +4055,22 @@ static bool check_path_parameter(const MXS_MODULE_PARAM* params, const char* val
}
int mode = F_OK;
int mask = X_OK;
int mask = 0;
if (params->options & MXS_MODULE_OPT_PATH_W_OK)
{
mask |= S_IWUSR;
mask |= S_IWUSR | S_IWGRP;
mode |= W_OK;
}
if (params->options & MXS_MODULE_OPT_PATH_R_OK)
{
mask |= S_IRUSR | S_IRGRP | S_IROTH;
mask |= S_IRUSR | S_IRGRP;
mode |= R_OK;
}
if (params->options & MXS_MODULE_OPT_PATH_X_OK)
{
mask |= S_IXUSR | S_IXGRP | S_IXOTH;
mask |= S_IXUSR | S_IXGRP;
mode |= X_OK;
}
if (access(buf, mode) == 0)

View File

@ -55,29 +55,33 @@
ON (u.user = t.user AND u.host = t.host) WHERE u.plugin IN ('', 'mysql_native_password') %s"
// Used with 10.2 or newer, supports composite roles
const char* mariadb_102_users_query
= // `t` is users that are not roles
"WITH RECURSIVE t AS ( "
" SELECT u.user, u.host, d.db, u.select_priv, u.password AS password, u.is_role, u.default_role"
" FROM mysql.user AS u LEFT JOIN mysql.db AS d "
" ON (u.user = d.user AND u.host = d.host) "
" UNION "
" SELECT u.user, u.host, t.db, u.select_priv, u.password AS password, u.is_role, u.default_role "
" FROM mysql.user AS u LEFT JOIN mysql.tables_priv AS t "
" ON (u.user = t.user AND u.host = t.host)"
"), users AS ("
// Select the root row, the actual user
" SELECT t.user, t.host, t.db, t.select_priv, t.password, t.default_role AS role FROM t"
" WHERE t.is_role <> 'Y'"
" UNION"
// Recursively select all roles for the users
" SELECT u.user, u.host, t.db, t.select_priv, u.password, r.role FROM t"
" JOIN users AS u"
" ON (t.user = u.role)"
" LEFT JOIN mysql.roles_mapping AS r"
" ON (t.user = r.user)"
")"
"SELECT DISTINCT t.user, t.host, t.db, t.select_priv, t.password FROM users AS t %s";
const char* mariadb_102_users_query =
// `t` is users that are not roles
"WITH RECURSIVE t AS ( "
" SELECT u.user, u.host, d.db, u.select_priv, "
" IF(u.password <> '', u.password, u.authentication_string) AS password, "
" u.is_role, u.default_role"
" FROM mysql.user AS u LEFT JOIN mysql.db AS d "
" ON (u.user = d.user AND u.host = d.host) "
" UNION "
" SELECT u.user, u.host, t.db, u.select_priv, "
" IF(u.password <> '', u.password, u.authentication_string), "
" u.is_role, u.default_role "
" FROM mysql.user AS u LEFT JOIN mysql.tables_priv AS t "
" ON (u.user = t.user AND u.host = t.host)"
"), users AS ("
// Select the root row, the actual user
" SELECT t.user, t.host, t.db, t.select_priv, t.password, t.default_role AS role FROM t"
" WHERE t.is_role <> 'Y'"
" UNION"
// Recursively select all roles for the users
" SELECT u.user, u.host, t.db, t.select_priv, u.password, r.role FROM t"
" JOIN users AS u"
" ON (t.user = u.role)"
" LEFT JOIN mysql.roles_mapping AS r"
" ON (t.user = r.user)"
")"
"SELECT DISTINCT t.user, t.host, t.db, t.select_priv, t.password FROM users AS t %s";
// Query used with MariaDB 10.1, supports basic roles
const char* mariadb_users_query
@ -930,11 +934,12 @@ static bool roles_are_available(MYSQL* conn, SERVICE* service, SERVER* server)
return rval;
}
static void report_mdev13453_problem(MYSQL* con, SERVER* server)
static bool have_mdev13453_problem(MYSQL *con, SERVER *server)
{
if (server->version >= 100200 && server->version < 100211
&& mxs_pcre2_simple_match("SELECT command denied to user .* for table 'users'",
mysql_error(con), 0, NULL) == MXS_PCRE2_MATCH)
bool rval = false;
if (mxs_pcre2_simple_match("SELECT command denied to user .* for table 'users'",
mysql_error(con), 0, NULL) == MXS_PCRE2_MATCH)
{
char user[256] = "<failed to query user>"; // Enough for all user-hostname combinations
const char* quoted_user = "select concat(\"'\", user, \"'@'\", host, \"'\") as user "
@ -954,9 +959,51 @@ static void report_mdev13453_problem(MYSQL* con, SERVER* server)
mysql_free_result(res);
}
MXS_ERROR("Due to MDEV-13453, the service user requires extra grants on the `mysql` database. "
"To fix the problem, add the following grant: GRANT SELECT ON `mysql`.* TO %s", user);
MXS_WARNING("Due to MDEV-13453, the service user requires extra grants on the `mysql` database in "
"order for roles to be used. To fix the problem, add the following grant: "
"GRANT SELECT ON `mysql`.* TO %s", user);
rval = true;
}
return rval;
}
bool query_and_process_users(const char* query, MYSQL *con, sqlite3* handle, SERVICE* service, int* users)
{
bool rval = false;
if (mxs_mysql_query(con, "USE mysql") == 0 && // Set default database in case we use CTEs
mxs_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]);
}
if (strchr(row[1], '/'))
{
merge_netmask(row[1]);
}
add_mysql_user(handle, row[0], row[1], row[2],
row[3] && strcmp(row[3], "Y") == 0, row[4]);
(*users)++;
}
mysql_free_result(result);
rval = true;
}
}
return rval;
}
int get_users_from_server(MYSQL* con, SERVER_REF* server_ref, SERVICE* service, SERV_LISTENER* listener)
@ -975,51 +1022,26 @@ int get_users_from_server(MYSQL* con, SERVER_REF* server_ref, SERVICE* service,
sqlite3* handle = get_handle(instance);
int users = 0;
if (query)
bool rv = query_and_process_users(query, con, handle, service, &users);
if (!rv && have_mdev13453_problem(con, server_ref->server))
{
if (mxs_mysql_query(con, "USE mysql") == 0 // Set default database in case we use CTEs
&& mxs_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]);
}
if (strchr(row[1], '/'))
{
merge_netmask(row[1]);
}
add_mysql_user(handle,
row[0],
row[1],
row[2],
row[3] && strcmp(row[3], "Y") == 0,
row[4]);
users++;
}
mysql_free_result(result);
}
}
else
{
MXS_ERROR("Failed to load users from server '%s': %s", server_ref->server->name,
mysql_error(con));
report_mdev13453_problem(con, server_ref->server);
}
/**
* Try to work around MDEV-13453 by using a query without CTEs. Masquerading as
* a 10.1.10 server makes sure CTEs aren't used.
*/
MXS_FREE(query);
query = get_users_query(server_ref->server->version_string, 100110, service->enable_root, true);
rv = query_and_process_users(query, con, handle, service, &users);
}
if (!rv)
{
MXS_ERROR("Failed to load users from server '%s': %s", server_ref->server->name, mysql_error(con));
}
MXS_FREE(query);
/** Load the list of databases */
if (mxs_mysql_query(con, "SHOW DATABASES") == 0)
{

View File

@ -201,6 +201,29 @@ static char* gw_default_auth()
return (char*)"MySQLAuth";
}
std::string get_version_string(SERVICE* service)
{
std::string rval;
uint64_t intver = UINT64_MAX;
for (SERVER_REF* ref = service->dbref; ref; ref = ref->next)
{
if (ref->server->version && ref->server->version < intver)
{
rval = ref->server->version_string;
intver = ref->server->version;
}
}
// Get the version string from service if no server version is available
if (rval.empty())
{
rval = service->version_string;
}
return rval;
}
/**
* MySQLSendHandshake
*
@ -227,8 +250,6 @@ int MySQLSendHandshake(DCB* dcb)
uint8_t mysql_filler_ten[10] = {};
/* uint8_t mysql_last_byte = 0x00; not needed */
char server_scramble[GW_MYSQL_SCRAMBLE_SIZE + 1] = "";
char* version_string;
int len_version_string = 0;
bool is_maria = false;
@ -243,11 +264,9 @@ int MySQLSendHandshake(DCB* dcb)
}
}
MySQLProtocol* protocol = DCB_PROTOCOL(dcb, MySQLProtocol);
GWBUF* buf;
version_string = dcb->service->version_string;
len_version_string = strlen(version_string);
MySQLProtocol *protocol = DCB_PROTOCOL(dcb, MySQLProtocol);
GWBUF *buf;
std::string version = get_version_string(dcb->service);
gw_generate_random_str(server_scramble, GW_MYSQL_SCRAMBLE_SIZE);
@ -281,12 +300,11 @@ int MySQLSendHandshake(DCB* dcb)
int plugin_name_len = strlen(plugin_name);
mysql_payload_size =
sizeof(mysql_protocol_version) + (len_version_string + 1) + sizeof(mysql_thread_id_num) + 8
+ sizeof( /* mysql_filler */ uint8_t) + sizeof(mysql_server_capabilities_one)
+ sizeof(mysql_server_language)
+ sizeof(mysql_server_status) + sizeof(mysql_server_capabilities_two) + sizeof(mysql_scramble_len)
+ sizeof(mysql_filler_ten) + 12 + sizeof( /* mysql_last_byte */ uint8_t) + plugin_name_len
+ sizeof( /* mysql_last_byte */ uint8_t);
sizeof(mysql_protocol_version) + (version.length() + 1) + sizeof(mysql_thread_id_num) + 8 +
sizeof(/* mysql_filler */ uint8_t) + sizeof(mysql_server_capabilities_one) + sizeof(mysql_server_language) +
sizeof(mysql_server_status) + sizeof(mysql_server_capabilities_two) + sizeof(mysql_scramble_len) +
sizeof(mysql_filler_ten) + 12 + sizeof(/* mysql_last_byte */ uint8_t) + plugin_name_len +
sizeof(/* mysql_last_byte */ uint8_t);
// allocate memory for packet header + payload
if ((buf = gwbuf_alloc(sizeof(mysql_packet_header) + mysql_payload_size)) == NULL)
@ -311,8 +329,8 @@ int MySQLSendHandshake(DCB* dcb)
mysql_handshake_payload = mysql_handshake_payload + sizeof(mysql_protocol_version);
// write server version plus 0 filler
strcpy((char*)mysql_handshake_payload, version_string);
mysql_handshake_payload = mysql_handshake_payload + len_version_string;
strcpy((char *)mysql_handshake_payload, version.c_str());
mysql_handshake_payload = mysql_handshake_payload + version.length();
*mysql_handshake_payload = 0x00;

View File

@ -519,7 +519,7 @@ static int blr_file_create(ROUTER_INSTANCE* router, char* orig_file)
// Set final file name full path
strcat(path, file);
int fd = open(path, O_RDWR | O_CREAT, 0666);
int fd = open(path, O_RDWR | O_CREAT, 0660);
if (fd != -1)
{
@ -621,7 +621,7 @@ void blr_file_append(ROUTER_INSTANCE* router, char* file)
// Add filename
strcat(path, file);
if ((fd = open(path, flags, 0666)) == -1)
if ((fd = open(path, flags, 0660)) == -1)
{
MXS_ERROR("Failed to open binlog file %s for append.",
path);
@ -940,7 +940,7 @@ BLFILE* blr_open_binlog(ROUTER_INSTANCE* router,
/* Add file name */
strcat(path, binlog);
if ((file->fd = open(path, O_RDONLY, 0666)) == -1)
if ((file->fd = open(path, O_RDONLY, 0660)) == -1)
{
MXS_ERROR("Failed to open binlog file %s", path);
MXS_FREE(file);
@ -1531,7 +1531,7 @@ void blr_cache_response(ROUTER_INSTANCE* router, char* response, GWBUF* buf)
strcat(path, "/");
strcat(path, response);
if ((fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0666)) == -1)
if ((fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0660)) == -1)
{
return;
}

View File

@ -160,7 +160,7 @@ int main(int argc, char** argv)
exit(EXIT_FAILURE);
}
int fd = open(path, binlog_file.fix ? O_RDWR : O_RDONLY, 0666);
int fd = open(path, binlog_file.fix ? O_RDWR : O_RDONLY, 0660);
if (fd == -1)
{
printf("ERROR: Failed to open binlog file %s: %s.\n",