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