diff --git a/libmariadb b/libmariadb index a4effc462dd..1e4b08bd298 160000 --- a/libmariadb +++ b/libmariadb @@ -1 +1 @@ -Subproject commit a4effc462ddb80b61ebb559d48b50fa8d6c0ed64 +Subproject commit 1e4b08bd2989c664f6f43e0dbb2c71be9552bc8c diff --git a/mysql-test/main/alter_user.result b/mysql-test/main/alter_user.result index cc3c4a43ffe..cae864fa437 100644 --- a/mysql-test/main/alter_user.result +++ b/mysql-test/main/alter_user.result @@ -65,7 +65,7 @@ alter user foo identified with 'somecoolplugin'; ERROR HY000: Operation ALTER USER failed for 'foo'@'%' show warnings; Level Code Message -Warning 1524 Plugin 'somecoolplugin' is not loaded +Error 1524 Plugin 'somecoolplugin' is not loaded Error 1396 Operation ALTER USER failed for 'foo'@'%' alter user foo identified with 'mysql_old_password'; select * from mysql.user where user = 'foo'; diff --git a/mysql-test/suite/plugins/r/multiauth.result b/mysql-test/suite/plugins/r/multiauth.result new file mode 100644 index 00000000000..998e6d4dce7 --- /dev/null +++ b/mysql-test/suite/plugins/r/multiauth.result @@ -0,0 +1,170 @@ +install soname 'auth_socket'; +install soname 'auth_ed25519'; +create user USER identified via unix_socket OR mysql_native_password as password("GOOD"); +create user mysqltest1 identified via unix_socket OR mysql_native_password as password("good"); +show create user mysqltest1; +CREATE USER for mysqltest1@% +CREATE USER 'mysqltest1'@'%' IDENTIFIED VIA unix_socket OR mysql_native_password USING '*8409037B3E362D6DAE24C8E667F4D3B66716144E' +# name match = ok +select user(), current_user(), database(); +user() current_user() database() +USER@localhost USER@% test +# name does not match, password good = ok +select user(), current_user(), database(); +user() current_user() database() +mysqltest1@localhost mysqltest1@% test +# name does not match, password bad = failure +drop user USER, mysqltest1; +create user USER identified via mysql_native_password as password("GOOD") OR unix_socket; +create user mysqltest1 identified via mysql_native_password as password("good") OR unix_socket; +show create user mysqltest1; +CREATE USER for mysqltest1@% +CREATE USER 'mysqltest1'@'%' IDENTIFIED VIA mysql_native_password USING '*8409037B3E362D6DAE24C8E667F4D3B66716144E' OR unix_socket +# name match = ok +select user(), current_user(), database(); +user() current_user() database() +USER@localhost USER@% test +# name does not match, password good = ok +select user(), current_user(), database(); +user() current_user() database() +mysqltest1@localhost mysqltest1@% test +# name does not match, password bad = failure +drop user USER, mysqltest1; +create user USER identified via unix_socket OR ed25519 as password("GOOD"); +create user mysqltest1 identified via unix_socket OR ed25519 as password("good"); +show create user mysqltest1; +CREATE USER for mysqltest1@% +CREATE USER 'mysqltest1'@'%' IDENTIFIED VIA unix_socket OR ed25519 USING 'F4aF8bw7130VaRbdLCl4f/P/wkjDmgJXwWvpJ5gmsZc' +# name match = ok +select user(), current_user(), database(); +user() current_user() database() +USER@localhost USER@% test +# name does not match, password good = ok +select user(), current_user(), database(); +user() current_user() database() +mysqltest1@localhost mysqltest1@% test +# name does not match, password bad = failure +drop user USER, mysqltest1; +create user USER identified via ed25519 as password("GOOD") OR unix_socket; +create user mysqltest1 identified via ed25519 as password("good") OR unix_socket; +show create user mysqltest1; +CREATE USER for mysqltest1@% +CREATE USER 'mysqltest1'@'%' IDENTIFIED VIA ed25519 USING 'F4aF8bw7130VaRbdLCl4f/P/wkjDmgJXwWvpJ5gmsZc' OR unix_socket +# name match = ok +select user(), current_user(), database(); +user() current_user() database() +USER@localhost USER@% test +# name does not match, password good = ok +select user(), current_user(), database(); +user() current_user() database() +mysqltest1@localhost mysqltest1@% test +# name does not match, password bad = failure +drop user USER, mysqltest1; +create user USER identified via ed25519 as password("GOOD") OR unix_socket OR mysql_native_password as password("works"); +create user mysqltest1 identified via ed25519 as password("good") OR unix_socket OR mysql_native_password as password("works"); +show create user mysqltest1; +CREATE USER for mysqltest1@% +CREATE USER 'mysqltest1'@'%' IDENTIFIED VIA ed25519 USING 'F4aF8bw7130VaRbdLCl4f/P/wkjDmgJXwWvpJ5gmsZc' OR unix_socket OR mysql_native_password USING '*7D8C3DF236D9163B6C274A9D47704BC496988460' +# name match = ok +select user(), current_user(), database(); +user() current_user() database() +USER@localhost USER@% test +# name does not match, password good = ok +select user(), current_user(), database(); +user() current_user() database() +mysqltest1@localhost mysqltest1@% test +# name does not match, second password works = ok +select user(), current_user(), database(); +user() current_user() database() +mysqltest1@localhost mysqltest1@% test +# name does not match, password bad = failure +drop user USER, mysqltest1; +create user mysqltest1 identified via mysql_native_password as password("good") OR mysql_native_password as password("works"); +show create user mysqltest1; +CREATE USER for mysqltest1@% +CREATE USER 'mysqltest1'@'%' IDENTIFIED VIA mysql_native_password USING '*8409037B3E362D6DAE24C8E667F4D3B66716144E' OR mysql_native_password USING '*7D8C3DF236D9163B6C274A9D47704BC496988460' +# password good = ok +select user(), current_user(), database(); +user() current_user() database() +mysqltest1@localhost mysqltest1@% test +# second password works = ok +select user(), current_user(), database(); +user() current_user() database() +mysqltest1@localhost mysqltest1@% test +# password bad = failure +drop user mysqltest1; +create user mysqltest1 identified via ed25519 as password("good") OR unix_socket OR mysql_native_password as password("works"); +show grants for mysqltest1; +Grants for mysqltest1@% +GRANT USAGE ON *.* TO 'mysqltest1'@'%' IDENTIFIED VIA ed25519 USING 'F4aF8bw7130VaRbdLCl4f/P/wkjDmgJXwWvpJ5gmsZc' OR unix_socket OR mysql_native_password USING '*7D8C3DF236D9163B6C274A9D47704BC496988460' +select json_detailed(priv) from mysql.global_priv where user='mysqltest1'; +json_detailed(priv) +{ + "access": 0, + "plugin": "mysql_native_password", + "authentication_string": "*7D8C3DF236D9163B6C274A9D47704BC496988460", + "auth_or": + [ + + { + "plugin": "ed25519", + "authentication_string": "F4aF8bw7130VaRbdLCl4f/P/wkjDmgJXwWvpJ5gmsZc" + }, + + { + "plugin": "unix_socket" + }, + + { + } + ] +} +select password,plugin,authentication_string from mysql.user where user='mysqltest1'; +Password plugin authentication_string +*7D8C3DF236D9163B6C274A9D47704BC496988460 mysql_native_password *7D8C3DF236D9163B6C274A9D47704BC496988460 +flush privileges; +show create user mysqltest1; +CREATE USER for mysqltest1@% +CREATE USER 'mysqltest1'@'%' IDENTIFIED VIA ed25519 USING 'F4aF8bw7130VaRbdLCl4f/P/wkjDmgJXwWvpJ5gmsZc' OR unix_socket OR mysql_native_password USING '*7D8C3DF236D9163B6C274A9D47704BC496988460' +set password for mysqltest1 = password('foobar'); +show create user mysqltest1; +CREATE USER for mysqltest1@% +CREATE USER 'mysqltest1'@'%' IDENTIFIED VIA ed25519 USING 'qv2mG6HWCuy32Slb5xhV4THStewNz2VINVPbgk+XAJ8' OR unix_socket OR mysql_native_password USING '*7D8C3DF236D9163B6C274A9D47704BC496988460' +alter user mysqltest1 identified via unix_socket OR mysql_native_password as password("some"); +show create user mysqltest1; +CREATE USER for mysqltest1@% +CREATE USER 'mysqltest1'@'%' IDENTIFIED VIA unix_socket OR mysql_native_password USING '*BFE3F4604CFD21E6595080A261D92EF0183B5971' +set password for mysqltest1 = password('foobar'); +show create user mysqltest1; +CREATE USER for mysqltest1@% +CREATE USER 'mysqltest1'@'%' IDENTIFIED VIA unix_socket OR mysql_native_password USING '*9B500343BC52E2911172EB52AE5CF4847604C6E5' +alter user mysqltest1 identified via unix_socket; +set password for mysqltest1 = password('bla'); +ERROR HY000: SET PASSWORD is ignored for users authenticating via unix_socket plugin +alter user mysqltest1 identified via mysql_native_password as password("some") or unix_socket; +show create user mysqltest1; +CREATE USER for mysqltest1@% +CREATE USER 'mysqltest1'@'%' IDENTIFIED VIA mysql_native_password USING '*BFE3F4604CFD21E6595080A261D92EF0183B5971' OR unix_socket +drop user mysqltest1; +create user mysqltest1 identified via ed25519 as password("good") OR unix_socket OR mysql_native_password as password("works"); +ERROR HY000: Column count of mysql.user is wrong. Expected 3, found 47. Created with MariaDB XX.YY.ZZ, now running XX.YY.ZZ. Please use mysql_upgrade to fix this error +create user USER identified via mysql_native_password as '1234567890123456789012345678901234567890a' OR unix_socket; +create user mysqltest1 identified via mysql_native_password as '1234567890123456789012345678901234567890a' OR unix_socket; +update mysql.global_priv set priv=replace(priv, '1234567890123456789012345678901234567890a', 'invalid password'); +flush privileges; +show create user mysqltest1; +CREATE USER for mysqltest1@% +CREATE USER 'mysqltest1'@'%' IDENTIFIED VIA mysql_native_password USING 'invalid password' OR unix_socket +# name match = ok +select user(), current_user(), database(); +user() current_user() database() +USER@localhost USER@% test +# name does not match = failure +# SET PASSWORD helps +set password for mysqltest1 = password('bla'); +select user(), current_user(), database(); +user() current_user() database() +mysqltest1@localhost mysqltest1@% test +drop user USER, mysqltest1; +uninstall soname 'auth_socket'; +uninstall soname 'auth_ed25519'; diff --git a/mysql-test/suite/plugins/t/multiauth.test b/mysql-test/suite/plugins/t/multiauth.test new file mode 100644 index 00000000000..cb86b4ea2f0 --- /dev/null +++ b/mysql-test/suite/plugins/t/multiauth.test @@ -0,0 +1,179 @@ +# +# MDEV-11340 Allow multiple alternative authentication methods for the same user +# +--source include/have_unix_socket.inc +if (`SELECT '$USER' = 'mysqltest1'`) { + skip USER is mysqltest1; +} +if (!$AUTH_ED25519_SO) { + skip No auth_ed25519 plugin; +} + +--let $plugindir=`SELECT @@global.plugin_dir` +install soname 'auth_socket'; +install soname 'auth_ed25519'; + +--let $try_auth=$MYSQL_TEST < $MYSQLTEST_VARDIR/tmp/peercred_test.txt + +--write_file $MYSQLTEST_VARDIR/tmp/peercred_test.txt +--let $replace1=$USER@localhost +--let $replace2=$USER@% +--replace_result $replace1 "USER@localhost" $replace2 "USER@%" +select user(), current_user(), database(); +EOF + +--let $creplace=create user $USER +--let $dreplace=drop user $USER + +# +# socket,password +# +--replace_result $creplace "create user USER" +eval $creplace identified via unix_socket OR mysql_native_password as password("GOOD"); +create user mysqltest1 identified via unix_socket OR mysql_native_password as password("good"); +show create user mysqltest1; +--echo # name match = ok +--exec $try_auth -u $USER +--echo # name does not match, password good = ok +--exec $try_auth -u mysqltest1 -pgood +--echo # name does not match, password bad = failure +--error 1 +--exec $try_auth -u mysqltest1 -pbad +--replace_result $dreplace "drop user USER" +eval $dreplace, mysqltest1; + +# +# password,socket +# +--replace_result $creplace "create user USER" +eval $creplace identified via mysql_native_password as password("GOOD") OR unix_socket; +create user mysqltest1 identified via mysql_native_password as password("good") OR unix_socket; +show create user mysqltest1; +--echo # name match = ok +--exec $try_auth -u $USER +--echo # name does not match, password good = ok +--exec $try_auth -u mysqltest1 -pgood +--echo # name does not match, password bad = failure +--error 1 +--exec $try_auth -u mysqltest1 -pbad +--replace_result $dreplace "drop user USER" +eval $dreplace, mysqltest1; + +# +# socket,ed25519 +# +--replace_result $creplace "create user USER" +eval $creplace identified via unix_socket OR ed25519 as password("GOOD"); +create user mysqltest1 identified via unix_socket OR ed25519 as password("good"); +show create user mysqltest1; +--echo # name match = ok +--exec $try_auth -u $USER +--echo # name does not match, password good = ok +--exec $try_auth -u mysqltest1 -pgood +--echo # name does not match, password bad = failure +--error 1 +--exec $try_auth -u mysqltest1 -pbad +--replace_result $dreplace "drop user USER" +eval $dreplace, mysqltest1; + +# +# ed25519,socket +# +--replace_result $creplace "create user USER" +eval $creplace identified via ed25519 as password("GOOD") OR unix_socket; +create user mysqltest1 identified via ed25519 as password("good") OR unix_socket; +show create user mysqltest1; +--echo # name match = ok +--exec $try_auth -u $USER +--echo # name does not match, password good = ok +--exec $try_auth -u mysqltest1 -pgood +--echo # name does not match, password bad = failure +--error 1 +--exec $try_auth -u mysqltest1 -pbad +--replace_result $dreplace "drop user USER" +eval $dreplace, mysqltest1; + +# +# ed25519,socket,password +# +--replace_result $creplace "create user USER" +eval $creplace identified via ed25519 as password("GOOD") OR unix_socket OR mysql_native_password as password("works"); +create user mysqltest1 identified via ed25519 as password("good") OR unix_socket OR mysql_native_password as password("works"); +show create user mysqltest1; +--echo # name match = ok +--exec $try_auth -u $USER +--echo # name does not match, password good = ok +--exec $try_auth -u mysqltest1 -pgood +--echo # name does not match, second password works = ok +--exec $try_auth -u mysqltest1 -pworks +--echo # name does not match, password bad = failure +--error 1 +--exec $try_auth -u mysqltest1 -pbad +--replace_result $dreplace "drop user USER" +eval $dreplace, mysqltest1; + +# +# password,password +# +create user mysqltest1 identified via mysql_native_password as password("good") OR mysql_native_password as password("works"); +show create user mysqltest1; +--echo # password good = ok +--exec $try_auth -u mysqltest1 -pgood +--echo # second password works = ok +--exec $try_auth -u mysqltest1 -pworks +--echo # password bad = failure +--error 1 +--exec $try_auth -u mysqltest1 -pbad +drop user mysqltest1; + +# +# show grants, flush privileges, set password, alter user +# +create user mysqltest1 identified via ed25519 as password("good") OR unix_socket OR mysql_native_password as password("works"); +show grants for mysqltest1; +select json_detailed(priv) from mysql.global_priv where user='mysqltest1'; +select password,plugin,authentication_string from mysql.user where user='mysqltest1'; +flush privileges; +show create user mysqltest1; +set password for mysqltest1 = password('foobar'); +show create user mysqltest1; +alter user mysqltest1 identified via unix_socket OR mysql_native_password as password("some"); +show create user mysqltest1; +set password for mysqltest1 = password('foobar'); +show create user mysqltest1; +alter user mysqltest1 identified via unix_socket; +--error ER_SET_PASSWORD_AUTH_PLUGIN +set password for mysqltest1 = password('bla'); +alter user mysqltest1 identified via mysql_native_password as password("some") or unix_socket; +show create user mysqltest1; +drop user mysqltest1; + +--source include/switch_to_mysql_user.inc +--replace_regex /\d{6}/XX.YY.ZZ/ +--error ER_COL_COUNT_DOESNT_MATCH_PLEASE_UPDATE +create user mysqltest1 identified via ed25519 as password("good") OR unix_socket OR mysql_native_password as password("works"); +--source include/switch_to_mysql_global_priv.inc + +# +# invalid password,socket +# +--replace_result $creplace "create user USER" +eval $creplace identified via mysql_native_password as '1234567890123456789012345678901234567890a' OR unix_socket; +create user mysqltest1 identified via mysql_native_password as '1234567890123456789012345678901234567890a' OR unix_socket; +update mysql.global_priv set priv=replace(priv, '1234567890123456789012345678901234567890a', 'invalid password'); +flush privileges; +show create user mysqltest1; +--echo # name match = ok +--exec $try_auth -u $USER +--echo # name does not match = failure +--error 1 +--exec $try_auth -u mysqltest1 +--echo # SET PASSWORD helps +set password for mysqltest1 = password('bla'); +--exec $try_auth -u mysqltest1 -pbla +--replace_result $dreplace "drop user USER" +eval $dreplace, mysqltest1; + +uninstall soname 'auth_socket'; +uninstall soname 'auth_ed25519'; +--remove_file $MYSQLTEST_VARDIR/tmp/peercred_test.txt diff --git a/plugin/auth_gssapi/mysql-test/auth_gssapi/multiauth.result b/plugin/auth_gssapi/mysql-test/auth_gssapi/multiauth.result new file mode 100644 index 00000000000..c65eb7a8634 --- /dev/null +++ b/plugin/auth_gssapi/mysql-test/auth_gssapi/multiauth.result @@ -0,0 +1,34 @@ +INSTALL SONAME 'auth_gssapi'; +Warnings: +Note 1105 SSPI: using principal name 'localhost', mech 'Negotiate' +CREATE USER 'nosuchuser' IDENTIFIED WITH gssapi OR mysql_native_password as password("good"); +connect(localhost,nosuchuser,,test,MASTER_MYPORT,MASTER_MYSOCK); +connect con1,localhost,nosuchuser,,; +ERROR 28000: Access denied for user 'nosuchuser'@'localhost' (using password: NO) +connect con1,localhost,nosuchuser,good,; +SELECT USER(),CURRENT_USER(); +USER() CURRENT_USER() +nosuchuser@localhost nosuchuser@% +disconnect con1; +connection default; +DROP USER nosuchuser; +CREATE USER 'nosuchuser' IDENTIFIED WITH mysql_native_password as password("good") OR gssapi; +connect(localhost,nosuchuser,,test,MASTER_MYPORT,MASTER_MYSOCK); +connect con1,localhost,nosuchuser,,; +ERROR 28000: GSSAPI name mismatch, requested 'nosuchuser', actual name 'GSSAPI_SHORTNAME' +connect con1,localhost,nosuchuser,good,; +SELECT USER(),CURRENT_USER(); +USER() CURRENT_USER() +nosuchuser@localhost nosuchuser@% +disconnect con1; +connection default; +DROP USER nosuchuser; +CREATE USER 'GSSAPI_SHORTNAME' IDENTIFIED WITH mysql_native_password as password("good") OR gssapi; +connect con1,localhost,$GSSAPI_SHORTNAME,,; +SELECT USER(),CURRENT_USER(); +USER() CURRENT_USER() +GSSAPI_SHORTNAME@localhost GSSAPI_SHORTNAME@% +disconnect con1; +connection default; +DROP USER 'GSSAPI_SHORTNAME'; +UNINSTALL SONAME 'auth_gssapi'; diff --git a/plugin/auth_gssapi/mysql-test/auth_gssapi/multiauth.test b/plugin/auth_gssapi/mysql-test/auth_gssapi/multiauth.test new file mode 100644 index 00000000000..10e1e80907e --- /dev/null +++ b/plugin/auth_gssapi/mysql-test/auth_gssapi/multiauth.test @@ -0,0 +1,36 @@ +--replace_regex /name '[^']+'/name 'localhost'/ +INSTALL SONAME 'auth_gssapi'; + +# gssapi,password +CREATE USER 'nosuchuser' IDENTIFIED WITH gssapi OR mysql_native_password as password("good"); +replace_result $MASTER_MYSOCK MASTER_MYSOCK $MASTER_MYPORT MASTER_MYPORT; +error ER_ACCESS_DENIED_ERROR; +connect (con1,localhost,nosuchuser,,); +connect (con1,localhost,nosuchuser,good,); +SELECT USER(),CURRENT_USER(); +disconnect con1; +connection default; +DROP USER nosuchuser; + +# password,gssapi +CREATE USER 'nosuchuser' IDENTIFIED WITH mysql_native_password as password("good") OR gssapi; +replace_result $MASTER_MYSOCK MASTER_MYSOCK $MASTER_MYPORT MASTER_MYPORT $GSSAPI_SHORTNAME GSSAPI_SHORTNAME; +error ER_ACCESS_DENIED_ERROR; +connect (con1,localhost,nosuchuser,,); +connect (con1,localhost,nosuchuser,good,); +SELECT USER(),CURRENT_USER(); +disconnect con1; +connection default; +DROP USER nosuchuser; + +replace_result $GSSAPI_SHORTNAME GSSAPI_SHORTNAME; +eval CREATE USER '$GSSAPI_SHORTNAME' IDENTIFIED WITH mysql_native_password as password("good") OR gssapi; +connect (con1,localhost,$GSSAPI_SHORTNAME,,); +replace_result $GSSAPI_SHORTNAME GSSAPI_SHORTNAME; +SELECT USER(),CURRENT_USER(); +disconnect con1; +connection default; +replace_result $GSSAPI_SHORTNAME GSSAPI_SHORTNAME; +eval DROP USER '$GSSAPI_SHORTNAME'; + +UNINSTALL SONAME 'auth_gssapi'; diff --git a/sql/sql_acl.cc b/sql/sql_acl.cc index aa7b08092b5..ad9e2b446ed 100644 --- a/sql/sql_acl.cc +++ b/sql/sql_acl.cc @@ -1,5 +1,5 @@ /* Copyright (c) 2000, 2018, Oracle and/or its affiliates. - Copyright (c) 2009, 2018, MariaDB + Copyright (c) 2009, 2019, MariaDB Corporation. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -149,29 +149,41 @@ public: enum SSL_type ssl_type; uint password_errors; const char *ssl_cipher, *x509_issuer, *x509_subject; - LEX_CSTRING plugin; - LEX_CSTRING auth_string; LEX_CSTRING default_rolename; - LEX_CSTRING salt; + struct AUTH { LEX_CSTRING plugin, auth_string, salt; } *auth; + uint nauth; + + bool alloc_auth(MEM_ROOT *root, uint n) + { + return !(auth= (AUTH*) alloc_root(root, (nauth= n)*sizeof(AUTH))); + } ACL_USER *copy(MEM_ROOT *root) { - ACL_USER *dst= (ACL_USER *) alloc_root(root, sizeof(ACL_USER)); - if (!dst) + ACL_USER *dst; + AUTH *dauth; + if (!multi_alloc_root(root, &dst, sizeof(ACL_USER), + &dauth, sizeof(AUTH)*nauth, NULL)) return 0; *dst= *this; dst->user= safe_lexcstrdup_root(root, user); dst->ssl_cipher= safe_strdup_root(root, ssl_cipher); dst->x509_issuer= safe_strdup_root(root, x509_issuer); dst->x509_subject= safe_strdup_root(root, x509_subject); - if (plugin.str == native_password_plugin_name.str || - plugin.str == old_password_plugin_name.str) - dst->plugin= plugin; - else - dst->plugin= safe_lexcstrdup_root(root, plugin); - dst->auth_string= safe_lexcstrdup_root(root, auth_string); - if (salt.str) - dst->salt= safe_lexcstrdup_root(root, salt); + dst->auth= dauth; + for (uint i=0; i < nauth; i++, dauth++) + { + if (auth[i].plugin.str == native_password_plugin_name.str || + auth[i].plugin.str == old_password_plugin_name.str) + dauth->plugin= auth[i].plugin; + else + dauth->plugin= safe_lexcstrdup_root(root, auth[i].plugin); + dauth->auth_string= safe_lexcstrdup_root(root, auth[i].auth_string); + if (auth[i].salt.length == 0) + dauth->salt= auth[i].salt; + else + dauth->salt= safe_lexcstrdup_root(root, auth[i].salt); + } dst->host.hostname= safe_strdup_root(root, host.hostname); dst->default_rolename= safe_lexcstrdup_root(root, default_rolename); bzero(&dst->role_grants, sizeof(role_grants)); @@ -244,7 +256,6 @@ ulong role_global_merges= 0, role_db_merges= 0, role_table_merges= 0, #endif #ifndef NO_EMBEDDED_ACCESS_CHECKS -static bool fix_and_copy_user(LEX_USER *to, LEX_USER *from, THD *thd); static void update_hostname(acl_host_and_ip *host, const char *hostname); static ulong get_sort(uint count,...); static bool show_proxy_grants (THD *, const char *, const char *, @@ -817,8 +828,8 @@ class User_table: public Grant_table_base } virtual LEX_CSTRING& name() const = 0; - virtual int get_auth(THD *, MEM_ROOT *, const char **, const char **) const= 0; - virtual void set_auth(const char *, size_t, const char *, size_t) const = 0; + virtual int get_auth(THD *, MEM_ROOT *, ACL_USER *u) const= 0; + virtual bool set_auth(const ACL_USER &u) const = 0; virtual ulong get_access() const = 0; virtual void set_access(ulong rights, bool revoke) const = 0; @@ -867,54 +878,67 @@ class User_table_tabular: public User_table LEX_CSTRING& name() const { return MYSQL_TABLE_NAME_USER; } - int get_auth(THD *thd, MEM_ROOT *root, const char **plugin, const char **authstr) const + int get_auth(THD *thd, MEM_ROOT *root, ACL_USER *u) const { + u->alloc_auth(root, 1); if (have_password()) { - *authstr= safe_str(::get_field(&acl_memroot, password())); - *plugin= guess_auth_plugin(thd, strlen(*authstr)).str; + const char *as= safe_str(::get_field(&acl_memroot, password())); + u->auth->auth_string.str= as; + u->auth->auth_string.length= strlen(as); + u->auth->plugin= guess_auth_plugin(thd, u->auth->auth_string.length); } else { - *plugin= native_password_plugin_name.str; - *authstr= ""; + u->auth->plugin= native_password_plugin_name; + u->auth->auth_string= empty_clex_str; } - if (this->plugin() && this->authstr()) + if (plugin() && authstr()) { - char *tmpstr= ::get_field(&acl_memroot, this->plugin()); + char *tmpstr= ::get_field(&acl_memroot, plugin()); if (tmpstr) { - const char *passw= *authstr; - *plugin= tmpstr; - *authstr= safe_str(::get_field(&acl_memroot, this->authstr())); - - if (*passw) + const char *pw= u->auth->auth_string.str; + const char *as= safe_str(::get_field(&acl_memroot, authstr())); + if (*pw) { - if (**authstr && strcmp(*authstr, passw)) + if (*as && strcmp(as, pw)) { sql_print_warning("'user' entry '%s@%s' has both a password and an " "authentication plugin specified. The password will be ignored.", safe_str(get_user(thd->mem_root)), safe_str(get_host(thd->mem_root))); } else - *authstr= passw; + as= pw; } + u->auth->plugin.str= tmpstr; + u->auth->plugin.length= strlen(tmpstr); + u->auth->auth_string.str= as; + u->auth->auth_string.length= strlen(as); } } return 0; } - void set_auth(const char *p, size_t pl, const char *as, size_t asl) const + bool set_auth(const ACL_USER &u) const { + if (u.nauth != 1) + return 1; if (plugin()) { if (have_password()) password()->reset(); - plugin()->store(p, pl, system_charset_info); - authstr()->store(as, asl, system_charset_info); + plugin()->store(u.auth->plugin.str, u.auth->plugin.length, system_charset_info); + authstr()->store(u.auth->auth_string.str, u.auth->auth_string.length, system_charset_info); } else - password()->store(as, asl, system_charset_info); + { + if (u.auth->plugin.str != native_password_plugin_name.str && + u.auth->plugin.str != old_password_plugin_name.str) + return 1; + password()->store(u.auth->auth_string.str, u.auth->auth_string.length, system_charset_info); + } + return 0; } ulong get_access() const @@ -1202,18 +1226,130 @@ class User_table_json: public User_table { LEX_CSTRING& name() const { return MYSQL_TABLE_NAME[USER_TABLE]; } - int get_auth(THD *thd, MEM_ROOT *root, const char **plugin, const char **authstr) const + int get_auth(THD *thd, MEM_ROOT *root, ACL_USER *u) const { - *plugin= get_str_value(root, "plugin"); - if (!**plugin) - *plugin= native_password_plugin_name.str; - *authstr= get_str_value(root, "authentication_string"); - return *plugin == NULL || *authstr == NULL; + size_t array_len; + const char *array; + int vl; + const char *v; + + if (get_value("auth_or", JSV_ARRAY, &array, &array_len)) + { + u->alloc_auth(root, 1); + return get_auth1(thd, root, u, 0); + } + + if (json_get_array_item(array, array + array_len, (int)array_len, + &v, &vl) != JSV_NOTHING) + return 1; + u->alloc_auth(root, vl); + for (uint i=0; i < u->nauth; i++) + { + if (json_get_array_item(array, array + array_len, i, &v, &vl) != JSV_OBJECT) + return 1; + + const char *p, *a; + int pl, al; + switch (json_get_object_key(v, v + vl, "plugin", &p, &pl)) { + case JSV_STRING: u->auth[i].plugin.str= strmake_root(root, p, pl); + u->auth[i].plugin.length= pl; + break; + case JSV_NOTHING: if (get_auth1(thd, root, u, i)) + return 1; + else + continue; + default: return 1; + } + switch (json_get_object_key(v, v + vl, "authentication_string", &a, &al)) { + case JSV_NOTHING: u->auth[i].auth_string= empty_clex_str; + break; + case JSV_STRING: u->auth[i].auth_string.str= strmake_root(root, a, al); + u->auth[i].auth_string.length= al; + break; + default: return 1; + } + } + return 0; } - void set_auth(const char *p, size_t pl, const char *as, size_t asl) const + + int get_auth1(THD *thd, MEM_ROOT *root, ACL_USER *u, uint n) const { - set_str_value("plugin", p, pl); - set_str_value("authentication_string", as, asl); + const char *authstr= get_str_value(root, "authentication_string"); + const char *plugin= get_str_value(root, "plugin"); + if (plugin && authstr) + { + if (plugin && *plugin) + { + u->auth[n].plugin.str= plugin; + u->auth[n].plugin.length= strlen(plugin); + } + else + u->auth[n].plugin= native_password_plugin_name; + u->auth[n].auth_string.str= authstr; + u->auth[n].auth_string.length= strlen(authstr); + return 0; + } + return 1; + } + + bool append_str_value(String *to, const LEX_CSTRING &str) const + { + to->append('"'); + to->reserve(str.length*2); + int len= json_escape(system_charset_info, (uchar*)str.str, (uchar*)str.str + str.length, + to->charset(), (uchar*)to->end(), (uchar*)to->end() + str.length*2); + if (len < 0) + return 1; + to->length(to->length() + len); + to->append('"'); + return 0; + } + + bool set_auth(const ACL_USER &u) const + { + StringBuffer json(m_table->field[2]->charset()); + if (u.nauth == 1) + return set_auth1(u, 0); + bool top_done = false; + json.append('['); + for (uint i=0; i < u.nauth; i++) + { + ACL_USER::AUTH * const auth= u.auth + i; + if (i) + json.append(','); + json.append('{'); + if (!top_done && + (auth->plugin.str == native_password_plugin_name.str || + auth->plugin.str == old_password_plugin_name.str || + i == u.nauth - 1)) + { + if (set_auth1(u, i)) + return 1; + top_done= true; + } + else + { + json.append(STRING_WITH_LEN("\"plugin\":")); + if (append_str_value(&json, auth->plugin)) + return 1; + if (auth->auth_string.length) + { + json.append(STRING_WITH_LEN(",\"authentication_string\":")); + if (append_str_value(&json, auth->auth_string)) + return 1; + } + } + json.append('}'); + } + json.append(']'); + return set_value("auth_or", json.ptr(), json.length(), false) == JSV_BAD_JSON; + } + bool set_auth1(const ACL_USER &u, uint i) const + { + return set_str_value("plugin", + u.auth[i].plugin.str, u.auth[i].plugin.length) || + set_str_value("authentication_string", + u.auth[i].auth_string.str, u.auth[i].auth_string.length); } ulong get_access() const { @@ -1349,8 +1485,8 @@ class User_table_json: public User_table return false; return true; } - bool set_value(const char *key, - const char *val, size_t vlen, bool string) const + enum json_types set_value(const char *key, + const char *val, size_t vlen, bool string) const { int value_len; const char *value_start; @@ -1361,7 +1497,7 @@ class User_table_json: public User_table value_type= json_get_object_key(res->ptr(), res->end(), key, &value_start, &value_len); if (value_type == JSV_BAD_JSON) - return 1; // invalid + return value_type; // invalid StringBuffer json(res->charset()); json.copy(res->ptr(), value_start - res->ptr(), res->charset()); if (value_type == JSV_NOTHING) @@ -1382,7 +1518,7 @@ class User_table_json: public User_table json.append(value_start, res->end() - value_start); DBUG_ASSERT(json_valid(json.ptr(), json.length(), json.charset())); m_table->field[2]->store(json.ptr(), json.length(), json.charset()); - return 0; + return value_type; } bool set_str_value(const char *key, const char *val, size_t vlen) const { @@ -1393,22 +1529,24 @@ class User_table_json: public User_table (uchar*)buf, (uchar*)buf+sizeof(buf)); if (blen < 0) return 1; - return set_value(key, buf, blen, true); + return set_value(key, buf, blen, true) == JSV_BAD_JSON; } bool set_int_value(const char *key, longlong val) const { char v[MY_INT64_NUM_DECIMAL_DIGITS+1]; size_t vlen= longlong10_to_str(val, v, -10) - v; - return set_value(key, v, vlen, false); + return set_value(key, v, vlen, false) == JSV_BAD_JSON; } bool set_double_value(const char *key, double val) const { char v[FLOATING_POINT_BUFFER+1]; size_t vlen= my_fcvt(val, TIME_SECOND_PART_DIGITS, v, NULL); - return set_value(key, v, vlen, false); + return set_value(key, v, vlen, false) == JSV_BAD_JSON; } bool set_bool_value(const char *key, bool val) const - { return set_value(key, val ? "true" : "false", val ? 4 : 5, false); } + { + return set_value(key, val ? "true" : "false", val ? 4 : 5, false) == JSV_BAD_JSON; + } }; class Db_table: public Grant_table_base @@ -1781,22 +1919,22 @@ static bool validate_password(THD *thd, const LEX_CSTRING &user, return false; } -static int set_user_salt(ACL_USER *acl_user, plugin_ref plugin) +static int set_user_salt(ACL_USER::AUTH *auth, plugin_ref plugin) { - st_mysql_auth *auth= (st_mysql_auth *) plugin_decl(plugin)->info; - if (auth->interface_version >= 0x0202 && acl_user->auth_string.length && - auth->preprocess_hash) + st_mysql_auth *info= (st_mysql_auth *) plugin_decl(plugin)->info; + if (info->interface_version >= 0x0202 && info->preprocess_hash && + auth->auth_string.length) { uchar buf[MAX_SCRAMBLE_LENGTH]; size_t len= sizeof(buf); - if (auth->preprocess_hash(acl_user->auth_string.str, - acl_user->auth_string.length, buf, &len)) + if (info->preprocess_hash(auth->auth_string.str, + auth->auth_string.length, buf, &len)) return 1; - acl_user->salt.str= (char*)memdup_root(&acl_memroot, buf, len); - acl_user->salt.length= len; + auth->salt.str= (char*)memdup_root(&acl_memroot, buf, len); + auth->salt.length= len; } else - acl_user->salt= acl_user->auth_string; + auth->salt= auth->auth_string; return 0; } @@ -1808,13 +1946,14 @@ static int set_user_salt(ACL_USER *acl_user, plugin_ref plugin) converts auth_string to salt. Fails if the plain-text password fails validation, if the plugin is - not loaded, if the auth_string is invalid. + not loaded, if the auth_string is invalid, if the password is not applicable */ -static int set_user_auth(THD *thd, ACL_USER *acl_user, const LEX_CSTRING &pwtext) +static int set_user_auth(THD *thd, const LEX_CSTRING &user, + ACL_USER::AUTH *auth, const LEX_CSTRING &pwtext) { - const char *plugin_name= acl_user->plugin.str; + const char *plugin_name= auth->plugin.str; bool unlock_plugin= false; - plugin_ref plugin= get_auth_plugin(thd, acl_user->plugin, &unlock_plugin); + plugin_ref plugin= get_auth_plugin(thd, auth->plugin, &unlock_plugin); int res= 1; if (!plugin) @@ -1822,40 +1961,49 @@ static int set_user_auth(THD *thd, ACL_USER *acl_user, const LEX_CSTRING &pwtext push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_PLUGIN_IS_NOT_LOADED, ER_THD(thd, ER_PLUGIN_IS_NOT_LOADED), plugin_name); - return res; + return ER_PLUGIN_IS_NOT_LOADED; } - acl_user->salt= acl_user->auth_string; + auth->salt= auth->auth_string; - st_mysql_auth *auth= (st_mysql_auth *) plugin_decl(plugin)->info; - if (auth->interface_version >= 0x0202) + st_mysql_auth *info= (st_mysql_auth *) plugin_decl(plugin)->info; + if (info->interface_version < 0x0202) { - if (auth->hash_password && - validate_password(thd, acl_user->user, pwtext, - acl_user->auth_string.length)) - goto end; - if (pwtext.length) + res= pwtext.length ? ER_SET_PASSWORD_AUTH_PLUGIN : 0; + goto end; + } + + if (info->hash_password && + validate_password(thd, user, pwtext, auth->auth_string.length)) + { + res= ER_NOT_VALID_PASSWORD; + goto end; + } + if (pwtext.length) + { + if (info->hash_password) { - if (auth->hash_password) + char buf[MAX_SCRAMBLE_LENGTH]; + size_t len= sizeof(buf) - 1; + if (info->hash_password(pwtext.str, pwtext.length, buf, &len)) { - char buf[MAX_SCRAMBLE_LENGTH]; - size_t len= sizeof(buf) - 1; - if (auth->hash_password(pwtext.str, pwtext.length, buf, &len)) - goto end; // OOM? - buf[len] = 0; - acl_user->auth_string.str= (char*)memdup_root(&acl_memroot, buf, len+1); - acl_user->auth_string.length= len; - } - else - { - push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, - ER_SET_PASSWORD_AUTH_PLUGIN, - ER_THD(thd, ER_SET_PASSWORD_AUTH_PLUGIN), - acl_user->plugin.str); + res= ER_OUTOFMEMORY; + goto end; } + buf[len] = 0; + auth->auth_string.str= (char*)memdup_root(&acl_memroot, buf, len+1); + auth->auth_string.length= len; } - if (set_user_salt(acl_user, plugin)) + else + { + res= ER_SET_PASSWORD_AUTH_PLUGIN; goto end; + } + } + if (set_user_salt(auth, plugin)) + { + res= ER_PASSWD_LENGTH; + goto end; } res= 0; @@ -1869,25 +2017,30 @@ end: /** Lazily computes user's salt from the password hash */ -static bool set_user_salt_if_needed(ACL_USER *user_copy, plugin_ref plugin) +static bool set_user_salt_if_needed(ACL_USER *user_copy, int curr_auth, + plugin_ref plugin) { - DBUG_ASSERT(!strcasecmp(user_copy->plugin.str, plugin_name(plugin)->str)); + ACL_USER::AUTH *auth_copy= user_copy->auth + curr_auth; + DBUG_ASSERT(!strcasecmp(auth_copy->plugin.str, plugin_name(plugin)->str)); - if (user_copy->salt.str) + if (auth_copy->salt.str) return 0; // already done - if (set_user_salt(user_copy, plugin)) + if (set_user_salt(auth_copy, plugin)) return 1; mysql_mutex_lock(&acl_cache->lock); ACL_USER *user= find_user_exact(user_copy->host.hostname, user_copy->user.str); // make sure the user wasn't altered or dropped meanwhile - if (user && !user->salt.str && - user->plugin.length == user_copy->plugin.length && - user->auth_string.length == user_copy->auth_string.length && - !memcmp(user->plugin.str, user_copy->plugin.str, user->plugin.length) && - !memcmp(user->auth_string.str, user_copy->auth_string.str, user->auth_string.length)) - user->salt= user_copy->salt; + if (user) + { + ACL_USER::AUTH *auth= user->auth + curr_auth; + if (!auth->salt.str && auth->plugin.length == auth_copy->plugin.length && + auth->auth_string.length == auth_copy->auth_string.length && + !memcmp(auth->plugin.str, auth_copy->plugin.str, auth->plugin.length) && + !memcmp(auth->auth_string.str, auth_copy->auth_string.str, auth->auth_string.length)) + auth->salt= auth_copy->salt; + } mysql_mutex_unlock(&acl_cache->lock); return 0; } @@ -1904,13 +2057,13 @@ static bool set_user_salt_if_needed(ACL_USER *user_copy, plugin_ref plugin) @retval 0 the pointers were fixed @retval 1 this ACL_USER uses a not built-in plugin */ -static bool fix_user_plugin_ptr(ACL_USER *user) +static bool fix_user_plugin_ptr(ACL_USER::AUTH *auth) { - if (lex_string_eq(&user->plugin, &native_password_plugin_name)) - user->plugin= native_password_plugin_name; + if (lex_string_eq(&auth->plugin, &native_password_plugin_name)) + auth->plugin= native_password_plugin_name; else - if (lex_string_eq(&user->plugin, &old_password_plugin_name)) - user->plugin= old_password_plugin_name; + if (lex_string_eq(&auth->plugin, &old_password_plugin_name)) + auth->plugin= old_password_plugin_name; else return true; return false; @@ -2133,12 +2286,14 @@ static bool acl_load(THD *thd, const Grant_tables& tables) continue; } - if (user_table.get_auth(thd, &acl_memroot, - &user.plugin.str, &user.auth_string.str)) + if (user_table.get_auth(thd, &acl_memroot, &user)) continue; - user.plugin.length= strlen(user.plugin.str); - user.auth_string.length= strlen(user.auth_string.str); - fix_user_plugin_ptr(&user); + for (uint i= 0; i < user.nauth; i++) + { + ACL_USER::AUTH *auth= user.auth + i; + auth->salt= null_clex_str; + fix_user_plugin_ptr(auth); + } user.ssl_type= user_table.get_ssl_type(); user.ssl_cipher= user_table.get_ssl_cipher(&acl_memroot); @@ -2858,11 +3013,12 @@ static void acl_update_role(const char *rolename, ulong privileges) } -static int acl_user_update(THD *thd, ACL_USER *acl_user, const ACL_USER *from, - const LEX_USER &combo, enum SSL_type ssl_type, - const char *ssl_cipher, const char *x509_issuer, - const char *x509_subject, const USER_RESOURCES *mqh, - ulong privileges) +static int acl_user_update(THD *thd, ACL_USER *acl_user, uint nauth, + const ACL_USER *from, const LEX_USER &combo, + const enum SSL_type ssl_type, + const char *ssl_cipher, const char *x509_issuer, + const char *x509_subject, const USER_RESOURCES *mqh, + const ulong privileges) { if (from) *acl_user= *from; @@ -2873,21 +3029,29 @@ static int acl_user_update(THD *thd, ACL_USER *acl_user, const ACL_USER *from, update_hostname(&acl_user->host, safe_strdup_root(&acl_memroot, combo.host.str)); acl_user->hostname_length= combo.host.length; acl_user->sort= get_sort(2, acl_user->host.hostname, acl_user->user.str); - acl_user->plugin= native_password_plugin_name; - acl_user->auth_string= empty_clex_str; my_init_dynamic_array(&acl_user->role_grants, sizeof(ACL_USER *), 0, 8, MYF(0)); } - if (combo.plugin.length) + if (nauth) { - acl_user->plugin= combo.plugin; - acl_user->auth_string= safe_lexcstrdup_root(&acl_memroot, combo.auth); - if (fix_user_plugin_ptr(acl_user)) - acl_user->plugin= safe_lexcstrdup_root(&acl_memroot, combo.plugin); - if (set_user_auth(thd, acl_user, combo.pwtext)) - return 1; + if (acl_user->nauth >= nauth) + acl_user->nauth= nauth; + else + acl_user->alloc_auth(&acl_memroot, nauth); + + USER_AUTH *auth= combo.auth; + for (uint i= 0; i < nauth; i++, auth= auth->next) + { + acl_user->auth[i].plugin= auth->plugin; + acl_user->auth[i].auth_string= safe_lexcstrdup_root(&acl_memroot, auth->auth_str); + if (fix_user_plugin_ptr(acl_user->auth + i)) + acl_user->auth[i].plugin= safe_lexcstrdup_root(&acl_memroot, auth->plugin); + if (set_user_auth(thd, acl_user->user, acl_user->auth + i, auth->pwtext)) + return 1; + } } + acl_user->access= privileges; if (mqh->specified_limits & USER_RESOURCES::QUERIES_PER_HOUR) acl_user->user_resource.questions= mqh->questions; @@ -3382,12 +3546,8 @@ end: bool check_change_password(THD *thd, LEX_USER *user) { LEX_USER *real_user= get_current_user(thd, user); - - if (fix_and_copy_user(real_user, user, thd)) - return true; - - *user= *real_user; - + user->user= real_user->user; + user->host= real_user->host; return check_alter_user(thd, user->host.str, user->user.str); } @@ -3410,10 +3570,13 @@ bool change_password(THD *thd, LEX_USER *user) ulong query_length= 0; enum_binlog_format save_binlog_format; int result=0; + ACL_USER *acl_user; + ACL_USER::AUTH auth; + const char *password_plugin= 0; const CSET_STRING query_save __attribute__((unused)) = thd->query_string; DBUG_ENTER("change_password"); DBUG_PRINT("enter",("host: '%s' user: '%s' new_password: '%s'", - user->host.str, user->user.str, user->auth.str)); + user->host.str, user->user.str, user->auth->auth_str.str)); DBUG_ASSERT(user->host.str != 0); // Ensured by caller /* @@ -3432,50 +3595,62 @@ bool change_password(THD *thd, LEX_USER *user) DBUG_RETURN(result != 1); result= 1; - mysql_mutex_lock(&acl_cache->lock); - ACL_USER *acl_user; + if (!(acl_user= find_user_exact(user->host.str, user->user.str))) { - mysql_mutex_unlock(&acl_cache->lock); - my_message(ER_PASSWORD_NO_MATCH, - ER_THD(thd, ER_PASSWORD_NO_MATCH), MYF(0)); + my_error(ER_PASSWORD_NO_MATCH, MYF(0)); goto end; } - if (acl_user->plugin.str == native_password_plugin_name.str || - acl_user->plugin.str == old_password_plugin_name.str) + if (acl_user->nauth == 1 && + (acl_user->auth[0].plugin.str == native_password_plugin_name.str || + acl_user->auth[0].plugin.str == old_password_plugin_name.str)) { /* historical hack of auto-changing the plugin */ - acl_user->plugin= guess_auth_plugin(thd, user->auth.length); + acl_user->auth[0].plugin= guess_auth_plugin(thd, user->auth->auth_str.length); } - acl_user->auth_string= safe_lexcstrdup_root(&acl_memroot, user->auth); - if (set_user_auth(thd, acl_user, user->pwtext)) + for (uint i=0; i < acl_user->nauth; i++) { - mysql_mutex_unlock(&acl_cache->lock); + auth= acl_user->auth[i]; + auth.auth_string= safe_lexcstrdup_root(&acl_memroot, user->auth->auth_str); + int r= set_user_auth(thd, user->user, &auth, user->auth->pwtext); + if (r == ER_SET_PASSWORD_AUTH_PLUGIN) + password_plugin= auth.plugin.str; + else if (r) + goto end; + else + { + acl_user->auth[i]= auth; + password_plugin= 0; + break; + } + } + if (password_plugin) + { + my_error(ER_SET_PASSWORD_AUTH_PLUGIN, MYF(0), password_plugin); goto end; } if (update_user_table_password(thd, tables.user_table(), *acl_user)) - { - mysql_mutex_unlock(&acl_cache->lock); /* purecov: deadcode */ goto end; - } - acl_cache->clear(1); // Clear locked hostname cache + acl_cache->clear(1); // Clear locked hostname cache mysql_mutex_unlock(&acl_cache->lock); result= 0; if (mysql_bin_log.is_open()) { query_length= sprintf(buff, "SET PASSWORD FOR '%-.120s'@'%-.120s'='%-.120s'", - user->user.str, safe_str(user->host.str), acl_user->auth_string.str); + user->user.str, safe_str(user->host.str), auth.auth_string.str); DBUG_ASSERT(query_length); thd->clear_error(); result= thd->binlog_query(THD::STMT_QUERY_TYPE, buff, query_length, FALSE, FALSE, FALSE, 0); } end: + if (result) + mysql_mutex_unlock(&acl_cache->lock); close_mysql_tables(thd); #ifdef WITH_WSREP @@ -3920,19 +4095,24 @@ static bool update_user_table_password(THD *thd, const User_table& user_table, HA_READ_KEY_EXACT)) { my_message(ER_PASSWORD_NO_MATCH, ER_THD(thd, ER_PASSWORD_NO_MATCH), - MYF(0)); /* purecov: deadcode */ - DBUG_RETURN(1); /* purecov: deadcode */ + MYF(0)); + DBUG_RETURN(1); } - store_record(table,record[1]); + store_record(table, record[1]); - user_table.set_auth(user.plugin.str, user.plugin.length, - user.auth_string.str, user.auth_string.length); + if (user_table.set_auth(user)) + { + my_error(ER_COL_COUNT_DOESNT_MATCH_PLEASE_UPDATE, MYF(0), + user_table.name().str, 3, user_table.num_fields(), + static_cast(table->s->mysql_version), MYSQL_VERSION_ID); + DBUG_RETURN(1); + } if (unlikely(error= table->file->ha_update_row(table->record[1], table->record[0])) && error != HA_ERR_RECORD_IS_THE_SAME) { - table->file->print_error(error,MYF(0)); /* purecov: deadcode */ + table->file->print_error(error,MYF(0)); DBUG_RETURN(1); } DBUG_RETURN(0); @@ -3977,6 +4157,7 @@ static bool test_if_create_new_users(THD *thd) /**************************************************************************** Handle GRANT commands ****************************************************************************/ +static USER_AUTH auth_no_password; static int replace_user_table(THD *thd, const User_table &user_table, LEX_USER * const combo, ulong rights, @@ -3984,6 +4165,7 @@ static int replace_user_table(THD *thd, const User_table &user_table, const bool no_auto_create) { int error = -1; + uint nauth= 0; bool old_row_exists=0; uchar user_key[MAX_KEY_LENGTH]; bool handle_as_role= combo->is_role(); @@ -4021,8 +4203,7 @@ static int replace_user_table(THD *thd, const User_table &user_table, see also test_if_create_new_users() */ - else if (!combo->auth.length && !combo->plugin.length && - !combo->pwtext.length && no_auto_create) + else if (!combo->has_auth() && no_auto_create) { my_error(ER_PASSWORD_NO_MATCH, MYF(0)); goto end; @@ -4032,18 +4213,9 @@ static int replace_user_table(THD *thd, const User_table &user_table, my_error(ER_CANT_CREATE_USER_WITH_GRANT, MYF(0)); goto end; } - else if (combo->plugin.length) - { - if (!plugin_is_ready(&combo->plugin, MYSQL_AUTHENTICATION_PLUGIN)) - { - my_error(ER_PLUGIN_IS_NOT_LOADED, MYF(0), combo->plugin.str); - goto end; - } - } - else /* combo->plugin.length == 0 */ - { - combo->plugin= guess_auth_plugin(thd, combo->auth.length); - } + + if (!combo->auth) + combo->auth= &auth_no_password; old_row_exists = 0; restore_record(table,s->default_values); @@ -4054,15 +4226,24 @@ static int replace_user_table(THD *thd, const User_table &user_table, { old_row_exists = 1; store_record(table,record[1]); // Save copy for update - if (!combo->plugin.length && (combo->auth.length || combo->pwtext.length)) + } + + for (USER_AUTH *auth= combo->auth; auth; auth= auth->next) + { + nauth++; + if (auth->plugin.length) { - /* GRANT ... IDENTIFIED BY */ - combo->plugin= guess_auth_plugin(thd, combo->auth.length); + if (!plugin_is_ready(&auth->plugin, MYSQL_AUTHENTICATION_PLUGIN)) + { + my_error(ER_PLUGIN_IS_NOT_LOADED, MYF(0), auth->plugin.str); + goto end; + } } + else + auth->plugin= guess_auth_plugin(thd, auth->auth_str.length); } /* Update table columns with new privileges */ - user_table.set_access(rights, revoke_grant); rights= user_table.get_access(); @@ -4089,15 +4270,20 @@ static int replace_user_table(THD *thd, const User_table &user_table, my_error(ER_PASSWORD_NO_MATCH, MYF(0)); goto end; } - if (acl_user_update(thd, &new_acl_user, + if (acl_user_update(thd, &new_acl_user, nauth, old_row_exists ? old_acl_user : NULL, *combo, lex->ssl_type, lex->ssl_cipher, lex->x509_issuer, lex->x509_subject, &lex->mqh, rights)) goto end; - user_table.set_auth(new_acl_user.plugin.str, new_acl_user.plugin.length, - new_acl_user.auth_string.str, new_acl_user.auth_string.length); + if (user_table.set_auth(new_acl_user)) + { + my_error(ER_COL_COUNT_DOESNT_MATCH_PLEASE_UPDATE, MYF(0), + user_table.name().str, 3, user_table.num_fields(), + static_cast(table->s->mysql_version), MYSQL_VERSION_ID); + DBUG_RETURN(1); + } switch (lex->ssl_type) { case SSL_TYPE_NOT_SPECIFIED: @@ -6325,28 +6511,14 @@ static bool merge_one_role_privileges(ACL_ROLE *grantee) static bool has_auth(LEX_USER *user, LEX *lex) { - return user->pwtext.length || user->plugin.length || user->auth.length || + return user->has_auth() || lex->ssl_type != SSL_TYPE_NOT_SPECIFIED || lex->ssl_cipher || - lex->x509_issuer || lex->x509_subject || - lex->mqh.specified_limits; -} - -static bool fix_and_copy_user(LEX_USER *to, LEX_USER *from, THD *thd) -{ - if (to != from) - { - /* preserve authentication information, if LEX_USER was reallocated */ - to->pwtext= from->pwtext; - to->plugin= from->plugin; - to->auth= from->auth; - } - return false; + lex->x509_issuer || lex->x509_subject || lex->mqh.specified_limits; } static bool copy_and_check_auth(LEX_USER *to, LEX_USER *from, THD *thd) { - if (fix_and_copy_user(to, from, thd)) - return true; + to->auth= from->auth; // if changing auth for an existing user if (has_auth(to, thd->lex) && find_user_exact(to->host.str, to->user.str)) @@ -8364,25 +8536,31 @@ static void add_user_parameters(String *result, ACL_USER* acl_user, system_charset_info); result->append('\''); - if (acl_user->plugin.str == native_password_plugin_name.str || - acl_user->plugin.str == old_password_plugin_name.str) + if (acl_user->nauth == 1 && + (acl_user->auth->plugin.str == native_password_plugin_name.str || + acl_user->auth->plugin.str == old_password_plugin_name.str)) { - if (acl_user->auth_string.length) + if (acl_user->auth->auth_string.length) { result->append(STRING_WITH_LEN(" IDENTIFIED BY PASSWORD '")); - result->append(&acl_user->auth_string); + result->append(&acl_user->auth->auth_string); result->append('\''); } } else { result->append(STRING_WITH_LEN(" IDENTIFIED VIA ")); - result->append(&acl_user->plugin); - if (acl_user->auth_string.length) + for (uint i=0; i < acl_user->nauth; i++) { - result->append(STRING_WITH_LEN(" USING '")); - result->append(&acl_user->auth_string); - result->append('\''); + if (i) + result->append(STRING_WITH_LEN(" OR ")); + result->append(&acl_user->auth[i].plugin); + if (acl_user->auth[i].auth_string.length) + { + result->append(STRING_WITH_LEN(" USING '")); + result->append(&acl_user->auth[i].auth_string); + result->append('\''); + } } } /* "show grants" SSL related stuff */ @@ -11023,20 +11201,14 @@ bool sp_grant_privileges(THD *thd, const char *sp_db, const char *sp_name, thd->make_lex_string(&combo->user, combo->user.str, strlen(combo->user.str)); thd->make_lex_string(&combo->host, combo->host.str, strlen(combo->host.str)); - combo->reset_auth(); - - if(au) - { - combo->plugin= au->plugin; - combo->auth= au->auth_string; - } + combo->auth= NULL; if (user_list.push_back(combo, thd->mem_root)) DBUG_RETURN(TRUE); thd->lex->ssl_type= SSL_TYPE_NOT_SPECIFIED; thd->lex->ssl_cipher= thd->lex->x509_subject= thd->lex->x509_issuer= 0; - bzero((char*) &thd->lex->mqh, sizeof(thd->lex->mqh)); + bzero(&thd->lex->mqh, sizeof(thd->lex->mqh)); /* Only care about whether the operation failed or succeeded @@ -11294,7 +11466,7 @@ static int show_database_grants(THD *thd, SHOW_VAR *var, char *buff, } #else -static bool set_user_salt_if_needed(ACL_USER *, plugin_ref) +static bool set_user_salt_if_needed(ACL_USER *, int, plugin_ref) { return 0; } bool check_grant(THD *, ulong, TABLE_LIST *, bool, uint, bool) { return 0; } @@ -12078,6 +12250,7 @@ struct MPVIO_EXT :public MYSQL_PLUGIN_VIO char *pkt; uint pkt_len; } cached_server_packet; + uint curr_auth; ///< an index in acl_user->auth[] int packets_read, packets_written; ///< counters for send/received packets bool make_it_fail; /** when plugin returns a failure this tells us what really happened */ @@ -12141,7 +12314,7 @@ static void login_failed_error(THD *thd) static bool send_server_handshake_packet(MPVIO_EXT *mpvio, const char *data, uint data_len) { - DBUG_ASSERT(mpvio->status == MPVIO_EXT::FAILURE); + DBUG_ASSERT(mpvio->status == MPVIO_EXT::RESTART); DBUG_ASSERT(data_len <= 255); THD *thd= mpvio->auth_info.thd; @@ -12295,14 +12468,10 @@ static bool secure_auth(THD *thd) static bool send_plugin_request_packet(MPVIO_EXT *mpvio, const uchar *data, uint data_len) { - DBUG_ASSERT(mpvio->packets_written == 1); - DBUG_ASSERT(mpvio->packets_read == 1); NET *net= &mpvio->auth_info.thd->net; static uchar switch_plugin_request_buf[]= { 254 }; DBUG_ENTER("send_plugin_request_packet"); - mpvio->status= MPVIO_EXT::FAILURE; // the status is no longer RESTART - const char *client_auth_plugin= ((st_mysql_auth *) (plugin_decl(mpvio->plugin)->info))->client_auth_plugin; @@ -12437,26 +12606,19 @@ static bool find_mpvio_user(MPVIO_EXT *mpvio) } /* user account requires non-default plugin and the client is too old */ - if (mpvio->acl_user->plugin.str != native_password_plugin_name.str && - mpvio->acl_user->plugin.str != old_password_plugin_name.str && + if (mpvio->acl_user->auth->plugin.str != native_password_plugin_name.str && + mpvio->acl_user->auth->plugin.str != old_password_plugin_name.str && !(mpvio->auth_info.thd->client_capabilities & CLIENT_PLUGIN_AUTH)) { - DBUG_ASSERT(my_strcasecmp(system_charset_info, mpvio->acl_user->plugin.str, - native_password_plugin_name.str)); - DBUG_ASSERT(my_strcasecmp(system_charset_info, mpvio->acl_user->plugin.str, - old_password_plugin_name.str)); + DBUG_ASSERT(my_strcasecmp(system_charset_info, + mpvio->acl_user->auth->plugin.str, native_password_plugin_name.str)); + DBUG_ASSERT(my_strcasecmp(system_charset_info, + mpvio->acl_user->auth->plugin.str, old_password_plugin_name.str)); my_error(ER_NOT_SUPPORTED_AUTH_MODE, MYF(0)); general_log_print(mpvio->auth_info.thd, COM_CONNECT, ER_THD(mpvio->auth_info.thd, ER_NOT_SUPPORTED_AUTH_MODE)); DBUG_RETURN (1); } - - DBUG_PRINT("info", ("exit: user=%s, auth_string=%s, authenticated as=%s" - "plugin=%s", - mpvio->auth_info.user_name, - mpvio->auth_info.auth_string, - mpvio->auth_info.authenticated_as, - mpvio->acl_user->plugin.str)); DBUG_RETURN(0); } @@ -12609,15 +12771,13 @@ static bool parse_com_change_user_packet(MPVIO_EXT *mpvio, uint packet_length) client_plugin= native_password_plugin_name.str; else { - client_plugin= old_password_plugin_name.str; /* - For a passwordless accounts we use native_password_plugin. - But when an old 4.0 client connects to it, we change it to - old_password_plugin, otherwise MySQL will think that server - and client plugins don't match. + Normally old clients use old_password_plugin, but for + a passwordless accounts we use native_password_plugin. + See guess_auth_plugin(). */ - if (mpvio->acl_user->auth_string.length == 0) - mpvio->acl_user->plugin= old_password_plugin_name; + client_plugin= passwd_len ? old_password_plugin_name.str + : native_password_plugin_name.str; } } @@ -12860,15 +13020,13 @@ static ulong parse_client_handshake_packet(MPVIO_EXT *mpvio, client_plugin= native_password_plugin_name.str; else { - client_plugin= old_password_plugin_name.str; /* - For a passwordless accounts we use native_password_plugin. - But when an old 4.0 client connects to it, we change it to - old_password_plugin, otherwise MySQL will think that server - and client plugins don't match. + Normally old clients use old_password_plugin, but for + a passwordless accounts we use native_password_plugin. + See guess_auth_plugin(). */ - if (mpvio->acl_user->auth_string.length == 0) - mpvio->acl_user->plugin= old_password_plugin_name; + client_plugin= passwd_len ? old_password_plugin_name.str + : native_password_plugin_name.str; } } @@ -12886,7 +13044,7 @@ static ulong parse_client_handshake_packet(MPVIO_EXT *mpvio, restarted and a server auth plugin will read the data that the client has just send. Cache them to return in the next server_mpvio_read_packet(). */ - if (!lex_string_eq(&mpvio->acl_user->plugin, plugin_name(mpvio->plugin))) + if (!lex_string_eq(&mpvio->acl_user->auth->plugin, plugin_name(mpvio->plugin))) { mpvio->cached_client_reply.pkt= passwd; mpvio->cached_client_reply.pkt_len= (uint)passwd_len; @@ -12966,6 +13124,7 @@ static int server_mpvio_write_packet(MYSQL_PLUGIN_VIO *param, res= my_net_write(&mpvio->auth_info.thd->net, packet, packet_len) || net_flush(&mpvio->auth_info.thd->net); } + mpvio->status= MPVIO_EXT::FAILURE; // the status is no longer RESTART mpvio->packets_written++; DBUG_RETURN(res); } @@ -12983,10 +13142,41 @@ static int server_mpvio_write_packet(MYSQL_PLUGIN_VIO *param, static int server_mpvio_read_packet(MYSQL_PLUGIN_VIO *param, uchar **buf) { MPVIO_EXT * const mpvio= (MPVIO_EXT *) param; + MYSQL_SERVER_AUTH_INFO * const ai= &mpvio->auth_info; ulong pkt_len; DBUG_ENTER("server_mpvio_read_packet"); - if (mpvio->packets_written == 0) + if (mpvio->status == MPVIO_EXT::RESTART) { + const char *client_auth_plugin= + ((st_mysql_auth *) (plugin_decl(mpvio->plugin)->info))->client_auth_plugin; + if (client_auth_plugin == 0) + { + mpvio->status= MPVIO_EXT::FAILURE; + pkt_len= 0; + *buf= 0; + goto done; + } + + if (mpvio->cached_client_reply.pkt) + { + DBUG_ASSERT(mpvio->packets_read > 0); + /* + if the have the data cached from the last server_mpvio_read_packet + (which can be the case if it's a restarted authentication) + and a client has used the correct plugin, then we can return the + cached data straight away and avoid one round trip. + */ + if (my_strcasecmp(system_charset_info, mpvio->cached_client_reply.plugin, + client_auth_plugin) == 0) + { + mpvio->status= MPVIO_EXT::FAILURE; + pkt_len= mpvio->cached_client_reply.pkt_len; + *buf= (uchar*) mpvio->cached_client_reply.pkt; + mpvio->packets_read++; + goto done; + } + } + /* plugin wants to read the data without sending anything first. send an empty packet to force a server handshake packet to be sent @@ -12994,44 +13184,10 @@ static int server_mpvio_read_packet(MYSQL_PLUGIN_VIO *param, uchar **buf) if (server_mpvio_write_packet(mpvio, 0, 0)) pkt_len= packet_error; else - pkt_len= my_net_read(&mpvio->auth_info.thd->net); - } - else if (mpvio->cached_client_reply.pkt) - { - DBUG_ASSERT(mpvio->status == MPVIO_EXT::RESTART); - DBUG_ASSERT(mpvio->packets_read > 0); - /* - if the have the data cached from the last server_mpvio_read_packet - (which can be the case if it's a restarted authentication) - and a client has used the correct plugin, then we can return the - cached data straight away and avoid one round trip. - */ - const char *client_auth_plugin= - ((st_mysql_auth *) (plugin_decl(mpvio->plugin)->info))->client_auth_plugin; - if (client_auth_plugin == 0 || - my_strcasecmp(system_charset_info, mpvio->cached_client_reply.plugin, - client_auth_plugin) == 0) - { - mpvio->status= MPVIO_EXT::FAILURE; - pkt_len= mpvio->cached_client_reply.pkt_len; - *buf= (uchar*) mpvio->cached_client_reply.pkt; - mpvio->cached_client_reply.pkt= 0; - mpvio->packets_read++; - goto done; - } - - /* - But if the client has used the wrong plugin, the cached data are - useless. Furthermore, we have to send a "change plugin" request - to the client. - */ - if (server_mpvio_write_packet(mpvio, 0, 0)) - pkt_len= packet_error; - else - pkt_len= my_net_read(&mpvio->auth_info.thd->net); + pkt_len= my_net_read(&ai->thd->net); } else - pkt_len= my_net_read(&mpvio->auth_info.thd->net); + pkt_len= my_net_read(&ai->thd->net); if (unlikely(pkt_len == packet_error)) goto err; @@ -13049,24 +13205,24 @@ static int server_mpvio_read_packet(MYSQL_PLUGIN_VIO *param, uchar **buf) goto err; } else - *buf= mpvio->auth_info.thd->net.read_pos; + *buf= ai->thd->net.read_pos; done: - if (set_user_salt_if_needed(mpvio->acl_user, mpvio->plugin)) + if (set_user_salt_if_needed(mpvio->acl_user, mpvio->curr_auth, mpvio->plugin)) goto err; - mpvio->auth_info.user_name= mpvio->auth_info.thd->security_ctx->user; - mpvio->auth_info.user_name_length= (uint)strlen(mpvio->auth_info.user_name); - mpvio->auth_info.auth_string= mpvio->acl_user->salt.str; - mpvio->auth_info.auth_string_length= (ulong) mpvio->acl_user->salt.length; - strmake_buf(mpvio->auth_info.authenticated_as, mpvio->acl_user->user.str); + ai->user_name= ai->thd->security_ctx->user; + ai->user_name_length= (uint) strlen(ai->user_name); + ai->auth_string= mpvio->acl_user->auth[mpvio->curr_auth].salt.str; + ai->auth_string_length= (ulong) mpvio->acl_user->auth[mpvio->curr_auth].salt.length; + strmake_buf(ai->authenticated_as, mpvio->acl_user->user.str); DBUG_RETURN((int)pkt_len); err: if (mpvio->status == MPVIO_EXT::FAILURE) { - if (!mpvio->auth_info.thd->is_error()) + if (!ai->thd->is_error()) my_error(ER_HANDSHAKE_ERROR, MYF(0)); } DBUG_RETURN(-1); @@ -13196,26 +13352,25 @@ static bool acl_check_ssl(THD *thd, const ACL_USER *acl_user) static int do_auth_once(THD *thd, const LEX_CSTRING *auth_plugin_name, MPVIO_EXT *mpvio) { - int res= CR_OK, old_status= MPVIO_EXT::FAILURE; + int res= CR_OK; bool unlock_plugin= false; plugin_ref plugin= get_auth_plugin(thd, *auth_plugin_name, &unlock_plugin); mpvio->plugin= plugin; mpvio->auth_info.user_name= NULL; - old_status= mpvio->status; if (plugin) { - st_mysql_auth *auth= (st_mysql_auth *) plugin_decl(plugin)->info; - switch (auth->interface_version >> 8) { + st_mysql_auth *info= (st_mysql_auth *) plugin_decl(plugin)->info; + switch (info->interface_version >> 8) { case 0x02: - res= auth->authenticate_user(mpvio, &mpvio->auth_info); + res= info->authenticate_user(mpvio, &mpvio->auth_info); break; case 0x01: { MYSQL_SERVER_AUTH_INFO_0x0100 compat; compat.downgrade(&mpvio->auth_info); - res= auth->authenticate_user(mpvio, (MYSQL_SERVER_AUTH_INFO *)&compat); + res= info->authenticate_user(mpvio, (MYSQL_SERVER_AUTH_INFO *)&compat); compat.upgrade(&mpvio->auth_info); } break; @@ -13235,17 +13390,6 @@ static int do_auth_once(THD *thd, const LEX_CSTRING *auth_plugin_name, res= CR_ERROR; } - /* - If the status was MPVIO_EXT::RESTART before the authenticate_user() call - it can never be MPVIO_EXT::RESTART after the call, because any call - to write_packet() or read_packet() will reset the status. - - But (!) if a plugin never called a read_packet() or write_packet(), the - status will stay unchanged. We'll fix it, by resetting the status here. - */ - if (old_status == MPVIO_EXT::RESTART && mpvio->status == MPVIO_EXT::RESTART) - mpvio->status= MPVIO_EXT::FAILURE; // reset to the default - return res; } @@ -13298,7 +13442,6 @@ bool acl_authenticate(THD *thd, uint com_change_user_pkt_len) { int res= CR_OK; MPVIO_EXT mpvio; - const LEX_CSTRING *auth_plugin_name= default_auth_plugin_name; enum enum_server_command command= com_change_user_pkt_len ? COM_CHANGE_USER : COM_CONNECT; DBUG_ENTER("acl_authenticate"); @@ -13306,9 +13449,9 @@ bool acl_authenticate(THD *thd, uint com_change_user_pkt_len) bzero(&mpvio, sizeof(mpvio)); mpvio.read_packet= server_mpvio_read_packet; mpvio.write_packet= server_mpvio_write_packet; + mpvio.cached_client_reply.plugin= ""; mpvio.info= server_mpvio_info; - mpvio.status= MPVIO_EXT::FAILURE; - mpvio.make_it_fail= false; + mpvio.status= MPVIO_EXT::RESTART; mpvio.auth_info.thd= thd; mpvio.auth_info.host_or_ip= thd->security_ctx->host_or_ip; mpvio.auth_info.host_or_ip_length= @@ -13324,6 +13467,8 @@ bool acl_authenticate(THD *thd, uint com_change_user_pkt_len) if (parse_com_change_user_packet(&mpvio, com_change_user_pkt_len)) DBUG_RETURN(1); + res= mpvio.status == MPVIO_EXT::SUCCESS ? CR_OK : CR_ERROR; + DBUG_ASSERT(mpvio.status == MPVIO_EXT::RESTART || mpvio.status == MPVIO_EXT::SUCCESS); } @@ -13339,29 +13484,33 @@ bool acl_authenticate(THD *thd, uint com_change_user_pkt_len) the correct plugin. */ - res= do_auth_once(thd, auth_plugin_name, &mpvio); + res= do_auth_once(thd, default_auth_plugin_name, &mpvio); } - /* - retry the authentication, if - after receiving the user name - - we found that we need to switch to a non-default plugin - */ - if (mpvio.status == MPVIO_EXT::RESTART) + Security_context * const sctx= thd->security_ctx; + const ACL_USER * acl_user= mpvio.acl_user; + + if (acl_user) { - DBUG_ASSERT(mpvio.acl_user); - DBUG_ASSERT(command == COM_CHANGE_USER || - !lex_string_eq(auth_plugin_name, &mpvio.acl_user->plugin)); - auth_plugin_name= &mpvio.acl_user->plugin; - res= do_auth_once(thd, auth_plugin_name, &mpvio); + /* + retry the authentication with curr_auth==0 if after receiving the user + name we found that we need to switch to a non-default plugin + */ + for (mpvio.curr_auth= mpvio.status != MPVIO_EXT::RESTART; + res != CR_OK && mpvio.curr_auth < acl_user->nauth; + mpvio.curr_auth++) + { + thd->clear_error(); + mpvio.status= MPVIO_EXT::RESTART; + res= do_auth_once(thd, &acl_user->auth[mpvio.curr_auth].plugin, &mpvio); + } } + if (mpvio.make_it_fail && res == CR_OK) { mpvio.status= MPVIO_EXT::FAILURE; res= CR_ERROR; } - - Security_context *sctx= thd->security_ctx; - const ACL_USER *acl_user= mpvio.acl_user; thd->password= mpvio.auth_info.password_used; // remember for error messages @@ -13389,7 +13538,6 @@ bool acl_authenticate(THD *thd, uint com_change_user_pkt_len) if (res > CR_OK && mpvio.status != MPVIO_EXT::SUCCESS) { Host_errors errors; - DBUG_ASSERT(mpvio.status == MPVIO_EXT::FAILURE); switch (res) { case CR_AUTH_PLUGIN_ERROR: @@ -13628,12 +13776,11 @@ static int native_password_authenticate(MYSQL_PLUGIN_VIO *vio, /* generate the scramble, or reuse the old one */ if (thd->scramble[SCRAMBLE_LENGTH]) - { thd_create_random_password(thd, thd->scramble, SCRAMBLE_LENGTH); - /* and send it to the client */ - if (mpvio->write_packet(mpvio, (uchar*)thd->scramble, SCRAMBLE_LENGTH + 1)) - DBUG_RETURN(CR_AUTH_HANDSHAKE); - } + + /* and send it to the client */ + if (mpvio->write_packet(mpvio, (uchar*)thd->scramble, SCRAMBLE_LENGTH + 1)) + DBUG_RETURN(CR_AUTH_HANDSHAKE); /* reply and authenticate */ @@ -13748,12 +13895,10 @@ static int old_password_authenticate(MYSQL_PLUGIN_VIO *vio, /* generate the scramble, or reuse the old one */ if (thd->scramble[SCRAMBLE_LENGTH]) - { thd_create_random_password(thd, thd->scramble, SCRAMBLE_LENGTH); - /* and send it to the client */ - if (mpvio->write_packet(mpvio, (uchar*)thd->scramble, SCRAMBLE_LENGTH + 1)) - return CR_AUTH_HANDSHAKE; - } + /* and send it to the client */ + if (mpvio->write_packet(mpvio, (uchar*)thd->scramble, SCRAMBLE_LENGTH + 1)) + return CR_AUTH_HANDSHAKE; /* read the reply and authenticate */ if ((pkt_len= mpvio->read_packet(mpvio, &pkt)) < 0) diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 78bfbd4dbd2..22e849e6322 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -1,6 +1,6 @@ /* Copyright (c) 2000, 2015, Oracle and/or its affiliates. - Copyright (c) 2008, 2018, MariaDB Corporation. + Copyright (c) 2008, 2019, MariaDB Corporation. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -5577,7 +5577,7 @@ void THD::get_definer(LEX_USER *definer, bool role) { definer->user= invoker.user; definer->host= invoker.host; - definer->reset_auth(); + definer->auth= NULL; } else #endif diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 7360ecb62da..c39d7de096e 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -1,5 +1,5 @@ /* Copyright (c) 2000, 2017, Oracle and/or its affiliates. - Copyright (c) 2008, 2018, MariaDB + Copyright (c) 2008, 2019, MariaDB Corporation. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -9829,8 +9829,7 @@ void get_default_definer(THD *thd, LEX_USER *definer, bool role) definer->host.length= strlen(definer->host.str); } definer->user.length= strlen(definer->user.str); - - definer->reset_auth(); + definer->auth= NULL; } @@ -9889,7 +9888,7 @@ LEX_USER *create_definer(THD *thd, LEX_CSTRING *user_name, definer->user= *user_name; definer->host= *host_name; - definer->reset_auth(); + definer->auth= NULL; return definer; } diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index f689180977c..81ece13e1c5 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -1,6 +1,6 @@ /* Copyright (c) 2000, 2015, Oracle and/or its affiliates. - Copyright (c) 2010, 2016, MariaDB + Copyright (c) 2010, 2019, MariaDB Corporation. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -722,6 +722,7 @@ Virtual_column_info *add_virtual_expression(THD *thd, Item *expr) class sp_lex_cursor *sp_cursor_stmt; LEX_CSTRING *lex_str_ptr; LEX_USER *lex_user; + USER_AUTH *user_auth; List *cond_info_list; List *dyncol_def_list; List *item_list; @@ -1945,6 +1946,8 @@ bool my_yyoverflow(short **a, YYSTYPE **b, size_t *yystacksize); %type user grant_user grant_role user_or_role current_role admin_option_for_role user_maybe_role +%type opt_auth_str auth_expression auth_token + %type opt_collate charset_name @@ -15520,11 +15523,9 @@ ident_or_text: user_maybe_role: ident_or_text { - if (unlikely(!($$=(LEX_USER*) thd->alloc(sizeof(LEX_USER))))) + if (unlikely(!($$=(LEX_USER*) thd->calloc(sizeof(LEX_USER))))) MYSQL_YYABORT; $$->user = $1; - $$->host= null_clex_str; // User or Role, see get_current_user() - $$->reset_auth(); if (unlikely(check_string_char_length(&$$->user, ER_USERNAME, username_char_length, @@ -15533,10 +15534,9 @@ user_maybe_role: } | ident_or_text '@' ident_or_text { - if (unlikely(!($$=(LEX_USER*) thd->alloc(sizeof(LEX_USER))))) + if (unlikely(!($$=(LEX_USER*) thd->calloc(sizeof(LEX_USER))))) MYSQL_YYABORT; $$->user = $1; $$->host=$3; - $$->reset_auth(); if (unlikely(check_string_char_length(&$$->user, ER_USERNAME, username_char_length, @@ -15566,8 +15566,7 @@ user_maybe_role: if (unlikely(!($$=(LEX_USER*)thd->calloc(sizeof(LEX_USER))))) MYSQL_YYABORT; $$->user= current_user; - $$->plugin= empty_clex_str; - $$->auth= empty_clex_str; + $$->auth= new (thd->mem_root) USER_AUTH(); } ; @@ -16550,21 +16549,29 @@ opt_for_user: thd->calloc(sizeof(LEX_USER))))) MYSQL_YYABORT; lex->definer->user= current_user; - lex->definer->plugin= empty_clex_str; - lex->definer->auth= empty_clex_str; + lex->definer->auth= new (thd->mem_root) USER_AUTH(); } | FOR_SYM user equal { Lex->definer= $2; } ; text_or_password: - TEXT_STRING { Lex->definer->auth= $1;} - | PASSWORD_SYM '(' TEXT_STRING ')' { Lex->definer->pwtext= $3; } + TEXT_STRING + { + Lex->definer->auth= new (thd->mem_root) USER_AUTH(); + Lex->definer->auth->auth_str= $1; + } + | PASSWORD_SYM '(' TEXT_STRING ')' + { + Lex->definer->auth= new (thd->mem_root) USER_AUTH(); + Lex->definer->auth->pwtext= $3; + } | OLD_PASSWORD_SYM '(' TEXT_STRING ')' { - Lex->definer->pwtext= $3; - Lex->definer->auth.str= Item_func_password::alloc(thd, + Lex->definer->auth= new (thd->mem_root) USER_AUTH(); + Lex->definer->auth->pwtext= $3; + Lex->definer->auth->auth_str.str= Item_func_password::alloc(thd, $3.str, $3.length, Item_func_password::OLD); - Lex->definer->auth.length= SCRAMBLED_PASSWORD_CHAR_LENGTH_323; + Lex->definer->auth->auth_str.length= SCRAMBLED_PASSWORD_CHAR_LENGTH_323; } ; @@ -16938,7 +16945,7 @@ current_role: if (unlikely(!($$=(LEX_USER*) thd->calloc(sizeof(LEX_USER))))) MYSQL_YYABORT; $$->user= current_role; - $$->reset_auth(); + $$->auth= NULL; } ; @@ -16955,7 +16962,7 @@ grant_role: MYSQL_YYABORT; $$->user= $1; $$->host= empty_clex_str; - $$->reset_auth(); + $$->auth= NULL; if (unlikely(check_string_char_length(&$$->user, ER_USERNAME, username_char_length, @@ -17152,37 +17159,65 @@ grant_user: user IDENTIFIED_SYM BY TEXT_STRING { $$= $1; - $1->pwtext= $4; + $1->auth= new (thd->mem_root) USER_AUTH(); + $1->auth->pwtext= $4; } | user IDENTIFIED_SYM BY PASSWORD_SYM TEXT_STRING { $$= $1; - $1->auth= $5; + $1->auth= new (thd->mem_root) USER_AUTH(); + $1->auth->auth_str= $5; } - | user IDENTIFIED_SYM via_or_with ident_or_text + | user IDENTIFIED_SYM via_or_with auth_expression { $$= $1; - $1->plugin= $4; - $1->auth= empty_clex_str; - } - | user IDENTIFIED_SYM via_or_with ident_or_text using_or_as - TEXT_STRING_sys - { - $$= $1; - $1->plugin= $4; - $1->auth= $6; - } - | user IDENTIFIED_SYM via_or_with ident_or_text using_or_as - PASSWORD_SYM '(' TEXT_STRING ')' - { - $$= $1; - $1->plugin= $4; - $1->pwtext= $8; + $1->auth= $4; } | user_or_role { $$= $1; } ; +auth_expression: + auth_token OR_SYM auth_expression + { + $$= $1; + DBUG_ASSERT($$->next == NULL); + $$->next= $3; + } + | auth_token + { + $$= $1; + } + ; + +auth_token: + ident_or_text opt_auth_str + { + $$= $2; + $$->plugin= $1; + } + ; + +opt_auth_str: + /* empty */ + { + if (!($$=(USER_AUTH*) thd->calloc(sizeof(USER_AUTH)))) + MYSQL_YYABORT; + } + | using_or_as TEXT_STRING_sys + { + if (!($$=(USER_AUTH*) thd->calloc(sizeof(USER_AUTH)))) + MYSQL_YYABORT; + $$->auth_str= $2; + } + | using_or_as PASSWORD_SYM '(' TEXT_STRING ')' + { + if (!($$=(USER_AUTH*) thd->calloc(sizeof(USER_AUTH)))) + MYSQL_YYABORT; + $$->pwtext= $4; + } + ; + opt_column_list: /* empty */ { diff --git a/sql/sql_yacc_ora.yy b/sql/sql_yacc_ora.yy index 0fa45dacd5e..aa622b61771 100644 --- a/sql/sql_yacc_ora.yy +++ b/sql/sql_yacc_ora.yy @@ -1,6 +1,6 @@ /* Copyright (c) 2000, 2015, Oracle and/or its affiliates. - Copyright (c) 2010, 2016, MariaDB + Copyright (c) 2010, 2019, MariaDB Corporation. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -218,6 +218,7 @@ void ORAerror(THD *thd, const char *s) class sp_lex_cursor *sp_cursor_stmt; LEX_CSTRING *lex_str_ptr; LEX_USER *lex_user; + USER_AUTH *user_auth; List *cond_info_list; List *dyncol_def_list; List *item_list; @@ -1449,6 +1450,8 @@ bool my_yyoverflow(short **a, YYSTYPE **b, size_t *yystacksize); %type user grant_user grant_role user_or_role current_role admin_option_for_role user_maybe_role +%type opt_auth_str auth_expression auth_token + %type opt_collate charset_name @@ -15595,11 +15598,9 @@ ident_or_text: user_maybe_role: ident_or_text { - if (unlikely(!($$=(LEX_USER*) thd->alloc(sizeof(LEX_USER))))) + if (unlikely(!($$=(LEX_USER*) thd->calloc(sizeof(LEX_USER))))) MYSQL_YYABORT; $$->user = $1; - $$->host= null_clex_str; // User or Role, see get_current_user() - $$->reset_auth(); if (unlikely(check_string_char_length(&$$->user, ER_USERNAME, username_char_length, @@ -15608,10 +15609,9 @@ user_maybe_role: } | ident_or_text '@' ident_or_text { - if (unlikely(!($$=(LEX_USER*) thd->alloc(sizeof(LEX_USER))))) + if (unlikely(!($$=(LEX_USER*) thd->calloc(sizeof(LEX_USER))))) MYSQL_YYABORT; $$->user = $1; $$->host=$3; - $$->reset_auth(); if (unlikely(check_string_char_length(&$$->user, ER_USERNAME, username_char_length, @@ -15641,8 +15641,7 @@ user_maybe_role: if (unlikely(!($$=(LEX_USER*)thd->calloc(sizeof(LEX_USER))))) MYSQL_YYABORT; $$->user= current_user; - $$->plugin= empty_clex_str; - $$->auth= empty_clex_str; + $$->auth= new (thd->mem_root) USER_AUTH(); } ; @@ -16687,21 +16686,29 @@ opt_for_user: thd->calloc(sizeof(LEX_USER))))) MYSQL_YYABORT; lex->definer->user= current_user; - lex->definer->plugin= empty_clex_str; - lex->definer->auth= empty_clex_str; + lex->definer->auth= new (thd->mem_root) USER_AUTH(); } | FOR_SYM user equal { Lex->definer= $2; } ; text_or_password: - TEXT_STRING { Lex->definer->auth= $1;} - | PASSWORD_SYM '(' TEXT_STRING ')' { Lex->definer->pwtext= $3; } + TEXT_STRING + { + Lex->definer->auth= new (thd->mem_root) USER_AUTH(); + Lex->definer->auth->auth_str= $1; + } + | PASSWORD_SYM '(' TEXT_STRING ')' + { + Lex->definer->auth= new (thd->mem_root) USER_AUTH(); + Lex->definer->auth->pwtext= $3; + } | OLD_PASSWORD_SYM '(' TEXT_STRING ')' { - Lex->definer->pwtext= $3; - Lex->definer->auth.str= Item_func_password::alloc(thd, + Lex->definer->auth= new (thd->mem_root) USER_AUTH(); + Lex->definer->auth->pwtext= $3; + Lex->definer->auth->auth_str.str= Item_func_password::alloc(thd, $3.str, $3.length, Item_func_password::OLD); - Lex->definer->auth.length= SCRAMBLED_PASSWORD_CHAR_LENGTH_323; + Lex->definer->auth->auth_str.length= SCRAMBLED_PASSWORD_CHAR_LENGTH_323; } ; @@ -17075,7 +17082,7 @@ current_role: if (unlikely(!($$=(LEX_USER*) thd->calloc(sizeof(LEX_USER))))) MYSQL_YYABORT; $$->user= current_role; - $$->reset_auth(); + $$->auth= NULL; } ; @@ -17092,7 +17099,7 @@ grant_role: MYSQL_YYABORT; $$->user= $1; $$->host= empty_clex_str; - $$->reset_auth(); + $$->auth= NULL; if (unlikely(check_string_char_length(&$$->user, ER_USERNAME, username_char_length, @@ -17289,37 +17296,65 @@ grant_user: user IDENTIFIED_SYM BY TEXT_STRING { $$= $1; - $1->pwtext= $4; + $1->auth= new (thd->mem_root) USER_AUTH(); + $1->auth->pwtext= $4; } | user IDENTIFIED_SYM BY PASSWORD_SYM TEXT_STRING { $$= $1; - $1->auth= $5; + $1->auth= new (thd->mem_root) USER_AUTH(); + $1->auth->auth_str= $5; } - | user IDENTIFIED_SYM via_or_with ident_or_text + | user IDENTIFIED_SYM via_or_with auth_expression { $$= $1; - $1->plugin= $4; - $1->auth= empty_clex_str; - } - | user IDENTIFIED_SYM via_or_with ident_or_text using_or_as - TEXT_STRING_sys - { - $$= $1; - $1->plugin= $4; - $1->auth= $6; - } - | user IDENTIFIED_SYM via_or_with ident_or_text using_or_as - PASSWORD_SYM '(' TEXT_STRING ')' - { - $$= $1; - $1->plugin= $4; - $1->pwtext= $8; + $1->auth= $4; } | user_or_role { $$= $1; } ; +auth_expression: + auth_token OR_SYM auth_expression + { + $$= $1; + DBUG_ASSERT($$->next == NULL); + $$->next= $3; + } + | auth_token + { + $$= $1; + } + ; + +auth_token: + ident_or_text opt_auth_str + { + $$= $2; + $$->plugin= $1; + } + ; + +opt_auth_str: + /* empty */ + { + if (!($$=(USER_AUTH*) thd->calloc(sizeof(USER_AUTH)))) + MYSQL_YYABORT; + } + | using_or_as TEXT_STRING_sys + { + if (!($$=(USER_AUTH*) thd->calloc(sizeof(USER_AUTH)))) + MYSQL_YYABORT; + $$->auth_str= $2; + } + | using_or_as PASSWORD_SYM '(' TEXT_STRING ')' + { + if (!($$=(USER_AUTH*) thd->calloc(sizeof(USER_AUTH)))) + MYSQL_YYABORT; + $$->pwtext= $4; + } + ; + opt_column_list: /* empty */ { diff --git a/sql/structs.h b/sql/structs.h index 3e29e137376..4c4cf137bc5 100644 --- a/sql/structs.h +++ b/sql/structs.h @@ -1,8 +1,8 @@ #ifndef STRUCTS_INCLUDED #define STRUCTS_INCLUDED -/* Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved. - Copyright (c) 2017, MariaDB Corporation. +/* Copyright (c) 2000, 2010, Oracle and/or its affiliates. + Copyright (c) 2009, 2019, MariaDB Corporation. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -203,6 +203,17 @@ extern const char *show_comp_option_name[]; typedef int *(*update_var)(THD *, struct st_mysql_show_var *); +struct USER_AUTH : public Sql_alloc +{ + LEX_CSTRING plugin, auth_str, pwtext; + USER_AUTH *next; + USER_AUTH() : next(NULL) + { + plugin.str= auth_str.str= ""; + pwtext.str= NULL; + plugin.length= auth_str.length= pwtext.length= 0; + } +}; struct AUTHID { @@ -227,12 +238,10 @@ struct AUTHID struct LEX_USER: public AUTHID { - LEX_CSTRING plugin, auth, pwtext; - void reset_auth() + USER_AUTH *auth; + bool has_auth() { - pwtext.length= plugin.length= auth.length= 0; - pwtext.str= 0; - plugin.str= auth.str= ""; + return auth && (auth->plugin.length || auth->auth_str.length || auth->pwtext.length); } };