Commit 5b15cc61 authored by Sergei Golubchik's avatar Sergei Golubchik

MDEV-11340 Allow multiple alternative authentication methods for the same user

introduce the syntax

... IDENTIFIED { WITH | VIA }
      plugin [ { USING | AS } auth ]
 [ OR plugin [ { USING | AS } auth ]
 [ OR ... ]]

Server will try auth plugins in the specified order until the first
success. No protocol changes, server uses the existing "switch plugin"
packet.

The auth chain is stored in json as

  "auth_or":[{"plugin":"xxx","authentication_string":"yyy"},
             {},
             {"plugin":"foo","authentication_string":"bar"},
            ...],
  "plugin":"aaa", "authentication_string":"bbb"

Note:
* "auth_or" implies that there might be "auth_and" someday;
* one entry in the array is an empty object, meaning to take plugin/auth
  from the main json object. This preserves compatibility with
  the existing mysql.global_priv table and with the mysql.user view.
  This entry is preferrably a mysql_native_password plugin for a
  non-empty mysql.user.password column.

SET PASSWORD is supported and changes the password for the *first*
plugin in the chain that has a notion of a "password"
parent 798d1a9d
Subproject commit a4effc462ddb80b61ebb559d48b50fa8d6c0ed64
Subproject commit 1e4b08bd2989c664f6f43e0dbb2c71be9552bc8c
......@@ -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';
......
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';
#
# 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
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';
--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';
/* 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 @@ class ACL_USER :public ACL_USER_BASE
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);
}
void set_auth(const char *p, size_t pl, const char *as, size_t asl) const
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++)
{
set_str_value("plugin", p, pl);
set_str_value("authentication_string", as, asl);
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;
}
int get_auth1(THD *thd, MEM_ROOT *root, ACL_USER *u, uint n) const
{
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_SIZE> 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,7 +1485,7 @@ class User_table_json: public User_table
return false;
return true;
}
bool set_value(const char *key,
enum json_types set_value(const char *key,
const char *val, size_t vlen, bool string) const
{
int value_len;
......@@ -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_SIZE> 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,39 +1961,48 @@ 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))
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 (auth->hash_password)
if (info->hash_password)
{
char buf[MAX_SCRAMBLE_LENGTH];
size_t len= sizeof(buf) - 1;
if (auth->hash_password(pwtext.str, pwtext.length, buf, &len))
goto end; // OOM?
if (info->hash_password(pwtext.str, pwtext.length, buf, &len))
{
res= ER_OUTOFMEMORY;
goto end;
}
buf[len] = 0;
acl_user->auth_string.str= (char*)memdup_root(&acl_memroot, buf, len+1);
acl_user->auth_string.length= len;
auth->auth_string.str= (char*)memdup_root(&acl_memroot, buf, len+1);
auth->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_SET_PASSWORD_AUTH_PLUGIN;
goto end;
}
}
if (set_user_salt(acl_user, plugin))
if (set_user_salt(auth, plugin))
{
res= ER_PASSWD_LENGTH;
goto end;
}
......@@ -1869,25 +2017,30 @@ static int set_user_auth(THD *thd, ACL_USER *acl_user, const LEX_CSTRING &pwtext
/**
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,
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,
ulong privileges)
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))
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 @@ static int check_alter_user(THD *thd, const char *host, const char *user)
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,36 +3595,46 @@ 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
mysql_mutex_unlock(&acl_cache->lock);
......@@ -3469,13 +3642,15 @@ bool change_password(THD *thd, LEX_USER *user)
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<int>(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)
{
if (!plugin_is_ready(&auth->plugin, MYSQL_AUTHENTICATION_PLUGIN))
{
/* GRANT ... IDENTIFIED BY */
combo->plugin= guess_auth_plugin(thd, combo->auth.length);
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<int>(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,27 +8536,33 @@ 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++)
{
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_string);
result->append(&acl_user->auth[i].auth_string);
result->append('\'');
}
}
}
/* "show grants" SSL related stuff */
if (acl_user->ssl_type == SSL_TYPE_ANY)
result->append(STRING_WITH_LEN(" REQUIRE SSL"));
......@@ -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,22 +13142,23 @@ 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)
{
/*
plugin wants to read the data without sending anything first.
send an empty packet to force a server handshake packet to be sent
*/
if (server_mpvio_write_packet(mpvio, 0, 0))
pkt_len= packet_error;
else
pkt_len= my_net_read(&mpvio->auth_info.thd->net);
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;
}
else if (mpvio->cached_client_reply.pkt)
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
......@@ -13006,32 +13166,28 @@ static int server_mpvio_read_packet(MYSQL_PLUGIN_VIO *param, uchar **buf)
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,
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->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.
plugin wants to read the data without sending anything first.
send an empty packet to force a server handshake packet to be sent
*/
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,30 +13484,34 @@ 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);
}
Security_context * const sctx= thd->security_ctx;
const ACL_USER * acl_user= mpvio.acl_user;
if (acl_user)
{
/*
retry the authentication, if - after receiving the user name -
we found that we need to switch to a non-default plugin
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
*/
if (mpvio.status == MPVIO_EXT::RESTART)
for (mpvio.curr_auth= mpvio.status != MPVIO_EXT::RESTART;
res != CR_OK && mpvio.curr_auth < acl_user->nauth;
mpvio.curr_auth++)
{
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);
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);
}
/* 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;
}
/* read the reply and authenticate */
if ((pkt_len= mpvio->read_packet(mpvio, &pkt)) < 0)
......
/*
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
......
/* 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;
}
......
/*
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<Condition_information_item> *cond_info_list;
List<DYNCALL_CREATE_DEF> *dyncol_def_list;
List<Item> *item_list;
......@@ -1945,6 +1946,8 @@ bool my_yyoverflow(short **a, YYSTYPE **b, size_t *yystacksize);
%type <lex_user> user grant_user grant_role user_or_role current_role
admin_option_for_role user_maybe_role
%type <user_auth> opt_auth_str auth_expression auth_token
%type <charset>
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,35 +17159,63 @@ 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;
$1->auth= $4;
}
| user IDENTIFIED_SYM via_or_with ident_or_text using_or_as
TEXT_STRING_sys
| user_or_role
{ $$= $1; }
;
auth_expression:
auth_token OR_SYM auth_expression
{
$$= $1;
$1->plugin= $4;
$1->auth= $6;
DBUG_ASSERT($$->next == NULL);
$$->next= $3;
}
| user IDENTIFIED_SYM via_or_with ident_or_text using_or_as
PASSWORD_SYM '(' TEXT_STRING ')'
| auth_token
{
$$= $1;
$1->plugin= $4;
$1->pwtext= $8;
}
| user_or_role
{ $$= $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:
......
/*
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<Condition_information_item> *cond_info_list;
List<DYNCALL_CREATE_DEF> *dyncol_def_list;
List<Item> *item_list;
......@@ -1449,6 +1450,8 @@ bool my_yyoverflow(short **a, YYSTYPE **b, size_t *yystacksize);
%type <lex_user> user grant_user grant_role user_or_role current_role
admin_option_for_role user_maybe_role
%type <user_auth> opt_auth_str auth_expression auth_token
%type <charset>
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,35 +17296,63 @@ 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;
$1->auth= $4;
}
| user IDENTIFIED_SYM via_or_with ident_or_text using_or_as
TEXT_STRING_sys
| user_or_role
{ $$= $1; }
;
auth_expression:
auth_token OR_SYM auth_expression
{
$$= $1;
$1->plugin= $4;
$1->auth= $6;
DBUG_ASSERT($$->next == NULL);
$$->next= $3;
}
| user IDENTIFIED_SYM via_or_with ident_or_text using_or_as
PASSWORD_SYM '(' TEXT_STRING ')'
| auth_token
{
$$= $1;
$1->plugin= $4;
$1->pwtext= $8;
}
| user_or_role
{ $$= $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:
......
#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);
}
};
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment