mirror of
				https://github.com/MariaDB/server.git
				synced 2025-10-31 02:46:29 +01:00 
			
		
		
		
	 26ea37be5d
			
		
	
	
	26ea37be5d
	
	
	
		
			
			report all sysvar tracker changes, as for the new login. also report db and other session state changes.
		
			
				
	
	
		
			15532 lines
		
	
	
	
		
			476 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			15532 lines
		
	
	
	
		
			476 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* Copyright (c) 2000, 2018, Oracle and/or its affiliates.
 | |
|    Copyright (c) 2009, 2023, MariaDB
 | |
| 
 | |
|    This program is free software; you can redistribute it and/or modify
 | |
|    it under the terms of the GNU General Public License as published by
 | |
|    the Free Software Foundation; version 2 of the License.
 | |
| 
 | |
|    This program is distributed in the hope that it will be useful,
 | |
|    but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
|    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | |
|    GNU General Public License for more details.
 | |
| 
 | |
|    You should have received a copy of the GNU General Public License
 | |
|    along with this program; if not, write to the Free Software
 | |
|    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1335  USA */
 | |
| 
 | |
| 
 | |
| /*
 | |
|   The privileges are saved in the following tables:
 | |
|   mysql/user	 ; super user who are allowed to do almost anything
 | |
|   mysql/host	 ; host privileges. This is used if host is empty in mysql/db.
 | |
|   mysql/db	 ; database privileges / user
 | |
| 
 | |
|   data in tables is sorted according to how many not-wild-cards there is
 | |
|   in the relevant fields. Empty strings comes last.
 | |
| */
 | |
| 
 | |
| #include "mariadb.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"                         // write_bin_log
 | |
| #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 "rpl_rli.h"
 | |
| #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 <sql_common.h>
 | |
| #include <mysql/plugin_auth.h>
 | |
| #include <mysql/plugin_password_validation.h>
 | |
| #include "sql_connect.h"
 | |
| #include "hostname.h"
 | |
| #include "sql_db.h"
 | |
| #include "sql_array.h"
 | |
| #include "sql_hset.h"
 | |
| #include "sql_audit.h"
 | |
| #include "password.h"
 | |
| #include "scope.h"
 | |
| 
 | |
| #include "sql_plugin_compat.h"
 | |
| #include "wsrep_mysqld.h"
 | |
| 
 | |
| #define MAX_SCRAMBLE_LENGTH 1024
 | |
| 
 | |
| bool mysql_user_table_is_in_short_password_format= false;
 | |
| bool using_global_priv_table= true;
 | |
| 
 | |
| // set that from field length in acl_load?
 | |
| #ifndef NO_EMBEDDED_ACCESS_CHECKS
 | |
| const uint max_hostname_length= HOSTNAME_LENGTH;
 | |
| const uint max_dbname_length= NAME_CHAR_LEN;
 | |
| #endif
 | |
| 
 | |
| const char *safe_vio_type_name(Vio *vio)
 | |
| {
 | |
|   size_t unused;
 | |
| #ifdef EMBEDDED_LIBRARY
 | |
|   if (!vio) return "Internal";
 | |
| #endif
 | |
|   return vio_type_name(vio_type(vio), &unused);
 | |
| }
 | |
| 
 | |
| #include "sql_acl_getsort.inl"
 | |
| 
 | |
| static Lex_ident_plugin native_password_plugin_name=
 | |
|   "mysql_native_password"_Lex_ident_plugin;
 | |
| 
 | |
| 
 | |
| static Lex_ident_plugin old_password_plugin_name=
 | |
|   "mysql_old_password"_Lex_ident_plugin;
 | |
| 
 | |
| 
 | |
| /// @todo make it configurable
 | |
| LEX_CSTRING *default_auth_plugin_name= &native_password_plugin_name;
 | |
| 
 | |
| /*
 | |
|   Wildcard host, matches any hostname
 | |
| */
 | |
| LEX_CSTRING host_not_specified= { STRING_WITH_LEN("%") };
 | |
| 
 | |
| /*
 | |
|   Constants, used in the SHOW GRANTS command.
 | |
|   Their actual string values are irrelevant, they're always compared
 | |
|   as pointers to these string constants.
 | |
| */
 | |
| LEX_CSTRING current_user= { STRING_WITH_LEN("*current_user") };
 | |
| LEX_CSTRING current_role= { STRING_WITH_LEN("*current_role") };
 | |
| LEX_CSTRING current_user_and_current_role=
 | |
|  { STRING_WITH_LEN("*current_user_and_current_role") };
 | |
| LEX_CSTRING none= {STRING_WITH_LEN("NONE") };
 | |
| LEX_CSTRING public_name= {STRING_WITH_LEN("PUBLIC") };
 | |
| 
 | |
| static plugin_ref old_password_plugin;
 | |
| static plugin_ref native_password_plugin;
 | |
| 
 | |
| static plugin_ref get_auth_plugin(THD *thd, const LEX_CSTRING &name, bool *locked)
 | |
| {
 | |
|   if (name.str == native_password_plugin_name.str)
 | |
|     return native_password_plugin;
 | |
|   else if (name.str == old_password_plugin_name.str)
 | |
|     return old_password_plugin;
 | |
|   *locked=true;
 | |
|   return my_plugin_lock_by_name(thd, &name, MYSQL_AUTHENTICATION_PLUGIN);
 | |
| }
 | |
| 
 | |
| /* Classes */
 | |
| 
 | |
| struct acl_host_and_ip
 | |
| {
 | |
|   char *hostname;
 | |
|   long ip, ip_mask;                      // Used with masked ip:s
 | |
| };
 | |
| 
 | |
| #ifndef NO_EMBEDDED_ACCESS_CHECKS
 | |
| static bool compare_hostname(const acl_host_and_ip *, const char *, const char *);
 | |
| static inline bool is_public(const char *l) { return l == public_name.str; }
 | |
| static inline bool is_public(const LEX_CSTRING *l) { return is_public(l->str); }
 | |
| static inline bool is_public(const LEX_USER *l) { return is_public(&l->user); }
 | |
| 
 | |
| #else
 | |
| #define compare_hostname(X,Y,Z) 0
 | |
| #endif
 | |
| 
 | |
| class ACL_ACCESS {
 | |
| public:
 | |
|   ulonglong sort;
 | |
|   privilege_t access;
 | |
|   ACL_ACCESS()
 | |
|    :sort(0), access(NO_ACL)
 | |
|   { }
 | |
| };
 | |
| 
 | |
| /* 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_BASE :public ACL_ACCESS, public Sql_alloc
 | |
| {
 | |
| 
 | |
| public:
 | |
|   ACL_USER_BASE()
 | |
|    :flags(0), user(null_clex_str)
 | |
|   {
 | |
|     bzero(&role_grants, sizeof(role_grants));
 | |
|   }
 | |
|   uchar flags;           // field used to store various state information
 | |
|   LEX_CSTRING user;
 | |
|   /* list to hold references to granted roles (ACL_ROLE instances) */
 | |
|   DYNAMIC_ARRAY role_grants;
 | |
|   const char *get_username() { return user.str; }
 | |
| };
 | |
| 
 | |
| class ACL_USER_PARAM
 | |
| {
 | |
| public:
 | |
|   ACL_USER_PARAM()
 | |
|   {
 | |
|     bzero(this, sizeof(*this));
 | |
|   }
 | |
|   acl_host_and_ip host;
 | |
|   size_t hostname_length;
 | |
|   USER_RESOURCES user_resource;
 | |
|   enum SSL_type ssl_type;
 | |
|   uint password_errors;
 | |
|   const char *ssl_cipher, *x509_issuer, *x509_subject;
 | |
|   LEX_CSTRING default_rolename;
 | |
|   struct AUTH { LEX_CSTRING plugin, auth_string, salt; } *auth;
 | |
|   uint nauth;
 | |
|   bool account_locked;
 | |
|   bool password_expired;
 | |
|   my_time_t password_last_changed;
 | |
|   longlong password_lifetime;
 | |
| 
 | |
|   bool alloc_auth(MEM_ROOT *root, uint n)
 | |
|   {
 | |
|     return !(auth= (AUTH*) alloc_root(root, (nauth= n)*sizeof(AUTH)));
 | |
|   }
 | |
|   Lex_ident_host hostname() const
 | |
|   {
 | |
|     DBUG_ASSERT(host.hostname[hostname_length] == '\0');
 | |
|     return Lex_ident_host(host.hostname, hostname_length);
 | |
|   }
 | |
| };
 | |
| 
 | |
| 
 | |
| class ACL_USER :public ACL_USER_BASE, public ACL_USER_PARAM
 | |
| {
 | |
| public:
 | |
|   ACL_USER() = default;
 | |
|   ACL_USER(THD *, const LEX_USER &, const Account_options &, const privilege_t);
 | |
| 
 | |
|   ACL_USER *copy(MEM_ROOT *root)
 | |
|   {
 | |
|     ACL_USER *dst;
 | |
|     AUTH *dauth;
 | |
|     if (!multi_alloc_root(root, &dst, sizeof(ACL_USER),
 | |
|                                 &dauth, sizeof(AUTH)*nauth, NULL))
 | |
|       return 0;
 | |
|     *dst= *this;
 | |
|     dst->user= safe_lexcstrdup_root(root, user);
 | |
|     dst->ssl_cipher= safe_strdup_root(root, ssl_cipher);
 | |
|     dst->x509_issuer= safe_strdup_root(root, x509_issuer);
 | |
|     dst->x509_subject= safe_strdup_root(root, x509_subject);
 | |
|     dst->auth= dauth;
 | |
|     for (uint i=0; i < nauth; i++, dauth++)
 | |
|     {
 | |
|       if (auth[i].plugin.str == native_password_plugin_name.str ||
 | |
|           auth[i].plugin.str == old_password_plugin_name.str)
 | |
|         dauth->plugin= auth[i].plugin;
 | |
|       else
 | |
|         dauth->plugin= safe_lexcstrdup_root(root, auth[i].plugin);
 | |
|       dauth->auth_string= safe_lexcstrdup_root(root, auth[i].auth_string);
 | |
|       if (auth[i].salt.length == 0)
 | |
|         dauth->salt= auth[i].salt;
 | |
|       else
 | |
|         dauth->salt= safe_lexcstrdup_root(root, auth[i].salt);
 | |
|     }
 | |
|     dst->host.hostname= safe_strdup_root(root, host.hostname);
 | |
|     dst->default_rolename= safe_lexcstrdup_root(root, default_rolename);
 | |
|     bzero(&dst->role_grants, sizeof(role_grants));
 | |
|     return dst;
 | |
|   }
 | |
| 
 | |
|   bool wild_eq(const char *user2, const char *host2, const char *ip2)
 | |
|   {
 | |
|     if (strcmp(user.str, user2))
 | |
|       return false;
 | |
| 
 | |
|     return compare_hostname(&host, host2, ip2 ? ip2 : host2);
 | |
|   }
 | |
| 
 | |
|   enum PASSWD_ERROR_ACTION
 | |
|   {
 | |
|     PASSWD_ERROR_CLEAR,
 | |
|     PASSWD_ERROR_INCREMENT
 | |
|   };
 | |
| 
 | |
|   void update_password_errors(PASSWD_ERROR_ACTION action)
 | |
|   {
 | |
|     switch (action)
 | |
|     {
 | |
|       case PASSWD_ERROR_INCREMENT:
 | |
|         password_errors++;
 | |
|         break;
 | |
|       case PASSWD_ERROR_CLEAR:
 | |
|         password_errors= 0;
 | |
|         break;
 | |
|       default:
 | |
|         DBUG_ASSERT(0);
 | |
|         break;
 | |
|     }
 | |
|   }
 | |
| };
 | |
| 
 | |
| class ACL_ROLE :public ACL_USER_BASE
 | |
| {
 | |
| public:
 | |
|   /*
 | |
|     In case of granting a role to a role, the access bits are merged together
 | |
|     via a bit OR operation and placed in the ACL_USER::access field.
 | |
| 
 | |
|     When rebuilding role_grants via the rebuild_role_grant function,
 | |
|     the ACL_USER::access field needs to be reset first. The field
 | |
|     initial_role_access holds initial grants, as granted directly to the role
 | |
|   */
 | |
|   privilege_t initial_role_access;
 | |
|   /*
 | |
|     In subgraph traversal, when we need to traverse only a part of the graph
 | |
|     (e.g. all direct and indirect grantees of a role X), the counter holds the
 | |
|     number of affected neighbour nodes.
 | |
|     See also propagate_role_grants()
 | |
|   */
 | |
|   uint  counter;
 | |
|   DYNAMIC_ARRAY parent_grantee; // array of backlinks to elements granted
 | |
| 
 | |
|   ACL_ROLE(ACL_USER *user);
 | |
|   ACL_ROLE(const char *rolename, privilege_t privileges, MEM_ROOT *mem);
 | |
| 
 | |
| };
 | |
| 
 | |
| class ACL_DB :public ACL_ACCESS
 | |
| {
 | |
| public:
 | |
|   ACL_DB() :initial_access(NO_ACL) { }
 | |
|   acl_host_and_ip host;
 | |
|   const char *user,*db;
 | |
|   privilege_t initial_access; /* access bits present in the table */
 | |
| 
 | |
|   const char *get_username() { return user; }
 | |
| };
 | |
| 
 | |
| #ifndef DBUG_OFF
 | |
| /* status variables, only visible in SHOW STATUS after -#d,role_merge_stats */
 | |
| ulong role_global_merges= 0, role_db_merges= 0, role_table_merges= 0,
 | |
|       role_column_merges= 0, role_routine_merges= 0;
 | |
| #endif
 | |
| 
 | |
| #ifndef NO_EMBEDDED_ACCESS_CHECKS
 | |
| static bool ignore_max_password_errors(const ACL_USER *acl_user);
 | |
| static void update_hostname(acl_host_and_ip *host, const char *hostname);
 | |
| static bool show_proxy_grants (THD *, const char *, const char *,
 | |
|                                char *, size_t);
 | |
| static bool show_role_grants(THD *, const char *,
 | |
|                              ACL_USER_BASE *, char *, size_t);
 | |
| static bool show_default_role(THD *, ACL_USER *, char *, size_t);
 | |
| static bool show_global_privileges(THD *, ACL_USER_BASE *,
 | |
|                                    bool, char *, size_t);
 | |
| static bool show_database_privileges(THD *, const char *, const char *,
 | |
|                                      char *, size_t);
 | |
| static bool show_table_and_column_privileges(THD *, const char *, const char *,
 | |
|                                              char *, size_t);
 | |
| static int show_routine_grants(THD *, const char *, const char *,
 | |
|                                const Sp_handler *sph, char *, int);
 | |
| 
 | |
| static ACL_ROLE *acl_public= NULL;
 | |
| 
 | |
| inline privilege_t public_access()
 | |
| {
 | |
|   return (acl_public ? acl_public->access : NO_ACL);
 | |
| }
 | |
| 
 | |
| class Grant_tables;
 | |
| class User_table;
 | |
| class Proxies_priv_table;
 | |
| 
 | |
| 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 } proxy_table_fields;
 | |
| public:
 | |
|   ACL_PROXY_USER () = default;
 | |
| 
 | |
|   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;
 | |
|     update_hostname (&host, (host_arg && *host_arg) ? host_arg : NULL);
 | |
|     proxied_user= proxied_user_arg;
 | |
|     update_hostname (&proxied_host,
 | |
|                      (proxied_host_arg && *proxied_host_arg) ?
 | |
|                      proxied_host_arg : NULL);
 | |
|     with_grant= with_grant_arg;
 | |
|     sort= get_magic_sort("huhu", 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,
 | |
|           strdup_root (mem, user_arg),
 | |
|           (proxied_host_arg && *proxied_host_arg) ?
 | |
|             strdup_root (mem, proxied_host_arg) : NULL,
 | |
|           strdup_root (mem, proxied_user_arg),
 | |
|           with_grant_arg);
 | |
|   }
 | |
| 
 | |
|   void init(const Proxies_priv_table& proxies_priv_table, MEM_ROOT *mem);
 | |
| 
 | |
|   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 ? strdup_root(mem, user_arg) : "";
 | |
|   }
 | |
|   void set_host(MEM_ROOT *mem, const char *host_arg)
 | |
|   {
 | |
|     update_hostname(&host, safe_strdup_root(mem, host_arg));
 | |
|   }
 | |
| 
 | |
|   bool check_validity()
 | |
|   {
 | |
|     if (opt_skip_name_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,
 | |
|                         safe_str(proxied_host.hostname), user,
 | |
|                         safe_str(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_arg, ip_arg, proxied_host.hostname,
 | |
|                         host_arg, ip_arg, user_arg, user,
 | |
|                         proxied_user_arg, proxied_user));
 | |
|     DBUG_RETURN(compare_hostname(&host, host_arg, ip_arg) &&
 | |
|                 compare_hostname(&proxied_host, host_arg, ip_arg) &&
 | |
|                 (!*user || !strcmp(user_arg, user)) &&
 | |
|                 (!*proxied_user || !strcmp(proxied_user_arg, proxied_user)));
 | |
|   }
 | |
| 
 | |
| 
 | |
|   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, grant->user, proxied_user, grant->proxied_user,
 | |
|                         host.hostname, grant->host.hostname,
 | |
|                         proxied_host.hostname, grant->proxied_host.hostname));
 | |
| 
 | |
|     bool res= 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);
 | |
|     DBUG_RETURN(res);
 | |
|   }
 | |
| 
 | |
| 
 | |
|   bool granted_on(const char *host_arg, const char *user_arg)
 | |
|   {
 | |
|     return (!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 '"));
 | |
|     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 '"));
 | |
|     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_CSTRING *host,
 | |
|                       const LEX_CSTRING *user,
 | |
|                       const LEX_CSTRING *proxied_host,
 | |
|                       const LEX_CSTRING *proxied_user)
 | |
|   {
 | |
|     DBUG_ENTER("ACL_PROXY_USER::store_pk");
 | |
|     DBUG_PRINT("info", ("host=%s, user=%s, proxied_host=%s, proxied_user=%s",
 | |
|                         host->str, user->str,
 | |
|                         proxied_host->str, proxied_user->str));
 | |
|     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_CSTRING *host,
 | |
|                                const LEX_CSTRING *user,
 | |
|                                const LEX_CSTRING *proxied_host,
 | |
|                                const LEX_CSTRING *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
 | |
| 
 | |
| class acl_entry :public hash_filo_element
 | |
| {
 | |
| public:
 | |
|   privilege_t access;
 | |
|   uint16 length;
 | |
|   char key[1];					// Key will be stored here
 | |
| };
 | |
| 
 | |
| 
 | |
| static const uchar *acl_entry_get_key(const void *entry_, size_t *length,
 | |
|                                       my_bool)
 | |
| {
 | |
|   auto entry= static_cast<const acl_entry *>(entry_);
 | |
|   *length=(uint) entry->length;
 | |
|   return reinterpret_cast<const uchar *>(entry->key);
 | |
| }
 | |
| 
 | |
| static const uchar *acl_role_get_key(const void *entry_, size_t *length,
 | |
|                                      my_bool)
 | |
| {
 | |
|   auto entry= static_cast<const ACL_ROLE *>(entry_);
 | |
|   *length=(uint) entry->user.length;
 | |
|   return reinterpret_cast<const uchar *>(entry->user.str);
 | |
| }
 | |
| 
 | |
| struct ROLE_GRANT_PAIR : public Sql_alloc
 | |
| {
 | |
|   LEX_CSTRING u_uname;
 | |
|   LEX_CSTRING u_hname;
 | |
|   LEX_CSTRING r_uname;
 | |
|   LEX_STRING hashkey;
 | |
|   bool with_admin;
 | |
| 
 | |
|   bool init(MEM_ROOT *mem,
 | |
|             const LEX_CSTRING &username,
 | |
|             const LEX_CSTRING &hostname,
 | |
|             const LEX_CSTRING &rolename,
 | |
|             bool with_admin_option);
 | |
| };
 | |
| 
 | |
| static const uchar *acl_role_map_get_key(const void *entry_, size_t *length,
 | |
|                                          my_bool)
 | |
| {
 | |
|   auto entry= static_cast<const ROLE_GRANT_PAIR *>(entry_);
 | |
|   *length=(uint) entry->hashkey.length;
 | |
|   return reinterpret_cast<const uchar *>(entry->hashkey.str);
 | |
| }
 | |
| 
 | |
| bool ROLE_GRANT_PAIR::init(MEM_ROOT *mem,
 | |
|                            const LEX_CSTRING &username,
 | |
|                            const LEX_CSTRING &hostname,
 | |
|                            const LEX_CSTRING &rolename,
 | |
|                            bool with_admin_option)
 | |
| {
 | |
|   /*
 | |
|     Create a buffer that holds all 3 NULL terminated strings in succession
 | |
|     To save memory space, the same buffer is used as the hashkey
 | |
|     Add the '\0' as well.
 | |
|   */
 | |
|   size_t bufflen= username.length + hostname.length + rolename.length + 3;
 | |
|   char *buff= (char *)alloc_root(mem, bufflen);
 | |
|   if (!buff)
 | |
|     return true;
 | |
| 
 | |
|   /*
 | |
|     Offsets in the buffer for all 3 strings
 | |
|   */
 | |
|   char *username_pos= buff;
 | |
|   char *hostname_pos= buff + username.length + 1;
 | |
|   char *rolename_pos= buff + username.length + hostname.length + 2;
 | |
| 
 | |
|   if (username.str) //prevent undefined behaviour
 | |
|     memcpy(username_pos, username.str, username.length);
 | |
|   username_pos[username.length]= '\0';         //#1 string terminator
 | |
|   u_uname= Lex_cstring(username_pos, username.length);
 | |
| 
 | |
|   if (hostname.str) //prevent undefined behaviour
 | |
|     memcpy(hostname_pos, hostname.str, hostname.length);
 | |
|   hostname_pos[hostname.length]= '\0';         //#2 string terminator
 | |
|   u_hname= Lex_cstring(hostname_pos, hostname.length);
 | |
| 
 | |
|   if (rolename.str) //prevent undefined behaviour
 | |
|     memcpy(rolename_pos, rolename.str, rolename.length);
 | |
|   rolename_pos[rolename.length]= '\0';         //#3 string terminator
 | |
|   r_uname= Lex_cstring(rolename_pos, rolename.length);
 | |
| 
 | |
|   hashkey.str = buff;
 | |
|   hashkey.length = bufflen;
 | |
| 
 | |
|   with_admin= with_admin_option;
 | |
| 
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| #define IP_ADDR_STRLEN (3 + 1 + 3 + 1 + 3 + 1 + 3)
 | |
| 
 | |
| #if defined(HAVE_OPENSSL)
 | |
| /*
 | |
|   Without SSL the handshake consists of one packet. This packet
 | |
|   has both client capabilities and scrambled password.
 | |
|   With SSL the handshake might consist of two packets. If the first
 | |
|   packet (client capabilities) has CLIENT_SSL flag set, we have to
 | |
|   switch to SSL and read the second packet. The scrambled password
 | |
|   is in the second packet and client_capabilities field will be ignored.
 | |
|   Maybe it is better to accept flags other than CLIENT_SSL from the
 | |
|   second packet?
 | |
| */
 | |
| #define SSL_HANDSHAKE_SIZE      2
 | |
| #define MIN_HANDSHAKE_SIZE      2
 | |
| #else
 | |
| #define MIN_HANDSHAKE_SIZE      6
 | |
| #endif /* HAVE_OPENSSL && !EMBEDDED_LIBRARY */
 | |
| #define NORMAL_HANDSHAKE_SIZE   6
 | |
| 
 | |
| #define ROLE_ASSIGN_COLUMN_IDX  44
 | |
| #define DEFAULT_ROLE_COLUMN_IDX 45
 | |
| #define MAX_STATEMENT_TIME_COLUMN_IDX 46
 | |
| 
 | |
| /* various flags valid for ACL_USER */
 | |
| #define IS_ROLE                 (1L << 0)
 | |
| /* Flag to mark that a ROLE is on the recursive DEPTH_FIRST_SEARCH stack */
 | |
| #define ROLE_ON_STACK            (1L << 1)
 | |
| /*
 | |
|   Flag to mark that a ROLE and all it's neighbours have
 | |
|   been visited
 | |
| */
 | |
| #define ROLE_EXPLORED           (1L << 2)
 | |
| /* Flag to mark that on_node was already called for this role */
 | |
| #define ROLE_OPENED             (1L << 3)
 | |
| 
 | |
| static DYNAMIC_ARRAY acl_hosts, acl_users, acl_proxy_users;
 | |
| static Dynamic_array<ACL_DB> acl_dbs(PSI_INSTRUMENT_MEM, 0, 50);
 | |
| static HASH acl_roles;
 | |
| /*
 | |
|   An hash containing mappings user <--> role
 | |
| 
 | |
|   A hash is used so as to make updates quickly
 | |
|   The hashkey used represents all the entries combined
 | |
| */
 | |
| static HASH acl_roles_mappings;
 | |
| static MEM_ROOT acl_memroot, grant_memroot;
 | |
| static bool initialized=0;
 | |
| static bool allow_all_hosts=1;
 | |
| static HASH acl_check_hosts, column_priv_hash, proc_priv_hash, func_priv_hash;
 | |
| static HASH package_spec_priv_hash, package_body_priv_hash;
 | |
| static DYNAMIC_ARRAY acl_wild_hosts;
 | |
| static Hash_filo<acl_entry> *acl_cache;
 | |
| static uint grant_version=0; /* Version of priv tables. incremented by acl_load */
 | |
| static privilege_t get_access(TABLE *form, uint fieldnr, uint *next_field=0);
 | |
| static int acl_compare(const void *a, const void *b);
 | |
| static int acl_user_compare(const void *a, const void *b);
 | |
| static void rebuild_acl_users();
 | |
| static int acl_db_compare(const void *a, const void *b);
 | |
| static void rebuild_acl_dbs();
 | |
| static void init_check_host(void);
 | |
| static void rebuild_check_host(void);
 | |
| static void rebuild_role_grants(void);
 | |
| static ACL_USER *find_user_exact(const LEX_CSTRING &host,
 | |
|                                  const LEX_CSTRING &user);
 | |
| static ACL_USER *find_user_wild(const LEX_CSTRING &host,
 | |
|                                 const LEX_CSTRING &user,
 | |
|                                 const LEX_CSTRING &ip= null_clex_str);
 | |
| static ACL_ROLE *find_acl_role(const LEX_CSTRING &user, bool allow_public);
 | |
| static ROLE_GRANT_PAIR *find_role_grant_pair(const LEX_CSTRING *u, const LEX_CSTRING *h, const LEX_CSTRING *r);
 | |
| static ACL_USER_BASE *find_acl_user_base(const LEX_CSTRING &user,
 | |
|                                          const LEX_CSTRING &host);
 | |
| static bool update_user_table_password(THD *, const User_table&, const ACL_USER&);
 | |
| static bool acl_load(THD *thd, const Grant_tables& grant_tables);
 | |
| static inline void get_grantor(THD *thd, char* grantor);
 | |
| static bool add_role_user_mapping(const LEX_CSTRING &uname,
 | |
|                                   const LEX_CSTRING &hname,
 | |
|                                   const LEX_CSTRING &rname);
 | |
| static bool get_YN_as_bool(Field *field);
 | |
| 
 | |
| #define ROLE_CYCLE_FOUND 2
 | |
| static int
 | |
| traverse_role_graph_up(ACL_ROLE *, void *, int (*)(ACL_USER_BASE *, void *),
 | |
|                        int (*)(ACL_USER_BASE *, ACL_ROLE *, void *));
 | |
| 
 | |
| static int traverse_role_graph_down(ACL_USER_BASE *, void *,
 | |
|                              int (*) (ACL_USER_BASE *, void *),
 | |
|                              int (*) (ACL_USER_BASE *, ACL_ROLE *, void *));
 | |
| 
 | |
| 
 | |
| HASH *Sp_handler_procedure::get_priv_hash() const
 | |
| {
 | |
|   return &proc_priv_hash;
 | |
| }
 | |
| 
 | |
| 
 | |
| HASH *Sp_handler_function::get_priv_hash() const
 | |
| {
 | |
|   return &func_priv_hash;
 | |
| }
 | |
| 
 | |
| 
 | |
| HASH *Sp_handler_package_spec::get_priv_hash() const
 | |
| {
 | |
|   return &package_spec_priv_hash;
 | |
| }
 | |
| 
 | |
| 
 | |
| HASH *Sp_handler_package_body::get_priv_hash() const
 | |
| {
 | |
|   return &package_body_priv_hash;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  Enumeration of ACL/GRANT tables in the mysql database
 | |
| */
 | |
| enum enum_acl_tables
 | |
| {
 | |
|   DB_TABLE,
 | |
|   TABLES_PRIV_TABLE,
 | |
|   COLUMNS_PRIV_TABLE,
 | |
| #define FIRST_OPTIONAL_TABLE HOST_TABLE
 | |
|   HOST_TABLE,
 | |
|   PROCS_PRIV_TABLE,
 | |
|   PROXIES_PRIV_TABLE,
 | |
|   ROLES_MAPPING_TABLE,
 | |
|   USER_TABLE // <== always the last
 | |
| };
 | |
| 
 | |
| static const int Table_user= 1 << USER_TABLE;
 | |
| static const int Table_db= 1 << DB_TABLE;
 | |
| static const int Table_tables_priv= 1 << TABLES_PRIV_TABLE;
 | |
| static const int Table_columns_priv= 1 << COLUMNS_PRIV_TABLE;
 | |
| static const int Table_host= 1 << HOST_TABLE;
 | |
| static const int Table_procs_priv= 1 << PROCS_PRIV_TABLE;
 | |
| static const int Table_proxies_priv= 1 << PROXIES_PRIV_TABLE;
 | |
| static const int Table_roles_mapping= 1 << ROLES_MAPPING_TABLE;
 | |
| 
 | |
| static LEX_CSTRING MYSQL_TABLE_NAME[USER_TABLE+1]= {
 | |
|   {STRING_WITH_LEN("db")},
 | |
|   {STRING_WITH_LEN("tables_priv")},
 | |
|   {STRING_WITH_LEN("columns_priv")},
 | |
|   {STRING_WITH_LEN("host")},
 | |
|   {STRING_WITH_LEN("procs_priv")},
 | |
|   {STRING_WITH_LEN("proxies_priv")},
 | |
|   {STRING_WITH_LEN("roles_mapping")},
 | |
|   {STRING_WITH_LEN("global_priv")}
 | |
| };
 | |
| static LEX_CSTRING MYSQL_TABLE_NAME_USER={STRING_WITH_LEN("user")};
 | |
| 
 | |
| /**
 | |
|   Choose from either native or old password plugins when assigning a password
 | |
| */
 | |
| 
 | |
| static LEX_CSTRING &guess_auth_plugin(THD *thd, size_t password_len)
 | |
| {
 | |
|   if (thd->variables.old_passwords == 1 ||
 | |
|       password_len == SCRAMBLED_PASSWORD_CHAR_LENGTH_323)
 | |
|     return old_password_plugin_name;
 | |
|   else
 | |
|     return native_password_plugin_name;
 | |
| }
 | |
| 
 | |
| /**
 | |
|   Base class representing a generic grant table from the mysql database.
 | |
| 
 | |
|   The potential tables that this class can represent are:
 | |
|   user, db, columns_priv, tables_priv, host, procs_priv, proxies_priv,
 | |
|   roles_mapping
 | |
| 
 | |
|   Objects belonging to this parent class can only be constructed by the
 | |
|   Grants_table class. This ensures the correct initialization of the objects.
 | |
| */
 | |
| class Grant_table_base
 | |
| {
 | |
|  public:
 | |
|   /* Number of fields for this Grant Table. */
 | |
|   uint num_fields() const { return m_table->s->fields; }
 | |
|   /* Check if the table exists after an attempt to open it was made.
 | |
|      Some tables, such as the host table in MySQL 5.6.7+ are missing. */
 | |
|   bool table_exists() const { return m_table; };
 | |
|   /* Initializes the READ_RECORD structure provided as a parameter
 | |
|      to read through the whole table, with all columns available. Cleaning up
 | |
|      is the caller's job. */
 | |
|   bool init_read_record(READ_RECORD* info) const
 | |
|   {
 | |
|     DBUG_ASSERT(m_table);
 | |
| 
 | |
|     if (num_fields() < min_columns)
 | |
|     {
 | |
|       my_printf_error(ER_UNKNOWN_ERROR, "Fatal error: mysql.%s table is "
 | |
|                       "damaged or in unsupported 3.20 format",
 | |
|                       MYF(ME_ERROR_LOG), m_table->s->table_name.str);
 | |
|       return 1;
 | |
|     }
 | |
| 
 | |
|     bool result= ::init_read_record(info, m_table->in_use, m_table,
 | |
|                                     NULL, NULL, 1, true, false);
 | |
|     if (!result)
 | |
|       m_table->use_all_columns();
 | |
|     return result;
 | |
|   }
 | |
| 
 | |
|   /* Return the underlying TABLE handle. */
 | |
|   TABLE* table() const { return m_table; }
 | |
| 
 | |
|   privilege_t get_access() const
 | |
|   {
 | |
|     ulonglong access_bits= 0, bit= 1;
 | |
|     for (uint i = start_priv_columns; i < end_priv_columns; i++, bit<<=1)
 | |
|     {
 | |
|       if (get_YN_as_bool(m_table->field[i]))
 | |
|         access_bits|= bit;
 | |
|     }
 | |
|     return ALL_KNOWN_ACL & access_bits;
 | |
|   }
 | |
| 
 | |
|  protected:
 | |
|   friend class Grant_tables;
 | |
| 
 | |
|   Grant_table_base() : min_columns(3), start_priv_columns(0), end_priv_columns(0), m_table(0)
 | |
|   { }
 | |
| 
 | |
|   /* Compute how many privilege columns this table has. This method
 | |
|      can only be called after the table has been opened.
 | |
| 
 | |
|      IMPLEMENTATION
 | |
|      A privilege column is of type enum('Y', 'N'). Privilege columns are
 | |
|      expected to be one after another.
 | |
|   */
 | |
|   void set_table(TABLE *table)
 | |
|   {
 | |
|     if (!(m_table= table)) // Table does not exist or not opened.
 | |
|       return;
 | |
| 
 | |
|     for (end_priv_columns= 0; end_priv_columns < num_fields(); end_priv_columns++)
 | |
|     {
 | |
|       Field *field= m_table->field[end_priv_columns];
 | |
|       if (field->real_type() == MYSQL_TYPE_ENUM &&
 | |
|           static_cast<Field_enum*>(field)->typelib()->count == 2)
 | |
|       {
 | |
|         if (!start_priv_columns)
 | |
|           start_priv_columns= end_priv_columns;
 | |
|       }
 | |
|       else if (start_priv_columns)
 | |
|           break;
 | |
|     }
 | |
|   }
 | |
| 
 | |
| 
 | |
|   /* the min number of columns a table should have */
 | |
|   uint min_columns;
 | |
|   /* The index at which privilege columns start. */
 | |
|   uint start_priv_columns;
 | |
|   /* The index after the last privilege column */
 | |
|   uint end_priv_columns;
 | |
| 
 | |
|   TABLE *m_table;
 | |
| };
 | |
| 
 | |
| class User_table: public Grant_table_base
 | |
| {
 | |
|  public:
 | |
|   bool init_read_record(READ_RECORD* info) const
 | |
|   {
 | |
|     return Grant_table_base::init_read_record(info) || setup_sysvars();
 | |
|   }
 | |
| 
 | |
|   virtual LEX_CSTRING& name() const = 0;
 | |
|   virtual int get_auth(THD *, MEM_ROOT *, ACL_USER *u) const= 0;
 | |
|   virtual bool set_auth(const ACL_USER &u) const = 0;
 | |
|   virtual privilege_t get_access() const = 0;
 | |
|   virtual void set_access(const privilege_t rights, bool revoke) const = 0;
 | |
| 
 | |
|   char *get_host(MEM_ROOT *root) const
 | |
|   { return ::get_field(root, m_table->field[0]); }
 | |
|   int set_host(const char *s, size_t l) const
 | |
|   { return m_table->field[0]->store(s, l, system_charset_info); };
 | |
|   char *get_user(MEM_ROOT *root) const
 | |
|   { return ::get_field(root, m_table->field[1]); }
 | |
|   int set_user(const char *s, size_t l) const
 | |
|   { return m_table->field[1]->store(s, l, system_charset_info); };
 | |
| 
 | |
|   virtual SSL_type get_ssl_type () const = 0;
 | |
|   virtual int set_ssl_type (SSL_type x) const = 0;
 | |
|   virtual const char* get_ssl_cipher (MEM_ROOT *root) const = 0;
 | |
|   virtual int set_ssl_cipher (const char *s, size_t l) const = 0;
 | |
|   virtual const char* get_x509_issuer (MEM_ROOT *root) const = 0;
 | |
|   virtual int set_x509_issuer (const char *s, size_t l) const = 0;
 | |
|   virtual const char* get_x509_subject (MEM_ROOT *root) const = 0;
 | |
|   virtual int set_x509_subject (const char *s, size_t l) const = 0;
 | |
|   virtual longlong get_max_questions () const = 0;
 | |
|   virtual int set_max_questions (longlong x) const = 0;
 | |
|   virtual longlong get_max_updates () const = 0;
 | |
|   virtual int set_max_updates (longlong x) const = 0;
 | |
|   virtual longlong get_max_connections () const = 0;
 | |
|   virtual int set_max_connections (longlong x) const = 0;
 | |
|   virtual longlong get_max_user_connections () const = 0;
 | |
|   virtual int set_max_user_connections (longlong x) const = 0;
 | |
|   virtual double get_max_statement_time () const = 0;
 | |
|   virtual int set_max_statement_time (double x) const = 0;
 | |
|   virtual bool get_is_role () const = 0;
 | |
|   virtual int set_is_role (bool x) const = 0;
 | |
|   virtual const char* get_default_role (MEM_ROOT *root) const = 0;
 | |
|   virtual int set_default_role (const char *s, size_t l) const = 0;
 | |
|   virtual bool get_account_locked () const = 0;
 | |
|   virtual int set_account_locked (bool x) const = 0;
 | |
|   virtual bool get_password_expired () const = 0;
 | |
|   virtual int set_password_expired (bool x) const = 0;
 | |
|   virtual my_time_t get_password_last_changed () const = 0;
 | |
|   virtual int set_password_last_changed (my_time_t x) const = 0;
 | |
|   virtual longlong get_password_lifetime () const = 0;
 | |
|   virtual int set_password_lifetime (longlong x) const = 0;
 | |
| 
 | |
|   virtual ~User_table() = default;
 | |
|  private:
 | |
|   friend class Grant_tables;
 | |
|   virtual int setup_sysvars() const = 0;
 | |
| };
 | |
| 
 | |
| /* MySQL-3.23 to MariaDB 10.3 `user` table */
 | |
| class User_table_tabular: public User_table
 | |
| {
 | |
|  public:
 | |
| 
 | |
|   LEX_CSTRING& name() const override { return MYSQL_TABLE_NAME_USER; }
 | |
| 
 | |
|   int get_auth(THD *thd, MEM_ROOT *root, ACL_USER *u) const override
 | |
|   {
 | |
|     mysql_mutex_assert_owner(&acl_cache->lock);
 | |
|     u->alloc_auth(root, 1);
 | |
|     if (have_password())
 | |
|     {
 | |
|       const char *as= safe_str(::get_field(&acl_memroot, password()));
 | |
|       u->auth->auth_string.str= as;
 | |
|       u->auth->auth_string.length= strlen(as);
 | |
|       u->auth->plugin= guess_auth_plugin(thd, u->auth->auth_string.length);
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       u->auth->plugin= native_password_plugin_name;
 | |
|       u->auth->auth_string= empty_clex_str;
 | |
|     }
 | |
|     if (plugin() && authstr())
 | |
|     {
 | |
|       char *tmpstr= ::get_field(&acl_memroot, plugin());
 | |
|       if (tmpstr)
 | |
|       {
 | |
|         const char *pw= u->auth->auth_string.str;
 | |
|         const char *as= safe_str(::get_field(&acl_memroot, authstr()));
 | |
|         if (*pw)
 | |
|         {
 | |
|           if (*as && strcmp(as, pw))
 | |
|           {
 | |
|             sql_print_warning("'user' entry '%s@%s' has both a password and an "
 | |
|               "authentication plugin specified. The password will be ignored.",
 | |
|               safe_str(get_user(thd->mem_root)), safe_str(get_host(thd->mem_root)));
 | |
|           }
 | |
|           else
 | |
|             as= pw;
 | |
|         }
 | |
|         u->auth->plugin.str= tmpstr;
 | |
|         u->auth->plugin.length= strlen(tmpstr);
 | |
|         u->auth->auth_string.str= as;
 | |
|         u->auth->auth_string.length= strlen(as);
 | |
|       }
 | |
|     }
 | |
|     return 0;
 | |
|   }
 | |
| 
 | |
|   bool set_auth(const ACL_USER &u) const override
 | |
|   {
 | |
|     if (u.nauth != 1)
 | |
|       return 1;
 | |
|     if (plugin())
 | |
|     {
 | |
|       if (have_password())
 | |
|         password()->reset();
 | |
|       plugin()->store(u.auth->plugin.str, u.auth->plugin.length, system_charset_info);
 | |
|       authstr()->store(u.auth->auth_string.str, u.auth->auth_string.length, system_charset_info);
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       if (u.auth->plugin.str != native_password_plugin_name.str &&
 | |
|           u.auth->plugin.str != old_password_plugin_name.str)
 | |
|         return 1;
 | |
|       password()->store(u.auth->auth_string.str, u.auth->auth_string.length, system_charset_info);
 | |
|     }
 | |
|     return 0;
 | |
|   }
 | |
| 
 | |
|   privilege_t get_access() const override
 | |
|   {
 | |
|     privilege_t access(Grant_table_base::get_access());
 | |
|     if ((num_fields() <= 13) && (access & CREATE_ACL))
 | |
|       access|=REFERENCES_ACL | INDEX_ACL | ALTER_ACL;
 | |
| 
 | |
|     if (num_fields() <= 18)
 | |
|     {
 | |
|       access|= LOCK_TABLES_ACL | CREATE_TMP_ACL | SHOW_DB_ACL;
 | |
|       if (access & FILE_ACL)
 | |
|         access|= BINLOG_MONITOR_ACL | REPL_SLAVE_ACL | BINLOG_ADMIN_ACL |
 | |
|                  BINLOG_REPLAY_ACL;
 | |
|       if (access & PROCESS_ACL)
 | |
|         access|= SUPER_ACL | EXECUTE_ACL;
 | |
|     }
 | |
| 
 | |
|     if (num_fields() <= 31 && (access & CREATE_ACL))
 | |
|       access|= (CREATE_VIEW_ACL | SHOW_VIEW_ACL);
 | |
| 
 | |
|     if (num_fields() <= 33)
 | |
|     {
 | |
|       if (access & CREATE_ACL)
 | |
|         access|= CREATE_PROC_ACL;
 | |
|       if (access & ALTER_ACL)
 | |
|         access|= ALTER_PROC_ACL;
 | |
|     }
 | |
| 
 | |
|     if (num_fields() <= 36 && (access & GRANT_ACL))
 | |
|       access|= CREATE_USER_ACL;
 | |
| 
 | |
|     if (num_fields() <= 37 && (access & SUPER_ACL))
 | |
|       access|= EVENT_ACL;
 | |
| 
 | |
|     if (num_fields() <= 38 && (access & SUPER_ACL))
 | |
|       access|= TRIGGER_ACL;
 | |
| 
 | |
|     if (num_fields() <= 46 && (access & DELETE_ACL))
 | |
|       access|= DELETE_HISTORY_ACL;
 | |
| 
 | |
|     if (access & SUPER_ACL)
 | |
|       access|= ALLOWED_BY_SUPER_BEFORE_101100 | ALLOWED_BY_SUPER_BEFORE_110000;
 | |
| 
 | |
|     /*
 | |
|       The SHOW SLAVE HOSTS statement :
 | |
|       - required REPLICATION SLAVE privilege prior to 10.5.2
 | |
|       - requires REPLICATION MASTER ADMIN privilege since 10.5.2
 | |
|       There is no a way to GRANT MASTER ADMIN with User_table_tabular.
 | |
|       So let's automatically add REPLICATION MASTER ADMIN for all users
 | |
|       that had REPLICATION SLAVE. This will allow to do SHOW SLAVE HOSTS.
 | |
|     */
 | |
|     if (access & REPL_SLAVE_ACL)
 | |
|       access|= REPL_MASTER_ADMIN_ACL;
 | |
| 
 | |
|     if (access & REPL_SLAVE_ACL)
 | |
|       access|= SLAVE_MONITOR_ACL;
 | |
| 
 | |
|     if ((access & ALL_KNOWN_ACL_100304) == ALL_KNOWN_ACL_100304)
 | |
|       access|= SHOW_CREATE_ROUTINE_ACL;
 | |
| 
 | |
|     return access & GLOBAL_ACLS;
 | |
|   }
 | |
| 
 | |
|   void set_access(const privilege_t rights, bool revoke) const override
 | |
|   {
 | |
|     ulonglong priv(SELECT_ACL);
 | |
|     for (uint i= start_priv_columns; i < end_priv_columns; i++, priv <<= 1)
 | |
|     {
 | |
|       if (priv & rights)
 | |
|         m_table->field[i]->store(1 + !revoke, 0);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   SSL_type get_ssl_type () const override
 | |
|   {
 | |
|     Field *f= get_field(end_priv_columns, MYSQL_TYPE_ENUM);
 | |
|     return (SSL_type)(f ? f->val_int()-1 : 0);
 | |
|   }
 | |
|   int set_ssl_type (SSL_type x) const override
 | |
|   {
 | |
|     if (Field *f= get_field(end_priv_columns, MYSQL_TYPE_ENUM))
 | |
|       return f->store(x+1, 0);
 | |
|     else
 | |
|       return 1;
 | |
|   }
 | |
|   const char* get_ssl_cipher (MEM_ROOT *root) const override
 | |
|   {
 | |
|     Field *f= get_field(end_priv_columns + 1, MYSQL_TYPE_BLOB);
 | |
|     return f ? ::get_field(root,f) : 0;
 | |
|   }
 | |
|   int set_ssl_cipher (const char *s, size_t l) const override
 | |
|   {
 | |
|     if (Field *f= get_field(end_priv_columns + 1, MYSQL_TYPE_BLOB))
 | |
|       return f->store(s, l, &my_charset_latin1);
 | |
|     else
 | |
|       return 1;
 | |
|   }
 | |
|   const char* get_x509_issuer (MEM_ROOT *root) const override
 | |
|   {
 | |
|     Field *f= get_field(end_priv_columns + 2, MYSQL_TYPE_BLOB);
 | |
|     return f ? ::get_field(root,f) : 0;
 | |
|   }
 | |
|   int set_x509_issuer (const char *s, size_t l) const override
 | |
|   {
 | |
|     if (Field *f= get_field(end_priv_columns + 2, MYSQL_TYPE_BLOB))
 | |
|       return f->store(s, l, &my_charset_latin1);
 | |
|     else
 | |
|       return 1;
 | |
|   }
 | |
|   const char* get_x509_subject (MEM_ROOT *root) const override
 | |
|   {
 | |
|     Field *f= get_field(end_priv_columns + 3, MYSQL_TYPE_BLOB);
 | |
|     return f ? ::get_field(root,f) : 0;
 | |
|   }
 | |
|   int set_x509_subject (const char *s, size_t l) const override
 | |
|   {
 | |
|     if (Field *f= get_field(end_priv_columns + 3, MYSQL_TYPE_BLOB))
 | |
|       return f->store(s, l, &my_charset_latin1);
 | |
|     else
 | |
|       return 1;
 | |
|   }
 | |
|   longlong get_max_questions () const override
 | |
|   {
 | |
|     Field *f= get_field(end_priv_columns + 4, MYSQL_TYPE_LONG);
 | |
|     return f ? f->val_int() : 0;
 | |
|   }
 | |
|   int set_max_questions (longlong x) const override
 | |
|   {
 | |
|     if (Field *f= get_field(end_priv_columns + 4, MYSQL_TYPE_LONG))
 | |
|       return f->store(x, 0);
 | |
|     else
 | |
|       return 1;
 | |
|   }
 | |
|   longlong get_max_updates () const override
 | |
|   {
 | |
|     Field *f= get_field(end_priv_columns + 5, MYSQL_TYPE_LONG);
 | |
|     return f ? f->val_int() : 0;
 | |
|   }
 | |
|   int set_max_updates (longlong x) const override
 | |
|   {
 | |
|     if (Field *f= get_field(end_priv_columns + 5, MYSQL_TYPE_LONG))
 | |
|       return f->store(x, 0);
 | |
|     else
 | |
|       return 1;
 | |
|   }
 | |
|   longlong get_max_connections () const override
 | |
|   {
 | |
|     Field *f= get_field(end_priv_columns + 6, MYSQL_TYPE_LONG);
 | |
|     return f ? f->val_int() : 0;
 | |
|   }
 | |
|   int set_max_connections (longlong x) const override
 | |
|   {
 | |
|     if (Field *f= get_field(end_priv_columns + 6, MYSQL_TYPE_LONG))
 | |
|       return f->store(x, 0);
 | |
|     else
 | |
|       return 1;
 | |
|   }
 | |
|   longlong get_max_user_connections () const override
 | |
|   {
 | |
|     Field *f= get_field(end_priv_columns + 7, MYSQL_TYPE_LONG);
 | |
|     return f ? f->val_int() : 0;
 | |
|   }
 | |
|   int set_max_user_connections (longlong x) const override
 | |
|   {
 | |
|     if (Field *f= get_field(end_priv_columns + 7, MYSQL_TYPE_LONG))
 | |
|       return f->store(x, 0);
 | |
|     else
 | |
|       return 1;
 | |
|   }
 | |
|   double get_max_statement_time () const override
 | |
|   {
 | |
|     Field *f= get_field(end_priv_columns + 13, MYSQL_TYPE_NEWDECIMAL);
 | |
|     return f ? f->val_real() : 0;
 | |
|   }
 | |
|   int set_max_statement_time (double x) const override
 | |
|   {
 | |
|     if (Field *f= get_field(end_priv_columns + 13, MYSQL_TYPE_NEWDECIMAL))
 | |
|       return f->store(x);
 | |
|     else
 | |
|       return 1;
 | |
|   }
 | |
|   bool get_is_role () const override
 | |
|   {
 | |
|     Field *f= get_field(end_priv_columns + 11, MYSQL_TYPE_ENUM);
 | |
|     return f ? f->val_int()-1 : 0;
 | |
|   }
 | |
|   int set_is_role (bool x) const override
 | |
|   {
 | |
|     if (Field *f= get_field(end_priv_columns + 11, MYSQL_TYPE_ENUM))
 | |
|       return f->store(x+1, 0);
 | |
|     else
 | |
|       return 1;
 | |
|   }
 | |
|   const char* get_default_role (MEM_ROOT *root) const override
 | |
|   {
 | |
|     Field *f= get_field(end_priv_columns + 12, MYSQL_TYPE_STRING);
 | |
|     return f ? ::get_field(root,f) : 0;
 | |
|   }
 | |
|   int set_default_role (const char *s, size_t l) const override
 | |
|   {
 | |
|     if (Field *f= get_field(end_priv_columns + 12, MYSQL_TYPE_STRING))
 | |
|       return f->store(s, l, system_charset_info);
 | |
|     else
 | |
|       return 1;
 | |
|   }
 | |
|   /* On a MariaDB 10.3 user table, the account locking accessors will try to
 | |
|      get the content of the max_statement_time column, but they will fail due
 | |
|      to the typecheck in get_field. */
 | |
|   bool get_account_locked () const override
 | |
|   {
 | |
|     Field *f= get_field(end_priv_columns + 13, MYSQL_TYPE_ENUM);
 | |
|     return f ? f->val_int()-1 : 0;
 | |
|   }
 | |
|   int set_account_locked (bool x) const override
 | |
|   {
 | |
|     if (Field *f= get_field(end_priv_columns + 13, MYSQL_TYPE_ENUM))
 | |
|       return f->store(x+1, 0);
 | |
| 
 | |
|     return 1;
 | |
|   }
 | |
| 
 | |
|   bool get_password_expired () const override
 | |
|   {
 | |
|     uint field_num= end_priv_columns + 10;
 | |
| 
 | |
|     Field *f= get_field(field_num, MYSQL_TYPE_ENUM);
 | |
|     return f ? f->val_int()-1 : 0;
 | |
|   }
 | |
|   int set_password_expired (bool x) const override
 | |
|   {
 | |
|     uint field_num= end_priv_columns + 10;
 | |
| 
 | |
|     if (Field *f= get_field(field_num, MYSQL_TYPE_ENUM))
 | |
|       return f->store(x+1, 0);
 | |
|     return 1;
 | |
|   }
 | |
|   my_time_t get_password_last_changed () const override
 | |
|   {
 | |
|     ulong unused_dec;
 | |
|     if (Field *f= get_field(end_priv_columns + 11, MYSQL_TYPE_TIMESTAMP2))
 | |
|       return f->get_timestamp(&unused_dec);
 | |
|     return 0;
 | |
|   }
 | |
|   int set_password_last_changed (my_time_t x) const override
 | |
|   {
 | |
|     if (Field *f= get_field(end_priv_columns + 11, MYSQL_TYPE_TIMESTAMP2))
 | |
|     {
 | |
|       f->set_notnull();
 | |
|       return f->store_timestamp(x, 0);
 | |
|     }
 | |
|     return 1;
 | |
|   }
 | |
|   longlong get_password_lifetime () const override
 | |
|   {
 | |
|     if (Field *f= get_field(end_priv_columns + 12, MYSQL_TYPE_SHORT))
 | |
|     {
 | |
|       if (f->is_null())
 | |
|         return -1;
 | |
|       return f->val_int();
 | |
|     }
 | |
|     return 0;
 | |
|   }
 | |
|   int set_password_lifetime (longlong x) const override
 | |
|   {
 | |
|     if (Field *f= get_field(end_priv_columns + 12, MYSQL_TYPE_SHORT))
 | |
|     {
 | |
|       if (x < 0)
 | |
|       {
 | |
|         f->set_null();
 | |
|         return 0;
 | |
|       }
 | |
|       f->set_notnull();
 | |
|       return f->store(x, 0);
 | |
|     }
 | |
|     return 1;
 | |
|   }
 | |
| 
 | |
|   ~User_table_tabular() override = default;
 | |
|  private:
 | |
|   friend class Grant_tables;
 | |
| 
 | |
|   /* Only Grant_tables can instantiate this class. */
 | |
|   User_table_tabular() { min_columns= 13; /* As in 3.20.13 */ }
 | |
| 
 | |
|   /* The user table is a bit different compared to the other Grant tables.
 | |
|      Usually, we only add columns to the grant tables when adding functionality.
 | |
|      This makes it easy to test which version of the table we are using, by
 | |
|      just looking at the number of fields present in the table.
 | |
| 
 | |
|      In MySQL 5.7.6 the Password column was removed. We need to guard for that.
 | |
|      The field-fetching methods for the User table return NULL if the field
 | |
|      doesn't exist. This simplifies checking of table "version", as we don't
 | |
|      have to make use of num_fields() any more.
 | |
|   */
 | |
|   inline Field* get_field(uint field_num, enum enum_field_types type) const
 | |
|   {
 | |
|     if (field_num >= num_fields())
 | |
|       return NULL;
 | |
|     Field *f= m_table->field[field_num];
 | |
|     return f->real_type() == type ? f : NULL;
 | |
|   }
 | |
| 
 | |
|   int setup_sysvars() const override
 | |
|   {
 | |
|     username_char_length= MY_MIN(m_table->field[1]->char_length(),
 | |
|                                  USERNAME_CHAR_LENGTH);
 | |
|     using_global_priv_table= false;
 | |
| 
 | |
|     if (have_password()) // Password column might be missing. (MySQL 5.7.6+)
 | |
|     {
 | |
|       int password_length= password()->field_length /
 | |
|                            password()->charset()->mbmaxlen;
 | |
|       if (password_length < SCRAMBLED_PASSWORD_CHAR_LENGTH_323)
 | |
|       {
 | |
|         sql_print_error("Fatal error: mysql.user table is damaged or in "
 | |
|                         "unsupported 3.20 format.");
 | |
|         return 1;
 | |
|       }
 | |
| 
 | |
|       mysql_mutex_lock(&LOCK_global_system_variables);
 | |
|       if (password_length < SCRAMBLED_PASSWORD_CHAR_LENGTH)
 | |
|       {
 | |
|         if (opt_secure_auth)
 | |
|         {
 | |
|           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.");
 | |
|           return 1;
 | |
|         }
 | |
|         mysql_user_table_is_in_short_password_format= true;
 | |
|         if (global_system_variables.old_passwords)
 | |
|           mysql_mutex_unlock(&LOCK_global_system_variables);
 | |
|         else
 | |
|         {
 | |
|           extern sys_var *Sys_old_passwords_ptr;
 | |
|           Sys_old_passwords_ptr->value_origin= sys_var::AUTO;
 | |
|           global_system_variables.old_passwords= 1;
 | |
|           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");
 | |
|         }
 | |
|         m_table->in_use->variables.old_passwords= 1;
 | |
|       }
 | |
|       else
 | |
|       {
 | |
|         mysql_user_table_is_in_short_password_format= false;
 | |
|         mysql_mutex_unlock(&LOCK_global_system_variables);
 | |
|       }
 | |
|     }
 | |
|     return 0;
 | |
|   }
 | |
| 
 | |
|   /* Normally password column is the third column in the table. If privileges
 | |
|      start on the third column instead, we are missing the password column.
 | |
|      This means we are using a MySQL 5.7.6+ data directory. */
 | |
|   bool have_password() const { return start_priv_columns == 3; }
 | |
| 
 | |
|   Field* password() const { return m_table->field[2]; }
 | |
|   Field* plugin() const   { return get_field(end_priv_columns + 8, MYSQL_TYPE_STRING); }
 | |
|   Field* authstr() const  { return get_field(end_priv_columns + 9, MYSQL_TYPE_BLOB); }
 | |
| };
 | |
| 
 | |
| /*
 | |
|   MariaDB 10.4 and up `global_priv` table
 | |
| 
 | |
|   TODO possible optimizations:
 | |
|   * update json in-place if the new value can fit
 | |
|   * don't repeat get_value for every key, but use a streaming parser
 | |
|     to convert json into in-memory object (ACL_USER?) in one json scan.
 | |
|     - this makes sense for acl_load(), but hardly for GRANT
 | |
|   * similarly, pack ACL_USER (?) into json in one go.
 | |
|     - doesn't make sense? GRANT rarely updates more than one field.
 | |
| */
 | |
| class User_table_json: public User_table
 | |
| {
 | |
|   LEX_CSTRING& name() const override { return MYSQL_TABLE_NAME[USER_TABLE]; }
 | |
| 
 | |
|   int get_auth(THD *thd, MEM_ROOT *root, ACL_USER *u) const override
 | |
|   {
 | |
|     size_t array_len;
 | |
|     const char *array;
 | |
|     int vl;
 | |
|     const char *v;
 | |
| 
 | |
|     if (get_value("auth_or", JSV_ARRAY, &array, &array_len))
 | |
|     {
 | |
|       u->alloc_auth(root, 1);
 | |
|       return get_auth1(thd, root, u, 0);
 | |
|     }
 | |
| 
 | |
|     if (json_get_array_item(array, array + array_len, (int)array_len,
 | |
|                             &v, &vl) != JSV_NOTHING)
 | |
|       return 1;
 | |
|     u->alloc_auth(root, vl);
 | |
|     for (uint i=0; i < u->nauth; i++)
 | |
|     {
 | |
|       if (json_get_array_item(array, array + array_len, i, &v, &vl) != JSV_OBJECT)
 | |
|         return 1;
 | |
| 
 | |
|       const char *p, *a;
 | |
|       int pl, al;
 | |
|       switch (json_get_object_key(v, v + vl, "plugin", &p, &pl)) {
 | |
|       case JSV_STRING: u->auth[i].plugin.str= strmake_root(root, p, pl);
 | |
|                        u->auth[i].plugin.length= pl;
 | |
|                        break;
 | |
|       case JSV_NOTHING: if (get_auth1(thd, root, u, i))
 | |
|                           return 1;
 | |
|                         else
 | |
|                           continue;
 | |
|       default: return 1;
 | |
|       }
 | |
|       switch (json_get_object_key(v, v + vl, "authentication_string", &a, &al)) {
 | |
|       case JSV_NOTHING: u->auth[i].auth_string= empty_clex_str;
 | |
|                         break;
 | |
|       case JSV_STRING: u->auth[i].auth_string.str= strmake_root(root, a, al);
 | |
|                        u->auth[i].auth_string.length= al;
 | |
|                        break;
 | |
|       default: return 1;
 | |
|       }
 | |
|     }
 | |
|     return 0;
 | |
|   }
 | |
| 
 | |
|   int get_auth1(THD *thd, MEM_ROOT *root, ACL_USER *u, uint n) const
 | |
|   {
 | |
|     const char *authstr= get_str_value(root, "authentication_string");
 | |
|     const char *plugin= get_str_value(root, "plugin");
 | |
|     if (plugin && authstr)
 | |
|     {
 | |
|       if (plugin && *plugin)
 | |
|       {
 | |
|         u->auth[n].plugin.str= plugin;
 | |
|         u->auth[n].plugin.length= strlen(plugin);
 | |
|       }
 | |
|       else
 | |
|         u->auth[n].plugin= native_password_plugin_name;
 | |
|       u->auth[n].auth_string.str= authstr;
 | |
|       u->auth[n].auth_string.length= strlen(authstr);
 | |
|       return 0;
 | |
|     }
 | |
|     return 1;
 | |
|   }
 | |
| 
 | |
|   bool append_str_value(String *to, const LEX_CSTRING &str) const
 | |
|   {
 | |
|     to->append('"');
 | |
|     to->reserve(str.length*2);
 | |
|     int len= json_escape(system_charset_info, (uchar*)str.str, (uchar*)str.str + str.length,
 | |
|                          to->charset(), (uchar*)to->end(), (uchar*)to->end() + str.length*2);
 | |
|     if (len < 0)
 | |
|       return 1;
 | |
|     to->length(to->length() + len);
 | |
|     to->append('"');
 | |
|     return 0;
 | |
|   }
 | |
| 
 | |
|   bool set_auth(const ACL_USER &u) const override
 | |
|   {
 | |
|     size_t array_len;
 | |
|     const char *array;
 | |
|     if (u.nauth == 1 && get_value("auth_or", JSV_ARRAY, &array, &array_len))
 | |
|       return set_auth1(u, 0);
 | |
| 
 | |
|     StringBuffer<JSON_SIZE> json(m_table->field[2]->charset());
 | |
|     bool top_done = false;
 | |
|     json.append('[');
 | |
|     for (uint i=0; i < u.nauth; i++)
 | |
|     {
 | |
|       ACL_USER::AUTH * const auth= u.auth + i;
 | |
|       if (i)
 | |
|         json.append(',');
 | |
|       json.append('{');
 | |
|       if (!top_done &&
 | |
|           (auth->plugin.str == native_password_plugin_name.str ||
 | |
|            auth->plugin.str == old_password_plugin_name.str ||
 | |
|            i == u.nauth - 1))
 | |
|       {
 | |
|         if (set_auth1(u, i))
 | |
|           return 1;
 | |
|         top_done= true;
 | |
|       }
 | |
|       else
 | |
|       {
 | |
|         json.append(STRING_WITH_LEN("\"plugin\":"));
 | |
|         if (append_str_value(&json, auth->plugin))
 | |
|           return 1;
 | |
|         if (auth->auth_string.length)
 | |
|         {
 | |
|           json.append(STRING_WITH_LEN(",\"authentication_string\":"));
 | |
|           if (append_str_value(&json, auth->auth_string))
 | |
|             return 1;
 | |
|         }
 | |
|       }
 | |
|       json.append('}');
 | |
|     }
 | |
|     json.append(']');
 | |
|     return set_value("auth_or", json.ptr(), json.length(), false) == JSV_BAD_JSON;
 | |
|   }
 | |
|   bool set_auth1(const ACL_USER &u, uint i) const
 | |
|   {
 | |
|     return set_str_value("plugin",
 | |
|                          u.auth[i].plugin.str, u.auth[i].plugin.length) ||
 | |
|             set_str_value("authentication_string",
 | |
|                          u.auth[i].auth_string.str, u.auth[i].auth_string.length);
 | |
|   }
 | |
| 
 | |
|   void print_warning_bad_version_id(ulonglong version_id) const
 | |
|   {
 | |
|     sql_print_warning("'user' entry '%s@%s' has a wrong 'version_id' value %lld",
 | |
|                       safe_str(get_user(current_thd->mem_root)),
 | |
|                       safe_str(get_host(current_thd->mem_root)),
 | |
|                       version_id);
 | |
|   }
 | |
| 
 | |
|   void print_warning_bad_access(ulonglong version_id,
 | |
|                                 privilege_t mask,
 | |
|                                 ulonglong access) const
 | |
|   {
 | |
|     sql_print_warning("'user' entry '%s@%s' "
 | |
|                       "has a wrong 'access' value 0x%llx "
 | |
|                       "(allowed mask is 0x%llx, version_id is %llu)",
 | |
|                       safe_str(get_user(current_thd->mem_root)),
 | |
|                       safe_str(get_host(current_thd->mem_root)),
 | |
|                       access, (ulonglong) mask, version_id);
 | |
|   }
 | |
| 
 | |
|   privilege_t adjust_access(ulonglong version_id, ulonglong access) const
 | |
|   {
 | |
|     privilege_t mask= ALL_KNOWN_ACL_100304;
 | |
|     ulonglong orig_access= access;
 | |
|     if (version_id < 110000)
 | |
|     {
 | |
|       if (access & SUPER_ACL)
 | |
|         access|= ALLOWED_BY_SUPER_BEFORE_110000;
 | |
|     }
 | |
|     if (version_id < 101100)
 | |
|     {
 | |
|       if (access & SUPER_ACL)
 | |
|         access|= ALLOWED_BY_SUPER_BEFORE_101100;
 | |
|     }
 | |
|     if (version_id >= 110300)
 | |
|     {
 | |
|       mask= ALL_KNOWN_ACL_110300;
 | |
|     }
 | |
|     else if (version_id >= 100509)
 | |
|     {
 | |
|       mask= ALL_KNOWN_ACL_100509;
 | |
|     }
 | |
|     else if (version_id >= 100502)
 | |
|     {
 | |
|       if (version_id >= 100508)
 | |
|         mask= ALL_KNOWN_ACL_100508;
 | |
|       else
 | |
|         mask= ALL_KNOWN_ACL_100502;
 | |
|       if (access & REPL_SLAVE_ADMIN_ACL)
 | |
|         access|= SLAVE_MONITOR_ACL;
 | |
|     }
 | |
|     else // 100501 or earlier
 | |
|     {
 | |
|       /*
 | |
|         REPLICATION_CLIENT(BINLOG_MONITOR_ACL) should allow SHOW SLAVE STATUS
 | |
|         REPLICATION SLAVE should allow SHOW RELAYLOG EVENTS
 | |
|       */
 | |
|       if (access & BINLOG_MONITOR_ACL || access & REPL_SLAVE_ACL)
 | |
|         access|= SLAVE_MONITOR_ACL;
 | |
|     }
 | |
| 
 | |
|     if (orig_access & ~mask)
 | |
|     {
 | |
|       print_warning_bad_access(version_id, mask, orig_access);
 | |
|       return NO_ACL;
 | |
|     }
 | |
| 
 | |
|     // ALL PRIVILEGES always means ALL PRIVILEGES
 | |
|     if ((orig_access & mask) == mask)
 | |
|       access= ALL_KNOWN_ACL;
 | |
| 
 | |
|     return access & ALL_KNOWN_ACL;
 | |
|   }
 | |
| 
 | |
|   privilege_t get_access() const override
 | |
|   {
 | |
|     ulonglong version_id= (ulonglong) get_int_value("version_id");
 | |
|     ulonglong access= (ulonglong) get_int_value("access");
 | |
| 
 | |
|     /*
 | |
|       Special case:
 | |
|       mysql_system_tables_data.sql populates "ALL PRIVILEGES"
 | |
|       for the super user this way:
 | |
|             {"access":18446744073709551615}
 | |
|     */
 | |
|     if (access == (ulonglong) ~0)
 | |
|       return GLOBAL_ACLS;
 | |
| 
 | |
|     /*
 | |
|       Reject obviously bad (negative and too large) version_id values.
 | |
|       Also reject versions before 10.4.0 (when JSON table was added).
 | |
|     */
 | |
|     if ((longlong) version_id < 0 || version_id > 999999 ||
 | |
|         (version_id > 0 && version_id < 100400))
 | |
|     {
 | |
|       print_warning_bad_version_id(version_id);
 | |
|       return NO_ACL;
 | |
|     }
 | |
|     return adjust_access(version_id, access) & GLOBAL_ACLS;
 | |
|   }
 | |
| 
 | |
|   void set_access(const privilege_t rights, bool revoke) const override
 | |
|   {
 | |
|     privilege_t access= get_access();
 | |
|     if (revoke)
 | |
|       access&= ~rights;
 | |
|     else
 | |
|       access|= rights;
 | |
|     set_int_value("access", (longlong) (access & GLOBAL_ACLS));
 | |
|     set_int_value("version_id", (longlong) MYSQL_VERSION_ID);
 | |
|   }
 | |
|   const char *unsafe_str(const char *s) const
 | |
|   { return s[0] ? s : NULL; }
 | |
| 
 | |
|   SSL_type get_ssl_type () const override
 | |
|   { return (SSL_type)get_int_value("ssl_type"); }
 | |
|   int set_ssl_type (SSL_type x) const override
 | |
|   { return set_int_value("ssl_type", x); }
 | |
|   const char* get_ssl_cipher (MEM_ROOT *root) const override
 | |
|   { return unsafe_str(get_str_value(root, "ssl_cipher")); }
 | |
|   int set_ssl_cipher (const char *s, size_t l) const override
 | |
|   { return set_str_value("ssl_cipher", s, l); }
 | |
|   const char* get_x509_issuer (MEM_ROOT *root) const override
 | |
|   { return unsafe_str(get_str_value(root, "x509_issuer")); }
 | |
|   int set_x509_issuer (const char *s, size_t l) const override
 | |
|   { return set_str_value("x509_issuer", s, l); }
 | |
|   const char* get_x509_subject (MEM_ROOT *root) const override
 | |
|   { return unsafe_str(get_str_value(root, "x509_subject")); }
 | |
|   int set_x509_subject (const char *s, size_t l) const override
 | |
|   { return set_str_value("x509_subject", s, l); }
 | |
|   longlong get_max_questions () const override
 | |
|   { return get_int_value("max_questions"); }
 | |
|   int set_max_questions (longlong x) const override
 | |
|   { return set_int_value("max_questions", x); }
 | |
|   longlong get_max_updates () const override
 | |
|   { return get_int_value("max_updates"); }
 | |
|   int set_max_updates (longlong x) const override
 | |
|   { return set_int_value("max_updates", x); }
 | |
|   longlong get_max_connections () const override
 | |
|   { return get_int_value("max_connections"); }
 | |
|   int set_max_connections (longlong x) const override
 | |
|   { return set_int_value("max_connections", x); }
 | |
|   longlong get_max_user_connections () const override
 | |
|   { return get_int_value("max_user_connections"); }
 | |
|   int set_max_user_connections (longlong x) const override
 | |
|   { return set_int_value("max_user_connections", x); }
 | |
|   double get_max_statement_time () const override
 | |
|   { return get_double_value("max_statement_time"); }
 | |
|   int set_max_statement_time (double x) const override
 | |
|   { return set_double_value("max_statement_time", x); }
 | |
|   bool get_is_role () const override
 | |
|   { return get_bool_value("is_role"); }
 | |
|   int set_is_role (bool x) const override
 | |
|   { return set_bool_value("is_role", x); }
 | |
|   const char* get_default_role (MEM_ROOT *root) const override
 | |
|   { return get_str_value(root, "default_role"); }
 | |
|   int set_default_role (const char *s, size_t l) const override
 | |
|   { return set_str_value("default_role", s, l); }
 | |
|   bool get_account_locked () const override
 | |
|   { return get_bool_value("account_locked"); }
 | |
|   int set_account_locked (bool x) const override
 | |
|   { return set_bool_value("account_locked", x); }
 | |
|   my_time_t get_password_last_changed () const override
 | |
|   { return static_cast<my_time_t>(get_int_value("password_last_changed")); }
 | |
|   int set_password_last_changed (my_time_t x) const override
 | |
|   { return set_int_value("password_last_changed", static_cast<longlong>(x)); }
 | |
|   int set_password_lifetime (longlong x) const override
 | |
|   { return set_int_value("password_lifetime", x); }
 | |
|   longlong get_password_lifetime () const override
 | |
|   { return get_int_value("password_lifetime", -1); }
 | |
|   /*
 | |
|      password_last_changed=0 means the password is manually expired.
 | |
|      In MySQL 5.7+ this state is described using the password_expired column
 | |
|      in mysql.user
 | |
|   */
 | |
|   bool get_password_expired () const override
 | |
|   { return get_int_value("password_last_changed", -1) == 0; }
 | |
|   int set_password_expired (bool x) const override
 | |
|   { return x ? set_password_last_changed(0) : 0; }
 | |
| 
 | |
|   ~User_table_json() override = default;
 | |
|  private:
 | |
|   friend class Grant_tables;
 | |
|   static const uint JSON_SIZE=1024;
 | |
|   int setup_sysvars() const override
 | |
|   {
 | |
|     using_global_priv_table= true;
 | |
|     username_char_length= MY_MIN(m_table->field[1]->char_length(),
 | |
|                                  USERNAME_CHAR_LENGTH);
 | |
|     return 0;
 | |
|   }
 | |
|   bool get_value(const char *key, 
 | |
|                  enum json_types vt, const char **v, size_t *vl) const
 | |
|   {
 | |
|     enum json_types value_type;
 | |
|     int int_vl;
 | |
|     String str, *res= m_table->field[2]->val_str(&str);
 | |
|     if (!res ||
 | |
|         (value_type= json_get_object_key(res->ptr(), res->end(), key,
 | |
|                                              v, &int_vl)) == JSV_BAD_JSON)
 | |
|       return 1; // invalid
 | |
|     *vl= int_vl;
 | |
|     return value_type != vt;
 | |
|   }
 | |
|   const char *get_str_value(MEM_ROOT *root, const char *key) const
 | |
|   {
 | |
|     size_t value_len;
 | |
|     const char *value_start;
 | |
|     if (get_value(key, JSV_STRING, &value_start, &value_len))
 | |
|       return "";
 | |
|     char *ptr= (char*)alloca(value_len);
 | |
|     int len= json_unescape(m_table->field[2]->charset(),
 | |
|                            (const uchar*)value_start,
 | |
|                            (const uchar*)value_start + value_len,
 | |
|                            system_charset_info,
 | |
|                            (uchar*)ptr, (uchar*)ptr + value_len);
 | |
|     if (len < 0)
 | |
|       return NULL;
 | |
|     return strmake_root(root, ptr, len);
 | |
|   }
 | |
|   longlong get_int_value(const char *key, longlong def_val= 0) const
 | |
|   {
 | |
|     int err;
 | |
|     size_t value_len;
 | |
|     const char *value_start;
 | |
|     if (get_value(key, JSV_NUMBER, &value_start, &value_len))
 | |
|       return def_val;
 | |
|     const char *value_end= value_start + value_len;
 | |
|     return my_strtoll10(value_start, (char**)&value_end, &err);
 | |
|   }
 | |
|   double get_double_value(const char *key) const
 | |
|   {
 | |
|     int err;
 | |
|     size_t value_len;
 | |
|     const char *value_start;
 | |
|     if (get_value(key, JSV_NUMBER, &value_start, &value_len))
 | |
|       return 0;
 | |
|     const char *value_end= value_start + value_len;
 | |
|     return my_strtod(value_start, (char**)&value_end, &err);
 | |
|   }
 | |
|   bool get_bool_value(const char *key) const
 | |
|   {
 | |
|     size_t value_len;
 | |
|     const char *value_start;
 | |
|     if (get_value(key, JSV_TRUE, &value_start, &value_len))
 | |
|       return false;
 | |
|     return true;
 | |
|   }
 | |
|   enum json_types set_value(const char *key,
 | |
|                             const char *val, size_t vlen, bool string) const
 | |
|   {
 | |
|     int value_len;
 | |
|     const char *value_start;
 | |
|     enum json_types value_type;
 | |
|     String str, *res= m_table->field[2]->val_str(&str);
 | |
|     if (!res || !res->length())
 | |
|       (res= &str)->set(STRING_WITH_LEN("{}"), m_table->field[2]->charset());
 | |
|     value_type= json_get_object_key(res->ptr(), res->end(), key,
 | |
|                                     &value_start, &value_len);
 | |
|     if (value_type == JSV_BAD_JSON)
 | |
|       return value_type; // invalid
 | |
|     StringBuffer<JSON_SIZE> json(res->charset());
 | |
|     json.copy(res->ptr(), value_start - res->ptr(), res->charset());
 | |
|     if (value_type == JSV_NOTHING)
 | |
|     {
 | |
|       if (value_len)
 | |
|         json.append(',');
 | |
|       json.append('"');
 | |
|       json.append(key, strlen(key));
 | |
|       json.append(STRING_WITH_LEN("\":"));
 | |
|       if (string)
 | |
|         json.append('"');
 | |
|     }
 | |
|     else
 | |
|       value_start+= value_len;
 | |
|     json.append(val, vlen);
 | |
|     if (!value_type && string)
 | |
|       json.append('"');
 | |
|     json.append(value_start, res->end() - value_start);
 | |
|     DBUG_ASSERT(json_valid(json.ptr(), json.length(), json.charset()));
 | |
|     m_table->field[2]->store(json.ptr(), json.length(), json.charset());
 | |
|     return value_type;
 | |
|   }
 | |
|   bool set_str_value(const char *key, const char *val, size_t vlen) const
 | |
|   {
 | |
|     char buf[JSON_SIZE];
 | |
|     int blen= json_escape(system_charset_info,
 | |
|                           (const uchar*)val, (const uchar*)val + vlen,
 | |
|                           m_table->field[2]->charset(),
 | |
|                           (uchar*)buf, (uchar*)buf+sizeof(buf));
 | |
|     if (blen < 0)
 | |
|       return 1;
 | |
|     return set_value(key, buf, blen, true) == JSV_BAD_JSON;
 | |
|   }
 | |
|   bool set_int_value(const char *key, longlong val) const
 | |
|   {
 | |
|     char v[MY_INT64_NUM_DECIMAL_DIGITS+1];
 | |
|     size_t vlen= longlong10_to_str(val, v, -10) - v;
 | |
|     return set_value(key, v, vlen, false) == JSV_BAD_JSON;
 | |
|   }
 | |
|   bool set_double_value(const char *key, double val) const
 | |
|   {
 | |
|     char v[FLOATING_POINT_BUFFER+1];
 | |
|     size_t vlen= my_fcvt(val, TIME_SECOND_PART_DIGITS, v, NULL);
 | |
|     return set_value(key, v, vlen, false) == JSV_BAD_JSON;
 | |
|   }
 | |
|   bool set_bool_value(const char *key, bool val) const
 | |
|   {
 | |
|     return set_value(key, val ? "true" : "false", val ? 4 : 5, false) == JSV_BAD_JSON;
 | |
|   }
 | |
| };
 | |
| 
 | |
| class Db_table: public Grant_table_base
 | |
| {
 | |
|  public:
 | |
|   Field* host() const { return m_table->field[0]; }
 | |
|   Field* db() const { return m_table->field[1]; }
 | |
|   Field* user() const { return m_table->field[2]; }
 | |
| 
 | |
|  private:
 | |
|   friend class Grant_tables;
 | |
| 
 | |
|   Db_table() { min_columns= 9; /* as in 3.20.13 */ }
 | |
| };
 | |
| 
 | |
| class Tables_priv_table: public Grant_table_base
 | |
| {
 | |
|  public:
 | |
|   Field* host() const { return m_table->field[0]; }
 | |
|   Field* db() const { return m_table->field[1]; }
 | |
|   Field* user() const { return m_table->field[2]; }
 | |
|   Field* table_name() const { return m_table->field[3]; }
 | |
|   Field* grantor() const { return m_table->field[4]; }
 | |
|   Field* timestamp() const { return m_table->field[5]; }
 | |
|   Field* table_priv() const { return m_table->field[6]; }
 | |
|   Field* column_priv() const { return m_table->field[7]; }
 | |
| 
 | |
|  private:
 | |
|   friend class Grant_tables;
 | |
| 
 | |
|   Tables_priv_table() { min_columns= 8; /* as in 3.22.26a */ }
 | |
| };
 | |
| 
 | |
| class Columns_priv_table: public Grant_table_base
 | |
| {
 | |
|  public:
 | |
|   Field* host() const { return m_table->field[0]; }
 | |
|   Field* db() const { return m_table->field[1]; }
 | |
|   Field* user() const { return m_table->field[2]; }
 | |
|   Field* table_name() const { return m_table->field[3]; }
 | |
|   Field* column_name() const { return m_table->field[4]; }
 | |
|   Field* timestamp() const { return m_table->field[5]; }
 | |
|   Field* column_priv() const { return m_table->field[6]; }
 | |
| 
 | |
|  private:
 | |
|   friend class Grant_tables;
 | |
| 
 | |
|   Columns_priv_table() { min_columns= 7; /* as in 3.22.26a */ }
 | |
| };
 | |
| 
 | |
| class Host_table: public Grant_table_base
 | |
| {
 | |
|  public:
 | |
|   Field* host() const { return m_table->field[0]; }
 | |
|   Field* db() const { return m_table->field[1]; }
 | |
| 
 | |
|  private:
 | |
|   friend class Grant_tables;
 | |
| 
 | |
|   Host_table() { min_columns= 8; /* as in 3.20.13 */ }
 | |
| };
 | |
| 
 | |
| class Procs_priv_table: public Grant_table_base
 | |
| {
 | |
|  public:
 | |
|   Field* host() const { return m_table->field[0]; }
 | |
|   Field* db() const { return m_table->field[1]; }
 | |
|   Field* user() const { return m_table->field[2]; }
 | |
|   Field* routine_name() const { return m_table->field[3]; }
 | |
|   Field* routine_type() const { return m_table->field[4]; }
 | |
|   Field* grantor() const { return m_table->field[5]; }
 | |
|   Field* proc_priv() const { return m_table->field[6]; }
 | |
|   Field* timestamp() const { return m_table->field[7]; }
 | |
| 
 | |
|  private:
 | |
|   friend class Grant_tables;
 | |
| 
 | |
|   Procs_priv_table() { min_columns=8; }
 | |
| };
 | |
| 
 | |
| class Proxies_priv_table: public Grant_table_base
 | |
| {
 | |
|  public:
 | |
|   Field* host() const { return m_table->field[0]; }
 | |
|   Field* user() const { return m_table->field[1]; }
 | |
|   Field* proxied_host() const { return m_table->field[2]; }
 | |
|   Field* proxied_user() const { return m_table->field[3]; }
 | |
|   Field* with_grant() const { return m_table->field[4]; }
 | |
|   Field* grantor() const { return m_table->field[5]; }
 | |
|   Field* timestamp() const { return m_table->field[6]; }
 | |
| 
 | |
|  private:
 | |
|   friend class Grant_tables;
 | |
| 
 | |
|   Proxies_priv_table() { min_columns= 7; }
 | |
| };
 | |
| 
 | |
| class Roles_mapping_table: public Grant_table_base
 | |
| {
 | |
|  public:
 | |
|   Field* host() const { return m_table->field[0]; }
 | |
|   Field* user() const { return m_table->field[1]; }
 | |
|   Field* role() const { return m_table->field[2]; }
 | |
|   Field* admin_option() const { return m_table->field[3]; }
 | |
| 
 | |
|  private:
 | |
|   friend class Grant_tables;
 | |
| 
 | |
|   Roles_mapping_table() { min_columns= 4; }
 | |
| };
 | |
| 
 | |
| /**
 | |
|   Class that represents a collection of grant tables.
 | |
| */
 | |
| class Grant_tables
 | |
| {
 | |
|  public:
 | |
|   Grant_tables() : p_user_table(&m_user_table_json) { }
 | |
| 
 | |
|   /**
 | |
|     An auxiliary to build a list of involved tables.
 | |
| 
 | |
|     @retval  0 Success
 | |
|     @retval -1 A my_error reported error
 | |
|    */
 | |
|   int build_table_list(THD *thd, TABLE_LIST** ptr_first,
 | |
|                        int which_tables, enum thr_lock_type lock_type,
 | |
|                        TABLE_LIST *tables)
 | |
|   {
 | |
|     DBUG_ENTER("Grant_tables::build_table_list");
 | |
| 
 | |
|     DBUG_ASSERT(which_tables); /* At least one table must be opened. */
 | |
|     /*
 | |
|        We can read privilege tables even when !initialized.
 | |
|        This can be acl_load() - server startup or FLUSH PRIVILEGES
 | |
|        */
 | |
|     if (lock_type >= TL_FIRST_WRITE && !initialized)
 | |
|     {
 | |
|       my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--skip-grant-tables");
 | |
|       DBUG_RETURN(-1);
 | |
|     }
 | |
| 
 | |
|     for (int i=USER_TABLE; i >=0; i--)
 | |
|     {
 | |
|       TABLE_LIST *tl= tables + i;
 | |
|       if (which_tables & (1 << i))
 | |
|       {
 | |
|         tl->init_one_table(&MYSQL_SCHEMA_NAME, &MYSQL_TABLE_NAME[i],
 | |
|                            NULL, lock_type);
 | |
|         tl->open_type= OT_BASE_ONLY;
 | |
|         tl->i_s_requested_object= OPEN_TABLE_ONLY;
 | |
|         tl->updating= lock_type >= TL_FIRST_WRITE;
 | |
|         if (i >= FIRST_OPTIONAL_TABLE)
 | |
|           tl->open_strategy= TABLE_LIST::OPEN_IF_EXISTS;
 | |
|         tl->next_global= tl->next_local= *ptr_first;
 | |
|         *ptr_first= tl;
 | |
|       }
 | |
|       else
 | |
|         tl->table= NULL;
 | |
|     }
 | |
|     DBUG_RETURN(0);
 | |
|   }
 | |
| 
 | |
|   int open_and_lock(THD *thd, int which_tables, enum thr_lock_type lock_type)
 | |
|   {
 | |
|     DBUG_ENTER("Grant_tables::open_and_lock");
 | |
| 
 | |
|     TABLE_LIST tables[USER_TABLE+1], *first= NULL;
 | |
| 
 | |
|     if (build_table_list(thd, &first, which_tables, lock_type, tables))
 | |
|       DBUG_RETURN(-1);
 | |
| 
 | |
|     uint counter;
 | |
|     int res= really_open(thd, first, &counter);
 | |
| 
 | |
|     /* if User_table_json wasn't found, let's try User_table_tabular */
 | |
|     if (!res && (which_tables & Table_user) && !tables[USER_TABLE].table)
 | |
|     {
 | |
|       uint unused;
 | |
|       TABLE_LIST *tl= tables + USER_TABLE;
 | |
|       TABLE *backup_open_tables= thd->open_tables;
 | |
|       thd->set_open_tables(NULL);
 | |
| 
 | |
|       tl->init_one_table(&MYSQL_SCHEMA_NAME, &MYSQL_TABLE_NAME_USER,
 | |
|                          NULL, lock_type);
 | |
|       tl->open_type= OT_BASE_ONLY;
 | |
|       tl->i_s_requested_object= OPEN_TABLE_ONLY;
 | |
|       tl->updating= lock_type >= TL_FIRST_WRITE;
 | |
|       p_user_table= &m_user_table_tabular;
 | |
|       counter++;
 | |
|       res= really_open(thd, tl, &unused);
 | |
|       thd->set_open_tables(backup_open_tables);
 | |
|       if (tables[USER_TABLE].table)
 | |
|       {
 | |
|         tables[USER_TABLE].table->next= backup_open_tables;
 | |
|         thd->set_open_tables(tables[USER_TABLE].table);
 | |
|       }
 | |
|     }
 | |
|     if (res)
 | |
|       DBUG_RETURN(res);
 | |
| 
 | |
|     if (lock_tables(thd, first, counter,
 | |
|                     MYSQL_LOCK_IGNORE_TIMEOUT |
 | |
|                     MYSQL_OPEN_IGNORE_LOGGING_FORMAT))
 | |
|       DBUG_RETURN(-1);
 | |
| 
 | |
|     p_user_table->set_table(tables[USER_TABLE].table);
 | |
|     m_db_table.set_table(tables[DB_TABLE].table);
 | |
|     m_tables_priv_table.set_table(tables[TABLES_PRIV_TABLE].table);
 | |
|     m_columns_priv_table.set_table(tables[COLUMNS_PRIV_TABLE].table);
 | |
|     m_host_table.set_table(tables[HOST_TABLE].table);
 | |
|     m_procs_priv_table.set_table(tables[PROCS_PRIV_TABLE].table);
 | |
|     m_proxies_priv_table.set_table(tables[PROXIES_PRIV_TABLE].table);
 | |
|     m_roles_mapping_table.set_table(tables[ROLES_MAPPING_TABLE].table);
 | |
|     DBUG_RETURN(0);
 | |
|   }
 | |
| 
 | |
|   inline const User_table& user_table() const
 | |
|   { return *p_user_table; }
 | |
| 
 | |
|   inline const Db_table& db_table() const
 | |
|   { return m_db_table; }
 | |
| 
 | |
|   inline const Tables_priv_table& tables_priv_table() const
 | |
|   { return m_tables_priv_table; }
 | |
| 
 | |
|   inline const Columns_priv_table& columns_priv_table() const
 | |
|   { return m_columns_priv_table; }
 | |
| 
 | |
|   inline const Host_table& host_table() const
 | |
|   { return m_host_table; }
 | |
| 
 | |
|   inline const Procs_priv_table& procs_priv_table() const
 | |
|   { return m_procs_priv_table; }
 | |
| 
 | |
|   inline const Proxies_priv_table& proxies_priv_table() const
 | |
|   { return m_proxies_priv_table; }
 | |
| 
 | |
|   inline const Roles_mapping_table& roles_mapping_table() const
 | |
|   { return m_roles_mapping_table; }
 | |
| 
 | |
| #ifdef HAVE_REPLICATION
 | |
|   /**
 | |
|     Checks if the tables targeted by a grant command should be ignored because
 | |
|     of the configured replication filters
 | |
| 
 | |
|     @retval 1 Tables are excluded for replication
 | |
|     @retval 0 tables are included for replication
 | |
|   */
 | |
|   int rpl_ignore_tables(THD *thd, TABLE_LIST* tables, int which_tables= 0,
 | |
|                         enum thr_lock_type lock_type= TL_IGNORE)
 | |
|   {
 | |
|     DBUG_ENTER("Grant_tables::rpl_ignore_tables");
 | |
| 
 | |
|     if (!(thd->slave_thread && !thd->spcont))
 | |
|       DBUG_RETURN(0);
 | |
| 
 | |
|     TABLE_LIST all_tables[USER_TABLE+1];
 | |
| 
 | |
|     if (!tables)
 | |
|     {
 | |
|       int rc __attribute__((unused))=
 | |
|         build_table_list(thd, &tables, which_tables, lock_type, all_tables);
 | |
| 
 | |
|       DBUG_ASSERT(!rc);  // Grant_tables must be already initialized
 | |
|       DBUG_ASSERT(tables);
 | |
|     }
 | |
| 
 | |
|     if (tables->lock_type >= TL_FIRST_WRITE)
 | |
|     {
 | |
|       /*
 | |
|         GRANT and REVOKE are applied the slave in/exclusion rules as they are
 | |
|         some kind of updates to the mysql.% tables.
 | |
|       */
 | |
|       Rpl_filter *rpl_filter= thd->system_thread_info.rpl_sql_info->rpl_filter;
 | |
|       if (rpl_filter->is_on() && !rpl_filter->tables_ok(0, tables))
 | |
|       {
 | |
|         thd->slave_expected_error= 0;
 | |
|         DBUG_RETURN(1);
 | |
|       }
 | |
|     }
 | |
|     DBUG_RETURN(0);
 | |
|   }
 | |
| #endif
 | |
| 
 | |
|  private:
 | |
| 
 | |
|   /* Before any operation is possible on grant tables, they must be opened.
 | |
| 
 | |
|      @retval  1 replication filters matched. Abort the operation,
 | |
|                 but return OK (!)
 | |
|      @retval  0 tables were opened successfully
 | |
|      @retval -1 error, tables could not be opened
 | |
|   */
 | |
|   int really_open(THD *thd, TABLE_LIST* tables, uint *counter)
 | |
|   {
 | |
|     DBUG_ENTER("Grant_tables::really_open:");
 | |
| #ifdef HAVE_REPLICATION
 | |
|     if (rpl_ignore_tables(thd, tables))
 | |
|     {
 | |
|       DBUG_RETURN(1);
 | |
|     }
 | |
| #endif
 | |
|     if (open_tables(thd, &tables, counter, MYSQL_LOCK_IGNORE_TIMEOUT))
 | |
|       DBUG_RETURN(-1);
 | |
|     DBUG_RETURN(0);
 | |
|   }
 | |
| 
 | |
|   User_table *p_user_table;
 | |
|   User_table_json m_user_table_json;
 | |
|   User_table_tabular m_user_table_tabular;
 | |
|   Db_table m_db_table;
 | |
|   Tables_priv_table m_tables_priv_table;
 | |
|   Columns_priv_table m_columns_priv_table;
 | |
|   Host_table m_host_table;
 | |
|   Procs_priv_table m_procs_priv_table;
 | |
|   Proxies_priv_table m_proxies_priv_table;
 | |
|   Roles_mapping_table m_roles_mapping_table;
 | |
| };
 | |
| 
 | |
| 
 | |
| void ACL_PROXY_USER::init(const Proxies_priv_table& proxies_priv_table,
 | |
|                           MEM_ROOT *mem)
 | |
| {
 | |
|   init(get_field(mem, proxies_priv_table.host()),
 | |
|        safe_str(get_field(mem, proxies_priv_table.user())),
 | |
|        get_field(mem, proxies_priv_table.proxied_host()),
 | |
|        safe_str(get_field(mem, proxies_priv_table.proxied_user())),
 | |
|        proxies_priv_table.with_grant()->val_int() != 0);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  Enumeration of various ACL's and Hashes used in handle_grant_struct()
 | |
| */
 | |
| enum enum_acl_lists
 | |
| {
 | |
|   USER_ACL= 0,
 | |
|   ROLE_ACL,
 | |
|   DB_ACL,
 | |
|   COLUMN_PRIVILEGES_HASH,
 | |
|   PROC_PRIVILEGES_HASH,
 | |
|   FUNC_PRIVILEGES_HASH,
 | |
|   PACKAGE_SPEC_PRIVILEGES_HASH,
 | |
|   PACKAGE_BODY_PRIVILEGES_HASH,
 | |
|   PROXY_USERS_ACL,
 | |
|   ROLES_MAPPINGS_HASH
 | |
| };
 | |
| 
 | |
| ACL_ROLE::ACL_ROLE(ACL_USER *user)
 | |
|  :
 | |
|   /* set initial role access the same as the table row privileges */
 | |
|   initial_role_access(user->access),
 | |
|   counter(0)
 | |
| {
 | |
|   access= user->access;
 | |
|   this->user= user->user;
 | |
|   bzero(&parent_grantee, sizeof(parent_grantee));
 | |
|   flags= IS_ROLE;
 | |
| }
 | |
| 
 | |
| ACL_ROLE::ACL_ROLE(const char *rolename, privilege_t privileges, MEM_ROOT *root)
 | |
|   : initial_role_access(privileges), counter(0)
 | |
| {
 | |
|   this->access= initial_role_access;
 | |
|   if (is_public(rolename))
 | |
|     this->user= public_name;
 | |
|   else
 | |
|   {
 | |
|     this->user.str= safe_strdup_root(root, rolename);
 | |
|     this->user.length= strlen(rolename);
 | |
|   }
 | |
|   bzero(&parent_grantee, sizeof(parent_grantee));
 | |
|   flags= IS_ROLE;
 | |
| }
 | |
| 
 | |
| enum role_name_check_result
 | |
| {
 | |
|   ROLE_NAME_OK= 0,
 | |
|   ROLE_NAME_PUBLIC,
 | |
|   ROLE_NAME_INVALID
 | |
| };
 | |
| 
 | |
| static role_name_check_result check_role_name(LEX_CSTRING *str,
 | |
|                                               bool public_is_ok)
 | |
| {
 | |
|   if (str->length)
 | |
|   {
 | |
|     if (str->length == public_name.length &&
 | |
|         strcasecmp(str->str, public_name.str) == 0)
 | |
|     {
 | |
|       *str= public_name;
 | |
|       if (public_is_ok)
 | |
|         return ROLE_NAME_PUBLIC;
 | |
|       else
 | |
|         goto error;
 | |
|     }
 | |
|     if (str->length != none.length || strcasecmp(str->str, none.str) != 0)
 | |
|       return ROLE_NAME_OK;
 | |
|   }
 | |
| 
 | |
| error:
 | |
|   my_error(ER_INVALID_ROLE, MYF(0), str->str);
 | |
|   return ROLE_NAME_INVALID;
 | |
| }
 | |
| 
 | |
| 
 | |
| static void free_acl_user(void *user_)
 | |
| {
 | |
|   ACL_USER *user= static_cast<ACL_USER *>(user_);
 | |
|   delete_dynamic(&(user->role_grants));
 | |
| }
 | |
| 
 | |
| static void free_acl_role(void *role_)
 | |
| {
 | |
|   ACL_ROLE *role= static_cast<ACL_ROLE *>(role_);
 | |
|   delete_dynamic(&(role->role_grants));
 | |
|   delete_dynamic(&(role->parent_grantee));
 | |
| }
 | |
| 
 | |
| static my_bool check_if_exists(THD *, plugin_ref, void *)
 | |
| {
 | |
|   return TRUE;
 | |
| }
 | |
| 
 | |
| static bool has_validation_plugins()
 | |
| {
 | |
|   return plugin_foreach(NULL, check_if_exists,
 | |
|                         MariaDB_PASSWORD_VALIDATION_PLUGIN, NULL);
 | |
| }
 | |
| 
 | |
| struct validation_data { const LEX_CSTRING *user, *password, *host; };
 | |
| 
 | |
| static my_bool do_validate(THD *, plugin_ref plugin, void *arg)
 | |
| {
 | |
|   struct validation_data *data= (struct validation_data *)arg;
 | |
|   struct st_mariadb_password_validation *handler=
 | |
|     (st_mariadb_password_validation *)plugin_decl(plugin)->info;
 | |
|   if (handler->validate_password(data->user, data->password, data->host))
 | |
|   {
 | |
|     my_error(ER_NOT_VALID_PASSWORD, MYF(0), plugin_ref_to_int(plugin)->name.str);
 | |
|     return true;
 | |
|   }
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| 
 | |
| static bool validate_password(THD *thd, const LEX_CSTRING &user,
 | |
|                               const LEX_CSTRING &host,
 | |
|                               const LEX_CSTRING &pwtext, bool has_hash)
 | |
| {
 | |
|   if (pwtext.length || !has_hash)
 | |
|   {
 | |
|     struct validation_data data= { &user,
 | |
|                                    pwtext.str ? &pwtext : &empty_clex_str,
 | |
|                                    &host };
 | |
|     if (plugin_foreach(NULL, do_validate,
 | |
|                        MariaDB_PASSWORD_VALIDATION_PLUGIN, &data))
 | |
|     {
 | |
|       return true;
 | |
|     }
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     if (!thd->slave_thread &&
 | |
|         strict_password_validation && has_validation_plugins()
 | |
| #ifdef WITH_WSREP
 | |
|         && !thd->wsrep_applier
 | |
| #endif
 | |
|        )
 | |
|     {
 | |
|       my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--strict-password-validation");
 | |
|       return true;
 | |
|     }
 | |
|   }
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| static int set_user_salt(ACL_USER::AUTH *auth, plugin_ref plugin)
 | |
| {
 | |
|   st_mysql_auth *info= (st_mysql_auth *) plugin_decl(plugin)->info;
 | |
| 
 | |
|   mysql_mutex_assert_owner(&acl_cache->lock);
 | |
| 
 | |
|   if (info->interface_version >= 0x0202 && info->preprocess_hash &&
 | |
|       auth->auth_string.length)
 | |
|   {
 | |
|     uchar buf[MAX_SCRAMBLE_LENGTH];
 | |
|     size_t len= sizeof(buf);
 | |
|     if (info->preprocess_hash(auth->auth_string.str,
 | |
|                               auth->auth_string.length, buf, &len))
 | |
|       return 1;
 | |
|     auth->salt.str= (char*)memdup_root(&acl_memroot, buf, len);
 | |
|     auth->salt.length= len;
 | |
|   }
 | |
|   else
 | |
|     auth->salt= safe_lexcstrdup_root(&acl_memroot, auth->auth_string);
 | |
| 
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| /**
 | |
|   Fills in ACL_USER::auth_string and ACL_USER::salt fields, as needed
 | |
| 
 | |
|   hashes the plain-text password (if provided) to auth_string,
 | |
|   converts auth_string to salt.
 | |
| 
 | |
|   Fails if the plain-text password fails validation, if the plugin is
 | |
|   not loaded, if the auth_string is invalid, if the password is not applicable
 | |
| */
 | |
| static int set_user_auth(THD *thd, const LEX_CSTRING &user,
 | |
|                          const LEX_CSTRING &host,
 | |
|                          ACL_USER::AUTH *auth, const LEX_CSTRING &pwtext)
 | |
| {
 | |
|   const char *plugin_name= auth->plugin.str;
 | |
|   bool unlock_plugin= false;
 | |
|   plugin_ref plugin= get_auth_plugin(thd, auth->plugin, &unlock_plugin);
 | |
|   int res= 1;
 | |
| 
 | |
|   mysql_mutex_assert_owner(&acl_cache->lock);
 | |
| 
 | |
|   // check for SET PASSWORD
 | |
|   if (!plugin)
 | |
|   {
 | |
|     my_error(ER_PLUGIN_IS_NOT_LOADED, MYF(0), plugin_name);
 | |
|     return ER_PLUGIN_IS_NOT_LOADED;
 | |
|   }
 | |
| 
 | |
|   auth->salt= auth->auth_string;
 | |
| 
 | |
|   st_mysql_auth *info= (st_mysql_auth *) plugin_decl(plugin)->info;
 | |
|   if (info->interface_version < 0x0202)
 | |
|   {
 | |
|     res= pwtext.length ? ER_SET_PASSWORD_AUTH_PLUGIN : 0;
 | |
|     goto end;
 | |
|   }
 | |
| 
 | |
|   if (thd->lex->sql_command == SQLCOM_SET_OPTION && !info->hash_password)
 | |
|   {
 | |
|     res= ER_SET_PASSWORD_AUTH_PLUGIN;
 | |
|     goto end;
 | |
|   }
 | |
| 
 | |
|   if (info->hash_password &&
 | |
|       validate_password(thd, user, host, pwtext, auth->auth_string.length))
 | |
|   {
 | |
|     res= ER_NOT_VALID_PASSWORD;
 | |
|     goto end;
 | |
|   }
 | |
| 
 | |
|   if (!auth->auth_string.length)
 | |
|   {
 | |
|     if (info->hash_password)
 | |
|     {
 | |
|       char buf[MAX_SCRAMBLE_LENGTH];
 | |
|       size_t len= sizeof(buf) - 1;
 | |
|       if (info->hash_password(pwtext.str, pwtext.length, buf, &len))
 | |
|       {
 | |
|         res= ER_OUTOFMEMORY;
 | |
|         goto end;
 | |
|       }
 | |
|       buf[len] = 0;
 | |
|       auth->auth_string.str= (char*)memdup_root(&acl_memroot, buf, len+1);
 | |
|       auth->auth_string.length= len;
 | |
|     }
 | |
|     else if (pwtext.length)
 | |
|     {
 | |
|       res= ER_SET_PASSWORD_AUTH_PLUGIN;
 | |
|       goto end;
 | |
|     }
 | |
|   }
 | |
|   if (set_user_salt(auth, plugin))
 | |
|   {
 | |
|     res= ER_PASSWD_LENGTH;
 | |
|     goto end;
 | |
|   }
 | |
| 
 | |
|   res= 0;
 | |
| end:
 | |
|   switch(res)
 | |
|   {
 | |
|     case ER_OUTOFMEMORY:        // should be reported by my_malloc
 | |
|     case ER_NOT_VALID_PASSWORD: // should be reported by plugin
 | |
|     case ER_PASSWD_LENGTH:      // should be reported by plugin
 | |
|       DBUG_ASSERT(thd->is_error());
 | |
|       /* fall through*/
 | |
|     case 0:
 | |
|       break;
 | |
|     case ER_SET_PASSWORD_AUTH_PLUGIN:
 | |
|       my_error(res, MYF(0), plugin_name);
 | |
|       break;
 | |
|     default:
 | |
|       DBUG_ASSERT(0);
 | |
|   }
 | |
|   if (unlock_plugin)
 | |
|     plugin_unlock(thd, plugin);
 | |
|   return res;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Lazily computes user's salt from the password hash
 | |
| */
 | |
| static bool set_user_salt_if_needed(ACL_USER *user_copy, int curr_auth,
 | |
|                                     plugin_ref plugin)
 | |
| {
 | |
|   ACL_USER::AUTH *auth_copy= user_copy->auth + curr_auth;
 | |
|   DBUG_ASSERT(!strcasecmp(auth_copy->plugin.str, plugin_name(plugin)->str));
 | |
| 
 | |
|   if (auth_copy->salt.str)
 | |
|     return 0; // already done
 | |
| 
 | |
|   mysql_mutex_lock(&acl_cache->lock);
 | |
|   if (set_user_salt(auth_copy, plugin))
 | |
|   {
 | |
|     mysql_mutex_unlock(&acl_cache->lock);
 | |
|     return 1;
 | |
|   }
 | |
| 
 | |
|   ACL_USER *user= find_user_exact(user_copy->hostname(), user_copy->user);
 | |
|   // make sure the user wasn't altered or dropped meanwhile
 | |
|   if (user)
 | |
|   {
 | |
|     ACL_USER::AUTH *auth= user->auth + curr_auth;
 | |
|     if (!auth->salt.str && auth->plugin.length == auth_copy->plugin.length &&
 | |
|         auth->auth_string.length == auth_copy->auth_string.length &&
 | |
|         !memcmp(auth->plugin.str, auth_copy->plugin.str, auth->plugin.length) &&
 | |
|         !memcmp(auth->auth_string.str, auth_copy->auth_string.str, auth->auth_string.length))
 | |
|       auth->salt= auth_copy->salt;
 | |
|   }
 | |
|   mysql_mutex_unlock(&acl_cache->lock);
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Fix ACL::plugin pointer to point to a hard-coded string, if appropriate
 | |
| 
 | |
|   Make sure that if ACL_USER's plugin is a built-in, then it points
 | |
|   to a hard coded string, not to an allocated copy. Run-time, for
 | |
|   authentication, we want to be able to detect built-ins by comparing
 | |
|   pointers, not strings.
 | |
| 
 | |
|   @retval 0 the pointers were fixed
 | |
|   @retval 1 this ACL_USER uses a not built-in plugin
 | |
| */
 | |
| static bool fix_user_plugin_ptr(ACL_USER::AUTH *auth)
 | |
| {
 | |
|   if (native_password_plugin_name.streq(auth->plugin))
 | |
|     auth->plugin= native_password_plugin_name;
 | |
|   else
 | |
|   if (old_password_plugin_name.streq(auth->plugin))
 | |
|     auth->plugin= old_password_plugin_name;
 | |
|   else
 | |
|     return true;
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| 
 | |
| static bool get_YN_as_bool(Field *field)
 | |
| {
 | |
|   char buff[2];
 | |
|   String res(buff,sizeof(buff),&my_charset_latin1);
 | |
|   field->val_str(&res);
 | |
|   return res[0] == 'Y' || res[0] == 'y';
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Initialize structures responsible for user/db-level privilege checking and
 | |
|   load privilege information for them from tables in the 'mysql' database.
 | |
| 
 | |
|   SYNOPSIS
 | |
|     acl_init()
 | |
|       dont_read_acl_tables  TRUE if we want to skip loading data from
 | |
|                             privilege tables and disable privilege checking.
 | |
| 
 | |
|   NOTES
 | |
|     This function is mostly responsible for preparatory steps, main work
 | |
|     on initialization and grants loading is done in acl_reload().
 | |
| 
 | |
|   RETURN VALUES
 | |
|     0	ok
 | |
|     1	Could not initialize grant's
 | |
| */
 | |
| 
 | |
| bool acl_init(bool dont_read_acl_tables)
 | |
| {
 | |
|   THD  *thd;
 | |
|   bool return_val;
 | |
|   DBUG_ENTER("acl_init");
 | |
| 
 | |
|   acl_cache= new Hash_filo<acl_entry>(key_memory_acl_cache, ACL_CACHE_SIZE, 0,
 | |
|                                       0, acl_entry_get_key, my_free,
 | |
|                                       &my_charset_utf8mb3_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 */
 | |
|   }
 | |
| 
 | |
|   /*
 | |
|     To be able to run this from boot, we allocate a temporary THD
 | |
|   */
 | |
|   if (!(thd=new THD(0)))
 | |
|     DBUG_RETURN(1); /* purecov: inspected */
 | |
|   thd->store_globals();
 | |
|   thd->set_query_inner((char*) STRING_WITH_LEN("intern:acl_init"),
 | |
|                        default_charset_info);
 | |
|   /*
 | |
|     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
 | |
|     by zeros at startup.
 | |
|   */
 | |
|   return_val= acl_reload(thd);
 | |
|   delete thd;
 | |
|   DBUG_RETURN(return_val);
 | |
| }
 | |
| 
 | |
| static void push_new_user(const ACL_USER &user)
 | |
| {
 | |
|   push_dynamic(&acl_users, &user);
 | |
|   if (!user.host.hostname ||
 | |
|       (user.host.hostname[0] == wild_many && !user.host.hostname[1]))
 | |
|     allow_all_hosts=1;                  // Anyone can connect
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Make a database name on mem_root from a String,
 | |
|   apply lower-case conversion if lower_case_table_names says so.
 | |
|   Perform database name length limit validation.
 | |
| 
 | |
|   @param thd      - the THD, to get the warning text from
 | |
|   @param mem_root - allocate the result on this memory root
 | |
|   @param dbstr    - the String, e.g. with Field::val_str() result
 | |
| 
 | |
|   @return         - {NULL,0} in case of EOM or a bad database name,
 | |
|                     or a good database name otherwise.
 | |
| */
 | |
| static LEX_STRING make_and_check_db_name(MEM_ROOT *mem_root,
 | |
|                                          const String &dbstr)
 | |
| {
 | |
|   LEX_STRING dbls= lower_case_table_names ?
 | |
|                    lex_string_casedn_root(mem_root, files_charset_info,
 | |
|                                           dbstr.ptr(), dbstr.length()) :
 | |
|                    lex_string_strmake_root(mem_root,
 | |
|                                            dbstr.ptr(), dbstr.length());
 | |
|   if (!dbls.str)
 | |
|     return LEX_STRING{NULL, 0}; // EOM
 | |
|   if (dbls.length > SAFE_NAME_LEN)
 | |
|   {
 | |
|     sql_print_warning(ER_DEFAULT(ER_WRONG_DB_NAME), dbls.str);
 | |
|     return LEX_STRING{NULL, 0}; // Bad name
 | |
|   }
 | |
|   return dbls; // Good name
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Initialize structures responsible for user/db-level privilege checking
 | |
|   and load information about grants from open privilege tables.
 | |
| 
 | |
|   SYNOPSIS
 | |
|     acl_load()
 | |
|       thd     Current thread
 | |
|       tables  List containing open "mysql.host", "mysql.user",
 | |
|               "mysql.db", "mysql.proxies_priv" and "mysql.roles_mapping"
 | |
|               tables.
 | |
| 
 | |
|   RETURN VALUES
 | |
|     FALSE  Success
 | |
|     TRUE   Error
 | |
| */
 | |
| 
 | |
| static bool acl_load(THD *thd, const Grant_tables& tables)
 | |
| {
 | |
|   READ_RECORD read_record_info;
 | |
|   DBUG_ENTER("acl_load");
 | |
| 
 | |
|   SCOPE_CLEAR(thd->variables.sql_mode, MODE_PAD_CHAR_TO_FULL_LENGTH);
 | |
| 
 | |
|   grant_version++; /* Privileges updated */
 | |
| 
 | |
|   const Host_table& host_table= tables.host_table();
 | |
|   init_sql_alloc(key_memory_acl_mem, &acl_memroot, ACL_ALLOC_BLOCK_SIZE, 0, MYF(0));
 | |
|   if (host_table.table_exists()) // "host" table may not exist (e.g. in MySQL 5.6.7+)
 | |
|   {
 | |
|     if (host_table.init_read_record(&read_record_info))
 | |
|       DBUG_RETURN(true);
 | |
|     while (!(read_record_info.read_record()))
 | |
|     {
 | |
|       ACL_HOST host;
 | |
|       update_hostname(&host.host, get_field(&acl_memroot, host_table.host()));
 | |
|       StringBuffer<SAFE_NAME_LEN> dbstr;
 | |
|       host_table.db()->val_str(&dbstr);
 | |
|       if (dbstr.length())
 | |
|       {
 | |
|         const LEX_STRING dbls= make_and_check_db_name(&acl_memroot, dbstr);
 | |
|         if (!(host.db= dbls.str))
 | |
|           continue; // EOM or a bad database name
 | |
|         /*
 | |
|           Issue a warning if lower case conversion happened
 | |
|           and it changed the database name.
 | |
|         */
 | |
|         if (lower_case_table_names && cmp(dbls, dbstr.to_lex_cstring()))
 | |
|           sql_print_warning("'host' entry '%s|%s' had database in mixed "
 | |
|                             "case that has been forced to lowercase because "
 | |
|                             "lower_case_table_names is set. It will not be "
 | |
|                             "possible to remove this privilege using REVOKE.",
 | |
|                             host.host.hostname, host.db);
 | |
|       }
 | |
|       else
 | |
|         host.db= const_cast<char*>(host_not_specified.str);
 | |
|       host.access= host_table.get_access();
 | |
|       host.access= fix_rights_for_db(host.access);
 | |
|       host.sort= get_magic_sort("hd", host.host.hostname, host.db);
 | |
|       if (opt_skip_name_resolve &&
 | |
|           hostname_requires_resolving(host.host.hostname))
 | |
|       {
 | |
|         sql_print_warning("'host' entry '%s|%s' "
 | |
|                         "ignored in --skip-name-resolve mode.",
 | |
|                          host.host.hostname, host.db);
 | |
|         continue;
 | |
|       }
 | |
| #ifndef TO_BE_REMOVED
 | |
|       if (host_table.num_fields() == 8)
 | |
|       {						// Without grant
 | |
|         if (host.access & CREATE_ACL)
 | |
|           host.access|=REFERENCES_ACL | INDEX_ACL | ALTER_ACL | CREATE_TMP_ACL;
 | |
|       }
 | |
| #endif
 | |
|       (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);
 | |
|     end_read_record(&read_record_info);
 | |
|   }
 | |
|   freeze_size(&acl_hosts);
 | |
| 
 | |
|   const User_table& user_table= tables.user_table();
 | |
|   if (user_table.init_read_record(&read_record_info))
 | |
|     DBUG_RETURN(true);
 | |
| 
 | |
|   allow_all_hosts=0;
 | |
|   while (!(read_record_info.read_record()))
 | |
|   {
 | |
|     ACL_USER user;
 | |
|     bool is_role= FALSE;
 | |
|     update_hostname(&user.host, user_table.get_host(&acl_memroot));
 | |
|     char *username= safe_str(user_table.get_user(&acl_memroot));
 | |
|     user.user.str= username;
 | |
|     user.user.length= strlen(username);
 | |
| 
 | |
|     is_role= user_table.get_is_role();
 | |
| 
 | |
|     user.access= user_table.get_access();
 | |
| 
 | |
|     user.sort= get_magic_sort("hu", user.host.hostname, user.user.str);
 | |
|     user.hostname_length= safe_strlen(user.host.hostname);
 | |
| 
 | |
|     my_init_dynamic_array(key_memory_acl_mem, &user.role_grants,
 | |
|                           sizeof(ACL_ROLE *), 0, 8, MYF(0));
 | |
| 
 | |
|     user.account_locked= user_table.get_account_locked();
 | |
| 
 | |
|     user.password_expired= user_table.get_password_expired();
 | |
|     user.password_last_changed= user_table.get_password_last_changed();
 | |
|     user.password_lifetime= user_table.get_password_lifetime();
 | |
| 
 | |
|     if (is_role)
 | |
|     {
 | |
|       role_name_check_result result= check_role_name(&user.user, true);
 | |
|       if (result == ROLE_NAME_INVALID)
 | |
|       {
 | |
|         thd->clear_error(); // the warning is still issued
 | |
|         continue;
 | |
|       }
 | |
| 
 | |
|       ACL_ROLE *entry= new (&acl_memroot) ACL_ROLE(&user);
 | |
|       entry->role_grants = user.role_grants;
 | |
|       my_init_dynamic_array(key_memory_acl_mem, &entry->parent_grantee,
 | |
|                             sizeof(ACL_USER_BASE *), 0, 8, MYF(0));
 | |
|       if (result == ROLE_NAME_PUBLIC)
 | |
|         acl_public= entry;
 | |
| 
 | |
|       my_hash_insert(&acl_roles, (uchar *)entry);
 | |
| 
 | |
|       continue;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       if (opt_skip_name_resolve &&
 | |
|           hostname_requires_resolving(user.host.hostname))
 | |
|       {
 | |
|         sql_print_warning("'user' entry '%s@%s' "
 | |
|                           "ignored in --skip-name-resolve mode.", user.user.str,
 | |
|                           safe_str(user.host.hostname));
 | |
|         continue;
 | |
|       }
 | |
| 
 | |
|       if (user_table.get_auth(thd, &acl_memroot, &user))
 | |
|         continue;
 | |
|       for (uint i= 0; i < user.nauth; i++)
 | |
|       {
 | |
|         ACL_USER::AUTH *auth= user.auth + i;
 | |
|         auth->salt= null_clex_str;
 | |
|         fix_user_plugin_ptr(auth);
 | |
|       }
 | |
| 
 | |
|       user.ssl_type=     user_table.get_ssl_type();
 | |
|       user.ssl_cipher=   user_table.get_ssl_cipher(&acl_memroot);
 | |
|       user.x509_issuer=  safe_str(user_table.get_x509_issuer(&acl_memroot));
 | |
|       user.x509_subject= safe_str(user_table.get_x509_subject(&acl_memroot));
 | |
|       user.user_resource.questions= (uint)user_table.get_max_questions();
 | |
|       user.user_resource.updates= (uint)user_table.get_max_updates();
 | |
|       user.user_resource.conn_per_hour= (uint)user_table.get_max_connections();
 | |
|       if (user.user_resource.questions || user.user_resource.updates ||
 | |
|           user.user_resource.conn_per_hour)
 | |
|         mqh_used=1;
 | |
| 
 | |
|       user.user_resource.user_conn= (int)user_table.get_max_user_connections();
 | |
|       user.user_resource.max_statement_time= user_table.get_max_statement_time();
 | |
| 
 | |
|       user.default_rolename.str= user_table.get_default_role(&acl_memroot);
 | |
|       user.default_rolename.length= safe_strlen(user.default_rolename.str);
 | |
|     }
 | |
|     push_new_user(user);
 | |
|   }
 | |
|   rebuild_acl_users();
 | |
|   end_read_record(&read_record_info);
 | |
|   freeze_size(&acl_users);
 | |
| 
 | |
|   const Db_table& db_table= tables.db_table();
 | |
|   if (db_table.init_read_record(&read_record_info))
 | |
|     DBUG_RETURN(TRUE);
 | |
|   while (!(read_record_info.read_record()))
 | |
|   {
 | |
|     ACL_DB db;
 | |
|     db.user=safe_str(get_field(&acl_memroot, db_table.user()));
 | |
|     const char *hostname= get_field(&acl_memroot, db_table.host());
 | |
|     if (!hostname && find_acl_role(Lex_cstring_strlen(db.user), true))
 | |
|       hostname= "";
 | |
|     update_hostname(&db.host, hostname);
 | |
| 
 | |
|     StringBuffer<SAFE_NAME_LEN> dbstr;
 | |
|     db_table.db()->val_str(&dbstr);
 | |
|     if (!dbstr.length())
 | |
|     {
 | |
|       sql_print_warning("Found an entry in the 'db' table with empty database name; Skipped");
 | |
|       continue;
 | |
|     }
 | |
|     const LEX_STRING dbls= make_and_check_db_name(&acl_memroot, dbstr);
 | |
|     if (!(db.db= dbls.str)) // EOM or a bad database name
 | |
|       continue;
 | |
|     /*
 | |
|       Issue a warning if lower case conversion happened
 | |
|       and it changed the database name.
 | |
|     */
 | |
|     if (lower_case_table_names && cmp(dbls, dbstr.to_lex_cstring()))
 | |
|     {
 | |
|       sql_print_warning("'db' entry '%s %s@%s' had database in mixed "
 | |
|                         "case that has been forced to lowercase because "
 | |
|                         "lower_case_table_names is set. It will not be "
 | |
|                         "possible to remove this privilege using REVOKE.",
 | |
|                         db.db, db.user, safe_str(db.host.hostname));
 | |
|     }
 | |
| 
 | |
|     if (opt_skip_name_resolve && hostname_requires_resolving(db.host.hostname))
 | |
|     {
 | |
|       sql_print_warning("'db' entry '%s %s@%s' "
 | |
|                         "ignored in --skip-name-resolve mode.",
 | |
| 		        db.db, db.user, safe_str(db.host.hostname));
 | |
|       continue;
 | |
|     }
 | |
|     db.access= db_table.get_access();
 | |
|     db.access=fix_rights_for_db(db.access);
 | |
|     db.initial_access= db.access;
 | |
|     db.sort=get_magic_sort("hdu", db.host.hostname, db.db, db.user);
 | |
| #ifndef TO_BE_REMOVED
 | |
|     if (db_table.num_fields() <=  9)
 | |
|     {						// Without grant
 | |
|       if (db.access & CREATE_ACL)
 | |
| 	db.access|=REFERENCES_ACL | INDEX_ACL | ALTER_ACL;
 | |
|     }
 | |
| #endif
 | |
|     if (db_table.num_fields() <= 23)
 | |
|       if ((db.access | SHOW_CREATE_ROUTINE_ACL | GRANT_ACL) == DB_ACLS)
 | |
|         db.access|= SHOW_CREATE_ROUTINE_ACL;
 | |
|     acl_dbs.push(db);
 | |
|   }
 | |
|   end_read_record(&read_record_info);
 | |
|   rebuild_acl_dbs();
 | |
|   acl_dbs.freeze();
 | |
| 
 | |
|   const Proxies_priv_table& proxies_priv_table= tables.proxies_priv_table();
 | |
|   if (proxies_priv_table.table_exists())
 | |
|   {
 | |
|     if (proxies_priv_table.init_read_record(&read_record_info))
 | |
|       DBUG_RETURN(TRUE);
 | |
|     while (!(read_record_info.read_record()))
 | |
|     {
 | |
|       ACL_PROXY_USER proxy;
 | |
|       proxy.init(proxies_priv_table, &acl_memroot);
 | |
|       if (proxy.check_validity())
 | |
|         continue;
 | |
|       if (push_dynamic(&acl_proxy_users, (uchar*) &proxy))
 | |
|         DBUG_RETURN(TRUE);
 | |
|     }
 | |
|     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);
 | |
| 
 | |
|   const Roles_mapping_table& roles_mapping_table= tables.roles_mapping_table();
 | |
|   if (roles_mapping_table.table_exists())
 | |
|   {
 | |
|     if (roles_mapping_table.init_read_record(&read_record_info))
 | |
|       DBUG_RETURN(TRUE);
 | |
| 
 | |
|     MEM_ROOT temp_root;
 | |
|     init_alloc_root(key_memory_acl_mem, &temp_root, ACL_ALLOC_BLOCK_SIZE, 0, MYF(0));
 | |
|     while (!(read_record_info.read_record()))
 | |
|     {
 | |
|       /*
 | |
|         acl_find_user_by_name() called later in this code block (through other
 | |
|         functions) needs a 0-terminated string. So does sql_print_error().
 | |
|         Let's use Field::val_lex_string_strmake() to have 0-terminated copies
 | |
|         of field values.
 | |
|       */
 | |
|       const Lex_cstring hostname(roles_mapping_table.host()->
 | |
|                                    val_lex_string_strmake(&temp_root));
 | |
|       const Lex_cstring username(roles_mapping_table.user()->
 | |
|                                    val_lex_string_strmake(&temp_root));
 | |
|       const Lex_cstring rolename(roles_mapping_table.role()->
 | |
|                                    val_lex_string_strmake(&temp_root));
 | |
| 
 | |
|       bool with_grant_option= get_YN_as_bool(roles_mapping_table.admin_option());
 | |
| 
 | |
|       if (add_role_user_mapping(username, hostname, rolename)) {
 | |
|         sql_print_error("Invalid roles_mapping table entry user:'%s@%s', rolename:'%s'",
 | |
|                         username.str, hostname.str, rolename.str);
 | |
|         continue;
 | |
|       }
 | |
| 
 | |
|       ROLE_GRANT_PAIR *mapping= new (&acl_memroot) ROLE_GRANT_PAIR;
 | |
| 
 | |
|       if (mapping->init(&acl_memroot, username, hostname, rolename, with_grant_option))
 | |
|         continue;
 | |
| 
 | |
|       my_hash_insert(&acl_roles_mappings, (uchar*) mapping);
 | |
|     }
 | |
| 
 | |
|     free_root(&temp_root, MYF(0));
 | |
|     end_read_record(&read_record_info);
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     sql_print_error("Missing system table mysql.roles_mapping; "
 | |
|                     "please run mysql_upgrade to create it");
 | |
|   }
 | |
| 
 | |
|   init_check_host();
 | |
| 
 | |
|   thd->bootstrap= !initialized; // keep FLUSH PRIVILEGES connection special
 | |
|   initialized=1;
 | |
|   DBUG_RETURN(FALSE);
 | |
| }
 | |
| 
 | |
| 
 | |
| void acl_free(bool end)
 | |
| {
 | |
|   my_hash_free(&acl_roles);
 | |
|   acl_public= NULL;
 | |
|   free_root(&acl_memroot,MYF(0));
 | |
|   delete_dynamic(&acl_hosts);
 | |
|   delete_dynamic_with_callback(&acl_users, free_acl_user);
 | |
|   acl_dbs.free_memory();
 | |
|   delete_dynamic(&acl_wild_hosts);
 | |
|   delete_dynamic(&acl_proxy_users);
 | |
|   my_hash_free(&acl_check_hosts);
 | |
|   my_hash_free(&acl_roles_mappings);
 | |
|   if (!end)
 | |
|     acl_cache->clear(1); /* purecov: inspected */
 | |
|   else
 | |
|   {
 | |
|     plugin_unlock(0, native_password_plugin);
 | |
|     plugin_unlock(0, old_password_plugin);
 | |
|     delete acl_cache;
 | |
|     acl_cache=0;
 | |
|   }
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Forget current user/db-level privileges and read new privileges
 | |
|   from the privilege tables.
 | |
| 
 | |
|   SYNOPSIS
 | |
|     acl_reload()
 | |
|       thd  Current thread
 | |
| 
 | |
|   NOTE
 | |
|     All tables of calling thread which were open and locked by LOCK TABLES
 | |
|     statement will be unlocked and closed.
 | |
|     This function is also used for initialization of structures responsible
 | |
|     for user/db-level privilege checking.
 | |
| 
 | |
|   RETURN VALUE
 | |
|     FALSE  Success
 | |
|     TRUE   Failure
 | |
| */
 | |
| 
 | |
| bool acl_reload(THD *thd)
 | |
| {
 | |
|   DYNAMIC_ARRAY old_acl_hosts, old_acl_users, old_acl_proxy_users;
 | |
|   Dynamic_array<ACL_DB> old_acl_dbs(PSI_INSTRUMENT_MEM, 0, 0);
 | |
|   HASH old_acl_roles, old_acl_roles_mappings;
 | |
|   ACL_ROLE *old_acl_public;
 | |
|   MEM_ROOT old_mem;
 | |
|   int result;
 | |
|   DBUG_ENTER("acl_reload");
 | |
| 
 | |
| 
 | |
|   Grant_tables tables;
 | |
|   /*
 | |
|     To avoid deadlocks we should obtain table locks before
 | |
|     obtaining acl_cache->lock mutex.
 | |
|   */
 | |
|   const uint tables_to_open= Table_host | Table_user | Table_db |
 | |
|                              Table_proxies_priv | Table_roles_mapping;
 | |
|   if ((result= tables.open_and_lock(thd, tables_to_open, TL_READ)))
 | |
|   {
 | |
|     DBUG_ASSERT(result <= 0);
 | |
|     /*
 | |
|       Execution might have been interrupted; only print the error message
 | |
|       if an error condition has been raised.
 | |
|     */
 | |
|     if (thd->get_stmt_da()->is_error())
 | |
|       sql_print_error("Fatal error: Can't open and lock privilege tables: %s",
 | |
|                       thd->get_stmt_da()->message());
 | |
|     goto end;
 | |
|   }
 | |
| 
 | |
|   acl_cache->clear(0);
 | |
|   mysql_mutex_record_order(&acl_cache->lock, &LOCK_status);
 | |
|   mysql_mutex_lock(&acl_cache->lock);
 | |
| 
 | |
|   old_acl_hosts= acl_hosts;
 | |
|   old_acl_users= acl_users;
 | |
|   old_acl_roles= acl_roles;
 | |
|   old_acl_public= acl_public;
 | |
|   old_acl_roles_mappings= acl_roles_mappings;
 | |
|   old_acl_proxy_users= acl_proxy_users;
 | |
|   old_acl_dbs= acl_dbs;
 | |
|   my_init_dynamic_array(key_memory_acl_mem, &acl_hosts, sizeof(ACL_HOST), 20, 50, MYF(0));
 | |
|   my_init_dynamic_array(key_memory_acl_mem, &acl_users, sizeof(ACL_USER), 50, 100, MYF(0));
 | |
|   acl_dbs.init(key_memory_acl_mem, 50, 100);
 | |
|   my_init_dynamic_array(key_memory_acl_mem, &acl_proxy_users, sizeof(ACL_PROXY_USER), 50, 100, MYF(0));
 | |
|   my_hash_init2(key_memory_acl_mem, &acl_roles, 50, &my_charset_utf8mb3_bin, 0,
 | |
|                 0, 0, acl_role_get_key, 0, free_acl_role, 0);
 | |
|   my_hash_init2(key_memory_acl_mem, &acl_roles_mappings, 50,
 | |
|                 &my_charset_utf8mb3_bin, 0, 0, 0, acl_role_map_get_key, 0, 0,
 | |
|                 0);
 | |
|   old_mem= acl_memroot;
 | |
|   delete_dynamic(&acl_wild_hosts);
 | |
|   my_hash_free(&acl_check_hosts);
 | |
|   acl_public= NULL;
 | |
| 
 | |
|   if ((result= 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_roles= old_acl_roles;
 | |
|     acl_public= old_acl_public;
 | |
|     acl_roles_mappings= old_acl_roles_mappings;
 | |
|     acl_proxy_users= old_acl_proxy_users;
 | |
|     acl_dbs= old_acl_dbs;
 | |
|     old_acl_dbs.init(0,0);
 | |
|     acl_memroot= old_mem;
 | |
|     init_check_host();
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     my_hash_free(&old_acl_roles);
 | |
|     free_root(&old_mem,MYF(0));
 | |
|     delete_dynamic(&old_acl_hosts);
 | |
|     delete_dynamic_with_callback(&old_acl_users, free_acl_user);
 | |
|     delete_dynamic(&old_acl_proxy_users);
 | |
|     my_hash_free(&old_acl_roles_mappings);
 | |
|   }
 | |
|   mysql_mutex_unlock(&acl_cache->lock);
 | |
| end:
 | |
|   close_mysql_tables(thd);
 | |
|   DBUG_RETURN(result);
 | |
| }
 | |
| 
 | |
| /*
 | |
|   Get all access bits from table after fieldnr
 | |
| 
 | |
|   IMPLEMENTATION
 | |
|   We know that the access privileges ends when there is no more fields
 | |
|   or the field is not an enum with two elements.
 | |
| 
 | |
|   SYNOPSIS
 | |
|     get_access()
 | |
|     form        an open table to read privileges from.
 | |
|                 The record should be already read in table->record[0]
 | |
|     fieldnr     number of the first privilege (that is ENUM('N','Y') field
 | |
|     next_field  on return - number of the field next to the last ENUM
 | |
|                 (unless next_field == 0)
 | |
| 
 | |
|   RETURN VALUE
 | |
|     privilege mask
 | |
| */
 | |
| 
 | |
| static privilege_t get_access(TABLE *form, uint fieldnr, uint *next_field)
 | |
| {
 | |
|   ulonglong access_bits=0,bit;
 | |
|   char buff[2];
 | |
|   String res(buff,sizeof(buff),&my_charset_latin1);
 | |
|   Field **pos;
 | |
| 
 | |
|   for (pos=form->field+fieldnr, bit=1;
 | |
|        *pos && (*pos)->real_type() == MYSQL_TYPE_ENUM &&
 | |
|          ((Field_enum*) (*pos))->typelib()->count == 2 ;
 | |
|        pos++, fieldnr++, bit<<=1)
 | |
|   {
 | |
|     if (get_YN_as_bool(*pos))
 | |
|       access_bits|= bit;
 | |
|   }
 | |
|   if (next_field)
 | |
|     *next_field=fieldnr;
 | |
|   return ALL_KNOWN_ACL & access_bits;
 | |
| }
 | |
| 
 | |
| 
 | |
| static int acl_compare(const void *a_, const void *b_)
 | |
| {
 | |
|   const ACL_ACCESS *a= static_cast<const ACL_ACCESS *>(a_);
 | |
|   const ACL_ACCESS *b= static_cast<const ACL_ACCESS *>(b_);
 | |
|   if (a->sort > b->sort)
 | |
|     return -1;
 | |
|   if (a->sort < b->sort)
 | |
|     return 1;
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| static int acl_user_compare(const void *a_, const void *b_)
 | |
| {
 | |
|   const ACL_USER *a= static_cast<const ACL_USER *>(a_);
 | |
|   const ACL_USER *b= static_cast<const ACL_USER *>(b_);
 | |
| 
 | |
|   int res= strcmp(a->user.str, b->user.str);
 | |
|   if (res)
 | |
|     return res;
 | |
| 
 | |
|   res= acl_compare(a, b);
 | |
|   if (res)
 | |
|     return res;
 | |
| 
 | |
|   /*
 | |
|     For more deterministic results, resolve ambiguity between
 | |
|     "localhost" and "127.0.0.1"/"::1" by sorting "localhost" before
 | |
|     loopback addresses.
 | |
|     Test suite (on Windows) expects "root@localhost", even if
 | |
|     root@::1 would also match.
 | |
|   */
 | |
|   return -strcmp(a->host.hostname, b->host.hostname);
 | |
| }
 | |
| 
 | |
| static int acl_db_compare(const void *a_, const void *b_)
 | |
| {
 | |
|   const ACL_DB *a= static_cast<const ACL_DB *>(a_);
 | |
|   const ACL_DB *b= static_cast<const ACL_DB *>(b_);
 | |
|   int res= strcmp(a->user, b->user);
 | |
|   if (res)
 | |
|     return res;
 | |
| 
 | |
|   return acl_compare(a, b);
 | |
| }
 | |
| 
 | |
| static void rebuild_acl_users()
 | |
| {
 | |
|    my_qsort((uchar*)dynamic_element(&acl_users, 0, ACL_USER*), acl_users.elements,
 | |
|      sizeof(ACL_USER), (qsort_cmp)acl_user_compare);
 | |
| }
 | |
| 
 | |
| static void rebuild_acl_dbs()
 | |
| {
 | |
|   acl_dbs.sort(acl_db_compare);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Return index of the first entry with given user in the array,
 | |
|   or SIZE_T_MAX if not found.
 | |
| 
 | |
|   Assumes the array is sorted by get_username
 | |
| */
 | |
| template<typename T> size_t find_first_user(T* arr, size_t len, const char *user)
 | |
| {
 | |
|   size_t low= 0;
 | |
|   size_t high= len;
 | |
|   size_t mid;
 | |
| 
 | |
|   bool found= false;
 | |
|   if(!len)
 | |
|     return  SIZE_T_MAX;
 | |
| 
 | |
| #ifndef DBUG_OFF
 | |
|   for (uint i = 0; i < len - 1; i++)
 | |
|     DBUG_ASSERT(strcmp(arr[i].get_username(), arr[i + 1].get_username()) <= 0);
 | |
| #endif
 | |
|   while (low < high)
 | |
|   {
 | |
|     mid= low + (high - low) / 2;
 | |
|     int cmp= strcmp(arr[mid].get_username(),user);
 | |
|     if (cmp == 0)
 | |
|       found= true;
 | |
| 
 | |
|     if (cmp >= 0 )
 | |
|       high= mid;
 | |
|     else
 | |
|       low= mid + 1;
 | |
|   }
 | |
|   return  (!found || low == len || strcmp(arr[low].get_username(), user)!=0 )?SIZE_T_MAX:low;
 | |
| }
 | |
| 
 | |
| static size_t acl_find_user_by_name(const char *user)
 | |
| {
 | |
|   return find_first_user<ACL_USER>((ACL_USER *)acl_users.buffer,acl_users.elements,user);
 | |
| }
 | |
| 
 | |
| static size_t acl_find_db_by_username(const char *user)
 | |
| {
 | |
|   return find_first_user<ACL_DB>(acl_dbs.front(), acl_dbs.elements(), user);
 | |
| }
 | |
| 
 | |
| static bool match_db(ACL_DB *acl_db, const char *db, my_bool db_is_pattern)
 | |
| {
 | |
|   return !acl_db->db || (db && !wild_compare(db, acl_db->db, db_is_pattern));
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Lookup in the acl_users or acl_dbs for the best matching entry corresponding to
 | |
|   given user, host and ip parameters (also db, in case of ACL_DB)
 | |
| 
 | |
|   Historical note:
 | |
| 
 | |
|   In the past, both arrays were sorted just by ACL_ENTRY::sort field and were
 | |
|   searched linearly, until the first match of (username,host) pair was found.
 | |
| 
 | |
|   This function uses optimizations (binary search by username), yet preserves the
 | |
|   historical behavior, i.e the returns a match with highest ACL_ENTRY::sort.
 | |
| */
 | |
| template <typename T> T* find_by_username_or_anon(T* arr, size_t len, const char *user,
 | |
|   const char *host, const char *ip,
 | |
|   const char *db, my_bool db_is_pattern, bool (*match_db_func)(T*,const char *,my_bool))
 | |
| {
 | |
|   size_t i;
 | |
|   T *ret = NULL;
 | |
| 
 | |
|   // Check  entries matching user name.
 | |
|   size_t start = find_first_user(arr, len, user);
 | |
|   for (i= start; i < len; i++)
 | |
|   {
 | |
|     T *entry= &arr[i];
 | |
|     if (i > start && strcmp(user, entry->get_username()))
 | |
|       break;
 | |
| 
 | |
|     if (compare_hostname(&entry->host, host, ip) && (!match_db_func || match_db_func(entry, db, db_is_pattern)))
 | |
|     {
 | |
|       ret= entry;
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Look also for anonymous user (username is empty string)
 | |
|   // Due to sort by name, entries for anonymous user start at the start of array.
 | |
|   for (i= 0; i < len; i++)
 | |
|   {
 | |
|     T *entry = &arr[i];
 | |
|     if (*entry->get_username() || (ret && acl_compare(entry, ret) >= 0))
 | |
|       break;
 | |
|     if (compare_hostname(&entry->host, host, ip) && (!match_db_func || match_db_func(entry, db, db_is_pattern)))
 | |
|     {
 | |
|       ret= entry;
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
|   return ret;
 | |
| }
 | |
| 
 | |
| static ACL_DB *acl_db_find(const char *db, const char *user, const char *host, const char *ip, my_bool db_is_pattern)
 | |
| {
 | |
|   return find_by_username_or_anon(acl_dbs.front(), acl_dbs.elements(),
 | |
|                                   user, host, ip, db, db_is_pattern, match_db);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Gets user credentials without authentication and resource limit checks.
 | |
| 
 | |
|   SYNOPSIS
 | |
|     acl_getroot()
 | |
|       sctx               Context which should be initialized
 | |
|       user               user name
 | |
|       host               host name
 | |
|       ip                 IP
 | |
|       db                 current data base name
 | |
| 
 | |
|   RETURN
 | |
|     FALSE  OK
 | |
|     TRUE   Error
 | |
| */
 | |
| 
 | |
| bool acl_getroot(Security_context *sctx,
 | |
|                  const LEX_CSTRING &user, const LEX_CSTRING &host,
 | |
|                  const LEX_CSTRING &ip, const LEX_CSTRING &db)
 | |
| {
 | |
|   int res= 1;
 | |
|   ACL_USER *acl_user= 0;
 | |
|   DBUG_ENTER("acl_getroot");
 | |
| 
 | |
|   DBUG_PRINT("enter", ("Host: '%s', Ip: '%s', User: '%s', db: '%s'",
 | |
|                        host.str, ip.str, user.str, db.str));
 | |
|   sctx->init();
 | |
|   sctx->user= *user.str ? user.str : NULL;
 | |
|   sctx->host= host.str;
 | |
|   sctx->ip= ip.str;
 | |
|   sctx->host_or_ip= host.str ? host.str : (safe_str(ip.str));
 | |
| 
 | |
|   if (!initialized)
 | |
|   {
 | |
|     /*
 | |
|       here if mysqld's been started with --skip-grant-tables option.
 | |
|     */
 | |
|     sctx->skip_grants();
 | |
|     DBUG_RETURN(FALSE);
 | |
|   }
 | |
| 
 | |
|   mysql_mutex_lock(&acl_cache->lock);
 | |
| 
 | |
|   sctx->db_access= NO_ACL;
 | |
| 
 | |
|   if (host.str[0]) // User, not Role
 | |
|   {
 | |
|     acl_user= find_user_wild(host, user, ip);
 | |
| 
 | |
|     if (acl_user)
 | |
|     {
 | |
|       res= 0;
 | |
|       if (ACL_DB *acl_db= acl_db_find(db.str, user.str, host.str, ip.str, FALSE))
 | |
|         sctx->db_access= acl_db->access;
 | |
| 
 | |
|       sctx->master_access= acl_user->access;
 | |
| 
 | |
|       strmake_buf(sctx->priv_user, user.str);
 | |
| 
 | |
|       if (acl_user->host.hostname)
 | |
|         strmake_buf(sctx->priv_host, acl_user->host.hostname);
 | |
|     }
 | |
|   }
 | |
|   else // Role, not User
 | |
|   {
 | |
|     ACL_ROLE *acl_role= find_acl_role(user, false);
 | |
|     if (acl_role)
 | |
|     {
 | |
|       res= 0;
 | |
|       if (ACL_DB *acl_db= acl_db_find(db.str, user.str, "", "", FALSE))
 | |
|         sctx->db_access = acl_db->access;
 | |
| 
 | |
|       sctx->master_access= acl_role->access;
 | |
| 
 | |
|       strmake_buf(sctx->priv_role, user.str);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (acl_public)
 | |
|   {
 | |
|     if (ACL_DB *acl_db= acl_db_find(db.str, public_name.str, "", "", FALSE))
 | |
|       sctx->db_access|= acl_db->access;
 | |
| 
 | |
|     sctx->master_access|= acl_public->access;
 | |
|   }
 | |
| 
 | |
|   mysql_mutex_unlock(&acl_cache->lock);
 | |
|   DBUG_RETURN(res);
 | |
| }
 | |
| 
 | |
| static int check_role_is_granted_callback(ACL_USER_BASE *grantee, void *data)
 | |
| {
 | |
|   LEX_CSTRING *rolename= static_cast<LEX_CSTRING *>(data);
 | |
|   if (rolename->length == grantee->user.length &&
 | |
|       !strcmp(rolename->str, grantee->user.str))
 | |
|     return -1; // End search, we've found our role.
 | |
| 
 | |
|   /* Keep looking, we haven't found our role yet. */
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| /*
 | |
|   unlike find_user_exact and find_user_wild,
 | |
|   this function finds anonymous users too, it's when a
 | |
|   user is not empty, but priv_user (acl_user->user) is empty.
 | |
| */
 | |
| static ACL_USER *find_user_or_anon(const char *host, const char *user, const char *ip)
 | |
| {
 | |
|   return find_by_username_or_anon<ACL_USER>
 | |
|     (reinterpret_cast<ACL_USER*>(acl_users.buffer), acl_users.elements,
 | |
|      user, host, ip, NULL, FALSE, NULL);
 | |
| }
 | |
| 
 | |
| 
 | |
| static int check_user_can_set_role(THD *thd,
 | |
|                                    const LEX_CSTRING &user,
 | |
|                                    const LEX_CSTRING &host,
 | |
|                                    const LEX_CSTRING &ip,
 | |
|                                    const LEX_CSTRING &rolename,
 | |
|                                    privilege_t *access)
 | |
| {
 | |
|   ACL_ROLE *role;
 | |
|   ACL_USER_BASE *acl_user_base;
 | |
|   ACL_USER *UNINIT_VAR(acl_user);
 | |
|   bool is_granted= FALSE;
 | |
|   int result= 0;
 | |
| 
 | |
|   /* clear role privileges */
 | |
|   mysql_mutex_lock(&acl_cache->lock);
 | |
| 
 | |
|   if (!strcasecmp(rolename.str, none.str))
 | |
|   {
 | |
|     /* have to clear the privileges */
 | |
|     /* get the current user */
 | |
|     acl_user= find_user_wild(host, user, ip);
 | |
|     if (acl_user == NULL)
 | |
|       result= ER_INVALID_CURRENT_USER;
 | |
|     else if (access)
 | |
|       *access= acl_user->access;
 | |
| 
 | |
|     goto end;
 | |
|   }
 | |
| 
 | |
|   role= find_acl_role(rolename, false);
 | |
| 
 | |
|   /* According to SQL standard, the same error message must be presented */
 | |
|   if (role == NULL)
 | |
|   {
 | |
|     result= ER_INVALID_ROLE;
 | |
|     goto end;
 | |
|   }
 | |
| 
 | |
|   for (uint i=0 ; i < role->parent_grantee.elements ; i++)
 | |
|   {
 | |
|     acl_user_base= *(dynamic_element(&role->parent_grantee, i, ACL_USER_BASE**));
 | |
|     if (acl_user_base->flags & IS_ROLE)
 | |
|       continue;
 | |
| 
 | |
|     acl_user= (ACL_USER *)acl_user_base;
 | |
|     if (acl_user->wild_eq(user.str, host.str, ip.str))
 | |
|     {
 | |
|       is_granted= TRUE;
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* According to SQL standard, the same error message must be presented */
 | |
|   if (!is_granted)
 | |
|   {
 | |
|     result= 1;
 | |
|     goto end;
 | |
|   }
 | |
| 
 | |
|   if (access)
 | |
|     *access = acl_user->access | role->access;
 | |
| 
 | |
| end:
 | |
|   if (acl_public && access)
 | |
|     *access|= acl_public->access;
 | |
| 
 | |
|   mysql_mutex_unlock(&acl_cache->lock);
 | |
| 
 | |
|   /* We present different error messages depending if the user has sufficient
 | |
|      privileges to know if the INVALID_ROLE exists. */
 | |
|   switch (result)
 | |
|   {
 | |
|     case ER_INVALID_CURRENT_USER:
 | |
|       my_error(ER_INVALID_CURRENT_USER, MYF(0));
 | |
|       break;
 | |
|     case ER_INVALID_ROLE:
 | |
|       /* Role doesn't exist at all */
 | |
|       my_error(ER_INVALID_ROLE, MYF(0), rolename.str);
 | |
|       break;
 | |
|     case 1:
 | |
|       LEX_CSTRING role_lex;
 | |
|       /* First, check if current user can see mysql database. */
 | |
|       bool read_access= !check_access(thd, SELECT_ACL, "mysql", NULL, NULL, 1, 1);
 | |
| 
 | |
|       role_lex= rolename;
 | |
|       mysql_mutex_lock(&acl_cache->lock);
 | |
|       ACL_USER *cur_user= find_user_or_anon(thd->security_ctx->priv_host,
 | |
|                                             thd->security_ctx->priv_user,
 | |
|                                             thd->security_ctx->ip);
 | |
| 
 | |
|       /* If the current user does not have select priv to mysql database,
 | |
|          see if the current user can discover the role if it was granted to him.
 | |
|       */
 | |
|       if (cur_user && (read_access ||
 | |
|                        traverse_role_graph_down(cur_user, &role_lex,
 | |
|                                                 check_role_is_granted_callback,
 | |
|                                                 NULL) == -1))
 | |
|       {
 | |
|         /* This happens for SET ROLE case and when `--skip-name-resolve` option
 | |
|            is used. In that situation host can be NULL and current user is always
 | |
|            target user, so printing `priv_user@priv_host` is not incorrect.
 | |
|          */
 | |
|         if (!host.str)
 | |
|           my_printf_error(ER_INVALID_ROLE, "User %sQ@%sQ has not been granted role %sQ",
 | |
|                           MYF(0), thd->security_ctx->priv_user,
 | |
|                           thd->security_ctx->priv_host, rolename.str);
 | |
|         else
 | |
|           /* Role is not granted but current user can see the role */
 | |
|           my_printf_error(ER_INVALID_ROLE, "User %sQ@%sQ has not been granted role %sQ",
 | |
|                           MYF(0), user.str, host.str, rolename.str);
 | |
|       }
 | |
|       else
 | |
|       {
 | |
|         /* Role is not granted and current user cannot see the role */
 | |
|         my_error(ER_INVALID_ROLE, MYF(0), rolename.str);
 | |
|       }
 | |
|       mysql_mutex_unlock(&acl_cache->lock);
 | |
|       break;
 | |
|   }
 | |
| 
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| 
 | |
| int acl_check_setrole(THD *thd,
 | |
|                       const LEX_CSTRING &rolename,
 | |
|                       privilege_t *access)
 | |
| {
 | |
|   if (!initialized)
 | |
|   {
 | |
|     my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--skip-grant-tables");
 | |
|     return 1;
 | |
|   }
 | |
| 
 | |
|   return check_user_can_set_role(thd,
 | |
|            Lex_cstring_strlen(thd->security_ctx->priv_user),
 | |
|            Lex_cstring_strlen(thd->security_ctx->host),
 | |
|            Lex_cstring_strlen(thd->security_ctx->ip),
 | |
|            rolename, access);
 | |
| }
 | |
| 
 | |
| 
 | |
| int acl_setrole(THD *thd, const LEX_CSTRING &rolename, privilege_t access)
 | |
| {
 | |
|   /* merge the privileges */
 | |
|   Security_context *sctx= thd->security_ctx;
 | |
|   sctx->master_access= access;
 | |
|   if (!strcasecmp(rolename.str, none.str))
 | |
|   {
 | |
|     thd->security_ctx->priv_role[0]= 0;
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     /* mark the current role */
 | |
|     strmake_buf(thd->security_ctx->priv_role, rolename.str);
 | |
|   }
 | |
|   if (thd->db.str)
 | |
|     sctx->db_access= acl_get_all3(sctx, thd->db.str, FALSE);
 | |
| 
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| static const uchar *check_get_key(const void *buff_, size_t *length, my_bool)
 | |
| {
 | |
|   auto buff= static_cast<const ACL_USER *>(buff_);
 | |
|   *length=buff->hostname_length;
 | |
|   return reinterpret_cast<const uchar *>(buff->host.hostname);
 | |
| }
 | |
| 
 | |
| 
 | |
| static void acl_update_role(const LEX_CSTRING &rolename,
 | |
|                             const privilege_t privileges)
 | |
| {
 | |
|   ACL_ROLE *role= find_acl_role(rolename, true);
 | |
|   if (role)
 | |
|   {
 | |
|     role->initial_role_access= role->access= privileges;
 | |
|     DBUG_ASSERT(strcasecmp(rolename.str, public_name.str) ||
 | |
|                            acl_public == role);
 | |
|   }
 | |
| }
 | |
| 
 | |
| 
 | |
| ACL_USER::ACL_USER(THD *thd, const LEX_USER &combo,
 | |
|                    const Account_options &options,
 | |
|                    const privilege_t privileges)
 | |
| {
 | |
|   mysql_mutex_assert_owner(&acl_cache->lock);
 | |
|   user= safe_lexcstrdup_root(&acl_memroot, combo.user);
 | |
|   update_hostname(&host, safe_strdup_root(&acl_memroot, combo.host.str));
 | |
|   hostname_length= combo.host.length;
 | |
|   sort= get_magic_sort("hu", host.hostname, user.str);
 | |
|   password_last_changed= thd->query_start();
 | |
|   password_lifetime= -1;
 | |
|   my_init_dynamic_array(PSI_INSTRUMENT_ME, &role_grants, sizeof(ACL_USER *), 0, 8, MYF(0));
 | |
| }
 | |
| 
 | |
| 
 | |
| static int acl_user_update(THD *thd, ACL_USER *acl_user, uint nauth,
 | |
|                            const LEX_USER &combo,
 | |
|                            const Account_options &options,
 | |
|                            const privilege_t privileges)
 | |
| {
 | |
|   ACL_USER_PARAM::AUTH *work_copy= NULL;
 | |
|   mysql_mutex_assert_owner(&acl_cache->lock);
 | |
| 
 | |
|   if (nauth)
 | |
|   {
 | |
|     if (!(work_copy= (ACL_USER_PARAM::AUTH*)
 | |
|             alloc_root(thd->mem_root, nauth * sizeof(ACL_USER_PARAM::AUTH))))
 | |
|       return 1;
 | |
| 
 | |
|     USER_AUTH *auth= combo.auth;
 | |
|     for (uint i= 0; i < nauth; i++, auth= auth->next)
 | |
|     {
 | |
|       work_copy[i].plugin= auth->plugin;
 | |
|       work_copy[i].auth_string= safe_lexcstrdup_root(&acl_memroot,
 | |
|                                                      auth->auth_str);
 | |
|       if (fix_user_plugin_ptr(work_copy + i))
 | |
|         work_copy[i].plugin= safe_lexcstrdup_root(&acl_memroot, auth->plugin);
 | |
|       if (set_user_auth(thd, acl_user->user,
 | |
|                         {acl_user->host.hostname, acl_user->hostname_length},
 | |
|                         work_copy + i, auth->pwtext))
 | |
|         return 1;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   acl_user->access= privileges;
 | |
|   if (options.specified_limits & USER_RESOURCES::QUERIES_PER_HOUR)
 | |
|     acl_user->user_resource.questions= options.questions;
 | |
|   if (options.specified_limits & USER_RESOURCES::UPDATES_PER_HOUR)
 | |
|     acl_user->user_resource.updates= options.updates;
 | |
|   if (options.specified_limits & USER_RESOURCES::CONNECTIONS_PER_HOUR)
 | |
|     acl_user->user_resource.conn_per_hour= options.conn_per_hour;
 | |
|   if (options.specified_limits & USER_RESOURCES::USER_CONNECTIONS)
 | |
|     acl_user->user_resource.user_conn= options.user_conn;
 | |
|   if (options.specified_limits & USER_RESOURCES::MAX_STATEMENT_TIME)
 | |
|     acl_user->user_resource.max_statement_time= options.max_statement_time;
 | |
|   if (options.ssl_type != SSL_TYPE_NOT_SPECIFIED)
 | |
|   {
 | |
|     acl_user->ssl_type= options.ssl_type;
 | |
|     acl_user->ssl_cipher= safe_strdup_root(&acl_memroot, options.ssl_cipher.str);
 | |
|     acl_user->x509_issuer= safe_strdup_root(&acl_memroot,
 | |
|                                             safe_str(options.x509_issuer.str));
 | |
|     acl_user->x509_subject= safe_strdup_root(&acl_memroot,
 | |
|                                              safe_str(options.x509_subject.str));
 | |
|   }
 | |
|   if (options.account_locked != ACCOUNTLOCK_UNSPECIFIED)
 | |
|     acl_user->account_locked= options.account_locked == ACCOUNTLOCK_LOCKED;
 | |
| 
 | |
|   if (thd->is_error())
 | |
|   {
 | |
|     // If something went wrong (including OOM) we will not spoil acl cache
 | |
|     return 1;
 | |
|   }
 | |
|   /* Unexpire the user password and copy AUTH (when no more errors possible)*/
 | |
|   if (nauth)
 | |
|   {
 | |
|     acl_user->password_expired= false;
 | |
|     acl_user->password_last_changed= thd->query_start();
 | |
| 
 | |
|     if (acl_user->nauth >= nauth)
 | |
|     {
 | |
|       acl_user->nauth= nauth;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       if (acl_user->alloc_auth(&acl_memroot, nauth))
 | |
|       {
 | |
|         /*
 | |
|           acl_user is a copy, so NULL assigned in case of an error do not
 | |
|           change the acl cache
 | |
|         */
 | |
|         return 1;
 | |
|       }
 | |
|     }
 | |
|     DBUG_ASSERT(work_copy); // allocated under the same condition
 | |
|     memcpy(acl_user->auth, work_copy,  nauth * sizeof(ACL_USER_PARAM::AUTH));
 | |
|   }
 | |
| 
 | |
|   switch (options.password_expire) {
 | |
|   case PASSWORD_EXPIRE_UNSPECIFIED:
 | |
|     break;
 | |
|   case PASSWORD_EXPIRE_NOW:
 | |
|     acl_user->password_expired= true;
 | |
|     break;
 | |
|   case PASSWORD_EXPIRE_NEVER:
 | |
|     acl_user->password_lifetime= 0;
 | |
|     break;
 | |
|   case PASSWORD_EXPIRE_DEFAULT:
 | |
|     acl_user->password_lifetime= -1;
 | |
|     break;
 | |
|   case PASSWORD_EXPIRE_INTERVAL:
 | |
|     acl_user->password_lifetime= options.num_expiration_days;
 | |
|     break;
 | |
|   }
 | |
| 
 | |
|   // Any alter user resets password_errors;
 | |
|   acl_user->update_password_errors(ACL_USER::PASSWD_ERROR_CLEAR);
 | |
| 
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| static void acl_insert_role(const LEX_CSTRING &rolename, privilege_t privileges)
 | |
| {
 | |
|   ACL_ROLE *entry;
 | |
|   DBUG_ENTER("acl_insert_role");
 | |
|   DBUG_PRINT("enter", ("Role: '%s'", rolename.str));
 | |
| 
 | |
|   mysql_mutex_assert_owner(&acl_cache->lock);
 | |
|   entry= new (&acl_memroot) ACL_ROLE(rolename.str, privileges, &acl_memroot);
 | |
|   my_init_dynamic_array(key_memory_acl_mem, &entry->parent_grantee,
 | |
|                         sizeof(ACL_USER_BASE *), 0, 8, MYF(0));
 | |
|   my_init_dynamic_array(key_memory_acl_mem, &entry->role_grants,
 | |
|                         sizeof(ACL_ROLE *), 0, 8, MYF(0));
 | |
| 
 | |
|   my_hash_insert(&acl_roles, (uchar *)entry);
 | |
|   DBUG_ASSERT(strcasecmp(rolename.str, public_name.str) ||
 | |
|               is_public(&rolename));
 | |
|   if (is_public(&rolename))
 | |
|     acl_public= entry;
 | |
| 
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| 
 | |
| 
 | |
| static bool acl_update_db(const char *user, const char *host, const char *db,
 | |
|                           privilege_t privileges)
 | |
| {
 | |
|   mysql_mutex_assert_owner(&acl_cache->lock);
 | |
| 
 | |
|   bool updated= false;
 | |
| 
 | |
|   for (size_t i= acl_find_db_by_username(user); i < acl_dbs.elements(); i++)
 | |
|   {
 | |
|     ACL_DB *acl_db= &acl_dbs.at(i);
 | |
|     if (!strcmp(user,acl_db->user))
 | |
|     {
 | |
|       if ((!acl_db->host.hostname && !host[0]) ||
 | |
|           (acl_db->host.hostname && !strcmp(host, acl_db->host.hostname)))
 | |
|       {
 | |
|         if ((!acl_db->db && !db[0]) ||
 | |
|             (acl_db->db && !strcmp(db,acl_db->db)))
 | |
| 
 | |
|         {
 | |
|           if (privileges)
 | |
|           {
 | |
|             acl_db->access= privileges;
 | |
|             acl_db->initial_access= acl_db->access;
 | |
|           }
 | |
|           else
 | |
|             acl_dbs.del(i);
 | |
|           updated= true;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     else
 | |
|      break;
 | |
|   }
 | |
| 
 | |
|   return updated;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Insert a user/db/host combination into the global acl_cache
 | |
| 
 | |
|   SYNOPSIS
 | |
|     acl_insert_db()
 | |
|     user		User name
 | |
|     host		Host name
 | |
|     db			Database name
 | |
|     privileges		Bitmap of privileges
 | |
| 
 | |
|   NOTES
 | |
|     acl_cache->lock must be locked when calling this
 | |
| */
 | |
| 
 | |
| static void acl_insert_db(const char *user, const char *host, const char *db,
 | |
|                           const privilege_t privileges)
 | |
| {
 | |
|   ACL_DB acl_db;
 | |
|   mysql_mutex_assert_owner(&acl_cache->lock);
 | |
|   acl_db.user=strdup_root(&acl_memroot,user);
 | |
|   update_hostname(&acl_db.host, safe_strdup_root(&acl_memroot, host));
 | |
|   acl_db.db=strdup_root(&acl_memroot,db);
 | |
|   acl_db.initial_access= acl_db.access= privileges;
 | |
|   acl_db.sort=get_magic_sort("hdu", acl_db.host.hostname, acl_db.db, acl_db.user);
 | |
|   acl_dbs.push(acl_db);
 | |
|   rebuild_acl_dbs();
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Get privilege for a host, user and db combination
 | |
| 
 | |
|   as db_is_pattern changes the semantics of comparison,
 | |
|   acl_cache is not used if db_is_pattern is set.
 | |
| */
 | |
| 
 | |
| privilege_t acl_get(const char *host, const char *ip,
 | |
|                     const char *user, const char *db, my_bool db_is_pattern)
 | |
| {
 | |
|   privilege_t host_access(ALL_KNOWN_ACL), db_access(NO_ACL);
 | |
|   uint i;
 | |
|   const char *tmp_db;
 | |
|   acl_entry *entry;
 | |
|   DBUG_ENTER("acl_get");
 | |
| 
 | |
|   // Key length, without the trailing '\0' byte
 | |
|   constexpr size_t key_data_size= IP_ADDR_STRLEN + 1/*'\0'*/ +
 | |
|                                   USERNAME_LENGTH + 1/*'\0'*/ +
 | |
|                                   NAME_LEN/*database*/;
 | |
|   /*
 | |
|     Let's reserve extra MY_CS_MBMAXLEN bytes in the buffer.
 | |
|     This is to catch cases when a too long database name gets truncated:
 | |
|       key.length() will return a length in the range:
 | |
|       [key_data_size + 1, key_data_size + MY_CS_MBMAXLEN].
 | |
|   */
 | |
|   CharBuffer<key_data_size + MY_CS_MBMAXLEN> key;
 | |
|   key.append(Lex_cstring_strlen(safe_str(ip))).append_char('\0')
 | |
|      .append(Lex_cstring_strlen(user)).append_char('\0');
 | |
|   tmp_db= key.end();
 | |
|   key.append_opt_casedn(files_charset_info, Lex_cstring_strlen(db),
 | |
|                         lower_case_table_names);
 | |
|   db= tmp_db;
 | |
| 
 | |
|   if (key.length() > key_data_size) // db name was truncated
 | |
|     DBUG_RETURN(NO_ACL);        // no privileges for an invalid db name
 | |
| 
 | |
|   mysql_mutex_lock(&acl_cache->lock);
 | |
|   if (!db_is_pattern &&
 | |
|       (entry= acl_cache->search((uchar*) key.ptr(), key.length())))
 | |
|   {
 | |
|     db_access=entry->access;
 | |
|     mysql_mutex_unlock(&acl_cache->lock);
 | |
|     DBUG_PRINT("exit", ("access: 0x%llx",  (longlong) db_access));
 | |
|     DBUG_RETURN(db_access);
 | |
|   }
 | |
| 
 | |
|   /*
 | |
|     Check if there are some access rights for database and user
 | |
|   */
 | |
|   if (ACL_DB *acl_db= acl_db_find(db,user, host, ip, db_is_pattern))
 | |
|   {
 | |
|     db_access= acl_db->access;
 | |
|     if (acl_db->host.hostname)
 | |
|       goto exit; // Fully specified. Take it
 | |
|     /* the host table is not used for roles */
 | |
|     if ((!host || !host[0]) && !acl_db->host.hostname &&
 | |
|         find_acl_role(Lex_cstring_strlen(user), false))
 | |
|       goto exit;
 | |
|   }
 | |
| 
 | |
|   if (!db_access)
 | |
|     goto exit;					// Can't be better
 | |
| 
 | |
|   /*
 | |
|     No host specified for user. Get hostdata from host table
 | |
|   */
 | |
|   host_access= NO_ACL;                          // Host must be found
 | |
|   for (i=0 ; i < acl_hosts.elements ; i++)
 | |
|   {
 | |
|     ACL_HOST *acl_host=dynamic_element(&acl_hosts,i,ACL_HOST*);
 | |
|     if (compare_hostname(&acl_host->host,host,ip))
 | |
|     {
 | |
|       if (!wild_compare(db, acl_host->db, db_is_pattern))
 | |
|       {
 | |
| 	host_access=acl_host->access;		// Fully specified. Take it
 | |
| 	break;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| exit:
 | |
|   /* Save entry in cache for quick retrieval */
 | |
|   if (!db_is_pattern &&
 | |
|       (entry= (acl_entry*) my_malloc(key_memory_acl_cache,
 | |
|                                      sizeof(acl_entry) + key.length(),
 | |
|                                      MYF(MY_WME))))
 | |
|   {
 | |
|     entry->access=(db_access & host_access);
 | |
|     DBUG_ASSERT(key.length() < 0xffff);
 | |
|     entry->length= (uint16) key.length();
 | |
|     memcpy((uchar*) entry->key, key.ptr(), key.length());
 | |
|     acl_cache->add(entry);
 | |
|   }
 | |
|   mysql_mutex_unlock(&acl_cache->lock);
 | |
|   DBUG_PRINT("exit", ("access: 0x%llx", (longlong) (db_access & host_access)));
 | |
|   DBUG_RETURN(db_access & host_access);
 | |
| }
 | |
| 
 | |
| /*
 | |
|   Check if there is access for the host/user, role, public on the database
 | |
| */
 | |
| 
 | |
| privilege_t acl_get_all3(Security_context *sctx, const char *db,
 | |
|                          bool db_is_patern)
 | |
| {
 | |
|   privilege_t access= acl_get(sctx->host, sctx->ip,
 | |
|                               sctx->priv_user, db, db_is_patern);
 | |
|   if (sctx->priv_role[0])
 | |
|     access|= acl_get("", "", sctx->priv_role, db, db_is_patern);
 | |
|   if (acl_public)
 | |
|     access|= acl_get("", "", public_name.str, db, db_is_patern);
 | |
|   return access;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Check if there are any possible matching entries for this host
 | |
| 
 | |
|   NOTES
 | |
|     All host names without wild cards are stored in a hash table,
 | |
|     entries with wildcards are stored in a dynamic array
 | |
| */
 | |
| 
 | |
| static void init_check_host(void)
 | |
| {
 | |
|   DBUG_ENTER("init_check_host");
 | |
|   (void) my_init_dynamic_array(key_memory_acl_mem, &acl_wild_hosts,
 | |
|                                sizeof(struct acl_host_and_ip),
 | |
|                                acl_users.elements, 1, MYF(0));
 | |
|   (void) my_hash_init(key_memory_acl_mem, &acl_check_hosts,
 | |
|                       Lex_ident_host::charset_info(),
 | |
|                       acl_users.elements, 0, 0, check_get_key, 0, 0);
 | |
|   if (!allow_all_hosts)
 | |
|   {
 | |
|     for (size_t i=0 ; i < acl_users.elements ; i++)
 | |
|     {
 | |
|       ACL_USER *acl_user=dynamic_element(&acl_users,i,ACL_USER*);
 | |
|       const Lex_ident_host acl_user_hostname(acl_user->hostname());
 | |
|       if (strchr(acl_user->host.hostname,wild_many) ||
 | |
| 	  strchr(acl_user->host.hostname,wild_one) ||
 | |
| 	  acl_user->host.ip_mask)
 | |
|       {						// Has wildcard
 | |
| 	size_t j;
 | |
| 	for (j=0 ; j < acl_wild_hosts.elements ; j++)
 | |
| 	{					// Check if host already exists
 | |
| 	  acl_host_and_ip *acl=dynamic_element(&acl_wild_hosts,j,
 | |
| 					       acl_host_and_ip *);
 | |
| 	  if (acl_user_hostname.streq(Lex_cstring_strlen(acl->hostname)))
 | |
| 	    break;				// already stored
 | |
| 	}
 | |
| 	if (j == acl_wild_hosts.elements)	// If new
 | |
| 	  (void) push_dynamic(&acl_wild_hosts,(uchar*) &acl_user->host);
 | |
|       }
 | |
|       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
 | |
| 	  allow_all_hosts=1;			// Should never happen
 | |
| 	  DBUG_VOID_RETURN;
 | |
| 	}
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   freeze_size(&acl_wild_hosts);
 | |
|   freeze_size(&acl_check_hosts.array);
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Rebuild lists used for checking of allowed hosts
 | |
| 
 | |
|   We need to rebuild 'acl_check_hosts' and 'acl_wild_hosts' after adding,
 | |
|   dropping or renaming user, since they contain pointers to elements of
 | |
|   'acl_user' array, which are invalidated by drop operation, and use
 | |
|   ACL_USER::host::hostname as a key, which is changed by rename.
 | |
| */
 | |
| static void rebuild_check_host(void)
 | |
| {
 | |
|   delete_dynamic(&acl_wild_hosts);
 | |
|   my_hash_free(&acl_check_hosts);
 | |
|   init_check_host();
 | |
| }
 | |
| 
 | |
| /*
 | |
|   Reset a role role_grants dynamic array.
 | |
|   Also, the role's access bits are reset to the ones present in the table.
 | |
| */
 | |
| static my_bool acl_role_reset_role_arrays(void *ptr,
 | |
|                                     void * not_used __attribute__((unused)))
 | |
| {
 | |
|   ACL_ROLE *role= (ACL_ROLE *)ptr;
 | |
|   reset_dynamic(&role->role_grants);
 | |
|   reset_dynamic(&role->parent_grantee);
 | |
|   role->counter= 0;
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| /*
 | |
|    Add the corresponding pointers present in the mapping to the entries in
 | |
|    acl_users and acl_roles
 | |
| */
 | |
| static bool add_role_user_mapping(ACL_USER_BASE *grantee, ACL_ROLE *role)
 | |
| {
 | |
|   return push_dynamic(&grantee->role_grants, (uchar*) &role)
 | |
|       || push_dynamic(&role->parent_grantee, (uchar*) &grantee);
 | |
| 
 | |
| }
 | |
| 
 | |
| /*
 | |
|   Revert the last add_role_user_mapping() action
 | |
| */
 | |
| static void undo_add_role_user_mapping(ACL_USER_BASE *grantee, ACL_ROLE *role)
 | |
| {
 | |
|   void *pop __attribute__((unused));
 | |
| 
 | |
|   pop= pop_dynamic(&grantee->role_grants);
 | |
|   DBUG_ASSERT(role == *(ACL_ROLE**)pop);
 | |
| 
 | |
|   pop= pop_dynamic(&role->parent_grantee);
 | |
|   DBUG_ASSERT(grantee == *(ACL_USER_BASE**)pop);
 | |
| }
 | |
| 
 | |
| /*
 | |
|   this helper is used when building role_grants and parent_grantee arrays
 | |
|   from scratch.
 | |
| 
 | |
|   this happens either on initial loading of data from tables, in acl_load().
 | |
|   or in rebuild_role_grants after acl_role_reset_role_arrays().
 | |
| */
 | |
| static bool add_role_user_mapping(const LEX_CSTRING &uname,
 | |
|                                   const LEX_CSTRING &hname,
 | |
|                                   const LEX_CSTRING &rname)
 | |
| {
 | |
|   ACL_USER_BASE *grantee= find_acl_user_base(uname, hname);
 | |
|   ACL_ROLE *role= find_acl_role(rname, false);
 | |
| 
 | |
|   if (grantee == NULL || role == NULL)
 | |
|     return 1;
 | |
| 
 | |
|   /*
 | |
|     because all arrays are rebuilt completely, and counters were also reset,
 | |
|     we can increment them here, and after the rebuild all counters will
 | |
|     have correct values (equal to the number of roles granted).
 | |
|   */
 | |
|   if (grantee->flags & IS_ROLE)
 | |
|     ((ACL_ROLE*)grantee)->counter++;
 | |
|   return add_role_user_mapping(grantee, role);
 | |
| }
 | |
| 
 | |
| /*
 | |
|   This helper function is used to removes roles and grantees
 | |
|   from the corresponding cross-reference arrays. see remove_role_user_mapping().
 | |
|   as such, it asserts that an element to delete is present in the array,
 | |
|   and is present only once.
 | |
| */
 | |
| static void remove_ptr_from_dynarray(DYNAMIC_ARRAY *array, void *ptr)
 | |
| {
 | |
|   bool found __attribute__((unused))= false;
 | |
|   for (size_t i= 0; i < array->elements; i++)
 | |
|   {
 | |
|     if (ptr == *dynamic_element(array, i, void**))
 | |
|     {
 | |
|       DBUG_ASSERT(!found);
 | |
|       delete_dynamic_element(array, i);
 | |
|       IF_DBUG_ASSERT(found= true, break);
 | |
|     }
 | |
|   }
 | |
|   DBUG_ASSERT(found);
 | |
| }
 | |
| 
 | |
| static void remove_role_user_mapping(ACL_USER_BASE *grantee, ACL_ROLE *role,
 | |
|                                      int grantee_idx=-1, int role_idx=-1)
 | |
| {
 | |
|   remove_ptr_from_dynarray(&grantee->role_grants, role);
 | |
|   remove_ptr_from_dynarray(&role->parent_grantee, grantee);
 | |
| }
 | |
| 
 | |
| 
 | |
| static my_bool add_role_user_mapping_action(void *ptr, void *unused __attribute__((unused)))
 | |
| {
 | |
|   ROLE_GRANT_PAIR *pair= (ROLE_GRANT_PAIR*)ptr;
 | |
|   bool status __attribute__((unused));
 | |
|   status= add_role_user_mapping(pair->u_uname, pair->u_hname, pair->r_uname);
 | |
|   /*
 | |
|      The invariant chosen is that acl_roles_mappings should _always_
 | |
|      only contain valid entries, referencing correct user and role grants.
 | |
|      If add_role_user_mapping detects an invalid entry, it will not add
 | |
|      the mapping into the ACL_USER::role_grants array.
 | |
|   */
 | |
|   DBUG_ASSERT(status == 0);
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Rebuild the role grants every time the acl_users is modified
 | |
| 
 | |
|   The role grants in the ACL_USER class need to be rebuilt, as they contain
 | |
|   pointers to elements of the acl_users array.
 | |
| */
 | |
| 
 | |
| static void rebuild_role_grants(void)
 | |
| {
 | |
|   DBUG_ENTER("rebuild_role_grants");
 | |
|   /*
 | |
|     Reset every user's and role's role_grants array
 | |
|   */
 | |
|   for (size_t i=0; i < acl_users.elements; i++) {
 | |
|     ACL_USER *user= dynamic_element(&acl_users, i, ACL_USER *);
 | |
|     reset_dynamic(&user->role_grants);
 | |
|   }
 | |
|   my_hash_iterate(&acl_roles, acl_role_reset_role_arrays, NULL);
 | |
| 
 | |
|   /* Rebuild the direct links between users and roles in ACL_USER::role_grants */
 | |
|   my_hash_iterate(&acl_roles_mappings, add_role_user_mapping_action, NULL);
 | |
| 
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| 
 | |
| 
 | |
| /* Return true if there is no users that can match the given host */
 | |
| bool acl_check_host(const char *host, const char *ip)
 | |
| {
 | |
|   if (allow_all_hosts)
 | |
|     return 0;
 | |
|   mysql_mutex_lock(&acl_cache->lock);
 | |
| 
 | |
|   if (allow_all_hosts ||
 | |
|       (host && my_hash_search(&acl_check_hosts,(uchar*) host,strlen(host))) ||
 | |
|       (ip && my_hash_search(&acl_check_hosts,(uchar*) ip, strlen(ip))))
 | |
|   {
 | |
|     mysql_mutex_unlock(&acl_cache->lock);
 | |
|     return 0;					// Found host
 | |
|   }
 | |
|   for (size_t i=0 ; i < acl_wild_hosts.elements ; i++)
 | |
|   {
 | |
|     acl_host_and_ip *acl=dynamic_element(&acl_wild_hosts,i,acl_host_and_ip*);
 | |
|     if (compare_hostname(acl, host, ip))
 | |
|     {
 | |
|       mysql_mutex_unlock(&acl_cache->lock);
 | |
|       return 0;					// Host ok
 | |
|     }
 | |
|   }
 | |
|   mysql_mutex_unlock(&acl_cache->lock);
 | |
|   if (ip != NULL)
 | |
|   {
 | |
|     /* Increment HOST_CACHE.COUNT_HOST_ACL_ERRORS. */
 | |
|     Host_errors errors;
 | |
|     errors.m_host_acl= 1;
 | |
|     inc_host_errors(ip, &errors);
 | |
|   }
 | |
|   return 1;					// Host is not allowed
 | |
| }
 | |
| 
 | |
| /**
 | |
|   Check if the user is allowed to alter the mysql.user table
 | |
| 
 | |
|  @param thd              THD
 | |
|  @param host             Hostname for the user
 | |
|  @param user             User name
 | |
| 
 | |
|  @return Error status
 | |
|    @retval 0 OK
 | |
|    @retval 1 Error
 | |
| */
 | |
| 
 | |
| static int check_alter_user(THD *thd,
 | |
|                             const LEX_CSTRING &host,
 | |
|                             const LEX_CSTRING &user)
 | |
| {
 | |
|   int error = 1;
 | |
|   if (!initialized)
 | |
|   {
 | |
|     my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--skip-grant-tables");
 | |
|     goto end;
 | |
|   }
 | |
| 
 | |
|   if (IF_WSREP((!WSREP(thd) || !thd->wsrep_applier), 1) &&
 | |
|       !thd->slave_thread && !thd->security_ctx->priv_user[0] &&
 | |
|       !thd->bootstrap)
 | |
|   {
 | |
|     my_message(ER_PASSWORD_ANONYMOUS_USER,
 | |
|                ER_THD(thd, ER_PASSWORD_ANONYMOUS_USER),
 | |
|                MYF(0));
 | |
|     goto end;
 | |
|   }
 | |
|   if (!host.str) // Role
 | |
|   {
 | |
|     my_error(ER_PASSWORD_NO_MATCH, MYF(0));
 | |
|     goto end;
 | |
|   }
 | |
| 
 | |
|   if (!thd->slave_thread &&
 | |
|       IF_WSREP((!WSREP(thd) || !thd->wsrep_applier),1) &&
 | |
|       !thd->security_ctx->is_priv_user(user, host))
 | |
|   {
 | |
|     if (thd->security_ctx->password_expired)
 | |
|     {
 | |
|       my_error(ER_MUST_CHANGE_PASSWORD, MYF(0));
 | |
|       goto end;
 | |
|     }
 | |
|     if (check_access(thd, UPDATE_ACL, "mysql", NULL, NULL, 1, 0))
 | |
|       goto end;
 | |
|   }
 | |
| 
 | |
|   error = 0;
 | |
| 
 | |
| end:
 | |
|   return error;
 | |
| }
 | |
| /**
 | |
|   Check if the user is allowed to change password
 | |
| 
 | |
|  @param thd              THD
 | |
|  @param user             User, hostname, new password or password hash
 | |
| 
 | |
|  @return Error status
 | |
|    @retval 0 OK
 | |
|    @retval 1 ERROR; In this case the error is sent to the client.
 | |
| */
 | |
| 
 | |
| bool check_change_password(THD *thd, LEX_USER *user)
 | |
| {
 | |
|   LEX_USER *real_user= get_current_user(thd, user);
 | |
|   user->user= real_user->user;
 | |
|   user->host= real_user->host;
 | |
|   return check_alter_user(thd, user->host, user->user);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Change a password for a user.
 | |
| 
 | |
|   @param thd            THD
 | |
|   @param user           User, hostname, new password hash
 | |
|  
 | |
|   @return Error code
 | |
|    @retval 0 ok
 | |
|    @retval 1 ERROR; In this case the error is sent to the client.
 | |
| */
 | |
| bool change_password(THD *thd, LEX_USER *user)
 | |
| {
 | |
|   Grant_tables tables;
 | |
|   /* Buffer should be extended when password length is extended. */
 | |
|   char buff[512];
 | |
|   ulong query_length= 0;
 | |
|   enum_binlog_format save_binlog_format;
 | |
|   bool result= false, acl_cache_is_locked= false;
 | |
|   ACL_USER *acl_user;
 | |
|   ACL_USER::AUTH auth;
 | |
|   const char *password_plugin= 0;
 | |
|   const CSET_STRING query_save __attribute__((unused)) = thd->query_string;
 | |
|   DBUG_ENTER("change_password");
 | |
|   DBUG_PRINT("enter",("host: '%s'  user: '%s'  new_password: '%s'",
 | |
| 		      user->host.str, user->user.str, user->auth->auth_str.str));
 | |
|   DBUG_ASSERT(user->host.str != 0);                     // Ensured by caller
 | |
| 
 | |
|   /*
 | |
|     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.
 | |
|     This has to be handled here as it's called by set_var.cc, which is
 | |
|     not automaticly handled by sql_parse.cc
 | |
|   */
 | |
|   save_binlog_format= thd->set_current_stmt_binlog_format_stmt();
 | |
| 
 | |
|   if (WSREP(thd) && !IF_WSREP(thd->wsrep_applier, 0))
 | |
|     WSREP_TO_ISOLATION_BEGIN(WSREP_MYSQL_DB, NULL, NULL);
 | |
| 
 | |
|   if ((result= tables.open_and_lock(thd, Table_user, TL_WRITE)))
 | |
|     DBUG_RETURN(result != 1);
 | |
| 
 | |
|   acl_cache_is_locked= 1;
 | |
|   mysql_mutex_lock(&acl_cache->lock);
 | |
| 
 | |
|   if (!(acl_user= find_user_exact(user->host, user->user)))
 | |
|   {
 | |
|     my_error(ER_PASSWORD_NO_MATCH, MYF(0));
 | |
|     goto end;
 | |
|   }
 | |
| 
 | |
|   if (acl_user->nauth == 1 &&
 | |
|       (acl_user->auth[0].plugin.str == native_password_plugin_name.str ||
 | |
|        acl_user->auth[0].plugin.str == old_password_plugin_name.str))
 | |
|   {
 | |
|     /* historical hack of auto-changing the plugin */
 | |
|     acl_user->auth[0].plugin= guess_auth_plugin(thd, user->auth->auth_str.length);
 | |
|   }
 | |
| 
 | |
|   for (uint i=0; i < acl_user->nauth; i++)
 | |
|   {
 | |
|     auth= acl_user->auth[i];
 | |
|     auth.auth_string= safe_lexcstrdup_root(&acl_memroot, user->auth->auth_str);
 | |
|     int r= set_user_auth(thd, user->user, user->host,
 | |
|                          &auth, user->auth->pwtext);
 | |
|     if (r == ER_SET_PASSWORD_AUTH_PLUGIN)
 | |
|       password_plugin= auth.plugin.str;
 | |
|     else if (r)
 | |
|       goto end;
 | |
|     else
 | |
|     {
 | |
|       acl_user->auth[i]= auth;
 | |
|       password_plugin= 0;
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
|   if (password_plugin)
 | |
|   {
 | |
|     my_error(ER_SET_PASSWORD_AUTH_PLUGIN, MYF(0), password_plugin);
 | |
|     goto end;
 | |
|   }
 | |
| 
 | |
|   /* Update the acl password expired state of user */
 | |
|   acl_user->password_last_changed= thd->query_start();
 | |
|   acl_user->password_expired= false;
 | |
| 
 | |
|   /* If user is the connected user, reset the password expired field on sctx
 | |
|      and allow the user to exit sandbox mode */
 | |
|   if (thd->security_ctx->is_priv_user(user->user, user->host))
 | |
|     thd->security_ctx->password_expired= false;
 | |
| 
 | |
|   if (update_user_table_password(thd, tables.user_table(), *acl_user))
 | |
|     goto end;
 | |
| 
 | |
|   hostname_cache_refresh();                    // Clear locked hostname cache
 | |
|   mysql_mutex_unlock(&acl_cache->lock);
 | |
|   result= acl_cache_is_locked= 0;
 | |
|   if (mysql_bin_log.is_open())
 | |
|   {
 | |
|     query_length= sprintf(buff, "SET PASSWORD FOR '%-.120s'@'%-.120s'='%-.120s'",
 | |
|            user->user.str, safe_str(user->host.str), auth.auth_string.str);
 | |
|     DBUG_ASSERT(query_length);
 | |
|     thd->clear_error();
 | |
|     result= thd->binlog_query(THD::STMT_QUERY_TYPE, buff, query_length,
 | |
|                               FALSE, FALSE, FALSE, 0) > 0;
 | |
|   }
 | |
| end:
 | |
|   if (acl_cache_is_locked)
 | |
|     mysql_mutex_unlock(&acl_cache->lock);
 | |
|   close_mysql_tables(thd);
 | |
| 
 | |
| #ifdef WITH_WSREP
 | |
| wsrep_error_label:
 | |
|   if (WSREP(thd))
 | |
|   {
 | |
|     wsrep_to_isolation_end(thd);
 | |
|     thd->set_query(query_save);
 | |
|   }
 | |
| #endif /* WITH_WSREP */
 | |
|   thd->restore_stmt_binlog_format(save_binlog_format);
 | |
| 
 | |
|   DBUG_RETURN(result);
 | |
| }
 | |
| 
 | |
| int acl_check_set_default_role(THD *thd,
 | |
|                                const LEX_CSTRING &host,
 | |
|                                const LEX_CSTRING &user,
 | |
|                                const LEX_CSTRING &role)
 | |
| {
 | |
|   DBUG_ENTER("acl_check_set_default_role");
 | |
| #ifdef HAVE_REPLICATION
 | |
|   /*
 | |
|     If the roles_mapping table is excluded by the replication filter, we return
 | |
|     successful without validating the user/role data because the command will
 | |
|     be ignored in a later call to `acl_set_default_role()` for a graceful exit.
 | |
|   */
 | |
|   Grant_tables tables;
 | |
|   TABLE_LIST* first= NULL;
 | |
|   if (tables.rpl_ignore_tables(thd, first, Table_roles_mapping, TL_WRITE))
 | |
|     DBUG_RETURN(0);
 | |
| #endif
 | |
|   DBUG_RETURN(check_alter_user(thd, host, user) ||
 | |
|               check_user_can_set_role(thd, user, host,
 | |
|                                       null_clex_str, role, NULL));
 | |
| }
 | |
| 
 | |
| int acl_set_default_role(THD *thd,
 | |
|                          const LEX_CSTRING &host,
 | |
|                          const LEX_CSTRING &user,
 | |
|                          const LEX_CSTRING &rolename)
 | |
| {
 | |
|   Grant_tables tables;
 | |
|   char user_key[MAX_KEY_LENGTH];
 | |
|   int result= 1;
 | |
|   int error;
 | |
|   ulong query_length= 0;
 | |
|   bool clear_role= FALSE;
 | |
|   char buff[512];
 | |
|   enum_binlog_format save_binlog_format= thd->get_current_stmt_binlog_format();
 | |
|   const CSET_STRING query_save __attribute__((unused)) = thd->query_string;
 | |
| 
 | |
|   DBUG_ENTER("acl_set_default_role");
 | |
|   DBUG_PRINT("enter",("host: '%s'  user: '%s'  rolename: '%s'",
 | |
|                       user.str, safe_str(host.str), safe_str(rolename.str)));
 | |
| 
 | |
|   if (!strcasecmp(rolename.str, none.str))
 | |
|     clear_role= TRUE;
 | |
| 
 | |
|   if (mysql_bin_log.is_open() ||
 | |
|       (WSREP(thd) && !IF_WSREP(thd->wsrep_applier, 0)))
 | |
|   {
 | |
|     query_length=
 | |
|       sprintf(buff,"SET DEFAULT ROLE '%-.120s' FOR '%-.120s'@'%-.120s'",
 | |
|               safe_str(rolename.str), user.str, safe_str(host.str));
 | |
|   }
 | |
| 
 | |
|   /*
 | |
|     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.
 | |
|     This has to be handled here as it's called by set_var.cc, which is
 | |
|     not automaticly handled by sql_parse.cc
 | |
|   */
 | |
|   save_binlog_format= thd->set_current_stmt_binlog_format_stmt();
 | |
| 
 | |
|   if (WSREP(thd) && !IF_WSREP(thd->wsrep_applier, 0))
 | |
|   {
 | |
|     thd->set_query(buff, query_length, system_charset_info);
 | |
|     // Attention!!! here is implicit goto error;
 | |
|     WSREP_TO_ISOLATION_BEGIN(WSREP_MYSQL_DB, NULL, NULL);
 | |
|   }
 | |
| 
 | |
|   /*
 | |
|     Extra block due to WSREP_TO_ISOLATION_BEGIN using goto.
 | |
|     TODO(cvicentiu) Should move  this block out in a new function.
 | |
|   */
 | |
|   {
 | |
|     if ((result= tables.open_and_lock(thd, Table_user, TL_WRITE)))
 | |
|       DBUG_RETURN(result != 1);
 | |
| 
 | |
|     const User_table& user_table= tables.user_table();
 | |
|     TABLE *table= user_table.table();
 | |
| 
 | |
|     result= 1;
 | |
| 
 | |
|     mysql_mutex_lock(&acl_cache->lock);
 | |
|     ACL_USER *acl_user;
 | |
|     if (!(acl_user= find_user_exact(host, user)))
 | |
|     {
 | |
|       mysql_mutex_unlock(&acl_cache->lock);
 | |
|       my_message(ER_PASSWORD_NO_MATCH, ER_THD(thd, ER_PASSWORD_NO_MATCH),
 | |
|                  MYF(0));
 | |
|       goto end;
 | |
|     }
 | |
| 
 | |
|     if (!clear_role)
 | |
|     {
 | |
|       /* set new default_rolename */
 | |
|       acl_user->default_rolename.str= safe_strdup_root(&acl_memroot,
 | |
|                                                        rolename.str);
 | |
|       acl_user->default_rolename.length= rolename.length;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       /* clear the default_rolename */
 | |
|       acl_user->default_rolename.str = NULL;
 | |
|       acl_user->default_rolename.length = 0;
 | |
|     }
 | |
| 
 | |
|     /* update the mysql.user table with the new default role */
 | |
|     tables.user_table().table()->use_all_columns();
 | |
|     user_table.set_host(host.str, host.length);
 | |
|     user_table.set_user(user.str, user.length);
 | |
|     key_copy((uchar *) user_key, table->record[0], table->key_info,
 | |
|              table->key_info->key_length);
 | |
| 
 | |
|     if (table->file->ha_index_read_idx_map(table->record[0], 0,
 | |
|                                            (uchar *) user_key, HA_WHOLE_KEY,
 | |
|                                            HA_READ_KEY_EXACT))
 | |
|     {
 | |
|       mysql_mutex_unlock(&acl_cache->lock);
 | |
|       my_message(ER_PASSWORD_NO_MATCH, ER_THD(thd, ER_PASSWORD_NO_MATCH),
 | |
|                  MYF(0));
 | |
|       goto end;
 | |
|     }
 | |
|     store_record(table, record[1]);
 | |
|     user_table.set_default_role(acl_user->default_rolename.str,
 | |
|                                 acl_user->default_rolename.length);
 | |
|     if (unlikely(error= table->file->ha_update_row(table->record[1],
 | |
|                                                    table->record[0])) &&
 | |
|         error != HA_ERR_RECORD_IS_THE_SAME)
 | |
|     {
 | |
|       mysql_mutex_unlock(&acl_cache->lock);
 | |
|       table->file->print_error(error,MYF(0));	/* purecov: deadcode */
 | |
|       goto end;
 | |
|     }
 | |
| 
 | |
|     mysql_mutex_unlock(&acl_cache->lock);
 | |
|     result= 0;
 | |
|     if (mysql_bin_log.is_open())
 | |
|     {
 | |
|       DBUG_ASSERT(query_length);
 | |
|       thd->clear_error();
 | |
|       result= thd->binlog_query(THD::STMT_QUERY_TYPE, buff, query_length,
 | |
|                                 FALSE, FALSE, FALSE, 0) > 0;
 | |
|     }
 | |
|   end:
 | |
|     close_mysql_tables(thd);
 | |
|   }
 | |
| 
 | |
| #ifdef WITH_WSREP
 | |
| wsrep_error_label:
 | |
|   if (WSREP(thd))
 | |
|   {
 | |
|     wsrep_to_isolation_end(thd);
 | |
|     thd->set_query(query_save);
 | |
|   }
 | |
| #endif /* WITH_WSREP */
 | |
| 
 | |
|   thd->restore_stmt_binlog_format(save_binlog_format);
 | |
| 
 | |
|   DBUG_RETURN(result);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Find user in ACL. Uses to check a definer
 | |
| 
 | |
|   SYNOPSIS
 | |
|     is_acl_user()
 | |
|     host                 host name
 | |
|     user                 user name
 | |
| 
 | |
|   RETURN
 | |
|    FALSE  definer not fond
 | |
|    TRUE   there is such definer
 | |
| */
 | |
| 
 | |
| bool is_acl_user(const LEX_CSTRING &host, const LEX_CSTRING &user)
 | |
| {
 | |
|   bool res;
 | |
| 
 | |
|   /* --skip-grants */
 | |
|   if (!initialized)
 | |
|     return TRUE;
 | |
| 
 | |
|   mysql_mutex_lock(&acl_cache->lock);
 | |
| 
 | |
|   if (*host.str) // User
 | |
|     res= find_user_exact(host, user) != NULL;
 | |
|   else // Role
 | |
|     res= find_acl_role(user, false) != NULL;
 | |
| 
 | |
|   mysql_mutex_unlock(&acl_cache->lock);
 | |
|   return res;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Find first entry that matches the specified user@host pair
 | |
| */
 | |
| static ACL_USER *find_user_exact(const LEX_CSTRING &host,
 | |
|                                  const LEX_CSTRING &user)
 | |
| {
 | |
|   mysql_mutex_assert_owner(&acl_cache->lock);
 | |
|   size_t start= acl_find_user_by_name(user.str);
 | |
| 
 | |
|   for (size_t i= start; i < acl_users.elements; i++)
 | |
|   {
 | |
|     ACL_USER *acl_user= dynamic_element(&acl_users, i, ACL_USER*);
 | |
|     if (i > start && strcmp(acl_user->user.str, user.str))
 | |
|       return 0;
 | |
| 
 | |
|     if (Lex_ident_host(host).streq(acl_user->hostname()))
 | |
|       return acl_user;
 | |
|   }
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| /*
 | |
|   Find first entry that matches the specified user@host pair
 | |
| */
 | |
| static ACL_USER * find_user_wild(const LEX_CSTRING &host,
 | |
|                                  const LEX_CSTRING &user,
 | |
|                                  const LEX_CSTRING &ip)
 | |
| {
 | |
|   mysql_mutex_assert_owner(&acl_cache->lock);
 | |
| 
 | |
|   size_t start = acl_find_user_by_name(user.str);
 | |
| 
 | |
|   for (size_t i= start; i < acl_users.elements; i++)
 | |
|   {
 | |
|     ACL_USER *acl_user=dynamic_element(&acl_users,i,ACL_USER*);
 | |
|     if (i > start && strcmp(acl_user->user.str, user.str))
 | |
|       break;
 | |
|     if (compare_hostname(&acl_user->host, host.str, ip.str ? ip.str : host.str))
 | |
|       return acl_user;
 | |
|   }
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| /*
 | |
|   Find a role with the specified name
 | |
| */
 | |
| static ACL_ROLE *find_acl_role(const LEX_CSTRING &role, bool allow_public)
 | |
| {
 | |
|   DBUG_ENTER("find_acl_role");
 | |
|   DBUG_PRINT("enter",("role: '%s'", role.str));
 | |
|   DBUG_PRINT("info", ("Hash elements: %ld", acl_roles.records));
 | |
| 
 | |
|   mysql_mutex_assert_owner(&acl_cache->lock);
 | |
| 
 | |
|   if (!role.length ||
 | |
|       (!allow_public &&
 | |
|        my_charset_utf8mb3_general1400_as_ci.streq(role, public_name)))
 | |
|     DBUG_RETURN(NULL);
 | |
| 
 | |
|   ACL_ROLE *r= (ACL_ROLE *)my_hash_search(&acl_roles, (uchar *)role.str,
 | |
|                                           role.length);
 | |
|   DBUG_RETURN(r);
 | |
| }
 | |
| 
 | |
| /*
 | |
|   Finds a grantee - something that privileges or roles can be granted to.
 | |
| */
 | |
| static ACL_USER_BASE *find_acl_user_base(const LEX_CSTRING &user,
 | |
|                                          const LEX_CSTRING &host)
 | |
| {
 | |
|   if (*host.str)
 | |
|     return find_user_exact(host, user);
 | |
| 
 | |
|   return find_acl_role(user, true);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Comparing of hostnames
 | |
| 
 | |
|   NOTES
 | |
|   A hostname may be of type:
 | |
|   hostname   (May include wildcards);   monty.pp.sci.fi
 | |
|   ip	   (May include wildcards);   192.168.0.0
 | |
|   ip/netmask			      192.168.0.0/255.255.255.0
 | |
| 
 | |
|   A net mask of 0.0.0.0 is not allowed.
 | |
| */
 | |
| 
 | |
| static const char *calc_ip(const char *ip, long *val, char end)
 | |
| {
 | |
|   long ip_val,tmp;
 | |
|   if (!(ip=str2int(ip,10,0,255,&ip_val)) || *ip != '.')
 | |
|     return 0;
 | |
|   ip_val<<=24;
 | |
|   if (!(ip=str2int(ip+1,10,0,255,&tmp)) || *ip != '.')
 | |
|     return 0;
 | |
|   ip_val+=tmp<<16;
 | |
|   if (!(ip=str2int(ip+1,10,0,255,&tmp)) || *ip != '.')
 | |
|     return 0;
 | |
|   ip_val+=tmp<<8;
 | |
|   if (!(ip=str2int(ip+1,10,0,255,&tmp)) || *ip != end)
 | |
|     return 0;
 | |
|   *val=ip_val+tmp;
 | |
|   return ip;
 | |
| }
 | |
| 
 | |
| 
 | |
| static void update_hostname(acl_host_and_ip *host, const char *hostname)
 | |
| {
 | |
|   // fix historical undocumented convention that empty host is the same as '%'
 | |
|   hostname=const_cast<char*>(hostname ? hostname : host_not_specified.str);
 | |
|   host->hostname=(char*) hostname;             // This will not be modified!
 | |
|   if (!(hostname= calc_ip(hostname,&host->ip,'/')) ||
 | |
|       !(hostname= calc_ip(hostname+1,&host->ip_mask,'\0')))
 | |
|   {
 | |
|     host->ip= host->ip_mask=0;			// Not a masked ip
 | |
|   }
 | |
| }
 | |
| 
 | |
| 
 | |
| static bool compare_hostname(const acl_host_and_ip *host, const char *hostname,
 | |
| 			     const char *ip)
 | |
| {
 | |
|   long tmp;
 | |
|   if (host->ip_mask && ip && calc_ip(ip,&tmp,'\0'))
 | |
|   {
 | |
|     return (tmp & host->ip_mask) == host->ip;
 | |
|   }
 | |
|   return (!host->hostname ||
 | |
| 	  (hostname && !wild_case_compare(system_charset_info,
 | |
|                                           hostname, host->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 pattern and
 | |
|           should not be resolved.
 | |
| */
 | |
| 
 | |
| bool hostname_requires_resolving(const char *hostname)
 | |
| {
 | |
|   if (!hostname)
 | |
|     return FALSE;
 | |
| 
 | |
|   /* Check if hostname is the localhost. */
 | |
| 
 | |
|   if (hostname == my_localhost ||
 | |
|       Lex_ident_host(Lex_cstring_strlen(hostname)).
 | |
|         streq(Lex_cstring_strlen(my_localhost)))
 | |
|   {
 | |
|     return FALSE;
 | |
|   }
 | |
| 
 | |
|   /*
 | |
|     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)
 | |
|   {
 | |
|     switch (*p) {
 | |
|       case ':':
 | |
|       case '%':
 | |
|       case '_':
 | |
|       case '/':
 | |
|         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. */
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Update record for user in mysql.user privilege table with new password.
 | |
| 
 | |
|   @see change_password
 | |
| */
 | |
| 
 | |
| static bool update_user_table_password(THD *thd, const User_table& user_table,
 | |
|                                        const ACL_USER &user)
 | |
| {
 | |
|   char user_key[MAX_KEY_LENGTH];
 | |
|   int error;
 | |
|   DBUG_ENTER("update_user_table_password");
 | |
| 
 | |
|   TABLE *table= user_table.table();
 | |
|   table->use_all_columns();
 | |
|   user_table.set_host(user.host.hostname, user.hostname_length);
 | |
|   user_table.set_user(user.user.str, user.user.length);
 | |
|   key_copy((uchar *) user_key, table->record[0], table->key_info,
 | |
|            table->key_info->key_length);
 | |
| 
 | |
|   if (table->file->ha_index_read_idx_map(table->record[0], 0,
 | |
|                                          (uchar *) user_key, HA_WHOLE_KEY,
 | |
|                                          HA_READ_KEY_EXACT))
 | |
|   {
 | |
|     my_message(ER_PASSWORD_NO_MATCH, ER_THD(thd, ER_PASSWORD_NO_MATCH),
 | |
|                MYF(0));
 | |
|     DBUG_RETURN(1);
 | |
|   }
 | |
|   store_record(table, record[1]);
 | |
| 
 | |
|   if (user_table.set_auth(user))
 | |
|   {
 | |
|     my_error(ER_COL_COUNT_DOESNT_MATCH_PLEASE_UPDATE, MYF(0),
 | |
|              user_table.name().str, 3, user_table.num_fields(),
 | |
|              static_cast<int>(table->s->mysql_version), MYSQL_VERSION_ID);
 | |
|     DBUG_RETURN(1);
 | |
|   }
 | |
| 
 | |
|   user_table.set_password_expired(user.password_expired);
 | |
|   user_table.set_password_last_changed(user.password_last_changed);
 | |
| 
 | |
|   if (unlikely(error= table->file->ha_update_row(table->record[1],
 | |
|                                                  table->record[0])) &&
 | |
|       error != HA_ERR_RECORD_IS_THE_SAME)
 | |
|   {
 | |
|     table->file->print_error(error,MYF(0));
 | |
|     DBUG_RETURN(1);
 | |
|   }
 | |
| 
 | |
|   DBUG_RETURN(0);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Return 1 if we are allowed to create new users
 | |
|   the logic here is: INSERT_ACL is sufficient.
 | |
|   It's also a requirement in opt_safe_user_create,
 | |
|   otherwise CREATE_USER_ACL is enough.
 | |
| */
 | |
| 
 | |
| static bool test_if_create_new_users(THD *thd)
 | |
| {
 | |
|   Security_context *sctx= thd->security_ctx;
 | |
|   bool create_new_users= MY_TEST(sctx->master_access & INSERT_ACL) ||
 | |
|                          (!opt_safe_user_create &&
 | |
|                           MY_TEST(sctx->master_access & CREATE_USER_ACL));
 | |
|   if (!create_new_users)
 | |
|   {
 | |
|     TABLE_LIST tl;
 | |
|     privilege_t db_access(NO_ACL);
 | |
|     tl.init_one_table(&MYSQL_SCHEMA_NAME, &MYSQL_TABLE_NAME[USER_TABLE],
 | |
|                       NULL, TL_WRITE);
 | |
|     create_new_users= 1;
 | |
| 
 | |
|     db_access= acl_get_all3(sctx, tl.db.str, FALSE);
 | |
|     if (!(db_access & INSERT_ACL))
 | |
|     {
 | |
|       if (check_grant(thd, INSERT_ACL, &tl, FALSE, UINT_MAX, TRUE))
 | |
|         create_new_users=0;
 | |
|     }
 | |
|   }
 | |
|   return create_new_users;
 | |
| }
 | |
| 
 | |
| 
 | |
| /****************************************************************************
 | |
|   Handle GRANT commands
 | |
| ****************************************************************************/
 | |
| static USER_AUTH auth_no_password;
 | |
| 
 | |
| static int replace_user_table(THD *thd, const User_table &user_table,
 | |
|                               LEX_USER * const combo, privilege_t rights,
 | |
|                               const bool revoke_grant,
 | |
|                               const bool can_create_user,
 | |
|                               const bool no_auto_create)
 | |
| {
 | |
|   int error = -1;
 | |
|   uint nauth= 0;
 | |
|   bool old_row_exists=0;
 | |
|   uchar user_key[MAX_KEY_LENGTH];
 | |
|   bool handle_as_role= combo->is_role();
 | |
|   LEX *lex= thd->lex;
 | |
|   TABLE *table= user_table.table();
 | |
|   ACL_USER new_acl_user, *old_acl_user= 0;
 | |
|   DBUG_ENTER("replace_user_table");
 | |
| 
 | |
|   mysql_mutex_assert_owner(&acl_cache->lock);
 | |
| 
 | |
|   table->use_all_columns();
 | |
|   user_table.set_host(combo->host.str,combo->host.length);
 | |
|   user_table.set_user(combo->user.str,combo->user.length);
 | |
|   key_copy(user_key, table->record[0], table->key_info,
 | |
|            table->key_info->key_length);
 | |
| 
 | |
|   if (table->file->ha_index_read_idx_map(table->record[0], 0, user_key,
 | |
|                                          HA_WHOLE_KEY, HA_READ_KEY_EXACT))
 | |
|   {
 | |
|     if (revoke_grant)
 | |
|     {
 | |
|       if (combo->host.length)
 | |
|         my_error(ER_NONEXISTING_GRANT, MYF(0), combo->user.str,
 | |
|                  combo->host.str);
 | |
|       else
 | |
|         my_error(ER_INVALID_ROLE, MYF(0), combo->user.str);
 | |
|       goto end;
 | |
|     }
 | |
|     /*
 | |
|       There are four options which affect the process of creation of
 | |
|       a new user (mysqld option --safe-create-user, 'insert' privilege
 | |
|       on 'mysql.user' table, using 'GRANT' with 'IDENTIFIED BY' and
 | |
|       SQL_MODE flag NO_AUTO_CREATE_USER). Below is the simplified rule
 | |
|       how it should work.
 | |
|       if (safe-user-create && ! INSERT_priv) => reject
 | |
|       else if (identified_by) => create
 | |
|       else if (no_auto_create_user) => reject
 | |
|       else create
 | |
| 
 | |
|       see also test_if_create_new_users()
 | |
|     */
 | |
|     else if (!combo->has_auth() && no_auto_create)
 | |
|     {
 | |
|       my_error(ER_PASSWORD_NO_MATCH, MYF(0));
 | |
|       goto end;
 | |
|     }
 | |
|     else if (!can_create_user)
 | |
|     {
 | |
|       my_error(ER_CANT_CREATE_USER_WITH_GRANT, MYF(0));
 | |
|       goto end;
 | |
|     }
 | |
| 
 | |
|     if (!combo->auth)
 | |
|       combo->auth= &auth_no_password;
 | |
| 
 | |
|     old_row_exists = 0;
 | |
|     restore_record(table, s->default_values);
 | |
|     user_table.set_host(combo->host.str, combo->host.length);
 | |
|     user_table.set_user(combo->user.str, combo->user.length);
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     old_row_exists = 1;
 | |
|     store_record(table,record[1]);			// Save copy for update
 | |
|   }
 | |
| 
 | |
|   for (USER_AUTH *auth= combo->auth; auth; auth= auth->next)
 | |
|   {
 | |
|     nauth++;
 | |
|     if (auth->plugin.length)
 | |
|     {
 | |
|       if (!plugin_is_ready(&auth->plugin, MYSQL_AUTHENTICATION_PLUGIN))
 | |
|       {
 | |
|         my_error(ER_PLUGIN_IS_NOT_LOADED, MYF(0), auth->plugin.str);
 | |
|         goto end;
 | |
|       }
 | |
|     }
 | |
|     else
 | |
|       auth->plugin= guess_auth_plugin(thd, auth->auth_str.length);
 | |
|   }
 | |
| 
 | |
|   /* Update table columns with new privileges */
 | |
|   user_table.set_access(rights, revoke_grant);
 | |
|   rights= user_table.get_access();
 | |
| 
 | |
|   if (handle_as_role)
 | |
|   {
 | |
|     if (old_row_exists && !user_table.get_is_role())
 | |
|     {
 | |
|       goto end;
 | |
|     }
 | |
|     if (user_table.set_is_role(true))
 | |
|     {
 | |
|       my_error(ER_COL_COUNT_DOESNT_MATCH_PLEASE_UPDATE, MYF(0),
 | |
|                user_table.name().str,
 | |
|                ROLE_ASSIGN_COLUMN_IDX + 1, user_table.num_fields(),
 | |
|                static_cast<int>(table->s->mysql_version), MYSQL_VERSION_ID);
 | |
|       goto end;
 | |
|     }
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     old_acl_user= find_user_exact(combo->host, combo->user);
 | |
|     if ((old_acl_user != NULL) != old_row_exists)
 | |
|     {
 | |
|       my_error(ER_PASSWORD_NO_MATCH, MYF(0));
 | |
|       goto end;
 | |
|     }
 | |
|     new_acl_user= old_row_exists ? *old_acl_user :
 | |
|                   ACL_USER(thd, *combo, lex->account_options, rights);
 | |
|     if (acl_user_update(thd, &new_acl_user, nauth,
 | |
|                         *combo, lex->account_options, rights))
 | |
|       goto end;
 | |
| 
 | |
|     if (user_table.set_auth(new_acl_user))
 | |
|     {
 | |
|       my_error(ER_COL_COUNT_DOESNT_MATCH_PLEASE_UPDATE, MYF(0),
 | |
|                user_table.name().str, 3, user_table.num_fields(),
 | |
|                static_cast<int>(table->s->mysql_version), MYSQL_VERSION_ID);
 | |
|       DBUG_RETURN(1);
 | |
|     }
 | |
| 
 | |
|     switch (lex->account_options.ssl_type) {
 | |
|     case SSL_TYPE_NOT_SPECIFIED:
 | |
|       break;
 | |
|     case SSL_TYPE_NONE:
 | |
|     case SSL_TYPE_ANY:
 | |
|     case SSL_TYPE_X509:
 | |
|       user_table.set_ssl_type(lex->account_options.ssl_type);
 | |
|       user_table.set_ssl_cipher("", 0);
 | |
|       user_table.set_x509_issuer("", 0);
 | |
|       user_table.set_x509_subject("", 0);
 | |
|       break;
 | |
|     case SSL_TYPE_SPECIFIED:
 | |
|       user_table.set_ssl_type(lex->account_options.ssl_type);
 | |
|       if (lex->account_options.ssl_cipher.str)
 | |
|         user_table.set_ssl_cipher(lex->account_options.ssl_cipher.str,
 | |
|                                   lex->account_options.ssl_cipher.length);
 | |
|       else
 | |
|         user_table.set_ssl_cipher("", 0);
 | |
|       if (lex->account_options.x509_issuer.str)
 | |
|         user_table.set_x509_issuer(lex->account_options.x509_issuer.str,
 | |
|                                    lex->account_options.x509_issuer.length);
 | |
|       else
 | |
|         user_table.set_x509_issuer("", 0);
 | |
|       if (lex->account_options.x509_subject.str)
 | |
|         user_table.set_x509_subject(lex->account_options.x509_subject.str,
 | |
|                                     lex->account_options.x509_subject.length);
 | |
|       else
 | |
|         user_table.set_x509_subject("", 0);
 | |
|       break;
 | |
|     }
 | |
| 
 | |
|     if (lex->account_options.specified_limits & USER_RESOURCES::QUERIES_PER_HOUR)
 | |
|       user_table.set_max_questions(lex->account_options.questions);
 | |
|     if (lex->account_options.specified_limits & USER_RESOURCES::UPDATES_PER_HOUR)
 | |
|       user_table.set_max_updates(lex->account_options.updates);
 | |
|     if (lex->account_options.specified_limits & USER_RESOURCES::CONNECTIONS_PER_HOUR)
 | |
|       user_table.set_max_connections(lex->account_options.conn_per_hour);
 | |
|     if (lex->account_options.specified_limits & USER_RESOURCES::USER_CONNECTIONS)
 | |
|       user_table.set_max_user_connections(lex->account_options.user_conn);
 | |
|     if (lex->account_options.specified_limits & USER_RESOURCES::MAX_STATEMENT_TIME)
 | |
|       user_table.set_max_statement_time(lex->account_options.max_statement_time);
 | |
| 
 | |
|     mqh_used= (mqh_used || lex->account_options.questions || lex->account_options.updates ||
 | |
|                lex->account_options.conn_per_hour || lex->account_options.user_conn ||
 | |
|                lex->account_options.max_statement_time != 0.0);
 | |
| 
 | |
|     if (lex->account_options.account_locked != ACCOUNTLOCK_UNSPECIFIED)
 | |
|       user_table.set_account_locked(new_acl_user.account_locked);
 | |
| 
 | |
|     if (nauth)
 | |
|       user_table.set_password_last_changed(new_acl_user.password_last_changed);
 | |
|     if (lex->account_options.password_expire != PASSWORD_EXPIRE_UNSPECIFIED)
 | |
|     {
 | |
|       user_table.set_password_lifetime(new_acl_user.password_lifetime);
 | |
|       user_table.set_password_expired(new_acl_user.password_expired);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (old_row_exists)
 | |
|   {
 | |
|     /*
 | |
|       We should NEVER delete from the user table, as a uses can still
 | |
|       use mysqld even if he doesn't have any privileges in the user table!
 | |
|     */
 | |
|     if (cmp_record(table, record[1]))
 | |
|     {
 | |
|       if (unlikely(error= table->file->ha_update_row(table->record[1],
 | |
|                                                      table->record[0])) &&
 | |
|           error != HA_ERR_RECORD_IS_THE_SAME)
 | |
|       {                                         // This should never happen
 | |
|         table->file->print_error(error,MYF(0)); /* purecov: deadcode */
 | |
|         error= -1;                              /* purecov: deadcode */
 | |
|         goto end;                               /* purecov: deadcode */
 | |
|       }
 | |
|       else
 | |
|         error= 0;
 | |
|     }
 | |
|   }
 | |
|   else if (unlikely(error=table->file->ha_write_row(table->record[0])))
 | |
|   {
 | |
|     // This should never happen
 | |
|     if (table->file->is_fatal_error(error, HA_CHECK_DUP))
 | |
|     {
 | |
|       table->file->print_error(error,MYF(0));	/* purecov: deadcode */
 | |
|       error= -1;				/* purecov: deadcode */
 | |
|       goto end;					/* purecov: deadcode */
 | |
|     }
 | |
|   }
 | |
|   error=0;					// Privileges granted / revoked
 | |
| 
 | |
| end:
 | |
|   if (likely(!error))
 | |
|   {
 | |
|     acl_cache->clear(1);			// Clear privilege cache
 | |
|     if (handle_as_role)
 | |
|     {
 | |
|       if (old_row_exists)
 | |
|         acl_update_role(combo->user, rights);
 | |
|       else
 | |
|         acl_insert_role(combo->user, rights);
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       if (old_acl_user)
 | |
|         *old_acl_user= new_acl_user;
 | |
|       else
 | |
|       {
 | |
|         push_new_user(new_acl_user);
 | |
|         rebuild_acl_users();
 | |
| 
 | |
|         /* Rebuild 'acl_check_hosts' since 'acl_users' has been modified */
 | |
|         rebuild_check_host();
 | |
| 
 | |
|         /*
 | |
|           Rebuild every user's role_grants since 'acl_users' has been sorted
 | |
|           and old pointers to ACL_USER elements are no longer valid
 | |
|         */
 | |
|         rebuild_role_grants();
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   DBUG_RETURN(error);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   change grants in the mysql.db table
 | |
| */
 | |
| 
 | |
| static int replace_db_table(TABLE *table, const char *db,
 | |
| 			    const LEX_USER &combo,
 | |
| 			    privilege_t rights, const bool revoke_grant)
 | |
| {
 | |
|   uint i;
 | |
|   ulonglong priv;
 | |
|   privilege_t store_rights(NO_ACL);
 | |
|   bool old_row_exists=0;
 | |
|   int error;
 | |
|   char what= revoke_grant ? 'N' : 'Y';
 | |
|   uchar user_key[MAX_KEY_LENGTH];
 | |
|   DBUG_ENTER("replace_db_table");
 | |
| 
 | |
|   /* Check if there is such a user in user table in memory? */
 | |
|   if (!find_user_wild(combo.host, combo.user))
 | |
|   {
 | |
|     /* The user could be a role, check if the user is registered as a role */
 | |
|     if (!combo.host.length && !find_acl_role(combo.user, true))
 | |
|     {
 | |
|       my_message(ER_PASSWORD_NO_MATCH, ER_THD(table->in_use,
 | |
|                                               ER_PASSWORD_NO_MATCH), MYF(0));
 | |
|       DBUG_RETURN(-1);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   table->use_all_columns();
 | |
|   table->field[0]->store(combo.host.str,combo.host.length,
 | |
|                          system_charset_info);
 | |
|   table->field[1]->store(db,(uint) strlen(db), system_charset_info);
 | |
|   table->field[2]->store(combo.user.str,combo.user.length,
 | |
|                          system_charset_info);
 | |
|   key_copy(user_key, table->record[0], table->key_info,
 | |
|            table->key_info->key_length);
 | |
| 
 | |
|   if (table->file->ha_index_read_idx_map(table->record[0],0, user_key,
 | |
|                                          HA_WHOLE_KEY,
 | |
|                                          HA_READ_KEY_EXACT))
 | |
|   {
 | |
|     if (revoke_grant)
 | |
|     { // no row, no revoke
 | |
|       my_error(ER_NONEXISTING_GRANT, MYF(0), combo.user.str, combo.host.str);
 | |
|       goto abort;
 | |
|     }
 | |
|     old_row_exists = 0;
 | |
|     restore_record(table, s->default_values);
 | |
|     table->field[0]->store(combo.host.str,combo.host.length,
 | |
|                            system_charset_info);
 | |
|     table->field[1]->store(db,(uint) strlen(db), system_charset_info);
 | |
|     table->field[2]->store(combo.user.str,combo.user.length,
 | |
|                            system_charset_info);
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     old_row_exists = 1;
 | |
|     store_record(table,record[1]);
 | |
|   }
 | |
| 
 | |
|   store_rights=get_rights_for_db(rights);
 | |
|   for (i= 3, priv= 1; i < table->s->fields; i++, priv <<= 1)
 | |
|   {
 | |
|     if (priv & store_rights)			// do it if priv is chosen
 | |
|       table->field [i]->store(&what,1, &my_charset_latin1);// set requested privileges
 | |
|   }
 | |
|   rights=get_access(table,3);
 | |
|   rights=fix_rights_for_db(rights);
 | |
|   if (table->s->fields <= 23)
 | |
|     if ((rights | SHOW_CREATE_ROUTINE_ACL | GRANT_ACL) == DB_ACLS)
 | |
|       rights|= SHOW_CREATE_ROUTINE_ACL;
 | |
| 
 | |
|   if (old_row_exists)
 | |
|   {
 | |
|     /* update old existing row */
 | |
|     if (rights)
 | |
|     {
 | |
|       if (unlikely((error= table->file->ha_update_row(table->record[1],
 | |
|                                                       table->record[0]))) &&
 | |
|           error != HA_ERR_RECORD_IS_THE_SAME)
 | |
| 	goto table_error;			/* purecov: deadcode */
 | |
|     }
 | |
|     else	/* must have been a revoke of all privileges */
 | |
|     {
 | |
|       if (unlikely((error= table->file->ha_delete_row(table->record[1]))))
 | |
| 	goto table_error;			/* purecov: deadcode */
 | |
|     }
 | |
|   }
 | |
|   else if (rights &&
 | |
|            (unlikely(error= table->file->ha_write_row(table->record[0]))))
 | |
|   {
 | |
|     if (table->file->is_fatal_error(error, HA_CHECK_DUP_KEY))
 | |
|       goto table_error; /* purecov: deadcode */
 | |
|   }
 | |
| 
 | |
|   acl_cache->clear(1);				// Clear privilege cache
 | |
|   if (old_row_exists)
 | |
|     acl_update_db(combo.user.str,combo.host.str,db,rights);
 | |
|   else if (rights)
 | |
|   {
 | |
|     /*
 | |
|        If we did not have an already existing row, for users, we must always
 | |
|        insert an ACL_DB entry. For roles however, it is possible that one was
 | |
|        already created when DB privileges were propagated from other granted
 | |
|        roles onto the current role. For this case, first try to update the
 | |
|        existing entry, otherwise insert a new one.
 | |
|     */
 | |
|     if (!combo.is_role() ||
 | |
|         !acl_update_db(combo.user.str, combo.host.str, db, rights))
 | |
|     {
 | |
|       acl_insert_db(combo.user.str,combo.host.str,db,rights);
 | |
|     }
 | |
|   }
 | |
|   DBUG_RETURN(0);
 | |
| 
 | |
|   /* This could only happen if the grant tables got corrupted */
 | |
| table_error:
 | |
|   table->file->print_error(error,MYF(0));	/* purecov: deadcode */
 | |
| 
 | |
| abort:
 | |
|   DBUG_RETURN(-1);
 | |
| }
 | |
| 
 | |
| /**
 | |
|   Updates the mysql.roles_mapping table
 | |
| 
 | |
|   @param table          TABLE to update
 | |
|   @param user           user name of the grantee
 | |
|   @param host           host name of the grantee
 | |
|   @param role           role name to grant
 | |
|   @param with_admin     WITH ADMIN OPTION flag
 | |
|   @param existing       the entry in the acl_roles_mappings hash or NULL.
 | |
|                         it is never NULL if revoke_grant is true.
 | |
|                         it is NULL when a new pair is added, it's not NULL
 | |
|                         when an existing pair is updated.
 | |
|   @param revoke_grant   true for REVOKE, false for GRANT
 | |
| */
 | |
| static int
 | |
| replace_roles_mapping_table(TABLE *table, LEX_CSTRING *user, LEX_CSTRING *host,
 | |
|                             LEX_CSTRING *role, bool with_admin,
 | |
|                             ROLE_GRANT_PAIR *existing, bool revoke_grant)
 | |
| {
 | |
|   DBUG_ENTER("replace_roles_mapping_table");
 | |
| 
 | |
|   uchar row_key[MAX_KEY_LENGTH];
 | |
|   int error;
 | |
|   table->use_all_columns();
 | |
|   restore_record(table, s->default_values);
 | |
|   table->field[0]->store(host->str, host->length, system_charset_info);
 | |
|   table->field[1]->store(user->str, user->length, system_charset_info);
 | |
|   table->field[2]->store(role->str, role->length, system_charset_info);
 | |
| 
 | |
|   DBUG_ASSERT(!revoke_grant || existing);
 | |
| 
 | |
|   if (existing) // delete or update
 | |
|   {
 | |
|     key_copy(row_key, table->record[0], table->key_info,
 | |
|              table->key_info->key_length);
 | |
|     if (table->file->ha_index_read_idx_map(table->record[1], 0, row_key,
 | |
|                                            HA_WHOLE_KEY, HA_READ_KEY_EXACT))
 | |
|     {
 | |
|       /* No match */
 | |
|       DBUG_RETURN(1);
 | |
|     }
 | |
|     if (revoke_grant && !with_admin) 
 | |
|     {
 | |
|       if (unlikely((error= table->file->ha_delete_row(table->record[1]))))
 | |
|       {
 | |
|         DBUG_PRINT("info", ("error deleting row '%s' '%s' '%s'",
 | |
|                             host->str, user->str, role->str));
 | |
|         goto table_error;
 | |
|       }
 | |
|     }
 | |
|     else if (with_admin)
 | |
|     {
 | |
|       table->field[3]->store(!revoke_grant + 1);
 | |
| 
 | |
|       if (unlikely((error= table->file->ha_update_row(table->record[1],
 | |
|                                                       table->record[0]))))
 | |
|       {
 | |
|         DBUG_PRINT("info", ("error updating row '%s' '%s' '%s'",
 | |
|                             host->str, user->str, role->str));
 | |
|         goto table_error;
 | |
|       }
 | |
|     }
 | |
|     DBUG_RETURN(0);
 | |
|   }
 | |
| 
 | |
|   table->field[3]->store(with_admin + 1);
 | |
| 
 | |
|   if (unlikely((error= table->file->ha_write_row(table->record[0]))))
 | |
|   {
 | |
|     DBUG_PRINT("info", ("error inserting row '%s' '%s' '%s'",
 | |
|                         host->str, user->str, role->str));
 | |
|     goto table_error;
 | |
|   }
 | |
| 
 | |
|   /* all ok */
 | |
|   DBUG_RETURN(0);
 | |
| 
 | |
| table_error:
 | |
|   DBUG_PRINT("info", ("table error"));
 | |
|   table->file->print_error(error, MYF(0));
 | |
|   DBUG_RETURN(1);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Updates the acl_roles_mappings hash
 | |
| 
 | |
|   @param user           user name of the grantee
 | |
|   @param host           host name of the grantee
 | |
|   @param role           role name to grant
 | |
|   @param with_admin     WITH ADMIN OPTION flag
 | |
|   @param existing       the entry in the acl_roles_mappings hash or NULL.
 | |
|                         it is never NULL if revoke_grant is true.
 | |
|                         it is NULL when a new pair is added, it's not NULL
 | |
|                         when an existing pair is updated.
 | |
|   @param revoke_grant   true for REVOKE, false for GRANT
 | |
| */
 | |
| static int
 | |
| update_role_mapping(LEX_CSTRING *user, LEX_CSTRING *host, LEX_CSTRING *role,
 | |
|                     bool with_admin, ROLE_GRANT_PAIR *existing, bool revoke_grant)
 | |
| {
 | |
|   if (revoke_grant)
 | |
|   {
 | |
|     if (with_admin)
 | |
|     {
 | |
|       existing->with_admin= false;
 | |
|       return 0;
 | |
|     }
 | |
|     return my_hash_delete(&acl_roles_mappings, (uchar*)existing);
 | |
|   }
 | |
| 
 | |
|   if (existing)
 | |
|   {
 | |
|     existing->with_admin|= with_admin;
 | |
|     return 0;
 | |
|   }
 | |
| 
 | |
|   mysql_mutex_assert_owner(&acl_cache->lock);
 | |
|   /* allocate a new entry that will go in the hash */
 | |
|   ROLE_GRANT_PAIR *hash_entry= new (&acl_memroot) ROLE_GRANT_PAIR;
 | |
|   if (hash_entry->init(&acl_memroot, *user, *host, *role, with_admin))
 | |
|     return 1;
 | |
|   return my_hash_insert(&acl_roles_mappings, (uchar*) hash_entry);
 | |
| }
 | |
| 
 | |
| 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 (size_t 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", ("deleting 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");
 | |
| 
 | |
|   mysql_mutex_assert_owner(&acl_cache->lock);
 | |
|   if (!table)
 | |
|   {
 | |
|     my_error(ER_NO_SUCH_TABLE, MYF(0), MYSQL_SCHEMA_NAME.str,
 | |
|              MYSQL_TABLE_NAME[PROXIES_PRIV_TABLE].str);
 | |
|     DBUG_RETURN(-1);
 | |
|   }
 | |
| 
 | |
|   /* Check if there is such a user in user table in memory? */
 | |
|   if (!find_user_wild(user->host, user->user))
 | |
|   {
 | |
|     my_message(ER_PASSWORD_NO_MATCH,
 | |
|                ER_THD(thd, 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 (unlikely((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->ha_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 (unlikely(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 (unlikely((error= table->file->ha_delete_row(table->record[1]))))
 | |
| 	goto table_error;			/* purecov: inspected */
 | |
|     }
 | |
|   }
 | |
|   else if (unlikely((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(&acl_memroot, 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:
 | |
|   char *column;
 | |
|   privilege_t rights;
 | |
|   privilege_t init_rights;
 | |
|   uint key_length;
 | |
|   GRANT_COLUMN(String &c, privilege_t y) :rights (y), init_rights(y)
 | |
|   {
 | |
|     column= (char*) memdup_root(&grant_memroot,c.ptr(), key_length=c.length());
 | |
|   }
 | |
| 
 | |
|   /* this constructor assumes that source->column is allocated in grant_memroot */
 | |
|   GRANT_COLUMN(GRANT_COLUMN *source) : column(source->column),
 | |
|     rights (source->rights), init_rights(NO_ACL), key_length(source->key_length) { }
 | |
| };
 | |
| 
 | |
| 
 | |
| static const uchar *get_key_column(const void *buff_, size_t *length, my_bool)
 | |
| {
 | |
|   auto buff=
 | |
|       static_cast<const GRANT_COLUMN *>(buff_);
 | |
|   *length=buff->key_length;
 | |
|   return reinterpret_cast<const uchar *>(buff->column);
 | |
| }
 | |
| 
 | |
| class GRANT_NAME :public Sql_alloc
 | |
| {
 | |
| public:
 | |
|   acl_host_and_ip host;
 | |
|   char *db, *user, *tname, *hash_key;
 | |
|   privilege_t privs;
 | |
|   privilege_t init_privs; /* privileges found in physical table */
 | |
|   ulonglong sort;
 | |
|   size_t key_length;
 | |
|   GRANT_NAME(const char *h, const char *d,const char *u,
 | |
|              const char *t, privilege_t p, bool is_routine);
 | |
|   GRANT_NAME (TABLE *form, bool is_routine);
 | |
|   virtual ~GRANT_NAME() = default;
 | |
|   virtual bool ok() { return privs != NO_ACL; }
 | |
|   void set_user_details(const char *h, const char *d,
 | |
|                         const char *u, const char *t,
 | |
|                         bool is_routine);
 | |
| };
 | |
| 
 | |
| 
 | |
| static privilege_t get_access_value_from_val_int(Field *field)
 | |
| {
 | |
|   return privilege_t(ALL_KNOWN_ACL & (ulonglong) field->val_int());
 | |
| }
 | |
| 
 | |
| 
 | |
| class GRANT_TABLE :public GRANT_NAME
 | |
| {
 | |
| public:
 | |
|   privilege_t cols;
 | |
|   privilege_t init_cols; /* privileges found in physical table */
 | |
|   HASH hash_columns;
 | |
| 
 | |
|   GRANT_TABLE(const char *h, const char *d,const char *u,
 | |
|               const char *t, privilege_t p, privilege_t c);
 | |
|   GRANT_TABLE (TABLE *form, TABLE *col_privs);
 | |
|   ~GRANT_TABLE() override;
 | |
|   bool ok() override { return privs != NO_ACL || cols != NO_ACL; }
 | |
|   void init_hash()
 | |
|   {
 | |
|     my_hash_init2(key_memory_acl_memex, &hash_columns, 4,
 | |
|                   Lex_ident_column::charset_info(),
 | |
|                   0, 0, 0, get_key_column, 0, 0, 0);
 | |
|   }
 | |
| };
 | |
| 
 | |
| 
 | |
| privilege_t GRANT_INFO::all_privilege()
 | |
| {
 | |
|   return (grant_table_user ? grant_table_user->cols : NO_ACL) |
 | |
|          (grant_table_role ? grant_table_role->cols : NO_ACL) |
 | |
|          (grant_public ?  grant_public->cols : NO_ACL) |
 | |
|          privilege;
 | |
| }
 | |
| 
 | |
| 
 | |
| void GRANT_NAME::set_user_details(const char *h, const char *d,
 | |
|                                   const char *u, const char *t,
 | |
|                                   bool is_routine)
 | |
| {
 | |
|   /* Host given by user */
 | |
|   update_hostname(&host, strdup_root(&grant_memroot, h));
 | |
|   if (db != d)
 | |
|   {
 | |
|     DBUG_ASSERT(d);
 | |
|     db= lower_case_table_names ?
 | |
|         lex_string_casedn_root(&grant_memroot, files_charset_info,
 | |
|                                d, strlen(d)).str :
 | |
|         strdup_root(&grant_memroot, d);
 | |
|   }
 | |
|   user = strdup_root(&grant_memroot,u);
 | |
|   sort=  get_magic_sort("hdu", host.hostname, db, user);
 | |
|   if (tname != t)
 | |
|   {
 | |
|     DBUG_ASSERT(t);
 | |
|     tname= lower_case_table_names || is_routine ?
 | |
|            lex_string_casedn_root(&grant_memroot, files_charset_info,
 | |
|                                   t, strlen(t)).str :
 | |
|            strdup_root(&grant_memroot, t);
 | |
|   }
 | |
|   key_length= strlen(d) + strlen(u)+ strlen(t)+3;
 | |
|   hash_key=   (char*) alloc_root(&grant_memroot,key_length);
 | |
|   strmov(strmov(strmov(hash_key,user)+1,db)+1,tname);
 | |
| }
 | |
| 
 | |
| GRANT_NAME::GRANT_NAME(const char *h, const char *d,const char *u,
 | |
|                        const char *t, privilege_t p, bool is_routine)
 | |
|   :db(0), tname(0), privs(p), init_privs(p)
 | |
| {
 | |
|   set_user_details(h, d, u, t, is_routine);
 | |
| }
 | |
| 
 | |
| GRANT_TABLE::GRANT_TABLE(const char *h, const char *d,const char *u,
 | |
|                          const char *t, privilege_t p, privilege_t c)
 | |
|   :GRANT_NAME(h,d,u,t,p, FALSE), cols(c), init_cols(c)
 | |
| {
 | |
|   init_hash();
 | |
| }
 | |
| 
 | |
| /*
 | |
|   create a new GRANT_TABLE entry for role inheritance. init_* fields are set
 | |
|   to 0
 | |
| */
 | |
| GRANT_NAME::GRANT_NAME(TABLE *form, bool is_routine)
 | |
|  :privs(NO_ACL), init_privs(NO_ACL)
 | |
| {
 | |
|   user= safe_str(get_field(&grant_memroot,form->field[2]));
 | |
| 
 | |
|   const char *hostname= get_field(&grant_memroot, form->field[0]);
 | |
|   mysql_mutex_lock(&acl_cache->lock);
 | |
|   if (!hostname && find_acl_role(Lex_cstring_strlen(user), true))
 | |
|     hostname= "";
 | |
|   mysql_mutex_unlock(&acl_cache->lock);
 | |
|   update_hostname(&host, hostname);
 | |
| 
 | |
|   db=    get_field(&grant_memroot,form->field[1]);
 | |
|   tname= get_field(&grant_memroot,form->field[3]);
 | |
|   if (!db || !tname)
 | |
|   {
 | |
|     /* Wrong table row; Ignore it */
 | |
|     return;					/* purecov: inspected */
 | |
|   }
 | |
|   sort=  get_magic_sort("hdu", host.hostname, db, user);
 | |
|   if (lower_case_table_names)
 | |
|   {
 | |
|     DBUG_ASSERT(db);
 | |
|     db= lex_string_casedn_root(&grant_memroot, files_charset_info,
 | |
|                                db, strlen(db)).str;
 | |
|   }
 | |
|   if (lower_case_table_names || is_routine)
 | |
|   {
 | |
|     DBUG_ASSERT(tname);
 | |
|     tname= lex_string_casedn_root(&grant_memroot, files_charset_info,
 | |
|                                   tname, strlen(tname)).str;
 | |
|   }
 | |
|   key_length= (strlen(db) + strlen(user) + strlen(tname) + 3);
 | |
|   hash_key=   (char*) alloc_root(&grant_memroot, key_length);
 | |
|   strmov(strmov(strmov(hash_key,user)+1,db)+1,tname);
 | |
|   privs = get_access_value_from_val_int(form->field[6]);
 | |
|   privs = fix_rights_for_table(privs);
 | |
|   init_privs= privs;
 | |
| }
 | |
| 
 | |
| 
 | |
| GRANT_TABLE::GRANT_TABLE(TABLE *form, TABLE *col_privs)
 | |
|   :GRANT_NAME(form, FALSE), cols(NO_ACL), init_cols(NO_ACL)
 | |
| {
 | |
|   uchar key[MAX_KEY_LENGTH];
 | |
| 
 | |
|   if (!db || !tname)
 | |
|   {
 | |
|     /* Wrong table row; Ignore it */
 | |
|     my_hash_clear(&hash_columns);               /* allow for destruction */
 | |
|     cols= NO_ACL;
 | |
|     return;
 | |
|   }
 | |
|   cols= get_access_value_from_val_int(form->field[7]);
 | |
|   cols= fix_rights_for_column(cols);
 | |
|   /*
 | |
|     Initial columns privileges are the same as column privileges on creation.
 | |
|     In case of roles, the cols privilege bits can get inherited and thus
 | |
|     cause the cols field to change. The init_cols field is always the same
 | |
|     as the physical table entry
 | |
|   */
 | |
|   init_cols= cols;
 | |
| 
 | |
|   init_hash();
 | |
| 
 | |
|   if (cols)
 | |
|   {
 | |
|     uint key_prefix_len;
 | |
|     KEY_PART_INFO *key_part= col_privs->key_info->key_part;
 | |
|     col_privs->field[0]->store(host.hostname,
 | |
|                                (uint) safe_strlen(host.hostname),
 | |
|                                system_charset_info);
 | |
|     col_privs->field[1]->store(db,(uint) strlen(db), system_charset_info);
 | |
|     col_privs->field[2]->store(user,(uint) strlen(user), system_charset_info);
 | |
|     col_privs->field[3]->store(tname,(uint) strlen(tname), system_charset_info);
 | |
| 
 | |
|     key_prefix_len= (key_part[0].store_length +
 | |
|                      key_part[1].store_length +
 | |
|                      key_part[2].store_length +
 | |
|                      key_part[3].store_length);
 | |
|     key_copy(key, col_privs->record[0], col_privs->key_info, key_prefix_len);
 | |
|     col_privs->field[4]->store("",0, &my_charset_latin1);
 | |
| 
 | |
|     if (col_privs->file->ha_index_init(0, 1))
 | |
|     {
 | |
|       cols= NO_ACL;
 | |
|       init_cols= NO_ACL;
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     if (col_privs->file->ha_index_read_map(col_privs->record[0], (uchar*) key,
 | |
|                                            (key_part_map)15,
 | |
|                                            HA_READ_KEY_EXACT))
 | |
|     {
 | |
|       cols= NO_ACL; /* purecov: deadcode */
 | |
|       init_cols= NO_ACL;
 | |
|       col_privs->file->ha_index_end();
 | |
|       return;
 | |
|     }
 | |
|     do
 | |
|     {
 | |
|       String *res,column_name;
 | |
|       GRANT_COLUMN *mem_check;
 | |
|       /* As column name is a string, we don't have to supply a buffer */
 | |
|       res=col_privs->field[4]->val_str(&column_name);
 | |
|       privilege_t priv= get_access_value_from_val_int(col_privs->field[6]);
 | |
|       if (!(mem_check = new GRANT_COLUMN(*res,
 | |
|                                          fix_rights_for_column(priv))))
 | |
|       {
 | |
|         /* Don't use this entry */
 | |
|         privs= cols= init_privs= init_cols= NO_ACL;   /* purecov: deadcode */
 | |
|         return;				/* purecov: deadcode */
 | |
|       }
 | |
|       if (my_hash_insert(&hash_columns, (uchar *) mem_check))
 | |
|       {
 | |
|         /* Invalidate this entry */
 | |
|         privs= cols= init_privs= init_cols= NO_ACL;
 | |
|         return;
 | |
|       }
 | |
|     } while (!col_privs->file->ha_index_next(col_privs->record[0]) &&
 | |
|              !key_cmp_if_same(col_privs,key,0,key_prefix_len));
 | |
|     col_privs->file->ha_index_end();
 | |
|   }
 | |
| }
 | |
| 
 | |
| 
 | |
| GRANT_TABLE::~GRANT_TABLE()
 | |
| {
 | |
|   my_hash_free(&hash_columns);
 | |
| }
 | |
| 
 | |
| 
 | |
| static const uchar *get_grant_table(const void *buff_, size_t *length, my_bool)
 | |
| {
 | |
|   auto buff= static_cast<const GRANT_NAME *>(buff_);
 | |
|   *length=buff->key_length;
 | |
|   return reinterpret_cast<const uchar *>(buff->hash_key);
 | |
| }
 | |
| 
 | |
| 
 | |
| static void free_grant_table(void *grant_table_)
 | |
| {
 | |
|   GRANT_TABLE *grant_table= static_cast<GRANT_TABLE *>(grant_table_);
 | |
|   grant_table->~GRANT_TABLE();
 | |
| }
 | |
| 
 | |
| 
 | |
| /* Search after a matching grant. Prefer exact grants before not exact ones */
 | |
| 
 | |
| static GRANT_NAME *name_hash_search(HASH *name_hash,
 | |
|                                     const char *host,const char* ip,
 | |
|                                     const char *db,
 | |
|                                     const char *user, const char *tname,
 | |
|                                     bool exact, bool name_tolower)
 | |
| {
 | |
|   constexpr size_t key_data_size= SAFE_NAME_LEN * 2 + USERNAME_LENGTH + 1;
 | |
|   // See earlier comments on MY_CS_MBMAXLEN above
 | |
|   CharBuffer<key_data_size + MY_CS_MBMAXLEN> key;
 | |
|   GRANT_NAME *grant_name,*found=0;
 | |
|   HASH_SEARCH_STATE state;
 | |
| 
 | |
|   key.append(Lex_cstring_strlen(user)).append_char('\0')
 | |
|      .append(Lex_cstring_strlen(db)).append_char('\0')
 | |
|      .append_opt_casedn(files_charset_info, Lex_cstring_strlen(tname),
 | |
|                         name_tolower);
 | |
|   key.append_char('\0');
 | |
|   if (key.length() > key_data_size)
 | |
|     return 0; // invalid name = not found
 | |
| 
 | |
|   for (grant_name= (GRANT_NAME*) my_hash_first(name_hash, (uchar*) key.ptr(),
 | |
|                                                key.length(), &state);
 | |
|        grant_name ;
 | |
|        grant_name= (GRANT_NAME*) my_hash_next(name_hash, (uchar*) key.ptr(),
 | |
|                                               key.length(), &state))
 | |
|   {
 | |
|     if (exact)
 | |
|     {
 | |
|       if (!grant_name->host.hostname ||
 | |
|           (host &&
 | |
|            Lex_ident_host(Lex_cstring_strlen(host)).
 | |
|              streq(Lex_cstring_strlen(grant_name->host.hostname))) ||
 | |
| 	  (ip && !strcmp(ip, grant_name->host.hostname)))
 | |
| 	return grant_name;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       if (compare_hostname(&grant_name->host, host, ip) &&
 | |
|           (!found || found->sort < grant_name->sort))
 | |
| 	found=grant_name;					// Host ok
 | |
|     }
 | |
|   }
 | |
|   return found;
 | |
| }
 | |
| 
 | |
| 
 | |
| static GRANT_NAME *
 | |
| routine_hash_search(const char *host, const char *ip, const char *db,
 | |
|                     const char *user, const char *tname, const Sp_handler *sph,
 | |
|                     bool exact)
 | |
| {
 | |
|   return (GRANT_NAME*)
 | |
|     name_hash_search(sph->get_priv_hash(),
 | |
| 		     host, ip, db, user, tname, exact, TRUE);
 | |
| }
 | |
| 
 | |
| 
 | |
| static GRANT_TABLE *
 | |
| table_hash_search(const char *host, const char *ip, const char *db,
 | |
| 		  const char *user, const char *tname, bool exact)
 | |
| {
 | |
|   return (GRANT_TABLE*) name_hash_search(&column_priv_hash, host, ip, db,
 | |
| 					 user, tname, exact, (lower_case_table_names > 0));
 | |
| }
 | |
| 
 | |
| static bool column_priv_insert(GRANT_TABLE *grant)
 | |
| {
 | |
|   return my_hash_insert(&column_priv_hash,(uchar*) grant);
 | |
| }
 | |
| 
 | |
| static GRANT_COLUMN *
 | |
| column_hash_search(GRANT_TABLE *t, const LEX_CSTRING &cname)
 | |
| {
 | |
|   if (!my_hash_inited(&t->hash_columns))
 | |
|     return (GRANT_COLUMN*) 0;
 | |
|   return (GRANT_COLUMN*)my_hash_search(&t->hash_columns,
 | |
|                                        (uchar*)cname.str, cname.length);
 | |
| }
 | |
| 
 | |
| 
 | |
| static int replace_column_table(GRANT_TABLE *g_t,
 | |
| 				TABLE *table, const LEX_USER &combo,
 | |
| 				List <LEX_COLUMN> &columns,
 | |
| 				const char *db, const char *table_name,
 | |
| 				privilege_t rights, bool revoke_grant)
 | |
| {
 | |
|   int result=0;
 | |
|   uchar key[MAX_KEY_LENGTH];
 | |
|   uint key_prefix_length;
 | |
|   KEY_PART_INFO *key_part= table->key_info->key_part;
 | |
|   DBUG_ENTER("replace_column_table");
 | |
| 
 | |
|   table->use_all_columns();
 | |
|   table->field[0]->store(combo.host.str,combo.host.length,
 | |
|                          system_charset_info);
 | |
|   table->field[1]->store(db,(uint) strlen(db),
 | |
|                          system_charset_info);
 | |
|   table->field[2]->store(combo.user.str,combo.user.length,
 | |
|                          system_charset_info);
 | |
|   table->field[3]->store(table_name,(uint) strlen(table_name),
 | |
|                          system_charset_info);
 | |
| 
 | |
|   /* Get length of 4 first key parts */
 | |
|   key_prefix_length= (key_part[0].store_length + key_part[1].store_length +
 | |
|                       key_part[2].store_length + key_part[3].store_length);
 | |
|   key_copy(key, table->record[0], table->key_info, key_prefix_length);
 | |
| 
 | |
|   rights&= COL_ACLS;				// Only ACL for columns
 | |
| 
 | |
|   /* first fix privileges for all columns in column list */
 | |
| 
 | |
|   List_iterator <LEX_COLUMN> iter(columns);
 | |
|   class LEX_COLUMN *column;
 | |
| 
 | |
|   int error= table->file->ha_index_init(0, 1);
 | |
|   if (unlikely(error))
 | |
|   {
 | |
|     table->file->print_error(error, MYF(0));
 | |
|     DBUG_RETURN(-1);
 | |
|   }
 | |
| 
 | |
|   while ((column= iter++))
 | |
|   {
 | |
|     privilege_t privileges= column->rights;
 | |
|     bool old_row_exists=0;
 | |
|     uchar user_key[MAX_KEY_LENGTH];
 | |
| 
 | |
|     key_restore(table->record[0],key,table->key_info,
 | |
|                 key_prefix_length);
 | |
|     table->field[4]->store(column->column.ptr(), column->column.length(),
 | |
|                            system_charset_info);
 | |
|     /* Get key for the first 4 columns */
 | |
|     key_copy(user_key, table->record[0], table->key_info,
 | |
|              table->key_info->key_length);
 | |
| 
 | |
|     if (table->file->ha_index_read_map(table->record[0], user_key,
 | |
|                                        HA_WHOLE_KEY, HA_READ_KEY_EXACT))
 | |
|     {
 | |
|       if (revoke_grant)
 | |
|       {
 | |
| 	my_error(ER_NONEXISTING_TABLE_GRANT, MYF(0),
 | |
|                  combo.user.str, combo.host.str,
 | |
|                  table_name);                   /* purecov: inspected */
 | |
| 	result= -1;                             /* purecov: inspected */
 | |
| 	continue;                               /* purecov: inspected */
 | |
|       }
 | |
|       old_row_exists = 0;
 | |
|       restore_record(table, s->default_values);		// Get empty record
 | |
|       key_restore(table->record[0],key,table->key_info,
 | |
|                   key_prefix_length);
 | |
|       table->field[4]->store(column->column.ptr(),column->column.length(),
 | |
|                              system_charset_info);
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       privilege_t tmp= get_access_value_from_val_int(table->field[6]);
 | |
|       tmp=fix_rights_for_column(tmp);
 | |
| 
 | |
|       if (revoke_grant)
 | |
| 	privileges = tmp & ~(privileges | rights);
 | |
|       else
 | |
| 	privileges |= tmp;
 | |
|       old_row_exists = 1;
 | |
|       store_record(table,record[1]);			// copy original row
 | |
|     }
 | |
| 
 | |
|     table->field[6]->store((longlong) get_rights_for_column(privileges), TRUE);
 | |
| 
 | |
|     if (old_row_exists)
 | |
|     {
 | |
|       GRANT_COLUMN *grant_column;
 | |
|       if (privileges)
 | |
| 	error=table->file->ha_update_row(table->record[1],table->record[0]);
 | |
|       else
 | |
| 	error=table->file->ha_delete_row(table->record[1]);
 | |
|       if (unlikely(error) && error != HA_ERR_RECORD_IS_THE_SAME)
 | |
|       {
 | |
| 	table->file->print_error(error,MYF(0)); /* purecov: inspected */
 | |
| 	result= -1;				/* purecov: inspected */
 | |
| 	goto end;				/* purecov: inspected */
 | |
|       }
 | |
|       else
 | |
|         error= 0;
 | |
|       grant_column= column_hash_search(g_t, column->column.to_lex_cstring());
 | |
|       if (grant_column)  // Should always be true
 | |
|       {
 | |
|         grant_column->rights= privileges;	// Update hash
 | |
|         grant_column->init_rights= privileges;
 | |
|       }
 | |
|     }
 | |
|     else					// new grant
 | |
|     {
 | |
|       GRANT_COLUMN *grant_column;
 | |
|       if (unlikely((error=table->file->ha_write_row(table->record[0]))))
 | |
|       {
 | |
| 	table->file->print_error(error,MYF(0)); /* purecov: inspected */
 | |
| 	result= -1;				/* purecov: inspected */
 | |
| 	goto end;				/* purecov: inspected */
 | |
|       }
 | |
|       grant_column= new GRANT_COLUMN(column->column,privileges);
 | |
|       if (my_hash_insert(&g_t->hash_columns,(uchar*) grant_column))
 | |
|       {
 | |
|         result= -1;
 | |
|         goto end;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /*
 | |
|     If revoke of privileges on the table level, remove all such privileges
 | |
|     for all columns
 | |
|   */
 | |
| 
 | |
|   if (revoke_grant)
 | |
|   {
 | |
|     uchar user_key[MAX_KEY_LENGTH];
 | |
|     key_copy(user_key, table->record[0], table->key_info,
 | |
|              key_prefix_length);
 | |
| 
 | |
|     if (table->file->ha_index_read_map(table->record[0], user_key,
 | |
|                                        (key_part_map)15,
 | |
|                                        HA_READ_KEY_EXACT))
 | |
|       goto end;
 | |
| 
 | |
|     /* Scan through all rows with the same host,db,user and table */
 | |
|     do
 | |
|     {
 | |
|       privilege_t privileges = get_access_value_from_val_int(table->field[6]);
 | |
|       privileges=fix_rights_for_column(privileges);
 | |
|       store_record(table,record[1]);
 | |
| 
 | |
|       if (privileges & rights)	// is in this record the priv to be revoked ??
 | |
|       {
 | |
| 	GRANT_COLUMN *grant_column = NULL;
 | |
| 	char  colum_name_buf[HOSTNAME_LENGTH+1];
 | |
| 	String column_name(colum_name_buf,sizeof(colum_name_buf),
 | |
|                            system_charset_info);
 | |
| 
 | |
| 	privileges&= ~rights;
 | |
| 	table->field[6]->store((longlong)
 | |
| 	                       get_rights_for_column(privileges), TRUE);
 | |
| 	table->field[4]->val_str(&column_name);
 | |
| 	grant_column = column_hash_search(g_t, column_name.to_lex_cstring());
 | |
| 	if (privileges)
 | |
| 	{
 | |
| 	  int tmp_error;
 | |
| 	  if (unlikely(tmp_error=
 | |
|                        table->file->ha_update_row(table->record[1],
 | |
|                                                   table->record[0])) &&
 | |
|               tmp_error != HA_ERR_RECORD_IS_THE_SAME)
 | |
| 	  {					/* purecov: deadcode */
 | |
| 	    table->file->print_error(tmp_error,MYF(0)); /* purecov: deadcode */
 | |
| 	    result= -1;				/* purecov: deadcode */
 | |
| 	    goto end;				/* purecov: deadcode */
 | |
| 	  }
 | |
| 	  if (grant_column)
 | |
|           {
 | |
|             grant_column->rights  = privileges; // Update hash
 | |
|             grant_column->init_rights = privileges;
 | |
|           }
 | |
| 	}
 | |
| 	else
 | |
| 	{
 | |
| 	  int tmp_error;
 | |
| 	  if (unlikely((tmp_error=
 | |
|                         table->file->ha_delete_row(table->record[1]))))
 | |
| 	  {					/* purecov: deadcode */
 | |
| 	    table->file->print_error(tmp_error,MYF(0)); /* purecov: deadcode */
 | |
| 	    result= -1;				/* purecov: deadcode */
 | |
| 	    goto end;				/* purecov: deadcode */
 | |
| 	  }
 | |
| 	  if (grant_column)
 | |
| 	    my_hash_delete(&g_t->hash_columns,(uchar*) grant_column);
 | |
| 	}
 | |
|       }
 | |
|     } while (!table->file->ha_index_next(table->record[0]) &&
 | |
| 	     !key_cmp_if_same(table, key, 0, key_prefix_length));
 | |
|   }
 | |
| 
 | |
| end:
 | |
|   table->file->ha_index_end();
 | |
|   DBUG_RETURN(result);
 | |
| }
 | |
| 
 | |
| static inline void get_grantor(THD *thd, char *grantor)
 | |
| {
 | |
|   const char *user= thd->security_ctx->user;
 | |
|   const char *host= thd->security_ctx->host_or_ip;
 | |
| 
 | |
| #if defined(HAVE_REPLICATION)
 | |
|   if (thd->slave_thread && thd->has_invoker())
 | |
|   {
 | |
|     user= thd->get_invoker_user().str;
 | |
|     host= thd->get_invoker_host().str;
 | |
|   }
 | |
| #endif
 | |
|   strxmov(grantor, user, "@", host, NullS);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|    Revoke rights from a grant table entry.
 | |
| 
 | |
|    @return 0  ok
 | |
|    @return 1  fatal error (error given)
 | |
|    @return -1 grant table was revoked
 | |
| */
 | |
| 
 | |
| static int replace_table_table(THD *thd, GRANT_TABLE *grant_table,
 | |
| 			       TABLE *table, const LEX_USER &combo,
 | |
| 			       const char *db, const char *table_name,
 | |
| 			       privilege_t rights, privilege_t col_rights,
 | |
| 			       bool revoke_grant)
 | |
| {
 | |
|   char grantor[USER_HOST_BUFF_SIZE];
 | |
|   int old_row_exists = 1;
 | |
|   int error=0;
 | |
|   privilege_t store_table_rights(NO_ACL), store_col_rights(NO_ACL);
 | |
|   uchar user_key[MAX_KEY_LENGTH];
 | |
|   DBUG_ENTER("replace_table_table");
 | |
|   DBUG_PRINT("enter", ("User: '%s'  Host: '%s'  Revoke:'%d'",
 | |
|                         combo.user.str, combo.host.str, (int) revoke_grant));
 | |
| 
 | |
|   get_grantor(thd, grantor);
 | |
|   /*
 | |
|     The following should always succeed as new users are created before
 | |
|     this function is called!
 | |
|   */
 | |
|   if (!find_user_wild(combo.host, combo.user))
 | |
|   {
 | |
|     if (!combo.host.length && !find_acl_role(combo.user, true))
 | |
|     {
 | |
|       my_message(ER_PASSWORD_NO_MATCH, ER_THD(thd, ER_PASSWORD_NO_MATCH),
 | |
|                  MYF(0)); /* purecov: deadcode */
 | |
|       DBUG_RETURN(1);                            /* purecov: deadcode */
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   table->use_all_columns();
 | |
|   restore_record(table, s->default_values);     // Get empty record
 | |
|   table->field[0]->store(combo.host.str,combo.host.length,
 | |
|                          system_charset_info);
 | |
|   table->field[1]->store(db,(uint) strlen(db), system_charset_info);
 | |
|   table->field[2]->store(combo.user.str,combo.user.length,
 | |
|                          system_charset_info);
 | |
|   table->field[3]->store(table_name,(uint) strlen(table_name),
 | |
|                          system_charset_info);
 | |
|   store_record(table,record[1]);			// store at pos 1
 | |
|   key_copy(user_key, table->record[0], table->key_info,
 | |
|            table->key_info->key_length);
 | |
| 
 | |
|   if (table->file->ha_index_read_idx_map(table->record[0], 0, user_key,
 | |
|                                          HA_WHOLE_KEY,
 | |
|                                          HA_READ_KEY_EXACT))
 | |
|   {
 | |
|     /*
 | |
|       The following should never happen as we first check the in memory
 | |
|       grant tables for the user.  There is however always a small change that
 | |
|       the user has modified the grant tables directly.
 | |
|     */
 | |
|     if (revoke_grant)
 | |
|     { // no row, no revoke
 | |
|       my_error(ER_NONEXISTING_TABLE_GRANT, MYF(0),
 | |
|                combo.user.str, combo.host.str,
 | |
|                table_name);		        /* purecov: deadcode */
 | |
|       DBUG_RETURN(1);				/* purecov: deadcode */
 | |
|     }
 | |
|     old_row_exists = 0;
 | |
|     restore_record(table,record[1]);			// Get saved record
 | |
|   }
 | |
| 
 | |
|   store_table_rights= get_rights_for_table(rights);
 | |
|   store_col_rights=   get_rights_for_column(col_rights);
 | |
|   if (old_row_exists)
 | |
|   {
 | |
|     store_record(table,record[1]);
 | |
|     privilege_t j= get_access_value_from_val_int(table->field[6]);
 | |
|     privilege_t k= get_access_value_from_val_int(table->field[7]);
 | |
| 
 | |
|     if (revoke_grant)
 | |
|     {
 | |
|       /* column rights are already fixed in mysql_table_grant */
 | |
|       store_table_rights=j & ~store_table_rights;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       store_table_rights|= j;
 | |
|       store_col_rights|=   k;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   table->field[4]->store(grantor,(uint) strlen(grantor), system_charset_info);
 | |
|   table->field[6]->store((longlong) store_table_rights, TRUE);
 | |
|   table->field[7]->store((longlong) store_col_rights, TRUE);
 | |
|   rights=fix_rights_for_table(store_table_rights);
 | |
|   col_rights=fix_rights_for_column(store_col_rights);
 | |
| 
 | |
|   if (old_row_exists)
 | |
|   {
 | |
|     if (store_table_rights || store_col_rights)
 | |
|     {
 | |
|       if (unlikely(error=table->file->ha_update_row(table->record[1],
 | |
|                                                     table->record[0])) &&
 | |
|           error != HA_ERR_RECORD_IS_THE_SAME)
 | |
| 	goto table_error;			/* purecov: deadcode */
 | |
|     }
 | |
|     else if (unlikely((error = table->file->ha_delete_row(table->record[1]))))
 | |
|       goto table_error;				/* purecov: deadcode */
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     error=table->file->ha_write_row(table->record[0]);
 | |
|     if (unlikely(table->file->is_fatal_error(error, HA_CHECK_DUP_KEY)))
 | |
|       goto table_error;				/* purecov: deadcode */
 | |
|   }
 | |
| 
 | |
|   if (rights | col_rights)
 | |
|   {
 | |
|     grant_table->init_privs= rights;
 | |
|     grant_table->init_cols=  col_rights;
 | |
| 
 | |
|     grant_table->privs= rights;
 | |
|     grant_table->cols=	col_rights;
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     my_hash_delete(&column_priv_hash,(uchar*) grant_table);
 | |
|     DBUG_RETURN(-1);                            // Entry revoked
 | |
|   }
 | |
|   DBUG_RETURN(0);
 | |
| 
 | |
|   /* This should never happen */
 | |
| table_error:
 | |
|   table->file->print_error(error,MYF(0)); /* purecov: deadcode */
 | |
|   DBUG_RETURN(1); /* purecov: deadcode */
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   @retval       0  success
 | |
|   @retval      -1  error
 | |
| */
 | |
| static int replace_routine_table(THD *thd, GRANT_NAME *grant_name,
 | |
| 			      TABLE *table, const LEX_USER &combo,
 | |
| 			      const char *db, const char *routine_name,
 | |
| 			      const Sp_handler *sph,
 | |
| 			      privilege_t rights, bool revoke_grant)
 | |
| {
 | |
|   char grantor[USER_HOST_BUFF_SIZE];
 | |
|   int old_row_exists= 1;
 | |
|   int error=0;
 | |
|   HASH *hash= sph->get_priv_hash();
 | |
|   DBUG_ENTER("replace_routine_table");
 | |
| 
 | |
|   if (!table)
 | |
|   {
 | |
|     my_error(ER_NO_SUCH_TABLE, MYF(0), MYSQL_SCHEMA_NAME.str,
 | |
|              MYSQL_TABLE_NAME[PROCS_PRIV_TABLE].str);
 | |
|     DBUG_RETURN(-1);
 | |
|   }
 | |
| 
 | |
|   if (revoke_grant && !grant_name->init_privs) // only inherited role privs
 | |
|   {
 | |
|     my_hash_delete(hash, (uchar*) grant_name);
 | |
|     DBUG_RETURN(0);
 | |
|   }
 | |
| 
 | |
|   get_grantor(thd, grantor);
 | |
|   /*
 | |
|     New users are created before this function is called.
 | |
| 
 | |
|     There may be some cases where a routine's definer is removed but the
 | |
|     routine remains.
 | |
|   */
 | |
| 
 | |
|   table->use_all_columns();
 | |
|   restore_record(table, s->default_values);            // Get empty record
 | |
|   table->field[0]->store(combo.host.str,combo.host.length, &my_charset_latin1);
 | |
|   table->field[1]->store(db,(uint) strlen(db), &my_charset_latin1);
 | |
|   table->field[2]->store(combo.user.str,combo.user.length, &my_charset_latin1);
 | |
|   table->field[3]->store(routine_name,(uint) strlen(routine_name),
 | |
|                          &my_charset_latin1);
 | |
|   table->field[4]->store((longlong) sph->type(), true);
 | |
|   store_record(table,record[1]);			// store at pos 1
 | |
| 
 | |
|   if (table->file->ha_index_read_idx_map(table->record[0], 0,
 | |
|                                          (uchar*) table->field[0]->ptr,
 | |
|                                          HA_WHOLE_KEY,
 | |
|                                          HA_READ_KEY_EXACT))
 | |
|   {
 | |
|     /*
 | |
|       The following should never happen as we first check the in memory
 | |
|       grant tables for the user.  There is however always a small change that
 | |
|       the user has modified the grant tables directly.
 | |
| 
 | |
|       Also, there is also a second possibility that this routine entry
 | |
|       is created for a role by being inherited from a granted role.
 | |
|     */
 | |
|     if (revoke_grant)
 | |
|     { // no row, no revoke
 | |
|       my_error(ER_NONEXISTING_PROC_GRANT, MYF(0),
 | |
|                combo.user.str, combo.host.str, routine_name);
 | |
|       DBUG_RETURN(-1);
 | |
|     }
 | |
|     old_row_exists= 0;
 | |
|     restore_record(table,record[1]);			// Get saved record
 | |
|   }
 | |
| 
 | |
|   privilege_t store_proc_rights= get_rights_for_procedure(rights);
 | |
|   if (old_row_exists)
 | |
|   {
 | |
|     store_record(table,record[1]);
 | |
|     privilege_t j= get_access_value_from_val_int(table->field[6]);
 | |
| 
 | |
|     if (revoke_grant)
 | |
|     {
 | |
|       /* column rights are already fixed in mysql_table_grant */
 | |
|       store_proc_rights=j & ~store_proc_rights;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       store_proc_rights|= j;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   table->field[5]->store(grantor,(uint) strlen(grantor), &my_charset_latin1);
 | |
|   table->field[6]->store((longlong) store_proc_rights, TRUE);
 | |
|   rights=fix_rights_for_procedure(store_proc_rights);
 | |
| 
 | |
|   if (old_row_exists)
 | |
|   {
 | |
|     if (store_proc_rights)
 | |
|     {
 | |
|       if (unlikely(error=table->file->ha_update_row(table->record[1],
 | |
|                                                     table->record[0])) &&
 | |
|                    error != HA_ERR_RECORD_IS_THE_SAME)
 | |
| 	goto table_error;
 | |
|     }
 | |
|     else if (unlikely((error= table->file->ha_delete_row(table->record[1]))))
 | |
|       goto table_error;
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     error=table->file->ha_write_row(table->record[0]);
 | |
|     if (unlikely(table->file->is_fatal_error(error, HA_CHECK_DUP_KEY)))
 | |
|       goto table_error;
 | |
|   }
 | |
| 
 | |
|   if (rights)
 | |
|   {
 | |
|     grant_name->init_privs= rights;
 | |
|     grant_name->privs= rights;
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     my_hash_delete(hash, (uchar*) grant_name);
 | |
|   }
 | |
|   DBUG_RETURN(0);
 | |
| 
 | |
|   /* This should never happen */
 | |
| table_error:
 | |
|   table->file->print_error(error,MYF(0));
 | |
|   DBUG_RETURN(-1);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*****************************************************************
 | |
|   Role privilege propagation and graph traversal functionality
 | |
| 
 | |
|   According to the SQL standard, a role can be granted to a role,
 | |
|   thus role grants can create an arbitrarily complex directed acyclic
 | |
|   graph (a standard explicitly specifies that cycles are not allowed).
 | |
| 
 | |
|   When a privilege is granted to a role, it becomes available to all grantees.
 | |
|   The code below recursively traverses a DAG of role grants, propagating
 | |
|   privilege changes.
 | |
| 
 | |
|   The traversal function can work both ways, from roles to grantees or
 | |
|   from grantees to roles. The first is used for privilege propagation,
 | |
|   the second - for SHOW GRANTS and I_S.APPLICABLE_ROLES
 | |
| 
 | |
|   The role propagation code is smart enough to propagate only privilege
 | |
|   changes to one specific database, table, or routine, if only they
 | |
|   were changed (like in GRANT ... ON ... TO ...) or it can propagate
 | |
|   everything (on startup or after FLUSH PRIVILEGES).
 | |
| 
 | |
|   It traverses only a subgraph that's accessible from the modified role,
 | |
|   only visiting roles that can be possibly affected by the GRANT statement.
 | |
| 
 | |
|   Additionally, it stops traversal early, if this particular GRANT statement
 | |
|   didn't result in any changes of privileges (e.g. both role1 and role2
 | |
|   are granted to the role3, both role1 and role2 have SELECT privilege.
 | |
|   if SELECT is revoked from role1 it won't change role3 privileges,
 | |
|   so we won't traverse from role3 to its grantees).
 | |
| ******************************************************************/
 | |
| struct PRIVS_TO_MERGE
 | |
| {
 | |
|   enum what
 | |
|   {
 | |
|     ALL, GLOBAL, DB, TABLE_COLUMN, PROC, FUNC, PACKAGE_SPEC, PACKAGE_BODY
 | |
|   } what;
 | |
|   const char *db, *name;
 | |
| };
 | |
| 
 | |
| 
 | |
| static enum PRIVS_TO_MERGE::what sp_privs_to_merge(enum_sp_type type)
 | |
| {
 | |
|   switch (type) {
 | |
|   case SP_TYPE_FUNCTION:
 | |
|     return PRIVS_TO_MERGE::FUNC;
 | |
|   case SP_TYPE_PROCEDURE:
 | |
|     return PRIVS_TO_MERGE::PROC;
 | |
|   case SP_TYPE_PACKAGE:
 | |
|     return PRIVS_TO_MERGE::PACKAGE_SPEC;
 | |
|   case SP_TYPE_PACKAGE_BODY:
 | |
|     return PRIVS_TO_MERGE::PACKAGE_BODY;
 | |
|   case SP_TYPE_EVENT:
 | |
|   case SP_TYPE_TRIGGER:
 | |
|     break;
 | |
|   }
 | |
|   DBUG_ASSERT(0);
 | |
|   return PRIVS_TO_MERGE::PROC;
 | |
| }
 | |
| 
 | |
| 
 | |
| static int init_role_for_merging(ACL_USER_BASE *role_, void *context)
 | |
| {
 | |
|   ACL_ROLE *role= static_cast<ACL_ROLE *>(role_);
 | |
|   role->counter= 0;
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| static int count_subgraph_nodes(ACL_USER_BASE *, ACL_ROLE *grantee, void *context)
 | |
| {
 | |
|   grantee->counter++;
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| static int merge_role_privileges(ACL_USER_BASE *, ACL_ROLE *, void *);
 | |
| static bool merge_one_role_privileges(ACL_ROLE *grantee, PRIVS_TO_MERGE what);
 | |
| 
 | |
| /**
 | |
|   rebuild privileges of all affected roles
 | |
| 
 | |
|   entry point into role privilege propagation. after privileges of the
 | |
|   'role' were changed, this function rebuilds privileges of all affected roles
 | |
|   as necessary.
 | |
| */
 | |
| static void propagate_role_grants(ACL_ROLE *role,
 | |
|                                   enum PRIVS_TO_MERGE::what what,
 | |
|                                   const char *db= 0, const char *name= 0)
 | |
| {
 | |
|   if (!role)
 | |
|     return;
 | |
| 
 | |
|   mysql_mutex_assert_owner(&acl_cache->lock);
 | |
|   PRIVS_TO_MERGE data= { what, db, name };
 | |
| 
 | |
|   /*
 | |
|      Before updating grants to roles that inherit from this role, ensure that
 | |
|      the effective grants on this role are up-to-date from *its* granted roles.
 | |
|   */
 | |
|   merge_one_role_privileges(role, data);
 | |
|   /*
 | |
|      Changing privileges of a role causes all other roles that had
 | |
|      this role granted to them to have their rights invalidated.
 | |
| 
 | |
|      We need to rebuild all roles' related access bits.
 | |
| 
 | |
|      This cannot be a simple depth-first search, instead we have to merge
 | |
|      privileges for all roles granted to a specific grantee, *before*
 | |
|      merging privileges for this grantee. In other words, we must visit all
 | |
|      parent nodes of a specific node, before descending into this node.
 | |
| 
 | |
|      For example, if role1 is granted to role2 and role3, and role3 is
 | |
|      granted to role2, after "GRANT ... role1", we cannot merge privileges
 | |
|      for role2, until role3 is merged.  The counter will be 0 for role1, 2
 | |
|      for role2, 1 for role3. Traversal will start from role1, go to role2,
 | |
|      decrement the counter, backtrack, go to role3, merge it, go to role2
 | |
|      again, merge it.
 | |
| 
 | |
|      And the counter is not just "all parent nodes", but only parent nodes
 | |
|      that are part of the subgraph we're interested in. For example, if
 | |
|      both roleA and roleB are granted to roleC, then roleC has two parent
 | |
|      nodes. But when granting a privilege to roleA, we're only looking at a
 | |
|      subgraph that includes roleA and roleC (roleB cannot be possibly
 | |
|      affected by that grant statement). In this subgraph roleC has only one
 | |
|      parent.
 | |
| 
 | |
|      (on the other hand, in acl_load we want to update all roles, and
 | |
|      the counter is exactly equal to the number of all parent nodes)
 | |
| 
 | |
|      Thus, we do two graph traversals here. First we only count parents
 | |
|      that are part of the subgraph. On the second traversal we decrement
 | |
|      the counter and actually merge privileges for a node when a counter
 | |
|      drops to zero.
 | |
|   */
 | |
|   traverse_role_graph_up(role, &data, init_role_for_merging, count_subgraph_nodes);
 | |
|   traverse_role_graph_up(role, &data, NULL, merge_role_privileges);
 | |
| }
 | |
| 
 | |
| 
 | |
| // State of a node during a Depth First Search exploration
 | |
| struct NODE_STATE
 | |
| {
 | |
|   ACL_USER_BASE *node_data; /* pointer to the node data */
 | |
|   uint neigh_idx;           /* the neighbour that needs to be evaluated next */
 | |
| };
 | |
| 
 | |
| /**
 | |
|   Traverse the role grant graph and invoke callbacks at the specified points. 
 | |
|   
 | |
|   @param user           user or role to start traversal from
 | |
|   @param context        opaque parameter to pass to callbacks
 | |
|   @param offset         offset to ACL_ROLE::parent_grantee or to
 | |
|                         ACL_USER_BASE::role_grants. Depending on this value,
 | |
|                         traversal will go from roles to grantees or from
 | |
|                         grantees to roles.
 | |
|   @param on_node        called when a node is visited for the first time.
 | |
|                         Returning a value <0 will abort the traversal.
 | |
|   @param on_edge        called for every edge in the graph, when traversal
 | |
|                         goes from a node to a neighbour node.
 | |
|                         Returning <0 will abort the traversal. Returning >0
 | |
|                         will make the traversal not to follow this edge.
 | |
| 
 | |
|   @note
 | |
|   The traverse method is a DEPTH FIRST SEARCH, but callbacks can influence
 | |
|   that (on_edge returning >0 value).
 | |
| 
 | |
|   @note
 | |
|   This function should not be called directly, use
 | |
|   traverse_role_graph_up() and traverse_role_graph_down() instead.
 | |
| 
 | |
|   @retval 0                 traversal finished successfully
 | |
|   @retval ROLE_CYCLE_FOUND  traversal aborted, cycle detected
 | |
|   @retval <0                traversal was aborted, because a callback returned
 | |
|                             this error code
 | |
| */
 | |
| static int traverse_role_graph_impl(ACL_USER_BASE *user, void *context,
 | |
|        off_t offset,
 | |
|        int (*on_node) (ACL_USER_BASE *role, void *context),
 | |
|        int (*on_edge) (ACL_USER_BASE *current, ACL_ROLE *neighbour, void *context))
 | |
| {
 | |
|   DBUG_ENTER("traverse_role_graph_impl");
 | |
|   DBUG_ASSERT(user);
 | |
|   DBUG_PRINT("enter",("role: '%s'", user->user.str));
 | |
|   /*
 | |
|      The search operation should always leave the ROLE_ON_STACK and
 | |
|      ROLE_EXPLORED flags clean for all nodes involved in the search
 | |
|   */
 | |
|   DBUG_ASSERT(!(user->flags & ROLE_ON_STACK));
 | |
|   DBUG_ASSERT(!(user->flags & ROLE_EXPLORED));
 | |
|   mysql_mutex_assert_owner(&acl_cache->lock);
 | |
| 
 | |
|   /*
 | |
|      Stack used to simulate the recursive calls of DFS.
 | |
|      It uses a Dynamic_array to reduce the number of
 | |
|      malloc calls to a minimum
 | |
|   */
 | |
|   Dynamic_array<NODE_STATE> stack(PSI_INSTRUMENT_MEM, 20,50);
 | |
|   Dynamic_array<ACL_USER_BASE *> to_clear(PSI_INSTRUMENT_MEM, 20, 50);
 | |
|   NODE_STATE state;     /* variable used to insert elements in the stack */
 | |
|   int result= 0;
 | |
| 
 | |
|   state.neigh_idx= 0;
 | |
|   state.node_data= user;
 | |
|   user->flags|= ROLE_ON_STACK;
 | |
| 
 | |
|   stack.push(state);
 | |
|   to_clear.push(user);
 | |
| 
 | |
|   user->flags|= ROLE_OPENED;
 | |
|   if (on_node && ((result= on_node(user, context)) < 0))
 | |
|     goto end;
 | |
| 
 | |
|   while (stack.elements())
 | |
|   {
 | |
|     NODE_STATE *curr_state= stack.back();
 | |
| 
 | |
|     DBUG_ASSERT(curr_state->node_data->flags & ROLE_ON_STACK);
 | |
| 
 | |
|     ACL_USER_BASE *current= curr_state->node_data;
 | |
|     ACL_USER_BASE *neighbour= NULL;
 | |
|     DBUG_PRINT("info", ("Examining role %s", current->user.str));
 | |
|     /*
 | |
|       Iterate through the neighbours until a first valid jump-to
 | |
|       neighbour is found
 | |
|     */
 | |
|     bool found= FALSE;
 | |
|     uint i;
 | |
|     DYNAMIC_ARRAY *array= (DYNAMIC_ARRAY *)(((char*)current) + offset);
 | |
| 
 | |
|     DBUG_ASSERT(array == ¤t->role_grants || current->flags & IS_ROLE);
 | |
|     for (i= curr_state->neigh_idx; i < array->elements; i++)
 | |
|     {
 | |
|       neighbour= *(dynamic_element(array, i, ACL_ROLE**));
 | |
|       if (!(neighbour->flags & IS_ROLE))
 | |
|         continue;
 | |
| 
 | |
|       DBUG_PRINT("info", ("Examining neighbour role %s", neighbour->user.str));
 | |
| 
 | |
|       /* check if it forms a cycle */
 | |
|       if (neighbour->flags & ROLE_ON_STACK)
 | |
|       {
 | |
|         DBUG_PRINT("info", ("Found cycle"));
 | |
|         result= ROLE_CYCLE_FOUND;
 | |
|         goto end;
 | |
|       }
 | |
| 
 | |
|       if (!(neighbour->flags & ROLE_OPENED))
 | |
|       {
 | |
|         neighbour->flags|= ROLE_OPENED;
 | |
|         to_clear.push(neighbour);
 | |
|         if (on_node && ((result= on_node(neighbour, context)) < 0))
 | |
|           goto end;
 | |
|       }
 | |
| 
 | |
|       if (on_edge)
 | |
|       {
 | |
|         result= on_edge(current, (ACL_ROLE*)neighbour, context);
 | |
|         if (result < 0)
 | |
|           goto end;
 | |
|         if (result > 0)
 | |
|           continue;
 | |
|       }
 | |
| 
 | |
|       /* Check if it was already explored, in that case, move on */
 | |
|       if (neighbour->flags & ROLE_EXPLORED)
 | |
|         continue;
 | |
| 
 | |
|       found= TRUE;
 | |
|       break;
 | |
|     }
 | |
| 
 | |
|     /* found states that we have found a node to jump next into */
 | |
|     if (found)
 | |
|     {
 | |
|       curr_state->neigh_idx= i + 1;
 | |
| 
 | |
|       /* some sanity checks */
 | |
|       DBUG_ASSERT(!(neighbour->flags & ROLE_ON_STACK));
 | |
| 
 | |
|       /* add the neighbour on the stack */
 | |
|       neighbour->flags|= ROLE_ON_STACK;
 | |
|       state.neigh_idx= 0;
 | |
|       state.node_data= neighbour;
 | |
|       stack.push(state);
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       /* Make sure we got a correct node */
 | |
|       DBUG_ASSERT(curr_state->node_data->flags & ROLE_ON_STACK);
 | |
|       /* Finished with exploring the current node, pop it off the stack */
 | |
|       curr_state= &stack.pop();
 | |
|       curr_state->node_data->flags&= ~ROLE_ON_STACK; /* clear the on-stack bit */
 | |
|       curr_state->node_data->flags|= ROLE_EXPLORED;
 | |
|     }
 | |
|   }
 | |
| 
 | |
| end:
 | |
|   /* Cleanup */
 | |
|   for (size_t i= 0; i < to_clear.elements(); i++)
 | |
|   {
 | |
|     ACL_USER_BASE *current= to_clear.at(i);
 | |
|     DBUG_ASSERT(current->flags & (ROLE_EXPLORED | ROLE_ON_STACK | ROLE_OPENED));
 | |
|     current->flags&= ~(ROLE_EXPLORED | ROLE_ON_STACK | ROLE_OPENED);
 | |
|   }
 | |
|   DBUG_RETURN(result);
 | |
| }
 | |
| 
 | |
| /**
 | |
|   Traverse the role grant graph, going from a role to its grantees.
 | |
| 
 | |
|   This is used to propagate changes in privileges, for example,
 | |
|   when GRANT or REVOKE is issued for a role.
 | |
| */
 | |
| 
 | |
| static int traverse_role_graph_up(ACL_ROLE *role, void *context,
 | |
|        int (*on_node) (ACL_USER_BASE *role, void *context),
 | |
|        int (*on_edge) (ACL_USER_BASE *current, ACL_ROLE *neighbour, void *context))
 | |
| {
 | |
|   return traverse_role_graph_impl(
 | |
|       role, context, my_offsetof(ACL_ROLE, parent_grantee), on_node, on_edge);
 | |
| }
 | |
| 
 | |
| /**
 | |
|   Traverse the role grant graph, going from a user or a role to granted roles.
 | |
| 
 | |
|   This is used, for example, to print all grants available to a user or a role
 | |
|   (as in SHOW GRANTS).
 | |
| */
 | |
| 
 | |
| static int traverse_role_graph_down(ACL_USER_BASE *user, void *context,
 | |
|        int (*on_node) (ACL_USER_BASE *role, void *context),
 | |
|        int (*on_edge) (ACL_USER_BASE *current, ACL_ROLE *neighbour, void *context))
 | |
| {
 | |
|   return traverse_role_graph_impl(user, context,
 | |
|                              my_offsetof(ACL_USER_BASE, role_grants),
 | |
|                              on_node, on_edge);
 | |
| }
 | |
| 
 | |
| /*
 | |
|   To find all db/table/routine privilege for a specific role
 | |
|   we need to scan the array of privileges. It can be big.
 | |
|   But the set of privileges granted to a role in question (or
 | |
|   to roles directly granted to the role in question) is supposedly
 | |
|   much smaller.
 | |
| 
 | |
|   We put a role and all roles directly granted to it in a hash, and iterate
 | |
|   the (suposedly long) array of privileges, filtering out "interesting"
 | |
|   entries using the role hash. We put all these "interesting"
 | |
|   entries in a (suposedly small) dynamic array and them use it for merging.
 | |
| */
 | |
| static const uchar *role_key(const void *role_, size_t *klen, my_bool)
 | |
| {
 | |
|   auto role= static_cast<const ACL_ROLE *>(role_);
 | |
|   *klen= role->user.length;
 | |
|   return reinterpret_cast<const uchar *>(role->user.str);
 | |
| }
 | |
| typedef Hash_set<ACL_ROLE> role_hash_t;
 | |
| 
 | |
| static bool merge_role_global_privileges(ACL_ROLE *grantee)
 | |
| {
 | |
|   privilege_t old= grantee->access;
 | |
|   grantee->access= grantee->initial_role_access;
 | |
| 
 | |
|   DBUG_EXECUTE_IF("role_merge_stats", role_global_merges++;);
 | |
| 
 | |
|   for (size_t i= 0; i < grantee->role_grants.elements; i++)
 | |
|   {
 | |
|     ACL_ROLE *r= *dynamic_element(&grantee->role_grants, i, ACL_ROLE**);
 | |
|     grantee->access|= r->access;
 | |
|   }
 | |
|   return old != grantee->access;
 | |
| }
 | |
| 
 | |
| static int db_name_sort(const void *db1_, const void *db2_)
 | |
| {
 | |
|   auto db1= static_cast<const int *>(db1_);
 | |
|   auto db2= static_cast<const int *>(db2_);
 | |
|   return strcmp(acl_dbs.at(*db1).db, acl_dbs.at(*db2).db);
 | |
| }
 | |
| 
 | |
| /**
 | |
|   update ACL_DB for given database and a given role with merged privileges
 | |
| 
 | |
|   @param merged ACL_DB of the role in question (or -1 if it wasn't found)
 | |
|   @param first  first ACL_DB in an array for the database in question
 | |
|   @param access new privileges for the given role on the gived database
 | |
|   @param role   the name of the given role
 | |
| 
 | |
|   @return a bitmap of
 | |
|           1 - privileges were changed
 | |
|           2 - ACL_DB was added
 | |
|           4 - ACL_DB was deleted
 | |
| */
 | |
| static int update_role_db(int merged, int first, privilege_t access,
 | |
|                           const char *role)
 | |
| {
 | |
|   if (first < 0)
 | |
|     return 0;
 | |
| 
 | |
|   DBUG_EXECUTE_IF("role_merge_stats", role_db_merges++;);
 | |
| 
 | |
|   if (merged < 0)
 | |
|   {
 | |
|     /*
 | |
|       there's no ACL_DB for this role (all db grants come from granted roles)
 | |
|       we need to create it
 | |
| 
 | |
|       Note that we cannot use acl_insert_db() now:
 | |
|       1. it'll sort elements in the acl_dbs, so the pointers will become invalid
 | |
|       2. we may need many of them, no need to sort every time
 | |
|     */
 | |
|     DBUG_ASSERT(access);
 | |
|     ACL_DB acl_db;
 | |
|     acl_db.user= role;
 | |
|     acl_db.host.hostname= const_cast<char*>("");
 | |
|     acl_db.host.ip= acl_db.host.ip_mask= 0;
 | |
|     acl_db.db= acl_dbs.at(first).db;
 | |
|     acl_db.access= access;
 | |
|     acl_db.initial_access= NO_ACL;
 | |
|     acl_db.sort= get_magic_sort("hdu", "", acl_db.db, role);
 | |
|     acl_dbs.push(acl_db);
 | |
|     return 2;
 | |
|   }
 | |
|   else if (access == NO_ACL)
 | |
|   {
 | |
|     /*
 | |
|       there is ACL_DB but the role has no db privileges granted
 | |
|       (all privileges were coming from granted roles, and now those roles
 | |
|       were dropped or had their privileges revoked).
 | |
|       we need to remove this ACL_DB entry
 | |
| 
 | |
|       Note, that we cannot delete now:
 | |
|       1. it'll shift elements in the acl_dbs, so the pointers will become invalid
 | |
|       2. it's O(N) operation, and we may need many of them
 | |
|       so we only mark elements deleted and will delete later.
 | |
|     */
 | |
|     acl_dbs.at(merged).sort= 0; // lower than any valid ACL_DB sort value, will be sorted last
 | |
|     return 4;
 | |
|   }
 | |
|   else if (acl_dbs.at(merged).access != access)
 | |
|   {
 | |
|     /* this is easy */
 | |
|     acl_dbs.at(merged).access= access;
 | |
|     return 1;
 | |
|   }
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| /**
 | |
|   merges db privileges from roles granted to the role 'grantee'.
 | |
| 
 | |
|   @return true if database privileges of the 'grantee' were changed
 | |
| 
 | |
| */
 | |
| static bool merge_role_db_privileges(ACL_ROLE *grantee, const char *dbname,
 | |
|                                      role_hash_t *rhash)
 | |
| {
 | |
|   Dynamic_array<int> dbs(PSI_INSTRUMENT_MEM);
 | |
| 
 | |
|   /*
 | |
|     Supposedly acl_dbs can be huge, but only a handful of db grants
 | |
|     apply to grantee or roles directly granted to grantee.
 | |
| 
 | |
|     Collect these applicable db grants.
 | |
|   */
 | |
|   for (uint i=0 ; i < acl_dbs.elements() ; i++)
 | |
|   {
 | |
|     ACL_DB *db= &acl_dbs.at(i);
 | |
|     if (db->host.hostname[0])
 | |
|       continue;
 | |
|     if (dbname && strcmp(db->db, dbname))
 | |
|       continue;
 | |
|     ACL_ROLE *r= rhash->find(db->user, strlen(db->user));
 | |
|     if (!r)
 | |
|       continue;
 | |
|     dbs.append(i);
 | |
|   }
 | |
|   dbs.sort(db_name_sort);
 | |
| 
 | |
|   /*
 | |
|     Because dbs array is sorted by the db name, all grants for the same db
 | |
|     (that should be merged) are sorted together. The grantee's ACL_DB element
 | |
|     is not necessarily the first and may be not present at all.
 | |
|   */
 | |
|   int first= -1, merged= -1;
 | |
|   privilege_t access(NO_ACL);
 | |
|   ulong update_flags= 0;
 | |
|   for (int *p= dbs.front(); p <= dbs.back(); p++)
 | |
|   {
 | |
|     if (first<0 || (!dbname && strcmp(acl_dbs.at(p[0]).db, acl_dbs.at(p[-1]).db)))
 | |
|     { // new db name series
 | |
|       update_flags|= update_role_db(merged, first, access, grantee->user.str);
 | |
|       merged= -1;
 | |
|       access= NO_ACL;
 | |
|       first= *p;
 | |
|     }
 | |
|     if (strcmp(acl_dbs.at(*p).user, grantee->user.str) == 0)
 | |
|       access|= acl_dbs.at(merged= *p).initial_access;
 | |
|     else
 | |
|       access|= acl_dbs.at(*p).access;
 | |
|   }
 | |
|   update_flags|= update_role_db(merged, first, access, grantee->user.str);
 | |
| 
 | |
|   if (update_flags & 4)
 | |
|   {
 | |
|     // Remove elements marked for deletion.
 | |
|     size_t count= 0;
 | |
|     for(size_t i= 0; i < acl_dbs.elements(); i++)
 | |
|     {
 | |
|       ACL_DB *acl_db= &acl_dbs.at(i);
 | |
|       if (acl_db->sort)
 | |
|       {
 | |
|         if (i > count)
 | |
|           acl_dbs.set(count, *acl_db);
 | |
|         count++;
 | |
|       }
 | |
|     }
 | |
|     acl_dbs.elements(count);
 | |
|   }
 | |
| 
 | |
| 
 | |
|   if (update_flags & 2)
 | |
|   { // inserted, need to sort
 | |
|     rebuild_acl_dbs();
 | |
|   }
 | |
| 
 | |
|   return update_flags;
 | |
| }
 | |
| 
 | |
| static int table_name_sort(const void *tbl1_, const void *tbl2_)
 | |
| {
 | |
|   auto tbl1= static_cast<const GRANT_TABLE *const *>(tbl1_);
 | |
|   auto tbl2= static_cast<const GRANT_TABLE *const *>(tbl2_);
 | |
|   int res = strcmp((*tbl1)->db, (*tbl2)->db);
 | |
|   if (res) return res;
 | |
|   return strcmp((*tbl1)->tname, (*tbl2)->tname);
 | |
| }
 | |
| 
 | |
| /**
 | |
|   merges column privileges for the entry 'merged'
 | |
| 
 | |
|   @param merged GRANT_TABLE to merge the privileges into
 | |
|   @param cur    first entry in the array of GRANT_TABLE's for a given table
 | |
|   @param last   last entry in the array of GRANT_TABLE's for a given table,
 | |
|                 all entries between cur and last correspond to the *same* table
 | |
| 
 | |
|   @return 1 if the _set of columns_ in 'merged' was changed
 | |
|           (not if the _set of privileges_ was changed).
 | |
| */
 | |
| static int update_role_columns(GRANT_TABLE *merged,
 | |
|                                GRANT_TABLE **cur, GRANT_TABLE **last)
 | |
| {
 | |
|   privilege_t rights __attribute__((unused)) (NO_ACL);
 | |
|   int changed= 0;
 | |
|   if (!merged->cols)
 | |
|   {
 | |
|     changed= merged->hash_columns.records > 0;
 | |
|     my_hash_reset(&merged->hash_columns);
 | |
|     return changed;
 | |
|   }
 | |
| 
 | |
|   DBUG_EXECUTE_IF("role_merge_stats", role_column_merges++;);
 | |
| 
 | |
|   HASH *mh= &merged->hash_columns;
 | |
|   for (uint i=0 ; i < mh->records ; i++)
 | |
|   {
 | |
|     GRANT_COLUMN *col = (GRANT_COLUMN *)my_hash_element(mh, i);
 | |
|     col->rights= col->init_rights;
 | |
|   }
 | |
| 
 | |
|   for (; cur < last; cur++)
 | |
|   {
 | |
|     if (*cur == merged)
 | |
|       continue;
 | |
|     HASH *ch= &cur[0]->hash_columns;
 | |
|     for (uint i=0 ; i < ch->records ; i++)
 | |
|     {
 | |
|       GRANT_COLUMN *ccol = (GRANT_COLUMN *)my_hash_element(ch, i);
 | |
|       GRANT_COLUMN *mcol = (GRANT_COLUMN *)my_hash_search(mh,
 | |
|                                   (uchar *)ccol->column, ccol->key_length);
 | |
|       if (mcol)
 | |
|         mcol->rights|= ccol->rights;
 | |
|       else
 | |
|       {
 | |
|         changed= 1;
 | |
|         my_hash_insert(mh, (uchar*)new (&grant_memroot) GRANT_COLUMN(ccol));
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
| restart:
 | |
|   for (uint i=0 ; i < mh->records ; i++)
 | |
|   {
 | |
|     GRANT_COLUMN *col = (GRANT_COLUMN *)my_hash_element(mh, i);
 | |
|     rights|= col->rights;
 | |
|     if (!col->rights)
 | |
|     {
 | |
|       changed= 1;
 | |
|       my_hash_delete(mh, (uchar*)col);
 | |
|       goto restart;
 | |
|     }
 | |
|   }
 | |
|   DBUG_ASSERT(rights == merged->cols);
 | |
|   return changed;
 | |
| }
 | |
| 
 | |
| /**
 | |
|   update GRANT_TABLE for a given table and a given role with merged privileges
 | |
| 
 | |
|   @param merged GRANT_TABLE of the role in question (or NULL if it wasn't found)
 | |
|   @param first  first GRANT_TABLE in an array for the table in question
 | |
|   @param last   last entry in the array of GRANT_TABLE's for a given table,
 | |
|                 all entries between first and last correspond to the *same* table
 | |
|   @param privs  new table-level privileges for 'merged'
 | |
|   @param cols   new OR-ed column-level privileges for 'merged'
 | |
|   @param role   the name of the given role
 | |
| 
 | |
|   @return a bitmap of
 | |
|           1 - privileges were changed
 | |
|           2 - GRANT_TABLE was added
 | |
|           4 - GRANT_TABLE was deleted
 | |
| */
 | |
| static int update_role_table_columns(GRANT_TABLE *merged,
 | |
|                                      GRANT_TABLE **first, GRANT_TABLE **last,
 | |
|                                      privilege_t privs, privilege_t cols,
 | |
|                                      const char *role)
 | |
| {
 | |
|   if (!first)
 | |
|     return 0;
 | |
| 
 | |
|   DBUG_EXECUTE_IF("role_merge_stats", role_table_merges++;);
 | |
| 
 | |
|   if (merged == NULL)
 | |
|   {
 | |
|     /*
 | |
|       there's no GRANT_TABLE for this role (all table grants come from granted
 | |
|       roles) we need to create it
 | |
|     */
 | |
|     DBUG_ASSERT(privs | cols);
 | |
|     merged= new (&grant_memroot) GRANT_TABLE("", first[0]->db, role, first[0]->tname,
 | |
|                                      privs, cols);
 | |
|     merged->init_privs= merged->init_cols= NO_ACL;
 | |
|     update_role_columns(merged, first, last);
 | |
|     column_priv_insert(merged);
 | |
|     return 2;
 | |
|   }
 | |
|   else if ((privs | cols) == NO_ACL)
 | |
|   {
 | |
|     /*
 | |
|       there is GRANT_TABLE object but the role has no table or column
 | |
|       privileges granted (all privileges were coming from granted roles, and
 | |
|       now those roles were dropped or had their privileges revoked).
 | |
|       we need to remove this GRANT_TABLE
 | |
|     */
 | |
|     DBUG_EXECUTE_IF("role_merge_stats",
 | |
|                     role_column_merges+= MY_TEST(merged->cols););
 | |
|     my_hash_delete(&column_priv_hash,(uchar*) merged);
 | |
|     return 4;
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     bool changed= merged->cols != cols || merged->privs != privs;
 | |
|     merged->cols= cols;
 | |
|     merged->privs= privs;
 | |
|     if (update_role_columns(merged, first, last))
 | |
|       changed= true;
 | |
|     return changed;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /**
 | |
|   merges table privileges from roles granted to the role 'grantee'.
 | |
| 
 | |
|   @return true if table privileges of the 'grantee' were changed
 | |
| 
 | |
| */
 | |
| static bool merge_role_table_and_column_privileges(ACL_ROLE *grantee,
 | |
|                         const char *db, const char *tname, role_hash_t *rhash)
 | |
| {
 | |
|   Dynamic_array<GRANT_TABLE *> grants(PSI_INSTRUMENT_MEM);
 | |
|   DBUG_ASSERT(MY_TEST(db) == MY_TEST(tname)); // both must be set, or neither
 | |
| 
 | |
|   /*
 | |
|     first, collect table/column privileges granted to
 | |
|     roles in question.
 | |
|   */
 | |
|   for (uint i=0 ; i < column_priv_hash.records ; i++)
 | |
|   {
 | |
|     GRANT_TABLE *grant= (GRANT_TABLE *) my_hash_element(&column_priv_hash, i);
 | |
|     if (grant->host.hostname[0])
 | |
|       continue;
 | |
|     if (tname && (strcmp(grant->db, db) || strcmp(grant->tname, tname)))
 | |
|       continue;
 | |
|     ACL_ROLE *r= rhash->find(grant->user, strlen(grant->user));
 | |
|     if (!r)
 | |
|       continue;
 | |
|     grants.append(grant);
 | |
|   }
 | |
|   grants.sort(table_name_sort);
 | |
| 
 | |
|   GRANT_TABLE **first= NULL, *merged= NULL, **cur;
 | |
|   privilege_t privs(NO_ACL), cols(NO_ACL);
 | |
|   ulong update_flags= 0;
 | |
|   for (cur= grants.front(); cur <= grants.back(); cur++)
 | |
|   {
 | |
|     if (!first ||
 | |
|         (!tname && (strcmp(cur[0]->db, cur[-1]->db) ||
 | |
|                    strcmp(cur[0]->tname, cur[-1]->tname))))
 | |
|     { // new db.tname series
 | |
|       update_flags|= update_role_table_columns(merged, first, cur,
 | |
|                                                privs, cols, grantee->user.str);
 | |
|       merged= NULL;
 | |
|       privs= cols= NO_ACL;
 | |
|       first= cur;
 | |
|     }
 | |
|     if (strcmp(cur[0]->user, grantee->user.str) == 0)
 | |
|     {
 | |
|       merged= cur[0];
 | |
|       cols|= cur[0]->init_cols;
 | |
|       privs|= cur[0]->init_privs;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       cols|= cur[0]->cols;
 | |
|       privs|= cur[0]->privs;
 | |
|     }
 | |
|   }
 | |
|   update_flags|= update_role_table_columns(merged, first, cur,
 | |
|                                            privs, cols, grantee->user.str);
 | |
| 
 | |
|   return update_flags;
 | |
| }
 | |
| 
 | |
| static int routine_name_sort(const void *r1_,  const void *r2_)
 | |
| {
 | |
|   auto r1= static_cast<const GRANT_NAME *const *>(r1_);
 | |
|   auto r2= static_cast<const GRANT_NAME *const *>(r2_);
 | |
|   int res= strcmp((*r1)->db, (*r2)->db);
 | |
|   if (res) return res;
 | |
|   return strcmp((*r1)->tname, (*r2)->tname);
 | |
| }
 | |
| 
 | |
| /**
 | |
|   update GRANT_NAME for a given routine and a given role with merged privileges
 | |
| 
 | |
|   @param merged GRANT_NAME of the role in question (or NULL if it wasn't found)
 | |
|   @param first  first GRANT_NAME in an array for the routine in question
 | |
|   @param privs  new routine-level privileges for 'merged'
 | |
|   @param role   the name of the given role
 | |
|   @param hash   proc_priv_hash or func_priv_hash
 | |
| 
 | |
|   @return a bitmap of
 | |
|           1 - privileges were changed
 | |
|           2 - GRANT_NAME was added
 | |
|           4 - GRANT_NAME was deleted
 | |
| */
 | |
| static int update_role_routines(GRANT_NAME *merged, GRANT_NAME **first,
 | |
|                                 privilege_t privs, const char *role, HASH *hash)
 | |
| {
 | |
|   if (!first)
 | |
|     return 0;
 | |
| 
 | |
|   DBUG_EXECUTE_IF("role_merge_stats", role_routine_merges++;);
 | |
| 
 | |
|   if (merged == NULL)
 | |
|   {
 | |
|     /*
 | |
|       there's no GRANT_NAME for this role (all routine grants come from granted
 | |
|       roles) we need to create it
 | |
|     */
 | |
|     DBUG_ASSERT(privs);
 | |
|     merged= new (&grant_memroot) GRANT_NAME("", first[0]->db, role, first[0]->tname,
 | |
|                                     privs, true);
 | |
|     merged->init_privs= NO_ACL; // all privs are inherited
 | |
|     my_hash_insert(hash, (uchar *)merged);
 | |
|     return 2;
 | |
|   }
 | |
|   else if (privs == NO_ACL)
 | |
|   {
 | |
|     /*
 | |
|       there is GRANT_NAME but the role has no privileges granted
 | |
|       (all privileges were coming from granted roles, and now those roles
 | |
|       were dropped or had their privileges revoked).
 | |
|       we need to remove this entry
 | |
|     */
 | |
|     my_hash_delete(hash, (uchar*)merged);
 | |
|     return 4;
 | |
|   }
 | |
|   else if (merged->privs != privs)
 | |
|   {
 | |
|     /* this is easy */
 | |
|     merged->privs= privs;
 | |
|     return 1;
 | |
|   }
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| /**
 | |
|   merges routine privileges from roles granted to the role 'grantee'.
 | |
| 
 | |
|   @return true if routine privileges of the 'grantee' were changed
 | |
| 
 | |
| */
 | |
| static bool merge_role_routine_grant_privileges(ACL_ROLE *grantee,
 | |
|             const char *db, const char *tname, role_hash_t *rhash, HASH *hash)
 | |
| {
 | |
|   ulong update_flags= 0;
 | |
| 
 | |
|   DBUG_ASSERT(MY_TEST(db) == MY_TEST(tname)); // both must be set, or neither
 | |
| 
 | |
|   Dynamic_array<GRANT_NAME *> grants(PSI_INSTRUMENT_MEM);
 | |
| 
 | |
|   /* first, collect routine privileges granted to roles in question */
 | |
|   for (uint i=0 ; i < hash->records ; i++)
 | |
|   {
 | |
|     GRANT_NAME *grant= (GRANT_NAME *) my_hash_element(hash, i);
 | |
|     if (grant->host.hostname[0])
 | |
|       continue;
 | |
|     if (tname && (strcmp(grant->db, db) || strcmp(grant->tname, tname)))
 | |
|       continue;
 | |
|     ACL_ROLE *r= rhash->find(grant->user, strlen(grant->user));
 | |
|     if (!r)
 | |
|       continue;
 | |
|     grants.append(grant);
 | |
|   }
 | |
|   grants.sort(routine_name_sort);
 | |
| 
 | |
|   GRANT_NAME **first= NULL, *merged= NULL;
 | |
|   privilege_t privs(NO_ACL);
 | |
|   for (GRANT_NAME **cur= grants.front(); cur <= grants.back(); cur++)
 | |
|   {
 | |
|     if (!first ||
 | |
|         (!tname && (strcmp(cur[0]->db, cur[-1]->db) ||
 | |
|                     strcmp(cur[0]->tname, cur[-1]->tname))))
 | |
|     { // new db.tname series
 | |
|       update_flags|= update_role_routines(merged, first, privs,
 | |
|                                           grantee->user.str, hash);
 | |
|       merged= NULL;
 | |
|       privs= NO_ACL;
 | |
|       first= cur;
 | |
|     }
 | |
|     if (strcmp(cur[0]->user, grantee->user.str) == 0)
 | |
|     {
 | |
|       merged= cur[0];
 | |
|       privs|= cur[0]->init_privs;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       privs|= cur[0]->privs;
 | |
|     }
 | |
|   }
 | |
|   update_flags|= update_role_routines(merged, first, privs,
 | |
|                                       grantee->user.str, hash);
 | |
|   return update_flags;
 | |
| }
 | |
| 
 | |
| /**
 | |
|   update privileges of the 'grantee' from all roles, granted to it
 | |
| */
 | |
| static int merge_role_privileges(ACL_USER_BASE *,
 | |
|                                  ACL_ROLE *grantee, void *context)
 | |
| {
 | |
|   PRIVS_TO_MERGE *data= (PRIVS_TO_MERGE *)context;
 | |
| 
 | |
|   DBUG_ASSERT(grantee->counter > 0);
 | |
|   if (--grantee->counter)
 | |
|     return 1; // don't recurse into grantee just yet
 | |
| 
 | |
|   grantee->counter= 1; // Mark the grantee as merged.
 | |
| 
 | |
|   /* if we'll do db/table/routine privileges, create a hash of role names */
 | |
|   role_hash_t role_hash(PSI_INSTRUMENT_MEM, role_key);
 | |
|   if (data->what != PRIVS_TO_MERGE::GLOBAL)
 | |
|   {
 | |
|     role_hash.insert(grantee);
 | |
|     for (size_t i= 0; i < grantee->role_grants.elements; i++)
 | |
|       role_hash.insert(*dynamic_element(&grantee->role_grants, i, ACL_ROLE**));
 | |
|   }
 | |
| 
 | |
|   bool all= data->what == PRIVS_TO_MERGE::ALL;
 | |
|   bool changed= false;
 | |
|   if (all || data->what == PRIVS_TO_MERGE::GLOBAL)
 | |
|     changed|= merge_role_global_privileges(grantee);
 | |
|   if (all || data->what == PRIVS_TO_MERGE::DB)
 | |
|     changed|= merge_role_db_privileges(grantee, data->db, &role_hash);
 | |
|   if (all || data->what == PRIVS_TO_MERGE::TABLE_COLUMN)
 | |
|     changed|= merge_role_table_and_column_privileges(grantee,
 | |
|                                              data->db, data->name, &role_hash);
 | |
|   if (all || data->what == PRIVS_TO_MERGE::PROC)
 | |
|     changed|= merge_role_routine_grant_privileges(grantee,
 | |
|                             data->db, data->name, &role_hash, &proc_priv_hash);
 | |
|   if (all || data->what == PRIVS_TO_MERGE::FUNC)
 | |
|     changed|= merge_role_routine_grant_privileges(grantee,
 | |
|                             data->db, data->name, &role_hash, &func_priv_hash);
 | |
|   if (all || data->what == PRIVS_TO_MERGE::PACKAGE_SPEC)
 | |
|     changed|= merge_role_routine_grant_privileges(grantee,
 | |
|                             data->db, data->name, &role_hash,
 | |
|                             &package_spec_priv_hash);
 | |
|   if (all || data->what == PRIVS_TO_MERGE::PACKAGE_BODY)
 | |
|     changed|= merge_role_routine_grant_privileges(grantee,
 | |
|                             data->db, data->name, &role_hash,
 | |
|                             &package_body_priv_hash);
 | |
|   return !changed; // don't recurse into the subgraph if privs didn't change
 | |
| }
 | |
| 
 | |
| static
 | |
| bool merge_one_role_privileges(ACL_ROLE *grantee,
 | |
|                                PRIVS_TO_MERGE what)
 | |
| {
 | |
|   grantee->counter= 1;
 | |
|   return merge_role_privileges(0, grantee, &what);
 | |
| }
 | |
| 
 | |
| /*****************************************************************
 | |
|   End of the role privilege propagation and graph traversal code
 | |
| ******************************************************************/
 | |
| 
 | |
| static bool has_auth(LEX_USER *user, LEX *lex)
 | |
| {
 | |
|   return user->has_auth() ||
 | |
|          lex->account_options.ssl_type != SSL_TYPE_NOT_SPECIFIED ||
 | |
|          lex->account_options.ssl_cipher.str ||
 | |
|          lex->account_options.x509_issuer.str ||
 | |
|          lex->account_options.x509_subject.str ||
 | |
|          lex->account_options.specified_limits;
 | |
| }
 | |
| 
 | |
| static bool copy_and_check_auth(LEX_USER *to, LEX_USER *from, THD *thd)
 | |
| {
 | |
|   to->auth= from->auth;
 | |
| 
 | |
|   // if changing auth for an existing user
 | |
|   if (has_auth(to, thd->lex) && find_user_exact(to->host, to->user))
 | |
|   {
 | |
|     mysql_mutex_unlock(&acl_cache->lock);
 | |
|     bool res= check_alter_user(thd, to->host, to->user);
 | |
|     mysql_mutex_lock(&acl_cache->lock);
 | |
|     return res;
 | |
|   }
 | |
| 
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Store table level and column level grants in the privilege tables
 | |
| 
 | |
|   SYNOPSIS
 | |
|     mysql_table_grant()
 | |
|     thd			Thread handle
 | |
|     table_list		List of tables to give grant
 | |
|     user_list		List of users to give grant
 | |
|     columns		List of columns to give grant
 | |
|     rights		Table level grant
 | |
|     revoke_grant	Set to 1 if this is a REVOKE command
 | |
| 
 | |
|   RETURN
 | |
|     FALSE ok
 | |
|     TRUE  error
 | |
| */
 | |
| 
 | |
| int mysql_table_grant(THD *thd, TABLE_LIST *table_list,
 | |
| 		      List <LEX_USER> &user_list,
 | |
| 		      List <LEX_COLUMN> &columns, privilege_t rights,
 | |
| 		      bool revoke_grant)
 | |
| {
 | |
|   privilege_t column_priv(NO_ACL);
 | |
|   int result, res;
 | |
|   List_iterator <LEX_USER> str_list (user_list);
 | |
|   LEX_USER *Str, *tmp_Str;
 | |
|   bool create_new_users=0;
 | |
|   Lex_ident_db db_name;
 | |
|   Lex_ident_table table_name;
 | |
|   DBUG_ENTER("mysql_table_grant");
 | |
| 
 | |
|   if (rights & ~TABLE_ACLS)
 | |
|   {
 | |
|     my_message(ER_ILLEGAL_GRANT_FOR_TABLE,
 | |
|                ER_THD(thd, ER_ILLEGAL_GRANT_FOR_TABLE),
 | |
|                MYF(0));
 | |
|     DBUG_RETURN(TRUE);
 | |
|   }
 | |
| 
 | |
|   if (!revoke_grant)
 | |
|   {
 | |
|     if (columns.elements)
 | |
|     {
 | |
|       class LEX_COLUMN *column;
 | |
|       List_iterator <LEX_COLUMN> column_iter(columns);
 | |
| 
 | |
|       if (open_normal_and_derived_tables(thd, table_list, 0, DT_PREPARE))
 | |
|         DBUG_RETURN(TRUE);
 | |
| 
 | |
|       while ((column = column_iter++))
 | |
|       {
 | |
|         field_index_t unused_field_idx= NO_CACHED_FIELD_INDEX;
 | |
|         TABLE_LIST *dummy;
 | |
|         Field *f=find_field_in_table_ref(thd, table_list,
 | |
|                             Lex_ident_column(column->column.to_lex_cstring()),
 | |
|                                          column->column.ptr(), NULL, NULL,
 | |
|                                          ignored_tables_list_t(NULL), NULL,
 | |
|                                          TRUE, FALSE, &unused_field_idx, FALSE,
 | |
|                                          &dummy);
 | |
|         if (unlikely(f == (Field*)0))
 | |
|         {
 | |
|           my_error(ER_BAD_FIELD_ERROR, MYF(0),
 | |
|                    column->column.c_ptr(), table_list->alias.str);
 | |
|           DBUG_RETURN(TRUE);
 | |
|         }
 | |
|         if (unlikely(f == (Field *)-1))
 | |
|           DBUG_RETURN(TRUE);
 | |
|         column_priv|= column->rights;
 | |
|       }
 | |
|       close_mysql_tables(thd);
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       if (!(rights & CREATE_ACL))
 | |
|       {
 | |
|         if (!ha_table_exists(thd, &table_list->db, &table_list->table_name))
 | |
|         {
 | |
|           my_error(ER_NO_SUCH_TABLE, MYF(0), table_list->db.str,
 | |
|                    table_list->alias.str);
 | |
|           DBUG_RETURN(TRUE);
 | |
|         }
 | |
|       }
 | |
|       if (table_list->grant.want_privilege)
 | |
|       {
 | |
|         char command[128];
 | |
|         get_privilege_desc(command, sizeof(command),
 | |
|                            table_list->grant.want_privilege);
 | |
|         my_error(ER_TABLEACCESS_DENIED_ERROR, MYF(0),
 | |
|                  command, thd->security_ctx->priv_user,
 | |
|                  thd->security_ctx->host_or_ip, table_list->db.str,
 | |
|                  table_list->alias.str);
 | |
|         DBUG_RETURN(-1);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /*
 | |
|     Open the mysql.user and mysql.tables_priv tables.
 | |
|     Don't open column table if we don't need it !
 | |
|   */
 | |
|   int tables_to_open= Table_user | Table_tables_priv;
 | |
|   if (column_priv ||
 | |
|       (revoke_grant && ((rights & COL_ACLS) || columns.elements)))
 | |
|     tables_to_open|= Table_columns_priv;
 | |
| 
 | |
|   /*
 | |
|     The lock api is depending on the thd->lex variable which needs to be
 | |
|     re-initialized.
 | |
|   */
 | |
|   Query_tables_list backup;
 | |
|   thd->lex->reset_n_backup_query_tables_list(&backup);
 | |
|   /*
 | |
|     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;
 | |
| 
 | |
|   Grant_tables tables;
 | |
|   if ((result= tables.open_and_lock(thd, tables_to_open, TL_WRITE)))
 | |
|   {
 | |
|     thd->lex->restore_backup_query_tables_list(&backup);
 | |
|     DBUG_RETURN(result != 1);
 | |
|   }
 | |
| 
 | |
|   if (!revoke_grant)
 | |
|     create_new_users= test_if_create_new_users(thd);
 | |
|   mysql_rwlock_wrlock(&LOCK_grant);
 | |
|   mysql_mutex_lock(&acl_cache->lock);
 | |
|   MEM_ROOT *old_root= thd->mem_root;
 | |
|   thd->mem_root= &grant_memroot;
 | |
|   grant_version++;
 | |
| 
 | |
|   while ((tmp_Str = str_list++))
 | |
|   {
 | |
|     int error;
 | |
|     GRANT_TABLE *grant_table;
 | |
|     if (!(Str= get_current_user(thd, tmp_Str, false)))
 | |
|     {
 | |
|       result= TRUE;
 | |
|       continue;
 | |
|     }
 | |
|     /* Create user if needed */
 | |
|     error= copy_and_check_auth(Str, Str, thd) ||
 | |
|            replace_user_table(thd, tables.user_table(), Str,
 | |
|                                NO_ACL, revoke_grant, create_new_users,
 | |
|                                MY_TEST(!is_public(Str) &&
 | |
|                                        (thd->variables.sql_mode &
 | |
|                                         MODE_NO_AUTO_CREATE_USER)));
 | |
|     if (unlikely(error))
 | |
|     {
 | |
|       result= TRUE;				// Remember error
 | |
|       continue;					// Add next user
 | |
|     }
 | |
| 
 | |
|     db_name= table_list->get_db_name();
 | |
|     table_name= table_list->get_table_name();
 | |
| 
 | |
|     /* Find/create cached table grant */
 | |
|     grant_table= table_hash_search(Str->host.str, NullS, db_name.str,
 | |
|                                    Str->user.str, table_name.str, 1);
 | |
|     if (!grant_table)
 | |
|     {
 | |
|       if (revoke_grant)
 | |
|       {
 | |
|         my_error(ER_NONEXISTING_TABLE_GRANT, MYF(0),
 | |
|                  Str->user.str, Str->host.str, table_list->table_name.str);
 | |
|         result= TRUE;
 | |
|         continue;
 | |
|       }
 | |
|       grant_table= new (&grant_memroot) GRANT_TABLE(Str->host.str,
 | |
|                                                     db_name.str,
 | |
|                                                     Str->user.str,
 | |
|                                                     table_name.str,
 | |
|                                                     rights,
 | |
|                                                     column_priv);
 | |
|       if (!grant_table ||
 | |
|           column_priv_insert(grant_table))
 | |
|       {
 | |
|         result= TRUE;				/* purecov: deadcode */
 | |
|         continue;				/* purecov: deadcode */
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     /* If revoke_grant, calculate the new column privilege for tables_priv */
 | |
|     if (revoke_grant)
 | |
|     {
 | |
|       class LEX_COLUMN *column;
 | |
|       List_iterator <LEX_COLUMN> column_iter(columns);
 | |
|       GRANT_COLUMN *grant_column;
 | |
| 
 | |
|       /* Fix old grants */
 | |
|       while ((column = column_iter++))
 | |
|       {
 | |
|         grant_column = column_hash_search(grant_table,
 | |
|                                           column->column.to_lex_cstring());
 | |
|         if (grant_column)
 | |
|         {
 | |
|           grant_column->init_rights&= ~(column->rights | rights);
 | |
|           // If this is a role, rights will need to be reconstructed.
 | |
|           grant_column->rights= grant_column->init_rights;
 | |
|         }
 | |
|       }
 | |
|       /* scan trough all columns to get new column grant */
 | |
|       column_priv= NO_ACL;
 | |
|       for (uint idx=0 ; idx < grant_table->hash_columns.records ; idx++)
 | |
|       {
 | |
|         grant_column= (GRANT_COLUMN*)
 | |
|           my_hash_element(&grant_table->hash_columns, idx);
 | |
|         grant_column->init_rights&= ~rights;  // Fix other columns
 | |
|         grant_column->rights= grant_column->init_rights;
 | |
|         column_priv|= grant_column->init_rights;
 | |
|       }
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       column_priv|= grant_table->init_cols;
 | |
|     }
 | |
| 
 | |
| 
 | |
|     /* update table and columns */
 | |
| 
 | |
|     /* TODO(cvicentiu) refactor replace_table_table to use Tables_priv_table
 | |
|        instead of TABLE directly. */
 | |
|     if (tables.columns_priv_table().table_exists())
 | |
|     {
 | |
|       /* TODO(cvicentiu) refactor replace_column_table to use Columns_priv_table
 | |
|          instead of TABLE directly. */
 | |
|       if (replace_column_table(grant_table, tables.columns_priv_table().table(),
 | |
|                                *Str, columns,
 | |
|                                db_name.str, table_name.str, rights,
 | |
|                                revoke_grant))
 | |
| 	result= TRUE;
 | |
|     }
 | |
|     if ((res= replace_table_table(thd, grant_table,
 | |
|                                   tables.tables_priv_table().table(),
 | |
|                                   *Str, db_name.str, table_name.str,
 | |
|                                   rights, column_priv, revoke_grant)))
 | |
|     {
 | |
|       if (res > 0)
 | |
|       {
 | |
|         /* Should only happen if table is crashed */
 | |
|         result= TRUE;			       /* purecov: deadcode */
 | |
|       }
 | |
|     }
 | |
|     if (Str->is_role())
 | |
|       propagate_role_grants(find_acl_role(Str->user, true),
 | |
|                             PRIVS_TO_MERGE::TABLE_COLUMN,
 | |
|                             db_name.str, table_name.str);
 | |
|   }
 | |
| 
 | |
|   thd->mem_root= old_root;
 | |
|   mysql_mutex_unlock(&acl_cache->lock);
 | |
| 
 | |
|   if (!result) /* success */
 | |
|     result= write_bin_log(thd, TRUE, thd->query(), thd->query_length());
 | |
| 
 | |
|   mysql_rwlock_unlock(&LOCK_grant);
 | |
| 
 | |
|   if (!result) /* success */
 | |
|     my_ok(thd);
 | |
| 
 | |
|   thd->lex->restore_backup_query_tables_list(&backup);
 | |
|   DBUG_RETURN(result);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Store routine level grants in the privilege tables
 | |
| 
 | |
|   @param thd Thread handle
 | |
|   @param table_list List of routines to give grant
 | |
|   @param sph SP handler
 | |
|   @param user_list List of users to give grant
 | |
|   @param rights Table level grant
 | |
|   @param revoke_grant Is this is a REVOKE command?
 | |
| 
 | |
|   @return
 | |
|     @retval FALSE Success.
 | |
|     @retval TRUE An error occurred.
 | |
| */
 | |
| 
 | |
| bool mysql_routine_grant(THD *thd, TABLE_LIST *table_list,
 | |
|                          const Sp_handler *sph,
 | |
| 			 List <LEX_USER> &user_list, privilege_t rights,
 | |
| 			 bool revoke_grant, bool write_to_binlog)
 | |
| {
 | |
|   List_iterator <LEX_USER> str_list (user_list);
 | |
|   LEX_USER *Str, *tmp_Str;
 | |
|   bool create_new_users= 0;
 | |
|   int result;
 | |
|   const char *db_name, *table_name;
 | |
|   DBUG_ENTER("mysql_routine_grant");
 | |
| 
 | |
|   if (rights & ~PROC_ACLS)
 | |
|   {
 | |
|     my_message(ER_ILLEGAL_GRANT_FOR_TABLE,
 | |
|                ER_THD(thd, ER_ILLEGAL_GRANT_FOR_TABLE),
 | |
|                MYF(0));
 | |
|     DBUG_RETURN(TRUE);
 | |
|   }
 | |
| 
 | |
|   if (!revoke_grant)
 | |
|   {
 | |
|     if (sph->sp_exist_routines(thd, table_list))
 | |
|       DBUG_RETURN(TRUE);
 | |
|   }
 | |
| 
 | |
|   Grant_tables tables;
 | |
|   if ((result= tables.open_and_lock(thd, Table_user | Table_procs_priv, TL_WRITE)))
 | |
|     DBUG_RETURN(result != 1);
 | |
| 
 | |
|   DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row());
 | |
| 
 | |
|   if (!revoke_grant)
 | |
|     create_new_users= test_if_create_new_users(thd);
 | |
|   mysql_rwlock_wrlock(&LOCK_grant);
 | |
|   mysql_mutex_lock(&acl_cache->lock);
 | |
|   MEM_ROOT *old_root= thd->mem_root;
 | |
|   thd->mem_root= &grant_memroot;
 | |
| 
 | |
|   DBUG_PRINT("info",("now time to iterate and add users"));
 | |
| 
 | |
|   while ((tmp_Str= str_list++))
 | |
|   {
 | |
|     GRANT_NAME *grant_name;
 | |
|     if (!(Str= get_current_user(thd, tmp_Str, false)))
 | |
|     {
 | |
|       result= TRUE;
 | |
|       continue;
 | |
|     }
 | |
|     /* Create user if needed */
 | |
|     if (copy_and_check_auth(Str, tmp_Str, thd) ||
 | |
|         replace_user_table(thd, tables.user_table(), Str,
 | |
| 			   NO_ACL, revoke_grant, create_new_users,
 | |
|                            !is_public(Str) && (thd->variables.sql_mode &
 | |
|                                      MODE_NO_AUTO_CREATE_USER)))
 | |
|     {
 | |
|       result= TRUE;
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     db_name= table_list->db.str;
 | |
|     table_name= table_list->table_name.str;
 | |
|     grant_name= routine_hash_search(Str->host.str, NullS, db_name,
 | |
|                                     Str->user.str, table_name, sph, 1);
 | |
|     if (revoke_grant && (!grant_name || !grant_name->init_privs))
 | |
|     {
 | |
|       my_error(ER_NONEXISTING_PROC_GRANT, MYF(0),
 | |
|                Str->user.str, Str->host.str, table_name);
 | |
|       result= TRUE;
 | |
|       continue;
 | |
|     }
 | |
|     if (!grant_name)
 | |
|     {
 | |
|       DBUG_ASSERT(!revoke_grant);
 | |
|       grant_name= new GRANT_NAME(Str->host.str, db_name,
 | |
|                                  Str->user.str, table_name,
 | |
|                                  rights, TRUE);
 | |
|       if (!grant_name ||
 | |
|           my_hash_insert(sph->get_priv_hash(), (uchar*) grant_name))
 | |
|       {
 | |
|         result= TRUE;
 | |
|         continue;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (replace_routine_table(thd, grant_name, tables.procs_priv_table().table(),
 | |
|           *Str, db_name, table_name, sph, rights, revoke_grant) != 0)
 | |
|     {
 | |
|       result= TRUE;
 | |
|       continue;
 | |
|     }
 | |
|     if (Str->is_role())
 | |
|       propagate_role_grants(find_acl_role(Str->user, true),
 | |
|                             sp_privs_to_merge(sph->type()),
 | |
|                             db_name, table_name);
 | |
|   }
 | |
|   thd->mem_root= old_root;
 | |
|   mysql_mutex_unlock(&acl_cache->lock);
 | |
| 
 | |
|   if (write_to_binlog)
 | |
|   {
 | |
|     if (write_bin_log(thd, FALSE, thd->query(), thd->query_length()))
 | |
|       result= TRUE;
 | |
|   }
 | |
| 
 | |
|   mysql_rwlock_unlock(&LOCK_grant);
 | |
| 
 | |
|   /* Tables are automatically closed */
 | |
|   DBUG_RETURN(result);
 | |
| }
 | |
| 
 | |
| /**
 | |
|   append a user or role name to a buffer that will be later used as an error message
 | |
| */
 | |
| static void append_user(THD *thd, String *str,
 | |
|                         const LEX_CSTRING *u, const LEX_CSTRING *h)
 | |
| {
 | |
|   if (str->length())
 | |
|     str->append(',');
 | |
|   append_query_string(system_charset_info, str, u->str, u->length,
 | |
|                       thd->variables.sql_mode & MODE_NO_BACKSLASH_ESCAPES);
 | |
|   /* hostname part is not relevant for roles, it is always empty */
 | |
|   if (u->length == 0 || h->length != 0)
 | |
|   {
 | |
|     str->append('@');
 | |
|     append_query_string(system_charset_info, str, h->str, h->length,
 | |
|                         thd->variables.sql_mode & MODE_NO_BACKSLASH_ESCAPES);
 | |
|   }
 | |
| }
 | |
| 
 | |
| static void append_user(THD *thd, String *str, LEX_USER *user)
 | |
| {
 | |
|   append_user(thd, str, & user->user, & user->host);
 | |
| }
 | |
| 
 | |
| /**
 | |
|   append a string to a buffer that will be later used as an error message
 | |
| 
 | |
|   @note
 | |
|   a string can be either CURRENT_USER or CURRENT_ROLE or NONE, it should be
 | |
|   neither quoted nor escaped.
 | |
| */
 | |
| static void append_str(String *str, const char *s, size_t l)
 | |
| {
 | |
|   if (str->length())
 | |
|     str->append(',');
 | |
|   str->append(s, l);
 | |
| }
 | |
| 
 | |
| static int can_grant_role_callback(ACL_USER_BASE *grantee,
 | |
|                                    ACL_ROLE *role, void *data)
 | |
| {
 | |
|   ROLE_GRANT_PAIR *pair;
 | |
| 
 | |
|   if (role != (ACL_ROLE*)data)
 | |
|     return 0; // keep searching
 | |
| 
 | |
|   if (grantee->flags & IS_ROLE)
 | |
|     pair= find_role_grant_pair(&grantee->user, &empty_clex_str, &role->user);
 | |
|   else
 | |
|   {
 | |
|     ACL_USER *user= (ACL_USER *)grantee;
 | |
|     LEX_CSTRING host= { user->host.hostname, user->hostname_length };
 | |
|     pair= find_role_grant_pair(&user->user, &host, &role->user);
 | |
|   }
 | |
|   if (!pair->with_admin)
 | |
|     return 0; // keep searching
 | |
| 
 | |
|   return -1; // abort the traversal
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   One can only grant a role if SELECT * FROM I_S.APPLICABLE_ROLES shows this
 | |
|   role as grantable.
 | |
|   
 | |
|   What this really means - we need to traverse role graph for the current user
 | |
|   looking for our role being granted with the admin option.
 | |
| */
 | |
| static bool can_grant_role(THD *thd, ACL_ROLE *role)
 | |
| {
 | |
|   Security_context *sctx= thd->security_ctx;
 | |
| 
 | |
|   if (!sctx->is_user_defined()) // galera
 | |
|     return true;
 | |
| 
 | |
|   ACL_USER *grantee= find_user_exact(Lex_cstring_strlen(sctx->priv_host),
 | |
|                                      Lex_cstring_strlen(sctx->priv_user));
 | |
|   if (!grantee)
 | |
|     return false;
 | |
| 
 | |
|   return traverse_role_graph_down(grantee, role, NULL,
 | |
|                                   can_grant_role_callback) == -1;
 | |
| }
 | |
| 
 | |
| 
 | |
| bool mysql_grant_role(THD *thd, List <LEX_USER> &list, bool revoke)
 | |
| {
 | |
|   DBUG_ENTER("mysql_grant_role");
 | |
|   /*
 | |
|      The first entry in the list is the granted role. Need at least two
 | |
|      entries for the command to be valid
 | |
|    */
 | |
|   DBUG_ASSERT(list.elements >= 2);
 | |
|   int result;
 | |
|   bool create_new_user, no_auto_create_user;
 | |
|   String wrong_users;
 | |
|   LEX_USER *user, *granted_role;
 | |
|   LEX_CSTRING rolename;
 | |
|   LEX_CSTRING username;
 | |
|   LEX_CSTRING hostname;
 | |
|   ACL_ROLE *role, *role_as_user;
 | |
| 
 | |
|   List_iterator <LEX_USER> user_list(list);
 | |
|   granted_role= user_list++;
 | |
|   if (!(granted_role= get_current_user(thd, granted_role)))
 | |
|     DBUG_RETURN(TRUE);
 | |
| 
 | |
|   DBUG_ASSERT(granted_role->is_role());
 | |
|   rolename= granted_role->user;
 | |
| 
 | |
|   create_new_user= test_if_create_new_users(thd);
 | |
|   no_auto_create_user= thd->variables.sql_mode & MODE_NO_AUTO_CREATE_USER;
 | |
| 
 | |
|   Grant_tables tables;
 | |
|   if ((result= tables.open_and_lock(thd, Table_user | Table_roles_mapping, TL_WRITE)))
 | |
|     DBUG_RETURN(result != 1);
 | |
| 
 | |
|   mysql_rwlock_wrlock(&LOCK_grant);
 | |
|   mysql_mutex_lock(&acl_cache->lock);
 | |
|   if (!(role= find_acl_role(rolename, false)))
 | |
|   {
 | |
|     mysql_mutex_unlock(&acl_cache->lock);
 | |
|     mysql_rwlock_unlock(&LOCK_grant);
 | |
|     my_error(ER_INVALID_ROLE, MYF(0), rolename.str);
 | |
|     DBUG_RETURN(TRUE);
 | |
|   }
 | |
| 
 | |
|   if (!can_grant_role(thd, role))
 | |
|   {
 | |
|     mysql_mutex_unlock(&acl_cache->lock);
 | |
|     mysql_rwlock_unlock(&LOCK_grant);
 | |
|     my_error(ER_ACCESS_DENIED_NO_PASSWORD_ERROR, MYF(0),
 | |
|              thd->security_ctx->priv_user, thd->security_ctx->priv_host);
 | |
|     DBUG_RETURN(TRUE);
 | |
|   }
 | |
| 
 | |
|   while ((user= user_list++))
 | |
|   {
 | |
|     role_as_user= NULL;
 | |
|     /* current_role is treated slightly different */
 | |
|     if (user->user.str == current_role.str)
 | |
|     {
 | |
|       /* current_role is NONE */
 | |
|       if (!thd->security_ctx->priv_role[0])
 | |
|       {
 | |
|         my_error(ER_INVALID_ROLE, MYF(0), none.str);
 | |
|         append_str(&wrong_users, none.str, none.length);
 | |
|         result= 1;
 | |
|         continue;
 | |
|       }
 | |
|       const Lex_cstring_strlen ls(thd->security_ctx->priv_role);
 | |
|       if (!(role_as_user= find_acl_role(ls, true)))
 | |
|       {
 | |
|         append_user(thd, &wrong_users, &ls, &empty_clex_str);
 | |
|         result= 1;
 | |
|         continue;
 | |
|       }
 | |
| 
 | |
|       /* can not grant current_role to current_role */
 | |
|       if (granted_role->user.str == current_role.str)
 | |
|       {
 | |
|         append_user(thd, &wrong_users, &role_as_user->user, &empty_clex_str);
 | |
|         result= 1;
 | |
|         continue;
 | |
|       }
 | |
|       username.str= thd->security_ctx->priv_role;
 | |
|       username.length= strlen(username.str);
 | |
|       hostname= empty_clex_str;
 | |
|     }
 | |
|     else if (user->user.str == current_user.str)
 | |
|     {
 | |
|       username.str= thd->security_ctx->priv_user;
 | |
|       username.length= strlen(username.str);
 | |
|       hostname.str= thd->security_ctx->priv_host;
 | |
|       hostname.length= strlen(hostname.str);
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       if (user->host.str)
 | |
|         hostname= user->host;
 | |
|       else
 | |
|       switch (check_role_name(&user->user, true)) {
 | |
|       case ROLE_NAME_INVALID:
 | |
|         append_user(thd, &wrong_users, &user->user, &empty_clex_str);
 | |
|         result= 1;
 | |
|         continue;
 | |
|       case ROLE_NAME_PUBLIC:
 | |
|         user->host= hostname= empty_clex_str;
 | |
|         role_as_user= acl_public;
 | |
|         break;
 | |
|       case ROLE_NAME_OK:
 | |
|         if ((role_as_user= find_acl_role(user->user, false)))
 | |
|           hostname= empty_clex_str;
 | |
|         else
 | |
|           hostname= host_not_specified;
 | |
|         break;
 | |
|       }
 | |
|       username= user->user;
 | |
|     }
 | |
| 
 | |
|     ROLE_GRANT_PAIR *hash_entry= find_role_grant_pair(&username, &hostname,
 | |
|                                                       &rolename);
 | |
|     ACL_USER_BASE *grantee= role_as_user;
 | |
| 
 | |
|     if (has_auth(user, thd->lex))
 | |
|       DBUG_ASSERT(!grantee);
 | |
|     else if (!grantee && !is_public(user))
 | |
|       grantee= find_user_exact(hostname, username);
 | |
| 
 | |
|     if (!grantee && !revoke)
 | |
|     {
 | |
|       LEX_USER user_combo = *user;
 | |
|       user_combo.user = username;
 | |
|       user_combo.host = hostname;
 | |
| 
 | |
|       if (copy_and_check_auth(&user_combo, &user_combo, thd) ||
 | |
|           replace_user_table(thd, tables.user_table(), &user_combo, NO_ACL,
 | |
|                              false, create_new_user,
 | |
|                              (!is_public(&user_combo) && no_auto_create_user)))
 | |
|       {
 | |
|         append_user(thd, &wrong_users, &username, &hostname);
 | |
|         result= 1;
 | |
|         continue;
 | |
|       }
 | |
|       if (!is_public(&user_combo))
 | |
|         grantee= find_user_exact(hostname, username);
 | |
|       else
 | |
|         grantee= role_as_user= acl_public;
 | |
| 
 | |
|       /* either replace_user_table failed, or we've added the user */
 | |
|       DBUG_ASSERT(grantee);
 | |
|     }
 | |
| 
 | |
|     if (!grantee)
 | |
|     {
 | |
|       append_user(thd, &wrong_users, &username, &hostname);
 | |
|       result= 1;
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     if (!revoke)
 | |
|     {
 | |
|       if (hash_entry)
 | |
|       {
 | |
|         // perhaps, updating an existing grant, adding WITH ADMIN OPTION
 | |
|       }
 | |
|       else
 | |
|       {
 | |
|         add_role_user_mapping(grantee, role);
 | |
| 
 | |
|         /*
 | |
|           Check if this grant would cause a cycle. It only needs to be run
 | |
|           if we're granting a role to a role
 | |
|         */
 | |
|         if (role_as_user &&
 | |
|             traverse_role_graph_down(role, 0, 0, 0) == ROLE_CYCLE_FOUND)
 | |
|         {
 | |
|           append_user(thd, &wrong_users, &username, &empty_clex_str);
 | |
|           result= 1;
 | |
|           undo_add_role_user_mapping(grantee, role);
 | |
|           continue;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       /* grant was already removed or never existed */
 | |
|       if (!hash_entry)
 | |
|       {
 | |
|         append_user(thd, &wrong_users, &username, &hostname);
 | |
|         result= 1;
 | |
|         continue;
 | |
|       }
 | |
|       if (thd->lex->with_admin_option)
 | |
|       {
 | |
|         // only revoking an admin option, not the complete grant
 | |
|       }
 | |
|       else
 | |
|       {
 | |
|         /* revoke a role grant */
 | |
|         remove_role_user_mapping(grantee, role);
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     /* write into the roles_mapping table */
 | |
|     /* TODO(cvicentiu) refactor replace_roles_mapping_table to use
 | |
|        Roles_mapping_table instead of TABLE directly. */
 | |
|     if (replace_roles_mapping_table(tables.roles_mapping_table().table(),
 | |
|                                     &username, &hostname, &rolename,
 | |
|                                     thd->lex->with_admin_option,
 | |
|                                     hash_entry, revoke))
 | |
|     {
 | |
|       append_user(thd, &wrong_users, &username, &empty_clex_str);
 | |
|       result= 1;
 | |
|       if (!revoke)
 | |
|       {
 | |
|         /* need to remove the mapping added previously */
 | |
|         undo_add_role_user_mapping(grantee, role);
 | |
|       }
 | |
|       else
 | |
|       {
 | |
|         /* need to restore the mapping deleted previously */
 | |
|         add_role_user_mapping(grantee, role);
 | |
|       }
 | |
|       continue;
 | |
|     }
 | |
|     update_role_mapping(&username, &hostname, &rolename,
 | |
|                         thd->lex->with_admin_option, hash_entry, revoke);
 | |
| 
 | |
|     /*
 | |
|        Only need to propagate grants when granting/revoking a role to/from
 | |
|        a role
 | |
|     */
 | |
|     if (role_as_user)
 | |
|     {
 | |
|       propagate_role_grants(role_as_user, PRIVS_TO_MERGE::ALL);
 | |
|       acl_cache->clear(1);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   mysql_mutex_unlock(&acl_cache->lock);
 | |
| 
 | |
|   if (result)
 | |
|     my_error(revoke ? ER_CANNOT_REVOKE_ROLE : ER_CANNOT_GRANT_ROLE, MYF(0),
 | |
|              rolename.str, wrong_users.c_ptr_safe());
 | |
|   else
 | |
|     result= write_bin_log(thd, TRUE, thd->query(), thd->query_length());
 | |
| 
 | |
|   mysql_rwlock_unlock(&LOCK_grant);
 | |
| 
 | |
|   DBUG_RETURN(result);
 | |
| }
 | |
| 
 | |
| 
 | |
| static
 | |
| bool mysql_grant(THD *thd, LEX_CSTRING db, List <LEX_USER> &list,
 | |
|                  privilege_t rights, bool revoke_grant, bool is_proxy)
 | |
| {
 | |
|   List_iterator <LEX_USER> str_list (list);
 | |
|   LEX_USER *Str, *tmp_Str, *proxied_user= NULL;
 | |
|   IdentBuffer<SAFE_NAME_LEN + MY_CS_MBMAXLEN> tmp_db;
 | |
|   bool create_new_users=0;
 | |
|   int result;
 | |
|   DBUG_ENTER("mysql_grant");
 | |
| 
 | |
|   if (lower_case_table_names && db.str)
 | |
|   {
 | |
|     if (tmp_db.copy_casedn(db).length() > SAFE_NAME_LEN)
 | |
|     {
 | |
|       my_error(ER_WRONG_DB_NAME ,MYF(0), db.str);
 | |
|       DBUG_RETURN(TRUE);
 | |
|     }
 | |
|     db= tmp_db.to_lex_cstring();
 | |
|   }
 | |
| 
 | |
|   if (is_proxy)
 | |
|   {
 | |
|     DBUG_ASSERT(!db.str);
 | |
|     proxied_user= str_list++;
 | |
|   }
 | |
| 
 | |
|   const uint tables_to_open= Table_user | (is_proxy ? Table_proxies_priv : Table_db);
 | |
|   Grant_tables tables;
 | |
|   if ((result= tables.open_and_lock(thd, tables_to_open, TL_WRITE)))
 | |
|     DBUG_RETURN(result != 1);
 | |
| 
 | |
|   DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row());
 | |
| 
 | |
|   if (!revoke_grant)
 | |
|     create_new_users= test_if_create_new_users(thd);
 | |
| 
 | |
|   /* go through users in user_list */
 | |
|   mysql_rwlock_wrlock(&LOCK_grant);
 | |
|   mysql_mutex_lock(&acl_cache->lock);
 | |
|   grant_version++;
 | |
| 
 | |
|   if (proxied_user)
 | |
|   {
 | |
|     if (!(proxied_user= get_current_user(thd, proxied_user, false)))
 | |
|       DBUG_RETURN(TRUE);
 | |
|     DBUG_ASSERT(proxied_user->host.length); // not a Role
 | |
|   }
 | |
| 
 | |
|   while ((tmp_Str = str_list++))
 | |
|   {
 | |
|     if (!(Str= get_current_user(thd, tmp_Str, false)))
 | |
|     {
 | |
|       result= true;
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     if (copy_and_check_auth(Str, tmp_Str, thd) ||
 | |
|         replace_user_table(thd, tables.user_table(), Str,
 | |
|                            (!db.str ? rights : NO_ACL),
 | |
|                            revoke_grant, create_new_users,
 | |
|                            MY_TEST(!is_public(Str) &&
 | |
|                                    (thd->variables.sql_mode &
 | |
|                                     MODE_NO_AUTO_CREATE_USER))))
 | |
|       result= true;
 | |
|     else if (db.str)
 | |
|     {
 | |
|       privilege_t db_rights(rights & DB_ACLS);
 | |
|       if (db_rights  == rights)
 | |
|       {
 | |
|         if (replace_db_table(tables.db_table().table(), db.str,
 | |
|                              *Str, db_rights, revoke_grant))
 | |
|           result= true;
 | |
|       }
 | |
|       else
 | |
|       {
 | |
| 	my_error(ER_WRONG_USAGE, MYF(0), "DB GRANT", "GLOBAL PRIVILEGES");
 | |
| 	result= true;
 | |
|       }
 | |
|     }
 | |
|     else if (is_proxy)
 | |
|     {
 | |
|       if (replace_proxies_priv_table(thd, tables.proxies_priv_table().table(),
 | |
|             Str, proxied_user, rights & GRANT_ACL ? TRUE : FALSE, revoke_grant))
 | |
|         result= true;
 | |
|     }
 | |
|     if (Str->is_role())
 | |
|       propagate_role_grants(find_acl_role(Str->user, true),
 | |
|                             db.str ? PRIVS_TO_MERGE::DB :
 | |
|                                      PRIVS_TO_MERGE::GLOBAL,
 | |
|                             db.str);
 | |
|   }
 | |
|   mysql_mutex_unlock(&acl_cache->lock);
 | |
| 
 | |
|   if (!result)
 | |
|   {
 | |
|     result= write_bin_log(thd, TRUE, thd->query(), thd->query_length());
 | |
|   }
 | |
| 
 | |
|   mysql_rwlock_unlock(&LOCK_grant);
 | |
| 
 | |
|   if (!result)
 | |
|     my_ok(thd);
 | |
| 
 | |
|   DBUG_RETURN(result);
 | |
| }
 | |
| 
 | |
| 
 | |
| /* Free grant array if possible */
 | |
| 
 | |
| void  grant_free(void)
 | |
| {
 | |
|   DBUG_ENTER("grant_free");
 | |
|   my_hash_free(&column_priv_hash);
 | |
|   my_hash_free(&proc_priv_hash);
 | |
|   my_hash_free(&func_priv_hash);
 | |
|   my_hash_free(&package_spec_priv_hash);
 | |
|   my_hash_free(&package_body_priv_hash);
 | |
|   free_root(&grant_memroot,MYF(0));
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   @brief Initialize structures responsible for table/column-level privilege
 | |
|    checking and load information for them from tables in the 'mysql' database.
 | |
| 
 | |
|   @return Error status
 | |
|     @retval 0 OK
 | |
|     @retval 1 Could not initialize grant subsystem.
 | |
| */
 | |
| 
 | |
| bool grant_init()
 | |
| {
 | |
|   THD  *thd;
 | |
|   bool return_val;
 | |
|   DBUG_ENTER("grant_init");
 | |
| 
 | |
|   if (!(thd= new THD(0)))
 | |
|     DBUG_RETURN(1);				/* purecov: deadcode */
 | |
|   thd->store_globals();
 | |
|   thd->set_query_inner((char*) STRING_WITH_LEN("intern:grant_init"),
 | |
|                        default_charset_info);
 | |
| 
 | |
|   return_val=  grant_reload(thd);
 | |
|   delete thd;
 | |
|   DBUG_RETURN(return_val);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   @brief Initialize structures responsible for table/column-level privilege
 | |
|     checking and load information about grants from open privilege tables.
 | |
| 
 | |
|   @param thd Current thread
 | |
|   @param tables List containing open "mysql.tables_priv" and
 | |
|     "mysql.columns_priv" tables.
 | |
| 
 | |
|   @see grant_reload
 | |
| 
 | |
|   @return Error state
 | |
|     @retval FALSE Success
 | |
|     @retval TRUE Error
 | |
| */
 | |
| 
 | |
| static bool grant_load(THD *thd,
 | |
|                        const Tables_priv_table& tables_priv,
 | |
|                        const Columns_priv_table& columns_priv,
 | |
|                        const Procs_priv_table& procs_priv)
 | |
| {
 | |
|   bool return_val= 1;
 | |
|   TABLE *t_table, *c_table, *p_table;
 | |
|   MEM_ROOT *save_mem_root= thd->mem_root;
 | |
|   DBUG_ENTER("grant_load");
 | |
| 
 | |
|   Sql_mode_instant_remove sms(thd, MODE_PAD_CHAR_TO_FULL_LENGTH);
 | |
| 
 | |
|   (void) my_hash_init(key_memory_acl_memex, &column_priv_hash,
 | |
|                       &my_charset_utf8mb3_bin, 0, 0, 0, get_grant_table,
 | |
|                       free_grant_table, 0);
 | |
|   (void) my_hash_init(key_memory_acl_memex, &proc_priv_hash,
 | |
|                       &my_charset_utf8mb3_bin, 0, 0, 0, get_grant_table, 0, 0);
 | |
|   (void) my_hash_init(key_memory_acl_memex, &func_priv_hash,
 | |
|                       &my_charset_utf8mb3_bin, 0, 0, 0, get_grant_table, 0, 0);
 | |
|   (void) my_hash_init(key_memory_acl_memex, &package_spec_priv_hash,
 | |
|                       &my_charset_utf8mb3_bin, 0, 0, 0, get_grant_table, 0, 0);
 | |
|   (void) my_hash_init(key_memory_acl_memex, &package_body_priv_hash,
 | |
|                       &my_charset_utf8mb3_bin, 0, 0, 0, get_grant_table, 0, 0);
 | |
|   init_sql_alloc(key_memory_acl_mem, &grant_memroot, ACL_ALLOC_BLOCK_SIZE, 0, MYF(0));
 | |
| 
 | |
|   t_table= tables_priv.table();
 | |
|   c_table= columns_priv.table();
 | |
|   p_table= procs_priv.table(); // this can be NULL
 | |
| 
 | |
|   if (t_table->file->ha_index_init(0, 1))
 | |
|     goto end_index_init;
 | |
| 
 | |
|   t_table->use_all_columns();
 | |
|   c_table->use_all_columns();
 | |
| 
 | |
|   thd->mem_root= &grant_memroot;
 | |
| 
 | |
|   if (!t_table->file->ha_index_first(t_table->record[0]))
 | |
|   {
 | |
|     do
 | |
|     {
 | |
|       GRANT_TABLE *mem_check;
 | |
|       /* TODO(cvicentiu) convert this to use tables_priv and columns_priv. */
 | |
|       if (!(mem_check= new (&grant_memroot) GRANT_TABLE(t_table, c_table)))
 | |
|       {
 | |
| 	/* This could only happen if we are out memory */
 | |
| 	goto end_unlock;
 | |
|       }
 | |
| 
 | |
|       if (opt_skip_name_resolve)
 | |
|       {
 | |
| 	if (hostname_requires_resolving(mem_check->host.hostname))
 | |
| 	{
 | |
|           sql_print_warning("'tables_priv' entry '%s %s@%s' "
 | |
|                             "ignored in --skip-name-resolve mode.",
 | |
|                             mem_check->tname, mem_check->user,
 | |
|                             safe_str(mem_check->host.hostname));
 | |
| 	  continue;
 | |
| 	}
 | |
|       }
 | |
| 
 | |
|       if (! mem_check->ok())
 | |
| 	delete mem_check;
 | |
|       else if (column_priv_insert(mem_check))
 | |
|       {
 | |
| 	delete mem_check;
 | |
| 	goto end_unlock;
 | |
|       }
 | |
|     }
 | |
|     while (!t_table->file->ha_index_next(t_table->record[0]));
 | |
|   }
 | |
| 
 | |
|   return_val= 0;
 | |
| 
 | |
|   if (p_table)
 | |
|   {
 | |
|     if (p_table->file->ha_index_init(0, 1))
 | |
|       goto end_unlock;
 | |
| 
 | |
|     p_table->use_all_columns();
 | |
| 
 | |
|     if (!p_table->file->ha_index_first(p_table->record[0]))
 | |
|     {
 | |
|       do
 | |
|       {
 | |
|         GRANT_NAME *mem_check;
 | |
|         HASH *hash;
 | |
|         if (!(mem_check= new (&grant_memroot) GRANT_NAME(p_table, TRUE)))
 | |
|         {
 | |
|           /* This could only happen if we are out memory */
 | |
|           goto end_unlock_p;
 | |
|         }
 | |
| 
 | |
|         if (opt_skip_name_resolve)
 | |
|         {
 | |
|           if (hostname_requires_resolving(mem_check->host.hostname))
 | |
|           {
 | |
|             sql_print_warning("'procs_priv' entry '%s %s@%s' "
 | |
|                               "ignored in --skip-name-resolve mode.",
 | |
|                               mem_check->tname, mem_check->user,
 | |
|                               safe_str(mem_check->host.hostname));
 | |
|             continue;
 | |
|           }
 | |
|         }
 | |
|         enum_sp_type type= (enum_sp_type)procs_priv.routine_type()->val_int();
 | |
|         const Sp_handler *sph= Sp_handler::handler(type);
 | |
|         if (!sph || !(hash= sph->get_priv_hash()))
 | |
|         {
 | |
|           sql_print_warning("'procs_priv' entry '%s' "
 | |
|                             "ignored, bad routine type",
 | |
|                             mem_check->tname);
 | |
|           continue;
 | |
|         }
 | |
| 
 | |
|         mem_check->privs= fix_rights_for_procedure(mem_check->privs);
 | |
|         mem_check->init_privs= mem_check->privs;
 | |
|         if (! mem_check->ok())
 | |
|           delete mem_check;
 | |
|         else if (my_hash_insert(hash, (uchar*) mem_check))
 | |
|         {
 | |
|           delete mem_check;
 | |
|           goto end_unlock_p;
 | |
|         }
 | |
|       }
 | |
|       while (!p_table->file->ha_index_next(p_table->record[0]));
 | |
|     }
 | |
|   }
 | |
| 
 | |
| end_unlock_p:
 | |
|   if (p_table)
 | |
|     p_table->file->ha_index_end();
 | |
| end_unlock:
 | |
|   t_table->file->ha_index_end();
 | |
|   thd->mem_root= save_mem_root;
 | |
| end_index_init:
 | |
|   DBUG_RETURN(return_val);
 | |
| }
 | |
| 
 | |
| static my_bool propagate_role_grants_action(void *role_ptr,
 | |
|                                             void *ptr __attribute__((unused)))
 | |
| {
 | |
|   ACL_ROLE *role= static_cast<ACL_ROLE *>(role_ptr);
 | |
|   if (role->counter)
 | |
|     return 0;
 | |
| 
 | |
|   mysql_mutex_assert_owner(&acl_cache->lock);
 | |
|   PRIVS_TO_MERGE data= { PRIVS_TO_MERGE::ALL, 0, 0 };
 | |
|   traverse_role_graph_up(role, &data, NULL, merge_role_privileges);
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   @brief Reload information about table and column level privileges if possible
 | |
| 
 | |
|   @param thd Current thread
 | |
| 
 | |
|   Locked tables are checked by acl_reload() and doesn't have to be checked
 | |
|   in this call.
 | |
|   This function is also used for initialization of structures responsible
 | |
|   for table/column-level privilege checking.
 | |
| 
 | |
|   @return Error state
 | |
|     @retval FALSE Success
 | |
|     @retval TRUE  Error
 | |
| */
 | |
| 
 | |
| bool grant_reload(THD *thd)
 | |
| {
 | |
|   HASH old_column_priv_hash, old_proc_priv_hash, old_func_priv_hash;
 | |
|   HASH old_package_spec_priv_hash, old_package_body_priv_hash;
 | |
|   MEM_ROOT old_mem;
 | |
|   int result;
 | |
|   DBUG_ENTER("grant_reload");
 | |
| 
 | |
|   /*
 | |
|     To avoid deadlocks we should obtain table locks before
 | |
|     obtaining LOCK_grant rwlock.
 | |
|   */
 | |
| 
 | |
|   Grant_tables tables;
 | |
|   const uint tables_to_open= Table_tables_priv | Table_columns_priv| Table_procs_priv;
 | |
|   if ((result= tables.open_and_lock(thd, tables_to_open, TL_READ)))
 | |
|     DBUG_RETURN(result != 1);
 | |
| 
 | |
|   mysql_rwlock_wrlock(&LOCK_grant);
 | |
|   grant_version++;
 | |
|   old_column_priv_hash= column_priv_hash;
 | |
|   old_proc_priv_hash= proc_priv_hash;
 | |
|   old_func_priv_hash= func_priv_hash;
 | |
|   old_package_spec_priv_hash= package_spec_priv_hash;
 | |
|   old_package_body_priv_hash= package_body_priv_hash;
 | |
| 
 | |
|   /*
 | |
|     Create a new memory pool but save the current memory pool to make an undo
 | |
|     opertion possible in case of failure.
 | |
|   */
 | |
|   old_mem= grant_memroot;
 | |
| 
 | |
|   if ((result= grant_load(thd,
 | |
|                           tables.tables_priv_table(),
 | |
|                           tables.columns_priv_table(),
 | |
|                           tables.procs_priv_table())))
 | |
|   {						// Error. Revert to old hash
 | |
|     DBUG_PRINT("error",("Reverting to old privileges"));
 | |
|     grant_free();				/* purecov: deadcode */
 | |
|     column_priv_hash= old_column_priv_hash;	/* purecov: deadcode */
 | |
|     proc_priv_hash= old_proc_priv_hash;
 | |
|     func_priv_hash= old_func_priv_hash;
 | |
|     package_spec_priv_hash= old_package_spec_priv_hash;
 | |
|     package_body_priv_hash= old_package_body_priv_hash;
 | |
|     grant_memroot= old_mem;                     /* purecov: deadcode */
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     my_hash_free(&old_column_priv_hash);
 | |
|     my_hash_free(&old_proc_priv_hash);
 | |
|     my_hash_free(&old_func_priv_hash);
 | |
|     my_hash_free(&old_package_spec_priv_hash);
 | |
|     my_hash_free(&old_package_body_priv_hash);
 | |
|     free_root(&old_mem,MYF(0));
 | |
|   }
 | |
| 
 | |
|   mysql_mutex_lock(&acl_cache->lock);
 | |
|   my_hash_iterate(&acl_roles, propagate_role_grants_action, NULL);
 | |
|   mysql_mutex_unlock(&acl_cache->lock);
 | |
| 
 | |
|   mysql_rwlock_unlock(&LOCK_grant);
 | |
| 
 | |
|   close_mysql_tables(thd);
 | |
| 
 | |
|   DBUG_RETURN(result);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   @brief Check table level grants
 | |
| 
 | |
|   @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.
 | |
| 
 | |
|   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 function assumes that either number of tables to be inspected
 | |
|      by it is limited explicitly (i.e. 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).
 | |
| 
 | |
|      We delay locking of LOCK_grant until we really need it as we assume that
 | |
|      most privileges be resolved with user or db level accesses.
 | |
| 
 | |
|    @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, privilege_t want_access, TABLE_LIST *tables,
 | |
|                  bool any_combination_will_do, uint number, bool no_errors)
 | |
| {
 | |
|   TABLE_LIST *tl;
 | |
|   TABLE_LIST *first_not_own_table= thd->lex->first_not_own_table();
 | |
|   Security_context *sctx= thd->security_ctx;
 | |
|   uint i;
 | |
|   privilege_t original_want_access(want_access);
 | |
|   bool locked= 0;
 | |
|   DBUG_ENTER("check_grant");
 | |
|   DBUG_ASSERT(number > 0);
 | |
| 
 | |
|   /*
 | |
|     Walk through the list of tables that belong to the query and save the
 | |
|     requested access (orig_want_privilege) to be able to use it when
 | |
|     checking access rights to the underlying tables of a view. Our grant
 | |
|     system gradually eliminates checked bits from want_privilege and thus
 | |
|     after all checks are done we can no longer use it.
 | |
|     The check that first_not_own_table is not reached is for the case when
 | |
|     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, tl= tables;
 | |
|        i < number  && tl != first_not_own_table;
 | |
|        tl= tl->next_global, i++)
 | |
|   {
 | |
|     /*
 | |
|       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);
 | |
|   }
 | |
|   number= i;
 | |
| 
 | |
|   for (tl= tables; number-- ; tl= tl->next_global)
 | |
|   {
 | |
|     TABLE_LIST *const t_ref=
 | |
|       tl->correspondent_table ? tl->correspondent_table : tl;
 | |
|     sctx= t_ref->security_ctx ? t_ref->security_ctx : thd->security_ctx;
 | |
|     privilege_t orig_want_access(original_want_access);
 | |
| 
 | |
|     /*
 | |
|       If sequence is used as part of NEXT VALUE, PREVIOUS VALUE or SELECT,
 | |
|       we need to modify the requested access rights depending on how the
 | |
|       sequence is used.
 | |
|     */
 | |
|     if (t_ref->sequence &&
 | |
|         !(want_access & ~(SELECT_ACL | INSERT_ACL | UPDATE_ACL | DELETE_ACL)))
 | |
|     {
 | |
|       /*
 | |
|         We want to have either SELECT or INSERT rights to sequences depending
 | |
|         on how they are accessed
 | |
|       */
 | |
|       orig_want_access= ((t_ref->lock_type >= TL_FIRST_WRITE) ?
 | |
|                          INSERT_ACL : SELECT_ACL);
 | |
|     }
 | |
| 
 | |
|     const ACL_internal_table_access *access=
 | |
|       get_cached_table_access(&t_ref->grant.m_internal,
 | |
|                               t_ref->get_db_name().str,
 | |
|                               t_ref->get_table_name().str);
 | |
| 
 | |
|     if (access)
 | |
|     {
 | |
|       switch(access->check(orig_want_access, &t_ref->grant.privilege,
 | |
|                            any_combination_will_do))
 | |
|       {
 | |
|       case ACL_INTERNAL_ACCESS_GRANTED:
 | |
|         t_ref->grant.privilege|= orig_want_access;
 | |
|         t_ref->grant.want_privilege= NO_ACL;
 | |
|         continue;
 | |
|       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 (!(~t_ref->grant.privilege & want_access) ||
 | |
|         t_ref->is_anonymous_derived_table() || t_ref->schema_table ||
 | |
|         t_ref->table_function)
 | |
|     {
 | |
|       /*
 | |
|         It is subquery in the FROM clause. VIEW set t_ref->derived after
 | |
|         table opening, but this function always called before table opening.
 | |
| 
 | |
|         NOTE: is_derived() can't be used here because subquery in this case
 | |
|         the FROM clase (derived tables) can be not be marked yet.
 | |
|       */
 | |
|       if (t_ref->is_anonymous_derived_table() || t_ref->schema_table ||
 | |
|           t_ref->table_function)
 | |
|       {
 | |
|         /*
 | |
|           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.
 | |
|         */
 | |
|         t_ref->grant.want_privilege= NO_ACL;
 | |
|       }
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     if (is_temporary_table(t_ref))
 | |
|     {
 | |
|       /*
 | |
|         If this table list element corresponds to a pre-opened temporary
 | |
|         table skip checking of all relevant table-level privileges for it.
 | |
|         Note that during creation of temporary table we still need to check
 | |
|         if user has CREATE_TMP_ACL.
 | |
|       */
 | |
|       t_ref->grant.privilege|= TMP_TABLE_ACLS;
 | |
|       t_ref->grant.want_privilege= NO_ACL;
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     if (!locked)
 | |
|     {
 | |
|       locked= 1;
 | |
|       mysql_rwlock_rdlock(&LOCK_grant);
 | |
|     }
 | |
| 
 | |
|     t_ref->grant.read(sctx, t_ref->get_db_name().str,
 | |
|                       t_ref->get_table_name().str);
 | |
| 
 | |
|     if (!t_ref->grant.grant_table_user &&
 | |
|         !t_ref->grant.grant_table_role &&
 | |
|         !t_ref->grant.grant_public)
 | |
|     {
 | |
|       want_access&= ~t_ref->grant.privilege;
 | |
|       goto err;					// No grants
 | |
|     }
 | |
| 
 | |
|     /*
 | |
|       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;
 | |
| 
 | |
|     t_ref->grant.privilege|= t_ref->grant.aggregate_privs();
 | |
|     t_ref->grant.want_privilege= ((want_access & COL_ACLS) & ~t_ref->grant.privilege);
 | |
| 
 | |
|     if (!(~t_ref->grant.privilege & want_access))
 | |
|       continue;
 | |
| 
 | |
|     if ((want_access&= ~t_ref->grant.all_privilege()))
 | |
|     {
 | |
|       goto err;                                 // impossible
 | |
|     }
 | |
|   }
 | |
|   if (locked)
 | |
|     mysql_rwlock_unlock(&LOCK_grant);
 | |
|   DBUG_RETURN(FALSE);
 | |
| 
 | |
| err:
 | |
|   if (locked)
 | |
|     mysql_rwlock_unlock(&LOCK_grant);
 | |
|   if (!no_errors)				// Not a silent skip of table
 | |
|   {
 | |
|     char command[128];
 | |
|     get_privilege_desc(command, sizeof(command), want_access);
 | |
|     status_var_increment(thd->status_var.access_denied_errors);
 | |
| 
 | |
|     my_error(ER_TABLEACCESS_DENIED_ERROR, MYF(0),
 | |
|              command,
 | |
|              sctx->priv_user,
 | |
|              sctx->host_or_ip, tl ? tl->db.str : "unknown",
 | |
|              tl ? tl->get_table_name().str : "unknown");
 | |
|   }
 | |
|   DBUG_RETURN(TRUE);
 | |
| }
 | |
| 
 | |
| 
 | |
| static void check_grant_column_int(GRANT_TABLE *grant_table,
 | |
|                                    const Lex_ident_column &name,
 | |
|                                    privilege_t *want_access)
 | |
| {
 | |
|   if (grant_table)
 | |
|   {
 | |
|     *want_access&= ~grant_table->privs;
 | |
|     if (*want_access & grant_table->cols)
 | |
|     {
 | |
|       GRANT_COLUMN *grant_column= column_hash_search(grant_table, name);
 | |
|       if (grant_column)
 | |
|         *want_access&= ~grant_column->rights;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| inline privilege_t GRANT_INFO::aggregate_privs()
 | |
| {
 | |
|   return (grant_table_user ? grant_table_user->privs : NO_ACL) |
 | |
|          (grant_table_role ?  grant_table_role->privs : NO_ACL) |
 | |
|          (grant_public ?  grant_public->privs : NO_ACL);
 | |
| }
 | |
| 
 | |
| inline privilege_t GRANT_INFO::aggregate_cols()
 | |
| {
 | |
|   return (grant_table_user ? grant_table_user->cols : NO_ACL) |
 | |
|          (grant_table_role ?  grant_table_role->cols : NO_ACL) |
 | |
|          (grant_public ?  grant_public->cols : NO_ACL);
 | |
| }
 | |
| 
 | |
| 
 | |
| void GRANT_INFO::refresh(const Security_context *sctx,
 | |
|                          const char *db, const char *table)
 | |
| {
 | |
|   if (version != grant_version)
 | |
|     read(sctx, db, table);
 | |
| }
 | |
| 
 | |
| void GRANT_INFO::read(const Security_context *sctx,
 | |
|                          const char *db, const char *table)
 | |
| {
 | |
| #ifdef EMBEDDED_LIBRARY
 | |
|   grant_table_user= grant_table_role= grant_public= NULL;
 | |
| #else
 | |
|   grant_table_user=
 | |
|     table_hash_search(sctx->host, sctx->ip, db, sctx->priv_user,
 | |
|                       table, FALSE);         /* purecov: inspected */
 | |
|   grant_table_role=
 | |
|     sctx->priv_role[0] ? table_hash_search("", NULL, db, sctx->priv_role,
 | |
|                                            table, TRUE) : NULL;
 | |
|   grant_public=
 | |
|     acl_public ? table_hash_search("", NULL, db, public_name.str,
 | |
|                                    table, TRUE) : NULL;
 | |
| #endif
 | |
|   version= grant_version;		/* purecov: inspected */
 | |
| }
 | |
| 
 | |
| /*
 | |
|   Check column rights in given security context
 | |
| 
 | |
|   SYNOPSIS
 | |
|     check_grant_column()
 | |
|     thd                  thread handler
 | |
|     grant                grant information structure
 | |
|     db_name              db name
 | |
|     table_name           table  name
 | |
|     name                 column name
 | |
|     sctx                 security context
 | |
| 
 | |
|   RETURN
 | |
|     FALSE OK
 | |
|     TRUE  access denied
 | |
| */
 | |
| 
 | |
| bool check_grant_column(THD *thd, GRANT_INFO *grant,
 | |
|                         const char *db_name, const char *table_name,
 | |
|                         const Lex_ident_column &column_name,
 | |
|                         Security_context *sctx)
 | |
| {
 | |
|   privilege_t want_access(grant->want_privilege & ~grant->privilege);
 | |
|   DBUG_ENTER("check_grant_column");
 | |
|   DBUG_PRINT("enter", ("table: %s  want_access: %llx",
 | |
|                        table_name, (longlong) want_access));
 | |
| 
 | |
|   if (!want_access)
 | |
|     DBUG_RETURN(0);				// Already checked
 | |
| 
 | |
|   mysql_rwlock_rdlock(&LOCK_grant);
 | |
| 
 | |
|   /* reload table if someone has modified any grants */
 | |
|   grant->refresh(sctx, db_name, table_name);
 | |
| 
 | |
|   check_grant_column_int(grant->grant_table_user, column_name, &want_access);
 | |
|   check_grant_column_int(grant->grant_table_role, column_name, &want_access);
 | |
|   check_grant_column_int(grant->grant_public, column_name, &want_access);
 | |
| 
 | |
|   mysql_rwlock_unlock(&LOCK_grant);
 | |
|   if (!want_access)
 | |
|     DBUG_RETURN(0);
 | |
| 
 | |
|   char command[128];
 | |
|   get_privilege_desc(command, sizeof(command), want_access);
 | |
|   /* TODO perhaps error should print current rolename as well */
 | |
|   my_error(ER_COLUMNACCESS_DENIED_ERROR, MYF(0), command, sctx->priv_user,
 | |
|            sctx->host_or_ip, column_name.str, table_name);
 | |
|   DBUG_RETURN(1);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Check the access right to a column depending on the type of table.
 | |
| 
 | |
|   SYNOPSIS
 | |
|     check_column_grant_in_table_ref()
 | |
|     thd              thread handler
 | |
|     table_ref        table reference where to check the field
 | |
|     name             name of field to check
 | |
|     length           length of name
 | |
|     fld              use fld object to check invisibility when it is
 | |
|                      not 0, not_found_field, view_ref_found
 | |
| 
 | |
|   DESCRIPTION
 | |
|     Check the access rights to a column depending on the type of table
 | |
|     reference where the column is checked. The function provides a
 | |
|     generic interface to check column access rights that hides the
 | |
|     heterogeneity of the column representation - whether it is a view
 | |
|     or a stored table column.
 | |
| 
 | |
|   RETURN
 | |
|     FALSE OK
 | |
|     TRUE  access denied
 | |
| */
 | |
| 
 | |
| bool check_column_grant_in_table_ref(THD *thd, TABLE_LIST * table_ref,
 | |
|                                      const Lex_ident_column &name,
 | |
|                                      Field *fld)
 | |
| {
 | |
|   GRANT_INFO *grant;
 | |
|   const char *db_name;
 | |
|   const char *table_name;
 | |
|   Security_context *sctx= table_ref->security_ctx ?
 | |
|                           table_ref->security_ctx : thd->security_ctx;
 | |
|   if (fld && fld != not_found_field && fld != view_ref_found
 | |
|           && fld->invisible >= INVISIBLE_SYSTEM)
 | |
|       return false;
 | |
| 
 | |
|   if (table_ref->view || table_ref->field_translation)
 | |
|   {
 | |
|     /* View or derived information schema table. */
 | |
|     privilege_t view_privs(NO_ACL);
 | |
|     grant= &(table_ref->grant);
 | |
|     db_name= table_ref->view_db.str;
 | |
|     table_name= table_ref->view_name.str;
 | |
|     if (table_ref->belong_to_view &&
 | |
|         thd->lex->sql_command == SQLCOM_SHOW_FIELDS)
 | |
|     {
 | |
|       view_privs= get_column_grant(thd, grant, db_name, table_name, name);
 | |
|       if (view_privs & VIEW_ANY_ACL)
 | |
|       {
 | |
|         table_ref->belong_to_view->allowed_show= TRUE;
 | |
|         return FALSE;
 | |
|       }
 | |
|       table_ref->belong_to_view->allowed_show= FALSE;
 | |
|       my_message(ER_VIEW_NO_EXPLAIN, ER_THD(thd, ER_VIEW_NO_EXPLAIN), MYF(0));
 | |
|       return TRUE;
 | |
|     }
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     /* Normal or temporary table. */
 | |
|     TABLE *table= table_ref->table;
 | |
|     grant= &(table->grant);
 | |
|     db_name= table->s->db.str;
 | |
|     table_name= table->s->table_name.str;
 | |
|   }
 | |
| 
 | |
|   if (grant->want_privilege)
 | |
|     return check_grant_column(thd, grant, db_name, table_name, name, sctx);
 | |
|   else
 | |
|     return FALSE;
 | |
| 
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   @brief check if a query can access a set of columns
 | |
| 
 | |
|   @param  thd  the current thread
 | |
|   @param  want_access_arg  the privileges requested
 | |
|   @param  fields an iterator over the fields of a table reference.
 | |
|   @return Operation status
 | |
|     @retval 0 Success
 | |
|     @retval 1 Failure
 | |
|   @details This function walks over the columns of a table reference
 | |
|    The columns may originate from different tables, depending on the kind of
 | |
|    table reference, e.g. join, view.
 | |
|    For each table it will retrieve the grant information and will use it
 | |
|    to check the required access privileges for the fields requested from it.
 | |
| */
 | |
| bool check_grant_all_columns(THD *thd, privilege_t want_access_arg,
 | |
|                              Field_iterator_table_ref *fields)
 | |
| {
 | |
|   Security_context *sctx= thd->security_ctx;
 | |
|   privilege_t want_access(NO_ACL);
 | |
|   const char *table_name= NULL;
 | |
|   const char* db_name= NULL;
 | |
|   GRANT_INFO *grant;
 | |
|   GRANT_TABLE *UNINIT_VAR(grant_table);
 | |
|   GRANT_TABLE *UNINIT_VAR(grant_table_role);
 | |
|   GRANT_TABLE *UNINIT_VAR(grant_public);
 | |
|   /*
 | |
|      Flag that gets set if privilege checking has to be performed on column
 | |
|      level.
 | |
|   */
 | |
|   bool using_column_privileges= FALSE;
 | |
| 
 | |
|   mysql_rwlock_rdlock(&LOCK_grant);
 | |
| 
 | |
|   for (; !fields->end_of_fields(); fields->next())
 | |
|   {
 | |
|     if (fields->field() &&
 | |
|         fields->field()->invisible >= INVISIBLE_SYSTEM)
 | |
|       continue;
 | |
| 
 | |
|     if (table_name != fields->get_table_name().str)
 | |
|     {
 | |
|       table_name= fields->get_table_name().str;
 | |
|       db_name= fields->get_db_name().str;
 | |
|       grant= fields->grant();
 | |
|       /* get a fresh one for each table */
 | |
|       want_access= want_access_arg & ~grant->privilege;
 | |
|       if (want_access)
 | |
|       {
 | |
|         /* reload table if someone has modified any grants */
 | |
|         grant->refresh(sctx, db_name, table_name);
 | |
| 
 | |
|         grant_table= grant->grant_table_user;
 | |
|         grant_table_role= grant->grant_table_role;
 | |
|         grant_public= grant->grant_public;
 | |
|         if (!grant_table && !grant_table_role && !grant_public)
 | |
|           goto err;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (want_access)
 | |
|     {
 | |
|       privilege_t have_access(NO_ACL);
 | |
|       if (grant_table)
 | |
|       {
 | |
|         GRANT_COLUMN *grant_column=
 | |
|           column_hash_search(grant_table, fields->name());
 | |
|         if (grant_column)
 | |
|           have_access= grant_column->rights;
 | |
|       }
 | |
|       if (grant_table_role)
 | |
|       {
 | |
|         GRANT_COLUMN *grant_column=
 | |
|           column_hash_search(grant_table_role, fields->name());
 | |
|         if (grant_column)
 | |
|           have_access|= grant_column->rights;
 | |
|       }
 | |
|       if (grant_public)
 | |
|       {
 | |
|         GRANT_COLUMN *grant_column=
 | |
|           column_hash_search(grant_public, fields->name());
 | |
|         if (grant_column)
 | |
|           have_access|= grant_column->rights;
 | |
| 
 | |
|       }
 | |
| 
 | |
|       if (have_access)
 | |
|         using_column_privileges= TRUE;
 | |
|       if (want_access & ~have_access)
 | |
|         goto err;
 | |
|     }
 | |
|   }
 | |
|   mysql_rwlock_unlock(&LOCK_grant);
 | |
|   return 0;
 | |
| 
 | |
| err:
 | |
|   mysql_rwlock_unlock(&LOCK_grant);
 | |
| 
 | |
|   char command[128];
 | |
|   get_privilege_desc(command, sizeof(command), want_access);
 | |
|   /*
 | |
|     Do not give an error message listing a column name unless the user has
 | |
|     privilege to see all columns.
 | |
|   */
 | |
|   if (using_column_privileges)
 | |
|     my_error(ER_TABLEACCESS_DENIED_ERROR, MYF(0),
 | |
|              command, sctx->priv_user,
 | |
|              sctx->host_or_ip, db_name, table_name);
 | |
|   else
 | |
|     my_error(ER_COLUMNACCESS_DENIED_ERROR, MYF(0),
 | |
|              command,
 | |
|              sctx->priv_user,
 | |
|              sctx->host_or_ip,
 | |
|              fields->name().str,
 | |
|              table_name);
 | |
|   return 1;
 | |
| }
 | |
| 
 | |
| 
 | |
| static bool check_grant_db_routine(THD *thd, const char *db, HASH *hash)
 | |
| {
 | |
|   Security_context *sctx= thd->security_ctx;
 | |
| 
 | |
|   for (uint idx= 0; idx < hash->records; ++idx)
 | |
|   {
 | |
|     GRANT_NAME *item= (GRANT_NAME*) my_hash_element(hash, idx);
 | |
| 
 | |
|     if (strcmp(item->user, sctx->priv_user) == 0 &&
 | |
|         strcmp(item->db, db) == 0 &&
 | |
|         compare_hostname(&item->host, sctx->host, sctx->ip))
 | |
|     {
 | |
|       return FALSE;
 | |
|     }
 | |
|     if (sctx->priv_role[0] && strcmp(item->user, sctx->priv_role) == 0 &&
 | |
|         strcmp(item->db, db) == 0 &&
 | |
|         (!item->host.hostname || !item->host.hostname[0]))
 | |
|     {
 | |
|       return FALSE; /* Found current role match */
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return TRUE;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Check if a user has the right to access a database
 | |
|   Access is accepted if he has a grant for any table/routine in the database
 | |
|   Return 1 if access is denied
 | |
| */
 | |
| 
 | |
| bool check_grant_db(THD *thd, const char *db)
 | |
| {
 | |
|   Security_context *sctx= thd->security_ctx;
 | |
|   constexpr size_t key_data_size= SAFE_NAME_LEN + USERNAME_LENGTH + 1;
 | |
|   // See earlier comments on MY_CS_MBMAXLEN above
 | |
|   CharBuffer<key_data_size + MY_CS_MBMAXLEN> key, key2;
 | |
|   bool error= TRUE;
 | |
| 
 | |
|   key.append(Lex_cstring_strlen(sctx->priv_user)).append_char('\0')
 | |
|      .append_opt_casedn(files_charset_info, Lex_cstring_strlen(db),
 | |
|                         lower_case_table_names)
 | |
|      .append_char('\0');
 | |
| 
 | |
|   if (key.length() > key_data_size) // db name was truncated
 | |
|     return 1;                        // no privileges for an invalid db name
 | |
| 
 | |
|   /*
 | |
|      If a role is set, we need to check for privileges here as well.
 | |
|   */
 | |
|   if (sctx->priv_role[0])
 | |
|   {
 | |
|     key2.append(Lex_cstring_strlen(sctx->priv_role)).append_char('\0')
 | |
|         .append_opt_casedn(files_charset_info, Lex_cstring_strlen(db),
 | |
|                            lower_case_table_names)
 | |
|         .append_char('\0');
 | |
|   }
 | |
| 
 | |
| 
 | |
|   mysql_rwlock_rdlock(&LOCK_grant);
 | |
| 
 | |
|   for (uint idx=0 ; idx < column_priv_hash.records ; idx++)
 | |
|   {
 | |
|     GRANT_TABLE *grant_table= (GRANT_TABLE*) my_hash_element(&column_priv_hash,
 | |
|                                                              idx);
 | |
|     if (key.length() < grant_table->key_length &&
 | |
|         !memcmp(grant_table->hash_key, key.ptr(), key.length()) &&
 | |
|         compare_hostname(&grant_table->host, sctx->host, sctx->ip))
 | |
|     {
 | |
|       error= FALSE; /* Found match. */
 | |
|       break;
 | |
|     }
 | |
|     if (sctx->priv_role[0] &&
 | |
|         key2.length() < grant_table->key_length &&
 | |
|         !memcmp(grant_table->hash_key, key2.ptr(), key2.length()) &&
 | |
|         (!grant_table->host.hostname || !grant_table->host.hostname[0]))
 | |
|     {
 | |
|       error= FALSE; /* Found role match */
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (error)
 | |
|     error= check_grant_db_routine(thd, db, &proc_priv_hash) &&
 | |
|            check_grant_db_routine(thd, db, &func_priv_hash) &&
 | |
|            check_grant_db_routine(thd, db, &package_spec_priv_hash) &&
 | |
|            check_grant_db_routine(thd, db, &package_body_priv_hash);
 | |
| 
 | |
|   mysql_rwlock_unlock(&LOCK_grant);
 | |
| 
 | |
|   return error;
 | |
| }
 | |
| 
 | |
| 
 | |
| /****************************************************************************
 | |
|   Check routine level grants
 | |
| 
 | |
|   SYNPOSIS
 | |
|    bool check_grant_routine()
 | |
|    thd		Thread handler
 | |
|    want_access  Bits of privileges user needs to have
 | |
|    procs	List of routines to check. The user should have 'want_access'
 | |
|    sph          SP handler
 | |
|    no_errors	If 0 then we write an error. The error is sent directly to
 | |
| 		the client
 | |
| 
 | |
|    RETURN
 | |
|      0  ok
 | |
|      1  Error: User did not have the requested privileges
 | |
| ****************************************************************************/
 | |
| 
 | |
| bool check_grant_routine(THD *thd, privilege_t want_access,
 | |
| 			 TABLE_LIST *procs, const Sp_handler *sph,
 | |
| 			 bool no_errors)
 | |
| {
 | |
|   TABLE_LIST *table;
 | |
|   Security_context *sctx= thd->security_ctx;
 | |
|   char *user= sctx->priv_user;
 | |
|   char *host= sctx->priv_host;
 | |
|   char *role= sctx->priv_role;
 | |
|   DBUG_ENTER("check_grant_routine");
 | |
| 
 | |
|   want_access&= ~sctx->master_access;
 | |
|   if (!want_access)
 | |
|     DBUG_RETURN(0);                             // ok
 | |
| 
 | |
|   mysql_rwlock_rdlock(&LOCK_grant);
 | |
|   for (table= procs; table; table= table->next_global)
 | |
|   {
 | |
|     GRANT_NAME *grant_proc;
 | |
|     if ((grant_proc= routine_hash_search(host, sctx->ip, table->db.str, user,
 | |
|                                          table->table_name.str, sph, 0)))
 | |
|       table->grant.privilege|= grant_proc->privs;
 | |
|     if (role[0]) /* current role set check */
 | |
|     {
 | |
|       if ((grant_proc= routine_hash_search("", NULL, table->db.str, role,
 | |
|                                            table->table_name.str, sph, 0)))
 | |
|       table->grant.privilege|= grant_proc->privs;
 | |
|     }
 | |
|     if (acl_public)
 | |
|     {
 | |
|       if ((grant_proc= routine_hash_search("", NULL, table->db.str,
 | |
|                                            public_name.str,
 | |
|                                            table->table_name.str, sph, 0)))
 | |
|       table->grant.privilege|= grant_proc->privs;
 | |
|     }
 | |
| 
 | |
|     if (want_access & ~table->grant.privilege)
 | |
|     {
 | |
|       want_access &= ~table->grant.privilege;
 | |
|       goto err;
 | |
|     }
 | |
|   }
 | |
|   mysql_rwlock_unlock(&LOCK_grant);
 | |
|   DBUG_RETURN(0);
 | |
| err:
 | |
|   mysql_rwlock_unlock(&LOCK_grant);
 | |
|   if (!no_errors)
 | |
|   {
 | |
|     char buff[1024];
 | |
|     const char *command="";
 | |
|     if (table)
 | |
|       strxmov(buff, table->db.str, ".", table->table_name.str, NullS);
 | |
|     if (want_access & EXECUTE_ACL)
 | |
|       command= "execute";
 | |
|     else if (want_access & ALTER_PROC_ACL)
 | |
|       command= "alter routine";
 | |
|     else if (want_access & GRANT_ACL)
 | |
|       command= "grant";
 | |
|     my_error(ER_PROCACCESS_DENIED_ERROR, MYF(0),
 | |
|              command, user, host, table ? buff : "unknown");
 | |
|   }
 | |
|   DBUG_RETURN(1);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Check if routine has any of the
 | |
|   routine level grants
 | |
| 
 | |
|   SYNPOSIS
 | |
|    bool    check_routine_level_acl()
 | |
|    thd	        Thread handler
 | |
|    db           Database name
 | |
|    name         Routine name
 | |
| 
 | |
|   RETURN
 | |
|    0            Ok
 | |
|    1            error
 | |
| */
 | |
| 
 | |
| bool check_routine_level_acl(THD *thd, privilege_t acl,
 | |
|                              const char *db, const char *name,
 | |
|                              const Sp_handler *sph)
 | |
| {
 | |
|   bool no_routine_acl= 1;
 | |
|   GRANT_NAME *grant_proc;
 | |
|   Security_context *sctx= thd->security_ctx;
 | |
|   mysql_rwlock_rdlock(&LOCK_grant);
 | |
|   if ((grant_proc= routine_hash_search(sctx->priv_host,
 | |
|                                        sctx->ip, db,
 | |
|                                        sctx->priv_user,
 | |
|                                        name, sph, 0)))
 | |
|     no_routine_acl= !(grant_proc->privs & acl);
 | |
| 
 | |
|   if (no_routine_acl && sctx->priv_role[0]) /* current set role check */
 | |
|   {
 | |
|     if ((grant_proc= routine_hash_search("",
 | |
|                                          NULL, db,
 | |
|                                          sctx->priv_role,
 | |
|                                          name, sph, 0)))
 | |
|       no_routine_acl= !(grant_proc->privs & SHOW_PROC_WITHOUT_DEFINITION_ACLS);
 | |
|   }
 | |
|   mysql_rwlock_unlock(&LOCK_grant);
 | |
|   return no_routine_acl;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*****************************************************************************
 | |
|   Functions to retrieve the grant for a table/column  (for SHOW functions)
 | |
| *****************************************************************************/
 | |
| 
 | |
| privilege_t get_table_grant(THD *thd, TABLE_LIST *table)
 | |
| {
 | |
|   Security_context *sctx= thd->security_ctx;
 | |
|   const char *db = table->db.str ? table->db.str : thd->db.str;
 | |
| 
 | |
|   mysql_rwlock_rdlock(&LOCK_grant);
 | |
|   table->grant.read(sctx, db, table->table_name.str);
 | |
|   table->grant.privilege|= table->grant.aggregate_privs();
 | |
|   privilege_t privilege(table->grant.privilege);
 | |
|   mysql_rwlock_unlock(&LOCK_grant);
 | |
|   return privilege;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Determine the access priviliges for a field.
 | |
| 
 | |
|   SYNOPSIS
 | |
|     get_column_grant()
 | |
|     thd         thread handler
 | |
|     grant       grants table descriptor
 | |
|     db_name     name of database that the field belongs to
 | |
|     table_name  name of table that the field belongs to
 | |
|     field_name  name of field
 | |
| 
 | |
|   DESCRIPTION
 | |
|     The procedure may also modify: grant->grant_table and grant->version.
 | |
| 
 | |
|   RETURN
 | |
|     The access priviliges for the field db_name.table_name.field_name
 | |
| */
 | |
| 
 | |
| privilege_t get_column_grant(THD *thd, GRANT_INFO *grant,
 | |
|                         const char *db_name, const char *table_name,
 | |
|                         const Lex_ident_column &field_name)
 | |
| {
 | |
|   GRANT_TABLE *grant_table;
 | |
|   GRANT_TABLE *grant_table_role;
 | |
|   GRANT_TABLE *grant_public;
 | |
|   GRANT_COLUMN *grant_column;
 | |
|   privilege_t priv(NO_ACL);
 | |
| 
 | |
|   mysql_rwlock_rdlock(&LOCK_grant);
 | |
|   /* reload table if someone has modified any grants */
 | |
|   grant->refresh(thd->security_ctx, db_name, table_name);
 | |
| 
 | |
|   grant_table= grant->grant_table_user;
 | |
|   grant_table_role= grant->grant_table_role;
 | |
|   grant_public= grant->grant_public;
 | |
| 
 | |
|   if (!grant_table && !grant_table_role && !grant_public)
 | |
|     priv= grant->privilege;
 | |
|   else
 | |
|   {
 | |
|     if (grant_table)
 | |
|     {
 | |
|       grant_column= column_hash_search(grant_table, field_name);
 | |
|       if (!grant_column)
 | |
|         priv= (grant->privilege | grant_table->privs);
 | |
|       else
 | |
|         priv= (grant->privilege | grant_table->privs | grant_column->rights);
 | |
|     }
 | |
| 
 | |
|     if (grant_table_role)
 | |
|     {
 | |
|       grant_column= column_hash_search(grant_table_role, field_name);
 | |
|       if (!grant_column)
 | |
|         priv|= (grant->privilege | grant_table_role->privs);
 | |
|       else
 | |
|         priv|= (grant->privilege | grant_table_role->privs |
 | |
|                 grant_column->rights);
 | |
|     }
 | |
|     if (grant_public)
 | |
|     {
 | |
|       grant_column= column_hash_search(grant_public, field_name);
 | |
|       if (!grant_column)
 | |
|         priv|= (grant->privilege | grant_public->privs);
 | |
|       else
 | |
|         priv|= (grant->privilege | grant_public->privs |
 | |
|                 grant_column->rights);
 | |
|     }
 | |
|   }
 | |
|   mysql_rwlock_unlock(&LOCK_grant);
 | |
|   return priv;
 | |
| }
 | |
| 
 | |
| 
 | |
| /* Help function for mysql_show_grants */
 | |
| 
 | |
| static void add_user_option(String *grant, long value, const char *name,
 | |
|                             bool is_signed)
 | |
| {
 | |
|   if (value)
 | |
|   {
 | |
|     char buff[22], *p; // just as in int2str
 | |
|     grant->append(' ');
 | |
|     grant->append(name, strlen(name));
 | |
|     grant->append(' ');
 | |
|     p=int10_to_str(value, buff, is_signed ? -10 : 10);
 | |
|     grant->append(buff,p-buff);
 | |
|   }
 | |
| }
 | |
| 
 | |
| 
 | |
| static void add_user_option(String *grant, double value, const char *name)
 | |
| {
 | |
|   if (value != 0.0 )
 | |
|   {
 | |
|     char buff[FLOATING_POINT_BUFFER];
 | |
|     size_t len;
 | |
|     grant->append(' ');
 | |
|     grant->append(name, strlen(name));
 | |
|     grant->append(' ');
 | |
|     len= my_fcvt(value, 6, buff, NULL);
 | |
|     grant->append(buff, len);
 | |
|   }
 | |
| }
 | |
| 
 | |
| static void add_user_parameters(THD *thd, String *result, ACL_USER* acl_user,
 | |
|                                 bool with_grant)
 | |
| {
 | |
|   result->append('@');
 | |
|   append_identifier(thd, result, acl_user->host.hostname,
 | |
|                     acl_user->hostname_length);
 | |
| 
 | |
|   if (acl_user->nauth == 1 &&
 | |
|       (acl_user->auth->plugin.str == native_password_plugin_name.str ||
 | |
|        acl_user->auth->plugin.str == old_password_plugin_name.str))
 | |
|   {
 | |
|     if (acl_user->auth->auth_string.length)
 | |
|     {
 | |
|       result->append(STRING_WITH_LEN(" IDENTIFIED BY PASSWORD '"));
 | |
|       result->append(&acl_user->auth->auth_string);
 | |
|       result->append('\'');
 | |
|     }
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     result->append(STRING_WITH_LEN(" IDENTIFIED VIA "));
 | |
|     for (uint i=0; i < acl_user->nauth; i++)
 | |
|     {
 | |
|       if (i)
 | |
|         result->append(STRING_WITH_LEN(" OR "));
 | |
|       result->append(&acl_user->auth[i].plugin);
 | |
|       if (acl_user->auth[i].auth_string.length)
 | |
|       {
 | |
|         result->append(STRING_WITH_LEN(" USING '"));
 | |
|         result->append(&acl_user->auth[i].auth_string);
 | |
|         result->append('\'');
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   /* "show grants" SSL related stuff */
 | |
|   if (acl_user->ssl_type == SSL_TYPE_ANY)
 | |
|     result->append(STRING_WITH_LEN(" REQUIRE SSL"));
 | |
|   else if (acl_user->ssl_type == SSL_TYPE_X509)
 | |
|     result->append(STRING_WITH_LEN(" REQUIRE X509"));
 | |
|   else if (acl_user->ssl_type == SSL_TYPE_SPECIFIED)
 | |
|   {
 | |
|     int ssl_options = 0;
 | |
|     result->append(STRING_WITH_LEN(" REQUIRE "));
 | |
|     if (acl_user->x509_issuer[0])
 | |
|     {
 | |
|       ssl_options++;
 | |
|       result->append(STRING_WITH_LEN("ISSUER \'"));
 | |
|       result->append(acl_user->x509_issuer,strlen(acl_user->x509_issuer));
 | |
|       result->append('\'');
 | |
|     }
 | |
|     if (acl_user->x509_subject[0])
 | |
|     {
 | |
|       if (ssl_options++)
 | |
|         result->append(' ');
 | |
|       result->append(STRING_WITH_LEN("SUBJECT \'"));
 | |
|       result->append(acl_user->x509_subject,strlen(acl_user->x509_subject),
 | |
|                     system_charset_info);
 | |
|       result->append('\'');
 | |
|     }
 | |
|     if (acl_user->ssl_cipher)
 | |
|     {
 | |
|       if (ssl_options++)
 | |
|         result->append(' ');
 | |
|       result->append(STRING_WITH_LEN("CIPHER '"));
 | |
|       result->append(acl_user->ssl_cipher,strlen(acl_user->ssl_cipher),
 | |
|                     system_charset_info);
 | |
|       result->append('\'');
 | |
|     }
 | |
|   }
 | |
|   if (with_grant ||
 | |
|       (acl_user->user_resource.questions ||
 | |
|        acl_user->user_resource.updates ||
 | |
|        acl_user->user_resource.conn_per_hour ||
 | |
|        acl_user->user_resource.user_conn ||
 | |
|        acl_user->user_resource.max_statement_time != 0.0))
 | |
|   {
 | |
|     result->append(STRING_WITH_LEN(" WITH"));
 | |
|     if (with_grant)
 | |
|       result->append(STRING_WITH_LEN(" GRANT OPTION"));
 | |
|     add_user_option(result, acl_user->user_resource.questions,
 | |
|                     "MAX_QUERIES_PER_HOUR", false);
 | |
|     add_user_option(result, acl_user->user_resource.updates,
 | |
|                     "MAX_UPDATES_PER_HOUR", false);
 | |
|     add_user_option(result, acl_user->user_resource.conn_per_hour,
 | |
|                     "MAX_CONNECTIONS_PER_HOUR", false);
 | |
|     add_user_option(result, acl_user->user_resource.user_conn,
 | |
|                     "MAX_USER_CONNECTIONS", true);
 | |
|     add_user_option(result, acl_user->user_resource.max_statement_time,
 | |
|                     "MAX_STATEMENT_TIME");
 | |
|   }
 | |
| }
 | |
| 
 | |
| static const char *command_array[]=
 | |
| {
 | |
|   "SELECT", "INSERT", "UPDATE", "DELETE", "CREATE", "DROP", "RELOAD",
 | |
|   "SHUTDOWN", "PROCESS","FILE", "GRANT", "REFERENCES", "INDEX",
 | |
|   "ALTER", "SHOW DATABASES", "SUPER", "CREATE TEMPORARY TABLES",
 | |
|   "LOCK TABLES", "EXECUTE", "REPLICATION SLAVE", "BINLOG MONITOR",
 | |
|   "CREATE VIEW", "SHOW VIEW", "CREATE ROUTINE", "ALTER ROUTINE",
 | |
|   "CREATE USER", "EVENT", "TRIGGER", "CREATE TABLESPACE", "DELETE HISTORY",
 | |
|   "SET USER", "FEDERATED ADMIN", "CONNECTION ADMIN", "READ_ONLY ADMIN",
 | |
|   "REPLICATION SLAVE ADMIN", "REPLICATION MASTER ADMIN", "BINLOG ADMIN",
 | |
|   "BINLOG REPLAY", "SLAVE MONITOR", "SHOW CREATE ROUTINE"
 | |
| };
 | |
| 
 | |
| static uint command_lengths[]=
 | |
| {
 | |
|   6, 6, 6, 6, 6, 4, 6,
 | |
|   8, 7, 4, 5, 10, 5,
 | |
|   5, 14, 5, 23,
 | |
|   11, 7, 17, 14,
 | |
|   11, 9, 14, 13,
 | |
|   11, 5, 7, 17, 14,
 | |
|   8, 15, 16, 15,
 | |
|   23, 24, 12,
 | |
|   13, 13, 19
 | |
| };
 | |
| 
 | |
| 
 | |
| static_assert(array_elements(command_array) == PRIVILEGE_T_MAX_BIT + 1,
 | |
|               "The definition of command_array does not match privilege_t");
 | |
| static_assert(array_elements(command_lengths) == PRIVILEGE_T_MAX_BIT + 1,
 | |
|               "The definition of command_lengths does not match privilege_t");
 | |
| 
 | |
| 
 | |
| static bool print_grants_for_role(THD *thd, ACL_ROLE * role)
 | |
| {
 | |
|   char buff[1024];
 | |
| 
 | |
|   if (show_role_grants(thd, "", role, buff, sizeof(buff)))
 | |
|     return TRUE;
 | |
| 
 | |
|   if (show_global_privileges(thd, role, TRUE, buff, sizeof(buff)))
 | |
|     return TRUE;
 | |
| 
 | |
|   if (show_database_privileges(thd, role->user.str, "", buff, sizeof(buff)))
 | |
|     return TRUE;
 | |
| 
 | |
|   if (show_table_and_column_privileges(thd, role->user.str, "", buff, sizeof(buff)))
 | |
|     return TRUE;
 | |
| 
 | |
|   if (show_routine_grants(thd, role->user.str, "", &sp_handler_procedure,
 | |
|                           buff, sizeof(buff)))
 | |
|     return TRUE;
 | |
| 
 | |
|   if (show_routine_grants(thd, role->user.str, "", &sp_handler_function,
 | |
|                           buff, sizeof(buff)))
 | |
|     return TRUE;
 | |
| 
 | |
|   if (show_routine_grants(thd, role->user.str, "", &sp_handler_package_spec,
 | |
|                           buff, sizeof(buff)))
 | |
|     return TRUE;
 | |
| 
 | |
|   if (show_routine_grants(thd, role->user.str, "", &sp_handler_package_body,
 | |
|                           buff, sizeof(buff)))
 | |
|     return TRUE;
 | |
| 
 | |
|   return FALSE;
 | |
| 
 | |
| }
 | |
| 
 | |
| static void append_auto_expiration_policy(ACL_USER *acl_user, String *r) {
 | |
|     if (!acl_user->password_lifetime)
 | |
|       r->append(STRING_WITH_LEN(" PASSWORD EXPIRE NEVER"));
 | |
|     else if (acl_user->password_lifetime > 0)
 | |
|     {
 | |
|       r->append(STRING_WITH_LEN(" PASSWORD EXPIRE INTERVAL "));
 | |
|       r->append_longlong(acl_user->password_lifetime);
 | |
|       r->append(STRING_WITH_LEN(" DAY"));
 | |
|     }
 | |
| }
 | |
| 
 | |
| bool mysql_show_create_user(THD *thd, LEX_USER *lex_user)
 | |
| {
 | |
|   const char *username= NULL, *hostname= NULL;
 | |
|   char buff[1024]; //Show create user should not take more than 1024 bytes.
 | |
|   Protocol *protocol= thd->protocol;
 | |
|   bool error= false;
 | |
|   ACL_USER *acl_user;
 | |
|   uint head_length;
 | |
|   DBUG_ENTER("mysql_show_create_user");
 | |
| 
 | |
|   if (!initialized)
 | |
|   {
 | |
|     my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--skip-grant-tables");
 | |
|     DBUG_RETURN(TRUE);
 | |
|   }
 | |
|   if (get_show_user(thd, lex_user, &username, &hostname, NULL))
 | |
|     DBUG_RETURN(TRUE);
 | |
| 
 | |
|   List<Item> field_list;
 | |
|   head_length= (uint) (strxmov(buff, "CREATE USER for ", username, "@",
 | |
|                                hostname, NullS) - buff);
 | |
|   Item_string *field = new (thd->mem_root) Item_string_ascii(thd, "", 0);
 | |
|   if (!field)
 | |
|     DBUG_RETURN(true);                          // Error given my my_alloc()
 | |
| 
 | |
|   field->name.str= buff;
 | |
|   field->name.length= head_length;
 | |
|   field->max_length= sizeof(buff);
 | |
|   field_list.push_back(field, thd->mem_root);
 | |
|   if (protocol->send_result_set_metadata(&field_list,
 | |
|                                          Protocol::SEND_NUM_ROWS |
 | |
|                                          Protocol::SEND_EOF))
 | |
|     DBUG_RETURN(true);
 | |
| 
 | |
|   String result(buff, sizeof(buff), system_charset_info);
 | |
|   result.length(0);
 | |
|   mysql_rwlock_rdlock(&LOCK_grant);
 | |
|   mysql_mutex_lock(&acl_cache->lock);
 | |
| 
 | |
|   acl_user= find_user_exact(Lex_cstring_strlen(hostname),
 | |
|                             Lex_cstring_strlen(username));
 | |
| 
 | |
|   // User not found in the internal data structures.
 | |
|   if (!acl_user)
 | |
|   {
 | |
|     my_error(ER_PASSWORD_NO_MATCH, MYF(0));
 | |
|     error= true;
 | |
|     goto end;
 | |
|   }
 | |
| 
 | |
|   result.append(STRING_WITH_LEN("CREATE USER "));
 | |
|   append_identifier(thd, &result, username, strlen(username));
 | |
|   add_user_parameters(thd, &result, acl_user, false);
 | |
| 
 | |
|   if (acl_user->account_locked)
 | |
|     result.append(STRING_WITH_LEN(" ACCOUNT LOCK"));
 | |
| 
 | |
|   if (acl_user->password_expired)
 | |
|     result.append(STRING_WITH_LEN(" PASSWORD EXPIRE"));
 | |
|   else
 | |
|     append_auto_expiration_policy(acl_user, &result);
 | |
| 
 | |
|   protocol->prepare_for_resend();
 | |
|   protocol->store(result.ptr(), result.length(), result.charset());
 | |
|   if (protocol->write())
 | |
|   {
 | |
|     error= true;
 | |
|   }
 | |
| 
 | |
|   /* MDEV-24114 - PASSWORD EXPIRE and PASSWORD EXPIRE [NEVER | INTERVAL X DAY]
 | |
|    are two different mechanisms. To make sure a tool can restore the state
 | |
|    of a user account, including both the manual expiration state of the
 | |
|    account and the automatic expiration policy attached to it, we should
 | |
|    print two statements here, a CREATE USER (printed above) and an ALTER USER */
 | |
|   if (acl_user->password_expired && acl_user->password_lifetime > -1)
 | |
|   {
 | |
|     result.length(0);
 | |
|     result.append(STRING_WITH_LEN("ALTER USER "));
 | |
|     append_identifier(thd, &result, username, strlen(username));
 | |
|     result.append('@');
 | |
|     append_identifier(thd, &result, acl_user->host.hostname,
 | |
|                       acl_user->hostname_length);
 | |
|     append_auto_expiration_policy(acl_user, &result);
 | |
|     protocol->prepare_for_resend();
 | |
|     protocol->store(result.ptr(), result.length(), result.charset());
 | |
|     if (protocol->write())
 | |
|     {
 | |
|       error= true;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   my_eof(thd);
 | |
| 
 | |
| end:
 | |
|   mysql_rwlock_unlock(&LOCK_grant);
 | |
|   mysql_mutex_unlock(&acl_cache->lock);
 | |
| 
 | |
|   DBUG_RETURN(error);
 | |
| }
 | |
| 
 | |
| 
 | |
| static int show_grants_callback(ACL_USER_BASE *role, void *data)
 | |
| {
 | |
|   THD *thd= (THD *)data;
 | |
|   DBUG_ASSERT(role->flags & IS_ROLE);
 | |
|   if (print_grants_for_role(thd, (ACL_ROLE *)role))
 | |
|     return -1;
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| void mysql_show_grants_get_fields(THD *thd, List<Item> *fields,
 | |
|                                   const char *name, size_t length)
 | |
| {
 | |
|   Item_string *field=new (thd->mem_root) Item_string_ascii(thd, "", 0);
 | |
|   /* Set name explicit to avoid character set conversions */
 | |
|   field->name.str= name;
 | |
|   field->name.length= length;
 | |
|   field->max_length=1024;
 | |
|   fields->push_back(field, thd->mem_root);
 | |
| }
 | |
| 
 | |
| /** checks privileges for SHOW GRANTS and SHOW CREATE USER
 | |
| 
 | |
|   @note that in case of SHOW CREATE USER the parser guarantees
 | |
|   that a role can never happen here, so *rolename will never
 | |
|   be assigned to
 | |
| */
 | |
| bool get_show_user(THD *thd, LEX_USER *lex_user, const char **username,
 | |
|                    const char **hostname, const char **rolename)
 | |
| {
 | |
|   if (lex_user->user.str == current_user.str)
 | |
|   {
 | |
|     *username= thd->security_ctx->priv_user;
 | |
|     *hostname= thd->security_ctx->priv_host;
 | |
|     return 0;
 | |
|   }
 | |
|   if (lex_user->user.str == current_role.str)
 | |
|   {
 | |
|     *rolename= thd->security_ctx->priv_role;
 | |
|     return 0;
 | |
|   }
 | |
|   if (lex_user->user.str == current_user_and_current_role.str)
 | |
|   {
 | |
|     *username= thd->security_ctx->priv_user;
 | |
|     *hostname= thd->security_ctx->priv_host;
 | |
|     *rolename= thd->security_ctx->priv_role;
 | |
|     return 0;
 | |
|   }
 | |
| 
 | |
|   Security_context *sctx= thd->security_ctx;
 | |
|   bool do_check_access;
 | |
| 
 | |
|   if (!(lex_user= get_current_user(thd, lex_user)))
 | |
|     return 1;
 | |
| 
 | |
|   if (lex_user->is_role())
 | |
|   {
 | |
|     *rolename= lex_user->user.str;
 | |
|     do_check_access= !is_public(lex_user) && strcmp(*rolename, sctx->priv_role);
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     *username= lex_user->user.str;
 | |
|     *hostname= lex_user->host.str;
 | |
|     do_check_access= strcmp(*username, sctx->priv_user) ||
 | |
|                      strcmp(*hostname, sctx->priv_host);
 | |
|   }
 | |
| 
 | |
|   if (do_check_access && check_access(thd, SELECT_ACL, "mysql", 0, 0, 1, 0))
 | |
|     return 1;
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| /*
 | |
|   SHOW GRANTS;  Send grants for a user to the client
 | |
| 
 | |
|   IMPLEMENTATION
 | |
|    Send to client grant-like strings depicting user@host privileges
 | |
| */
 | |
| 
 | |
| bool mysql_show_grants(THD *thd, LEX_USER *lex_user)
 | |
| {
 | |
|   int  error = -1;
 | |
|   ACL_USER *UNINIT_VAR(acl_user);
 | |
|   ACL_ROLE *acl_role= NULL;
 | |
|   char buff[1024];
 | |
|   Protocol *protocol= thd->protocol;
 | |
|   const char *username= NULL, *hostname= NULL, *rolename= NULL, *end;
 | |
|   DBUG_ENTER("mysql_show_grants");
 | |
| 
 | |
|   if (!initialized)
 | |
|   {
 | |
|     my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--skip-grant-tables");
 | |
|     DBUG_RETURN(TRUE);
 | |
|   }
 | |
| 
 | |
|   if (get_show_user(thd, lex_user, &username, &hostname, &rolename))
 | |
|     DBUG_RETURN(TRUE);
 | |
| 
 | |
|   DBUG_ASSERT(rolename || username);
 | |
| 
 | |
|   List<Item> field_list;
 | |
|   if (username)
 | |
|     end= strxmov(buff,"Grants for ",username,"@",hostname, NullS);
 | |
|   else
 | |
|     end= strxmov(buff,"Grants for ",rolename, NullS);
 | |
| 
 | |
|   mysql_show_grants_get_fields(thd, &field_list, buff, (uint) (end-buff));
 | |
| 
 | |
|   if (protocol->send_result_set_metadata(&field_list,
 | |
|                                Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF))
 | |
|     DBUG_RETURN(TRUE);
 | |
| 
 | |
|   mysql_rwlock_rdlock(&LOCK_grant);
 | |
|   mysql_mutex_lock(&acl_cache->lock);
 | |
| 
 | |
|   if (username)
 | |
|   {
 | |
|     acl_user= find_user_exact(Lex_cstring_strlen(hostname),
 | |
|                               Lex_cstring_strlen(username));
 | |
|     if (!acl_user)
 | |
|     {
 | |
|       mysql_mutex_unlock(&acl_cache->lock);
 | |
|       mysql_rwlock_unlock(&LOCK_grant);
 | |
| 
 | |
|       my_error(ER_NONEXISTING_GRANT, MYF(0),
 | |
|                username, hostname);
 | |
|       DBUG_RETURN(TRUE);
 | |
|     }
 | |
| 
 | |
|     /* Show granted roles to acl_user */
 | |
|     if (show_role_grants(thd, hostname, acl_user, buff, sizeof(buff)))
 | |
|       goto end;
 | |
| 
 | |
|     /* Add first global access grants */
 | |
|     if (show_global_privileges(thd, acl_user, FALSE, buff, sizeof(buff)))
 | |
|       goto end;
 | |
| 
 | |
|     /* Add database access */
 | |
|     if (show_database_privileges(thd, username, hostname, buff, sizeof(buff)))
 | |
|       goto end;
 | |
| 
 | |
|     /* Add table & column access */
 | |
|     if (show_table_and_column_privileges(thd, username, hostname, buff, sizeof(buff)))
 | |
|       goto end;
 | |
| 
 | |
|     if (show_routine_grants(thd, username, hostname, &sp_handler_procedure,
 | |
|                             buff, sizeof(buff)))
 | |
|       goto end;
 | |
| 
 | |
|     if (show_routine_grants(thd, username, hostname, &sp_handler_function,
 | |
|                             buff, sizeof(buff)))
 | |
|       goto end;
 | |
| 
 | |
|     if (show_routine_grants(thd, username, hostname, &sp_handler_package_spec,
 | |
|                             buff, sizeof(buff)))
 | |
|       goto end;
 | |
| 
 | |
|     if (show_routine_grants(thd, username, hostname, &sp_handler_package_body,
 | |
|                             buff, sizeof(buff)))
 | |
|       goto end;
 | |
| 
 | |
|     if (show_proxy_grants(thd, username, hostname, buff, sizeof(buff)))
 | |
|       goto end;
 | |
|   }
 | |
| 
 | |
|   if (rolename)
 | |
|   {
 | |
|     acl_role= find_acl_role(Lex_cstring_strlen(rolename), true);
 | |
|     if (acl_role)
 | |
|     {
 | |
|       /* get a list of all inherited roles */
 | |
|       traverse_role_graph_down(acl_role, thd, show_grants_callback, NULL);
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       if (lex_user->user.str == current_role.str)
 | |
|       {
 | |
|         mysql_mutex_unlock(&acl_cache->lock);
 | |
|         mysql_rwlock_unlock(&LOCK_grant);
 | |
|         my_error(ER_NONEXISTING_GRANT, MYF(0),
 | |
|                  thd->security_ctx->priv_user,
 | |
|                  thd->security_ctx->priv_host);
 | |
|         DBUG_RETURN(TRUE);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (username && rolename) // show everything, incl. PUBLIC
 | |
|   {
 | |
|     if (acl_public)
 | |
|       traverse_role_graph_down(acl_public, thd, show_grants_callback, NULL);
 | |
|   }
 | |
| 
 | |
|   if (username)
 | |
|   {
 | |
|     /* Show default role to acl_user */
 | |
|     if (show_default_role(thd, acl_user, buff, sizeof(buff)))
 | |
|       goto end;
 | |
|   }
 | |
| 
 | |
| 
 | |
|   error= 0;
 | |
| end:
 | |
|   mysql_mutex_unlock(&acl_cache->lock);
 | |
|   mysql_rwlock_unlock(&LOCK_grant);
 | |
| 
 | |
|   my_eof(thd);
 | |
|   DBUG_RETURN(error);
 | |
| }
 | |
| 
 | |
| static ROLE_GRANT_PAIR *find_role_grant_pair(const LEX_CSTRING *u,
 | |
|                                              const LEX_CSTRING *h,
 | |
|                                              const LEX_CSTRING *r)
 | |
| {
 | |
|   char buf[1024];
 | |
|   String pair_key(buf, sizeof(buf), &my_charset_bin);
 | |
| 
 | |
|   size_t key_length= u->length + h->length + r->length + 3;
 | |
|   pair_key.alloc(key_length);
 | |
| 
 | |
|   strmov(strmov(strmov(const_cast<char*>(pair_key.ptr()),
 | |
|                        safe_str(u->str)) + 1, h->str) + 1, r->str);
 | |
| 
 | |
|   return (ROLE_GRANT_PAIR *)
 | |
|     my_hash_search(&acl_roles_mappings, (uchar*)pair_key.ptr(), key_length);
 | |
| }
 | |
| 
 | |
| static bool show_default_role(THD *thd, ACL_USER *acl_entry,
 | |
|                               char *buff, size_t buffsize)
 | |
| {
 | |
|   Protocol *protocol= thd->protocol;
 | |
|   LEX_CSTRING def_rolename= acl_entry->default_rolename;
 | |
| 
 | |
|   if (def_rolename.length)
 | |
|   {
 | |
|     String def_str(buff, buffsize, system_charset_info);
 | |
|     def_str.length(0);
 | |
|     def_str.append(STRING_WITH_LEN("SET DEFAULT ROLE "));
 | |
|     append_identifier(thd, &def_str, def_rolename.str, def_rolename.length);
 | |
|     def_str.append(STRING_WITH_LEN(" FOR "));
 | |
|     append_identifier(thd, &def_str, acl_entry->user.str, acl_entry->user.length);
 | |
|     DBUG_ASSERT(!(acl_entry->flags & IS_ROLE));
 | |
|     def_str.append('@');
 | |
|     append_identifier(thd, &def_str, acl_entry->host.hostname,
 | |
|                       acl_entry->hostname_length);
 | |
|     protocol->prepare_for_resend();
 | |
|     protocol->store(def_str.ptr(),def_str.length(),def_str.charset());
 | |
|     if (protocol->write())
 | |
|     {
 | |
|       return TRUE;
 | |
|     }
 | |
|   }
 | |
|   return FALSE;
 | |
| }
 | |
| 
 | |
| static bool show_role_grants(THD *thd, const char *hostname,
 | |
|                              ACL_USER_BASE *acl_entry,
 | |
|                              char *buff, size_t buffsize)
 | |
| {
 | |
|   size_t counter;
 | |
|   Protocol *protocol= thd->protocol;
 | |
|   LEX_CSTRING host= {const_cast<char*>(hostname), strlen(hostname)};
 | |
| 
 | |
|   String grant(buff, buffsize, system_charset_info);
 | |
|   for (counter= 0; counter < acl_entry->role_grants.elements; counter++)
 | |
|   {
 | |
|     grant.length(0);
 | |
|     grant.append(STRING_WITH_LEN("GRANT "));
 | |
|     ACL_ROLE *acl_role= *(dynamic_element(&acl_entry->role_grants, counter,
 | |
|                                           ACL_ROLE**));
 | |
|     append_identifier(thd, &grant, acl_role->user.str, acl_role->user.length);
 | |
|     grant.append(STRING_WITH_LEN(" TO "));
 | |
|     if (acl_entry == acl_public)
 | |
|       grant.append(public_name);
 | |
|     else
 | |
|       append_identifier(thd, &grant, acl_entry->user.str, acl_entry->user.length);
 | |
|     if (!(acl_entry->flags & IS_ROLE))
 | |
|     {
 | |
|       grant.append('@');
 | |
|       append_identifier(thd, &grant, host.str, host.length);
 | |
|     }
 | |
| 
 | |
|     ROLE_GRANT_PAIR *pair=
 | |
|       find_role_grant_pair(&acl_entry->user, &host, &acl_role->user);
 | |
|     DBUG_ASSERT(pair);
 | |
| 
 | |
|     if (pair->with_admin)
 | |
|       grant.append(STRING_WITH_LEN(" WITH ADMIN OPTION"));
 | |
| 
 | |
|     protocol->prepare_for_resend();
 | |
|     protocol->store(grant.ptr(),grant.length(),grant.charset());
 | |
|     if (protocol->write())
 | |
|     {
 | |
|       return TRUE;
 | |
|     }
 | |
|   }
 | |
|   return FALSE;
 | |
| }
 | |
| 
 | |
| static bool show_global_privileges(THD *thd, ACL_USER_BASE *acl_entry,
 | |
|                                    bool handle_as_role,
 | |
|                                    char *buff, size_t buffsize)
 | |
| {
 | |
|   uint counter;
 | |
|   privilege_t want_access(NO_ACL);
 | |
|   Protocol *protocol= thd->protocol;
 | |
| 
 | |
|   String global(buff, buffsize, system_charset_info);
 | |
|   global.length(0);
 | |
|   global.append(STRING_WITH_LEN("GRANT "));
 | |
| 
 | |
|   if (handle_as_role)
 | |
|     want_access= ((ACL_ROLE *)acl_entry)->initial_role_access;
 | |
|   else
 | |
|     want_access= acl_entry->access;
 | |
| 
 | |
|   // suppress "GRANT USAGE ON *.* TO `PUBLIC`"
 | |
|   if (!(want_access & ~GRANT_ACL) && acl_entry == acl_public)
 | |
|     return FALSE;
 | |
| 
 | |
|   if (test_all_bits(want_access, (GLOBAL_ACLS & ~ GRANT_ACL)))
 | |
|     global.append(STRING_WITH_LEN("ALL PRIVILEGES"));
 | |
|   else if (!(want_access & ~GRANT_ACL))
 | |
|     global.append(STRING_WITH_LEN("USAGE"));
 | |
|   else
 | |
|   {
 | |
|     bool found=0;
 | |
|     ulonglong j;
 | |
|     privilege_t test_access(want_access & ~GRANT_ACL);
 | |
|     for (counter=0, j = SELECT_ACL;j <= GLOBAL_ACLS;counter++,j <<= 1)
 | |
|     {
 | |
|       if (test_access & j)
 | |
|       {
 | |
|         if (found)
 | |
|           global.append(STRING_WITH_LEN(", "));
 | |
|         found=1;
 | |
|         global.append(command_array[counter],command_lengths[counter]);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   global.append (STRING_WITH_LEN(" ON *.* TO "));
 | |
|   if (acl_entry == acl_public)
 | |
|     global.append(public_name);
 | |
|   else
 | |
|     append_identifier(thd, &global, acl_entry->user.str, acl_entry->user.length);
 | |
| 
 | |
|   if (!handle_as_role)
 | |
|     add_user_parameters(thd, &global, (ACL_USER *)acl_entry,
 | |
|                         (want_access & GRANT_ACL));
 | |
| 
 | |
|   else if (want_access & GRANT_ACL)
 | |
|     global.append(STRING_WITH_LEN(" WITH GRANT OPTION"));
 | |
|   protocol->prepare_for_resend();
 | |
|   protocol->store(global.ptr(),global.length(),global.charset());
 | |
|   if (protocol->write())
 | |
|     return TRUE;
 | |
| 
 | |
|   return FALSE;
 | |
| 
 | |
| }
 | |
| 
 | |
| 
 | |
| static void add_to_user(THD *thd, String *result, const char *user,
 | |
|                         bool is_user, const char *host)
 | |
| {
 | |
|   result->append(STRING_WITH_LEN(" TO "));
 | |
|   if (is_public(user))
 | |
|     result->append(public_name);
 | |
|   else
 | |
|     append_identifier(thd, result, user, strlen(user));
 | |
|   if (is_user)
 | |
|   {
 | |
|     result->append('@');
 | |
|     // host and lex_user->host are equal except for case
 | |
|     append_identifier(thd, result, host, strlen(host));
 | |
|   }
 | |
| }
 | |
| 
 | |
| 
 | |
| static bool show_database_privileges(THD *thd, const char *username,
 | |
|                                      const char *hostname,
 | |
|                                      char *buff, size_t buffsize)
 | |
| {
 | |
|   privilege_t want_access(NO_ACL);
 | |
|   Protocol *protocol= thd->protocol;
 | |
| 
 | |
|   for (size_t i=0 ; i < acl_dbs.elements() ; i++)
 | |
|   {
 | |
|     const char *user, *host;
 | |
| 
 | |
|     ACL_DB *acl_db= &acl_dbs.at(i);
 | |
|     user= acl_db->user;
 | |
|     host=acl_db->host.hostname;
 | |
| 
 | |
|     /*
 | |
|       We do not make SHOW GRANTS case-sensitive here (like REVOKE),
 | |
|       but make it case-insensitive because that's the way they are
 | |
|       actually applied, and showing fewer privileges than are applied
 | |
|       would be wrong from a security point of view.
 | |
|     */
 | |
| 
 | |
|     if (!strcmp(username, user) &&
 | |
|         Lex_ident_host(Lex_cstring_strlen(hostname)).
 | |
|           streq(Lex_cstring_strlen(host)))
 | |
|     {
 | |
|       /*
 | |
|         do not print inherited access bits for roles,
 | |
|         the role bits present in the table are what matters
 | |
|       */
 | |
|       if (*hostname) // User
 | |
|         want_access=acl_db->access;
 | |
|       else // Role
 | |
|         want_access=acl_db->initial_access;
 | |
|       if (want_access)
 | |
|       {
 | |
|         String db(buff, buffsize, system_charset_info);
 | |
|         db.length(0);
 | |
|         db.append(STRING_WITH_LEN("GRANT "));
 | |
| 
 | |
|         if (test_all_bits(want_access,(DB_ACLS & ~GRANT_ACL)))
 | |
|           db.append(STRING_WITH_LEN("ALL PRIVILEGES"));
 | |
|         else if (!(want_access & ~GRANT_ACL))
 | |
|           db.append(STRING_WITH_LEN("USAGE"));
 | |
|         else
 | |
|         {
 | |
|           int found=0, cnt;
 | |
|           ulonglong j;
 | |
|           privilege_t test_access(want_access & ~GRANT_ACL);
 | |
|           for (cnt=0, j = SELECT_ACL; j <= DB_ACLS; cnt++,j <<= 1)
 | |
|           {
 | |
|             if (test_access & j)
 | |
|             {
 | |
|               if (found)
 | |
|                 db.append(STRING_WITH_LEN(", "));
 | |
|               found = 1;
 | |
|               db.append(command_array[cnt],command_lengths[cnt]);
 | |
|             }
 | |
|           }
 | |
|         }
 | |
|         db.append (STRING_WITH_LEN(" ON "));
 | |
|         append_identifier(thd, &db, acl_db->db, strlen(acl_db->db));
 | |
|         db.append (STRING_WITH_LEN(".*"));
 | |
|         add_to_user(thd, &db, username, (*hostname), host);
 | |
|         if (want_access & GRANT_ACL)
 | |
|           db.append(STRING_WITH_LEN(" WITH GRANT OPTION"));
 | |
|         protocol->prepare_for_resend();
 | |
|         protocol->store(db.ptr(),db.length(),db.charset());
 | |
|         if (protocol->write())
 | |
|         {
 | |
|           return TRUE;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   return FALSE;
 | |
| 
 | |
| }
 | |
| 
 | |
| static bool show_table_and_column_privileges(THD *thd, const char *username,
 | |
|                                              const char *hostname,
 | |
|                                              char *buff, size_t buffsize)
 | |
| {
 | |
|   uint counter, index;
 | |
|   Protocol *protocol= thd->protocol;
 | |
| 
 | |
|   for (index=0 ; index < column_priv_hash.records ; index++)
 | |
|   {
 | |
|     const char *user, *host;
 | |
|     GRANT_TABLE *grant_table= (GRANT_TABLE*)
 | |
|       my_hash_element(&column_priv_hash, index);
 | |
| 
 | |
|     user= grant_table->user;
 | |
|     host= grant_table->host.hostname;
 | |
| 
 | |
|     /*
 | |
|       We do not make SHOW GRANTS case-sensitive here (like REVOKE),
 | |
|       but make it case-insensitive because that's the way they are
 | |
|       actually applied, and showing fewer privileges than are applied
 | |
|       would be wrong from a security point of view.
 | |
|     */
 | |
| 
 | |
|     if (!strcmp(username,user) &&
 | |
|         Lex_ident_host(Lex_cstring_strlen(hostname)).
 | |
|           streq(Lex_cstring_strlen(host)))
 | |
|     {
 | |
|       privilege_t table_access(NO_ACL);
 | |
|       privilege_t cols_access(NO_ACL);
 | |
|       if (*hostname) // User
 | |
|       {
 | |
|         table_access= grant_table->privs;
 | |
|         cols_access= grant_table->cols;
 | |
|       }
 | |
|       else // Role
 | |
|       {
 | |
|         table_access= grant_table->init_privs;
 | |
|         cols_access= grant_table->init_cols;
 | |
|       }
 | |
| 
 | |
|       if ((table_access | cols_access) != NO_ACL)
 | |
|       {
 | |
|         String global(buff, sizeof(buff), system_charset_info);
 | |
|         privilege_t test_access= (table_access | cols_access) & ~GRANT_ACL;
 | |
| 
 | |
|         global.length(0);
 | |
|         global.append(STRING_WITH_LEN("GRANT "));
 | |
| 
 | |
|         if (test_all_bits(table_access, (TABLE_ACLS & ~GRANT_ACL)))
 | |
|           global.append(STRING_WITH_LEN("ALL PRIVILEGES"));
 | |
|         else if (!test_access)
 | |
|           global.append(STRING_WITH_LEN("USAGE"));
 | |
|         else
 | |
|         {
 | |
|           /* Add specific column access */
 | |
|           int found= 0;
 | |
|           ulonglong j;
 | |
| 
 | |
|           for (counter= 0, j= SELECT_ACL; j <= TABLE_ACLS; counter++, j<<= 1)
 | |
|           {
 | |
|             if (test_access & j)
 | |
|             {
 | |
|               if (found)
 | |
|                 global.append(STRING_WITH_LEN(", "));
 | |
|               found= 1;
 | |
|               global.append(command_array[counter],command_lengths[counter]);
 | |
| 
 | |
|               if (grant_table->cols)
 | |
|               {
 | |
|                 uint found_col= 0;
 | |
|                 HASH *hash_columns;
 | |
|                 hash_columns= &grant_table->hash_columns;
 | |
| 
 | |
|                 for (uint col_index=0 ;
 | |
|                      col_index < hash_columns->records ;
 | |
|                      col_index++)
 | |
|                 {
 | |
|                   GRANT_COLUMN *grant_column = (GRANT_COLUMN*)
 | |
|                     my_hash_element(hash_columns,col_index);
 | |
|                   if (j & (*hostname ? grant_column->rights         // User
 | |
|                                      : grant_column->init_rights))  // Role
 | |
|                   {
 | |
|                     if (!found_col)
 | |
|                     {
 | |
|                       found_col= 1;
 | |
|                       /*
 | |
|                         If we have a duplicated table level privilege, we
 | |
|                         must write the access privilege name again.
 | |
|                       */
 | |
|                       if (table_access & j)
 | |
|                       {
 | |
|                         global.append(STRING_WITH_LEN(", "));
 | |
|                         global.append(command_array[counter],
 | |
|                                       command_lengths[counter]);
 | |
|                       }
 | |
|                       global.append(STRING_WITH_LEN(" ("));
 | |
|                     }
 | |
|                     else
 | |
|                       global.append(STRING_WITH_LEN(", "));
 | |
|                     append_identifier(thd, &global, grant_column->column,
 | |
|                                       grant_column->key_length);
 | |
|                   }
 | |
|                 }
 | |
|                 if (found_col)
 | |
|                   global.append(')');
 | |
|               }
 | |
|             }
 | |
|           }
 | |
|         }
 | |
|         global.append(STRING_WITH_LEN(" ON "));
 | |
|         append_identifier(thd, &global, grant_table->db,
 | |
|                           strlen(grant_table->db));
 | |
|         global.append('.');
 | |
|         append_identifier(thd, &global, grant_table->tname,
 | |
|                           strlen(grant_table->tname));
 | |
|         add_to_user(thd, &global, username, (*hostname), host);
 | |
|         if (table_access & GRANT_ACL)
 | |
|           global.append(STRING_WITH_LEN(" WITH GRANT OPTION"));
 | |
|         protocol->prepare_for_resend();
 | |
|         protocol->store(global.ptr(),global.length(),global.charset());
 | |
|         if (protocol->write())
 | |
|         {
 | |
|           return TRUE;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   return FALSE;
 | |
| 
 | |
| }
 | |
| 
 | |
| static int show_routine_grants(THD* thd, const char *username,
 | |
|                                const char *hostname, const Sp_handler *sph,
 | |
|                                char *buff, int buffsize)
 | |
| {
 | |
|   uint counter, index;
 | |
|   int error= 0;
 | |
|   Protocol *protocol= thd->protocol;
 | |
|   HASH *hash= sph->get_priv_hash();
 | |
|   /* Add routine access */
 | |
|   for (index=0 ; index < hash->records ; index++)
 | |
|   {
 | |
|     const char *user, *host;
 | |
|     GRANT_NAME *grant_proc= (GRANT_NAME*) my_hash_element(hash, index);
 | |
| 
 | |
|     user= grant_proc->user;
 | |
|     host= grant_proc->host.hostname;
 | |
| 
 | |
|     /*
 | |
|       We do not make SHOW GRANTS case-sensitive here (like REVOKE),
 | |
|       but make it case-insensitive because that's the way they are
 | |
|       actually applied, and showing fewer privileges than are applied
 | |
|       would be wrong from a security point of view.
 | |
|     */
 | |
| 
 | |
|     if (!strcmp(username, user) &&
 | |
|         Lex_ident_host(Lex_cstring_strlen(hostname)).
 | |
|           streq(Lex_cstring_strlen(host)))
 | |
|     {
 | |
|       privilege_t proc_access(NO_ACL);
 | |
|       if (*hostname) // User
 | |
|         proc_access= grant_proc->privs;
 | |
|       else // Role
 | |
|         proc_access= grant_proc->init_privs;
 | |
| 
 | |
|       if (proc_access != NO_ACL)
 | |
|       {
 | |
| 	String global(buff, buffsize, system_charset_info);
 | |
| 	privilege_t test_access(proc_access & ~GRANT_ACL);
 | |
| 
 | |
| 	global.length(0);
 | |
| 	global.append(STRING_WITH_LEN("GRANT "));
 | |
| 
 | |
| 	if (!test_access)
 | |
|  	  global.append(STRING_WITH_LEN("USAGE"));
 | |
| 	else
 | |
| 	{
 | |
|           /* Add specific procedure access */
 | |
| 	  int found= 0;
 | |
| 	  ulonglong j;
 | |
| 
 | |
| 	  for (counter= 0, j= SELECT_ACL; j <= PROC_ACLS; counter++, j<<= 1)
 | |
| 	  {
 | |
| 	    if (test_access & j)
 | |
| 	    {
 | |
| 	      if (found)
 | |
| 		global.append(STRING_WITH_LEN(", "));
 | |
| 	      found= 1;
 | |
| 	      global.append(command_array[counter],command_lengths[counter]);
 | |
| 	    }
 | |
| 	  }
 | |
| 	}
 | |
| 	global.append(STRING_WITH_LEN(" ON "));
 | |
|         LEX_CSTRING tmp= sph->type_lex_cstring();
 | |
|         global.append(&tmp);
 | |
|         global.append(' ');
 | |
| 	append_identifier(thd, &global, grant_proc->db,
 | |
| 			  strlen(grant_proc->db));
 | |
| 	global.append('.');
 | |
| 	append_identifier(thd, &global, grant_proc->tname,
 | |
| 			  strlen(grant_proc->tname));
 | |
|         add_to_user(thd, &global, username, (*hostname), host);
 | |
| 	if (proc_access & GRANT_ACL)
 | |
| 	  global.append(STRING_WITH_LEN(" WITH GRANT OPTION"));
 | |
| 	protocol->prepare_for_resend();
 | |
| 	protocol->store(global.ptr(),global.length(),global.charset());
 | |
| 	if (protocol->write())
 | |
| 	{
 | |
| 	  error= -1;
 | |
| 	  break;
 | |
| 	}
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   return error;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Make a clear-text version of the requested privilege.
 | |
| */
 | |
| 
 | |
| void get_privilege_desc(char *to, uint max_length, privilege_t access_arg)
 | |
| {
 | |
|   uint pos;
 | |
|   char *start=to;
 | |
|   DBUG_ASSERT(max_length >= 30);                // For end ', ' removal
 | |
| 
 | |
|   if (ulonglong access= access_arg)
 | |
|   {
 | |
|     max_length--;				// Reserve place for end-zero
 | |
|     for (pos=0 ; access ; pos++, access>>=1)
 | |
|     {
 | |
|       if ((access & 1) &&
 | |
| 	  command_lengths[pos] + (uint) (to-start) < max_length)
 | |
|       {
 | |
| 	to= strmov(to, command_array[pos]);
 | |
|         *to++= ',';
 | |
|         *to++= ' ';
 | |
|       }
 | |
|     }
 | |
|     to--;                                       // Remove end ' '
 | |
|     to--;					// Remove end ','
 | |
|   }
 | |
|   *to=0;
 | |
| }
 | |
| 
 | |
| 
 | |
| void get_mqh(const char *user, const char *host, USER_CONN *uc)
 | |
| {
 | |
|   ACL_USER *acl_user;
 | |
| 
 | |
|   mysql_mutex_lock(&acl_cache->lock);
 | |
| 
 | |
|   if (initialized && (acl_user= find_user_wild(Lex_cstring_strlen(host),
 | |
|                                                Lex_cstring_strlen(user))))
 | |
|     uc->user_resources= acl_user->user_resource;
 | |
|   else
 | |
|     bzero((char*) &uc->user_resources, sizeof(uc->user_resources));
 | |
| 
 | |
|   mysql_mutex_unlock(&acl_cache->lock);
 | |
| }
 | |
| 
 | |
| /*
 | |
|   Modify a privilege table.
 | |
| 
 | |
|   SYNOPSIS
 | |
|     modify_grant_table()
 | |
|     table                       The table to modify.
 | |
|     host_field                  The host name field.
 | |
|     user_field                  The user name field.
 | |
|     user_to                     The new name for the user if to be renamed,
 | |
|                                 NULL otherwise.
 | |
| 
 | |
|   DESCRIPTION
 | |
|   Update user/host in the current record if user_to is not NULL.
 | |
|   Delete the current record if user_to is NULL.
 | |
| 
 | |
|   RETURN
 | |
|     0           OK.
 | |
|     != 0        Error.
 | |
| */
 | |
| 
 | |
| static int modify_grant_table(TABLE *table, Field *host_field,
 | |
|                               Field *user_field, LEX_USER *user_to)
 | |
| {
 | |
|   int error;
 | |
|   DBUG_ENTER("modify_grant_table");
 | |
| 
 | |
|   if (user_to)
 | |
|   {
 | |
|     /* rename */
 | |
|     store_record(table, record[1]);
 | |
|     host_field->store(user_to->host.str, user_to->host.length,
 | |
|                       system_charset_info);
 | |
|     user_field->store(user_to->user.str, user_to->user.length,
 | |
|                       system_charset_info);
 | |
|     if (unlikely(error= table->file->ha_update_row(table->record[1],
 | |
|                                                    table->record[0])) &&
 | |
|         error != HA_ERR_RECORD_IS_THE_SAME)
 | |
|       table->file->print_error(error, MYF(0));
 | |
|     else
 | |
|       error= 0;
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     /* delete */
 | |
|     if (unlikely((error=table->file->ha_delete_row(table->record[0]))))
 | |
|       table->file->print_error(error, MYF(0));
 | |
|   }
 | |
| 
 | |
|   DBUG_RETURN(error);
 | |
| }
 | |
| 
 | |
| /*
 | |
|   Handle the roles_mapping privilege table
 | |
| */
 | |
| static int handle_roles_mappings_table(TABLE *table, bool drop,
 | |
|                                        LEX_USER *user_from, LEX_USER *user_to)
 | |
| {
 | |
|   /*
 | |
|     All entries (Host, User) that match user_from will be renamed,
 | |
|     as well as all Role entries that match if user_from.host.str == ""
 | |
| 
 | |
|     Otherwise, only matching (Host, User) will be renamed.
 | |
|   */
 | |
|   DBUG_ENTER("handle_roles_mappings_table");
 | |
| 
 | |
|   int error;
 | |
|   int result= 0;
 | |
|   Field *host_field= table->field[0];
 | |
|   Field *user_field= table->field[1];
 | |
|   Field *role_field= table->field[2];
 | |
| 
 | |
|   DBUG_PRINT("info", ("Rewriting entry in roles_mapping table: %s@%s",
 | |
|                       user_from->user.str, user_from->host.str));
 | |
|   table->use_all_columns();
 | |
| 
 | |
|   if (unlikely(table->file->ha_rnd_init_with_error(1)))
 | |
|     result= -1;
 | |
|   else
 | |
|   {
 | |
|     while((error= table->file->ha_rnd_next(table->record[0])) !=
 | |
|           HA_ERR_END_OF_FILE)
 | |
|     {
 | |
|       if (error)
 | |
|       {
 | |
|         DBUG_PRINT("info", ("scan error: %d", error));
 | |
|         continue;
 | |
|       }
 | |
| 
 | |
|       StringBuffer<MAX_FIELD_WIDTH> host_buff;
 | |
|       StringBuffer<MAX_FIELD_WIDTH> user_buff;
 | |
| 
 | |
|       if (user_field->val_lex_cstring(&user_buff).bin_eq(user_from->user) &&
 | |
|           Lex_ident_host::streq(host_field->val_lex_cstring(&host_buff),
 | |
|                                 user_from->host))
 | |
|         result= ((drop || user_to) &&
 | |
|                  modify_grant_table(table, host_field, user_field, user_to)) ?
 | |
|           -1 : result ? result : 1; /* Error or keep result or found. */
 | |
|       else
 | |
|       {
 | |
|         StringBuffer<MAX_FIELD_WIDTH> role_buff;
 | |
|         if (!user_from->is_role() ||
 | |
|             !role_field->val_lex_cstring(&role_buff).bin_eq(user_from->user))
 | |
|           continue;
 | |
| 
 | |
|         error= 0;
 | |
| 
 | |
|         if (drop) /* drop if requested */
 | |
|         {
 | |
|           if (unlikely((error= table->file->ha_delete_row(table->record[0]))))
 | |
|             table->file->print_error(error, MYF(0));
 | |
|         }
 | |
|         else if (user_to)
 | |
|         {
 | |
|           store_record(table, record[1]);
 | |
|           role_field->store(user_to->user.str, user_to->user.length,
 | |
|                             system_charset_info);
 | |
|           if (unlikely(error= table->file->ha_update_row(table->record[1],
 | |
|                                                          table->record[0])) &&
 | |
|               error != HA_ERR_RECORD_IS_THE_SAME)
 | |
|             table->file->print_error(error, MYF(0));
 | |
|         }
 | |
| 
 | |
|         /* Error or keep result or found. */
 | |
|         result= error ? -1 : result ? result : 1;
 | |
|       }
 | |
|     }
 | |
|     table->file->ha_rnd_end();
 | |
|   }
 | |
|   DBUG_RETURN(result);
 | |
| }
 | |
| 
 | |
| /*
 | |
|   Handle a privilege table.
 | |
| 
 | |
|   SYNOPSIS
 | |
|     handle_grant_table()
 | |
|     grant_table                 An open grant table handle.
 | |
|     which_table                 Which grant table to handle.
 | |
|     drop                        If user_from is to be dropped.
 | |
|     user_from                   The the user to be searched/dropped/renamed.
 | |
|     user_to                     The new name for the user if to be renamed,
 | |
|                                 NULL otherwise.
 | |
| 
 | |
|   DESCRIPTION
 | |
|     Scan through all records in a grant table and apply the requested
 | |
|     operation. For the "user" table, a single index access is sufficient,
 | |
|     since there is an unique index on (host, user).
 | |
|     Delete from grant table if drop is true.
 | |
|     Update in grant table if drop is false and user_to is not NULL.
 | |
|     Search in grant table if drop is false and user_to is NULL.
 | |
| 
 | |
|   RETURN
 | |
|     > 0         At least one record matched.
 | |
|     0           OK, but no record matched.
 | |
|     < 0         Error.
 | |
| 
 | |
|    TODO(cvicentiu) refactor handle_grant_table to use
 | |
|    Grant_table_base instead of TABLE directly.
 | |
| */
 | |
| 
 | |
| static int handle_grant_table(THD *thd, const Grant_table_base& grant_table,
 | |
|                               enum enum_acl_tables which_table, bool drop,
 | |
|                               LEX_USER *user_from, LEX_USER *user_to)
 | |
| {
 | |
|   int result= 0;
 | |
|   int error;
 | |
|   TABLE *table= grant_table.table();
 | |
|   DBUG_ENTER("handle_grant_table");
 | |
|   if (!table)
 | |
|     DBUG_RETURN(0);
 | |
| 
 | |
|   Field *host_field= table->field[0];
 | |
|   Field *user_field= table->field[which_table == USER_TABLE ||
 | |
|                                   which_table == PROXIES_PRIV_TABLE ? 1 : 2];
 | |
|   uchar user_key[MAX_KEY_LENGTH];
 | |
|   uint key_prefix_length;
 | |
| 
 | |
|   if (which_table == ROLES_MAPPING_TABLE)
 | |
|   {
 | |
|     result= handle_roles_mappings_table(table, drop, user_from, user_to);
 | |
|     DBUG_RETURN(result);
 | |
|   }
 | |
| 
 | |
|   table->use_all_columns();
 | |
|   if (which_table == USER_TABLE) // mysql.user table
 | |
|   {
 | |
|     /*
 | |
|       The 'user' table has an unique index on (host, user).
 | |
|       Thus, we can handle everything with a single index access.
 | |
|       The host- and user fields are consecutive in the user table records.
 | |
|       So we set host- and user fields of table->record[0] and use the
 | |
|       pointer to the host field as key.
 | |
|       index_read_idx() will replace table->record[0] (its first argument)
 | |
|       by the searched record, if it exists.
 | |
|     */
 | |
|     DBUG_PRINT("info",("read table: '%s'  search: '%s'@'%s'",
 | |
|                        table->s->table_name.str,
 | |
|                        user_from->user.str, user_from->host.str));
 | |
|     host_field->store(user_from->host, system_charset_info);
 | |
|     user_field->store(user_from->user, system_charset_info);
 | |
| 
 | |
|     key_prefix_length= (table->key_info->key_part[0].store_length +
 | |
|                         table->key_info->key_part[1].store_length);
 | |
|     key_copy(user_key, table->record[0], table->key_info, key_prefix_length);
 | |
| 
 | |
|     error= table->file->ha_index_read_idx_map(table->record[0], 0,
 | |
|                                               user_key, (key_part_map)3,
 | |
|                                               HA_READ_KEY_EXACT);
 | |
|     if (!unlikely(error) && !*user_from->host.str)
 | |
|     {
 | |
|       // verify that we got a role or a user, as needed
 | |
|       if (static_cast<const User_table&>(grant_table).get_is_role() !=
 | |
|           user_from->is_role())
 | |
|         error= HA_ERR_KEY_NOT_FOUND;
 | |
|     }
 | |
|     if (unlikely(error))
 | |
|     {
 | |
|       if (error != HA_ERR_KEY_NOT_FOUND && error != HA_ERR_END_OF_FILE)
 | |
|       {
 | |
|         table->file->print_error(error, MYF(0));
 | |
|         result= -1;
 | |
|       }
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       /* If requested, delete or update the record. */
 | |
|       result= ((drop || user_to) &&
 | |
|                modify_grant_table(table, host_field, user_field, user_to)) ?
 | |
|         -1 : 1; /* Error or found. */
 | |
|     }
 | |
|     DBUG_PRINT("info",("read result: %d", result));
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     /*
 | |
|       The non-'user' table do not have indexes on (host, user).
 | |
|       And their host- and user fields are not consecutive.
 | |
|       Thus, we need to do a table scan to find all matching records.
 | |
|     */
 | |
|     if (unlikely(table->file->ha_rnd_init_with_error(1)))
 | |
|       result= -1;
 | |
|     else
 | |
|     {
 | |
| #ifdef EXTRA_DEBUG
 | |
|       DBUG_PRINT("info",("scan table: '%s'  search: '%s'@'%s'",
 | |
|                          table->s->table_name.str,
 | |
|                          user_from->user.str,
 | |
|                          user_from->host.str));
 | |
| #endif
 | |
|       while ((error= table->file->ha_rnd_next(table->record[0])) !=
 | |
|              HA_ERR_END_OF_FILE)
 | |
|       {
 | |
|         if (error)
 | |
|         {
 | |
|           /* Most probable 'deleted record'. */
 | |
|           DBUG_PRINT("info",("scan error: %d", error));
 | |
|           continue;
 | |
|         }
 | |
| 
 | |
| #ifdef EXTRA_DEBUG
 | |
|         if (which_table != PROXIES_PRIV_TABLE)
 | |
|         {
 | |
|           DBUG_PRINT("loop",("scan fields: '%s'@'%s' '%s' '%s' '%s'",
 | |
|                              user_from->user.str, user_from->host.str,
 | |
|                              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
 | |
|         StringBuffer<MAX_FIELD_WIDTH> user_buff, host_buff;
 | |
|         if (!user_field->val_lex_cstring(&user_buff).bin_eq(user_from->user) ||
 | |
|             !Lex_ident_host::streq(host_field->val_lex_cstring(&host_buff),
 | |
|                                    user_from->host))
 | |
|           continue;
 | |
| 
 | |
|         /* If requested, delete or update the record. */
 | |
|         result= ((drop || user_to) &&
 | |
|                  modify_grant_table(table, host_field, user_field, user_to)) ?
 | |
|           -1 : result ? result : 1; /* Error or keep result or found. */
 | |
|         /* If search is requested, we do not need to search further. */
 | |
|         if (! drop && ! user_to)
 | |
|           break ;
 | |
|       }
 | |
|       (void) table->file->ha_rnd_end();
 | |
|       DBUG_PRINT("info",("scan result: %d", result));
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   DBUG_RETURN(result);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Handle an in-memory privilege structure.
 | |
| 
 | |
|   @param struct_no  The number of the structure to handle (0..6).
 | |
|   @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.
 | |
| 
 | |
|   @note
 | |
|     Scan through all elements in an in-memory grant structure and apply
 | |
|     the requested operation.
 | |
|     Delete from grant structure if drop is true.
 | |
|     Update in grant structure if drop is false and user_to is not NULL.
 | |
|     Search in grant structure if drop is false and user_to is NULL.
 | |
| 
 | |
|   @retval > 0  At least one element matched.
 | |
|   @retval 0    OK, but no element matched.
 | |
| */
 | |
| 
 | |
| static int handle_grant_struct(enum enum_acl_lists struct_no, bool drop,
 | |
|                                LEX_USER *user_from, LEX_USER *user_to)
 | |
| {
 | |
|   int result= 0;
 | |
|   int elements;
 | |
|   bool restart;
 | |
|   const char *UNINIT_VAR(user);
 | |
|   const char *UNINIT_VAR(host);
 | |
|   ACL_USER *acl_user= NULL;
 | |
|   ACL_ROLE *acl_role= NULL;
 | |
|   ACL_DB *acl_db= NULL;
 | |
|   ACL_PROXY_USER *acl_proxy_user= NULL;
 | |
|   GRANT_NAME *grant_name= NULL;
 | |
|   ROLE_GRANT_PAIR *UNINIT_VAR(role_grant_pair);
 | |
|   HASH *grant_name_hash= NULL;
 | |
|   HASH *roles_mappings_hash= NULL;
 | |
|   DBUG_ENTER("handle_grant_struct");
 | |
|   DBUG_PRINT("info",("scan struct: %u  search: '%s'@'%s'",
 | |
|                      struct_no, user_from->user.str, user_from->host.str));
 | |
| 
 | |
|   mysql_mutex_assert_owner(&acl_cache->lock);
 | |
| 
 | |
|   /* No point in querying ROLE ACL if user_from is not a role */
 | |
|   if (struct_no == ROLE_ACL && user_from->host.length)
 | |
|     DBUG_RETURN(0);
 | |
| 
 | |
|   /* same. no roles in PROXY_USERS_ACL */
 | |
|   if (struct_no == PROXY_USERS_ACL && user_from->is_role())
 | |
|     DBUG_RETURN(0);
 | |
| 
 | |
|   if (struct_no == ROLE_ACL) //no need to scan the structures in this case
 | |
|   {
 | |
|     acl_role= find_acl_role(user_from->user, true);
 | |
|     if (!acl_role)
 | |
|       DBUG_RETURN(0);
 | |
| 
 | |
|     if (!drop && !user_to) //role was found
 | |
|       DBUG_RETURN(1);
 | |
| 
 | |
|     /* this calls for a role update */
 | |
|     const char *old_key= acl_role->user.str;
 | |
|     size_t old_key_length= acl_role->user.length;
 | |
|     if (drop)
 | |
|     {
 | |
|       // delete the role from cross-reference arrays
 | |
|       for (size_t i=0; i < acl_role->role_grants.elements; i++)
 | |
|       {
 | |
|         ACL_ROLE *grant= *dynamic_element(&acl_role->role_grants,
 | |
|                                           i, ACL_ROLE**);
 | |
|         remove_ptr_from_dynarray(&grant->parent_grantee, acl_role);
 | |
|       }
 | |
| 
 | |
|       for (size_t i=0; i < acl_role->parent_grantee.elements; i++)
 | |
|       {
 | |
|         ACL_USER_BASE *grantee= *dynamic_element(&acl_role->parent_grantee,
 | |
|                                                  i, ACL_USER_BASE**);
 | |
|         remove_ptr_from_dynarray(&grantee->role_grants, acl_role);
 | |
|       }
 | |
| 
 | |
|       /* Remove all of the role_grants from this role. */
 | |
|       delete_dynamic(&acl_role->role_grants);
 | |
| 
 | |
|       /* all grants must be revoked from this role by now. propagate this */
 | |
|       propagate_role_grants(acl_role, PRIVS_TO_MERGE::ALL);
 | |
| 
 | |
|       my_hash_delete(&acl_roles, (uchar*) acl_role);
 | |
|       DBUG_RETURN(1);
 | |
|     }
 | |
|     acl_role->user= safe_lexcstrdup_root(&acl_memroot, user_to->user);
 | |
| 
 | |
|     my_hash_update(&acl_roles, (uchar*) acl_role, (uchar*) old_key,
 | |
|                    old_key_length);
 | |
|     DBUG_RETURN(1);
 | |
| 
 | |
|   }
 | |
| 
 | |
|   /* Get the number of elements in the in-memory structure. */
 | |
|   switch (struct_no) {
 | |
|   case USER_ACL:
 | |
|     elements= int(acl_users.elements);
 | |
|     break;
 | |
|   case DB_ACL:
 | |
|     elements= int(acl_dbs.elements());
 | |
|     break;
 | |
|   case COLUMN_PRIVILEGES_HASH:
 | |
|     grant_name_hash= &column_priv_hash;
 | |
|     elements= grant_name_hash->records;
 | |
|     break;
 | |
|   case PROC_PRIVILEGES_HASH:
 | |
|     grant_name_hash= &proc_priv_hash;
 | |
|     elements= grant_name_hash->records;
 | |
|     break;
 | |
|   case FUNC_PRIVILEGES_HASH:
 | |
|     grant_name_hash= &func_priv_hash;
 | |
|     elements= grant_name_hash->records;
 | |
|     break;
 | |
|   case PACKAGE_SPEC_PRIVILEGES_HASH:
 | |
|     grant_name_hash= &package_spec_priv_hash;
 | |
|     elements= grant_name_hash->records;
 | |
|     break;
 | |
|   case PACKAGE_BODY_PRIVILEGES_HASH:
 | |
|     grant_name_hash= &package_body_priv_hash;
 | |
|     elements= grant_name_hash->records;
 | |
|     break;
 | |
|   case PROXY_USERS_ACL:
 | |
|     elements= int(acl_proxy_users.elements);
 | |
|     break;
 | |
|   case ROLES_MAPPINGS_HASH:
 | |
|     roles_mappings_hash= &acl_roles_mappings;
 | |
|     elements= roles_mappings_hash->records;
 | |
|     break;
 | |
|   default:
 | |
|     DBUG_ASSERT(0);
 | |
|     DBUG_RETURN(-1);
 | |
|   }
 | |
| 
 | |
| 
 | |
| #ifdef EXTRA_DEBUG
 | |
|     DBUG_PRINT("loop",("scan struct: %u  search    user: '%s'  host: '%s'",
 | |
|                        struct_no, user_from->user.str, user_from->host.str));
 | |
| #endif
 | |
|   /* Loop over elements backwards as it may reduce the number of mem-moves
 | |
|      for dynamic arrays.
 | |
| 
 | |
|      We restart the loop, if we deleted or updated anything in a hash table
 | |
|      because calling my_hash_delete or my_hash_update shuffles elements indices
 | |
|      and we can miss some if we do only one scan.
 | |
|   */
 | |
|   do {
 | |
|     restart= false;
 | |
|     for (int idx= elements - 1; idx >= 0; idx--)
 | |
|     {
 | |
|       /*
 | |
|         Get a pointer to the element.
 | |
|       */
 | |
|       switch (struct_no) {
 | |
|       case USER_ACL:
 | |
|         acl_user= dynamic_element(&acl_users, idx, ACL_USER*);
 | |
|         user= acl_user->user.str;
 | |
|         host= acl_user->host.hostname;
 | |
|       break;
 | |
| 
 | |
|       case DB_ACL:
 | |
|         acl_db= &acl_dbs.at(idx);
 | |
|         user= acl_db->user;
 | |
|         host= acl_db->host.hostname;
 | |
|         break;
 | |
| 
 | |
|       case COLUMN_PRIVILEGES_HASH:
 | |
|       case PROC_PRIVILEGES_HASH:
 | |
|       case FUNC_PRIVILEGES_HASH:
 | |
|       case PACKAGE_SPEC_PRIVILEGES_HASH:
 | |
|       case PACKAGE_BODY_PRIVILEGES_HASH:
 | |
|         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;
 | |
| 
 | |
|       case ROLES_MAPPINGS_HASH:
 | |
|         role_grant_pair= (ROLE_GRANT_PAIR *) my_hash_element(roles_mappings_hash, idx);
 | |
|         user= role_grant_pair->u_uname.str;
 | |
|         host= role_grant_pair->u_hname.str;
 | |
|         break;
 | |
| 
 | |
|       default:
 | |
|         DBUG_ASSERT(0);
 | |
|       }
 | |
|       if (! host)
 | |
|         host= "";
 | |
| 
 | |
| #ifdef EXTRA_DEBUG
 | |
|       DBUG_PRINT("loop",("scan struct: %u  index: %u  user: '%s'  host: '%s'",
 | |
|                          struct_no, idx, user, host));
 | |
| #endif
 | |
| 
 | |
|       if (struct_no == ROLES_MAPPINGS_HASH)
 | |
|       {
 | |
|         const char* role= safe_str(role_grant_pair->r_uname.str);
 | |
|         if (user_from->is_role())
 | |
|         {
 | |
|           /* When searching for roles within the ROLES_MAPPINGS_HASH, we have
 | |
|              to check both the user field as well as the role field for a match.
 | |
| 
 | |
|              It is possible to have a role granted to a role. If we are going
 | |
|              to modify the mapping entry, it needs to be done on either on the
 | |
|              "user" end (here represented by a role) or the "role" end. At least
 | |
|              one part must match.
 | |
| 
 | |
|              If the "user" end has a not-empty host string, it can never match
 | |
|              as we are searching for a role here. A role always has an empty host
 | |
|              string.
 | |
|           */
 | |
|           if ((*host || strcmp(user_from->user.str, user)) &&
 | |
|               strcmp(user_from->user.str, role))
 | |
|             continue;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|           if (strcmp(user_from->user.str, user) ||
 | |
|               !Lex_ident_host(user_from->host).streq(Lex_cstring_strlen(host)))
 | |
|             continue;
 | |
|         }
 | |
|       }
 | |
|       else
 | |
|       {
 | |
|         if (strcmp(user_from->user.str, user) ||
 | |
|             !Lex_ident_host(user_from->host).streq(Lex_cstring_strlen(host)))
 | |
|           continue;
 | |
|       }
 | |
| 
 | |
|       result= 1; /* At least one element found. */
 | |
|       if ( drop )
 | |
|       {
 | |
|         elements--;
 | |
|         switch ( struct_no ) {
 | |
|         case USER_ACL:
 | |
|           free_acl_user(dynamic_element(&acl_users, idx, ACL_USER*));
 | |
|           delete_dynamic_element(&acl_users, idx);
 | |
|           break;
 | |
| 
 | |
|         case DB_ACL:
 | |
|           acl_dbs.del(idx);
 | |
|           break;
 | |
| 
 | |
|         case COLUMN_PRIVILEGES_HASH:
 | |
|         case PROC_PRIVILEGES_HASH:
 | |
|         case FUNC_PRIVILEGES_HASH:
 | |
|         case PACKAGE_SPEC_PRIVILEGES_HASH:
 | |
|         case PACKAGE_BODY_PRIVILEGES_HASH:
 | |
|           my_hash_delete(grant_name_hash, (uchar*) grant_name);
 | |
|           restart= true;
 | |
|           break;
 | |
| 
 | |
|         case PROXY_USERS_ACL:
 | |
|           delete_dynamic_element(&acl_proxy_users, idx);
 | |
|           break;
 | |
| 
 | |
|         case ROLES_MAPPINGS_HASH:
 | |
|           my_hash_delete(roles_mappings_hash, (uchar*) role_grant_pair);
 | |
|           restart= true;
 | |
|           break;
 | |
| 
 | |
|         default:
 | |
|           DBUG_ASSERT(0);
 | |
|           break;
 | |
|         }
 | |
|       }
 | |
|       else if ( user_to )
 | |
|       {
 | |
|         switch ( struct_no ) {
 | |
|         case USER_ACL:
 | |
|           acl_user->user= safe_lexcstrdup_root(&acl_memroot, user_to->user);
 | |
|           update_hostname(&acl_user->host, strdup_root(&acl_memroot, user_to->host.str));
 | |
|           acl_user->hostname_length= strlen(acl_user->host.hostname);
 | |
|           break;
 | |
| 
 | |
|         case DB_ACL:
 | |
|           acl_db->user= strdup_root(&acl_memroot, user_to->user.str);
 | |
|           update_hostname(&acl_db->host, strdup_root(&acl_memroot, user_to->host.str));
 | |
|           break;
 | |
| 
 | |
|         case COLUMN_PRIVILEGES_HASH:
 | |
|         case PROC_PRIVILEGES_HASH:
 | |
|         case FUNC_PRIVILEGES_HASH:
 | |
|         case PACKAGE_SPEC_PRIVILEGES_HASH:
 | |
|         case PACKAGE_BODY_PRIVILEGES_HASH:
 | |
|           {
 | |
|             /*
 | |
|               Save old hash key and its length to be able to properly update
 | |
|               element position in hash.
 | |
|             */
 | |
|             char *old_key= grant_name->hash_key;
 | |
|             size_t old_key_length= grant_name->key_length;
 | |
| 
 | |
|             /*
 | |
|               Update the grant structure with the new user name and host name.
 | |
|             */
 | |
|             grant_name->set_user_details(user_to->host.str, grant_name->db,
 | |
|                                          user_to->user.str, grant_name->tname,
 | |
|                                          TRUE);
 | |
| 
 | |
|             /*
 | |
|               Since username is part of the hash key, when the user name
 | |
|               is renamed, the hash key is changed. Update the hash to
 | |
|               ensure that the position matches the new hash key value
 | |
|             */
 | |
|             my_hash_update(grant_name_hash, (uchar*) grant_name, (uchar*) old_key,
 | |
|                            old_key_length);
 | |
|             restart= true;
 | |
|             break;
 | |
|           }
 | |
| 
 | |
|         case PROXY_USERS_ACL:
 | |
|           acl_proxy_user->set_user (&acl_memroot, user_to->user.str);
 | |
|           acl_proxy_user->set_host (&acl_memroot, user_to->host.str);
 | |
|           break;
 | |
| 
 | |
|         case ROLES_MAPPINGS_HASH:
 | |
|           {
 | |
|             /*
 | |
|               Save old hash key and its length to be able to properly update
 | |
|               element position in hash.
 | |
|             */
 | |
|             char *old_key= role_grant_pair->hashkey.str;
 | |
|             size_t old_key_length= role_grant_pair->hashkey.length;
 | |
|             bool oom;
 | |
| 
 | |
|             if (user_to->is_role())
 | |
|               oom= role_grant_pair->init(&acl_memroot,
 | |
|                                          role_grant_pair->u_uname,
 | |
|                                          role_grant_pair->u_hname,
 | |
|                                          user_to->user, false);
 | |
|             else
 | |
|               oom= role_grant_pair->init(&acl_memroot, user_to->user,
 | |
|                                          user_to->host,
 | |
|                                          role_grant_pair->r_uname, false);
 | |
|             if (oom)
 | |
|               DBUG_RETURN(-1);
 | |
| 
 | |
|             my_hash_update(roles_mappings_hash, (uchar*) role_grant_pair,
 | |
|                            (uchar*) old_key, old_key_length);
 | |
|             restart= true;
 | |
|             break;
 | |
|           }
 | |
| 
 | |
|         default:
 | |
|           DBUG_ASSERT(0);
 | |
|           break;
 | |
|         }
 | |
| 
 | |
|       }
 | |
|       else
 | |
|       {
 | |
|         /* If search is requested, we do not need to search further. */
 | |
|         break;
 | |
|       }
 | |
|     }
 | |
|   } while (restart);
 | |
| #ifdef EXTRA_DEBUG
 | |
|   DBUG_PRINT("loop",("scan struct: %u  result %d", struct_no, result));
 | |
| #endif
 | |
| 
 | |
|   DBUG_RETURN(result);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Handle all privilege tables and in-memory privilege structures.
 | |
| 
 | |
|   SYNOPSIS
 | |
|     handle_grant_data()
 | |
|     tables                      The array with the four open tables.
 | |
|     drop                        If user_from is to be dropped.
 | |
|     user_from                   The the user to be searched/dropped/renamed.
 | |
|     user_to                     The new name for the user if to be renamed,
 | |
|                                 NULL otherwise.
 | |
| 
 | |
|   DESCRIPTION
 | |
|     Go through all grant tables and in-memory grant structures and apply
 | |
|     the requested operation.
 | |
|     Delete from grant data if drop is true.
 | |
|     Update in grant data if drop is false and user_to is not NULL.
 | |
|     Search in grant data if drop is false and user_to is NULL.
 | |
| 
 | |
|   RETURN
 | |
|     > 0         At least one element matched.
 | |
|     0           OK, but no element matched.
 | |
|     < 0         Error.
 | |
| */
 | |
| 
 | |
| static int handle_grant_data(THD *thd, Grant_tables& tables, bool drop,
 | |
|                              LEX_USER *user_from, LEX_USER *user_to)
 | |
| {
 | |
|   int result= 0;
 | |
|   int found;
 | |
|   bool handle_as_role= user_from->is_role();
 | |
|   bool search_only= !drop && !user_to;
 | |
|   DBUG_ENTER("handle_grant_data");
 | |
| 
 | |
|   if (user_to)
 | |
|     DBUG_ASSERT(handle_as_role == user_to->is_role());
 | |
| 
 | |
|   if (search_only)
 | |
|   {
 | |
|     /* quickly search in-memory structures first */
 | |
|     if (handle_as_role && find_acl_role(user_from->user, true))
 | |
|       DBUG_RETURN(1); // found
 | |
| 
 | |
|     if (!handle_as_role && find_user_exact(user_from->host, user_from->user))
 | |
|       DBUG_RETURN(1); // found
 | |
|   }
 | |
| 
 | |
|   /* Handle db table. */
 | |
|   if ((found= handle_grant_table(thd, tables.db_table(),
 | |
|                                  DB_TABLE, drop, user_from,
 | |
|                                  user_to)) < 0)
 | |
|   {
 | |
|     /* Handle of table failed, don't touch the in-memory array. */
 | |
|     result= -1;
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     /* Handle db array. */
 | |
|     if ((handle_grant_struct(DB_ACL, drop, user_from, user_to) || found)
 | |
|         && ! result)
 | |
|     {
 | |
|       result= 1; /* At least one record/element found. */
 | |
|       /* If search is requested, we do not need to search further. */
 | |
|       if (search_only)
 | |
|         goto end;
 | |
|       acl_cache->clear(1);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* Handle stored routines table. */
 | |
|   if ((found= handle_grant_table(thd, tables.procs_priv_table(),
 | |
|                                  PROCS_PRIV_TABLE, drop,
 | |
|                                  user_from, user_to)) < 0)
 | |
|   {
 | |
|     /* Handle of table failed, don't touch in-memory array. */
 | |
|     result= -1;
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     /* Handle procs array. */
 | |
|     if ((handle_grant_struct(PROC_PRIVILEGES_HASH, drop, user_from, user_to) || found)
 | |
|         && ! result)
 | |
|     {
 | |
|       result= 1; /* At least one record/element found. */
 | |
|       /* If search is requested, we do not need to search further. */
 | |
|       if (search_only)
 | |
|         goto end;
 | |
|     }
 | |
|     /* Handle funcs array. */
 | |
|     if ((handle_grant_struct(FUNC_PRIVILEGES_HASH, drop, user_from, user_to) || found)
 | |
|         && ! result)
 | |
|     {
 | |
|       result= 1; /* At least one record/element found. */
 | |
|       /* If search is requested, we do not need to search further. */
 | |
|       if (search_only)
 | |
|         goto end;
 | |
|     }
 | |
|     /* Handle package spec array. */
 | |
|     if ((handle_grant_struct(PACKAGE_SPEC_PRIVILEGES_HASH,
 | |
|                              drop, user_from, user_to) || found)
 | |
|         && ! result)
 | |
|     {
 | |
|       result= 1; /* At least one record/element found. */
 | |
|       /* If search is requested, we do not need to search further. */
 | |
|       if (search_only)
 | |
|         goto end;
 | |
|     }
 | |
|     /* Handle package body array. */
 | |
|     if ((handle_grant_struct(PACKAGE_BODY_PRIVILEGES_HASH,
 | |
|                              drop, user_from, user_to) || found)
 | |
|         && ! result)
 | |
|     {
 | |
|       result= 1; /* At least one record/element found. */
 | |
|       /* If search is requested, we do not need to search further. */
 | |
|       if (search_only)
 | |
|         goto end;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* Handle tables table. */
 | |
|   if ((found= handle_grant_table(thd, tables.tables_priv_table(),
 | |
|                                  TABLES_PRIV_TABLE, drop,
 | |
|                                  user_from, user_to)) < 0)
 | |
|   {
 | |
|     /* Handle of table failed, don't touch columns and in-memory array. */
 | |
|     result= -1;
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     if (found && ! result)
 | |
|     {
 | |
|       result= 1; /* At least one record found. */
 | |
|       /* If search is requested, we do not need to search further. */
 | |
|       if (search_only)
 | |
|         goto end;
 | |
|     }
 | |
| 
 | |
|     /* Handle columns table. */
 | |
|     if ((found= handle_grant_table(thd, tables.columns_priv_table(),
 | |
|                                    COLUMNS_PRIV_TABLE, drop,
 | |
|                                    user_from, user_to)) < 0)
 | |
|     {
 | |
|       /* Handle of table failed, don't touch the in-memory array. */
 | |
|       result= -1;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       /* Handle columns hash. */
 | |
|       if ((handle_grant_struct(COLUMN_PRIVILEGES_HASH, drop, user_from, user_to) || found)
 | |
|           && ! result)
 | |
|         result= 1; /* At least one record/element found. */
 | |
|       if (search_only)
 | |
|         goto end;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* Handle proxies_priv table. */
 | |
|   if (tables.proxies_priv_table().table_exists())
 | |
|   {
 | |
|     if ((found= handle_grant_table(thd, tables.proxies_priv_table(),
 | |
|                                    PROXIES_PRIV_TABLE, 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 ((handle_grant_struct(PROXY_USERS_ACL, drop, user_from, user_to) || found)
 | |
|           && ! result)
 | |
|         result= 1; /* At least one record/element found. */
 | |
|       if (search_only)
 | |
|         goto end;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* Handle roles_mapping table. */
 | |
|   if (tables.roles_mapping_table().table_exists() &&
 | |
|       (found= handle_grant_table(thd, tables.roles_mapping_table(),
 | |
|                          ROLES_MAPPING_TABLE, drop, user_from, user_to)) < 0)
 | |
|   {
 | |
|     /* Handle of table failed, don't touch the in-memory array. */
 | |
|     result= -1;
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     /* Handle acl_roles_mappings array */
 | |
|     if ((handle_grant_struct(ROLES_MAPPINGS_HASH, drop, user_from, user_to) || found)
 | |
|         && ! result)
 | |
|       result= 1; /* At least one record/element found */
 | |
|     if (search_only)
 | |
|       goto end;
 | |
|   }
 | |
| 
 | |
|   /* Handle user table. */
 | |
|   if ((found= handle_grant_table(thd, tables.user_table(), USER_TABLE,
 | |
|                                  drop, user_from, user_to)) < 0)
 | |
|   {
 | |
|     /* Handle of table failed, don't touch the in-memory array. */
 | |
|     result= -1;
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     enum enum_acl_lists what= handle_as_role ? ROLE_ACL : USER_ACL;
 | |
|     if (((handle_grant_struct(what, drop, user_from, user_to)) || found) && !result)
 | |
|     {
 | |
|       result= 1; /* At least one record/element found. */
 | |
|       DBUG_ASSERT(! search_only);
 | |
|     }
 | |
|   }
 | |
| 
 | |
| end:
 | |
|   DBUG_RETURN(result);
 | |
| }
 | |
| 
 | |
| /*
 | |
|   Create a list of users.
 | |
| 
 | |
|   SYNOPSIS
 | |
|     mysql_create_user()
 | |
|     thd                         The current thread.
 | |
|     list                        The users to create.
 | |
|     handle_as_role              Handle the user list as roles if true
 | |
| 
 | |
|   RETURN
 | |
|     FALSE       OK.
 | |
|     TRUE        Error.
 | |
| */
 | |
| 
 | |
| bool mysql_create_user(THD *thd, List <LEX_USER> &list, bool handle_as_role)
 | |
| {
 | |
|   int result;
 | |
|   String wrong_users;
 | |
|   LEX_USER *user_name;
 | |
|   List_iterator <LEX_USER> user_list(list);
 | |
|   bool binlog= false;
 | |
|   bool some_users_dropped= false;
 | |
|   DBUG_ENTER("mysql_create_user");
 | |
|   DBUG_PRINT("entry", ("Handle as %s", handle_as_role ? "role" : "user"));
 | |
| 
 | |
|   if (handle_as_role && sp_process_definer(thd))
 | |
|     DBUG_RETURN(TRUE);
 | |
| 
 | |
|   /* CREATE USER may be skipped on replication client. */
 | |
|   Grant_tables tables;
 | |
|   const uint tables_to_open= Table_user | Table_db | Table_tables_priv |
 | |
|                              Table_columns_priv | Table_procs_priv |
 | |
|                              Table_proxies_priv | Table_roles_mapping;
 | |
|   if ((result= tables.open_and_lock(thd, tables_to_open, TL_WRITE)))
 | |
|     DBUG_RETURN(result != 1);
 | |
| 
 | |
|   mysql_rwlock_wrlock(&LOCK_grant);
 | |
|   mysql_mutex_lock(&acl_cache->lock);
 | |
| 
 | |
|   while ((user_name= user_list++))
 | |
|   {
 | |
|     if (user_name->user.str == current_user.str)
 | |
|     {
 | |
|       append_str(&wrong_users, STRING_WITH_LEN("CURRENT_USER"));
 | |
|       result= TRUE;
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     if (user_name->user.str == current_role.str)
 | |
|     {
 | |
|       append_str(&wrong_users, STRING_WITH_LEN("CURRENT_ROLE"));
 | |
|       result= TRUE;
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     if (handle_as_role &&
 | |
|         (check_role_name(&user_name->user, false) == ROLE_NAME_INVALID))
 | |
|     {
 | |
|       append_user(thd, &wrong_users, user_name);
 | |
|       result= TRUE;
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     if (!user_name->host.str)
 | |
|       user_name->host= host_not_specified;
 | |
| 
 | |
|     /*
 | |
|       Search all in-memory structures and grant tables
 | |
|       for a mention of the new user/role name.
 | |
|     */
 | |
|     if (handle_grant_data(thd, tables, 0, user_name, NULL))
 | |
|     {
 | |
|       if (thd->lex->create_info.or_replace())
 | |
|       {
 | |
|         // Drop the existing user
 | |
|         if (handle_grant_data(thd, tables, 1, user_name, NULL) <= 0)
 | |
|         {
 | |
|           // DROP failed
 | |
|           append_user(thd, &wrong_users, user_name);
 | |
|           result= true;
 | |
|           continue;
 | |
|         }
 | |
|         else
 | |
|           some_users_dropped= true;
 | |
|         // Proceed with the creation
 | |
|       }
 | |
|       else if (thd->lex->create_info.if_not_exists())
 | |
|       {
 | |
|         binlog= true;
 | |
|         if (handle_as_role)
 | |
|           push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
 | |
|                               ER_ROLE_CREATE_EXISTS,
 | |
|                               ER_THD(thd, ER_ROLE_CREATE_EXISTS),
 | |
|                               user_name->user.str);
 | |
|         else
 | |
|           push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
 | |
|                               ER_USER_CREATE_EXISTS,
 | |
|                               ER_THD(thd, ER_USER_CREATE_EXISTS),
 | |
|                               user_name->user.str, user_name->host.str);
 | |
|         continue;
 | |
|       }
 | |
|       else
 | |
|       {
 | |
|         // "CREATE USER user1" for an existing user
 | |
|         append_user(thd, &wrong_users, user_name);
 | |
|         result= true;
 | |
|         continue;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (replace_user_table(thd, tables.user_table(), user_name,
 | |
|                            NO_ACL, 0, 1, 0))
 | |
|     {
 | |
|       append_user(thd, &wrong_users, user_name);
 | |
|       result= TRUE;
 | |
|       continue;
 | |
|     }
 | |
|     binlog= true;
 | |
| 
 | |
|     // every created role is automatically granted to its creator-admin
 | |
|     if (handle_as_role)
 | |
|     {
 | |
|       ACL_USER_BASE *grantee= find_acl_user_base(thd->lex->definer->user,
 | |
|                                                  thd->lex->definer->host);
 | |
|       ACL_ROLE *role= find_acl_role(user_name->user, false);
 | |
| 
 | |
|       /*
 | |
|         just like with routines, views, triggers, and events we allow
 | |
|         non-existant definers here with a warning (see sp_process_definer())
 | |
|       */
 | |
|       if (grantee)
 | |
|         add_role_user_mapping(grantee, role);
 | |
| 
 | |
|       /* TODO(cvicentiu) refactor replace_roles_mapping_table to use
 | |
|          Roles_mapping_table instead of TABLE directly. */
 | |
|       if (replace_roles_mapping_table(tables.roles_mapping_table().table(),
 | |
|                                       &thd->lex->definer->user,
 | |
|                                       &thd->lex->definer->host,
 | |
|                                       &user_name->user, true,
 | |
|                                       NULL, false))
 | |
|       {
 | |
|         append_user(thd, &wrong_users, user_name);
 | |
|         if (grantee)
 | |
|           undo_add_role_user_mapping(grantee, role);
 | |
|         result= TRUE;
 | |
|       }
 | |
|       else if (grantee)
 | |
|              update_role_mapping(&thd->lex->definer->user,
 | |
|                                  &thd->lex->definer->host,
 | |
|                                  &user_name->user, true, NULL, false);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (result && some_users_dropped && !handle_as_role)
 | |
|   {
 | |
|     /* Rebuild in-memory structs, since 'acl_users' has been modified */
 | |
|     rebuild_check_host();
 | |
|     rebuild_role_grants();
 | |
|   }
 | |
| 
 | |
|   mysql_mutex_unlock(&acl_cache->lock);
 | |
| 
 | |
|   if (result)
 | |
|   {
 | |
|     my_error(ER_CANNOT_USER, MYF(0),
 | |
|              (handle_as_role) ? "CREATE ROLE" : "CREATE USER",
 | |
|              wrong_users.c_ptr_safe());
 | |
|   }
 | |
| 
 | |
|   if (binlog)
 | |
|     result |= write_bin_log(thd, FALSE, thd->query(), thd->query_length());
 | |
| 
 | |
|   mysql_rwlock_unlock(&LOCK_grant);
 | |
|   DBUG_RETURN(result);
 | |
| }
 | |
| 
 | |
| /*
 | |
|   Drop a list of users and all their privileges.
 | |
| 
 | |
|   SYNOPSIS
 | |
|     mysql_drop_user()
 | |
|     thd                         The current thread.
 | |
|     list                        The users to drop.
 | |
| 
 | |
|   RETURN
 | |
|     FALSE       OK.
 | |
|     TRUE        Error.
 | |
| */
 | |
| 
 | |
| bool mysql_drop_user(THD *thd, List <LEX_USER> &list, bool handle_as_role)
 | |
| {
 | |
|   int result;
 | |
|   String wrong_users;
 | |
|   LEX_USER *user_name, *tmp_user_name;
 | |
|   List_iterator <LEX_USER> user_list(list);
 | |
|   bool binlog= false;
 | |
|   DBUG_ENTER("mysql_drop_user");
 | |
|   DBUG_PRINT("entry", ("Handle as %s", handle_as_role ? "role" : "user"));
 | |
| 
 | |
|   /* DROP USER may be skipped on replication client. */
 | |
|   Grant_tables tables;
 | |
|   const uint tables_to_open= Table_user | Table_db | Table_tables_priv |
 | |
|                              Table_columns_priv | Table_procs_priv |
 | |
|                              Table_proxies_priv | Table_roles_mapping;
 | |
|   if ((result= tables.open_and_lock(thd, tables_to_open, TL_WRITE)))
 | |
|     DBUG_RETURN(result != 1);
 | |
| 
 | |
|   Sql_mode_instant_remove sms(thd, MODE_PAD_CHAR_TO_FULL_LENGTH);
 | |
| 
 | |
|   mysql_rwlock_wrlock(&LOCK_grant);
 | |
|   mysql_mutex_lock(&acl_cache->lock);
 | |
| 
 | |
|   while ((tmp_user_name= user_list++))
 | |
|   {
 | |
|     int rc;
 | |
|     user_name= get_current_user(thd, tmp_user_name, false);
 | |
|     if (!user_name || (handle_as_role && is_public(user_name)))
 | |
|     {
 | |
|       thd->clear_error();
 | |
|       if (!user_name)
 | |
|         append_str(&wrong_users, STRING_WITH_LEN("CURRENT_ROLE"));
 | |
|       else
 | |
|         append_str(&wrong_users, public_name.str, public_name.length);
 | |
|       result= TRUE;
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     if (handle_as_role != user_name->is_role())
 | |
|     {
 | |
|       append_user(thd, &wrong_users, user_name);
 | |
|       result= TRUE;
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     if ((rc= handle_grant_data(thd, tables, 1, user_name, NULL)) > 0)
 | |
|     {
 | |
|       // The user or role was successfully deleted
 | |
|       binlog= true;
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     if (rc == 0 && thd->lex->if_exists())
 | |
|     {
 | |
|       // "DROP USER IF EXISTS user1" for a non-existing user or role
 | |
|       if (handle_as_role)
 | |
|         push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
 | |
|                             ER_ROLE_DROP_EXISTS,
 | |
|                             ER_THD(thd, ER_ROLE_DROP_EXISTS),
 | |
|                             user_name->user.str);
 | |
|       else
 | |
|         push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
 | |
|                             ER_USER_DROP_EXISTS,
 | |
|                             ER_THD(thd, ER_USER_DROP_EXISTS),
 | |
|                             user_name->user.str, user_name->host.str);
 | |
|       binlog= true;
 | |
|       continue;
 | |
|     }
 | |
|     // Internal error, or "DROP USER user1" for a non-existing user
 | |
|     append_user(thd, &wrong_users, user_name);
 | |
|     result= TRUE;
 | |
|   }
 | |
| 
 | |
|   if (!handle_as_role)
 | |
|   {
 | |
|     /* Rebuild 'acl_check_hosts' since 'acl_users' has been modified */
 | |
|     rebuild_check_host();
 | |
| 
 | |
|     /*
 | |
|       Rebuild every user's role_grants since 'acl_users' has been sorted
 | |
|       and old pointers to ACL_USER elements are no longer valid
 | |
|     */
 | |
|     rebuild_role_grants();
 | |
|   }
 | |
| 
 | |
|   mysql_mutex_unlock(&acl_cache->lock);
 | |
| 
 | |
|   if (result)
 | |
|     my_error(ER_CANNOT_USER, MYF(0),
 | |
|              (handle_as_role) ? "DROP ROLE" : "DROP USER",
 | |
|              wrong_users.c_ptr_safe());
 | |
| 
 | |
|   if (binlog)
 | |
|     result |= write_bin_log(thd, FALSE, thd->query(), thd->query_length());
 | |
| 
 | |
|   mysql_rwlock_unlock(&LOCK_grant);
 | |
|   DBUG_RETURN(result);
 | |
| }
 | |
| 
 | |
| /*
 | |
|   Rename a user.
 | |
| 
 | |
|   SYNOPSIS
 | |
|     mysql_rename_user()
 | |
|     thd                         The current thread.
 | |
|     list                        The user name pairs: (from, to).
 | |
| 
 | |
|   RETURN
 | |
|     FALSE       OK.
 | |
|     TRUE        Error.
 | |
| */
 | |
| 
 | |
| bool mysql_rename_user(THD *thd, List <LEX_USER> &list)
 | |
| {
 | |
|   int result;
 | |
|   String wrong_users;
 | |
|   LEX_USER *user_from, *tmp_user_from;
 | |
|   LEX_USER *user_to, *tmp_user_to;
 | |
|   List_iterator <LEX_USER> user_list(list);
 | |
|   bool some_users_renamed= FALSE;
 | |
|   DBUG_ENTER("mysql_rename_user");
 | |
| 
 | |
|   /* RENAME USER may be skipped on replication client. */
 | |
|   Grant_tables tables;
 | |
|   const uint tables_to_open= Table_user | Table_db | Table_tables_priv |
 | |
|                              Table_columns_priv | Table_procs_priv |
 | |
|                              Table_proxies_priv | Table_roles_mapping;
 | |
|   if ((result= tables.open_and_lock(thd, tables_to_open, TL_WRITE)))
 | |
|     DBUG_RETURN(result != 1);
 | |
| 
 | |
|   DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row());
 | |
| 
 | |
|   mysql_rwlock_wrlock(&LOCK_grant);
 | |
|   mysql_mutex_lock(&acl_cache->lock);
 | |
| 
 | |
|   while ((tmp_user_from= user_list++))
 | |
|   {
 | |
|     tmp_user_to= user_list++;
 | |
|     if (!(user_from= get_current_user(thd, tmp_user_from, false)))
 | |
|     {
 | |
|       append_user(thd, &wrong_users, user_from);
 | |
|       result= TRUE;
 | |
|       continue;
 | |
|     }
 | |
|     if (!(user_to= get_current_user(thd, tmp_user_to, false)))
 | |
|     {
 | |
|       append_user(thd, &wrong_users, user_to);
 | |
|       result= TRUE;
 | |
|       continue;
 | |
|     }
 | |
|     DBUG_ASSERT(!user_from->is_role());
 | |
|     DBUG_ASSERT(!user_to->is_role());
 | |
| 
 | |
|     /*
 | |
|       Search all in-memory structures and grant tables
 | |
|       for a mention of the new user name.
 | |
|     */
 | |
|     if (handle_grant_data(thd, tables, 0, user_to, NULL) ||
 | |
|         handle_grant_data(thd, tables, 0, user_from, user_to) <= 0)
 | |
|     {
 | |
|       /* NOTE TODO renaming roles is not yet implemented */
 | |
|       append_user(thd, &wrong_users, user_from);
 | |
|       result= TRUE;
 | |
|       continue;
 | |
|     }
 | |
|     some_users_renamed= TRUE;
 | |
|     rebuild_acl_users();
 | |
|   }
 | |
| 
 | |
|   /* Rebuild 'acl_dbs' since 'acl_users' has been modified */
 | |
|   rebuild_acl_dbs();
 | |
| 
 | |
|   /* Rebuild 'acl_check_hosts' since 'acl_users' has been modified */
 | |
|   rebuild_check_host();
 | |
| 
 | |
|   /*
 | |
|     Rebuild every user's role_grants since 'acl_users' has been sorted
 | |
|     and old pointers to ACL_USER elements are no longer valid
 | |
|   */
 | |
|   rebuild_role_grants();
 | |
| 
 | |
|   mysql_mutex_unlock(&acl_cache->lock);
 | |
| 
 | |
|   if (result)
 | |
|     my_error(ER_CANNOT_USER, MYF(0), "RENAME USER", wrong_users.c_ptr_safe());
 | |
| 
 | |
|   if (some_users_renamed && mysql_bin_log.is_open())
 | |
|     result |= write_bin_log(thd, FALSE, thd->query(), thd->query_length());
 | |
| 
 | |
|   mysql_rwlock_unlock(&LOCK_grant);
 | |
|   DBUG_RETURN(result);
 | |
| }
 | |
| 
 | |
| /*
 | |
|   Alter a user's connection and resource settings.
 | |
| 
 | |
|   SYNOPSIS
 | |
|     mysql_alter_user()
 | |
|     thd                         The current thread.
 | |
|     list                        The users to alter.
 | |
| 
 | |
|   RETURN
 | |
|     > 0         Error. Error message already sent.
 | |
|     0           OK.
 | |
| */
 | |
| int mysql_alter_user(THD* thd, List<LEX_USER> &users_list)
 | |
| {
 | |
|   DBUG_ENTER("mysql_alter_user");
 | |
|   int result= 0;
 | |
|   String wrong_users;
 | |
|   bool some_users_altered= false;
 | |
| 
 | |
|   /* The only table we're altering is the user table. */
 | |
|   Grant_tables tables;
 | |
|   if ((result= tables.open_and_lock(thd, Table_user, TL_WRITE)))
 | |
|     DBUG_RETURN(result != 1);
 | |
| 
 | |
|   /* Lock ACL data structures until we finish altering all users. */
 | |
|   mysql_rwlock_wrlock(&LOCK_grant);
 | |
|   mysql_mutex_lock(&acl_cache->lock);
 | |
| 
 | |
|   LEX_USER *tmp_lex_user;
 | |
|   List_iterator<LEX_USER> users_list_iterator(users_list);
 | |
| 
 | |
|   while ((tmp_lex_user= users_list_iterator++))
 | |
|   {
 | |
|     LEX_USER* lex_user= get_current_user(thd, tmp_lex_user, false);
 | |
|     if (!lex_user ||
 | |
|         replace_user_table(thd, tables.user_table(), lex_user, NO_ACL,
 | |
|                            false, false, true))
 | |
|     {
 | |
|       thd->clear_error();
 | |
|       append_user(thd, &wrong_users, tmp_lex_user);
 | |
|       result= TRUE;
 | |
|       continue;
 | |
|     }
 | |
|     some_users_altered= true;
 | |
|   }
 | |
| 
 | |
|   /* Unlock ACL data structures. */
 | |
|   mysql_mutex_unlock(&acl_cache->lock);
 | |
|   mysql_rwlock_unlock(&LOCK_grant);
 | |
| 
 | |
|   if (result)
 | |
|   {
 | |
|     /* 'if exists' flag leads to warnings instead of errors. */
 | |
|     if (thd->lex->create_info.if_exists())
 | |
|     {
 | |
|       push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
 | |
|                           ER_CANNOT_USER,
 | |
|                           ER_THD(thd, ER_CANNOT_USER),
 | |
|                           "ALTER USER", wrong_users.c_ptr_safe());
 | |
|       result= FALSE;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       my_error(ER_CANNOT_USER, MYF(0),
 | |
|                "ALTER USER",
 | |
|                wrong_users.c_ptr_safe());
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (some_users_altered)
 | |
|     result|= write_bin_log(thd, FALSE, thd->query(),
 | |
|                                      thd->query_length());
 | |
|   DBUG_RETURN(result);
 | |
| }
 | |
| 
 | |
| 
 | |
| static bool
 | |
| mysql_revoke_sp_privs(THD *thd, Grant_tables *tables, const Sp_handler *sph,
 | |
|                       const LEX_USER *lex_user)
 | |
| {
 | |
|   bool rc= false;
 | |
|   uint counter, revoked;
 | |
|   do {
 | |
|     HASH *hash= sph->get_priv_hash();
 | |
|     for (counter= 0, revoked= 0 ; counter < hash->records ; )
 | |
|     {
 | |
|       const char *user,*host;
 | |
|       GRANT_NAME *grant_proc= (GRANT_NAME*) my_hash_element(hash, counter);
 | |
|       user= grant_proc->user;
 | |
|       host= safe_str(grant_proc->host.hostname);
 | |
| 
 | |
|       if (!strcmp(lex_user->user.str, user) &&
 | |
|           !strcmp(lex_user->host.str, host))
 | |
|       {
 | |
|         if (replace_routine_table(thd, grant_proc,
 | |
|                                   tables->procs_priv_table().table(),
 | |
|                                   *lex_user,
 | |
|                                   grant_proc->db, grant_proc->tname,
 | |
|                                   sph, ALL_KNOWN_ACL, 1) == 0)
 | |
|         {
 | |
|           revoked= 1;
 | |
|           continue;
 | |
|         }
 | |
|         rc= true;  // Something went wrong
 | |
|       }
 | |
|       counter++;
 | |
|     }
 | |
|   } while (revoked);
 | |
|   return rc;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Revoke all privileges from a list of users.
 | |
| 
 | |
|   SYNOPSIS
 | |
|     mysql_revoke_all()
 | |
|     thd                         The current thread.
 | |
|     list                        The users to revoke all privileges from.
 | |
| 
 | |
|   RETURN
 | |
|     > 0         Error. Error message already sent.
 | |
|     0           OK.
 | |
|     < 0         Error. Error message not yet sent.
 | |
| */
 | |
| 
 | |
| bool mysql_revoke_all(THD *thd,  List <LEX_USER> &list)
 | |
| {
 | |
|   uint counter, revoked;
 | |
|   int result, res;
 | |
|   ACL_DB *acl_db;
 | |
|   DBUG_ENTER("mysql_revoke_all");
 | |
| 
 | |
|   Grant_tables tables;
 | |
|   const uint tables_to_open= Table_user | Table_db | Table_tables_priv |
 | |
|                              Table_columns_priv | Table_procs_priv |
 | |
|                              Table_proxies_priv | Table_roles_mapping;
 | |
|   if ((result= tables.open_and_lock(thd, tables_to_open, TL_WRITE)))
 | |
|     DBUG_RETURN(result != 1);
 | |
| 
 | |
|   DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row());
 | |
| 
 | |
|   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);
 | |
|   while ((tmp_lex_user= user_list++))
 | |
|   {
 | |
|     if (!(lex_user= get_current_user(thd, tmp_lex_user, false)))
 | |
|     {
 | |
|       result= -1;
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     /* This is not a role and the user could not be found */
 | |
|     if (!lex_user->is_role() &&
 | |
|         !find_user_exact(lex_user->host, lex_user->user))
 | |
|     {
 | |
|       result= -1;
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     if (replace_user_table(thd, tables.user_table(), lex_user,
 | |
|                            ALL_KNOWN_ACL, 1, 0, 0))
 | |
|     {
 | |
|       result= -1;
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     /* Remove db access privileges */
 | |
|     /*
 | |
|       Because acl_dbs and column_priv_hash shrink and may re-order
 | |
|       as privileges are removed, removal occurs in a repeated loop
 | |
|       until no more privileges are revoked.
 | |
|      */
 | |
|     do
 | |
|     {
 | |
|       for (counter= 0, revoked= 0 ; counter < acl_dbs.elements() ; )
 | |
|       {
 | |
|         const char *user, *host;
 | |
| 
 | |
|         acl_db= &acl_dbs.at(counter);
 | |
| 
 | |
|         user= acl_db->user;
 | |
|         host= safe_str(acl_db->host.hostname);
 | |
| 
 | |
| 	if (!strcmp(lex_user->user.str, user) &&
 | |
|             !strcmp(lex_user->host.str, host))
 | |
| 	{
 | |
|       /* TODO(cvicentiu) refactor replace_db_table to use
 | |
|          Db_table instead of TABLE directly. */
 | |
| 	  if (!replace_db_table(tables.db_table().table(), acl_db->db, *lex_user,
 | |
|                                 ALL_KNOWN_ACL, 1))
 | |
| 	  {
 | |
| 	    /*
 | |
| 	      Don't increment counter as replace_db_table deleted the
 | |
| 	      current element in acl_dbs.
 | |
| 	     */
 | |
| 	    revoked= 1;
 | |
| 	    continue;
 | |
| 	  }
 | |
| 	  result= -1; // Something went wrong
 | |
| 	}
 | |
| 	counter++;
 | |
|       }
 | |
|     } while (revoked);
 | |
| 
 | |
|     /* Remove column access */
 | |
|     do
 | |
|     {
 | |
|       for (counter= 0, revoked= 0 ; counter < column_priv_hash.records ; )
 | |
|       {
 | |
| 	const char *user,*host;
 | |
|         GRANT_TABLE *grant_table= ((GRANT_TABLE*)
 | |
|                                    my_hash_element(&column_priv_hash, counter));
 | |
| 
 | |
|         user= grant_table->user;
 | |
|         host= safe_str(grant_table->host.hostname);
 | |
| 
 | |
|         if (!strcmp(lex_user->user.str,user) &&
 | |
|             !strcmp(lex_user->host.str, host))
 | |
| 	{
 | |
| 	    List<LEX_COLUMN> columns;
 | |
|             /* TODO(cvicentiu) refactor replace_db_table to use
 | |
|                Db_table instead of TABLE directly. */
 | |
| 	    if (replace_column_table(grant_table,
 | |
|                                      tables.columns_priv_table().table(),
 | |
|                                      *lex_user, columns, grant_table->db,
 | |
|                                      grant_table->tname, ALL_KNOWN_ACL, 1))
 | |
|               result= -1;
 | |
| 
 | |
|           /* TODO(cvicentiu) refactor replace_db_table to use
 | |
|              Db_table instead of TABLE directly. */
 | |
| 	  if ((res= replace_table_table(thd, grant_table,
 | |
|                                         tables.tables_priv_table().table(),
 | |
|                                         *lex_user, grant_table->db,
 | |
|                                         grant_table->tname, ALL_KNOWN_ACL,
 | |
|                                         NO_ACL, 1)))
 | |
| 	  {
 | |
|             if (res > 0)
 | |
|               result= -1;
 | |
|             else
 | |
|             {
 | |
|               /*
 | |
|                 Entry was deleted. We have to retry the loop as the
 | |
|                 hash table has probably been reorganized.
 | |
|               */
 | |
|               revoked= 1;
 | |
|               continue;
 | |
|             }
 | |
|           }
 | |
| 	}
 | |
| 	counter++;
 | |
|       }
 | |
|     } while (revoked);
 | |
| 
 | |
|     /* Remove procedure access */
 | |
|     if (mysql_revoke_sp_privs(thd, &tables, &sp_handler_function, lex_user) ||
 | |
|         mysql_revoke_sp_privs(thd, &tables, &sp_handler_procedure, lex_user) ||
 | |
|         mysql_revoke_sp_privs(thd, &tables, &sp_handler_package_spec, lex_user) ||
 | |
|         mysql_revoke_sp_privs(thd, &tables, &sp_handler_package_body, lex_user))
 | |
|       result= -1;
 | |
| 
 | |
|     ACL_USER_BASE *user_or_role;
 | |
|     /* remove role grants */
 | |
|     if (lex_user->is_role())
 | |
|     {
 | |
|       /* this can not fail due to get_current_user already having searched for it */
 | |
|       user_or_role= find_acl_role(lex_user->user, true);
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       user_or_role= find_user_exact(lex_user->host, lex_user->user);
 | |
|     }
 | |
|     /*
 | |
|       Find every role grant pair matching the role_grants array and remove it,
 | |
|       both from the acl_roles_mappings and the roles_mapping table
 | |
|     */
 | |
|     for (counter= 0; counter < user_or_role->role_grants.elements; counter++)
 | |
|     {
 | |
|       ACL_ROLE *role_grant= *dynamic_element(&user_or_role->role_grants,
 | |
|                                              counter, ACL_ROLE**);
 | |
|       ROLE_GRANT_PAIR *pair = find_role_grant_pair(&lex_user->user,
 | |
|                                                    &lex_user->host,
 | |
|                                                    &role_grant->user);
 | |
|       /* TODO(cvicentiu) refactor replace_roles_mapping_table to use
 | |
|          Roles_mapping_table instead of TABLE directly. */
 | |
|       if (replace_roles_mapping_table(tables.roles_mapping_table().table(),
 | |
|                                       &lex_user->user, &lex_user->host,
 | |
|                                       &role_grant->user, false, pair, true))
 | |
|       {
 | |
|         result= -1; //Something went wrong
 | |
|       }
 | |
|       update_role_mapping(&lex_user->user, &lex_user->host,
 | |
|                           &role_grant->user, false, pair, true);
 | |
|       /*
 | |
|         Delete from the parent_grantee array of the roles granted,
 | |
|         the entry pointing to this user_or_role
 | |
|       */
 | |
|       remove_ptr_from_dynarray(&role_grant->parent_grantee, user_or_role);
 | |
|     }
 | |
|     /* TODO
 | |
|        How to handle an error in the replace_roles_mapping_table, in
 | |
|        regards to the privileges held in memory
 | |
|     */
 | |
| 
 | |
|     /* Finally, clear the role_grants array */
 | |
|     if (counter == user_or_role->role_grants.elements)
 | |
|     {
 | |
|       reset_dynamic(&user_or_role->role_grants);
 | |
|     }
 | |
|     /*
 | |
|       If we are revoking from a role, we need to update all the parent grantees
 | |
|     */
 | |
|     if (lex_user->is_role())
 | |
|     {
 | |
|       propagate_role_grants((ACL_ROLE *)user_or_role, PRIVS_TO_MERGE::ALL);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   mysql_mutex_unlock(&acl_cache->lock);
 | |
| 
 | |
|   if (result)
 | |
|     my_message(ER_REVOKE_GRANTS, ER_THD(thd, ER_REVOKE_GRANTS), MYF(0));
 | |
|   
 | |
|   result= result |
 | |
|     write_bin_log(thd, FALSE, thd->query(), thd->query_length());
 | |
| 
 | |
|   mysql_rwlock_unlock(&LOCK_grant);
 | |
| 
 | |
|   DBUG_RETURN(result);
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| /**
 | |
|   If the defining user for a routine does not exist, then the ACL lookup
 | |
|   code should raise two errors which we should intercept.  We convert the more
 | |
|   descriptive error into a warning, and consume the other.
 | |
| 
 | |
|   If any other errors are raised, then we set a flag that should indicate
 | |
|   that there was some failure we should complain at a higher level.
 | |
| */
 | |
| class Silence_routine_definer_errors : public Internal_error_handler
 | |
| {
 | |
| public:
 | |
|   Silence_routine_definer_errors()
 | |
|     : is_grave(FALSE)
 | |
|   {}
 | |
| 
 | |
|   ~Silence_routine_definer_errors() override = default;
 | |
| 
 | |
|   bool handle_condition(THD *thd,
 | |
|                                 uint sql_errno,
 | |
|                                 const char* sqlstate,
 | |
|                                 Sql_condition::enum_warning_level *level,
 | |
|                                 const char* msg,
 | |
|                                 Sql_condition ** cond_hdl) override;
 | |
| 
 | |
|   bool has_errors() { return is_grave; }
 | |
| 
 | |
| private:
 | |
|   bool is_grave;
 | |
| };
 | |
| 
 | |
| bool
 | |
| Silence_routine_definer_errors::handle_condition(
 | |
|   THD *thd,
 | |
|   uint sql_errno,
 | |
|   const char*,
 | |
|   Sql_condition::enum_warning_level *level,
 | |
|   const char* msg,
 | |
|   Sql_condition ** cond_hdl)
 | |
| {
 | |
|   *cond_hdl= NULL;
 | |
|   if (*level == Sql_condition::WARN_LEVEL_ERROR)
 | |
|   {
 | |
|     switch (sql_errno)
 | |
|     {
 | |
|       case ER_NONEXISTING_PROC_GRANT:
 | |
|         /* Convert the error into a warning. */
 | |
|         push_warning(thd, Sql_condition::WARN_LEVEL_WARN,
 | |
|                      sql_errno, msg);
 | |
|         return TRUE;
 | |
|       default:
 | |
|         is_grave= TRUE;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return FALSE;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Revoke privileges for all users on a stored procedure.  Use an error handler
 | |
|   that converts errors about missing grants into warnings.
 | |
| 
 | |
|   @param
 | |
|     thd                         The current thread.
 | |
|   @param
 | |
|     db				DB of the stored procedure
 | |
|   @param
 | |
|     name			Name of the stored procedure
 | |
| 
 | |
|   @retval
 | |
|     0           OK.
 | |
|   @retval
 | |
|     < 0         Error. Error message not yet sent.
 | |
| */
 | |
| 
 | |
| bool sp_revoke_privileges(THD *thd,
 | |
|                           const Lex_ident_db &sp_db,
 | |
|                           const Lex_ident_routine &sp_name,
 | |
|                           const Sp_handler *sph)
 | |
| {
 | |
|   uint counter, revoked;
 | |
|   int result;
 | |
|   HASH *hash= sph->get_priv_hash();
 | |
|   Silence_routine_definer_errors error_handler;
 | |
|   DBUG_ENTER("sp_revoke_privileges");
 | |
| 
 | |
|   Grant_tables tables;
 | |
|   const uint tables_to_open= Table_user | Table_db | Table_tables_priv |
 | |
|                              Table_columns_priv | Table_procs_priv |
 | |
|                              Table_proxies_priv | Table_roles_mapping;
 | |
|   if ((result= tables.open_and_lock(thd, tables_to_open, TL_WRITE)))
 | |
|     DBUG_RETURN(result != 1);
 | |
| 
 | |
|   DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row());
 | |
| 
 | |
|   /* Be sure to pop this before exiting this scope! */
 | |
|   thd->push_internal_handler(&error_handler);
 | |
| 
 | |
|   mysql_rwlock_wrlock(&LOCK_grant);
 | |
|   mysql_mutex_lock(&acl_cache->lock);
 | |
| 
 | |
|   /* Remove procedure access */
 | |
|   do
 | |
|   {
 | |
|     for (counter= 0, revoked= 0 ; counter < hash->records ; )
 | |
|     {
 | |
|       GRANT_NAME *grant_proc= (GRANT_NAME*) my_hash_element(hash, counter);
 | |
|       if (sp_db.streq(Lex_cstring_strlen(grant_proc->db)) &&
 | |
|           sp_name.streq(Lex_cstring_strlen(grant_proc->tname)))
 | |
|       {
 | |
|         LEX_USER lex_user;
 | |
| 	lex_user.user.str= grant_proc->user;
 | |
| 	lex_user.user.length= strlen(grant_proc->user);
 | |
|         lex_user.host.str= safe_str(grant_proc->host.hostname);
 | |
|         lex_user.host.length= strlen(lex_user.host.str);
 | |
|         if (replace_routine_table(thd, grant_proc,
 | |
|                                   tables.procs_priv_table().table(), lex_user,
 | |
|                                   grant_proc->db, grant_proc->tname,
 | |
|                                   sph, ALL_KNOWN_ACL, 1) == 0)
 | |
| 	{
 | |
| 	  revoked= 1;
 | |
| 	  continue;
 | |
| 	}
 | |
|       }
 | |
|       counter++;
 | |
|     }
 | |
|   } while (revoked);
 | |
| 
 | |
|   mysql_mutex_unlock(&acl_cache->lock);
 | |
|   mysql_rwlock_unlock(&LOCK_grant);
 | |
| 
 | |
|   thd->pop_internal_handler();
 | |
| 
 | |
|   DBUG_RETURN(error_handler.has_errors());
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Grant EXECUTE,ALTER privilege for a stored procedure
 | |
| 
 | |
|   @param thd The current thread.
 | |
|   @param sp_db
 | |
|   @param sp_name
 | |
|   @param sph
 | |
| 
 | |
|   @return
 | |
|     @retval FALSE Success
 | |
|     @retval TRUE An error occurred. Error message not yet sent.
 | |
| */
 | |
| 
 | |
| bool sp_grant_privileges(THD *thd,
 | |
|                          const Lex_ident_db &sp_db,
 | |
|                          const Lex_ident_routine &sp_name,
 | |
|                          const Sp_handler *sph)
 | |
| {
 | |
|   Security_context *sctx= thd->security_ctx;
 | |
|   LEX_USER *combo;
 | |
|   TABLE_LIST tables[1];
 | |
|   List<LEX_USER> user_list;
 | |
|   bool result;
 | |
|   ACL_USER *au;
 | |
|   Dummy_error_handler error_handler;
 | |
|   DBUG_ENTER("sp_grant_privileges");
 | |
| 
 | |
|   Lex_cstring_strlen sctx_user(sctx->priv_user);
 | |
|   Lex_cstring_strlen sctx_host(sctx->priv_host);
 | |
| 
 | |
|   if (!(combo= thd->alloc<LEX_USER>(1)))
 | |
|     DBUG_RETURN(TRUE);
 | |
| 
 | |
|   mysql_mutex_lock(&acl_cache->lock);
 | |
| 
 | |
|   if ((au= find_user_exact(sctx_host, sctx_user)))
 | |
|     goto found_acl;
 | |
| 
 | |
|   mysql_mutex_unlock(&acl_cache->lock);
 | |
|   DBUG_RETURN(TRUE);
 | |
| 
 | |
|  found_acl:
 | |
|   mysql_mutex_unlock(&acl_cache->lock);
 | |
| 
 | |
|   bzero((char*)tables, sizeof(TABLE_LIST));
 | |
|   user_list.empty();
 | |
| 
 | |
|   tables->db= sp_db;
 | |
|   tables->table_name= tables->alias= Lex_ident_table(sp_name);
 | |
| 
 | |
|   thd->make_lex_string(&combo->user, sctx_user.str, sctx_user.length);
 | |
|   thd->make_lex_string(&combo->host, sctx_host.str, sctx_host.length);
 | |
| 
 | |
|   combo->auth= NULL;
 | |
| 
 | |
|   if (user_list.push_back(combo, thd->mem_root))
 | |
|     DBUG_RETURN(TRUE);
 | |
| 
 | |
|   thd->lex->account_options.reset();
 | |
| 
 | |
|   /*
 | |
|     Only care about whether the operation failed or succeeded
 | |
|     as all errors will be handled later.
 | |
|   */
 | |
|   thd->push_internal_handler(&error_handler);
 | |
|   result= mysql_routine_grant(thd, tables, sph, user_list,
 | |
|                               DEFAULT_CREATE_PROC_ACLS, FALSE, FALSE);
 | |
|   thd->pop_internal_handler();
 | |
|   DBUG_RETURN(result);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   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 LEX_CSTRING &host,
 | |
|                              const LEX_CSTRING &user,
 | |
|                              bool with_grant)
 | |
| {
 | |
|   DBUG_ENTER("acl_check_proxy_grant_access");
 | |
|   DBUG_PRINT("info", ("user=%s host=%s with_grant=%d", user.str, host.str,
 | |
|                       (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
 | |
|         authentication 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 (thd->security_ctx->is_priv_user(user, host))
 | |
|   {
 | |
|     DBUG_PRINT("info", ("strcmp (%s, %s) my_casestrcmp (%s, %s) equal",
 | |
|                         thd->security_ctx->priv_user, user.str,
 | |
|                         host.str, thd->security_ctx->priv_host));
 | |
|     DBUG_RETURN(FALSE);
 | |
|   }
 | |
| 
 | |
|   mysql_mutex_lock(&acl_cache->lock);
 | |
| 
 | |
|   /* 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.str) &&
 | |
|         proxy->get_with_grant())
 | |
|     {
 | |
|       DBUG_PRINT("info", ("found"));
 | |
|       mysql_mutex_unlock(&acl_cache->lock);
 | |
|       DBUG_RETURN(FALSE);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   mysql_mutex_unlock(&acl_cache->lock);
 | |
|   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, const char *username, const char *hostname,
 | |
|                   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(hostname, username))
 | |
|     {
 | |
|       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;
 | |
| }
 | |
| 
 | |
| static int enabled_roles_insert(ACL_USER_BASE *role, void *context_data)
 | |
| {
 | |
|   TABLE *table= (TABLE*) context_data;
 | |
|   DBUG_ASSERT(role->flags & IS_ROLE);
 | |
| 
 | |
|   restore_record(table, s->default_values);
 | |
|   table->field[0]->set_notnull();
 | |
|   table->field[0]->store(role->user.str, role->user.length,
 | |
|                          system_charset_info);
 | |
|   if (schema_table_store_record(table->in_use, table))
 | |
|     return -1;
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| struct APPLICABLE_ROLES_DATA
 | |
| {
 | |
|   TABLE *table;
 | |
|   const LEX_CSTRING host;
 | |
|   const LEX_CSTRING user_and_host;
 | |
|   ACL_USER *user;
 | |
| };
 | |
| 
 | |
| static int
 | |
| applicable_roles_insert(ACL_USER_BASE *grantee, ACL_ROLE *role, void *ptr)
 | |
| {
 | |
|   APPLICABLE_ROLES_DATA *data= (APPLICABLE_ROLES_DATA *)ptr;
 | |
|   CHARSET_INFO *cs= system_charset_info;
 | |
|   TABLE *table= data->table;
 | |
|   bool is_role= grantee != data->user;
 | |
|   const LEX_CSTRING *user_and_host= is_role ? &grantee->user
 | |
|                                            : &data->user_and_host;
 | |
|   const LEX_CSTRING *host= is_role ? &empty_clex_str : &data->host;
 | |
| 
 | |
|   restore_record(table, s->default_values);
 | |
|   table->field[0]->store(user_and_host->str, user_and_host->length, cs);
 | |
|   table->field[1]->store(role->user.str, role->user.length, cs);
 | |
| 
 | |
|   ROLE_GRANT_PAIR *pair=
 | |
|     find_role_grant_pair(&grantee->user, host, &role->user);
 | |
|   DBUG_ASSERT(pair);
 | |
| 
 | |
|   if (pair->with_admin)
 | |
|     table->field[2]->store(STRING_WITH_LEN("YES"), cs);
 | |
|   else
 | |
|     table->field[2]->store(STRING_WITH_LEN("NO"), cs);
 | |
| 
 | |
|   /* Default role is only valid when looking at a role granted to a user. */
 | |
|   if (!is_role)
 | |
|   {
 | |
|     if (data->user->default_rolename.length &&
 | |
|         lex_string_eq(&data->user->default_rolename, &role->user))
 | |
|       table->field[3]->store(STRING_WITH_LEN("YES"), cs);
 | |
|     else
 | |
|       table->field[3]->store(STRING_WITH_LEN("NO"), cs);
 | |
|     table->field[3]->set_notnull();
 | |
|   }
 | |
| 
 | |
|   if (schema_table_store_record(table->in_use, table))
 | |
|     return -1;
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| /**
 | |
|   Hash iterate function to count the number of total column privileges granted.
 | |
| */
 | |
| static my_bool count_column_grants(void *grant_table,
 | |
|                                        void *current_count)
 | |
| {
 | |
|   HASH hash_columns = ((GRANT_TABLE *)grant_table)->hash_columns;
 | |
|   *(ulong *)current_count+= hash_columns.records;
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| /**
 | |
|   SHOW function that computes the number of column grants.
 | |
| 
 | |
|   This must be performed under the mutex in order to make sure the
 | |
|   iteration does not fail.
 | |
| */
 | |
| static int show_column_grants(THD *thd, SHOW_VAR *var, void *buff,
 | |
|                               system_status_var *, enum enum_var_type scope)
 | |
| {
 | |
|   var->type= SHOW_ULONG;
 | |
|   var->value= buff;
 | |
|   *(ulong *)buff= 0;
 | |
|   if (initialized)
 | |
|   {
 | |
|     mysql_rwlock_rdlock(&LOCK_grant);
 | |
|     mysql_mutex_lock(&acl_cache->lock);
 | |
|     my_hash_iterate(&column_priv_hash, count_column_grants, buff);
 | |
|     mysql_mutex_unlock(&acl_cache->lock);
 | |
|     mysql_rwlock_unlock(&LOCK_grant);
 | |
|   }
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| static int show_database_grants(THD *thd, SHOW_VAR *var, void *buff,
 | |
|                                 system_status_var *, enum enum_var_type scope)
 | |
| {
 | |
|   var->type= SHOW_UINT;
 | |
|   var->value= buff;
 | |
|   *(uint *)buff= uint(acl_dbs.elements());
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| #else
 | |
| static bool set_user_salt_if_needed(ACL_USER *, int, plugin_ref)
 | |
| { return 0; }
 | |
| bool check_grant(THD *, privilege_t, TABLE_LIST *, bool, uint, bool)
 | |
| { return 0; }
 | |
| inline privilege_t public_access()
 | |
| { return NO_ACL; }
 | |
| privilege_t get_column_grant(THD *, GRANT_INFO *, const char *, const char *,
 | |
|                              const Lex_ident_column &)
 | |
| { return ALL_KNOWN_ACL; }
 | |
| int acl_check_setrole(THD *, const LEX_CSTRING &, privilege_t *) { return 0; }
 | |
| int acl_setrole(THD *, const LEX_CSTRING &, privilege_t) { return 0; }
 | |
| #endif /*NO_EMBEDDED_ACCESS_CHECKS */
 | |
| 
 | |
| static int set_privs_on_login(THD *thd, const ACL_USER *acl_user)
 | |
| {
 | |
|   Security_context *sctx= thd->security_ctx;
 | |
|   strmake_buf(sctx->priv_user, acl_user->user.str);
 | |
| 
 | |
|   if (acl_user->host.hostname)
 | |
|     strmake_buf(sctx->priv_host, acl_user->host.hostname);
 | |
| 
 | |
|   sctx->master_access= acl_user->access | public_access();
 | |
| 
 | |
|   if (acl_user->default_rolename.length)
 | |
|   {
 | |
|     privilege_t access(NO_ACL);
 | |
|     int result= acl_check_setrole(thd, acl_user->default_rolename, &access);
 | |
|     if (!result)
 | |
|       result= acl_setrole(thd, acl_user->default_rolename, access);
 | |
|     thd->clear_error();
 | |
|   }
 | |
| 
 | |
|   /*
 | |
|     Don't allow the user to connect if he has done too many queries.
 | |
|     As we are testing max_user_connections == 0 here, it means that we
 | |
|     can't let the user change max_user_connections from 0 in the server
 | |
|     without a restart as it would lead to wrong connect counting.
 | |
|   */
 | |
|   if ((acl_user->user_resource.questions ||
 | |
|        acl_user->user_resource.updates ||
 | |
|        acl_user->user_resource.conn_per_hour ||
 | |
|        acl_user->user_resource.user_conn ||
 | |
|        acl_user->user_resource.max_statement_time != 0.0 ||
 | |
|        max_user_connections_checking) &&
 | |
|        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))
 | |
|     return 1; // The error is set by get_or_create_user_conn()
 | |
| 
 | |
|   if (acl_user->user_resource.max_statement_time != 0.0)
 | |
|   {
 | |
|     thd->variables.max_statement_time_double=
 | |
|       acl_user->user_resource.max_statement_time;
 | |
|     thd->variables.max_statement_time=
 | |
|       (ulonglong) (thd->variables.max_statement_time_double * 1e6 + 0.1);
 | |
|   }
 | |
| 
 | |
|   thd->session_tracker.sysvars.mark_all_as_changed(thd);
 | |
|   thd->session_tracker.current_schema.mark_as_changed(thd);
 | |
|   thd->session_tracker.state_change.mark_as_changed(thd);
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| #ifdef NO_EMBEDDED_ACCESS_CHECKS
 | |
| 
 | |
| bool Sql_cmd_grant_proxy::execute(THD *thd)
 | |
| {
 | |
|   my_ok(thd);
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| bool Sql_cmd_grant_table::execute(THD *thd)
 | |
| {
 | |
|   my_ok(thd);
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| 
 | |
| bool Sql_cmd_grant_sp::execute(THD *thd)
 | |
| {
 | |
|   my_ok(thd);
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| #else // not NO_EMBEDDED_ACCESS_CHECKS
 | |
| 
 | |
| 
 | |
| void Sql_cmd_grant::warn_hostname_requires_resolving(THD *thd,
 | |
|                                                      List<LEX_USER> &users)
 | |
| {
 | |
|   LEX_USER *user;
 | |
|   List_iterator <LEX_USER> it(users);
 | |
|   while ((user= it++))
 | |
|   {
 | |
|     if (opt_skip_name_resolve && hostname_requires_resolving(user->host.str))
 | |
|       push_warning(thd, Sql_condition::WARN_LEVEL_WARN,
 | |
|                    ER_WARN_HOSTNAME_WONT_WORK,
 | |
|                    ER_THD(thd, ER_WARN_HOSTNAME_WONT_WORK));
 | |
|   }
 | |
| }
 | |
| 
 | |
| 
 | |
| void Sql_cmd_grant::grant_stage0(THD *thd)
 | |
| {
 | |
|   thd->binlog_invoker(false);   // Replicate current user as grantor
 | |
|   if (thd->security_ctx->user)  // If not replication
 | |
|     warn_hostname_requires_resolving(thd, thd->lex->users_list);
 | |
| }
 | |
| 
 | |
| 
 | |
| bool Sql_cmd_grant::user_list_reset_mqh(THD *thd, List<LEX_USER> &users)
 | |
| {
 | |
|   List_iterator <LEX_USER> it(users);
 | |
|   LEX_USER *user, *tmp_user;
 | |
|   while ((tmp_user= it++))
 | |
|   {
 | |
|     if (!(user= get_current_user(thd, tmp_user)))
 | |
|       return true;
 | |
|     reset_mqh(user, 0);
 | |
|   }
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| 
 | |
| bool Sql_cmd_grant_proxy::check_access_proxy(THD *thd, List<LEX_USER> &users)
 | |
| {
 | |
|   LEX_USER *user;
 | |
|   List_iterator <LEX_USER> it(users);
 | |
|   if ((user= it++))
 | |
|   {
 | |
|     // GRANT/REVOKE PROXY has the target user as a first entry in the list
 | |
|     if (!(user= get_current_user(thd, user)) || !user->host.str)
 | |
|       return true;
 | |
|     if (acl_check_proxy_grant_access(thd, user->host, user->user,
 | |
|                                      m_grant_option & GRANT_ACL))
 | |
|       return true;
 | |
|   }
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| 
 | |
| bool Sql_cmd_grant_proxy::execute(THD *thd)
 | |
| {
 | |
|   LEX  *lex= thd->lex;
 | |
| 
 | |
|   DBUG_ASSERT(lex->first_select_lex()->table_list.first == NULL);
 | |
|   DBUG_ASSERT((m_grant_option & ~GRANT_ACL) == NO_ACL); // only WITH GRANT OPTION
 | |
| 
 | |
|   grant_stage0(thd);
 | |
| 
 | |
|   if (thd->security_ctx->user /* If not replication */ &&
 | |
|       check_access_proxy(thd, lex->users_list))
 | |
|     return true;
 | |
| 
 | |
|   WSREP_TO_ISOLATION_BEGIN(WSREP_MYSQL_DB, NULL, NULL);
 | |
|   /* Conditionally writes to binlog */
 | |
|   if (mysql_grant(thd, null_clex_str/*db*/, lex->users_list, m_grant_option,
 | |
|                   is_revoke(), true/*proxy*/))
 | |
|     return true;
 | |
| 
 | |
|   return !is_revoke() && user_list_reset_mqh(thd, lex->users_list);
 | |
| 
 | |
| #ifdef WITH_WSREP
 | |
| wsrep_error_label:
 | |
|   return true;
 | |
| #endif // WITH_WSREP
 | |
| }
 | |
| 
 | |
| 
 | |
| bool Sql_cmd_grant_object::grant_stage0_exact_object(THD *thd,
 | |
|                                                      TABLE_LIST *table)
 | |
| {
 | |
|   privilege_t priv= m_object_privilege | m_column_privilege_total | GRANT_ACL;
 | |
|   if (check_access(thd, priv, table->db.str,
 | |
|                    &table->grant.privilege, &table->grant.m_internal,
 | |
|                    0, 0))
 | |
|     return true;
 | |
|   grant_stage0(thd);
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| 
 | |
| bool Sql_cmd_grant_table::execute_exact_table(THD *thd, TABLE_LIST *table)
 | |
| {
 | |
|   LEX  *lex= thd->lex;
 | |
|   if (grant_stage0_exact_object(thd, table) ||
 | |
|       check_grant(thd, m_object_privilege | m_column_privilege_total | GRANT_ACL,
 | |
|                   lex->query_tables, FALSE, UINT_MAX, FALSE))
 | |
|     return true;
 | |
|   /* Conditionally writes to binlog */
 | |
|   WSREP_TO_ISOLATION_BEGIN(WSREP_MYSQL_DB, NULL, NULL);
 | |
|   return mysql_table_grant(thd, lex->query_tables, lex->users_list,
 | |
|                            m_columns, m_object_privilege,
 | |
|                            is_revoke());
 | |
| #ifdef WITH_WSREP
 | |
| wsrep_error_label:
 | |
|   return true;
 | |
| #endif // WITH_WSREP
 | |
| }
 | |
| 
 | |
| 
 | |
| bool Sql_cmd_grant_sp::execute(THD *thd)
 | |
| {
 | |
|   DBUG_ASSERT(!m_columns.elements);
 | |
|   DBUG_ASSERT(!m_column_privilege_total);
 | |
|   LEX  *lex= thd->lex;
 | |
|   TABLE_LIST *table= lex->first_select_lex()->table_list.first;
 | |
|   privilege_t grants= m_all_privileges
 | |
|                ? (PROC_ACLS & ~GRANT_ACL) | (m_object_privilege & GRANT_ACL)
 | |
|                : m_object_privilege;
 | |
| 
 | |
|   if (!table) // e.g: GRANT EXECUTE ON PROCEDURE *.*
 | |
|   {
 | |
|     my_message(ER_ILLEGAL_GRANT_FOR_TABLE, ER_THD(thd, ER_ILLEGAL_GRANT_FOR_TABLE),
 | |
|                MYF(0));
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   if (grant_stage0_exact_object(thd, table) ||
 | |
|       check_grant_routine(thd, grants|GRANT_ACL, lex->query_tables, &m_sph, 0))
 | |
|     return true;
 | |
| 
 | |
|   /* Conditionally writes to binlog */
 | |
|   WSREP_TO_ISOLATION_BEGIN(WSREP_MYSQL_DB, NULL, NULL);
 | |
|   if (mysql_routine_grant(thd, lex->query_tables, &m_sph,
 | |
|                           lex->users_list, grants,
 | |
|                           is_revoke(), true))
 | |
|     return true;
 | |
|   my_ok(thd);
 | |
|   return false;
 | |
| #ifdef WITH_WSREP
 | |
| wsrep_error_label:
 | |
|   return true;
 | |
| #endif // WITH_WSREP
 | |
| }
 | |
| 
 | |
| 
 | |
| bool Sql_cmd_grant_table::execute_table_mask(THD *thd)
 | |
| {
 | |
|   LEX  *lex= thd->lex;
 | |
|   DBUG_ASSERT(lex->first_select_lex()->table_list.first == NULL);
 | |
| 
 | |
|   if (check_access(thd, m_object_privilege | m_column_privilege_total | GRANT_ACL,
 | |
|                    m_db.str, NULL, NULL, 1, 0))
 | |
|     return true;
 | |
| 
 | |
|   grant_stage0(thd);
 | |
| 
 | |
|   if (m_columns.elements) // e.g. GRANT SELECT (a) ON *.*
 | |
|   {
 | |
|     my_message(ER_ILLEGAL_GRANT_FOR_TABLE, ER_THD(thd, ER_ILLEGAL_GRANT_FOR_TABLE),
 | |
|                MYF(0));
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   WSREP_TO_ISOLATION_BEGIN(WSREP_MYSQL_DB, NULL, NULL);
 | |
|   /* Conditionally writes to binlog */
 | |
|   if (mysql_grant(thd, m_db, lex->users_list, m_object_privilege,
 | |
|                   is_revoke(), false/*not proxy*/))
 | |
|     return true;
 | |
| 
 | |
|   return !is_revoke() && user_list_reset_mqh(thd, lex->users_list);
 | |
| 
 | |
| #ifdef WITH_WSREP
 | |
| wsrep_error_label:
 | |
|   return true;
 | |
| #endif // WITH_WSREP
 | |
| }
 | |
| 
 | |
| 
 | |
| bool Sql_cmd_grant_table::execute(THD *thd)
 | |
| {
 | |
|   TABLE_LIST *table= thd->lex->first_select_lex()->table_list.first;
 | |
|   return table ? execute_exact_table(thd, table) :
 | |
|                  execute_table_mask(thd);
 | |
| }
 | |
| 
 | |
| 
 | |
| int acl_setauthorization(THD *thd, const LEX_USER *user)
 | |
| {
 | |
|   if (!initialized)
 | |
|   {
 | |
|     my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--skip-grant-tables");
 | |
|     return 1;
 | |
|   }
 | |
| 
 | |
|   Security_context *sctx= thd->security_ctx, save_security_ctx= *sctx;
 | |
| 
 | |
|   DBUG_ASSERT(*user->host.str); // guaranteed by the parser
 | |
| 
 | |
|   if (user->host.str == host_not_specified.str)
 | |
|   {
 | |
|     my_error(ER_NO_SUCH_USER, MYF(0), user->user.str, "");
 | |
|     return 1;
 | |
|   }
 | |
| 
 | |
|   if (!user->user.length)
 | |
|   {
 | |
|     my_error(ER_NO_SUCH_USER, MYF(0), "", user->host.str);
 | |
|     return 1;
 | |
|   }
 | |
| 
 | |
|   mysql_mutex_lock(&acl_cache->lock);
 | |
|   ACL_USER *acl_user= find_user_or_anon(user->host.str, user->user.str, user->host.str);
 | |
|   if (acl_user)
 | |
|     acl_user= acl_user->copy(thd->mem_root);
 | |
|   mysql_mutex_unlock(&acl_cache->lock);
 | |
| 
 | |
|   if (!acl_user)
 | |
|   {
 | |
|     if (!(sctx->master_access & PRIV_SUDO_CHANGE_USER))
 | |
|       goto access_denied;
 | |
|     my_error(ER_NO_SUCH_USER, MYF(0), user->user.str, user->host.str);
 | |
|     return 1;
 | |
|   }
 | |
| 
 | |
|   if (!(sctx->master_access & PRIV_SUDO_CHANGE_USER) &&
 | |
|       (strcmp(user->user.str, sctx->user) ||
 | |
|        strcmp(user->host.str, sctx->host_or_ip) ||
 | |
|        strcmp(acl_user->user.str, sctx->priv_user) ||
 | |
|        strcmp(acl_user->host.hostname, sctx->priv_host)))
 | |
|     goto access_denied;
 | |
| 
 | |
|   thd->change_user();
 | |
|   thd->clear_error();                         // if errors from rollback
 | |
|   my_free(const_cast<char*>(thd->db.str));
 | |
|   thd->reset_db(&null_clex_str);
 | |
| 
 | |
|   sctx->init();
 | |
|   sctx->user= *user->user.str
 | |
|               ? my_strndup(key_memory_MPVIO_EXT_auth_info, user->user.str,
 | |
|                            user->user.length, MYF(MY_WME))
 | |
|               : NULL;
 | |
|   if (strcmp(user->host.str, my_localhost) == 0)
 | |
|     sctx->host= my_localhost;
 | |
|   else
 | |
|     sctx->host= my_strndup(PSI_INSTRUMENT_ME, user->host.str,
 | |
|                            user->host.length, MYF(MY_WME));
 | |
|   sctx->host_or_ip= sctx->host;
 | |
|   sctx->ip= 0;
 | |
| 
 | |
|   if (set_privs_on_login(thd, acl_user))
 | |
|   {
 | |
|     sctx->destroy();
 | |
|     *sctx= save_security_ctx;
 | |
|     return 1;
 | |
|   }
 | |
| 
 | |
|   mysql_audit_notify_connection_change_user(thd, &save_security_ctx);
 | |
|   save_security_ctx.destroy();
 | |
|   return 0;
 | |
| 
 | |
| access_denied:
 | |
|   my_error(ER_ACCESS_DENIED_CHANGE_USER_ERROR, MYF(0),
 | |
|            user->user.str, user->host.str);
 | |
|   status_var_increment(thd->status_var.access_denied_errors);
 | |
|   return 1;
 | |
| }
 | |
| 
 | |
| #endif // NO_EMBEDDED_ACCESS_CHECKS
 | |
| 
 | |
| 
 | |
| 
 | |
| SHOW_VAR acl_statistics[] = {
 | |
| #ifndef NO_EMBEDDED_ACCESS_CHECKS
 | |
|   {"column_grants",    (char*)show_column_grants,          SHOW_SIMPLE_FUNC},
 | |
|   {"database_grants",  (char*)show_database_grants,        SHOW_SIMPLE_FUNC},
 | |
|   {"function_grants",  (char*)&func_priv_hash.records,     SHOW_ULONG},
 | |
|   {"procedure_grants", (char*)&proc_priv_hash.records,     SHOW_ULONG},
 | |
|   {"package_spec_grants", (char*)&package_spec_priv_hash.records, SHOW_ULONG},
 | |
|   {"package_body_grants", (char*)&package_body_priv_hash.records, SHOW_ULONG},
 | |
|   {"proxy_users",      (char*)&acl_proxy_users.elements,   SHOW_SIZE_T},
 | |
|   {"role_grants",      (char*)&acl_roles_mappings.records, SHOW_ULONG},
 | |
|   {"roles",            (char*)&acl_roles.records,          SHOW_ULONG},
 | |
|   {"table_grants",     (char*)&column_priv_hash.records,   SHOW_ULONG},
 | |
|   {"users",            (char*)&acl_users.elements,         SHOW_SIZE_T},
 | |
| #endif
 | |
|   {NullS, NullS, SHOW_LONG},
 | |
| };
 | |
| 
 | |
| /* Check if a role is granted to a user/role. We traverse the role graph
 | |
|    and return true if we find a match.
 | |
| 
 | |
|    hostname == NULL means we are looking for a role as a starting point,
 | |
|    otherwise a user.
 | |
| */
 | |
| bool check_role_is_granted(const char *username, const char *hostname,
 | |
|                            const char *rolename)
 | |
| {
 | |
|   DBUG_ENTER("check_role_is_granted");
 | |
|   bool result= false;
 | |
| #ifndef NO_EMBEDDED_ACCESS_CHECKS
 | |
|   ACL_USER_BASE *root;
 | |
|   mysql_mutex_lock(&acl_cache->lock);
 | |
|   if (hostname)
 | |
|     root= find_user_exact(Lex_cstring_strlen(hostname),
 | |
|                           Lex_cstring_strlen(username));
 | |
|   else
 | |
|     root= find_acl_role(Lex_cstring_strlen(username), false);
 | |
| 
 | |
|   LEX_CSTRING role_lex;
 | |
|   role_lex.str= rolename;
 | |
|   role_lex.length= strlen(rolename);
 | |
| 
 | |
|   if (root && /* No grantee, nothing to search. */
 | |
|       traverse_role_graph_down(root, &role_lex, check_role_is_granted_callback,
 | |
|                                NULL) == -1)
 | |
|   {
 | |
|     /* We have found the role during our search. */
 | |
|     result= true;
 | |
|   }
 | |
| 
 | |
|   /* We haven't found the role or we had no initial grantee to start from. */
 | |
|   mysql_mutex_unlock(&acl_cache->lock);
 | |
| #endif
 | |
|   DBUG_RETURN(result);
 | |
| }
 | |
| 
 | |
| int fill_schema_enabled_roles(THD *thd, TABLE_LIST *tables, COND *cond)
 | |
| {
 | |
|   TABLE *table= tables->table;
 | |
| #ifndef NO_EMBEDDED_ACCESS_CHECKS
 | |
|   if (thd->security_ctx->priv_role[0])
 | |
|   {
 | |
|     mysql_rwlock_rdlock(&LOCK_grant);
 | |
|     mysql_mutex_lock(&acl_cache->lock);
 | |
|     ACL_ROLE *acl_role= find_acl_role(Lex_cstring_strlen(
 | |
|                                         thd->security_ctx->priv_role),
 | |
|                                       false);
 | |
|     if (acl_role)
 | |
|       traverse_role_graph_down(acl_role, table, enabled_roles_insert, NULL);
 | |
|     mysql_mutex_unlock(&acl_cache->lock);
 | |
|     mysql_rwlock_unlock(&LOCK_grant);
 | |
|     if (acl_role)
 | |
|       return 0;
 | |
|   }
 | |
| #endif
 | |
| 
 | |
|   restore_record(table, s->default_values);
 | |
|   table->field[0]->set_null();
 | |
|   return schema_table_store_record(table->in_use, table);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   This shows all roles granted to current user
 | |
|   and recursively all roles granted to those roles
 | |
| */
 | |
| int fill_schema_applicable_roles(THD *thd, TABLE_LIST *tables, COND *cond)
 | |
| {
 | |
|   int res= 0;
 | |
| #ifndef NO_EMBEDDED_ACCESS_CHECKS
 | |
|   if (initialized)
 | |
|   {
 | |
|     TABLE *table= tables->table;
 | |
|     Security_context *sctx= thd->security_ctx;
 | |
|     mysql_rwlock_rdlock(&LOCK_grant);
 | |
|     mysql_mutex_lock(&acl_cache->lock);
 | |
|     ACL_USER *user= find_user_exact(Lex_cstring_strlen(sctx->priv_host),
 | |
|                                     Lex_cstring_strlen(sctx->priv_user));
 | |
|     if (user)
 | |
|     {
 | |
|       char buff[USER_HOST_BUFF_SIZE+10];
 | |
|       DBUG_ASSERT(user->user.length + user->hostname_length +2 < sizeof(buff));
 | |
|       char *end= strxmov(buff, user->user.str, "@", user->host.hostname, NULL);
 | |
|       APPLICABLE_ROLES_DATA data= { table,
 | |
|         { user->host.hostname, user->hostname_length },
 | |
|         { buff, (size_t)(end - buff) }, user
 | |
|       };
 | |
| 
 | |
|       res= traverse_role_graph_down(user, &data, 0, applicable_roles_insert);
 | |
|     }
 | |
| 
 | |
|     mysql_mutex_unlock(&acl_cache->lock);
 | |
|     mysql_rwlock_unlock(&LOCK_grant);
 | |
|   }
 | |
| #endif
 | |
| 
 | |
|   return res;
 | |
| }
 | |
| 
 | |
| 
 | |
| int wild_case_compare(CHARSET_INFO *cs, const char *str,const char *wildstr)
 | |
| {
 | |
|   int flag;
 | |
|   DBUG_ENTER("wild_case_compare");
 | |
|   DBUG_PRINT("enter",("str: '%s'  wildstr: '%s'",str,wildstr));
 | |
|   while (*wildstr)
 | |
|   {
 | |
|     while (*wildstr && *wildstr != wild_many && *wildstr != wild_one)
 | |
|     {
 | |
|       if (*wildstr == wild_prefix && wildstr[1])
 | |
| 	wildstr++;
 | |
|       if (my_toupper(cs, *wildstr++) !=
 | |
|           my_toupper(cs, *str++)) DBUG_RETURN(1);
 | |
|     }
 | |
|     if (! *wildstr ) DBUG_RETURN (*str != 0);
 | |
|     if (*wildstr++ == wild_one)
 | |
|     {
 | |
|       if (! *str++) DBUG_RETURN (1);	/* One char; skip */
 | |
|     }
 | |
|     else
 | |
|     {						/* Found '*' */
 | |
|       if (!*wildstr) DBUG_RETURN(0);		/* '*' as last char: OK */
 | |
|       flag=(*wildstr != wild_many && *wildstr != wild_one);
 | |
|       do
 | |
|       {
 | |
| 	if (flag)
 | |
| 	{
 | |
| 	  char cmp;
 | |
| 	  if ((cmp= *wildstr) == wild_prefix && wildstr[1])
 | |
| 	    cmp=wildstr[1];
 | |
| 	  cmp=my_toupper(cs, cmp);
 | |
| 	  while (*str && my_toupper(cs, *str) != cmp)
 | |
| 	    str++;
 | |
| 	  if (!*str) DBUG_RETURN (1);
 | |
| 	}
 | |
| 	if (wild_case_compare(cs, str,wildstr) == 0) DBUG_RETURN (0);
 | |
|       } while (*str++);
 | |
|       DBUG_RETURN(1);
 | |
|     }
 | |
|   }
 | |
|   DBUG_RETURN (*str != '\0');
 | |
| }
 | |
| 
 | |
| 
 | |
| #ifndef NO_EMBEDDED_ACCESS_CHECKS
 | |
| static bool update_schema_privilege(THD *thd, TABLE *table, const char *buff,
 | |
|                                     const char* db, const char* t_name,
 | |
|                                     const char* column, uint col_length,
 | |
|                                     const char *priv, uint priv_length,
 | |
|                                     const char* is_grantable)
 | |
| {
 | |
|   int i= 2;
 | |
|   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)
 | |
|     table->field[i++]->store(t_name, (uint) strlen(t_name), cs);
 | |
|   if (column)
 | |
|     table->field[i++]->store(column, col_length, cs);
 | |
|   table->field[i++]->store(priv, priv_length, cs);
 | |
|   table->field[i]->store(is_grantable, strlen(is_grantable), cs);
 | |
|   return schema_table_store_record(thd, table);
 | |
| }
 | |
| #endif
 | |
| 
 | |
| 
 | |
| #ifndef NO_EMBEDDED_ACCESS_CHECKS
 | |
| class Grantee_str
 | |
| {
 | |
|   char m_buff[USER_HOST_BUFF_SIZE + 6 /* 4 quotes, @, '\0' */];
 | |
| public:
 | |
|   Grantee_str(const LEX_CSTRING &user, const LEX_CSTRING &host)
 | |
|   {
 | |
|     DBUG_ASSERT(user.length + host.length + 6 < sizeof(m_buff));
 | |
|     my_snprintf(m_buff, sizeof(m_buff), "'%.*s'@'%.*s'",
 | |
|                 (int) user.length, user.str,
 | |
|                 (int) host.length, host.str);
 | |
|   }
 | |
|   operator const char *() const { return m_buff; }
 | |
| };
 | |
| #endif
 | |
| 
 | |
| 
 | |
| int fill_schema_user_privileges(THD *thd, TABLE_LIST *tables, COND *cond)
 | |
| {
 | |
| #ifndef NO_EMBEDDED_ACCESS_CHECKS
 | |
|   int error= 0;
 | |
|   uint counter;
 | |
|   TABLE *table= tables->table;
 | |
|   bool no_global_access= check_access(thd, SELECT_ACL, "mysql",
 | |
|                                       NULL, NULL, 1, 1);
 | |
|   DBUG_ENTER("fill_schema_user_privileges");
 | |
| 
 | |
|   if (!initialized)
 | |
|     DBUG_RETURN(0);
 | |
|   mysql_mutex_lock(&acl_cache->lock);
 | |
| 
 | |
|   for (counter=0 ; counter < acl_users.elements ; counter++)
 | |
|   {
 | |
|     const char *is_grantable="YES";
 | |
|     const ACL_USER *acl_user= dynamic_element(&acl_users,counter,ACL_USER*);
 | |
|     const LEX_CSTRING user= acl_user->user;
 | |
|     const LEX_CSTRING host= acl_user->hostname();
 | |
| 
 | |
|     if (no_global_access &&
 | |
|         !thd->security_ctx->is_priv_user(user, host))
 | |
|       continue;
 | |
| 
 | |
|     privilege_t want_access(acl_user->access);
 | |
|     if (!(want_access & GRANT_ACL))
 | |
|       is_grantable= "NO";
 | |
| 
 | |
|     Grantee_str grantee(user, host);
 | |
|     if (!(want_access & ~GRANT_ACL))
 | |
|     {
 | |
|       if (update_schema_privilege(thd, table, grantee, 0, 0, 0, 0,
 | |
|                                   STRING_WITH_LEN("USAGE"), is_grantable))
 | |
|       {
 | |
|         error= 1;
 | |
|         goto err;
 | |
|       }
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       uint priv_id;
 | |
|       ulonglong j;
 | |
|       privilege_t test_access(want_access & ~GRANT_ACL);
 | |
|       for (priv_id=0, j = SELECT_ACL;j <= GLOBAL_ACLS; priv_id++,j <<= 1)
 | |
|       {
 | |
|         if (test_access & j)
 | |
|         {
 | |
|           if (update_schema_privilege(thd, table, grantee, 0, 0, 0, 0,
 | |
|                                       command_array[priv_id],
 | |
|                                       command_lengths[priv_id], is_grantable))
 | |
|           {
 | |
|             error= 1;
 | |
|             goto err;
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| err:
 | |
|   mysql_mutex_unlock(&acl_cache->lock);
 | |
| 
 | |
|   DBUG_RETURN(error);
 | |
| #else
 | |
|   return(0);
 | |
| #endif
 | |
| }
 | |
| 
 | |
| 
 | |
| int fill_schema_schema_privileges(THD *thd, TABLE_LIST *tables, COND *cond)
 | |
| {
 | |
| #ifndef NO_EMBEDDED_ACCESS_CHECKS
 | |
|   int error= 0;
 | |
|   uint counter;
 | |
|   ACL_DB *acl_db;
 | |
|   TABLE *table= tables->table;
 | |
|   bool no_global_access= check_access(thd, SELECT_ACL, "mysql",
 | |
|                                       NULL, NULL, 1, 1);
 | |
|   DBUG_ENTER("fill_schema_schema_privileges");
 | |
| 
 | |
|   if (!initialized)
 | |
|     DBUG_RETURN(0);
 | |
|   mysql_mutex_lock(&acl_cache->lock);
 | |
| 
 | |
|   for (counter=0 ; counter < acl_dbs.elements() ; counter++)
 | |
|   {
 | |
|     const char *is_grantable="YES";
 | |
| 
 | |
|     acl_db=&acl_dbs.at(counter);
 | |
|     const LEX_CSTRING user= Lex_cstring_strlen(acl_db->user);
 | |
|     const LEX_CSTRING host= Lex_cstring_strlen(
 | |
|                               safe_str(acl_db->host.hostname));
 | |
| 
 | |
|     if (no_global_access &&
 | |
|         !thd->security_ctx->is_priv_user(user, host))
 | |
|       continue;
 | |
| 
 | |
|     privilege_t want_access(acl_db->access);
 | |
|     if (want_access)
 | |
|     {
 | |
|       if (!(want_access & GRANT_ACL))
 | |
|       {
 | |
|         is_grantable= "NO";
 | |
|       }
 | |
|       Grantee_str grantee(user, host);
 | |
|       if (!(want_access & ~GRANT_ACL))
 | |
|       {
 | |
|         if (update_schema_privilege(thd, table, grantee, acl_db->db, 0, 0,
 | |
|                                     0, STRING_WITH_LEN("USAGE"), is_grantable))
 | |
|         {
 | |
|           error= 1;
 | |
|           goto err;
 | |
|         }
 | |
|       }
 | |
|       else
 | |
|       {
 | |
|         int cnt;
 | |
|         ulonglong j;
 | |
|         privilege_t test_access(want_access & ~GRANT_ACL);
 | |
|         for (cnt=0, j = SELECT_ACL; j <= DB_ACLS; cnt++,j <<= 1)
 | |
|           if (test_access & j)
 | |
|           {
 | |
|             if (update_schema_privilege(thd, table,
 | |
|                                         grantee, acl_db->db, 0, 0, 0,
 | |
|                                         command_array[cnt], command_lengths[cnt],
 | |
|                                         is_grantable))
 | |
|             {
 | |
|               error= 1;
 | |
|               goto err;
 | |
|             }
 | |
|           }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| err:
 | |
|   mysql_mutex_unlock(&acl_cache->lock);
 | |
| 
 | |
|   DBUG_RETURN(error);
 | |
| #else
 | |
|   return (0);
 | |
| #endif
 | |
| }
 | |
| 
 | |
| 
 | |
| int fill_schema_table_privileges(THD *thd, TABLE_LIST *tables, COND *cond)
 | |
| {
 | |
| #ifndef NO_EMBEDDED_ACCESS_CHECKS
 | |
|   int error= 0;
 | |
|   uint index;
 | |
|   TABLE *table= tables->table;
 | |
|   bool no_global_access= check_access(thd, SELECT_ACL, "mysql",
 | |
|                                       NULL, NULL, 1, 1);
 | |
|   DBUG_ENTER("fill_schema_table_privileges");
 | |
| 
 | |
|   mysql_rwlock_rdlock(&LOCK_grant);
 | |
| 
 | |
|   for (index=0 ; index < column_priv_hash.records ; index++)
 | |
|   {
 | |
|     const char *is_grantable= "YES";
 | |
|     GRANT_TABLE *grant_table= (GRANT_TABLE*) my_hash_element(&column_priv_hash,
 | |
|                                                              index);
 | |
|     const LEX_CSTRING user= Lex_cstring_strlen(grant_table->user);
 | |
|     const LEX_CSTRING host= Lex_cstring_strlen(
 | |
|                               safe_str(grant_table->host.hostname));
 | |
| 
 | |
|     if (no_global_access &&
 | |
|         !thd->security_ctx->is_priv_user(user, host))
 | |
|       continue;
 | |
| 
 | |
|     privilege_t table_access(grant_table->privs);
 | |
|     if (table_access)
 | |
|     {
 | |
|       privilege_t test_access(table_access & ~GRANT_ACL);
 | |
|       /*
 | |
|         We should skip 'usage' privilege on table if
 | |
|         we have any privileges on column(s) of this table
 | |
|       */
 | |
|       if (!test_access && grant_table->cols)
 | |
|         continue;
 | |
|       if (!(table_access & GRANT_ACL))
 | |
|         is_grantable= "NO";
 | |
| 
 | |
|       Grantee_str grantee(user, host);
 | |
|       if (!test_access)
 | |
|       {
 | |
|         if (update_schema_privilege(thd, table,
 | |
|                                     grantee, grant_table->db,
 | |
|                                     grant_table->tname, 0, 0,
 | |
|                                     STRING_WITH_LEN("USAGE"), is_grantable))
 | |
|         {
 | |
|           error= 1;
 | |
|           goto err;
 | |
|         }
 | |
|       }
 | |
|       else
 | |
|       {
 | |
|         ulonglong j;
 | |
|         int cnt;
 | |
|         for (cnt= 0, j= SELECT_ACL; j <= TABLE_ACLS; cnt++, j<<= 1)
 | |
|         {
 | |
|           if (test_access & j)
 | |
|           {
 | |
|             if (update_schema_privilege(thd, table,
 | |
|                                         grantee, grant_table->db,
 | |
|                                         grant_table->tname, 0, 0,
 | |
|                                         command_array[cnt],
 | |
|                                         command_lengths[cnt], is_grantable))
 | |
|             {
 | |
|               error= 1;
 | |
|               goto err;
 | |
|             }
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| err:
 | |
|   mysql_rwlock_unlock(&LOCK_grant);
 | |
| 
 | |
|   DBUG_RETURN(error);
 | |
| #else
 | |
|   return (0);
 | |
| #endif
 | |
| }
 | |
| 
 | |
| 
 | |
| int fill_schema_column_privileges(THD *thd, TABLE_LIST *tables, COND *cond)
 | |
| {
 | |
| #ifndef NO_EMBEDDED_ACCESS_CHECKS
 | |
|   int error= 0;
 | |
|   uint index;
 | |
|   TABLE *table= tables->table;
 | |
|   bool no_global_access= check_access(thd, SELECT_ACL, "mysql",
 | |
|                                       NULL, NULL, 1, 1);
 | |
|   DBUG_ENTER("fill_schema_table_privileges");
 | |
| 
 | |
|   mysql_rwlock_rdlock(&LOCK_grant);
 | |
| 
 | |
|   for (index=0 ; index < column_priv_hash.records ; index++)
 | |
|   {
 | |
|     const char *is_grantable= "YES";
 | |
|     GRANT_TABLE *grant_table= (GRANT_TABLE*) my_hash_element(&column_priv_hash,
 | |
|                                                           index);
 | |
|     const LEX_CSTRING user= Lex_cstring_strlen(grant_table->user);
 | |
|     const LEX_CSTRING host= Lex_cstring_strlen(
 | |
|                               safe_str(grant_table->host.hostname));
 | |
| 
 | |
|     if (no_global_access &&
 | |
|         !thd->security_ctx->is_priv_user(user, host))
 | |
|       continue;
 | |
| 
 | |
|     privilege_t table_access(grant_table->cols);
 | |
|     if (table_access != NO_ACL)
 | |
|     {
 | |
|       if (!(grant_table->privs & GRANT_ACL))
 | |
|         is_grantable= "NO";
 | |
| 
 | |
|       privilege_t test_access(table_access & ~GRANT_ACL);
 | |
|       Grantee_str grantee(user, host);
 | |
|       if (!test_access)
 | |
|         continue;
 | |
|       else
 | |
|       {
 | |
|         ulonglong j;
 | |
|         int cnt;
 | |
|         for (cnt= 0, j= SELECT_ACL; j <= TABLE_ACLS; cnt++, j<<= 1)
 | |
|         {
 | |
|           if (test_access & j)
 | |
|           {
 | |
|             for (uint col_index=0 ;
 | |
|                  col_index < grant_table->hash_columns.records ;
 | |
|                  col_index++)
 | |
|             {
 | |
|               GRANT_COLUMN *grant_column = (GRANT_COLUMN*)
 | |
|                 my_hash_element(&grant_table->hash_columns,col_index);
 | |
|               if ((grant_column->rights & j) && (table_access & j))
 | |
|               {
 | |
|                 if (update_schema_privilege(thd, table,
 | |
|                                             grantee,
 | |
|                                             grant_table->db,
 | |
|                                             grant_table->tname,
 | |
|                                             grant_column->column,
 | |
|                                             grant_column->key_length,
 | |
|                                             command_array[cnt],
 | |
|                                             command_lengths[cnt], is_grantable))
 | |
|                 {
 | |
|                   error= 1;
 | |
|                   goto err;
 | |
|                 }
 | |
|               }
 | |
|             }
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| err:
 | |
|   mysql_rwlock_unlock(&LOCK_grant);
 | |
| 
 | |
|   DBUG_RETURN(error);
 | |
| #else
 | |
|   return (0);
 | |
| #endif
 | |
| }
 | |
| 
 | |
| namespace Show
 | |
| {
 | |
|   ST_FIELD_INFO users_fields_info[] =
 | |
|   {
 | |
|     Column("USER", Userhost(), NOT_NULL),
 | |
|     Column("PASSWORD_ERRORS", SLonglong(), NULLABLE),
 | |
|     Column("PASSWORD_EXPIRATION_TIME", Datetime(0), NULLABLE),
 | |
|     CEnd()
 | |
|   };
 | |
| };
 | |
| 
 | |
| #ifndef NO_EMBEDDED_ACCESS_CHECKS
 | |
| static int fill_users_schema_record(THD *thd, TABLE * table, ACL_USER *user)
 | |
| {
 | |
|   ulonglong lifetime= user->password_lifetime < 0
 | |
|                       ? default_password_lifetime
 | |
|                       : user->password_lifetime;
 | |
| 
 | |
|   bool ignore_password_errors= ignore_max_password_errors(user);
 | |
|   bool ignore_expiration_date= lifetime == 0 && !user->password_expired;
 | |
| 
 | |
|   Grantee_str grantee(user->user,
 | |
|                       Lex_cstring_strlen(safe_str(user->host.hostname)));
 | |
|   table->field[0]->store(grantee, strlen(grantee), system_charset_info);
 | |
|   if (ignore_password_errors)
 | |
|   {
 | |
|     table->field[1]->set_null();
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     table->field[1]->set_notnull();
 | |
|     table->field[1]->store(user->password_errors);
 | |
|   }
 | |
|   if (ignore_expiration_date)
 | |
|   {
 | |
|     table->field[2]->set_null();
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     table->field[2]->set_notnull();
 | |
|     if (user->password_expired)
 | |
|       table->field[2]->store(0, true);
 | |
|     else
 | |
|       table->field[2]->store_timestamp(user->password_last_changed +
 | |
|                                        lifetime * 3600 * 24, 0);
 | |
|   }
 | |
| 
 | |
|   return schema_table_store_record(thd, table);
 | |
| }
 | |
| #endif
 | |
| 
 | |
| int fill_users_schema_table(THD *thd, TABLE_LIST *tables, COND *cond)
 | |
| {
 | |
|   int res= 0;
 | |
| 
 | |
| #ifndef NO_EMBEDDED_ACCESS_CHECKS
 | |
|   /* --skip-grants */
 | |
|   if (!initialized)
 | |
|     return res;
 | |
| 
 | |
|   bool see_whole_table= check_access(thd, SELECT_ACL, "mysql", NULL, NULL,
 | |
|                                      true, true) == 0;
 | |
|   TABLE *table= tables->table;
 | |
| 
 | |
|   if (!see_whole_table)
 | |
|   {
 | |
|     Security_context *sctx= thd->security_ctx;
 | |
|     mysql_mutex_lock(&acl_cache->lock);
 | |
|     ACL_USER *cur_user= find_user_exact(Lex_cstring_strlen(sctx->priv_host),
 | |
|                                         Lex_cstring_strlen(sctx->priv_user));
 | |
|     if (!cur_user)
 | |
|     {
 | |
|       mysql_mutex_unlock(&acl_cache->lock);
 | |
|       my_error(ER_INVALID_CURRENT_USER, MYF(0));
 | |
|       return 1;
 | |
|     }
 | |
| 
 | |
|     res= fill_users_schema_record(thd, table, cur_user);
 | |
|     mysql_mutex_unlock(&acl_cache->lock);
 | |
|     return res;
 | |
|   }
 | |
| 
 | |
|   mysql_mutex_lock(&acl_cache->lock);
 | |
|   for (size_t i= 0; res == 0 && i < acl_users.elements; i++)
 | |
|   {
 | |
|     ACL_USER *user= dynamic_element(&acl_users, i, ACL_USER*);
 | |
|     res= fill_users_schema_record(thd, table, user);
 | |
|   }
 | |
|   mysql_mutex_unlock(&acl_cache->lock);
 | |
| #endif
 | |
|   return res;
 | |
| }
 | |
| 
 | |
| 
 | |
| #ifndef NO_EMBEDDED_ACCESS_CHECKS
 | |
| /*
 | |
|   fill effective privileges for table
 | |
| 
 | |
|   SYNOPSIS
 | |
|     fill_effective_table_privileges()
 | |
|     thd     thread handler
 | |
|     grant   grants table descriptor
 | |
|     db      db name
 | |
|     table   table name
 | |
| */
 | |
| 
 | |
| void fill_effective_table_privileges(THD *thd, GRANT_INFO *grant,
 | |
|                                      const char *db, const char *table)
 | |
| {
 | |
|   Security_context *sctx= thd->security_ctx;
 | |
|   DBUG_ENTER("fill_effective_table_privileges");
 | |
|   DBUG_PRINT("enter", ("Host: '%s', Ip: '%s', User: '%s', table: `%s`.`%s`",
 | |
|                        sctx->priv_host, sctx->ip, sctx->priv_user, db, table));
 | |
|   /* --skip-grants */
 | |
|   if (!initialized)
 | |
|   {
 | |
|     DBUG_PRINT("info", ("skip grants"));
 | |
|     grant->privilege= ALL_KNOWN_ACL;             // everything is allowed
 | |
|     DBUG_PRINT("info", ("privilege 0x%llx", (longlong) grant->privilege));
 | |
|     DBUG_VOID_RETURN;
 | |
|   }
 | |
| 
 | |
|   /* global privileges */
 | |
|   grant->privilege= sctx->master_access;
 | |
| 
 | |
|   if (!thd->db.str || strcmp(db, thd->db.str))
 | |
|   {
 | |
|     grant->privilege|= acl_get_all3(sctx, db, FALSE);
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     grant->privilege|= sctx->db_access;
 | |
|   }
 | |
| 
 | |
|   /* table privileges */
 | |
|   mysql_rwlock_rdlock(&LOCK_grant);
 | |
|   grant->refresh(sctx, db, table);
 | |
| 
 | |
|   if (grant->grant_table_user != 0)
 | |
|   {
 | |
|     grant->privilege|= grant->grant_table_user->privs;
 | |
|   }
 | |
|   if (grant->grant_table_role != 0)
 | |
|   {
 | |
|     grant->privilege|= grant->grant_table_role->privs;
 | |
|   }
 | |
|   if (grant->grant_public != 0)
 | |
|   {
 | |
|     grant->privilege|= grant->grant_public->privs;
 | |
|   }
 | |
|   mysql_rwlock_unlock(&LOCK_grant);
 | |
| 
 | |
|   DBUG_PRINT("info", ("privilege 0x%llx", (longlong) grant->privilege));
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| 
 | |
| #else /* NO_EMBEDDED_ACCESS_CHECKS */
 | |
| 
 | |
| /****************************************************************************
 | |
|  Dummy wrappers when we don't have any access checks
 | |
| ****************************************************************************/
 | |
| 
 | |
| bool check_routine_level_acl(THD *thd, privilege_t acl,
 | |
|                              const char *db, const char *name,
 | |
|                              const Sp_handler *sph)
 | |
| {
 | |
|   return FALSE;
 | |
| }
 | |
| 
 | |
| #endif
 | |
| 
 | |
| /**
 | |
|   Return information about user or current user.
 | |
| 
 | |
|   @param[in] thd          thread handler
 | |
|   @param[in] user         user
 | |
|   @param[in] lock         whether &acl_cache->lock mutex needs to be locked
 | |
| 
 | |
|   @return
 | |
|     - On success, return a valid pointer to initialized
 | |
|     LEX_USER, which contains user information.
 | |
|     - On error, return 0.
 | |
| */
 | |
| 
 | |
| LEX_USER *get_current_user(THD *thd, LEX_USER *user, bool lock)
 | |
| {
 | |
|   if (user->user.str == current_user.str)  // current_user
 | |
|     return create_default_definer(thd, false);
 | |
| 
 | |
|   if (user->user.str == current_role.str)  // current_role
 | |
|     return create_default_definer(thd, true);
 | |
| 
 | |
|   if (user->host.str == NULL) // Possibly a role
 | |
|   {
 | |
|     // to be reexecution friendly we have to make a copy
 | |
|     LEX_USER *dup= (LEX_USER*) thd->memdup(user, sizeof(*user));
 | |
|     if (!dup)
 | |
|       return 0;
 | |
| 
 | |
| #ifndef NO_EMBEDDED_ACCESS_CHECKS
 | |
|     if (has_auth(user, thd->lex))
 | |
|     {
 | |
|       dup->host= host_not_specified;
 | |
|       return dup;
 | |
|     }
 | |
| 
 | |
|     role_name_check_result result= check_role_name(&dup->user, true);
 | |
|     if (result == ROLE_NAME_INVALID)
 | |
|       return 0;
 | |
|     if (result == ROLE_NAME_PUBLIC)
 | |
|     {
 | |
|       dup->host= empty_clex_str;
 | |
|       return dup;
 | |
|     }
 | |
| 
 | |
|     if (!initialized)
 | |
|       return dup;
 | |
| 
 | |
|     if (lock)
 | |
|       mysql_mutex_lock(&acl_cache->lock);
 | |
|     if (find_acl_role(dup->user, false))
 | |
|       dup->host= empty_clex_str;
 | |
|     else
 | |
|       dup->host= host_not_specified;
 | |
|     if (lock)
 | |
|       mysql_mutex_unlock(&acl_cache->lock);
 | |
| #endif
 | |
| 
 | |
|     return dup;
 | |
|   }
 | |
| 
 | |
|   return user;
 | |
| }
 | |
| 
 | |
| struct ACL_internal_schema_registry_entry
 | |
| {
 | |
|   Lex_ident_i_s_table 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_CSTRING *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= Lex_ident_i_s_table(*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;
 | |
| 
 | |
|   const Lex_cstring_strlen name_ls(name);
 | |
|   for (i= 0; i<m_registry_array_size; i++)
 | |
|   {
 | |
|     if (registry_array[i].m_name.streq(name_ls))
 | |
|       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
 | |
| #define check_for_max_user_connections(X,Y)   0
 | |
| #define get_or_create_user_conn(A,B,C,D) 0
 | |
| #endif
 | |
| #endif
 | |
| #ifndef HAVE_OPENSSL
 | |
| #define ssl_acceptor_fd 0
 | |
| #define sslaccept(A,B,C,D) 1
 | |
| #endif
 | |
| 
 | |
| /**
 | |
|   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;
 | |
|   ACL_USER *acl_user;       ///< a copy, independent from acl_users array
 | |
|   plugin_ref plugin;        ///< what plugin we're under
 | |
|   LEX_CSTRING db;           ///< db name from the handshake packet
 | |
|   /** when restarting a plugin this caches the last client reply */
 | |
|   struct {
 | |
|     LEX_CSTRING plugin;
 | |
|     char *pkt;              ///< pointer 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;
 | |
|   uint curr_auth;                    ///< an index in acl_user->auth[]
 | |
|   int packets_read, packets_written; ///< counters for send/received packets
 | |
|   bool make_it_fail;
 | |
|   /** when plugin returns a failure this tells us what really happened */
 | |
|   enum { SUCCESS, FAILURE, RESTART } status;
 | |
| };
 | |
| 
 | |
| /**
 | |
|   a helper function to report an access denied error in most proper places
 | |
| */
 | |
| static void login_failed_error(THD *thd)
 | |
| {
 | |
|   my_error(access_denied_error_code(thd->password), MYF(0),
 | |
|            thd->main_security_ctx.user,
 | |
|            thd->main_security_ctx.host_or_ip,
 | |
|            thd->password ? ER_THD(thd, ER_YES) : ER_THD(thd, ER_NO));
 | |
|   general_log_print(thd, COM_CONNECT,
 | |
|                     ER_THD(thd, access_denied_error_code(thd->password)),
 | |
|                     thd->main_security_ctx.user,
 | |
|                     thd->main_security_ctx.host_or_ip,
 | |
|                     thd->password ? ER_THD(thd, ER_YES) : ER_THD(thd, ER_NO));
 | |
|   status_var_increment(thd->status_var.access_denied_errors);
 | |
|   /*
 | |
|     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_DEFAULT(access_denied_error_code(thd->password)),
 | |
|                       thd->main_security_ctx.user,
 | |
|                       thd->main_security_ctx.host_or_ip,
 | |
|                       thd->password ? ER_THD(thd, ER_YES) : ER_THD(thd, 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::RESTART);
 | |
|   DBUG_ASSERT(data_len <= 255);
 | |
| 
 | |
|   THD *thd= mpvio->auth_info.thd;
 | |
|   char *buff= (char *) my_alloca(1 + SERVER_VERSION_LENGTH + 1 + data_len + 64);
 | |
|   char scramble_buf[SCRAMBLE_LENGTH];
 | |
|   char *end= buff;
 | |
|   DBUG_ENTER("send_server_handshake_packet");
 | |
| 
 | |
|   *end++= protocol_version;
 | |
| 
 | |
|   thd->client_capabilities= CLIENT_BASIC_FLAGS;
 | |
| 
 | |
|   if (opt_using_transactions)
 | |
|     thd->client_capabilities|= CLIENT_TRANSACTIONS;
 | |
| 
 | |
|   thd->client_capabilities|= CAN_CLIENT_COMPRESS;
 | |
| 
 | |
|   if (ssl_acceptor_fd)
 | |
|   {
 | |
|     thd->client_capabilities |= CLIENT_SSL;
 | |
|   }
 | |
| 
 | |
|   if (data_len)
 | |
|   {
 | |
|     mpvio->cached_server_packet.pkt= (char*)thd->memdup(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.
 | |
|       */
 | |
|       thd_create_random_password(thd, thd->scramble, SCRAMBLE_LENGTH);
 | |
|       data= thd->scramble;
 | |
|     }
 | |
|     data_len= SCRAMBLE_LENGTH;
 | |
|   }
 | |
| 
 | |
|   end= strnmov(end, server_version, SERVER_VERSION_LENGTH) + 1;
 | |
| 
 | |
|   int4store((uchar*) end, mpvio->auth_info.thd->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, thd->client_capabilities);
 | |
| 
 | |
|   CHARSET_INFO *handshake_cs= default_charset_info;
 | |
|   if (handshake_cs->number > 0xFF)
 | |
|   {
 | |
|     /*
 | |
|       A workaround for a 2-byte collation ID: translate it into
 | |
|       the ID of the primary collation of this character set.
 | |
|     */
 | |
|     CHARSET_INFO *cs= get_charset_by_csname(handshake_cs->cs_name.str,
 | |
|                                             MY_CS_PRIMARY, MYF(MY_WME));
 | |
|     /*
 | |
|       cs should not normally be NULL, however it may be possible
 | |
|       with a dynamic character set incorrectly defined in Index.xml.
 | |
|       For safety let's fallback to latin1 in case cs is NULL.
 | |
|     */
 | |
|     handshake_cs= cs ? cs : &my_charset_latin1;
 | |
|   }
 | |
| 
 | |
|   /* write server characteristics: up to 16 bytes allowed */
 | |
|   end[2]= (char) handshake_cs->number;
 | |
| 
 | |
|   int2store(end+3, mpvio->auth_info.thd->server_status);
 | |
|   int2store(end+5, thd->client_capabilities >> 16);
 | |
|   end[7]= data_len;
 | |
|   DBUG_EXECUTE_IF("poison_srv_handshake_scramble_len", end[7]= -100;);
 | |
|   DBUG_EXECUTE_IF("increase_srv_handshake_scramble_len", end[7]= 50;);
 | |
|   bzero(end + 8, 6);
 | |
|   int4store(end + 14, thd->client_capabilities >> 32);
 | |
|   end+= 18;
 | |
|   /* write scramble tail */
 | |
|   end= (char*) memcpy(end, data + SCRAMBLE_LENGTH_323,
 | |
|                       data_len - SCRAMBLE_LENGTH_323);
 | |
|   end+= data_len - SCRAMBLE_LENGTH_323;
 | |
|   st_mysql_auth *info= (st_mysql_auth*)plugin_decl(mpvio->plugin)->info;
 | |
|   end= strmake(end, info->client_auth_plugin, strlen(info->client_auth_plugin));
 | |
| 
 | |
|   int res= my_net_write(&mpvio->auth_info.thd->net, (uchar*) buff,
 | |
|                         (size_t) (end - buff + 1)) ||
 | |
|            net_flush(&mpvio->auth_info.thd->net);
 | |
|   my_afree(buff);
 | |
|   DBUG_RETURN (res);
 | |
| }
 | |
| 
 | |
| static bool secure_auth(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.
 | |
|   */
 | |
|   if (thd->client_capabilities & CLIENT_PROTOCOL_41)
 | |
|   {
 | |
|     my_error(ER_SERVER_IS_IN_SECURE_AUTH_MODE, MYF(0),
 | |
|              thd->security_ctx->user,
 | |
|              thd->security_ctx->host_or_ip);
 | |
|     general_log_print(thd, COM_CONNECT,
 | |
|                       ER_THD(thd, ER_SERVER_IS_IN_SECURE_AUTH_MODE),
 | |
|                       thd->security_ctx->user,
 | |
|                       thd->security_ctx->host_or_ip);
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     my_error(ER_NOT_SUPPORTED_AUTH_MODE, MYF(0));
 | |
|     general_log_print(thd, COM_CONNECT, "%s",
 | |
|                       ER_THD(thd, 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)
 | |
| {
 | |
|   NET *net= &mpvio->auth_info.thd->net;
 | |
|   static uchar switch_plugin_request_buf[]= { 254 };
 | |
|   DBUG_ENTER("send_plugin_request_packet");
 | |
| 
 | |
|   const char *client_auth_plugin=
 | |
|     ((st_mysql_auth *) (plugin_decl(mpvio->plugin)->info))->client_auth_plugin;
 | |
| 
 | |
|   DBUG_EXECUTE_IF("auth_disconnect", { DBUG_RETURN(1); });
 | |
|   DBUG_EXECUTE_IF("auth_invalid_plugin", client_auth_plugin="foo/bar"; );
 | |
|   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=
 | |
|     client_auth_plugin == old_password_plugin_name.str &&
 | |
|     Lex_ident_plugin(mpvio->cached_client_reply.plugin).
 | |
|       streq(native_password_plugin_name);
 | |
| 
 | |
|   if (switch_from_long_to_short_scramble)
 | |
|     DBUG_RETURN (secure_auth(mpvio->auth_info.thd) ||
 | |
|                  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=
 | |
|     client_auth_plugin == native_password_plugin_name.str &&
 | |
|     Lex_ident_plugin(mpvio->cached_client_reply.plugin).
 | |
|       streq(old_password_plugin_name);
 | |
| 
 | |
|   if (switch_from_short_to_long_scramble)
 | |
|   {
 | |
|     my_error(ER_NOT_SUPPORTED_AUTH_MODE, MYF(0));
 | |
|     general_log_print(mpvio->auth_info.thd, COM_CONNECT, "%s",
 | |
|                       ER_THD(mpvio->auth_info.thd, ER_NOT_SUPPORTED_AUTH_MODE));
 | |
|     DBUG_RETURN (1);
 | |
|   }
 | |
| 
 | |
|   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
 | |
| 
 | |
| /**
 | |
|   Safeguard to avoid blocking the root, when max_password_errors
 | |
|   limit is reached.
 | |
| 
 | |
|   Currently, we allow password errors for superuser on localhost.
 | |
| 
 | |
|   @return true, if password errors should be ignored, and user should not be locked.
 | |
| */
 | |
| static bool ignore_max_password_errors(const ACL_USER *acl_user)
 | |
| {
 | |
|  const char *host= acl_user->host.hostname;
 | |
|  return (acl_user->access & PRIV_IGNORE_MAX_PASSWORD_ERRORS)
 | |
|    && (!strcasecmp(host, "localhost") ||
 | |
|        !strcmp(host, "127.0.0.1") ||
 | |
|        !strcmp(host, "::1"));
 | |
| }
 | |
| /**
 | |
|    Finds acl entry in user database for authentication purposes.
 | |
| 
 | |
|    Finds a user and copies it into mpvio. Creates a fake user
 | |
|    if no matching user account is found.
 | |
| 
 | |
|    @retval 0    found
 | |
|    @retval 1    error
 | |
| */
 | |
| static bool find_mpvio_user(MPVIO_EXT *mpvio)
 | |
| {
 | |
|   Security_context *sctx= mpvio->auth_info.thd->security_ctx;
 | |
|   DBUG_ENTER("find_mpvio_user");
 | |
|   DBUG_ASSERT(mpvio->acl_user == 0);
 | |
| 
 | |
|   mysql_mutex_lock(&acl_cache->lock);
 | |
| 
 | |
|   ACL_USER *user= find_user_or_anon(sctx->host, sctx->user, sctx->ip);
 | |
| 
 | |
|   if (user)
 | |
|     mpvio->acl_user= user->copy(mpvio->auth_info.thd->mem_root);
 | |
| 
 | |
|   mysql_mutex_unlock(&acl_cache->lock);
 | |
| 
 | |
|   if (!mpvio->acl_user)
 | |
|   {
 | |
|     /*
 | |
|       A matching user was not found. Fake it. Take any user, make the
 | |
|       authentication fail later.
 | |
|       This way we get a realistically looking failure, with occasional
 | |
|       "change auth plugin" requests even for nonexistent users. The ratio
 | |
|       of "change auth plugin" request will be the same for real and
 | |
|       nonexistent users.
 | |
|       Note, that we cannot pick any user at random, it must always be
 | |
|       the same user account for the incoming sctx->user name.
 | |
|     */
 | |
|     ulong nr1=1, nr2=4;
 | |
|     CHARSET_INFO *cs= &my_charset_latin1;
 | |
|     cs->hash_sort((uchar*) sctx->user, strlen(sctx->user), &nr1, &nr2);
 | |
| 
 | |
|     mysql_mutex_lock(&acl_cache->lock);
 | |
|     if (!acl_users.elements)
 | |
|     {
 | |
|       mysql_mutex_unlock(&acl_cache->lock);
 | |
|       login_failed_error(mpvio->auth_info.thd);
 | |
|       DBUG_RETURN(1);
 | |
|     }
 | |
|     uint i= nr1 % acl_users.elements;
 | |
|     ACL_USER *acl_user_tmp= dynamic_element(&acl_users, i, ACL_USER*);
 | |
|     mpvio->acl_user= acl_user_tmp->copy(mpvio->auth_info.thd->mem_root);
 | |
|     mysql_mutex_unlock(&acl_cache->lock);
 | |
| 
 | |
|     mpvio->make_it_fail= true;
 | |
|   }
 | |
| 
 | |
|   if (mpvio->acl_user->password_errors >= max_password_errors &&
 | |
|       !ignore_max_password_errors(mpvio->acl_user))
 | |
|   {
 | |
|     my_error(ER_USER_IS_BLOCKED, MYF(0));
 | |
|     general_log_print(mpvio->auth_info.thd, COM_CONNECT, "%s",
 | |
|       ER_THD(mpvio->auth_info.thd, ER_USER_IS_BLOCKED));
 | |
|     DBUG_RETURN(1);
 | |
|   }
 | |
| 
 | |
|   /* user account requires non-default plugin and the client is too old */
 | |
|   if (mpvio->acl_user->auth->plugin.str != native_password_plugin_name.str &&
 | |
|       mpvio->acl_user->auth->plugin.str != old_password_plugin_name.str &&
 | |
|       !(mpvio->auth_info.thd->client_capabilities & CLIENT_PLUGIN_AUTH))
 | |
|   {
 | |
|     DBUG_ASSERT(!Lex_ident_plugin(mpvio->acl_user->auth->plugin).
 | |
|                   streq(native_password_plugin_name));
 | |
|     DBUG_ASSERT(!Lex_ident_plugin(mpvio->acl_user->auth->plugin).
 | |
|                   streq(old_password_plugin_name));
 | |
|     my_error(ER_NOT_SUPPORTED_AUTH_MODE, MYF(0));
 | |
|     general_log_print(mpvio->auth_info.thd, COM_CONNECT, "%s",
 | |
|                       ER_THD(mpvio->auth_info.thd, ER_NOT_SUPPORTED_AUTH_MODE));
 | |
|     DBUG_RETURN (1);
 | |
|   }
 | |
|   DBUG_RETURN(0);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Determine if the client is MySQL Connector/NET.
 | |
| 
 | |
|   Checks whether the given connection attributes blob corresponds to
 | |
|   MySQL Connector/NET by examining the "_client_name" attribute, which is
 | |
|   expected to be the first attribute in the blob.
 | |
| 
 | |
|   @param connection_attrs - The connection attributes blob.
 | |
|   @param length - The length of the blob.
 | |
| 
 | |
|   @return true if the client is MySQL Connector/NET, false otherwise.
 | |
| */
 | |
| static inline bool is_connector_net_client(const char *connection_attrs,
 | |
|                                            size_t length)
 | |
| {
 | |
|   constexpr LEX_CSTRING prefix=
 | |
|     {STRING_WITH_LEN("\x0c_client_name\x13mysql-connector-net")};
 | |
| 
 | |
|   if (length < prefix.length)
 | |
|     return false;
 | |
| 
 | |
|   /* Optimization to avoid following memcmp in common cases.*/
 | |
|   if (connection_attrs[prefix.length - 1] != prefix.str[prefix.length - 1])
 | |
|     return false;
 | |
| 
 | |
|   return !memcmp(connection_attrs, prefix.str, prefix.length);
 | |
| }
 | |
| 
 | |
| static bool
 | |
| read_client_connect_attrs(char **ptr, char *end, THD* thd)
 | |
| {
 | |
|   ulonglong length;
 | |
|   char *ptr_save= *ptr;
 | |
| 
 | |
|   /* not enough bytes to hold the length */
 | |
|   if (ptr_save >= end)
 | |
|     return true;
 | |
| 
 | |
|   length= safe_net_field_length_ll((uchar **) ptr, end - ptr_save);
 | |
| 
 | |
|   /* cannot even read the length */
 | |
|   if (*ptr == NULL)
 | |
|     return true;
 | |
| 
 | |
|   /* length says there're more data than can fit into the packet */
 | |
|   if (*ptr + length > end)
 | |
|     return true;
 | |
| 
 | |
|   /* impose an artificial length limit of 64k */
 | |
|   if (length > 65535)
 | |
|     return true;
 | |
| 
 | |
|   if (PSI_CALL_set_thread_connect_attrs(*ptr, (uint)length, thd->charset()) &&
 | |
|       current_thd->variables.log_warnings)
 | |
|     sql_print_warning("Connection attributes of length %llu were truncated",
 | |
|                       length);
 | |
| 
 | |
|   /* Connector/Net crashes, when "show collations" returns NULL IDs*/
 | |
|   if (is_connector_net_client(*ptr, length))
 | |
|     thd->variables.old_behavior |= OLD_MODE_NO_NULL_COLLATION_IDS;
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| #endif
 | |
| 
 | |
| /* the packet format is described in send_change_user_packet() */
 | |
| static bool parse_com_change_user_packet(MPVIO_EXT *mpvio, uint packet_length)
 | |
| {
 | |
|   THD *thd= mpvio->auth_info.thd;
 | |
|   NET *net= &thd->net;
 | |
|   Security_context *sctx= thd->security_ctx;
 | |
| 
 | |
|   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= (uint)(passwd - user - 1);
 | |
|   char *db= passwd;
 | |
|   char db_buff[SAFE_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_THD(thd, 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= (thd->client_capabilities & CLIENT_SECURE_CONNECTION ?
 | |
|                     (uchar) (*passwd++) : (uint)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_THD(thd, ER_UNKNOWN_COM_ERROR),
 | |
|                MYF(0));
 | |
|     DBUG_RETURN (1);
 | |
|   }
 | |
| 
 | |
|   size_t db_len= strlen(db);
 | |
| 
 | |
|   char *next_field= db + db_len + 1;
 | |
| 
 | |
|   if (next_field + 1 < end)
 | |
|   {
 | |
|     if (thd_init_client_charset(thd, uint2korr(next_field)))
 | |
|       DBUG_RETURN(1);
 | |
|     next_field+= 2;
 | |
|   }
 | |
| 
 | |
|   /* Convert database and user names to utf8 */
 | |
|   db_len= copy_and_convert(db_buff, sizeof(db_buff) - 1, system_charset_info,
 | |
|                            db, db_len, thd->charset(), &dummy_errors);
 | |
| 
 | |
|   user_len= copy_and_convert(user_buff, sizeof(user_buff) - 1,
 | |
|                              system_charset_info, user, user_len,
 | |
|                              thd->charset(), &dummy_errors);
 | |
| 
 | |
|   if (!(sctx->user= my_strndup(key_memory_MPVIO_EXT_auth_info, user_buff,
 | |
|                                user_len, MYF(MY_WME))))
 | |
|     DBUG_RETURN(1);
 | |
| 
 | |
|   /* Clear variables that are allocated */
 | |
|   thd->user_connect= 0;
 | |
|   strmake_buf(sctx->priv_user, sctx->user);
 | |
| 
 | |
|   if (thd->make_lex_string(&mpvio->db, db_buff, db_len) == 0)
 | |
|     DBUG_RETURN(1); /* The error is set by make_lex_string(). */
 | |
| 
 | |
|   /*
 | |
|     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_clex_str);
 | |
| 
 | |
|   if (!initialized)
 | |
|   {
 | |
|     // if mysqld's been started with --skip-grant-tables option
 | |
|     mpvio->status= MPVIO_EXT::SUCCESS;
 | |
|     DBUG_RETURN(0);
 | |
|   }
 | |
| 
 | |
| #ifndef NO_EMBEDDED_ACCESS_CHECKS
 | |
|   thd->password= passwd_len > 0;
 | |
|   if (find_mpvio_user(mpvio))
 | |
|     DBUG_RETURN(1);
 | |
| 
 | |
|   LEX_CSTRING client_plugin;
 | |
|   if (thd->client_capabilities & CLIENT_PLUGIN_AUTH)
 | |
|   {
 | |
|     if (next_field >= end)
 | |
|     {
 | |
|       my_message(ER_UNKNOWN_COM_ERROR, ER_THD(thd, ER_UNKNOWN_COM_ERROR),
 | |
|                  MYF(0));
 | |
|       DBUG_RETURN(1);
 | |
|     }
 | |
|     client_plugin= Lex_cstring_strlen(next_field);
 | |
|     next_field+= client_plugin.length + 1;
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     if (thd->client_capabilities & CLIENT_SECURE_CONNECTION)
 | |
|       client_plugin= native_password_plugin_name;
 | |
|     else
 | |
|     {
 | |
|       /*
 | |
|         Normally old clients use old_password_plugin, but for
 | |
|         a passwordless accounts we use native_password_plugin.
 | |
|         See guess_auth_plugin().
 | |
|       */
 | |
|       client_plugin= passwd_len ? old_password_plugin_name
 | |
|                                 : native_password_plugin_name;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if ((thd->client_capabilities & CLIENT_CONNECT_ATTRS) &&
 | |
|       read_client_connect_attrs(&next_field, end, thd))
 | |
|   {
 | |
|     my_message(ER_UNKNOWN_COM_ERROR, ER_THD(thd, ER_UNKNOWN_COM_ERROR),
 | |
|                MYF(0));
 | |
|     DBUG_RETURN(1);
 | |
|   }
 | |
| 
 | |
|   DBUG_PRINT("info", ("client_plugin=%s, restart", client_plugin.str));
 | |
|   /*
 | |
|     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
 | |
| /**
 | |
|   Check that a client uses secure connection type in case the option
 | |
|   require_secure_transport is on.
 | |
| 
 | |
|   @param thd                     thread handle
 | |
| 
 | |
|   @return true in case the option require_secure_transport is on and the client
 | |
|           uses either named pipe or unix socket or ssl, else return false
 | |
| */
 | |
| 
 | |
| static bool check_require_secured_transport(THD *thd)
 | |
| {
 | |
|   Vio *vio= thd->net.vio;
 | |
|   if (opt_require_secure_transport)
 | |
|   {
 | |
|     enum enum_vio_type type= vio_type(vio);
 | |
| 
 | |
|     return
 | |
|       (type != VIO_TYPE_SSL) &&
 | |
|       (type != VIO_TYPE_NAMEDPIPE) &&
 | |
|       (type != VIO_TYPE_SOCKET);
 | |
|   }
 | |
|   return 0;
 | |
| }
 | |
| #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
 | |
|   THD *thd= mpvio->auth_info.thd;
 | |
|   NET *net= &thd->net;
 | |
|   char *end;
 | |
|   DBUG_ASSERT(mpvio->status == MPVIO_EXT::FAILURE);
 | |
| 
 | |
|   if (pkt_len < MIN_HANDSHAKE_SIZE)
 | |
|     return packet_error;
 | |
| 
 | |
|   /*
 | |
|     Protocol buffer is guaranteed to always end with \0. (see my_net_read())
 | |
|     As the code below depends on this, lets check that.
 | |
|   */
 | |
|   DBUG_ASSERT(net->read_pos[pkt_len] == 0);
 | |
| 
 | |
|   ulonglong client_capabilities= uint2korr(net->read_pos);
 | |
|   compile_time_assert(sizeof(client_capabilities) >= 8);
 | |
|   if (client_capabilities & CLIENT_PROTOCOL_41)
 | |
|   {
 | |
|     if (pkt_len < 32)
 | |
|       return packet_error;
 | |
|     client_capabilities|= ((ulong) uint2korr(net->read_pos+2)) << 16;
 | |
|     if (!(client_capabilities & CLIENT_MYSQL))
 | |
|     {
 | |
|       // it is client with mariadb extensions
 | |
|       ulonglong ext_client_capabilities=
 | |
|         (((ulonglong)uint4korr(net->read_pos + 28)) << 32);
 | |
|       client_capabilities|= ext_client_capabilities;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* Disable those bits which are not supported by the client. */
 | |
|   compile_time_assert(sizeof(thd->client_capabilities) >= 8);
 | |
|   thd->client_capabilities&= client_capabilities;
 | |
| 
 | |
|   DBUG_PRINT("info", ("client capabilities: %llu", thd->client_capabilities));
 | |
|   if (thd->client_capabilities & CLIENT_SSL)
 | |
|   {
 | |
|     unsigned long errptr __attribute__((unused));
 | |
| 
 | |
|     /* Do the SSL layering. */
 | |
|     if (!ssl_acceptor_fd)
 | |
|       return packet_error;
 | |
| 
 | |
|     DBUG_PRINT("info", ("IO layer change in progress..."));
 | |
|     mysql_rwlock_rdlock(&LOCK_ssl_refresh);
 | |
|     int ssl_ret = sslaccept(ssl_acceptor_fd, net->vio, net->read_timeout, &errptr);
 | |
|     mysql_rwlock_unlock(&LOCK_ssl_refresh);
 | |
|     ssl_acceptor_stats_update(ssl_ret);
 | |
| 
 | |
|     if(ssl_ret)
 | |
|     {
 | |
|       DBUG_PRINT("error", ("Failed to accept new SSL connection"));
 | |
|       return packet_error;
 | |
|     }
 | |
| 
 | |
|     DBUG_PRINT("info", ("Reading user information over SSL layer"));
 | |
|     pkt_len= my_net_read(net);
 | |
|     if (unlikely(pkt_len == packet_error || pkt_len < NORMAL_HANDSHAKE_SIZE))
 | |
|     {
 | |
|       DBUG_PRINT("error", ("Failed to read user information (pkt_len= %lu)",
 | |
| 			   pkt_len));
 | |
|       return packet_error;
 | |
|     }
 | |
|   }
 | |
|   /*
 | |
|     Check whether the option require_secure_transport is on and in case
 | |
|     it is true that the secured connection type is used, that is either
 | |
|     unix socket or named pipe or ssl is in use.
 | |
|   */
 | |
|   else if (check_require_secured_transport(thd))
 | |
|   {
 | |
|     Host_errors errors;
 | |
| 
 | |
|     errors.m_ssl= 1;
 | |
|     inc_host_errors(mpvio->auth_info.thd->security_ctx->ip, &errors);
 | |
|     status_var_increment(thd->status_var.access_denied_errors);
 | |
|     my_error(ER_SECURE_TRANSPORT_REQUIRED, MYF(0));
 | |
| 
 | |
|     return packet_error;
 | |
|   }
 | |
| 
 | |
|   if (client_capabilities & CLIENT_PROTOCOL_41)
 | |
|   {
 | |
|     thd->max_client_packet_length= uint4korr(net->read_pos+4);
 | |
|     DBUG_PRINT("info", ("client_character_set: %d", (uint) net->read_pos[8]));
 | |
|     if (thd_init_client_charset(thd, (uint) net->read_pos[8]))
 | |
|       return packet_error;
 | |
|     end= (char*) net->read_pos+32;
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     if (pkt_len < 5)
 | |
|       return packet_error;
 | |
|     thd->max_client_packet_length= uint3korr(net->read_pos+2);
 | |
|     end= (char*) net->read_pos+5;
 | |
|   }
 | |
| 
 | |
|   if (end >= (char*) net->read_pos+ pkt_len +2)
 | |
|     return packet_error;
 | |
| 
 | |
|   if (thd->client_capabilities & CLIENT_IGNORE_SPACE)
 | |
|     thd->variables.sql_mode|= MODE_IGNORE_SPACE;
 | |
|   if (thd->client_capabilities & CLIENT_INTERACTIVE)
 | |
|     thd->variables.net_wait_timeout= thd->variables.net_interactive_timeout;
 | |
| 
 | |
|   if (end >= (char*) net->read_pos+ pkt_len +2)
 | |
|     return packet_error;
 | |
| 
 | |
|   if ((thd->client_capabilities & CLIENT_TRANSACTIONS) &&
 | |
|       opt_using_transactions)
 | |
|     net->return_status= &thd->server_status;
 | |
| 
 | |
|   char *user= end;
 | |
|   char *passwd= strend(user)+1;
 | |
|   size_t user_len= (size_t)(passwd - user - 1), db_len;
 | |
|   char *db= passwd;
 | |
|   char user_buff[USERNAME_LENGTH + 1];	// buffer to store user in utf8
 | |
|   uint dummy_errors;
 | |
| 
 | |
|   /*
 | |
|     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.
 | |
|   */
 | |
|   ulonglong len;
 | |
|   size_t passwd_len;
 | |
| 
 | |
|   if (!(thd->client_capabilities & CLIENT_SECURE_CONNECTION))
 | |
|     len= strlen(passwd);
 | |
|   else if (!(thd->client_capabilities & CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA))
 | |
|     len= (uchar)(*passwd++);
 | |
|   else
 | |
|   {
 | |
|     len= safe_net_field_length_ll((uchar**)&passwd,
 | |
|                                       net->read_pos + pkt_len - (uchar*)passwd);
 | |
|     if (len > pkt_len)
 | |
|       return packet_error;
 | |
|   }
 | |
| 
 | |
|   passwd_len= (size_t)len;
 | |
|   db= thd->client_capabilities & CLIENT_CONNECT_WITH_DB ?
 | |
|     db + passwd_len + 1 : 0;
 | |
| 
 | |
|   if (passwd == NULL ||
 | |
|       passwd + passwd_len + MY_TEST(db) > (char*) net->read_pos + pkt_len)
 | |
|     return packet_error;
 | |
| 
 | |
|   /* strlen() can't be easily deleted without changing protocol */
 | |
|   db_len= safe_strlen(db);
 | |
| 
 | |
|   char *next_field= passwd + passwd_len + (db ? db_len + 1 : 0);
 | |
|   Lex_ident_plugin client_plugin= Lex_cstring_strlen(next_field);
 | |
| 
 | |
|   /*
 | |
|     Since 4.1 all database names are stored in utf8
 | |
|     The cast is ok as copy_with_error will create a new area for db
 | |
|   */
 | |
|   DBUG_ASSERT(db || !db_len);
 | |
|   // Don't pass db==nullptr to avoid UB nullptr+0 inside copy_with_error()
 | |
|   if (unlikely(thd->copy_with_error(system_charset_info,
 | |
|                                     (LEX_STRING*) &mpvio->db,
 | |
|                                     thd->charset(), db ? db : "", db_len)))
 | |
|     return packet_error;
 | |
| 
 | |
|   user_len= copy_and_convert(user_buff, sizeof(user_buff) - 1,
 | |
|                              system_charset_info, user, user_len,
 | |
|                              thd->charset(), &dummy_errors);
 | |
|   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-= 2;
 | |
|   }
 | |
| 
 | |
|   /*
 | |
|     Clip username to allowed length in characters (not bytes).  This is
 | |
|     mostly for backward compatibility (to truncate long usernames, as
 | |
|     old 5.1 did)
 | |
|   */
 | |
|   user_len= Well_formed_prefix(system_charset_info, user, user_len,
 | |
|                                username_char_length).length();
 | |
|   user[user_len]= '\0';
 | |
| 
 | |
|   Security_context *sctx= thd->security_ctx;
 | |
| 
 | |
|   my_free(const_cast<char*>(sctx->user));
 | |
|   if (!(sctx->user= my_strndup(key_memory_MPVIO_EXT_auth_info, user, user_len, MYF(MY_WME))))
 | |
|     return packet_error; /* The error is set by my_strdup(). */
 | |
| 
 | |
| 
 | |
|   /*
 | |
|     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_clex_str);
 | |
| 
 | |
|   if (!initialized)
 | |
|   {
 | |
|     // if mysqld's been started with --skip-grant-tables option
 | |
|     mpvio->status= MPVIO_EXT::SUCCESS;
 | |
|     return packet_error;
 | |
|   }
 | |
| 
 | |
|   thd->password= passwd_len > 0;
 | |
|   if (find_mpvio_user(mpvio))
 | |
|     return packet_error;
 | |
| 
 | |
|   if ((thd->client_capabilities & CLIENT_PLUGIN_AUTH) &&
 | |
|       (client_plugin.str < (char *)net->read_pos + pkt_len))
 | |
|   {
 | |
|     next_field+= strlen(next_field) + 1;
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     /* Some clients lie. Sad, but true */
 | |
|     thd->client_capabilities &= ~CLIENT_PLUGIN_AUTH;
 | |
| 
 | |
|     if (thd->client_capabilities & CLIENT_SECURE_CONNECTION)
 | |
|       client_plugin= native_password_plugin_name;
 | |
|     else
 | |
|     {
 | |
|       /*
 | |
|         Normally old clients use old_password_plugin, but for
 | |
|         a passwordless accounts we use native_password_plugin.
 | |
|         See guess_auth_plugin().
 | |
|       */
 | |
|       client_plugin= passwd_len ? old_password_plugin_name
 | |
|                                 : native_password_plugin_name;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if ((thd->client_capabilities & CLIENT_CONNECT_ATTRS) &&
 | |
|       read_client_connect_attrs(&next_field, ((char *)net->read_pos) + pkt_len,
 | |
|                                 mpvio->auth_info.thd))
 | |
|     return packet_error;
 | |
| 
 | |
|   /*
 | |
|     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 (!lex_string_eq(&mpvio->acl_user->auth->plugin, plugin_name(mpvio->plugin)))
 | |
|   {
 | |
|     mpvio->cached_client_reply.pkt= passwd;
 | |
|     mpvio->cached_client_reply.pkt_len= (uint)passwd_len;
 | |
|     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 &&
 | |
|       !client_plugin.streq(Lex_cstring_strlen(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(&thd->net);
 | |
|     passwd= (char*)thd->net.read_pos;
 | |
|   }
 | |
| 
 | |
|   *buff= (uchar*) passwd;
 | |
|   return (ulong)passwd_len;
 | |
| #else
 | |
|   return 0;
 | |
| #endif
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   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 */
 | |
|   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 if (packet_len > 0 && (*packet == 1 || *packet == 255 || *packet == 254))
 | |
|   {
 | |
|     /*
 | |
|       we cannot allow plugin data packet to start from 255 or 254 -
 | |
|       as the client will treat it as an error or "change plugin" packet.
 | |
|       We'll escape these bytes with \1. Consequently, we
 | |
|       have to escape \1 byte too.
 | |
|     */
 | |
|     res= net_write_command(&mpvio->auth_info.thd->net, 1, (uchar*)"", 0,
 | |
|                            packet, packet_len);
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     res= my_net_write(&mpvio->auth_info.thd->net, packet, packet_len) ||
 | |
|          net_flush(&mpvio->auth_info.thd->net);
 | |
|   }
 | |
|   mpvio->cached_client_reply.plugin= ""_LEX_CSTRING;
 | |
|   mpvio->status= MPVIO_EXT::FAILURE; // the status is no longer RESTART
 | |
|   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 * const mpvio= (MPVIO_EXT *) param;
 | |
|   MYSQL_SERVER_AUTH_INFO * const ai= &mpvio->auth_info;
 | |
|   ulong pkt_len;
 | |
|   DBUG_ENTER("server_mpvio_read_packet");
 | |
|   if (mpvio->status == MPVIO_EXT::RESTART)
 | |
|   {
 | |
|     const char *client_auth_plugin=
 | |
|       ((st_mysql_auth *) (plugin_decl(mpvio->plugin)->info))->client_auth_plugin;
 | |
|     if (client_auth_plugin == 0)
 | |
|     {
 | |
|       mpvio->status= MPVIO_EXT::FAILURE;
 | |
|       pkt_len= 0;
 | |
|       *buf= 0;
 | |
|       goto done;
 | |
|     }
 | |
| 
 | |
|     if (mpvio->cached_client_reply.pkt)
 | |
|     {
 | |
|       DBUG_ASSERT(mpvio->packets_read > 0);
 | |
|       /*
 | |
|         if the have the data cached from the last server_mpvio_read_packet
 | |
|         (which can be the case if it's a restarted authentication)
 | |
|         and a client has used the correct plugin, then we can return the
 | |
|         cached data straight away and avoid one round trip.
 | |
|       */
 | |
|       if (Lex_ident_plugin(Lex_cstring_strlen(client_auth_plugin)).
 | |
|             streq(mpvio->cached_client_reply.plugin))
 | |
|       {
 | |
|         mpvio->status= MPVIO_EXT::FAILURE;
 | |
|         pkt_len= mpvio->cached_client_reply.pkt_len;
 | |
|         *buf= (uchar*) mpvio->cached_client_reply.pkt;
 | |
|         mpvio->packets_read++;
 | |
|         goto done;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     /*
 | |
|       plugin wants to read the data without sending anything first.
 | |
|       send an empty packet to force a server handshake packet to be sent
 | |
|     */
 | |
|     if (server_mpvio_write_packet(mpvio, 0, 0))
 | |
|       pkt_len= packet_error;
 | |
|     else
 | |
|       pkt_len= my_net_read(&ai->thd->net);
 | |
|   }
 | |
|   else
 | |
|     pkt_len= my_net_read(&ai->thd->net);
 | |
| 
 | |
|   if (unlikely(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 (unlikely(pkt_len == packet_error))
 | |
|       goto err;
 | |
|   }
 | |
|   else
 | |
|     *buf= ai->thd->net.read_pos;
 | |
| 
 | |
| done:
 | |
|   if (set_user_salt_if_needed(mpvio->acl_user, mpvio->curr_auth, mpvio->plugin))
 | |
|   {
 | |
|     ai->thd->clear_error(); // authenticating user should not see these errors
 | |
|     my_error(ER_ACCESS_DENIED_ERROR, MYF(0), ai->thd->security_ctx->user,
 | |
|              ai->thd->security_ctx->host_or_ip, ER_THD(ai->thd, ER_YES));
 | |
|     goto err;
 | |
|   }
 | |
| 
 | |
|   ai->user_name= ai->thd->security_ctx->user;
 | |
|   ai->user_name_length= (uint) strlen(ai->user_name);
 | |
|   ai->auth_string= mpvio->acl_user->auth[mpvio->curr_auth].salt.str;
 | |
|   ai->auth_string_length= (ulong) mpvio->acl_user->auth[mpvio->curr_auth].salt.length;
 | |
|   strmake_buf(ai->authenticated_as, mpvio->acl_user->user.str);
 | |
| 
 | |
|   DBUG_RETURN((int)pkt_len);
 | |
| 
 | |
| err:
 | |
|   if (mpvio->status == MPVIO_EXT::FAILURE)
 | |
|   {
 | |
|     if (!ai->thd->is_error())
 | |
|       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->auth_info.thd->net.vio, info);
 | |
| }
 | |
| 
 | |
| static bool acl_check_ssl(THD *thd, const ACL_USER *acl_user)
 | |
| {
 | |
| #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 FOR THIS SPECIFIC USER
 | |
|     return 0;
 | |
| #ifdef 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)
 | |
|     {
 | |
|       const char *ssl_cipher= SSL_get_cipher(ssl);
 | |
|       DBUG_PRINT("info", ("comparing ciphers: '%s' and '%s'",
 | |
|                          acl_user->ssl_cipher, ssl_cipher));
 | |
|       if (strcmp(acl_user->ssl_cipher, ssl_cipher))
 | |
|       {
 | |
|         if (global_system_variables.log_warnings)
 | |
|           sql_print_information("X509 ciphers mismatch: should be '%s' but is '%s'",
 | |
|                             acl_user->ssl_cipher, ssl_cipher);
 | |
|         return 1;
 | |
|       }
 | |
|     }
 | |
|     if (!acl_user->x509_issuer[0] && !acl_user->x509_subject[0])
 | |
|       return 0; // all done
 | |
| 
 | |
|     /* 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[0])
 | |
|     {
 | |
|       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);
 | |
|         OPENSSL_free(ptr);
 | |
|         X509_free(cert);
 | |
|         return 1;
 | |
|       }
 | |
|       OPENSSL_free(ptr);
 | |
|     }
 | |
|     /* X509 subject is specified, we check it .. */
 | |
|     if (acl_user->x509_subject[0])
 | |
|     {
 | |
|       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);
 | |
|         OPENSSL_free(ptr);
 | |
|         X509_free(cert);
 | |
|         return 1;
 | |
|       }
 | |
|       OPENSSL_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;
 | |
| }
 | |
| 
 | |
| static void make_ssl_info(THD *thd, LEX_CSTRING salt, char *info)
 | |
| {
 | |
| #ifdef HAVE_OPENSSL
 | |
|   uchar digest[256/8];
 | |
|   if (!salt.length)
 | |
|     return;
 | |
| 
 | |
|   /*
 | |
|     mark that it's after-auth mysql->info version 1.
 | |
|     meaning, it contains sha2(salt, scramble, sha2_cert_fingerprint)
 | |
|     encoded in 64 lowercase letters 'a'..'p', one letter per 4 bits (0..15)
 | |
|   */
 | |
|   *info++= 1; // Version 1
 | |
| 
 | |
|   DBUG_ASSERT(thd->scramble[SCRAMBLE_LENGTH] == 0);
 | |
| 
 | |
|   LEX_CUSTRING fp= ssl_acceptor_fingerprint();
 | |
|   my_sha256_multi(digest, salt.str, salt.length, thd->scramble,
 | |
|                   (size_t)SCRAMBLE_LENGTH, fp.str, fp.length, NULL);
 | |
|   octet2hex(info, digest, sizeof(digest));
 | |
| 
 | |
| #endif
 | |
| }
 | |
| 
 | |
| static int do_auth_once(THD *thd, const LEX_CSTRING *auth_plugin_name,
 | |
|                         MPVIO_EXT *mpvio)
 | |
| {
 | |
|   int res= CR_OK;
 | |
|   bool unlock_plugin= false;
 | |
|   plugin_ref plugin= get_auth_plugin(thd, *auth_plugin_name, &unlock_plugin);
 | |
| 
 | |
|   mpvio->plugin= plugin;
 | |
|   mpvio->auth_info.user_name= NULL;
 | |
| 
 | |
|   if (plugin)
 | |
|   {
 | |
|     st_mysql_auth *info= (st_mysql_auth *) plugin_decl(plugin)->info;
 | |
|     switch (info->interface_version >> 8) {
 | |
|     case 0x02:
 | |
|       res= info->authenticate_user(mpvio, &mpvio->auth_info);
 | |
|       break;
 | |
|     case 0x01:
 | |
|       {
 | |
|         MYSQL_SERVER_AUTH_INFO_0x0100 compat;
 | |
|         compat.downgrade(&mpvio->auth_info);
 | |
|         res= info->authenticate_user(mpvio, (MYSQL_SERVER_AUTH_INFO *)&compat);
 | |
|         compat.upgrade(&mpvio->auth_info);
 | |
|       }
 | |
|       break;
 | |
|     default: DBUG_ASSERT(0);
 | |
|     }
 | |
| 
 | |
|     if (unlock_plugin)
 | |
|       plugin_unlock(thd, plugin);
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     /* Server cannot load the required plugin. */
 | |
|     Host_errors errors;
 | |
|     errors.m_no_auth_plugin= 1;
 | |
|     inc_host_errors(mpvio->auth_info.thd->security_ctx->ip, &errors);
 | |
|     my_error(ER_PLUGIN_IS_NOT_LOADED, MYF(0), auth_plugin_name->str);
 | |
|     res= CR_ERROR;
 | |
|   }
 | |
| 
 | |
|   return res;
 | |
| }
 | |
| 
 | |
| 
 | |
| /* Increment, or clear password errors for a user. */
 | |
| static void handle_password_errors(const LEX_CSTRING &user,
 | |
|                                    const LEX_CSTRING &hostname,
 | |
|                                    ACL_USER::PASSWD_ERROR_ACTION action)
 | |
| {
 | |
| #ifndef NO_EMBEDDED_ACCESS_CHECKS
 | |
|   mysql_mutex_assert_not_owner(&acl_cache->lock);
 | |
|   mysql_mutex_lock(&acl_cache->lock);
 | |
|   ACL_USER *u = find_user_exact(hostname, user);
 | |
|   if (u)
 | |
|     u->update_password_errors(action);
 | |
|   mysql_mutex_unlock(&acl_cache->lock);
 | |
| #endif
 | |
| }
 | |
| 
 | |
| static bool check_password_lifetime(THD *thd, const ACL_USER &acl_user)
 | |
| {
 | |
|   /* the password should never expire */
 | |
|   if (!acl_user.password_lifetime)
 | |
|     return false;
 | |
| 
 | |
|   longlong interval= acl_user.password_lifetime;
 | |
|   if (interval < 0)
 | |
|   {
 | |
|     interval= default_password_lifetime;
 | |
| 
 | |
|     /* default global policy applies, and that is password never expires */
 | |
|     if (!interval)
 | |
|       return false;
 | |
|   }
 | |
| 
 | |
|   thd->set_time();
 | |
| 
 | |
|   if ((((longlong) thd->query_start() - (longlong) acl_user.password_last_changed))/3600/24 >=
 | |
|       interval)
 | |
|     return true;
 | |
| 
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| /**
 | |
|   Perform the handshake, authorize the client and update thd sctx variables.
 | |
| 
 | |
|   @param thd                     thread handle
 | |
|   @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 com_change_user_pkt_len)
 | |
| {
 | |
|   int res= CR_OK;
 | |
|   MPVIO_EXT mpvio;
 | |
|   char ssl_info[256/4 + 2]= {0}; // '\1', SHA256 (1 char per 4 bits), '\0'
 | |
|   enum  enum_server_command command= com_change_user_pkt_len ? COM_CHANGE_USER
 | |
|                                                              : COM_CONNECT;
 | |
|   DBUG_ENTER("acl_authenticate");
 | |
| 
 | |
|   bzero(&mpvio, sizeof(mpvio));
 | |
|   mpvio.read_packet= server_mpvio_read_packet;
 | |
|   mpvio.write_packet= server_mpvio_write_packet;
 | |
|   mpvio.cached_client_reply.plugin= ""_LEX_CSTRING;
 | |
|   mpvio.info= server_mpvio_info;
 | |
|   mpvio.status= MPVIO_EXT::RESTART;
 | |
|   mpvio.auth_info.thd= thd;
 | |
|   mpvio.auth_info.host_or_ip= thd->security_ctx->host_or_ip;
 | |
|   mpvio.auth_info.host_or_ip_length=
 | |
|     (unsigned int) strlen(thd->security_ctx->host_or_ip);
 | |
| 
 | |
|   DBUG_PRINT("info", ("com_change_user_pkt_len=%u", com_change_user_pkt_len));
 | |
| 
 | |
|   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
 | |
| 
 | |
|     if (parse_com_change_user_packet(&mpvio, com_change_user_pkt_len))
 | |
|       DBUG_RETURN(1);
 | |
| 
 | |
|     res= mpvio.status ==  MPVIO_EXT::SUCCESS ? CR_OK : CR_ERROR;
 | |
| 
 | |
|     DBUG_ASSERT(mpvio.status == MPVIO_EXT::RESTART ||
 | |
|                 mpvio.status == MPVIO_EXT::SUCCESS);
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     /* mark the thd as having no scramble yet */
 | |
|     thd->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, default_auth_plugin_name, &mpvio);
 | |
|   }
 | |
| 
 | |
|   PSI_CALL_set_connection_type(vio_type(thd->net.vio));
 | |
| 
 | |
|   Security_context * const sctx= thd->security_ctx;
 | |
|   const ACL_USER * acl_user= mpvio.acl_user;
 | |
|   if (!acl_user)
 | |
|     statistic_increment(aborted_connects_preauth, &LOCK_status);
 | |
| 
 | |
|   if (acl_user)
 | |
|   {
 | |
|     /*
 | |
|       retry the authentication with curr_auth==0 if after receiving the user
 | |
|       name we found that we need to switch to a non-default plugin
 | |
|     */
 | |
|     for (mpvio.curr_auth= mpvio.status != MPVIO_EXT::RESTART;
 | |
|          res != CR_OK && mpvio.curr_auth < acl_user->nauth;
 | |
|          mpvio.curr_auth++)
 | |
|     {
 | |
|       thd->clear_error();
 | |
|       mpvio.status= MPVIO_EXT::RESTART;
 | |
|       res= do_auth_once(thd, &acl_user->auth[mpvio.curr_auth].plugin, &mpvio);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (mpvio.make_it_fail && res == CR_OK)
 | |
|   {
 | |
|     mpvio.status= MPVIO_EXT::FAILURE;
 | |
|     res= CR_ERROR;
 | |
|   }
 | |
| 
 | |
|   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 (sctx->user)
 | |
|   {
 | |
|     general_log_print(thd, command, (char*) "%s@%s on %s using %s",
 | |
|                       sctx->user, sctx->host_or_ip,
 | |
|                       safe_str(mpvio.db.str), safe_vio_type_name(thd->net.vio));
 | |
|   }
 | |
| 
 | |
|   if (res > CR_OK && mpvio.status != MPVIO_EXT::SUCCESS)
 | |
|   {
 | |
|     Host_errors errors;
 | |
|     switch (res)
 | |
|     {
 | |
|     case CR_AUTH_PLUGIN_ERROR:
 | |
|       errors.m_auth_plugin= 1;
 | |
|       break;
 | |
|     case CR_AUTH_HANDSHAKE:
 | |
|       errors.m_handshake= 1;
 | |
|       break;
 | |
|     case CR_AUTH_USER_CREDENTIALS:
 | |
|       errors.m_authentication= 1;
 | |
|       if (thd->password && !mpvio.make_it_fail)
 | |
|         handle_password_errors(acl_user->user, acl_user->hostname(),
 | |
|                                ACL_USER::PASSWD_ERROR_INCREMENT);
 | |
|       break;
 | |
|     case CR_ERROR:
 | |
|     default:
 | |
|       /* Unknown of unspecified auth plugin error. */
 | |
|       errors.m_auth_plugin= 1;
 | |
|       break;
 | |
|     }
 | |
|     inc_host_errors(mpvio.auth_info.thd->security_ctx->ip, &errors);
 | |
|     if (!thd->is_error())
 | |
|       login_failed_error(thd);
 | |
|     DBUG_RETURN(1);
 | |
|   }
 | |
| 
 | |
|   sctx->proxy_user[0]= 0;
 | |
|   if (thd->password && acl_user->password_errors)
 | |
|   {
 | |
|     /* Login succeeded, clear password errors.*/
 | |
|     handle_password_errors(acl_user->user, acl_user->hostname(),
 | |
|                            ACL_USER::PASSWD_ERROR_CLEAR);
 | |
|   }
 | |
| 
 | |
|   if (initialized) // if not --skip-grant-tables
 | |
|   {
 | |
|     /*
 | |
|       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))
 | |
|     {
 | |
|       Host_errors errors;
 | |
|       errors.m_ssl= 1;
 | |
|       inc_host_errors(mpvio.auth_info.thd->security_ctx->ip, &errors);
 | |
|       login_failed_error(thd);
 | |
|       DBUG_RETURN(1);
 | |
|     }
 | |
| 
 | |
|     if (acl_user->account_locked)
 | |
|     {
 | |
|       status_var_increment(denied_connections);
 | |
|       my_error(ER_ACCOUNT_HAS_BEEN_LOCKED, MYF(0));
 | |
|       DBUG_RETURN(1);
 | |
|     }
 | |
| 
 | |
|     bool client_can_handle_exp_pass= thd->client_capabilities &
 | |
|                                      CLIENT_CAN_HANDLE_EXPIRED_PASSWORDS;
 | |
|     bool password_expired= thd->password != PASSWORD_USED_NO_MENTION
 | |
|                            && (acl_user->password_expired ||
 | |
|                                check_password_lifetime(thd, *acl_user));
 | |
| 
 | |
|     if (!client_can_handle_exp_pass && disconnect_on_expired_password &&
 | |
|         password_expired)
 | |
|     {
 | |
|       status_var_increment(denied_connections);
 | |
|       my_error(ER_MUST_CHANGE_PASSWORD_LOGIN, MYF(0));
 | |
|       DBUG_RETURN(1);
 | |
|     }
 | |
| 
 | |
|     sctx->password_expired= password_expired;
 | |
| 
 | |
| #ifndef NO_EMBEDDED_ACCESS_CHECKS
 | |
|     if (!password_expired)
 | |
|     {
 | |
|       bool is_proxy_user= FALSE;
 | |
|       const char *auth_user = acl_user->user.str;
 | |
|       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)
 | |
|         {
 | |
|           Host_errors errors;
 | |
|           errors.m_proxy_user= 1;
 | |
|           inc_host_errors(mpvio.auth_info.thd->security_ctx->ip, &errors);
 | |
|           if (!thd->is_error())
 | |
|             login_failed_error(thd);
 | |
|           DBUG_RETURN(1);
 | |
|         }
 | |
| 
 | |
|         my_snprintf(sctx->proxy_user, sizeof(sctx->proxy_user) - 1,
 | |
|                     "'%s'@'%s'", auth_user,
 | |
|                     safe_str(acl_user->host.hostname));
 | |
| 
 | |
|         /* we're proxying : find the proxy user definition */
 | |
|         mysql_mutex_lock(&acl_cache->lock);
 | |
|         acl_proxy_user=
 | |
|           find_user_exact(
 | |
|             Lex_cstring_strlen(safe_str(proxy_user->get_proxied_host())),
 | |
|             Lex_cstring_strlen(mpvio.auth_info.authenticated_as));
 | |
| 
 | |
|         if (!acl_proxy_user)
 | |
|         {
 | |
|           mysql_mutex_unlock(&acl_cache->lock);
 | |
| 
 | |
|           Host_errors errors;
 | |
|           errors.m_proxy_user_acl= 1;
 | |
|           inc_host_errors(mpvio.auth_info.thd->security_ctx->ip, &errors);
 | |
|           if (!thd->is_error())
 | |
|             login_failed_error(thd);
 | |
|           DBUG_RETURN(1);
 | |
|         }
 | |
|         acl_user= acl_proxy_user->copy(thd->mem_root);
 | |
|         mysql_mutex_unlock(&acl_cache->lock);
 | |
|       }
 | |
|     }
 | |
| #endif
 | |
| 
 | |
|     if (set_privs_on_login(thd, acl_user))
 | |
|       DBUG_RETURN(1);
 | |
|   }
 | |
|   else
 | |
|     sctx->skip_grants();
 | |
| 
 | |
|   if (thd->user_connect &&
 | |
|       (thd->user_connect->user_resources.conn_per_hour ||
 | |
|        thd->user_connect->user_resources.user_conn ||
 | |
|        max_user_connections_checking) &&
 | |
|        check_for_max_user_connections(thd, thd->user_connect))
 | |
|   {
 | |
|     /* Ensure we don't decrement thd->user_connections->connections twice */
 | |
|     thd->user_connect= 0;
 | |
|     status_var_increment(denied_connections);
 | |
|     DBUG_RETURN(1); // The error is set in check_for_max_user_connections()
 | |
|   }
 | |
| 
 | |
|   DBUG_PRINT("info",
 | |
|              ("Capabilities: %llu  packet_length: %ld  Host: '%s'  "
 | |
|               "Login user: '%s' Priv_user: '%s'  Using password: %s "
 | |
|               "Access: %llx  db: '%s'",
 | |
|               thd->client_capabilities, thd->max_client_packet_length,
 | |
|               sctx->host_or_ip, sctx->user, sctx->priv_user,
 | |
|               thd->password ? "yes": "no",
 | |
|               (longlong) sctx->master_access, mpvio.db.str));
 | |
| 
 | |
|   if (command == COM_CONNECT &&
 | |
|       !(thd->main_security_ctx.master_access & PRIV_IGNORE_MAX_CONNECTIONS))
 | |
|   {
 | |
|     if (*thd->scheduler->connection_count > *thd->scheduler->max_connections)
 | |
|     {                                         // too many connections
 | |
|       my_error(ER_CON_COUNT_ERROR, MYF(0));
 | |
|       DBUG_RETURN(1);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* Change a database if necessary */
 | |
|   if (mpvio.db.length)
 | |
|   {
 | |
|     uint err = mysql_change_db(thd, &mpvio.db, FALSE);
 | |
|     if(err)
 | |
|     {
 | |
|       if (err == ER_DBACCESS_DENIED_ERROR)
 | |
|       {
 | |
|         /*
 | |
|           Got an "access denied" error, which must be handled
 | |
|           other access denied errors (see login_failed_error()).
 | |
|           mysql_change_db() already sent error to client, and
 | |
|           wrote to general log, we only need to increment the counter
 | |
|           and maybe write a warning to error log.
 | |
|         */
 | |
|         status_var_increment(thd->status_var.access_denied_errors);
 | |
|         if (global_system_variables.log_warnings > 1)
 | |
|         {
 | |
|           Security_context* sctx = thd->security_ctx;
 | |
|           sql_print_warning(ER_DEFAULT(err),
 | |
|             sctx->priv_user, sctx->priv_host, mpvio.db.str);
 | |
|         }
 | |
|       }
 | |
|       DBUG_RETURN(1);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   thd->net.net_skip_rest_factor= 2;  // skip at most 2*max_packet_size
 | |
| 
 | |
|   if (mpvio.auth_info.external_user[0])
 | |
|     sctx->external_user= my_strdup(key_memory_MPVIO_EXT_auth_info,
 | |
|                                    mpvio.auth_info.external_user, MYF(0));
 | |
| 
 | |
|   if (initialized && !com_change_user_pkt_len)
 | |
|     make_ssl_info(thd, acl_user->auth[mpvio.curr_auth-1].salt, ssl_info);
 | |
| 
 | |
|   my_ok(thd, 0, 0, ssl_info[0] == '\1' ? ssl_info : NULL);
 | |
| 
 | |
|   PSI_CALL_set_thread_account
 | |
|     (thd->main_security_ctx.user, static_cast<uint>(strlen(thd->main_security_ctx.user)),
 | |
|     thd->main_security_ctx.host_or_ip, static_cast<uint>(strlen(thd->main_security_ctx.host_or_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;
 | |
|   THD *thd=info->thd;
 | |
|   DBUG_ENTER("native_password_authenticate");
 | |
| 
 | |
|   /* generate the scramble, or reuse the old one */
 | |
|   if (thd->scramble[SCRAMBLE_LENGTH])
 | |
|     thd_create_random_password(thd, thd->scramble, SCRAMBLE_LENGTH);
 | |
| 
 | |
|   /* and send it to the client */
 | |
|   if (mpvio->write_packet(mpvio, (uchar*)thd->scramble, SCRAMBLE_LENGTH + 1))
 | |
|     DBUG_RETURN(CR_AUTH_HANDSHAKE);
 | |
| 
 | |
|   /* reply and authenticate */
 | |
| 
 | |
|   /*
 | |
|     <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_AUTH_HANDSHAKE);
 | |
|   DBUG_PRINT("info", ("reply read : pkt_len=%d", pkt_len));
 | |
| 
 | |
| #ifdef NO_EMBEDDED_ACCESS_CHECKS
 | |
|   DBUG_RETURN(CR_OK);
 | |
| #endif
 | |
| 
 | |
|   DBUG_EXECUTE_IF("native_password_bad_reply", { pkt_len= 12; });
 | |
| 
 | |
|   if (pkt_len == 0) /* no password */
 | |
|     DBUG_RETURN(info->auth_string_length != 0
 | |
|                 ? CR_AUTH_USER_CREDENTIALS : CR_OK);
 | |
| 
 | |
|   info->password_used= PASSWORD_USED_YES;
 | |
|   if (pkt_len == SCRAMBLE_LENGTH)
 | |
|   {
 | |
|     if (info->auth_string_length != SCRAMBLE_LENGTH)
 | |
|       DBUG_RETURN(CR_AUTH_USER_CREDENTIALS);
 | |
| 
 | |
|     if (check_scramble(pkt, thd->scramble, (uchar*)info->auth_string))
 | |
|       DBUG_RETURN(CR_AUTH_USER_CREDENTIALS);
 | |
|     else
 | |
|       DBUG_RETURN(CR_OK);
 | |
|   }
 | |
| 
 | |
|   my_error(ER_HANDSHAKE_ERROR, MYF(0));
 | |
|   DBUG_RETURN(CR_AUTH_HANDSHAKE);
 | |
| }
 | |
| 
 | |
| static int native_password_make_scramble(const char *password,
 | |
|                       size_t password_length, char *hash, size_t *hash_length)
 | |
| {
 | |
|   DBUG_ASSERT(*hash_length >= SCRAMBLED_PASSWORD_CHAR_LENGTH);
 | |
|   if (password_length == 0)
 | |
|     *hash_length= 0;
 | |
|   else
 | |
|   {
 | |
|     *hash_length= SCRAMBLED_PASSWORD_CHAR_LENGTH;
 | |
|     my_make_scrambled_password(hash, password, password_length);
 | |
|   }
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| /* As this contains is a string of not a valid SCRAMBLE_LENGTH */
 | |
| static const char invalid_password[] = "*THISISNOTAVALIDPASSWORDTHATCANBEUSEDHERE";
 | |
| 
 | |
| static int native_password_get_salt(const char *hash, size_t hash_length,
 | |
|                                     unsigned char *out, size_t *out_length)
 | |
| {
 | |
|   DBUG_ASSERT(sizeof(invalid_password) > SCRAMBLE_LENGTH);
 | |
|   DBUG_ASSERT(*out_length >= SCRAMBLE_LENGTH);
 | |
|   DBUG_ASSERT(*out_length >= sizeof(invalid_password));
 | |
|   if (hash_length == 0)
 | |
|   {
 | |
|     *out_length= 0;
 | |
|     return 0;
 | |
|   }
 | |
| 
 | |
|   if (hash_length != SCRAMBLED_PASSWORD_CHAR_LENGTH)
 | |
|   {
 | |
|     if (hash_length == 7 && strcmp(hash, "invalid") == 0)
 | |
|     {
 | |
|       memcpy(out, invalid_password, sizeof(invalid_password));
 | |
|       *out_length= sizeof(invalid_password);
 | |
|       return 0;
 | |
|     }
 | |
|     my_error(ER_PASSWD_LENGTH, MYF(0), SCRAMBLED_PASSWORD_CHAR_LENGTH);
 | |
|     return 1;
 | |
|   }
 | |
| 
 | |
|   for (const char *c= hash + 1; c < (hash + hash_length); c++)
 | |
|   {
 | |
|     /* If any non-hex characters are found, mark the password as invalid. */
 | |
|     if (!(*c >= '0' && *c <= '9') &&
 | |
|         !(*c >= 'A' && *c <= 'F') &&
 | |
|         !(*c >= 'a' && *c <= 'f'))
 | |
|     {
 | |
|       memcpy(out, invalid_password, sizeof(invalid_password));
 | |
|       *out_length= sizeof(invalid_password);
 | |
|       return 0;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   *out_length= SCRAMBLE_LENGTH;
 | |
|   get_salt_from_password(out, hash);
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| 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;
 | |
|   THD *thd=info->thd;
 | |
| 
 | |
|   /* generate the scramble, or reuse the old one */
 | |
|   if (thd->scramble[SCRAMBLE_LENGTH])
 | |
|     thd_create_random_password(thd, thd->scramble, SCRAMBLE_LENGTH);
 | |
|   /* and send it to the client */
 | |
|   if (mpvio->write_packet(mpvio, (uchar*)thd->scramble, SCRAMBLE_LENGTH + 1))
 | |
|     return CR_AUTH_HANDSHAKE;
 | |
| 
 | |
|   /* read the reply and authenticate */
 | |
|   if ((pkt_len= mpvio->read_packet(mpvio, &pkt)) < 0)
 | |
|     return CR_AUTH_HANDSHAKE;
 | |
| 
 | |
| #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= (int)strnlen((char*)pkt, pkt_len);
 | |
| 
 | |
|   if (pkt_len == 0) /* no password */
 | |
|     return info->auth_string_length ? CR_AUTH_USER_CREDENTIALS : CR_OK;
 | |
| 
 | |
|   if (secure_auth(thd))
 | |
|     return CR_AUTH_HANDSHAKE;
 | |
| 
 | |
|   info->password_used= PASSWORD_USED_YES;
 | |
| 
 | |
|   if (pkt_len == SCRAMBLE_LENGTH_323)
 | |
|   {
 | |
|     if (!info->auth_string_length)
 | |
|       return CR_AUTH_USER_CREDENTIALS;
 | |
| 
 | |
|     return check_scramble_323(pkt, thd->scramble, (ulong *) info->auth_string)
 | |
|              ? CR_AUTH_USER_CREDENTIALS : CR_OK;
 | |
|   }
 | |
| 
 | |
|   my_error(ER_HANDSHAKE_ERROR, MYF(0));
 | |
|   return CR_AUTH_HANDSHAKE;
 | |
| }
 | |
| 
 | |
| static int old_password_make_scramble(const char *password,
 | |
|                       size_t password_length, char *hash, size_t *hash_length)
 | |
| {
 | |
|   DBUG_ASSERT(*hash_length >= SCRAMBLED_PASSWORD_CHAR_LENGTH_323);
 | |
|   if (password_length == 0)
 | |
|     *hash_length= 0;
 | |
|   else
 | |
|   {
 | |
|     *hash_length= SCRAMBLED_PASSWORD_CHAR_LENGTH_323;
 | |
|     my_make_scrambled_password_323(hash, password, password_length);
 | |
|   }
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| #define SALT_LENGTH_323 (sizeof(ulong)*2)
 | |
| static int old_password_get_salt(const char *hash, size_t hash_length,
 | |
|                                  unsigned char *out, size_t *out_length)
 | |
| {
 | |
|   DBUG_ASSERT(*out_length >= SALT_LENGTH_323);
 | |
| 
 | |
|   if (hash_length != SCRAMBLED_PASSWORD_CHAR_LENGTH_323)
 | |
|   {
 | |
|     my_error(ER_PASSWD_LENGTH, MYF(0), SCRAMBLED_PASSWORD_CHAR_LENGTH_323);
 | |
|     return 1;
 | |
|   }
 | |
| 
 | |
|   *out_length= SALT_LENGTH_323;
 | |
|   get_salt_from_password_323((ulong*)out, hash);
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| static struct st_mysql_auth native_password_handler=
 | |
| {
 | |
|   MYSQL_AUTHENTICATION_INTERFACE_VERSION,
 | |
|   native_password_plugin_name.str,
 | |
|   native_password_authenticate,
 | |
|   native_password_make_scramble,
 | |
|   native_password_get_salt
 | |
| };
 | |
| 
 | |
| static struct st_mysql_auth old_password_handler=
 | |
| {
 | |
|   MYSQL_AUTHENTICATION_INTERFACE_VERSION,
 | |
|   old_password_plugin_name.str,
 | |
|   old_password_authenticate,
 | |
|   old_password_make_scramble,
 | |
|   old_password_get_salt
 | |
| };
 | |
| 
 | |
| maria_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 */
 | |
|   "1.0",                                        /* String version   */
 | |
|   MariaDB_PLUGIN_MATURITY_STABLE                /* Maturity         */
 | |
| },
 | |
| {
 | |
|   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 */
 | |
|   "1.0",                                        /* String version   */
 | |
|   MariaDB_PLUGIN_MATURITY_STABLE                /* Maturity         */
 | |
| }
 | |
| maria_declare_plugin_end;
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Exporting functions that allow plugins to do server-style
 | |
|   host/user matching. Used in server_audit2 plugin.
 | |
| */
 | |
| extern "C" int maria_compare_hostname(
 | |
|                   const char *wild_host, long wild_ip, long ip_mask,
 | |
|                   const char *host, const char *ip)
 | |
| {
 | |
| #ifndef NO_EMBEDDED_ACCESS_CHECKS
 | |
|   acl_host_and_ip h;
 | |
|   h.hostname= (char *) wild_host;
 | |
|   h.ip= wild_ip;
 | |
|   h.ip_mask= ip_mask;
 | |
| 
 | |
|   return compare_hostname(&h, host, ip);
 | |
| #else
 | |
|   return 0;
 | |
| #endif
 | |
| }
 | |
| 
 | |
| 
 | |
| extern "C" void maria_update_hostname(
 | |
|                   const char **wild_host, long *wild_ip, long *ip_mask,
 | |
|                   const char *host)
 | |
| {
 | |
| #ifndef NO_EMBEDDED_ACCESS_CHECKS
 | |
|   acl_host_and_ip h;
 | |
|   update_hostname(&h, host);
 | |
|   *wild_host= h.hostname;
 | |
|   *wild_ip= h.ip;
 | |
|   *ip_mask= h.ip_mask;
 | |
| #endif
 | |
| }
 |