1 /* Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.
2    This program is free software; you can redistribute it and/or modify
3    it under the terms of the GNU General Public License, version 2.0,
4    as published by the Free Software Foundation.
5 
6    This program is also distributed with certain software (including
7    but not limited to OpenSSL) that is licensed under separate terms,
8    as designated in a particular file or component or in included license
9    documentation.  The authors of MySQL hereby grant you an additional
10    permission to link the program and your derivative works with the
11    separately licensed software that they have included with MySQL.
12 
13    This program is distributed in the hope that it will be useful,
14    but WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16    GNU General Public License, version 2.0, for more details.
17 
18    You should have received a copy of the GNU General Public License
19    along with this program; if not, write to the Free Software
20    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA */
21 
22 #include "sql_parse.h"                  /* check_access */
23 #include "rpl_filter.h"                 /* rpl_filter */
24 #include "sql_base.h"                   /* MYSQL_LOCK_IGNORE_TIMEOUT */
25 #include "sql_table.h"                  /* open_ltable */
26 #include "sql_plugin.h"                 /* lock_plugin_data etc. */
27 #include "password.h"                   /* my_make_scrambled_password */
28 #include "log_event.h"                  /* append_query_string */
29 #include "key.h"                        /* key_copy, key_cmp_if_same */
30                                         /* key_restore */
31 
32 #include "auth_internal.h"
33 #include "sql_auth_cache.h"
34 #include "sql_authentication.h"
35 #include "prealloced_array.h"
36 #include "tztime.h"
37 #include "crypt_genhash_impl.h"         /* CRYPT_MAX_PASSWORD_SIZE */
38 #include "sql_user_table.h"
39 #include <set>
40 
41 #ifndef DBUG_OFF
42 #define HASH_STRING_WITH_QUOTE \
43         "$5$BVZy9O>'a+2MH]_?$fpWyabcdiHjfCVqId/quykZzjaA7adpkcen/uiQrtmOK4p4"
44 #endif
45 
46 /**
47   Auxiliary function for constructing a  user list string.
48   This function is used for error reporting and logging.
49 
50   @param thd     Thread context
51   @param str     A String to store the user list.
52   @param user    A LEX_USER which will be appended into user list.
53   @param comma   If TRUE, append a ',' before the the user.
54   @param ident   If TRUE, append ' IDENTIFIED BY/WITH...' after the user,
55                  if the given user has credentials set with 'IDENTIFIED BY/WITH'
56  */
append_user(THD * thd,String * str,LEX_USER * user,bool comma=true,bool ident=false)57 void append_user(THD *thd, String *str, LEX_USER *user, bool comma= true,
58                  bool ident= false)
59 {
60   String from_user(user->user.str, user->user.length, system_charset_info);
61   String from_plugin(user->plugin.str, user->plugin.length, system_charset_info);
62   String from_auth(user->auth.str, user->auth.length, system_charset_info);
63   String from_host(user->host.str, user->host.length, system_charset_info);
64 
65   if (comma)
66     str->append(',');
67   append_query_string(thd, system_charset_info, &from_user, str);
68   str->append(STRING_WITH_LEN("@"));
69   append_query_string(thd, system_charset_info, &from_host, str);
70 
71   if (ident)
72   {
73     if (user->plugin.str && (user->plugin.length > 0) &&
74         memcmp(user->plugin.str, native_password_plugin_name.str,
75                user->plugin.length))
76     {
77       /**
78           The plugin identifier is allowed to be specified,
79           both with and without quote marks. We log it with
80           quotes always.
81         */
82       str->append(STRING_WITH_LEN(" IDENTIFIED WITH "));
83       append_query_string(thd, system_charset_info, &from_plugin, str);
84 
85       if (user->auth.str && (user->auth.length > 0))
86       {
87         str->append(STRING_WITH_LEN(" AS "));
88         append_query_string(thd, system_charset_info, &from_auth, str);
89       }
90     }
91     else if (user->auth.str)
92     {
93       str->append(STRING_WITH_LEN(" IDENTIFIED BY PASSWORD '"));
94       if (user->uses_identified_by_password_clause ||
95           user->uses_authentication_string_clause)
96       {
97         str->append(user->auth.str, user->auth.length);
98         str->append("'");
99       }
100       else
101       {
102         /*
103           Password algorithm is chosen based on old_passwords variable or
104           TODO the new password_algorithm variable.
105           It is assumed that the variable hasn't changed since parsing.
106         */
107         if (thd->variables.old_passwords == 0)
108         {
109           /*
110             my_make_scrambled_password_sha1() requires a target buffer size of
111             SCRAMBLED_PASSWORD_CHAR_LENGTH + 1.
112             The extra character is for the probably originate from either '\0'
113             or the initial '*' character.
114           */
115           char tmp[SCRAMBLED_PASSWORD_CHAR_LENGTH + 1];
116           my_make_scrambled_password_sha1(tmp, user->auth.str,
117                                           user->auth.length);
118           str->append(tmp);
119         }
120         else
121         {
122           /*
123             With old_passwords == 2 the scrambled password will be binary.
124           */
125           DBUG_ASSERT(thd->variables.old_passwords == 2);
126           str->append("<secret>");
127         }
128         str->append("'");
129       }
130     }
131   }
132 }
133 
append_user_new(THD * thd,String * str,LEX_USER * user,bool comma,bool hide_password_hash)134 void append_user_new(THD *thd, String *str, LEX_USER *user, bool comma,
135                      bool hide_password_hash)
136 {
137   String from_user(user->user.str, user->user.length, system_charset_info);
138   String from_plugin(user->plugin.str, user->plugin.length, system_charset_info);
139   String default_plugin(default_auth_plugin_name.str,
140                         default_auth_plugin_name.length, system_charset_info);
141   String from_auth(user->auth.str, user->auth.length, system_charset_info);
142   String from_host(user->host.str, user->host.length, system_charset_info);
143 
144   if (comma)
145     str->append(',');
146   append_query_string(thd, system_charset_info, &from_user, str);
147   str->append(STRING_WITH_LEN("@"));
148   append_query_string(thd, system_charset_info, &from_host, str);
149 
150   /* CREATE USER is always rewritten with IDENTIFIED WITH .. AS */
151   if (thd->lex->sql_command == SQLCOM_CREATE_USER)
152   {
153     str->append(STRING_WITH_LEN(" IDENTIFIED WITH "));
154     if (user->plugin.length > 0)
155       append_query_string(thd, system_charset_info, &from_plugin, str);
156     else
157       append_query_string(thd, system_charset_info, &default_plugin, str);
158     if (user->auth.length > 0)
159     {
160       str->append(STRING_WITH_LEN(" AS "));
161       if (thd->lex->contains_plaintext_password)
162       {
163         str->append("'");
164         str->append(STRING_WITH_LEN("<secret>"));
165         str->append("'");
166       }
167       else
168         append_query_string(thd, system_charset_info, &from_auth, str);
169     }
170   }
171   else
172   {
173     if (user->uses_identified_by_clause ||
174         user->uses_identified_with_clause ||
175         user->uses_identified_by_password_clause)
176     {
177       str->append(STRING_WITH_LEN(" IDENTIFIED WITH "));
178       if (user->plugin.length > 0)
179         append_query_string(thd, system_charset_info, &from_plugin, str);
180       else
181         append_query_string(thd, system_charset_info, &default_plugin, str);
182       if (user->auth.length > 0)
183       {
184         str->append(STRING_WITH_LEN(" AS "));
185         if (thd->lex->contains_plaintext_password ||
186             hide_password_hash)
187         {
188           str->append("'");
189           str->append(STRING_WITH_LEN("<secret>"));
190           str->append("'");
191         }
192         else
193           append_query_string(thd, system_charset_info, &from_auth, str);
194       }
195     }
196   }
197 }
198 
199 /**
200   Escapes special characters in the unescaped string, taking into account
201   the current character set and sql mode.
202 
203   @param thd    [in]  The thd structure.
204   @param to     [out] Escaped string output buffer.
205   @param from   [in]  String to escape.
206   @param length [in]  String to escape length.
207 
208   @return Result value.
209     @retval != (ulong)-1 Succeeded. Number of bytes written to the output
210                          buffer without the '\0' character.
211     @retval (ulong)-1    Failed.
212 */
213 
escape_string_mysql(THD * thd,char * to,const char * from,ulong length)214 inline ulong escape_string_mysql(THD *thd, char *to, const char *from,
215                                  ulong length)
216 {
217     if (!(thd->variables.sql_mode & MODE_NO_BACKSLASH_ESCAPES))
218       return (uint)escape_string_for_mysql(system_charset_info, to, 0, from,
219                                            length);
220     else
221       return (uint)escape_quotes_for_mysql(system_charset_info, to, 0, from,
222                                            length, '\'');
223 }
224 
225 #ifndef NO_EMBEDDED_ACCESS_CHECKS
226 
227 /*
228  Enumeration of various ACL's and Hashes used in handle_grant_struct()
229 */
230 enum enum_acl_lists
231 {
232   USER_ACL= 0,
233   DB_ACL,
234   COLUMN_PRIVILEGES_HASH,
235   PROC_PRIVILEGES_HASH,
236   FUNC_PRIVILEGES_HASH,
237   PROXY_USERS_ACL
238 };
239 
check_change_password(THD * thd,const char * host,const char * user,const char * new_password,size_t new_password_len)240 int check_change_password(THD *thd, const char *host, const char *user,
241                           const char *new_password, size_t new_password_len)
242 {
243   Security_context *sctx;
244   if (!initialized)
245   {
246     my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--skip-grant-tables");
247     return(1);
248   }
249 
250   sctx= thd->security_context();
251   if (!thd->slave_thread &&
252       (strcmp(sctx->user().str, user) ||
253        my_strcasecmp(system_charset_info, host,
254                      sctx->priv_host().str)))
255   {
256     if (sctx->password_expired())
257     {
258       my_error(ER_MUST_CHANGE_PASSWORD, MYF(0));
259       return(1);
260     }
261     if (check_access(thd, UPDATE_ACL, "mysql", NULL, NULL, 1, 0))
262       return(1);
263   }
264   if (!thd->slave_thread &&
265       likely((get_server_state() == SERVER_OPERATING)) &&
266       !strcmp(thd->security_context()->priv_user().str,""))
267   {
268     my_message(ER_PASSWORD_ANONYMOUS_USER, ER(ER_PASSWORD_ANONYMOUS_USER),
269                MYF(0));
270     return(1);
271   }
272 
273   return(0);
274 }
275 
276 /**
277   Auxiliary function for constructing CREATE USER sql for a given user.
278 
279   @param thd                    Thread context
280   @param user_name              user for which the sql should be constructed.
281   @param are_both_users_same    If the command is issued for self or not.
282 
283   @retval
284     0         OK.
285     1         Error.
286  */
287 
mysql_show_create_user(THD * thd,LEX_USER * user_name,bool are_both_users_same)288 bool mysql_show_create_user(THD *thd, LEX_USER *user_name,
289                             bool are_both_users_same)
290 {
291   int error= 0;
292   ACL_USER *acl_user;
293   LEX *lex= thd->lex;
294   Protocol *protocol= thd->get_protocol();
295   USER_RESOURCES tmp_user_resource;
296   enum SSL_type ssl_type;
297   char *ssl_cipher, *x509_issuer, *x509_subject;
298   char buff[256];
299   Item_string *field= NULL;
300   List<Item> field_list;
301   String sql_text(buff,sizeof(buff),system_charset_info);
302   LEX_ALTER alter_info;
303   bool hide_password_hash= false;
304 
305   DBUG_ENTER("mysql_show_create_user");
306   if (are_both_users_same)
307   {
308     TABLE_LIST t1;
309     t1.init_one_table(C_STRING_WITH_LEN("mysql"), C_STRING_WITH_LEN("user"),
310                       "user", TL_READ);
311     hide_password_hash= check_table_access(thd, SELECT_ACL, &t1, false,
312                                            UINT_MAX, true);
313   }
314 
315   mysql_mutex_lock(&acl_cache->lock);
316   if (!(acl_user= find_acl_user(user_name->host.str, user_name->user.str, TRUE)))
317   {
318     mysql_mutex_unlock(&acl_cache->lock);
319     String wrong_users;
320     append_user(thd, &wrong_users, user_name, wrong_users.length() > 0, false);
321     my_error(ER_CANNOT_USER, MYF(0), "SHOW CREATE USER",
322              wrong_users.c_ptr_safe());
323     DBUG_RETURN(1);
324   }
325   /* fill in plugin, auth_str from acl_user */
326   user_name->auth.str= acl_user->auth_string.str;
327   user_name->auth.length= acl_user->auth_string.length;
328   user_name->plugin= acl_user->plugin;
329   user_name->uses_identified_by_clause= true;
330   user_name->uses_identified_with_clause= false;
331   user_name->uses_identified_by_password_clause= false;
332   user_name->uses_authentication_string_clause= false;
333 
334   /* make a copy of user resources, ssl and password expire attributes */
335   tmp_user_resource= lex->mqh;
336   lex->mqh= acl_user->user_resource;
337 
338   /* Set specified_limits flags so user resources are shown properly. */
339   if (lex->mqh.user_conn)
340     lex->mqh.specified_limits|= USER_RESOURCES::USER_CONNECTIONS;
341   if (lex->mqh.questions)
342     lex->mqh.specified_limits|= USER_RESOURCES::QUERIES_PER_HOUR;
343   if (lex->mqh.updates)
344     lex->mqh.specified_limits|= USER_RESOURCES::UPDATES_PER_HOUR;
345   if (lex->mqh.conn_per_hour)
346     lex->mqh.specified_limits|= USER_RESOURCES::CONNECTIONS_PER_HOUR;
347 
348   ssl_type= lex->ssl_type;
349   ssl_cipher= lex->ssl_cipher;
350   x509_issuer= lex->x509_issuer;
351   x509_subject= lex->x509_subject;
352 
353   lex->ssl_type= acl_user->ssl_type;
354   lex->ssl_cipher= const_cast<char*>(acl_user->ssl_cipher);
355   lex->x509_issuer= const_cast<char*>(acl_user->x509_issuer);
356   lex->x509_subject= const_cast<char*>(acl_user->x509_subject);
357 
358   alter_info= lex->alter_password;
359 
360   lex->alter_password.update_password_expired_column= acl_user->password_expired;
361   lex->alter_password.use_default_password_lifetime= acl_user->use_default_password_lifetime;
362   lex->alter_password.expire_after_days= acl_user->password_lifetime;
363   lex->alter_password.update_account_locked_column= true;
364   lex->alter_password.account_locked= acl_user->account_locked;
365   lex->alter_password.update_password_expired_fields= true;
366 
367   /* send the metadata to client */
368   field=new Item_string("",0,&my_charset_latin1);
369   field->max_length=256;
370   strxmov(buff,"CREATE USER for ",user_name->user.str,"@",
371           user_name->host.str,NullS);
372   field->item_name.set(buff);
373   field_list.push_back(field);
374   if (thd->send_result_metadata(&field_list,
375                                 Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF))
376   {
377     error= 1;
378     goto err;
379   }
380   sql_text.length(0);
381   lex->users_list.push_back(user_name);
382   mysql_rewrite_create_alter_user(thd, &sql_text, NULL, hide_password_hash);
383   /* send the result row to client */
384   protocol->start_row();
385   protocol->store(sql_text.ptr(),sql_text.length(),sql_text.charset());
386   if (protocol->end_row())
387   {
388     error= 1;
389     goto err;
390   }
391 
392 err:
393   /* restore user resources, ssl and password expire attributes */
394   lex->mqh= tmp_user_resource;
395   lex->ssl_type= ssl_type;
396   lex->ssl_cipher= ssl_cipher;
397   lex->x509_issuer= x509_issuer;
398   lex->x509_subject= x509_subject;
399 
400   lex->alter_password= alter_info;
401 
402   mysql_mutex_unlock(&acl_cache->lock);
403   my_eof(thd);
404   DBUG_RETURN(error);
405 }
406 
407 /**
408    This function does following:
409    1. Convert plain text password to hash and update the same in
410       user definition.
411    2. Validate hash string if specified in user definition.
412    3. Identify what all fields needs to be updated in mysql.user
413       table based on user definition.
414 
415   @param thd          Thread context
416   @param Str          user on which attributes has to be applied
417   @param what_to_set  User attributes
418   @param is_privileged_user     Whether caller has CREATE_USER_ACL
419                                 or UPDATE_ACL over mysql.*
420   @param cmd          Command information
421 
422   @retval 0 ok
423   @retval 1 ERROR;
424 */
425 
set_and_validate_user_attributes(THD * thd,LEX_USER * Str,ulong & what_to_set,bool is_privileged_user,const char * cmd)426 bool set_and_validate_user_attributes(THD *thd,
427                                       LEX_USER *Str,
428                                       ulong &what_to_set,
429                                       bool is_privileged_user,
430                                       const char * cmd)
431 {
432   bool user_exists= false;
433   ACL_USER *acl_user;
434   plugin_ref plugin= NULL;
435   char outbuf[MAX_FIELD_WIDTH]= {0};
436   unsigned int buflen= MAX_FIELD_WIDTH, inbuflen;
437   const char *inbuf;
438   char *password= NULL;
439 
440   what_to_set= 0;
441   /* update plugin,auth str attributes */
442   if (Str->uses_identified_by_clause ||
443       Str->uses_identified_by_password_clause ||
444       Str->uses_identified_with_clause ||
445       Str->uses_authentication_string_clause)
446     what_to_set|= PLUGIN_ATTR;
447   else
448     what_to_set|= DEFAULT_AUTH_ATTR;
449 
450   /* update ssl attributes */
451   if (thd->lex->ssl_type != SSL_TYPE_NOT_SPECIFIED)
452     what_to_set|= SSL_ATTR;
453   /* update connection attributes */
454   if (thd->lex->mqh.specified_limits)
455     what_to_set|= RESOURCE_ATTR;
456 
457   if ((acl_user= find_acl_user(Str->host.str, Str->user.str, TRUE)))
458     user_exists= true;
459 
460   /* copy password expire attributes to individual user */
461   Str->alter_status= thd->lex->alter_password;
462 
463   /* update password expire attributes */
464   if (Str->alter_status.update_password_expired_column ||
465       !Str->alter_status.use_default_password_lifetime ||
466       Str->alter_status.expire_after_days)
467     what_to_set|= PASSWORD_EXPIRE_ATTR;
468 
469   /* update account lock attribute */
470   if (Str->alter_status.update_account_locked_column)
471     what_to_set|= ACCOUNT_LOCK_ATTR;
472 
473   if (user_exists)
474   {
475     if (thd->lex->sql_command == SQLCOM_ALTER_USER)
476     {
477       /* If no plugin is given, get existing plugin */
478       if (!Str->uses_identified_with_clause)
479         Str->plugin= acl_user->plugin;
480       /*
481         always check for password expire/interval attributes as there is no
482         way to differentiate NEVER EXPIRE and EXPIRE DEFAULT scenario
483       */
484       if (Str->alter_status.update_password_expired_fields)
485         what_to_set|= PASSWORD_EXPIRE_ATTR;
486     }
487     else
488     {
489       /* if IDENTIFIED WITH is not specified set plugin from cache */
490       if (!Str->uses_identified_with_clause)
491       {
492         Str->plugin= acl_user->plugin;
493         /* set auth str from cache when not specified for existing user */
494         if (!(Str->uses_identified_by_clause ||
495             Str->uses_identified_by_password_clause ||
496             Str->uses_authentication_string_clause))
497         {
498           Str->auth.str= acl_user->auth_string.str;
499           Str->auth.length= acl_user->auth_string.length;
500         }
501       }
502     }
503     /*
504       if there is a plugin specified with no auth string, and that
505       plugin supports password expiration then set the account as expired.
506     */
507     if (Str->uses_identified_with_clause &&
508         !(Str->uses_identified_by_clause ||
509         Str->uses_authentication_string_clause) &&
510         auth_plugin_supports_expiration(Str->plugin.str))
511     {
512       Str->alter_status.update_password_expired_column= true;
513       what_to_set|= PASSWORD_EXPIRE_ATTR;
514     }
515   }
516   else
517   {
518     /* set default plugin for new users if not specified */
519     if (!Str->uses_identified_with_clause)
520       Str->plugin= default_auth_plugin_name;
521   }
522 
523   plugin= my_plugin_lock_by_name(0, Str->plugin,
524                                  MYSQL_AUTHENTICATION_PLUGIN);
525 
526   /* check if plugin is loaded */
527   if (!plugin)
528   {
529     my_error(ER_PLUGIN_IS_NOT_LOADED, MYF(0), Str->plugin.str);
530     return(1);
531   }
532 
533   if (user_exists &&
534       (what_to_set & PLUGIN_ATTR))
535   {
536     st_mysql_auth *auth= (st_mysql_auth *) plugin_decl(plugin)->info;
537     if (auth->authentication_flags &
538          AUTH_FLAG_PRIVILEGED_USER_FOR_PASSWORD_CHANGE)
539     {
540       if (!is_privileged_user &&
541           (thd->lex->sql_command == SQLCOM_ALTER_USER ||
542            thd->lex->sql_command == SQLCOM_GRANT))
543       {
544         /*
545           An external plugin that prevents user
546           to change authentication_string information
547           unless user is privileged.
548         */
549         what_to_set= NONE_ATTR;
550         my_error(ER_ACCESS_DENIED_ERROR, MYF(0),
551                  thd->security_context()->priv_user().str,
552                  thd->security_context()->priv_host().str,
553                  thd->password ? ER_THD(thd, ER_YES) : ER_THD(thd, ER_NO));
554         plugin_unlock(0, plugin);
555         return (1);
556       }
557     }
558 
559     if (!(auth->authentication_flags & AUTH_FLAG_USES_INTERNAL_STORAGE))
560     {
561       if (thd->lex->sql_command == SQLCOM_SET_OPTION)
562       {
563         /*
564           A plugin that does not use internal storage and
565           hence does not support SET PASSWORD
566         */
567         char warning_buffer[MYSQL_ERRMSG_SIZE];
568         my_snprintf(warning_buffer, sizeof(warning_buffer),
569                     "SET PASSWORD has no significance for user '%s'@'%s' as "
570                     "authentication plugin does not support it.",
571                     Str->user.str, Str->host.str);
572         warning_buffer[MYSQL_ERRMSG_SIZE-1]= '\0';
573         push_warning(thd, Sql_condition::SL_NOTE,
574                      ER_SET_PASSWORD_AUTH_PLUGIN,
575                      warning_buffer);
576         plugin_unlock(0, plugin);
577         what_to_set= NONE_ATTR;
578         return (0);
579       }
580     }
581   }
582 
583   /*
584     If auth string is specified, change it to hash.
585     Validate empty credentials for new user ex: CREATE USER u1;
586   */
587   if (Str->uses_identified_by_clause ||
588       (Str->auth.length == 0 && !user_exists))
589   {
590     st_mysql_auth *auth= (st_mysql_auth *) plugin_decl(plugin)->info;
591     inbuf= Str->auth.str;
592     inbuflen= Str->auth.length;
593     if (auth->generate_authentication_string(outbuf,
594                                              &buflen,
595                                              inbuf,
596                                              inbuflen))
597     {
598       plugin_unlock(0, plugin);
599 
600       /*
601         generate_authentication_string may return error status
602         without setting actual error.
603       */
604       if (!thd->is_error())
605       {
606         String error_user;
607         append_user(thd, &error_user, Str, FALSE, FALSE);
608         my_error(ER_CANNOT_USER, MYF(0), cmd, error_user.c_ptr_safe());
609       }
610       return(1);
611     }
612     if (buflen)
613     {
614       password= (char *) thd->alloc(buflen);
615       memcpy(password, outbuf, buflen);
616     }
617     else
618       password= const_cast<char*>("");
619     /* erase in memory copy of plain text password */
620     memset((char*)(Str->auth.str), 0, Str->auth.length);
621     /* Use the authentication_string field as password */
622     Str->auth.str= password;
623     Str->auth.length= buflen;
624     thd->lex->contains_plaintext_password= false;
625   }
626 
627   /* Validate hash string */
628   if(Str->uses_identified_by_password_clause ||
629      Str->uses_authentication_string_clause)
630   {
631     st_mysql_auth *auth= (st_mysql_auth *) plugin_decl(plugin)->info;
632     if (auth->validate_authentication_string((char*)Str->auth.str,
633                                              Str->auth.length))
634     {
635       my_error(ER_PASSWORD_FORMAT, MYF(0));
636       plugin_unlock(0, plugin);
637       return(1);
638     }
639   }
640   plugin_unlock(0, plugin);
641   return(0);
642 }
643 
644 /**
645   Change a password hash for a user.
646 
647   @param thd Thread handle
648   @param host Hostname
649   @param user User name
650   @param new_password New password hash for host@user
651 
652   Note : it will also reset the change_password flag.
653   This is safe to do unconditionally since the simple userless form
654   SET PASSWORD = 'text' will be the only allowed form when
655   this flag is on. So we don't need to check user names here.
656 
657 
658   @see set_var_password::update(THD *thd)
659 
660   @return Error code
661    @retval 0 ok
662    @retval 1 ERROR; In this case the error is sent to the client.
663 */
664 
change_password(THD * thd,const char * host,const char * user,char * new_password)665 bool change_password(THD *thd, const char *host, const char *user,
666                      char *new_password)
667 {
668   TABLE_LIST tables;
669   TABLE *table;
670   Acl_table_intact table_intact;
671   LEX_USER *combo= NULL;
672   /* Buffer should be extended when password length is extended. */
673   char buff[2048];
674   /* buffer to store the hash string */
675   char hash_str[MAX_FIELD_WIDTH]= {0};
676   char *hash_str_escaped= NULL;
677   ulong query_length= 0;
678   ulong what_to_set= 0;
679   bool save_binlog_row_based;
680   size_t new_password_len= strlen(new_password);
681   size_t escaped_hash_str_len= 0;
682   bool result= true, rollback_whole_statement= false;
683   sql_mode_t old_sql_mode= thd->variables.sql_mode;
684   int ret;
685 
686   DBUG_ENTER("change_password");
687   DBUG_PRINT("enter",("host: '%s'  user: '%s'  new_password: '%s'",
688                       host,user,new_password));
689   DBUG_ASSERT(host != 0);                        // Ensured by parent
690 
691   if (check_change_password(thd, host, user, new_password, new_password_len))
692     DBUG_RETURN(1);
693 
694   tables.init_one_table("mysql", 5, "user", 4, "user", TL_WRITE);
695 
696 #ifdef HAVE_REPLICATION
697   /*
698     GRANT and REVOKE are applied the slave in/exclusion rules as they are
699     some kind of updates to the mysql.% tables.
700   */
701   if (thd->slave_thread && rpl_filter->is_on())
702   {
703     /*
704       The tables must be marked "updating" so that tables_ok() takes them into
705       account in tests.  It's ok to leave 'updating' set after tables_ok.
706     */
707     tables.updating= 1;
708     /* Thanks to memset, tables.next==0 */
709     if (!(thd->sp_runtime_ctx || rpl_filter->tables_ok(0, &tables)))
710       DBUG_RETURN(0);
711   }
712 #endif
713   if (!(table= open_ltable(thd, &tables, TL_WRITE, MYSQL_LOCK_IGNORE_TIMEOUT)))
714     DBUG_RETURN(1);
715 
716   if (table_intact.check(table, &mysql_user_table_def))
717     DBUG_RETURN(1);
718 
719   /*
720     This statement will be replicated as a statement, even when using
721     row-based replication.  The flag will be reset at the end of the
722     statement.
723   */
724   if ((save_binlog_row_based= thd->is_current_stmt_binlog_format_row()))
725     thd->clear_current_stmt_binlog_format_row();
726 
727   mysql_mutex_lock(&acl_cache->lock);
728   ACL_USER *acl_user;
729   if (!(acl_user= find_acl_user(host, user, TRUE)))
730   {
731     mysql_mutex_unlock(&acl_cache->lock);
732     my_message(ER_PASSWORD_NO_MATCH, ER(ER_PASSWORD_NO_MATCH), MYF(0));
733     goto end;
734   }
735 
736   DBUG_ASSERT(acl_user->plugin.length != 0);
737 
738   if (!(combo=(LEX_USER*) thd->alloc(sizeof(st_lex_user))))
739     DBUG_RETURN(1);
740 
741   combo->user.str= user;
742   combo->host.str= host;
743   combo->user.length= strlen(user);
744   combo->host.length= strlen(host);
745 
746   thd->make_lex_string(&combo->user,
747                        combo->user.str, strlen(combo->user.str), 0);
748   thd->make_lex_string(&combo->host,
749                        combo->host.str, strlen(combo->host.str), 0);
750 
751   combo->plugin= EMPTY_CSTR;
752   combo->auth.str= new_password;
753   combo->auth.length= new_password_len;
754   combo->uses_identified_by_clause= true;
755   combo->uses_identified_with_clause= false;
756   combo->uses_identified_by_password_clause= false;
757   combo->uses_authentication_string_clause= false;
758   /* set default values */
759   thd->lex->ssl_type= SSL_TYPE_NOT_SPECIFIED;
760   memset(&(thd->lex->mqh), 0, sizeof(thd->lex->mqh));
761   thd->lex->alter_password.update_password_expired_column= false;
762   thd->lex->alter_password.use_default_password_lifetime= true;
763   thd->lex->alter_password.expire_after_days= 0;
764   thd->lex->alter_password.update_account_locked_column= false;
765   thd->lex->alter_password.account_locked= false;
766   thd->lex->alter_password.update_password_expired_fields= false;
767 
768 
769   /*
770     In case its a slave thread or a binlog applier thread, the password
771     is already hashed. Do not generate another hash!
772    */
773   if (thd->slave_thread || thd->is_binlog_applier())
774   {
775     /* Password is in hash form */
776     combo->uses_authentication_string_clause= true;
777     /* Password is not plain text */
778     combo->uses_identified_by_clause= false;
779   }
780 
781   if (set_and_validate_user_attributes(thd, combo, what_to_set,
782                                        true, "SET PASSWORD"))
783   {
784     result= 1;
785     mysql_mutex_unlock(&acl_cache->lock);
786     goto end;
787   }
788 
789   thd->variables.sql_mode&= ~MODE_PAD_CHAR_TO_FULL_LENGTH;
790   ret= replace_user_table(thd, table, combo, 0, false, false, what_to_set);
791   thd->variables.sql_mode= old_sql_mode;
792 
793   if (ret)
794   {
795     mysql_mutex_unlock(&acl_cache->lock);
796     result= 1;
797     if (ret < 0)
798       rollback_whole_statement= true;
799     goto end;
800   }
801   if (!update_sctx_cache(thd->security_context(), acl_user, false) &&
802        thd->security_context()->password_expired())
803   {
804     /* the current user is not the same as the user we operate on */
805     my_error(ER_MUST_CHANGE_PASSWORD, MYF(0));
806     result= 1;
807     mysql_mutex_unlock(&acl_cache->lock);
808     goto end;
809   }
810 
811   mysql_mutex_unlock(&acl_cache->lock);
812   result= 0;
813   escaped_hash_str_len= (opt_log_builtin_as_identified_by_password?
814                          combo->auth.length:
815                          acl_user->auth_string.length)*2+1;
816   /*
817      Allocate a buffer for the escaped password. It should at least have place
818      for length*2+1 chars.
819   */
820   hash_str_escaped= (char *)alloc_root(thd->mem_root, escaped_hash_str_len);
821   if (!hash_str_escaped)
822   {
823     my_error(ER_OUTOFMEMORY, MYF(ME_FATALERROR), 0);
824     result= 1;
825     goto end;
826   }
827   /*
828     Based on @@log-backward-compatible-user-definitions variable
829     rewrite SET PASSWORD
830   */
831   if (opt_log_builtin_as_identified_by_password)
832   {
833     memcpy(hash_str, combo->auth.str, combo->auth.length);
834 
835     DBUG_EXECUTE_IF("force_hash_string_with_quote",
836 		     strcpy(hash_str, HASH_STRING_WITH_QUOTE);
837                    );
838 
839     escape_string_mysql(thd, hash_str_escaped, hash_str, strlen(hash_str));
840 
841     query_length= sprintf(buff, "SET PASSWORD FOR '%-.120s'@'%-.120s'='%s'",
842                           acl_user->user ? acl_user->user : "",
843                           acl_user->host.get_host() ? acl_user->host.get_host() : "",
844                           hash_str_escaped);
845   }
846   else
847   {
848     DBUG_EXECUTE_IF("force_hash_string_with_quote",
849                      strcpy(acl_user->auth_string.str, HASH_STRING_WITH_QUOTE);
850                    );
851 
852     escape_string_mysql(thd, hash_str_escaped, acl_user->auth_string.str,
853                         strlen(acl_user->auth_string.str));
854 
855     query_length= sprintf(buff, "ALTER USER '%-.120s'@'%-.120s' IDENTIFIED WITH '%-.120s' AS '%s'",
856                           acl_user->user ? acl_user->user : "",
857                           acl_user->host.get_host() ? acl_user->host.get_host() : "",
858                           acl_user->plugin.str,
859                           hash_str_escaped);
860   }
861   result= write_bin_log(thd, true, buff, query_length,
862                         table->file->has_transactions());
863 end:
864   result|= acl_end_trans_and_close_tables(thd,
865                                           thd->transaction_rollback_request ||
866                                           rollback_whole_statement);
867 
868   if (!result)
869     acl_notify_htons(thd, buff, query_length);
870 
871   /* Restore the state of binlog format */
872   DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row());
873   if (save_binlog_row_based)
874     thd->set_current_stmt_binlog_format_row();
875 
876   DBUG_RETURN(result);
877 }
878 
879 /**
880   Handle an in-memory privilege structure.
881 
882   @param struct_no  The number of the structure to handle (0..5).
883   @param drop       If user_from is to be dropped.
884   @param user_from  The the user to be searched/dropped/renamed.
885   @param user_to    The new name for the user if to be renamed, NULL otherwise.
886 
887   @note
888     Scan through all elements in an in-memory grant structure and apply
889     the requested operation.
890     Delete from grant structure if drop is true.
891     Update in grant structure if drop is false and user_to is not NULL.
892     Search in grant structure if drop is false and user_to is NULL.
893     Structures are enumerated as follows:
894     0 ACL_USER
895     1 ACL_DB
896     2 COLUMN_PRIVILIGES_HASH
897     3 PROC_PRIVILEGES_HASH
898     4 FUNC_PRIVILEGES_HASH
899     5 ACL_PROXY_USERS
900 
901   @retval > 0  At least one element matched.
902   @retval 0    OK, but no element matched.
903   @retval -1   Wrong arguments to function or Out of Memory.
904 */
905 
handle_grant_struct(enum enum_acl_lists struct_no,bool drop,LEX_USER * user_from,LEX_USER * user_to)906 static int handle_grant_struct(enum enum_acl_lists struct_no, bool drop,
907                                LEX_USER *user_from, LEX_USER *user_to)
908 {
909   int result= 0;
910   size_t idx;
911   size_t elements;
912   const char *user= NULL;
913   const char *host= NULL;
914   ACL_USER *acl_user= NULL;
915   ACL_DB *acl_db= NULL;
916   ACL_PROXY_USER *acl_proxy_user= NULL;
917   GRANT_NAME *grant_name= NULL;
918   /*
919     Dynamic array acl_grant_name used to store pointers to all
920     GRANT_NAME objects
921   */
922   Prealloced_array<GRANT_NAME *, 16> acl_grant_name(PSI_INSTRUMENT_ME);
923   HASH *grant_name_hash= NULL;
924   DBUG_ENTER("handle_grant_struct");
925   DBUG_PRINT("info",("scan struct: %u  search: '%s'@'%s'",
926                      struct_no, user_from->user.str, user_from->host.str));
927 
928   mysql_mutex_assert_owner(&acl_cache->lock);
929 
930   /* Get the number of elements in the in-memory structure. */
931   switch (struct_no) {
932   case USER_ACL:
933     elements= acl_users->size();
934     break;
935   case DB_ACL:
936     elements= acl_dbs->size();
937     break;
938   case COLUMN_PRIVILEGES_HASH:
939     elements= column_priv_hash.records;
940     grant_name_hash= &column_priv_hash;
941     break;
942   case PROC_PRIVILEGES_HASH:
943     elements= proc_priv_hash.records;
944     grant_name_hash= &proc_priv_hash;
945     break;
946   case FUNC_PRIVILEGES_HASH:
947     elements= func_priv_hash.records;
948     grant_name_hash= &func_priv_hash;
949     break;
950   case PROXY_USERS_ACL:
951     elements= acl_proxy_users->size();
952     break;
953   default:
954     DBUG_RETURN(-1);
955   }
956 
957 #ifdef EXTRA_DEBUG
958     DBUG_PRINT("loop",("scan struct: %u  search    user: '%s'  host: '%s'",
959                        struct_no, user_from->user.str, user_from->host.str));
960 #endif
961   /* Loop over all elements. */
962   for (idx= 0; idx < elements; idx++)
963   {
964     /*
965       Get a pointer to the element.
966     */
967     switch (struct_no) {
968     case USER_ACL:
969       acl_user= &acl_users->at(idx);
970       user= acl_user->user;
971       host= acl_user->host.get_host();
972     break;
973 
974     case DB_ACL:
975       acl_db= &acl_dbs->at(idx);
976       user= acl_db->user;
977       host= acl_db->host.get_host();
978       break;
979 
980     case COLUMN_PRIVILEGES_HASH:
981     case PROC_PRIVILEGES_HASH:
982     case FUNC_PRIVILEGES_HASH:
983       grant_name= (GRANT_NAME*) my_hash_element(grant_name_hash, idx);
984       user= grant_name->user;
985       host= grant_name->host.get_host();
986       break;
987 
988     case PROXY_USERS_ACL:
989       acl_proxy_user= &acl_proxy_users->at(idx);
990       user= acl_proxy_user->get_user();
991       host= acl_proxy_user->host.get_host();
992       break;
993 
994     default:
995       MY_ASSERT_UNREACHABLE();
996     }
997     if (! user)
998       user= "";
999     if (! host)
1000       host= "";
1001 
1002 #ifdef EXTRA_DEBUG
1003     DBUG_PRINT("loop",("scan struct: %u  index: %zu  user: '%s'  host: '%s'",
1004                        struct_no, idx, user, host));
1005 #endif
1006     if (strcmp(user_from->user.str, user) ||
1007         my_strcasecmp(system_charset_info, user_from->host.str, host))
1008       continue;
1009 
1010     result= 1; /* At least one element found. */
1011     if ( drop )
1012     {
1013       switch ( struct_no ) {
1014       case USER_ACL:
1015         acl_users->erase(idx);
1016         elements--;
1017         /*
1018         - If we are iterating through an array then we just have moved all
1019           elements after the current element one position closer to its head.
1020           This means that we have to take another look at the element at
1021           current position as it is a new element from the array's tail.
1022         - This is valid for case USER_ACL, DB_ACL and PROXY_USERS_ACL.
1023         */
1024         idx--;
1025         break;
1026 
1027       case DB_ACL:
1028         acl_dbs->erase(idx);
1029         elements--;
1030         idx--;
1031         break;
1032 
1033       case COLUMN_PRIVILEGES_HASH:
1034       case PROC_PRIVILEGES_HASH:
1035       case FUNC_PRIVILEGES_HASH:
1036         /*
1037           Deleting while traversing a hash table is not valid procedure and
1038           hence we save pointers to GRANT_NAME objects for later processing.
1039         */
1040         if (acl_grant_name.push_back(grant_name))
1041           DBUG_RETURN(-1);
1042         break;
1043 
1044       case PROXY_USERS_ACL:
1045         acl_proxy_users->erase(idx);
1046         elements--;
1047         idx--;
1048         break;
1049       }
1050     }
1051     else if ( user_to )
1052     {
1053       switch ( struct_no ) {
1054       case USER_ACL:
1055         acl_user->user= strdup_root(&global_acl_memory, user_to->user.str);
1056         acl_user->host.update_hostname(strdup_root(&global_acl_memory, user_to->host.str));
1057         break;
1058 
1059       case DB_ACL:
1060         acl_db->user= strdup_root(&global_acl_memory, user_to->user.str);
1061         acl_db->host.update_hostname(strdup_root(&global_acl_memory, user_to->host.str));
1062         break;
1063 
1064       case COLUMN_PRIVILEGES_HASH:
1065       case PROC_PRIVILEGES_HASH:
1066       case FUNC_PRIVILEGES_HASH:
1067         /*
1068           Updating while traversing a hash table is not valid procedure and
1069           hence we save pointers to GRANT_NAME objects for later processing.
1070         */
1071         if (acl_grant_name.push_back(grant_name))
1072           DBUG_RETURN(-1);
1073         break;
1074 
1075       case PROXY_USERS_ACL:
1076         acl_proxy_user->set_user(&global_acl_memory, user_to->user.str);
1077         acl_proxy_user->host.update_hostname((user_to->host.str && *user_to->host.str) ?
1078                                              strdup_root(&global_acl_memory, user_to->host.str) : NULL);
1079         break;
1080       }
1081     }
1082     else
1083     {
1084       /* If search is requested, we do not need to search further. */
1085       break;
1086     }
1087   }
1088 
1089   if (drop || user_to)
1090   {
1091     /*
1092       Traversing the elements stored in acl_grant_name dynamic array
1093       to either delete or update them.
1094     */
1095     for (GRANT_NAME **iter= acl_grant_name.begin();
1096          iter != acl_grant_name.end(); ++iter)
1097     {
1098       grant_name= *iter;
1099 
1100       if (drop)
1101       {
1102         my_hash_delete(grant_name_hash, (uchar *) grant_name);
1103       }
1104       else
1105       {
1106         /*
1107           Save old hash key and its length to be able properly update
1108           element position in hash.
1109         */
1110         char *old_key= grant_name->hash_key;
1111         size_t old_key_length= grant_name->key_length;
1112 
1113         /*
1114           Update the grant structure with the new user name and host name.
1115         */
1116         grant_name->set_user_details(user_to->host.str, grant_name->db,
1117                                      user_to->user.str, grant_name->tname,
1118                                      TRUE);
1119 
1120         /*
1121           Since username is part of the hash key, when the user name
1122           is renamed, the hash key is changed. Update the hash to
1123           ensure that the position matches the new hash key value
1124         */
1125         my_hash_update(grant_name_hash, (uchar*) grant_name, (uchar*) old_key,
1126                        old_key_length);
1127       }
1128     }
1129   }
1130 
1131 #ifdef EXTRA_DEBUG
1132   DBUG_PRINT("loop",("scan struct: %u  result %d", struct_no, result));
1133 #endif
1134 
1135   DBUG_RETURN(result);
1136 }
1137 
1138 
1139 /*
1140   Handle all privilege tables and in-memory privilege structures.
1141 
1142   SYNOPSIS
1143     handle_grant_data()
1144     tables                      The array with the four open tables.
1145     drop                        If user_from is to be dropped.
1146     user_from                   The the user to be searched/dropped/renamed.
1147     user_to                     The new name for the user if to be renamed,
1148                                 NULL otherwise.
1149 
1150   DESCRIPTION
1151     Go through all grant tables and in-memory grant structures and apply
1152     the requested operation.
1153     Delete from grant data if drop is true.
1154     Update in grant data if drop is false and user_to is not NULL.
1155     Search in grant data if drop is false and user_to is NULL.
1156 
1157   RETURN
1158     > 0         At least one element matched.
1159     0           OK, but no element matched.
1160     < 0         Error.
1161 */
1162 
handle_grant_data(TABLE_LIST * tables,bool drop,LEX_USER * user_from,LEX_USER * user_to)1163 static int handle_grant_data(TABLE_LIST *tables, bool drop,
1164                              LEX_USER *user_from, LEX_USER *user_to)
1165 {
1166   int result= 0;
1167   int found;
1168   int ret;
1169   Acl_table_intact table_intact;
1170   DBUG_ENTER("handle_grant_data");
1171 
1172   /* Handle user table. */
1173   if (table_intact.check(tables[0].table, &mysql_user_table_def))
1174   {
1175     result= -1;
1176     goto end;
1177   }
1178 
1179   if ((found= handle_grant_table(tables, 0, drop, user_from, user_to)) < 0)
1180   {
1181     /* Handle of table failed, don't touch the in-memory array. */
1182     DBUG_RETURN(-1);
1183   }
1184   else
1185   {
1186     /* Handle user array. */
1187     if (((ret= handle_grant_struct(USER_ACL, drop, user_from, user_to) > 0) &&
1188          ! result) || found)
1189     {
1190       result= 1; /* At least one record/element found. */
1191       /* If search is requested, we do not need to search further. */
1192       if (! drop && ! user_to)
1193         goto end;
1194     }
1195     else if (ret < 0)
1196     {
1197       result= -1;
1198       goto end;
1199     }
1200   }
1201 
1202   /* Handle db table. */
1203   if (table_intact.check(tables[1].table, &mysql_db_table_def))
1204   {
1205     result= -1;
1206     goto end;
1207   }
1208 
1209   if ((found= handle_grant_table(tables, 1, drop, user_from, user_to)) < 0)
1210   {
1211     /* Handle of table failed, don't touch the in-memory array. */
1212     DBUG_RETURN(-1);
1213   }
1214   else
1215   {
1216     /* Handle db array. */
1217     if ((((ret= handle_grant_struct(DB_ACL, drop, user_from, user_to) > 0) &&
1218           ! result) || found) && ! result)
1219     {
1220       result= 1; /* At least one record/element found. */
1221       /* If search is requested, we do not need to search further. */
1222       if (! drop && ! user_to)
1223         goto end;
1224     }
1225     else if (ret < 0)
1226     {
1227       result= -1;
1228       goto end;
1229     }
1230   }
1231 
1232   /* Handle stored routines table. */
1233   if (table_intact.check(tables[4].table, &mysql_procs_priv_table_def))
1234   {
1235     result= -1;
1236     goto end;
1237   }
1238 
1239   if ((found= handle_grant_table(tables, 4, drop, user_from, user_to)) < 0)
1240   {
1241     /* Handle of table failed, don't touch in-memory array. */
1242     DBUG_RETURN(-1);
1243   }
1244   else
1245   {
1246     /* Handle procs array. */
1247     if ((((ret= handle_grant_struct(PROC_PRIVILEGES_HASH, drop, user_from,
1248                                     user_to) > 0) && ! result) || found) &&
1249         ! result)
1250     {
1251       result= 1; /* At least one record/element found. */
1252       /* If search is requested, we do not need to search further. */
1253       if (! drop && ! user_to)
1254         goto end;
1255     }
1256     else if (ret < 0)
1257     {
1258       result= -1;
1259       goto end;
1260     }
1261     /* Handle funcs array. */
1262     if ((((ret= handle_grant_struct(FUNC_PRIVILEGES_HASH, drop, user_from,
1263                                     user_to) > 0) && ! result) || found) &&
1264         ! result)
1265     {
1266       result= 1; /* At least one record/element found. */
1267       /* If search is requested, we do not need to search further. */
1268       if (! drop && ! user_to)
1269         goto end;
1270     }
1271     else if (ret < 0)
1272     {
1273       result= -1;
1274       goto end;
1275     }
1276   }
1277 
1278   /* Handle tables table. */
1279   if (table_intact.check(tables[2].table, &mysql_tables_priv_table_def))
1280   {
1281     result= -1;
1282     goto end;
1283   }
1284 
1285   if ((found= handle_grant_table(tables, 2, drop, user_from, user_to)) < 0)
1286   {
1287     /* Handle of table failed, don't touch columns and in-memory array. */
1288     DBUG_RETURN(-1);
1289   }
1290   else
1291   {
1292     if (found && ! result)
1293     {
1294       result= 1; /* At least one record found. */
1295       /* If search is requested, we do not need to search further. */
1296       if (! drop && ! user_to)
1297         goto end;
1298     }
1299 
1300     /* Handle columns table. */
1301     if (table_intact.check(tables[3].table, &mysql_columns_priv_table_def))
1302     {
1303       result= -1;
1304       goto end;
1305     }
1306 
1307     if ((found= handle_grant_table(tables, 3, drop, user_from, user_to)) < 0)
1308     {
1309       /* Handle of table failed, don't touch the in-memory array. */
1310       DBUG_RETURN(-1);
1311     }
1312     else
1313     {
1314       /* Handle columns hash. */
1315       if ((((ret= handle_grant_struct(COLUMN_PRIVILEGES_HASH, drop, user_from,
1316                                       user_to) > 0) && ! result) || found) &&
1317           ! result)
1318         result= 1; /* At least one record/element found. */
1319       else if (ret < 0)
1320         result= -1;
1321     }
1322   }
1323 
1324   /* Handle proxies_priv table. */
1325   if (tables[5].table)
1326   {
1327     if (table_intact.check(tables[5].table, &mysql_proxies_priv_table_def))
1328     {
1329       result= -1;
1330       goto end;
1331     }
1332 
1333     if ((found= handle_grant_table(tables, 5, drop, user_from, user_to)) < 0)
1334     {
1335       /* Handle of table failed, don't touch the in-memory array. */
1336       DBUG_RETURN(-1);
1337     }
1338     else
1339     {
1340       /* Handle proxies_priv array. */
1341       if (((ret= handle_grant_struct(PROXY_USERS_ACL, drop, user_from, user_to) > 0)
1342            && !result) || found)
1343         result= 1; /* At least one record/element found. */
1344       else if (ret < 0)
1345         result= -1;
1346     }
1347   }
1348  end:
1349   DBUG_RETURN(result);
1350 }
1351 
1352 
1353 /*
1354   Create a list of users.
1355 
1356   SYNOPSIS
1357     mysql_create_user()
1358     thd                         The current thread.
1359     list                        The users to create.
1360 
1361   RETURN
1362     FALSE       OK.
1363     TRUE        Error.
1364 */
1365 
mysql_create_user(THD * thd,List<LEX_USER> & list,bool if_not_exists)1366 bool mysql_create_user(THD *thd, List <LEX_USER> &list, bool if_not_exists)
1367 {
1368   int result;
1369   String wrong_users;
1370   LEX_USER *user_name, *tmp_user_name;
1371   List_iterator <LEX_USER> user_list(list);
1372   TABLE_LIST tables[GRANT_TABLES];
1373   bool some_users_created= FALSE;
1374   bool save_binlog_row_based;
1375   bool transactional_tables;
1376   ulong what_to_update= 0;
1377   bool is_anonymous_user= false;
1378   bool rollback_whole_statement= false;
1379   std::set<LEX_USER *> extra_users;
1380   DBUG_ENTER("mysql_create_user");
1381 
1382   /*
1383     This statement will be replicated as a statement, even when using
1384     row-based replication.  The flag will be reset at the end of the
1385     statement.
1386   */
1387   if ((save_binlog_row_based= thd->is_current_stmt_binlog_format_row()))
1388     thd->clear_current_stmt_binlog_format_row();
1389 
1390   /* CREATE USER may be skipped on replication client. */
1391   if ((result= open_grant_tables(thd, tables, &transactional_tables)))
1392   {
1393     /* Restore the state of binlog format */
1394     DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row());
1395     if (save_binlog_row_based)
1396       thd->set_current_stmt_binlog_format_row();
1397     DBUG_RETURN(result != 1);
1398   }
1399 
1400   Partitioned_rwlock_write_guard lock(&LOCK_grant);
1401   mysql_mutex_lock(&acl_cache->lock);
1402 
1403   while ((tmp_user_name= user_list++))
1404   {
1405     /*
1406       If tmp_user_name.user.str is == NULL then
1407       user_name := tmp_user_name.
1408       Else user_name.user := sctx->user
1409       TODO and all else is turned to NULL !! Why?
1410     */
1411     if (!(user_name= get_current_user(thd, tmp_user_name)))
1412     {
1413       result= TRUE;
1414       continue;
1415     }
1416     if (set_and_validate_user_attributes(thd, user_name, what_to_update,
1417                                          true, "CREATE USER"))
1418     {
1419       result= TRUE;
1420       continue;
1421     }
1422     if (!strcmp(user_name->user.str,"") &&
1423         (what_to_update & PASSWORD_EXPIRE_ATTR))
1424     {
1425       is_anonymous_user= true;
1426       result= true;
1427       continue;
1428     }
1429 
1430     /*
1431       Search all in-memory structures and grant tables
1432       for a mention of the new user name.
1433     */
1434     int ret1= 0, ret2= 0;
1435     if ((ret1= handle_grant_data(tables, 0, user_name, NULL)) ||
1436         (ret2= replace_user_table(thd, tables[0].table, user_name, 0,
1437                                   false, true, what_to_update)))
1438     {
1439       if (ret1 < 0 || ret2 < 0)
1440       {
1441         rollback_whole_statement= true;
1442         result= true;
1443         break;
1444       }
1445       else if (if_not_exists)
1446       {
1447         String warn_user;
1448         append_user(thd, &warn_user, user_name, FALSE, FALSE);
1449         push_warning_printf(thd, Sql_condition::SL_NOTE,
1450                             ER_USER_ALREADY_EXISTS,
1451                             ER_THD(thd, ER_USER_ALREADY_EXISTS),
1452                             warn_user.c_ptr_safe());
1453         try
1454         {
1455           extra_users.insert(user_name);
1456         }
1457         catch (...) {}
1458         continue;
1459       }
1460       else
1461       {
1462         append_user(thd, &wrong_users, user_name, wrong_users.length() > 0,
1463                     false);
1464         result= true;
1465         continue;
1466       }
1467     }
1468 
1469     some_users_created= TRUE;
1470   } // END while tmp_user_name= user_lists++
1471 
1472   mysql_mutex_unlock(&acl_cache->lock);
1473 
1474   if (result && !rollback_whole_statement)
1475   {
1476     if (is_anonymous_user)
1477       my_error(ER_CANNOT_USER, MYF(0), "CREATE USER", "anonymous user");
1478     else
1479       my_error(ER_CANNOT_USER, MYF(0), "CREATE USER", wrong_users.c_ptr_safe());
1480   }
1481 
1482   if (some_users_created || (if_not_exists && !thd->is_error()))
1483   {
1484     /*
1485       Rewrite CREATE USER statements to use password hashes instead
1486       of <secret> style obfuscation so it can be used in binlog. We
1487       are rewriting to a private string rather than the public one on
1488       the THD (thd->m_rewritten_query). This will save us from having
1489       to acquire the lock to update the string on the THD. As
1490       slow-logging (if enabled) will happen later and use the string
1491       on the THD, the slow log will not contain the local rewrite
1492       we're doing here, but the original one.
1493     */
1494     String rlb;
1495 
1496     mysql_rewrite_create_alter_user(thd, &rlb, &extra_users);
1497 
1498     int ret= commit_owned_gtid_by_partial_command(thd);
1499 
1500     if (ret == 1)
1501     {
1502       if (!rlb.length())
1503         result|= write_bin_log(thd, false, thd->query().str, thd->query().length,
1504                                transactional_tables);
1505       else {
1506         result|= write_bin_log(thd, false, rlb.c_ptr_safe(), rlb.length(),
1507                                transactional_tables);
1508         thd->swap_rewritten_query(rlb); // must come last!
1509       }
1510     }
1511     else if (ret == -1)
1512       result|= -1;
1513   }
1514 
1515   lock.unlock();
1516 
1517   result|= acl_end_trans_and_close_tables(thd,
1518                                           thd->transaction_rollback_request ||
1519                                           rollback_whole_statement);
1520 
1521   if (some_users_created && !result)
1522     acl_notify_htons(thd, thd->query().str, thd->query().length);
1523 
1524   /* Restore the state of binlog format */
1525   DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row());
1526   if (save_binlog_row_based)
1527     thd->set_current_stmt_binlog_format_row();
1528   DBUG_RETURN(result);
1529 }
1530 
1531 
1532 /*
1533   Drop a list of users and all their privileges.
1534 
1535   SYNOPSIS
1536     mysql_drop_user()
1537     thd                         The current thread.
1538     list                        The users to drop.
1539 
1540   RETURN
1541     FALSE       OK.
1542     TRUE        Error.
1543 */
1544 
mysql_drop_user(THD * thd,List<LEX_USER> & list,bool if_exists)1545 bool mysql_drop_user(THD *thd, List <LEX_USER> &list, bool if_exists)
1546 {
1547   int result;
1548   String wrong_users;
1549   LEX_USER *user_name, *tmp_user_name;
1550   List_iterator <LEX_USER> user_list(list);
1551   TABLE_LIST tables[GRANT_TABLES];
1552   bool some_users_deleted= FALSE;
1553   sql_mode_t old_sql_mode= thd->variables.sql_mode;
1554   bool save_binlog_row_based;
1555   bool transactional_tables;
1556   bool rollback_whole_statement= false;
1557   DBUG_ENTER("mysql_drop_user");
1558 
1559   /*
1560     This statement will be replicated as a statement, even when using
1561     row-based replication.  The flag will be reset at the end of the
1562     statement.
1563   */
1564   if ((save_binlog_row_based= thd->is_current_stmt_binlog_format_row()))
1565     thd->clear_current_stmt_binlog_format_row();
1566 
1567   /* DROP USER may be skipped on replication client. */
1568   if ((result= open_grant_tables(thd, tables, &transactional_tables)))
1569   {
1570     /* Restore the state of binlog format */
1571     DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row());
1572     if (save_binlog_row_based)
1573       thd->set_current_stmt_binlog_format_row();
1574     DBUG_RETURN(result != 1);
1575   }
1576 
1577   thd->variables.sql_mode&= ~MODE_PAD_CHAR_TO_FULL_LENGTH;
1578 
1579   Partitioned_rwlock_write_guard lock(&LOCK_grant);
1580   mysql_mutex_lock(&acl_cache->lock);
1581 
1582   while ((tmp_user_name= user_list++))
1583   {
1584     if (!(user_name= get_current_user(thd, tmp_user_name)))
1585     {
1586       result= TRUE;
1587       continue;
1588     }
1589     int ret= handle_grant_data(tables, 1, user_name, NULL);
1590     if (ret <= 0)
1591     {
1592       if (ret < 0)
1593       {
1594         rollback_whole_statement= true;
1595         result= true;
1596         break;
1597       }
1598       if (if_exists)
1599       {
1600         String warn_user;
1601         append_user(thd, &warn_user, user_name, FALSE, FALSE);
1602         push_warning_printf(thd, Sql_condition::SL_NOTE,
1603                             ER_USER_DOES_NOT_EXIST,
1604                             ER_THD(thd, ER_USER_DOES_NOT_EXIST),
1605                             warn_user.c_ptr_safe());
1606       }
1607       else
1608       {
1609         result= true;
1610         append_user(thd, &wrong_users, user_name, wrong_users.length() > 0, FALSE);
1611       }
1612     }
1613     else
1614       some_users_deleted= true;
1615   }
1616 
1617   /* Rebuild 'acl_check_hosts' since 'acl_users' has been modified */
1618   rebuild_check_host();
1619 
1620   mysql_mutex_unlock(&acl_cache->lock);
1621 
1622   if (result && !rollback_whole_statement)
1623     my_error(ER_CANNOT_USER, MYF(0), "DROP USER", wrong_users.c_ptr_safe());
1624 
1625   if (some_users_deleted || if_exists)
1626   {
1627     int ret= commit_owned_gtid_by_partial_command(thd);
1628     if (ret == 1)
1629       result |= write_bin_log(thd, FALSE, thd->query().str,
1630                               thd->query().length,
1631                               transactional_tables);
1632     else if (ret == -1)
1633       result |= -1;
1634   }
1635   lock.unlock();
1636 
1637   result|=
1638     acl_end_trans_and_close_tables(thd,
1639                                    thd->transaction_rollback_request ||
1640                                    rollback_whole_statement);
1641 
1642   if (some_users_deleted && !result)
1643     acl_notify_htons(thd, thd->query().str, thd->query().length);
1644 
1645   thd->variables.sql_mode= old_sql_mode;
1646   /* Restore the state of binlog format */
1647   DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row());
1648   if (save_binlog_row_based)
1649     thd->set_current_stmt_binlog_format_row();
1650   DBUG_RETURN(result);
1651 }
1652 
1653 
1654 /*
1655   Rename a user.
1656 
1657   SYNOPSIS
1658     mysql_rename_user()
1659     thd                         The current thread.
1660     list                        The user name pairs: (from, to).
1661 
1662   RETURN
1663     FALSE       OK.
1664     TRUE        Error.
1665 */
1666 
mysql_rename_user(THD * thd,List<LEX_USER> & list)1667 bool mysql_rename_user(THD *thd, List <LEX_USER> &list)
1668 {
1669   int result;
1670   String wrong_users;
1671   LEX_USER *user_from, *tmp_user_from;
1672   LEX_USER *user_to, *tmp_user_to;
1673   List_iterator <LEX_USER> user_list(list);
1674   TABLE_LIST tables[GRANT_TABLES];
1675   bool some_users_renamed= FALSE;
1676   bool save_binlog_row_based;
1677   bool transactional_tables;
1678   bool rollback_whole_statement= false;
1679   DBUG_ENTER("mysql_rename_user");
1680 
1681   /*
1682     This statement will be replicated as a statement, even when using
1683     row-based replication.  The flag will be reset at the end of the
1684     statement.
1685   */
1686   if ((save_binlog_row_based= thd->is_current_stmt_binlog_format_row()))
1687     thd->clear_current_stmt_binlog_format_row();
1688 
1689   /* RENAME USER may be skipped on replication client. */
1690   if ((result= open_grant_tables(thd, tables, &transactional_tables)))
1691   {
1692     /* Restore the state of binlog format */
1693     DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row());
1694     if (save_binlog_row_based)
1695       thd->set_current_stmt_binlog_format_row();
1696     DBUG_RETURN(result != 1);
1697   }
1698 
1699   Partitioned_rwlock_write_guard lock(&LOCK_grant);
1700   mysql_mutex_lock(&acl_cache->lock);
1701 
1702   while ((tmp_user_from= user_list++))
1703   {
1704     if (!(user_from= get_current_user(thd, tmp_user_from)))
1705     {
1706       result= TRUE;
1707       continue;
1708     }
1709     tmp_user_to= user_list++;
1710     if (!(user_to= get_current_user(thd, tmp_user_to)))
1711     {
1712       result= TRUE;
1713       continue;
1714     }
1715     DBUG_ASSERT(user_to != 0); /* Syntax enforces pairs of users. */
1716 
1717     /*
1718       Search all in-memory structures and grant tables
1719       for a mention of the new user name.
1720     */
1721     int ret= handle_grant_data(tables, 0, user_to, NULL);
1722 
1723     if (ret != 0)
1724     {
1725       result= true;
1726 
1727       if (ret < 0)
1728       {
1729         rollback_whole_statement= true;
1730         break;
1731       }
1732 
1733       append_user(thd, &wrong_users, user_from, wrong_users.length() > 0,
1734                   false);
1735       continue;
1736     }
1737 
1738     ret= handle_grant_data(tables, 0, user_from, user_to);
1739 
1740     if (ret <= 0)
1741     {
1742       result= true;
1743 
1744       if (ret < 0)
1745       {
1746         rollback_whole_statement= true;
1747         break;
1748       }
1749 
1750       append_user(thd, &wrong_users, user_from, wrong_users.length() > 0, FALSE);
1751       continue;
1752     }
1753     some_users_renamed= TRUE;
1754   }
1755 
1756   /* Rebuild 'acl_check_hosts' since 'acl_users' has been modified */
1757   rebuild_check_host();
1758 
1759   mysql_mutex_unlock(&acl_cache->lock);
1760 
1761   if (result && !rollback_whole_statement)
1762     my_error(ER_CANNOT_USER, MYF(0), "RENAME USER", wrong_users.c_ptr_safe());
1763 
1764   if (some_users_renamed)
1765   {
1766     int ret= commit_owned_gtid_by_partial_command(thd);
1767     if (ret == 1)
1768       result|= write_bin_log(thd, FALSE, thd->query().str, thd->query().length,
1769                               transactional_tables);
1770     else if (ret == -1)
1771       result|= -1;
1772   }
1773 
1774   lock.unlock();
1775 
1776   result|=
1777     acl_end_trans_and_close_tables(thd,
1778                                    thd->transaction_rollback_request ||
1779                                    rollback_whole_statement);
1780 
1781   if (some_users_renamed && !result)
1782     acl_notify_htons(thd, thd->query().str, thd->query().length);
1783 
1784   /* Restore the state of binlog format */
1785   DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row());
1786   if (save_binlog_row_based)
1787     thd->set_current_stmt_binlog_format_row();
1788   DBUG_RETURN(result);
1789 }
1790 
1791 
1792 /*
1793   Alter user list.
1794 
1795   SYNOPSIS
1796     mysql_alter_user()
1797     thd                         The current thread.
1798     list                        The user names.
1799 
1800   RETURN
1801     FALSE       OK.
1802     TRUE        Error.
1803 */
1804 
mysql_alter_user(THD * thd,List<LEX_USER> & list,bool if_exists)1805 bool mysql_alter_user(THD *thd, List <LEX_USER> &list, bool if_exists)
1806 {
1807   bool result= false;
1808   bool is_anonymous_user= false;
1809   String wrong_users;
1810   LEX_USER *user_from, *tmp_user_from;
1811   List_iterator <LEX_USER> user_list(list);
1812   TABLE_LIST tables;
1813   TABLE *table;
1814   bool some_user_altered= false;
1815   bool save_binlog_row_based;
1816   bool is_privileged_user= false;
1817   bool rollback_whole_statement= false;
1818   std::set<LEX_USER *> extra_users;
1819   std::set<LEX_USER *> reset_users;
1820   Acl_table_intact table_intact;
1821 
1822   DBUG_ENTER("mysql_alter_user");
1823 
1824   if (!initialized)
1825   {
1826     my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--skip-grant-tables");
1827     DBUG_RETURN(true);
1828   }
1829   tables.init_one_table("mysql", 5, "user", 4, "user", TL_WRITE);
1830 
1831 #ifdef HAVE_REPLICATION
1832   /*
1833     GRANT and REVOKE are applied the slave in/exclusion rules as they are
1834     some kind of updates to the mysql.% tables.
1835   */
1836   if (thd->slave_thread && rpl_filter->is_on())
1837   {
1838     /*
1839       The tables must be marked "updating" so that tables_ok() takes them into
1840       account in tests.  It's ok to leave 'updating' set after tables_ok.
1841     */
1842     tables.updating= 1;
1843     /* Thanks to memset, tables.next==0 */
1844     if (!(thd->sp_runtime_ctx || rpl_filter->tables_ok(0, &tables)))
1845       DBUG_RETURN(false);
1846   }
1847 #endif
1848   if (!(table= open_ltable(thd, &tables, TL_WRITE, MYSQL_LOCK_IGNORE_TIMEOUT)))
1849     DBUG_RETURN(true);
1850 
1851   if (table_intact.check(table, &mysql_user_table_def))
1852     DBUG_RETURN(true);
1853 
1854   /*
1855     This statement will be replicated as a statement, even when using
1856     row-based replication.  The flag will be reset at the end of the
1857     statement.
1858   */
1859   if ((save_binlog_row_based= thd->is_current_stmt_binlog_format_row()))
1860     thd->clear_current_stmt_binlog_format_row();
1861 
1862   is_privileged_user= is_privileged_user_for_credential_change(thd);
1863 
1864   Partitioned_rwlock_write_guard lock(&LOCK_grant);
1865   mysql_mutex_lock(&acl_cache->lock);
1866 
1867   while ((tmp_user_from= user_list++))
1868   {
1869     ACL_USER *acl_user;
1870     ulong what_to_alter= 0;
1871 
1872     /* add the defaults where needed */
1873     if (!(user_from= get_current_user(thd, tmp_user_from)))
1874     {
1875       result= true;
1876       append_user(thd, &wrong_users, tmp_user_from, wrong_users.length() > 0,
1877                   false);
1878       continue;
1879     }
1880 
1881     if (user_from && user_from->plugin.str)
1882       optimize_plugin_compare_by_pointer(&user_from->plugin);
1883 
1884     /* copy password expire attributes to individual lex user */
1885     user_from->alter_status= thd->lex->alter_password;
1886 
1887     if (set_and_validate_user_attributes(thd, user_from, what_to_alter,
1888                                          is_privileged_user, "ALTER USER"))
1889     {
1890       result= true;
1891       continue;
1892     }
1893 
1894     /*
1895       Check if the user's authentication method supports expiration only
1896       if PASSWORD EXPIRE attribute is specified
1897     */
1898     if (user_from->alter_status.update_password_expired_column &&
1899         !auth_plugin_supports_expiration(user_from->plugin.str))
1900     {
1901       result= true;
1902       append_user(thd, &wrong_users, user_from, wrong_users.length() > 0,
1903                   false);
1904       continue;
1905     }
1906 
1907     if (!strcmp(user_from->user.str, "") &&
1908         (what_to_alter & PASSWORD_EXPIRE_ATTR) &&
1909         user_from->alter_status.update_password_expired_column)
1910     {
1911       result = true;
1912       is_anonymous_user = true;
1913       append_user(thd, &wrong_users, user_from, wrong_users.length() > 0,
1914         false);
1915       continue;
1916     }
1917 
1918     /* look up the user */
1919     if (!(acl_user= find_acl_user(user_from->host.str,
1920                                   user_from->user.str, TRUE)))
1921     {
1922       if (if_exists)
1923       {
1924         String warn_user;
1925         append_user(thd, &warn_user, user_from, FALSE, FALSE);
1926         push_warning_printf(thd, Sql_condition::SL_NOTE,
1927                             ER_USER_DOES_NOT_EXIST,
1928                             ER_THD(thd, ER_USER_DOES_NOT_EXIST),
1929                             warn_user.c_ptr_safe());
1930         try
1931         {
1932           extra_users.insert(user_from);
1933         }
1934         catch (...) {}
1935       }
1936       else
1937       {
1938         result= TRUE;
1939         append_user(thd, &wrong_users, user_from, wrong_users.length() > 0,
1940                     false);
1941       }
1942 
1943       continue;
1944     }
1945 
1946     /* update the mysql.user table */
1947     int ret= replace_user_table(thd, table, user_from, 0, false, false,
1948                                 what_to_alter);
1949     if (ret)
1950     {
1951       result= true;
1952       if (ret < 0)
1953       {
1954         rollback_whole_statement= true;
1955         break;
1956       }
1957       append_user(thd, &wrong_users, user_from, wrong_users.length() > 0,
1958                   false);
1959       continue;
1960     }
1961     if (what_to_alter & RESOURCE_ATTR)
1962       reset_users.insert(tmp_user_from);
1963     some_user_altered= true;
1964     update_sctx_cache(thd->security_context(), acl_user,
1965                       user_from->alter_status.update_password_expired_column);
1966   }
1967 
1968   acl_cache->clear(1);                          // Clear locked hostname cache
1969   mysql_mutex_unlock(&acl_cache->lock);
1970 
1971   if (result && !rollback_whole_statement)
1972   {
1973     if (is_anonymous_user)
1974       my_error(ER_PASSWORD_EXPIRE_ANONYMOUS_USER, MYF(0));
1975     else
1976       my_error(ER_CANNOT_USER, MYF(0), "ALTER USER", wrong_users.c_ptr_safe());
1977   }
1978 
1979   if (some_user_altered || (if_exists && !thd->is_error()))
1980   {
1981     /*
1982       Rewrite ALTER USER statements to use password hashes instead
1983       of <secret> style obfuscation so it can be used in binlog. We
1984       are rewriting to a private string rather than the public one on
1985       the THD (thd->m_rewritten_query). This will save us from having
1986       to acquire the lock to update the string on the THD. As
1987       slow-logging (if enabled) will happen later and use the string
1988       on the THD, the slow log will not contain the local rewrite
1989       we're doing here, but the original one.
1990     */
1991     String rlb;
1992 
1993     mysql_rewrite_create_alter_user(thd, &rlb, &extra_users);
1994 
1995     int ret= commit_owned_gtid_by_partial_command(thd);
1996     if (ret == 1)
1997       result|= (write_bin_log(thd, false, rlb.c_ptr_safe(), rlb.length(),
1998                               table->file->has_transactions()) != 0);
1999 
2000     else if (ret == -1)
2001       result|= -1;
2002 
2003     thd->swap_rewritten_query(rlb); // must come last!
2004   }
2005 
2006   lock.unlock();
2007 
2008   result|=
2009     acl_end_trans_and_close_tables(thd,
2010                                    thd->transaction_rollback_request ||
2011                                    rollback_whole_statement);
2012 
2013   if (some_user_altered && !result)
2014   {
2015     std::set<LEX_USER *>::iterator one_user;
2016     LEX_USER *ext_user;
2017     for (one_user= reset_users.begin(); one_user != reset_users.end(); one_user++)
2018     {
2019       LEX_USER *user= *one_user;
2020       if ((ext_user= get_current_user(thd, user)))
2021         reset_mqh(ext_user, false);
2022     }
2023     acl_notify_htons(thd, thd->query().str, thd->query().length);
2024   }
2025 
2026   /* Restore the state of binlog format */
2027   DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row());
2028   if (save_binlog_row_based)
2029     thd->set_current_stmt_binlog_format_row();
2030   DBUG_RETURN(result);
2031 }
2032 
2033 
2034 #endif
2035