From 9ca9b2ed446be384035b7b85711147840151201c Mon Sep 17 00:00:00 2001 From: Luke Lu Date: Wed, 13 May 2026 19:22:25 +0000 Subject: [PATCH] MDEV-8628 Expand current_user In binlog For GRANT/REVOKE/RENAME/DROPUSER/ALTERUSER When GRANT, REVOKE, RENAME USER, DROP USER, or ALTER USER reference CURRENT_USER, the binlog stores the raw SQL with the literal keyword rather than the resolved 'user'@'host' value. Live replication works because the slave SQL thread reads Q_INVOKER metadata from the binary event to resolve CURRENT_USER correctly. However, when the binlog is replayed via mysqlbinlog | mysql, the metadata is lost and CURRENT_USER resolves to whoever runs the replay client, producing wrong results. Fix by replacing CURRENT_USER with the expanded 'user'@'host' in the SQL text before writing to the binlog. Unlike SET PASSWORD which has a rigid fixed format (SET PASSWORD FOR 'user'@'host' = 'hash') and can reconstruct the entire query via sprintf, statements like GRANT and REVOKE have variable syntax with CURRENT_USER appearing at arbitrary positions in multi-user lists, optional clauses, and different grant targets. A text replacement helper is needed to handle this without reconstructing the full query for each statement type. The helper scans the query string, skips quoted literals, and performs a case-insensitive replacement of standalone current_user (with optional parentheses). It is called at each affected write_bin_log() call site only when the statement's user list contains CURRENT_USER, adding zero overhead to the common case. All new code of the whole pull request, including one or several files that are either new files or modified ones, are contributed under the BSD-new license. I am contributing on behalf of my employer Amazon Web Services, Inc. --- .../suite/binlog/r/binlog_mdev8628.result | 180 +++++++++++ .../suite/binlog/t/binlog_mdev8628.test | 302 ++++++++++++++++++ sql/sql_acl.cc | 216 ++++++++++++- 3 files changed, 687 insertions(+), 11 deletions(-) create mode 100644 mysql-test/suite/binlog/r/binlog_mdev8628.result create mode 100644 mysql-test/suite/binlog/t/binlog_mdev8628.test diff --git a/mysql-test/suite/binlog/r/binlog_mdev8628.result b/mysql-test/suite/binlog/r/binlog_mdev8628.result new file mode 100644 index 0000000000000..e684fddfc350e --- /dev/null +++ b/mysql-test/suite/binlog/r/binlog_mdev8628.result @@ -0,0 +1,180 @@ +RESET MASTER; +create user u1@'%'; +grant all on *.* to u1@'%' with grant option; +connect con1,localhost,u1,,; +# +# Test with lowercase current_user (no parentheses) +# +create database if not exists db; +grant select on db.* to current_user; +revoke select on db.* from current_user; +rename user current_user to u2; +rename user u2 to current_user; +drop user current_user; +Warnings: +Note 4227 Dropped users 'u1'@'%' have active connections. Use KILL CONNECTION if they should not be used anymore. +disconnect con1; +connection default; +# +# Test with CURRENT_USER() (uppercase, with parentheses) +# +create user u1@'%'; +grant all on *.* to u1@'%' with grant option; +connect con2,localhost,u1,,; +grant select on db.* to CURRENT_USER(); +revoke select on db.* from CURRENT_USER(); +rename user CURRENT_USER() to u2; +rename user u2 to CURRENT_USER(); +drop user CURRENT_USER(); +Warnings: +Note 4227 Dropped users 'u1'@'%' have active connections. Use KILL CONNECTION if they should not be used anymore. +disconnect con2; +connection default; +# +# Test ALTER USER with CURRENT_USER +# +create user u1@'%'; +grant all on *.* to u1@'%' with grant option; +connect con3,localhost,u1,,; +alter user CURRENT_USER PASSWORD EXPIRE NEVER; +disconnect con3; +connection default; +drop user u1@'%'; +# +# Test CURRENT_USER in a multi-user GRANT list +# +create user u1@'%'; +create user u2@'%'; +grant all on *.* to u1@'%' with grant option; +connect con4,localhost,u1,,; +grant select on db.* to u2@'%', CURRENT_USER, u2@'%'; +disconnect con4; +connection default; +drop user u1@'%'; +drop user u2@'%'; +# +# Test RENAME USER with CURRENT_USER on both sides +# +create user u1@'%'; +grant all on *.* to u1@'%' with grant option; +connect con5,localhost,u1,,; +rename user CURRENT_USER TO u3; +disconnect con5; +connection default; +rename user u3 to u1; +drop user u1@'%'; +# +# Test case-insensitivity: mixed case cUrReNt_UsEr +# +create user u1@'%'; +grant all on *.* to u1@'%' with grant option; +connect con6,localhost,u1,,; +grant select on db.* to cUrReNt_UsEr; +revoke select on db.* from cUrReNt_UsEr; +drop user cUrReNt_UsEr; +Warnings: +Note 4227 Dropped users 'u1'@'%' have active connections. Use KILL CONNECTION if they should not be used anymore. +disconnect con6; +connection default; +# +# Test REVOKE ALL PRIVILEGES with CURRENT_USER (mysql_revoke_all path) +# +create user u1@'%'; +grant all on *.* to u1@'%' with grant option; +create database if not exists db; +Warnings: +Note 1007 Can't create database 'db'; database exists +grant select on db.* to u1@'%'; +connect con7,localhost,u1,,; +revoke all privileges, grant option from CURRENT_USER; +disconnect con7; +connection default; +drop user u1@'%'; +# +# Test GRANT ROLE with CURRENT_USER (mysql_grant_role path) +# +create user u1@'%'; +grant all on *.* to u1@'%' with grant option; +create role r1; +grant r1 to u1@'%' with admin option; +connect con8,localhost,u1,,; +grant r1 to CURRENT_USER; +revoke r1 from CURRENT_USER; +disconnect con8; +connection default; +drop role r1; +drop user u1@'%'; +# +# Test GRANT ON PROCEDURE with CURRENT_USER (mysql_routine_grant path) +# +create user u1@'%'; +grant all on *.* to u1@'%' with grant option; +create database if not exists db; +Warnings: +Note 1007 Can't create database 'db'; database exists +create procedure db.p1() select 1; +connect con9,localhost,u1,,; +grant execute on procedure db.p1 to CURRENT_USER; +revoke execute on procedure db.p1 from CURRENT_USER; +disconnect con9; +connection default; +drop procedure db.p1; +drop user u1@'%'; +# +# Test SET PASSWORD with CURRENT_USER (already correct before this fix, +# regression guard) +# +create user u1@'%' identified by 'old'; +grant all on *.* to u1@'%' with grant option; +connect con11,localhost,u1,old,; +set password for CURRENT_USER() = password('new'); +disconnect con11; +connection default; +drop user u1@'%'; +# +# Test that host is also expanded (user with specific host, not just '%') +# +create user u1@'127.0.0.1'; +grant all on *.* to u1@'127.0.0.1' with grant option; +connect con10,127.0.0.1,u1,,; +grant select on db.* to CURRENT_USER; +drop user CURRENT_USER; +Warnings: +Note 4227 Dropped users 'u1'@'127.0.0.1' have active connections. Use KILL CONNECTION if they should not be used anymore. +disconnect con10; +connection default; +drop database if exists db; +# +# Verify that CURRENT_USER is expanded in binlog output. +# The binlog should contain 'u1'@'%' instead of literal current_user. +# +NOT FOUND /grant select on db\.\* to current_user/ in mdev8628.sql +NOT FOUND /revoke select on db\.\* from current_user/ in mdev8628.sql +NOT FOUND /rename user current_user/ in mdev8628.sql +NOT FOUND /drop user current_user/ in mdev8628.sql +NOT FOUND /drop user CURRENT_USER/ in mdev8628.sql +NOT FOUND /grant select on db\.\* to CURRENT_USER/ in mdev8628.sql +NOT FOUND /revoke select on db\.\* from CURRENT_USER/ in mdev8628.sql +NOT FOUND /rename user CURRENT_USER/ in mdev8628.sql +NOT FOUND /alter user CURRENT_USER/ in mdev8628.sql +NOT FOUND /drop user cUrReNt_UsEr/ in mdev8628.sql +NOT FOUND /grant select on db\.\* to cUrReNt_UsEr/ in mdev8628.sql +NOT FOUND /revoke all privileges.*from CURRENT_USER/ in mdev8628.sql +NOT FOUND /grant r1 to CURRENT_USER/ in mdev8628.sql +NOT FOUND /revoke r1 from CURRENT_USER/ in mdev8628.sql +NOT FOUND /grant execute on procedure.*to CURRENT_USER/ in mdev8628.sql +NOT FOUND /revoke execute on procedure.*from CURRENT_USER/ in mdev8628.sql +NOT FOUND /SET PASSWORD FOR CURRENT_USER/ in mdev8628.sql +FOUND 3 /grant select on db\.\* to 'u1'@'%'/ in mdev8628.sql +FOUND 3 /revoke select on db\.\* from 'u1'@'%'/ in mdev8628.sql +FOUND 2 /rename user 'u1'@'%' to u2/ in mdev8628.sql +FOUND 2 /rename user u2 to 'u1'@'%'/ in mdev8628.sql +FOUND 3 /drop user 'u1'@'%'/ in mdev8628.sql +FOUND 1 /alter user 'u1'@'%'/ in mdev8628.sql +FOUND 1 /SET PASSWORD FOR 'u1'@'%'/ in mdev8628.sql +FOUND 1 /grant select on db\.\* to 'u1'@'127\.0\.0\.1'/ in mdev8628.sql +FOUND 1 /drop user 'u1'@'127\.0\.0\.1'/ in mdev8628.sql +FOUND 1 /grant r1 to 'u1'@'%'/ in mdev8628.sql +FOUND 1 /revoke r1 from 'u1'@'%'/ in mdev8628.sql +FOUND 1 /grant execute on procedure.*to 'u1'@'%'/ in mdev8628.sql +FOUND 1 /revoke execute on procedure.*from 'u1'@'%'/ in mdev8628.sql diff --git a/mysql-test/suite/binlog/t/binlog_mdev8628.test b/mysql-test/suite/binlog/t/binlog_mdev8628.test new file mode 100644 index 0000000000000..c5e6a6dd9306a --- /dev/null +++ b/mysql-test/suite/binlog/t/binlog_mdev8628.test @@ -0,0 +1,302 @@ +# MDEV-8628 Replication magic for CURRENT_USER in binlog does not work +# when binlog is replayed +# +# CURRENT_USER should be expanded to the actual 'user'@'host' in the binlog +# SQL text for GRANT, REVOKE, RENAME USER, DROP USER, and ALTER USER. +# SET PASSWORD already does this correctly. Without expansion, replaying +# the binlog via mysqlbinlog | mysql produces wrong results. + +--source include/have_log_bin.inc +--source include/have_binlog_format_mixed.inc + +RESET MASTER; + +create user u1@'%'; +grant all on *.* to u1@'%' with grant option; + +--connect(con1,localhost,u1,,) + +--echo # +--echo # Test with lowercase current_user (no parentheses) +--echo # +create database if not exists db; +grant select on db.* to current_user; +revoke select on db.* from current_user; +rename user current_user to u2; +rename user u2 to current_user; +drop user current_user; + +--disconnect con1 +--connection default + +--echo # +--echo # Test with CURRENT_USER() (uppercase, with parentheses) +--echo # +create user u1@'%'; +grant all on *.* to u1@'%' with grant option; + +--connect(con2,localhost,u1,,) + +grant select on db.* to CURRENT_USER(); +revoke select on db.* from CURRENT_USER(); +rename user CURRENT_USER() to u2; +rename user u2 to CURRENT_USER(); +drop user CURRENT_USER(); + +--disconnect con2 +--connection default + +--echo # +--echo # Test ALTER USER with CURRENT_USER +--echo # +create user u1@'%'; +grant all on *.* to u1@'%' with grant option; + +--connect(con3,localhost,u1,,) + +alter user CURRENT_USER PASSWORD EXPIRE NEVER; + +--disconnect con3 +--connection default +drop user u1@'%'; + +--echo # +--echo # Test CURRENT_USER in a multi-user GRANT list +--echo # +create user u1@'%'; +create user u2@'%'; +grant all on *.* to u1@'%' with grant option; + +--connect(con4,localhost,u1,,) + +grant select on db.* to u2@'%', CURRENT_USER, u2@'%'; + +--disconnect con4 +--connection default +drop user u1@'%'; +drop user u2@'%'; + +--echo # +--echo # Test RENAME USER with CURRENT_USER on both sides +--echo # +create user u1@'%'; +grant all on *.* to u1@'%' with grant option; + +--connect(con5,localhost,u1,,) + +rename user CURRENT_USER TO u3; + +--disconnect con5 +--connection default + +rename user u3 to u1; +drop user u1@'%'; + +--echo # +--echo # Test case-insensitivity: mixed case cUrReNt_UsEr +--echo # +create user u1@'%'; +grant all on *.* to u1@'%' with grant option; + +--connect(con6,localhost,u1,,) + +grant select on db.* to cUrReNt_UsEr; +revoke select on db.* from cUrReNt_UsEr; +drop user cUrReNt_UsEr; + +--disconnect con6 +--connection default + +--echo # +--echo # Test REVOKE ALL PRIVILEGES with CURRENT_USER (mysql_revoke_all path) +--echo # +create user u1@'%'; +grant all on *.* to u1@'%' with grant option; +create database if not exists db; +grant select on db.* to u1@'%'; + +--connect(con7,localhost,u1,,) + +revoke all privileges, grant option from CURRENT_USER; + +--disconnect con7 +--connection default +drop user u1@'%'; + +--echo # +--echo # Test GRANT ROLE with CURRENT_USER (mysql_grant_role path) +--echo # +create user u1@'%'; +grant all on *.* to u1@'%' with grant option; +create role r1; +# WITH ADMIN OPTION lets u1 grant r1 to others (including themselves) +grant r1 to u1@'%' with admin option; + +--connect(con8,localhost,u1,,) + +# u1 grants r1 to CURRENT_USER (u1 itself) - tests mysql_grant_role binlog path +grant r1 to CURRENT_USER; +revoke r1 from CURRENT_USER; + +--disconnect con8 +--connection default +drop role r1; +drop user u1@'%'; + +--echo # +--echo # Test GRANT ON PROCEDURE with CURRENT_USER (mysql_routine_grant path) +--echo # +create user u1@'%'; +grant all on *.* to u1@'%' with grant option; +create database if not exists db; +create procedure db.p1() select 1; + +--connect(con9,localhost,u1,,) + +grant execute on procedure db.p1 to CURRENT_USER; +revoke execute on procedure db.p1 from CURRENT_USER; + +--disconnect con9 +--connection default +drop procedure db.p1; +drop user u1@'%'; + +--echo # +--echo # Test SET PASSWORD with CURRENT_USER (already correct before this fix, +--echo # regression guard) +--echo # +create user u1@'%' identified by 'old'; +grant all on *.* to u1@'%' with grant option; + +--connect(con11,localhost,u1,old,) + +set password for CURRENT_USER() = password('new'); + +--disconnect con11 +--connection default +drop user u1@'%'; + +--echo # +--echo # Test that host is also expanded (user with specific host, not just '%') +--echo # +create user u1@'127.0.0.1'; +grant all on *.* to u1@'127.0.0.1' with grant option; + +--connect(con10,127.0.0.1,u1,,) + +grant select on db.* to CURRENT_USER; +drop user CURRENT_USER; + +--disconnect con10 +--connection default + +drop database if exists db; + +--echo # +--echo # Verify that CURRENT_USER is expanded in binlog output. +--echo # The binlog should contain 'u1'@'%' instead of literal current_user. +--echo # + +--let $MYSQLD_DATADIR= `select @@datadir` +--exec $MYSQL_BINLOG --short-form $MYSQLD_DATADIR/master-bin.000001 > $MYSQLTEST_VARDIR/tmp/mdev8628.sql + +# Check that the binlog SQL does NOT contain unexpanded current_user +# in user positions for GRANT/REVOKE/RENAME/DROP/ALTER statements. + +--let SEARCH_FILE= $MYSQLTEST_VARDIR/tmp/mdev8628.sql +--let SEARCH_PATTERN= grant select on db\.\* to current_user +--source include/search_pattern_in_file.inc + +--let SEARCH_PATTERN= revoke select on db\.\* from current_user +--source include/search_pattern_in_file.inc + +--let SEARCH_PATTERN= rename user current_user +--source include/search_pattern_in_file.inc + +--let SEARCH_PATTERN= drop user current_user +--source include/search_pattern_in_file.inc + +--let SEARCH_PATTERN= drop user CURRENT_USER +--source include/search_pattern_in_file.inc + +--let SEARCH_PATTERN= grant select on db\.\* to CURRENT_USER +--source include/search_pattern_in_file.inc + +--let SEARCH_PATTERN= revoke select on db\.\* from CURRENT_USER +--source include/search_pattern_in_file.inc + +--let SEARCH_PATTERN= rename user CURRENT_USER +--source include/search_pattern_in_file.inc + +--let SEARCH_PATTERN= alter user CURRENT_USER +--source include/search_pattern_in_file.inc + +--let SEARCH_PATTERN= drop user cUrReNt_UsEr +--source include/search_pattern_in_file.inc + +--let SEARCH_PATTERN= grant select on db\.\* to cUrReNt_UsEr +--source include/search_pattern_in_file.inc + +--let SEARCH_PATTERN= revoke all privileges.*from CURRENT_USER +--source include/search_pattern_in_file.inc + +--let SEARCH_PATTERN= grant r1 to CURRENT_USER +--source include/search_pattern_in_file.inc + +--let SEARCH_PATTERN= revoke r1 from CURRENT_USER +--source include/search_pattern_in_file.inc + +--let SEARCH_PATTERN= grant execute on procedure.*to CURRENT_USER +--source include/search_pattern_in_file.inc + +--let SEARCH_PATTERN= revoke execute on procedure.*from CURRENT_USER +--source include/search_pattern_in_file.inc + +--let SEARCH_PATTERN= SET PASSWORD FOR CURRENT_USER +--source include/search_pattern_in_file.inc + +# Verify expanded forms ARE present +--let SEARCH_PATTERN= grant select on db\.\* to 'u1'@'%' +--source include/search_pattern_in_file.inc + +--let SEARCH_PATTERN= revoke select on db\.\* from 'u1'@'%' +--source include/search_pattern_in_file.inc + +--let SEARCH_PATTERN= rename user 'u1'@'%' to u2 +--source include/search_pattern_in_file.inc + +--let SEARCH_PATTERN= rename user u2 to 'u1'@'%' +--source include/search_pattern_in_file.inc + +--let SEARCH_PATTERN= drop user 'u1'@'%' +--source include/search_pattern_in_file.inc + +--let SEARCH_PATTERN= alter user 'u1'@'%' +--source include/search_pattern_in_file.inc + +# Verify SET PASSWORD expanded correctly (regression guard) +--let SEARCH_PATTERN= SET PASSWORD FOR 'u1'@'%' +--source include/search_pattern_in_file.inc + +# Verify host-specific user is also expanded correctly +--let SEARCH_PATTERN= grant select on db\.\* to 'u1'@'127\.0\.0\.1' +--source include/search_pattern_in_file.inc + +--let SEARCH_PATTERN= drop user 'u1'@'127\.0\.0\.1' +--source include/search_pattern_in_file.inc + +# Verify role grant/revoke expanded forms +--let SEARCH_PATTERN= grant r1 to 'u1'@'%' +--source include/search_pattern_in_file.inc + +--let SEARCH_PATTERN= revoke r1 from 'u1'@'%' +--source include/search_pattern_in_file.inc + +# Verify routine grant/revoke expanded forms +--let SEARCH_PATTERN= grant execute on procedure.*to 'u1'@'%' +--source include/search_pattern_in_file.inc + +--let SEARCH_PATTERN= revoke execute on procedure.*from 'u1'@'%' +--source include/search_pattern_in_file.inc + +--remove_file $MYSQLTEST_VARDIR/tmp/mdev8628.sql diff --git a/sql/sql_acl.cc b/sql/sql_acl.cc index 85e8db91c0d7d..d466e4a9b32c1 100644 --- a/sql/sql_acl.cc +++ b/sql/sql_acl.cc @@ -7351,6 +7351,10 @@ static bool copy_and_check_auth(LEX_USER *to, LEX_USER *from, THD *thd) TRUE error */ +static bool rewrite_query_expanding_current_user(THD *thd, + List &users_list, + String *buf); + int mysql_table_grant(THD *thd, TABLE_LIST *table_list, List &user_list, List &columns, privilege_t rights, @@ -7589,7 +7593,14 @@ int mysql_table_grant(THD *thd, TABLE_LIST *table_list, mysql_mutex_unlock(&acl_cache->lock); if (!result) /* success */ - result= write_bin_log(thd, TRUE, thd->query(), thd->query_length()); + { + String rewritten_query; + if (rewrite_query_expanding_current_user(thd, user_list, &rewritten_query)) + result= write_bin_log(thd, TRUE, rewritten_query.c_ptr_safe(), + rewritten_query.length()); + else + result= write_bin_log(thd, TRUE, thd->query(), thd->query_length()); + } mysql_rwlock_unlock(&LOCK_grant); @@ -7717,8 +7728,18 @@ bool mysql_routine_grant(THD *thd, TABLE_LIST *table_list, if (write_to_binlog && !result) { - if (write_bin_log(thd, FALSE, thd->query(), thd->query_length())) - result= TRUE; + String rewritten_query; + if (rewrite_query_expanding_current_user(thd, user_list, &rewritten_query)) + { + if (write_bin_log(thd, FALSE, rewritten_query.c_ptr_safe(), + rewritten_query.length())) + result= TRUE; + } + else + { + if (write_bin_log(thd, FALSE, thd->query(), thd->query_length())) + result= TRUE; + } } mysql_rwlock_unlock(&LOCK_grant); @@ -7751,6 +7772,138 @@ static void append_user(THD *thd, String *str, LEX_USER *user) append_user(thd, str, & user->user, & user->host); } +/** + Rewrite a query string, expanding CURRENT_USER to 'user'@'host'. + + This is needed for GRANT, REVOKE, RENAME USER, and DROP USER so that + when the binlog is replayed via mysqlbinlog | mysql, the statements + reference the correct user rather than the literal CURRENT_USER keyword + (which would resolve to whoever runs the replay). + + @param thd Thread handle + @param users_list List of LEX_USER from the parsed statement (before + get_current_user resolution) + @param[out] buf Output buffer; if rewriting occurred, contains the + rewritten query. Otherwise empty. + + @return true if the query was rewritten (use buf), false otherwise + (use thd->query() as before) + + @note MDEV-8628 +*/ +static bool rewrite_query_expanding_current_user(THD *thd, + List &users_list, + String *buf) +{ + /* Check if any user in the list was specified as CURRENT_USER */ + bool has_current_user= false; + List_iterator it(users_list); + LEX_USER *user; + while ((user= it++)) + { + if (user->user.str == current_user.str) + { + has_current_user= true; + break; + } + } + + if (!has_current_user) + return false; + + /* + Replace all case-insensitive occurrences of "current_user" (with optional + parentheses) in the query with the quoted 'user'@'host' of the session. + */ + Security_context *sctx= thd->security_ctx; + if (!sctx) + return false; + + const char *query= thd->query(); + size_t query_len= thd->query_length(); + + /* + Build the replacement string: 'priv_user'@'priv_host' + Use append_user() so that special characters (single quotes, backslashes) + in the username or hostname are properly escaped, preventing syntax errors + and SQL injection during binlog replay. + */ + String replacement; + LEX_CSTRING user_name= { sctx->priv_user, strlen(sctx->priv_user) }; + LEX_CSTRING host_name= { sctx->priv_host, strlen(sctx->priv_host) }; + append_user(thd, &replacement, &user_name, &host_name); + + buf->length(0); + const char *pos= query; + const char *end= query + query_len; + + while (pos < end) + { + /* Skip quoted strings to avoid replacing inside them */ + if (*pos == '\'' || *pos == '"' || *pos == '`') + { + char quote= *pos; + buf->append(pos, 1); + pos++; + while (pos < end) + { + if (*pos == quote) + { + buf->append(pos, 1); + pos++; + if (pos < end && *pos == quote) + { + /* Escaped quote (doubled) */ + buf->append(pos, 1); + pos++; + } + else + break; + } + else if (*pos == '\\' && quote != '`' && pos + 1 < end) + { + buf->append(pos, 2); + pos+= 2; + } + else + { + buf->append(pos, 1); + pos++; + } + } + continue; + } + + /* Check for "current_user" keyword (case-insensitive) */ + if ((size_t)(end - pos) >= 12 && + strncasecmp(pos, "current_user", 12) == 0) + { + /* Make sure it's not part of a longer identifier */ + if ((pos == query || !my_isvar(system_charset_info, *(pos - 1))) && + (pos + 12 >= end || !my_isvar(system_charset_info, *(pos + 12)))) + { + const char *after= pos + 12; + /* Skip optional "()" */ + if (after < end && *after == '(') + { + after++; + if (after < end && *after == ')') + after++; + } + buf->append(replacement); + pos= after; + continue; + } + } + + buf->append(pos, 1); + pos++; + } + + return true; +} + + /** append a string to a buffer that will be later used as an error message @@ -8053,7 +8206,14 @@ bool mysql_grant_role(THD *thd, List &list, bool revoke) my_error(revoke ? ER_CANNOT_REVOKE_ROLE : ER_CANNOT_GRANT_ROLE, MYF(0), rolename.str, wrong_users.c_ptr_safe()); else - result= write_bin_log(thd, TRUE, thd->query(), thd->query_length()); + { + String rewritten_query; + if (rewrite_query_expanding_current_user(thd, list, &rewritten_query)) + result= write_bin_log(thd, TRUE, rewritten_query.c_ptr_safe(), + rewritten_query.length()); + else + result= write_bin_log(thd, TRUE, thd->query(), thd->query_length()); + } mysql_rwlock_unlock(&LOCK_grant); @@ -8157,7 +8317,12 @@ bool mysql_grant(THD *thd, LEX_CSTRING db, List &list, if (!result) { - result= write_bin_log(thd, TRUE, thd->query(), thd->query_length()); + String rewritten_query; + if (rewrite_query_expanding_current_user(thd, list, &rewritten_query)) + result= write_bin_log(thd, TRUE, rewritten_query.c_ptr_safe(), + rewritten_query.length()); + else + result= write_bin_log(thd, TRUE, thd->query(), thd->query_length()); } mysql_rwlock_unlock(&LOCK_grant); @@ -11576,7 +11741,14 @@ bool mysql_drop_user(THD *thd, List &list, bool handle_as_role) wrong_users.c_ptr_safe()); if (binlog) - result |= write_bin_log(thd, FALSE, thd->query(), thd->query_length()); + { + String rewritten_query; + if (rewrite_query_expanding_current_user(thd, list, &rewritten_query)) + result |= write_bin_log(thd, FALSE, rewritten_query.c_ptr_safe(), + rewritten_query.length()); + else + result |= write_bin_log(thd, FALSE, thd->query(), thd->query_length()); + } mysql_rwlock_unlock(&LOCK_grant); DBUG_RETURN(result); @@ -11670,7 +11842,14 @@ bool mysql_rename_user(THD *thd, List &list) my_error(ER_CANNOT_USER, MYF(0), "RENAME USER", wrong_users.c_ptr_safe()); if (some_users_renamed && mysql_bin_log.is_open()) - result |= write_bin_log(thd, FALSE, thd->query(), thd->query_length()); + { + String rewritten_query; + if (rewrite_query_expanding_current_user(thd, list, &rewritten_query)) + result |= write_bin_log(thd, FALSE, rewritten_query.c_ptr_safe(), + rewritten_query.length()); + else + result |= write_bin_log(thd, FALSE, thd->query(), thd->query_length()); + } mysql_rwlock_unlock(&LOCK_grant); DBUG_RETURN(result); @@ -11746,8 +11925,15 @@ int mysql_alter_user(THD* thd, List &users_list) } if (some_users_altered) - result|= write_bin_log(thd, FALSE, thd->query(), - thd->query_length()); + { + String rewritten_query; + if (rewrite_query_expanding_current_user(thd, users_list, &rewritten_query)) + result|= write_bin_log(thd, FALSE, rewritten_query.c_ptr_safe(), + rewritten_query.length()); + else + result|= write_bin_log(thd, FALSE, thd->query(), + thd->query_length()); + } DBUG_RETURN(result); } @@ -12002,8 +12188,16 @@ bool mysql_revoke_all(THD *thd, List &list) if (result) my_message(ER_REVOKE_GRANTS, ER_THD(thd, ER_REVOKE_GRANTS), MYF(0)); - result= result | - write_bin_log(thd, FALSE, thd->query(), thd->query_length()); + { + String rewritten_query; + if (rewrite_query_expanding_current_user(thd, list, &rewritten_query)) + result= result | + write_bin_log(thd, FALSE, rewritten_query.c_ptr_safe(), + rewritten_query.length()); + else + result= result | + write_bin_log(thd, FALSE, thd->query(), thd->query_length()); + } mysql_rwlock_unlock(&LOCK_grant);