diff options
Diffstat (limited to 'sql/sql_acl.cc')
-rw-r--r-- | sql/sql_acl.cc | 4411 |
1 files changed, 3645 insertions, 766 deletions
diff --git a/sql/sql_acl.cc b/sql/sql_acl.cc index 80662832140..fdd9b107bf2 100644 --- a/sql/sql_acl.cc +++ b/sql/sql_acl.cc @@ -24,12 +24,34 @@ in the relevant fields. Empty strings comes last. */ -#include "mysql_priv.h" +#include "my_global.h" /* NO_EMBEDDED_ACCESS_CHECKS */ +#include "sql_priv.h" +#include "sql_acl.h" // MYSQL_DB_FIELD_COUNT, ACL_ACCESS +#include "sql_base.h" // close_mysql_tables +#include "key.h" // key_copy, key_cmp_if_same, key_restore +#include "sql_show.h" // append_identifier +#include "sql_table.h" // build_table_filename #include "hash_filo.h" +#include "sql_parse.h" // check_access +#include "sql_view.h" // VIEW_ANY_ACL +#include "records.h" // READ_RECORD, read_record_info, + // init_read_record, end_read_record +#include "rpl_filter.h" // rpl_filter #include <m_ctype.h> #include <stdarg.h> #include "sp_head.h" #include "sp.h" +#include "transaction.h" +#include "lock.h" // MYSQL_LOCK_IGNORE_TIMEOUT +#include "records.h" // init_read_record, end_read_record +#include <sql_common.h> +#include <mysql/plugin_auth.h> +#include "sql_connect.h" +#include "hostname.h" +#include "sql_db.h" +#include "sql_array.h" + +bool mysql_user_table_is_in_short_password_format= false; static const TABLE_FIELD_TYPE mysql_db_table_fields[MYSQL_DB_FIELD_COUNT] = { @@ -148,7 +170,334 @@ TABLE_FIELD_TYPE mysql_db_table_fields[MYSQL_DB_FIELD_COUNT] = { const TABLE_FIELD_DEF mysql_db_table_def= {MYSQL_DB_FIELD_COUNT, mysql_db_table_fields}; +static LEX_STRING native_password_plugin_name= { + C_STRING_WITH_LEN("mysql_native_password") +}; + +static LEX_STRING old_password_plugin_name= { + C_STRING_WITH_LEN("mysql_old_password") +}; + +/// @todo make it configurable +LEX_STRING *default_auth_plugin_name= &native_password_plugin_name; + +#ifndef NO_EMBEDDED_ACCESS_CHECKS +static plugin_ref old_password_plugin; +#endif +static plugin_ref native_password_plugin; + +/* Classes */ + +struct acl_host_and_ip +{ + char *hostname; + long ip, ip_mask; // Used with masked ip:s +}; + +class ACL_ACCESS { +public: + ulong sort; + ulong access; +}; + +/* ACL_HOST is used if no host is specified */ + +class ACL_HOST :public ACL_ACCESS +{ +public: + acl_host_and_ip host; + char *db; +}; + +class ACL_USER :public ACL_ACCESS +{ +public: + acl_host_and_ip host; + uint hostname_length; + USER_RESOURCES user_resource; + char *user; + uint8 salt[SCRAMBLE_LENGTH + 1]; // scrambled password in binary form + uint8 salt_len; // 0 - no password, 4 - 3.20, 8 - 4.0, 20 - 4.1.1 + enum SSL_type ssl_type; + const char *ssl_cipher, *x509_issuer, *x509_subject; + LEX_STRING plugin; + LEX_STRING auth_string; + + ACL_USER *copy(MEM_ROOT *root) + { + ACL_USER *dst= (ACL_USER *) alloc_root(root, sizeof(ACL_USER)); + if (!dst) + return 0; + *dst= *this; + dst->user= safe_strdup_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.str= strmake_root(root, plugin.str, plugin.length); + dst->auth_string.str= safe_strdup_root(root, auth_string.str); + dst->host.hostname= safe_strdup_root(root, host.hostname); + return dst; + } +}; + +class ACL_DB :public ACL_ACCESS +{ +public: + acl_host_and_ip host; + char *user,*db; +}; + + #ifndef NO_EMBEDDED_ACCESS_CHECKS +static void update_hostname(acl_host_and_ip *host, const char *hostname); +static ulong get_sort(uint count,...); +static bool compare_hostname(const acl_host_and_ip *host, const char *hostname, + const char *ip); +static bool show_proxy_grants (THD *thd, LEX_USER *user, + char *buff, size_t buffsize); + +class ACL_PROXY_USER :public ACL_ACCESS +{ + acl_host_and_ip host; + const char *user; + acl_host_and_ip proxied_host; + const char *proxied_user; + bool with_grant; + + typedef enum { + MYSQL_PROXIES_PRIV_HOST, + MYSQL_PROXIES_PRIV_USER, + MYSQL_PROXIES_PRIV_PROXIED_HOST, + MYSQL_PROXIES_PRIV_PROXIED_USER, + MYSQL_PROXIES_PRIV_WITH_GRANT, + MYSQL_PROXIES_PRIV_GRANTOR, + MYSQL_PROXIES_PRIV_TIMESTAMP } old_acl_proxy_users; +public: + ACL_PROXY_USER () {}; + + void init(const char *host_arg, const char *user_arg, + const char *proxied_host_arg, const char *proxied_user_arg, + bool with_grant_arg) + { + user= (user_arg && *user_arg) ? user_arg : NULL; + update_hostname (&host, + (host_arg && *host_arg) ? host_arg : NULL); + proxied_user= (proxied_user_arg && *proxied_user_arg) ? + proxied_user_arg : NULL; + update_hostname (&proxied_host, + (proxied_host_arg && *proxied_host_arg) ? + proxied_host_arg : NULL); + with_grant= with_grant_arg; + sort= get_sort(4, host.hostname, user, + proxied_host.hostname, proxied_user); + } + + void init(MEM_ROOT *mem, const char *host_arg, const char *user_arg, + const char *proxied_host_arg, const char *proxied_user_arg, + bool with_grant_arg) + { + init ((host_arg && *host_arg) ? strdup_root (mem, host_arg) : NULL, + (user_arg && *user_arg) ? strdup_root (mem, user_arg) : NULL, + (proxied_host_arg && *proxied_host_arg) ? + strdup_root (mem, proxied_host_arg) : NULL, + (proxied_user_arg && *proxied_user_arg) ? + strdup_root (mem, proxied_user_arg) : NULL, + with_grant_arg); + } + + void init(TABLE *table, MEM_ROOT *mem) + { + init (get_field(mem, table->field[MYSQL_PROXIES_PRIV_HOST]), + get_field(mem, table->field[MYSQL_PROXIES_PRIV_USER]), + get_field(mem, table->field[MYSQL_PROXIES_PRIV_PROXIED_HOST]), + get_field(mem, table->field[MYSQL_PROXIES_PRIV_PROXIED_USER]), + table->field[MYSQL_PROXIES_PRIV_WITH_GRANT]->val_int() != 0); + } + + bool get_with_grant() { return with_grant; } + const char *get_user() { return user; } + const char *get_host() { return host.hostname; } + const char *get_proxied_user() { return proxied_user; } + const char *get_proxied_host() { return proxied_host.hostname; } + void set_user(MEM_ROOT *mem, const char *user_arg) + { + user= user_arg && *user_arg ? strdup_root(mem, user_arg) : NULL; + } + void set_host(MEM_ROOT *mem, const char *host_arg) + { + update_hostname(&host, + (host_arg && *host_arg) ? + strdup_root(mem, host_arg) : NULL); + } + + bool check_validity(bool check_no_resolve) + { + if (check_no_resolve && + (hostname_requires_resolving(host.hostname) || + hostname_requires_resolving(proxied_host.hostname))) + { + sql_print_warning("'proxies_priv' entry '%s@%s %s@%s' " + "ignored in --skip-name-resolve mode.", + proxied_user ? proxied_user : "", + proxied_host.hostname ? proxied_host.hostname : "", + user ? user : "", + host.hostname ? host.hostname : ""); + return TRUE; + } + return FALSE; + } + + bool matches(const char *host_arg, const char *user_arg, const char *ip_arg, + const char *proxied_user_arg) + { + DBUG_ENTER("ACL_PROXY_USER::matches"); + DBUG_PRINT("info", ("compare_hostname(%s,%s,%s) &&" + "compare_hostname(%s,%s,%s) &&" + "wild_compare (%s,%s) &&" + "wild_compare (%s,%s)", + host.hostname ? host.hostname : "<NULL>", + host_arg ? host_arg : "<NULL>", + ip_arg ? ip_arg : "<NULL>", + proxied_host.hostname ? proxied_host.hostname : "<NULL>", + host_arg ? host_arg : "<NULL>", + ip_arg ? ip_arg : "<NULL>", + user_arg ? user_arg : "<NULL>", + user ? user : "<NULL>", + proxied_user_arg ? proxied_user_arg : "<NULL>", + proxied_user ? proxied_user : "<NULL>")); + DBUG_RETURN(compare_hostname(&host, host_arg, ip_arg) && + compare_hostname(&proxied_host, host_arg, ip_arg) && + (!user || + (user_arg && !wild_compare(user_arg, user, TRUE))) && + (!proxied_user || + (proxied_user && !wild_compare(proxied_user_arg, + proxied_user, TRUE)))); + } + + + inline static bool auth_element_equals(const char *a, const char *b) + { + return (a == b || (a != NULL && b != NULL && !strcmp(a,b))); + } + + + bool pk_equals(ACL_PROXY_USER *grant) + { + DBUG_ENTER("pk_equals"); + DBUG_PRINT("info", ("strcmp(%s,%s) &&" + "strcmp(%s,%s) &&" + "wild_compare (%s,%s) &&" + "wild_compare (%s,%s)", + user ? user : "<NULL>", + grant->user ? grant->user : "<NULL>", + proxied_user ? proxied_user : "<NULL>", + grant->proxied_user ? grant->proxied_user : "<NULL>", + host.hostname ? host.hostname : "<NULL>", + grant->host.hostname ? grant->host.hostname : "<NULL>", + proxied_host.hostname ? proxied_host.hostname : "<NULL>", + grant->proxied_host.hostname ? + grant->proxied_host.hostname : "<NULL>")); + + DBUG_RETURN(auth_element_equals(user, grant->user) && + auth_element_equals(proxied_user, grant->proxied_user) && + auth_element_equals(host.hostname, grant->host.hostname) && + auth_element_equals(proxied_host.hostname, + grant->proxied_host.hostname)); + } + + + bool granted_on(const char *host_arg, const char *user_arg) + { + return (((!user && (!user_arg || !user_arg[0])) || + (user && user_arg && !strcmp(user, user_arg))) && + ((!host.hostname && (!host_arg || !host_arg[0])) || + (host.hostname && host_arg && !strcmp(host.hostname, host_arg)))); + } + + + void print_grant(String *str) + { + str->append(STRING_WITH_LEN("GRANT PROXY ON '")); + if (proxied_user) + str->append(proxied_user, strlen(proxied_user)); + str->append(STRING_WITH_LEN("'@'")); + if (proxied_host.hostname) + str->append(proxied_host.hostname, strlen(proxied_host.hostname)); + str->append(STRING_WITH_LEN("' TO '")); + if (user) + str->append(user, strlen(user)); + str->append(STRING_WITH_LEN("'@'")); + if (host.hostname) + str->append(host.hostname, strlen(host.hostname)); + str->append(STRING_WITH_LEN("'")); + if (with_grant) + str->append(STRING_WITH_LEN(" WITH GRANT OPTION")); + } + + void set_data(ACL_PROXY_USER *grant) + { + with_grant= grant->with_grant; + } + + static int store_pk(TABLE *table, + const LEX_STRING *host, + const LEX_STRING *user, + const LEX_STRING *proxied_host, + const LEX_STRING *proxied_user) + { + DBUG_ENTER("ACL_PROXY_USER::store_pk"); + DBUG_PRINT("info", ("host=%s, user=%s, proxied_host=%s, proxied_user=%s", + host->str ? host->str : "<NULL>", + user->str ? user->str : "<NULL>", + proxied_host->str ? proxied_host->str : "<NULL>", + proxied_user->str ? proxied_user->str : "<NULL>")); + if (table->field[MYSQL_PROXIES_PRIV_HOST]->store(host->str, + host->length, + system_charset_info)) + DBUG_RETURN(TRUE); + if (table->field[MYSQL_PROXIES_PRIV_USER]->store(user->str, + user->length, + system_charset_info)) + DBUG_RETURN(TRUE); + if (table->field[MYSQL_PROXIES_PRIV_PROXIED_HOST]->store(proxied_host->str, + proxied_host->length, + system_charset_info)) + DBUG_RETURN(TRUE); + if (table->field[MYSQL_PROXIES_PRIV_PROXIED_USER]->store(proxied_user->str, + proxied_user->length, + system_charset_info)) + DBUG_RETURN(TRUE); + + DBUG_RETURN(FALSE); + } + + static int store_data_record(TABLE *table, + const LEX_STRING *host, + const LEX_STRING *user, + const LEX_STRING *proxied_host, + const LEX_STRING *proxied_user, + bool with_grant, + const char *grantor) + { + DBUG_ENTER("ACL_PROXY_USER::store_pk"); + if (store_pk(table, host, user, proxied_host, proxied_user)) + DBUG_RETURN(TRUE); + DBUG_PRINT("info", ("with_grant=%s", with_grant ? "TRUE" : "FALSE")); + if (table->field[MYSQL_PROXIES_PRIV_WITH_GRANT]->store(with_grant ? 1 : 0, + TRUE)) + DBUG_RETURN(TRUE); + if (table->field[MYSQL_PROXIES_PRIV_GRANTOR]->store(grantor, + strlen(grantor), + system_charset_info)) + DBUG_RETURN(TRUE); + + DBUG_RETURN(FALSE); + } +}; #define FIRST_NON_YN_FIELD 26 @@ -168,10 +517,15 @@ static uchar* acl_entry_get_key(acl_entry *entry, size_t *length, return (uchar*) entry->key; } -#define IP_ADDR_STRLEN (3+1+3+1+3+1+3) -#define ACL_KEY_LENGTH (IP_ADDR_STRLEN+1+NAME_LEN+1+USERNAME_LENGTH+1) +#define IP_ADDR_STRLEN (3 + 1 + 3 + 1 + 3 + 1 + 3) +#define ACL_KEY_LENGTH (IP_ADDR_STRLEN + 1 + NAME_LEN + \ + 1 + USERNAME_LENGTH + 1) -static DYNAMIC_ARRAY acl_hosts,acl_users,acl_dbs; +/** Size of the header fields of an authentication packet. */ +#define AUTH_PACKET_HEADER_SIZE_PROTO_41 32 +#define AUTH_PACKET_HEADER_SIZE_PROTO_40 5 + +static DYNAMIC_ARRAY acl_hosts, acl_users, acl_dbs, acl_proxy_users; static MEM_ROOT mem, memex; static bool initialized=0; static bool allow_all_hosts=1; @@ -189,9 +543,6 @@ static ACL_USER *find_acl_user(const char *host, const char *user, static bool update_user_table(THD *thd, TABLE *table, const char *host, const char *user, const char *new_password, uint new_password_len); -static void update_hostname(acl_host_and_ip *host, const char *hostname); -static bool compare_hostname(const acl_host_and_ip *host,const char *hostname, - const char *ip); static my_bool acl_load(THD *thd, TABLE_LIST *tables); static my_bool grant_load(THD *thd, TABLE_LIST *tables); static inline void get_grantor(THD *thd, char* grantor); @@ -204,8 +555,10 @@ enum enum_acl_lists DB_ACL, COLUMN_PRIVILEGES_HASH, PROC_PRIVILEGES_HASH, - FUNC_PRIVILEGES_HASH + FUNC_PRIVILEGES_HASH, + PROXY_USERS_ACL }; + /* Convert scrambled password to binary form, according to scramble type, Binary form is stored in user.salt. @@ -230,25 +583,6 @@ set_user_salt(ACL_USER *acl_user, const char *password, uint password_len) } /* - This after_update function is used when user.password is less than - SCRAMBLE_LENGTH bytes. -*/ - -static void restrict_update_of_old_passwords_var(THD *thd, - enum_var_type var_type) -{ - if (var_type == OPT_GLOBAL) - { - pthread_mutex_lock(&LOCK_global_system_variables); - global_system_variables.old_passwords= 1; - pthread_mutex_unlock(&LOCK_global_system_variables); - } - else - thd->variables.old_passwords= 1; -} - - -/* Initialize structures responsible for user/db-level privilege checking and load privilege information for them from tables in the 'mysql' database. @@ -273,9 +607,22 @@ my_bool acl_init(bool dont_read_acl_tables) DBUG_ENTER("acl_init"); acl_cache= new hash_filo(ACL_CACHE_SIZE, 0, 0, - (hash_get_key) acl_entry_get_key, - (hash_free_key) free, + (my_hash_get_key) acl_entry_get_key, + (my_hash_free_key) free, &my_charset_utf8_bin); + + /* + cache built-in native authentication plugins, + to avoid hash searches and a global mutex lock on every connect + */ + native_password_plugin= my_plugin_lock_by_name(0, + &native_password_plugin_name, MYSQL_AUTHENTICATION_PLUGIN); + old_password_plugin= my_plugin_lock_by_name(0, + &old_password_plugin_name, MYSQL_AUTHENTICATION_PLUGIN); + + if (!native_password_plugin || !old_password_plugin) + DBUG_RETURN(1); + if (dont_read_acl_tables) { DBUG_RETURN(0); /* purecov: tested */ @@ -288,7 +635,6 @@ my_bool acl_init(bool dont_read_acl_tables) DBUG_RETURN(1); /* purecov: inspected */ thd->thread_stack= (char*) &thd; thd->store_globals(); - lex_start(thd); /* It is safe to call acl_reload() since acl_* arrays and hashes which will be freed there are global static objects and thus are initialized @@ -301,6 +647,37 @@ my_bool acl_init(bool dont_read_acl_tables) DBUG_RETURN(return_val); } +/** + Choose from either native or old password plugins when assigning a password +*/ + +static bool +set_user_plugin (ACL_USER *user, int password_len) +{ + switch (password_len) + { + case 0: /* no password */ + case SCRAMBLED_PASSWORD_CHAR_LENGTH: + user->plugin= native_password_plugin_name; + return FALSE; + case SCRAMBLED_PASSWORD_CHAR_LENGTH_323: + user->plugin= old_password_plugin_name; + return FALSE; + case 45: /* 4.1: to be removed */ + sql_print_warning("Found 4.1.0 style password for user '%s@%s'. " + "Ignoring user. " + "You should change password for this user.", + user->user ? user->user : "", + user->host.hostname ? user->host.hostname : ""); + return TRUE; + default: + sql_print_warning("Found invalid password for user: '%s@%s'; " + "Ignoring user", user->user ? user->user : "", + user->host.hostname ? user->host.hostname : ""); + return TRUE; + } +} + /* Initialize structures responsible for user/db-level privilege checking @@ -338,7 +715,7 @@ static my_bool acl_load(THD *thd, TABLE_LIST *tables) init_read_record(&read_record_info,thd,table= tables[0].table,NULL,1,0, FALSE); table->use_all_columns(); - VOID(my_init_dynamic_array(&acl_hosts,sizeof(ACL_HOST),20,50)); + (void) my_init_dynamic_array(&acl_hosts,sizeof(ACL_HOST),20,50); while (!(read_record_info.read_record(&read_record_info))) { ACL_HOST host; @@ -378,7 +755,7 @@ static my_bool acl_load(THD *thd, TABLE_LIST *tables) host.access|=REFERENCES_ACL | INDEX_ACL | ALTER_ACL | CREATE_TMP_ACL; } #endif - VOID(push_dynamic(&acl_hosts,(uchar*) &host)); + (void) push_dynamic(&acl_hosts,(uchar*) &host); } my_qsort((uchar*) dynamic_element(&acl_hosts,0,ACL_HOST*),acl_hosts.elements, sizeof(ACL_HOST),(qsort_cmp) acl_compare); @@ -387,7 +764,7 @@ static my_bool acl_load(THD *thd, TABLE_LIST *tables) init_read_record(&read_record_info,thd,table=tables[1].table,NULL,1,0,FALSE); table->use_all_columns(); - VOID(my_init_dynamic_array(&acl_users,sizeof(ACL_USER),50,100)); + (void) my_init_dynamic_array(&acl_users,sizeof(ACL_USER),50,100); password_length= table->field[2]->field_length / table->field[2]->charset()->mbmaxlen; if (password_length < SCRAMBLED_PASSWORD_CHAR_LENGTH_323) @@ -400,23 +777,23 @@ static my_bool acl_load(THD *thd, TABLE_LIST *tables) DBUG_PRINT("info",("user table fields: %d, password length: %d", table->s->fields, password_length)); - pthread_mutex_lock(&LOCK_global_system_variables); + mysql_mutex_lock(&LOCK_global_system_variables); if (password_length < SCRAMBLED_PASSWORD_CHAR_LENGTH) { if (opt_secure_auth) { - pthread_mutex_unlock(&LOCK_global_system_variables); + mysql_mutex_unlock(&LOCK_global_system_variables); sql_print_error("Fatal error: mysql.user table is in old format, " "but server started with --secure-auth option."); goto end; } - sys_old_passwords.after_update= restrict_update_of_old_passwords_var; + mysql_user_table_is_in_short_password_format= true; if (global_system_variables.old_passwords) - pthread_mutex_unlock(&LOCK_global_system_variables); + mysql_mutex_unlock(&LOCK_global_system_variables); else { global_system_variables.old_passwords= 1; - pthread_mutex_unlock(&LOCK_global_system_variables); + mysql_mutex_unlock(&LOCK_global_system_variables); sql_print_warning("mysql.user table is not updated to new password format; " "Disabling new password usage until " "mysql_fix_privilege_tables is run"); @@ -425,14 +802,15 @@ static my_bool acl_load(THD *thd, TABLE_LIST *tables) } else { - sys_old_passwords.after_update= 0; - pthread_mutex_unlock(&LOCK_global_system_variables); + mysql_user_table_is_in_short_password_format= false; + mysql_mutex_unlock(&LOCK_global_system_variables); } allow_all_hosts=0; while (!(read_record_info.read_record(&read_record_info))) { ACL_USER user; + bzero(&user, sizeof(user)); update_hostname(&user.host, get_field(&mem, table->field[0])); user.user= get_field(&mem, table->field[1]); if (check_no_resolve && hostname_requires_resolving(user.host.hostname)) @@ -444,27 +822,13 @@ static my_bool acl_load(THD *thd, TABLE_LIST *tables) continue; } - const char *password= get_field(thd->mem_root, table->field[2]); + char *password= get_field(&mem, table->field[2]); uint password_len= password ? strlen(password) : 0; set_user_salt(&user, password, password_len); - if (user.salt_len == 0 && password_len != 0) - { - switch (password_len) { - case 45: /* 4.1: to be removed */ - sql_print_warning("Found 4.1 style password for user '%s@%s'. " - "Ignoring user. " - "You should change password for this user.", - user.user ? user.user : "", - user.host.hostname ? user.host.hostname : ""); - break; - default: - sql_print_warning("Found invalid password for user: '%s@%s'; " - "Ignoring user", user.user ? user.user : "", - user.host.hostname ? user.host.hostname : ""); - break; - } - } - else // password is correct + + if (set_user_plugin(&user, password_len)) + continue; + { uint next_field; user.access= get_access(table,3,&next_field) & GLOBAL_ACLS; @@ -541,13 +905,43 @@ static my_bool acl_load(THD *thd, TABLE_LIST *tables) ptr= get_field(thd->mem_root, table->field[next_field++]); user.user_resource.user_conn= ptr ? atoi(ptr) : 0; } - else - user.user_resource.user_conn= 0; + + if (table->s->fields >= 41) + { + /* We may have plugin & auth_String fields */ + char *tmpstr= get_field(&mem, table->field[next_field++]); + if (tmpstr) + { + if (password_len) + { + sql_print_warning("'user' entry '%s@%s' has both a password " + "and an authentication plugin specified. The " + "password will be ignored.", + user.user ? user.user : "", + user.host.hostname ? user.host.hostname : ""); + } + if (my_strcasecmp(system_charset_info, tmpstr, + native_password_plugin_name.str) == 0) + user.plugin= native_password_plugin_name; + else + if (my_strcasecmp(system_charset_info, tmpstr, + old_password_plugin_name.str) == 0) + user.plugin= old_password_plugin_name; + else + { + user.plugin.str= tmpstr; + user.plugin.length= strlen(tmpstr); + } + user.auth_string.str= get_field(&mem, table->field[next_field++]); + if (!user.auth_string.str) + user.auth_string.str= const_cast<char*>(""); + user.auth_string.length= strlen(user.auth_string.str); + } + } } else { user.ssl_type=SSL_TYPE_NONE; - bzero((char *)&(user.user_resource),sizeof(user.user_resource)); #ifndef TO_BE_REMOVED if (table->s->fields <= 13) { // Without grant @@ -562,7 +956,7 @@ static my_bool acl_load(THD *thd, TABLE_LIST *tables) user.access|= SUPER_ACL | EXECUTE_ACL; #endif } - VOID(push_dynamic(&acl_users,(uchar*) &user)); + (void) push_dynamic(&acl_users,(uchar*) &user); if (!user.host.hostname || (user.host.hostname[0] == wild_many && !user.host.hostname[1])) allow_all_hosts=1; // Anyone can connect @@ -575,7 +969,7 @@ static my_bool acl_load(THD *thd, TABLE_LIST *tables) init_read_record(&read_record_info,thd,table=tables[2].table,NULL,1,0,FALSE); table->use_all_columns(); - VOID(my_init_dynamic_array(&acl_dbs,sizeof(ACL_DB),50,100)); + (void) my_init_dynamic_array(&acl_dbs,sizeof(ACL_DB),50,100); while (!(read_record_info.read_record(&read_record_info))) { ACL_DB db; @@ -625,12 +1019,44 @@ static my_bool acl_load(THD *thd, TABLE_LIST *tables) db.access|=REFERENCES_ACL | INDEX_ACL | ALTER_ACL; } #endif - VOID(push_dynamic(&acl_dbs,(uchar*) &db)); + (void) push_dynamic(&acl_dbs,(uchar*) &db); } my_qsort((uchar*) dynamic_element(&acl_dbs,0,ACL_DB*),acl_dbs.elements, sizeof(ACL_DB),(qsort_cmp) acl_compare); end_read_record(&read_record_info); freeze_size(&acl_dbs); + + (void) my_init_dynamic_array(&acl_proxy_users, sizeof(ACL_PROXY_USER), + 50, 100); + if (tables[3].table) + { + init_read_record(&read_record_info, thd, table= tables[3].table, NULL, 1, + 0, FALSE); + table->use_all_columns(); + while (!(read_record_info.read_record(&read_record_info))) + { + ACL_PROXY_USER proxy; + proxy.init(table, &mem); + if (proxy.check_validity(check_no_resolve)) + continue; + if (push_dynamic(&acl_proxy_users, (uchar*) &proxy)) + { + end_read_record(&read_record_info); + goto end; + } + } + my_qsort((uchar*) dynamic_element(&acl_proxy_users, 0, ACL_PROXY_USER*), + acl_proxy_users.elements, + sizeof(ACL_PROXY_USER), (qsort_cmp) acl_compare); + end_read_record(&read_record_info); + } + else + { + sql_print_error("Missing system table mysql.proxies_priv; " + "please run mysql_upgrade to create it"); + } + freeze_size(&acl_proxy_users); + init_check_host(); initialized=1; @@ -649,7 +1075,10 @@ void acl_free(bool end) delete_dynamic(&acl_users); delete_dynamic(&acl_dbs); delete_dynamic(&acl_wild_hosts); - hash_free(&acl_check_hosts); + delete_dynamic(&acl_proxy_users); + my_hash_free(&acl_check_hosts); + plugin_unlock(0, native_password_plugin); + plugin_unlock(0, old_password_plugin); if (!end) acl_cache->clear(1); /* purecov: inspected */ else @@ -681,65 +1110,65 @@ void acl_free(bool end) my_bool acl_reload(THD *thd) { - TABLE_LIST tables[3]; - DYNAMIC_ARRAY old_acl_hosts,old_acl_users,old_acl_dbs; + TABLE_LIST tables[4]; + DYNAMIC_ARRAY old_acl_hosts, old_acl_users, old_acl_dbs, old_acl_proxy_users; MEM_ROOT old_mem; bool old_initialized; my_bool return_val= TRUE; DBUG_ENTER("acl_reload"); - if (thd->locked_tables) - { // Can't have locked tables here - thd->lock=thd->locked_tables; - thd->locked_tables=0; - close_thread_tables(thd); - } - /* To avoid deadlocks we should obtain table locks before obtaining acl_cache->lock mutex. */ - bzero((char*) tables, sizeof(tables)); - tables[0].alias= tables[0].table_name= (char*) "host"; - tables[1].alias= tables[1].table_name= (char*) "user"; - tables[2].alias= tables[2].table_name= (char*) "db"; - tables[0].db=tables[1].db=tables[2].db=(char*) "mysql"; - tables[0].next_local= tables[0].next_global= tables+1; - tables[1].next_local= tables[1].next_global= tables+2; - tables[0].lock_type=tables[1].lock_type=tables[2].lock_type=TL_READ; - tables[0].skip_temporary= tables[1].skip_temporary= - tables[2].skip_temporary= TRUE; - - if (simple_open_n_lock_tables(thd, tables)) + tables[0].init_one_table(C_STRING_WITH_LEN("mysql"), + C_STRING_WITH_LEN("host"), "host", TL_READ); + tables[1].init_one_table(C_STRING_WITH_LEN("mysql"), + C_STRING_WITH_LEN("user"), "user", TL_READ); + tables[2].init_one_table(C_STRING_WITH_LEN("mysql"), + C_STRING_WITH_LEN("db"), "db", TL_READ); + tables[3].init_one_table(C_STRING_WITH_LEN("mysql"), + C_STRING_WITH_LEN("proxies_priv"), + "proxies_priv", TL_READ); + tables[0].next_local= tables[0].next_global= tables + 1; + tables[1].next_local= tables[1].next_global= tables + 2; + tables[2].next_local= tables[2].next_global= tables + 3; + tables[0].open_type= tables[1].open_type= tables[2].open_type= + tables[3].open_type= OT_BASE_ONLY; + tables[3].open_strategy= TABLE_LIST::OPEN_IF_EXISTS; + + if (open_and_lock_tables(thd, tables, FALSE, MYSQL_LOCK_IGNORE_TIMEOUT)) { /* Execution might have been interrupted; only print the error message if an error condition has been raised. */ - if (thd->main_da.is_error()) + if (thd->stmt_da->is_error()) sql_print_error("Fatal error: Can't open and lock privilege tables: %s", - thd->main_da.message()); + thd->stmt_da->message()); goto end; } if ((old_initialized=initialized)) - VOID(pthread_mutex_lock(&acl_cache->lock)); + mysql_mutex_lock(&acl_cache->lock); - old_acl_hosts=acl_hosts; - old_acl_users=acl_users; - old_acl_dbs=acl_dbs; - old_mem=mem; + old_acl_hosts= acl_hosts; + old_acl_users= acl_users; + old_acl_proxy_users= acl_proxy_users; + old_acl_dbs= acl_dbs; + old_mem= mem; delete_dynamic(&acl_wild_hosts); - hash_free(&acl_check_hosts); + my_hash_free(&acl_check_hosts); if ((return_val= acl_load(thd, tables))) { // Error. Revert to old list DBUG_PRINT("error",("Reverting to old privileges")); acl_free(); /* purecov: inspected */ - acl_hosts=old_acl_hosts; - acl_users=old_acl_users; - acl_dbs=old_acl_dbs; - mem=old_mem; + acl_hosts= old_acl_hosts; + acl_users= old_acl_users; + acl_proxy_users= old_acl_proxy_users; + acl_dbs= old_acl_dbs; + mem= old_mem; init_check_host(); } else @@ -747,12 +1176,13 @@ my_bool acl_reload(THD *thd) free_root(&old_mem,MYF(0)); delete_dynamic(&old_acl_hosts); delete_dynamic(&old_acl_users); + delete_dynamic(&old_acl_proxy_users); delete_dynamic(&old_acl_dbs); } if (old_initialized) - VOID(pthread_mutex_unlock(&acl_cache->lock)); + mysql_mutex_unlock(&acl_cache->lock); end: - close_thread_tables(thd); + close_mysql_tables(thd); DBUG_RETURN(return_val); } @@ -852,246 +1282,10 @@ static int acl_compare(ACL_ACCESS *a,ACL_ACCESS *b) /* - Seek ACL entry for a user, check password, SSL cypher, and if - everything is OK, update THD user data and USER_RESOURCES struct. - - IMPLEMENTATION - This function does not check if the user has any sensible privileges: - only user's existence and validity is checked. - Note, that entire operation is protected by acl_cache_lock. + Gets user credentials without authentication and resource limit checks. SYNOPSIS acl_getroot() - thd thread handle. If all checks are OK, - thd->security_ctx->priv_user/master_access are updated. - thd->security_ctx->host/ip/user are used for checks. - mqh user resources; on success mqh is reset, else - unchanged - passwd scrambled & crypted password, received from client - (to check): thd->scramble or thd->scramble_323 is - used to decrypt passwd, so they must contain - original random string, - passwd_len length of passwd, must be one of 0, 8, - SCRAMBLE_LENGTH_323, SCRAMBLE_LENGTH - 'thd' and 'mqh' are updated on success; other params are IN. - - RETURN VALUE - 0 success: thd->priv_user, thd->priv_host, thd->master_access, mqh are - updated - 1 user not found or authentication failure - 2 user found, has long (4.1.1) salt, but passwd is in old (3.23) format. - -1 user found, has short (3.23) salt, but passwd is in new (4.1.1) format. -*/ - -int acl_getroot(THD *thd, USER_RESOURCES *mqh, - const char *passwd, uint passwd_len) -{ - ulong user_access= NO_ACCESS; - int res= 1; - ACL_USER *acl_user= 0; - Security_context *sctx= thd->security_ctx; - DBUG_ENTER("acl_getroot"); - - if (!initialized) - { - /* - here if mysqld's been started with --skip-grant-tables option. - */ - sctx->skip_grants(); - bzero((char*) mqh, sizeof(*mqh)); - DBUG_RETURN(0); - } - - VOID(pthread_mutex_lock(&acl_cache->lock)); - - /* - Find acl entry in user database. Note, that find_acl_user is not the same, - because it doesn't take into account the case when user is not empty, - but acl_user->user is empty - */ - - for (uint i=0 ; i < acl_users.elements ; i++) - { - ACL_USER *acl_user_tmp= dynamic_element(&acl_users,i,ACL_USER*); - if (!acl_user_tmp->user || !strcmp(sctx->user, acl_user_tmp->user)) - { - if (compare_hostname(&acl_user_tmp->host, sctx->host, sctx->ip)) - { - /* check password: it should be empty or valid */ - if (passwd_len == acl_user_tmp->salt_len) - { - if (acl_user_tmp->salt_len == 0 || - (acl_user_tmp->salt_len == SCRAMBLE_LENGTH ? - check_scramble(passwd, thd->scramble, acl_user_tmp->salt) : - check_scramble_323(passwd, thd->scramble, - (ulong *) acl_user_tmp->salt)) == 0) - { - acl_user= acl_user_tmp; - res= 0; - } - } - else if (passwd_len == SCRAMBLE_LENGTH && - acl_user_tmp->salt_len == SCRAMBLE_LENGTH_323) - res= -1; - else if (passwd_len == SCRAMBLE_LENGTH_323 && - acl_user_tmp->salt_len == SCRAMBLE_LENGTH) - res= 2; - /* linear search complete: */ - break; - } - } - } - /* - This was moved to separate tree because of heavy HAVE_OPENSSL case. - If acl_user is not null, res is 0. - */ - - if (acl_user) - { - /* OK. User found and password checked continue validation */ -#ifdef HAVE_OPENSSL - Vio *vio=thd->net.vio; - SSL *ssl= (SSL*) vio->ssl_arg; - X509 *cert; -#endif - - /* - At this point we know that user is allowed to connect - from given host by given username/password pair. Now - we check if SSL is required, if user is using SSL and - if X509 certificate attributes are OK - */ - switch (acl_user->ssl_type) { - case SSL_TYPE_NOT_SPECIFIED: // Impossible - case SSL_TYPE_NONE: // SSL is not required - user_access= acl_user->access; - break; -#ifdef HAVE_OPENSSL - case SSL_TYPE_ANY: // Any kind of SSL is ok - if (vio_type(vio) == VIO_TYPE_SSL) - user_access= acl_user->access; - break; - case SSL_TYPE_X509: /* Client should have any valid certificate. */ - /* - Connections with non-valid certificates are dropped already - in sslaccept() anyway, so we do not check validity here. - - We need to check for absence of SSL because without SSL - we should reject connection. - */ - if (vio_type(vio) == VIO_TYPE_SSL && - SSL_get_verify_result(ssl) == X509_V_OK && - (cert= SSL_get_peer_certificate(ssl))) - { - user_access= acl_user->access; - X509_free(cert); - } - break; - case SSL_TYPE_SPECIFIED: /* Client should have specified attrib */ - /* - We do not check for absence of SSL because without SSL it does - not pass all checks here anyway. - If cipher name is specified, we compare it to actual cipher in - use. - */ - if (vio_type(vio) != VIO_TYPE_SSL || - SSL_get_verify_result(ssl) != X509_V_OK) - break; - if (acl_user->ssl_cipher) - { - DBUG_PRINT("info",("comparing ciphers: '%s' and '%s'", - acl_user->ssl_cipher,SSL_get_cipher(ssl))); - if (!strcmp(acl_user->ssl_cipher,SSL_get_cipher(ssl))) - user_access= acl_user->access; - else - { - if (global_system_variables.log_warnings) - sql_print_information("X509 ciphers mismatch: should be '%s' but is '%s'", - acl_user->ssl_cipher, - SSL_get_cipher(ssl)); - break; - } - } - /* Prepare certificate (if exists) */ - DBUG_PRINT("info",("checkpoint 1")); - if (!(cert= SSL_get_peer_certificate(ssl))) - { - user_access=NO_ACCESS; - break; - } - DBUG_PRINT("info",("checkpoint 2")); - /* If X509 issuer is specified, we check it... */ - if (acl_user->x509_issuer) - { - DBUG_PRINT("info",("checkpoint 3")); - char *ptr = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0); - DBUG_PRINT("info",("comparing issuers: '%s' and '%s'", - acl_user->x509_issuer, ptr)); - if (strcmp(acl_user->x509_issuer, ptr)) - { - if (global_system_variables.log_warnings) - sql_print_information("X509 issuer mismatch: should be '%s' " - "but is '%s'", acl_user->x509_issuer, ptr); - free(ptr); - X509_free(cert); - user_access=NO_ACCESS; - break; - } - user_access= acl_user->access; - free(ptr); - } - DBUG_PRINT("info",("checkpoint 4")); - /* X509 subject is specified, we check it .. */ - if (acl_user->x509_subject) - { - char *ptr= X509_NAME_oneline(X509_get_subject_name(cert), 0, 0); - DBUG_PRINT("info",("comparing subjects: '%s' and '%s'", - acl_user->x509_subject, ptr)); - if (strcmp(acl_user->x509_subject,ptr)) - { - if (global_system_variables.log_warnings) - sql_print_information("X509 subject mismatch: should be '%s' but is '%s'", - acl_user->x509_subject, ptr); - free(ptr); - X509_free(cert); - user_access=NO_ACCESS; - break; - } - user_access= acl_user->access; - free(ptr); - } - /* Deallocate the X509 certificate. */ - X509_free(cert); - break; -#else /* HAVE_OPENSSL */ - default: - /* - If we don't have SSL but SSL is required for this user the - authentication should fail. - */ - break; -#endif /* HAVE_OPENSSL */ - } - sctx->master_access= user_access; - sctx->priv_user= acl_user->user ? sctx->user : (char *) ""; - *mqh= acl_user->user_resource; - - if (acl_user->host.hostname) - strmake(sctx->priv_host, acl_user->host.hostname, MAX_HOSTNAME - 1); - else - *sctx->priv_host= 0; - } - VOID(pthread_mutex_unlock(&acl_cache->lock)); - DBUG_RETURN(res); -} - - -/* - This is like acl_getroot() above, but it doesn't check password, - and we don't care about the user resources. - - SYNOPSIS - acl_getroot_no_password() sctx Context which should be initialized user user name host host name @@ -1103,13 +1297,13 @@ int acl_getroot(THD *thd, USER_RESOURCES *mqh, TRUE Error */ -bool acl_getroot_no_password(Security_context *sctx, char *user, char *host, - char *ip, char *db) +bool acl_getroot(Security_context *sctx, char *user, char *host, + char *ip, char *db) { int res= 1; uint i; ACL_USER *acl_user= 0; - DBUG_ENTER("acl_getroot_no_password"); + DBUG_ENTER("acl_getroot"); DBUG_PRINT("enter", ("Host: '%s', Ip: '%s', User: '%s', db: '%s'", (host ? host : "(NULL)"), (ip ? ip : "(NULL)"), @@ -1128,12 +1322,11 @@ bool acl_getroot_no_password(Security_context *sctx, char *user, char *host, DBUG_RETURN(FALSE); } - VOID(pthread_mutex_lock(&acl_cache->lock)); + mysql_mutex_lock(&acl_cache->lock); sctx->master_access= 0; sctx->db_access= 0; - sctx->priv_user= (char *) ""; - *sctx->priv_host= 0; + *sctx->priv_user= *sctx->priv_host= 0; /* Find acl entry in user database. @@ -1175,14 +1368,18 @@ bool acl_getroot_no_password(Security_context *sctx, char *user, char *host, } } sctx->master_access= acl_user->access; - sctx->priv_user= acl_user->user ? user : (char *) ""; + + if (acl_user->user) + strmake(sctx->priv_user, user, USERNAME_LENGTH); + else + *sctx->priv_user= 0; if (acl_user->host.hostname) strmake(sctx->priv_host, acl_user->host.hostname, MAX_HOSTNAME - 1); else *sctx->priv_host= 0; } - VOID(pthread_mutex_unlock(&acl_cache->lock)); + mysql_mutex_unlock(&acl_cache->lock); DBUG_RETURN(res); } @@ -1201,9 +1398,11 @@ static void acl_update_user(const char *user, const char *host, const char *x509_issuer, const char *x509_subject, USER_RESOURCES *mqh, - ulong privileges) + ulong privileges, + const LEX_STRING *plugin, + const LEX_STRING *auth) { - safe_mutex_assert_owner(&acl_cache->lock); + mysql_mutex_assert_owner(&acl_cache->lock); for (uint i=0 ; i < acl_users.elements ; i++) { @@ -1215,6 +1414,14 @@ static void acl_update_user(const char *user, const char *host, (acl_user->host.hostname && !my_strcasecmp(system_charset_info, host, acl_user->host.hostname))) { + if (plugin->str[0]) + { + acl_user->plugin.str= strmake_root(&mem, plugin->str, plugin->length); + acl_user->plugin.length= plugin->length; + acl_user->auth_string.str= auth->str ? + strmake_root(&mem, auth->str, auth->length) : const_cast<char*>(""); + acl_user->auth_string.length= auth->length; + } acl_user->access=privileges; if (mqh->specified_limits & USER_RESOURCES::QUERIES_PER_HOUR) acl_user->user_resource.questions=mqh->questions; @@ -1251,14 +1458,32 @@ static void acl_insert_user(const char *user, const char *host, const char *x509_issuer, const char *x509_subject, USER_RESOURCES *mqh, - ulong privileges) + ulong privileges, + const LEX_STRING *plugin, + const LEX_STRING *auth) { ACL_USER acl_user; - safe_mutex_assert_owner(&acl_cache->lock); + mysql_mutex_assert_owner(&acl_cache->lock); acl_user.user=*user ? strdup_root(&mem,user) : 0; update_hostname(&acl_user.host, *host ? strdup_root(&mem, host): 0); + if (plugin->str[0]) + { + acl_user.plugin.str= strmake_root(&mem, plugin->str, plugin->length); + acl_user.plugin.length= plugin->length; + acl_user.auth_string.str= auth->str ? + strmake_root(&mem, auth->str, auth->length) : const_cast<char*>(""); + acl_user.auth_string.length= auth->length; + } + else + { + acl_user.plugin= password_len == SCRAMBLED_PASSWORD_CHAR_LENGTH_323 ? + old_password_plugin_name : native_password_plugin_name; + acl_user.auth_string.str= const_cast<char*>(""); + acl_user.auth_string.length= 0; + } + acl_user.access=privileges; acl_user.user_resource = *mqh; acl_user.sort=get_sort(2,acl_user.host.hostname,acl_user.user); @@ -1271,7 +1496,7 @@ static void acl_insert_user(const char *user, const char *host, set_user_salt(&acl_user, password, password_len); - VOID(push_dynamic(&acl_users,(uchar*) &acl_user)); + (void) push_dynamic(&acl_users,(uchar*) &acl_user); if (!acl_user.host.hostname || (acl_user.host.hostname[0] == wild_many && !acl_user.host.hostname[1])) allow_all_hosts=1; // Anyone can connect /* purecov: tested */ @@ -1286,7 +1511,7 @@ static void acl_insert_user(const char *user, const char *host, static void acl_update_db(const char *user, const char *host, const char *db, ulong privileges) { - safe_mutex_assert_owner(&acl_cache->lock); + mysql_mutex_assert_owner(&acl_cache->lock); for (uint i=0 ; i < acl_dbs.elements ; i++) { @@ -1331,13 +1556,13 @@ static void acl_insert_db(const char *user, const char *host, const char *db, ulong privileges) { ACL_DB acl_db; - safe_mutex_assert_owner(&acl_cache->lock); + mysql_mutex_assert_owner(&acl_cache->lock); acl_db.user=strdup_root(&mem,user); update_hostname(&acl_db.host, *host ? strdup_root(&mem,host) : 0); acl_db.db=strdup_root(&mem,db); acl_db.access=privileges; acl_db.sort=get_sort(3,acl_db.host.hostname,acl_db.db,acl_db.user); - VOID(push_dynamic(&acl_dbs,(uchar*) &acl_db)); + (void) push_dynamic(&acl_dbs,(uchar*) &acl_db); my_qsort((uchar*) dynamic_element(&acl_dbs,0,ACL_DB*),acl_dbs.elements, sizeof(ACL_DB),(qsort_cmp) acl_compare); } @@ -1360,7 +1585,7 @@ ulong acl_get(const char *host, const char *ip, char key[ACL_KEY_LENGTH],*tmp_db,*end; acl_entry *entry; DBUG_ENTER("acl_get"); - + copy_length= (size_t) (strlen(ip ? ip : "") + strlen(user ? user : "") + strlen(db ? db : "")); @@ -1370,7 +1595,7 @@ ulong acl_get(const char *host, const char *ip, if (copy_length >= ACL_KEY_LENGTH) DBUG_RETURN(0); - VOID(pthread_mutex_lock(&acl_cache->lock)); + mysql_mutex_lock(&acl_cache->lock); end=strmov((tmp_db=strmov(strmov(key, ip ? ip : "")+1,user)+1),db); if (lower_case_table_names) { @@ -1382,7 +1607,7 @@ ulong acl_get(const char *host, const char *ip, key_length))) { db_access=entry->access; - VOID(pthread_mutex_unlock(&acl_cache->lock)); + mysql_mutex_unlock(&acl_cache->lock); DBUG_PRINT("exit", ("access: 0x%lx", db_access)); DBUG_RETURN(db_access); } @@ -1436,7 +1661,7 @@ exit: memcpy((uchar*) entry->key,key,key_length); acl_cache->add(entry); } - VOID(pthread_mutex_unlock(&acl_cache->lock)); + mysql_mutex_unlock(&acl_cache->lock); DBUG_PRINT("exit", ("access: 0x%lx", db_access & host_access)); DBUG_RETURN(db_access & host_access); } @@ -1452,10 +1677,11 @@ exit: static void init_check_host(void) { DBUG_ENTER("init_check_host"); - VOID(my_init_dynamic_array(&acl_wild_hosts,sizeof(struct acl_host_and_ip), - acl_users.elements,1)); - VOID(hash_init(&acl_check_hosts,system_charset_info,acl_users.elements,0,0, - (hash_get_key) check_get_key,0,0)); + (void) my_init_dynamic_array(&acl_wild_hosts,sizeof(struct acl_host_and_ip), + acl_users.elements,1); + (void) my_hash_init(&acl_check_hosts,system_charset_info, + acl_users.elements, 0, 0, + (my_hash_get_key) check_get_key, 0, 0); if (!allow_all_hosts) { for (uint i=0 ; i < acl_users.elements ; i++) @@ -1477,8 +1703,9 @@ static void init_check_host(void) if (j == acl_wild_hosts.elements) // If new (void) push_dynamic(&acl_wild_hosts,(uchar*) &acl_user->host); } - else if (!hash_search(&acl_check_hosts,(uchar*) acl_user->host.hostname, - strlen(acl_user->host.hostname))) + else if (!my_hash_search(&acl_check_hosts,(uchar*) + acl_user->host.hostname, + strlen(acl_user->host.hostname))) { if (my_hash_insert(&acl_check_hosts,(uchar*) acl_user)) { // End of memory @@ -1505,7 +1732,7 @@ static void init_check_host(void) void rebuild_check_host(void) { delete_dynamic(&acl_wild_hosts); - hash_free(&acl_check_hosts); + my_hash_free(&acl_check_hosts); init_check_host(); } @@ -1516,12 +1743,12 @@ bool acl_check_host(const char *host, const char *ip) { if (allow_all_hosts) return 0; - VOID(pthread_mutex_lock(&acl_cache->lock)); + mysql_mutex_lock(&acl_cache->lock); - if ((host && hash_search(&acl_check_hosts,(uchar*) host,strlen(host))) || - (ip && hash_search(&acl_check_hosts,(uchar*) ip, strlen(ip)))) + if ((host && my_hash_search(&acl_check_hosts,(uchar*) host,strlen(host))) || + (ip && my_hash_search(&acl_check_hosts,(uchar*) ip, strlen(ip)))) { - VOID(pthread_mutex_unlock(&acl_cache->lock)); + mysql_mutex_unlock(&acl_cache->lock); return 0; // Found host } for (uint i=0 ; i < acl_wild_hosts.elements ; i++) @@ -1529,11 +1756,11 @@ bool acl_check_host(const char *host, const char *ip) acl_host_and_ip *acl=dynamic_element(&acl_wild_hosts,i,acl_host_and_ip*); if (compare_hostname(acl, host, ip)) { - VOID(pthread_mutex_unlock(&acl_cache->lock)); + mysql_mutex_unlock(&acl_cache->lock); return 0; // Host ok } } - VOID(pthread_mutex_unlock(&acl_cache->lock)); + mysql_mutex_unlock(&acl_cache->lock); return 1; // Host is not allowed } @@ -1569,7 +1796,7 @@ int check_change_password(THD *thd, const char *host, const char *user, my_strcasecmp(system_charset_info, host, thd->security_ctx->priv_host))) { - if (check_access(thd, UPDATE_ACL, "mysql",0,1,0,0)) + if (check_access(thd, UPDATE_ACL, "mysql", NULL, NULL, 1, 0)) return(1); } if (!thd->slave_thread && !thd->security_ctx->user[0]) @@ -1623,9 +1850,7 @@ bool change_password(THD *thd, const char *host, const char *user, if (check_change_password(thd, host, user, new_password, new_password_len)) DBUG_RETURN(1); - bzero((char*) &tables, sizeof(tables)); - tables.alias= tables.table_name= (char*) "user"; - tables.db= (char*) "mysql"; + tables.init_one_table("mysql", 5, "user", 4, "user", TL_WRITE); #ifdef HAVE_REPLICATION /* @@ -1644,8 +1869,7 @@ bool change_password(THD *thd, const char *host, const char *user, DBUG_RETURN(0); } #endif - - if (!(table= open_ltable(thd, &tables, TL_WRITE, 0))) + if (!(table= open_ltable(thd, &tables, TL_WRITE, MYSQL_LOCK_IGNORE_TIMEOUT))) DBUG_RETURN(1); /* @@ -1653,31 +1877,41 @@ bool change_password(THD *thd, const char *host, const char *user, row-based replication. The flag will be reset at the end of the statement. */ - if ((save_binlog_row_based= thd->current_stmt_binlog_row_based)) - thd->clear_current_stmt_binlog_row_based(); + if ((save_binlog_row_based= thd->is_current_stmt_binlog_format_row())) + thd->clear_current_stmt_binlog_format_row(); - VOID(pthread_mutex_lock(&acl_cache->lock)); + mysql_mutex_lock(&acl_cache->lock); ACL_USER *acl_user; if (!(acl_user= find_acl_user(host, user, TRUE))) { - VOID(pthread_mutex_unlock(&acl_cache->lock)); + mysql_mutex_unlock(&acl_cache->lock); my_message(ER_PASSWORD_NO_MATCH, ER(ER_PASSWORD_NO_MATCH), MYF(0)); goto end; } + /* update loaded acl entry: */ set_user_salt(acl_user, new_password, new_password_len); + if (my_strcasecmp(system_charset_info, acl_user->plugin.str, + native_password_plugin_name.str) && + my_strcasecmp(system_charset_info, acl_user->plugin.str, + old_password_plugin_name.str)) + push_warning(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, + ER_SET_PASSWORD_AUTH_PLUGIN, ER(ER_SET_PASSWORD_AUTH_PLUGIN)); + else + set_user_plugin(acl_user, new_password_len); + if (update_user_table(thd, table, acl_user->host.hostname ? acl_user->host.hostname : "", acl_user->user ? acl_user->user : "", new_password, new_password_len)) { - VOID(pthread_mutex_unlock(&acl_cache->lock)); /* purecov: deadcode */ + mysql_mutex_unlock(&acl_cache->lock); /* purecov: deadcode */ goto end; } acl_cache->clear(1); // Clear locked hostname cache - VOID(pthread_mutex_unlock(&acl_cache->lock)); + mysql_mutex_unlock(&acl_cache->lock); result= 0; if (mysql_bin_log.is_open()) { @@ -1686,16 +1920,16 @@ bool change_password(THD *thd, const char *host, const char *user, acl_user->host.hostname ? acl_user->host.hostname : "", new_password); thd->clear_error(); - result= thd->binlog_query(THD::MYSQL_QUERY_TYPE, buff, query_length, - FALSE, FALSE, 0); + result= thd->binlog_query(THD::STMT_QUERY_TYPE, buff, query_length, + FALSE, FALSE, FALSE, 0); } end: - close_thread_tables(thd); + close_mysql_tables(thd); /* Restore the state of binlog format */ - DBUG_ASSERT(!thd->current_stmt_binlog_row_based); + DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); if (save_binlog_row_based) - thd->set_current_stmt_binlog_row_based(); + thd->set_current_stmt_binlog_format_row(); DBUG_RETURN(result); } @@ -1722,9 +1956,9 @@ bool is_acl_user(const char *host, const char *user) if (!initialized) return TRUE; - VOID(pthread_mutex_lock(&acl_cache->lock)); + mysql_mutex_lock(&acl_cache->lock); res= find_acl_user(host, user, TRUE) != NULL; - VOID(pthread_mutex_unlock(&acl_cache->lock)); + mysql_mutex_unlock(&acl_cache->lock); return res; } @@ -1739,7 +1973,7 @@ find_acl_user(const char *host, const char *user, my_bool exact) DBUG_ENTER("find_acl_user"); DBUG_PRINT("enter",("host: '%s' user: '%s'",host,user)); - safe_mutex_assert_owner(&acl_cache->lock); + mysql_mutex_assert_owner(&acl_cache->lock); for (uint i=0 ; i < acl_users.elements ; i++) { @@ -1822,24 +2056,83 @@ static bool compare_hostname(const acl_host_and_ip *host, const char *hostname, (ip && !wild_compare(ip, host->hostname, 0))); } +/** + Check if the given host name needs to be resolved or not. + Host name has to be resolved if it actually contains *name*. + + For example: + 192.168.1.1 --> FALSE + 192.168.1.0/255.255.255.0 --> FALSE + % --> FALSE + 192.168.1.% --> FALSE + AB% --> FALSE + + AAAAFFFF --> TRUE (Hostname) + AAAA:FFFF:1234:5678 --> FALSE + ::1 --> FALSE + + This function does not check if the given string is a valid host name or + not. It assumes that the argument is a valid host name. + + @param hostname the string to check. + + @return a flag telling if the argument needs to be resolved or not. + @retval TRUE the argument is a host name and needs to be resolved. + @retval FALSE the argument is either an IP address, or a patter and + should not be resolved. +*/ + bool hostname_requires_resolving(const char *hostname) { - char cur; if (!hostname) return FALSE; - size_t namelen= strlen(hostname); - size_t lhlen= strlen(my_localhost); - if ((namelen == lhlen) && - !my_strnncoll(system_charset_info, (const uchar *)hostname, namelen, - (const uchar *)my_localhost, strlen(my_localhost))) + + /* Check if hostname is the localhost. */ + + size_t hostname_len= strlen(hostname); + size_t localhost_len= strlen(my_localhost); + + if (hostname == my_localhost || + (hostname_len == localhost_len && + !my_strnncoll(system_charset_info, + (const uchar *) hostname, hostname_len, + (const uchar *) my_localhost, strlen(my_localhost)))) + { return FALSE; - for (; (cur=*hostname); hostname++) + } + + /* + If the string contains any of {':', '%', '_', '/'}, it is definitely + not a host name: + - ':' means that the string is an IPv6 address; + - '%' or '_' means that the string is a pattern; + - '/' means that the string is an IPv4 network address; + */ + + for (const char *p= hostname; *p; ++p) { - if ((cur != '%') && (cur != '_') && (cur != '.') && (cur != '/') && - ((cur < '0') || (cur > '9'))) - return TRUE; + switch (*p) { + case ':': + case '%': + case '_': + case '/': + return FALSE; + } } - return FALSE; + + /* + Now we have to tell a host name (ab.cd, 12.ab) from an IPv4 address + (12.34.56.78). The assumption is that if the string contains only + digits and dots, it is an IPv4 address. Otherwise -- a host name. + */ + + for (const char *p= hostname; *p; ++p) + { + if (*p != '.' && !my_isdigit(&my_charset_latin1, *p)) + return TRUE; /* a "letter" has been found. */ + } + + return FALSE; /* all characters are either dots or digits. */ } @@ -1908,16 +2201,15 @@ static bool test_if_create_new_users(THD *thd) { TABLE_LIST tl; ulong db_access; - bzero((char*) &tl,sizeof(tl)); - tl.db= (char*) "mysql"; - tl.table_name= (char*) "user"; + tl.init_one_table(C_STRING_WITH_LEN("mysql"), + C_STRING_WITH_LEN("user"), "user", TL_WRITE); create_new_users= 1; db_access=acl_get(sctx->host, sctx->ip, sctx->priv_user, tl.db, 0); if (!(db_access & INSERT_ACL)) { - if (check_grant(thd, INSERT_ACL, &tl, 0, UINT_MAX, 1)) + if (check_grant(thd, INSERT_ACL, &tl, FALSE, UINT_MAX, TRUE)) create_new_users=0; } } @@ -1942,7 +2234,7 @@ static int replace_user_table(THD *thd, TABLE *table, const LEX_USER &combo, LEX *lex= thd->lex; DBUG_ENTER("replace_user_table"); - safe_mutex_assert_owner(&acl_cache->lock); + mysql_mutex_assert_owner(&acl_cache->lock); if (combo.password.str && combo.password.str[0]) { @@ -1987,7 +2279,7 @@ static int replace_user_table(THD *thd, TABLE *table, const LEX_USER &combo, see also test_if_create_new_users() */ - else if (!password_len && no_auto_create) + else if (!password_len && !combo.plugin.length && no_auto_create) { my_error(ER_PASSWORD_NO_MATCH, MYF(0)); goto end; @@ -1997,6 +2289,15 @@ static int replace_user_table(THD *thd, TABLE *table, const LEX_USER &combo, my_error(ER_CANT_CREATE_USER_WITH_GRANT, MYF(0)); goto end; } + else if (combo.plugin.str[0]) + { + if (!plugin_is_ready(&combo.plugin, MYSQL_AUTHENTICATION_PLUGIN)) + { + my_error(ER_PLUGIN_IS_NOT_LOADED, MYF(0), combo.plugin.str); + goto end; + } + } + old_row_exists = 0; restore_record(table,s->default_values); table->field[0]->store(combo.host.str,combo.host.length, @@ -2010,7 +2311,14 @@ static int replace_user_table(THD *thd, TABLE *table, const LEX_USER &combo, { old_row_exists = 1; store_record(table,record[1]); // Save copy for update - if (combo.password.str) // If password given + /* what == 'N' means revoke */ + if (combo.plugin.length && what != 'N') + { + my_error(ER_GRANT_PLUGIN_USER_EXISTS, MYF(0), + static_cast<int>(combo.user.length), combo.user.str); + goto end; + } + if (combo.password.str) // If password given table->field[2]->store(password, password_len, system_charset_info); else if (!rights && !revoke_grant && lex->ssl_type == SSL_TYPE_NOT_SPECIFIED && @@ -2091,7 +2399,27 @@ static int replace_user_table(THD *thd, TABLE *table, const LEX_USER &combo, (mqh.specified_limits & USER_RESOURCES::USER_CONNECTIONS)) table->field[next_field+3]->store((longlong) mqh.user_conn, TRUE); mqh_used= mqh_used || mqh.questions || mqh.updates || mqh.conn_per_hour; + + next_field+= 4; + if (combo.plugin.str[0]) + { + if (table->s->fields >= 41 && combo.plugin.str[0]) + { + table->field[next_field]->store(combo.plugin.str, combo.plugin.length, + system_charset_info); + table->field[next_field]->set_notnull(); + table->field[next_field + 1]->store(combo.auth.str, combo.auth.length, + system_charset_info); + table->field[next_field + 1]->set_notnull(); + } + else + { + my_error(ER_BAD_FIELD_ERROR, MYF(0), "plugin", "mysql.user"); + goto end; + } + } } + if (old_row_exists) { /* @@ -2135,7 +2463,9 @@ end: lex->x509_issuer, lex->x509_subject, &lex->mqh, - rights); + rights, + &combo.plugin, + &combo.auth); else acl_insert_user(combo.user.str, combo.host.str, password, password_len, lex->ssl_type, @@ -2143,7 +2473,9 @@ end: lex->x509_issuer, lex->x509_subject, &lex->mqh, - rights); + rights, + &combo.plugin, + &combo.auth); } DBUG_RETURN(error); } @@ -2258,6 +2590,170 @@ abort: } +static void +acl_update_proxy_user(ACL_PROXY_USER *new_value, bool is_revoke) +{ + mysql_mutex_assert_owner(&acl_cache->lock); + + DBUG_ENTER("acl_update_proxy_user"); + for (uint i= 0; i < acl_proxy_users.elements; i++) + { + ACL_PROXY_USER *acl_user= + dynamic_element(&acl_proxy_users, i, ACL_PROXY_USER *); + + if (acl_user->pk_equals(new_value)) + { + if (is_revoke) + { + DBUG_PRINT("info", ("delting ACL_PROXY_USER")); + delete_dynamic_element(&acl_proxy_users, i); + } + else + { + DBUG_PRINT("info", ("updating ACL_PROXY_USER")); + acl_user->set_data(new_value); + } + break; + } + } + DBUG_VOID_RETURN; +} + + +static void +acl_insert_proxy_user(ACL_PROXY_USER *new_value) +{ + DBUG_ENTER("acl_insert_proxy_user"); + mysql_mutex_assert_owner(&acl_cache->lock); + (void) push_dynamic(&acl_proxy_users, (uchar *) new_value); + my_qsort((uchar*) dynamic_element(&acl_proxy_users, 0, ACL_PROXY_USER *), + acl_proxy_users.elements, + sizeof(ACL_PROXY_USER), (qsort_cmp) acl_compare); + DBUG_VOID_RETURN; +} + + +static int +replace_proxies_priv_table(THD *thd, TABLE *table, const LEX_USER *user, + const LEX_USER *proxied_user, bool with_grant_arg, + bool revoke_grant) +{ + bool old_row_exists= 0; + int error; + uchar user_key[MAX_KEY_LENGTH]; + ACL_PROXY_USER new_grant; + char grantor[USER_HOST_BUFF_SIZE]; + + DBUG_ENTER("replace_proxies_priv_table"); + + if (!initialized) + { + my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--skip-grant-tables"); + DBUG_RETURN(-1); + } + + /* Check if there is such a user in user table in memory? */ + if (!find_acl_user(user->host.str,user->user.str, FALSE)) + { + my_message(ER_PASSWORD_NO_MATCH, ER(ER_PASSWORD_NO_MATCH), MYF(0)); + DBUG_RETURN(-1); + } + + table->use_all_columns(); + ACL_PROXY_USER::store_pk (table, &user->host, &user->user, + &proxied_user->host, &proxied_user->user); + + key_copy(user_key, table->record[0], table->key_info, + table->key_info->key_length); + + get_grantor(thd, grantor); + + if ((error= table->file->ha_index_init(0, 1))) + { + table->file->print_error(error, MYF(0)); + DBUG_PRINT("info", ("ha_index_init error")); + DBUG_RETURN(-1); + } + + if (table->file->index_read_map(table->record[0], user_key, + HA_WHOLE_KEY, + HA_READ_KEY_EXACT)) + { + DBUG_PRINT ("info", ("Row not found")); + if (revoke_grant) + { // no row, no revoke + my_error(ER_NONEXISTING_GRANT, MYF(0), user->user.str, user->host.str); + goto abort; + } + old_row_exists= 0; + restore_record(table, s->default_values); + ACL_PROXY_USER::store_data_record(table, &user->host, &user->user, + &proxied_user->host, + &proxied_user->user, + with_grant_arg, + grantor); + } + else + { + DBUG_PRINT("info", ("Row found")); + old_row_exists= 1; + store_record(table, record[1]); + } + + if (old_row_exists) + { + /* update old existing row */ + if (!revoke_grant) + { + if ((error= table->file->ha_update_row(table->record[1], + table->record[0])) && + error != HA_ERR_RECORD_IS_THE_SAME) + goto table_error; /* purecov: inspected */ + } + else + { + if ((error= table->file->ha_delete_row(table->record[1]))) + goto table_error; /* purecov: inspected */ + } + } + else if ((error= table->file->ha_write_row(table->record[0]))) + { + DBUG_PRINT("info", ("error inserting the row")); + if (table->file->is_fatal_error(error, HA_CHECK_DUP_KEY)) + goto table_error; /* purecov: inspected */ + } + + acl_cache->clear(1); // Clear privilege cache + if (old_row_exists) + { + new_grant.init(user->host.str, user->user.str, + proxied_user->host.str, proxied_user->user.str, + with_grant_arg); + acl_update_proxy_user(&new_grant, revoke_grant); + } + else + { + new_grant.init(&mem, user->host.str, user->user.str, + proxied_user->host.str, proxied_user->user.str, + with_grant_arg); + acl_insert_proxy_user(&new_grant); + } + + table->file->ha_index_end(); + DBUG_RETURN(0); + + /* This could only happen if the grant tables got corrupted */ +table_error: + DBUG_PRINT("info", ("table error")); + table->file->print_error(error, MYF(0)); /* purecov: inspected */ + +abort: + DBUG_PRINT("info", ("aborting replace_proxies_priv_table")); + table->file->ha_index_end(); + DBUG_RETURN(-1); +} + + class GRANT_COLUMN :public Sql_alloc { public: @@ -2348,8 +2844,8 @@ GRANT_TABLE::GRANT_TABLE(const char *h, const char *d,const char *u, const char *t, ulong p, ulong c) :GRANT_NAME(h,d,u,t,p, FALSE), cols(c) { - (void) hash_init2(&hash_columns,4,system_charset_info, - 0,0,0, (hash_get_key) get_key_column,0,0); + (void) my_hash_init2(&hash_columns,4,system_charset_info, + 0,0,0, (my_hash_get_key) get_key_column,0,0); } @@ -2392,15 +2888,15 @@ GRANT_TABLE::GRANT_TABLE(TABLE *form, TABLE *col_privs) if (!db || !tname) { /* Wrong table row; Ignore it */ - hash_clear(&hash_columns); /* allow for destruction */ + my_hash_clear(&hash_columns); /* allow for destruction */ cols= 0; return; } cols= (ulong) form->field[7]->val_int(); cols = fix_rights_for_column(cols); - (void) hash_init2(&hash_columns,4,system_charset_info, - 0,0,0, (hash_get_key) get_key_column,0,0); + (void) my_hash_init2(&hash_columns,4,system_charset_info, + 0,0,0, (my_hash_get_key) get_key_column,0,0); if (cols) { uint key_prefix_len; @@ -2420,7 +2916,12 @@ GRANT_TABLE::GRANT_TABLE(TABLE *form, TABLE *col_privs) key_copy(key, col_privs->record[0], col_privs->key_info, key_prefix_len); col_privs->field[4]->store("",0, &my_charset_latin1); - col_privs->file->ha_index_init(0, 1); + if (col_privs->file->ha_index_init(0, 1)) + { + cols= 0; + return; + } + if (col_privs->file->index_read_map(col_privs->record[0], (uchar*) key, (key_part_map)15, HA_READ_KEY_EXACT)) { @@ -2457,7 +2958,7 @@ GRANT_TABLE::GRANT_TABLE(TABLE *form, TABLE *col_privs) GRANT_TABLE::~GRANT_TABLE() { - hash_free(&hash_columns); + my_hash_free(&hash_columns); } @@ -2471,7 +2972,7 @@ static uchar* get_grant_table(GRANT_NAME *buff, size_t *length, void free_grant_table(GRANT_TABLE *grant_table) { - hash_free(&grant_table->hash_columns); + my_hash_free(&grant_table->hash_columns); } @@ -2492,11 +2993,11 @@ static GRANT_NAME *name_hash_search(HASH *name_hash, len = (uint) (strmov(name_ptr, tname) - helping) + 1; if (name_tolower) my_casedn_str(files_charset_info, name_ptr); - for (grant_name= (GRANT_NAME*) hash_first(name_hash, (uchar*) helping, - len, &state); + for (grant_name= (GRANT_NAME*) my_hash_first(name_hash, (uchar*) helping, + len, &state); grant_name ; - grant_name= (GRANT_NAME*) hash_next(name_hash,(uchar*) helping, - len, &state)) + grant_name= (GRANT_NAME*) my_hash_next(name_hash,(uchar*) helping, + len, &state)) { if (exact) { @@ -2540,7 +3041,8 @@ table_hash_search(const char *host, const char *ip, const char *db, inline GRANT_COLUMN * column_hash_search(GRANT_TABLE *t, const char *cname, uint length) { - return (GRANT_COLUMN*) hash_search(&t->hash_columns, (uchar*) cname,length); + return (GRANT_COLUMN*) my_hash_search(&t->hash_columns, + (uchar*) cname, length); } @@ -2550,7 +3052,7 @@ static int replace_column_table(GRANT_TABLE *g_t, const char *db, const char *table_name, ulong rights, bool revoke_grant) { - int error=0,result=0; + int result=0; uchar key[MAX_KEY_LENGTH]; uint key_prefix_length; KEY_PART_INFO *key_part= table->key_info->key_part; @@ -2577,7 +3079,13 @@ static int replace_column_table(GRANT_TABLE *g_t, List_iterator <LEX_COLUMN> iter(columns); class LEX_COLUMN *column; - table->file->ha_index_init(0, 1); + int error= table->file->ha_index_init(0, 1); + if (error) + { + table->file->print_error(error, MYF(0)); + DBUG_RETURN(-1); + } + while ((column= iter++)) { ulong privileges= column->rights; @@ -2724,7 +3232,7 @@ static int replace_column_table(GRANT_TABLE *g_t, goto end; /* purecov: deadcode */ } if (grant_column) - hash_delete(&g_t->hash_columns,(uchar*) grant_column); + my_hash_delete(&g_t->hash_columns,(uchar*) grant_column); } } } while (!table->file->index_next(table->record[0]) && @@ -2862,7 +3370,7 @@ static int replace_table_table(THD *thd, GRANT_TABLE *grant_table, } else { - hash_delete(&column_priv_hash,(uchar*) grant_table); + my_hash_delete(&column_priv_hash,(uchar*) grant_table); } DBUG_RETURN(0); @@ -2981,7 +3489,8 @@ static int replace_routine_table(THD *thd, GRANT_NAME *grant_name, } else { - hash_delete(is_proc ? &proc_priv_hash : &func_priv_hash,(uchar*) grant_name); + my_hash_delete(is_proc ? &proc_priv_hash : &func_priv_hash,(uchar*) + grant_name); } DBUG_RETURN(0); @@ -3043,7 +3552,7 @@ int mysql_table_grant(THD *thd, TABLE_LIST *table_list, class LEX_COLUMN *column; List_iterator <LEX_COLUMN> column_iter(columns); - if (open_and_lock_tables(thd, table_list)) + if (open_normal_and_derived_tables(thd, table_list, 0)) DBUG_RETURN(TRUE); while ((column = column_iter++)) @@ -3065,7 +3574,7 @@ int mysql_table_grant(THD *thd, TABLE_LIST *table_list, DBUG_RETURN(TRUE); column_priv|= column->rights; } - close_thread_tables(thd); + close_mysql_tables(thd); } else { @@ -3097,27 +3606,26 @@ int mysql_table_grant(THD *thd, TABLE_LIST *table_list, /* open the mysql.tables_priv and mysql.columns_priv tables */ - bzero((char*) &tables,sizeof(tables)); - tables[0].alias=tables[0].table_name= (char*) "user"; - tables[1].alias=tables[1].table_name= (char*) "tables_priv"; - tables[2].alias=tables[2].table_name= (char*) "columns_priv"; + tables[0].init_one_table(C_STRING_WITH_LEN("mysql"), + C_STRING_WITH_LEN("user"), "user", TL_WRITE); + tables[1].init_one_table(C_STRING_WITH_LEN("mysql"), + C_STRING_WITH_LEN("tables_priv"), + "tables_priv", TL_WRITE); + tables[2].init_one_table(C_STRING_WITH_LEN("mysql"), + C_STRING_WITH_LEN("columns_priv"), + "columns_priv", TL_WRITE); tables[0].next_local= tables[0].next_global= tables+1; /* Don't open column table if we don't need it ! */ - tables[1].next_local= - tables[1].next_global= ((column_priv || - (revoke_grant && - ((rights & COL_ACLS) || columns.elements))) - ? tables+2 : 0); - tables[0].lock_type=tables[1].lock_type=tables[2].lock_type=TL_WRITE; - tables[0].db=tables[1].db=tables[2].db=(char*) "mysql"; + if (column_priv || (revoke_grant && ((rights & COL_ACLS) || columns.elements))) + tables[1].next_local= tables[1].next_global= tables+2; /* This statement will be replicated as a statement, even when using row-based replication. The flag will be reset at the end of the statement. */ - save_binlog_row_based= thd->current_stmt_binlog_row_based; - thd->clear_current_stmt_binlog_row_based(); + if ((save_binlog_row_based= thd->is_current_stmt_binlog_format_row())) + thd->clear_current_stmt_binlog_format_row(); #ifdef HAVE_REPLICATION /* @@ -3134,7 +3642,9 @@ int mysql_table_grant(THD *thd, TABLE_LIST *table_list, if (!(thd->spcont || rpl_filter->tables_ok(0, tables))) { /* Restore the state of binlog format */ - thd->current_stmt_binlog_row_based= save_binlog_row_based; + DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); + if (save_binlog_row_based) + thd->set_current_stmt_binlog_format_row(); DBUG_RETURN(FALSE); } } @@ -3146,19 +3656,27 @@ int mysql_table_grant(THD *thd, TABLE_LIST *table_list, */ Query_tables_list backup; thd->lex->reset_n_backup_query_tables_list(&backup); - if (simple_open_n_lock_tables(thd,tables)) + /* + Restore Query_tables_list::sql_command value, which was reset + above, as the code writing query to the binary log assumes that + this value corresponds to the statement being executed. + */ + thd->lex->sql_command= backup.sql_command; + if (open_and_lock_tables(thd, tables, FALSE, MYSQL_LOCK_IGNORE_TIMEOUT)) { // Should never happen - close_thread_tables(thd); /* purecov: deadcode */ /* Restore the state of binlog format */ - thd->current_stmt_binlog_row_based= save_binlog_row_based; + DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); + thd->lex->restore_backup_query_tables_list(&backup); + if (save_binlog_row_based) + thd->set_current_stmt_binlog_format_row(); DBUG_RETURN(TRUE); /* purecov: deadcode */ } if (!revoke_grant) create_new_users= test_if_create_new_users(thd); bool result= FALSE; - rw_wrlock(&LOCK_grant); - pthread_mutex_lock(&acl_cache->lock); + mysql_rwlock_wrlock(&LOCK_grant); + mysql_mutex_lock(&acl_cache->lock); MEM_ROOT *old_root= thd->mem_root; thd->mem_root= &memex; grant_version++; @@ -3230,8 +3748,8 @@ int mysql_table_grant(THD *thd, TABLE_LIST *table_list, column_priv= 0; for (uint idx=0 ; idx < grant_table->hash_columns.records ; idx++) { - grant_column= (GRANT_COLUMN*) hash_element(&grant_table->hash_columns, - idx); + grant_column= (GRANT_COLUMN*) + my_hash_element(&grant_table->hash_columns, idx); grant_column->rights&= ~rights; // Fix other columns column_priv|= grant_column->rights; } @@ -3263,14 +3781,14 @@ int mysql_table_grant(THD *thd, TABLE_LIST *table_list, } } thd->mem_root= old_root; - pthread_mutex_unlock(&acl_cache->lock); + mysql_mutex_unlock(&acl_cache->lock); if (!result) /* success */ { result= write_bin_log(thd, TRUE, thd->query(), thd->query_length()); } - rw_unlock(&LOCK_grant); + mysql_rwlock_unlock(&LOCK_grant); if (!result) /* success */ my_ok(thd); @@ -3278,7 +3796,9 @@ int mysql_table_grant(THD *thd, TABLE_LIST *table_list, /* Tables are automatically closed */ thd->lex->restore_backup_query_tables_list(&backup); /* Restore the state of binlog format */ - thd->current_stmt_binlog_row_based= save_binlog_row_based; + DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); + if (save_binlog_row_based) + thd->set_current_stmt_binlog_format_row(); DBUG_RETURN(result); } @@ -3331,20 +3851,19 @@ bool mysql_routine_grant(THD *thd, TABLE_LIST *table_list, bool is_proc, /* open the mysql.user and mysql.procs_priv tables */ - bzero((char*) &tables,sizeof(tables)); - tables[0].alias=tables[0].table_name= (char*) "user"; - tables[1].alias=tables[1].table_name= (char*) "procs_priv"; + tables[0].init_one_table(C_STRING_WITH_LEN("mysql"), + C_STRING_WITH_LEN("user"), "user", TL_WRITE); + tables[1].init_one_table(C_STRING_WITH_LEN("mysql"), + C_STRING_WITH_LEN("procs_priv"), "procs_priv", TL_WRITE); tables[0].next_local= tables[0].next_global= tables+1; - tables[0].lock_type=tables[1].lock_type=TL_WRITE; - tables[0].db=tables[1].db=(char*) "mysql"; /* This statement will be replicated as a statement, even when using row-based replication. The flag will be reset at the end of the statement. */ - save_binlog_row_based= thd->current_stmt_binlog_row_based; - thd->clear_current_stmt_binlog_row_based(); + if ((save_binlog_row_based= thd->is_current_stmt_binlog_format_row())) + thd->clear_current_stmt_binlog_format_row(); #ifdef HAVE_REPLICATION /* @@ -3361,24 +3880,27 @@ bool mysql_routine_grant(THD *thd, TABLE_LIST *table_list, bool is_proc, if (!(thd->spcont || rpl_filter->tables_ok(0, tables))) { /* Restore the state of binlog format */ - thd->current_stmt_binlog_row_based= save_binlog_row_based; + DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); + if (save_binlog_row_based) + thd->set_current_stmt_binlog_format_row(); DBUG_RETURN(FALSE); } } #endif - if (simple_open_n_lock_tables(thd,tables)) + if (open_and_lock_tables(thd, tables, FALSE, MYSQL_LOCK_IGNORE_TIMEOUT)) { // Should never happen - close_thread_tables(thd); /* Restore the state of binlog format */ - thd->current_stmt_binlog_row_based= save_binlog_row_based; + DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); + if (save_binlog_row_based) + thd->set_current_stmt_binlog_format_row(); DBUG_RETURN(TRUE); } if (!revoke_grant) create_new_users= test_if_create_new_users(thd); - rw_wrlock(&LOCK_grant); - pthread_mutex_lock(&acl_cache->lock); + mysql_rwlock_wrlock(&LOCK_grant); + mysql_mutex_lock(&acl_cache->lock); MEM_ROOT *old_root= thd->mem_root; thd->mem_root= &memex; @@ -3439,7 +3961,7 @@ bool mysql_routine_grant(THD *thd, TABLE_LIST *table_list, bool is_proc, } } thd->mem_root= old_root; - pthread_mutex_unlock(&acl_cache->lock); + mysql_mutex_unlock(&acl_cache->lock); if (write_to_binlog) { @@ -3447,9 +3969,11 @@ bool mysql_routine_grant(THD *thd, TABLE_LIST *table_list, bool is_proc, result= TRUE; } - rw_unlock(&LOCK_grant); + mysql_rwlock_unlock(&LOCK_grant); /* Restore the state of binlog format */ - thd->current_stmt_binlog_row_based= save_binlog_row_based; + DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); + if (save_binlog_row_based) + thd->set_current_stmt_binlog_format_row(); /* Tables are automatically closed */ DBUG_RETURN(result); @@ -3457,10 +3981,10 @@ bool mysql_routine_grant(THD *thd, TABLE_LIST *table_list, bool is_proc, bool mysql_grant(THD *thd, const char *db, List <LEX_USER> &list, - ulong rights, bool revoke_grant) + ulong rights, bool revoke_grant, bool is_proxy) { List_iterator <LEX_USER> str_list (list); - LEX_USER *Str, *tmp_Str; + LEX_USER *Str, *tmp_Str, *proxied_user= NULL; char tmp_db[NAME_LEN+1]; bool create_new_users=0; TABLE_LIST tables[2]; @@ -3480,21 +4004,35 @@ bool mysql_grant(THD *thd, const char *db, List <LEX_USER> &list, db=tmp_db; } - /* open the mysql.user and mysql.db tables */ - bzero((char*) &tables,sizeof(tables)); - tables[0].alias=tables[0].table_name=(char*) "user"; - tables[1].alias=tables[1].table_name=(char*) "db"; + if (is_proxy) + { + DBUG_ASSERT(!db); + proxied_user= str_list++; + } + + /* open the mysql.user and mysql.db or mysql.proxies_priv tables */ + tables[0].init_one_table(C_STRING_WITH_LEN("mysql"), + C_STRING_WITH_LEN("user"), "user", TL_WRITE); + if (is_proxy) + + tables[1].init_one_table(C_STRING_WITH_LEN("mysql"), + C_STRING_WITH_LEN("proxies_priv"), + "proxies_priv", + TL_WRITE); + else + tables[1].init_one_table(C_STRING_WITH_LEN("mysql"), + C_STRING_WITH_LEN("db"), + "db", + TL_WRITE); tables[0].next_local= tables[0].next_global= tables+1; - tables[0].lock_type=tables[1].lock_type=TL_WRITE; - tables[0].db=tables[1].db=(char*) "mysql"; /* This statement will be replicated as a statement, even when using row-based replication. The flag will be reset at the end of the statement. */ - save_binlog_row_based= thd->current_stmt_binlog_row_based; - thd->clear_current_stmt_binlog_row_based(); + if ((save_binlog_row_based= thd->is_current_stmt_binlog_format_row())) + thd->clear_current_stmt_binlog_format_row(); #ifdef HAVE_REPLICATION /* @@ -3511,17 +4049,20 @@ bool mysql_grant(THD *thd, const char *db, List <LEX_USER> &list, if (!(thd->spcont || rpl_filter->tables_ok(0, tables))) { /* Restore the state of binlog format */ - thd->current_stmt_binlog_row_based= save_binlog_row_based; + DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); + if (save_binlog_row_based) + thd->set_current_stmt_binlog_format_row(); DBUG_RETURN(FALSE); } } #endif - if (simple_open_n_lock_tables(thd,tables)) + if (open_and_lock_tables(thd, tables, FALSE, MYSQL_LOCK_IGNORE_TIMEOUT)) { // This should never happen - close_thread_tables(thd); /* purecov: deadcode */ /* Restore the state of binlog format */ - thd->current_stmt_binlog_row_based= save_binlog_row_based; + DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); + if (save_binlog_row_based) + thd->set_current_stmt_binlog_format_row(); DBUG_RETURN(TRUE); /* purecov: deadcode */ } @@ -3529,8 +4070,8 @@ bool mysql_grant(THD *thd, const char *db, List <LEX_USER> &list, create_new_users= test_if_create_new_users(thd); /* go through users in user_list */ - rw_wrlock(&LOCK_grant); - VOID(pthread_mutex_lock(&acl_cache->lock)); + mysql_rwlock_wrlock(&LOCK_grant); + mysql_mutex_lock(&acl_cache->lock); grant_version++; int result=0; @@ -3568,21 +4109,29 @@ bool mysql_grant(THD *thd, const char *db, List <LEX_USER> &list, result= -1; } } + else if (is_proxy) + { + if (replace_proxies_priv_table (thd, tables[1].table, Str, proxied_user, + rights & GRANT_ACL ? TRUE : FALSE, + revoke_grant)) + result= -1; + } } - VOID(pthread_mutex_unlock(&acl_cache->lock)); + mysql_mutex_unlock(&acl_cache->lock); if (!result) { result= write_bin_log(thd, TRUE, thd->query(), thd->query_length()); } - rw_unlock(&LOCK_grant); - close_thread_tables(thd); + mysql_rwlock_unlock(&LOCK_grant); if (!result) my_ok(thd); /* Restore the state of binlog format */ - thd->current_stmt_binlog_row_based= save_binlog_row_based; + DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); + if (save_binlog_row_based) + thd->set_current_stmt_binlog_format_row(); DBUG_RETURN(result); } @@ -3593,9 +4142,9 @@ bool mysql_grant(THD *thd, const char *db, List <LEX_USER> &list, void grant_free(void) { DBUG_ENTER("grant_free"); - hash_free(&column_priv_hash); - hash_free(&proc_priv_hash); - hash_free(&func_priv_hash); + my_hash_free(&column_priv_hash); + my_hash_free(&proc_priv_hash); + my_hash_free(&func_priv_hash); free_root(&memex,MYF(0)); DBUG_VOID_RETURN; } @@ -3620,7 +4169,6 @@ my_bool grant_init() DBUG_RETURN(1); /* purecov: deadcode */ thd->thread_stack= (char*) &thd; thd->store_globals(); - lex_start(thd); return_val= grant_reload(thd); delete thd; /* Remember that we don't have a THD */ @@ -3652,13 +4200,16 @@ static my_bool grant_load_procs_priv(TABLE *p_table) MEM_ROOT **save_mem_root_ptr= my_pthread_getspecific_ptr(MEM_ROOT**, THR_MALLOC); DBUG_ENTER("grant_load_procs_priv"); - (void) hash_init(&proc_priv_hash, &my_charset_utf8_bin, - 0,0,0, (hash_get_key) get_grant_table, - 0,0); - (void) hash_init(&func_priv_hash, &my_charset_utf8_bin, - 0,0,0, (hash_get_key) get_grant_table, - 0,0); - p_table->file->ha_index_init(0, 1); + (void) my_hash_init(&proc_priv_hash, &my_charset_utf8_bin, + 0,0,0, (my_hash_get_key) get_grant_table, + 0,0); + (void) my_hash_init(&func_priv_hash, &my_charset_utf8_bin, + 0,0,0, (my_hash_get_key) get_grant_table, + 0,0); + + if (p_table->file->ha_index_init(0, 1)) + DBUG_RETURN(TRUE); + p_table->use_all_columns(); if (!p_table->file->index_first(p_table->record[0])) @@ -3753,13 +4304,16 @@ static my_bool grant_load(THD *thd, TABLE_LIST *tables) thd->variables.sql_mode&= ~MODE_PAD_CHAR_TO_FULL_LENGTH; - (void) hash_init(&column_priv_hash, &my_charset_utf8_bin, - 0,0,0, (hash_get_key) get_grant_table, - (hash_free_key) free_grant_table,0); + (void) my_hash_init(&column_priv_hash, &my_charset_utf8_bin, + 0,0,0, (my_hash_get_key) get_grant_table, + (my_hash_free_key) free_grant_table,0); t_table = tables[0].table; c_table = tables[1].table; - t_table->file->ha_index_init(0, 1); + + if (t_table->file->ha_index_init(0, 1)) + goto end_index_init; + t_table->use_all_columns(); c_table->use_all_columns(); @@ -3804,9 +4358,10 @@ static my_bool grant_load(THD *thd, TABLE_LIST *tables) return_val=0; // Return ok end_unlock: - thd->variables.sql_mode= old_sql_mode; t_table->file->ha_index_end(); my_pthread_setspecific_ptr(THR_MALLOC, save_mem_root_ptr); +end_index_init: + thd->variables.sql_mode= old_sql_mode; DBUG_RETURN(return_val); } @@ -3831,19 +4386,15 @@ static my_bool grant_reload_procs_priv(THD *thd) my_bool return_val= FALSE; DBUG_ENTER("grant_reload_procs_priv"); - bzero((char*) &table, sizeof(table)); - table.alias= table.table_name= (char*) "procs_priv"; - table.db= (char *) "mysql"; - table.lock_type= TL_READ; - table.skip_temporary= 1; + table.init_one_table("mysql", 5, "procs_priv", + strlen("procs_priv"), "procs_priv", + TL_READ); + table.open_type= OT_BASE_ONLY; - if (simple_open_n_lock_tables(thd, &table)) - { - close_thread_tables(thd); + if (open_and_lock_tables(thd, &table, FALSE, MYSQL_LOCK_IGNORE_TIMEOUT)) DBUG_RETURN(TRUE); - } - rw_wrlock(&LOCK_grant); + mysql_rwlock_wrlock(&LOCK_grant); /* Save a copy of the current hash if we need to undo the grant load */ old_proc_priv_hash= proc_priv_hash; old_func_priv_hash= func_priv_hash; @@ -3858,12 +4409,12 @@ static my_bool grant_reload_procs_priv(THD *thd) } else { - hash_free(&old_proc_priv_hash); - hash_free(&old_func_priv_hash); + my_hash_free(&old_proc_priv_hash); + my_hash_free(&old_func_priv_hash); } - rw_unlock(&LOCK_grant); + mysql_rwlock_unlock(&LOCK_grant); - close_thread_tables(thd); + close_mysql_tables(thd); DBUG_RETURN(return_val); } @@ -3895,21 +4446,23 @@ my_bool grant_reload(THD *thd) if (!initialized) DBUG_RETURN(0); - bzero((char*) tables, sizeof(tables)); - tables[0].alias= tables[0].table_name= (char*) "tables_priv"; - tables[1].alias= tables[1].table_name= (char*) "columns_priv"; - tables[0].db= tables[1].db= (char *) "mysql"; + tables[0].init_one_table(C_STRING_WITH_LEN("mysql"), + C_STRING_WITH_LEN("tables_priv"), + "tables_priv", TL_READ); + tables[1].init_one_table(C_STRING_WITH_LEN("mysql"), + C_STRING_WITH_LEN("columns_priv"), + "columns_priv", TL_READ); tables[0].next_local= tables[0].next_global= tables+1; - tables[0].lock_type= tables[1].lock_type= TL_READ; - tables[0].skip_temporary= tables[1].skip_temporary= TRUE; + tables[0].open_type= tables[1].open_type= OT_BASE_ONLY; + /* To avoid deadlocks we should obtain table locks before obtaining LOCK_grant rwlock. */ - if (simple_open_n_lock_tables(thd, tables)) + if (open_and_lock_tables(thd, tables, FALSE, MYSQL_LOCK_IGNORE_TIMEOUT)) goto end; - rw_wrlock(&LOCK_grant); + mysql_rwlock_wrlock(&LOCK_grant); old_column_priv_hash= column_priv_hash; /* @@ -3928,11 +4481,11 @@ my_bool grant_reload(THD *thd) } else { - hash_free(&old_column_priv_hash); + my_hash_free(&old_column_priv_hash); free_root(&old_mem,MYF(0)); } - rw_unlock(&LOCK_grant); - close_thread_tables(thd); + mysql_rwlock_unlock(&LOCK_grant); + close_mysql_tables(thd); /* It is OK failing to load procs_priv table because we may be @@ -3941,45 +4494,59 @@ my_bool grant_reload(THD *thd) if (grant_reload_procs_priv(thd)) return_val= 1; - rw_wrlock(&LOCK_grant); + mysql_rwlock_wrlock(&LOCK_grant); grant_version++; - rw_unlock(&LOCK_grant); + mysql_rwlock_unlock(&LOCK_grant); end: DBUG_RETURN(return_val); } -/**************************************************************************** - Check table level grants - SYNOPSIS - bool check_grant() - thd Thread handler - want_access Bits of privileges user needs to have - tables List of tables to check. The user should have 'want_access' - to all tables in list. - show_table <> 0 if we are in show table. In this case it's enough to have - any privilege for the table - number Check at most this number of tables. - no_errors If 0 then we write an error. The error is sent directly to - the client +/** + @brief Check table level grants - RETURN - 0 ok - 1 Error: User did not have the requested privileges + @param thd Thread handler + @param want_access Bits of privileges user needs to have. + @param tables List of tables to check. The user should have + 'want_access' to all tables in list. + @param any_combination_will_do TRUE if it's enough to have any privilege for + any combination of the table columns. + @param number Check at most this number of tables. + @param no_errors TRUE if no error should be sent directly to the client. + + If table->grant.want_privilege != 0 then the requested privileges where + in the set of COL_ACLS but access was not granted on the table level. As + a consequence an extra check of column privileges is required. - NOTE - This functions assumes that either number of tables to be inspected + Specifically if this function returns FALSE the user has some kind of + privilege on a combination of columns in each table. + + This function is usually preceeded by check_access which establish the + User-, Db- and Host access rights. + + @see check_access + @see check_table_access + + @note This functions assumes that either number of tables to be inspected by it is limited explicitly (i.e. is is not UINT_MAX) or table list used and thd->lex->query_tables_own_last value correspond to each other (the latter should be either 0 or point to next_global member of one of elements of this table list). -****************************************************************************/ + + @return Access status + @retval FALSE Access granted; But column privileges might need to be + checked. + @retval TRUE The user did not have the requested privileges on any of the + tables. + +*/ bool check_grant(THD *thd, ulong want_access, TABLE_LIST *tables, - uint show_table, uint number, bool no_errors) + bool any_combination_will_do, uint number, bool no_errors) { - TABLE_LIST *table, *first_not_own_table= thd->lex->first_not_own_table(); + TABLE_LIST *tl; + TABLE_LIST *first_not_own_table= thd->lex->first_not_own_table(); Security_context *sctx= thd->security_ctx; uint i; ulong orig_want_access= want_access; @@ -3996,76 +4563,111 @@ bool check_grant(THD *thd, ulong want_access, TABLE_LIST *tables, the given table list refers to the list for prelocking (contains tables of other queries). For simple queries first_not_own_table is 0. */ - for (i= 0, table= tables; - i < number && table != first_not_own_table; - table= table->next_global, i++) + for (i= 0, tl= tables; + i < number && tl != first_not_own_table; + tl= tl->next_global, i++) { - /* Remove SHOW_VIEW_ACL, because it will be checked during making view */ - table->grant.orig_want_privilege= (want_access & ~SHOW_VIEW_ACL); + /* + Save a copy of the privileges without the SHOW_VIEW_ACL attribute. + It will be checked during making view. + */ + tl->grant.orig_want_privilege= (want_access & ~SHOW_VIEW_ACL); } - rw_rdlock(&LOCK_grant); - for (table= tables; - table && number-- && table != first_not_own_table; - table= table->next_global) + mysql_rwlock_rdlock(&LOCK_grant); + for (tl= tables; + tl && number-- && tl != first_not_own_table; + tl= tl->next_global) { - GRANT_TABLE *grant_table; - sctx = test(table->security_ctx) ? - table->security_ctx : thd->security_ctx; + sctx = test(tl->security_ctx) ? tl->security_ctx : thd->security_ctx; + + const ACL_internal_table_access *access= + get_cached_table_access(&tl->grant.m_internal, + tl->get_db_name(), + tl->get_table_name()); + + if (access) + { + switch(access->check(orig_want_access, &tl->grant.privilege)) + { + case ACL_INTERNAL_ACCESS_GRANTED: + /* + Currently, + - the information_schema does not subclass ACL_internal_table_access, + there are no per table privilege checks for I_S, + - the performance schema does use per tables checks, but at most + returns 'CHECK_GRANT', and never 'ACCESS_GRANTED'. + so this branch is not used. + */ + DBUG_ASSERT(0); + case ACL_INTERNAL_ACCESS_DENIED: + goto err; + case ACL_INTERNAL_ACCESS_CHECK_GRANT: + break; + } + } want_access= orig_want_access; want_access&= ~sctx->master_access; if (!want_access) continue; // ok - if (!(~table->grant.privilege & want_access) || - table->is_anonymous_derived_table() || table->schema_table) + if (!(~tl->grant.privilege & want_access) || + tl->is_anonymous_derived_table() || tl->schema_table) { /* - It is subquery in the FROM clause. VIEW set table->derived after + It is subquery in the FROM clause. VIEW set tl->derived after table opening, but this function always called before table opening. */ - if (!table->referencing_view) + if (!tl->referencing_view) { /* If it's a temporary table created for a subquery in the FROM clause, or an INFORMATION_SCHEMA table, drop the request for a privilege. */ - table->grant.want_privilege= 0; + tl->grant.want_privilege= 0; } continue; } - if (!(grant_table= table_hash_search(sctx->host, sctx->ip, - table->get_db_name(), sctx->priv_user, - table->get_table_name(), FALSE))) + GRANT_TABLE *grant_table= table_hash_search(sctx->host, sctx->ip, + tl->get_db_name(), + sctx->priv_user, + tl->get_table_name(), + FALSE); + + if (!grant_table) { - want_access &= ~table->grant.privilege; + want_access &= ~tl->grant.privilege; goto err; // No grants } - if (show_table) - continue; // We have some priv on this - table->grant.grant_table=grant_table; // Remember for column test - table->grant.version=grant_version; - table->grant.privilege|= grant_table->privs; - table->grant.want_privilege= ((want_access & COL_ACLS) - & ~table->grant.privilege); + /* + For SHOW COLUMNS, SHOW INDEX it is enough to have some + privileges on any column combination on the table. + */ + if (any_combination_will_do) + continue; + + tl->grant.grant_table= grant_table; // Remember for column test + tl->grant.version= grant_version; + tl->grant.privilege|= grant_table->privs; + tl->grant.want_privilege= ((want_access & COL_ACLS) & ~tl->grant.privilege); - if (!(~table->grant.privilege & want_access)) + if (!(~tl->grant.privilege & want_access)) continue; - if (want_access & ~(grant_table->cols | table->grant.privilege)) + if (want_access & ~(grant_table->cols | tl->grant.privilege)) { - want_access &= ~(grant_table->cols | table->grant.privilege); + want_access &= ~(grant_table->cols | tl->grant.privilege); goto err; // impossible } } - rw_unlock(&LOCK_grant); - DBUG_RETURN(0); + mysql_rwlock_unlock(&LOCK_grant); + DBUG_RETURN(FALSE); err: - rw_unlock(&LOCK_grant); + mysql_rwlock_unlock(&LOCK_grant); if (!no_errors) // Not a silent skip of table { char command[128]; @@ -4074,9 +4676,9 @@ err: command, sctx->priv_user, sctx->host_or_ip, - table ? table->get_table_name() : "unknown"); + tl ? tl->get_table_name() : "unknown"); } - DBUG_RETURN(1); + DBUG_RETURN(TRUE); } @@ -4111,7 +4713,7 @@ bool check_grant_column(THD *thd, GRANT_INFO *grant, if (!want_access) DBUG_RETURN(0); // Already checked - rw_rdlock(&LOCK_grant); + mysql_rwlock_rdlock(&LOCK_grant); /* reload table if someone has modified any grants */ @@ -4129,12 +4731,12 @@ bool check_grant_column(THD *thd, GRANT_INFO *grant, grant_column=column_hash_search(grant_table, name, length); if (grant_column && !(~grant_column->rights & want_access)) { - rw_unlock(&LOCK_grant); + mysql_rwlock_unlock(&LOCK_grant); DBUG_RETURN(0); } err: - rw_unlock(&LOCK_grant); + mysql_rwlock_unlock(&LOCK_grant); char command[128]; get_privilege_desc(command, sizeof(command), want_access); my_error(ER_COLUMNACCESS_DENIED_ERROR, MYF(0), @@ -4249,7 +4851,7 @@ bool check_grant_all_columns(THD *thd, ulong want_access_arg, */ bool using_column_privileges= FALSE; - rw_rdlock(&LOCK_grant); + mysql_rwlock_rdlock(&LOCK_grant); for (; !fields->end_of_fields(); fields->next()) { @@ -4290,11 +4892,11 @@ bool check_grant_all_columns(THD *thd, ulong want_access_arg, goto err; } } - rw_unlock(&LOCK_grant); + mysql_rwlock_unlock(&LOCK_grant); return 0; err: - rw_unlock(&LOCK_grant); + mysql_rwlock_unlock(&LOCK_grant); char command[128]; get_privilege_desc(command, sizeof(command), want_access); @@ -4323,7 +4925,7 @@ static bool check_grant_db_routine(THD *thd, const char *db, HASH *hash) for (uint idx= 0; idx < hash->records; ++idx) { - GRANT_NAME *item= (GRANT_NAME*) hash_element(hash, idx); + GRANT_NAME *item= (GRANT_NAME*) my_hash_element(hash, idx); if (strcmp(item->user, sctx->priv_user) == 0 && strcmp(item->db, db) == 0 && @@ -4362,12 +4964,13 @@ bool check_grant_db(THD *thd,const char *db) len= (uint) (strmov(strmov(helping, sctx->priv_user) + 1, db) - helping) + 1; - rw_rdlock(&LOCK_grant); + mysql_rwlock_rdlock(&LOCK_grant); for (uint idx=0 ; idx < column_priv_hash.records ; idx++) { - GRANT_TABLE *grant_table= (GRANT_TABLE*) hash_element(&column_priv_hash, - idx); + GRANT_TABLE *grant_table= (GRANT_TABLE*) + my_hash_element(&column_priv_hash, + idx); if (len < grant_table->key_length && !memcmp(grant_table->hash_key,helping,len) && compare_hostname(&grant_table->host, sctx->host, sctx->ip)) @@ -4381,7 +4984,7 @@ bool check_grant_db(THD *thd,const char *db) error= check_grant_db_routine(thd, db, &proc_priv_hash) && check_grant_db_routine(thd, db, &func_priv_hash); - rw_unlock(&LOCK_grant); + mysql_rwlock_unlock(&LOCK_grant); return error; } @@ -4417,7 +5020,7 @@ bool check_grant_routine(THD *thd, ulong want_access, if (!want_access) DBUG_RETURN(0); // ok - rw_rdlock(&LOCK_grant); + mysql_rwlock_rdlock(&LOCK_grant); for (table= procs; table; table= table->next_global) { GRANT_NAME *grant_proc; @@ -4431,10 +5034,10 @@ bool check_grant_routine(THD *thd, ulong want_access, goto err; } } - rw_unlock(&LOCK_grant); + mysql_rwlock_unlock(&LOCK_grant); DBUG_RETURN(0); err: - rw_unlock(&LOCK_grant); + mysql_rwlock_unlock(&LOCK_grant); if (!no_errors) { char buff[1024]; @@ -4475,13 +5078,13 @@ bool check_routine_level_acl(THD *thd, const char *db, const char *name, bool no_routine_acl= 1; GRANT_NAME *grant_proc; Security_context *sctx= thd->security_ctx; - rw_rdlock(&LOCK_grant); + mysql_rwlock_rdlock(&LOCK_grant); if ((grant_proc= routine_hash_search(sctx->priv_host, sctx->ip, db, sctx->priv_user, name, is_proc, 0))) no_routine_acl= !(grant_proc->privs & SHOW_PROC_ACLS); - rw_unlock(&LOCK_grant); + mysql_rwlock_unlock(&LOCK_grant); return no_routine_acl; } @@ -4497,7 +5100,7 @@ ulong get_table_grant(THD *thd, TABLE_LIST *table) const char *db = table->db ? table->db : thd->db; GRANT_TABLE *grant_table; - rw_rdlock(&LOCK_grant); + mysql_rwlock_rdlock(&LOCK_grant); #ifdef EMBEDDED_LIBRARY grant_table= NULL; #else @@ -4509,7 +5112,7 @@ ulong get_table_grant(THD *thd, TABLE_LIST *table) if (grant_table) table->grant.privilege|= grant_table->privs; privilege= table->grant.privilege; - rw_unlock(&LOCK_grant); + mysql_rwlock_unlock(&LOCK_grant); return privilege; } @@ -4540,7 +5143,7 @@ ulong get_column_grant(THD *thd, GRANT_INFO *grant, GRANT_COLUMN *grant_column; ulong priv; - rw_rdlock(&LOCK_grant); + mysql_rwlock_rdlock(&LOCK_grant); /* reload table if someone has modified any grants */ if (grant->version != grant_version) { @@ -4563,7 +5166,7 @@ ulong get_column_grant(THD *thd, GRANT_INFO *grant, else priv= (grant->privilege | grant_table->privs | grant_column->rights); } - rw_unlock(&LOCK_grant); + mysql_rwlock_unlock(&LOCK_grant); return priv; } @@ -4590,13 +5193,13 @@ static const char *command_array[]= "ALTER", "SHOW DATABASES", "SUPER", "CREATE TEMPORARY TABLES", "LOCK TABLES", "EXECUTE", "REPLICATION SLAVE", "REPLICATION CLIENT", "CREATE VIEW", "SHOW VIEW", "CREATE ROUTINE", "ALTER ROUTINE", - "CREATE USER", "EVENT", "TRIGGER" + "CREATE USER", "EVENT", "TRIGGER", "CREATE TABLESPACE" }; static uint command_lengths[]= { 6, 6, 6, 6, 6, 4, 6, 8, 7, 4, 5, 10, 5, 5, 14, 5, 23, 11, 7, 17, 18, 11, 9, - 14, 13, 11, 5, 7 + 14, 13, 11, 5, 7, 17 }; @@ -4630,14 +5233,14 @@ bool mysql_show_grants(THD *thd,LEX_USER *lex_user) DBUG_RETURN(TRUE); } - rw_rdlock(&LOCK_grant); - VOID(pthread_mutex_lock(&acl_cache->lock)); + mysql_rwlock_rdlock(&LOCK_grant); + mysql_mutex_lock(&acl_cache->lock); acl_user= find_acl_user(lex_user->host.str, lex_user->user.str, TRUE); if (!acl_user) { - VOID(pthread_mutex_unlock(&acl_cache->lock)); - rw_unlock(&LOCK_grant); + mysql_mutex_unlock(&acl_cache->lock); + mysql_rwlock_unlock(&LOCK_grant); my_error(ER_NONEXISTING_GRANT, MYF(0), lex_user->user.str, lex_user->host.str); @@ -4651,11 +5254,11 @@ bool mysql_show_grants(THD *thd,LEX_USER *lex_user) strxmov(buff,"Grants for ",lex_user->user.str,"@", lex_user->host.str,NullS); field_list.push_back(field); - if (protocol->send_fields(&field_list, + if (protocol->send_result_set_metadata(&field_list, Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)) { - VOID(pthread_mutex_unlock(&acl_cache->lock)); - rw_unlock(&LOCK_grant); + mysql_mutex_unlock(&acl_cache->lock); + mysql_rwlock_unlock(&LOCK_grant); DBUG_RETURN(TRUE); } @@ -4839,8 +5442,8 @@ bool mysql_show_grants(THD *thd,LEX_USER *lex_user) for (index=0 ; index < column_priv_hash.records ; index++) { const char *user, *host; - GRANT_TABLE *grant_table= (GRANT_TABLE*) hash_element(&column_priv_hash, - index); + GRANT_TABLE *grant_table= (GRANT_TABLE*) + my_hash_element(&column_priv_hash, index); if (!(user=grant_table->user)) user= ""; @@ -4893,7 +5496,7 @@ bool mysql_show_grants(THD *thd,LEX_USER *lex_user) col_index++) { GRANT_COLUMN *grant_column = (GRANT_COLUMN*) - hash_element(&grant_table->hash_columns,col_index); + my_hash_element(&grant_table->hash_columns,col_index); if (grant_column->rights & j) { if (!found_col) @@ -4964,9 +5567,15 @@ bool mysql_show_grants(THD *thd,LEX_USER *lex_user) goto end; } + if (show_proxy_grants(thd, lex_user, buff, sizeof(buff))) + { + error= -1; + goto end; + } + end: - VOID(pthread_mutex_unlock(&acl_cache->lock)); - rw_unlock(&LOCK_grant); + mysql_mutex_unlock(&acl_cache->lock); + mysql_rwlock_unlock(&LOCK_grant); my_eof(thd); DBUG_RETURN(error); @@ -4983,7 +5592,7 @@ static int show_routine_grants(THD* thd, LEX_USER *lex_user, HASH *hash, for (index=0 ; index < hash->records ; index++) { const char *user, *host; - GRANT_NAME *grant_proc= (GRANT_NAME*) hash_element(hash, index); + GRANT_NAME *grant_proc= (GRANT_NAME*) my_hash_element(hash, index); if (!(user=grant_proc->user)) user= ""; @@ -5090,14 +5699,14 @@ void get_mqh(const char *user, const char *host, USER_CONN *uc) { ACL_USER *acl_user; - pthread_mutex_lock(&acl_cache->lock); + mysql_mutex_lock(&acl_cache->lock); if (initialized && (acl_user= find_acl_user(host,user, FALSE))) uc->user_resources= acl_user->user_resource; else bzero((char*) &uc->user_resources, sizeof(uc->user_resources)); - pthread_mutex_unlock(&acl_cache->lock); + mysql_mutex_unlock(&acl_cache->lock); } /* @@ -5121,7 +5730,7 @@ void get_mqh(const char *user, const char *host, USER_CONN *uc) < 0 Error. */ -#define GRANT_TABLES 5 +#define GRANT_TABLES 6 int open_grant_tables(THD *thd, TABLE_LIST *tables) { DBUG_ENTER("open_grant_tables"); @@ -5132,21 +5741,29 @@ int open_grant_tables(THD *thd, TABLE_LIST *tables) DBUG_RETURN(-1); } - bzero((char*) tables, GRANT_TABLES*sizeof(*tables)); - tables->alias= tables->table_name= (char*) "user"; - (tables+1)->alias= (tables+1)->table_name= (char*) "db"; - (tables+2)->alias= (tables+2)->table_name= (char*) "tables_priv"; - (tables+3)->alias= (tables+3)->table_name= (char*) "columns_priv"; - (tables+4)->alias= (tables+4)->table_name= (char*) "procs_priv"; - tables->next_local= tables->next_global= tables+1; - (tables+1)->next_local= (tables+1)->next_global= tables+2; - (tables+2)->next_local= (tables+2)->next_global= tables+3; - (tables+3)->next_local= (tables+3)->next_global= tables+4; - tables->lock_type= (tables+1)->lock_type= - (tables+2)->lock_type= (tables+3)->lock_type= - (tables+4)->lock_type= TL_WRITE; - tables->db= (tables+1)->db= (tables+2)->db= - (tables+3)->db= (tables+4)->db= (char*) "mysql"; + tables->init_one_table(C_STRING_WITH_LEN("mysql"), + C_STRING_WITH_LEN("user"), "user", TL_WRITE); + (tables+1)->init_one_table(C_STRING_WITH_LEN("mysql"), + C_STRING_WITH_LEN("db"), "db", TL_WRITE); + (tables+2)->init_one_table(C_STRING_WITH_LEN("mysql"), + C_STRING_WITH_LEN("tables_priv"), + "tables_priv", TL_WRITE); + (tables+3)->init_one_table(C_STRING_WITH_LEN("mysql"), + C_STRING_WITH_LEN("columns_priv"), + "columns_priv", TL_WRITE); + (tables+4)->init_one_table(C_STRING_WITH_LEN("mysql"), + C_STRING_WITH_LEN("procs_priv"), + "procs_priv", TL_WRITE); + (tables+5)->init_one_table(C_STRING_WITH_LEN("mysql"), + C_STRING_WITH_LEN("proxies_priv"), + "proxies_priv", TL_WRITE); + tables[5].open_strategy= TABLE_LIST::OPEN_IF_EXISTS; + + tables->next_local= tables->next_global= tables + 1; + (tables+1)->next_local= (tables+1)->next_global= tables + 2; + (tables+2)->next_local= (tables+2)->next_global= tables + 3; + (tables+3)->next_local= (tables+3)->next_global= tables + 4; + (tables+4)->next_local= (tables+4)->next_global= tables + 5; #ifdef HAVE_REPLICATION /* @@ -5159,18 +5776,17 @@ int open_grant_tables(THD *thd, TABLE_LIST *tables) The tables must be marked "updating" so that tables_ok() takes them into account in tests. */ - tables[0].updating=tables[1].updating=tables[2].updating= - tables[3].updating=tables[4].updating=1; + tables[0].updating= tables[1].updating= tables[2].updating= + tables[3].updating= tables[4].updating= tables[5].updating= 1; if (!(thd->spcont || rpl_filter->tables_ok(0, tables))) DBUG_RETURN(1); - tables[0].updating=tables[1].updating=tables[2].updating= - tables[3].updating=tables[4].updating=0;; + tables[0].updating= tables[1].updating= tables[2].updating= + tables[3].updating= tables[4].updating= tables[5].updating= 0; } #endif - if (simple_open_n_lock_tables(thd, tables)) + if (open_and_lock_tables(thd, tables, FALSE, MYSQL_LOCK_IGNORE_TIMEOUT)) { // This should never happen - close_thread_tables(thd); DBUG_RETURN(-1); } @@ -5183,7 +5799,7 @@ ACL_USER *check_acl_user(LEX_USER *user_name, ACL_USER *acl_user= 0; uint counter; - safe_mutex_assert_owner(&acl_cache->lock); + mysql_mutex_assert_owner(&acl_cache->lock); for (counter= 0 ; counter < acl_users.elements ; counter++) { @@ -5294,7 +5910,7 @@ static int handle_grant_table(TABLE_LIST *tables, uint table_no, bool drop, int error; TABLE *table= tables[table_no].table; Field *host_field= table->field[0]; - Field *user_field= table->field[table_no ? 2 : 1]; + Field *user_field= table->field[table_no && table_no != 5 ? 2 : 1]; char *host_str= user_from->host.str; char *user_str= user_from->user.str; const char *host; @@ -5377,12 +5993,15 @@ static int handle_grant_table(TABLE_LIST *tables, uint table_no, bool drop, user= ""; #ifdef EXTRA_DEBUG - DBUG_PRINT("loop",("scan fields: '%s'@'%s' '%s' '%s' '%s'", - user, host, - get_field(thd->mem_root, table->field[1]) /*db*/, - get_field(thd->mem_root, table->field[3]) /*table*/, - get_field(thd->mem_root, - table->field[4]) /*column*/)); + if (table_no != 5) + { + DBUG_PRINT("loop",("scan fields: '%s'@'%s' '%s' '%s' '%s'", + user, host, + get_field(thd->mem_root, table->field[1]) /*db*/, + get_field(thd->mem_root, table->field[3]) /*table*/, + get_field(thd->mem_root, + table->field[4]) /*column*/)); + } #endif if (strcmp(user_str, user) || my_strcasecmp(system_charset_info, host_str, host)) @@ -5408,7 +6027,7 @@ static int handle_grant_table(TABLE_LIST *tables, uint table_no, bool drop, /** Handle an in-memory privilege structure. - @param struct_no The number of the structure to handle (0..4). + @param struct_no The number of the structure to handle (0..5). @param drop If user_from is to be dropped. @param user_from The the user to be searched/dropped/renamed. @param user_to The new name for the user if to be renamed, NULL otherwise. @@ -5422,13 +6041,14 @@ static int handle_grant_table(TABLE_LIST *tables, uint table_no, bool drop, Structures are enumerated as follows: 0 ACL_USER 1 ACL_DB - 2 COLUMN_PRIVILEGES_HASH + 2 COLUMN_PRIVILIGES_HASH 3 PROC_PRIVILEGES_HASH 4 FUNC_PRIVILEGES_HASH + 5 ACL_PROXY_USERS @retval > 0 At least one element matched. @retval 0 OK, but no element matched. - @retval -1 Wrong arguments to function or Out of Memory + @retval -1 Wrong arguments to function or Out of Memory. */ static int handle_grant_struct(enum enum_acl_lists struct_no, bool drop, @@ -5441,6 +6061,7 @@ static int handle_grant_struct(enum enum_acl_lists struct_no, bool drop, const char *host; ACL_USER *acl_user= NULL; ACL_DB *acl_db= NULL; + ACL_PROXY_USER *acl_proxy_user= NULL; GRANT_NAME *grant_name= NULL; /* Dynamic array acl_grant_name used to store pointers to all @@ -5455,7 +6076,7 @@ static int handle_grant_struct(enum enum_acl_lists struct_no, bool drop, LINT_INIT(user); LINT_INIT(host); - safe_mutex_assert_owner(&acl_cache->lock); + mysql_mutex_assert_owner(&acl_cache->lock); /* Get the number of elements in the in-memory structure. */ switch (struct_no) { @@ -5477,6 +6098,9 @@ static int handle_grant_struct(enum enum_acl_lists struct_no, bool drop, elements= func_priv_hash.records; grant_name_hash= &func_priv_hash; break; + case PROXY_USERS_ACL: + elements= acl_proxy_users.elements; + break; default: return -1; } @@ -5507,11 +6131,17 @@ static int handle_grant_struct(enum enum_acl_lists struct_no, bool drop, case COLUMN_PRIVILEGES_HASH: case PROC_PRIVILEGES_HASH: case FUNC_PRIVILEGES_HASH: - grant_name= (GRANT_NAME*) hash_element(grant_name_hash, idx); + grant_name= (GRANT_NAME*) my_hash_element(grant_name_hash, idx); user= grant_name->user; host= grant_name->host.hostname; break; + case PROXY_USERS_ACL: + acl_proxy_user= dynamic_element(&acl_proxy_users, idx, ACL_PROXY_USER*); + user= acl_proxy_user->get_user(); + host= acl_proxy_user->get_host(); + break; + default: MY_ASSERT_UNREACHABLE(); } @@ -5540,7 +6170,7 @@ static int handle_grant_struct(enum enum_acl_lists struct_no, bool drop, elements after the current element one position closer to its head. This means that we have to take another look at the element at current position as it is a new element from the array's tail. - - This is valid for USER_ACL, DB_ACL. + - This is valid for USER_ACL, DB_ACL and PROXY_USERS_ACL. */ idx--; break; @@ -5561,6 +6191,13 @@ static int handle_grant_struct(enum enum_acl_lists struct_no, bool drop, if (acl_grant_name.append(grant_name)) DBUG_RETURN(-1); break; + + case PROXY_USERS_ACL: + delete_dynamic_element(&acl_proxy_users, idx); + elements--; + idx--; + break; + } } else if ( user_to ) @@ -5586,6 +6223,11 @@ static int handle_grant_struct(enum enum_acl_lists struct_no, bool drop, if (acl_grant_name.append(grant_name)) DBUG_RETURN(-1); break; + + case PROXY_USERS_ACL: + acl_proxy_user->set_user (&mem, user_to->user.str); + acl_proxy_user->set_host (&mem, user_to->host.str); + break; } } else @@ -5797,6 +6439,25 @@ static int handle_grant_data(TABLE_LIST *tables, bool drop, result= -1; } } + + /* Handle proxies_priv table. */ + if (tables[5].table) + { + if ((found= handle_grant_table(tables, 5, drop, user_from, user_to)) < 0) + { + /* Handle of table failed, don't touch the in-memory array. */ + result= -1; + } + else + { + /* Handle proxies_priv array. */ + if (((ret= handle_grant_struct(PROXY_USERS_ACL, drop, user_from, user_to) > 0) + && !result) || found) + result= 1; /* At least one record/element found. */ + else if (ret < 0) + result= -1; + } + } end: DBUG_RETURN(result); } @@ -5843,19 +6504,21 @@ bool mysql_create_user(THD *thd, List <LEX_USER> &list) row-based replication. The flag will be reset at the end of the statement. */ - save_binlog_row_based= thd->current_stmt_binlog_row_based; - thd->clear_current_stmt_binlog_row_based(); + if ((save_binlog_row_based= thd->is_current_stmt_binlog_format_row())) + thd->clear_current_stmt_binlog_format_row(); /* CREATE USER may be skipped on replication client. */ if ((result= open_grant_tables(thd, tables))) { /* Restore the state of binlog format */ - thd->current_stmt_binlog_row_based= save_binlog_row_based; + DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); + if (save_binlog_row_based) + thd->set_current_stmt_binlog_format_row(); DBUG_RETURN(result != 1); } - rw_wrlock(&LOCK_grant); - VOID(pthread_mutex_lock(&acl_cache->lock)); + mysql_rwlock_wrlock(&LOCK_grant); + mysql_mutex_lock(&acl_cache->lock); while ((tmp_user_name= user_list++)) { @@ -5884,7 +6547,7 @@ bool mysql_create_user(THD *thd, List <LEX_USER> &list) } } - VOID(pthread_mutex_unlock(&acl_cache->lock)); + mysql_mutex_unlock(&acl_cache->lock); if (result) my_error(ER_CANNOT_USER, MYF(0), "CREATE USER", wrong_users.c_ptr_safe()); @@ -5892,10 +6555,11 @@ bool mysql_create_user(THD *thd, List <LEX_USER> &list) if (some_users_created) result |= write_bin_log(thd, FALSE, thd->query(), thd->query_length()); - rw_unlock(&LOCK_grant); - close_thread_tables(thd); + mysql_rwlock_unlock(&LOCK_grant); /* Restore the state of binlog format */ - thd->current_stmt_binlog_row_based= save_binlog_row_based; + DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); + if (save_binlog_row_based) + thd->set_current_stmt_binlog_format_row(); DBUG_RETURN(result); } @@ -5930,21 +6594,23 @@ bool mysql_drop_user(THD *thd, List <LEX_USER> &list) row-based replication. The flag will be reset at the end of the statement. */ - save_binlog_row_based= thd->current_stmt_binlog_row_based; - thd->clear_current_stmt_binlog_row_based(); + if ((save_binlog_row_based= thd->is_current_stmt_binlog_format_row())) + thd->clear_current_stmt_binlog_format_row(); /* DROP USER may be skipped on replication client. */ if ((result= open_grant_tables(thd, tables))) { /* Restore the state of binlog format */ - thd->current_stmt_binlog_row_based= save_binlog_row_based; + DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); + if (save_binlog_row_based) + thd->set_current_stmt_binlog_format_row(); DBUG_RETURN(result != 1); } thd->variables.sql_mode&= ~MODE_PAD_CHAR_TO_FULL_LENGTH; - rw_wrlock(&LOCK_grant); - VOID(pthread_mutex_lock(&acl_cache->lock)); + mysql_rwlock_wrlock(&LOCK_grant); + mysql_mutex_lock(&acl_cache->lock); while ((tmp_user_name= user_list++)) { @@ -5965,7 +6631,7 @@ bool mysql_drop_user(THD *thd, List <LEX_USER> &list) /* Rebuild 'acl_check_hosts' since 'acl_users' has been modified */ rebuild_check_host(); - VOID(pthread_mutex_unlock(&acl_cache->lock)); + mysql_mutex_unlock(&acl_cache->lock); if (result) my_error(ER_CANNOT_USER, MYF(0), "DROP USER", wrong_users.c_ptr_safe()); @@ -5973,11 +6639,12 @@ bool mysql_drop_user(THD *thd, List <LEX_USER> &list) if (some_users_deleted) result |= write_bin_log(thd, FALSE, thd->query(), thd->query_length()); - rw_unlock(&LOCK_grant); - close_thread_tables(thd); + mysql_rwlock_unlock(&LOCK_grant); thd->variables.sql_mode= old_sql_mode; /* Restore the state of binlog format */ - thd->current_stmt_binlog_row_based= save_binlog_row_based; + DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); + if (save_binlog_row_based) + thd->set_current_stmt_binlog_format_row(); DBUG_RETURN(result); } @@ -6012,19 +6679,21 @@ bool mysql_rename_user(THD *thd, List <LEX_USER> &list) row-based replication. The flag will be reset at the end of the statement. */ - save_binlog_row_based= thd->current_stmt_binlog_row_based; - thd->clear_current_stmt_binlog_row_based(); + if ((save_binlog_row_based= thd->is_current_stmt_binlog_format_row())) + thd->clear_current_stmt_binlog_format_row(); /* RENAME USER may be skipped on replication client. */ if ((result= open_grant_tables(thd, tables))) { /* Restore the state of binlog format */ - thd->current_stmt_binlog_row_based= save_binlog_row_based; + DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); + if (save_binlog_row_based) + thd->set_current_stmt_binlog_format_row(); DBUG_RETURN(result != 1); } - rw_wrlock(&LOCK_grant); - VOID(pthread_mutex_lock(&acl_cache->lock)); + mysql_rwlock_wrlock(&LOCK_grant); + mysql_mutex_lock(&acl_cache->lock); while ((tmp_user_from= user_list++)) { @@ -6058,7 +6727,7 @@ bool mysql_rename_user(THD *thd, List <LEX_USER> &list) /* Rebuild 'acl_check_hosts' since 'acl_users' has been modified */ rebuild_check_host(); - VOID(pthread_mutex_unlock(&acl_cache->lock)); + mysql_mutex_unlock(&acl_cache->lock); if (result) my_error(ER_CANNOT_USER, MYF(0), "RENAME USER", wrong_users.c_ptr_safe()); @@ -6066,10 +6735,11 @@ bool mysql_rename_user(THD *thd, List <LEX_USER> &list) if (some_users_renamed && mysql_bin_log.is_open()) result |= write_bin_log(thd, FALSE, thd->query(), thd->query_length()); - rw_unlock(&LOCK_grant); - close_thread_tables(thd); + mysql_rwlock_unlock(&LOCK_grant); /* Restore the state of binlog format */ - thd->current_stmt_binlog_row_based= save_binlog_row_based; + DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); + if (save_binlog_row_based) + thd->set_current_stmt_binlog_format_row(); DBUG_RETURN(result); } @@ -6102,18 +6772,20 @@ bool mysql_revoke_all(THD *thd, List <LEX_USER> &list) row-based replication. The flag will be reset at the end of the statement. */ - save_binlog_row_based= thd->current_stmt_binlog_row_based; - thd->clear_current_stmt_binlog_row_based(); + if ((save_binlog_row_based= thd->is_current_stmt_binlog_format_row())) + thd->clear_current_stmt_binlog_format_row(); if ((result= open_grant_tables(thd, tables))) { /* Restore the state of binlog format */ - thd->current_stmt_binlog_row_based= save_binlog_row_based; + DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); + if (save_binlog_row_based) + thd->set_current_stmt_binlog_format_row(); DBUG_RETURN(result != 1); } - rw_wrlock(&LOCK_grant); - VOID(pthread_mutex_lock(&acl_cache->lock)); + mysql_rwlock_wrlock(&LOCK_grant); + mysql_mutex_lock(&acl_cache->lock); LEX_USER *lex_user, *tmp_lex_user; List_iterator <LEX_USER> user_list(list); @@ -6180,8 +6852,8 @@ bool mysql_revoke_all(THD *thd, List <LEX_USER> &list) for (counter= 0, revoked= 0 ; counter < column_priv_hash.records ; ) { const char *user,*host; - GRANT_TABLE *grant_table= (GRANT_TABLE*)hash_element(&column_priv_hash, - counter); + GRANT_TABLE *grant_table= + (GRANT_TABLE*) my_hash_element(&column_priv_hash, counter); if (!(user=grant_table->user)) user= ""; if (!(host=grant_table->host.hostname)) @@ -6227,7 +6899,7 @@ bool mysql_revoke_all(THD *thd, List <LEX_USER> &list) for (counter= 0, revoked= 0 ; counter < hash->records ; ) { const char *user,*host; - GRANT_NAME *grant_proc= (GRANT_NAME*) hash_element(hash, counter); + GRANT_NAME *grant_proc= (GRANT_NAME*) my_hash_element(hash, counter); if (!(user=grant_proc->user)) user= ""; if (!(host=grant_proc->host.hostname)) @@ -6252,7 +6924,7 @@ bool mysql_revoke_all(THD *thd, List <LEX_USER> &list) } while (revoked); } - VOID(pthread_mutex_unlock(&acl_cache->lock)); + mysql_mutex_unlock(&acl_cache->lock); if (result) my_message(ER_REVOKE_GRANTS, ER(ER_REVOKE_GRANTS), MYF(0)); @@ -6260,11 +6932,11 @@ bool mysql_revoke_all(THD *thd, List <LEX_USER> &list) result= result | write_bin_log(thd, FALSE, thd->query(), thd->query_length()); - rw_unlock(&LOCK_grant); - close_thread_tables(thd); - + mysql_rwlock_unlock(&LOCK_grant); /* Restore the state of binlog format */ - thd->current_stmt_binlog_row_based= save_binlog_row_based; + DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); + if (save_binlog_row_based) + thd->set_current_stmt_binlog_format_row(); DBUG_RETURN(result); } @@ -6290,9 +6962,12 @@ public: virtual ~Silence_routine_definer_errors() {} - virtual bool handle_error(uint sql_errno, const char *message, - MYSQL_ERROR::enum_warning_level level, - THD *thd); + virtual bool handle_condition(THD *thd, + uint sql_errno, + const char* sqlstate, + MYSQL_ERROR::enum_warning_level level, + const char* msg, + MYSQL_ERROR ** cond_hdl); bool has_errors() { return is_grave; } @@ -6301,18 +6976,23 @@ private: }; bool -Silence_routine_definer_errors::handle_error(uint sql_errno, - const char *message, - MYSQL_ERROR::enum_warning_level level, - THD *thd) +Silence_routine_definer_errors::handle_condition( + THD *thd, + uint sql_errno, + const char*, + MYSQL_ERROR::enum_warning_level level, + const char* msg, + MYSQL_ERROR ** cond_hdl) { + *cond_hdl= NULL; if (level == MYSQL_ERROR::WARN_LEVEL_ERROR) { switch (sql_errno) { case ER_NONEXISTING_PROC_GRANT: /* Convert the error into a warning. */ - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, sql_errno, message); + push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + sql_errno, msg); return TRUE; default: is_grave= TRUE; @@ -6357,23 +7037,23 @@ bool sp_revoke_privileges(THD *thd, const char *sp_db, const char *sp_name, /* Be sure to pop this before exiting this scope! */ thd->push_internal_handler(&error_handler); - rw_wrlock(&LOCK_grant); - VOID(pthread_mutex_lock(&acl_cache->lock)); + mysql_rwlock_wrlock(&LOCK_grant); + mysql_mutex_lock(&acl_cache->lock); /* This statement will be replicated as a statement, even when using row-based replication. The flag will be reset at the end of the statement. */ - save_binlog_row_based= thd->current_stmt_binlog_row_based; - thd->clear_current_stmt_binlog_row_based(); + if ((save_binlog_row_based= thd->is_current_stmt_binlog_format_row())) + thd->clear_current_stmt_binlog_format_row(); /* Remove procedure access */ do { for (counter= 0, revoked= 0 ; counter < hash->records ; ) { - GRANT_NAME *grant_proc= (GRANT_NAME*) hash_element(hash, counter); + GRANT_NAME *grant_proc= (GRANT_NAME*) my_hash_element(hash, counter); if (!my_strcasecmp(&my_charset_utf8_bin, grant_proc->db, sp_db) && !my_strcasecmp(system_charset_info, grant_proc->tname, sp_name)) { @@ -6397,13 +7077,14 @@ bool sp_revoke_privileges(THD *thd, const char *sp_db, const char *sp_name, } } while (revoked); - VOID(pthread_mutex_unlock(&acl_cache->lock)); - rw_unlock(&LOCK_grant); - close_thread_tables(thd); + mysql_mutex_unlock(&acl_cache->lock); + mysql_rwlock_unlock(&LOCK_grant); thd->pop_internal_handler(); /* Restore the state of binlog format */ - thd->current_stmt_binlog_row_based= save_binlog_row_based; + DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); + if (save_binlog_row_based) + thd->set_current_stmt_binlog_format_row(); DBUG_RETURN(error_handler.has_errors()); } @@ -6440,7 +7121,7 @@ bool sp_grant_privileges(THD *thd, const char *sp_db, const char *sp_name, combo->user.str= sctx->user; - VOID(pthread_mutex_lock(&acl_cache->lock)); + mysql_mutex_lock(&acl_cache->lock); if ((au= find_acl_user(combo->host.str=(char*)sctx->host_or_ip,combo->user.str,FALSE))) goto found_acl; @@ -6451,11 +7132,11 @@ bool sp_grant_privileges(THD *thd, const char *sp_db, const char *sp_name, if((au= find_acl_user(combo->host.str=(char*)"%", combo->user.str, FALSE))) goto found_acl; - VOID(pthread_mutex_unlock(&acl_cache->lock)); + mysql_mutex_unlock(&acl_cache->lock); DBUG_RETURN(TRUE); found_acl: - VOID(pthread_mutex_unlock(&acl_cache->lock)); + mysql_mutex_unlock(&acl_cache->lock); bzero((char*)tables, sizeof(TABLE_LIST)); user_list.empty(); @@ -6463,38 +7144,44 @@ bool sp_grant_privileges(THD *thd, const char *sp_db, const char *sp_name, tables->db= (char*)sp_db; tables->table_name= tables->alias= (char*)sp_name; - combo->host.length= strlen(combo->host.str); - combo->user.length= strlen(combo->user.str); - combo->host.str= thd->strmake(combo->host.str,combo->host.length); - combo->user.str= thd->strmake(combo->user.str,combo->user.length); + thd->make_lex_string(&combo->user, + combo->user.str, strlen(combo->user.str), 0); + thd->make_lex_string(&combo->host, + combo->host.str, strlen(combo->host.str), 0); + combo->password= empty_lex_str; + combo->plugin= empty_lex_str; + combo->auth= empty_lex_str; - if(au && au->salt_len) + if(au) { - if (au->salt_len == SCRAMBLE_LENGTH) - { - make_password_from_salt(passwd_buff, au->salt); - combo->password.length= SCRAMBLED_PASSWORD_CHAR_LENGTH; - } - else if (au->salt_len == SCRAMBLE_LENGTH_323) + if (au->salt_len) { - make_password_from_salt_323(passwd_buff, (ulong *) au->salt); - combo->password.length= SCRAMBLED_PASSWORD_CHAR_LENGTH_323; + if (au->salt_len == SCRAMBLE_LENGTH) + { + make_password_from_salt(passwd_buff, au->salt); + combo->password.length= SCRAMBLED_PASSWORD_CHAR_LENGTH; + } + else if (au->salt_len == SCRAMBLE_LENGTH_323) + { + make_password_from_salt_323(passwd_buff, (ulong *) au->salt); + combo->password.length= SCRAMBLED_PASSWORD_CHAR_LENGTH_323; + } + else + { + push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, ER_PASSWD_LENGTH, + ER(ER_PASSWD_LENGTH), SCRAMBLED_PASSWORD_CHAR_LENGTH); + return TRUE; + } + combo->password.str= passwd_buff; } - else + + if (au->plugin.str != native_password_plugin_name.str && + au->plugin.str != old_password_plugin_name.str) { - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_PASSWD_LENGTH, - ER(ER_PASSWD_LENGTH), - SCRAMBLED_PASSWORD_CHAR_LENGTH); - return TRUE; + combo->plugin= au->plugin; + combo->auth= au->auth_string; } - combo->password.str= passwd_buff; - } - else - { - combo->password.str= (char*)""; - combo->password.length= 0; } if (user_list.push_back(combo)) @@ -6527,6 +7214,140 @@ template class List<LEX_COLUMN>; template class List<LEX_USER>; #endif +/** + Validate if a user can proxy as another user + + @thd current thread + @param user the logged in user (proxy user) + @param authenticated_as the effective user a plugin is trying to + impersonate as (proxied user) + @return proxy user definition + @retval NULL proxy user definition not found or not applicable + @retval non-null the proxy user data +*/ + +static ACL_PROXY_USER * +acl_find_proxy_user(const char *user, const char *host, const char *ip, + const char *authenticated_as, bool *proxy_used) +{ + uint i; + /* if the proxied and proxy user are the same return OK */ + DBUG_ENTER("acl_find_proxy_user"); + DBUG_PRINT("info", ("user=%s host=%s ip=%s authenticated_as=%s", + user, host, ip, authenticated_as)); + + if (!strcmp(authenticated_as, user)) + { + DBUG_PRINT ("info", ("user is the same as authenticated_as")); + DBUG_RETURN (NULL); + } + + *proxy_used= TRUE; + for (i=0; i < acl_proxy_users.elements; i++) + { + ACL_PROXY_USER *proxy= dynamic_element(&acl_proxy_users, i, + ACL_PROXY_USER *); + if (proxy->matches(host, user, ip, authenticated_as)) + DBUG_RETURN(proxy); + } + + DBUG_RETURN(NULL); +} + + +bool +acl_check_proxy_grant_access(THD *thd, const char *host, const char *user, + bool with_grant) +{ + DBUG_ENTER("acl_check_proxy_grant_access"); + DBUG_PRINT("info", ("user=%s host=%s with_grant=%d", user, host, + (int) with_grant)); + if (!initialized) + { + my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--skip-grant-tables"); + DBUG_RETURN(1); + } + + /* replication slave thread can do anything */ + if (thd->slave_thread) + { + DBUG_PRINT("info", ("replication slave")); + DBUG_RETURN(FALSE); + } + + /* + one can grant proxy for self to others. + Security context in THD contains two pairs of (user,host): + 1. (user,host) pair referring to inbound connection. + 2. (priv_user,priv_host) pair obtained from mysql.user table after doing + authnetication of incoming connection. + Privileges should be checked wrt (priv_user, priv_host) tuple, because + (user,host) pair obtained from inbound connection may have different + values than what is actually stored in mysql.user table and while granting + or revoking proxy privilege, user is expected to provide entries mentioned + in mysql.user table. + */ + if (!strcmp(thd->security_ctx->priv_user, user) && + !my_strcasecmp(system_charset_info, host, + thd->security_ctx->priv_host)) + { + DBUG_PRINT("info", ("strcmp (%s, %s) my_casestrcmp (%s, %s) equal", + thd->security_ctx->priv_user, user, + host, thd->security_ctx->priv_host)); + DBUG_RETURN(FALSE); + } + + /* check for matching WITH PROXY rights */ + for (uint i=0; i < acl_proxy_users.elements; i++) + { + ACL_PROXY_USER *proxy= dynamic_element(&acl_proxy_users, i, + ACL_PROXY_USER *); + if (proxy->matches(thd->security_ctx->host, + thd->security_ctx->user, + thd->security_ctx->ip, + user) && + proxy->get_with_grant()) + { + DBUG_PRINT("info", ("found")); + DBUG_RETURN(FALSE); + } + } + + my_error(ER_ACCESS_DENIED_NO_PASSWORD_ERROR, MYF(0), + thd->security_ctx->user, + thd->security_ctx->host_or_ip); + DBUG_RETURN(TRUE); +} + + +static bool +show_proxy_grants(THD *thd, LEX_USER *user, char *buff, size_t buffsize) +{ + Protocol *protocol= thd->protocol; + int error= 0; + + for (uint i=0; i < acl_proxy_users.elements; i++) + { + ACL_PROXY_USER *proxy= dynamic_element(&acl_proxy_users, i, + ACL_PROXY_USER *); + if (proxy->granted_on(user->host.str, user->user.str)) + { + String global(buff, buffsize, system_charset_info); + global.length(0); + proxy->print_grant(&global); + protocol->prepare_for_resend(); + protocol->store(global.ptr(), global.length(), global.charset()); + if (protocol->write()) + { + error= -1; + break; + } + } + } + return error; +} + + #endif /*NO_EMBEDDED_ACCESS_CHECKS */ @@ -6585,6 +7406,7 @@ static bool update_schema_privilege(THD *thd, TABLE *table, char *buff, CHARSET_INFO *cs= system_charset_info; restore_record(table, s->default_values); table->field[0]->store(buff, (uint) strlen(buff), cs); + table->field[1]->store(STRING_WITH_LEN("def"), cs); if (db) table->field[i++]->store(db, (uint) strlen(db), cs); if (t_name) @@ -6607,13 +7429,14 @@ int fill_schema_user_privileges(THD *thd, TABLE_LIST *tables, COND *cond) ulong want_access; char buff[100]; TABLE *table= tables->table; - bool no_global_access= check_access(thd, SELECT_ACL, "mysql",0,1,1,0); + bool no_global_access= check_access(thd, SELECT_ACL, "mysql", + NULL, NULL, 1, 1); char *curr_host= thd->security_ctx->priv_host_name(); DBUG_ENTER("fill_schema_user_privileges"); if (!initialized) DBUG_RETURN(0); - pthread_mutex_lock(&acl_cache->lock); + mysql_mutex_lock(&acl_cache->lock); for (counter=0 ; counter < acl_users.elements ; counter++) { @@ -6663,7 +7486,7 @@ int fill_schema_user_privileges(THD *thd, TABLE_LIST *tables, COND *cond) } } err: - pthread_mutex_unlock(&acl_cache->lock); + mysql_mutex_unlock(&acl_cache->lock); DBUG_RETURN(error); #else @@ -6681,13 +7504,14 @@ int fill_schema_schema_privileges(THD *thd, TABLE_LIST *tables, COND *cond) ulong want_access; char buff[100]; TABLE *table= tables->table; - bool no_global_access= check_access(thd, SELECT_ACL, "mysql",0,1,1,0); + bool no_global_access= check_access(thd, SELECT_ACL, "mysql", + NULL, NULL, 1, 1); char *curr_host= thd->security_ctx->priv_host_name(); DBUG_ENTER("fill_schema_schema_privileges"); if (!initialized) DBUG_RETURN(0); - pthread_mutex_lock(&acl_cache->lock); + mysql_mutex_lock(&acl_cache->lock); for (counter=0 ; counter < acl_dbs.elements ; counter++) { @@ -6740,7 +7564,7 @@ int fill_schema_schema_privileges(THD *thd, TABLE_LIST *tables, COND *cond) } } err: - pthread_mutex_unlock(&acl_cache->lock); + mysql_mutex_unlock(&acl_cache->lock); DBUG_RETURN(error); #else @@ -6756,16 +7580,17 @@ int fill_schema_table_privileges(THD *thd, TABLE_LIST *tables, COND *cond) uint index; char buff[100]; TABLE *table= tables->table; - bool no_global_access= check_access(thd, SELECT_ACL, "mysql",0,1,1,0); + bool no_global_access= check_access(thd, SELECT_ACL, "mysql", + NULL, NULL, 1, 1); char *curr_host= thd->security_ctx->priv_host_name(); DBUG_ENTER("fill_schema_table_privileges"); - rw_rdlock(&LOCK_grant); + mysql_rwlock_rdlock(&LOCK_grant); for (index=0 ; index < column_priv_hash.records ; index++) { const char *user, *host, *is_grantable= "YES"; - GRANT_TABLE *grant_table= (GRANT_TABLE*) hash_element(&column_priv_hash, + GRANT_TABLE *grant_table= (GRANT_TABLE*) my_hash_element(&column_priv_hash, index); if (!(user=grant_table->user)) user= ""; @@ -6823,7 +7648,7 @@ int fill_schema_table_privileges(THD *thd, TABLE_LIST *tables, COND *cond) } } err: - rw_unlock(&LOCK_grant); + mysql_rwlock_unlock(&LOCK_grant); DBUG_RETURN(error); #else @@ -6839,16 +7664,17 @@ int fill_schema_column_privileges(THD *thd, TABLE_LIST *tables, COND *cond) uint index; char buff[100]; TABLE *table= tables->table; - bool no_global_access= check_access(thd, SELECT_ACL, "mysql",0,1,1,0); + bool no_global_access= check_access(thd, SELECT_ACL, "mysql", + NULL, NULL, 1, 1); char *curr_host= thd->security_ctx->priv_host_name(); DBUG_ENTER("fill_schema_table_privileges"); - rw_rdlock(&LOCK_grant); + mysql_rwlock_rdlock(&LOCK_grant); for (index=0 ; index < column_priv_hash.records ; index++) { const char *user, *host, *is_grantable= "YES"; - GRANT_TABLE *grant_table= (GRANT_TABLE*) hash_element(&column_priv_hash, + GRANT_TABLE *grant_table= (GRANT_TABLE*) my_hash_element(&column_priv_hash, index); if (!(user=grant_table->user)) user= ""; @@ -6883,7 +7709,7 @@ int fill_schema_column_privileges(THD *thd, TABLE_LIST *tables, COND *cond) col_index++) { GRANT_COLUMN *grant_column = (GRANT_COLUMN*) - hash_element(&grant_table->hash_columns,col_index); + my_hash_element(&grant_table->hash_columns,col_index); if ((grant_column->rights & j) && (table_access & j)) { if (update_schema_privilege(thd, table, buff, grant_table->db, @@ -6904,7 +7730,7 @@ int fill_schema_column_privileges(THD *thd, TABLE_LIST *tables, COND *cond) } } err: - rw_unlock(&LOCK_grant); + mysql_rwlock_unlock(&LOCK_grant); DBUG_RETURN(error); #else @@ -6956,7 +7782,7 @@ void fill_effective_table_privileges(THD *thd, GRANT_INFO *grant, grant->privilege|= acl_get(sctx->host, sctx->ip, sctx->priv_user, db, 0); /* table privileges */ - rw_rdlock(&LOCK_grant); + mysql_rwlock_rdlock(&LOCK_grant); if (grant->version != grant_version) { grant->grant_table= @@ -6969,7 +7795,7 @@ void fill_effective_table_privileges(THD *thd, GRANT_INFO *grant, { grant->privilege|= grant->grant_table->privs; } - rw_unlock(&LOCK_grant); + mysql_rwlock_unlock(&LOCK_grant); DBUG_PRINT("info", ("privilege 0x%lx", grant->privilege)); DBUG_VOID_RETURN; @@ -6988,3 +7814,2056 @@ bool check_routine_level_acl(THD *thd, const char *db, const char *name, } #endif + +struct ACL_internal_schema_registry_entry +{ + const LEX_STRING *m_name; + const ACL_internal_schema_access *m_access; +}; + +/** + Internal schema registered. + Currently, this is only: + - performance_schema + - information_schema, + This can be reused later for: + - mysql +*/ +static ACL_internal_schema_registry_entry registry_array[2]; +static uint m_registry_array_size= 0; + +/** + Add an internal schema to the registry. + @param name the schema name + @param access the schema ACL specific rules +*/ +void ACL_internal_schema_registry::register_schema + (const LEX_STRING *name, const ACL_internal_schema_access *access) +{ + DBUG_ASSERT(m_registry_array_size < array_elements(registry_array)); + + /* Not thread safe, and does not need to be. */ + registry_array[m_registry_array_size].m_name= name; + registry_array[m_registry_array_size].m_access= access; + m_registry_array_size++; +} + +/** + Search per internal schema ACL by name. + @param name a schema name + @return per schema rules, or NULL +*/ +const ACL_internal_schema_access * +ACL_internal_schema_registry::lookup(const char *name) +{ + DBUG_ASSERT(name != NULL); + + uint i; + + for (i= 0; i<m_registry_array_size; i++) + { + if (my_strcasecmp(system_charset_info, registry_array[i].m_name->str, + name) == 0) + return registry_array[i].m_access; + } + return NULL; +} + +/** + Get a cached internal schema access. + @param grant_internal_info the cache + @param schema_name the name of the internal schema +*/ +const ACL_internal_schema_access * +get_cached_schema_access(GRANT_INTERNAL_INFO *grant_internal_info, + const char *schema_name) +{ + if (grant_internal_info) + { + if (! grant_internal_info->m_schema_lookup_done) + { + grant_internal_info->m_schema_access= + ACL_internal_schema_registry::lookup(schema_name); + grant_internal_info->m_schema_lookup_done= TRUE; + } + return grant_internal_info->m_schema_access; + } + return ACL_internal_schema_registry::lookup(schema_name); +} + +/** + Get a cached internal table access. + @param grant_internal_info the cache + @param schema_name the name of the internal schema + @param table_name the name of the internal table +*/ +const ACL_internal_table_access * +get_cached_table_access(GRANT_INTERNAL_INFO *grant_internal_info, + const char *schema_name, + const char *table_name) +{ + DBUG_ASSERT(grant_internal_info); + if (! grant_internal_info->m_table_lookup_done) + { + const ACL_internal_schema_access *schema_access; + schema_access= get_cached_schema_access(grant_internal_info, schema_name); + if (schema_access) + grant_internal_info->m_table_access= schema_access->lookup(table_name); + grant_internal_info->m_table_lookup_done= TRUE; + } + return grant_internal_info->m_table_access; +} + + +/**************************************************************************** + AUTHENTICATION CODE + including initial connect handshake, invoking appropriate plugins, + client-server plugin negotiation, COM_CHANGE_USER, and native + MySQL authentication plugins. +****************************************************************************/ + +/* few defines to have less ifdef's in the code below */ +#ifdef EMBEDDED_LIBRARY +#undef HAVE_OPENSSL +#ifdef NO_EMBEDDED_ACCESS_CHECKS +#define initialized 0 +#endif +#endif +#ifndef HAVE_OPENSSL +#define ssl_acceptor_fd 0 +#define sslaccept(A,B,C) 1 +#endif + + +class Thd_charset_adapter +{ + THD *thd; +public: + Thd_charset_adapter(THD *thd_arg) : thd (thd_arg) {} + bool init_client_charset(uint cs_number) + { + if (thd_init_client_charset(thd, cs_number)) + return true; + thd->update_charset(); + return thd->is_error(); + } + + CHARSET_INFO *charset() { return thd->charset(); } +}; + + +/** + The internal version of what plugins know as MYSQL_PLUGIN_VIO, + basically the context of the authentication session +*/ +struct MPVIO_EXT :public MYSQL_PLUGIN_VIO +{ + MYSQL_SERVER_AUTH_INFO auth_info; + const ACL_USER *acl_user; + plugin_ref plugin; ///< what plugin we're under + LEX_STRING db; ///< db name from the handshake packet + /** when restarting a plugin this caches the last client reply */ + struct { + char *plugin, *pkt; ///< pointers into NET::buff + uint pkt_len; + } cached_client_reply; + /** this caches the first plugin packet for restart request on the client */ + struct { + char *pkt; + uint pkt_len; + } cached_server_packet; + int packets_read, packets_written; ///< counters for send/received packets + uint connect_errors; ///< if there were connect errors for this host + /** when plugin returns a failure this tells us what really happened */ + enum { SUCCESS, FAILURE, RESTART } status; + + /* encapsulation members */ + ulong client_capabilities; + char *scramble; + MEM_ROOT *mem_root; + struct rand_struct *rand; + my_thread_id thread_id; + uint *server_status; + NET *net; + ulong max_client_packet_length; + char *ip; + char *host; + Thd_charset_adapter *charset_adapter; + LEX_STRING acl_user_plugin; +}; + +/** + a helper function to report an access denied error in all the proper places +*/ +static void login_failed_error(MPVIO_EXT *mpvio, int passwd_used) +{ + THD *thd= current_thd; + if (passwd_used == 2) + { + my_error(ER_ACCESS_DENIED_NO_PASSWORD_ERROR, MYF(0), + mpvio->auth_info.user_name, + mpvio->auth_info.host_or_ip); + general_log_print(thd, COM_CONNECT, ER(ER_ACCESS_DENIED_NO_PASSWORD_ERROR), + mpvio->auth_info.user_name, + mpvio->auth_info.host_or_ip); + /* + Log access denied messages to the error log when log-warnings = 2 + so that the overhead of the general query log is not required to track + failed connections. + */ + if (global_system_variables.log_warnings > 1) + { + sql_print_warning(ER(ER_ACCESS_DENIED_NO_PASSWORD_ERROR), + mpvio->auth_info.user_name, + mpvio->auth_info.host_or_ip); + } + } + else + { + my_error(ER_ACCESS_DENIED_ERROR, MYF(0), + mpvio->auth_info.user_name, + mpvio->auth_info.host_or_ip, + passwd_used ? ER(ER_YES) : ER(ER_NO)); + general_log_print(thd, COM_CONNECT, ER(ER_ACCESS_DENIED_ERROR), + mpvio->auth_info.user_name, + mpvio->auth_info.host_or_ip, + passwd_used ? ER(ER_YES) : ER(ER_NO)); + /* + Log access denied messages to the error log when log-warnings = 2 + so that the overhead of the general query log is not required to track + failed connections. + */ + if (global_system_variables.log_warnings > 1) + { + sql_print_warning(ER(ER_ACCESS_DENIED_ERROR), + mpvio->auth_info.user_name, + mpvio->auth_info.host_or_ip, + passwd_used ? ER(ER_YES) : ER(ER_NO)); + } + } +} + +/** + sends a server handshake initialization packet, the very first packet + after the connection was established + + Packet format: + + Bytes Content + ----- ---- + 1 protocol version (always 10) + n server version string, \0-terminated + 4 thread id + 8 first 8 bytes of the plugin provided data (scramble) + 1 \0 byte, terminating the first part of a scramble + 2 server capabilities (two lower bytes) + 1 server character set + 2 server status + 2 server capabilities (two upper bytes) + 1 length of the scramble + 10 reserved, always 0 + n rest of the plugin provided data (at least 12 bytes) + 1 \0 byte, terminating the second part of a scramble + + @retval 0 ok + @retval 1 error +*/ +static bool send_server_handshake_packet(MPVIO_EXT *mpvio, + const char *data, uint data_len) +{ + DBUG_ASSERT(mpvio->status == MPVIO_EXT::FAILURE); + DBUG_ASSERT(data_len <= 255); + + char *buff= (char *) my_alloca(1 + SERVER_VERSION_LENGTH + data_len + 64); + char scramble_buf[SCRAMBLE_LENGTH]; + char *end= buff; + + DBUG_ENTER("send_server_handshake_packet"); + *end++= protocol_version; + + mpvio->client_capabilities= CLIENT_BASIC_FLAGS; + + if (opt_using_transactions) + mpvio->client_capabilities|= CLIENT_TRANSACTIONS; + + mpvio->client_capabilities|= CAN_CLIENT_COMPRESS; + + if (ssl_acceptor_fd) + { + mpvio->client_capabilities|= CLIENT_SSL; + mpvio->client_capabilities|= CLIENT_SSL_VERIFY_SERVER_CERT; + } + + if (data_len) + { + mpvio->cached_server_packet.pkt= (char*) memdup_root(mpvio->mem_root, + data, data_len); + mpvio->cached_server_packet.pkt_len= data_len; + } + + if (data_len < SCRAMBLE_LENGTH) + { + if (data_len) + { + /* + the first packet *must* have at least 20 bytes of a scramble. + if a plugin provided less, we pad it to 20 with zeros + */ + memcpy(scramble_buf, data, data_len); + bzero(scramble_buf + data_len, SCRAMBLE_LENGTH - data_len); + data= scramble_buf; + } + else + { + /* + if the default plugin does not provide the data for the scramble at + all, we generate a scramble internally anyway, just in case the + user account (that will be known only later) uses a + native_password_plugin (which needs a scramble). If we don't send a + scramble now - wasting 20 bytes in the packet - + native_password_plugin will have to send it in a separate packet, + adding one more round trip. + */ + create_random_string(mpvio->scramble, SCRAMBLE_LENGTH, mpvio->rand); + data= mpvio->scramble; + } + data_len= SCRAMBLE_LENGTH; + } + + end= strnmov(end, server_version, SERVER_VERSION_LENGTH) + 1; + int4store((uchar*) end, mpvio->thread_id); + end+= 4; + + /* + Old clients does not understand long scrambles, but can ignore packet + tail: that's why first part of the scramble is placed here, and second + part at the end of packet. + */ + end= (char*) memcpy(end, data, SCRAMBLE_LENGTH_323); + end+= SCRAMBLE_LENGTH_323; + *end++= 0; + + int2store(end, mpvio->client_capabilities); + /* write server characteristics: up to 16 bytes allowed */ + end[2]= (char) default_charset_info->number; + int2store(end + 3, mpvio->server_status[0]); + int2store(end + 5, mpvio->client_capabilities >> 16); + end[7]= data_len; + DBUG_EXECUTE_IF("poison_srv_handshake_scramble_len", end[7]= -100;); + bzero(end + 8, 10); + end+= 18; + /* write scramble tail */ + end= (char*) memcpy(end, data + SCRAMBLE_LENGTH_323, + data_len - SCRAMBLE_LENGTH_323); + end+= data_len - SCRAMBLE_LENGTH_323; + end= strmake(end, plugin_name(mpvio->plugin)->str, + plugin_name(mpvio->plugin)->length); + + int res= my_net_write(mpvio->net, (uchar*) buff, (size_t) (end - buff + 1)) || + net_flush(mpvio->net); + my_afree(buff); + DBUG_RETURN (res); +} + +static bool secure_auth(MPVIO_EXT *mpvio) +{ + THD *thd; + if (!opt_secure_auth) + return 0; + /* + If the server is running in secure auth mode, short scrambles are + forbidden. Extra juggling to report the same error as the old code. + */ + + thd= current_thd; + if (mpvio->client_capabilities & CLIENT_PROTOCOL_41) + { + my_error(ER_SERVER_IS_IN_SECURE_AUTH_MODE, MYF(0), + mpvio->auth_info.user_name, + mpvio->auth_info.host_or_ip); + general_log_print(thd, COM_CONNECT, ER(ER_SERVER_IS_IN_SECURE_AUTH_MODE), + mpvio->auth_info.user_name, + mpvio->auth_info.host_or_ip); + } + else + { + my_error(ER_NOT_SUPPORTED_AUTH_MODE, MYF(0)); + general_log_print(thd, COM_CONNECT, ER(ER_NOT_SUPPORTED_AUTH_MODE)); + } + return 1; +} + +/** + sends a "change plugin" packet, requesting a client to restart authentication + using a different authentication plugin + + Packet format: + + Bytes Content + ----- ---- + 1 byte with the value 254 + n client plugin to use, \0-terminated + n plugin provided data + + In a special case of switching from native_password_plugin to + old_password_plugin, the packet contains only one - the first - byte, + plugin name is omitted, plugin data aren't needed as the scramble was + already sent. This one-byte packet is identical to the "use the short + scramble" packet in the protocol before plugins were introduced. + + @retval 0 ok + @retval 1 error +*/ +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->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; + + DBUG_ASSERT(client_auth_plugin); + + /* + we send an old "short 4.0 scramble request", if we need to request a + client to use 4.0 auth plugin (short scramble) and the scramble was + already sent to the client + + below, cached_client_reply.plugin is the plugin name that client has used, + client_auth_plugin is derived from mysql.user table, for the given + user account, it's the plugin that the client need to use to login. + */ + bool switch_from_long_to_short_scramble= + native_password_plugin_name.str == mpvio->cached_client_reply.plugin && + client_auth_plugin == old_password_plugin_name.str; + + if (switch_from_long_to_short_scramble) + DBUG_RETURN (secure_auth(mpvio) || + my_net_write(net, switch_plugin_request_buf, 1) || + net_flush(net)); + + /* + We never request a client to switch from a short to long scramble. + Plugin-aware clients can do that, but traditionally it meant to + ask an old 4.0 client to use the new 4.1 authentication protocol. + */ + bool switch_from_short_to_long_scramble= + old_password_plugin_name.str == mpvio->cached_client_reply.plugin && + client_auth_plugin == native_password_plugin_name.str; + + if (switch_from_short_to_long_scramble) + { + my_error(ER_NOT_SUPPORTED_AUTH_MODE, MYF(0)); + general_log_print(current_thd, COM_CONNECT, ER(ER_NOT_SUPPORTED_AUTH_MODE)); + DBUG_RETURN (1); + } + + /* + If we're dealing with an older client we can't just send a change plugin + packet to re-initiate the authentication handshake, because the client + won't understand it. The good thing is that we don't need to : the old client + expects us to just check the user credentials here, which we can do by just reading + the cached data that are placed there by parse_com_change_user_packet() + In this case we just do nothing and behave as if normal authentication + should continue. + */ + if (!(mpvio->client_capabilities & CLIENT_PLUGIN_AUTH)) + { + DBUG_PRINT("info", ("old client sent a COM_CHANGE_USER")); + DBUG_ASSERT(mpvio->cached_client_reply.pkt); + /* get the status back so the read can process the cached result */ + mpvio->status= MPVIO_EXT::RESTART; + DBUG_RETURN(0); + } + + DBUG_PRINT("info", ("requesting client to use the %s plugin", + client_auth_plugin)); + DBUG_RETURN(net_write_command(net, switch_plugin_request_buf[0], + (uchar*) client_auth_plugin, + strlen(client_auth_plugin) + 1, + (uchar*) data, data_len)); +} + +#ifndef NO_EMBEDDED_ACCESS_CHECKS +/** + Finds acl entry in user database for authentication purposes. + + Finds a user and copies it into mpvio. Reports an authentication + failure if a user is not found. + + @note find_acl_user is not the same, because it doesn't take into + account the case when user is not empty, but acl_user->user is empty + + @retval 0 found + @retval 1 not found +*/ +static bool find_mpvio_user(MPVIO_EXT *mpvio) +{ + DBUG_ENTER("find_mpvio_user"); + DBUG_PRINT("info", ("entry: %s", mpvio->auth_info.user_name)); + DBUG_ASSERT(mpvio->acl_user == 0); + mysql_mutex_lock(&acl_cache->lock); + for (uint i=0; i < acl_users.elements; i++) + { + ACL_USER *acl_user_tmp= dynamic_element(&acl_users, i, ACL_USER*); + if ((!acl_user_tmp->user || + !strcmp(mpvio->auth_info.user_name, acl_user_tmp->user)) && + compare_hostname(&acl_user_tmp->host, mpvio->host, mpvio->ip)) + { + mpvio->acl_user= acl_user_tmp->copy(mpvio->mem_root); + if (acl_user_tmp->plugin.str == native_password_plugin_name.str || + acl_user_tmp->plugin.str == old_password_plugin_name.str) + mpvio->acl_user_plugin= acl_user_tmp->plugin; + else + make_lex_string_root(mpvio->mem_root, + &mpvio->acl_user_plugin, + acl_user_tmp->plugin.str, + acl_user_tmp->plugin.length, 0); + break; + } + } + mysql_mutex_unlock(&acl_cache->lock); + + if (!mpvio->acl_user) + { + login_failed_error(mpvio, mpvio->auth_info.password_used); + DBUG_RETURN (1); + } + + /* 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 && + !(mpvio->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)); + my_error(ER_NOT_SUPPORTED_AUTH_MODE, MYF(0)); + general_log_print(current_thd, COM_CONNECT, ER(ER_NOT_SUPPORTED_AUTH_MODE)); + DBUG_RETURN (1); + } + + mpvio->auth_info.auth_string= mpvio->acl_user->auth_string.str; + mpvio->auth_info.auth_string_length= + (unsigned long) mpvio->acl_user->auth_string.length; + strmake(mpvio->auth_info.authenticated_as, mpvio->acl_user->user ? + mpvio->acl_user->user : "", USERNAME_LENGTH); + 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); +} +#endif + +/* the packet format is described in send_change_user_packet() */ +static bool parse_com_change_user_packet(MPVIO_EXT *mpvio, uint packet_length) +{ + NET *net= mpvio->net; + + char *user= (char*) net->read_pos; + char *end= user + packet_length; + /* Safe because there is always a trailing \0 at the end of the packet */ + char *passwd= strend(user) + 1; + uint user_len= passwd - user - 1; + char *db= passwd; + char db_buff[NAME_LEN + 1]; // buffer to store db in utf8 + char user_buff[USERNAME_LENGTH + 1]; // buffer to store user in utf8 + uint dummy_errors; + + DBUG_ENTER ("parse_com_change_user_packet"); + if (passwd >= end) + { + my_message(ER_UNKNOWN_COM_ERROR, ER(ER_UNKNOWN_COM_ERROR), MYF(0)); + DBUG_RETURN (1); + } + + /* + Old clients send null-terminated string as password; new clients send + the size (1 byte) + string (not null-terminated). Hence in case of empty + password both send '\0'. + + This strlen() can't be easily deleted without changing protocol. + + Cast *passwd to an unsigned char, so that it doesn't extend the sign for + *passwd > 127 and become 2**32-127+ after casting to uint. + */ + uint passwd_len= (mpvio->client_capabilities & CLIENT_SECURE_CONNECTION ? + (uchar) (*passwd++) : strlen(passwd)); + + db+= passwd_len + 1; + /* + Database name is always NUL-terminated, so in case of empty database + the packet must contain at least the trailing '\0'. + */ + if (db >= end) + { + my_message(ER_UNKNOWN_COM_ERROR, ER(ER_UNKNOWN_COM_ERROR), MYF(0)); + DBUG_RETURN (1); + } + + uint db_len= strlen(db); + + char *ptr= db + db_len + 1; + + if (ptr + 1 < end) + { + if (mpvio->charset_adapter->init_client_charset(uint2korr(ptr))) + DBUG_RETURN(1); + } + + + /* Convert database and user names to utf8 */ + db_len= copy_and_convert(db_buff, sizeof(db_buff) - 1, system_charset_info, + db, db_len, mpvio->charset_adapter->charset(), + &dummy_errors); + db_buff[db_len]= 0; + + user_len= copy_and_convert(user_buff, sizeof(user_buff) - 1, + system_charset_info, user, user_len, + mpvio->charset_adapter->charset(), + &dummy_errors); + user_buff[user_len]= 0; + + /* we should not free mpvio->user here: it's saved by dispatch_command() */ + if (!(mpvio->auth_info.user_name= my_strndup(user_buff, user_len, MYF(MY_WME)))) + return 1; + mpvio->auth_info.user_name_length= user_len; + + if (make_lex_string_root(mpvio->mem_root, + &mpvio->db, db_buff, db_len, 0) == 0) + DBUG_RETURN(1); /* The error is set by make_lex_string(). */ + + if (!initialized) + { + // if mysqld's been started with --skip-grant-tables option + strmake(mpvio->auth_info.authenticated_as, + mpvio->auth_info.user_name, USERNAME_LENGTH); + + mpvio->status= MPVIO_EXT::SUCCESS; + DBUG_RETURN(0); + } + +#ifndef NO_EMBEDDED_ACCESS_CHECKS + if (find_mpvio_user(mpvio)) + DBUG_RETURN(1); + + char *client_plugin; + if (mpvio->client_capabilities & CLIENT_PLUGIN_AUTH) + { + client_plugin= ptr + 2; + if (client_plugin >= end) + { + my_message(ER_UNKNOWN_COM_ERROR, ER(ER_UNKNOWN_COM_ERROR), MYF(0)); + DBUG_RETURN(1); + } + } + else + { + if (mpvio->client_capabilities & CLIENT_SECURE_CONNECTION) + 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. + */ + if (mpvio->acl_user->salt_len == 0) + mpvio->acl_user_plugin= old_password_plugin_name; + } + } + + DBUG_PRINT("info", ("client_plugin=%s, restart", client_plugin)); + /* + Remember the data part of the packet, to present it to plugin in + read_packet() + */ + mpvio->cached_client_reply.pkt= passwd; + mpvio->cached_client_reply.pkt_len= passwd_len; + mpvio->cached_client_reply.plugin= client_plugin; + mpvio->status= MPVIO_EXT::RESTART; +#endif + + DBUG_RETURN (0); +} + +#ifndef EMBEDDED_LIBRARY + +/** Get a string according to the protocol of the underlying buffer. */ +typedef char * (*get_proto_string_func_t) (char **, size_t *, size_t *); + +/** + Get a string formatted according to the 4.1 version of the MySQL protocol. + + @param buffer[in, out] Pointer to the user-supplied buffer to be scanned. + @param max_bytes_available[in, out] Limit the bytes to scan. + @param string_length[out] The number of characters scanned not including + the null character. + + @remark Strings are always null character terminated in this version of the + protocol. + + @remark The string_length does not include the terminating null character. + However, after the call, the buffer is increased by string_length+1 + bytes, beyond the null character if there still available bytes to + scan. + + @return pointer to beginning of the string scanned. + @retval NULL The buffer content is malformed +*/ + +static +char *get_41_protocol_string(char **buffer, + size_t *max_bytes_available, + size_t *string_length) +{ + char *str= (char *)memchr(*buffer, '\0', *max_bytes_available); + + if (str == NULL) + return NULL; + + *string_length= (size_t)(str - *buffer); + *max_bytes_available-= *string_length + 1; + str= *buffer; + *buffer += *string_length + 1; + + return str; +} + + +/** + Get a string formatted according to the 4.0 version of the MySQL protocol. + + @param buffer[in, out] Pointer to the user-supplied buffer to be scanned. + @param max_bytes_available[in, out] Limit the bytes to scan. + @param string_length[out] The number of characters scanned not including + the null character. + + @remark If there are not enough bytes left after the current position of + the buffer to satisfy the current string, the string is considered + to be empty and a pointer to empty_c_string is returned. + + @remark A string at the end of the packet is not null terminated. + + @return Pointer to beginning of the string scanned, or a pointer to a empty + string. +*/ +static +char *get_40_protocol_string(char **buffer, + size_t *max_bytes_available, + size_t *string_length) +{ + char *str; + size_t len; + + /* No bytes to scan left, treat string as empty. */ + if ((*max_bytes_available) == 0) + { + *string_length= 0; + return empty_c_string; + } + + str= (char *) memchr(*buffer, '\0', *max_bytes_available); + + /* + If the string was not null terminated by the client, + the remainder of the packet is the string. Otherwise, + advance the buffer past the end of the null terminated + string. + */ + if (str == NULL) + len= *string_length= *max_bytes_available; + else + len= (*string_length= (size_t)(str - *buffer)) + 1; + + str= *buffer; + *buffer+= len; + *max_bytes_available-= len; + + return str; +} + +/** + Get a length encoded string from a user-supplied buffer. + + @param buffer[in, out] The buffer to scan; updates position after scan. + @param max_bytes_available[in, out] Limit the number of bytes to scan + @param string_length[out] Number of characters scanned + + @remark In case the length is zero, then the total size of the string is + considered to be 1 byte; the size byte. + + @return pointer to first byte after the header in buffer. + @retval NULL The buffer content is malformed +*/ + +static +char *get_length_encoded_string(char **buffer, + size_t *max_bytes_available, + size_t *string_length) +{ + if (*max_bytes_available == 0) + return NULL; + + /* Do double cast to prevent overflow from signed / unsigned conversion */ + size_t str_len= (size_t)(unsigned char)**buffer; + + /* + If the length encoded string has the length 0 + the total size of the string is only one byte long (the size byte) + */ + if (str_len == 0) + { + ++*buffer; + *string_length= 0; + /* + Return a pointer to the 0 character so the return value will be + an empty string. + */ + return *buffer-1; + } + + if (str_len >= *max_bytes_available) + return NULL; + + char *str= *buffer+1; + *string_length= str_len; + *max_bytes_available-= *string_length + 1; + *buffer+= *string_length + 1; + return str; +} +#endif + + +/* the packet format is described in send_client_reply_packet() */ +static ulong parse_client_handshake_packet(MPVIO_EXT *mpvio, + uchar **buff, ulong pkt_len) +{ +#ifndef EMBEDDED_LIBRARY + NET *net= mpvio->net; + char *end; + bool packet_has_required_size= false; + DBUG_ASSERT(mpvio->status == MPVIO_EXT::FAILURE); + + + uint charset_code= 0; + end= (char *)net->read_pos; + /* + In order to safely scan a head for '\0' string terminators + we must keep track of how many bytes remain in the allocated + buffer or we might read past the end of the buffer. + */ + size_t bytes_remaining_in_packet= pkt_len; + + DBUG_EXECUTE_IF("host_error_packet_length", + { + bytes_remaining_in_packet= 0; + };); + + /* + Peek ahead on the client capability packet and determine which version of + the protocol should be used. + */ + if (bytes_remaining_in_packet < 2) + return packet_error; + + mpvio->client_capabilities= uint2korr(end); + + /* + JConnector only sends server capabilities before starting SSL + negotiation. The below code is patch for this. + */ + if (bytes_remaining_in_packet == 4 && + mpvio->client_capabilities & CLIENT_SSL) + { + mpvio->client_capabilities= uint4korr(end); + mpvio->max_client_packet_length= 0xfffff; + charset_code= default_charset_info->number; + if (mpvio->charset_adapter->init_client_charset(charset_code)) + return packet_error; + goto skip_to_ssl; + } + + if (mpvio->client_capabilities & CLIENT_PROTOCOL_41) + packet_has_required_size= bytes_remaining_in_packet >= + AUTH_PACKET_HEADER_SIZE_PROTO_41; + else + packet_has_required_size= bytes_remaining_in_packet >= + AUTH_PACKET_HEADER_SIZE_PROTO_40; + + if (!packet_has_required_size) + return packet_error; + + if (mpvio->client_capabilities & CLIENT_PROTOCOL_41) + { + mpvio->client_capabilities= uint4korr(end); + mpvio->max_client_packet_length= uint4korr(end + 4); + charset_code= (uint)(uchar)*(end + 8); + /* + Skip 23 remaining filler bytes which have no particular meaning. + */ + end+= AUTH_PACKET_HEADER_SIZE_PROTO_41; + bytes_remaining_in_packet-= AUTH_PACKET_HEADER_SIZE_PROTO_41; + } + else + { + mpvio->client_capabilities= uint2korr(end); + mpvio->max_client_packet_length= uint3korr(end + 2); + end+= AUTH_PACKET_HEADER_SIZE_PROTO_40; + bytes_remaining_in_packet-= AUTH_PACKET_HEADER_SIZE_PROTO_40; + /** + Old clients didn't have their own charset. Instead the assumption + was that they used what ever the server used. + */ + charset_code= default_charset_info->number; + } + DBUG_EXECUTE_IF("host_error_charset", + { + return packet_error; + };); + + + DBUG_PRINT("info", ("client_character_set: %u", charset_code)); + if (mpvio->charset_adapter->init_client_charset(charset_code)) + return packet_error; + +skip_to_ssl: +#if defined(HAVE_OPENSSL) + DBUG_PRINT("info", ("client capabilities: %lu", mpvio->client_capabilities)); + + /* + If client requested SSL then we must stop parsing, try to switch to SSL, + and wait for the client to send a new handshake packet. + The client isn't expected to send any more bytes until SSL is initialized. + */ + if (mpvio->client_capabilities & CLIENT_SSL) + { + unsigned long errptr; + + /* Do the SSL layering. */ + if (!ssl_acceptor_fd) + return packet_error; + + DBUG_PRINT("info", ("IO layer change in progress...")); + if (sslaccept(ssl_acceptor_fd, net->vio, net->read_timeout, &errptr)) + { + DBUG_PRINT("error", ("Failed to accept new SSL connection")); + return packet_error; + } + + DBUG_PRINT("info", ("Reading user information over SSL layer")); + if ((pkt_len= my_net_read(net)) == packet_error) + { + DBUG_PRINT("error", ("Failed to read user information (pkt_len= %lu)", + pkt_len)); + return packet_error; + } + /* + A new packet was read and the statistics reflecting the remaining bytes + in the packet must be updated. + */ + bytes_remaining_in_packet= pkt_len; + + /* + After the SSL handshake is performed the client resends the handshake + packet but because of legacy reasons we chose not to parse the packet + fields a second time and instead only assert the length of the packet. + */ + if (mpvio->client_capabilities & CLIENT_PROTOCOL_41) + { + packet_has_required_size= bytes_remaining_in_packet >= + AUTH_PACKET_HEADER_SIZE_PROTO_41; + end= (char *)net->read_pos + AUTH_PACKET_HEADER_SIZE_PROTO_41; + bytes_remaining_in_packet -= AUTH_PACKET_HEADER_SIZE_PROTO_41; + } + else + { + packet_has_required_size= bytes_remaining_in_packet >= + AUTH_PACKET_HEADER_SIZE_PROTO_40; + end= (char *)net->read_pos + AUTH_PACKET_HEADER_SIZE_PROTO_40; + bytes_remaining_in_packet -= AUTH_PACKET_HEADER_SIZE_PROTO_40; + } + + DBUG_EXECUTE_IF("host_error_SSL_layering", + { + packet_has_required_size= 0; + };); + + if (!packet_has_required_size) + return packet_error; + } +#endif /* HAVE_OPENSSL */ + + if ((mpvio->client_capabilities & CLIENT_TRANSACTIONS) && + opt_using_transactions) + net->return_status= mpvio->server_status; + + /* + The 4.0 and 4.1 versions of the protocol differ on how strings + are terminated. In the 4.0 version, if a string is at the end + of the packet, the string is not null terminated. Do not assume + that the returned string is always null terminated. + */ + get_proto_string_func_t get_string; + + if (mpvio->client_capabilities & CLIENT_PROTOCOL_41) + get_string= get_41_protocol_string; + else + get_string= get_40_protocol_string; + + /* + In order to safely scan a head for '\0' string terminators + we must keep track of how many bytes remain in the allocated + buffer or we might read past the end of the buffer. + */ + bytes_remaining_in_packet= pkt_len - (end - (char *)net->read_pos); + + size_t user_len; + char *user= get_string(&end, &bytes_remaining_in_packet, &user_len); + DBUG_EXECUTE_IF("host_error_user", + { + user= NULL; + };); + + if (user == NULL) + return packet_error; + + /* + Old clients send a null-terminated string as password; new clients send + the size (1 byte) + string (not null-terminated). Hence in case of empty + password both send '\0'. + */ + size_t passwd_len= 0; + char *passwd= NULL; + + if (mpvio->client_capabilities & CLIENT_SECURE_CONNECTION) + { + /* + 4.1+ password. First byte is password length. + */ + passwd= get_length_encoded_string(&end, &bytes_remaining_in_packet, + &passwd_len); + } + else + { + /* + Old passwords are zero terminated strings. + */ + passwd= get_string(&end, &bytes_remaining_in_packet, &passwd_len); + } + + DBUG_EXECUTE_IF("host_error_password", + { + passwd= NULL; + };); + + if (passwd == NULL) + return packet_error; + + size_t db_len= 0; + char *db= NULL; + + if (mpvio->client_capabilities & CLIENT_CONNECT_WITH_DB) + { + db= get_string(&end, &bytes_remaining_in_packet, &db_len); + if (db == NULL) + return packet_error; + } + + /* + Set the default for the password supplied flag for non-existing users + as the default plugin (native passsword authentication) would do it + for compatibility reasons. + */ + if (passwd_len) + mpvio->auth_info.password_used= PASSWORD_USED_YES; + + size_t client_plugin_len= 0; + char *client_plugin= get_string(&end, &bytes_remaining_in_packet, + &client_plugin_len); + if (client_plugin == NULL) + client_plugin= &empty_c_string[0]; + + char db_buff[NAME_LEN + 1]; // buffer to store db in utf8 + char user_buff[USERNAME_LENGTH + 1]; // buffer to store user in utf8 + uint dummy_errors; + + + /* + Copy and convert the user and database names to the character set used + by the server. Since 4.1 all database names are stored in UTF-8. Also, + ensure that the names are properly null-terminated as this is relied + upon later. + */ + if (db) + { + db_len= copy_and_convert(db_buff, sizeof(db_buff) - 1, system_charset_info, + db, db_len, mpvio->charset_adapter->charset(), + &dummy_errors); + db_buff[db_len]= '\0'; + db= db_buff; + } + + user_len= copy_and_convert(user_buff, sizeof(user_buff) - 1, + system_charset_info, user, user_len, + mpvio->charset_adapter->charset(), + &dummy_errors); + user_buff[user_len]= '\0'; + user= user_buff; + + /* If username starts and ends in "'", chop them off */ + if (user_len > 1 && user[0] == '\'' && user[user_len - 1] == '\'') + { + user[user_len - 1]= 0; + user++; + user_len-= 2; + } + + if (make_lex_string_root(mpvio->mem_root, + &mpvio->db, db, db_len, 0) == 0) + return packet_error; /* The error is set by make_lex_string(). */ + if (mpvio->auth_info.user_name) + my_free(mpvio->auth_info.user_name); + if (!(mpvio->auth_info.user_name= my_strndup(user, user_len, MYF(MY_WME)))) + return packet_error; /* The error is set by my_strdup(). */ + mpvio->auth_info.user_name_length= user_len; + + if (!initialized) + { + // if mysqld's been started with --skip-grant-tables option + mpvio->status= MPVIO_EXT::SUCCESS; + return packet_error; + } + + if (find_mpvio_user(mpvio)) + return packet_error; + + if (!(mpvio->client_capabilities & CLIENT_PLUGIN_AUTH)) + { + /* + An old client is connecting + */ + if (mpvio->client_capabilities & CLIENT_SECURE_CONNECTION) + client_plugin= native_password_plugin_name.str; + else + { + /* + A really old client is connecting + */ + 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. + */ + if (mpvio->acl_user->salt_len == 0) + mpvio->acl_user_plugin= old_password_plugin_name; + } + } + + /* + if the acl_user needs a different plugin to authenticate + (specified in GRANT ... AUTHENTICATED VIA plugin_name ..) + we need to restart the authentication in the server. + But perhaps the client has already used the correct plugin - + in that case the authentication on the client may not need to be + 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 (my_strcasecmp(system_charset_info, mpvio->acl_user_plugin.str, + plugin_name(mpvio->plugin)->str) != 0) + { + mpvio->cached_client_reply.pkt= passwd; + mpvio->cached_client_reply.pkt_len= passwd_len; + mpvio->cached_client_reply.plugin= client_plugin; + mpvio->status= MPVIO_EXT::RESTART; + return packet_error; + } + + /* + ok, we don't need to restart the authentication on the server. + but if the client used the wrong plugin, we need to restart + the authentication on the client. Do it here, the server plugin + doesn't need to know. + */ + const char *client_auth_plugin= + ((st_mysql_auth *) (plugin_decl(mpvio->plugin)->info))->client_auth_plugin; + + if (client_auth_plugin && + my_strcasecmp(system_charset_info, client_plugin, client_auth_plugin)) + { + mpvio->cached_client_reply.plugin= client_plugin; + if (send_plugin_request_packet(mpvio, + (uchar*) mpvio->cached_server_packet.pkt, + mpvio->cached_server_packet.pkt_len)) + return packet_error; + + passwd_len= my_net_read(mpvio->net); + passwd = (char*) mpvio->net->read_pos; + } + + *buff= (uchar*) passwd; + return passwd_len; +#else + return 0; +#endif +} + + +/** + Make sure that when sending plugin supplied data to the client they + are not considered a special out-of-band command, like e.g. + \255 (error) or \254 (change user request packet) or \0 (OK). + To avoid this the server will send all plugin data packets "wrapped" + in a command \1. + Note that the client will continue sending its replies unrwapped. +*/ + +static inline int +wrap_plguin_data_into_proper_command(NET *net, + const uchar *packet, int packet_len) +{ + return net_write_command(net, 1, (uchar *) "", 0, packet, packet_len); +} + + +/** + vio->write_packet() callback method for server authentication plugins + + This function is called by a server authentication plugin, when it wants + to send data to the client. + + It transparently wraps the data into a handshake packet, + and handles plugin negotiation with the client. If necessary, + it escapes the plugin data, if it starts with a mysql protocol packet byte. +*/ +static int server_mpvio_write_packet(MYSQL_PLUGIN_VIO *param, + const uchar *packet, int packet_len) +{ + MPVIO_EXT *mpvio= (MPVIO_EXT *) param; + int res; + + DBUG_ENTER("server_mpvio_write_packet"); + /* + Reset cached_client_reply if not an old client doing mysql_change_user, + as this is where the password from COM_CHANGE_USER is stored. + */ + if (!((!(mpvio->client_capabilities & CLIENT_PLUGIN_AUTH)) && + mpvio->status == MPVIO_EXT::RESTART && + mpvio->cached_client_reply.plugin == + ((st_mysql_auth *) (plugin_decl(mpvio->plugin)->info))->client_auth_plugin + )) + mpvio->cached_client_reply.pkt= 0; + /* for the 1st packet we wrap plugin data into the handshake packet */ + if (mpvio->packets_written == 0) + res= send_server_handshake_packet(mpvio, (char*) packet, packet_len); + else if (mpvio->status == MPVIO_EXT::RESTART) + res= send_plugin_request_packet(mpvio, packet, packet_len); + else + res= wrap_plguin_data_into_proper_command(mpvio->net, packet, packet_len); + mpvio->packets_written++; + DBUG_RETURN(res); +} + +/** + vio->read_packet() callback method for server authentication plugins + + This function is called by a server authentication plugin, when it wants + to read data from the client. + + It transparently extracts the client plugin data, if embedded into + a client authentication handshake packet, and handles plugin negotiation + with the client, if necessary. +*/ +static int server_mpvio_read_packet(MYSQL_PLUGIN_VIO *param, uchar **buf) +{ + MPVIO_EXT *mpvio= (MPVIO_EXT *) param; + ulong pkt_len; + + DBUG_ENTER("server_mpvio_read_packet"); + if (mpvio->packets_written == 0) + { + /* + plugin wants to read the data without sending anything first. + send an empty packet to force a server handshake packet to be sent + */ + if (mpvio->write_packet(mpvio, 0, 0)) + pkt_len= packet_error; + else + pkt_len= my_net_read(mpvio->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; + *buf= (uchar*) mpvio->cached_client_reply.pkt; + mpvio->cached_client_reply.pkt= 0; + mpvio->packets_read++; + DBUG_RETURN ((int) mpvio->cached_client_reply.pkt_len); + } + + /* older clients don't support change of client plugin request */ + if (!(mpvio->client_capabilities & CLIENT_PLUGIN_AUTH)) + { + mpvio->status= MPVIO_EXT::FAILURE; + pkt_len= packet_error; + goto err; + } + + /* + 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 (mpvio->write_packet(mpvio, 0, 0)) + pkt_len= packet_error; + else + pkt_len= my_net_read(mpvio->net); + } + else + pkt_len= my_net_read(mpvio->net); + + if (pkt_len == packet_error) + goto err; + + mpvio->packets_read++; + + /* + the 1st packet has the plugin data wrapped into the client authentication + handshake packet + */ + if (mpvio->packets_read == 1) + { + pkt_len= parse_client_handshake_packet(mpvio, buf, pkt_len); + if (pkt_len == packet_error) + goto err; + } + else + *buf= mpvio->net->read_pos; + + DBUG_RETURN((int)pkt_len); + +err: + if (mpvio->status == MPVIO_EXT::FAILURE) + { + inc_host_errors(mpvio->ip); + my_error(ER_HANDSHAKE_ERROR, MYF(0)); + } + DBUG_RETURN(-1); +} + +/** + fills MYSQL_PLUGIN_VIO_INFO structure with the information about the + connection +*/ +static void server_mpvio_info(MYSQL_PLUGIN_VIO *vio, + MYSQL_PLUGIN_VIO_INFO *info) +{ + MPVIO_EXT *mpvio= (MPVIO_EXT *) vio; + mpvio_info(mpvio->net->vio, info); +} + +#ifndef NO_EMBEDDED_ACCESS_CHECKS +static bool acl_check_ssl(THD *thd, const ACL_USER *acl_user) +{ +#if defined(HAVE_OPENSSL) + Vio *vio= thd->net.vio; + SSL *ssl= (SSL *) vio->ssl_arg; + X509 *cert; +#endif + + /* + At this point we know that user is allowed to connect + from given host by given username/password pair. Now + we check if SSL is required, if user is using SSL and + if X509 certificate attributes are OK + */ + switch (acl_user->ssl_type) { + case SSL_TYPE_NOT_SPECIFIED: // Impossible + case SSL_TYPE_NONE: // SSL is not required + return 0; +#if defined(HAVE_OPENSSL) + case SSL_TYPE_ANY: // Any kind of SSL is ok + return vio_type(vio) != VIO_TYPE_SSL; + case SSL_TYPE_X509: /* Client should have any valid certificate. */ + /* + Connections with non-valid certificates are dropped already + in sslaccept() anyway, so we do not check validity here. + + We need to check for absence of SSL because without SSL + we should reject connection. + */ + if (vio_type(vio) == VIO_TYPE_SSL && + SSL_get_verify_result(ssl) == X509_V_OK && + (cert= SSL_get_peer_certificate(ssl))) + { + X509_free(cert); + return 0; + } + return 1; + case SSL_TYPE_SPECIFIED: /* Client should have specified attrib */ + /* If a cipher name is specified, we compare it to actual cipher in use. */ + if (vio_type(vio) != VIO_TYPE_SSL || + SSL_get_verify_result(ssl) != X509_V_OK) + return 1; + if (acl_user->ssl_cipher) + { + DBUG_PRINT("info", ("comparing ciphers: '%s' and '%s'", + acl_user->ssl_cipher, SSL_get_cipher(ssl))); + if (strcmp(acl_user->ssl_cipher, SSL_get_cipher(ssl))) + { + if (global_system_variables.log_warnings) + sql_print_information("X509 ciphers mismatch: should be '%s' but is '%s'", + acl_user->ssl_cipher, SSL_get_cipher(ssl)); + return 1; + } + } + /* Prepare certificate (if exists) */ + if (!(cert= SSL_get_peer_certificate(ssl))) + return 1; + /* If X509 issuer is specified, we check it... */ + if (acl_user->x509_issuer) + { + char *ptr= X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0); + DBUG_PRINT("info", ("comparing issuers: '%s' and '%s'", + acl_user->x509_issuer, ptr)); + if (strcmp(acl_user->x509_issuer, ptr)) + { + if (global_system_variables.log_warnings) + sql_print_information("X509 issuer mismatch: should be '%s' " + "but is '%s'", acl_user->x509_issuer, ptr); + free(ptr); + X509_free(cert); + return 1; + } + free(ptr); + } + /* X509 subject is specified, we check it .. */ + if (acl_user->x509_subject) + { + char *ptr= X509_NAME_oneline(X509_get_subject_name(cert), 0, 0); + DBUG_PRINT("info", ("comparing subjects: '%s' and '%s'", + acl_user->x509_subject, ptr)); + if (strcmp(acl_user->x509_subject, ptr)) + { + if (global_system_variables.log_warnings) + sql_print_information("X509 subject mismatch: should be '%s' but is '%s'", + acl_user->x509_subject, ptr); + free(ptr); + X509_free(cert); + return 1; + } + free(ptr); + } + X509_free(cert); + return 0; +#else /* HAVE_OPENSSL */ + default: + /* + If we don't have SSL but SSL is required for this user the + authentication should fail. + */ + return 1; +#endif /* HAVE_OPENSSL */ + } + return 1; +} +#endif + + +static int do_auth_once(THD *thd, const LEX_STRING *auth_plugin_name, + MPVIO_EXT *mpvio) +{ + int res= CR_OK, old_status= MPVIO_EXT::FAILURE; + bool unlock_plugin= false; + plugin_ref plugin; + + if (auth_plugin_name->str == native_password_plugin_name.str) + plugin= native_password_plugin; + else +#ifndef EMBEDDED_LIBRARY + if (auth_plugin_name->str == old_password_plugin_name.str) + plugin= old_password_plugin; + else if ((plugin= my_plugin_lock_by_name(thd, auth_plugin_name, + MYSQL_AUTHENTICATION_PLUGIN))) + unlock_plugin= true; + else +#endif + plugin= NULL; + + mpvio->plugin= plugin; + old_status= mpvio->status; + + if (plugin) + { + st_mysql_auth *auth= (st_mysql_auth *) plugin_decl(plugin)->info; + res= auth->authenticate_user(mpvio, &mpvio->auth_info); + + if (unlock_plugin) + plugin_unlock(thd, plugin); + } + else + { + /* Server cannot load the required plugin. */ + my_error(ER_PLUGIN_IS_NOT_LOADED, MYF(0), auth_plugin_name->str); + 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; +} + + +static void +server_mpvio_initialize(THD *thd, MPVIO_EXT *mpvio, uint connect_errors, + Thd_charset_adapter *charset_adapter) +{ + memset(mpvio, 0, sizeof(MPVIO_EXT)); + mpvio->read_packet= server_mpvio_read_packet; + mpvio->write_packet= server_mpvio_write_packet; + mpvio->info= server_mpvio_info; + mpvio->auth_info.host_or_ip= thd->security_ctx->host_or_ip; + mpvio->auth_info.host_or_ip_length= + (unsigned int) strlen(thd->security_ctx->host_or_ip); + mpvio->auth_info.user_name= NULL; + mpvio->auth_info.user_name_length= 0; + mpvio->connect_errors= connect_errors; + mpvio->status= MPVIO_EXT::FAILURE; + + mpvio->client_capabilities= thd->client_capabilities; + mpvio->mem_root= thd->mem_root; + mpvio->scramble= thd->scramble; + mpvio->rand= &thd->rand; + mpvio->thread_id= thd->thread_id; + mpvio->server_status= &thd->server_status; + mpvio->net= &thd->net; + mpvio->ip= thd->security_ctx->ip; + mpvio->host= thd->security_ctx->host; + mpvio->charset_adapter= charset_adapter; +} + + +static void +server_mpvio_update_thd(THD *thd, MPVIO_EXT *mpvio) +{ + thd->client_capabilities= mpvio->client_capabilities; + thd->max_client_packet_length= mpvio->max_client_packet_length; + if (mpvio->client_capabilities & CLIENT_INTERACTIVE) + thd->variables.net_wait_timeout= thd->variables.net_interactive_timeout; + thd->security_ctx->user= mpvio->auth_info.user_name; + if (thd->client_capabilities & CLIENT_IGNORE_SPACE) + thd->variables.sql_mode|= MODE_IGNORE_SPACE; +} + +/** + Perform the handshake, authorize the client and update thd sctx variables. + + @param thd thread handle + @param connect_errors number of previous failed connect attemps + from this host + @param com_change_user_pkt_len size of the COM_CHANGE_USER packet + (without the first, command, byte) or 0 + if it's not a COM_CHANGE_USER (that is, if + it's a new connection) + + @retval 0 success, thd is updated. + @retval 1 error +*/ +bool +acl_authenticate(THD *thd, uint connect_errors, uint com_change_user_pkt_len) +{ + int res= CR_OK; + MPVIO_EXT mpvio; + Thd_charset_adapter charset_adapter(thd); + + const LEX_STRING *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"); + compile_time_assert(MYSQL_USERNAME_LENGTH == USERNAME_LENGTH); + + server_mpvio_initialize(thd, &mpvio, connect_errors, &charset_adapter); + + DBUG_PRINT("info", ("com_change_user_pkt_len=%u", com_change_user_pkt_len)); + + /* + Clear thd->db as it points to something, that will be freed when + connection is closed. We don't want to accidentally free a wrong + pointer if connect failed. + */ + thd->reset_db(NULL, 0); + + if (command == COM_CHANGE_USER) + { + mpvio.packets_written++; // pretend that a server handshake packet was sent + mpvio.packets_read++; // take COM_CHANGE_USER packet into account + + /* Clear variables that are allocated */ + thd->set_user_connect(NULL); + + if (parse_com_change_user_packet(&mpvio, com_change_user_pkt_len)) + { + server_mpvio_update_thd(thd, &mpvio); + DBUG_RETURN(1); + } + + DBUG_ASSERT(mpvio.status == MPVIO_EXT::RESTART || + mpvio.status == MPVIO_EXT::SUCCESS); + } + else + { + /* mark the thd as having no scramble yet */ + mpvio.scramble[SCRAMBLE_LENGTH]= 1; + + /* + perform the first authentication attempt, with the default plugin. + This sends the server handshake packet, reads the client reply + with a user name, and performs the authentication if everyone has used + the correct plugin. + */ + + res= do_auth_once(thd, 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) + { + DBUG_ASSERT(mpvio.acl_user); + DBUG_ASSERT(command == COM_CHANGE_USER || + my_strcasecmp(system_charset_info, auth_plugin_name->str, + mpvio.acl_user->plugin.str)); + auth_plugin_name= &mpvio.acl_user->plugin; + res= do_auth_once(thd, auth_plugin_name, &mpvio); + } + + server_mpvio_update_thd(thd, &mpvio); + + 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 + + /* + Log the command here so that the user can check the log + for the tried logins and also to detect break-in attempts. + + if sctx->user is unset it's protocol failure, bad packet. + */ + if (mpvio.auth_info.user_name) + { + if (strcmp(mpvio.auth_info.authenticated_as, mpvio.auth_info.user_name)) + { + general_log_print(thd, command, "%s@%s as %s on %s", + mpvio.auth_info.user_name, mpvio.auth_info.host_or_ip, + mpvio.auth_info.authenticated_as ? + mpvio.auth_info.authenticated_as : "anonymous", + mpvio.db.str ? mpvio.db.str : (char*) ""); + } + else + general_log_print(thd, command, (char*) "%s@%s on %s", + mpvio.auth_info.user_name, mpvio.auth_info.host_or_ip, + mpvio.db.str ? mpvio.db.str : (char*) ""); + } + + if (res > CR_OK && mpvio.status != MPVIO_EXT::SUCCESS) + { + DBUG_ASSERT(mpvio.status == MPVIO_EXT::FAILURE); + + if (!thd->is_error()) + login_failed_error(&mpvio, mpvio.auth_info.password_used); + DBUG_RETURN (1); + } + + sctx->proxy_user[0]= 0; + + if (initialized) // if not --skip-grant-tables + { +#ifndef NO_EMBEDDED_ACCESS_CHECKS + bool is_proxy_user= FALSE; + const char *auth_user = acl_user->user ? acl_user->user : ""; + ACL_PROXY_USER *proxy_user; + /* check if the user is allowed to proxy as another user */ + proxy_user= acl_find_proxy_user(auth_user, sctx->host, sctx->ip, + mpvio.auth_info.authenticated_as, + &is_proxy_user); + if (is_proxy_user) + { + ACL_USER *acl_proxy_user; + + /* we need to find the proxy user, but there was none */ + if (!proxy_user) + { + if (!thd->is_error()) + login_failed_error(&mpvio, mpvio.auth_info.password_used); + DBUG_RETURN(1); + } + + my_snprintf(sctx->proxy_user, sizeof(sctx->proxy_user) - 1, + "'%s'@'%s'", auth_user, + acl_user->host.hostname ? acl_user->host.hostname : ""); + + /* we're proxying : find the proxy user definition */ + mysql_mutex_lock(&acl_cache->lock); + acl_proxy_user= find_acl_user(proxy_user->get_proxied_host() ? + proxy_user->get_proxied_host() : "", + mpvio.auth_info.authenticated_as, TRUE); + if (!acl_proxy_user) + { + if (!thd->is_error()) + login_failed_error(&mpvio, mpvio.auth_info.password_used); + mysql_mutex_unlock(&acl_cache->lock); + DBUG_RETURN(1); + } + acl_user= acl_proxy_user->copy(thd->mem_root); + mysql_mutex_unlock(&acl_cache->lock); + } +#endif + + sctx->master_access= acl_user->access; + if (acl_user->user) + strmake(sctx->priv_user, acl_user->user, USERNAME_LENGTH - 1); + else + *sctx->priv_user= 0; + + if (acl_user->host.hostname) + strmake(sctx->priv_host, acl_user->host.hostname, MAX_HOSTNAME - 1); + else + *sctx->priv_host= 0; + +#ifndef NO_EMBEDDED_ACCESS_CHECKS + /* + OK. Let's check the SSL. Historically it was checked after the password, + as an additional layer, not instead of the password + (in which case it would've been a plugin too). + */ + if (acl_check_ssl(thd, acl_user)) + { + if (!thd->is_error()) + login_failed_error(&mpvio, thd->password); + DBUG_RETURN(1); + } + + /* Don't allow the user to connect if he has done too many queries */ + if ((acl_user->user_resource.questions || acl_user->user_resource.updates || + acl_user->user_resource.conn_per_hour || + acl_user->user_resource.user_conn || + global_system_variables.max_user_connections) && + get_or_create_user_conn(thd, + (opt_old_style_user_limits ? sctx->user : sctx->priv_user), + (opt_old_style_user_limits ? sctx->host_or_ip : sctx->priv_host), + &acl_user->user_resource)) + DBUG_RETURN(1); // The error is set by get_or_create_user_conn() + +#endif + } + else + sctx->skip_grants(); + + const USER_CONN *uc; + if ((uc= thd->get_user_connect()) && + (uc->user_resources.conn_per_hour || uc->user_resources.user_conn || + global_system_variables.max_user_connections) && + check_for_max_user_connections(thd, uc)) + { + DBUG_RETURN(1); // The error is set in check_for_max_user_connections() + } + + DBUG_PRINT("info", + ("Capabilities: %lu packet_length: %ld Host: '%s' " + "Login user: '%s' Priv_user: '%s' Using password: %s " + "Access: %lu db: '%s'", + thd->client_capabilities, thd->max_client_packet_length, + sctx->host_or_ip, sctx->user, sctx->priv_user, + thd->password ? "yes": "no", + sctx->master_access, mpvio.db.str)); + + if (command == COM_CONNECT && + !(thd->main_security_ctx.master_access & SUPER_ACL)) + { + mysql_mutex_lock(&LOCK_connection_count); + bool count_ok= (connection_count <= max_connections); + mysql_mutex_unlock(&LOCK_connection_count); + if (!count_ok) + { // too many connections + release_user_connection(thd); + my_error(ER_CON_COUNT_ERROR, MYF(0)); + DBUG_RETURN(1); + } + } + + /* + This is the default access rights for the current database. It's + set to 0 here because we don't have an active database yet (and we + may not have an active database to set. + */ + sctx->db_access=0; + + /* Change a database if necessary */ + if (mpvio.db.length) + { + if (mysql_change_db(thd, &mpvio.db, FALSE)) + { + /* mysql_change_db() has pushed the error message. */ + release_user_connection(thd); + DBUG_RETURN(1); + } + } + + if (mpvio.auth_info.external_user[0]) + sctx->external_user= my_strdup(mpvio.auth_info.external_user, MYF(0)); + + if (res == CR_OK_HANDSHAKE_COMPLETE) + thd->stmt_da->disable_status(); + else + my_ok(thd); + +#if defined(MYSQL_SERVER) && !defined(EMBEDDED_LIBRARY) + /* + Allow the network layer to skip big packets. Although a malicious + authenticated session might use this to trick the server to read + big packets indefinitely, this is a previously established behavior + that needs to be preserved as to not break backwards compatibility. + */ + thd->net.skip_big_packet= TRUE; +#endif + + /* + Reset previous connection failures if any. + */ + if (mpvio.connect_errors) + reset_host_errors(mpvio.ip); + + /* Ready to handle queries */ + DBUG_RETURN(0); +} + +/** + MySQL Server Password Authentication Plugin + + In the MySQL authentication protocol: + 1. the server sends the random scramble to the client + 2. client sends the encrypted password back to the server + 3. the server checks the password. +*/ +static int native_password_authenticate(MYSQL_PLUGIN_VIO *vio, + MYSQL_SERVER_AUTH_INFO *info) +{ + uchar *pkt; + int pkt_len; + MPVIO_EXT *mpvio= (MPVIO_EXT *) vio; + + DBUG_ENTER("native_password_authenticate"); + + /* generate the scramble, or reuse the old one */ + if (mpvio->scramble[SCRAMBLE_LENGTH]) + create_random_string(mpvio->scramble, SCRAMBLE_LENGTH, mpvio->rand); + + /* send it to the client */ + if (mpvio->write_packet(mpvio, (uchar*) mpvio->scramble, SCRAMBLE_LENGTH + 1)) + DBUG_RETURN(CR_ERROR); + + /* reply and authenticate */ + + /* + <digression> + This is more complex than it looks. + + The plugin (we) may be called right after the client was connected - + and will need to send a scramble, read reply, authenticate. + + Or the plugin may be called after another plugin has sent a scramble, + and read the reply. If the client has used the correct client-plugin, + we won't need to read anything here from the client, the client + has already sent a reply with everything we need for authentication. + + Or the plugin may be called after another plugin has sent a scramble, + and read the reply, but the client has used the wrong client-plugin. + We'll need to sent a "switch to another plugin" packet to the + client and read the reply. "Use the short scramble" packet is a special + case of "switch to another plugin" packet. + + Or, perhaps, the plugin may be called after another plugin has + done the handshake but did not send a useful scramble. We'll need + to send a scramble (and perhaps a "switch to another plugin" packet) + and read the reply. + + Besides, a client may be an old one, that doesn't understand plugins. + Or doesn't even understand 4.0 scramble. + + And we want to keep the same protocol on the wire unless non-native + plugins are involved. + + Anyway, it still looks simple from a plugin point of view: + "send the scramble, read the reply and authenticate" + All the magic is transparently handled by the server. + </digression> + */ + + /* read the reply with the encrypted password */ + if ((pkt_len= mpvio->read_packet(mpvio, &pkt)) < 0) + DBUG_RETURN(CR_ERROR); + DBUG_PRINT("info", ("reply read : pkt_len=%d", pkt_len)); + +#ifdef NO_EMBEDDED_ACCESS_CHECKS + DBUG_RETURN(CR_OK); +#endif + + if (pkt_len == 0) /* no password */ + DBUG_RETURN(mpvio->acl_user->salt_len != 0 ? CR_ERROR : CR_OK); + + info->password_used= PASSWORD_USED_YES; + if (pkt_len == SCRAMBLE_LENGTH) + { + if (!mpvio->acl_user->salt_len) + DBUG_RETURN(CR_ERROR); + + DBUG_RETURN(check_scramble(pkt, mpvio->scramble, mpvio->acl_user->salt) ? + CR_ERROR : CR_OK); + } + + inc_host_errors(mpvio->ip); + my_error(ER_HANDSHAKE_ERROR, MYF(0)); + DBUG_RETURN(CR_ERROR); +} + +static int old_password_authenticate(MYSQL_PLUGIN_VIO *vio, + MYSQL_SERVER_AUTH_INFO *info) +{ + uchar *pkt; + int pkt_len; + MPVIO_EXT *mpvio= (MPVIO_EXT *) vio; + + /* generate the scramble, or reuse the old one */ + if (mpvio->scramble[SCRAMBLE_LENGTH]) + create_random_string(mpvio->scramble, SCRAMBLE_LENGTH, mpvio->rand); + + /* send it to the client */ + if (mpvio->write_packet(mpvio, (uchar*) mpvio->scramble, SCRAMBLE_LENGTH + 1)) + return CR_ERROR; + + /* read the reply and authenticate */ + if ((pkt_len= mpvio->read_packet(mpvio, &pkt)) < 0) + return CR_ERROR; + +#ifdef NO_EMBEDDED_ACCESS_CHECKS + return CR_OK; +#endif + + /* + legacy: if switch_from_long_to_short_scramble, + the password is sent \0-terminated, the pkt_len is always 9 bytes. + We need to figure out the correct scramble length here. + */ + if (pkt_len == SCRAMBLE_LENGTH_323 + 1) + pkt_len= strnlen((char*)pkt, pkt_len); + + if (pkt_len == 0) /* no password */ + return mpvio->acl_user->salt_len != 0 ? CR_ERROR : CR_OK; + + if (secure_auth(mpvio)) + return CR_ERROR; + + info->password_used= PASSWORD_USED_YES; + + if (pkt_len == SCRAMBLE_LENGTH_323) + { + if (!mpvio->acl_user->salt_len) + return CR_ERROR; + + return check_scramble_323(pkt, mpvio->scramble, + (ulong *) mpvio->acl_user->salt) ? + CR_ERROR : CR_OK; + } + + inc_host_errors(mpvio->ip); + my_error(ER_HANDSHAKE_ERROR, MYF(0)); + return CR_ERROR; +} + +static struct st_mysql_auth native_password_handler= +{ + MYSQL_AUTHENTICATION_INTERFACE_VERSION, + native_password_plugin_name.str, + native_password_authenticate +}; + +static struct st_mysql_auth old_password_handler= +{ + MYSQL_AUTHENTICATION_INTERFACE_VERSION, + old_password_plugin_name.str, + old_password_authenticate +}; + +mysql_declare_plugin(mysql_password) +{ + MYSQL_AUTHENTICATION_PLUGIN, /* type constant */ + &native_password_handler, /* type descriptor */ + native_password_plugin_name.str, /* Name */ + "R.J.Silk, Sergei Golubchik", /* Author */ + "Native MySQL authentication", /* Description */ + PLUGIN_LICENSE_GPL, /* License */ + NULL, /* Init function */ + NULL, /* Deinit function */ + 0x0100, /* Version (1.0) */ + NULL, /* status variables */ + NULL, /* system variables */ + NULL, /* config options */ + 0, /* flags */ +}, +{ + MYSQL_AUTHENTICATION_PLUGIN, /* type constant */ + &old_password_handler, /* type descriptor */ + old_password_plugin_name.str, /* Name */ + "R.J.Silk, Sergei Golubchik", /* Author */ + "Old MySQL-4.0 authentication", /* Description */ + PLUGIN_LICENSE_GPL, /* License */ + NULL, /* Init function */ + NULL, /* Deinit function */ + 0x0100, /* Version (1.0) */ + NULL, /* status variables */ + NULL, /* system variables */ + NULL, /* config options */ + 0, /* flags */ +} +mysql_declare_plugin_end; + |