1 /* Copyright (c) 2000, 2021, Oracle and/or its affiliates.
2
3 This program is free software; you can redistribute it and/or modify
4 it under the terms of the GNU General Public License, version 2.0,
5 as published by the Free Software Foundation.
6
7 This program is also distributed with certain software (including
8 but not limited to OpenSSL) that is licensed under separate terms,
9 as designated in a particular file or component or in included license
10 documentation. The authors of MySQL hereby grant you an additional
11 permission to link the program and your derivative works with the
12 separately licensed software that they have included with MySQL.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License, version 2.0, for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software Foundation,
21 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA */
22
23 #include "sql_base.h" /* open_normal_and_derived_tables */
24 #include "sql_table.h" /* build_table_filename */
25 #include "sql_show.h" /* append_identifier */
26 #include "sql_view.h" /* VIEW_ANY_ACL */
27 #include "rpl_filter.h" /* rpl_filter */
28 #include "sql_parse.h" /* get_current_user */
29 /* any_db */
30 #include "binlog.h" /* mysql_bin_log */
31 #include "sp.h" /* sp_exist_routines */
32 #include "sql_insert.h" /* Sql_cmd_insert_base */
33 #include "log.h" /* sql_print_warning */
34
35 #include "sql_update.h"
36 #include "auth_internal.h"
37 #include "sql_auth_cache.h"
38 #include "sql_authentication.h"
39 #include "sql_authorization.h"
40 #include "debug_sync.h"
41 #include "sql_user_table.h"
42
43 const char *command_array[]=
44 {
45 "SELECT", "INSERT", "UPDATE", "DELETE", "CREATE", "DROP", "RELOAD",
46 "SHUTDOWN", "PROCESS","FILE", "GRANT", "REFERENCES", "INDEX",
47 "ALTER", "SHOW DATABASES", "SUPER", "CREATE TEMPORARY TABLES",
48 "LOCK TABLES", "EXECUTE", "REPLICATION SLAVE", "REPLICATION CLIENT",
49 "CREATE VIEW", "SHOW VIEW", "CREATE ROUTINE", "ALTER ROUTINE",
50 "CREATE USER", "EVENT", "TRIGGER", "CREATE TABLESPACE"
51 };
52
53 uint command_lengths[]=
54 {
55 6, 6, 6, 6, 6, 4, 6, 8, 7, 4, 5, 10, 5, 5, 14, 5, 23, 11, 7, 17, 18, 11, 9,
56 14, 13, 11, 5, 7, 17
57 };
58
59 const char *any_db="*any*"; // Special symbol for check_access
60
61
62 static bool check_show_access(THD *thd, TABLE_LIST *table);
63
64 /**
65 Get a cached internal schema access.
66 @param grant_internal_info the cache
67 @param schema_name the name of the internal schema
68 */
69 const ACL_internal_schema_access *
get_cached_schema_access(GRANT_INTERNAL_INFO * grant_internal_info,const char * schema_name)70 get_cached_schema_access(GRANT_INTERNAL_INFO *grant_internal_info,
71 const char *schema_name)
72 {
73 if (grant_internal_info)
74 {
75 if (! grant_internal_info->m_schema_lookup_done)
76 {
77 grant_internal_info->m_schema_access=
78 ACL_internal_schema_registry::lookup(schema_name);
79 grant_internal_info->m_schema_lookup_done= TRUE;
80 }
81 return grant_internal_info->m_schema_access;
82 }
83 return ACL_internal_schema_registry::lookup(schema_name);
84 }
85
86
87 /**
88 Get a cached internal table access.
89 @param grant_internal_info the cache
90 @param schema_name the name of the internal schema
91 @param table_name the name of the internal table
92 */
93 const ACL_internal_table_access *
get_cached_table_access(GRANT_INTERNAL_INFO * grant_internal_info,const char * schema_name,const char * table_name)94 get_cached_table_access(GRANT_INTERNAL_INFO *grant_internal_info,
95 const char *schema_name,
96 const char *table_name)
97 {
98 assert(grant_internal_info);
99 if (! grant_internal_info->m_table_lookup_done)
100 {
101 const ACL_internal_schema_access *schema_access;
102 schema_access= get_cached_schema_access(grant_internal_info, schema_name);
103 if (schema_access)
104 grant_internal_info->m_table_access= schema_access->lookup(table_name);
105 grant_internal_info->m_table_lookup_done= TRUE;
106 }
107 return grant_internal_info->m_table_access;
108 }
109
110
111 ACL_internal_access_result
check(ulong want_access,ulong * save_priv) const112 IS_internal_schema_access::check(ulong want_access,
113 ulong *save_priv) const
114 {
115 want_access &= ~SELECT_ACL;
116
117 /*
118 We don't allow any simple privileges but SELECT_ACL on
119 the information_schema database.
120 */
121 if (unlikely(want_access & DB_ACLS))
122 return ACL_INTERNAL_ACCESS_DENIED;
123
124 /* Always grant SELECT for the information schema. */
125 *save_priv|= SELECT_ACL;
126
127 return want_access ? ACL_INTERNAL_ACCESS_CHECK_GRANT :
128 ACL_INTERNAL_ACCESS_GRANTED;
129 }
130
131 const ACL_internal_table_access *
lookup(const char * name) const132 IS_internal_schema_access::lookup(const char *name) const
133 {
134 /* There are no per table rules for the information schema. */
135 return NULL;
136 }
137
138 /**
139 Perform first stage of privilege checking for SELECT statement.
140
141 @param thd Thread context.
142 @param lex LEX for SELECT statement.
143 @param tables List of tables used by statement.
144 @param first_table First table in the main SELECT of the SELECT
145 statement.
146
147 @retval FALSE - Success (column-level privilege checks might be required).
148 @retval TRUE - Failure, privileges are insufficient.
149 */
150
select_precheck(THD * thd,LEX * lex,TABLE_LIST * tables,TABLE_LIST * first_table)151 bool select_precheck(THD *thd, LEX *lex, TABLE_LIST *tables,
152 TABLE_LIST *first_table)
153 {
154 bool res;
155 /*
156 lex->exchange != NULL implies SELECT .. INTO OUTFILE and this
157 requires FILE_ACL access.
158 */
159 ulong privileges_requested= lex->exchange ? SELECT_ACL | FILE_ACL :
160 SELECT_ACL;
161
162 if (tables)
163 {
164 res= check_table_access(thd,
165 privileges_requested,
166 tables, FALSE, UINT_MAX, FALSE) ||
167 (first_table && first_table->schema_table_reformed &&
168 check_show_access(thd, first_table));
169 }
170 else
171 res= check_access(thd, privileges_requested, any_db, NULL, NULL, 0, 0);
172
173 return res;
174 }
175
176
177 /**
178 Multi update query pre-check.
179
180 @param thd Thread handler
181 @param tables Global/local table list (have to be the same)
182
183 @retval
184 FALSE OK
185 @retval
186 TRUE Error
187 */
188
multi_update_precheck(THD * thd,TABLE_LIST * tables)189 bool Sql_cmd_update::multi_update_precheck(THD *thd, TABLE_LIST *tables)
190 {
191 DBUG_ENTER("multi_update_precheck");
192
193 /*
194 Ensure that we have UPDATE or SELECT privilege for each table
195 The exact privilege is checked in mysql_multi_update()
196 */
197 for (TABLE_LIST *table= tables; table; table= table->next_global)
198 {
199 /*
200 "uses_materialization()" covers the case where a prepared statement is
201 executed and a view is decided to be materialized during preparation.
202 */
203 if (table->is_derived() || table->uses_materialization())
204 table->grant.privilege= SELECT_ACL;
205 else if ((check_access(thd, UPDATE_ACL, table->db,
206 &table->grant.privilege,
207 &table->grant.m_internal,
208 0, 1) ||
209 check_grant(thd, UPDATE_ACL, table, FALSE, 1, TRUE)) &&
210 (check_access(thd, SELECT_ACL, table->db,
211 &table->grant.privilege,
212 &table->grant.m_internal,
213 0, 0) ||
214 check_grant(thd, SELECT_ACL, table, FALSE, 1, FALSE)))
215 DBUG_RETURN(TRUE);
216
217 table->table_in_first_from_clause= 1;
218 }
219
220 DBUG_RETURN(FALSE);
221 }
222
223
224 /**
225 Multi delete query pre-check.
226
227 @param thd Thread handler
228 @param tables Global/local table list
229
230 @retval
231 FALSE OK
232 @retval
233 TRUE error
234 */
235
multi_delete_precheck(THD * thd,TABLE_LIST * tables)236 bool multi_delete_precheck(THD *thd, TABLE_LIST *tables)
237 {
238 TABLE_LIST *aux_tables= thd->lex->auxiliary_table_list.first;
239 TABLE_LIST **save_query_tables_own_last= thd->lex->query_tables_own_last;
240 DBUG_ENTER("multi_delete_precheck");
241
242 /* sql_yacc guarantees that tables and aux_tables are not zero */
243 assert(aux_tables != 0);
244 if (check_table_access(thd, SELECT_ACL, tables, FALSE, UINT_MAX, FALSE))
245 DBUG_RETURN(TRUE);
246
247 /*
248 Since aux_tables list is not part of LEX::query_tables list we
249 have to juggle with LEX::query_tables_own_last value to be able
250 call check_table_access() safely.
251 */
252 thd->lex->query_tables_own_last= 0;
253 if (check_table_access(thd, DELETE_ACL, aux_tables, FALSE, UINT_MAX, FALSE))
254 {
255 thd->lex->query_tables_own_last= save_query_tables_own_last;
256 DBUG_RETURN(TRUE);
257 }
258 thd->lex->query_tables_own_last= save_query_tables_own_last;
259
260 DBUG_RETURN(FALSE);
261 }
262
263
264 /**
265 simple UPDATE query pre-check.
266
267 @param thd Thread handler
268 @param tables Global table list
269
270 @retval
271 FALSE OK
272 @retval
273 TRUE Error
274 */
275
update_precheck(THD * thd,TABLE_LIST * tables)276 bool Sql_cmd_update::update_precheck(THD *thd, TABLE_LIST *tables)
277 {
278 DBUG_ENTER("update_precheck");
279 const bool res= check_one_table_access(thd, UPDATE_ACL, tables);
280 DBUG_RETURN(res);
281 }
282
283
284 /**
285 simple DELETE query pre-check.
286
287 @param thd Thread handler
288 @param tables Global table list
289
290 @retval
291 FALSE OK
292 @retval
293 TRUE error
294 */
295
delete_precheck(THD * thd,TABLE_LIST * tables)296 bool delete_precheck(THD *thd, TABLE_LIST *tables)
297 {
298 DBUG_ENTER("delete_precheck");
299 if (check_one_table_access(thd, DELETE_ACL, tables))
300 DBUG_RETURN(TRUE);
301 /* Set privilege for the WHERE clause */
302 tables->set_want_privilege(SELECT_ACL);
303 DBUG_RETURN(FALSE);
304 }
305
306
307 /**
308 simple INSERT query pre-check.
309
310 @param thd Thread handler
311 @param tables Global table list
312
313 @retval
314 FALSE OK
315 @retval
316 TRUE error
317 */
318
insert_precheck(THD * thd,TABLE_LIST * tables)319 bool Sql_cmd_insert_base::insert_precheck(THD *thd, TABLE_LIST *tables)
320 {
321 LEX *lex= thd->lex;
322 DBUG_ENTER("insert_precheck");
323
324 /*
325 Check that we have modify privileges for the first table and
326 select privileges for the rest
327 */
328 ulong privilege= (INSERT_ACL |
329 (lex->duplicates == DUP_REPLACE ? DELETE_ACL : 0) |
330 (insert_value_list.elements ? UPDATE_ACL : 0));
331
332 if (check_one_table_access(thd, privilege, tables))
333 DBUG_RETURN(TRUE);
334
335 DBUG_RETURN(FALSE);
336 }
337
338
339 /**
340 Check privileges for LOCK TABLES statement.
341
342 @param thd Thread context.
343 @param tables List of tables to be locked.
344
345 @retval FALSE - Success.
346 @retval TRUE - Failure.
347 */
348
lock_tables_precheck(THD * thd,TABLE_LIST * tables)349 bool lock_tables_precheck(THD *thd, TABLE_LIST *tables)
350 {
351 TABLE_LIST *first_not_own_table= thd->lex->first_not_own_table();
352
353 for (TABLE_LIST *table= tables; table != first_not_own_table && table;
354 table= table->next_global)
355 {
356 if (is_temporary_table(table))
357 continue;
358
359 if (check_table_access(thd, LOCK_TABLES_ACL | SELECT_ACL, table,
360 FALSE, 1, FALSE))
361 return TRUE;
362 }
363
364 return FALSE;
365 }
366
367
368 /**
369 CREATE TABLE query pre-check.
370
371 @param thd Thread handler
372 @param tables Global table list
373 @param create_table Table which will be created
374
375 @retval
376 FALSE OK
377 @retval
378 TRUE Error
379 */
380
create_table_precheck(THD * thd,TABLE_LIST * tables,TABLE_LIST * create_table)381 bool create_table_precheck(THD *thd, TABLE_LIST *tables,
382 TABLE_LIST *create_table)
383 {
384 LEX *lex= thd->lex;
385 SELECT_LEX *select_lex= lex->select_lex;
386 ulong want_priv;
387 bool error= TRUE; // Error message is given
388 DBUG_ENTER("create_table_precheck");
389
390 /*
391 Require CREATE [TEMPORARY] privilege on new table; for
392 CREATE TABLE ... SELECT, also require INSERT.
393 */
394
395 want_priv= (lex->create_info.options & HA_LEX_CREATE_TMP_TABLE) ?
396 CREATE_TMP_ACL :
397 (CREATE_ACL | (select_lex->item_list.elements ? INSERT_ACL : 0));
398
399 if (check_access(thd, want_priv, create_table->db,
400 &create_table->grant.privilege,
401 &create_table->grant.m_internal,
402 0, 0))
403 goto err;
404
405 /* If it is a merge table, check privileges for merge children. */
406 if (lex->create_info.merge_list.first)
407 {
408 /*
409 The user must have (SELECT_ACL | UPDATE_ACL | DELETE_ACL) on the
410 underlying base tables, even if there are temporary tables with the same
411 names.
412
413 From user's point of view, it might look as if the user must have these
414 privileges on temporary tables to create a merge table over them. This is
415 one of two cases when a set of privileges is required for operations on
416 temporary tables (see also CREATE TABLE).
417
418 The reason for this behavior stems from the following facts:
419
420 - For merge tables, the underlying table privileges are checked only
421 at CREATE TABLE / ALTER TABLE time.
422
423 In other words, once a merge table is created, the privileges of
424 the underlying tables can be revoked, but the user will still have
425 access to the merge table (provided that the user has privileges on
426 the merge table itself).
427
428 - Temporary tables shadow base tables.
429
430 I.e. there might be temporary and base tables with the same name, and
431 the temporary table takes the precedence in all operations.
432
433 - For temporary MERGE tables we do not track if their child tables are
434 base or temporary. As result we can't guarantee that privilege check
435 which was done in presence of temporary child will stay relevant later
436 as this temporary table might be removed.
437
438 If SELECT_ACL | UPDATE_ACL | DELETE_ACL privileges were not checked for
439 the underlying *base* tables, it would create a security breach as in
440 Bug#12771903.
441 */
442
443 if (check_table_access(thd, SELECT_ACL | UPDATE_ACL | DELETE_ACL,
444 lex->create_info.merge_list.first,
445 FALSE, UINT_MAX, FALSE))
446 goto err;
447 }
448
449 if (want_priv != CREATE_TMP_ACL &&
450 check_grant(thd, want_priv, create_table, FALSE, 1, FALSE))
451 goto err;
452
453 if (select_lex->item_list.elements)
454 {
455 /* Check permissions for used tables in CREATE TABLE ... SELECT */
456 if (tables && check_table_access(thd, SELECT_ACL, tables, FALSE,
457 UINT_MAX, FALSE))
458 goto err;
459 }
460 else if (lex->create_info.options & HA_LEX_CREATE_TABLE_LIKE)
461 {
462 if (check_table_access(thd, SELECT_ACL, tables, FALSE, UINT_MAX, FALSE))
463 goto err;
464 }
465
466 if (check_fk_parent_table_access(thd, create_table->db,
467 &lex->create_info, &lex->alter_info))
468 goto err;
469
470 error= FALSE;
471
472 err:
473 DBUG_RETURN(error);
474 }
475
476 /**
477 @brief Performs standardized check whether to prohibit (TRUE)
478 or allow (FALSE) operations based on read_only and super_read_only
479 state.
480 @param thd Thread handler
481 @param err_if_readonly Boolean indicating whether or not
482 to add the error to the thread context if read-only is
483 violated.
484
485 @returns Status code
486 @retval TRUE The operation should be prohibited.
487 @ retval FALSE The operation should be allowed.
488 */
check_readonly(THD * thd,bool err_if_readonly)489 bool check_readonly(THD *thd, bool err_if_readonly)
490 {
491 DBUG_ENTER("check_readonly");
492
493 /* read_only=OFF, do not prohibit operation: */
494 if (!opt_readonly)
495 DBUG_RETURN(FALSE);
496
497 /*
498 Thread is replication slave or skip_read_only check is enabled for the
499 command, do not prohibit operation.
500 */
501 if (thd->slave_thread || thd->is_cmd_skip_readonly())
502 DBUG_RETURN(FALSE);
503
504 bool is_super = thd->security_context()->check_access(SUPER_ACL);
505
506 /* super_read_only=OFF and user has SUPER privilege,
507 do not prohibit operation:
508 */
509 if (is_super && !opt_super_readonly)
510
511 DBUG_RETURN(FALSE);
512
513 /* throw error in standardized way if requested: */
514 if (err_if_readonly)
515 err_readonly(thd);
516
517
518 /* in all other cases, prohibit operation: */
519 DBUG_RETURN(TRUE);
520 }
521
522 /**
523 @brief Generates appropriate error messages for read-only state
524 depending on whether user has SUPER privilege or not.
525
526 @param thd Thread handler
527
528 */
err_readonly(THD * thd)529 void err_readonly(THD *thd)
530 {
531 my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0),
532 thd->security_context()->check_access(SUPER_ACL) ?
533 "--super-read-only" : "--read-only");
534
535 }
536
537
538 #ifndef NO_EMBEDDED_ACCESS_CHECKS
539
540 /**
541 Wrapper class which simplifies read guard usage for LOCK_grant.
542 */
543 class LOCK_grant_read_guard : public Partitioned_rwlock_read_guard
544 {
545 public:
LOCK_grant_read_guard(THD * thd)546 explicit LOCK_grant_read_guard(THD *thd)
547 : Partitioned_rwlock_read_guard(&LOCK_grant, thd->thread_id())
548 { }
549 };
550
551
552 /**
553 Check grants for commands which work only with one table and all other
554 tables belonging to subselects or implicitly opened tables.
555
556 @param thd Thread handler
557 @param privilege requested privilege
558 @param all_tables global table list of query
559
560 @returns false on success, true on access denied error
561 */
562
check_one_table_access(THD * thd,ulong privilege,TABLE_LIST * all_tables)563 bool check_one_table_access(THD *thd, ulong privilege, TABLE_LIST *all_tables)
564 {
565 if (check_single_table_access(thd, privilege, all_tables, false))
566 return true;
567
568 // Check privileges on tables from subqueries and implicitly opened tables
569 TABLE_LIST *subquery_table;
570 TABLE_LIST *const view= all_tables->is_view() ? all_tables : NULL;
571
572 if ((subquery_table= all_tables->next_global))
573 {
574 /*
575 Access rights asked for the first table of a view should be the same
576 as for the view
577 */
578 if (view && subquery_table->belong_to_view == view)
579 {
580 if (check_single_table_access(thd, privilege, subquery_table, false))
581 return true; /* purecov: inspected */
582 subquery_table= subquery_table->next_global;
583 }
584 if (subquery_table &&
585 check_table_access(thd, SELECT_ACL, subquery_table, false,
586 UINT_MAX, false))
587 return true;
588 }
589 return false;
590 }
591
592
593 /**
594 Check grants for commands which work only with one table.
595
596 @param thd Thread handler
597 @param privilege requested privilege
598 @param all_tables global table list of query
599 @param no_errors FALSE/TRUE - report/don't report error to
600 the client (using my_error() call).
601
602 @retval
603 0 OK
604 @retval
605 1 access denied, error is sent to client
606 */
607
check_single_table_access(THD * thd,ulong privilege,TABLE_LIST * all_tables,bool no_errors)608 bool check_single_table_access(THD *thd, ulong privilege,
609 TABLE_LIST *all_tables, bool no_errors)
610 {
611 Security_context *backup_ctx= thd->security_context();
612
613 /* we need to switch to the saved context (if any) */
614 if (all_tables->security_ctx)
615 thd->set_security_context(all_tables->security_ctx);
616
617 const char *db_name;
618 if ((all_tables->is_view() || all_tables->field_translation) &&
619 !all_tables->schema_table)
620 db_name= all_tables->view_db.str;
621 else
622 db_name= all_tables->db;
623
624 if (check_access(thd, privilege, db_name,
625 &all_tables->grant.privilege,
626 &all_tables->grant.m_internal,
627 0, no_errors))
628 goto deny;
629
630 /* Show only 1 table for check_grant */
631 if (!(all_tables->belong_to_view &&
632 (thd->lex->sql_command == SQLCOM_SHOW_FIELDS)) &&
633 check_grant(thd, privilege, all_tables, FALSE, 1, no_errors))
634 goto deny;
635
636 thd->set_security_context(backup_ctx);
637 return 0;
638
639 deny:
640 thd->set_security_context(backup_ctx);
641 return 1;
642 }
643
644
645 bool
check_routine_access(THD * thd,ulong want_access,const char * db,char * name,bool is_proc,bool no_errors)646 check_routine_access(THD *thd, ulong want_access, const char *db, char *name,
647 bool is_proc, bool no_errors)
648 {
649 TABLE_LIST tables[1];
650
651 memset(tables, 0, sizeof(TABLE_LIST));
652 tables->db= db;
653 tables->table_name= tables->alias= name;
654
655 /*
656 The following test is just a shortcut for check_access() (to avoid
657 calculating db_access) under the assumption that it's common to
658 give persons global right to execute all stored SP (but not
659 necessary to create them).
660 Note that this effectively bypasses the ACL_internal_schema_access checks
661 that are implemented for the INFORMATION_SCHEMA and PERFORMANCE_SCHEMA,
662 which are located in check_access().
663 Since the I_S and P_S do not contain routines, this bypass is ok,
664 as long as this code path is not abused to create routines.
665 The assert enforce that.
666 */
667 assert((want_access & CREATE_PROC_ACL) == 0);
668 if (thd->security_context()->check_access(want_access))
669 tables->grant.privilege= want_access;
670 else if (check_access(thd, want_access, db,
671 &tables->grant.privilege,
672 &tables->grant.m_internal,
673 0, no_errors))
674 return TRUE;
675
676 return check_grant_routine(thd, want_access, tables, is_proc, no_errors);
677 }
678
679
680 /**
681 Check if the given table has any of the asked privileges
682
683 @param thd Thread handler
684 @param want_access Bitmap of possible privileges to check for
685
686 @retval
687 0 ok
688 @retval
689 1 error
690 */
691
check_some_access(THD * thd,ulong want_access,TABLE_LIST * table)692 bool check_some_access(THD *thd, ulong want_access, TABLE_LIST *table)
693 {
694 ulong access;
695 DBUG_ENTER("check_some_access");
696
697 /* This loop will work as long as we have less than 32 privileges */
698 for (access= 1; access < want_access ; access<<= 1)
699 {
700 if (access & want_access)
701 {
702 if (!check_access(thd, access, table->db,
703 &table->grant.privilege,
704 &table->grant.m_internal,
705 0, 1) &&
706 !check_grant(thd, access, table, FALSE, 1, TRUE))
707 DBUG_RETURN(0);
708 }
709 }
710 DBUG_PRINT("exit",("no matching access rights"));
711 DBUG_RETURN(1);
712 }
713
714
715 /**
716 Check if the routine has any of the routine privileges.
717
718 @param thd Thread handler
719 @param db Database name
720 @param name Routine name
721
722 @retval
723 0 ok
724 @retval
725 1 error
726 */
727
check_some_routine_access(THD * thd,const char * db,const char * name,bool is_proc)728 bool check_some_routine_access(THD *thd, const char *db, const char *name,
729 bool is_proc)
730 {
731 ulong save_priv;
732 /*
733 The following test is just a shortcut for check_access() (to avoid
734 calculating db_access)
735 Note that this effectively bypasses the ACL_internal_schema_access checks
736 that are implemented for the INFORMATION_SCHEMA and PERFORMANCE_SCHEMA,
737 which are located in check_access().
738 Since the I_S and P_S do not contain routines, this bypass is ok,
739 as it only opens SHOW_PROC_ACLS.
740 */
741 if (thd->security_context()->check_access(SHOW_PROC_ACLS, true))
742 return FALSE;
743 if (!check_access(thd, SHOW_PROC_ACLS, db, &save_priv, NULL, 0, 1) ||
744 (save_priv & SHOW_PROC_ACLS))
745 return FALSE;
746 return check_routine_level_acl(thd, db, name, is_proc);
747 }
748
749
750
751 /**
752 @brief Compare requested privileges with the privileges acquired from the
753 User- and Db-tables.
754 @param thd Thread handler
755 @param want_access The requested access privileges.
756 @param db A pointer to the Db name.
757 @param[out] save_priv A pointer to the granted privileges will be stored.
758 @param grant_internal_info A pointer to the internal grant cache.
759 @param dont_check_global_grants True if no global grants are checked.
760 @param no_error True if no errors should be sent to the client.
761
762 'save_priv' is used to save the User-table (global) and Db-table grants for
763 the supplied db name. Note that we don't store db level grants if the global
764 grants is enough to satisfy the request AND the global grants contains a
765 SELECT grant.
766
767 For internal databases (INFORMATION_SCHEMA, PERFORMANCE_SCHEMA),
768 additional rules apply, see ACL_internal_schema_access.
769
770 @see check_grant
771
772 @return Status of denial of access by exclusive ACLs.
773 @retval FALSE Access can't exclusively be denied by Db- and User-table
774 access unless Column- and Table-grants are checked too.
775 @retval TRUE Access denied.
776 */
777
778 bool
check_access(THD * thd,ulong want_access,const char * db,ulong * save_priv,GRANT_INTERNAL_INFO * grant_internal_info,bool dont_check_global_grants,bool no_errors)779 check_access(THD *thd, ulong want_access, const char *db, ulong *save_priv,
780 GRANT_INTERNAL_INFO *grant_internal_info,
781 bool dont_check_global_grants, bool no_errors)
782 {
783 Security_context *sctx= thd->security_context();
784 ulong db_access;
785
786 /*
787 GRANT command:
788 In case of database level grant the database name may be a pattern,
789 in case of table|column level grant the database name can not be a pattern.
790 We use 'dont_check_global_grants' as a flag to determine
791 if it's database level grant command
792 (see SQLCOM_GRANT case, mysql_execute_command() function) and
793 set db_is_pattern according to 'dont_check_global_grants' value.
794 */
795 bool db_is_pattern= ((want_access & GRANT_ACL) && dont_check_global_grants);
796 ulong dummy;
797 DBUG_ENTER("check_access");
798 DBUG_PRINT("enter",("db: %s want_access: %lu master_access: %lu",
799 db ? db : "", want_access, sctx->master_access()));
800
801 if (save_priv)
802 *save_priv=0;
803 else
804 {
805 save_priv= &dummy;
806 dummy= 0;
807 }
808
809 THD_STAGE_INFO(thd, stage_checking_permissions);
810 if ((!db || !db[0]) && !thd->db().str && !dont_check_global_grants)
811 {
812 DBUG_PRINT("error",("No database"));
813 if (!no_errors)
814 my_message(ER_NO_DB_ERROR, ER(ER_NO_DB_ERROR),
815 MYF(0)); /* purecov: tested */
816 DBUG_RETURN(TRUE); /* purecov: tested */
817 }
818
819 if ((db != NULL) && (db != any_db))
820 {
821 const ACL_internal_schema_access *access;
822 access= get_cached_schema_access(grant_internal_info, db);
823 if (access)
824 {
825 switch (access->check(want_access, save_priv))
826 {
827 case ACL_INTERNAL_ACCESS_GRANTED:
828 /*
829 All the privileges requested have been granted internally.
830 [out] *save_privileges= Internal privileges.
831 */
832 DBUG_RETURN(FALSE);
833 case ACL_INTERNAL_ACCESS_DENIED:
834 if (! no_errors)
835 {
836 my_error(ER_DBACCESS_DENIED_ERROR, MYF(0),
837 sctx->priv_user().str, sctx->priv_host().str, db);
838 }
839 DBUG_RETURN(TRUE);
840 case ACL_INTERNAL_ACCESS_CHECK_GRANT:
841 /*
842 Only some of the privilege requested have been granted internally,
843 proceed with the remaining bits of the request (want_access).
844 */
845 want_access&= ~(*save_priv);
846 break;
847 }
848 }
849 }
850
851 if (sctx->check_access(want_access))
852 {
853 /*
854 1. If we don't have a global SELECT privilege, we have to get the
855 database specific access rights to be able to handle queries of type
856 UPDATE t1 SET a=1 WHERE b > 0
857 2. Change db access if it isn't current db which is being addressed
858 */
859 if (!(sctx->check_access(SELECT_ACL)))
860 {
861 if (db && (!thd->db().str || db_is_pattern ||
862 strcmp(db, thd->db().str)))
863 db_access= acl_get(sctx->host().str, sctx->ip().str,
864 sctx->priv_user().str, db, db_is_pattern);
865 else
866 {
867 /* get access for current db */
868 db_access= sctx->db_access();
869 }
870 /*
871 The effective privileges are the union of the global privileges
872 and the intersection of db- and host-privileges,
873 plus the internal privileges.
874 */
875 *save_priv|= sctx->master_access() | db_access;
876 }
877 else
878 *save_priv|= sctx->master_access();
879 DBUG_RETURN(FALSE);
880 }
881 if (((want_access & ~sctx->master_access()) & ~DB_ACLS) ||
882 (! db && dont_check_global_grants))
883 { // We can never grant this
884 DBUG_PRINT("error",("No possible access"));
885 if (!no_errors)
886 {
887 if (thd->password == 2)
888 my_error(ER_ACCESS_DENIED_NO_PASSWORD_ERROR, MYF(0),
889 sctx->priv_user().str,
890 sctx->priv_host().str);
891 else
892 my_error(ER_ACCESS_DENIED_ERROR, MYF(0),
893 sctx->priv_user().str,
894 sctx->priv_host().str,
895 (thd->password ?
896 ER(ER_YES) :
897 ER(ER_NO))); /* purecov: tested */
898 }
899 DBUG_RETURN(TRUE); /* purecov: tested */
900 }
901
902 if (db == any_db)
903 {
904 /*
905 Access granted; Allow select on *any* db.
906 [out] *save_privileges= 0
907 */
908 DBUG_RETURN(FALSE);
909 }
910
911 if (db && (!thd->db().str || db_is_pattern || strcmp(db,thd->db().str)))
912 db_access= acl_get(sctx->host().str, sctx->ip().str,
913 sctx->priv_user().str, db, db_is_pattern);
914 else
915 db_access= sctx->db_access();
916 DBUG_PRINT("info",("db_access: %lu want_access: %lu",
917 db_access, want_access));
918
919 /*
920 Save the union of User-table and the intersection between Db-table and
921 Host-table privileges, with the already saved internal privileges.
922 */
923 db_access= (db_access | sctx->master_access());
924 *save_priv|= db_access;
925
926 /*
927 We need to investigate column- and table access if all requested privileges
928 belongs to the bit set of .
929 */
930 bool need_table_or_column_check=
931 (want_access & (TABLE_ACLS | PROC_ACLS | db_access)) == want_access;
932
933 /*
934 Grant access if the requested access is in the intersection of
935 host- and db-privileges (as retrieved from the acl cache),
936 also grant access if all the requested privileges are in the union of
937 TABLES_ACLS and PROC_ACLS; see check_grant.
938 */
939 if ( (db_access & want_access) == want_access ||
940 (!dont_check_global_grants &&
941 need_table_or_column_check))
942 {
943 /*
944 Ok; but need to check table- and column privileges.
945 [out] *save_privileges is (User-priv | (Db-priv & Host-priv) | Internal-priv)
946 */
947 DBUG_RETURN(FALSE);
948 }
949
950 /*
951 Access is denied;
952 [out] *save_privileges is (User-priv | (Db-priv & Host-priv) | Internal-priv)
953 */
954 DBUG_PRINT("error",("Access denied"));
955 if (!no_errors)
956 my_error(ER_DBACCESS_DENIED_ERROR, MYF(0),
957 sctx->priv_user().str, sctx->priv_host().str,
958 (db ? db : (thd->db().str ?
959 thd->db().str :
960 "unknown")));
961 DBUG_RETURN(TRUE);
962
963 }
964
965
966 /**
967 @brief Check if the requested privileges exists in either User-, Host- or
968 Db-tables.
969 @param thd Thread context
970 @param want_access Privileges requested
971 @param tables List of tables to be compared against
972 @param no_errors Don't report error to the client (using my_error() call).
973 @param any_combination_of_privileges_will_do TRUE if any privileges on any
974 column combination is enough.
975 @param number Only the first 'number' tables in the linked list are
976 relevant.
977
978 The suppled table list contains cached privileges. This functions calls the
979 help functions check_access and check_grant to verify the first three steps
980 in the privileges check queue:
981 1. Global privileges
982 2. OR (db privileges AND host privileges)
983 3. OR table privileges
984 4. OR column privileges (not checked by this function!)
985 5. OR routine privileges (not checked by this function!)
986
987 @see check_access
988 @see check_grant
989
990 @note This functions assumes that table list used and
991 thd->lex->query_tables_own_last value correspond to each other
992 (the latter should be either 0 or point to next_global member
993 of one of elements of this table list).
994
995 @return
996 @retval FALSE OK
997 @retval TRUE Access denied; But column or routine privileges might need to
998 be checked also.
999 */
1000
1001 bool
check_table_access(THD * thd,ulong requirements,TABLE_LIST * tables,bool any_combination_of_privileges_will_do,uint number,bool no_errors)1002 check_table_access(THD *thd, ulong requirements,TABLE_LIST *tables,
1003 bool any_combination_of_privileges_will_do,
1004 uint number, bool no_errors)
1005 {
1006 TABLE_LIST *org_tables= tables;
1007 TABLE_LIST *first_not_own_table= thd->lex->first_not_own_table();
1008 uint i= 0;
1009 Security_context *sctx= thd->security_context();
1010 Security_context *backup_ctx= thd->security_context();
1011
1012 DBUG_EXECUTE_IF("force_check_table_access_return_ok",
1013 return false;);
1014 /*
1015 The check that first_not_own_table is not reached is for the case when
1016 the given table list refers to the list for prelocking (contains tables
1017 of other queries). For simple queries first_not_own_table is 0.
1018 */
1019 for (; i < number && tables != first_not_own_table && tables;
1020 tables= tables->next_global, i++)
1021 {
1022 TABLE_LIST *const table_ref= tables->correspondent_table ?
1023 tables->correspondent_table : tables;
1024 ulong want_access= requirements;
1025 if (table_ref->security_ctx)
1026 sctx= table_ref->security_ctx;
1027 else
1028 sctx= backup_ctx;
1029
1030 /*
1031 We should not encounter table list elements for reformed SHOW
1032 statements unless this is first table list element in the main
1033 select.
1034 Such table list elements require additional privilege check
1035 (see check_show_access()). This check is carried out by caller,
1036 but only for the first table list element from the main select.
1037 */
1038 assert(!table_ref->schema_table_reformed ||
1039 table_ref == thd->lex->select_lex->table_list.first);
1040
1041 DBUG_PRINT("info", ("derived: %d view: %d", table_ref->is_derived(),
1042 table_ref->is_view()));
1043
1044 if (table_ref->is_derived())
1045 continue;
1046
1047 thd->set_security_context(sctx);
1048
1049 if (check_access(thd, want_access, table_ref->get_db_name(),
1050 &table_ref->grant.privilege,
1051 &table_ref->grant.m_internal,
1052 0, no_errors))
1053 goto deny;
1054 }
1055 thd->set_security_context(backup_ctx);
1056 return check_grant(thd,requirements,org_tables,
1057 any_combination_of_privileges_will_do,
1058 number, no_errors);
1059 deny:
1060 thd->set_security_context(backup_ctx);
1061 return TRUE;
1062 }
1063
1064
1065 /****************************************************************************
1066 Handle GRANT commands
1067 ****************************************************************************/
1068
1069
1070 /*
1071 Return 1 if we are allowed to create new users
1072 the logic here is: INSERT_ACL is sufficient.
1073 It's also a requirement in opt_safe_user_create,
1074 otherwise CREATE_USER_ACL is enough.
1075 */
1076
test_if_create_new_users(THD * thd)1077 static bool test_if_create_new_users(THD *thd)
1078 {
1079 Security_context *sctx= thd->security_context();
1080 bool create_new_users= MY_TEST(sctx->check_access(INSERT_ACL)) ||
1081 (!opt_safe_user_create &&
1082 MY_TEST(sctx->check_access(CREATE_USER_ACL)));
1083 if (!create_new_users)
1084 {
1085 TABLE_LIST tl;
1086 ulong db_access;
1087 tl.init_one_table(C_STRING_WITH_LEN("mysql"),
1088 C_STRING_WITH_LEN("user"), "user", TL_WRITE);
1089 create_new_users= 1;
1090
1091 db_access= acl_get(sctx->host().str, sctx->ip().str,
1092 sctx->priv_user().str, tl.db, 0);
1093 if (!(db_access & INSERT_ACL))
1094 {
1095 if (check_grant(thd, INSERT_ACL, &tl, FALSE, UINT_MAX, TRUE))
1096 create_new_users=0;
1097 }
1098 }
1099 return create_new_users;
1100 }
1101
1102
1103 /*
1104 Store table level and column level grants in the privilege tables
1105
1106 SYNOPSIS
1107 mysql_table_grant()
1108 thd Thread handle
1109 table_list List of tables to give grant
1110 user_list List of users to give grant
1111 columns List of columns to give grant
1112 rights Table level grant
1113 revoke_grant Set to 1 if this is a REVOKE command
1114
1115 RETURN
1116 FALSE ok
1117 TRUE error
1118 */
1119
mysql_table_grant(THD * thd,TABLE_LIST * table_list,List<LEX_USER> & user_list,List<LEX_COLUMN> & columns,ulong rights,bool revoke_grant)1120 int mysql_table_grant(THD *thd, TABLE_LIST *table_list,
1121 List <LEX_USER> &user_list,
1122 List <LEX_COLUMN> &columns, ulong rights,
1123 bool revoke_grant)
1124 {
1125 ulong column_priv= 0;
1126 List_iterator <LEX_USER> str_list (user_list);
1127 LEX_USER *Str, *tmp_Str;
1128 TABLE_LIST tables[3];
1129 bool create_new_users=0;
1130 const char *db_name, *table_name;
1131 bool save_binlog_row_based;
1132 bool transactional_tables;
1133 ulong what_to_set= 0;
1134 bool is_privileged_user= false;
1135
1136 DBUG_ENTER("mysql_table_grant");
1137
1138 if (!initialized)
1139 {
1140 my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0),
1141 "--skip-grant-tables"); /* purecov: inspected */
1142 DBUG_RETURN(TRUE); /* purecov: inspected */
1143 }
1144 if (rights & ~TABLE_ACLS)
1145 {
1146 my_message(ER_ILLEGAL_GRANT_FOR_TABLE, ER(ER_ILLEGAL_GRANT_FOR_TABLE),
1147 MYF(0));
1148 DBUG_RETURN(TRUE);
1149 }
1150
1151 if (!revoke_grant)
1152 {
1153 if (columns.elements)
1154 {
1155 class LEX_COLUMN *column;
1156 List_iterator <LEX_COLUMN> column_iter(columns);
1157
1158 if (open_tables_for_query(thd, table_list, 0))
1159 DBUG_RETURN(TRUE);
1160
1161 if (table_list->is_view())
1162 {
1163 if (table_list->resolve_derived(thd, false))
1164 DBUG_RETURN(true); /* purecov: inspected */
1165
1166 // Prepare a readonly (materialized) view for access to columns
1167 if (table_list->setup_materialized_derived(thd))
1168 DBUG_RETURN(true); /* purecov: inspected */
1169 }
1170 while ((column = column_iter++))
1171 {
1172 uint unused_field_idx= NO_CACHED_FIELD_INDEX;
1173 TABLE_LIST *dummy;
1174 Field *f=find_field_in_table_ref(thd, table_list, column->column.ptr(),
1175 column->column.length(),
1176 column->column.ptr(), NULL, NULL,
1177 NULL,
1178 // check that we have the
1179 // to-be-granted privilege:
1180 column->rights,
1181 false,
1182 &unused_field_idx, false, &dummy);
1183 if (f == (Field*)0)
1184 {
1185 my_error(ER_BAD_FIELD_ERROR, MYF(0),
1186 column->column.c_ptr(), table_list->alias);
1187 DBUG_RETURN(TRUE);
1188 }
1189 if (f == (Field *)-1)
1190 DBUG_RETURN(TRUE);
1191 column_priv|= column->rights;
1192 }
1193 close_mysql_tables(thd);
1194 }
1195 else
1196 {
1197 if (!(rights & CREATE_ACL))
1198 {
1199 char buf[FN_REFLEN + 1];
1200 build_table_filename(buf, sizeof(buf) - 1, table_list->db,
1201 table_list->table_name, reg_ext, 0);
1202 fn_format(buf, buf, "", "", MY_UNPACK_FILENAME | MY_RESOLVE_SYMLINKS |
1203 MY_RETURN_REAL_PATH | MY_APPEND_EXT);
1204 if (access(buf,F_OK))
1205 {
1206 my_error(ER_NO_SUCH_TABLE, MYF(0), table_list->db, table_list->alias);
1207 DBUG_RETURN(TRUE);
1208 }
1209 }
1210 ulong missing_privilege= rights & ~table_list->grant.privilege;
1211 assert(missing_privilege == table_list->grant.want_privilege);
1212 if (missing_privilege)
1213 {
1214 char command[128];
1215 get_privilege_desc(command, sizeof(command), missing_privilege);
1216 my_error(ER_TABLEACCESS_DENIED_ERROR, MYF(0),
1217 command, thd->security_context()->priv_user().str,
1218 thd->security_context()->host_or_ip().str, table_list->alias);
1219 DBUG_RETURN(true);
1220 }
1221 }
1222 }
1223
1224 /* open the mysql.tables_priv and mysql.columns_priv tables */
1225
1226 tables[0].init_one_table(C_STRING_WITH_LEN("mysql"),
1227 C_STRING_WITH_LEN("user"), "user", TL_WRITE);
1228 tables[1].init_one_table(C_STRING_WITH_LEN("mysql"),
1229 C_STRING_WITH_LEN("tables_priv"),
1230 "tables_priv", TL_WRITE);
1231 tables[2].init_one_table(C_STRING_WITH_LEN("mysql"),
1232 C_STRING_WITH_LEN("columns_priv"),
1233 "columns_priv", TL_WRITE);
1234 tables[0].next_local= tables[0].next_global= tables+1;
1235 /* Don't open column table if we don't need it ! */
1236 if (column_priv || (revoke_grant && ((rights & COL_ACLS) || columns.elements)))
1237 tables[1].next_local= tables[1].next_global= tables+2;
1238
1239 /*
1240 This statement will be replicated as a statement, even when using
1241 row-based replication. The flag will be reset at the end of the
1242 statement.
1243 */
1244 if ((save_binlog_row_based= thd->is_current_stmt_binlog_format_row()))
1245 thd->clear_current_stmt_binlog_format_row();
1246
1247 #ifdef HAVE_REPLICATION
1248 /*
1249 GRANT and REVOKE are applied the slave in/exclusion rules as they are
1250 some kind of updates to the mysql.% tables.
1251 */
1252 if (thd->slave_thread && rpl_filter->is_on())
1253 {
1254 /*
1255 The tables must be marked "updating" so that tables_ok() takes them into
1256 account in tests.
1257 */
1258 tables[0].updating= tables[1].updating= tables[2].updating= 1;
1259 if (!(thd->sp_runtime_ctx || rpl_filter->tables_ok(0, tables)))
1260 {
1261 /* Restore the state of binlog format */
1262 assert(!thd->is_current_stmt_binlog_format_row());
1263 if (save_binlog_row_based)
1264 thd->set_current_stmt_binlog_format_row();
1265 DBUG_RETURN(FALSE);
1266 }
1267 }
1268 #endif /* HAVE_REPLICATION */
1269
1270 /*
1271 The lock api is depending on the thd->lex variable which needs to be
1272 re-initialized.
1273 */
1274 Query_tables_list backup;
1275 thd->lex->reset_n_backup_query_tables_list(&backup);
1276 /*
1277 Restore Query_tables_list::sql_command value, which was reset
1278 above, as the code writing query to the binary log assumes that
1279 this value corresponds to the statement being executed.
1280 */
1281 thd->lex->sql_command= backup.sql_command;
1282 if (open_and_lock_tables(thd, tables, MYSQL_LOCK_IGNORE_TIMEOUT))
1283 { // Should never happen
1284 /* Restore the state of binlog format */
1285 assert(!thd->is_current_stmt_binlog_format_row());
1286 thd->lex->restore_backup_query_tables_list(&backup);
1287 if (save_binlog_row_based)
1288 thd->set_current_stmt_binlog_format_row();
1289 DBUG_RETURN(TRUE); /* purecov: deadcode */
1290 }
1291
1292 transactional_tables= (tables[0].table->file->has_transactions() ||
1293 tables[1].table->file->has_transactions() ||
1294 (tables[2].table &&
1295 tables[2].table->file->has_transactions()));
1296
1297 if (!revoke_grant)
1298 create_new_users= test_if_create_new_users(thd);
1299 bool result= FALSE;
1300 bool is_partial_execution= false;
1301
1302 is_privileged_user= is_privileged_user_for_credential_change(thd);
1303
1304 Partitioned_rwlock_write_guard lock(&LOCK_grant);
1305 mysql_mutex_lock(&acl_cache->lock);
1306 MEM_ROOT *old_root= thd->mem_root;
1307 thd->mem_root= &memex;
1308 grant_version++;
1309
1310 bool rollback_whole_statement= false;
1311 while ((tmp_Str = str_list++))
1312 {
1313 int error;
1314 GRANT_TABLE *grant_table;
1315
1316 if (!(Str= get_current_user(thd, tmp_Str)))
1317 {
1318 result= TRUE;
1319 continue;
1320 }
1321
1322 if (set_and_validate_user_attributes(thd, Str, what_to_set,
1323 is_privileged_user,
1324 revoke_grant?"REVOKE":"GRANT"))
1325 {
1326 result= TRUE;
1327 continue;
1328 }
1329
1330 ACL_USER *acl_user= find_acl_user(Str->host.str, Str->user.str, TRUE);
1331
1332 /* Create user if needed */
1333 error= replace_user_table(thd, tables[0].table, Str,
1334 0, revoke_grant, create_new_users,
1335 what_to_set);
1336 /*
1337 If the user did not exist and replace_user_table() succeeded and if this
1338 is a GRANT statement, then it means that a new user is created.
1339
1340 So, set the is_partial_execution flag to true.
1341 */
1342 if (!error)
1343 is_partial_execution= (!acl_user && !revoke_grant) || is_partial_execution;
1344
1345 if (error > 0)
1346 {
1347 result= TRUE; // Remember error
1348 continue; // Add next user
1349 }
1350 else if (error < 0)
1351 {
1352 rollback_whole_statement= true;
1353 result= true;
1354 break;
1355 }
1356 db_name= table_list->get_db_name();
1357 thd->add_to_binlog_accessed_dbs(db_name); // collecting db:s for MTS
1358 table_name= table_list->get_table_name();
1359
1360 /* Find/create cached table grant */
1361 grant_table= table_hash_search(Str->host.str, NullS, db_name,
1362 Str->user.str, table_name, 1);
1363 if (!grant_table)
1364 {
1365 if (revoke_grant)
1366 {
1367 my_error(ER_NONEXISTING_TABLE_GRANT, MYF(0),
1368 Str->user.str, Str->host.str, table_list->table_name);
1369 result= TRUE;
1370 continue;
1371 }
1372 grant_table = new GRANT_TABLE (Str->host.str, db_name,
1373 Str->user.str, table_name,
1374 rights,
1375 column_priv);
1376 if (!grant_table ||
1377 my_hash_insert(&column_priv_hash,(uchar*) grant_table))
1378 {
1379 rollback_whole_statement= true;
1380 result= TRUE; /* purecov: deadcode */
1381 break; /* purecov: deadcode */
1382 }
1383 }
1384
1385 /* If revoke_grant, calculate the new column privilege for tables_priv */
1386 if (revoke_grant)
1387 {
1388 class LEX_COLUMN *column;
1389 List_iterator <LEX_COLUMN> column_iter(columns);
1390 GRANT_COLUMN *grant_column;
1391
1392 /* Fix old grants */
1393 while ((column = column_iter++))
1394 {
1395 grant_column = column_hash_search(grant_table,
1396 column->column.ptr(),
1397 column->column.length());
1398 if (grant_column)
1399 grant_column->rights&= ~(column->rights | rights);
1400 }
1401 /* scan trough all columns to get new column grant */
1402 column_priv= 0;
1403 for (uint idx=0 ; idx < grant_table->hash_columns.records ; idx++)
1404 {
1405 grant_column= (GRANT_COLUMN*)
1406 my_hash_element(&grant_table->hash_columns, idx);
1407 grant_column->rights&= ~rights; // Fix other columns
1408 column_priv|= grant_column->rights;
1409 }
1410 }
1411 else
1412 {
1413 column_priv|= grant_table->cols;
1414 }
1415
1416 /* update table and columns */
1417
1418 error= replace_table_table(thd, grant_table, tables[1].table, *Str,
1419 db_name, table_name,
1420 rights, column_priv, revoke_grant);
1421
1422 if (error > 0)
1423 {
1424 result= true;
1425 continue;
1426 }
1427 else if (error < 0)
1428 {
1429 rollback_whole_statement= true;
1430 result= true;
1431 break;
1432 }
1433
1434 if (tables[2].table)
1435 {
1436 error= replace_column_table(grant_table, tables[2].table, *Str,
1437 columns,
1438 db_name, table_name,
1439 rights, revoke_grant);
1440 if (error > 0)
1441 {
1442 result= true;
1443 continue;
1444 }
1445 else if (error < 0)
1446 {
1447 rollback_whole_statement= true;
1448 result= true;
1449 break;
1450 }
1451 }
1452 is_partial_execution= true;
1453 }
1454 thd->mem_root= old_root;
1455 mysql_mutex_unlock(&acl_cache->lock);
1456
1457 /*
1458 We only log "complete" successful commands, because partially
1459 failed REVOKE/GRANTS that fail because of insufficient privileges
1460 on the master, will succeed on the slave due to SQL thread SUPER
1461 privilege. Even though replication will stop (the error code from
1462 the master will mismatch the error code on the slave), the
1463 operation will already be executed (thence revoking or granting
1464 additional privileges on the slave).
1465 Before ACLs are changed to execute fully or none at all, when
1466 some error happens, write an incident if one or more users are
1467 granted/revoked successfully (it has a partial execution).
1468 */
1469 if (result)
1470 {
1471 if (!rollback_whole_statement || !transactional_tables)
1472 {
1473 if (is_partial_execution)
1474 {
1475 const char* err_msg= "REVOKE/GRANT failed while storing table level "
1476 "and column level grants in the privilege tables.";
1477 mysql_bin_log.write_incident(thd, true /* need_lock_log=true */,
1478 err_msg);
1479 }
1480 }
1481 }
1482 else
1483 {
1484 /*
1485 Rewrite (table) GRANT statements to use password hashes
1486 instead of <secret> style obfuscation so it can be used
1487 in binlog.
1488 */
1489 if (!revoke_grant) {
1490 String rlb;
1491 mysql_rewrite_grant(thd, &rlb);
1492 thd->swap_rewritten_query(rlb);
1493 }
1494
1495 if (thd->rewritten_query().length() > 0)
1496 result= result |
1497 write_bin_log(thd, FALSE, thd->rewritten_query().ptr(),
1498 thd->rewritten_query().length(),
1499 transactional_tables);
1500 else
1501 result= result |
1502 write_bin_log(thd, FALSE, thd->query().str, thd->query().length,
1503 transactional_tables);
1504 }
1505
1506 lock.unlock();
1507
1508 result|=
1509 acl_end_trans_and_close_tables(thd,
1510 thd->transaction_rollback_request ||
1511 rollback_whole_statement);
1512
1513 if (!result) /* success */
1514 {
1515 acl_notify_htons(thd, thd->query().str, thd->query().length);
1516 my_ok(thd);
1517 }
1518
1519 thd->lex->restore_backup_query_tables_list(&backup);
1520 /* Restore the state of binlog format */
1521 assert(!thd->is_current_stmt_binlog_format_row());
1522 if (save_binlog_row_based)
1523 thd->set_current_stmt_binlog_format_row();
1524 DBUG_RETURN(result);
1525 }
1526
1527
1528 /**
1529 Store routine level grants in the privilege tables
1530
1531 @param thd Thread handle
1532 @param table_list List of routines to give grant
1533 @param is_proc Is this a list of procedures?
1534 @param user_list List of users to give grant
1535 @param rights Table level grant
1536 @param revoke_grant Is this is a REVOKE command?
1537
1538 @return
1539 @retval FALSE Success.
1540 @retval TRUE An error occurred.
1541 */
1542
mysql_routine_grant(THD * thd,TABLE_LIST * table_list,bool is_proc,List<LEX_USER> & user_list,ulong rights,bool revoke_grant,bool write_to_binlog)1543 bool mysql_routine_grant(THD *thd, TABLE_LIST *table_list, bool is_proc,
1544 List <LEX_USER> &user_list, ulong rights,
1545 bool revoke_grant, bool write_to_binlog)
1546 {
1547 List_iterator <LEX_USER> str_list (user_list);
1548 LEX_USER *Str, *tmp_Str;
1549 TABLE_LIST tables[2];
1550 bool create_new_users=0, result=0;
1551 const char *db_name, *table_name;
1552 bool save_binlog_row_based;
1553 bool transactional_tables;
1554 ulong what_to_set= 0;
1555 bool is_privileged_user= false;
1556
1557 DBUG_ENTER("mysql_routine_grant");
1558
1559 if (!initialized)
1560 {
1561 my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0),
1562 "--skip-grant-tables");
1563 DBUG_RETURN(TRUE);
1564 }
1565 if (rights & ~PROC_ACLS)
1566 {
1567 my_message(ER_ILLEGAL_GRANT_FOR_TABLE, ER(ER_ILLEGAL_GRANT_FOR_TABLE),
1568 MYF(0));
1569 DBUG_RETURN(TRUE);
1570 }
1571
1572 if (!revoke_grant)
1573 {
1574 if (sp_exist_routines(thd, table_list, is_proc))
1575 DBUG_RETURN(TRUE);
1576 }
1577
1578 /* open the mysql.user and mysql.procs_priv tables */
1579
1580 tables[0].init_one_table(C_STRING_WITH_LEN("mysql"),
1581 C_STRING_WITH_LEN("user"), "user", TL_WRITE);
1582 tables[1].init_one_table(C_STRING_WITH_LEN("mysql"),
1583 C_STRING_WITH_LEN("procs_priv"), "procs_priv", TL_WRITE);
1584 tables[0].next_local= tables[0].next_global= tables+1;
1585
1586 /*
1587 This statement will be replicated as a statement, even when using
1588 row-based replication. The flag will be reset at the end of the
1589 statement.
1590 */
1591 if ((save_binlog_row_based= thd->is_current_stmt_binlog_format_row()))
1592 thd->clear_current_stmt_binlog_format_row();
1593
1594 #ifdef HAVE_REPLICATION
1595 /*
1596 GRANT and REVOKE are applied the slave in/exclusion rules as they are
1597 some kind of updates to the mysql.% tables.
1598 */
1599 if (thd->slave_thread && rpl_filter->is_on())
1600 {
1601 /*
1602 The tables must be marked "updating" so that tables_ok() takes them into
1603 account in tests.
1604 */
1605 tables[0].updating= tables[1].updating= 1;
1606 if (!(thd->sp_runtime_ctx || rpl_filter->tables_ok(0, tables)))
1607 {
1608 /* Restore the state of binlog format */
1609 assert(!thd->is_current_stmt_binlog_format_row());
1610 if (save_binlog_row_based)
1611 thd->set_current_stmt_binlog_format_row();
1612 DBUG_RETURN(FALSE);
1613 }
1614 }
1615 #endif /* HAVE_REPLICATION */
1616
1617 if (open_and_lock_tables(thd, tables, MYSQL_LOCK_IGNORE_TIMEOUT))
1618 { // Should never happen
1619 /* Restore the state of binlog format */
1620 assert(!thd->is_current_stmt_binlog_format_row());
1621 if (save_binlog_row_based)
1622 thd->set_current_stmt_binlog_format_row();
1623 DBUG_RETURN(TRUE);
1624 }
1625
1626 transactional_tables= (tables[0].table->file->has_transactions() ||
1627 tables[1].table->file->has_transactions());
1628
1629 if (!revoke_grant)
1630 create_new_users= test_if_create_new_users(thd);
1631
1632 is_privileged_user= is_privileged_user_for_credential_change(thd);
1633 Partitioned_rwlock_write_guard lock(&LOCK_grant);
1634 mysql_mutex_lock(&acl_cache->lock);
1635 MEM_ROOT *old_root= thd->mem_root;
1636 thd->mem_root= &memex;
1637
1638 DBUG_PRINT("info",("now time to iterate and add users"));
1639
1640 bool is_partial_execution= false;
1641 bool rollback_whole_statement= false;
1642 while ((tmp_Str= str_list++))
1643 {
1644 int error;
1645 GRANT_NAME *grant_name;
1646
1647 if (!(Str= get_current_user(thd, tmp_Str)))
1648 {
1649 result= TRUE;
1650 continue;
1651 }
1652
1653 if (set_and_validate_user_attributes(thd, Str, what_to_set,
1654 is_privileged_user,
1655 revoke_grant?"REVOKE":"GRANT"))
1656 {
1657 result= TRUE;
1658 continue;
1659 }
1660
1661 ACL_USER *acl_user= find_acl_user(Str->host.str, Str->user.str, TRUE);
1662
1663 /* Create user if needed */
1664 error= replace_user_table(thd, tables[0].table, Str,
1665 0, revoke_grant, create_new_users,
1666 what_to_set);
1667 /*
1668 If the user did not exist and replace_user_table() succeeded and if this
1669 is a GRANT statement, then it means that a new user is created.
1670
1671 So, set the is_partial_execution flag to true.
1672 */
1673 if (!error)
1674 is_partial_execution= (!acl_user && !revoke_grant) || is_partial_execution;
1675
1676 if (error > 0)
1677 {
1678 result= TRUE; // Remember error
1679 continue; // Add next user
1680 }
1681 else if (error < 0)
1682 {
1683 rollback_whole_statement= true;
1684 result= true;
1685 break;
1686 }
1687 db_name= table_list->db;
1688 if (write_to_binlog)
1689 thd->add_to_binlog_accessed_dbs(db_name);
1690 table_name= table_list->table_name;
1691 grant_name= routine_hash_search(Str->host.str, NullS, db_name,
1692 Str->user.str, table_name, is_proc, 1);
1693 if (!grant_name)
1694 {
1695 if (revoke_grant)
1696 {
1697 my_error(ER_NONEXISTING_PROC_GRANT, MYF(0),
1698 Str->user.str, Str->host.str, table_name);
1699 result= TRUE;
1700 continue;
1701 }
1702 grant_name= new GRANT_NAME(Str->host.str, db_name,
1703 Str->user.str, table_name,
1704 rights, TRUE);
1705 if (!grant_name ||
1706 my_hash_insert(is_proc ?
1707 &proc_priv_hash : &func_priv_hash,(uchar*) grant_name))
1708 {
1709 result= TRUE;
1710 rollback_whole_statement= true;
1711 break;
1712 }
1713 }
1714
1715 error= replace_routine_table(thd, grant_name, tables[1].table, *Str,
1716 db_name, table_name, is_proc, rights,
1717 revoke_grant);
1718 if (error > 0)
1719 {
1720 result= TRUE;
1721 continue;
1722 }
1723 else if (error < 0)
1724 {
1725 result= true;
1726 rollback_whole_statement= true;
1727 break;
1728 }
1729 is_partial_execution= true;
1730 }
1731 thd->mem_root= old_root;
1732 mysql_mutex_unlock(&acl_cache->lock);
1733
1734 if (write_to_binlog)
1735 {
1736 /*
1737 Before ACLs are changed to execute fully or none at all, when
1738 some error happens, write an incident if one or more users are
1739 granted/revoked successfully (it has a partial execution).
1740 */
1741 if (result)
1742 {
1743 if (!rollback_whole_statement || !transactional_tables)
1744 {
1745 if (is_partial_execution)
1746 {
1747 const char* err_msg= "REVOKE/GRANT failed while storing routine "
1748 "level grants in the privilege tables.";
1749 mysql_bin_log.write_incident(thd, true /* need_lock_log=true */,
1750 err_msg);
1751 }
1752 }
1753 }
1754 else
1755 {
1756 /*
1757 Rewrite (routine) GRANT statements to use password hashes
1758 instead of <secret> style obfuscation so it can be used
1759 in binlog.
1760 */
1761 if (!revoke_grant) {
1762 String rlb;
1763 mysql_rewrite_grant(thd, &rlb);
1764 thd->swap_rewritten_query(rlb);
1765 }
1766
1767 /*
1768 For performance reasons, we don't rewrite the query if we don't have to.
1769 If that was the case, write the original query.
1770 */
1771 if (thd->rewritten_query().length() == 0)
1772 {
1773 if (write_bin_log(thd, false, thd->query().str, thd->query().length,
1774 transactional_tables))
1775 result= TRUE;
1776 }
1777 else
1778 {
1779 if (write_bin_log(thd, false, thd->rewritten_query().ptr(),
1780 thd->rewritten_query().length(),
1781 transactional_tables))
1782 result= TRUE;
1783 }
1784 }
1785 }
1786
1787 lock.unlock();
1788
1789 result|=
1790 acl_end_trans_and_close_tables(thd,
1791 thd->transaction_rollback_request ||
1792 rollback_whole_statement);
1793
1794 if (write_to_binlog && !result)
1795 acl_notify_htons(thd, thd->query().str, thd->query().length);
1796
1797 /* Restore the state of binlog format */
1798 assert(!thd->is_current_stmt_binlog_format_row());
1799 if (save_binlog_row_based)
1800 thd->set_current_stmt_binlog_format_row();
1801
1802 DBUG_RETURN(result);
1803 }
1804
1805
mysql_grant(THD * thd,const char * db,List<LEX_USER> & list,ulong rights,bool revoke_grant,bool is_proxy)1806 bool mysql_grant(THD *thd, const char *db, List <LEX_USER> &list,
1807 ulong rights, bool revoke_grant, bool is_proxy)
1808 {
1809 List_iterator <LEX_USER> str_list (list);
1810 LEX_USER *Str, *tmp_Str, *proxied_user= NULL;
1811 char tmp_db[NAME_LEN+1];
1812 bool create_new_users=0;
1813 TABLE_LIST tables[2];
1814 bool save_binlog_row_based;
1815 bool transactional_tables;
1816 ulong what_to_set= 0;
1817 bool is_privileged_user= false;
1818
1819 DBUG_ENTER("mysql_grant");
1820 if (!initialized)
1821 {
1822 my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0),
1823 "--skip-grant-tables"); /* purecov: tested */
1824 DBUG_RETURN(TRUE); /* purecov: tested */
1825 }
1826
1827 if (lower_case_table_names && db)
1828 {
1829 my_stpnmov(tmp_db,db,NAME_LEN);
1830 tmp_db[NAME_LEN]= '\0';
1831 my_casedn_str(files_charset_info, tmp_db);
1832 db=tmp_db;
1833 }
1834
1835 if (is_proxy)
1836 {
1837 assert(!db);
1838 proxied_user= str_list++;
1839 }
1840
1841 /* open the mysql.user and mysql.db or mysql.proxies_priv tables */
1842 tables[0].init_one_table(C_STRING_WITH_LEN("mysql"),
1843 C_STRING_WITH_LEN("user"), "user", TL_WRITE);
1844 if (is_proxy)
1845
1846 tables[1].init_one_table(C_STRING_WITH_LEN("mysql"),
1847 C_STRING_WITH_LEN("proxies_priv"),
1848 "proxies_priv",
1849 TL_WRITE);
1850 else
1851 tables[1].init_one_table(C_STRING_WITH_LEN("mysql"),
1852 C_STRING_WITH_LEN("db"),
1853 "db",
1854 TL_WRITE);
1855 tables[0].next_local= tables[0].next_global= tables+1;
1856
1857 /*
1858 This statement will be replicated as a statement, even when using
1859 row-based replication. The flag will be reset at the end of the
1860 statement.
1861 */
1862 if ((save_binlog_row_based= thd->is_current_stmt_binlog_format_row()))
1863 thd->clear_current_stmt_binlog_format_row();
1864
1865 #ifdef HAVE_REPLICATION
1866 /*
1867 GRANT and REVOKE are applied the slave in/exclusion rules as they are
1868 some kind of updates to the mysql.% tables.
1869 */
1870 if (thd->slave_thread && rpl_filter->is_on())
1871 {
1872 /*
1873 The tables must be marked "updating" so that tables_ok() takes them into
1874 account in tests.
1875 */
1876 tables[0].updating= tables[1].updating= 1;
1877 if (!(thd->sp_runtime_ctx || rpl_filter->tables_ok(0, tables)))
1878 {
1879 /* Restore the state of binlog format */
1880 assert(!thd->is_current_stmt_binlog_format_row());
1881 if (save_binlog_row_based)
1882 thd->set_current_stmt_binlog_format_row();
1883 DBUG_RETURN(FALSE);
1884 }
1885 }
1886 #endif /*HAVE_REPLICATION */
1887
1888 if (open_and_lock_tables(thd, tables, MYSQL_LOCK_IGNORE_TIMEOUT))
1889 { // This should never happen
1890 /* Restore the state of binlog format */
1891 assert(!thd->is_current_stmt_binlog_format_row());
1892 if (save_binlog_row_based)
1893 thd->set_current_stmt_binlog_format_row();
1894 DBUG_RETURN(TRUE); /* purecov: deadcode */
1895 }
1896
1897 transactional_tables= (tables[0].table->file->has_transactions() ||
1898 tables[1].table->file->has_transactions());
1899
1900 if (!revoke_grant)
1901 create_new_users= test_if_create_new_users(thd);
1902
1903 is_privileged_user= is_privileged_user_for_credential_change(thd);
1904 /* go through users in user_list */
1905 Partitioned_rwlock_write_guard lock(&LOCK_grant);
1906 mysql_mutex_lock(&acl_cache->lock);
1907 grant_version++;
1908
1909 int result= 0;
1910 bool is_partial_execution= false;
1911 bool rollback_whole_statement= false;
1912 while ((tmp_Str = str_list++))
1913 {
1914 if (!(Str= get_current_user(thd, tmp_Str)))
1915 {
1916 result= TRUE;
1917 continue;
1918 }
1919
1920 if (set_and_validate_user_attributes(thd, Str, what_to_set,
1921 is_privileged_user,
1922 revoke_grant?"REVOKE":"GRANT"))
1923 {
1924 result= TRUE;
1925 continue;
1926 }
1927
1928 ACL_USER *acl_user= find_acl_user(Str->host.str, Str->user.str, TRUE);
1929 int ret= replace_user_table(thd, tables[0].table, Str,
1930 (!db ? rights : 0), revoke_grant,
1931 create_new_users,
1932 (what_to_set | ACCESS_RIGHTS_ATTR));
1933 /*
1934 If the user did not exist and replace_user_table() succeeded and if
1935 this is a GRANT statement, then it means that a new user is created.
1936 So, set the is_partial_execution flag to true.
1937 */
1938 if (!ret)
1939 {
1940 /* In case of GRANT, user creation is partial execution */
1941 is_partial_execution= (!acl_user && !revoke_grant) || is_partial_execution;
1942 }
1943
1944 if (ret)
1945 {
1946 result= -1;
1947 if (ret < 0)
1948 {
1949 /*
1950 If error in storage egine or system error happen then
1951 it doesn't make sense to continue handling of statement's
1952 arguments (users list). In this case leave a loop, rollback
1953 the whole statement and return an error.
1954 */
1955 rollback_whole_statement= true;
1956 break;
1957 }
1958 continue;
1959 }
1960 else if (db)
1961 {
1962 ulong db_rights= rights & DB_ACLS;
1963 if (db_rights == rights)
1964 {
1965 ret= replace_db_table(tables[1].table, db, *Str, db_rights,
1966 revoke_grant);
1967 if (ret)
1968 {
1969 result= -1;
1970 if (ret < 0)
1971 {
1972 /*
1973 If error in storage egine or system error happen then
1974 it doesn't make sense to continue handling of statement's
1975 arguments (users list). In this case leave a loop, rollback
1976 the whole statement and return an error.
1977 */
1978 rollback_whole_statement= true;
1979 break;
1980 }
1981 continue;
1982 }
1983 thd->add_to_binlog_accessed_dbs(db);
1984 }
1985 else
1986 {
1987 my_error(ER_WRONG_USAGE, MYF(0), "DB GRANT", "GLOBAL PRIVILEGES");
1988 result= -1;
1989 continue;
1990 }
1991 }
1992 else if (is_proxy)
1993 {
1994 ret= replace_proxies_priv_table(thd, tables[1].table, Str, proxied_user,
1995 rights & GRANT_ACL ? true : false,
1996 revoke_grant);
1997 if (ret)
1998 {
1999 result= -1;
2000 if (ret < 0)
2001 {
2002 /*
2003 If error in storage egine or system error happen then
2004 it doesn't make sense to continue handling of statement's
2005 arguments (users list). In this case leave a loop, rollback
2006 the whole statement and return an error.
2007 */
2008 rollback_whole_statement= true;
2009 break;
2010 }
2011 continue;
2012 }
2013 }
2014 is_partial_execution= true;
2015 }
2016 mysql_mutex_unlock(&acl_cache->lock);
2017
2018 /*
2019 Before ACLs are changed to execute fully or none at all, when
2020 some error happens, write an incident if one or more users are
2021 granted/revoked successfully (it has a partial execution).
2022 */
2023 if (result)
2024 {
2025 if (!rollback_whole_statement || !transactional_tables)
2026 {
2027 if (is_partial_execution)
2028 {
2029 const char* err_msg= "REVOKE/GRANT failed while granting/revoking "
2030 "privileges in databases.";
2031 mysql_bin_log.write_incident(thd, true /* need_lock_log=true */,
2032 err_msg);
2033 }
2034 }
2035 }
2036 else
2037 {
2038 /*
2039 Rewrite GRANT statements to use password hashes instead of
2040 <secret> style obfuscation so it can be used in binlog.
2041 */
2042 if (!revoke_grant) {
2043 String rlb;
2044 mysql_rewrite_grant(thd, &rlb);
2045 thd->swap_rewritten_query(rlb);
2046 }
2047
2048 if (thd->rewritten_query().length() > 0) {
2049 result= result |
2050 write_bin_log(thd, FALSE, thd->rewritten_query().ptr(),
2051 thd->rewritten_query().length(),
2052 transactional_tables);
2053 }
2054 else
2055 result= result |
2056 write_bin_log(thd, FALSE, thd->query().str, thd->query().length,
2057 transactional_tables);
2058 }
2059
2060 lock.unlock();
2061
2062 result|=
2063 acl_end_trans_and_close_tables(thd,
2064 thd->transaction_rollback_request ||
2065 rollback_whole_statement);
2066
2067 if (!result)
2068 {
2069 acl_notify_htons(thd, thd->query().str, thd->query().length);
2070 my_ok(thd);
2071 }
2072
2073 /* Restore the state of binlog format */
2074 assert(!thd->is_current_stmt_binlog_format_row());
2075 if (save_binlog_row_based)
2076 thd->set_current_stmt_binlog_format_row();
2077
2078 DBUG_RETURN(result);
2079 }
2080
2081
2082
2083
2084 /**
2085 @brief Check table level grants
2086
2087 @param thd Thread handler
2088 @param want_access Bits of privileges user needs to have.
2089 @param tables List of tables to check. The user should have
2090 'want_access' to all tables in list.
2091 @param any_combination_will_do TRUE if it's enough to have any privilege for
2092 any combination of the table columns.
2093 @param number Check at most this number of tables.
2094 @param no_errors TRUE if no error should be sent directly to the client.
2095
2096 If table->grant.want_privilege != 0 then the requested privileges where
2097 in the set of COL_ACLS but access was not granted on the table level. As
2098 a consequence an extra check of column privileges is required.
2099
2100 Specifically if this function returns FALSE the user has some kind of
2101 privilege on a combination of columns in each table.
2102
2103 This function is usually preceeded by check_access which establish the
2104 User-, Db- and Host access rights.
2105
2106 @see check_access
2107 @see check_table_access
2108
2109 @note This functions assumes that either number of tables to be inspected
2110 by it is limited explicitly (i.e. is is not UINT_MAX) or table list
2111 used and thd->lex->query_tables_own_last value correspond to each
2112 other (the latter should be either 0 or point to next_global member
2113 of one of elements of this table list).
2114
2115 @return Access status
2116 @retval FALSE Access granted; But column privileges might need to be
2117 checked.
2118 @retval TRUE The user did not have the requested privileges on any of the
2119 tables.
2120
2121 */
2122
check_grant(THD * thd,ulong want_access,TABLE_LIST * tables,bool any_combination_will_do,uint number,bool no_errors)2123 bool check_grant(THD *thd, ulong want_access, TABLE_LIST *tables,
2124 bool any_combination_will_do, uint number, bool no_errors)
2125 {
2126 TABLE_LIST *tl;
2127 TABLE_LIST *const first_not_own_table= thd->lex->first_not_own_table();
2128 Security_context *sctx= thd->security_context();
2129 ulong orig_want_access= want_access;
2130 DBUG_ENTER("check_grant");
2131 assert(number > 0);
2132
2133 LOCK_grant_read_guard lock(thd);
2134 for (tl= tables;
2135 tl && number-- && tl != first_not_own_table;
2136 tl= tl->next_global)
2137 {
2138 TABLE_LIST *const t_ref=
2139 tl->correspondent_table ? tl->correspondent_table : tl;
2140 sctx = MY_TEST(t_ref->security_ctx) ? t_ref->security_ctx :
2141 thd->security_context();
2142
2143 const ACL_internal_table_access *access=
2144 get_cached_table_access(&t_ref->grant.m_internal,
2145 t_ref->get_db_name(),
2146 t_ref->get_table_name());
2147
2148 if (access)
2149 {
2150 switch(access->check(orig_want_access, &t_ref->grant.privilege))
2151 {
2152 case ACL_INTERNAL_ACCESS_GRANTED:
2153 /*
2154 Grant all access to the table to skip column checks.
2155 Depend on the controls in the P_S table itself.
2156 */
2157 t_ref->grant.privilege|= TMP_TABLE_ACLS;
2158 #ifndef NDEBUG
2159 t_ref->grant.want_privilege= 0;
2160 #endif
2161 continue;
2162 case ACL_INTERNAL_ACCESS_DENIED:
2163 goto err;
2164 case ACL_INTERNAL_ACCESS_CHECK_GRANT:
2165 break;
2166 }
2167 }
2168
2169 want_access= orig_want_access;
2170 want_access&= ~sctx->master_access();
2171 if (!want_access)
2172 continue; // ok
2173
2174 if (!(~t_ref->grant.privilege & want_access) ||
2175 t_ref->is_derived() || t_ref->schema_table)
2176 {
2177 /*
2178 It is subquery in the FROM clause. VIEW set t_ref->derived after
2179 table opening, but this function always called before table opening.
2180 */
2181 if (!t_ref->referencing_view)
2182 {
2183 /*
2184 If it's a temporary table created for a subquery in the FROM
2185 clause, or an INFORMATION_SCHEMA table, drop the request for
2186 a privilege.
2187 */
2188 #ifndef NDEBUG
2189 t_ref->grant.want_privilege= 0;
2190 #endif
2191 }
2192 continue;
2193 }
2194
2195 if (is_temporary_table(t_ref))
2196 {
2197 /*
2198 If this table list element corresponds to a pre-opened temporary
2199 table skip checking of all relevant table-level privileges for it.
2200 Note that during creation of temporary table we still need to check
2201 if user has CREATE_TMP_ACL.
2202 */
2203 t_ref->grant.privilege|= TMP_TABLE_ACLS;
2204 #ifndef NDEBUG
2205 t_ref->grant.want_privilege= 0;
2206 #endif
2207 continue;
2208 }
2209
2210 GRANT_TABLE *grant_table= table_hash_search(sctx->host().str,
2211 sctx->ip().str,
2212 t_ref->get_db_name(),
2213 sctx->priv_user().str,
2214 t_ref->get_table_name(),
2215 FALSE);
2216
2217 if (!grant_table)
2218 {
2219 want_access &= ~t_ref->grant.privilege;
2220 goto err; // No grants
2221 }
2222
2223 /*
2224 For SHOW COLUMNS, SHOW INDEX it is enough to have some
2225 privileges on any column combination on the table.
2226 */
2227 if (any_combination_will_do)
2228 continue;
2229
2230 t_ref->grant.grant_table= grant_table; // Remember for column test
2231 t_ref->grant.version= grant_version;
2232 t_ref->grant.privilege|= grant_table->privs;
2233 t_ref->set_want_privilege(want_access & COL_ACLS);
2234
2235 if (!(~t_ref->grant.privilege & want_access))
2236 continue;
2237
2238 if (want_access & ~(grant_table->cols | t_ref->grant.privilege))
2239 {
2240 want_access &= ~(grant_table->cols | t_ref->grant.privilege);
2241 goto err; // impossible
2242 }
2243 }
2244 DBUG_RETURN(FALSE);
2245
2246 err:
2247 lock.unlock();
2248 if (!no_errors) // Not a silent skip of table
2249 {
2250 char command[128];
2251 get_privilege_desc(command, sizeof(command), want_access);
2252 my_error(ER_TABLEACCESS_DENIED_ERROR, MYF(0),
2253 command,
2254 sctx->priv_user().str,
2255 sctx->host_or_ip().str,
2256 tl ? tl->get_table_name() : "unknown");
2257 }
2258 DBUG_RETURN(TRUE);
2259 }
2260
2261
2262 /*
2263 Check column rights in given security context
2264
2265 SYNOPSIS
2266 check_grant_column()
2267 thd thread handler
2268 grant grant information structure
2269 db_name db name
2270 table_name table name
2271 name column name
2272 length column name length
2273 sctx security context
2274 want_privilege wanted privileges
2275
2276 RETURN
2277 FALSE OK
2278 TRUE access denied
2279 */
2280
check_grant_column(THD * thd,GRANT_INFO * grant,const char * db_name,const char * table_name,const char * name,size_t length,Security_context * sctx,ulong want_privilege)2281 bool check_grant_column(THD *thd, GRANT_INFO *grant,
2282 const char *db_name, const char *table_name,
2283 const char *name, size_t length,
2284 Security_context *sctx, ulong want_privilege)
2285 {
2286 GRANT_TABLE *grant_table;
2287 GRANT_COLUMN *grant_column;
2288 DBUG_ENTER("check_grant_column");
2289 DBUG_PRINT("enter", ("table: %s want_privilege: %lu",
2290 table_name, want_privilege));
2291
2292 /*
2293 Make sure that the privilege request is aligned with the overall privileges
2294 granted to and requested for the table.
2295 */
2296 assert(!(want_privilege & ~(grant->want_privilege | grant->privilege)));
2297 // Adjust wanted privileges based on privileges granted to table:
2298 want_privilege&= ~grant->privilege;
2299 if (!want_privilege)
2300 DBUG_RETURN(0); // Already checked
2301
2302 LOCK_grant_read_guard lock(thd);
2303
2304 /* reload table if someone has modified any grants */
2305
2306 if (grant->version != grant_version)
2307 {
2308 grant->grant_table=
2309 table_hash_search(sctx->host().str, sctx->ip().str,
2310 db_name, sctx->priv_user().str,
2311 table_name, 0); /* purecov: inspected */
2312 grant->version= grant_version; /* purecov: inspected */
2313 }
2314 if (!(grant_table= grant->grant_table))
2315 goto err; /* purecov: deadcode */
2316
2317 grant_column=column_hash_search(grant_table, name, length);
2318 if (grant_column && !(~grant_column->rights & want_privilege))
2319 {
2320 DBUG_RETURN(0);
2321 }
2322
2323 err:
2324 lock.unlock();
2325 char command[128];
2326 get_privilege_desc(command, sizeof(command), want_privilege);
2327 my_error(ER_COLUMNACCESS_DENIED_ERROR, MYF(0),
2328 command,
2329 sctx->priv_user().str,
2330 sctx->host_or_ip().str,
2331 name,
2332 table_name);
2333 DBUG_RETURN(1);
2334 }
2335
2336
2337 /*
2338 Check the privileges to a column depending on the type of table.
2339
2340 SYNOPSIS
2341 check_column_grant_in_table_ref()
2342 thd thread handler
2343 table_ref table reference where to check the field
2344 name name of field to check
2345 length length of name
2346 want_privilege wanted privileges
2347
2348 DESCRIPTION
2349 Check the privileges to a column depending on the type of table
2350 reference where the column is checked. The function provides a
2351 generic interface to check column privileges that hides the
2352 heterogeneity of the column representation - whether it is a view
2353 or a stored table column.
2354
2355 Notice that this function does not understand that a column from a view
2356 reference must be checked for privileges both in the view and in the
2357 underlying base table (or view) reference. This is the responsibility of
2358 the caller.
2359
2360 RETURN
2361 FALSE OK
2362 TRUE access denied
2363 */
2364
check_column_grant_in_table_ref(THD * thd,TABLE_LIST * table_ref,const char * name,size_t length,ulong want_privilege)2365 bool check_column_grant_in_table_ref(THD *thd, TABLE_LIST * table_ref,
2366 const char *name, size_t length,
2367 ulong want_privilege)
2368 {
2369 GRANT_INFO *grant;
2370 const char *db_name;
2371 const char *table_name;
2372 Security_context *sctx= MY_TEST(table_ref->security_ctx) ?
2373 table_ref->security_ctx : thd->security_context();
2374
2375 assert(want_privilege);
2376
2377 if (table_ref->is_view() || table_ref->field_translation)
2378 {
2379 /* View or derived information schema table. */
2380 ulong view_privs;
2381 grant= &(table_ref->grant);
2382 db_name= table_ref->view_db.str;
2383 table_name= table_ref->view_name.str;
2384 if (table_ref->belong_to_view &&
2385 thd->lex->sql_command == SQLCOM_SHOW_FIELDS)
2386 {
2387 view_privs= get_column_grant(thd, grant, db_name, table_name, name);
2388 if (view_privs & VIEW_ANY_ACL)
2389 {
2390 table_ref->belong_to_view->allowed_show= TRUE;
2391 return FALSE;
2392 }
2393 table_ref->belong_to_view->allowed_show= FALSE;
2394 my_message(ER_VIEW_NO_EXPLAIN, ER(ER_VIEW_NO_EXPLAIN), MYF(0));
2395 return TRUE;
2396 }
2397 }
2398 else if (table_ref->nested_join)
2399 {
2400 bool error= FALSE;
2401 List_iterator<TABLE_LIST> it(table_ref->nested_join->join_list);
2402 TABLE_LIST *table;
2403 while (!error && (table= it++))
2404 error|= check_column_grant_in_table_ref(thd, table, name, length,
2405 want_privilege);
2406 return error;
2407 }
2408 else
2409 {
2410 /* Normal or temporary table. */
2411 TABLE *table= table_ref->table;
2412 grant= &(table->grant);
2413 db_name= table->s->db.str;
2414 table_name= table->s->table_name.str;
2415 }
2416
2417 return check_grant_column(thd, grant, db_name, table_name, name,
2418 length, sctx, want_privilege);
2419 }
2420
2421
2422 /**
2423 @brief check if a query can access a set of columns
2424
2425 @param thd the current thread
2426 @param want_access_arg the privileges requested
2427 @param fields an iterator over the fields of a table reference.
2428 @return Operation status
2429 @retval 0 Success
2430 @retval 1 Falure
2431 @details This function walks over the columns of a table reference
2432 The columns may originate from different tables, depending on the kind of
2433 table reference, e.g. join, view.
2434 For each table it will retrieve the grant information and will use it
2435 to check the required access privileges for the fields requested from it.
2436 */
check_grant_all_columns(THD * thd,ulong want_access_arg,Field_iterator_table_ref * fields)2437 bool check_grant_all_columns(THD *thd, ulong want_access_arg,
2438 Field_iterator_table_ref *fields)
2439 {
2440 Security_context *sctx= thd->security_context();
2441 ulong want_access= want_access_arg;
2442 const char *table_name= NULL;
2443
2444 const char* db_name;
2445 GRANT_INFO *grant;
2446 /* Initialized only to make gcc happy */
2447 GRANT_TABLE *grant_table= NULL;
2448 /*
2449 Flag that gets set if privilege checking has to be performed on column
2450 level.
2451 */
2452 bool using_column_privileges= FALSE;
2453
2454 LOCK_grant_read_guard lock(thd);
2455
2456 for (; !fields->end_of_fields(); fields->next())
2457 {
2458 const char *field_name= fields->name();
2459
2460 if (table_name != fields->get_table_name())
2461 {
2462 table_name= fields->get_table_name();
2463 db_name= fields->get_db_name();
2464 grant= fields->grant();
2465 /* get a fresh one for each table */
2466 want_access= want_access_arg & ~grant->privilege;
2467 if (want_access)
2468 {
2469 /* reload table if someone has modified any grants */
2470 if (grant->version != grant_version)
2471 {
2472 grant->grant_table=
2473 table_hash_search(sctx->host().str, sctx->ip().str,
2474 db_name, sctx->priv_user().str,
2475 table_name, 0); /* purecov: inspected */
2476 grant->version= grant_version; /* purecov: inspected */
2477 }
2478
2479 grant_table= grant->grant_table;
2480 assert (grant_table);
2481 }
2482 }
2483
2484 if (want_access)
2485 {
2486 GRANT_COLUMN *grant_column=
2487 column_hash_search(grant_table, field_name, strlen(field_name));
2488 if (grant_column)
2489 using_column_privileges= TRUE;
2490 if (!grant_column || (~grant_column->rights & want_access))
2491 goto err;
2492 }
2493 }
2494 return 0;
2495
2496 err:
2497 lock.unlock();
2498
2499 char command[128];
2500 get_privilege_desc(command, sizeof(command), want_access);
2501 /*
2502 Do not give an error message listing a column name unless the user has
2503 privilege to see all columns.
2504 */
2505 if (using_column_privileges)
2506 my_error(ER_TABLEACCESS_DENIED_ERROR, MYF(0),
2507 command, sctx->priv_user().str,
2508 sctx->host_or_ip().str, table_name);
2509 else
2510 my_error(ER_COLUMNACCESS_DENIED_ERROR, MYF(0),
2511 command,
2512 sctx->priv_user().str,
2513 sctx->host_or_ip().str,
2514 fields->name(),
2515 table_name);
2516 return 1;
2517 }
2518
2519
check_grant_db_routine(THD * thd,const char * db,HASH * hash)2520 static bool check_grant_db_routine(THD *thd, const char *db, HASH *hash)
2521 {
2522 Security_context *sctx= thd->security_context();
2523
2524 for (uint idx= 0; idx < hash->records; ++idx)
2525 {
2526 GRANT_NAME *item= (GRANT_NAME*) my_hash_element(hash, idx);
2527
2528 if (strcmp(item->user, sctx->priv_user().str) == 0 &&
2529 strcmp(item->db, db) == 0 &&
2530 item->host.compare_hostname(sctx->host().str,
2531 sctx->ip().str))
2532 {
2533 return FALSE;
2534 }
2535 }
2536
2537 return TRUE;
2538 }
2539
2540
2541 /*
2542 Check if a user has the right to access a database
2543 Access is accepted if he has a grant for any table/routine in the database
2544 Return 1 if access is denied
2545 */
2546
check_grant_db(THD * thd,const char * db)2547 bool check_grant_db(THD *thd,const char *db)
2548 {
2549 Security_context *sctx= thd->security_context();
2550 LEX_CSTRING priv_user= sctx->priv_user();
2551 char helping [NAME_LEN+USERNAME_LENGTH+2];
2552 uint len;
2553 bool error= TRUE;
2554 size_t copy_length;
2555
2556 /* Added 1 at the end to avoid buffer overflow at strmov()*/
2557 copy_length= ((priv_user.str ? strlen(priv_user.str) : 0) +
2558 (db ? strlen(db) : 0)) + 1;
2559
2560 /*
2561 Make sure that my_stpcpy() operations do not result in buffer overflow.
2562 */
2563 if (copy_length >= (NAME_LEN+USERNAME_LENGTH+2))
2564 return 1;
2565
2566 len= (uint) (my_stpcpy(my_stpcpy(helping, priv_user.str) + 1, db) -
2567 helping) + 1;
2568
2569 LOCK_grant_read_guard lock(thd);
2570
2571 for (uint idx=0 ; idx < column_priv_hash.records ; idx++)
2572 {
2573 GRANT_TABLE *grant_table= (GRANT_TABLE*)
2574 my_hash_element(&column_priv_hash,
2575 idx);
2576 if (len < grant_table->key_length &&
2577 !memcmp(grant_table->hash_key,helping,len) &&
2578 grant_table->host.compare_hostname(sctx->host().str,
2579 sctx->ip().str))
2580 {
2581 error= FALSE; /* Found match. */
2582 break;
2583 }
2584 }
2585
2586 if (error)
2587 error= check_grant_db_routine(thd, db, &proc_priv_hash) &&
2588 check_grant_db_routine(thd, db, &func_priv_hash);
2589
2590 return error;
2591 }
2592
2593
2594 /****************************************************************************
2595 Check routine level grants
2596
2597 SYNPOSIS
2598 bool check_grant_routine()
2599 thd Thread handler
2600 want_access Bits of privileges user needs to have
2601 procs List of routines to check. The user should have 'want_access'
2602 is_proc True if the list is all procedures, else functions
2603 no_errors If 0 then we write an error. The error is sent directly to
2604 the client
2605
2606 RETURN
2607 0 ok
2608 1 Error: User did not have the requested privielges
2609 ****************************************************************************/
2610
check_grant_routine(THD * thd,ulong want_access,TABLE_LIST * procs,bool is_proc,bool no_errors)2611 bool check_grant_routine(THD *thd, ulong want_access,
2612 TABLE_LIST *procs, bool is_proc, bool no_errors)
2613 {
2614 TABLE_LIST *table;
2615 Security_context *sctx= thd->security_context();
2616 char *user= (char *) sctx->priv_user().str;
2617 char *host= (char *) sctx->priv_host().str;
2618 DBUG_ENTER("check_grant_routine");
2619
2620 want_access&= ~sctx->master_access();
2621 if (!want_access)
2622 DBUG_RETURN(0); // ok
2623
2624 LOCK_grant_read_guard lock(thd);
2625
2626 for (table= procs; table; table= table->next_global)
2627 {
2628 GRANT_NAME *grant_proc;
2629 if ((grant_proc= routine_hash_search(host, sctx->ip().str, table->db, user,
2630 table->table_name, is_proc, 0)))
2631 table->grant.privilege|= grant_proc->privs;
2632
2633 if (want_access & ~table->grant.privilege)
2634 {
2635 want_access &= ~table->grant.privilege;
2636 goto err;
2637 }
2638 }
2639 DBUG_RETURN(0);
2640
2641 err:
2642 lock.unlock();
2643 if (!no_errors)
2644 {
2645 char buff[1024];
2646 const char *command="";
2647 if (table)
2648 strxmov(buff, table->db, ".", table->table_name, NullS);
2649 if (want_access & EXECUTE_ACL)
2650 command= "execute";
2651 else if (want_access & ALTER_PROC_ACL)
2652 command= "alter routine";
2653 else if (want_access & GRANT_ACL)
2654 command= "grant";
2655 my_error(ER_PROCACCESS_DENIED_ERROR, MYF(0),
2656 command, user, host, table ? buff : "unknown");
2657 }
2658 DBUG_RETURN(1);
2659 }
2660
2661
2662 /*
2663 Check if routine has any of the
2664 routine level grants
2665
2666 SYNPOSIS
2667 bool check_routine_level_acl()
2668 thd Thread handler
2669 db Database name
2670 name Routine name
2671
2672 RETURN
2673 0 Ok
2674 1 error
2675 */
2676
check_routine_level_acl(THD * thd,const char * db,const char * name,bool is_proc)2677 bool check_routine_level_acl(THD *thd, const char *db, const char *name,
2678 bool is_proc)
2679 {
2680 bool no_routine_acl= 1;
2681 GRANT_NAME *grant_proc;
2682 Security_context *sctx= thd->security_context();
2683
2684 LOCK_grant_read_guard lock(thd);
2685
2686 if ((grant_proc= routine_hash_search(sctx->priv_host().str,
2687 sctx->ip().str, db,
2688 sctx->priv_user().str,
2689 name, is_proc, 0)))
2690 no_routine_acl= !(grant_proc->privs & SHOW_PROC_ACLS);
2691 return no_routine_acl;
2692 }
2693
2694
2695 /*****************************************************************************
2696 Functions to retrieve the grant for a table/column (for SHOW functions)
2697 *****************************************************************************/
2698
get_table_grant(THD * thd,TABLE_LIST * table)2699 ulong get_table_grant(THD *thd, TABLE_LIST *table)
2700 {
2701 ulong privilege;
2702 Security_context *sctx= thd->security_context();
2703 const char *db = table->db ? table->db : thd->db().str;
2704 GRANT_TABLE *grant_table;
2705
2706 LOCK_grant_read_guard lock(thd);
2707
2708 #ifdef EMBEDDED_LIBRARY
2709 grant_table= NULL;
2710 #else
2711 grant_table= table_hash_search(sctx->host().str,
2712 sctx->ip().str, db, sctx->priv_user().str,
2713 table->table_name, 0);
2714 #endif /* EMBEDDED_LIBRARY */
2715 table->grant.grant_table=grant_table; // Remember for column test
2716 table->grant.version=grant_version;
2717 if (grant_table)
2718 table->grant.privilege|= grant_table->privs;
2719 privilege= table->grant.privilege;
2720 return privilege;
2721 }
2722
2723
2724 /*
2725 Determine the access priviliges for a field.
2726
2727 SYNOPSIS
2728 get_column_grant()
2729 thd thread handler
2730 grant grants table descriptor
2731 db_name name of database that the field belongs to
2732 table_name name of table that the field belongs to
2733 field_name name of field
2734
2735 DESCRIPTION
2736 The procedure may also modify: grant->grant_table and grant->version.
2737
2738 RETURN
2739 The access priviliges for the field db_name.table_name.field_name
2740 */
2741
get_column_grant(THD * thd,GRANT_INFO * grant,const char * db_name,const char * table_name,const char * field_name)2742 ulong get_column_grant(THD *thd, GRANT_INFO *grant,
2743 const char *db_name, const char *table_name,
2744 const char *field_name)
2745 {
2746 GRANT_TABLE *grant_table;
2747 GRANT_COLUMN *grant_column;
2748 ulong priv;
2749
2750 LOCK_grant_read_guard lock(thd);
2751
2752 /* reload table if someone has modified any grants */
2753 if (grant->version != grant_version)
2754 {
2755 Security_context *sctx= thd->security_context();
2756 grant->grant_table=
2757 table_hash_search(sctx->host().str, sctx->ip().str,
2758 db_name, sctx->priv_user().str,
2759 table_name, 0); /* purecov: inspected */
2760 grant->version= grant_version; /* purecov: inspected */
2761 }
2762
2763 if (!(grant_table= grant->grant_table))
2764 priv= grant->privilege;
2765 else
2766 {
2767 grant_column= column_hash_search(grant_table, field_name,
2768 strlen(field_name));
2769 if (!grant_column)
2770 priv= (grant->privilege | grant_table->privs);
2771 else
2772 priv= (grant->privilege | grant_table->privs | grant_column->rights);
2773 }
2774 return priv;
2775 }
2776
show_routine_grants(THD * thd,LEX_USER * lex_user,HASH * hash,const char * type,int typelen,char * buff,int buffsize)2777 static int show_routine_grants(THD* thd, LEX_USER *lex_user, HASH *hash,
2778 const char *type, int typelen,
2779 char *buff, int buffsize)
2780 {
2781 uint counter, index;
2782 int error= 0;
2783 Protocol *protocol= thd->get_protocol();
2784 /* Add routine access */
2785 for (index=0 ; index < hash->records ; index++)
2786 {
2787 const char *user, *host;
2788 GRANT_NAME *grant_proc= (GRANT_NAME*) my_hash_element(hash, index);
2789
2790 if (!(user=grant_proc->user))
2791 user= "";
2792 host= grant_proc->host.get_host();
2793
2794 /*
2795 We do not make SHOW GRANTS case-sensitive here (like REVOKE),
2796 but make it case-insensitive because that's the way they are
2797 actually applied, and showing fewer privileges than are applied
2798 would be wrong from a security point of view.
2799 */
2800
2801 if (!strcmp(lex_user->user.str,user) &&
2802 !my_strcasecmp(system_charset_info, lex_user->host.str, host))
2803 {
2804 ulong proc_access= grant_proc->privs;
2805 if (proc_access != 0)
2806 {
2807 String global(buff, buffsize, system_charset_info);
2808 ulong test_access= proc_access & ~GRANT_ACL;
2809
2810 global.length(0);
2811 global.append(STRING_WITH_LEN("GRANT "));
2812
2813 if (!test_access)
2814 global.append(STRING_WITH_LEN("USAGE"));
2815 else
2816 {
2817 /* Add specific procedure access */
2818 int found= 0;
2819 ulong j;
2820
2821 for (counter= 0, j= SELECT_ACL; j <= PROC_ACLS; counter++, j<<= 1)
2822 {
2823 if (test_access & j)
2824 {
2825 if (found)
2826 global.append(STRING_WITH_LEN(", "));
2827 found= 1;
2828 global.append(command_array[counter],command_lengths[counter]);
2829 }
2830 }
2831 }
2832 global.append(STRING_WITH_LEN(" ON "));
2833 global.append(type,typelen);
2834 global.append(' ');
2835 append_identifier(thd, &global, grant_proc->db,
2836 strlen(grant_proc->db));
2837 global.append('.');
2838 append_identifier(thd, &global, grant_proc->tname,
2839 strlen(grant_proc->tname));
2840 global.append(STRING_WITH_LEN(" TO '"));
2841 global.append(lex_user->user.str, lex_user->user.length,
2842 system_charset_info);
2843 global.append(STRING_WITH_LEN("'@'"));
2844 // host and lex_user->host are equal except for case
2845 global.append(host, strlen(host), system_charset_info);
2846 global.append('\'');
2847 if (proc_access & GRANT_ACL)
2848 global.append(STRING_WITH_LEN(" WITH GRANT OPTION"));
2849 protocol->start_row();
2850 protocol->store(global.ptr(),global.length(),global.charset());
2851 if (protocol->end_row())
2852 {
2853 error= -1;
2854 break;
2855 }
2856 }
2857 }
2858 }
2859 return error;
2860 }
2861
2862
2863 static bool
show_proxy_grants(THD * thd,LEX_USER * user,char * buff,size_t buffsize)2864 show_proxy_grants(THD *thd, LEX_USER *user, char *buff, size_t buffsize)
2865 {
2866 Protocol *protocol= thd->get_protocol();
2867 int error= 0;
2868
2869 for (ACL_PROXY_USER *proxy= acl_proxy_users->begin();
2870 proxy != acl_proxy_users->end(); ++proxy)
2871 {
2872 if (proxy->granted_on(user->host.str, user->user.str))
2873 {
2874 String global(buff, buffsize, system_charset_info);
2875 global.length(0);
2876 proxy->print_grant(&global);
2877 protocol->start_row();
2878 protocol->store(global.ptr(), global.length(), global.charset());
2879 if (protocol->end_row())
2880 {
2881 error= -1;
2882 break;
2883 }
2884 }
2885 }
2886 return error;
2887 }
2888
2889
2890 /*
2891 Make a clear-text version of the requested privilege.
2892 */
2893
get_privilege_desc(char * to,uint max_length,ulong access)2894 void get_privilege_desc(char *to, uint max_length, ulong access)
2895 {
2896 uint pos;
2897 char *start=to;
2898 assert(max_length >= 30); // For end ', ' removal
2899
2900 if (access)
2901 {
2902 max_length--; // Reserve place for end-zero
2903 for (pos=0 ; access ; pos++, access>>=1)
2904 {
2905 if ((access & 1) &&
2906 command_lengths[pos] + (uint) (to-start) < max_length)
2907 {
2908 to= my_stpcpy(to, command_array[pos]);
2909 *to++= ',';
2910 *to++= ' ';
2911 }
2912 }
2913 to--; // Remove end ' '
2914 to--; // Remove end ','
2915 }
2916 *to=0;
2917 }
2918
2919
2920 /*
2921 SHOW GRANTS; Send grants for a user to the client
2922
2923 IMPLEMENTATION
2924 Send to client grant-like strings depicting user@host privileges
2925 */
2926
mysql_show_grants(THD * thd,LEX_USER * lex_user)2927 bool mysql_show_grants(THD *thd,LEX_USER *lex_user)
2928 {
2929 ulong want_access;
2930 uint counter,index;
2931 int error = 0;
2932 ACL_USER *acl_user= NULL;
2933 ACL_DB *acl_db;
2934 char buff[1024];
2935 Protocol *protocol= thd->get_protocol();
2936 DBUG_ENTER("mysql_show_grants");
2937
2938 if (!initialized)
2939 {
2940 my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--skip-grant-tables");
2941 DBUG_RETURN(TRUE);
2942 }
2943
2944 LOCK_grant_read_guard lock(thd);
2945 mysql_mutex_lock(&acl_cache->lock);
2946
2947 acl_user= find_acl_user(lex_user->host.str, lex_user->user.str, TRUE);
2948 if (!acl_user)
2949 {
2950 mysql_mutex_unlock(&acl_cache->lock);
2951 lock.unlock();
2952
2953 my_error(ER_NONEXISTING_GRANT, MYF(0),
2954 lex_user->user.str, lex_user->host.str);
2955 DBUG_RETURN(TRUE);
2956 }
2957
2958 Item_string *field=new Item_string("",0,&my_charset_latin1);
2959 List<Item> field_list;
2960 field->max_length=1024;
2961 strxmov(buff,"Grants for ",lex_user->user.str,"@",
2962 lex_user->host.str,NullS);
2963 field->item_name.set(buff);
2964 field_list.push_back(field);
2965 if (thd->send_result_metadata(&field_list,
2966 Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF))
2967 {
2968 mysql_mutex_unlock(&acl_cache->lock);
2969
2970 DBUG_RETURN(TRUE);
2971 }
2972
2973 /* Add first global access grants */
2974 {
2975 String global(buff,sizeof(buff),system_charset_info);
2976 global.length(0);
2977 global.append(STRING_WITH_LEN("GRANT "));
2978
2979 want_access= acl_user->access;
2980 if (test_all_bits(want_access, (GLOBAL_ACLS & ~ GRANT_ACL)))
2981 global.append(STRING_WITH_LEN("ALL PRIVILEGES"));
2982 else if (!(want_access & ~GRANT_ACL))
2983 global.append(STRING_WITH_LEN("USAGE"));
2984 else
2985 {
2986 bool found=0;
2987 ulong j,test_access= want_access & ~GRANT_ACL;
2988 for (counter=0, j = SELECT_ACL;j <= GLOBAL_ACLS;counter++,j <<= 1)
2989 {
2990 if (test_access & j)
2991 {
2992 if (found)
2993 global.append(STRING_WITH_LEN(", "));
2994 found=1;
2995 global.append(command_array[counter],command_lengths[counter]);
2996 }
2997 }
2998 }
2999 global.append (STRING_WITH_LEN(" ON *.* TO '"));
3000 global.append(lex_user->user.str, lex_user->user.length,
3001 system_charset_info);
3002 global.append (STRING_WITH_LEN("'@'"));
3003 global.append(lex_user->host.str,lex_user->host.length,
3004 system_charset_info);
3005 global.append ('\'');
3006 if (want_access & GRANT_ACL)
3007 global.append(STRING_WITH_LEN(" WITH GRANT OPTION"));
3008 protocol->start_row();
3009 protocol->store(global.ptr(),global.length(),global.charset());
3010 if (protocol->end_row())
3011 {
3012 error= -1;
3013 goto end;
3014 }
3015 }
3016
3017 /* Add database access */
3018 for (acl_db= acl_dbs->begin(); acl_db != acl_dbs->end(); ++acl_db)
3019 {
3020 const char *user, *host;
3021
3022 if (!(user=acl_db->user))
3023 user= "";
3024 host= acl_db->host.get_host();
3025
3026 /*
3027 We do not make SHOW GRANTS case-sensitive here (like REVOKE),
3028 but make it case-insensitive because that's the way they are
3029 actually applied, and showing fewer privileges than are applied
3030 would be wrong from a security point of view.
3031 */
3032
3033 if (!strcmp(lex_user->user.str,user) &&
3034 !my_strcasecmp(system_charset_info, lex_user->host.str, host))
3035 {
3036 want_access=acl_db->access;
3037 if (want_access)
3038 {
3039 String db(buff,sizeof(buff),system_charset_info);
3040 db.length(0);
3041 db.append(STRING_WITH_LEN("GRANT "));
3042
3043 if (test_all_bits(want_access,(DB_ACLS & ~GRANT_ACL)))
3044 db.append(STRING_WITH_LEN("ALL PRIVILEGES"));
3045 else if (!(want_access & ~GRANT_ACL))
3046 db.append(STRING_WITH_LEN("USAGE"));
3047 else
3048 {
3049 int found=0, cnt;
3050 ulong j,test_access= want_access & ~GRANT_ACL;
3051 for (cnt=0, j = SELECT_ACL; j <= DB_ACLS; cnt++,j <<= 1)
3052 {
3053 if (test_access & j)
3054 {
3055 if (found)
3056 db.append(STRING_WITH_LEN(", "));
3057 found = 1;
3058 db.append(command_array[cnt],command_lengths[cnt]);
3059 }
3060 }
3061 }
3062 db.append (STRING_WITH_LEN(" ON "));
3063 append_identifier(thd, &db, acl_db->db, strlen(acl_db->db));
3064 db.append (STRING_WITH_LEN(".* TO '"));
3065 db.append(lex_user->user.str, lex_user->user.length,
3066 system_charset_info);
3067 db.append (STRING_WITH_LEN("'@'"));
3068 // host and lex_user->host are equal except for case
3069 db.append(host, strlen(host), system_charset_info);
3070 db.append ('\'');
3071 if (want_access & GRANT_ACL)
3072 db.append(STRING_WITH_LEN(" WITH GRANT OPTION"));
3073 protocol->start_row();
3074 protocol->store(db.ptr(),db.length(),db.charset());
3075 if (protocol->end_row())
3076 {
3077 error= -1;
3078 goto end;
3079 }
3080 }
3081 }
3082 }
3083
3084 /* Add table & column access */
3085 for (index=0 ; index < column_priv_hash.records ; index++)
3086 {
3087 const char *user, *host;
3088 GRANT_TABLE *grant_table= (GRANT_TABLE*)
3089 my_hash_element(&column_priv_hash, index);
3090
3091 if (!(user=grant_table->user))
3092 user= "";
3093 host= grant_table->host.get_host();
3094
3095 /*
3096 We do not make SHOW GRANTS case-sensitive here (like REVOKE),
3097 but make it case-insensitive because that's the way they are
3098 actually applied, and showing fewer privileges than are applied
3099 would be wrong from a security point of view.
3100 */
3101
3102 if (!strcmp(lex_user->user.str,user) &&
3103 !my_strcasecmp(system_charset_info, lex_user->host.str, host))
3104 {
3105 ulong table_access= grant_table->privs;
3106 if ((table_access | grant_table->cols) != 0)
3107 {
3108 String global(buff, sizeof(buff), system_charset_info);
3109 ulong test_access= (table_access | grant_table->cols) & ~GRANT_ACL;
3110
3111 global.length(0);
3112 global.append(STRING_WITH_LEN("GRANT "));
3113
3114 if (test_all_bits(table_access, (TABLE_ACLS & ~GRANT_ACL)))
3115 global.append(STRING_WITH_LEN("ALL PRIVILEGES"));
3116 else if (!test_access)
3117 global.append(STRING_WITH_LEN("USAGE"));
3118 else
3119 {
3120 /* Add specific column access */
3121 int found= 0;
3122 ulong j;
3123
3124 for (counter= 0, j= SELECT_ACL; j <= TABLE_ACLS; counter++, j<<= 1)
3125 {
3126 if (test_access & j)
3127 {
3128 if (found)
3129 global.append(STRING_WITH_LEN(", "));
3130 found= 1;
3131 global.append(command_array[counter],command_lengths[counter]);
3132
3133 if (grant_table->cols)
3134 {
3135 uint found_col= 0;
3136 for (uint col_index=0 ;
3137 col_index < grant_table->hash_columns.records ;
3138 col_index++)
3139 {
3140 GRANT_COLUMN *grant_column = (GRANT_COLUMN*)
3141 my_hash_element(&grant_table->hash_columns,col_index);
3142 if (grant_column->rights & j)
3143 {
3144 if (!found_col)
3145 {
3146 found_col= 1;
3147 /*
3148 If we have a duplicated table level privilege, we
3149 must write the access privilege name again.
3150 */
3151 if (table_access & j)
3152 {
3153 global.append(STRING_WITH_LEN(", "));
3154 global.append(command_array[counter],
3155 command_lengths[counter]);
3156 }
3157 global.append(STRING_WITH_LEN(" ("));
3158 }
3159 else
3160 global.append(STRING_WITH_LEN(", "));
3161 global.append(grant_column->column,
3162 grant_column->key_length,
3163 system_charset_info);
3164 }
3165 }
3166 if (found_col)
3167 global.append(')');
3168 }
3169 }
3170 }
3171 }
3172 global.append(STRING_WITH_LEN(" ON "));
3173 append_identifier(thd, &global, grant_table->db,
3174 strlen(grant_table->db));
3175 global.append('.');
3176 append_identifier(thd, &global, grant_table->tname,
3177 strlen(grant_table->tname));
3178 global.append(STRING_WITH_LEN(" TO '"));
3179 global.append(lex_user->user.str, lex_user->user.length,
3180 system_charset_info);
3181 global.append(STRING_WITH_LEN("'@'"));
3182 // host and lex_user->host are equal except for case
3183 global.append(host, strlen(host), system_charset_info);
3184 global.append('\'');
3185 if (table_access & GRANT_ACL)
3186 global.append(STRING_WITH_LEN(" WITH GRANT OPTION"));
3187 protocol->start_row();
3188 protocol->store(global.ptr(),global.length(),global.charset());
3189 if (protocol->end_row())
3190 {
3191 error= -1;
3192 break;
3193 }
3194 }
3195 }
3196 }
3197
3198 if (show_routine_grants(thd, lex_user, &proc_priv_hash,
3199 STRING_WITH_LEN("PROCEDURE"), buff, sizeof(buff)))
3200 {
3201 error= -1;
3202 goto end;
3203 }
3204
3205 if (show_routine_grants(thd, lex_user, &func_priv_hash,
3206 STRING_WITH_LEN("FUNCTION"), buff, sizeof(buff)))
3207 {
3208 error= -1;
3209 goto end;
3210 }
3211
3212 if (show_proxy_grants(thd, lex_user, buff, sizeof(buff)))
3213 {
3214 error= -1;
3215 goto end;
3216 }
3217
3218 end:
3219 mysql_mutex_unlock(&acl_cache->lock);
3220 lock.unlock();
3221
3222 my_eof(thd);
3223 DBUG_RETURN(error);
3224 }
3225
3226
3227 /*
3228 Revoke all privileges from a list of users.
3229
3230 SYNOPSIS
3231 mysql_revoke_all()
3232 thd The current thread.
3233 list The users to revoke all privileges from.
3234
3235 RETURN
3236 > 0 Error. Error message already sent.
3237 0 OK.
3238 < 0 Error. Error message not yet sent.
3239 */
3240
mysql_revoke_all(THD * thd,List<LEX_USER> & list)3241 bool mysql_revoke_all(THD *thd, List <LEX_USER> &list)
3242 {
3243 uint revoked, is_proc;
3244 int result;
3245 ACL_DB *acl_db;
3246 TABLE_LIST tables[GRANT_TABLES];
3247 bool save_binlog_row_based;
3248 bool transactional_tables;
3249 DBUG_ENTER("mysql_revoke_all");
3250
3251 /*
3252 This statement will be replicated as a statement, even when using
3253 row-based replication. The flag will be reset at the end of the
3254 statement.
3255 */
3256 if ((save_binlog_row_based= thd->is_current_stmt_binlog_format_row()))
3257 thd->clear_current_stmt_binlog_format_row();
3258
3259 if ((result= open_grant_tables(thd, tables, &transactional_tables)))
3260 {
3261 /* Restore the state of binlog format */
3262 assert(!thd->is_current_stmt_binlog_format_row());
3263 if (save_binlog_row_based)
3264 thd->set_current_stmt_binlog_format_row();
3265 DBUG_RETURN(result != 1);
3266 }
3267
3268 Partitioned_rwlock_write_guard lock(&LOCK_grant);
3269 mysql_mutex_lock(&acl_cache->lock);
3270
3271 LEX_USER *lex_user, *tmp_lex_user;
3272 List_iterator <LEX_USER> user_list(list);
3273
3274 bool is_partial_execution= false;
3275 bool rollback_whole_statement= false;
3276 while ((tmp_lex_user= user_list++))
3277 {
3278 bool is_user_applied= true;
3279 ulong what_to_set= 0;
3280 if (!(lex_user= get_current_user(thd, tmp_lex_user)))
3281 {
3282 result= -1;
3283 continue;
3284 }
3285 if (!find_acl_user(lex_user->host.str, lex_user->user.str, TRUE))
3286 {
3287 result= -1;
3288 continue;
3289 }
3290
3291 /* copy password expire attributes to individual user */
3292 lex_user->alter_status= thd->lex->alter_password;
3293
3294 int ret= replace_user_table(thd, tables[0].table,
3295 lex_user, ~(ulong) 0, true, false,
3296 (what_to_set | ACCESS_RIGHTS_ATTR));
3297 if (ret > 0)
3298 {
3299 result= -1;
3300 continue;
3301 }
3302 else if (ret < 0)
3303 {
3304 result= -1;
3305 rollback_whole_statement= true;
3306 break;
3307 }
3308
3309 /* Remove db access privileges */
3310 /*
3311 Because acl_dbs and column_priv_hash shrink and may re-order
3312 as privileges are removed, removal occurs in a repeated loop
3313 until no more privileges are revoked.
3314 */
3315 do
3316 {
3317 for (revoked= 0, acl_db= acl_dbs->begin(); acl_db != acl_dbs->end(); )
3318 {
3319 const char *user,*host;
3320
3321 if (!(user=acl_db->user))
3322 user= "";
3323 host= acl_db->host.get_host();
3324
3325 if (!strcmp(lex_user->user.str,user) &&
3326 !strcmp(lex_user->host.str, host))
3327 {
3328 ret= replace_db_table(tables[1].table, acl_db->db, *lex_user,
3329 ~(ulong)0, 1);
3330
3331 if (ret == 0)
3332 {
3333 /*
3334 Don't increment loop variable as replace_db_table deleted the
3335 current element in acl_dbs.
3336 */
3337 revoked= 1;
3338 continue;
3339 }
3340 else if (ret < 0)
3341 {
3342 result= -1;
3343 rollback_whole_statement= true;
3344 goto user_end;
3345 }
3346 result= -1; // Something went wrong
3347 is_user_applied= false;
3348 }
3349 ++acl_db;
3350 }
3351 } while (revoked);
3352
3353 /* Remove column access */
3354 do
3355 {
3356 uint counter;
3357 for (counter= 0, revoked= 0 ; counter < column_priv_hash.records ; )
3358 {
3359 const char *user,*host;
3360 GRANT_TABLE *grant_table=
3361 (GRANT_TABLE*) my_hash_element(&column_priv_hash, counter);
3362 if (!(user=grant_table->user))
3363 user= "";
3364 host= grant_table->host.get_host();
3365
3366 if (!strcmp(lex_user->user.str,user) &&
3367 !strcmp(lex_user->host.str, host))
3368 {
3369 ret= replace_table_table(thd,grant_table,tables[2].table,*lex_user,
3370 grant_table->db,
3371 grant_table->tname,
3372 ~(ulong)0, 0, 1);
3373 if (ret > 0)
3374 {
3375 result= -1;
3376 is_user_applied= false;
3377 }
3378 else if (ret < 0)
3379 {
3380 result= -1;
3381 rollback_whole_statement= true;
3382 goto user_end;
3383 }
3384 else
3385 {
3386 if (!grant_table->cols)
3387 {
3388 revoked= 1;
3389 continue;
3390 }
3391 List<LEX_COLUMN> columns;
3392 ret= replace_column_table(grant_table,tables[3].table, *lex_user,
3393 columns,
3394 grant_table->db,
3395 grant_table->tname,
3396 ~(ulong)0, 1);
3397
3398 if (ret == 0)
3399 {
3400 revoked= 1;
3401 continue;
3402 }
3403 else if (ret < 0)
3404 {
3405 result= -1;
3406 rollback_whole_statement= true;
3407 goto user_end;
3408 }
3409 result= -1;
3410 is_user_applied= false;
3411 }
3412 }
3413 counter++;
3414 }
3415 } while (revoked);
3416
3417 /* Remove procedure access */
3418 for (is_proc=0; is_proc<2; is_proc++) do {
3419 HASH *hash= is_proc ? &proc_priv_hash : &func_priv_hash;
3420 uint counter;
3421 for (counter= 0, revoked= 0 ; counter < hash->records ; )
3422 {
3423 const char *user,*host;
3424 GRANT_NAME *grant_proc= (GRANT_NAME*) my_hash_element(hash, counter);
3425 if (!(user=grant_proc->user))
3426 user= "";
3427 host= grant_proc->host.get_host();
3428
3429 if (!strcmp(lex_user->user.str,user) &&
3430 !strcmp(lex_user->host.str, host))
3431 {
3432 ret= replace_routine_table(thd,grant_proc,tables[4].table,*lex_user,
3433 grant_proc->db,
3434 grant_proc->tname,
3435 is_proc,
3436 ~(ulong)0, 1);
3437
3438 if (ret == 0)
3439 {
3440 revoked= 1;
3441 continue;
3442 }
3443 else if (ret < 0)
3444 {
3445 result= -1;
3446 rollback_whole_statement= true;
3447 goto user_end;
3448 }
3449 result= -1; // Something went wrong
3450 is_user_applied= false;
3451 }
3452 counter++;
3453 }
3454 } while (revoked);
3455 if (is_user_applied)
3456 is_partial_execution= true;
3457 }
3458
3459 user_end:
3460
3461 mysql_mutex_unlock(&acl_cache->lock);
3462
3463 DBUG_EXECUTE_IF("force_mysql_revoke_all_fail", {
3464 result= 1;
3465 is_partial_execution= true;
3466 rollback_whole_statement= false;
3467 });
3468
3469 if (result && !rollback_whole_statement)
3470 my_message(ER_REVOKE_GRANTS, ER(ER_REVOKE_GRANTS), MYF(0));
3471
3472 /*
3473 Before ACLs are changed to execute fully or none at all, when
3474 some error happens, write an incident if one or more users are
3475 revoked successfully (it has a partial execution).
3476 */
3477 if (result)
3478 {
3479 if (!rollback_whole_statement || !transactional_tables)
3480 {
3481 if (is_partial_execution)
3482 {
3483 const char* err_msg= "REVOKE failed while revoking all_privileges "
3484 "from a list of users.";
3485 DEBUG_SYNC(thd, "revoke_all_before_write_incident_to_binlog");
3486 mysql_bin_log.write_incident(thd, true /* need_lock_log=true */,
3487 err_msg);
3488 }
3489 }
3490 }
3491 else
3492 {
3493 result= result |
3494 write_bin_log(thd, FALSE, thd->query().str, thd->query().length,
3495 transactional_tables);
3496 }
3497
3498 lock.unlock();
3499
3500 result|=
3501 acl_end_trans_and_close_tables(thd,
3502 thd->transaction_rollback_request ||
3503 rollback_whole_statement);
3504
3505 if (!result)
3506 acl_notify_htons(thd, thd->query().str, thd->query().length);
3507
3508 /* Restore the state of binlog format */
3509 assert(!thd->is_current_stmt_binlog_format_row());
3510 if (save_binlog_row_based)
3511 thd->set_current_stmt_binlog_format_row();
3512
3513 DBUG_RETURN(result);
3514 }
3515
3516
3517 /**
3518 If the defining user for a routine does not exist, then the ACL lookup
3519 code should raise two errors which we should intercept. We convert the more
3520 descriptive error into a warning, and consume the other.
3521
3522 If any other errors are raised, then we set a flag that should indicate
3523 that there was some failure we should complain at a higher level.
3524 */
3525 class Silence_routine_definer_errors : public Internal_error_handler
3526 {
3527 public:
Silence_routine_definer_errors()3528 Silence_routine_definer_errors()
3529 : is_grave(false)
3530 {}
3531
handle_condition(THD * thd,uint sql_errno,const char * sqlstate,Sql_condition::enum_severity_level * level,const char * msg)3532 virtual bool handle_condition(THD *thd,
3533 uint sql_errno,
3534 const char* sqlstate,
3535 Sql_condition::enum_severity_level *level,
3536 const char* msg)
3537 {
3538 if (*level == Sql_condition::SL_ERROR)
3539 {
3540 if (sql_errno == ER_NONEXISTING_PROC_GRANT)
3541 {
3542 /* Convert the error into a warning. */
3543 *level= Sql_condition::SL_WARNING;
3544 return true;
3545 }
3546 else
3547 is_grave= true;
3548 }
3549
3550 return false;
3551 }
3552
has_errors() const3553 bool has_errors() const { return is_grave; }
3554
3555 private:
3556 bool is_grave;
3557 };
3558
3559
3560 /**
3561 Revoke privileges for all users on a stored procedure. Use an error handler
3562 that converts errors about missing grants into warnings.
3563
3564 @param
3565 thd The current thread.
3566 @param
3567 db DB of the stored procedure
3568 @param
3569 name Name of the stored procedure
3570
3571 @retval
3572 0 OK.
3573 @retval
3574 < 0 Error. Error message not yet sent.
3575 */
3576
sp_revoke_privileges(THD * thd,const char * sp_db,const char * sp_name,bool is_proc)3577 bool sp_revoke_privileges(THD *thd, const char *sp_db, const char *sp_name,
3578 bool is_proc)
3579 {
3580 uint counter, revoked;
3581 int result;
3582 TABLE_LIST tables[GRANT_TABLES];
3583 HASH *hash= is_proc ? &proc_priv_hash : &func_priv_hash;
3584 Silence_routine_definer_errors error_handler;
3585 bool save_binlog_row_based;
3586 bool not_used;
3587 DBUG_ENTER("sp_revoke_privileges");
3588
3589 if ((result= open_grant_tables(thd, tables, ¬_used)))
3590 DBUG_RETURN(result != 1);
3591
3592 /* Be sure to pop this before exiting this scope! */
3593 thd->push_internal_handler(&error_handler);
3594
3595 Partitioned_rwlock_write_guard lock(&LOCK_grant);
3596 mysql_mutex_lock(&acl_cache->lock);
3597
3598 /*
3599 This statement will be replicated as a statement, even when using
3600 row-based replication. The flag will be reset at the end of the
3601 statement.
3602 */
3603 if ((save_binlog_row_based= thd->is_current_stmt_binlog_format_row()))
3604 thd->clear_current_stmt_binlog_format_row();
3605
3606 /* Remove procedure access */
3607 bool rollback_whole_statement= false;
3608 do
3609 {
3610 for (counter= 0, revoked= 0 ; counter < hash->records ; )
3611 {
3612 GRANT_NAME *grant_proc= (GRANT_NAME*) my_hash_element(hash, counter);
3613 if (!my_strcasecmp(&my_charset_utf8_bin, grant_proc->db, sp_db) &&
3614 !my_strcasecmp(system_charset_info, grant_proc->tname, sp_name))
3615 {
3616 LEX_USER lex_user;
3617 lex_user.user.str= grant_proc->user;
3618 lex_user.user.length= strlen(grant_proc->user);
3619 lex_user.host.str= (char *) (grant_proc->host.get_host());
3620 lex_user.host.length= grant_proc->host.get_host_len();
3621
3622 int ret=
3623 replace_routine_table(thd,grant_proc,tables[4].table,lex_user,
3624 grant_proc->db, grant_proc->tname,
3625 is_proc, ~(ulong)0, true);
3626 if (ret < 0)
3627 {
3628 rollback_whole_statement= true;
3629 revoked= false;
3630 break;
3631 }
3632 else if (ret == 0)
3633 {
3634 revoked= 1;
3635 continue;
3636 }
3637 }
3638 counter++;
3639 }
3640 } while (revoked);
3641
3642 mysql_mutex_unlock(&acl_cache->lock);
3643 lock.unlock();
3644
3645 result|=
3646 acl_end_trans_and_close_tables(thd,
3647 thd->transaction_rollback_request ||
3648 rollback_whole_statement);
3649
3650 thd->pop_internal_handler();
3651
3652 /* Restore the state of binlog format */
3653 assert(!thd->is_current_stmt_binlog_format_row());
3654 if (save_binlog_row_based)
3655 thd->set_current_stmt_binlog_format_row();
3656
3657 DBUG_RETURN(error_handler.has_errors() || result);
3658 }
3659
3660
3661 /**
3662 Grant EXECUTE,ALTER privilege for a stored procedure
3663
3664 @param thd The current thread.
3665 @param sp_db
3666 @param sp_name
3667 @param is_proc
3668
3669 @return
3670 @retval FALSE Success
3671 @retval TRUE An error occured. Error message not yet sent.
3672 */
3673
sp_grant_privileges(THD * thd,const char * sp_db,const char * sp_name,bool is_proc)3674 bool sp_grant_privileges(THD *thd, const char *sp_db, const char *sp_name,
3675 bool is_proc)
3676 {
3677 Security_context *sctx= thd->security_context();
3678 LEX_USER *combo;
3679 TABLE_LIST tables[1];
3680 List<LEX_USER> user_list;
3681 bool result;
3682 ACL_USER *au;
3683 Dummy_error_handler error_handler;
3684 DBUG_ENTER("sp_grant_privileges");
3685
3686 if (!(combo=(LEX_USER*) thd->alloc(sizeof(st_lex_user))))
3687 DBUG_RETURN(TRUE);
3688
3689 combo->user.str= (char *) sctx->priv_user().str;
3690
3691 mysql_mutex_lock(&acl_cache->lock);
3692
3693 if ((au= find_acl_user(combo->host.str= (char *) sctx->priv_host().str,
3694 combo->user.str, false)))
3695 goto found_acl;
3696
3697 mysql_mutex_unlock(&acl_cache->lock);
3698 DBUG_RETURN(TRUE);
3699
3700 found_acl:
3701 mysql_mutex_unlock(&acl_cache->lock);
3702
3703 memset(tables, 0, sizeof(TABLE_LIST));
3704 user_list.empty();
3705
3706 tables->db= (char*)sp_db;
3707 tables->table_name= tables->alias= (char*)sp_name;
3708
3709 thd->make_lex_string(&combo->user,
3710 combo->user.str, strlen(combo->user.str), 0);
3711 thd->make_lex_string(&combo->host,
3712 combo->host.str, strlen(combo->host.str), 0);
3713
3714 combo->plugin= EMPTY_CSTR;
3715 combo->auth= EMPTY_CSTR;
3716 combo->uses_identified_by_clause= false;
3717 combo->uses_identified_with_clause= false;
3718 combo->uses_identified_by_password_clause= false;
3719 combo->uses_authentication_string_clause= false;
3720
3721 if (user_list.push_back(combo))
3722 DBUG_RETURN(TRUE);
3723
3724 thd->lex->ssl_type= SSL_TYPE_NOT_SPECIFIED;
3725 thd->lex->ssl_cipher= thd->lex->x509_subject= thd->lex->x509_issuer= 0;
3726 memset(&thd->lex->mqh, 0, sizeof(thd->lex->mqh));
3727 /* set default values */
3728 thd->lex->alter_password.update_password_expired_column= false;
3729 thd->lex->alter_password.use_default_password_lifetime= true;
3730 thd->lex->alter_password.expire_after_days= 0;
3731 thd->lex->alter_password.update_account_locked_column= false;
3732 thd->lex->alter_password.account_locked= false;
3733
3734 combo->alter_status= thd->lex->alter_password;
3735
3736 /*
3737 Only care about whether the operation failed or succeeded
3738 as all errors will be handled later.
3739 */
3740 thd->push_internal_handler(&error_handler);
3741 result= mysql_routine_grant(thd, tables, is_proc, user_list,
3742 DEFAULT_CREATE_PROC_ACLS, FALSE, FALSE);
3743 thd->pop_internal_handler();
3744 DBUG_RETURN(result);
3745 }
3746
3747
update_schema_privilege(THD * thd,TABLE * table,char * buff,const char * db,const char * t_name,const char * column,size_t col_length,const char * priv,size_t priv_length,const char * is_grantable)3748 static bool update_schema_privilege(THD *thd, TABLE *table, char *buff,
3749 const char* db, const char* t_name,
3750 const char* column, size_t col_length,
3751 const char *priv, size_t priv_length,
3752 const char* is_grantable)
3753 {
3754 int i= 2;
3755 CHARSET_INFO *cs= system_charset_info;
3756 restore_record(table, s->default_values);
3757 table->field[0]->store(buff, strlen(buff), cs);
3758 table->field[1]->store(STRING_WITH_LEN("def"), cs);
3759 if (db)
3760 table->field[i++]->store(db, strlen(db), cs);
3761 if (t_name)
3762 table->field[i++]->store(t_name, strlen(t_name), cs);
3763 if (column)
3764 table->field[i++]->store(column, col_length, cs);
3765 table->field[i++]->store(priv, priv_length, cs);
3766 table->field[i]->store(is_grantable, strlen(is_grantable), cs);
3767 return schema_table_store_record(thd, table);
3768 }
3769
3770
3771 /*
3772 fill effective privileges for table
3773
3774 SYNOPSIS
3775 fill_effective_table_privileges()
3776 thd thread handler
3777 grant grants table descriptor
3778 db db name
3779 table table name
3780 */
3781
fill_effective_table_privileges(THD * thd,GRANT_INFO * grant,const char * db,const char * table)3782 void fill_effective_table_privileges(THD *thd, GRANT_INFO *grant,
3783 const char *db, const char *table)
3784 {
3785 Security_context *sctx= thd->security_context();
3786 LEX_CSTRING priv_user= sctx->priv_user();
3787 DBUG_ENTER("fill_effective_table_privileges");
3788 DBUG_PRINT("enter", ("Host: '%s', Ip: '%s', User: '%s', table: `%s`.`%s`",
3789 sctx->priv_host().str, (sctx->ip().length ?
3790 sctx->ip().str : "(NULL)"),
3791 (priv_user.str ? priv_user.str : "(NULL)"),
3792 db, table));
3793 /*
3794 This function is not intended for derived tables which doesn't have a
3795 name. If this happens something is wrong.
3796 */
3797 assert(table != 0);
3798 /* --skip-grants */
3799 if (!initialized)
3800 {
3801 DBUG_PRINT("info", ("skip grants"));
3802 grant->privilege= ~NO_ACCESS; // everything is allowed
3803 DBUG_PRINT("info", ("privilege 0x%lx", grant->privilege));
3804 DBUG_VOID_RETURN;
3805 }
3806
3807 /* global privileges */
3808 grant->privilege= sctx->master_access();
3809
3810 /* db privileges */
3811 grant->privilege|= acl_get(sctx->host().str, sctx->ip().str,
3812 priv_user.str, db, 0);
3813
3814 DEBUG_SYNC(thd, "fill_effective_table_privileges");
3815 /* table privileges */
3816 LOCK_grant_read_guard lock(thd);
3817
3818 if (grant->version != grant_version)
3819 {
3820 grant->grant_table=
3821 table_hash_search(sctx->host().str, sctx->ip().str, db,
3822 priv_user.str, table, 0); /* purecov: inspected */
3823 grant->version= grant_version; /* purecov: inspected */
3824 }
3825 if (grant->grant_table != 0)
3826 {
3827 grant->privilege|= grant->grant_table->privs;
3828 }
3829
3830 DBUG_PRINT("info", ("privilege 0x%lx", grant->privilege));
3831 DBUG_VOID_RETURN;
3832 }
3833
3834
3835 bool
acl_check_proxy_grant_access(THD * thd,const char * host,const char * user,bool with_grant)3836 acl_check_proxy_grant_access(THD *thd, const char *host, const char *user,
3837 bool with_grant)
3838 {
3839 DBUG_ENTER("acl_check_proxy_grant_access");
3840 DBUG_PRINT("info", ("user=%s host=%s with_grant=%d", user, host,
3841 (int) with_grant));
3842 if (!initialized)
3843 {
3844 my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--skip-grant-tables");
3845 DBUG_RETURN(1);
3846 }
3847
3848 /* replication slave thread can do anything */
3849 if (thd->slave_thread)
3850 {
3851 DBUG_PRINT("info", ("replication slave"));
3852 DBUG_RETURN(FALSE);
3853 }
3854
3855 /*
3856 one can grant proxy for self to others.
3857 Security context in THD contains two pairs of (user,host):
3858 1. (user,host) pair referring to inbound connection.
3859 2. (priv_user,priv_host) pair obtained from mysql.user table after doing
3860 authnetication of incoming connection.
3861 Privileges should be checked wrt (priv_user, priv_host) tuple, because
3862 (user,host) pair obtained from inbound connection may have different
3863 values than what is actually stored in mysql.user table and while granting
3864 or revoking proxy privilege, user is expected to provide entries mentioned
3865 in mysql.user table.
3866 */
3867 if (!strcmp(thd->security_context()->priv_user().str, user) &&
3868 !my_strcasecmp(system_charset_info, host,
3869 thd->security_context()->priv_host().str))
3870 {
3871 DBUG_PRINT("info", ("strcmp (%s, %s) my_casestrcmp (%s, %s) equal",
3872 thd->security_context()->priv_user().str, user,
3873 host, thd->security_context()->priv_host().str));
3874 DBUG_RETURN(FALSE);
3875 }
3876
3877 mysql_mutex_lock(&acl_cache->lock);
3878
3879 /* check for matching WITH PROXY rights */
3880 for (ACL_PROXY_USER *proxy= acl_proxy_users->begin();
3881 proxy != acl_proxy_users->end(); ++proxy)
3882 {
3883 DEBUG_SYNC(thd, "before_proxy_matches");
3884 if (proxy->matches(thd->security_context()->host().str,
3885 thd->security_context()->user().str,
3886 thd->security_context()->ip().str,
3887 user, FALSE) &&
3888 proxy->get_with_grant())
3889 {
3890 DBUG_PRINT("info", ("found"));
3891 mysql_mutex_unlock(&acl_cache->lock);
3892 DBUG_RETURN(FALSE);
3893 }
3894 }
3895
3896 mysql_mutex_unlock(&acl_cache->lock);
3897 my_error(ER_ACCESS_DENIED_NO_PASSWORD_ERROR, MYF(0),
3898 thd->security_context()->user().str,
3899 thd->security_context()->host_or_ip().str);
3900 DBUG_RETURN(TRUE);
3901 }
3902
3903
3904 #else /* NO_EMBEDDED_ACCESS_CHECKS */
3905
3906 /****************************************************************************
3907 Dummy wrappers when we don't have any access checks
3908 ****************************************************************************/
3909
check_routine_level_acl(THD * thd,const char * db,const char * name,bool is_proc)3910 bool check_routine_level_acl(THD *thd, const char *db, const char *name,
3911 bool is_proc)
3912 {
3913 return FALSE;
3914 }
3915
3916
3917 #endif /* NO_EMBEDDED_ACCESS_CHECKS */
3918
3919
fill_schema_user_privileges(THD * thd,TABLE_LIST * tables,Item * cond)3920 int fill_schema_user_privileges(THD *thd, TABLE_LIST *tables, Item *cond)
3921 {
3922 #ifndef NO_EMBEDDED_ACCESS_CHECKS
3923 int error= 0;
3924 ACL_USER *acl_user;
3925 ulong want_access;
3926 char buff[USERNAME_LENGTH + HOSTNAME_LENGTH + 3];
3927 TABLE *table= tables->table;
3928 bool no_global_access= check_access(thd, SELECT_ACL, "mysql",
3929 NULL, NULL, 1, 1);
3930 const char *curr_host= thd->security_context()->priv_host_name();
3931 DBUG_ENTER("fill_schema_user_privileges");
3932
3933 if (!initialized)
3934 DBUG_RETURN(0);
3935 mysql_mutex_lock(&acl_cache->lock);
3936
3937 for (acl_user= acl_users->begin(); acl_user != acl_users->end(); ++acl_user)
3938 {
3939 const char *user,*host, *is_grantable="YES";
3940 if (!(user=acl_user->user))
3941 user= "";
3942 host= acl_user->host.get_host();
3943
3944 if (no_global_access &&
3945 (strcmp(thd->security_context()->priv_user().str, user) ||
3946 my_strcasecmp(system_charset_info, curr_host, host)))
3947 continue;
3948
3949 want_access= acl_user->access;
3950 if (!(want_access & GRANT_ACL))
3951 is_grantable= "NO";
3952
3953 strxmov(buff,"'",user,"'@'",host,"'",NullS);
3954 if (!(want_access & ~GRANT_ACL))
3955 {
3956 if (update_schema_privilege(thd, table, buff, 0, 0, 0, 0,
3957 STRING_WITH_LEN("USAGE"), is_grantable))
3958 {
3959 error= 1;
3960 goto err;
3961 }
3962 }
3963 else
3964 {
3965 uint priv_id;
3966 ulong j,test_access= want_access & ~GRANT_ACL;
3967 for (priv_id=0, j = SELECT_ACL;j <= GLOBAL_ACLS; priv_id++,j <<= 1)
3968 {
3969 if (test_access & j)
3970 {
3971 if (update_schema_privilege(thd, table, buff, 0, 0, 0, 0,
3972 command_array[priv_id],
3973 command_lengths[priv_id], is_grantable))
3974 {
3975 error= 1;
3976 goto err;
3977 }
3978 }
3979 }
3980 }
3981 }
3982 err:
3983 mysql_mutex_unlock(&acl_cache->lock);
3984
3985 DBUG_RETURN(error);
3986 #else
3987 return(0);
3988 #endif /* NO_EMBEDDED_ACCESS_CHECKS */
3989 }
3990
3991
fill_schema_schema_privileges(THD * thd,TABLE_LIST * tables,Item * cond)3992 int fill_schema_schema_privileges(THD *thd, TABLE_LIST *tables, Item *cond)
3993 {
3994 #ifndef NO_EMBEDDED_ACCESS_CHECKS
3995 int error= 0;
3996 ACL_DB *acl_db;
3997 ulong want_access;
3998 char buff[USERNAME_LENGTH + HOSTNAME_LENGTH + 3];
3999 TABLE *table= tables->table;
4000 bool no_global_access= check_access(thd, SELECT_ACL, "mysql",
4001 NULL, NULL, 1, 1);
4002 const char *curr_host= thd->security_context()->priv_host_name();
4003 DBUG_ENTER("fill_schema_schema_privileges");
4004
4005 if (!initialized)
4006 DBUG_RETURN(0);
4007 mysql_mutex_lock(&acl_cache->lock);
4008
4009 for (acl_db= acl_dbs->begin(); acl_db != acl_dbs->end(); ++acl_db)
4010 {
4011 const char *user, *host, *is_grantable="YES";
4012
4013 if (!(user=acl_db->user))
4014 user= "";
4015 host= acl_db->host.get_host();
4016
4017 if (no_global_access &&
4018 (strcmp(thd->security_context()->priv_user().str, user) ||
4019 my_strcasecmp(system_charset_info, curr_host, host)))
4020 continue;
4021
4022 want_access=acl_db->access;
4023 if (want_access)
4024 {
4025 if (!(want_access & GRANT_ACL))
4026 {
4027 is_grantable= "NO";
4028 }
4029 strxmov(buff,"'",user,"'@'",host,"'",NullS);
4030 if (!(want_access & ~GRANT_ACL))
4031 {
4032 if (update_schema_privilege(thd, table, buff, acl_db->db, 0, 0,
4033 0, STRING_WITH_LEN("USAGE"), is_grantable))
4034 {
4035 error= 1;
4036 goto err;
4037 }
4038 }
4039 else
4040 {
4041 int cnt;
4042 ulong j,test_access= want_access & ~GRANT_ACL;
4043 for (cnt=0, j = SELECT_ACL; j <= DB_ACLS; cnt++,j <<= 1)
4044 if (test_access & j)
4045 {
4046 if (update_schema_privilege(thd, table, buff, acl_db->db, 0, 0, 0,
4047 command_array[cnt], command_lengths[cnt],
4048 is_grantable))
4049 {
4050 error= 1;
4051 goto err;
4052 }
4053 }
4054 }
4055 }
4056 }
4057 err:
4058 mysql_mutex_unlock(&acl_cache->lock);
4059
4060 DBUG_RETURN(error);
4061 #else
4062 return (0);
4063 #endif /* NO_EMBEDDED_ACCESS_CHECKS */
4064 }
4065
4066
fill_schema_table_privileges(THD * thd,TABLE_LIST * tables,Item * cond)4067 int fill_schema_table_privileges(THD *thd, TABLE_LIST *tables, Item *cond)
4068 {
4069 #ifndef NO_EMBEDDED_ACCESS_CHECKS
4070 int error= 0;
4071 uint index;
4072 char buff[USERNAME_LENGTH + HOSTNAME_LENGTH + 3];
4073 TABLE *table= tables->table;
4074 bool no_global_access= check_access(thd, SELECT_ACL, "mysql",
4075 NULL, NULL, 1, 1);
4076 const char *curr_host= thd->security_context()->priv_host_name();
4077 DBUG_ENTER("fill_schema_table_privileges");
4078
4079 LOCK_grant_read_guard lock(thd);
4080
4081 for (index=0 ; index < column_priv_hash.records ; index++)
4082 {
4083 const char *user, *host, *is_grantable= "YES";
4084 GRANT_TABLE *grant_table= (GRANT_TABLE*) my_hash_element(&column_priv_hash,
4085 index);
4086 if (!(user=grant_table->user))
4087 user= "";
4088 host= grant_table->host.get_host();
4089
4090 if (no_global_access &&
4091 (strcmp(thd->security_context()->priv_user().str, user) ||
4092 my_strcasecmp(system_charset_info, curr_host, host)))
4093 continue;
4094
4095 ulong table_access= grant_table->privs;
4096 if (table_access)
4097 {
4098 ulong test_access= table_access & ~GRANT_ACL;
4099 /*
4100 We should skip 'usage' privilege on table if
4101 we have any privileges on column(s) of this table
4102 */
4103 if (!test_access && grant_table->cols)
4104 continue;
4105 if (!(table_access & GRANT_ACL))
4106 is_grantable= "NO";
4107
4108 strxmov(buff, "'", user, "'@'", host, "'", NullS);
4109 if (!test_access)
4110 {
4111 if (update_schema_privilege(thd, table, buff, grant_table->db,
4112 grant_table->tname, 0, 0,
4113 STRING_WITH_LEN("USAGE"), is_grantable))
4114 {
4115 error= 1;
4116 goto err;
4117 }
4118 }
4119 else
4120 {
4121 ulong j;
4122 int cnt;
4123 for (cnt= 0, j= SELECT_ACL; j <= TABLE_ACLS; cnt++, j<<= 1)
4124 {
4125 if (test_access & j)
4126 {
4127 if (update_schema_privilege(thd, table, buff, grant_table->db,
4128 grant_table->tname, 0, 0,
4129 command_array[cnt],
4130 command_lengths[cnt], is_grantable))
4131 {
4132 error= 1;
4133 goto err;
4134 }
4135 }
4136 }
4137 }
4138 }
4139 }
4140 err:
4141
4142 DBUG_RETURN(error);
4143 #else
4144 return (0);
4145 #endif /* NO_EMBEDDED_ACCESS_CHECKS */
4146 }
4147
4148
fill_schema_column_privileges(THD * thd,TABLE_LIST * tables,Item * cond)4149 int fill_schema_column_privileges(THD *thd, TABLE_LIST *tables, Item *cond)
4150 {
4151 #ifndef NO_EMBEDDED_ACCESS_CHECKS
4152 int error= 0;
4153 uint index;
4154 char buff[USERNAME_LENGTH + HOSTNAME_LENGTH + 3];
4155 TABLE *table= tables->table;
4156 bool no_global_access= check_access(thd, SELECT_ACL, "mysql",
4157 NULL, NULL, 1, 1);
4158 const char *curr_host= thd->security_context()->priv_host_name();
4159 DBUG_ENTER("fill_schema_table_privileges");
4160
4161 LOCK_grant_read_guard lock(thd);
4162
4163 for (index=0 ; index < column_priv_hash.records ; index++)
4164 {
4165 const char *user, *host, *is_grantable= "YES";
4166 GRANT_TABLE *grant_table= (GRANT_TABLE*) my_hash_element(&column_priv_hash,
4167 index);
4168 if (!(user=grant_table->user))
4169 user= "";
4170 host= grant_table->host.get_host();
4171
4172 if (no_global_access &&
4173 (strcmp(thd->security_context()->priv_user().str, user) ||
4174 my_strcasecmp(system_charset_info, curr_host, host)))
4175 continue;
4176
4177 ulong table_access= grant_table->cols;
4178 if (table_access != 0)
4179 {
4180 if (!(grant_table->privs & GRANT_ACL))
4181 is_grantable= "NO";
4182
4183 ulong test_access= table_access & ~GRANT_ACL;
4184 strxmov(buff, "'", user, "'@'", host, "'", NullS);
4185 if (!test_access)
4186 continue;
4187 else
4188 {
4189 ulong j;
4190 int cnt;
4191 for (cnt= 0, j= SELECT_ACL; j <= TABLE_ACLS; cnt++, j<<= 1)
4192 {
4193 if (test_access & j)
4194 {
4195 for (uint col_index=0 ;
4196 col_index < grant_table->hash_columns.records ;
4197 col_index++)
4198 {
4199 GRANT_COLUMN *grant_column = (GRANT_COLUMN*)
4200 my_hash_element(&grant_table->hash_columns,col_index);
4201 if ((grant_column->rights & j) && (table_access & j))
4202 {
4203 if (update_schema_privilege(thd, table, buff, grant_table->db,
4204 grant_table->tname,
4205 grant_column->column,
4206 grant_column->key_length,
4207 command_array[cnt],
4208 command_lengths[cnt], is_grantable))
4209 {
4210 error= 1;
4211 goto err;
4212 }
4213 }
4214 }
4215 }
4216 }
4217 }
4218 }
4219 }
4220 err:
4221
4222 DBUG_RETURN(error);
4223 #else
4224 return (0);
4225 #endif /* NO_EMBEDDED_ACCESS_CHECKS */
4226 }
4227
4228 #ifndef NO_EMBEDDED_ACCESS_CHECKS
4229 bool
is_privileged_user_for_credential_change(THD * thd)4230 is_privileged_user_for_credential_change(THD *thd)
4231 {
4232 #ifdef HAVE_REPLICATION
4233 if (thd->slave_thread)
4234 return true;
4235 #endif /* HAVE_REPLICATION */
4236 return (!check_access(thd, UPDATE_ACL, "mysql", NULL, NULL, 1, 1) ||
4237 thd->security_context()->check_access(CREATE_USER_ACL, false));
4238 }
4239 #endif /* NO_EMBEDDED_ACCESS_CHECKS */
4240
4241 /**
4242 Check if user has enough privileges for execution of SHOW statement,
4243 which was converted to query to one of I_S tables.
4244
4245 @param thd Thread context.
4246 @param table Table list element for I_S table to be queried..
4247
4248 @retval FALSE - Success.
4249 @retval TRUE - Failure.
4250 */
4251
check_show_access(THD * thd,TABLE_LIST * table)4252 static bool check_show_access(THD *thd, TABLE_LIST *table)
4253 {
4254 #ifndef NO_EMBEDDED_ACCESS_CHECKS
4255 switch (get_schema_table_idx(table->schema_table)) {
4256 case SCH_SCHEMATA:
4257 return (specialflag & SPECIAL_SKIP_SHOW_DB) &&
4258 check_global_access(thd, SHOW_DB_ACL);
4259
4260 case SCH_TABLE_NAMES:
4261 case SCH_TABLES:
4262 case SCH_VIEWS:
4263 case SCH_TRIGGERS:
4264 case SCH_EVENTS:
4265 {
4266 const char *dst_db_name= table->schema_select_lex->db;
4267
4268 assert(dst_db_name);
4269
4270 if (check_access(thd, SELECT_ACL, dst_db_name,
4271 &thd->col_access, NULL, FALSE, FALSE))
4272 return TRUE;
4273
4274 if (!thd->col_access && check_grant_db(thd, dst_db_name))
4275 {
4276 my_error(ER_DBACCESS_DENIED_ERROR, MYF(0),
4277 thd->security_context()->priv_user().str,
4278 thd->security_context()->priv_host().str,
4279 dst_db_name);
4280 return TRUE;
4281 }
4282
4283 return FALSE;
4284 }
4285
4286 case SCH_COLUMNS:
4287 case SCH_STATISTICS:
4288 {
4289 TABLE_LIST *dst_table;
4290 dst_table= table->schema_select_lex->table_list.first;
4291
4292 assert(dst_table);
4293
4294 /*
4295 Open temporary tables to be able to detect them during privilege check.
4296 */
4297 if (open_temporary_tables(thd, dst_table))
4298 return TRUE;
4299
4300 if (check_access(thd, SELECT_ACL, dst_table->db,
4301 &dst_table->grant.privilege,
4302 &dst_table->grant.m_internal,
4303 FALSE, FALSE))
4304 return TRUE; /* Access denied */
4305
4306 /*
4307 Check_grant will grant access if there is any column privileges on
4308 all of the tables thanks to the fourth parameter (bool show_table).
4309 */
4310 if (check_grant(thd, SELECT_ACL, dst_table, TRUE, UINT_MAX, FALSE))
4311 return TRUE; /* Access denied */
4312
4313 close_thread_tables(thd);
4314 dst_table->table= NULL;
4315
4316 /* Access granted */
4317 return FALSE;
4318 }
4319 default:
4320 break;
4321 }
4322 #endif /* NO_EMBEDDED_ACCESS_CHECKS */
4323 return FALSE;
4324 }
4325
4326
4327 /**
4328 check for global access and give descriptive error message if it fails.
4329
4330 @param thd Thread handler
4331 @param want_access Use should have any of these global rights
4332
4333 @warning
4334 One gets access right if one has ANY of the rights in want_access.
4335 This is useful as one in most cases only need one global right,
4336 but in some case we want to check if the user has SUPER or
4337 REPL_CLIENT_ACL rights.
4338
4339 @retval
4340 0 ok
4341 @retval
4342 1 Access denied. In this case an error is sent to the client
4343 */
4344
check_global_access(THD * thd,ulong want_access)4345 bool check_global_access(THD *thd, ulong want_access)
4346 {
4347 DBUG_ENTER("check_global_access");
4348 #ifndef NO_EMBEDDED_ACCESS_CHECKS
4349 char command[128];
4350 if (thd->security_context()->check_access(want_access, true))
4351 DBUG_RETURN(0);
4352 get_privilege_desc(command, sizeof(command), want_access);
4353 my_error(ER_SPECIFIC_ACCESS_DENIED_ERROR, MYF(0), command);
4354 DBUG_RETURN(1);
4355 #else
4356 DBUG_RETURN(0);
4357 #endif /*NO_EMBEDDED_ACCESS_CHECKS */
4358 }
4359
4360
4361 /**
4362 Checks foreign key's parent table access.
4363
4364 @param thd [in] Thread handler
4365 @param child_table_db [in] Database of child table
4366 @param create_info [in] Create information (like MAX_ROWS, ENGINE or
4367 temporary table flag)
4368 @param alter_info [in] Initial list of columns and indexes for the
4369 table to be created
4370
4371 @retval
4372 false ok.
4373 @retval
4374 true error or access denied. Error is sent to client in this case.
4375 */
check_fk_parent_table_access(THD * thd,const char * child_table_db,HA_CREATE_INFO * create_info,Alter_info * alter_info)4376 bool check_fk_parent_table_access(THD *thd,
4377 const char *child_table_db,
4378 HA_CREATE_INFO *create_info,
4379 Alter_info *alter_info)
4380 {
4381 Key *key;
4382 List_iterator<Key> key_iterator(alter_info->key_list);
4383 handlerton *db_type= create_info->db_type ? create_info->db_type :
4384 ha_default_handlerton(thd);
4385
4386 // Return if engine does not support Foreign key Constraint.
4387 if (!ha_check_storage_engine_flag(db_type, HTON_SUPPORTS_FOREIGN_KEYS))
4388 return false;
4389
4390 while ((key= key_iterator++))
4391 {
4392 if (key->type == KEYTYPE_FOREIGN)
4393 {
4394 TABLE_LIST parent_table;
4395 bool is_qualified_table_name;
4396 Foreign_key *fk_key= (Foreign_key *)key;
4397 LEX_STRING db_name;
4398 LEX_STRING table_name= { (char *) fk_key->ref_table.str,
4399 fk_key->ref_table.length };
4400
4401 // Check if tablename is valid or not.
4402 assert(table_name.str != NULL);
4403 if (check_table_name(table_name.str, table_name.length, false))
4404 {
4405 my_error(ER_WRONG_TABLE_NAME, MYF(0), table_name.str);
4406 return true;
4407 }
4408
4409 if (fk_key->ref_db.str)
4410 {
4411 is_qualified_table_name= true;
4412 db_name.str= (char *) thd->memdup(fk_key->ref_db.str,
4413 fk_key->ref_db.length+1);
4414 db_name.length= fk_key->ref_db.length;
4415
4416 // Check if database name is valid or not.
4417 if (fk_key->ref_db.str && check_and_convert_db_name(&db_name, false))
4418 return true;
4419 }
4420 else
4421 {
4422 /*
4423 If database name for parent table is not specified explicitly
4424 SEs assume that it is the same as database name of child table.
4425 We do the same here.
4426 */
4427 is_qualified_table_name= false;
4428 db_name.str= const_cast<char*>(child_table_db);
4429 db_name.length= strlen(child_table_db);
4430 }
4431
4432 // if lower_case_table_names is set then convert tablename to lower case.
4433 if (lower_case_table_names)
4434 {
4435 table_name.str= (char *) thd->memdup(fk_key->ref_table.str,
4436 fk_key->ref_table.length+1);
4437 table_name.length= my_casedn_str(files_charset_info, table_name.str);
4438 }
4439
4440 parent_table.init_one_table(db_name.str, db_name.length,
4441 table_name.str, table_name.length,
4442 table_name.str, TL_IGNORE);
4443
4444 /*
4445 Check if user has REFERENCES_ACL privilege at table level on
4446 "parent_table".
4447 Having privilege on any of the parent_table column is not
4448 enough so checking whether user has REFERENCES_ACL privilege
4449 at table level here.
4450 */
4451 if ((check_access(thd, REFERENCES_ACL, parent_table.db,
4452 &parent_table.grant.privilege,
4453 &parent_table.grant.m_internal, false, true) ||
4454 check_grant(thd, REFERENCES_ACL, &parent_table, false, 1, true)) ||
4455 (parent_table.grant.privilege & REFERENCES_ACL) == 0)
4456 {
4457 if (is_qualified_table_name)
4458 {
4459 const size_t qualified_table_name_len= NAME_LEN + 1 + NAME_LEN + 1;
4460 char *qualified_table_name=
4461 (char *) thd->alloc(qualified_table_name_len);
4462
4463 my_snprintf(qualified_table_name, qualified_table_name_len, "%s.%s",
4464 db_name.str, table_name.str);
4465 table_name.str= qualified_table_name;
4466 }
4467
4468 my_error(ER_TABLEACCESS_DENIED_ERROR, MYF(0),
4469 "REFERENCES",
4470 thd->security_context()->priv_user().str,
4471 thd->security_context()->host_or_ip().str,
4472 table_name.str);
4473
4474 return true;
4475 }
4476 }
4477 }
4478
4479 return false;
4480 }
4481
4482
4483 /**
4484 For LOCK TABLES on a view checks if user in which context view is executed
4485 or user that has initiated this operation has SELECT and LOCK TABLES
4486 privileges on one of its underlying tables.
4487
4488 @param [in] thd Thread context.
4489 @param [in] tbl Table list element for underlying table
4490 on which we check privilege.
4491 @param [out] fake_lock_tables_acl Set to true if table in question is one
4492 of special I_S or P_S tables on which
4493 nobody can get LOCK TABLES privilege.
4494 So to preserve compatibility with dump
4495 tools we need to fake this privilege.
4496 Set to false otherwise.
4497
4498 @retval false Success.
4499 @retval true Access denied. Error has been reported.
4500 */
check_lock_view_underlying_table_access(THD * thd,TABLE_LIST * tbl,bool * fake_lock_tables_acl)4501 bool check_lock_view_underlying_table_access(THD *thd, TABLE_LIST *tbl,
4502 bool *fake_lock_tables_acl)
4503 {
4504 ulong want_access= SELECT_ACL | LOCK_TABLES_ACL;
4505 *fake_lock_tables_acl= false;
4506
4507 /*
4508 I_S and P_S tables require special handling of LOCK TABLES privilege
4509 in this case.
4510 On the one hand we don't grant this privileges on I_S and read-only/
4511 truncatable-only P_S tables to anyone. So normally you can't lock
4512 them directly using LOCK TABLES.
4513 On the other hand we allow creation of views which reference these
4514 tables. And mysqldump/pump tools routinely lock views using LOCK
4515 TABLES just to dump their definition in default mode. So refusing
4516 locking of such views will break mysqldump/pump. It will also break
4517 user scenarios in when views on top of I_S/P_S tables are locked along
4518 with other tables by LOCK TABLES, so they are accessible under LOCK
4519 TABLES mode. So we simply skip LOCK TABLES privilege check for I_S and
4520 read-only/ truncatable-only P_S tables. However, we report the fact to
4521 the caller, so it won't acquire strong metadata locks in this case,
4522 which can be considered privilege escalation.
4523 */
4524 const ACL_internal_schema_access *schema_access=
4525 get_cached_schema_access(&tbl->grant.m_internal, tbl->db);
4526 if (schema_access)
4527 {
4528 ulong dummy= 0;
4529 switch (schema_access->check(LOCK_TABLES_ACL, &dummy))
4530 {
4531 case ACL_INTERNAL_ACCESS_DENIED:
4532 *fake_lock_tables_acl= true;
4533 // Fall through.
4534 case ACL_INTERNAL_ACCESS_GRANTED:
4535 want_access&= ~LOCK_TABLES_ACL;
4536 break;
4537 case ACL_INTERNAL_ACCESS_CHECK_GRANT:
4538 const ACL_internal_table_access *table_access= get_cached_table_access(
4539 &tbl->grant.m_internal, tbl->db, tbl->table_name);
4540 if (table_access)
4541 {
4542 switch (table_access->check(LOCK_TABLES_ACL, &dummy))
4543 {
4544 case ACL_INTERNAL_ACCESS_DENIED:
4545 *fake_lock_tables_acl= true;
4546 // Fall through.
4547 case ACL_INTERNAL_ACCESS_GRANTED:
4548 want_access&= ~LOCK_TABLES_ACL;
4549 break;
4550 case ACL_INTERNAL_ACCESS_CHECK_GRANT:
4551 break;
4552 }
4553 }
4554 break;
4555 }
4556 }
4557
4558 if (!check_single_table_access(thd, want_access, tbl, true))
4559 return false;
4560
4561 /*
4562 As it was mentioned earlier mysqldump/pump tools routinely lock
4563 views just to dump their definition. This is supposed to work even
4564 for views with (temporarily) invalid definer. To avoid breaking
4565 this scenario we allow locking of view not only when user which
4566 security context will be used for its execution has LOCK TABLES
4567 and SELECT privileges on its underlying tbales, but also when
4568 the user which originally requested LOCK TABLES has the similar
4569 privileges on its underlying tables (which is likely to be the
4570 case for users invoking mysqldump/pump).
4571 */
4572 Security_context *save_security_ctx= tbl->security_ctx;
4573 tbl->security_ctx= NULL;
4574 bool top_user_has_privs=
4575 !check_single_table_access(thd, want_access, tbl, true);
4576 tbl->security_ctx= save_security_ctx;
4577
4578 if (top_user_has_privs)
4579 return false;
4580
4581 my_error(ER_VIEW_INVALID, MYF(0), tbl->belong_to_view->get_db_name(),
4582 tbl->belong_to_view->get_table_name());
4583 return true;
4584 }
4585