1 | /* Copyright (c) 2000, 2016, Oracle and/or its affiliates. |
2 | Copyright (c) 2009, 2018, MariaDB |
3 | |
4 | This program is free software; you can redistribute it and/or modify |
5 | it under the terms of the GNU General Public License as published by |
6 | the Free Software Foundation; version 2 of the License. |
7 | |
8 | This program is distributed in the hope that it will be useful, |
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
11 | GNU General Public License for more details. |
12 | |
13 | You should have received a copy of the GNU General Public License |
14 | along with this program; if not, write to the Free Software |
15 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ |
16 | |
17 | |
18 | /* |
19 | The privileges are saved in the following tables: |
20 | mysql/user ; super user who are allowed to do almost anything |
21 | mysql/host ; host privileges. This is used if host is empty in mysql/db. |
22 | mysql/db ; database privileges / user |
23 | |
24 | data in tables is sorted according to how many not-wild-cards there is |
25 | in the relevant fields. Empty strings comes last. |
26 | */ |
27 | |
28 | #include "mariadb.h" /* NO_EMBEDDED_ACCESS_CHECKS */ |
29 | #include "sql_priv.h" |
30 | #include "sql_acl.h" // MYSQL_DB_FIELD_COUNT, ACL_ACCESS |
31 | #include "sql_base.h" // close_mysql_tables |
32 | #include "key.h" // key_copy, key_cmp_if_same, key_restore |
33 | #include "sql_show.h" // append_identifier |
34 | #include "sql_table.h" // write_bin_log |
35 | #include "hash_filo.h" |
36 | #include "sql_parse.h" // check_access |
37 | #include "sql_view.h" // VIEW_ANY_ACL |
38 | #include "records.h" // READ_RECORD, read_record_info, |
39 | // init_read_record, end_read_record |
40 | #include "rpl_filter.h" // rpl_filter |
41 | #include "rpl_rli.h" |
42 | #include <m_ctype.h> |
43 | #include <stdarg.h> |
44 | #include "sp_head.h" |
45 | #include "sp.h" |
46 | #include "transaction.h" |
47 | #include "lock.h" // MYSQL_LOCK_IGNORE_TIMEOUT |
48 | #include <sql_common.h> |
49 | #include <mysql/plugin_auth.h> |
50 | #include <mysql/plugin_password_validation.h> |
51 | #include "sql_connect.h" |
52 | #include "hostname.h" |
53 | #include "sql_db.h" |
54 | #include "sql_array.h" |
55 | #include "sql_hset.h" |
56 | #include "password.h" |
57 | |
58 | #include "sql_plugin_compat.h" |
59 | |
60 | bool mysql_user_table_is_in_short_password_format= false; |
61 | |
62 | static LEX_CSTRING native_password_plugin_name= { |
63 | STRING_WITH_LEN("mysql_native_password" ) |
64 | }; |
65 | |
66 | static LEX_CSTRING old_password_plugin_name= { |
67 | STRING_WITH_LEN("mysql_old_password" ) |
68 | }; |
69 | |
70 | /// @todo make it configurable |
71 | LEX_CSTRING *default_auth_plugin_name= &native_password_plugin_name; |
72 | |
73 | /* |
74 | Wildcard host, matches any hostname |
75 | */ |
76 | LEX_CSTRING host_not_specified= { STRING_WITH_LEN("%" ) }; |
77 | |
78 | /* |
79 | Constants, used in the SHOW GRANTS command. |
80 | Their actual string values are irrelevant, they're always compared |
81 | as pointers to these string constants. |
82 | */ |
83 | LEX_CSTRING current_user= { STRING_WITH_LEN("*current_user" ) }; |
84 | LEX_CSTRING current_role= { STRING_WITH_LEN("*current_role" ) }; |
85 | LEX_CSTRING current_user_and_current_role= { STRING_WITH_LEN("*current_user_and_current_role" ) }; |
86 | |
87 | |
88 | #ifndef NO_EMBEDDED_ACCESS_CHECKS |
89 | static plugin_ref old_password_plugin; |
90 | #endif |
91 | static plugin_ref native_password_plugin; |
92 | |
93 | /* Classes */ |
94 | |
95 | struct acl_host_and_ip |
96 | { |
97 | char *hostname; |
98 | long ip, ip_mask; // Used with masked ip:s |
99 | }; |
100 | |
101 | #ifndef NO_EMBEDDED_ACCESS_CHECKS |
102 | static bool compare_hostname(const acl_host_and_ip *, const char *, const char *); |
103 | #else |
104 | #define compare_hostname(X,Y,Z) 0 |
105 | #endif |
106 | |
107 | class ACL_ACCESS { |
108 | public: |
109 | ulong sort; |
110 | ulong access; |
111 | }; |
112 | |
113 | /* ACL_HOST is used if no host is specified */ |
114 | |
115 | class ACL_HOST :public ACL_ACCESS |
116 | { |
117 | public: |
118 | acl_host_and_ip host; |
119 | char *db; |
120 | }; |
121 | |
122 | class ACL_USER_BASE :public ACL_ACCESS |
123 | { |
124 | |
125 | public: |
126 | static void *operator new(size_t size, MEM_ROOT *mem_root) |
127 | { return (void*) alloc_root(mem_root, size); } |
128 | static void operator delete(void *, MEM_ROOT *){} |
129 | uchar flags; // field used to store various state information |
130 | LEX_CSTRING user; |
131 | /* list to hold references to granted roles (ACL_ROLE instances) */ |
132 | DYNAMIC_ARRAY role_grants; |
133 | }; |
134 | |
135 | class ACL_USER :public ACL_USER_BASE |
136 | { |
137 | public: |
138 | acl_host_and_ip host; |
139 | size_t hostname_length; |
140 | USER_RESOURCES user_resource; |
141 | uint8 salt[SCRAMBLE_LENGTH + 1]; // scrambled password in binary form |
142 | uint8 salt_len; // 0 - no password, 4 - 3.20, 8 - 4.0, 20 - 4.1.1 |
143 | enum SSL_type ssl_type; |
144 | const char *ssl_cipher, *x509_issuer, *x509_subject; |
145 | LEX_CSTRING plugin; |
146 | LEX_CSTRING auth_string; |
147 | LEX_CSTRING default_rolename; |
148 | |
149 | ACL_USER *copy(MEM_ROOT *root) |
150 | { |
151 | ACL_USER *dst= (ACL_USER *) alloc_root(root, sizeof(ACL_USER)); |
152 | if (!dst) |
153 | return 0; |
154 | *dst= *this; |
155 | dst->user.str= safe_strdup_root(root, user.str); |
156 | dst->user.length= user.length; |
157 | dst->ssl_cipher= safe_strdup_root(root, ssl_cipher); |
158 | dst->x509_issuer= safe_strdup_root(root, x509_issuer); |
159 | dst->x509_subject= safe_strdup_root(root, x509_subject); |
160 | if (plugin.str == native_password_plugin_name.str || |
161 | plugin.str == old_password_plugin_name.str) |
162 | dst->plugin= plugin; |
163 | else |
164 | dst->plugin.str= strmake_root(root, plugin.str, plugin.length); |
165 | dst->auth_string.str= safe_strdup_root(root, auth_string.str); |
166 | dst->host.hostname= safe_strdup_root(root, host.hostname); |
167 | dst->default_rolename.str= safe_strdup_root(root, default_rolename.str); |
168 | dst->default_rolename.length= default_rolename.length; |
169 | bzero(&dst->role_grants, sizeof(role_grants)); |
170 | return dst; |
171 | } |
172 | |
173 | int cmp(const char *user2, const char *host2) |
174 | { |
175 | CHARSET_INFO *cs= system_charset_info; |
176 | int res; |
177 | res= strcmp(safe_str(user.str), safe_str(user2)); |
178 | if (!res) |
179 | res= my_strcasecmp(cs, host.hostname, host2); |
180 | return res; |
181 | } |
182 | |
183 | bool eq(const char *user2, const char *host2) { return !cmp(user2, host2); } |
184 | |
185 | bool wild_eq(const char *user2, const char *host2, const char *ip2) |
186 | { |
187 | if (strcmp(safe_str(user.str), safe_str(user2))) |
188 | return false; |
189 | |
190 | return compare_hostname(&host, host2, ip2 ? ip2 : host2); |
191 | } |
192 | }; |
193 | |
194 | class ACL_ROLE :public ACL_USER_BASE |
195 | { |
196 | public: |
197 | /* |
198 | In case of granting a role to a role, the access bits are merged together |
199 | via a bit OR operation and placed in the ACL_USER::access field. |
200 | |
201 | When rebuilding role_grants via the rebuild_role_grant function, |
202 | the ACL_USER::access field needs to be reset first. The field |
203 | initial_role_access holds initial grants, as granted directly to the role |
204 | */ |
205 | ulong initial_role_access; |
206 | /* |
207 | In subgraph traversal, when we need to traverse only a part of the graph |
208 | (e.g. all direct and indirect grantees of a role X), the counter holds the |
209 | number of affected neighbour nodes. |
210 | See also propagate_role_grants() |
211 | */ |
212 | uint counter; |
213 | DYNAMIC_ARRAY parent_grantee; // array of backlinks to elements granted |
214 | |
215 | ACL_ROLE(ACL_USER * user, MEM_ROOT *mem); |
216 | ACL_ROLE(const char * rolename, ulong privileges, MEM_ROOT *mem); |
217 | |
218 | }; |
219 | |
220 | class ACL_DB :public ACL_ACCESS |
221 | { |
222 | public: |
223 | acl_host_and_ip host; |
224 | const char *user,*db; |
225 | ulong initial_access; /* access bits present in the table */ |
226 | }; |
227 | |
228 | #ifndef DBUG_OFF |
229 | /* status variables, only visible in SHOW STATUS after -#d,role_merge_stats */ |
230 | ulong role_global_merges= 0, role_db_merges= 0, role_table_merges= 0, |
231 | role_column_merges= 0, role_routine_merges= 0; |
232 | #endif |
233 | |
234 | #ifndef NO_EMBEDDED_ACCESS_CHECKS |
235 | static bool fix_and_copy_user(LEX_USER *to, LEX_USER *from, THD *thd); |
236 | static void update_hostname(acl_host_and_ip *host, const char *hostname); |
237 | static ulong get_sort(uint count,...); |
238 | static bool show_proxy_grants (THD *, const char *, const char *, |
239 | char *, size_t); |
240 | static bool show_role_grants(THD *, const char *, const char *, |
241 | ACL_USER_BASE *, char *, size_t); |
242 | static bool show_global_privileges(THD *, ACL_USER_BASE *, |
243 | bool, char *, size_t); |
244 | static bool show_database_privileges(THD *, const char *, const char *, |
245 | char *, size_t); |
246 | static bool show_table_and_column_privileges(THD *, const char *, const char *, |
247 | char *, size_t); |
248 | static int show_routine_grants(THD *, const char *, const char *, |
249 | const Sp_handler *sph, char *, int); |
250 | |
251 | class Grant_tables; |
252 | class User_table; |
253 | class Proxies_priv_table; |
254 | |
255 | class ACL_PROXY_USER :public ACL_ACCESS |
256 | { |
257 | acl_host_and_ip host; |
258 | const char *user; |
259 | acl_host_and_ip proxied_host; |
260 | const char *proxied_user; |
261 | bool with_grant; |
262 | |
263 | typedef enum { |
264 | MYSQL_PROXIES_PRIV_HOST, |
265 | MYSQL_PROXIES_PRIV_USER, |
266 | MYSQL_PROXIES_PRIV_PROXIED_HOST, |
267 | MYSQL_PROXIES_PRIV_PROXIED_USER, |
268 | MYSQL_PROXIES_PRIV_WITH_GRANT, |
269 | MYSQL_PROXIES_PRIV_GRANTOR, |
270 | MYSQL_PROXIES_PRIV_TIMESTAMP } proxy_table_fields; |
271 | public: |
272 | ACL_PROXY_USER () {}; |
273 | |
274 | void init(const char *host_arg, const char *user_arg, |
275 | const char *proxied_host_arg, const char *proxied_user_arg, |
276 | bool with_grant_arg) |
277 | { |
278 | user= (user_arg && *user_arg) ? user_arg : NULL; |
279 | update_hostname (&host, (host_arg && *host_arg) ? host_arg : NULL); |
280 | proxied_user= (proxied_user_arg && *proxied_user_arg) ? |
281 | proxied_user_arg : NULL; |
282 | update_hostname (&proxied_host, |
283 | (proxied_host_arg && *proxied_host_arg) ? |
284 | proxied_host_arg : NULL); |
285 | with_grant= with_grant_arg; |
286 | sort= get_sort(4, host.hostname, user, proxied_host.hostname, proxied_user); |
287 | } |
288 | |
289 | void init(MEM_ROOT *mem, const char *host_arg, const char *user_arg, |
290 | const char *proxied_host_arg, const char *proxied_user_arg, |
291 | bool with_grant_arg) |
292 | { |
293 | init ((host_arg && *host_arg) ? strdup_root (mem, host_arg) : NULL, |
294 | (user_arg && *user_arg) ? strdup_root (mem, user_arg) : NULL, |
295 | (proxied_host_arg && *proxied_host_arg) ? |
296 | strdup_root (mem, proxied_host_arg) : NULL, |
297 | (proxied_user_arg && *proxied_user_arg) ? |
298 | strdup_root (mem, proxied_user_arg) : NULL, |
299 | with_grant_arg); |
300 | } |
301 | |
302 | void init(const Proxies_priv_table& proxies_priv_table, MEM_ROOT *mem); |
303 | |
304 | bool get_with_grant() { return with_grant; } |
305 | const char *get_user() { return user; } |
306 | const char *get_host() { return host.hostname; } |
307 | const char *get_proxied_user() { return proxied_user; } |
308 | const char *get_proxied_host() { return proxied_host.hostname; } |
309 | void set_user(MEM_ROOT *mem, const char *user_arg) |
310 | { |
311 | user= user_arg && *user_arg ? strdup_root(mem, user_arg) : NULL; |
312 | } |
313 | void set_host(MEM_ROOT *mem, const char *host_arg) |
314 | { |
315 | update_hostname(&host, safe_strdup_root(mem, host_arg)); |
316 | } |
317 | |
318 | bool check_validity(bool check_no_resolve) |
319 | { |
320 | if (check_no_resolve && |
321 | (hostname_requires_resolving(host.hostname) || |
322 | hostname_requires_resolving(proxied_host.hostname))) |
323 | { |
324 | sql_print_warning("'proxies_priv' entry '%s@%s %s@%s' " |
325 | "ignored in --skip-name-resolve mode." , |
326 | safe_str(proxied_user), |
327 | safe_str(proxied_host.hostname), |
328 | safe_str(user), |
329 | safe_str(host.hostname)); |
330 | return TRUE; |
331 | } |
332 | return FALSE; |
333 | } |
334 | |
335 | bool matches(const char *host_arg, const char *user_arg, const char *ip_arg, |
336 | const char *proxied_user_arg) |
337 | { |
338 | DBUG_ENTER("ACL_PROXY_USER::matches" ); |
339 | DBUG_PRINT("info" , ("compare_hostname(%s,%s,%s) &&" |
340 | "compare_hostname(%s,%s,%s) &&" |
341 | "wild_compare (%s,%s) &&" |
342 | "wild_compare (%s,%s)" , |
343 | host.hostname, host_arg, ip_arg, proxied_host.hostname, |
344 | host_arg, ip_arg, user_arg, user, |
345 | proxied_user_arg, proxied_user)); |
346 | DBUG_RETURN(compare_hostname(&host, host_arg, ip_arg) && |
347 | compare_hostname(&proxied_host, host_arg, ip_arg) && |
348 | (!user || |
349 | (user_arg && !wild_compare(user_arg, user, TRUE))) && |
350 | (!proxied_user || |
351 | (proxied_user && !wild_compare(proxied_user_arg, |
352 | proxied_user, TRUE)))); |
353 | } |
354 | |
355 | |
356 | inline static bool auth_element_equals(const char *a, const char *b) |
357 | { |
358 | return (a == b || (a != NULL && b != NULL && !strcmp(a,b))); |
359 | } |
360 | |
361 | |
362 | bool pk_equals(ACL_PROXY_USER *grant) |
363 | { |
364 | DBUG_ENTER("pk_equals" ); |
365 | DBUG_PRINT("info" , ("strcmp(%s,%s) &&" |
366 | "strcmp(%s,%s) &&" |
367 | "wild_compare (%s,%s) &&" |
368 | "wild_compare (%s,%s)" , |
369 | user, grant->user, proxied_user, grant->proxied_user, |
370 | host.hostname, grant->host.hostname, |
371 | proxied_host.hostname, grant->proxied_host.hostname)); |
372 | |
373 | bool res= auth_element_equals(user, grant->user) && |
374 | auth_element_equals(proxied_user, grant->proxied_user) && |
375 | auth_element_equals(host.hostname, grant->host.hostname) && |
376 | auth_element_equals(proxied_host.hostname, |
377 | grant->proxied_host.hostname); |
378 | DBUG_RETURN(res); |
379 | } |
380 | |
381 | |
382 | bool granted_on(const char *host_arg, const char *user_arg) |
383 | { |
384 | return (((!user && (!user_arg || !user_arg[0])) || |
385 | (user && user_arg && !strcmp(user, user_arg))) && |
386 | ((!host.hostname && (!host_arg || !host_arg[0])) || |
387 | (host.hostname && host_arg && !strcmp(host.hostname, host_arg)))); |
388 | } |
389 | |
390 | |
391 | void print_grant(String *str) |
392 | { |
393 | str->append(STRING_WITH_LEN("GRANT PROXY ON '" )); |
394 | if (proxied_user) |
395 | str->append(proxied_user, strlen(proxied_user)); |
396 | str->append(STRING_WITH_LEN("'@'" )); |
397 | if (proxied_host.hostname) |
398 | str->append(proxied_host.hostname, strlen(proxied_host.hostname)); |
399 | str->append(STRING_WITH_LEN("' TO '" )); |
400 | if (user) |
401 | str->append(user, strlen(user)); |
402 | str->append(STRING_WITH_LEN("'@'" )); |
403 | if (host.hostname) |
404 | str->append(host.hostname, strlen(host.hostname)); |
405 | str->append(STRING_WITH_LEN("'" )); |
406 | if (with_grant) |
407 | str->append(STRING_WITH_LEN(" WITH GRANT OPTION" )); |
408 | } |
409 | |
410 | void set_data(ACL_PROXY_USER *grant) |
411 | { |
412 | with_grant= grant->with_grant; |
413 | } |
414 | |
415 | static int store_pk(TABLE *table, |
416 | const LEX_CSTRING *host, |
417 | const LEX_CSTRING *user, |
418 | const LEX_CSTRING *proxied_host, |
419 | const LEX_CSTRING *proxied_user) |
420 | { |
421 | DBUG_ENTER("ACL_PROXY_USER::store_pk" ); |
422 | DBUG_PRINT("info" , ("host=%s, user=%s, proxied_host=%s, proxied_user=%s" , |
423 | host->str, user->str, |
424 | proxied_host->str, proxied_user->str)); |
425 | if (table->field[MYSQL_PROXIES_PRIV_HOST]->store(host->str, |
426 | host->length, |
427 | system_charset_info)) |
428 | DBUG_RETURN(TRUE); |
429 | if (table->field[MYSQL_PROXIES_PRIV_USER]->store(user->str, |
430 | user->length, |
431 | system_charset_info)) |
432 | DBUG_RETURN(TRUE); |
433 | if (table->field[MYSQL_PROXIES_PRIV_PROXIED_HOST]->store(proxied_host->str, |
434 | proxied_host->length, |
435 | system_charset_info)) |
436 | DBUG_RETURN(TRUE); |
437 | if (table->field[MYSQL_PROXIES_PRIV_PROXIED_USER]->store(proxied_user->str, |
438 | proxied_user->length, |
439 | system_charset_info)) |
440 | DBUG_RETURN(TRUE); |
441 | |
442 | DBUG_RETURN(FALSE); |
443 | } |
444 | |
445 | static int store_data_record(TABLE *table, |
446 | const LEX_CSTRING *host, |
447 | const LEX_CSTRING *user, |
448 | const LEX_CSTRING *proxied_host, |
449 | const LEX_CSTRING *proxied_user, |
450 | bool with_grant, |
451 | const char *grantor) |
452 | { |
453 | DBUG_ENTER("ACL_PROXY_USER::store_pk" ); |
454 | if (store_pk(table, host, user, proxied_host, proxied_user)) |
455 | DBUG_RETURN(TRUE); |
456 | DBUG_PRINT("info" , ("with_grant=%s" , with_grant ? "TRUE" : "FALSE" )); |
457 | if (table->field[MYSQL_PROXIES_PRIV_WITH_GRANT]->store(with_grant ? 1 : 0, |
458 | TRUE)) |
459 | DBUG_RETURN(TRUE); |
460 | if (table->field[MYSQL_PROXIES_PRIV_GRANTOR]->store(grantor, |
461 | strlen(grantor), |
462 | system_charset_info)) |
463 | DBUG_RETURN(TRUE); |
464 | |
465 | DBUG_RETURN(FALSE); |
466 | } |
467 | }; |
468 | |
469 | #define FIRST_NON_YN_FIELD 26 |
470 | |
471 | class acl_entry :public hash_filo_element |
472 | { |
473 | public: |
474 | ulong access; |
475 | uint16 length; |
476 | char key[1]; // Key will be stored here |
477 | }; |
478 | |
479 | |
480 | static uchar* acl_entry_get_key(acl_entry *entry, size_t *length, |
481 | my_bool not_used __attribute__((unused))) |
482 | { |
483 | *length=(uint) entry->length; |
484 | return (uchar*) entry->key; |
485 | } |
486 | |
487 | static uchar* acl_role_get_key(ACL_ROLE *entry, size_t *length, |
488 | my_bool not_used __attribute__((unused))) |
489 | { |
490 | *length=(uint) entry->user.length; |
491 | return (uchar*) entry->user.str; |
492 | } |
493 | |
494 | struct ROLE_GRANT_PAIR : public Sql_alloc |
495 | { |
496 | char *u_uname; |
497 | char *u_hname; |
498 | char *r_uname; |
499 | LEX_STRING hashkey; |
500 | bool with_admin; |
501 | |
502 | bool init(MEM_ROOT *mem, const char *username, const char *hostname, |
503 | const char *rolename, bool with_admin_option); |
504 | }; |
505 | |
506 | static uchar* acl_role_map_get_key(ROLE_GRANT_PAIR *entry, size_t *length, |
507 | my_bool not_used __attribute__((unused))) |
508 | { |
509 | *length=(uint) entry->hashkey.length; |
510 | return (uchar*) entry->hashkey.str; |
511 | } |
512 | |
513 | bool ROLE_GRANT_PAIR::init(MEM_ROOT *mem, const char *username, |
514 | const char *hostname, const char *rolename, |
515 | bool with_admin_option) |
516 | { |
517 | size_t uname_l = safe_strlen(username); |
518 | size_t hname_l = safe_strlen(hostname); |
519 | size_t rname_l = safe_strlen(rolename); |
520 | /* |
521 | Create a buffer that holds all 3 NULL terminated strings in succession |
522 | To save memory space, the same buffer is used as the hashkey |
523 | */ |
524 | size_t bufflen = uname_l + hname_l + rname_l + 3; //add the '\0' aswell |
525 | char *buff= (char *)alloc_root(mem, bufflen); |
526 | if (!buff) |
527 | return true; |
528 | |
529 | /* |
530 | Offsets in the buffer for all 3 strings |
531 | */ |
532 | char *username_pos= buff; |
533 | char *hostname_pos= buff + uname_l + 1; |
534 | char *rolename_pos= buff + uname_l + hname_l + 2; |
535 | |
536 | if (username) //prevent undefined behaviour |
537 | memcpy(username_pos, username, uname_l); |
538 | username_pos[uname_l]= '\0'; //#1 string terminator |
539 | u_uname= username_pos; |
540 | |
541 | if (hostname) //prevent undefined behaviour |
542 | memcpy(hostname_pos, hostname, hname_l); |
543 | hostname_pos[hname_l]= '\0'; //#2 string terminator |
544 | u_hname= hostname_pos; |
545 | |
546 | if (rolename) //prevent undefined behaviour |
547 | memcpy(rolename_pos, rolename, rname_l); |
548 | rolename_pos[rname_l]= '\0'; //#3 string terminator |
549 | r_uname= rolename_pos; |
550 | |
551 | hashkey.str = buff; |
552 | hashkey.length = bufflen; |
553 | |
554 | with_admin= with_admin_option; |
555 | |
556 | return false; |
557 | } |
558 | |
559 | #define IP_ADDR_STRLEN (3 + 1 + 3 + 1 + 3 + 1 + 3) |
560 | #define ACL_KEY_LENGTH (IP_ADDR_STRLEN + 1 + NAME_LEN + \ |
561 | 1 + USERNAME_LENGTH + 1) |
562 | |
563 | #if defined(HAVE_OPENSSL) |
564 | /* |
565 | Without SSL the handshake consists of one packet. This packet |
566 | has both client capabilities and scrambled password. |
567 | With SSL the handshake might consist of two packets. If the first |
568 | packet (client capabilities) has CLIENT_SSL flag set, we have to |
569 | switch to SSL and read the second packet. The scrambled password |
570 | is in the second packet and client_capabilities field will be ignored. |
571 | Maybe it is better to accept flags other than CLIENT_SSL from the |
572 | second packet? |
573 | */ |
574 | #define SSL_HANDSHAKE_SIZE 2 |
575 | #define MIN_HANDSHAKE_SIZE 2 |
576 | #else |
577 | #define MIN_HANDSHAKE_SIZE 6 |
578 | #endif /* HAVE_OPENSSL && !EMBEDDED_LIBRARY */ |
579 | #define NORMAL_HANDSHAKE_SIZE 6 |
580 | |
581 | #define ROLE_ASSIGN_COLUMN_IDX 44 |
582 | #define DEFAULT_ROLE_COLUMN_IDX 45 |
583 | #define MAX_STATEMENT_TIME_COLUMN_IDX 46 |
584 | |
585 | /* various flags valid for ACL_USER */ |
586 | #define IS_ROLE (1L << 0) |
587 | /* Flag to mark that a ROLE is on the recursive DEPTH_FIRST_SEARCH stack */ |
588 | #define ROLE_ON_STACK (1L << 1) |
589 | /* |
590 | Flag to mark that a ROLE and all it's neighbours have |
591 | been visited |
592 | */ |
593 | #define ROLE_EXPLORED (1L << 2) |
594 | /* Flag to mark that on_node was already called for this role */ |
595 | #define ROLE_OPENED (1L << 3) |
596 | |
597 | static DYNAMIC_ARRAY acl_hosts, acl_users, acl_dbs, acl_proxy_users; |
598 | static HASH acl_roles; |
599 | /* |
600 | An hash containing mappings user <--> role |
601 | |
602 | A hash is used so as to make updates quickly |
603 | The hashkey used represents all the entries combined |
604 | */ |
605 | static HASH acl_roles_mappings; |
606 | static MEM_ROOT acl_memroot, grant_memroot; |
607 | static bool initialized=0; |
608 | static bool allow_all_hosts=1; |
609 | static HASH acl_check_hosts, column_priv_hash, proc_priv_hash, func_priv_hash; |
610 | static HASH package_spec_priv_hash, package_body_priv_hash; |
611 | static DYNAMIC_ARRAY acl_wild_hosts; |
612 | static Hash_filo<acl_entry> *acl_cache; |
613 | static uint grant_version=0; /* Version of priv tables. incremented by acl_load */ |
614 | static ulong get_access(TABLE *form,uint fieldnr, uint *next_field=0); |
615 | static int acl_compare(ACL_ACCESS *a,ACL_ACCESS *b); |
616 | static ulong get_sort(uint count,...); |
617 | static void init_check_host(void); |
618 | static void rebuild_check_host(void); |
619 | static void rebuild_role_grants(void); |
620 | static ACL_USER *find_user_exact(const char *host, const char *user); |
621 | static ACL_USER *find_user_wild(const char *host, const char *user, const char *ip= 0); |
622 | static ACL_ROLE *find_acl_role(const char *user); |
623 | static ROLE_GRANT_PAIR *find_role_grant_pair(const LEX_CSTRING *u, const LEX_CSTRING *h, const LEX_CSTRING *r); |
624 | static ACL_USER_BASE *find_acl_user_base(const char *user, const char *host); |
625 | static bool update_user_table(THD *, const User_table &, const char *, const char *, const |
626 | char *, size_t new_password_len); |
627 | static bool acl_load(THD *thd, const Grant_tables& grant_tables); |
628 | static inline void get_grantor(THD *thd, char* grantor); |
629 | static bool add_role_user_mapping(const char *uname, const char *hname, const char *rname); |
630 | static bool get_YN_as_bool(Field *field); |
631 | |
632 | #define ROLE_CYCLE_FOUND 2 |
633 | static int traverse_role_graph_up(ACL_ROLE *, void *, |
634 | int (*) (ACL_ROLE *, void *), |
635 | int (*) (ACL_ROLE *, ACL_ROLE *, void *)); |
636 | |
637 | static int traverse_role_graph_down(ACL_USER_BASE *, void *, |
638 | int (*) (ACL_USER_BASE *, void *), |
639 | int (*) (ACL_USER_BASE *, ACL_ROLE *, void *)); |
640 | |
641 | |
642 | HASH *Sp_handler_procedure::get_priv_hash() const |
643 | { |
644 | return &proc_priv_hash; |
645 | } |
646 | |
647 | |
648 | HASH *Sp_handler_function::get_priv_hash() const |
649 | { |
650 | return &func_priv_hash; |
651 | } |
652 | |
653 | |
654 | HASH *Sp_handler_package_spec::get_priv_hash() const |
655 | { |
656 | return &package_spec_priv_hash; |
657 | } |
658 | |
659 | |
660 | HASH *Sp_handler_package_body::get_priv_hash() const |
661 | { |
662 | return &package_body_priv_hash; |
663 | } |
664 | |
665 | |
666 | /* |
667 | Enumeration of ACL/GRANT tables in the mysql database |
668 | */ |
669 | enum enum_acl_tables |
670 | { |
671 | USER_TABLE, |
672 | DB_TABLE, |
673 | TABLES_PRIV_TABLE, |
674 | COLUMNS_PRIV_TABLE, |
675 | #define FIRST_OPTIONAL_TABLE HOST_TABLE |
676 | HOST_TABLE, |
677 | PROCS_PRIV_TABLE, |
678 | PROXIES_PRIV_TABLE, |
679 | ROLES_MAPPING_TABLE, |
680 | TABLES_MAX // <== always the last |
681 | }; |
682 | // bits for open_grant_tables |
683 | static const int Table_user= 1 << USER_TABLE; |
684 | static const int Table_db= 1 << DB_TABLE; |
685 | static const int Table_tables_priv= 1 << TABLES_PRIV_TABLE; |
686 | static const int Table_columns_priv= 1 << COLUMNS_PRIV_TABLE; |
687 | static const int Table_host= 1 << HOST_TABLE; |
688 | static const int Table_procs_priv= 1 << PROCS_PRIV_TABLE; |
689 | static const int Table_proxies_priv= 1 << PROXIES_PRIV_TABLE; |
690 | static const int Table_roles_mapping= 1 << ROLES_MAPPING_TABLE; |
691 | |
692 | /** |
693 | Base class representing a generic grant table from the mysql database. |
694 | |
695 | The potential tables that this class can represent are: |
696 | user, db, columns_priv, tables_priv, host, procs_priv, proxies_priv, |
697 | roles_mapping |
698 | |
699 | Objects belonging to this parent class can only be constructed by the |
700 | Grants_table class. This ensures the correct initialization of the objects. |
701 | */ |
702 | class Grant_table_base |
703 | { |
704 | public: |
705 | /* Number of fields for this Grant Table. */ |
706 | uint num_fields() const { return tl.table->s->fields; } |
707 | /* Check if the table exists after an attempt to open it was made. |
708 | Some tables, such as the host table in MySQL 5.6.7+ are missing. */ |
709 | bool table_exists() const { return tl.table; }; |
710 | /* Initializes the READ_RECORD structure provided as a parameter |
711 | to read through the whole table, with all columns available. Cleaning up |
712 | is the caller's job. */ |
713 | bool init_read_record(READ_RECORD* info, THD* thd) const |
714 | { |
715 | DBUG_ASSERT(tl.table); |
716 | bool result= ::init_read_record(info, thd, tl.table, NULL, NULL, 1, |
717 | true, false); |
718 | if (!result) |
719 | tl.table->use_all_columns(); |
720 | return result; |
721 | } |
722 | |
723 | /* Return the number of privilege columns for this table. */ |
724 | uint num_privileges() const { return num_privilege_cols; } |
725 | /* Return a privilege column by index. */ |
726 | Field* priv_field(uint privilege_idx) const |
727 | { |
728 | DBUG_ASSERT(privilege_idx < num_privileges()); |
729 | return tl.table->field[start_privilege_column + privilege_idx]; |
730 | } |
731 | |
732 | /* Fetch the privileges from the table as a set of bits. The first column |
733 | is represented by the first bit in the result, the second column by the |
734 | second bit, etc. */ |
735 | ulong get_access() const |
736 | { |
737 | return get_access(start_privilege_column, |
738 | start_privilege_column + num_privileges() - 1); |
739 | } |
740 | |
741 | /* Return the underlying TABLE handle. */ |
742 | TABLE* table() const |
743 | { |
744 | return tl.table; |
745 | } |
746 | |
747 | /** Check if the table was opened, issue an error otherwise. */ |
748 | int no_such_table() const |
749 | { |
750 | if (table_exists()) |
751 | return 0; |
752 | |
753 | my_error(ER_NO_SUCH_TABLE, MYF(0), tl.db.str, tl.alias.str); |
754 | return 1; |
755 | } |
756 | |
757 | |
758 | protected: |
759 | friend class Grant_tables; |
760 | |
761 | Grant_table_base() : start_privilege_column(0), num_privilege_cols(0) |
762 | { |
763 | bzero(&tl, sizeof(tl)); |
764 | }; |
765 | |
766 | /* Initialization sequence common for all grant tables. This should be called |
767 | after all table-specific initialization is performed. */ |
768 | void init(enum thr_lock_type lock_type, bool is_optional) |
769 | { |
770 | tl.open_type= OT_BASE_ONLY; |
771 | if (lock_type >= TL_WRITE_ALLOW_WRITE) |
772 | tl.updating= 1; |
773 | if (is_optional) |
774 | tl.open_strategy= TABLE_LIST::OPEN_IF_EXISTS; |
775 | } |
776 | |
777 | /* |
778 | Get all access bits from table between start_field and end_field indices. |
779 | |
780 | IMPLEMENTATION |
781 | The record should be already read in table->record[0]. All privileges |
782 | are specified as an ENUM(Y,N). |
783 | |
784 | SYNOPSIS |
785 | get_access() |
786 | start_field_idx The field index at which the first privilege |
787 | specification begins. |
788 | end_field_idx The field index at which the last privilege |
789 | specification is located. |
790 | |
791 | RETURN VALUE |
792 | privilege mask |
793 | */ |
794 | ulong get_access(uint start_field_idx, uint end_field_idx) const |
795 | { |
796 | ulong access_bits= 0, bit= 1; |
797 | for (uint i = start_field_idx; i <= end_field_idx; i++, bit<<=1) |
798 | { |
799 | if (get_YN_as_bool(tl.table->field[i])) |
800 | access_bits|= bit; |
801 | } |
802 | return access_bits; |
803 | } |
804 | |
805 | /* Compute how many privilege columns this table has. This method |
806 | can only be called after the table has been opened. |
807 | |
808 | IMPLEMENTATION |
809 | A privilege column is of type enum('Y', 'N'). Privilege columns are |
810 | expected to be one after another. |
811 | */ |
812 | void compute_num_privilege_cols() |
813 | { |
814 | if (!table_exists()) // Table does not exist or not opened. |
815 | return; |
816 | |
817 | num_privilege_cols= 0; |
818 | for (uint i= 0; i < num_fields(); i++) |
819 | { |
820 | Field *field= tl.table->field[i]; |
821 | if (num_privilege_cols > 0 && field->real_type() != MYSQL_TYPE_ENUM) |
822 | return; |
823 | if (field->real_type() == MYSQL_TYPE_ENUM && |
824 | static_cast<Field_enum*>(field)->typelib->count == 2) |
825 | { |
826 | num_privilege_cols++; |
827 | if (num_privilege_cols == 1) |
828 | start_privilege_column= i; |
829 | } |
830 | } |
831 | } |
832 | |
833 | /* The index at which privilege columns start. */ |
834 | uint start_privilege_column; |
835 | /* The number of privilege columns in the table. */ |
836 | uint num_privilege_cols; |
837 | |
838 | TABLE_LIST tl; |
839 | }; |
840 | |
841 | class User_table: public Grant_table_base |
842 | { |
843 | public: |
844 | /* Field getters return NULL if the column is not present in the table. |
845 | This is consistent only if the table is in a supported version. We do |
846 | not guard against corrupt tables. (yet) */ |
847 | Field* host() const |
848 | { return get_field(0); } |
849 | Field* user() const |
850 | { return get_field(1); } |
851 | Field* password() const |
852 | { return have_password() ? NULL : tl.table->field[2]; } |
853 | /* Columns after privilege columns. */ |
854 | Field* ssl_type() const |
855 | { return get_field(start_privilege_column + num_privileges()); } |
856 | Field* ssl_cipher() const |
857 | { return get_field(start_privilege_column + num_privileges() + 1); } |
858 | Field* x509_issuer() const |
859 | { return get_field(start_privilege_column + num_privileges() + 2); } |
860 | Field* x509_subject() const |
861 | { return get_field(start_privilege_column + num_privileges() + 3); } |
862 | Field* max_questions() const |
863 | { return get_field(start_privilege_column + num_privileges() + 4); } |
864 | Field* max_updates() const |
865 | { return get_field(start_privilege_column + num_privileges() + 5); } |
866 | Field* max_connections() const |
867 | { return get_field(start_privilege_column + num_privileges() + 6); } |
868 | Field* max_user_connections() const |
869 | { return get_field(start_privilege_column + num_privileges() + 7); } |
870 | Field* plugin() const |
871 | { return get_field(start_privilege_column + num_privileges() + 8); } |
872 | Field* authentication_string() const |
873 | { return get_field(start_privilege_column + num_privileges() + 9); } |
874 | Field* password_expired() const |
875 | { return get_field(start_privilege_column + num_privileges() + 10); } |
876 | Field* is_role() const |
877 | { return get_field(start_privilege_column + num_privileges() + 11); } |
878 | Field* default_role() const |
879 | { return get_field(start_privilege_column + num_privileges() + 12); } |
880 | Field* max_statement_time() const |
881 | { return get_field(start_privilege_column + num_privileges() + 13); } |
882 | |
883 | /* |
884 | Check if a user entry in the user table is marked as being a role entry |
885 | |
886 | IMPLEMENTATION |
887 | Access the coresponding column and check the coresponding ENUM of the form |
888 | ENUM('N', 'Y') |
889 | |
890 | SYNOPSIS |
891 | check_is_role() |
892 | form an open table to read the entry from. |
893 | The record should be already read in table->record[0] |
894 | |
895 | RETURN VALUE |
896 | TRUE if the user is marked as a role |
897 | FALSE otherwise |
898 | */ |
899 | bool check_is_role() const |
900 | { |
901 | /* Table version does not support roles */ |
902 | if (!is_role()) |
903 | return false; |
904 | |
905 | return get_YN_as_bool(is_role()); |
906 | } |
907 | |
908 | |
909 | private: |
910 | friend class Grant_tables; |
911 | |
912 | /* Only Grant_tables can instantiate this class. */ |
913 | User_table() {}; |
914 | |
915 | void init(enum thr_lock_type lock_type) |
916 | { |
917 | /* We are relying on init_one_table zeroing out the TABLE_LIST structure. */ |
918 | tl.init_one_table(&MYSQL_SCHEMA_NAME, &MYSQL_USER_NAME, NULL, lock_type); |
919 | Grant_table_base::init(lock_type, false); |
920 | } |
921 | |
922 | /* The user table is a bit different compared to the other Grant tables. |
923 | Usually, we only add columns to the grant tables when adding functionality. |
924 | This makes it easy to test which version of the table we are using, by |
925 | just looking at the number of fields present in the table. |
926 | |
927 | In MySQL 5.7.6 the Password column was removed. We need to guard for that. |
928 | The field-fetching methods for the User table return NULL if the field |
929 | doesn't exist. This simplifies checking of table "version", as we don't |
930 | have to make use of num_fields() any more. |
931 | */ |
932 | inline Field* get_field(uint field_num) const |
933 | { |
934 | if (field_num >= num_fields()) |
935 | return NULL; |
936 | |
937 | return tl.table->field[field_num]; |
938 | } |
939 | |
940 | /* Normally password column is the third column in the table. If privileges |
941 | start on the third column instead, we are missing the password column. |
942 | This means we are using a MySQL 5.7.6+ data directory. */ |
943 | bool have_password() const { return start_privilege_column == 2; } |
944 | |
945 | }; |
946 | |
947 | class Db_table: public Grant_table_base |
948 | { |
949 | public: |
950 | Field* host() const { return tl.table->field[0]; } |
951 | Field* db() const { return tl.table->field[1]; } |
952 | Field* user() const { return tl.table->field[2]; } |
953 | |
954 | private: |
955 | friend class Grant_tables; |
956 | |
957 | Db_table() {}; |
958 | |
959 | void init(enum thr_lock_type lock_type) |
960 | { |
961 | /* We are relying on init_one_table zeroing out the TABLE_LIST structure. */ |
962 | tl.init_one_table(&MYSQL_SCHEMA_NAME, &MYSQL_DB_NAME, NULL, lock_type); |
963 | Grant_table_base::init(lock_type, false); |
964 | } |
965 | }; |
966 | |
967 | class Tables_priv_table: public Grant_table_base |
968 | { |
969 | public: |
970 | Field* host() const { return tl.table->field[0]; } |
971 | Field* db() const { return tl.table->field[1]; } |
972 | Field* user() const { return tl.table->field[2]; } |
973 | Field* table_name() const { return tl.table->field[3]; } |
974 | Field* grantor() const { return tl.table->field[4]; } |
975 | Field* timestamp() const { return tl.table->field[5]; } |
976 | Field* table_priv() const { return tl.table->field[6]; } |
977 | Field* column_priv() const { return tl.table->field[7]; } |
978 | |
979 | private: |
980 | friend class Grant_tables; |
981 | |
982 | Tables_priv_table() {}; |
983 | |
984 | void init(enum thr_lock_type lock_type, Grant_table_base *next_table= NULL) |
985 | { |
986 | /* We are relying on init_one_table zeroing out the TABLE_LIST structure. */ |
987 | LEX_CSTRING MYSQL_TABLES_PRIV_NAME={STRING_WITH_LEN("tables_priv" ) }; |
988 | tl.init_one_table(&MYSQL_SCHEMA_NAME, &MYSQL_TABLES_PRIV_NAME, NULL, lock_type); |
989 | Grant_table_base::init(lock_type, false); |
990 | } |
991 | }; |
992 | |
993 | class Columns_priv_table: public Grant_table_base |
994 | { |
995 | public: |
996 | Field* host() const { return tl.table->field[0]; } |
997 | Field* db() const { return tl.table->field[1]; } |
998 | Field* user() const { return tl.table->field[2]; } |
999 | Field* table_name() const { return tl.table->field[3]; } |
1000 | Field* column_name() const { return tl.table->field[4]; } |
1001 | Field* timestamp() const { return tl.table->field[5]; } |
1002 | Field* column_priv() const { return tl.table->field[6]; } |
1003 | |
1004 | private: |
1005 | friend class Grant_tables; |
1006 | |
1007 | Columns_priv_table() {}; |
1008 | |
1009 | void init(enum thr_lock_type lock_type) |
1010 | { |
1011 | /* We are relying on init_one_table zeroing out the TABLE_LIST structure. */ |
1012 | LEX_CSTRING MYSQL_COLUMNS_PRIV_NAME={ STRING_WITH_LEN("columns_priv" ) }; |
1013 | tl.init_one_table(&MYSQL_SCHEMA_NAME, &MYSQL_COLUMNS_PRIV_NAME, NULL, lock_type); |
1014 | Grant_table_base::init(lock_type, false); |
1015 | } |
1016 | }; |
1017 | |
1018 | class Host_table: public Grant_table_base |
1019 | { |
1020 | public: |
1021 | Field* host() const { return tl.table->field[0]; } |
1022 | Field* db() const { return tl.table->field[1]; } |
1023 | |
1024 | private: |
1025 | friend class Grant_tables; |
1026 | |
1027 | Host_table() {} |
1028 | |
1029 | void init(enum thr_lock_type lock_type) |
1030 | { |
1031 | /* We are relying on init_one_table zeroing out the TABLE_LIST structure. */ |
1032 | LEX_CSTRING MYSQL_HOST_NAME={STRING_WITH_LEN("host" ) }; |
1033 | tl.init_one_table(&MYSQL_SCHEMA_NAME, &MYSQL_HOST_NAME, NULL, lock_type); |
1034 | Grant_table_base::init(lock_type, true); |
1035 | } |
1036 | }; |
1037 | |
1038 | class Procs_priv_table: public Grant_table_base |
1039 | { |
1040 | public: |
1041 | Field* host() const { return tl.table->field[0]; } |
1042 | Field* db() const { return tl.table->field[1]; } |
1043 | Field* user() const { return tl.table->field[2]; } |
1044 | Field* routine_name() const { return tl.table->field[3]; } |
1045 | Field* routine_type() const { return tl.table->field[4]; } |
1046 | Field* grantor() const { return tl.table->field[5]; } |
1047 | Field* proc_priv() const { return tl.table->field[6]; } |
1048 | Field* timestamp() const { return tl.table->field[7]; } |
1049 | |
1050 | private: |
1051 | friend class Grant_tables; |
1052 | |
1053 | Procs_priv_table() {} |
1054 | |
1055 | void init(enum thr_lock_type lock_type) |
1056 | { |
1057 | /* We are relying on init_one_table zeroing out the TABLE_LIST structure. */ |
1058 | LEX_CSTRING MYSQL_PROCS_PRIV_NAME={STRING_WITH_LEN("procs_priv" ) }; |
1059 | tl.init_one_table(&MYSQL_SCHEMA_NAME, &MYSQL_PROCS_PRIV_NAME, NULL, lock_type); |
1060 | Grant_table_base::init(lock_type, true); |
1061 | } |
1062 | }; |
1063 | |
1064 | class Proxies_priv_table: public Grant_table_base |
1065 | { |
1066 | public: |
1067 | Field* host() const { return tl.table->field[0]; } |
1068 | Field* user() const { return tl.table->field[1]; } |
1069 | Field* proxied_host() const { return tl.table->field[2]; } |
1070 | Field* proxied_user() const { return tl.table->field[3]; } |
1071 | Field* with_grant() const { return tl.table->field[4]; } |
1072 | Field* grantor() const { return tl.table->field[5]; } |
1073 | Field* timestamp() const { return tl.table->field[6]; } |
1074 | |
1075 | private: |
1076 | friend class Grant_tables; |
1077 | |
1078 | Proxies_priv_table() {} |
1079 | |
1080 | void init(enum thr_lock_type lock_type) |
1081 | { |
1082 | /* We are relying on init_one_table zeroing out the TABLE_LIST structure. */ |
1083 | LEX_CSTRING MYSQL_PROXIES_PRIV_NAME={STRING_WITH_LEN("proxies_priv" ) }; |
1084 | tl.init_one_table(&MYSQL_SCHEMA_NAME, &MYSQL_PROXIES_PRIV_NAME, NULL, lock_type); |
1085 | Grant_table_base::init(lock_type, true); |
1086 | } |
1087 | }; |
1088 | |
1089 | class Roles_mapping_table: public Grant_table_base |
1090 | { |
1091 | public: |
1092 | Field* host() const { return tl.table->field[0]; } |
1093 | Field* user() const { return tl.table->field[1]; } |
1094 | Field* role() const { return tl.table->field[2]; } |
1095 | Field* admin_option() const { return tl.table->field[3]; } |
1096 | |
1097 | private: |
1098 | friend class Grant_tables; |
1099 | |
1100 | Roles_mapping_table() {} |
1101 | |
1102 | void init(enum thr_lock_type lock_type) |
1103 | { |
1104 | /* We are relying on init_one_table zeroing out the TABLE_LIST structure. */ |
1105 | LEX_CSTRING MYSQL_ROLES_MAPPING_NAME={STRING_WITH_LEN("roles_mapping" ) }; |
1106 | tl.init_one_table(&MYSQL_SCHEMA_NAME, &MYSQL_ROLES_MAPPING_NAME, NULL, lock_type); |
1107 | Grant_table_base::init(lock_type, true); |
1108 | } |
1109 | }; |
1110 | |
1111 | /** |
1112 | Class that represents a collection of grant tables. |
1113 | */ |
1114 | class Grant_tables |
1115 | { |
1116 | public: |
1117 | /* When constructing the Grant_tables object, we initialize only |
1118 | the tables which are going to be opened. |
1119 | @param which_tables Bitmap of which tables to open. |
1120 | @param lock_type Lock type to use when opening tables. |
1121 | */ |
1122 | Grant_tables(int which_tables, enum thr_lock_type lock_type) |
1123 | { |
1124 | DBUG_ENTER("Grant_tables::Grant_tables" ); |
1125 | DBUG_PRINT("info" , ("which_tables: %x, lock_type: %u" , |
1126 | which_tables, lock_type)); |
1127 | DBUG_ASSERT(which_tables); /* At least one table must be opened. */ |
1128 | Grant_table_base* prev= NULL; |
1129 | /* We start from the last table, Table_roles_mapping, such that |
1130 | the first one in the linked list is Table_user. */ |
1131 | if (which_tables & Table_roles_mapping) |
1132 | { |
1133 | m_roles_mapping_table.init(lock_type); |
1134 | prev= &m_roles_mapping_table; |
1135 | } |
1136 | if (which_tables & Table_proxies_priv) |
1137 | { |
1138 | m_proxies_priv_table.init(lock_type); |
1139 | link_tables(&m_proxies_priv_table, prev); |
1140 | prev= &m_proxies_priv_table; |
1141 | } |
1142 | if (which_tables & Table_procs_priv) |
1143 | { |
1144 | m_procs_priv_table.init(lock_type); |
1145 | link_tables(&m_procs_priv_table, prev); |
1146 | prev= &m_procs_priv_table; |
1147 | } |
1148 | if (which_tables & Table_host) |
1149 | { |
1150 | m_host_table.init(lock_type); |
1151 | link_tables(&m_host_table, prev); |
1152 | prev= &m_host_table; |
1153 | } |
1154 | if (which_tables & Table_columns_priv) |
1155 | { |
1156 | m_columns_priv_table.init(lock_type); |
1157 | link_tables(&m_columns_priv_table, prev); |
1158 | prev= &m_columns_priv_table; |
1159 | } |
1160 | if (which_tables & Table_tables_priv) |
1161 | { |
1162 | m_tables_priv_table.init(lock_type); |
1163 | link_tables(&m_tables_priv_table, prev); |
1164 | prev= &m_tables_priv_table; |
1165 | } |
1166 | if (which_tables & Table_db) |
1167 | { |
1168 | m_db_table.init(lock_type); |
1169 | link_tables(&m_db_table, prev); |
1170 | prev= &m_db_table; |
1171 | } |
1172 | if (which_tables & Table_user) |
1173 | { |
1174 | m_user_table.init(lock_type); |
1175 | link_tables(&m_user_table, prev); |
1176 | prev= &m_user_table; |
1177 | } |
1178 | |
1179 | first_table_in_list= prev; |
1180 | DBUG_VOID_RETURN; |
1181 | } |
1182 | |
1183 | /* Before any operation is possible on grant tables, they must be opened. |
1184 | This opens the tables according to the lock type specified during |
1185 | construction. |
1186 | |
1187 | @retval 1 replication filters matched. Abort the operation, |
1188 | but return OK (!) |
1189 | @retval 0 tables were opened successfully |
1190 | @retval -1 error, tables could not be opened |
1191 | */ |
1192 | int open_and_lock(THD *thd) |
1193 | { |
1194 | DBUG_ENTER("Grant_tables::open_and_lock" ); |
1195 | DBUG_ASSERT(first_table_in_list); |
1196 | #ifdef HAVE_REPLICATION |
1197 | if (first_table_in_list->tl.lock_type >= TL_WRITE_ALLOW_WRITE && |
1198 | thd->slave_thread && !thd->spcont) |
1199 | { |
1200 | /* |
1201 | GRANT and REVOKE are applied the slave in/exclusion rules as they are |
1202 | some kind of updates to the mysql.% tables. |
1203 | */ |
1204 | Rpl_filter *rpl_filter= thd->system_thread_info.rpl_sql_info->rpl_filter; |
1205 | if (rpl_filter->is_on() && |
1206 | !rpl_filter->tables_ok(0, &first_table_in_list->tl)) |
1207 | DBUG_RETURN(1); |
1208 | } |
1209 | #endif |
1210 | if (open_and_lock_tables(thd, &first_table_in_list->tl, FALSE, |
1211 | MYSQL_LOCK_IGNORE_TIMEOUT)) |
1212 | DBUG_RETURN(-1); |
1213 | |
1214 | /* |
1215 | We can read privilege tables even when !initialized. |
1216 | This can be acl_load() - server startup or FLUSH PRIVILEGES |
1217 | */ |
1218 | if (first_table_in_list->tl.lock_type >= TL_WRITE_ALLOW_WRITE && |
1219 | !initialized) |
1220 | { |
1221 | my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--skip-grant-tables" ); |
1222 | DBUG_RETURN(-1); |
1223 | } |
1224 | |
1225 | /* The privilge columns vary based on MariaDB version. Figure out |
1226 | how many we have after we've opened the table. */ |
1227 | m_user_table.compute_num_privilege_cols(); |
1228 | m_db_table.compute_num_privilege_cols(); |
1229 | m_tables_priv_table.compute_num_privilege_cols(); |
1230 | m_columns_priv_table.compute_num_privilege_cols(); |
1231 | m_host_table.compute_num_privilege_cols(); |
1232 | m_procs_priv_table.compute_num_privilege_cols(); |
1233 | m_proxies_priv_table.compute_num_privilege_cols(); |
1234 | m_roles_mapping_table.compute_num_privilege_cols(); |
1235 | DBUG_RETURN(0); |
1236 | } |
1237 | |
1238 | inline const User_table& user_table() const |
1239 | { |
1240 | return m_user_table; |
1241 | } |
1242 | |
1243 | inline const Db_table& db_table() const |
1244 | { |
1245 | return m_db_table; |
1246 | } |
1247 | |
1248 | |
1249 | inline const Tables_priv_table& tables_priv_table() const |
1250 | { |
1251 | return m_tables_priv_table; |
1252 | } |
1253 | |
1254 | inline const Columns_priv_table& columns_priv_table() const |
1255 | { |
1256 | return m_columns_priv_table; |
1257 | } |
1258 | |
1259 | inline const Host_table& host_table() const |
1260 | { |
1261 | return m_host_table; |
1262 | } |
1263 | |
1264 | inline const Procs_priv_table& procs_priv_table() const |
1265 | { |
1266 | return m_procs_priv_table; |
1267 | } |
1268 | |
1269 | inline const Proxies_priv_table& proxies_priv_table() const |
1270 | { |
1271 | return m_proxies_priv_table; |
1272 | } |
1273 | |
1274 | inline const Roles_mapping_table& roles_mapping_table() const |
1275 | { |
1276 | return m_roles_mapping_table; |
1277 | } |
1278 | |
1279 | private: |
1280 | User_table m_user_table; |
1281 | Db_table m_db_table; |
1282 | Tables_priv_table m_tables_priv_table; |
1283 | Columns_priv_table m_columns_priv_table; |
1284 | Host_table m_host_table; |
1285 | Procs_priv_table m_procs_priv_table; |
1286 | Proxies_priv_table m_proxies_priv_table; |
1287 | Roles_mapping_table m_roles_mapping_table; |
1288 | |
1289 | /* The grant tables are set-up in a linked list. We keep the head of it. */ |
1290 | Grant_table_base *first_table_in_list; |
1291 | /** |
1292 | Chain two grant tables' TABLE_LIST members. |
1293 | */ |
1294 | static void link_tables(Grant_table_base *from, Grant_table_base *to) |
1295 | { |
1296 | DBUG_ASSERT(from); |
1297 | if (to) |
1298 | from->tl.next_local= from->tl.next_global= &to->tl; |
1299 | else |
1300 | from->tl.next_local= from->tl.next_global= NULL; |
1301 | } |
1302 | }; |
1303 | |
1304 | |
1305 | void ACL_PROXY_USER::init(const Proxies_priv_table& proxies_priv_table, |
1306 | MEM_ROOT *mem) |
1307 | { |
1308 | init(get_field(mem, proxies_priv_table.host()), |
1309 | get_field(mem, proxies_priv_table.user()), |
1310 | get_field(mem, proxies_priv_table.proxied_host()), |
1311 | get_field(mem, proxies_priv_table.proxied_user()), |
1312 | proxies_priv_table.with_grant()->val_int() != 0); |
1313 | } |
1314 | |
1315 | |
1316 | |
1317 | /* |
1318 | Enumeration of various ACL's and Hashes used in handle_grant_struct() |
1319 | */ |
1320 | enum enum_acl_lists |
1321 | { |
1322 | USER_ACL= 0, |
1323 | ROLE_ACL, |
1324 | DB_ACL, |
1325 | COLUMN_PRIVILEGES_HASH, |
1326 | PROC_PRIVILEGES_HASH, |
1327 | FUNC_PRIVILEGES_HASH, |
1328 | PACKAGE_SPEC_PRIVILEGES_HASH, |
1329 | PACKAGE_BODY_PRIVILEGES_HASH, |
1330 | PROXY_USERS_ACL, |
1331 | ROLES_MAPPINGS_HASH |
1332 | }; |
1333 | |
1334 | ACL_ROLE::ACL_ROLE(ACL_USER *user, MEM_ROOT *root) : counter(0) |
1335 | { |
1336 | |
1337 | access= user->access; |
1338 | /* set initial role access the same as the table row privileges */ |
1339 | initial_role_access= user->access; |
1340 | this->user.str= safe_strdup_root(root, user->user.str); |
1341 | this->user.length= user->user.length; |
1342 | bzero(&role_grants, sizeof(role_grants)); |
1343 | bzero(&parent_grantee, sizeof(parent_grantee)); |
1344 | flags= IS_ROLE; |
1345 | } |
1346 | |
1347 | ACL_ROLE::ACL_ROLE(const char * rolename, ulong privileges, MEM_ROOT *root) : |
1348 | initial_role_access(privileges), counter(0) |
1349 | { |
1350 | this->access= initial_role_access; |
1351 | this->user.str= safe_strdup_root(root, rolename); |
1352 | this->user.length= strlen(rolename); |
1353 | bzero(&role_grants, sizeof(role_grants)); |
1354 | bzero(&parent_grantee, sizeof(parent_grantee)); |
1355 | flags= IS_ROLE; |
1356 | } |
1357 | |
1358 | |
1359 | static bool is_invalid_role_name(const char *str) |
1360 | { |
1361 | if (*str && strcasecmp(str, "PUBLIC" ) && strcasecmp(str, "NONE" )) |
1362 | return false; |
1363 | |
1364 | my_error(ER_INVALID_ROLE, MYF(0), str); |
1365 | return true; |
1366 | } |
1367 | |
1368 | |
1369 | static void free_acl_user(ACL_USER *user) |
1370 | { |
1371 | delete_dynamic(&(user->role_grants)); |
1372 | } |
1373 | |
1374 | static void free_acl_role(ACL_ROLE *role) |
1375 | { |
1376 | delete_dynamic(&(role->role_grants)); |
1377 | delete_dynamic(&(role->parent_grantee)); |
1378 | } |
1379 | |
1380 | static my_bool check_if_exists(THD *, plugin_ref, void *) |
1381 | { |
1382 | return TRUE; |
1383 | } |
1384 | |
1385 | static bool has_validation_plugins() |
1386 | { |
1387 | return plugin_foreach(NULL, check_if_exists, |
1388 | MariaDB_PASSWORD_VALIDATION_PLUGIN, NULL); |
1389 | } |
1390 | |
1391 | struct validation_data { LEX_CSTRING *user, *password; }; |
1392 | |
1393 | static my_bool do_validate(THD *, plugin_ref plugin, void *arg) |
1394 | { |
1395 | struct validation_data *data= (struct validation_data *)arg; |
1396 | struct st_mariadb_password_validation *handler= |
1397 | (st_mariadb_password_validation *)plugin_decl(plugin)->info; |
1398 | return handler->validate_password(data->user, data->password); |
1399 | } |
1400 | |
1401 | |
1402 | static bool validate_password(LEX_USER *user, THD *thd) |
1403 | { |
1404 | if (user->pwtext.length || !user->pwhash.length) |
1405 | { |
1406 | struct validation_data data= { &user->user, |
1407 | user->pwtext.str ? &user->pwtext : |
1408 | const_cast<LEX_CSTRING *>(&empty_clex_str) }; |
1409 | if (plugin_foreach(NULL, do_validate, |
1410 | MariaDB_PASSWORD_VALIDATION_PLUGIN, &data)) |
1411 | { |
1412 | my_error(ER_NOT_VALID_PASSWORD, MYF(0)); |
1413 | return true; |
1414 | } |
1415 | } |
1416 | else |
1417 | { |
1418 | if (!thd->slave_thread && |
1419 | strict_password_validation && has_validation_plugins()) |
1420 | { |
1421 | my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--strict-password-validation" ); |
1422 | return true; |
1423 | } |
1424 | } |
1425 | return false; |
1426 | } |
1427 | |
1428 | /** |
1429 | Convert scrambled password to binary form, according to scramble type, |
1430 | Binary form is stored in user.salt. |
1431 | |
1432 | @param acl_user The object where to store the salt |
1433 | @param password The password hash containing the salt |
1434 | @param password_len The length of the password hash |
1435 | |
1436 | Despite the name of the function it is used when loading ACLs from disk |
1437 | to store the password hash in the ACL_USER object. |
1438 | */ |
1439 | |
1440 | static void |
1441 | set_user_salt(ACL_USER *acl_user, const char *password, size_t password_len) |
1442 | { |
1443 | if (password_len == SCRAMBLED_PASSWORD_CHAR_LENGTH) |
1444 | { |
1445 | get_salt_from_password(acl_user->salt, password); |
1446 | acl_user->salt_len= SCRAMBLE_LENGTH; |
1447 | } |
1448 | else if (password_len == SCRAMBLED_PASSWORD_CHAR_LENGTH_323) |
1449 | { |
1450 | get_salt_from_password_323((ulong *) acl_user->salt, password); |
1451 | acl_user->salt_len= SCRAMBLE_LENGTH_323; |
1452 | } |
1453 | else |
1454 | acl_user->salt_len= 0; |
1455 | } |
1456 | |
1457 | static const char *fix_plugin_ptr(const char *name) |
1458 | { |
1459 | if (my_strcasecmp(system_charset_info, name, |
1460 | native_password_plugin_name.str) == 0) |
1461 | return native_password_plugin_name.str; |
1462 | else |
1463 | if (my_strcasecmp(system_charset_info, name, |
1464 | old_password_plugin_name.str) == 0) |
1465 | return old_password_plugin_name.str; |
1466 | else |
1467 | return name; |
1468 | } |
1469 | |
1470 | /** |
1471 | Fix ACL::plugin pointer to point to a hard-coded string, if appropriate |
1472 | |
1473 | Make sure that if ACL_USER's plugin is a built-in, then it points |
1474 | to a hard coded string, not to an allocated copy. Run-time, for |
1475 | authentication, we want to be able to detect built-ins by comparing |
1476 | pointers, not strings. |
1477 | |
1478 | Additionally - update the salt if the plugin is built-in. |
1479 | |
1480 | @retval 0 the pointers were fixed |
1481 | @retval 1 this ACL_USER uses a not built-in plugin |
1482 | */ |
1483 | static bool fix_user_plugin_ptr(ACL_USER *user) |
1484 | { |
1485 | if (lex_string_eq(&user->plugin, &native_password_plugin_name)) |
1486 | user->plugin= native_password_plugin_name; |
1487 | else |
1488 | if (lex_string_eq(&user->plugin, &old_password_plugin_name)) |
1489 | user->plugin= old_password_plugin_name; |
1490 | else |
1491 | return true; |
1492 | |
1493 | if (user->auth_string.length) |
1494 | set_user_salt(user, user->auth_string.str, user->auth_string.length); |
1495 | return false; |
1496 | } |
1497 | |
1498 | |
1499 | /* |
1500 | Validates the password, calculates password hash, transforms |
1501 | equivalent LEX_USER representations. |
1502 | |
1503 | Upon entering this function: |
1504 | |
1505 | - if user->plugin is specified, user->auth is the plugin auth data. |
1506 | - if user->plugin is mysql_native_password or mysql_old_password, |
1507 | user->auth is the password hash, and LEX_USER is transformed |
1508 | to match the next case (that is, user->plugin is cleared). |
1509 | - if user->plugin is NOT specified, built-in auth is assumed, that is |
1510 | mysql_native_password or mysql_old_password. In that case, |
1511 | user->pwhash is the password hash. And user->pwtext is the original |
1512 | plain-text password. Either one can be set or both. |
1513 | |
1514 | Upon exiting this function: |
1515 | |
1516 | - user->pwtext is left untouched |
1517 | - user->pwhash is the password hash, as the mysql.user.password column |
1518 | - user->plugin is the plugin name, as the mysql.user.plugin column |
1519 | - user->auth is the plugin auth data, as the mysql.user.authentication_string column |
1520 | */ |
1521 | static bool fix_lex_user(THD *thd, LEX_USER *user) |
1522 | { |
1523 | size_t check_length; |
1524 | |
1525 | DBUG_ASSERT(user->plugin.length || !user->auth.length); |
1526 | DBUG_ASSERT(!(user->plugin.length && (user->pwtext.length || user->pwhash.length))); |
1527 | |
1528 | if (lex_string_eq(&user->plugin, &native_password_plugin_name)) |
1529 | check_length= SCRAMBLED_PASSWORD_CHAR_LENGTH; |
1530 | else |
1531 | if (lex_string_eq(&user->plugin, &old_password_plugin_name)) |
1532 | check_length= SCRAMBLED_PASSWORD_CHAR_LENGTH_323; |
1533 | else |
1534 | if (user->plugin.length) |
1535 | return false; // nothing else to do |
1536 | else if (thd->variables.old_passwords == 1 || |
1537 | user->pwhash.length == SCRAMBLED_PASSWORD_CHAR_LENGTH_323) |
1538 | check_length= SCRAMBLED_PASSWORD_CHAR_LENGTH_323; |
1539 | else |
1540 | check_length= SCRAMBLED_PASSWORD_CHAR_LENGTH; |
1541 | |
1542 | if (user->plugin.length) |
1543 | { |
1544 | user->pwhash= user->auth; |
1545 | user->plugin= empty_clex_str; |
1546 | user->auth= empty_clex_str; |
1547 | } |
1548 | |
1549 | if (user->pwhash.length && user->pwhash.length != check_length) |
1550 | { |
1551 | my_error(ER_PASSWD_LENGTH, MYF(0), (int) check_length); |
1552 | return true; |
1553 | } |
1554 | |
1555 | if (user->pwtext.length && !user->pwhash.length) |
1556 | { |
1557 | size_t scramble_length; |
1558 | void (*make_scramble)(char *, const char *, size_t); |
1559 | |
1560 | if (thd->variables.old_passwords == 1) |
1561 | { |
1562 | scramble_length= SCRAMBLED_PASSWORD_CHAR_LENGTH_323; |
1563 | make_scramble= my_make_scrambled_password_323; |
1564 | } |
1565 | else |
1566 | { |
1567 | scramble_length= SCRAMBLED_PASSWORD_CHAR_LENGTH; |
1568 | make_scramble= my_make_scrambled_password; |
1569 | } |
1570 | |
1571 | Query_arena *arena, backup; |
1572 | arena= thd->activate_stmt_arena_if_needed(&backup); |
1573 | char *buff= (char *) thd->alloc(scramble_length + 1); |
1574 | if (arena) |
1575 | thd->restore_active_arena(arena, &backup); |
1576 | |
1577 | if (buff == NULL) |
1578 | return true; |
1579 | make_scramble(buff, user->pwtext.str, user->pwtext.length); |
1580 | user->pwhash.str= buff; |
1581 | user->pwhash.length= scramble_length; |
1582 | } |
1583 | |
1584 | return false; |
1585 | } |
1586 | |
1587 | |
1588 | static bool get_YN_as_bool(Field *field) |
1589 | { |
1590 | char buff[2]; |
1591 | String res(buff,sizeof(buff),&my_charset_latin1); |
1592 | field->val_str(&res); |
1593 | return res[0] == 'Y' || res[0] == 'y'; |
1594 | } |
1595 | |
1596 | |
1597 | /* |
1598 | Initialize structures responsible for user/db-level privilege checking and |
1599 | load privilege information for them from tables in the 'mysql' database. |
1600 | |
1601 | SYNOPSIS |
1602 | acl_init() |
1603 | dont_read_acl_tables TRUE if we want to skip loading data from |
1604 | privilege tables and disable privilege checking. |
1605 | |
1606 | NOTES |
1607 | This function is mostly responsible for preparatory steps, main work |
1608 | on initialization and grants loading is done in acl_reload(). |
1609 | |
1610 | RETURN VALUES |
1611 | 0 ok |
1612 | 1 Could not initialize grant's |
1613 | */ |
1614 | |
1615 | bool acl_init(bool dont_read_acl_tables) |
1616 | { |
1617 | THD *thd; |
1618 | bool return_val; |
1619 | DBUG_ENTER("acl_init" ); |
1620 | |
1621 | acl_cache= new Hash_filo<acl_entry>(ACL_CACHE_SIZE, 0, 0, |
1622 | (my_hash_get_key) acl_entry_get_key, |
1623 | (my_hash_free_key) free, |
1624 | &my_charset_utf8_bin); |
1625 | |
1626 | /* |
1627 | cache built-in native authentication plugins, |
1628 | to avoid hash searches and a global mutex lock on every connect |
1629 | */ |
1630 | native_password_plugin= my_plugin_lock_by_name(0, |
1631 | &native_password_plugin_name, MYSQL_AUTHENTICATION_PLUGIN); |
1632 | old_password_plugin= my_plugin_lock_by_name(0, |
1633 | &old_password_plugin_name, MYSQL_AUTHENTICATION_PLUGIN); |
1634 | |
1635 | if (!native_password_plugin || !old_password_plugin) |
1636 | DBUG_RETURN(1); |
1637 | |
1638 | if (dont_read_acl_tables) |
1639 | { |
1640 | DBUG_RETURN(0); /* purecov: tested */ |
1641 | } |
1642 | |
1643 | /* |
1644 | To be able to run this from boot, we allocate a temporary THD |
1645 | */ |
1646 | if (!(thd=new THD(0))) |
1647 | DBUG_RETURN(1); /* purecov: inspected */ |
1648 | thd->thread_stack= (char*) &thd; |
1649 | thd->store_globals(); |
1650 | /* |
1651 | It is safe to call acl_reload() since acl_* arrays and hashes which |
1652 | will be freed there are global static objects and thus are initialized |
1653 | by zeros at startup. |
1654 | */ |
1655 | return_val= acl_reload(thd); |
1656 | delete thd; |
1657 | DBUG_RETURN(return_val); |
1658 | } |
1659 | |
1660 | /** |
1661 | Choose from either native or old password plugins when assigning a password |
1662 | */ |
1663 | |
1664 | static bool set_user_plugin (ACL_USER *user, size_t password_len) |
1665 | { |
1666 | switch (password_len) |
1667 | { |
1668 | case 0: /* no password */ |
1669 | case SCRAMBLED_PASSWORD_CHAR_LENGTH: |
1670 | user->plugin= native_password_plugin_name; |
1671 | return FALSE; |
1672 | case SCRAMBLED_PASSWORD_CHAR_LENGTH_323: |
1673 | user->plugin= old_password_plugin_name; |
1674 | return FALSE; |
1675 | default: |
1676 | sql_print_warning("Found invalid password for user: '%s@%s'; " |
1677 | "Ignoring user" , safe_str(user->user.str), |
1678 | safe_str(user->host.hostname)); |
1679 | return TRUE; |
1680 | } |
1681 | } |
1682 | |
1683 | |
1684 | /* |
1685 | Initialize structures responsible for user/db-level privilege checking |
1686 | and load information about grants from open privilege tables. |
1687 | |
1688 | SYNOPSIS |
1689 | acl_load() |
1690 | thd Current thread |
1691 | tables List containing open "mysql.host", "mysql.user", |
1692 | "mysql.db", "mysql.proxies_priv" and "mysql.roles_mapping" |
1693 | tables. |
1694 | |
1695 | RETURN VALUES |
1696 | FALSE Success |
1697 | TRUE Error |
1698 | */ |
1699 | |
1700 | static bool acl_load(THD *thd, const Grant_tables& tables) |
1701 | { |
1702 | READ_RECORD read_record_info; |
1703 | bool check_no_resolve= specialflag & SPECIAL_NO_RESOLVE; |
1704 | char tmp_name[SAFE_NAME_LEN+1]; |
1705 | int password_length; |
1706 | Sql_mode_save old_mode_save(thd); |
1707 | DBUG_ENTER("acl_load" ); |
1708 | |
1709 | thd->variables.sql_mode&= ~MODE_PAD_CHAR_TO_FULL_LENGTH; |
1710 | |
1711 | grant_version++; /* Privileges updated */ |
1712 | |
1713 | const Host_table& host_table= tables.host_table(); |
1714 | init_sql_alloc(&acl_memroot, "ACL" , ACL_ALLOC_BLOCK_SIZE, 0, MYF(0)); |
1715 | if (host_table.table_exists()) // "host" table may not exist (e.g. in MySQL 5.6.7+) |
1716 | { |
1717 | if (host_table.init_read_record(&read_record_info, thd)) |
1718 | DBUG_RETURN(true); |
1719 | while (!(read_record_info.read_record())) |
1720 | { |
1721 | ACL_HOST host; |
1722 | update_hostname(&host.host, get_field(&acl_memroot, host_table.host())); |
1723 | host.db= get_field(&acl_memroot, host_table.db()); |
1724 | if (lower_case_table_names && host.db) |
1725 | { |
1726 | /* |
1727 | convert db to lower case and give a warning if the db wasn't |
1728 | already in lower case |
1729 | */ |
1730 | char *end = strnmov(tmp_name, host.db, sizeof(tmp_name)); |
1731 | if (end >= tmp_name + sizeof(tmp_name)) |
1732 | { |
1733 | sql_print_warning(ER_THD(thd, ER_WRONG_DB_NAME), host.db); |
1734 | continue; |
1735 | } |
1736 | my_casedn_str(files_charset_info, host.db); |
1737 | if (strcmp(host.db, tmp_name) != 0) |
1738 | sql_print_warning("'host' entry '%s|%s' had database in mixed " |
1739 | "case that has been forced to lowercase because " |
1740 | "lower_case_table_names is set. It will not be " |
1741 | "possible to remove this privilege using REVOKE." , |
1742 | host.host.hostname, host.db); |
1743 | } |
1744 | host.access= host_table.get_access(); |
1745 | host.access= fix_rights_for_db(host.access); |
1746 | host.sort= get_sort(2, host.host.hostname, host.db); |
1747 | if (check_no_resolve && hostname_requires_resolving(host.host.hostname)) |
1748 | { |
1749 | sql_print_warning("'host' entry '%s|%s' " |
1750 | "ignored in --skip-name-resolve mode." , |
1751 | safe_str(host.host.hostname), |
1752 | safe_str(host.db)); |
1753 | continue; |
1754 | } |
1755 | #ifndef TO_BE_REMOVED |
1756 | if (host_table.num_fields() == 8) |
1757 | { // Without grant |
1758 | if (host.access & CREATE_ACL) |
1759 | host.access|=REFERENCES_ACL | INDEX_ACL | ALTER_ACL | CREATE_TMP_ACL; |
1760 | } |
1761 | #endif |
1762 | (void) push_dynamic(&acl_hosts,(uchar*) &host); |
1763 | } |
1764 | my_qsort((uchar*) dynamic_element(&acl_hosts, 0, ACL_HOST*), |
1765 | acl_hosts.elements, sizeof(ACL_HOST),(qsort_cmp) acl_compare); |
1766 | end_read_record(&read_record_info); |
1767 | } |
1768 | freeze_size(&acl_hosts); |
1769 | |
1770 | const User_table& user_table= tables.user_table(); |
1771 | if (user_table.init_read_record(&read_record_info, thd)) |
1772 | DBUG_RETURN(true); |
1773 | |
1774 | username_char_length= MY_MIN(user_table.user()->char_length(), |
1775 | USERNAME_CHAR_LENGTH); |
1776 | if (user_table.password()) // Password column might be missing. (MySQL 5.7.6+) |
1777 | { |
1778 | password_length= user_table.password()->field_length / |
1779 | user_table.password()->charset()->mbmaxlen; |
1780 | if (password_length < SCRAMBLED_PASSWORD_CHAR_LENGTH_323) |
1781 | { |
1782 | sql_print_error("Fatal error: mysql.user table is damaged or in " |
1783 | "unsupported 3.20 format." ); |
1784 | DBUG_RETURN(TRUE); |
1785 | } |
1786 | |
1787 | DBUG_PRINT("info" ,("user table fields: %d, password length: %d" , |
1788 | user_table.num_fields(), password_length)); |
1789 | |
1790 | mysql_mutex_lock(&LOCK_global_system_variables); |
1791 | if (password_length < SCRAMBLED_PASSWORD_CHAR_LENGTH) |
1792 | { |
1793 | if (opt_secure_auth) |
1794 | { |
1795 | mysql_mutex_unlock(&LOCK_global_system_variables); |
1796 | sql_print_error("Fatal error: mysql.user table is in old format, " |
1797 | "but server started with --secure-auth option." ); |
1798 | DBUG_RETURN(TRUE); |
1799 | } |
1800 | mysql_user_table_is_in_short_password_format= true; |
1801 | if (global_system_variables.old_passwords) |
1802 | mysql_mutex_unlock(&LOCK_global_system_variables); |
1803 | else |
1804 | { |
1805 | extern sys_var *Sys_old_passwords_ptr; |
1806 | Sys_old_passwords_ptr->value_origin= sys_var::AUTO; |
1807 | global_system_variables.old_passwords= 1; |
1808 | mysql_mutex_unlock(&LOCK_global_system_variables); |
1809 | sql_print_warning("mysql.user table is not updated to new password format; " |
1810 | "Disabling new password usage until " |
1811 | "mysql_fix_privilege_tables is run" ); |
1812 | } |
1813 | thd->variables.old_passwords= 1; |
1814 | } |
1815 | else |
1816 | { |
1817 | mysql_user_table_is_in_short_password_format= false; |
1818 | mysql_mutex_unlock(&LOCK_global_system_variables); |
1819 | } |
1820 | } |
1821 | |
1822 | allow_all_hosts=0; |
1823 | while (!(read_record_info.read_record())) |
1824 | { |
1825 | ACL_USER user; |
1826 | bool is_role= FALSE; |
1827 | bzero(&user, sizeof(user)); |
1828 | update_hostname(&user.host, get_field(&acl_memroot, user_table.host())); |
1829 | char *username= get_field(&acl_memroot, user_table.user()); |
1830 | user.user.str= username; |
1831 | user.user.length= safe_strlen(username); |
1832 | |
1833 | /* |
1834 | If the user entry is a role, skip password and hostname checks |
1835 | A user can not log in with a role so some checks are not necessary |
1836 | */ |
1837 | is_role= user_table.check_is_role(); |
1838 | |
1839 | if (is_role && is_invalid_role_name(username)) |
1840 | { |
1841 | thd->clear_error(); // the warning is still issued |
1842 | continue; |
1843 | } |
1844 | |
1845 | if (!is_role && check_no_resolve && |
1846 | hostname_requires_resolving(user.host.hostname)) |
1847 | { |
1848 | sql_print_warning("'user' entry '%s@%s' " |
1849 | "ignored in --skip-name-resolve mode." , |
1850 | safe_str(user.user.str), |
1851 | safe_str(user.host.hostname)); |
1852 | continue; |
1853 | } |
1854 | |
1855 | char *password= const_cast<char*>("" ); |
1856 | if (user_table.password()) |
1857 | password= get_field(&acl_memroot, user_table.password()); |
1858 | size_t password_len= safe_strlen(password); |
1859 | user.auth_string.str= safe_str(password); |
1860 | user.auth_string.length= password_len; |
1861 | set_user_salt(&user, password, password_len); |
1862 | |
1863 | if (!is_role && set_user_plugin(&user, password_len)) |
1864 | continue; |
1865 | |
1866 | { |
1867 | user.access= user_table.get_access() & GLOBAL_ACLS; |
1868 | /* |
1869 | if it is pre 5.0.1 privilege table then map CREATE privilege on |
1870 | CREATE VIEW & SHOW VIEW privileges |
1871 | */ |
1872 | if (user_table.num_fields() <= 31 && (user.access & CREATE_ACL)) |
1873 | user.access|= (CREATE_VIEW_ACL | SHOW_VIEW_ACL); |
1874 | |
1875 | /* |
1876 | if it is pre 5.0.2 privilege table then map CREATE/ALTER privilege on |
1877 | CREATE PROCEDURE & ALTER PROCEDURE privileges |
1878 | */ |
1879 | if (user_table.num_fields() <= 33 && (user.access & CREATE_ACL)) |
1880 | user.access|= CREATE_PROC_ACL; |
1881 | if (user_table.num_fields() <= 33 && (user.access & ALTER_ACL)) |
1882 | user.access|= ALTER_PROC_ACL; |
1883 | |
1884 | /* |
1885 | pre 5.0.3 did not have CREATE_USER_ACL |
1886 | */ |
1887 | if (user_table.num_fields() <= 36 && (user.access & GRANT_ACL)) |
1888 | user.access|= CREATE_USER_ACL; |
1889 | |
1890 | |
1891 | /* |
1892 | if it is pre 5.1.6 privilege table then map CREATE privilege on |
1893 | CREATE|ALTER|DROP|EXECUTE EVENT |
1894 | */ |
1895 | if (user_table.num_fields() <= 37 && (user.access & SUPER_ACL)) |
1896 | user.access|= EVENT_ACL; |
1897 | |
1898 | /* |
1899 | if it is pre 5.1.6 privilege then map TRIGGER privilege on CREATE. |
1900 | */ |
1901 | if (user_table.num_fields() <= 38 && (user.access & SUPER_ACL)) |
1902 | user.access|= TRIGGER_ACL; |
1903 | |
1904 | if (user_table.num_fields() <= 46 && (user.access & DELETE_ACL)) |
1905 | user.access|= DELETE_HISTORY_ACL; |
1906 | |
1907 | user.sort= get_sort(2, user.host.hostname, user.user.str); |
1908 | user.hostname_length= safe_strlen(user.host.hostname); |
1909 | user.user_resource.user_conn= 0; |
1910 | user.user_resource.max_statement_time= 0.0; |
1911 | |
1912 | /* Starting from 4.0.2 we have more fields */ |
1913 | if (user_table.ssl_type()) |
1914 | { |
1915 | char *ssl_type=get_field(thd->mem_root, user_table.ssl_type()); |
1916 | if (!ssl_type) |
1917 | user.ssl_type=SSL_TYPE_NONE; |
1918 | else if (!strcmp(ssl_type, "ANY" )) |
1919 | user.ssl_type=SSL_TYPE_ANY; |
1920 | else if (!strcmp(ssl_type, "X509" )) |
1921 | user.ssl_type=SSL_TYPE_X509; |
1922 | else /* !strcmp(ssl_type, "SPECIFIED") */ |
1923 | user.ssl_type=SSL_TYPE_SPECIFIED; |
1924 | |
1925 | user.ssl_cipher= get_field(&acl_memroot, user_table.ssl_cipher()); |
1926 | user.x509_issuer= get_field(&acl_memroot, user_table.x509_issuer()); |
1927 | user.x509_subject= get_field(&acl_memroot, user_table.x509_subject()); |
1928 | |
1929 | char *ptr = get_field(thd->mem_root, user_table.max_questions()); |
1930 | user.user_resource.questions=ptr ? atoi(ptr) : 0; |
1931 | ptr = get_field(thd->mem_root, user_table.max_updates()); |
1932 | user.user_resource.updates=ptr ? atoi(ptr) : 0; |
1933 | ptr = get_field(thd->mem_root, user_table.max_connections()); |
1934 | user.user_resource.conn_per_hour= ptr ? atoi(ptr) : 0; |
1935 | if (user.user_resource.questions || user.user_resource.updates || |
1936 | user.user_resource.conn_per_hour) |
1937 | mqh_used=1; |
1938 | |
1939 | if (user_table.max_user_connections()) |
1940 | { |
1941 | /* Starting from 5.0.3 we have max_user_connections field */ |
1942 | ptr= get_field(thd->mem_root, user_table.max_user_connections()); |
1943 | user.user_resource.user_conn= ptr ? atoi(ptr) : 0; |
1944 | } |
1945 | |
1946 | if (!is_role && user_table.plugin()) |
1947 | { |
1948 | /* We may have plugin & auth_String fields */ |
1949 | char *tmpstr= get_field(&acl_memroot, user_table.plugin()); |
1950 | if (tmpstr) |
1951 | { |
1952 | user.plugin.str= tmpstr; |
1953 | user.plugin.length= strlen(user.plugin.str); |
1954 | user.auth_string.str= |
1955 | safe_str(get_field(&acl_memroot, |
1956 | user_table.authentication_string())); |
1957 | user.auth_string.length= strlen(user.auth_string.str); |
1958 | |
1959 | if (user.auth_string.length && password_len) |
1960 | { |
1961 | sql_print_warning("'user' entry '%s@%s' has both a password " |
1962 | "and an authentication plugin specified. The " |
1963 | "password will be ignored." , |
1964 | safe_str(user.user.str), |
1965 | safe_str(user.host.hostname)); |
1966 | } |
1967 | |
1968 | fix_user_plugin_ptr(&user); |
1969 | } |
1970 | } |
1971 | |
1972 | if (user_table.max_statement_time()) |
1973 | { |
1974 | /* Starting from 10.1.1 we can have max_statement_time */ |
1975 | ptr= get_field(thd->mem_root, |
1976 | user_table.max_statement_time()); |
1977 | user.user_resource.max_statement_time= ptr ? atof(ptr) : 0.0; |
1978 | } |
1979 | } |
1980 | else |
1981 | { |
1982 | user.ssl_type=SSL_TYPE_NONE; |
1983 | #ifndef TO_BE_REMOVED |
1984 | if (user_table.num_fields() <= 13) |
1985 | { // Without grant |
1986 | if (user.access & CREATE_ACL) |
1987 | user.access|=REFERENCES_ACL | INDEX_ACL | ALTER_ACL; |
1988 | } |
1989 | /* Convert old privileges */ |
1990 | user.access|= LOCK_TABLES_ACL | CREATE_TMP_ACL | SHOW_DB_ACL; |
1991 | if (user.access & FILE_ACL) |
1992 | user.access|= REPL_CLIENT_ACL | REPL_SLAVE_ACL; |
1993 | if (user.access & PROCESS_ACL) |
1994 | user.access|= SUPER_ACL | EXECUTE_ACL; |
1995 | #endif |
1996 | } |
1997 | |
1998 | (void) my_init_dynamic_array(&user.role_grants,sizeof(ACL_ROLE *), |
1999 | 8, 8, MYF(0)); |
2000 | |
2001 | /* check default role, if any */ |
2002 | if (!is_role && user_table.default_role()) |
2003 | { |
2004 | user.default_rolename.str= |
2005 | get_field(&acl_memroot, user_table.default_role()); |
2006 | user.default_rolename.length= safe_strlen(user.default_rolename.str); |
2007 | } |
2008 | |
2009 | if (is_role) |
2010 | { |
2011 | DBUG_PRINT("info" , ("Found role %s" , user.user.str)); |
2012 | ACL_ROLE *entry= new (&acl_memroot) ACL_ROLE(&user, &acl_memroot); |
2013 | entry->role_grants = user.role_grants; |
2014 | (void) my_init_dynamic_array(&entry->parent_grantee, |
2015 | sizeof(ACL_USER_BASE *), 8, 8, MYF(0)); |
2016 | my_hash_insert(&acl_roles, (uchar *)entry); |
2017 | |
2018 | continue; |
2019 | } |
2020 | else |
2021 | { |
2022 | DBUG_PRINT("info" , ("Found user %s" , user.user.str)); |
2023 | (void) push_dynamic(&acl_users,(uchar*) &user); |
2024 | } |
2025 | if (!user.host.hostname || |
2026 | (user.host.hostname[0] == wild_many && !user.host.hostname[1])) |
2027 | allow_all_hosts=1; // Anyone can connect |
2028 | } |
2029 | } |
2030 | my_qsort((uchar*) dynamic_element(&acl_users,0,ACL_USER*),acl_users.elements, |
2031 | sizeof(ACL_USER),(qsort_cmp) acl_compare); |
2032 | end_read_record(&read_record_info); |
2033 | freeze_size(&acl_users); |
2034 | |
2035 | const Db_table& db_table= tables.db_table(); |
2036 | if (db_table.init_read_record(&read_record_info, thd)) |
2037 | DBUG_RETURN(TRUE); |
2038 | while (!(read_record_info.read_record())) |
2039 | { |
2040 | ACL_DB db; |
2041 | char *db_name; |
2042 | db.user=get_field(&acl_memroot, db_table.user()); |
2043 | const char *hostname= get_field(&acl_memroot, db_table.host()); |
2044 | if (!hostname && find_acl_role(db.user)) |
2045 | hostname= "" ; |
2046 | update_hostname(&db.host, hostname); |
2047 | db.db= db_name= get_field(&acl_memroot, db_table.db()); |
2048 | if (!db.db) |
2049 | { |
2050 | sql_print_warning("Found an entry in the 'db' table with empty database name; Skipped" ); |
2051 | continue; |
2052 | } |
2053 | if (check_no_resolve && hostname_requires_resolving(db.host.hostname)) |
2054 | { |
2055 | sql_print_warning("'db' entry '%s %s@%s' " |
2056 | "ignored in --skip-name-resolve mode." , |
2057 | db.db, safe_str(db.user), safe_str(db.host.hostname)); |
2058 | continue; |
2059 | } |
2060 | db.access= db_table.get_access(); |
2061 | db.access=fix_rights_for_db(db.access); |
2062 | db.initial_access= db.access; |
2063 | if (lower_case_table_names) |
2064 | { |
2065 | /* |
2066 | convert db to lower case and give a warning if the db wasn't |
2067 | already in lower case |
2068 | */ |
2069 | char *end = strnmov(tmp_name, db.db, sizeof(tmp_name)); |
2070 | if (end >= tmp_name + sizeof(tmp_name)) |
2071 | { |
2072 | sql_print_warning(ER_THD(thd, ER_WRONG_DB_NAME), db.db); |
2073 | continue; |
2074 | } |
2075 | my_casedn_str(files_charset_info, db_name); |
2076 | if (strcmp(db_name, tmp_name) != 0) |
2077 | { |
2078 | sql_print_warning("'db' entry '%s %s@%s' had database in mixed " |
2079 | "case that has been forced to lowercase because " |
2080 | "lower_case_table_names is set. It will not be " |
2081 | "possible to remove this privilege using REVOKE." , |
2082 | db.db, safe_str(db.user), safe_str(db.host.hostname)); |
2083 | } |
2084 | } |
2085 | db.sort=get_sort(3,db.host.hostname,db.db,db.user); |
2086 | #ifndef TO_BE_REMOVED |
2087 | if (db_table.num_fields() <= 9) |
2088 | { // Without grant |
2089 | if (db.access & CREATE_ACL) |
2090 | db.access|=REFERENCES_ACL | INDEX_ACL | ALTER_ACL; |
2091 | } |
2092 | #endif |
2093 | (void) push_dynamic(&acl_dbs,(uchar*) &db); |
2094 | } |
2095 | my_qsort((uchar*) dynamic_element(&acl_dbs,0,ACL_DB*),acl_dbs.elements, |
2096 | sizeof(ACL_DB),(qsort_cmp) acl_compare); |
2097 | end_read_record(&read_record_info); |
2098 | freeze_size(&acl_dbs); |
2099 | |
2100 | const Proxies_priv_table& proxies_priv_table= tables.proxies_priv_table(); |
2101 | if (proxies_priv_table.table_exists()) |
2102 | { |
2103 | if (proxies_priv_table.init_read_record(&read_record_info, thd)) |
2104 | DBUG_RETURN(TRUE); |
2105 | while (!(read_record_info.read_record())) |
2106 | { |
2107 | ACL_PROXY_USER proxy; |
2108 | proxy.init(proxies_priv_table, &acl_memroot); |
2109 | if (proxy.check_validity(check_no_resolve)) |
2110 | continue; |
2111 | if (push_dynamic(&acl_proxy_users, (uchar*) &proxy)) |
2112 | DBUG_RETURN(TRUE); |
2113 | } |
2114 | my_qsort((uchar*) dynamic_element(&acl_proxy_users, 0, ACL_PROXY_USER*), |
2115 | acl_proxy_users.elements, |
2116 | sizeof(ACL_PROXY_USER), (qsort_cmp) acl_compare); |
2117 | end_read_record(&read_record_info); |
2118 | } |
2119 | else |
2120 | { |
2121 | sql_print_error("Missing system table mysql.proxies_priv; " |
2122 | "please run mysql_upgrade to create it" ); |
2123 | } |
2124 | freeze_size(&acl_proxy_users); |
2125 | |
2126 | const Roles_mapping_table& roles_mapping_table= tables.roles_mapping_table(); |
2127 | if (roles_mapping_table.table_exists()) |
2128 | { |
2129 | if (roles_mapping_table.init_read_record(&read_record_info, thd)) |
2130 | DBUG_RETURN(TRUE); |
2131 | |
2132 | MEM_ROOT temp_root; |
2133 | init_alloc_root(&temp_root, "ACL_tmp" , ACL_ALLOC_BLOCK_SIZE, 0, MYF(0)); |
2134 | while (!(read_record_info.read_record())) |
2135 | { |
2136 | char *hostname= safe_str(get_field(&temp_root, roles_mapping_table.host())); |
2137 | char *username= safe_str(get_field(&temp_root, roles_mapping_table.user())); |
2138 | char *rolename= safe_str(get_field(&temp_root, roles_mapping_table.role())); |
2139 | bool with_grant_option= get_YN_as_bool(roles_mapping_table.admin_option()); |
2140 | |
2141 | if (add_role_user_mapping(username, hostname, rolename)) { |
2142 | sql_print_error("Invalid roles_mapping table entry user:'%s@%s', rolename:'%s'" , |
2143 | username, hostname, rolename); |
2144 | continue; |
2145 | } |
2146 | |
2147 | ROLE_GRANT_PAIR *mapping= new (&acl_memroot) ROLE_GRANT_PAIR; |
2148 | |
2149 | if (mapping->init(&acl_memroot, username, hostname, rolename, with_grant_option)) |
2150 | continue; |
2151 | |
2152 | my_hash_insert(&acl_roles_mappings, (uchar*) mapping); |
2153 | } |
2154 | |
2155 | free_root(&temp_root, MYF(0)); |
2156 | end_read_record(&read_record_info); |
2157 | } |
2158 | else |
2159 | { |
2160 | sql_print_error("Missing system table mysql.roles_mapping; " |
2161 | "please run mysql_upgrade to create it" ); |
2162 | } |
2163 | |
2164 | init_check_host(); |
2165 | |
2166 | initialized=1; |
2167 | DBUG_RETURN(FALSE); |
2168 | } |
2169 | |
2170 | |
2171 | void acl_free(bool end) |
2172 | { |
2173 | my_hash_free(&acl_roles); |
2174 | free_root(&acl_memroot,MYF(0)); |
2175 | delete_dynamic(&acl_hosts); |
2176 | delete_dynamic_with_callback(&acl_users, (FREE_FUNC) free_acl_user); |
2177 | delete_dynamic(&acl_dbs); |
2178 | delete_dynamic(&acl_wild_hosts); |
2179 | delete_dynamic(&acl_proxy_users); |
2180 | my_hash_free(&acl_check_hosts); |
2181 | my_hash_free(&acl_roles_mappings); |
2182 | if (!end) |
2183 | acl_cache->clear(1); /* purecov: inspected */ |
2184 | else |
2185 | { |
2186 | plugin_unlock(0, native_password_plugin); |
2187 | plugin_unlock(0, old_password_plugin); |
2188 | delete acl_cache; |
2189 | acl_cache=0; |
2190 | } |
2191 | } |
2192 | |
2193 | |
2194 | /* |
2195 | Forget current user/db-level privileges and read new privileges |
2196 | from the privilege tables. |
2197 | |
2198 | SYNOPSIS |
2199 | acl_reload() |
2200 | thd Current thread |
2201 | |
2202 | NOTE |
2203 | All tables of calling thread which were open and locked by LOCK TABLES |
2204 | statement will be unlocked and closed. |
2205 | This function is also used for initialization of structures responsible |
2206 | for user/db-level privilege checking. |
2207 | |
2208 | RETURN VALUE |
2209 | FALSE Success |
2210 | TRUE Failure |
2211 | */ |
2212 | |
2213 | bool acl_reload(THD *thd) |
2214 | { |
2215 | DYNAMIC_ARRAY old_acl_hosts, old_acl_users, old_acl_dbs, old_acl_proxy_users; |
2216 | HASH old_acl_roles, old_acl_roles_mappings; |
2217 | MEM_ROOT old_mem; |
2218 | int result; |
2219 | DBUG_ENTER("acl_reload" ); |
2220 | |
2221 | Grant_tables tables(Table_host | Table_user | Table_db | Table_proxies_priv | |
2222 | Table_roles_mapping, TL_READ); |
2223 | /* |
2224 | To avoid deadlocks we should obtain table locks before |
2225 | obtaining acl_cache->lock mutex. |
2226 | */ |
2227 | if (unlikely((result= tables.open_and_lock(thd)))) |
2228 | { |
2229 | DBUG_ASSERT(result <= 0); |
2230 | /* |
2231 | Execution might have been interrupted; only print the error message |
2232 | if an error condition has been raised. |
2233 | */ |
2234 | if (thd->get_stmt_da()->is_error()) |
2235 | sql_print_error("Fatal error: Can't open and lock privilege tables: %s" , |
2236 | thd->get_stmt_da()->message()); |
2237 | goto end; |
2238 | } |
2239 | |
2240 | acl_cache->clear(0); |
2241 | mysql_mutex_lock(&acl_cache->lock); |
2242 | |
2243 | old_acl_hosts= acl_hosts; |
2244 | old_acl_users= acl_users; |
2245 | old_acl_roles= acl_roles; |
2246 | old_acl_roles_mappings= acl_roles_mappings; |
2247 | old_acl_proxy_users= acl_proxy_users; |
2248 | old_acl_dbs= acl_dbs; |
2249 | my_init_dynamic_array(&acl_hosts, sizeof(ACL_HOST), 20, 50, MYF(0)); |
2250 | my_init_dynamic_array(&acl_users, sizeof(ACL_USER), 50, 100, MYF(0)); |
2251 | my_init_dynamic_array(&acl_dbs, sizeof(ACL_DB), 50, 100, MYF(0)); |
2252 | my_init_dynamic_array(&acl_proxy_users, sizeof(ACL_PROXY_USER), 50, 100, MYF(0)); |
2253 | my_hash_init2(&acl_roles,50, &my_charset_utf8_bin, |
2254 | 0, 0, 0, (my_hash_get_key) acl_role_get_key, 0, |
2255 | (void (*)(void *))free_acl_role, 0); |
2256 | my_hash_init2(&acl_roles_mappings, 50, &my_charset_utf8_bin, 0, 0, 0, |
2257 | (my_hash_get_key) acl_role_map_get_key, 0, 0, 0); |
2258 | old_mem= acl_memroot; |
2259 | delete_dynamic(&acl_wild_hosts); |
2260 | my_hash_free(&acl_check_hosts); |
2261 | |
2262 | if ((result= acl_load(thd, tables))) |
2263 | { // Error. Revert to old list |
2264 | DBUG_PRINT("error" ,("Reverting to old privileges" )); |
2265 | acl_free(); /* purecov: inspected */ |
2266 | acl_hosts= old_acl_hosts; |
2267 | acl_users= old_acl_users; |
2268 | acl_roles= old_acl_roles; |
2269 | acl_roles_mappings= old_acl_roles_mappings; |
2270 | acl_proxy_users= old_acl_proxy_users; |
2271 | acl_dbs= old_acl_dbs; |
2272 | acl_memroot= old_mem; |
2273 | init_check_host(); |
2274 | } |
2275 | else |
2276 | { |
2277 | my_hash_free(&old_acl_roles); |
2278 | free_root(&old_mem,MYF(0)); |
2279 | delete_dynamic(&old_acl_hosts); |
2280 | delete_dynamic_with_callback(&old_acl_users, (FREE_FUNC) free_acl_user); |
2281 | delete_dynamic(&old_acl_proxy_users); |
2282 | delete_dynamic(&old_acl_dbs); |
2283 | my_hash_free(&old_acl_roles_mappings); |
2284 | } |
2285 | mysql_mutex_unlock(&acl_cache->lock); |
2286 | end: |
2287 | close_mysql_tables(thd); |
2288 | DBUG_RETURN(result); |
2289 | } |
2290 | |
2291 | /* |
2292 | Get all access bits from table after fieldnr |
2293 | |
2294 | IMPLEMENTATION |
2295 | We know that the access privileges ends when there is no more fields |
2296 | or the field is not an enum with two elements. |
2297 | |
2298 | SYNOPSIS |
2299 | get_access() |
2300 | form an open table to read privileges from. |
2301 | The record should be already read in table->record[0] |
2302 | fieldnr number of the first privilege (that is ENUM('N','Y') field |
2303 | next_field on return - number of the field next to the last ENUM |
2304 | (unless next_field == 0) |
2305 | |
2306 | RETURN VALUE |
2307 | privilege mask |
2308 | */ |
2309 | |
2310 | static ulong get_access(TABLE *form, uint fieldnr, uint *next_field) |
2311 | { |
2312 | ulong access_bits=0,bit; |
2313 | char buff[2]; |
2314 | String res(buff,sizeof(buff),&my_charset_latin1); |
2315 | Field **pos; |
2316 | |
2317 | for (pos=form->field+fieldnr, bit=1; |
2318 | *pos && (*pos)->real_type() == MYSQL_TYPE_ENUM && |
2319 | ((Field_enum*) (*pos))->typelib->count == 2 ; |
2320 | pos++, fieldnr++, bit<<=1) |
2321 | { |
2322 | if (get_YN_as_bool(*pos)) |
2323 | access_bits|= bit; |
2324 | } |
2325 | if (next_field) |
2326 | *next_field=fieldnr; |
2327 | return access_bits; |
2328 | } |
2329 | |
2330 | |
2331 | /* |
2332 | Return a number which, if sorted 'desc', puts strings in this order: |
2333 | no wildcards |
2334 | wildcards |
2335 | empty string |
2336 | */ |
2337 | |
2338 | static ulong get_sort(uint count,...) |
2339 | { |
2340 | va_list args; |
2341 | va_start(args,count); |
2342 | ulong sort=0; |
2343 | |
2344 | /* Should not use this function with more than 4 arguments for compare. */ |
2345 | DBUG_ASSERT(count <= 4); |
2346 | |
2347 | while (count--) |
2348 | { |
2349 | char *start, *str= va_arg(args,char*); |
2350 | uint chars= 0; |
2351 | uint wild_pos= 0; /* first wildcard position */ |
2352 | |
2353 | if ((start= str)) |
2354 | { |
2355 | for (; *str ; str++) |
2356 | { |
2357 | if (*str == wild_prefix && str[1]) |
2358 | str++; |
2359 | else if (*str == wild_many || *str == wild_one) |
2360 | { |
2361 | wild_pos= (uint) (str - start) + 1; |
2362 | break; |
2363 | } |
2364 | chars= 128; // Marker that chars existed |
2365 | } |
2366 | } |
2367 | sort= (sort << 8) + (wild_pos ? MY_MIN(wild_pos, 127U) : chars); |
2368 | } |
2369 | va_end(args); |
2370 | return sort; |
2371 | } |
2372 | |
2373 | |
2374 | static int acl_compare(ACL_ACCESS *a,ACL_ACCESS *b) |
2375 | { |
2376 | if (a->sort > b->sort) |
2377 | return -1; |
2378 | if (a->sort < b->sort) |
2379 | return 1; |
2380 | return 0; |
2381 | } |
2382 | |
2383 | |
2384 | /* |
2385 | Gets user credentials without authentication and resource limit checks. |
2386 | |
2387 | SYNOPSIS |
2388 | acl_getroot() |
2389 | sctx Context which should be initialized |
2390 | user user name |
2391 | host host name |
2392 | ip IP |
2393 | db current data base name |
2394 | |
2395 | RETURN |
2396 | FALSE OK |
2397 | TRUE Error |
2398 | */ |
2399 | |
2400 | bool acl_getroot(Security_context *sctx, const char *user, const char *host, |
2401 | const char *ip, const char *db) |
2402 | { |
2403 | int res= 1; |
2404 | uint i; |
2405 | ACL_USER *acl_user= 0; |
2406 | DBUG_ENTER("acl_getroot" ); |
2407 | |
2408 | DBUG_PRINT("enter" , ("Host: '%s', Ip: '%s', User: '%s', db: '%s'" , |
2409 | host, ip, user, db)); |
2410 | sctx->user= user; |
2411 | sctx->host= host; |
2412 | sctx->ip= ip; |
2413 | sctx->host_or_ip= host ? host : (safe_str(ip)); |
2414 | |
2415 | if (!initialized) |
2416 | { |
2417 | /* |
2418 | here if mysqld's been started with --skip-grant-tables option. |
2419 | */ |
2420 | sctx->skip_grants(); |
2421 | DBUG_RETURN(FALSE); |
2422 | } |
2423 | |
2424 | mysql_mutex_lock(&acl_cache->lock); |
2425 | |
2426 | sctx->master_access= 0; |
2427 | sctx->db_access= 0; |
2428 | *sctx->priv_user= *sctx->priv_host= *sctx->priv_role= 0; |
2429 | |
2430 | if (host[0]) // User, not Role |
2431 | { |
2432 | acl_user= find_user_wild(host, user, ip); |
2433 | |
2434 | if (acl_user) |
2435 | { |
2436 | res= 0; |
2437 | for (i=0 ; i < acl_dbs.elements ; i++) |
2438 | { |
2439 | ACL_DB *acl_db= dynamic_element(&acl_dbs, i, ACL_DB*); |
2440 | if (!acl_db->user || |
2441 | (user && user[0] && !strcmp(user, acl_db->user))) |
2442 | { |
2443 | if (compare_hostname(&acl_db->host, host, ip)) |
2444 | { |
2445 | if (!acl_db->db || (db && !wild_compare(db, acl_db->db, 0))) |
2446 | { |
2447 | sctx->db_access= acl_db->access; |
2448 | break; |
2449 | } |
2450 | } |
2451 | } |
2452 | } |
2453 | sctx->master_access= acl_user->access; |
2454 | |
2455 | if (acl_user->user.str) |
2456 | strmake_buf(sctx->priv_user, user); |
2457 | |
2458 | if (acl_user->host.hostname) |
2459 | strmake_buf(sctx->priv_host, acl_user->host.hostname); |
2460 | } |
2461 | } |
2462 | else // Role, not User |
2463 | { |
2464 | ACL_ROLE *acl_role= find_acl_role(user); |
2465 | if (acl_role) |
2466 | { |
2467 | res= 0; |
2468 | for (i=0 ; i < acl_dbs.elements ; i++) |
2469 | { |
2470 | ACL_DB *acl_db= dynamic_element(&acl_dbs, i, ACL_DB*); |
2471 | if (!acl_db->user || |
2472 | (user && user[0] && !strcmp(user, acl_db->user))) |
2473 | { |
2474 | if (compare_hostname(&acl_db->host, "" , "" )) |
2475 | { |
2476 | if (!acl_db->db || (db && !wild_compare(db, acl_db->db, 0))) |
2477 | { |
2478 | sctx->db_access= acl_db->access; |
2479 | break; |
2480 | } |
2481 | } |
2482 | } |
2483 | } |
2484 | sctx->master_access= acl_role->access; |
2485 | |
2486 | if (acl_role->user.str) |
2487 | strmake_buf(sctx->priv_role, user); |
2488 | } |
2489 | } |
2490 | |
2491 | mysql_mutex_unlock(&acl_cache->lock); |
2492 | DBUG_RETURN(res); |
2493 | } |
2494 | |
2495 | static int check_user_can_set_role(const char *user, const char *host, |
2496 | const char *ip, const char *rolename, ulonglong *access) |
2497 | { |
2498 | ACL_ROLE *role; |
2499 | ACL_USER_BASE *acl_user_base; |
2500 | ACL_USER *UNINIT_VAR(acl_user); |
2501 | bool is_granted= FALSE; |
2502 | int result= 0; |
2503 | |
2504 | /* clear role privileges */ |
2505 | mysql_mutex_lock(&acl_cache->lock); |
2506 | |
2507 | if (!strcasecmp(rolename, "NONE" )) |
2508 | { |
2509 | /* have to clear the privileges */ |
2510 | /* get the current user */ |
2511 | acl_user= find_user_wild(host, user, ip); |
2512 | if (acl_user == NULL) |
2513 | { |
2514 | my_error(ER_INVALID_CURRENT_USER, MYF(0)); |
2515 | result= -1; |
2516 | } |
2517 | else if (access) |
2518 | *access= acl_user->access; |
2519 | |
2520 | goto end; |
2521 | } |
2522 | |
2523 | role= find_acl_role(rolename); |
2524 | |
2525 | /* According to SQL standard, the same error message must be presented */ |
2526 | if (role == NULL) { |
2527 | my_error(ER_INVALID_ROLE, MYF(0), rolename); |
2528 | result= -1; |
2529 | goto end; |
2530 | } |
2531 | |
2532 | for (uint i=0 ; i < role->parent_grantee.elements ; i++) |
2533 | { |
2534 | acl_user_base= *(dynamic_element(&role->parent_grantee, i, ACL_USER_BASE**)); |
2535 | if (acl_user_base->flags & IS_ROLE) |
2536 | continue; |
2537 | |
2538 | acl_user= (ACL_USER *)acl_user_base; |
2539 | if (acl_user->wild_eq(user, host, ip)) |
2540 | { |
2541 | is_granted= TRUE; |
2542 | break; |
2543 | } |
2544 | } |
2545 | |
2546 | /* According to SQL standard, the same error message must be presented */ |
2547 | if (!is_granted) |
2548 | { |
2549 | my_error(ER_INVALID_ROLE, MYF(0), rolename); |
2550 | result= 1; |
2551 | goto end; |
2552 | } |
2553 | |
2554 | if (access) |
2555 | { |
2556 | *access = acl_user->access | role->access; |
2557 | } |
2558 | end: |
2559 | mysql_mutex_unlock(&acl_cache->lock); |
2560 | return result; |
2561 | |
2562 | } |
2563 | |
2564 | int acl_check_setrole(THD *thd, const char *rolename, ulonglong *access) |
2565 | { |
2566 | /* Yes! priv_user@host. Don't ask why - that's what check_access() does. */ |
2567 | return check_user_can_set_role(thd->security_ctx->priv_user, |
2568 | thd->security_ctx->host, thd->security_ctx->ip, rolename, access); |
2569 | } |
2570 | |
2571 | |
2572 | int acl_setrole(THD *thd, const char *rolename, ulonglong access) |
2573 | { |
2574 | /* merge the privileges */ |
2575 | Security_context *sctx= thd->security_ctx; |
2576 | sctx->master_access= static_cast<ulong>(access); |
2577 | if (thd->db.str) |
2578 | sctx->db_access= acl_get(sctx->host, sctx->ip, sctx->user, thd->db.str, FALSE); |
2579 | |
2580 | if (!strcasecmp(rolename, "NONE" )) |
2581 | { |
2582 | thd->security_ctx->priv_role[0]= 0; |
2583 | } |
2584 | else |
2585 | { |
2586 | if (thd->db.str) |
2587 | sctx->db_access|= acl_get("" , "" , rolename, thd->db.str, FALSE); |
2588 | /* mark the current role */ |
2589 | strmake_buf(thd->security_ctx->priv_role, rolename); |
2590 | } |
2591 | return 0; |
2592 | } |
2593 | |
2594 | static uchar* check_get_key(ACL_USER *buff, size_t *length, |
2595 | my_bool not_used __attribute__((unused))) |
2596 | { |
2597 | *length=buff->hostname_length; |
2598 | return (uchar*) buff->host.hostname; |
2599 | } |
2600 | |
2601 | |
2602 | static void acl_update_role(const char *rolename, ulong privileges) |
2603 | { |
2604 | ACL_ROLE *role= find_acl_role(rolename); |
2605 | if (role) |
2606 | role->initial_role_access= role->access= privileges; |
2607 | } |
2608 | |
2609 | |
2610 | static void acl_update_user(const char *user, const char *host, |
2611 | const char *password, size_t password_len, |
2612 | enum SSL_type ssl_type, |
2613 | const char *ssl_cipher, |
2614 | const char *x509_issuer, |
2615 | const char *x509_subject, |
2616 | USER_RESOURCES *mqh, |
2617 | ulong privileges, |
2618 | const LEX_CSTRING *plugin, |
2619 | const LEX_CSTRING *auth) |
2620 | { |
2621 | mysql_mutex_assert_owner(&acl_cache->lock); |
2622 | |
2623 | for (uint i=0 ; i < acl_users.elements ; i++) |
2624 | { |
2625 | ACL_USER *acl_user=dynamic_element(&acl_users,i,ACL_USER*); |
2626 | if (acl_user->eq(user, host)) |
2627 | { |
2628 | if (plugin->str[0]) |
2629 | { |
2630 | acl_user->plugin= *plugin; |
2631 | acl_user->auth_string.str= auth->str ? |
2632 | strmake_root(&acl_memroot, auth->str, auth->length) : const_cast<char*>("" ); |
2633 | acl_user->auth_string.length= auth->length; |
2634 | if (fix_user_plugin_ptr(acl_user)) |
2635 | acl_user->plugin.str= strmake_root(&acl_memroot, plugin->str, plugin->length); |
2636 | } |
2637 | else |
2638 | if (password[0]) |
2639 | { |
2640 | acl_user->auth_string.str= strmake_root(&acl_memroot, password, password_len); |
2641 | acl_user->auth_string.length= password_len; |
2642 | set_user_salt(acl_user, password, password_len); |
2643 | set_user_plugin(acl_user, password_len); |
2644 | } |
2645 | acl_user->access=privileges; |
2646 | if (mqh->specified_limits & USER_RESOURCES::QUERIES_PER_HOUR) |
2647 | acl_user->user_resource.questions=mqh->questions; |
2648 | if (mqh->specified_limits & USER_RESOURCES::UPDATES_PER_HOUR) |
2649 | acl_user->user_resource.updates=mqh->updates; |
2650 | if (mqh->specified_limits & USER_RESOURCES::CONNECTIONS_PER_HOUR) |
2651 | acl_user->user_resource.conn_per_hour= mqh->conn_per_hour; |
2652 | if (mqh->specified_limits & USER_RESOURCES::USER_CONNECTIONS) |
2653 | acl_user->user_resource.user_conn= mqh->user_conn; |
2654 | if (mqh->specified_limits & USER_RESOURCES::MAX_STATEMENT_TIME) |
2655 | acl_user->user_resource.max_statement_time= mqh->max_statement_time; |
2656 | if (ssl_type != SSL_TYPE_NOT_SPECIFIED) |
2657 | { |
2658 | acl_user->ssl_type= ssl_type; |
2659 | acl_user->ssl_cipher= (ssl_cipher ? strdup_root(&acl_memroot,ssl_cipher) : |
2660 | 0); |
2661 | acl_user->x509_issuer= (x509_issuer ? strdup_root(&acl_memroot,x509_issuer) : |
2662 | 0); |
2663 | acl_user->x509_subject= (x509_subject ? |
2664 | strdup_root(&acl_memroot,x509_subject) : 0); |
2665 | } |
2666 | /* search complete: */ |
2667 | break; |
2668 | } |
2669 | } |
2670 | } |
2671 | |
2672 | |
2673 | static void acl_insert_role(const char *rolename, ulong privileges) |
2674 | { |
2675 | ACL_ROLE *entry; |
2676 | |
2677 | mysql_mutex_assert_owner(&acl_cache->lock); |
2678 | entry= new (&acl_memroot) ACL_ROLE(rolename, privileges, &acl_memroot); |
2679 | (void) my_init_dynamic_array(&entry->parent_grantee, |
2680 | sizeof(ACL_USER_BASE *), 8, 8, MYF(0)); |
2681 | (void) my_init_dynamic_array(&entry->role_grants,sizeof(ACL_ROLE *), |
2682 | 8, 8, MYF(0)); |
2683 | |
2684 | my_hash_insert(&acl_roles, (uchar *)entry); |
2685 | } |
2686 | |
2687 | |
2688 | static void acl_insert_user(const char *user, const char *host, |
2689 | const char *password, size_t password_len, |
2690 | enum SSL_type ssl_type, |
2691 | const char *ssl_cipher, |
2692 | const char *x509_issuer, |
2693 | const char *x509_subject, |
2694 | USER_RESOURCES *mqh, |
2695 | ulong privileges, |
2696 | const LEX_CSTRING *plugin, |
2697 | const LEX_CSTRING *auth) |
2698 | { |
2699 | ACL_USER acl_user; |
2700 | |
2701 | mysql_mutex_assert_owner(&acl_cache->lock); |
2702 | |
2703 | bzero(&acl_user, sizeof(acl_user)); |
2704 | acl_user.user.str=*user ? strdup_root(&acl_memroot,user) : 0; |
2705 | acl_user.user.length= strlen(user); |
2706 | update_hostname(&acl_user.host, safe_strdup_root(&acl_memroot, host)); |
2707 | if (plugin->str[0]) |
2708 | { |
2709 | acl_user.plugin= *plugin; |
2710 | acl_user.auth_string.str= auth->str ? |
2711 | strmake_root(&acl_memroot, auth->str, auth->length) : const_cast<char*>("" ); |
2712 | acl_user.auth_string.length= auth->length; |
2713 | if (fix_user_plugin_ptr(&acl_user)) |
2714 | acl_user.plugin.str= strmake_root(&acl_memroot, plugin->str, plugin->length); |
2715 | } |
2716 | else |
2717 | { |
2718 | acl_user.auth_string.str= strmake_root(&acl_memroot, password, password_len); |
2719 | acl_user.auth_string.length= password_len; |
2720 | set_user_salt(&acl_user, password, password_len); |
2721 | set_user_plugin(&acl_user, password_len); |
2722 | } |
2723 | |
2724 | acl_user.flags= 0; |
2725 | acl_user.access=privileges; |
2726 | acl_user.user_resource = *mqh; |
2727 | acl_user.sort=get_sort(2, acl_user.host.hostname, acl_user.user.str); |
2728 | acl_user.hostname_length=(uint) strlen(host); |
2729 | acl_user.ssl_type= (ssl_type != SSL_TYPE_NOT_SPECIFIED ? |
2730 | ssl_type : SSL_TYPE_NONE); |
2731 | acl_user.ssl_cipher= ssl_cipher ? strdup_root(&acl_memroot,ssl_cipher) : 0; |
2732 | acl_user.x509_issuer= x509_issuer ? strdup_root(&acl_memroot,x509_issuer) : 0; |
2733 | acl_user.x509_subject=x509_subject ? strdup_root(&acl_memroot,x509_subject) : 0; |
2734 | (void) my_init_dynamic_array(&acl_user.role_grants, sizeof(ACL_USER *), |
2735 | 8, 8, MYF(0)); |
2736 | |
2737 | (void) push_dynamic(&acl_users,(uchar*) &acl_user); |
2738 | if (!acl_user.host.hostname || |
2739 | (acl_user.host.hostname[0] == wild_many && !acl_user.host.hostname[1])) |
2740 | allow_all_hosts=1; // Anyone can connect /* purecov: tested */ |
2741 | my_qsort((uchar*) dynamic_element(&acl_users,0,ACL_USER*),acl_users.elements, |
2742 | sizeof(ACL_USER),(qsort_cmp) acl_compare); |
2743 | |
2744 | /* Rebuild 'acl_check_hosts' since 'acl_users' has been modified */ |
2745 | rebuild_check_host(); |
2746 | |
2747 | /* |
2748 | Rebuild every user's role_grants since 'acl_users' has been sorted |
2749 | and old pointers to ACL_USER elements are no longer valid |
2750 | */ |
2751 | rebuild_role_grants(); |
2752 | } |
2753 | |
2754 | |
2755 | static bool acl_update_db(const char *user, const char *host, const char *db, |
2756 | ulong privileges) |
2757 | { |
2758 | mysql_mutex_assert_owner(&acl_cache->lock); |
2759 | |
2760 | bool updated= false; |
2761 | |
2762 | for (uint i=0 ; i < acl_dbs.elements ; i++) |
2763 | { |
2764 | ACL_DB *acl_db=dynamic_element(&acl_dbs,i,ACL_DB*); |
2765 | if ((!acl_db->user && !user[0]) || |
2766 | (acl_db->user && |
2767 | !strcmp(user,acl_db->user))) |
2768 | { |
2769 | if ((!acl_db->host.hostname && !host[0]) || |
2770 | (acl_db->host.hostname && |
2771 | !strcmp(host, acl_db->host.hostname))) |
2772 | { |
2773 | if ((!acl_db->db && !db[0]) || |
2774 | (acl_db->db && !strcmp(db,acl_db->db))) |
2775 | |
2776 | { |
2777 | if (privileges) |
2778 | { |
2779 | acl_db->access= privileges; |
2780 | acl_db->initial_access= acl_db->access; |
2781 | } |
2782 | else |
2783 | delete_dynamic_element(&acl_dbs,i); |
2784 | updated= true; |
2785 | } |
2786 | } |
2787 | } |
2788 | } |
2789 | |
2790 | return updated; |
2791 | } |
2792 | |
2793 | |
2794 | /* |
2795 | Insert a user/db/host combination into the global acl_cache |
2796 | |
2797 | SYNOPSIS |
2798 | acl_insert_db() |
2799 | user User name |
2800 | host Host name |
2801 | db Database name |
2802 | privileges Bitmap of privileges |
2803 | |
2804 | NOTES |
2805 | acl_cache->lock must be locked when calling this |
2806 | */ |
2807 | |
2808 | static void acl_insert_db(const char *user, const char *host, const char *db, |
2809 | ulong privileges) |
2810 | { |
2811 | ACL_DB acl_db; |
2812 | mysql_mutex_assert_owner(&acl_cache->lock); |
2813 | acl_db.user=strdup_root(&acl_memroot,user); |
2814 | update_hostname(&acl_db.host, safe_strdup_root(&acl_memroot, host)); |
2815 | acl_db.db=strdup_root(&acl_memroot,db); |
2816 | acl_db.initial_access= acl_db.access= privileges; |
2817 | acl_db.sort=get_sort(3,acl_db.host.hostname,acl_db.db,acl_db.user); |
2818 | (void) push_dynamic(&acl_dbs,(uchar*) &acl_db); |
2819 | my_qsort((uchar*) dynamic_element(&acl_dbs,0,ACL_DB*),acl_dbs.elements, |
2820 | sizeof(ACL_DB),(qsort_cmp) acl_compare); |
2821 | } |
2822 | |
2823 | |
2824 | /* |
2825 | Get privilege for a host, user and db combination |
2826 | |
2827 | as db_is_pattern changes the semantics of comparison, |
2828 | acl_cache is not used if db_is_pattern is set. |
2829 | */ |
2830 | |
2831 | ulong acl_get(const char *host, const char *ip, |
2832 | const char *user, const char *db, my_bool db_is_pattern) |
2833 | { |
2834 | ulong host_access= ~(ulong)0, db_access= 0; |
2835 | uint i; |
2836 | size_t key_length; |
2837 | char key[ACL_KEY_LENGTH],*tmp_db,*end; |
2838 | acl_entry *entry; |
2839 | DBUG_ENTER("acl_get" ); |
2840 | |
2841 | tmp_db= strmov(strmov(key, safe_str(ip)) + 1, user) + 1; |
2842 | end= strnmov(tmp_db, db, key + sizeof(key) - tmp_db); |
2843 | |
2844 | if (end >= key + sizeof(key)) // db name was truncated |
2845 | DBUG_RETURN(0); // no privileges for an invalid db name |
2846 | |
2847 | if (lower_case_table_names) |
2848 | { |
2849 | my_casedn_str(files_charset_info, tmp_db); |
2850 | db=tmp_db; |
2851 | } |
2852 | key_length= (size_t) (end-key); |
2853 | |
2854 | mysql_mutex_lock(&acl_cache->lock); |
2855 | if (!db_is_pattern && (entry=acl_cache->search((uchar*) key, key_length))) |
2856 | { |
2857 | db_access=entry->access; |
2858 | mysql_mutex_unlock(&acl_cache->lock); |
2859 | DBUG_PRINT("exit" , ("access: 0x%lx" , db_access)); |
2860 | DBUG_RETURN(db_access); |
2861 | } |
2862 | |
2863 | /* |
2864 | Check if there are some access rights for database and user |
2865 | */ |
2866 | for (i=0 ; i < acl_dbs.elements ; i++) |
2867 | { |
2868 | ACL_DB *acl_db=dynamic_element(&acl_dbs,i,ACL_DB*); |
2869 | if (!acl_db->user || !strcmp(user,acl_db->user)) |
2870 | { |
2871 | if (compare_hostname(&acl_db->host,host,ip)) |
2872 | { |
2873 | if (!acl_db->db || !wild_compare(db,acl_db->db,db_is_pattern)) |
2874 | { |
2875 | db_access=acl_db->access; |
2876 | if (acl_db->host.hostname) |
2877 | goto exit; // Fully specified. Take it |
2878 | /* the host table is not used for roles */ |
2879 | if ((!host || !host[0]) && !acl_db->host.hostname && find_acl_role(user)) |
2880 | goto exit; |
2881 | break; /* purecov: tested */ |
2882 | } |
2883 | } |
2884 | } |
2885 | } |
2886 | if (!db_access) |
2887 | goto exit; // Can't be better |
2888 | |
2889 | /* |
2890 | No host specified for user. Get hostdata from host table |
2891 | */ |
2892 | host_access=0; // Host must be found |
2893 | for (i=0 ; i < acl_hosts.elements ; i++) |
2894 | { |
2895 | ACL_HOST *acl_host=dynamic_element(&acl_hosts,i,ACL_HOST*); |
2896 | if (compare_hostname(&acl_host->host,host,ip)) |
2897 | { |
2898 | if (!acl_host->db || !wild_compare(db,acl_host->db,db_is_pattern)) |
2899 | { |
2900 | host_access=acl_host->access; // Fully specified. Take it |
2901 | break; |
2902 | } |
2903 | } |
2904 | } |
2905 | exit: |
2906 | /* Save entry in cache for quick retrieval */ |
2907 | if (!db_is_pattern && |
2908 | (entry= (acl_entry*) malloc(sizeof(acl_entry)+key_length))) |
2909 | { |
2910 | entry->access=(db_access & host_access); |
2911 | DBUG_ASSERT(key_length < 0xffff); |
2912 | entry->length=(uint16)key_length; |
2913 | memcpy((uchar*) entry->key,key,key_length); |
2914 | acl_cache->add(entry); |
2915 | } |
2916 | mysql_mutex_unlock(&acl_cache->lock); |
2917 | DBUG_PRINT("exit" , ("access: 0x%lx" , db_access & host_access)); |
2918 | DBUG_RETURN(db_access & host_access); |
2919 | } |
2920 | |
2921 | /* |
2922 | Check if there are any possible matching entries for this host |
2923 | |
2924 | NOTES |
2925 | All host names without wild cards are stored in a hash table, |
2926 | entries with wildcards are stored in a dynamic array |
2927 | */ |
2928 | |
2929 | static void init_check_host(void) |
2930 | { |
2931 | DBUG_ENTER("init_check_host" ); |
2932 | (void) my_init_dynamic_array(&acl_wild_hosts,sizeof(struct acl_host_and_ip), |
2933 | acl_users.elements, 1, MYF(0)); |
2934 | (void) my_hash_init(&acl_check_hosts,system_charset_info, |
2935 | acl_users.elements, 0, 0, |
2936 | (my_hash_get_key) check_get_key, 0, 0); |
2937 | if (!allow_all_hosts) |
2938 | { |
2939 | for (uint i=0 ; i < acl_users.elements ; i++) |
2940 | { |
2941 | ACL_USER *acl_user=dynamic_element(&acl_users,i,ACL_USER*); |
2942 | if (strchr(acl_user->host.hostname,wild_many) || |
2943 | strchr(acl_user->host.hostname,wild_one) || |
2944 | acl_user->host.ip_mask) |
2945 | { // Has wildcard |
2946 | uint j; |
2947 | for (j=0 ; j < acl_wild_hosts.elements ; j++) |
2948 | { // Check if host already exists |
2949 | acl_host_and_ip *acl=dynamic_element(&acl_wild_hosts,j, |
2950 | acl_host_and_ip *); |
2951 | if (!my_strcasecmp(system_charset_info, |
2952 | acl_user->host.hostname, acl->hostname)) |
2953 | break; // already stored |
2954 | } |
2955 | if (j == acl_wild_hosts.elements) // If new |
2956 | (void) push_dynamic(&acl_wild_hosts,(uchar*) &acl_user->host); |
2957 | } |
2958 | else if (!my_hash_search(&acl_check_hosts,(uchar*) |
2959 | acl_user->host.hostname, |
2960 | strlen(acl_user->host.hostname))) |
2961 | { |
2962 | if (my_hash_insert(&acl_check_hosts,(uchar*) acl_user)) |
2963 | { // End of memory |
2964 | allow_all_hosts=1; // Should never happen |
2965 | DBUG_VOID_RETURN; |
2966 | } |
2967 | } |
2968 | } |
2969 | } |
2970 | freeze_size(&acl_wild_hosts); |
2971 | freeze_size(&acl_check_hosts.array); |
2972 | DBUG_VOID_RETURN; |
2973 | } |
2974 | |
2975 | |
2976 | /* |
2977 | Rebuild lists used for checking of allowed hosts |
2978 | |
2979 | We need to rebuild 'acl_check_hosts' and 'acl_wild_hosts' after adding, |
2980 | dropping or renaming user, since they contain pointers to elements of |
2981 | 'acl_user' array, which are invalidated by drop operation, and use |
2982 | ACL_USER::host::hostname as a key, which is changed by rename. |
2983 | */ |
2984 | static void rebuild_check_host(void) |
2985 | { |
2986 | delete_dynamic(&acl_wild_hosts); |
2987 | my_hash_free(&acl_check_hosts); |
2988 | init_check_host(); |
2989 | } |
2990 | |
2991 | /* |
2992 | Reset a role role_grants dynamic array. |
2993 | Also, the role's access bits are reset to the ones present in the table. |
2994 | */ |
2995 | static my_bool acl_role_reset_role_arrays(void *ptr, |
2996 | void * not_used __attribute__((unused))) |
2997 | { |
2998 | ACL_ROLE *role= (ACL_ROLE *)ptr; |
2999 | reset_dynamic(&role->role_grants); |
3000 | reset_dynamic(&role->parent_grantee); |
3001 | role->counter= 0; |
3002 | return 0; |
3003 | } |
3004 | |
3005 | /* |
3006 | Add a the coresponding pointers present in the mapping to the entries in |
3007 | acl_users and acl_roles |
3008 | */ |
3009 | static bool add_role_user_mapping(ACL_USER_BASE *grantee, ACL_ROLE *role) |
3010 | { |
3011 | return push_dynamic(&grantee->role_grants, (uchar*) &role) |
3012 | || push_dynamic(&role->parent_grantee, (uchar*) &grantee); |
3013 | |
3014 | } |
3015 | |
3016 | /* |
3017 | Revert the last add_role_user_mapping() action |
3018 | */ |
3019 | static void undo_add_role_user_mapping(ACL_USER_BASE *grantee, ACL_ROLE *role) |
3020 | { |
3021 | void *pop __attribute__((unused)); |
3022 | |
3023 | pop= pop_dynamic(&grantee->role_grants); |
3024 | DBUG_ASSERT(role == *(ACL_ROLE**)pop); |
3025 | |
3026 | pop= pop_dynamic(&role->parent_grantee); |
3027 | DBUG_ASSERT(grantee == *(ACL_USER_BASE**)pop); |
3028 | } |
3029 | |
3030 | /* |
3031 | this helper is used when building role_grants and parent_grantee arrays |
3032 | from scratch. |
3033 | |
3034 | this happens either on initial loading of data from tables, in acl_load(). |
3035 | or in rebuild_role_grants after acl_role_reset_role_arrays(). |
3036 | */ |
3037 | static bool add_role_user_mapping(const char *uname, const char *hname, |
3038 | const char *rname) |
3039 | { |
3040 | ACL_USER_BASE *grantee= find_acl_user_base(uname, hname); |
3041 | ACL_ROLE *role= find_acl_role(rname); |
3042 | |
3043 | if (grantee == NULL || role == NULL) |
3044 | return 1; |
3045 | |
3046 | /* |
3047 | because all arrays are rebuilt completely, and counters were also reset, |
3048 | we can increment them here, and after the rebuild all counters will |
3049 | have correct values (equal to the number of roles granted). |
3050 | */ |
3051 | if (grantee->flags & IS_ROLE) |
3052 | ((ACL_ROLE*)grantee)->counter++; |
3053 | return add_role_user_mapping(grantee, role); |
3054 | } |
3055 | |
3056 | /* |
3057 | This helper function is used to removes roles and grantees |
3058 | from the corresponding cross-reference arrays. see remove_role_user_mapping(). |
3059 | as such, it asserts that an element to delete is present in the array, |
3060 | and is present only once. |
3061 | */ |
3062 | static void remove_ptr_from_dynarray(DYNAMIC_ARRAY *array, void *ptr) |
3063 | { |
3064 | bool found __attribute__((unused))= false; |
3065 | for (uint i= 0; i < array->elements; i++) |
3066 | { |
3067 | if (ptr == *dynamic_element(array, i, void**)) |
3068 | { |
3069 | DBUG_ASSERT(!found); |
3070 | delete_dynamic_element(array, i); |
3071 | IF_DBUG_ASSERT(found= true, break); |
3072 | } |
3073 | } |
3074 | DBUG_ASSERT(found); |
3075 | } |
3076 | |
3077 | static void remove_role_user_mapping(ACL_USER_BASE *grantee, ACL_ROLE *role, |
3078 | int grantee_idx=-1, int role_idx=-1) |
3079 | { |
3080 | remove_ptr_from_dynarray(&grantee->role_grants, role); |
3081 | remove_ptr_from_dynarray(&role->parent_grantee, grantee); |
3082 | } |
3083 | |
3084 | |
3085 | static my_bool add_role_user_mapping_action(void *ptr, void *unused __attribute__((unused))) |
3086 | { |
3087 | ROLE_GRANT_PAIR *pair= (ROLE_GRANT_PAIR*)ptr; |
3088 | bool status __attribute__((unused)); |
3089 | status= add_role_user_mapping(pair->u_uname, pair->u_hname, pair->r_uname); |
3090 | /* |
3091 | The invariant chosen is that acl_roles_mappings should _always_ |
3092 | only contain valid entries, referencing correct user and role grants. |
3093 | If add_role_user_mapping detects an invalid entry, it will not add |
3094 | the mapping into the ACL_USER::role_grants array. |
3095 | */ |
3096 | DBUG_ASSERT(status == 0); |
3097 | return 0; |
3098 | } |
3099 | |
3100 | |
3101 | /* |
3102 | Rebuild the role grants every time the acl_users is modified |
3103 | |
3104 | The role grants in the ACL_USER class need to be rebuilt, as they contain |
3105 | pointers to elements of the acl_users array. |
3106 | */ |
3107 | |
3108 | static void rebuild_role_grants(void) |
3109 | { |
3110 | DBUG_ENTER("rebuild_role_grants" ); |
3111 | /* |
3112 | Reset every user's and role's role_grants array |
3113 | */ |
3114 | for (uint i=0; i < acl_users.elements; i++) { |
3115 | ACL_USER *user= dynamic_element(&acl_users, i, ACL_USER *); |
3116 | reset_dynamic(&user->role_grants); |
3117 | } |
3118 | my_hash_iterate(&acl_roles, acl_role_reset_role_arrays, NULL); |
3119 | |
3120 | /* Rebuild the direct links between users and roles in ACL_USER::role_grants */ |
3121 | my_hash_iterate(&acl_roles_mappings, add_role_user_mapping_action, NULL); |
3122 | |
3123 | DBUG_VOID_RETURN; |
3124 | } |
3125 | |
3126 | |
3127 | /* Return true if there is no users that can match the given host */ |
3128 | bool acl_check_host(const char *host, const char *ip) |
3129 | { |
3130 | if (allow_all_hosts) |
3131 | return 0; |
3132 | mysql_mutex_lock(&acl_cache->lock); |
3133 | |
3134 | if ((host && my_hash_search(&acl_check_hosts,(uchar*) host,strlen(host))) || |
3135 | (ip && my_hash_search(&acl_check_hosts,(uchar*) ip, strlen(ip)))) |
3136 | { |
3137 | mysql_mutex_unlock(&acl_cache->lock); |
3138 | return 0; // Found host |
3139 | } |
3140 | for (uint i=0 ; i < acl_wild_hosts.elements ; i++) |
3141 | { |
3142 | acl_host_and_ip *acl=dynamic_element(&acl_wild_hosts,i,acl_host_and_ip*); |
3143 | if (compare_hostname(acl, host, ip)) |
3144 | { |
3145 | mysql_mutex_unlock(&acl_cache->lock); |
3146 | return 0; // Host ok |
3147 | } |
3148 | } |
3149 | mysql_mutex_unlock(&acl_cache->lock); |
3150 | if (ip != NULL) |
3151 | { |
3152 | /* Increment HOST_CACHE.COUNT_HOST_ACL_ERRORS. */ |
3153 | Host_errors errors; |
3154 | errors.m_host_acl= 1; |
3155 | inc_host_errors(ip, &errors); |
3156 | } |
3157 | return 1; // Host is not allowed |
3158 | } |
3159 | |
3160 | /** |
3161 | Check if the user is allowed to alter the mysql.user table |
3162 | |
3163 | @param thd THD |
3164 | @param host Hostname for the user |
3165 | @param user User name |
3166 | |
3167 | @return Error status |
3168 | @retval 0 OK |
3169 | @retval 1 Error |
3170 | */ |
3171 | |
3172 | static int check_alter_user(THD *thd, const char *host, const char *user) |
3173 | { |
3174 | int error = 1; |
3175 | if (!initialized) |
3176 | { |
3177 | my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--skip-grant-tables" ); |
3178 | goto end; |
3179 | } |
3180 | |
3181 | if (IF_WSREP((!WSREP(thd) || !thd->wsrep_applier), 1) && |
3182 | !thd->slave_thread && !thd->security_ctx->priv_user[0] && |
3183 | !in_bootstrap) |
3184 | { |
3185 | my_message(ER_PASSWORD_ANONYMOUS_USER, |
3186 | ER_THD(thd, ER_PASSWORD_ANONYMOUS_USER), |
3187 | MYF(0)); |
3188 | goto end; |
3189 | } |
3190 | if (!host) // Role |
3191 | { |
3192 | my_error(ER_PASSWORD_NO_MATCH, MYF(0)); |
3193 | goto end; |
3194 | } |
3195 | |
3196 | if (!thd->slave_thread && |
3197 | IF_WSREP((!WSREP(thd) || !thd->wsrep_applier),1) && |
3198 | (strcmp(thd->security_ctx->priv_user, user) || |
3199 | my_strcasecmp(system_charset_info, host, |
3200 | thd->security_ctx->priv_host))) |
3201 | { |
3202 | if (check_access(thd, UPDATE_ACL, "mysql" , NULL, NULL, 1, 0)) |
3203 | goto end; |
3204 | } |
3205 | |
3206 | error = 0; |
3207 | |
3208 | end: |
3209 | return error; |
3210 | } |
3211 | /** |
3212 | Check if the user is allowed to change password |
3213 | |
3214 | @param thd THD |
3215 | @param user User, hostname, new password or password hash |
3216 | |
3217 | @return Error status |
3218 | @retval 0 OK |
3219 | @retval 1 ERROR; In this case the error is sent to the client. |
3220 | */ |
3221 | |
3222 | bool check_change_password(THD *thd, LEX_USER *user) |
3223 | { |
3224 | LEX_USER *real_user= get_current_user(thd, user); |
3225 | |
3226 | if (fix_and_copy_user(real_user, user, thd) || |
3227 | validate_password(real_user, thd)) |
3228 | return true; |
3229 | |
3230 | *user= *real_user; |
3231 | |
3232 | return check_alter_user(thd, user->host.str, user->user.str); |
3233 | } |
3234 | |
3235 | |
3236 | /** |
3237 | Change a password for a user. |
3238 | |
3239 | @param thd THD |
3240 | @param user User, hostname, new password hash |
3241 | |
3242 | @return Error code |
3243 | @retval 0 ok |
3244 | @retval 1 ERROR; In this case the error is sent to the client. |
3245 | */ |
3246 | bool change_password(THD *thd, LEX_USER *user) |
3247 | { |
3248 | Grant_tables tables(Table_user, TL_WRITE); |
3249 | /* Buffer should be extended when password length is extended. */ |
3250 | char buff[512]; |
3251 | ulong query_length= 0; |
3252 | enum_binlog_format save_binlog_format; |
3253 | int result=0; |
3254 | const CSET_STRING query_save __attribute__((unused)) = thd->query_string; |
3255 | DBUG_ENTER("change_password" ); |
3256 | DBUG_PRINT("enter" ,("host: '%s' user: '%s' new_password: '%s'" , |
3257 | user->host.str, user->user.str, user->pwhash.str)); |
3258 | DBUG_ASSERT(user->host.str != 0); // Ensured by parent |
3259 | |
3260 | /* |
3261 | This statement will be replicated as a statement, even when using |
3262 | row-based replication. The flag will be reset at the end of the |
3263 | statement. |
3264 | This has to be handled here as it's called by set_var.cc, which is |
3265 | not automaticly handled by sql_parse.cc |
3266 | */ |
3267 | save_binlog_format= thd->set_current_stmt_binlog_format_stmt(); |
3268 | |
3269 | if (mysql_bin_log.is_open() || |
3270 | (WSREP(thd) && !IF_WSREP(thd->wsrep_applier, 0))) |
3271 | { |
3272 | query_length= sprintf(buff, "SET PASSWORD FOR '%-.120s'@'%-.120s'='%-.120s'" , |
3273 | safe_str(user->user.str), safe_str(user->host.str), |
3274 | safe_str(user->pwhash.str)); |
3275 | } |
3276 | |
3277 | if (WSREP(thd) && !IF_WSREP(thd->wsrep_applier, 0)) |
3278 | { |
3279 | thd->set_query(buff, query_length, system_charset_info); |
3280 | WSREP_TO_ISOLATION_BEGIN(WSREP_MYSQL_DB, (char*)"user" , NULL); |
3281 | } |
3282 | |
3283 | if ((result= tables.open_and_lock(thd))) |
3284 | DBUG_RETURN(result != 1); |
3285 | |
3286 | result= 1; |
3287 | |
3288 | mysql_mutex_lock(&acl_cache->lock); |
3289 | ACL_USER *acl_user; |
3290 | if (!(acl_user= find_user_exact(user->host.str, user->user.str))) |
3291 | { |
3292 | mysql_mutex_unlock(&acl_cache->lock); |
3293 | my_message(ER_PASSWORD_NO_MATCH, |
3294 | ER_THD(thd, ER_PASSWORD_NO_MATCH), MYF(0)); |
3295 | goto end; |
3296 | } |
3297 | |
3298 | /* update loaded acl entry: */ |
3299 | if (acl_user->plugin.str == native_password_plugin_name.str || |
3300 | acl_user->plugin.str == old_password_plugin_name.str) |
3301 | { |
3302 | acl_user->auth_string.str= strmake_root(&acl_memroot, user->pwhash.str, user->pwhash.length); |
3303 | acl_user->auth_string.length= user->pwhash.length; |
3304 | set_user_salt(acl_user, user->pwhash.str, user->pwhash.length); |
3305 | |
3306 | set_user_plugin(acl_user, user->pwhash.length); |
3307 | } |
3308 | else |
3309 | push_warning(thd, Sql_condition::WARN_LEVEL_NOTE, |
3310 | ER_SET_PASSWORD_AUTH_PLUGIN, |
3311 | ER_THD(thd, ER_SET_PASSWORD_AUTH_PLUGIN)); |
3312 | |
3313 | if (update_user_table(thd, tables.user_table(), |
3314 | safe_str(acl_user->host.hostname), |
3315 | safe_str(acl_user->user.str), |
3316 | user->pwhash.str, user->pwhash.length)) |
3317 | { |
3318 | mysql_mutex_unlock(&acl_cache->lock); /* purecov: deadcode */ |
3319 | goto end; |
3320 | } |
3321 | |
3322 | acl_cache->clear(1); // Clear locked hostname cache |
3323 | mysql_mutex_unlock(&acl_cache->lock); |
3324 | result= 0; |
3325 | if (mysql_bin_log.is_open()) |
3326 | { |
3327 | DBUG_ASSERT(query_length); |
3328 | thd->clear_error(); |
3329 | result= thd->binlog_query(THD::STMT_QUERY_TYPE, buff, query_length, |
3330 | FALSE, FALSE, FALSE, 0); |
3331 | } |
3332 | end: |
3333 | close_mysql_tables(thd); |
3334 | |
3335 | #ifdef WITH_WSREP |
3336 | error: // this label is used in WSREP_TO_ISOLATION_BEGIN |
3337 | if (WSREP(thd) && !thd->wsrep_applier) |
3338 | { |
3339 | WSREP_TO_ISOLATION_END; |
3340 | |
3341 | thd->set_query(query_save); |
3342 | thd->wsrep_exec_mode = LOCAL_STATE; |
3343 | } |
3344 | #endif /* WITH_WSREP */ |
3345 | thd->restore_stmt_binlog_format(save_binlog_format); |
3346 | |
3347 | DBUG_RETURN(result); |
3348 | } |
3349 | |
3350 | int acl_check_set_default_role(THD *thd, const char *host, const char *user) |
3351 | { |
3352 | return check_alter_user(thd, host, user); |
3353 | } |
3354 | |
3355 | int acl_set_default_role(THD *thd, const char *host, const char *user, |
3356 | const char *rolename) |
3357 | { |
3358 | Grant_tables tables(Table_user, TL_WRITE); |
3359 | char user_key[MAX_KEY_LENGTH]; |
3360 | int result= 1; |
3361 | int error; |
3362 | ulong query_length= 0; |
3363 | bool clear_role= FALSE; |
3364 | char buff[512]; |
3365 | enum_binlog_format save_binlog_format; |
3366 | const CSET_STRING query_save __attribute__((unused)) = thd->query_string; |
3367 | |
3368 | DBUG_ENTER("acl_set_default_role" ); |
3369 | DBUG_PRINT("enter" ,("host: '%s' user: '%s' rolename: '%s'" , |
3370 | safe_str(user), safe_str(host), safe_str(rolename))); |
3371 | |
3372 | if (rolename == current_role.str) { |
3373 | if (!thd->security_ctx->priv_role[0]) |
3374 | rolename= "NONE" ; |
3375 | else |
3376 | rolename= thd->security_ctx->priv_role; |
3377 | } |
3378 | |
3379 | if (check_user_can_set_role(user, host, host, rolename, NULL)) |
3380 | DBUG_RETURN(result); |
3381 | |
3382 | if (!strcasecmp(rolename, "NONE" )) |
3383 | clear_role= TRUE; |
3384 | |
3385 | if (mysql_bin_log.is_open() || |
3386 | (WSREP(thd) && !IF_WSREP(thd->wsrep_applier, 0))) |
3387 | { |
3388 | query_length= |
3389 | sprintf(buff,"SET DEFAULT ROLE '%-.120s' FOR '%-.120s'@'%-.120s'" , |
3390 | safe_str(rolename), safe_str(user), safe_str(host)); |
3391 | } |
3392 | |
3393 | /* |
3394 | This statement will be replicated as a statement, even when using |
3395 | row-based replication. The flag will be reset at the end of the |
3396 | statement. |
3397 | This has to be handled here as it's called by set_var.cc, which is |
3398 | not automaticly handled by sql_parse.cc |
3399 | */ |
3400 | save_binlog_format= thd->set_current_stmt_binlog_format_stmt(); |
3401 | |
3402 | if (WSREP(thd) && !IF_WSREP(thd->wsrep_applier, 0)) |
3403 | { |
3404 | thd->set_query(buff, query_length, system_charset_info); |
3405 | WSREP_TO_ISOLATION_BEGIN(WSREP_MYSQL_DB, (char*)"user" , NULL); |
3406 | } |
3407 | |
3408 | /* |
3409 | Extra block due to WSREP_TO_ISOLATION_BEGIN using goto. |
3410 | TODO(cvicentiu) Should move this block out in a new function. |
3411 | */ |
3412 | { |
3413 | if ((result= tables.open_and_lock(thd))) |
3414 | DBUG_RETURN(result != 1); |
3415 | |
3416 | const User_table& user_table= tables.user_table(); |
3417 | TABLE *table= user_table.table(); |
3418 | |
3419 | result= 1; |
3420 | |
3421 | mysql_mutex_lock(&acl_cache->lock); |
3422 | ACL_USER *acl_user; |
3423 | if (!(acl_user= find_user_exact(host, user))) |
3424 | { |
3425 | mysql_mutex_unlock(&acl_cache->lock); |
3426 | my_message(ER_PASSWORD_NO_MATCH, ER_THD(thd, ER_PASSWORD_NO_MATCH), |
3427 | MYF(0)); |
3428 | goto end; |
3429 | } |
3430 | |
3431 | if (!clear_role) |
3432 | { |
3433 | /* set new default_rolename */ |
3434 | acl_user->default_rolename.str= safe_strdup_root(&acl_memroot, rolename); |
3435 | acl_user->default_rolename.length= strlen(rolename); |
3436 | } |
3437 | else |
3438 | { |
3439 | /* clear the default_rolename */ |
3440 | acl_user->default_rolename.str = NULL; |
3441 | acl_user->default_rolename.length = 0; |
3442 | } |
3443 | |
3444 | /* update the mysql.user table with the new default role */ |
3445 | tables.user_table().table()->use_all_columns(); |
3446 | if (!tables.user_table().default_role()) |
3447 | { |
3448 | my_error(ER_COL_COUNT_DOESNT_MATCH_PLEASE_UPDATE, MYF(0), |
3449 | table->alias.c_ptr(), DEFAULT_ROLE_COLUMN_IDX + 1, |
3450 | tables.user_table().num_fields(), |
3451 | static_cast<int>(table->s->mysql_version), MYSQL_VERSION_ID); |
3452 | mysql_mutex_unlock(&acl_cache->lock); |
3453 | goto end; |
3454 | } |
3455 | user_table.host()->store(host,(uint) strlen(host), system_charset_info); |
3456 | user_table.user()->store(user,(uint) strlen(user), system_charset_info); |
3457 | key_copy((uchar *) user_key, table->record[0], table->key_info, |
3458 | table->key_info->key_length); |
3459 | |
3460 | if (table->file->ha_index_read_idx_map(table->record[0], 0, |
3461 | (uchar *) user_key, HA_WHOLE_KEY, |
3462 | HA_READ_KEY_EXACT)) |
3463 | { |
3464 | mysql_mutex_unlock(&acl_cache->lock); |
3465 | my_message(ER_PASSWORD_NO_MATCH, ER_THD(thd, ER_PASSWORD_NO_MATCH), |
3466 | MYF(0)); |
3467 | goto end; |
3468 | } |
3469 | store_record(table, record[1]); |
3470 | user_table.default_role()->store(acl_user->default_rolename.str, |
3471 | acl_user->default_rolename.length, |
3472 | system_charset_info); |
3473 | if (unlikely(error= table->file->ha_update_row(table->record[1], |
3474 | table->record[0])) && |
3475 | error != HA_ERR_RECORD_IS_THE_SAME) |
3476 | { |
3477 | mysql_mutex_unlock(&acl_cache->lock); |
3478 | table->file->print_error(error,MYF(0)); /* purecov: deadcode */ |
3479 | goto end; |
3480 | } |
3481 | |
3482 | acl_cache->clear(1); |
3483 | mysql_mutex_unlock(&acl_cache->lock); |
3484 | result= 0; |
3485 | if (mysql_bin_log.is_open()) |
3486 | { |
3487 | DBUG_ASSERT(query_length); |
3488 | thd->clear_error(); |
3489 | result= thd->binlog_query(THD::STMT_QUERY_TYPE, buff, query_length, |
3490 | FALSE, FALSE, FALSE, 0); |
3491 | } |
3492 | end: |
3493 | close_mysql_tables(thd); |
3494 | } |
3495 | |
3496 | #ifdef WITH_WSREP |
3497 | error: // this label is used in WSREP_TO_ISOLATION_END |
3498 | if (WSREP(thd) && !thd->wsrep_applier) |
3499 | { |
3500 | WSREP_TO_ISOLATION_END; |
3501 | |
3502 | thd->set_query(query_save); |
3503 | thd->wsrep_exec_mode = LOCAL_STATE; |
3504 | } |
3505 | #endif /* WITH_WSREP */ |
3506 | |
3507 | thd->restore_stmt_binlog_format(save_binlog_format); |
3508 | |
3509 | DBUG_RETURN(result); |
3510 | } |
3511 | |
3512 | |
3513 | /* |
3514 | Find user in ACL |
3515 | |
3516 | SYNOPSIS |
3517 | is_acl_user() |
3518 | host host name |
3519 | user user name |
3520 | |
3521 | RETURN |
3522 | FALSE user not fond |
3523 | TRUE there is such user |
3524 | */ |
3525 | |
3526 | bool is_acl_user(const char *host, const char *user) |
3527 | { |
3528 | bool res; |
3529 | |
3530 | /* --skip-grants */ |
3531 | if (!initialized) |
3532 | return TRUE; |
3533 | |
3534 | mysql_mutex_lock(&acl_cache->lock); |
3535 | |
3536 | if (*host) // User |
3537 | res= find_user_exact(host, user) != NULL; |
3538 | else // Role |
3539 | res= find_acl_role(user) != NULL; |
3540 | |
3541 | mysql_mutex_unlock(&acl_cache->lock); |
3542 | return res; |
3543 | } |
3544 | |
3545 | |
3546 | /* |
3547 | unlike find_user_exact and find_user_wild, |
3548 | this function finds anonymous users too, it's when a |
3549 | user is not empty, but priv_user (acl_user->user) is empty. |
3550 | */ |
3551 | static ACL_USER *find_user_or_anon(const char *host, const char *user, const char *ip) |
3552 | { |
3553 | ACL_USER *result= NULL; |
3554 | mysql_mutex_assert_owner(&acl_cache->lock); |
3555 | for (uint i=0; i < acl_users.elements; i++) |
3556 | { |
3557 | ACL_USER *acl_user_tmp= dynamic_element(&acl_users, i, ACL_USER*); |
3558 | if ((!acl_user_tmp->user.str || |
3559 | !strcmp(user, acl_user_tmp->user.str)) && |
3560 | compare_hostname(&acl_user_tmp->host, host, ip)) |
3561 | { |
3562 | result= acl_user_tmp; |
3563 | break; |
3564 | } |
3565 | } |
3566 | return result; |
3567 | } |
3568 | |
3569 | |
3570 | /* |
3571 | Find first entry that matches the specified user@host pair |
3572 | */ |
3573 | static ACL_USER * find_user_exact(const char *host, const char *user) |
3574 | { |
3575 | mysql_mutex_assert_owner(&acl_cache->lock); |
3576 | |
3577 | for (uint i=0 ; i < acl_users.elements ; i++) |
3578 | { |
3579 | ACL_USER *acl_user=dynamic_element(&acl_users,i,ACL_USER*); |
3580 | if (acl_user->eq(user, host)) |
3581 | return acl_user; |
3582 | } |
3583 | return 0; |
3584 | } |
3585 | |
3586 | /* |
3587 | Find first entry that matches the specified user@host pair |
3588 | */ |
3589 | static ACL_USER * find_user_wild(const char *host, const char *user, const char *ip) |
3590 | { |
3591 | mysql_mutex_assert_owner(&acl_cache->lock); |
3592 | |
3593 | for (uint i=0 ; i < acl_users.elements ; i++) |
3594 | { |
3595 | ACL_USER *acl_user=dynamic_element(&acl_users,i,ACL_USER*); |
3596 | if (acl_user->wild_eq(user, host, ip)) |
3597 | return acl_user; |
3598 | } |
3599 | return 0; |
3600 | } |
3601 | |
3602 | /* |
3603 | Find a role with the specified name |
3604 | */ |
3605 | static ACL_ROLE *find_acl_role(const char *role) |
3606 | { |
3607 | DBUG_ENTER("find_acl_role" ); |
3608 | DBUG_PRINT("enter" ,("role: '%s'" , role)); |
3609 | DBUG_PRINT("info" , ("Hash elements: %ld" , acl_roles.records)); |
3610 | |
3611 | mysql_mutex_assert_owner(&acl_cache->lock); |
3612 | |
3613 | ACL_ROLE *r= (ACL_ROLE *)my_hash_search(&acl_roles, (uchar *)role, |
3614 | safe_strlen(role)); |
3615 | DBUG_RETURN(r); |
3616 | } |
3617 | |
3618 | |
3619 | static ACL_USER_BASE *find_acl_user_base(const char *user, const char *host) |
3620 | { |
3621 | if (*host) |
3622 | return find_user_exact(host, user); |
3623 | |
3624 | return find_acl_role(user); |
3625 | } |
3626 | |
3627 | |
3628 | /* |
3629 | Comparing of hostnames |
3630 | |
3631 | NOTES |
3632 | A hostname may be of type: |
3633 | hostname (May include wildcards); monty.pp.sci.fi |
3634 | ip (May include wildcards); 192.168.0.0 |
3635 | ip/netmask 192.168.0.0/255.255.255.0 |
3636 | |
3637 | A net mask of 0.0.0.0 is not allowed. |
3638 | */ |
3639 | |
3640 | static const char *calc_ip(const char *ip, long *val, char end) |
3641 | { |
3642 | long ip_val,tmp; |
3643 | if (!(ip=str2int(ip,10,0,255,&ip_val)) || *ip != '.') |
3644 | return 0; |
3645 | ip_val<<=24; |
3646 | if (!(ip=str2int(ip+1,10,0,255,&tmp)) || *ip != '.') |
3647 | return 0; |
3648 | ip_val+=tmp<<16; |
3649 | if (!(ip=str2int(ip+1,10,0,255,&tmp)) || *ip != '.') |
3650 | return 0; |
3651 | ip_val+=tmp<<8; |
3652 | if (!(ip=str2int(ip+1,10,0,255,&tmp)) || *ip != end) |
3653 | return 0; |
3654 | *val=ip_val+tmp; |
3655 | return ip; |
3656 | } |
3657 | |
3658 | |
3659 | static void update_hostname(acl_host_and_ip *host, const char *hostname) |
3660 | { |
3661 | // fix historical undocumented convention that empty host is the same as '%' |
3662 | hostname=const_cast<char*>(hostname ? hostname : host_not_specified.str); |
3663 | host->hostname=(char*) hostname; // This will not be modified! |
3664 | if (!(hostname= calc_ip(hostname,&host->ip,'/')) || |
3665 | !(hostname= calc_ip(hostname+1,&host->ip_mask,'\0'))) |
3666 | { |
3667 | host->ip= host->ip_mask=0; // Not a masked ip |
3668 | } |
3669 | } |
3670 | |
3671 | |
3672 | static bool compare_hostname(const acl_host_and_ip *host, const char *hostname, |
3673 | const char *ip) |
3674 | { |
3675 | long tmp; |
3676 | if (host->ip_mask && ip && calc_ip(ip,&tmp,'\0')) |
3677 | { |
3678 | return (tmp & host->ip_mask) == host->ip; |
3679 | } |
3680 | return (!host->hostname || |
3681 | (hostname && !wild_case_compare(system_charset_info, |
3682 | hostname, host->hostname)) || |
3683 | (ip && !wild_compare(ip, host->hostname, 0))); |
3684 | } |
3685 | |
3686 | /** |
3687 | Check if the given host name needs to be resolved or not. |
3688 | Host name has to be resolved if it actually contains *name*. |
3689 | |
3690 | For example: |
3691 | 192.168.1.1 --> FALSE |
3692 | 192.168.1.0/255.255.255.0 --> FALSE |
3693 | % --> FALSE |
3694 | 192.168.1.% --> FALSE |
3695 | AB% --> FALSE |
3696 | |
3697 | AAAAFFFF --> TRUE (Hostname) |
3698 | AAAA:FFFF:1234:5678 --> FALSE |
3699 | ::1 --> FALSE |
3700 | |
3701 | This function does not check if the given string is a valid host name or |
3702 | not. It assumes that the argument is a valid host name. |
3703 | |
3704 | @param hostname the string to check. |
3705 | |
3706 | @return a flag telling if the argument needs to be resolved or not. |
3707 | @retval TRUE the argument is a host name and needs to be resolved. |
3708 | @retval FALSE the argument is either an IP address, or a patter and |
3709 | should not be resolved. |
3710 | */ |
3711 | |
3712 | bool hostname_requires_resolving(const char *hostname) |
3713 | { |
3714 | if (!hostname) |
3715 | return FALSE; |
3716 | |
3717 | /* Check if hostname is the localhost. */ |
3718 | |
3719 | size_t hostname_len= strlen(hostname); |
3720 | size_t localhost_len= strlen(my_localhost); |
3721 | |
3722 | if (hostname == my_localhost || |
3723 | (hostname_len == localhost_len && |
3724 | !my_strnncoll(system_charset_info, |
3725 | (const uchar *) hostname, hostname_len, |
3726 | (const uchar *) my_localhost, strlen(my_localhost)))) |
3727 | { |
3728 | return FALSE; |
3729 | } |
3730 | |
3731 | /* |
3732 | If the string contains any of {':', '%', '_', '/'}, it is definitely |
3733 | not a host name: |
3734 | - ':' means that the string is an IPv6 address; |
3735 | - '%' or '_' means that the string is a pattern; |
3736 | - '/' means that the string is an IPv4 network address; |
3737 | */ |
3738 | |
3739 | for (const char *p= hostname; *p; ++p) |
3740 | { |
3741 | switch (*p) { |
3742 | case ':': |
3743 | case '%': |
3744 | case '_': |
3745 | case '/': |
3746 | return FALSE; |
3747 | } |
3748 | } |
3749 | |
3750 | /* |
3751 | Now we have to tell a host name (ab.cd, 12.ab) from an IPv4 address |
3752 | (12.34.56.78). The assumption is that if the string contains only |
3753 | digits and dots, it is an IPv4 address. Otherwise -- a host name. |
3754 | */ |
3755 | |
3756 | for (const char *p= hostname; *p; ++p) |
3757 | { |
3758 | if (*p != '.' && !my_isdigit(&my_charset_latin1, *p)) |
3759 | return TRUE; /* a "letter" has been found. */ |
3760 | } |
3761 | |
3762 | return FALSE; /* all characters are either dots or digits. */ |
3763 | } |
3764 | |
3765 | |
3766 | void set_authentication_plugin_from_password(const User_table& user_table, |
3767 | const char* password, size_t password_length) |
3768 | { |
3769 | if (password_length == SCRAMBLED_PASSWORD_CHAR_LENGTH) |
3770 | { |
3771 | user_table.plugin()->store(native_password_plugin_name.str, |
3772 | native_password_plugin_name.length, |
3773 | system_charset_info); |
3774 | } |
3775 | else |
3776 | { |
3777 | DBUG_ASSERT(password_length == SCRAMBLED_PASSWORD_CHAR_LENGTH_323); |
3778 | user_table.plugin()->store(old_password_plugin_name.str, |
3779 | old_password_plugin_name.length, |
3780 | system_charset_info); |
3781 | } |
3782 | user_table.authentication_string()->store(password, |
3783 | password_length, |
3784 | system_charset_info); |
3785 | } |
3786 | /** |
3787 | Update record for user in mysql.user privilege table with new password. |
3788 | |
3789 | @param thd THD |
3790 | @param table Pointer to TABLE object for open mysql.user table |
3791 | @param host Hostname |
3792 | @param user Username |
3793 | @param new_password New password hash |
3794 | @param new_password_len Length of new password hash |
3795 | |
3796 | @see change_password |
3797 | */ |
3798 | |
3799 | static bool update_user_table(THD *thd, const User_table& user_table, |
3800 | const char *host, const char *user, |
3801 | const char *new_password, size_t new_password_len) |
3802 | { |
3803 | char user_key[MAX_KEY_LENGTH]; |
3804 | int error; |
3805 | DBUG_ENTER("update_user_table" ); |
3806 | DBUG_PRINT("enter" ,("user: %s host: %s" ,user,host)); |
3807 | |
3808 | TABLE *table= user_table.table(); |
3809 | table->use_all_columns(); |
3810 | user_table.host()->store(host,(uint) strlen(host), system_charset_info); |
3811 | user_table.user()->store(user,(uint) strlen(user), system_charset_info); |
3812 | key_copy((uchar *) user_key, table->record[0], table->key_info, |
3813 | table->key_info->key_length); |
3814 | |
3815 | if (table->file->ha_index_read_idx_map(table->record[0], 0, |
3816 | (uchar *) user_key, HA_WHOLE_KEY, |
3817 | HA_READ_KEY_EXACT)) |
3818 | { |
3819 | my_message(ER_PASSWORD_NO_MATCH, ER_THD(thd, ER_PASSWORD_NO_MATCH), |
3820 | MYF(0)); /* purecov: deadcode */ |
3821 | DBUG_RETURN(1); /* purecov: deadcode */ |
3822 | } |
3823 | store_record(table,record[1]); |
3824 | /* If the password column is missing, we use the |
3825 | authentication_string column. */ |
3826 | if (user_table.password()) |
3827 | user_table.password()->store(new_password, new_password_len, system_charset_info); |
3828 | else |
3829 | set_authentication_plugin_from_password(user_table, new_password, |
3830 | new_password_len); |
3831 | |
3832 | |
3833 | if (unlikely(error= table->file->ha_update_row(table->record[1], |
3834 | table->record[0])) && |
3835 | error != HA_ERR_RECORD_IS_THE_SAME) |
3836 | { |
3837 | table->file->print_error(error,MYF(0)); /* purecov: deadcode */ |
3838 | DBUG_RETURN(1); |
3839 | } |
3840 | DBUG_RETURN(0); |
3841 | } |
3842 | |
3843 | |
3844 | /* |
3845 | Return 1 if we are allowed to create new users |
3846 | the logic here is: INSERT_ACL is sufficient. |
3847 | It's also a requirement in opt_safe_user_create, |
3848 | otherwise CREATE_USER_ACL is enough. |
3849 | */ |
3850 | |
3851 | static bool test_if_create_new_users(THD *thd) |
3852 | { |
3853 | Security_context *sctx= thd->security_ctx; |
3854 | bool create_new_users= MY_TEST(sctx->master_access & INSERT_ACL) || |
3855 | (!opt_safe_user_create && |
3856 | MY_TEST(sctx->master_access & CREATE_USER_ACL)); |
3857 | if (!create_new_users) |
3858 | { |
3859 | TABLE_LIST tl; |
3860 | ulong db_access; |
3861 | tl.init_one_table(&MYSQL_SCHEMA_NAME, &MYSQL_USER_NAME, NULL, TL_WRITE); |
3862 | create_new_users= 1; |
3863 | |
3864 | db_access=acl_get(sctx->host, sctx->ip, |
3865 | sctx->priv_user, tl.db.str, 0); |
3866 | if (sctx->priv_role[0]) |
3867 | db_access|= acl_get("" , "" , sctx->priv_role, tl.db.str, 0); |
3868 | if (!(db_access & INSERT_ACL)) |
3869 | { |
3870 | if (check_grant(thd, INSERT_ACL, &tl, FALSE, UINT_MAX, TRUE)) |
3871 | create_new_users=0; |
3872 | } |
3873 | } |
3874 | return create_new_users; |
3875 | } |
3876 | |
3877 | |
3878 | /**************************************************************************** |
3879 | Handle GRANT commands |
3880 | ****************************************************************************/ |
3881 | |
3882 | static int replace_user_table(THD *thd, const User_table &user_table, |
3883 | LEX_USER &combo, |
3884 | ulong rights, bool revoke_grant, |
3885 | bool can_create_user, bool no_auto_create) |
3886 | { |
3887 | int error = -1; |
3888 | bool old_row_exists=0; |
3889 | char what= (revoke_grant) ? 'N' : 'Y'; |
3890 | uchar user_key[MAX_KEY_LENGTH]; |
3891 | bool handle_as_role= combo.is_role(); |
3892 | LEX *lex= thd->lex; |
3893 | TABLE *table= user_table.table(); |
3894 | DBUG_ENTER("replace_user_table" ); |
3895 | |
3896 | mysql_mutex_assert_owner(&acl_cache->lock); |
3897 | |
3898 | if (combo.pwhash.str && combo.pwhash.str[0]) |
3899 | { |
3900 | if (combo.pwhash.length != SCRAMBLED_PASSWORD_CHAR_LENGTH && |
3901 | combo.pwhash.length != SCRAMBLED_PASSWORD_CHAR_LENGTH_323) |
3902 | { |
3903 | DBUG_ASSERT(0); |
3904 | my_error(ER_PASSWD_LENGTH, MYF(0), SCRAMBLED_PASSWORD_CHAR_LENGTH); |
3905 | DBUG_RETURN(-1); |
3906 | } |
3907 | } |
3908 | else |
3909 | combo.pwhash= empty_clex_str; |
3910 | |
3911 | /* if the user table is not up to date, we can't handle role updates */ |
3912 | if (!user_table.is_role() && handle_as_role) |
3913 | { |
3914 | my_error(ER_COL_COUNT_DOESNT_MATCH_PLEASE_UPDATE, MYF(0), |
3915 | "user" , ROLE_ASSIGN_COLUMN_IDX + 1, user_table.num_fields(), |
3916 | static_cast<int>(table->s->mysql_version), MYSQL_VERSION_ID); |
3917 | DBUG_RETURN(-1); |
3918 | } |
3919 | |
3920 | table->use_all_columns(); |
3921 | user_table.host()->store(combo.host.str,combo.host.length, |
3922 | system_charset_info); |
3923 | user_table.user()->store(combo.user.str,combo.user.length, |
3924 | system_charset_info); |
3925 | key_copy(user_key, table->record[0], table->key_info, |
3926 | table->key_info->key_length); |
3927 | |
3928 | if (table->file->ha_index_read_idx_map(table->record[0], 0, user_key, |
3929 | HA_WHOLE_KEY, |
3930 | HA_READ_KEY_EXACT)) |
3931 | { |
3932 | /* what == 'N' means revoke */ |
3933 | if (what == 'N') |
3934 | { |
3935 | my_error(ER_NONEXISTING_GRANT, MYF(0), combo.user.str, combo.host.str); |
3936 | goto end; |
3937 | } |
3938 | /* |
3939 | There are four options which affect the process of creation of |
3940 | a new user (mysqld option --safe-create-user, 'insert' privilege |
3941 | on 'mysql.user' table, using 'GRANT' with 'IDENTIFIED BY' and |
3942 | SQL_MODE flag NO_AUTO_CREATE_USER). Below is the simplified rule |
3943 | how it should work. |
3944 | if (safe-user-create && ! INSERT_priv) => reject |
3945 | else if (identified_by) => create |
3946 | else if (no_auto_create_user) => reject |
3947 | else create |
3948 | |
3949 | see also test_if_create_new_users() |
3950 | */ |
3951 | else if (!combo.pwhash.length && !combo.plugin.length && no_auto_create) |
3952 | { |
3953 | my_error(ER_PASSWORD_NO_MATCH, MYF(0)); |
3954 | goto end; |
3955 | } |
3956 | else if (!can_create_user) |
3957 | { |
3958 | my_error(ER_CANT_CREATE_USER_WITH_GRANT, MYF(0)); |
3959 | goto end; |
3960 | } |
3961 | else if (combo.plugin.str[0]) |
3962 | { |
3963 | if (!plugin_is_ready(&combo.plugin, MYSQL_AUTHENTICATION_PLUGIN)) |
3964 | { |
3965 | my_error(ER_PLUGIN_IS_NOT_LOADED, MYF(0), combo.plugin.str); |
3966 | goto end; |
3967 | } |
3968 | } |
3969 | |
3970 | old_row_exists = 0; |
3971 | restore_record(table,s->default_values); |
3972 | user_table.host()->store(combo.host.str,combo.host.length, |
3973 | system_charset_info); |
3974 | user_table.user()->store(combo.user.str,combo.user.length, |
3975 | system_charset_info); |
3976 | } |
3977 | else |
3978 | { |
3979 | old_row_exists = 1; |
3980 | store_record(table,record[1]); // Save copy for update |
3981 | } |
3982 | |
3983 | if (!old_row_exists || combo.pwtext.length || combo.pwhash.length) |
3984 | if (!handle_as_role && validate_password(&combo, thd)) |
3985 | goto end; |
3986 | |
3987 | /* Update table columns with new privileges */ |
3988 | |
3989 | ulong priv; |
3990 | priv = SELECT_ACL; |
3991 | for (uint i= 0; i < user_table.num_privileges(); i++, priv <<= 1) |
3992 | { |
3993 | if (priv & rights) |
3994 | user_table.priv_field(i)->store(&what, 1, &my_charset_latin1); |
3995 | } |
3996 | |
3997 | rights= user_table.get_access(); |
3998 | |
3999 | DBUG_PRINT("info" ,("table fields: %d" , user_table.num_fields())); |
4000 | /* If we don't have a password column, we'll use the authentication_string |
4001 | column later. */ |
4002 | if (combo.pwhash.str[0] && user_table.password()) |
4003 | user_table.password()->store(combo.pwhash.str, combo.pwhash.length, |
4004 | system_charset_info); |
4005 | /* We either have the password column, the plugin column, or both. Otherwise |
4006 | we have a corrupt user table. */ |
4007 | DBUG_ASSERT(user_table.password() || user_table.plugin()); |
4008 | if (user_table.ssl_type()) /* From 4.0.0 we have more fields */ |
4009 | { |
4010 | /* We write down SSL related ACL stuff */ |
4011 | switch (lex->ssl_type) { |
4012 | case SSL_TYPE_ANY: |
4013 | user_table.ssl_type()->store(STRING_WITH_LEN("ANY" ), |
4014 | &my_charset_latin1); |
4015 | user_table.ssl_cipher()->store("" , 0, &my_charset_latin1); |
4016 | user_table.x509_issuer()->store("" , 0, &my_charset_latin1); |
4017 | user_table.x509_subject()->store("" , 0, &my_charset_latin1); |
4018 | break; |
4019 | case SSL_TYPE_X509: |
4020 | user_table.ssl_type()->store(STRING_WITH_LEN("X509" ), |
4021 | &my_charset_latin1); |
4022 | user_table.ssl_cipher()->store("" , 0, &my_charset_latin1); |
4023 | user_table.x509_issuer()->store("" , 0, &my_charset_latin1); |
4024 | user_table.x509_subject()->store("" , 0, &my_charset_latin1); |
4025 | break; |
4026 | case SSL_TYPE_SPECIFIED: |
4027 | user_table.ssl_type()->store(STRING_WITH_LEN("SPECIFIED" ), |
4028 | &my_charset_latin1); |
4029 | user_table.ssl_cipher()->store("" , 0, &my_charset_latin1); |
4030 | user_table.x509_issuer()->store("" , 0, &my_charset_latin1); |
4031 | user_table.x509_subject()->store("" , 0, &my_charset_latin1); |
4032 | if (lex->ssl_cipher) |
4033 | user_table.ssl_cipher()->store(lex->ssl_cipher, |
4034 | strlen(lex->ssl_cipher), |
4035 | system_charset_info); |
4036 | if (lex->x509_issuer) |
4037 | user_table.x509_issuer()->store(lex->x509_issuer, |
4038 | strlen(lex->x509_issuer), |
4039 | system_charset_info); |
4040 | if (lex->x509_subject) |
4041 | user_table.x509_subject()->store(lex->x509_subject, |
4042 | strlen(lex->x509_subject), |
4043 | system_charset_info); |
4044 | break; |
4045 | case SSL_TYPE_NOT_SPECIFIED: |
4046 | break; |
4047 | case SSL_TYPE_NONE: |
4048 | user_table.ssl_type()->store("" , 0, &my_charset_latin1); |
4049 | user_table.ssl_cipher()->store("" , 0, &my_charset_latin1); |
4050 | user_table.x509_issuer()->store("" , 0, &my_charset_latin1); |
4051 | user_table.x509_subject()->store("" , 0, &my_charset_latin1); |
4052 | break; |
4053 | } |
4054 | |
4055 | USER_RESOURCES mqh= lex->mqh; |
4056 | if (mqh.specified_limits & USER_RESOURCES::QUERIES_PER_HOUR) |
4057 | user_table.max_questions()->store((longlong) mqh.questions, TRUE); |
4058 | if (mqh.specified_limits & USER_RESOURCES::UPDATES_PER_HOUR) |
4059 | user_table.max_updates()->store((longlong) mqh.updates, TRUE); |
4060 | if (mqh.specified_limits & USER_RESOURCES::CONNECTIONS_PER_HOUR) |
4061 | user_table.max_connections()->store((longlong) mqh.conn_per_hour, TRUE); |
4062 | if (user_table.max_user_connections() && |
4063 | (mqh.specified_limits & USER_RESOURCES::USER_CONNECTIONS)) |
4064 | user_table.max_user_connections()->store((longlong) mqh.user_conn, FALSE); |
4065 | if (user_table.plugin()) |
4066 | { |
4067 | user_table.plugin()->set_notnull(); |
4068 | user_table.authentication_string()->set_notnull(); |
4069 | if (combo.plugin.str[0]) |
4070 | { |
4071 | DBUG_ASSERT(combo.pwhash.str[0] == 0); |
4072 | if (user_table.password()) |
4073 | user_table.password()->reset(); |
4074 | user_table.plugin()->store(combo.plugin.str, combo.plugin.length, |
4075 | system_charset_info); |
4076 | user_table.authentication_string()->store(combo.auth.str, combo.auth.length, |
4077 | system_charset_info); |
4078 | } |
4079 | if (combo.pwhash.str[0]) |
4080 | { |
4081 | DBUG_ASSERT(combo.plugin.str[0] == 0); |
4082 | /* We have Password column. */ |
4083 | if (user_table.password()) |
4084 | { |
4085 | user_table.plugin()->reset(); |
4086 | user_table.authentication_string()->reset(); |
4087 | } |
4088 | else |
4089 | { |
4090 | /* We do not have Password column. Use PLUGIN && Authentication_string |
4091 | columns instead. */ |
4092 | set_authentication_plugin_from_password(user_table, |
4093 | combo.pwhash.str, |
4094 | combo.pwhash.length); |
4095 | } |
4096 | } |
4097 | |
4098 | if (user_table.max_statement_time()) |
4099 | { |
4100 | if (mqh.specified_limits & USER_RESOURCES::MAX_STATEMENT_TIME) |
4101 | user_table.max_statement_time()->store(mqh.max_statement_time); |
4102 | } |
4103 | } |
4104 | mqh_used= (mqh_used || mqh.questions || mqh.updates || mqh.conn_per_hour || |
4105 | mqh.user_conn || mqh.max_statement_time != 0.0); |
4106 | |
4107 | /* table format checked earlier */ |
4108 | if (handle_as_role) |
4109 | { |
4110 | if (old_row_exists && !user_table.check_is_role()) |
4111 | { |
4112 | goto end; |
4113 | } |
4114 | user_table.is_role()->store("Y" , 1, system_charset_info); |
4115 | } |
4116 | } |
4117 | |
4118 | if (old_row_exists) |
4119 | { |
4120 | /* |
4121 | We should NEVER delete from the user table, as a uses can still |
4122 | use mysqld even if he doesn't have any privileges in the user table! |
4123 | */ |
4124 | if (cmp_record(table, record[1])) |
4125 | { |
4126 | if (unlikely(error= table->file->ha_update_row(table->record[1], |
4127 | table->record[0])) && |
4128 | error != HA_ERR_RECORD_IS_THE_SAME) |
4129 | { // This should never happen |
4130 | table->file->print_error(error,MYF(0)); /* purecov: deadcode */ |
4131 | error= -1; /* purecov: deadcode */ |
4132 | goto end; /* purecov: deadcode */ |
4133 | } |
4134 | else |
4135 | error= 0; |
4136 | } |
4137 | } |
4138 | else if (unlikely(error=table->file->ha_write_row(table->record[0]))) |
4139 | { |
4140 | // This should never happen |
4141 | if (table->file->is_fatal_error(error, HA_CHECK_DUP)) |
4142 | { |
4143 | table->file->print_error(error,MYF(0)); /* purecov: deadcode */ |
4144 | error= -1; /* purecov: deadcode */ |
4145 | goto end; /* purecov: deadcode */ |
4146 | } |
4147 | } |
4148 | error=0; // Privileges granted / revoked |
4149 | |
4150 | end: |
4151 | if (likely(!error)) |
4152 | { |
4153 | acl_cache->clear(1); // Clear privilege cache |
4154 | if (old_row_exists) |
4155 | { |
4156 | if (handle_as_role) |
4157 | acl_update_role(combo.user.str, rights); |
4158 | else |
4159 | acl_update_user(combo.user.str, combo.host.str, |
4160 | combo.pwhash.str, combo.pwhash.length, |
4161 | lex->ssl_type, |
4162 | lex->ssl_cipher, |
4163 | lex->x509_issuer, |
4164 | lex->x509_subject, |
4165 | &lex->mqh, |
4166 | rights, |
4167 | &combo.plugin, |
4168 | &combo.auth); |
4169 | } |
4170 | else |
4171 | { |
4172 | if (handle_as_role) |
4173 | acl_insert_role(combo.user.str, rights); |
4174 | else |
4175 | acl_insert_user(combo.user.str, combo.host.str, |
4176 | combo.pwhash.str, combo.pwhash.length, |
4177 | lex->ssl_type, |
4178 | lex->ssl_cipher, |
4179 | lex->x509_issuer, |
4180 | lex->x509_subject, |
4181 | &lex->mqh, |
4182 | rights, |
4183 | &combo.plugin, |
4184 | &combo.auth); |
4185 | } |
4186 | } |
4187 | DBUG_RETURN(error); |
4188 | } |
4189 | |
4190 | |
4191 | /* |
4192 | change grants in the mysql.db table |
4193 | */ |
4194 | |
4195 | static int replace_db_table(TABLE *table, const char *db, |
4196 | const LEX_USER &combo, |
4197 | ulong rights, bool revoke_grant) |
4198 | { |
4199 | uint i; |
4200 | ulong priv,store_rights; |
4201 | bool old_row_exists=0; |
4202 | int error; |
4203 | char what= (revoke_grant) ? 'N' : 'Y'; |
4204 | uchar user_key[MAX_KEY_LENGTH]; |
4205 | DBUG_ENTER("replace_db_table" ); |
4206 | |
4207 | /* Check if there is such a user in user table in memory? */ |
4208 | if (!find_user_wild(combo.host.str,combo.user.str)) |
4209 | { |
4210 | /* The user could be a role, check if the user is registered as a role */ |
4211 | if (!combo.host.length && !find_acl_role(combo.user.str)) |
4212 | { |
4213 | my_message(ER_PASSWORD_NO_MATCH, ER_THD(table->in_use, |
4214 | ER_PASSWORD_NO_MATCH), MYF(0)); |
4215 | DBUG_RETURN(-1); |
4216 | } |
4217 | } |
4218 | |
4219 | table->use_all_columns(); |
4220 | table->field[0]->store(combo.host.str,combo.host.length, |
4221 | system_charset_info); |
4222 | table->field[1]->store(db,(uint) strlen(db), system_charset_info); |
4223 | table->field[2]->store(combo.user.str,combo.user.length, |
4224 | system_charset_info); |
4225 | key_copy(user_key, table->record[0], table->key_info, |
4226 | table->key_info->key_length); |
4227 | |
4228 | if (table->file->ha_index_read_idx_map(table->record[0],0, user_key, |
4229 | HA_WHOLE_KEY, |
4230 | HA_READ_KEY_EXACT)) |
4231 | { |
4232 | if (what == 'N') |
4233 | { // no row, no revoke |
4234 | my_error(ER_NONEXISTING_GRANT, MYF(0), combo.user.str, combo.host.str); |
4235 | goto abort; |
4236 | } |
4237 | old_row_exists = 0; |
4238 | restore_record(table, s->default_values); |
4239 | table->field[0]->store(combo.host.str,combo.host.length, |
4240 | system_charset_info); |
4241 | table->field[1]->store(db,(uint) strlen(db), system_charset_info); |
4242 | table->field[2]->store(combo.user.str,combo.user.length, |
4243 | system_charset_info); |
4244 | } |
4245 | else |
4246 | { |
4247 | old_row_exists = 1; |
4248 | store_record(table,record[1]); |
4249 | } |
4250 | |
4251 | store_rights=get_rights_for_db(rights); |
4252 | for (i= 3, priv= 1; i < table->s->fields; i++, priv <<= 1) |
4253 | { |
4254 | if (priv & store_rights) // do it if priv is chosen |
4255 | table->field [i]->store(&what,1, &my_charset_latin1);// set requested privileges |
4256 | } |
4257 | rights=get_access(table,3); |
4258 | rights=fix_rights_for_db(rights); |
4259 | |
4260 | if (old_row_exists) |
4261 | { |
4262 | /* update old existing row */ |
4263 | if (rights) |
4264 | { |
4265 | if (unlikely((error= table->file->ha_update_row(table->record[1], |
4266 | table->record[0]))) && |
4267 | error != HA_ERR_RECORD_IS_THE_SAME) |
4268 | goto table_error; /* purecov: deadcode */ |
4269 | } |
4270 | else /* must have been a revoke of all privileges */ |
4271 | { |
4272 | if (unlikely((error= table->file->ha_delete_row(table->record[1])))) |
4273 | goto table_error; /* purecov: deadcode */ |
4274 | } |
4275 | } |
4276 | else if (rights && |
4277 | (unlikely(error= table->file->ha_write_row(table->record[0])))) |
4278 | { |
4279 | if (table->file->is_fatal_error(error, HA_CHECK_DUP_KEY)) |
4280 | goto table_error; /* purecov: deadcode */ |
4281 | } |
4282 | |
4283 | acl_cache->clear(1); // Clear privilege cache |
4284 | if (old_row_exists) |
4285 | acl_update_db(combo.user.str,combo.host.str,db,rights); |
4286 | else if (rights) |
4287 | { |
4288 | /* |
4289 | If we did not have an already existing row, for users, we must always |
4290 | insert an ACL_DB entry. For roles however, it is possible that one was |
4291 | already created when DB privileges were propagated from other granted |
4292 | roles onto the current role. For this case, first try to update the |
4293 | existing entry, otherwise insert a new one. |
4294 | */ |
4295 | if (!combo.is_role() || |
4296 | !acl_update_db(combo.user.str, combo.host.str, db, rights)) |
4297 | { |
4298 | acl_insert_db(combo.user.str,combo.host.str,db,rights); |
4299 | } |
4300 | } |
4301 | DBUG_RETURN(0); |
4302 | |
4303 | /* This could only happen if the grant tables got corrupted */ |
4304 | table_error: |
4305 | table->file->print_error(error,MYF(0)); /* purecov: deadcode */ |
4306 | |
4307 | abort: |
4308 | DBUG_RETURN(-1); |
4309 | } |
4310 | |
4311 | /** |
4312 | Updates the mysql.roles_mapping table |
4313 | |
4314 | @param table TABLE to update |
4315 | @param user user name of the grantee |
4316 | @param host host name of the grantee |
4317 | @param role role name to grant |
4318 | @param with_admin WITH ADMIN OPTION flag |
4319 | @param existing the entry in the acl_roles_mappings hash or NULL. |
4320 | it is never NULL if revoke_grant is true. |
4321 | it is NULL when a new pair is added, it's not NULL |
4322 | when an existing pair is updated. |
4323 | @param revoke_grant true for REVOKE, false for GRANT |
4324 | */ |
4325 | static int |
4326 | replace_roles_mapping_table(TABLE *table, LEX_CSTRING *user, LEX_CSTRING *host, |
4327 | LEX_CSTRING *role, bool with_admin, |
4328 | ROLE_GRANT_PAIR *existing, bool revoke_grant) |
4329 | { |
4330 | DBUG_ENTER("replace_roles_mapping_table" ); |
4331 | |
4332 | uchar row_key[MAX_KEY_LENGTH]; |
4333 | int error; |
4334 | table->use_all_columns(); |
4335 | restore_record(table, s->default_values); |
4336 | table->field[0]->store(host->str, host->length, system_charset_info); |
4337 | table->field[1]->store(user->str, user->length, system_charset_info); |
4338 | table->field[2]->store(role->str, role->length, system_charset_info); |
4339 | |
4340 | DBUG_ASSERT(!revoke_grant || existing); |
4341 | |
4342 | if (existing) // delete or update |
4343 | { |
4344 | key_copy(row_key, table->record[0], table->key_info, |
4345 | table->key_info->key_length); |
4346 | if (table->file->ha_index_read_idx_map(table->record[1], 0, row_key, |
4347 | HA_WHOLE_KEY, HA_READ_KEY_EXACT)) |
4348 | { |
4349 | /* No match */ |
4350 | DBUG_RETURN(1); |
4351 | } |
4352 | if (revoke_grant && !with_admin) |
4353 | { |
4354 | if (unlikely((error= table->file->ha_delete_row(table->record[1])))) |
4355 | { |
4356 | DBUG_PRINT("info" , ("error deleting row '%s' '%s' '%s'" , |
4357 | host->str, user->str, role->str)); |
4358 | goto table_error; |
4359 | } |
4360 | } |
4361 | else if (with_admin) |
4362 | { |
4363 | table->field[3]->store(!revoke_grant + 1); |
4364 | |
4365 | if (unlikely((error= table->file->ha_update_row(table->record[1], |
4366 | table->record[0])))) |
4367 | { |
4368 | DBUG_PRINT("info" , ("error updating row '%s' '%s' '%s'" , |
4369 | host->str, user->str, role->str)); |
4370 | goto table_error; |
4371 | } |
4372 | } |
4373 | DBUG_RETURN(0); |
4374 | } |
4375 | |
4376 | table->field[3]->store(with_admin + 1); |
4377 | |
4378 | if (unlikely((error= table->file->ha_write_row(table->record[0])))) |
4379 | { |
4380 | DBUG_PRINT("info" , ("error inserting row '%s' '%s' '%s'" , |
4381 | host->str, user->str, role->str)); |
4382 | goto table_error; |
4383 | } |
4384 | |
4385 | /* all ok */ |
4386 | DBUG_RETURN(0); |
4387 | |
4388 | table_error: |
4389 | DBUG_PRINT("info" , ("table error" )); |
4390 | table->file->print_error(error, MYF(0)); |
4391 | DBUG_RETURN(1); |
4392 | } |
4393 | |
4394 | |
4395 | /** |
4396 | Updates the acl_roles_mappings hash |
4397 | |
4398 | @param user user name of the grantee |
4399 | @param host host name of the grantee |
4400 | @param role role name to grant |
4401 | @param with_admin WITH ADMIN OPTION flag |
4402 | @param existing the entry in the acl_roles_mappings hash or NULL. |
4403 | it is never NULL if revoke_grant is true. |
4404 | it is NULL when a new pair is added, it's not NULL |
4405 | when an existing pair is updated. |
4406 | @param revoke_grant true for REVOKE, false for GRANT |
4407 | */ |
4408 | static int |
4409 | update_role_mapping(LEX_CSTRING *user, LEX_CSTRING *host, LEX_CSTRING *role, |
4410 | bool with_admin, ROLE_GRANT_PAIR *existing, bool revoke_grant) |
4411 | { |
4412 | if (revoke_grant) |
4413 | { |
4414 | if (with_admin) |
4415 | { |
4416 | existing->with_admin= false; |
4417 | return 0; |
4418 | } |
4419 | return my_hash_delete(&acl_roles_mappings, (uchar*)existing); |
4420 | } |
4421 | |
4422 | if (existing) |
4423 | { |
4424 | existing->with_admin|= with_admin; |
4425 | return 0; |
4426 | } |
4427 | |
4428 | /* allocate a new entry that will go in the hash */ |
4429 | ROLE_GRANT_PAIR *hash_entry= new (&acl_memroot) ROLE_GRANT_PAIR; |
4430 | if (hash_entry->init(&acl_memroot, user->str, host->str, |
4431 | role->str, with_admin)) |
4432 | return 1; |
4433 | return my_hash_insert(&acl_roles_mappings, (uchar*) hash_entry); |
4434 | } |
4435 | |
4436 | static void |
4437 | acl_update_proxy_user(ACL_PROXY_USER *new_value, bool is_revoke) |
4438 | { |
4439 | mysql_mutex_assert_owner(&acl_cache->lock); |
4440 | |
4441 | DBUG_ENTER("acl_update_proxy_user" ); |
4442 | for (uint i= 0; i < acl_proxy_users.elements; i++) |
4443 | { |
4444 | ACL_PROXY_USER *acl_user= |
4445 | dynamic_element(&acl_proxy_users, i, ACL_PROXY_USER *); |
4446 | |
4447 | if (acl_user->pk_equals(new_value)) |
4448 | { |
4449 | if (is_revoke) |
4450 | { |
4451 | DBUG_PRINT("info" , ("delting ACL_PROXY_USER" )); |
4452 | delete_dynamic_element(&acl_proxy_users, i); |
4453 | } |
4454 | else |
4455 | { |
4456 | DBUG_PRINT("info" , ("updating ACL_PROXY_USER" )); |
4457 | acl_user->set_data(new_value); |
4458 | } |
4459 | break; |
4460 | } |
4461 | } |
4462 | DBUG_VOID_RETURN; |
4463 | } |
4464 | |
4465 | |
4466 | static void |
4467 | acl_insert_proxy_user(ACL_PROXY_USER *new_value) |
4468 | { |
4469 | DBUG_ENTER("acl_insert_proxy_user" ); |
4470 | mysql_mutex_assert_owner(&acl_cache->lock); |
4471 | (void) push_dynamic(&acl_proxy_users, (uchar *) new_value); |
4472 | my_qsort((uchar*) dynamic_element(&acl_proxy_users, 0, ACL_PROXY_USER *), |
4473 | acl_proxy_users.elements, |
4474 | sizeof(ACL_PROXY_USER), (qsort_cmp) acl_compare); |
4475 | DBUG_VOID_RETURN; |
4476 | } |
4477 | |
4478 | |
4479 | static int |
4480 | replace_proxies_priv_table(THD *thd, TABLE *table, const LEX_USER *user, |
4481 | const LEX_USER *proxied_user, bool with_grant_arg, |
4482 | bool revoke_grant) |
4483 | { |
4484 | bool old_row_exists= 0; |
4485 | int error; |
4486 | uchar user_key[MAX_KEY_LENGTH]; |
4487 | ACL_PROXY_USER new_grant; |
4488 | char grantor[USER_HOST_BUFF_SIZE]; |
4489 | |
4490 | DBUG_ENTER("replace_proxies_priv_table" ); |
4491 | |
4492 | /* Check if there is such a user in user table in memory? */ |
4493 | if (!find_user_wild(user->host.str,user->user.str)) |
4494 | { |
4495 | my_message(ER_PASSWORD_NO_MATCH, |
4496 | ER_THD(thd, ER_PASSWORD_NO_MATCH), MYF(0)); |
4497 | DBUG_RETURN(-1); |
4498 | } |
4499 | |
4500 | table->use_all_columns(); |
4501 | ACL_PROXY_USER::store_pk (table, &user->host, &user->user, |
4502 | &proxied_user->host, &proxied_user->user); |
4503 | |
4504 | key_copy(user_key, table->record[0], table->key_info, |
4505 | table->key_info->key_length); |
4506 | |
4507 | get_grantor(thd, grantor); |
4508 | |
4509 | if (unlikely((error= table->file->ha_index_init(0, 1)))) |
4510 | { |
4511 | table->file->print_error(error, MYF(0)); |
4512 | DBUG_PRINT("info" , ("ha_index_init error" )); |
4513 | DBUG_RETURN(-1); |
4514 | } |
4515 | |
4516 | if (table->file->ha_index_read_map(table->record[0], user_key, |
4517 | HA_WHOLE_KEY, |
4518 | HA_READ_KEY_EXACT)) |
4519 | { |
4520 | DBUG_PRINT ("info" , ("Row not found" )); |
4521 | if (revoke_grant) |
4522 | { // no row, no revoke |
4523 | my_error(ER_NONEXISTING_GRANT, MYF(0), user->user.str, user->host.str); |
4524 | goto abort; |
4525 | } |
4526 | old_row_exists= 0; |
4527 | restore_record(table, s->default_values); |
4528 | ACL_PROXY_USER::store_data_record(table, &user->host, &user->user, |
4529 | &proxied_user->host, |
4530 | &proxied_user->user, |
4531 | with_grant_arg, |
4532 | grantor); |
4533 | } |
4534 | else |
4535 | { |
4536 | DBUG_PRINT("info" , ("Row found" )); |
4537 | old_row_exists= 1; |
4538 | store_record(table, record[1]); |
4539 | } |
4540 | |
4541 | if (old_row_exists) |
4542 | { |
4543 | /* update old existing row */ |
4544 | if (!revoke_grant) |
4545 | { |
4546 | if (unlikely(error= table->file->ha_update_row(table->record[1], |
4547 | table->record[0])) && |
4548 | error != HA_ERR_RECORD_IS_THE_SAME) |
4549 | goto table_error; /* purecov: inspected */ |
4550 | } |
4551 | else |
4552 | { |
4553 | if (unlikely((error= table->file->ha_delete_row(table->record[1])))) |
4554 | goto table_error; /* purecov: inspected */ |
4555 | } |
4556 | } |
4557 | else if (unlikely((error= table->file->ha_write_row(table->record[0])))) |
4558 | { |
4559 | DBUG_PRINT("info" , ("error inserting the row" )); |
4560 | if (table->file->is_fatal_error(error, HA_CHECK_DUP_KEY)) |
4561 | goto table_error; /* purecov: inspected */ |
4562 | } |
4563 | |
4564 | acl_cache->clear(1); // Clear privilege cache |
4565 | if (old_row_exists) |
4566 | { |
4567 | new_grant.init(user->host.str, user->user.str, |
4568 | proxied_user->host.str, proxied_user->user.str, |
4569 | with_grant_arg); |
4570 | acl_update_proxy_user(&new_grant, revoke_grant); |
4571 | } |
4572 | else |
4573 | { |
4574 | new_grant.init(&acl_memroot, user->host.str, user->user.str, |
4575 | proxied_user->host.str, proxied_user->user.str, |
4576 | with_grant_arg); |
4577 | acl_insert_proxy_user(&new_grant); |
4578 | } |
4579 | |
4580 | table->file->ha_index_end(); |
4581 | DBUG_RETURN(0); |
4582 | |
4583 | /* This could only happen if the grant tables got corrupted */ |
4584 | table_error: |
4585 | DBUG_PRINT("info" , ("table error" )); |
4586 | table->file->print_error(error, MYF(0)); /* purecov: inspected */ |
4587 | |
4588 | abort: |
4589 | DBUG_PRINT("info" , ("aborting replace_proxies_priv_table" )); |
4590 | table->file->ha_index_end(); |
4591 | DBUG_RETURN(-1); |
4592 | } |
4593 | |
4594 | |
4595 | class GRANT_COLUMN :public Sql_alloc |
4596 | { |
4597 | public: |
4598 | char *column; |
4599 | ulong rights; |
4600 | ulong init_rights; |
4601 | uint key_length; |
4602 | GRANT_COLUMN(String &c, ulong y) :rights (y), init_rights(y) |
4603 | { |
4604 | column= (char*) memdup_root(&grant_memroot,c.ptr(), key_length=c.length()); |
4605 | } |
4606 | |
4607 | /* this constructor assumes thas source->column is allocated in grant_memroot */ |
4608 | GRANT_COLUMN(GRANT_COLUMN *source) : column(source->column), |
4609 | rights (source->rights), init_rights(0), key_length(source->key_length) { } |
4610 | }; |
4611 | |
4612 | |
4613 | static uchar* get_key_column(GRANT_COLUMN *buff, size_t *length, |
4614 | my_bool not_used __attribute__((unused))) |
4615 | { |
4616 | *length=buff->key_length; |
4617 | return (uchar*) buff->column; |
4618 | } |
4619 | |
4620 | class GRANT_NAME :public Sql_alloc |
4621 | { |
4622 | public: |
4623 | acl_host_and_ip host; |
4624 | char *db, *user, *tname, *hash_key; |
4625 | ulong privs; |
4626 | ulong init_privs; /* privileges found in physical table */ |
4627 | ulong sort; |
4628 | size_t key_length; |
4629 | GRANT_NAME(const char *h, const char *d,const char *u, |
4630 | const char *t, ulong p, bool is_routine); |
4631 | GRANT_NAME (TABLE *form, bool is_routine); |
4632 | virtual ~GRANT_NAME() {}; |
4633 | virtual bool ok() { return privs != 0; } |
4634 | void set_user_details(const char *h, const char *d, |
4635 | const char *u, const char *t, |
4636 | bool is_routine); |
4637 | }; |
4638 | |
4639 | |
4640 | class GRANT_TABLE :public GRANT_NAME |
4641 | { |
4642 | public: |
4643 | ulong cols; |
4644 | ulong init_cols; /* privileges found in physical table */ |
4645 | HASH hash_columns; |
4646 | |
4647 | GRANT_TABLE(const char *h, const char *d,const char *u, |
4648 | const char *t, ulong p, ulong c); |
4649 | GRANT_TABLE (TABLE *form, TABLE *col_privs); |
4650 | ~GRANT_TABLE(); |
4651 | bool ok() { return privs != 0 || cols != 0; } |
4652 | void init_hash() |
4653 | { |
4654 | my_hash_init2(&hash_columns, 4, system_charset_info, 0, 0, 0, |
4655 | (my_hash_get_key) get_key_column, 0, 0, 0); |
4656 | } |
4657 | }; |
4658 | |
4659 | |
4660 | void GRANT_NAME::set_user_details(const char *h, const char *d, |
4661 | const char *u, const char *t, |
4662 | bool is_routine) |
4663 | { |
4664 | /* Host given by user */ |
4665 | update_hostname(&host, strdup_root(&grant_memroot, h)); |
4666 | if (db != d) |
4667 | { |
4668 | db= strdup_root(&grant_memroot, d); |
4669 | if (lower_case_table_names) |
4670 | my_casedn_str(files_charset_info, db); |
4671 | } |
4672 | user = strdup_root(&grant_memroot,u); |
4673 | sort= get_sort(3,host.hostname,db,user); |
4674 | if (tname != t) |
4675 | { |
4676 | tname= strdup_root(&grant_memroot, t); |
4677 | if (lower_case_table_names || is_routine) |
4678 | my_casedn_str(files_charset_info, tname); |
4679 | } |
4680 | key_length= strlen(d) + strlen(u)+ strlen(t)+3; |
4681 | hash_key= (char*) alloc_root(&grant_memroot,key_length); |
4682 | strmov(strmov(strmov(hash_key,user)+1,db)+1,tname); |
4683 | } |
4684 | |
4685 | GRANT_NAME::GRANT_NAME(const char *h, const char *d,const char *u, |
4686 | const char *t, ulong p, bool is_routine) |
4687 | :db(0), tname(0), privs(p), init_privs(p) |
4688 | { |
4689 | set_user_details(h, d, u, t, is_routine); |
4690 | } |
4691 | |
4692 | GRANT_TABLE::GRANT_TABLE(const char *h, const char *d,const char *u, |
4693 | const char *t, ulong p, ulong c) |
4694 | :GRANT_NAME(h,d,u,t,p, FALSE), cols(c) |
4695 | { |
4696 | init_hash(); |
4697 | } |
4698 | |
4699 | /* |
4700 | create a new GRANT_TABLE entry for role inheritance. init_* fields are set |
4701 | to 0 |
4702 | */ |
4703 | GRANT_NAME::GRANT_NAME(TABLE *form, bool is_routine) |
4704 | { |
4705 | user= safe_str(get_field(&grant_memroot,form->field[2])); |
4706 | |
4707 | const char *hostname= get_field(&grant_memroot, form->field[0]); |
4708 | mysql_mutex_lock(&acl_cache->lock); |
4709 | if (!hostname && find_acl_role(user)) |
4710 | hostname= "" ; |
4711 | mysql_mutex_unlock(&acl_cache->lock); |
4712 | update_hostname(&host, hostname); |
4713 | |
4714 | db= get_field(&grant_memroot,form->field[1]); |
4715 | sort= get_sort(3, host.hostname, db, user); |
4716 | tname= get_field(&grant_memroot,form->field[3]); |
4717 | if (!db || !tname) |
4718 | { |
4719 | /* Wrong table row; Ignore it */ |
4720 | privs= 0; |
4721 | return; /* purecov: inspected */ |
4722 | } |
4723 | if (lower_case_table_names) |
4724 | { |
4725 | my_casedn_str(files_charset_info, db); |
4726 | } |
4727 | if (lower_case_table_names || is_routine) |
4728 | { |
4729 | my_casedn_str(files_charset_info, tname); |
4730 | } |
4731 | key_length= (strlen(db) + strlen(user) + strlen(tname) + 3); |
4732 | hash_key= (char*) alloc_root(&grant_memroot, key_length); |
4733 | strmov(strmov(strmov(hash_key,user)+1,db)+1,tname); |
4734 | privs = (ulong) form->field[6]->val_int(); |
4735 | privs = fix_rights_for_table(privs); |
4736 | init_privs= privs; |
4737 | } |
4738 | |
4739 | |
4740 | GRANT_TABLE::GRANT_TABLE(TABLE *form, TABLE *col_privs) |
4741 | :GRANT_NAME(form, FALSE) |
4742 | { |
4743 | uchar key[MAX_KEY_LENGTH]; |
4744 | |
4745 | if (!db || !tname) |
4746 | { |
4747 | /* Wrong table row; Ignore it */ |
4748 | my_hash_clear(&hash_columns); /* allow for destruction */ |
4749 | cols= 0; |
4750 | return; |
4751 | } |
4752 | cols= (ulong) form->field[7]->val_int(); |
4753 | cols= fix_rights_for_column(cols); |
4754 | /* |
4755 | Initial columns privileges are the same as column privileges on creation. |
4756 | In case of roles, the cols privilege bits can get inherited and thus |
4757 | cause the cols field to change. The init_cols field is always the same |
4758 | as the physical table entry |
4759 | */ |
4760 | init_cols= cols; |
4761 | |
4762 | init_hash(); |
4763 | |
4764 | if (cols) |
4765 | { |
4766 | uint key_prefix_len; |
4767 | KEY_PART_INFO *key_part= col_privs->key_info->key_part; |
4768 | col_privs->field[0]->store(host.hostname, |
4769 | (uint) safe_strlen(host.hostname), |
4770 | system_charset_info); |
4771 | col_privs->field[1]->store(db,(uint) strlen(db), system_charset_info); |
4772 | col_privs->field[2]->store(user,(uint) strlen(user), system_charset_info); |
4773 | col_privs->field[3]->store(tname,(uint) strlen(tname), system_charset_info); |
4774 | |
4775 | key_prefix_len= (key_part[0].store_length + |
4776 | key_part[1].store_length + |
4777 | key_part[2].store_length + |
4778 | key_part[3].store_length); |
4779 | key_copy(key, col_privs->record[0], col_privs->key_info, key_prefix_len); |
4780 | col_privs->field[4]->store("" ,0, &my_charset_latin1); |
4781 | |
4782 | if (col_privs->file->ha_index_init(0, 1)) |
4783 | { |
4784 | cols= 0; |
4785 | init_cols= 0; |
4786 | return; |
4787 | } |
4788 | |
4789 | if (col_privs->file->ha_index_read_map(col_privs->record[0], (uchar*) key, |
4790 | (key_part_map)15, |
4791 | HA_READ_KEY_EXACT)) |
4792 | { |
4793 | cols= 0; /* purecov: deadcode */ |
4794 | init_cols= 0; |
4795 | col_privs->file->ha_index_end(); |
4796 | return; |
4797 | } |
4798 | do |
4799 | { |
4800 | String *res,column_name; |
4801 | GRANT_COLUMN *mem_check; |
4802 | /* As column name is a string, we don't have to supply a buffer */ |
4803 | res=col_privs->field[4]->val_str(&column_name); |
4804 | ulong priv= (ulong) col_privs->field[6]->val_int(); |
4805 | if (!(mem_check = new GRANT_COLUMN(*res, |
4806 | fix_rights_for_column(priv)))) |
4807 | { |
4808 | /* Don't use this entry */ |
4809 | privs= cols= init_privs= init_cols=0; /* purecov: deadcode */ |
4810 | return; /* purecov: deadcode */ |
4811 | } |
4812 | if (my_hash_insert(&hash_columns, (uchar *) mem_check)) |
4813 | { |
4814 | /* Invalidate this entry */ |
4815 | privs= cols= init_privs= init_cols=0; |
4816 | return; |
4817 | } |
4818 | } while (!col_privs->file->ha_index_next(col_privs->record[0]) && |
4819 | !key_cmp_if_same(col_privs,key,0,key_prefix_len)); |
4820 | col_privs->file->ha_index_end(); |
4821 | } |
4822 | } |
4823 | |
4824 | |
4825 | GRANT_TABLE::~GRANT_TABLE() |
4826 | { |
4827 | my_hash_free(&hash_columns); |
4828 | } |
4829 | |
4830 | |
4831 | static uchar* get_grant_table(GRANT_NAME *buff, size_t *length, |
4832 | my_bool not_used __attribute__((unused))) |
4833 | { |
4834 | *length=buff->key_length; |
4835 | return (uchar*) buff->hash_key; |
4836 | } |
4837 | |
4838 | |
4839 | static void free_grant_table(GRANT_TABLE *grant_table) |
4840 | { |
4841 | grant_table->~GRANT_TABLE(); |
4842 | } |
4843 | |
4844 | |
4845 | /* Search after a matching grant. Prefer exact grants before not exact ones */ |
4846 | |
4847 | static GRANT_NAME *name_hash_search(HASH *name_hash, |
4848 | const char *host,const char* ip, |
4849 | const char *db, |
4850 | const char *user, const char *tname, |
4851 | bool exact, bool name_tolower) |
4852 | { |
4853 | char helping[SAFE_NAME_LEN*2+USERNAME_LENGTH+3]; |
4854 | char *hend = helping + sizeof(helping); |
4855 | uint len; |
4856 | GRANT_NAME *grant_name,*found=0; |
4857 | HASH_SEARCH_STATE state; |
4858 | |
4859 | char *db_ptr= strmov(helping, user) + 1; |
4860 | char *tname_ptr= strnmov(db_ptr, db, hend - db_ptr) + 1; |
4861 | if (tname_ptr > hend) |
4862 | return 0; // invalid name = not found |
4863 | char *end= strnmov(tname_ptr, tname, hend - tname_ptr) + 1; |
4864 | if (end > hend) |
4865 | return 0; // invalid name = not found |
4866 | |
4867 | len = (uint) (end - helping); |
4868 | if (name_tolower) |
4869 | my_casedn_str(files_charset_info, tname_ptr); |
4870 | for (grant_name= (GRANT_NAME*) my_hash_first(name_hash, (uchar*) helping, |
4871 | len, &state); |
4872 | grant_name ; |
4873 | grant_name= (GRANT_NAME*) my_hash_next(name_hash,(uchar*) helping, |
4874 | len, &state)) |
4875 | { |
4876 | if (exact) |
4877 | { |
4878 | if (!grant_name->host.hostname || |
4879 | (host && |
4880 | !my_strcasecmp(system_charset_info, host, |
4881 | grant_name->host.hostname)) || |
4882 | (ip && !strcmp(ip, grant_name->host.hostname))) |
4883 | return grant_name; |
4884 | } |
4885 | else |
4886 | { |
4887 | if (compare_hostname(&grant_name->host, host, ip) && |
4888 | (!found || found->sort < grant_name->sort)) |
4889 | found=grant_name; // Host ok |
4890 | } |
4891 | } |
4892 | return found; |
4893 | } |
4894 | |
4895 | |
4896 | static GRANT_NAME * |
4897 | routine_hash_search(const char *host, const char *ip, const char *db, |
4898 | const char *user, const char *tname, const Sp_handler *sph, |
4899 | bool exact) |
4900 | { |
4901 | return (GRANT_TABLE*) |
4902 | name_hash_search(sph->get_priv_hash(), |
4903 | host, ip, db, user, tname, exact, TRUE); |
4904 | } |
4905 | |
4906 | |
4907 | static GRANT_TABLE * |
4908 | table_hash_search(const char *host, const char *ip, const char *db, |
4909 | const char *user, const char *tname, bool exact) |
4910 | { |
4911 | return (GRANT_TABLE*) name_hash_search(&column_priv_hash, host, ip, db, |
4912 | user, tname, exact, FALSE); |
4913 | } |
4914 | |
4915 | |
4916 | static GRANT_COLUMN * |
4917 | column_hash_search(GRANT_TABLE *t, const char *cname, size_t length) |
4918 | { |
4919 | if (!my_hash_inited(&t->hash_columns)) |
4920 | return (GRANT_COLUMN*) 0; |
4921 | return (GRANT_COLUMN*) my_hash_search(&t->hash_columns, |
4922 | (uchar*) cname, length); |
4923 | } |
4924 | |
4925 | |
4926 | static int replace_column_table(GRANT_TABLE *g_t, |
4927 | TABLE *table, const LEX_USER &combo, |
4928 | List <LEX_COLUMN> &columns, |
4929 | const char *db, const char *table_name, |
4930 | ulong rights, bool revoke_grant) |
4931 | { |
4932 | int result=0; |
4933 | uchar key[MAX_KEY_LENGTH]; |
4934 | uint key_prefix_length; |
4935 | KEY_PART_INFO *key_part= table->key_info->key_part; |
4936 | DBUG_ENTER("replace_column_table" ); |
4937 | |
4938 | table->use_all_columns(); |
4939 | table->field[0]->store(combo.host.str,combo.host.length, |
4940 | system_charset_info); |
4941 | table->field[1]->store(db,(uint) strlen(db), |
4942 | system_charset_info); |
4943 | table->field[2]->store(combo.user.str,combo.user.length, |
4944 | system_charset_info); |
4945 | table->field[3]->store(table_name,(uint) strlen(table_name), |
4946 | system_charset_info); |
4947 | |
4948 | /* Get length of 4 first key parts */ |
4949 | key_prefix_length= (key_part[0].store_length + key_part[1].store_length + |
4950 | key_part[2].store_length + key_part[3].store_length); |
4951 | key_copy(key, table->record[0], table->key_info, key_prefix_length); |
4952 | |
4953 | rights&= COL_ACLS; // Only ACL for columns |
4954 | |
4955 | /* first fix privileges for all columns in column list */ |
4956 | |
4957 | List_iterator <LEX_COLUMN> iter(columns); |
4958 | class LEX_COLUMN *column; |
4959 | int error= table->file->ha_index_init(0, 1); |
4960 | if (unlikely(error)) |
4961 | { |
4962 | table->file->print_error(error, MYF(0)); |
4963 | DBUG_RETURN(-1); |
4964 | } |
4965 | |
4966 | while ((column= iter++)) |
4967 | { |
4968 | ulong privileges= column->rights; |
4969 | bool old_row_exists=0; |
4970 | uchar user_key[MAX_KEY_LENGTH]; |
4971 | |
4972 | key_restore(table->record[0],key,table->key_info, |
4973 | key_prefix_length); |
4974 | table->field[4]->store(column->column.ptr(), column->column.length(), |
4975 | system_charset_info); |
4976 | /* Get key for the first 4 columns */ |
4977 | key_copy(user_key, table->record[0], table->key_info, |
4978 | table->key_info->key_length); |
4979 | |
4980 | if (table->file->ha_index_read_map(table->record[0], user_key, |
4981 | HA_WHOLE_KEY, HA_READ_KEY_EXACT)) |
4982 | { |
4983 | if (revoke_grant) |
4984 | { |
4985 | my_error(ER_NONEXISTING_TABLE_GRANT, MYF(0), |
4986 | combo.user.str, combo.host.str, |
4987 | table_name); /* purecov: inspected */ |
4988 | result= -1; /* purecov: inspected */ |
4989 | continue; /* purecov: inspected */ |
4990 | } |
4991 | old_row_exists = 0; |
4992 | restore_record(table, s->default_values); // Get empty record |
4993 | key_restore(table->record[0],key,table->key_info, |
4994 | key_prefix_length); |
4995 | table->field[4]->store(column->column.ptr(),column->column.length(), |
4996 | system_charset_info); |
4997 | } |
4998 | else |
4999 | { |
5000 | ulong tmp= (ulong) table->field[6]->val_int(); |
5001 | tmp=fix_rights_for_column(tmp); |
5002 | |
5003 | if (revoke_grant) |
5004 | privileges = tmp & ~(privileges | rights); |
5005 | else |
5006 | privileges |= tmp; |
5007 | old_row_exists = 1; |
5008 | store_record(table,record[1]); // copy original row |
5009 | } |
5010 | |
5011 | table->field[6]->store((longlong) get_rights_for_column(privileges), TRUE); |
5012 | |
5013 | if (old_row_exists) |
5014 | { |
5015 | GRANT_COLUMN *grant_column; |
5016 | if (privileges) |
5017 | error=table->file->ha_update_row(table->record[1],table->record[0]); |
5018 | else |
5019 | error=table->file->ha_delete_row(table->record[1]); |
5020 | if (unlikely(error) && error != HA_ERR_RECORD_IS_THE_SAME) |
5021 | { |
5022 | table->file->print_error(error,MYF(0)); /* purecov: inspected */ |
5023 | result= -1; /* purecov: inspected */ |
5024 | goto end; /* purecov: inspected */ |
5025 | } |
5026 | else |
5027 | error= 0; |
5028 | grant_column= column_hash_search(g_t, column->column.ptr(), |
5029 | column->column.length()); |
5030 | if (grant_column) // Should always be true |
5031 | grant_column->rights= privileges; // Update hash |
5032 | } |
5033 | else // new grant |
5034 | { |
5035 | GRANT_COLUMN *grant_column; |
5036 | if (unlikely((error=table->file->ha_write_row(table->record[0])))) |
5037 | { |
5038 | table->file->print_error(error,MYF(0)); /* purecov: inspected */ |
5039 | result= -1; /* purecov: inspected */ |
5040 | goto end; /* purecov: inspected */ |
5041 | } |
5042 | grant_column= new GRANT_COLUMN(column->column,privileges); |
5043 | if (my_hash_insert(&g_t->hash_columns,(uchar*) grant_column)) |
5044 | { |
5045 | result= -1; |
5046 | goto end; |
5047 | } |
5048 | } |
5049 | } |
5050 | |
5051 | /* |
5052 | If revoke of privileges on the table level, remove all such privileges |
5053 | for all columns |
5054 | */ |
5055 | |
5056 | if (revoke_grant) |
5057 | { |
5058 | uchar user_key[MAX_KEY_LENGTH]; |
5059 | key_copy(user_key, table->record[0], table->key_info, |
5060 | key_prefix_length); |
5061 | |
5062 | if (table->file->ha_index_read_map(table->record[0], user_key, |
5063 | (key_part_map)15, |
5064 | HA_READ_KEY_EXACT)) |
5065 | goto end; |
5066 | |
5067 | /* Scan through all rows with the same host,db,user and table */ |
5068 | do |
5069 | { |
5070 | ulong privileges = (ulong) table->field[6]->val_int(); |
5071 | privileges=fix_rights_for_column(privileges); |
5072 | store_record(table,record[1]); |
5073 | |
5074 | if (privileges & rights) // is in this record the priv to be revoked ?? |
5075 | { |
5076 | GRANT_COLUMN *grant_column = NULL; |
5077 | char colum_name_buf[HOSTNAME_LENGTH+1]; |
5078 | String column_name(colum_name_buf,sizeof(colum_name_buf), |
5079 | system_charset_info); |
5080 | |
5081 | privileges&= ~rights; |
5082 | table->field[6]->store((longlong) |
5083 | get_rights_for_column(privileges), TRUE); |
5084 | table->field[4]->val_str(&column_name); |
5085 | grant_column = column_hash_search(g_t, |
5086 | column_name.ptr(), |
5087 | column_name.length()); |
5088 | if (privileges) |
5089 | { |
5090 | int tmp_error; |
5091 | if (unlikely(tmp_error= |
5092 | table->file->ha_update_row(table->record[1], |
5093 | table->record[0])) && |
5094 | tmp_error != HA_ERR_RECORD_IS_THE_SAME) |
5095 | { /* purecov: deadcode */ |
5096 | table->file->print_error(tmp_error,MYF(0)); /* purecov: deadcode */ |
5097 | result= -1; /* purecov: deadcode */ |
5098 | goto end; /* purecov: deadcode */ |
5099 | } |
5100 | if (grant_column) |
5101 | { |
5102 | grant_column->rights = privileges; // Update hash |
5103 | grant_column->init_rights = privileges; |
5104 | } |
5105 | } |
5106 | else |
5107 | { |
5108 | int tmp_error; |
5109 | if (unlikely((tmp_error= |
5110 | table->file->ha_delete_row(table->record[1])))) |
5111 | { /* purecov: deadcode */ |
5112 | table->file->print_error(tmp_error,MYF(0)); /* purecov: deadcode */ |
5113 | result= -1; /* purecov: deadcode */ |
5114 | goto end; /* purecov: deadcode */ |
5115 | } |
5116 | if (grant_column) |
5117 | my_hash_delete(&g_t->hash_columns,(uchar*) grant_column); |
5118 | } |
5119 | } |
5120 | } while (!table->file->ha_index_next(table->record[0]) && |
5121 | !key_cmp_if_same(table, key, 0, key_prefix_length)); |
5122 | } |
5123 | |
5124 | end: |
5125 | table->file->ha_index_end(); |
5126 | DBUG_RETURN(result); |
5127 | } |
5128 | |
5129 | static inline void get_grantor(THD *thd, char *grantor) |
5130 | { |
5131 | const char *user= thd->security_ctx->user; |
5132 | const char *host= thd->security_ctx->host_or_ip; |
5133 | |
5134 | #if defined(HAVE_REPLICATION) |
5135 | if (thd->slave_thread && thd->has_invoker()) |
5136 | { |
5137 | user= thd->get_invoker_user().str; |
5138 | host= thd->get_invoker_host().str; |
5139 | } |
5140 | #endif |
5141 | strxmov(grantor, user, "@" , host, NullS); |
5142 | } |
5143 | |
5144 | static int replace_table_table(THD *thd, GRANT_TABLE *grant_table, |
5145 | TABLE *table, const LEX_USER &combo, |
5146 | const char *db, const char *table_name, |
5147 | ulong rights, ulong col_rights, |
5148 | bool revoke_grant) |
5149 | { |
5150 | char grantor[USER_HOST_BUFF_SIZE]; |
5151 | int old_row_exists = 1; |
5152 | int error=0; |
5153 | ulong store_table_rights, store_col_rights; |
5154 | uchar user_key[MAX_KEY_LENGTH]; |
5155 | DBUG_ENTER("replace_table_table" ); |
5156 | |
5157 | get_grantor(thd, grantor); |
5158 | /* |
5159 | The following should always succeed as new users are created before |
5160 | this function is called! |
5161 | */ |
5162 | if (!find_user_wild(combo.host.str,combo.user.str)) |
5163 | { |
5164 | if (!combo.host.length && !find_acl_role(combo.user.str)) |
5165 | { |
5166 | my_message(ER_PASSWORD_NO_MATCH, ER_THD(thd, ER_PASSWORD_NO_MATCH), |
5167 | MYF(0)); /* purecov: deadcode */ |
5168 | DBUG_RETURN(-1); /* purecov: deadcode */ |
5169 | } |
5170 | } |
5171 | |
5172 | table->use_all_columns(); |
5173 | restore_record(table, s->default_values); // Get empty record |
5174 | table->field[0]->store(combo.host.str,combo.host.length, |
5175 | system_charset_info); |
5176 | table->field[1]->store(db,(uint) strlen(db), system_charset_info); |
5177 | table->field[2]->store(combo.user.str,combo.user.length, |
5178 | system_charset_info); |
5179 | table->field[3]->store(table_name,(uint) strlen(table_name), |
5180 | system_charset_info); |
5181 | store_record(table,record[1]); // store at pos 1 |
5182 | key_copy(user_key, table->record[0], table->key_info, |
5183 | table->key_info->key_length); |
5184 | |
5185 | if (table->file->ha_index_read_idx_map(table->record[0], 0, user_key, |
5186 | HA_WHOLE_KEY, |
5187 | HA_READ_KEY_EXACT)) |
5188 | { |
5189 | /* |
5190 | The following should never happen as we first check the in memory |
5191 | grant tables for the user. There is however always a small change that |
5192 | the user has modified the grant tables directly. |
5193 | */ |
5194 | if (revoke_grant) |
5195 | { // no row, no revoke |
5196 | my_error(ER_NONEXISTING_TABLE_GRANT, MYF(0), |
5197 | combo.user.str, combo.host.str, |
5198 | table_name); /* purecov: deadcode */ |
5199 | DBUG_RETURN(-1); /* purecov: deadcode */ |
5200 | } |
5201 | old_row_exists = 0; |
5202 | restore_record(table,record[1]); // Get saved record |
5203 | } |
5204 | |
5205 | store_table_rights= get_rights_for_table(rights); |
5206 | store_col_rights= get_rights_for_column(col_rights); |
5207 | if (old_row_exists) |
5208 | { |
5209 | ulong j,k; |
5210 | store_record(table,record[1]); |
5211 | j = (ulong) table->field[6]->val_int(); |
5212 | k = (ulong) table->field[7]->val_int(); |
5213 | |
5214 | if (revoke_grant) |
5215 | { |
5216 | /* column rights are already fixed in mysql_table_grant */ |
5217 | store_table_rights=j & ~store_table_rights; |
5218 | } |
5219 | else |
5220 | { |
5221 | store_table_rights|= j; |
5222 | store_col_rights|= k; |
5223 | } |
5224 | } |
5225 | |
5226 | table->field[4]->store(grantor,(uint) strlen(grantor), system_charset_info); |
5227 | table->field[6]->store((longlong) store_table_rights, TRUE); |
5228 | table->field[7]->store((longlong) store_col_rights, TRUE); |
5229 | rights=fix_rights_for_table(store_table_rights); |
5230 | col_rights=fix_rights_for_column(store_col_rights); |
5231 | |
5232 | if (old_row_exists) |
5233 | { |
5234 | if (store_table_rights || store_col_rights) |
5235 | { |
5236 | if (unlikely(error=table->file->ha_update_row(table->record[1], |
5237 | table->record[0])) && |
5238 | error != HA_ERR_RECORD_IS_THE_SAME) |
5239 | goto table_error; /* purecov: deadcode */ |
5240 | } |
5241 | else if (unlikely((error = table->file->ha_delete_row(table->record[1])))) |
5242 | goto table_error; /* purecov: deadcode */ |
5243 | } |
5244 | else |
5245 | { |
5246 | error=table->file->ha_write_row(table->record[0]); |
5247 | if (unlikely(table->file->is_fatal_error(error, HA_CHECK_DUP_KEY))) |
5248 | goto table_error; /* purecov: deadcode */ |
5249 | } |
5250 | |
5251 | if (rights | col_rights) |
5252 | { |
5253 | grant_table->init_privs= rights; |
5254 | grant_table->init_cols= col_rights; |
5255 | |
5256 | grant_table->privs= rights; |
5257 | grant_table->cols= col_rights; |
5258 | } |
5259 | else |
5260 | { |
5261 | my_hash_delete(&column_priv_hash,(uchar*) grant_table); |
5262 | } |
5263 | DBUG_RETURN(0); |
5264 | |
5265 | /* This should never happen */ |
5266 | table_error: |
5267 | table->file->print_error(error,MYF(0)); /* purecov: deadcode */ |
5268 | DBUG_RETURN(-1); /* purecov: deadcode */ |
5269 | } |
5270 | |
5271 | |
5272 | /** |
5273 | @retval 0 success |
5274 | @retval -1 error |
5275 | */ |
5276 | static int replace_routine_table(THD *thd, GRANT_NAME *grant_name, |
5277 | TABLE *table, const LEX_USER &combo, |
5278 | const char *db, const char *routine_name, |
5279 | const Sp_handler *sph, |
5280 | ulong rights, bool revoke_grant) |
5281 | { |
5282 | char grantor[USER_HOST_BUFF_SIZE]; |
5283 | int old_row_exists= 1; |
5284 | int error=0; |
5285 | ulong store_proc_rights; |
5286 | HASH *hash= sph->get_priv_hash(); |
5287 | DBUG_ENTER("replace_routine_table" ); |
5288 | |
5289 | if (revoke_grant && !grant_name->init_privs) // only inherited role privs |
5290 | { |
5291 | my_hash_delete(hash, (uchar*) grant_name); |
5292 | DBUG_RETURN(0); |
5293 | } |
5294 | |
5295 | get_grantor(thd, grantor); |
5296 | /* |
5297 | New users are created before this function is called. |
5298 | |
5299 | There may be some cases where a routine's definer is removed but the |
5300 | routine remains. |
5301 | */ |
5302 | |
5303 | table->use_all_columns(); |
5304 | restore_record(table, s->default_values); // Get empty record |
5305 | table->field[0]->store(combo.host.str,combo.host.length, &my_charset_latin1); |
5306 | table->field[1]->store(db,(uint) strlen(db), &my_charset_latin1); |
5307 | table->field[2]->store(combo.user.str,combo.user.length, &my_charset_latin1); |
5308 | table->field[3]->store(routine_name,(uint) strlen(routine_name), |
5309 | &my_charset_latin1); |
5310 | table->field[4]->store((longlong) sph->type(), true); |
5311 | store_record(table,record[1]); // store at pos 1 |
5312 | |
5313 | if (table->file->ha_index_read_idx_map(table->record[0], 0, |
5314 | (uchar*) table->field[0]->ptr, |
5315 | HA_WHOLE_KEY, |
5316 | HA_READ_KEY_EXACT)) |
5317 | { |
5318 | /* |
5319 | The following should never happen as we first check the in memory |
5320 | grant tables for the user. There is however always a small change that |
5321 | the user has modified the grant tables directly. |
5322 | |
5323 | Also, there is also a second posibility that this routine entry |
5324 | is created for a role by being inherited from a granted role. |
5325 | */ |
5326 | if (revoke_grant) |
5327 | { // no row, no revoke |
5328 | my_error(ER_NONEXISTING_PROC_GRANT, MYF(0), |
5329 | combo.user.str, combo.host.str, routine_name); |
5330 | DBUG_RETURN(-1); |
5331 | } |
5332 | old_row_exists= 0; |
5333 | restore_record(table,record[1]); // Get saved record |
5334 | } |
5335 | |
5336 | store_proc_rights= get_rights_for_procedure(rights); |
5337 | if (old_row_exists) |
5338 | { |
5339 | ulong j; |
5340 | store_record(table,record[1]); |
5341 | j= (ulong) table->field[6]->val_int(); |
5342 | |
5343 | if (revoke_grant) |
5344 | { |
5345 | /* column rights are already fixed in mysql_table_grant */ |
5346 | store_proc_rights=j & ~store_proc_rights; |
5347 | } |
5348 | else |
5349 | { |
5350 | store_proc_rights|= j; |
5351 | } |
5352 | } |
5353 | |
5354 | table->field[5]->store(grantor,(uint) strlen(grantor), &my_charset_latin1); |
5355 | table->field[6]->store((longlong) store_proc_rights, TRUE); |
5356 | rights=fix_rights_for_procedure(store_proc_rights); |
5357 | |
5358 | if (old_row_exists) |
5359 | { |
5360 | if (store_proc_rights) |
5361 | { |
5362 | if (unlikely(error=table->file->ha_update_row(table->record[1], |
5363 | table->record[0])) && |
5364 | error != HA_ERR_RECORD_IS_THE_SAME) |
5365 | goto table_error; |
5366 | } |
5367 | else if (unlikely((error= table->file->ha_delete_row(table->record[1])))) |
5368 | goto table_error; |
5369 | } |
5370 | else |
5371 | { |
5372 | error=table->file->ha_write_row(table->record[0]); |
5373 | if (unlikely(table->file->is_fatal_error(error, HA_CHECK_DUP_KEY))) |
5374 | goto table_error; |
5375 | } |
5376 | |
5377 | if (rights) |
5378 | { |
5379 | grant_name->init_privs= rights; |
5380 | grant_name->privs= rights; |
5381 | } |
5382 | else |
5383 | { |
5384 | my_hash_delete(hash, (uchar*) grant_name); |
5385 | } |
5386 | DBUG_RETURN(0); |
5387 | |
5388 | /* This should never happen */ |
5389 | table_error: |
5390 | table->file->print_error(error,MYF(0)); |
5391 | DBUG_RETURN(-1); |
5392 | } |
5393 | |
5394 | |
5395 | /***************************************************************** |
5396 | Role privilege propagation and graph traversal functionality |
5397 | |
5398 | According to the SQL standard, a role can be granted to a role, |
5399 | thus role grants can create an arbitrarily complex directed acyclic |
5400 | graph (a standard explicitly specifies that cycles are not allowed). |
5401 | |
5402 | When a privilege is granted to a role, it becomes available to all grantees. |
5403 | The code below recursively traverses a DAG of role grants, propagating |
5404 | privilege changes. |
5405 | |
5406 | The traversal function can work both ways, from roles to grantees or |
5407 | from grantees to roles. The first is used for privilege propagation, |
5408 | the second - for SHOW GRANTS and I_S.APPLICABLE_ROLES |
5409 | |
5410 | The role propagation code is smart enough to propagate only privilege |
5411 | changes to one specific database, table, or routine, if only they |
5412 | were changed (like in GRANT ... ON ... TO ...) or it can propagate |
5413 | everything (on startup or after FLUSH PRIVILEGES). |
5414 | |
5415 | It traverses only a subgraph that's accessible from the modified role, |
5416 | only visiting roles that can be possibly affected by the GRANT statement. |
5417 | |
5418 | Additionally, it stops traversal early, if this particular GRANT statement |
5419 | didn't result in any changes of privileges (e.g. both role1 and role2 |
5420 | are granted to the role3, both role1 and role2 have SELECT privilege. |
5421 | if SELECT is revoked from role1 it won't change role3 privileges, |
5422 | so we won't traverse from role3 to its grantees). |
5423 | ******************************************************************/ |
5424 | struct PRIVS_TO_MERGE |
5425 | { |
5426 | enum what |
5427 | { |
5428 | ALL, GLOBAL, DB, TABLE_COLUMN, PROC, FUNC, PACKAGE_SPEC, PACKAGE_BODY |
5429 | } what; |
5430 | const char *db, *name; |
5431 | }; |
5432 | |
5433 | |
5434 | static enum PRIVS_TO_MERGE::what sp_privs_to_merge(stored_procedure_type type) |
5435 | { |
5436 | switch (type) { |
5437 | case TYPE_ENUM_FUNCTION: |
5438 | return PRIVS_TO_MERGE::FUNC; |
5439 | case TYPE_ENUM_PROCEDURE: |
5440 | return PRIVS_TO_MERGE::PROC; |
5441 | case TYPE_ENUM_PACKAGE: |
5442 | return PRIVS_TO_MERGE::PACKAGE_SPEC; |
5443 | case TYPE_ENUM_PACKAGE_BODY: |
5444 | return PRIVS_TO_MERGE::PACKAGE_BODY; |
5445 | case TYPE_ENUM_TRIGGER: |
5446 | case TYPE_ENUM_PROXY: |
5447 | break; |
5448 | } |
5449 | DBUG_ASSERT(0); |
5450 | return PRIVS_TO_MERGE::PROC; |
5451 | } |
5452 | |
5453 | |
5454 | static int init_role_for_merging(ACL_ROLE *role, void *context) |
5455 | { |
5456 | role->counter= 0; |
5457 | return 0; |
5458 | } |
5459 | |
5460 | static int count_subgraph_nodes(ACL_ROLE *role, ACL_ROLE *grantee, void *context) |
5461 | { |
5462 | grantee->counter++; |
5463 | return 0; |
5464 | } |
5465 | |
5466 | static int merge_role_privileges(ACL_ROLE *, ACL_ROLE *, void *); |
5467 | |
5468 | /** |
5469 | rebuild privileges of all affected roles |
5470 | |
5471 | entry point into role privilege propagation. after privileges of the |
5472 | 'role' were changed, this function rebuilds privileges of all affected roles |
5473 | as necessary. |
5474 | */ |
5475 | static void propagate_role_grants(ACL_ROLE *role, |
5476 | enum PRIVS_TO_MERGE::what what, |
5477 | const char *db= 0, const char *name= 0) |
5478 | { |
5479 | |
5480 | mysql_mutex_assert_owner(&acl_cache->lock); |
5481 | PRIVS_TO_MERGE data= { what, db, name }; |
5482 | |
5483 | /* |
5484 | Changing privileges of a role causes all other roles that had |
5485 | this role granted to them to have their rights invalidated. |
5486 | |
5487 | We need to rebuild all roles' related access bits. |
5488 | |
5489 | This cannot be a simple depth-first search, instead we have to merge |
5490 | privieges for all roles granted to a specific grantee, *before* |
5491 | merging privileges for this grantee. In other words, we must visit all |
5492 | parent nodes of a specific node, before descencing into this node. |
5493 | |
5494 | For example, if role1 is granted to role2 and role3, and role3 is |
5495 | granted to role2, after "GRANT ... role1", we cannot merge privileges |
5496 | for role2, until role3 is merged. The counter will be 0 for role1, 2 |
5497 | for role2, 1 for role3. Traversal will start from role1, go to role2, |
5498 | decrement the counter, backtrack, go to role3, merge it, go to role2 |
5499 | again, merge it. |
5500 | |
5501 | And the counter is not just "all parent nodes", but only parent nodes |
5502 | that are part of the subgraph we're interested in. For example, if |
5503 | both roleA and roleB are granted to roleC, then roleC has two parent |
5504 | nodes. But when granting a privilege to roleA, we're only looking at a |
5505 | subgraph that includes roleA and roleC (roleB cannot be possibly |
5506 | affected by that grant statement). In this subgraph roleC has only one |
5507 | parent. |
5508 | |
5509 | (on the other hand, in acl_load we want to update all roles, and |
5510 | the counter is exactly equal to the number of all parent nodes) |
5511 | |
5512 | Thus, we do two graph traversals here. First we only count parents |
5513 | that are part of the subgraph. On the second traversal we decrement |
5514 | the counter and actually merge privileges for a node when a counter |
5515 | drops to zero. |
5516 | */ |
5517 | traverse_role_graph_up(role, &data, init_role_for_merging, count_subgraph_nodes); |
5518 | traverse_role_graph_up(role, &data, NULL, merge_role_privileges); |
5519 | } |
5520 | |
5521 | |
5522 | // State of a node during a Depth First Search exploration |
5523 | struct NODE_STATE |
5524 | { |
5525 | ACL_USER_BASE *node_data; /* pointer to the node data */ |
5526 | uint neigh_idx; /* the neighbour that needs to be evaluated next */ |
5527 | }; |
5528 | |
5529 | /** |
5530 | Traverse the role grant graph and invoke callbacks at the specified points. |
5531 | |
5532 | @param user user or role to start traversal from |
5533 | @param context opaque parameter to pass to callbacks |
5534 | @param offset offset to ACL_ROLE::parent_grantee or to |
5535 | ACL_USER_BASE::role_grants. Depending on this value, |
5536 | traversal will go from roles to grantees or from |
5537 | grantees to roles. |
5538 | @param on_node called when a node is visited for the first time. |
5539 | Returning a value <0 will abort the traversal. |
5540 | @param on_edge called for every edge in the graph, when traversal |
5541 | goes from a node to a neighbour node. |
5542 | Returning <0 will abort the traversal. Returning >0 |
5543 | will make the traversal not to follow this edge. |
5544 | |
5545 | @note |
5546 | The traverse method is a DEPTH FIRST SEARCH, but callbacks can influence |
5547 | that (on_edge returning >0 value). |
5548 | |
5549 | @note |
5550 | This function should not be called directly, use |
5551 | traverse_role_graph_up() and traverse_role_graph_down() instead. |
5552 | |
5553 | @retval 0 traversal finished successfully |
5554 | @retval ROLE_CYCLE_FOUND traversal aborted, cycle detected |
5555 | @retval <0 traversal was aborted, because a callback returned |
5556 | this error code |
5557 | */ |
5558 | static int traverse_role_graph_impl(ACL_USER_BASE *user, void *context, |
5559 | off_t offset, |
5560 | int (*on_node) (ACL_USER_BASE *role, void *context), |
5561 | int (*on_edge) (ACL_USER_BASE *current, ACL_ROLE *neighbour, void *context)) |
5562 | { |
5563 | DBUG_ENTER("traverse_role_graph_impl" ); |
5564 | DBUG_ASSERT(user); |
5565 | DBUG_PRINT("enter" ,("role: '%s'" , user->user.str)); |
5566 | /* |
5567 | The search operation should always leave the ROLE_ON_STACK and |
5568 | ROLE_EXPLORED flags clean for all nodes involved in the search |
5569 | */ |
5570 | DBUG_ASSERT(!(user->flags & ROLE_ON_STACK)); |
5571 | DBUG_ASSERT(!(user->flags & ROLE_EXPLORED)); |
5572 | mysql_mutex_assert_owner(&acl_cache->lock); |
5573 | |
5574 | /* |
5575 | Stack used to simulate the recursive calls of DFS. |
5576 | It uses a Dynamic_array to reduce the number of |
5577 | malloc calls to a minimum |
5578 | */ |
5579 | Dynamic_array<NODE_STATE> stack(20,50); |
5580 | Dynamic_array<ACL_USER_BASE *> to_clear(20,50); |
5581 | NODE_STATE state; /* variable used to insert elements in the stack */ |
5582 | int result= 0; |
5583 | |
5584 | state.neigh_idx= 0; |
5585 | state.node_data= user; |
5586 | user->flags|= ROLE_ON_STACK; |
5587 | |
5588 | stack.push(state); |
5589 | to_clear.push(user); |
5590 | |
5591 | user->flags|= ROLE_OPENED; |
5592 | if (on_node && ((result= on_node(user, context)) < 0)) |
5593 | goto end; |
5594 | |
5595 | while (stack.elements()) |
5596 | { |
5597 | NODE_STATE *curr_state= stack.back(); |
5598 | |
5599 | DBUG_ASSERT(curr_state->node_data->flags & ROLE_ON_STACK); |
5600 | |
5601 | ACL_USER_BASE *current= curr_state->node_data; |
5602 | ACL_USER_BASE *neighbour= NULL; |
5603 | DBUG_PRINT("info" , ("Examining role %s" , current->user.str)); |
5604 | /* |
5605 | Iterate through the neighbours until a first valid jump-to |
5606 | neighbour is found |
5607 | */ |
5608 | bool found= FALSE; |
5609 | uint i; |
5610 | DYNAMIC_ARRAY *array= (DYNAMIC_ARRAY *)(((char*)current) + offset); |
5611 | |
5612 | DBUG_ASSERT(array == ¤t->role_grants || current->flags & IS_ROLE); |
5613 | for (i= curr_state->neigh_idx; i < array->elements; i++) |
5614 | { |
5615 | neighbour= *(dynamic_element(array, i, ACL_ROLE**)); |
5616 | if (!(neighbour->flags & IS_ROLE)) |
5617 | continue; |
5618 | |
5619 | DBUG_PRINT("info" , ("Examining neighbour role %s" , neighbour->user.str)); |
5620 | |
5621 | /* check if it forms a cycle */ |
5622 | if (neighbour->flags & ROLE_ON_STACK) |
5623 | { |
5624 | DBUG_PRINT("info" , ("Found cycle" )); |
5625 | result= ROLE_CYCLE_FOUND; |
5626 | goto end; |
5627 | } |
5628 | |
5629 | if (!(neighbour->flags & ROLE_OPENED)) |
5630 | { |
5631 | neighbour->flags|= ROLE_OPENED; |
5632 | to_clear.push(neighbour); |
5633 | if (on_node && ((result= on_node(neighbour, context)) < 0)) |
5634 | goto end; |
5635 | } |
5636 | |
5637 | if (on_edge) |
5638 | { |
5639 | result= on_edge(current, (ACL_ROLE*)neighbour, context); |
5640 | if (result < 0) |
5641 | goto end; |
5642 | if (result > 0) |
5643 | continue; |
5644 | } |
5645 | |
5646 | /* Check if it was already explored, in that case, move on */ |
5647 | if (neighbour->flags & ROLE_EXPLORED) |
5648 | continue; |
5649 | |
5650 | found= TRUE; |
5651 | break; |
5652 | } |
5653 | |
5654 | /* found states that we have found a node to jump next into */ |
5655 | if (found) |
5656 | { |
5657 | curr_state->neigh_idx= i + 1; |
5658 | |
5659 | /* some sanity checks */ |
5660 | DBUG_ASSERT(!(neighbour->flags & ROLE_ON_STACK)); |
5661 | |
5662 | /* add the neighbour on the stack */ |
5663 | neighbour->flags|= ROLE_ON_STACK; |
5664 | state.neigh_idx= 0; |
5665 | state.node_data= neighbour; |
5666 | stack.push(state); |
5667 | } |
5668 | else |
5669 | { |
5670 | /* Make sure we got a correct node */ |
5671 | DBUG_ASSERT(curr_state->node_data->flags & ROLE_ON_STACK); |
5672 | /* Finished with exploring the current node, pop it off the stack */ |
5673 | curr_state= &stack.pop(); |
5674 | curr_state->node_data->flags&= ~ROLE_ON_STACK; /* clear the on-stack bit */ |
5675 | curr_state->node_data->flags|= ROLE_EXPLORED; |
5676 | } |
5677 | } |
5678 | |
5679 | end: |
5680 | /* Cleanup */ |
5681 | for (uint i= 0; i < to_clear.elements(); i++) |
5682 | { |
5683 | ACL_USER_BASE *current= to_clear.at(i); |
5684 | DBUG_ASSERT(current->flags & (ROLE_EXPLORED | ROLE_ON_STACK | ROLE_OPENED)); |
5685 | current->flags&= ~(ROLE_EXPLORED | ROLE_ON_STACK | ROLE_OPENED); |
5686 | } |
5687 | DBUG_RETURN(result); |
5688 | } |
5689 | |
5690 | /** |
5691 | Traverse the role grant graph, going from a role to its grantees. |
5692 | |
5693 | This is used to propagate changes in privileges, for example, |
5694 | when GRANT or REVOKE is issued for a role. |
5695 | */ |
5696 | |
5697 | static int traverse_role_graph_up(ACL_ROLE *role, void *context, |
5698 | int (*on_node) (ACL_ROLE *role, void *context), |
5699 | int (*on_edge) (ACL_ROLE *current, ACL_ROLE *neighbour, void *context)) |
5700 | { |
5701 | return traverse_role_graph_impl(role, context, |
5702 | my_offsetof(ACL_ROLE, parent_grantee), |
5703 | (int (*)(ACL_USER_BASE *, void *))on_node, |
5704 | (int (*)(ACL_USER_BASE *, ACL_ROLE *, void *))on_edge); |
5705 | } |
5706 | |
5707 | /** |
5708 | Traverse the role grant graph, going from a user or a role to granted roles. |
5709 | |
5710 | This is used, for example, to print all grants available to a user or a role |
5711 | (as in SHOW GRANTS). |
5712 | */ |
5713 | |
5714 | static int traverse_role_graph_down(ACL_USER_BASE *user, void *context, |
5715 | int (*on_node) (ACL_USER_BASE *role, void *context), |
5716 | int (*on_edge) (ACL_USER_BASE *current, ACL_ROLE *neighbour, void *context)) |
5717 | { |
5718 | return traverse_role_graph_impl(user, context, |
5719 | my_offsetof(ACL_USER_BASE, role_grants), |
5720 | on_node, on_edge); |
5721 | } |
5722 | |
5723 | /* |
5724 | To find all db/table/routine privilege for a specific role |
5725 | we need to scan the array of privileges. It can be big. |
5726 | But the set of privileges granted to a role in question (or |
5727 | to roles directly granted to the role in question) is supposedly |
5728 | much smaller. |
5729 | |
5730 | We put a role and all roles directly granted to it in a hash, and iterate |
5731 | the (suposedly long) array of privileges, filtering out "interesting" |
5732 | entries using the role hash. We put all these "interesting" |
5733 | entries in a (suposedly small) dynamic array and them use it for merging. |
5734 | */ |
5735 | static uchar* role_key(const ACL_ROLE *role, size_t *klen, my_bool) |
5736 | { |
5737 | *klen= role->user.length; |
5738 | return (uchar*) role->user.str; |
5739 | } |
5740 | typedef Hash_set<ACL_ROLE> role_hash_t; |
5741 | |
5742 | static bool merge_role_global_privileges(ACL_ROLE *grantee) |
5743 | { |
5744 | ulong old= grantee->access; |
5745 | grantee->access= grantee->initial_role_access; |
5746 | |
5747 | DBUG_EXECUTE_IF("role_merge_stats" , role_global_merges++;); |
5748 | |
5749 | for (uint i= 0; i < grantee->role_grants.elements; i++) |
5750 | { |
5751 | ACL_ROLE *r= *dynamic_element(&grantee->role_grants, i, ACL_ROLE**); |
5752 | grantee->access|= r->access; |
5753 | } |
5754 | return old != grantee->access; |
5755 | } |
5756 | |
5757 | static int db_name_sort(ACL_DB * const *db1, ACL_DB * const *db2) |
5758 | { |
5759 | return strcmp((*db1)->db, (*db2)->db); |
5760 | } |
5761 | |
5762 | /** |
5763 | update ACL_DB for given database and a given role with merged privileges |
5764 | |
5765 | @param merged ACL_DB of the role in question (or NULL if it wasn't found) |
5766 | @param first first ACL_DB in an array for the database in question |
5767 | @param access new privileges for the given role on the gived database |
5768 | @param role the name of the given role |
5769 | |
5770 | @return a bitmap of |
5771 | 1 - privileges were changed |
5772 | 2 - ACL_DB was added |
5773 | 4 - ACL_DB was deleted |
5774 | */ |
5775 | static int update_role_db(ACL_DB *merged, ACL_DB **first, ulong access, |
5776 | const char *role) |
5777 | { |
5778 | if (!first) |
5779 | return 0; |
5780 | |
5781 | DBUG_EXECUTE_IF("role_merge_stats" , role_db_merges++;); |
5782 | |
5783 | if (merged == NULL) |
5784 | { |
5785 | /* |
5786 | there's no ACL_DB for this role (all db grants come from granted roles) |
5787 | we need to create it |
5788 | |
5789 | Note that we cannot use acl_insert_db() now: |
5790 | 1. it'll sort elements in the acl_dbs, so the pointers will become invalid |
5791 | 2. we may need many of them, no need to sort every time |
5792 | */ |
5793 | DBUG_ASSERT(access); |
5794 | ACL_DB acl_db; |
5795 | acl_db.user= role; |
5796 | acl_db.host.hostname= const_cast<char*>("" ); |
5797 | acl_db.host.ip= acl_db.host.ip_mask= 0; |
5798 | acl_db.db= first[0]->db; |
5799 | acl_db.access= access; |
5800 | acl_db.initial_access= 0; |
5801 | acl_db.sort=get_sort(3, "" , acl_db.db, role); |
5802 | push_dynamic(&acl_dbs,(uchar*) &acl_db); |
5803 | return 2; |
5804 | } |
5805 | else if (access == 0) |
5806 | { |
5807 | /* |
5808 | there is ACL_DB but the role has no db privileges granted |
5809 | (all privileges were coming from granted roles, and now those roles |
5810 | were dropped or had their privileges revoked). |
5811 | we need to remove this ACL_DB entry |
5812 | |
5813 | Note, that we cannot delete now: |
5814 | 1. it'll shift elements in the acl_dbs, so the pointers will become invalid |
5815 | 2. it's O(N) operation, and we may need many of them |
5816 | so we only mark elements deleted and will delete later. |
5817 | */ |
5818 | merged->sort= 0; // lower than any valid ACL_DB sort value, will be sorted last |
5819 | return 4; |
5820 | } |
5821 | else if (merged->access != access) |
5822 | { |
5823 | /* this is easy */ |
5824 | merged->access= access; |
5825 | return 1; |
5826 | } |
5827 | return 0; |
5828 | } |
5829 | |
5830 | /** |
5831 | merges db privileges from roles granted to the role 'grantee'. |
5832 | |
5833 | @return true if database privileges of the 'grantee' were changed |
5834 | |
5835 | */ |
5836 | static bool merge_role_db_privileges(ACL_ROLE *grantee, const char *dbname, |
5837 | role_hash_t *rhash) |
5838 | { |
5839 | Dynamic_array<ACL_DB *> dbs; |
5840 | |
5841 | /* |
5842 | Supposedly acl_dbs can be huge, but only a handful of db grants |
5843 | apply to grantee or roles directly granted to grantee. |
5844 | |
5845 | Collect these applicable db grants. |
5846 | */ |
5847 | for (uint i=0 ; i < acl_dbs.elements ; i++) |
5848 | { |
5849 | ACL_DB *db= dynamic_element(&acl_dbs,i,ACL_DB*); |
5850 | if (db->host.hostname[0]) |
5851 | continue; |
5852 | if (dbname && strcmp(db->db, dbname)) |
5853 | continue; |
5854 | ACL_ROLE *r= rhash->find(db->user, strlen(db->user)); |
5855 | if (!r) |
5856 | continue; |
5857 | dbs.append(db); |
5858 | } |
5859 | dbs.sort(db_name_sort); |
5860 | |
5861 | /* |
5862 | Because dbs array is sorted by the db name, all grants for the same db |
5863 | (that should be merged) are sorted together. The grantee's ACL_DB element |
5864 | is not necessarily the first and may be not present at all. |
5865 | */ |
5866 | ACL_DB **first= NULL, *merged= NULL; |
5867 | ulong access= 0, update_flags= 0; |
5868 | for (ACL_DB **cur= dbs.front(); cur <= dbs.back(); cur++) |
5869 | { |
5870 | if (!first || (!dbname && strcmp(cur[0]->db, cur[-1]->db))) |
5871 | { // new db name series |
5872 | update_flags|= update_role_db(merged, first, access, grantee->user.str); |
5873 | merged= NULL; |
5874 | access= 0; |
5875 | first= cur; |
5876 | } |
5877 | if (strcmp(cur[0]->user, grantee->user.str) == 0) |
5878 | access|= (merged= cur[0])->initial_access; |
5879 | else |
5880 | access|= cur[0]->access; |
5881 | } |
5882 | update_flags|= update_role_db(merged, first, access, grantee->user.str); |
5883 | |
5884 | /* |
5885 | to make this code a bit simpler, we sort on deletes, to move |
5886 | deleted elements to the end of the array. strictly speaking it's |
5887 | unnecessary, it'd be faster to remove them in one O(N) array scan. |
5888 | |
5889 | on the other hand, qsort on almost sorted array is pretty fast anyway... |
5890 | */ |
5891 | if (update_flags & (2|4)) |
5892 | { // inserted or deleted, need to sort |
5893 | my_qsort((uchar*) dynamic_element(&acl_dbs,0,ACL_DB*),acl_dbs.elements, |
5894 | sizeof(ACL_DB),(qsort_cmp) acl_compare); |
5895 | } |
5896 | if (update_flags & 4) |
5897 | { // deleted, trim the end |
5898 | while (acl_dbs.elements && |
5899 | dynamic_element(&acl_dbs, acl_dbs.elements-1, ACL_DB*)->sort == 0) |
5900 | acl_dbs.elements--; |
5901 | } |
5902 | return update_flags; |
5903 | } |
5904 | |
5905 | static int table_name_sort(GRANT_TABLE * const *tbl1, GRANT_TABLE * const *tbl2) |
5906 | { |
5907 | int res = strcmp((*tbl1)->db, (*tbl2)->db); |
5908 | if (res) return res; |
5909 | return strcmp((*tbl1)->tname, (*tbl2)->tname); |
5910 | } |
5911 | |
5912 | /** |
5913 | merges column privileges for the entry 'merged' |
5914 | |
5915 | @param merged GRANT_TABLE to merge the privileges into |
5916 | @param cur first entry in the array of GRANT_TABLE's for a given table |
5917 | @param last last entry in the array of GRANT_TABLE's for a given table, |
5918 | all entries between cur and last correspond to the *same* table |
5919 | |
5920 | @return 1 if the _set of columns_ in 'merged' was changed |
5921 | (not if the _set of privileges_ was changed). |
5922 | */ |
5923 | static int update_role_columns(GRANT_TABLE *merged, |
5924 | GRANT_TABLE **cur, GRANT_TABLE **last) |
5925 | |
5926 | { |
5927 | ulong rights __attribute__((unused))= 0; |
5928 | int changed= 0; |
5929 | if (!merged->cols) |
5930 | { |
5931 | changed= merged->hash_columns.records > 0; |
5932 | my_hash_reset(&merged->hash_columns); |
5933 | return changed; |
5934 | } |
5935 | |
5936 | DBUG_EXECUTE_IF("role_merge_stats" , role_column_merges++;); |
5937 | |
5938 | HASH *mh= &merged->hash_columns; |
5939 | for (uint i=0 ; i < mh->records ; i++) |
5940 | { |
5941 | GRANT_COLUMN *col = (GRANT_COLUMN *)my_hash_element(mh, i); |
5942 | col->rights= col->init_rights; |
5943 | } |
5944 | |
5945 | for (; cur < last; cur++) |
5946 | { |
5947 | if (*cur == merged) |
5948 | continue; |
5949 | HASH *ch= &cur[0]->hash_columns; |
5950 | for (uint i=0 ; i < ch->records ; i++) |
5951 | { |
5952 | GRANT_COLUMN *ccol = (GRANT_COLUMN *)my_hash_element(ch, i); |
5953 | GRANT_COLUMN *mcol = (GRANT_COLUMN *)my_hash_search(mh, |
5954 | (uchar *)ccol->column, ccol->key_length); |
5955 | if (mcol) |
5956 | mcol->rights|= ccol->rights; |
5957 | else |
5958 | { |
5959 | changed= 1; |
5960 | my_hash_insert(mh, (uchar*)new (&grant_memroot) GRANT_COLUMN(ccol)); |
5961 | } |
5962 | } |
5963 | } |
5964 | |
5965 | for (uint i=0 ; i < mh->records ; i++) |
5966 | { |
5967 | GRANT_COLUMN *col = (GRANT_COLUMN *)my_hash_element(mh, i); |
5968 | rights|= col->rights; |
5969 | if (!col->rights) |
5970 | { |
5971 | changed= 1; |
5972 | my_hash_delete(mh, (uchar*)col); |
5973 | } |
5974 | } |
5975 | DBUG_ASSERT(rights == merged->cols); |
5976 | return changed; |
5977 | } |
5978 | |
5979 | /** |
5980 | update GRANT_TABLE for a given table and a given role with merged privileges |
5981 | |
5982 | @param merged GRANT_TABLE of the role in question (or NULL if it wasn't found) |
5983 | @param first first GRANT_TABLE in an array for the table in question |
5984 | @param last last entry in the array of GRANT_TABLE's for a given table, |
5985 | all entries between first and last correspond to the *same* table |
5986 | @param privs new table-level privileges for 'merged' |
5987 | @param cols new OR-ed column-level privileges for 'merged' |
5988 | @param role the name of the given role |
5989 | |
5990 | @return a bitmap of |
5991 | 1 - privileges were changed |
5992 | 2 - GRANT_TABLE was added |
5993 | 4 - GRANT_TABLE was deleted |
5994 | */ |
5995 | static int update_role_table_columns(GRANT_TABLE *merged, |
5996 | GRANT_TABLE **first, GRANT_TABLE **last, |
5997 | ulong privs, ulong cols, const char *role) |
5998 | { |
5999 | if (!first) |
6000 | return 0; |
6001 | |
6002 | DBUG_EXECUTE_IF("role_merge_stats" , role_table_merges++;); |
6003 | |
6004 | if (merged == NULL) |
6005 | { |
6006 | /* |
6007 | there's no GRANT_TABLE for this role (all table grants come from granted |
6008 | roles) we need to create it |
6009 | */ |
6010 | DBUG_ASSERT(privs | cols); |
6011 | merged= new (&grant_memroot) GRANT_TABLE("" , first[0]->db, role, first[0]->tname, |
6012 | privs, cols); |
6013 | merged->init_privs= merged->init_cols= 0; |
6014 | update_role_columns(merged, first, last); |
6015 | my_hash_insert(&column_priv_hash,(uchar*) merged); |
6016 | return 2; |
6017 | } |
6018 | else if ((privs | cols) == 0) |
6019 | { |
6020 | /* |
6021 | there is GRANT_TABLE object but the role has no table or column |
6022 | privileges granted (all privileges were coming from granted roles, and |
6023 | now those roles were dropped or had their privileges revoked). |
6024 | we need to remove this GRANT_TABLE |
6025 | */ |
6026 | DBUG_EXECUTE_IF("role_merge_stats" , |
6027 | role_column_merges+= MY_TEST(merged->cols);); |
6028 | my_hash_delete(&column_priv_hash,(uchar*) merged); |
6029 | return 4; |
6030 | } |
6031 | else |
6032 | { |
6033 | bool changed= merged->cols != cols || merged->privs != privs; |
6034 | merged->cols= cols; |
6035 | merged->privs= privs; |
6036 | if (update_role_columns(merged, first, last)) |
6037 | changed= true; |
6038 | return changed; |
6039 | } |
6040 | } |
6041 | |
6042 | /** |
6043 | merges table privileges from roles granted to the role 'grantee'. |
6044 | |
6045 | @return true if table privileges of the 'grantee' were changed |
6046 | |
6047 | */ |
6048 | static bool merge_role_table_and_column_privileges(ACL_ROLE *grantee, |
6049 | const char *db, const char *tname, role_hash_t *rhash) |
6050 | { |
6051 | Dynamic_array<GRANT_TABLE *> grants; |
6052 | DBUG_ASSERT(MY_TEST(db) == MY_TEST(tname)); // both must be set, or neither |
6053 | |
6054 | /* |
6055 | first, collect table/column privileges granted to |
6056 | roles in question. |
6057 | */ |
6058 | for (uint i=0 ; i < column_priv_hash.records ; i++) |
6059 | { |
6060 | GRANT_TABLE *grant= (GRANT_TABLE *) my_hash_element(&column_priv_hash, i); |
6061 | if (grant->host.hostname[0]) |
6062 | continue; |
6063 | if (tname && (strcmp(grant->db, db) || strcmp(grant->tname, tname))) |
6064 | continue; |
6065 | ACL_ROLE *r= rhash->find(grant->user, strlen(grant->user)); |
6066 | if (!r) |
6067 | continue; |
6068 | grants.append(grant); |
6069 | } |
6070 | grants.sort(table_name_sort); |
6071 | |
6072 | GRANT_TABLE **first= NULL, *merged= NULL, **cur; |
6073 | ulong privs= 0, cols= 0, update_flags= 0; |
6074 | for (cur= grants.front(); cur <= grants.back(); cur++) |
6075 | { |
6076 | if (!first || |
6077 | (!tname && (strcmp(cur[0]->db, cur[-1]->db) || |
6078 | strcmp(cur[0]->tname, cur[-1]->tname)))) |
6079 | { // new db.tname series |
6080 | update_flags|= update_role_table_columns(merged, first, cur, |
6081 | privs, cols, grantee->user.str); |
6082 | merged= NULL; |
6083 | privs= cols= 0; |
6084 | first= cur; |
6085 | } |
6086 | if (strcmp(cur[0]->user, grantee->user.str) == 0) |
6087 | { |
6088 | merged= cur[0]; |
6089 | cols|= cur[0]->init_cols; |
6090 | privs|= cur[0]->init_privs; |
6091 | } |
6092 | else |
6093 | { |
6094 | cols|= cur[0]->cols; |
6095 | privs|= cur[0]->privs; |
6096 | } |
6097 | } |
6098 | update_flags|= update_role_table_columns(merged, first, cur, |
6099 | privs, cols, grantee->user.str); |
6100 | |
6101 | return update_flags; |
6102 | } |
6103 | |
6104 | static int routine_name_sort(GRANT_NAME * const *r1, GRANT_NAME * const *r2) |
6105 | { |
6106 | int res= strcmp((*r1)->db, (*r2)->db); |
6107 | if (res) return res; |
6108 | return strcmp((*r1)->tname, (*r2)->tname); |
6109 | } |
6110 | |
6111 | /** |
6112 | update GRANT_NAME for a given routine and a given role with merged privileges |
6113 | |
6114 | @param merged GRANT_NAME of the role in question (or NULL if it wasn't found) |
6115 | @param first first GRANT_NAME in an array for the routine in question |
6116 | @param privs new routine-level privileges for 'merged' |
6117 | @param role the name of the given role |
6118 | @param hash proc_priv_hash or func_priv_hash |
6119 | |
6120 | @return a bitmap of |
6121 | 1 - privileges were changed |
6122 | 2 - GRANT_NAME was added |
6123 | 4 - GRANT_NAME was deleted |
6124 | */ |
6125 | static int update_role_routines(GRANT_NAME *merged, GRANT_NAME **first, |
6126 | ulong privs, const char *role, HASH *hash) |
6127 | { |
6128 | if (!first) |
6129 | return 0; |
6130 | |
6131 | DBUG_EXECUTE_IF("role_merge_stats" , role_routine_merges++;); |
6132 | |
6133 | if (merged == NULL) |
6134 | { |
6135 | /* |
6136 | there's no GRANT_NAME for this role (all routine grants come from granted |
6137 | roles) we need to create it |
6138 | */ |
6139 | DBUG_ASSERT(privs); |
6140 | merged= new (&grant_memroot) GRANT_NAME("" , first[0]->db, role, first[0]->tname, |
6141 | privs, true); |
6142 | merged->init_privs= 0; // all privs are inherited |
6143 | my_hash_insert(hash, (uchar *)merged); |
6144 | return 2; |
6145 | } |
6146 | else if (privs == 0) |
6147 | { |
6148 | /* |
6149 | there is GRANT_NAME but the role has no privileges granted |
6150 | (all privileges were coming from granted roles, and now those roles |
6151 | were dropped or had their privileges revoked). |
6152 | we need to remove this entry |
6153 | */ |
6154 | my_hash_delete(hash, (uchar*)merged); |
6155 | return 4; |
6156 | } |
6157 | else if (merged->privs != privs) |
6158 | { |
6159 | /* this is easy */ |
6160 | merged->privs= privs; |
6161 | return 1; |
6162 | } |
6163 | return 0; |
6164 | } |
6165 | |
6166 | /** |
6167 | merges routine privileges from roles granted to the role 'grantee'. |
6168 | |
6169 | @return true if routine privileges of the 'grantee' were changed |
6170 | |
6171 | */ |
6172 | static bool merge_role_routine_grant_privileges(ACL_ROLE *grantee, |
6173 | const char *db, const char *tname, role_hash_t *rhash, HASH *hash) |
6174 | { |
6175 | ulong update_flags= 0; |
6176 | |
6177 | DBUG_ASSERT(MY_TEST(db) == MY_TEST(tname)); // both must be set, or neither |
6178 | |
6179 | Dynamic_array<GRANT_NAME *> grants; |
6180 | |
6181 | /* first, collect routine privileges granted to roles in question */ |
6182 | for (uint i=0 ; i < hash->records ; i++) |
6183 | { |
6184 | GRANT_NAME *grant= (GRANT_NAME *) my_hash_element(hash, i); |
6185 | if (grant->host.hostname[0]) |
6186 | continue; |
6187 | if (tname && (strcmp(grant->db, db) || strcmp(grant->tname, tname))) |
6188 | continue; |
6189 | ACL_ROLE *r= rhash->find(grant->user, strlen(grant->user)); |
6190 | if (!r) |
6191 | continue; |
6192 | grants.append(grant); |
6193 | } |
6194 | grants.sort(routine_name_sort); |
6195 | |
6196 | GRANT_NAME **first= NULL, *merged= NULL; |
6197 | ulong privs= 0 ; |
6198 | for (GRANT_NAME **cur= grants.front(); cur <= grants.back(); cur++) |
6199 | { |
6200 | if (!first || |
6201 | (!tname && (strcmp(cur[0]->db, cur[-1]->db) || |
6202 | strcmp(cur[0]->tname, cur[-1]->tname)))) |
6203 | { // new db.tname series |
6204 | update_flags|= update_role_routines(merged, first, privs, |
6205 | grantee->user.str, hash); |
6206 | merged= NULL; |
6207 | privs= 0; |
6208 | first= cur; |
6209 | } |
6210 | if (strcmp(cur[0]->user, grantee->user.str) == 0) |
6211 | { |
6212 | merged= cur[0]; |
6213 | privs|= cur[0]->init_privs; |
6214 | } |
6215 | else |
6216 | { |
6217 | privs|= cur[0]->privs; |
6218 | } |
6219 | } |
6220 | update_flags|= update_role_routines(merged, first, privs, |
6221 | grantee->user.str, hash); |
6222 | return update_flags; |
6223 | } |
6224 | |
6225 | /** |
6226 | update privileges of the 'grantee' from all roles, granted to it |
6227 | */ |
6228 | static int merge_role_privileges(ACL_ROLE *role __attribute__((unused)), |
6229 | ACL_ROLE *grantee, void *context) |
6230 | { |
6231 | PRIVS_TO_MERGE *data= (PRIVS_TO_MERGE *)context; |
6232 | |
6233 | DBUG_ASSERT(grantee->counter > 0); |
6234 | if (--grantee->counter) |
6235 | return 1; // don't recurse into grantee just yet |
6236 | |
6237 | grantee->counter= 1; // Mark the grantee as merged. |
6238 | |
6239 | /* if we'll do db/table/routine privileges, create a hash of role names */ |
6240 | role_hash_t role_hash(role_key); |
6241 | if (data->what != PRIVS_TO_MERGE::GLOBAL) |
6242 | { |
6243 | role_hash.insert(grantee); |
6244 | for (uint i= 0; i < grantee->role_grants.elements; i++) |
6245 | role_hash.insert(*dynamic_element(&grantee->role_grants, i, ACL_ROLE**)); |
6246 | } |
6247 | |
6248 | bool all= data->what == PRIVS_TO_MERGE::ALL; |
6249 | bool changed= false; |
6250 | if (all || data->what == PRIVS_TO_MERGE::GLOBAL) |
6251 | changed|= merge_role_global_privileges(grantee); |
6252 | if (all || data->what == PRIVS_TO_MERGE::DB) |
6253 | changed|= merge_role_db_privileges(grantee, data->db, &role_hash); |
6254 | if (all || data->what == PRIVS_TO_MERGE::TABLE_COLUMN) |
6255 | changed|= merge_role_table_and_column_privileges(grantee, |
6256 | data->db, data->name, &role_hash); |
6257 | if (all || data->what == PRIVS_TO_MERGE::PROC) |
6258 | changed|= merge_role_routine_grant_privileges(grantee, |
6259 | data->db, data->name, &role_hash, &proc_priv_hash); |
6260 | if (all || data->what == PRIVS_TO_MERGE::FUNC) |
6261 | changed|= merge_role_routine_grant_privileges(grantee, |
6262 | data->db, data->name, &role_hash, &func_priv_hash); |
6263 | if (all || data->what == PRIVS_TO_MERGE::PACKAGE_SPEC) |
6264 | changed|= merge_role_routine_grant_privileges(grantee, |
6265 | data->db, data->name, &role_hash, |
6266 | &package_spec_priv_hash); |
6267 | if (all || data->what == PRIVS_TO_MERGE::PACKAGE_BODY) |
6268 | changed|= merge_role_routine_grant_privileges(grantee, |
6269 | data->db, data->name, &role_hash, |
6270 | &package_body_priv_hash); |
6271 | return !changed; // don't recurse into the subgraph if privs didn't change |
6272 | } |
6273 | |
6274 | static bool merge_one_role_privileges(ACL_ROLE *grantee) |
6275 | { |
6276 | PRIVS_TO_MERGE data= { PRIVS_TO_MERGE::ALL, 0, 0 }; |
6277 | grantee->counter= 1; |
6278 | return merge_role_privileges(0, grantee, &data); |
6279 | } |
6280 | |
6281 | /***************************************************************** |
6282 | End of the role privilege propagation and graph traversal code |
6283 | ******************************************************************/ |
6284 | |
6285 | static bool has_auth(LEX_USER *user, LEX *lex) |
6286 | { |
6287 | return user->pwtext.length || user->pwhash.length || user->plugin.length || user->auth.length || |
6288 | lex->ssl_type != SSL_TYPE_NOT_SPECIFIED || lex->ssl_cipher || |
6289 | lex->x509_issuer || lex->x509_subject || |
6290 | lex->mqh.specified_limits; |
6291 | } |
6292 | |
6293 | static bool fix_and_copy_user(LEX_USER *to, LEX_USER *from, THD *thd) |
6294 | { |
6295 | if (to != from) |
6296 | { |
6297 | /* preserve authentication information, if LEX_USER was reallocated */ |
6298 | to->pwtext= from->pwtext; |
6299 | to->pwhash= from->pwhash; |
6300 | to->plugin= from->plugin; |
6301 | to->auth= from->auth; |
6302 | } |
6303 | |
6304 | if (fix_lex_user(thd, to)) |
6305 | return true; |
6306 | |
6307 | return false; |
6308 | } |
6309 | |
6310 | static bool copy_and_check_auth(LEX_USER *to, LEX_USER *from, THD *thd) |
6311 | { |
6312 | if (fix_and_copy_user(to, from, thd)) |
6313 | return true; |
6314 | |
6315 | // if changing auth for an existing user |
6316 | if (has_auth(to, thd->lex) && find_user_exact(to->host.str, to->user.str)) |
6317 | { |
6318 | mysql_mutex_unlock(&acl_cache->lock); |
6319 | bool res= check_alter_user(thd, to->host.str, to->user.str); |
6320 | mysql_mutex_lock(&acl_cache->lock); |
6321 | return res; |
6322 | } |
6323 | |
6324 | return false; |
6325 | } |
6326 | |
6327 | |
6328 | /* |
6329 | Store table level and column level grants in the privilege tables |
6330 | |
6331 | SYNOPSIS |
6332 | mysql_table_grant() |
6333 | thd Thread handle |
6334 | table_list List of tables to give grant |
6335 | user_list List of users to give grant |
6336 | columns List of columns to give grant |
6337 | rights Table level grant |
6338 | revoke_grant Set to 1 if this is a REVOKE command |
6339 | |
6340 | RETURN |
6341 | FALSE ok |
6342 | TRUE error |
6343 | */ |
6344 | |
6345 | int mysql_table_grant(THD *thd, TABLE_LIST *table_list, |
6346 | List <LEX_USER> &user_list, |
6347 | List <LEX_COLUMN> &columns, ulong rights, |
6348 | bool revoke_grant) |
6349 | { |
6350 | ulong column_priv= 0; |
6351 | int result; |
6352 | List_iterator <LEX_USER> str_list (user_list); |
6353 | LEX_USER *Str, *tmp_Str; |
6354 | bool create_new_users=0; |
6355 | const char *db_name, *table_name; |
6356 | DBUG_ENTER("mysql_table_grant" ); |
6357 | |
6358 | if (rights & ~TABLE_ACLS) |
6359 | { |
6360 | my_message(ER_ILLEGAL_GRANT_FOR_TABLE, |
6361 | ER_THD(thd, ER_ILLEGAL_GRANT_FOR_TABLE), |
6362 | MYF(0)); |
6363 | DBUG_RETURN(TRUE); |
6364 | } |
6365 | |
6366 | if (!revoke_grant) |
6367 | { |
6368 | if (columns.elements) |
6369 | { |
6370 | class LEX_COLUMN *column; |
6371 | List_iterator <LEX_COLUMN> column_iter(columns); |
6372 | |
6373 | if (open_normal_and_derived_tables(thd, table_list, 0, DT_PREPARE)) |
6374 | DBUG_RETURN(TRUE); |
6375 | |
6376 | while ((column = column_iter++)) |
6377 | { |
6378 | uint unused_field_idx= NO_CACHED_FIELD_INDEX; |
6379 | TABLE_LIST *dummy; |
6380 | Field *f=find_field_in_table_ref(thd, table_list, column->column.ptr(), |
6381 | column->column.length(), |
6382 | column->column.ptr(), NULL, NULL, |
6383 | NULL, TRUE, FALSE, |
6384 | &unused_field_idx, FALSE, &dummy); |
6385 | if (unlikely(f == (Field*)0)) |
6386 | { |
6387 | my_error(ER_BAD_FIELD_ERROR, MYF(0), |
6388 | column->column.c_ptr(), table_list->alias.str); |
6389 | DBUG_RETURN(TRUE); |
6390 | } |
6391 | if (unlikely(f == (Field *)-1)) |
6392 | DBUG_RETURN(TRUE); |
6393 | column_priv|= column->rights; |
6394 | } |
6395 | close_mysql_tables(thd); |
6396 | } |
6397 | else |
6398 | { |
6399 | if (!(rights & CREATE_ACL)) |
6400 | { |
6401 | if (!ha_table_exists(thd, &table_list->db, &table_list->table_name)) |
6402 | { |
6403 | my_error(ER_NO_SUCH_TABLE, MYF(0), table_list->db.str, |
6404 | table_list->alias.str); |
6405 | DBUG_RETURN(TRUE); |
6406 | } |
6407 | } |
6408 | if (table_list->grant.want_privilege) |
6409 | { |
6410 | char command[128]; |
6411 | get_privilege_desc(command, sizeof(command), |
6412 | table_list->grant.want_privilege); |
6413 | my_error(ER_TABLEACCESS_DENIED_ERROR, MYF(0), |
6414 | command, thd->security_ctx->priv_user, |
6415 | thd->security_ctx->host_or_ip, table_list->alias.str); |
6416 | DBUG_RETURN(-1); |
6417 | } |
6418 | } |
6419 | } |
6420 | |
6421 | /* |
6422 | Open the mysql.user and mysql.tables_priv tables. |
6423 | Don't open column table if we don't need it ! |
6424 | */ |
6425 | int maybe_columns_priv= 0; |
6426 | if (column_priv || |
6427 | (revoke_grant && ((rights & COL_ACLS) || columns.elements))) |
6428 | maybe_columns_priv= Table_columns_priv; |
6429 | |
6430 | /* |
6431 | The lock api is depending on the thd->lex variable which needs to be |
6432 | re-initialized. |
6433 | */ |
6434 | Query_tables_list backup; |
6435 | thd->lex->reset_n_backup_query_tables_list(&backup); |
6436 | /* |
6437 | Restore Query_tables_list::sql_command value, which was reset |
6438 | above, as the code writing query to the binary log assumes that |
6439 | this value corresponds to the statement being executed. |
6440 | */ |
6441 | thd->lex->sql_command= backup.sql_command; |
6442 | |
6443 | Grant_tables tables(Table_user | Table_tables_priv | maybe_columns_priv, |
6444 | TL_WRITE); |
6445 | if ((result= tables.open_and_lock(thd))) |
6446 | { |
6447 | thd->lex->restore_backup_query_tables_list(&backup); |
6448 | DBUG_RETURN(result != 1); |
6449 | } |
6450 | |
6451 | if (!revoke_grant) |
6452 | create_new_users= test_if_create_new_users(thd); |
6453 | mysql_rwlock_wrlock(&LOCK_grant); |
6454 | mysql_mutex_lock(&acl_cache->lock); |
6455 | MEM_ROOT *old_root= thd->mem_root; |
6456 | thd->mem_root= &grant_memroot; |
6457 | grant_version++; |
6458 | |
6459 | while ((tmp_Str = str_list++)) |
6460 | { |
6461 | int error; |
6462 | GRANT_TABLE *grant_table; |
6463 | if (!(Str= get_current_user(thd, tmp_Str, false))) |
6464 | { |
6465 | result= TRUE; |
6466 | continue; |
6467 | } |
6468 | /* Create user if needed */ |
6469 | error= copy_and_check_auth(Str, tmp_Str, thd) || |
6470 | replace_user_table(thd, tables.user_table(), *Str, |
6471 | 0, revoke_grant, create_new_users, |
6472 | MY_TEST(thd->variables.sql_mode & |
6473 | MODE_NO_AUTO_CREATE_USER)); |
6474 | if (unlikely(error)) |
6475 | { |
6476 | result= TRUE; // Remember error |
6477 | continue; // Add next user |
6478 | } |
6479 | |
6480 | db_name= table_list->get_db_name(); |
6481 | table_name= table_list->get_table_name(); |
6482 | |
6483 | /* Find/create cached table grant */ |
6484 | grant_table= table_hash_search(Str->host.str, NullS, db_name, |
6485 | Str->user.str, table_name, 1); |
6486 | if (!grant_table) |
6487 | { |
6488 | if (revoke_grant) |
6489 | { |
6490 | my_error(ER_NONEXISTING_TABLE_GRANT, MYF(0), |
6491 | Str->user.str, Str->host.str, table_list->table_name.str); |
6492 | result= TRUE; |
6493 | continue; |
6494 | } |
6495 | grant_table = new GRANT_TABLE (Str->host.str, db_name, |
6496 | Str->user.str, table_name, |
6497 | rights, |
6498 | column_priv); |
6499 | if (!grant_table || |
6500 | my_hash_insert(&column_priv_hash,(uchar*) grant_table)) |
6501 | { |
6502 | result= TRUE; /* purecov: deadcode */ |
6503 | continue; /* purecov: deadcode */ |
6504 | } |
6505 | } |
6506 | |
6507 | /* If revoke_grant, calculate the new column privilege for tables_priv */ |
6508 | if (revoke_grant) |
6509 | { |
6510 | class LEX_COLUMN *column; |
6511 | List_iterator <LEX_COLUMN> column_iter(columns); |
6512 | GRANT_COLUMN *grant_column; |
6513 | |
6514 | /* Fix old grants */ |
6515 | while ((column = column_iter++)) |
6516 | { |
6517 | grant_column = column_hash_search(grant_table, |
6518 | column->column.ptr(), |
6519 | column->column.length()); |
6520 | if (grant_column) |
6521 | grant_column->rights&= ~(column->rights | rights); |
6522 | } |
6523 | /* scan trough all columns to get new column grant */ |
6524 | column_priv= 0; |
6525 | for (uint idx=0 ; idx < grant_table->hash_columns.records ; idx++) |
6526 | { |
6527 | grant_column= (GRANT_COLUMN*) |
6528 | my_hash_element(&grant_table->hash_columns, idx); |
6529 | grant_column->rights&= ~rights; // Fix other columns |
6530 | column_priv|= grant_column->rights; |
6531 | } |
6532 | } |
6533 | else |
6534 | { |
6535 | column_priv|= grant_table->cols; |
6536 | } |
6537 | |
6538 | |
6539 | /* update table and columns */ |
6540 | |
6541 | /* TODO(cvicentiu) refactor replace_table_table to use Tables_priv_table |
6542 | instead of TABLE directly. */ |
6543 | if (replace_table_table(thd, grant_table, tables.tables_priv_table().table(), |
6544 | *Str, db_name, table_name, |
6545 | rights, column_priv, revoke_grant)) |
6546 | { |
6547 | /* Should only happen if table is crashed */ |
6548 | result= TRUE; /* purecov: deadcode */ |
6549 | } |
6550 | else if (tables.columns_priv_table().table_exists()) |
6551 | { |
6552 | /* TODO(cvicentiu) refactor replace_column_table to use Columns_priv_table |
6553 | instead of TABLE directly. */ |
6554 | if (replace_column_table(grant_table, tables.columns_priv_table().table(), |
6555 | *Str, columns, db_name, table_name, rights, |
6556 | revoke_grant)) |
6557 | { |
6558 | result= TRUE; |
6559 | } |
6560 | } |
6561 | if (Str->is_role()) |
6562 | propagate_role_grants(find_acl_role(Str->user.str), |
6563 | PRIVS_TO_MERGE::TABLE_COLUMN, db_name, table_name); |
6564 | } |
6565 | |
6566 | thd->mem_root= old_root; |
6567 | mysql_mutex_unlock(&acl_cache->lock); |
6568 | |
6569 | if (!result) /* success */ |
6570 | { |
6571 | result= write_bin_log(thd, TRUE, thd->query(), thd->query_length()); |
6572 | } |
6573 | |
6574 | mysql_rwlock_unlock(&LOCK_grant); |
6575 | |
6576 | if (!result) /* success */ |
6577 | my_ok(thd); |
6578 | |
6579 | thd->lex->restore_backup_query_tables_list(&backup); |
6580 | DBUG_RETURN(result); |
6581 | } |
6582 | |
6583 | |
6584 | /** |
6585 | Store routine level grants in the privilege tables |
6586 | |
6587 | @param thd Thread handle |
6588 | @param table_list List of routines to give grant |
6589 | @param sph SP handler |
6590 | @param user_list List of users to give grant |
6591 | @param rights Table level grant |
6592 | @param revoke_grant Is this is a REVOKE command? |
6593 | |
6594 | @return |
6595 | @retval FALSE Success. |
6596 | @retval TRUE An error occurred. |
6597 | */ |
6598 | |
6599 | bool mysql_routine_grant(THD *thd, TABLE_LIST *table_list, |
6600 | const Sp_handler *sph, |
6601 | List <LEX_USER> &user_list, ulong rights, |
6602 | bool revoke_grant, bool write_to_binlog) |
6603 | { |
6604 | List_iterator <LEX_USER> str_list (user_list); |
6605 | LEX_USER *Str, *tmp_Str; |
6606 | bool create_new_users= 0, result; |
6607 | const char *db_name, *table_name; |
6608 | DBUG_ENTER("mysql_routine_grant" ); |
6609 | |
6610 | if (rights & ~PROC_ACLS) |
6611 | { |
6612 | my_message(ER_ILLEGAL_GRANT_FOR_TABLE, |
6613 | ER_THD(thd, ER_ILLEGAL_GRANT_FOR_TABLE), |
6614 | MYF(0)); |
6615 | DBUG_RETURN(TRUE); |
6616 | } |
6617 | |
6618 | if (!revoke_grant) |
6619 | { |
6620 | if (sph->sp_exist_routines(thd, table_list)) |
6621 | DBUG_RETURN(TRUE); |
6622 | } |
6623 | |
6624 | Grant_tables tables(Table_user | Table_procs_priv, TL_WRITE); |
6625 | if ((result= tables.open_and_lock(thd))) |
6626 | DBUG_RETURN(result != 1); |
6627 | |
6628 | DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); |
6629 | |
6630 | if (!revoke_grant) |
6631 | create_new_users= test_if_create_new_users(thd); |
6632 | mysql_rwlock_wrlock(&LOCK_grant); |
6633 | mysql_mutex_lock(&acl_cache->lock); |
6634 | MEM_ROOT *old_root= thd->mem_root; |
6635 | thd->mem_root= &grant_memroot; |
6636 | |
6637 | DBUG_PRINT("info" ,("now time to iterate and add users" )); |
6638 | |
6639 | while ((tmp_Str= str_list++)) |
6640 | { |
6641 | GRANT_NAME *grant_name; |
6642 | if (!(Str= get_current_user(thd, tmp_Str, false))) |
6643 | { |
6644 | result= TRUE; |
6645 | continue; |
6646 | } |
6647 | /* Create user if needed */ |
6648 | if (copy_and_check_auth(Str, tmp_Str, thd) || |
6649 | replace_user_table(thd, tables.user_table(), *Str, |
6650 | 0, revoke_grant, create_new_users, |
6651 | MY_TEST(thd->variables.sql_mode & |
6652 | MODE_NO_AUTO_CREATE_USER))) |
6653 | { |
6654 | result= TRUE; |
6655 | continue; |
6656 | } |
6657 | |
6658 | db_name= table_list->db.str; |
6659 | table_name= table_list->table_name.str; |
6660 | grant_name= routine_hash_search(Str->host.str, NullS, db_name, |
6661 | Str->user.str, table_name, sph, 1); |
6662 | if (!grant_name || !grant_name->init_privs) |
6663 | { |
6664 | if (revoke_grant) |
6665 | { |
6666 | my_error(ER_NONEXISTING_PROC_GRANT, MYF(0), |
6667 | Str->user.str, Str->host.str, table_name); |
6668 | result= TRUE; |
6669 | continue; |
6670 | } |
6671 | grant_name= new GRANT_NAME(Str->host.str, db_name, |
6672 | Str->user.str, table_name, |
6673 | rights, TRUE); |
6674 | if (!grant_name || |
6675 | my_hash_insert(sph->get_priv_hash(), (uchar*) grant_name)) |
6676 | { |
6677 | result= TRUE; |
6678 | continue; |
6679 | } |
6680 | } |
6681 | |
6682 | /* TODO(cvicentiu) refactor replace_routine_table to use Tables_procs_priv |
6683 | instead of TABLE directly. */ |
6684 | if (tables.procs_priv_table().no_such_table() || |
6685 | replace_routine_table(thd, grant_name, tables.procs_priv_table().table(), |
6686 | *Str, db_name, table_name, sph, rights, |
6687 | revoke_grant) != 0) |
6688 | { |
6689 | result= TRUE; |
6690 | continue; |
6691 | } |
6692 | if (Str->is_role()) |
6693 | propagate_role_grants(find_acl_role(Str->user.str), |
6694 | sp_privs_to_merge(sph->type()), |
6695 | db_name, table_name); |
6696 | } |
6697 | thd->mem_root= old_root; |
6698 | mysql_mutex_unlock(&acl_cache->lock); |
6699 | |
6700 | if (write_to_binlog) |
6701 | { |
6702 | if (write_bin_log(thd, FALSE, thd->query(), thd->query_length())) |
6703 | result= TRUE; |
6704 | } |
6705 | |
6706 | mysql_rwlock_unlock(&LOCK_grant); |
6707 | |
6708 | /* Tables are automatically closed */ |
6709 | DBUG_RETURN(result); |
6710 | } |
6711 | |
6712 | /** |
6713 | append a user or role name to a buffer that will be later used as an error message |
6714 | */ |
6715 | static void append_user(THD *thd, String *str, |
6716 | const LEX_CSTRING *u, const LEX_CSTRING *h) |
6717 | { |
6718 | if (str->length()) |
6719 | str->append(','); |
6720 | append_query_string(system_charset_info, str, u->str, u->length, |
6721 | thd->variables.sql_mode & MODE_NO_BACKSLASH_ESCAPES); |
6722 | /* hostname part is not relevant for roles, it is always empty */ |
6723 | if (u->length == 0 || h->length != 0) |
6724 | { |
6725 | str->append('@'); |
6726 | append_query_string(system_charset_info, str, h->str, h->length, |
6727 | thd->variables.sql_mode & MODE_NO_BACKSLASH_ESCAPES); |
6728 | } |
6729 | } |
6730 | |
6731 | static void append_user(THD *thd, String *str, LEX_USER *user) |
6732 | { |
6733 | append_user(thd, str, & user->user, & user->host); |
6734 | } |
6735 | |
6736 | /** |
6737 | append a string to a buffer that will be later used as an error message |
6738 | |
6739 | @note |
6740 | a string can be either CURRENT_USER or CURRENT_ROLE or NONE, it should be |
6741 | neither quoted nor escaped. |
6742 | */ |
6743 | static void append_str(String *str, const char *s, size_t l) |
6744 | { |
6745 | if (str->length()) |
6746 | str->append(','); |
6747 | str->append(s, l); |
6748 | } |
6749 | |
6750 | static int can_grant_role_callback(ACL_USER_BASE *grantee, |
6751 | ACL_ROLE *role, void *data) |
6752 | { |
6753 | ROLE_GRANT_PAIR *pair; |
6754 | |
6755 | if (role != (ACL_ROLE*)data) |
6756 | return 0; // keep searching |
6757 | |
6758 | if (grantee->flags & IS_ROLE) |
6759 | pair= find_role_grant_pair(&grantee->user, &empty_clex_str, &role->user); |
6760 | else |
6761 | { |
6762 | ACL_USER *user= (ACL_USER *)grantee; |
6763 | LEX_CSTRING host= { user->host.hostname, user->hostname_length }; |
6764 | pair= find_role_grant_pair(&user->user, &host, &role->user); |
6765 | } |
6766 | if (!pair->with_admin) |
6767 | return 0; // keep searching |
6768 | |
6769 | return -1; // abort the traversal |
6770 | } |
6771 | |
6772 | |
6773 | /* |
6774 | One can only grant a role if SELECT * FROM I_S.APPLICABLE_ROLES shows this |
6775 | role as grantable. |
6776 | |
6777 | What this really means - we need to traverse role graph for the current user |
6778 | looking for our role being granted with the admin option. |
6779 | */ |
6780 | static bool can_grant_role(THD *thd, ACL_ROLE *role) |
6781 | { |
6782 | Security_context *sctx= thd->security_ctx; |
6783 | |
6784 | if (!sctx->user) // replication |
6785 | return true; |
6786 | |
6787 | ACL_USER *grantee= find_user_exact(sctx->priv_host, sctx->priv_user); |
6788 | if (!grantee) |
6789 | return false; |
6790 | |
6791 | return traverse_role_graph_down(grantee, role, NULL, |
6792 | can_grant_role_callback) == -1; |
6793 | } |
6794 | |
6795 | |
6796 | bool mysql_grant_role(THD *thd, List <LEX_USER> &list, bool revoke) |
6797 | { |
6798 | DBUG_ENTER("mysql_grant_role" ); |
6799 | /* |
6800 | The first entry in the list is the granted role. Need at least two |
6801 | entries for the command to be valid |
6802 | */ |
6803 | DBUG_ASSERT(list.elements >= 2); |
6804 | int result; |
6805 | bool create_new_user, no_auto_create_user; |
6806 | String wrong_users; |
6807 | LEX_USER *user, *granted_role; |
6808 | LEX_CSTRING rolename; |
6809 | LEX_CSTRING username; |
6810 | LEX_CSTRING hostname; |
6811 | ACL_ROLE *role, *role_as_user; |
6812 | |
6813 | List_iterator <LEX_USER> user_list(list); |
6814 | granted_role= user_list++; |
6815 | if (!(granted_role= get_current_user(thd, granted_role))) |
6816 | DBUG_RETURN(TRUE); |
6817 | |
6818 | DBUG_ASSERT(granted_role->is_role()); |
6819 | rolename= granted_role->user; |
6820 | |
6821 | create_new_user= test_if_create_new_users(thd); |
6822 | no_auto_create_user= MY_TEST(thd->variables.sql_mode & |
6823 | MODE_NO_AUTO_CREATE_USER); |
6824 | |
6825 | Grant_tables tables(Table_user | Table_roles_mapping, TL_WRITE); |
6826 | if ((result= tables.open_and_lock(thd))) |
6827 | DBUG_RETURN(result != 1); |
6828 | |
6829 | mysql_rwlock_wrlock(&LOCK_grant); |
6830 | mysql_mutex_lock(&acl_cache->lock); |
6831 | if (!(role= find_acl_role(rolename.str))) |
6832 | { |
6833 | mysql_mutex_unlock(&acl_cache->lock); |
6834 | mysql_rwlock_unlock(&LOCK_grant); |
6835 | my_error(ER_INVALID_ROLE, MYF(0), rolename.str); |
6836 | DBUG_RETURN(TRUE); |
6837 | } |
6838 | |
6839 | if (!can_grant_role(thd, role)) |
6840 | { |
6841 | mysql_mutex_unlock(&acl_cache->lock); |
6842 | mysql_rwlock_unlock(&LOCK_grant); |
6843 | my_error(ER_ACCESS_DENIED_NO_PASSWORD_ERROR, MYF(0), |
6844 | thd->security_ctx->priv_user, thd->security_ctx->priv_host); |
6845 | DBUG_RETURN(TRUE); |
6846 | } |
6847 | |
6848 | while ((user= user_list++)) |
6849 | { |
6850 | role_as_user= NULL; |
6851 | /* current_role is treated slightly different */ |
6852 | if (user->user.str == current_role.str) |
6853 | { |
6854 | /* current_role is NONE */ |
6855 | if (!thd->security_ctx->priv_role[0]) |
6856 | { |
6857 | my_error(ER_INVALID_ROLE, MYF(0), "NONE" ); |
6858 | append_str(&wrong_users, STRING_WITH_LEN("NONE" )); |
6859 | result= 1; |
6860 | continue; |
6861 | } |
6862 | if (!(role_as_user= find_acl_role(thd->security_ctx->priv_role))) |
6863 | { |
6864 | LEX_CSTRING ls= { thd->security_ctx->priv_role, |
6865 | strlen(thd->security_ctx->priv_role) }; |
6866 | append_user(thd, &wrong_users, &ls, &empty_clex_str); |
6867 | result= 1; |
6868 | continue; |
6869 | } |
6870 | |
6871 | /* can not grant current_role to current_role */ |
6872 | if (granted_role->user.str == current_role.str) |
6873 | { |
6874 | append_user(thd, &wrong_users, &role_as_user->user, &empty_clex_str); |
6875 | result= 1; |
6876 | continue; |
6877 | } |
6878 | username.str= thd->security_ctx->priv_role; |
6879 | username.length= strlen(username.str); |
6880 | hostname= empty_clex_str; |
6881 | } |
6882 | else if (user->user.str == current_user.str) |
6883 | { |
6884 | username.str= thd->security_ctx->priv_user; |
6885 | username.length= strlen(username.str); |
6886 | hostname.str= thd->security_ctx->priv_host; |
6887 | hostname.length= strlen(hostname.str); |
6888 | } |
6889 | else |
6890 | { |
6891 | username= user->user; |
6892 | if (user->host.str) |
6893 | hostname= user->host; |
6894 | else |
6895 | if ((role_as_user= find_acl_role(user->user.str))) |
6896 | hostname= empty_clex_str; |
6897 | else |
6898 | { |
6899 | if (is_invalid_role_name(username.str)) |
6900 | { |
6901 | append_user(thd, &wrong_users, &username, &empty_clex_str); |
6902 | result= 1; |
6903 | continue; |
6904 | } |
6905 | hostname= host_not_specified; |
6906 | } |
6907 | } |
6908 | |
6909 | ROLE_GRANT_PAIR *hash_entry= find_role_grant_pair(&username, &hostname, |
6910 | &rolename); |
6911 | ACL_USER_BASE *grantee= role_as_user; |
6912 | |
6913 | if (has_auth(user, thd->lex)) |
6914 | DBUG_ASSERT(!grantee); |
6915 | else if (!grantee) |
6916 | grantee= find_user_exact(hostname.str, username.str); |
6917 | |
6918 | if (!grantee && !revoke) |
6919 | { |
6920 | LEX_USER user_combo = *user; |
6921 | user_combo.host = hostname; |
6922 | user_combo.user = username; |
6923 | |
6924 | if (copy_and_check_auth(&user_combo, &user_combo, thd) || |
6925 | replace_user_table(thd, tables.user_table(), user_combo, 0, |
6926 | false, create_new_user, |
6927 | no_auto_create_user)) |
6928 | { |
6929 | append_user(thd, &wrong_users, &username, &hostname); |
6930 | result= 1; |
6931 | continue; |
6932 | } |
6933 | grantee= find_user_exact(hostname.str, username.str); |
6934 | |
6935 | /* either replace_user_table failed, or we've added the user */ |
6936 | DBUG_ASSERT(grantee); |
6937 | } |
6938 | |
6939 | if (!grantee) |
6940 | { |
6941 | append_user(thd, &wrong_users, &username, &hostname); |
6942 | result= 1; |
6943 | continue; |
6944 | } |
6945 | |
6946 | if (!revoke) |
6947 | { |
6948 | if (hash_entry) |
6949 | { |
6950 | // perhaps, updating an existing grant, adding WITH ADMIN OPTION |
6951 | } |
6952 | else |
6953 | { |
6954 | add_role_user_mapping(grantee, role); |
6955 | |
6956 | /* |
6957 | Check if this grant would cause a cycle. It only needs to be run |
6958 | if we're granting a role to a role |
6959 | */ |
6960 | if (role_as_user && |
6961 | traverse_role_graph_down(role, 0, 0, 0) == ROLE_CYCLE_FOUND) |
6962 | { |
6963 | append_user(thd, &wrong_users, &username, &empty_clex_str); |
6964 | result= 1; |
6965 | undo_add_role_user_mapping(grantee, role); |
6966 | continue; |
6967 | } |
6968 | } |
6969 | } |
6970 | else |
6971 | { |
6972 | /* grant was already removed or never existed */ |
6973 | if (!hash_entry) |
6974 | { |
6975 | append_user(thd, &wrong_users, &username, &hostname); |
6976 | result= 1; |
6977 | continue; |
6978 | } |
6979 | if (thd->lex->with_admin_option) |
6980 | { |
6981 | // only revoking an admin option, not the complete grant |
6982 | } |
6983 | else |
6984 | { |
6985 | /* revoke a role grant */ |
6986 | remove_role_user_mapping(grantee, role); |
6987 | } |
6988 | } |
6989 | |
6990 | /* write into the roles_mapping table */ |
6991 | /* TODO(cvicentiu) refactor replace_roles_mapping_table to use |
6992 | Roles_mapping_table instead of TABLE directly. */ |
6993 | if (replace_roles_mapping_table(tables.roles_mapping_table().table(), |
6994 | &username, &hostname, &rolename, |
6995 | thd->lex->with_admin_option, |
6996 | hash_entry, revoke)) |
6997 | { |
6998 | append_user(thd, &wrong_users, &username, &empty_clex_str); |
6999 | result= 1; |
7000 | if (!revoke) |
7001 | { |
7002 | /* need to remove the mapping added previously */ |
7003 | undo_add_role_user_mapping(grantee, role); |
7004 | } |
7005 | else |
7006 | { |
7007 | /* need to restore the mapping deleted previously */ |
7008 | add_role_user_mapping(grantee, role); |
7009 | } |
7010 | continue; |
7011 | } |
7012 | update_role_mapping(&username, &hostname, &rolename, |
7013 | thd->lex->with_admin_option, hash_entry, revoke); |
7014 | |
7015 | /* |
7016 | Only need to propagate grants when granting/revoking a role to/from |
7017 | a role |
7018 | */ |
7019 | if (role_as_user && merge_one_role_privileges(role_as_user) == 0) |
7020 | propagate_role_grants(role_as_user, PRIVS_TO_MERGE::ALL); |
7021 | } |
7022 | |
7023 | mysql_mutex_unlock(&acl_cache->lock); |
7024 | |
7025 | if (result) |
7026 | my_error(revoke ? ER_CANNOT_REVOKE_ROLE : ER_CANNOT_GRANT_ROLE, MYF(0), |
7027 | rolename.str, wrong_users.c_ptr_safe()); |
7028 | else |
7029 | result= write_bin_log(thd, TRUE, thd->query(), thd->query_length()); |
7030 | |
7031 | mysql_rwlock_unlock(&LOCK_grant); |
7032 | |
7033 | DBUG_RETURN(result); |
7034 | } |
7035 | |
7036 | |
7037 | bool mysql_grant(THD *thd, const char *db, List <LEX_USER> &list, |
7038 | ulong rights, bool revoke_grant, bool is_proxy) |
7039 | { |
7040 | List_iterator <LEX_USER> str_list (list); |
7041 | LEX_USER *Str, *tmp_Str, *proxied_user= NULL; |
7042 | char tmp_db[SAFE_NAME_LEN+1]; |
7043 | bool create_new_users=0, result; |
7044 | DBUG_ENTER("mysql_grant" ); |
7045 | |
7046 | if (lower_case_table_names && db) |
7047 | { |
7048 | char *end= strnmov(tmp_db,db, sizeof(tmp_db)); |
7049 | if (end >= tmp_db + sizeof(tmp_db)) |
7050 | { |
7051 | my_error(ER_WRONG_DB_NAME ,MYF(0), db); |
7052 | DBUG_RETURN(TRUE); |
7053 | } |
7054 | my_casedn_str(files_charset_info, tmp_db); |
7055 | db=tmp_db; |
7056 | } |
7057 | |
7058 | if (is_proxy) |
7059 | { |
7060 | DBUG_ASSERT(!db); |
7061 | proxied_user= str_list++; |
7062 | } |
7063 | |
7064 | Grant_tables tables(Table_user | (is_proxy ? Table_proxies_priv : Table_db), |
7065 | TL_WRITE); |
7066 | if ((result= tables.open_and_lock(thd))) |
7067 | DBUG_RETURN(result != 1); |
7068 | |
7069 | DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); |
7070 | |
7071 | if (!revoke_grant) |
7072 | create_new_users= test_if_create_new_users(thd); |
7073 | |
7074 | /* go through users in user_list */ |
7075 | mysql_rwlock_wrlock(&LOCK_grant); |
7076 | mysql_mutex_lock(&acl_cache->lock); |
7077 | grant_version++; |
7078 | |
7079 | if (proxied_user) |
7080 | { |
7081 | if (!(proxied_user= get_current_user(thd, proxied_user, false))) |
7082 | DBUG_RETURN(TRUE); |
7083 | DBUG_ASSERT(proxied_user->host.length); // not a Role |
7084 | } |
7085 | |
7086 | while ((tmp_Str = str_list++)) |
7087 | { |
7088 | if (!(Str= get_current_user(thd, tmp_Str, false))) |
7089 | { |
7090 | result= true; |
7091 | continue; |
7092 | } |
7093 | |
7094 | if (copy_and_check_auth(Str, tmp_Str, thd) || |
7095 | replace_user_table(thd, tables.user_table(), *Str, |
7096 | (!db ? rights : 0), revoke_grant, create_new_users, |
7097 | MY_TEST(thd->variables.sql_mode & |
7098 | MODE_NO_AUTO_CREATE_USER))) |
7099 | result= true; |
7100 | else if (db) |
7101 | { |
7102 | ulong db_rights= rights & DB_ACLS; |
7103 | if (db_rights == rights) |
7104 | { |
7105 | if (replace_db_table(tables.db_table().table(), db, *Str, db_rights, |
7106 | revoke_grant)) |
7107 | result= true; |
7108 | } |
7109 | else |
7110 | { |
7111 | my_error(ER_WRONG_USAGE, MYF(0), "DB GRANT" , "GLOBAL PRIVILEGES" ); |
7112 | result= true; |
7113 | } |
7114 | } |
7115 | else if (is_proxy) |
7116 | { |
7117 | /* TODO(cvicentiu) refactor replace_proxies_priv_table to use |
7118 | Proxies_priv_table instead of TABLE directly. */ |
7119 | if (tables.proxies_priv_table().no_such_table() || |
7120 | replace_proxies_priv_table (thd, tables.proxies_priv_table().table(), |
7121 | Str, proxied_user, |
7122 | rights & GRANT_ACL ? TRUE : FALSE, |
7123 | revoke_grant)) |
7124 | result= true; |
7125 | } |
7126 | if (Str->is_role()) |
7127 | propagate_role_grants(find_acl_role(Str->user.str), |
7128 | db ? PRIVS_TO_MERGE::DB : PRIVS_TO_MERGE::GLOBAL, |
7129 | db); |
7130 | } |
7131 | mysql_mutex_unlock(&acl_cache->lock); |
7132 | |
7133 | if (!result) |
7134 | { |
7135 | result= write_bin_log(thd, TRUE, thd->query(), thd->query_length()); |
7136 | } |
7137 | |
7138 | mysql_rwlock_unlock(&LOCK_grant); |
7139 | |
7140 | if (!result) |
7141 | my_ok(thd); |
7142 | |
7143 | DBUG_RETURN(result); |
7144 | } |
7145 | |
7146 | |
7147 | /* Free grant array if possible */ |
7148 | |
7149 | void grant_free(void) |
7150 | { |
7151 | DBUG_ENTER("grant_free" ); |
7152 | my_hash_free(&column_priv_hash); |
7153 | my_hash_free(&proc_priv_hash); |
7154 | my_hash_free(&func_priv_hash); |
7155 | my_hash_free(&package_spec_priv_hash); |
7156 | my_hash_free(&package_body_priv_hash); |
7157 | free_root(&grant_memroot,MYF(0)); |
7158 | DBUG_VOID_RETURN; |
7159 | } |
7160 | |
7161 | |
7162 | /** |
7163 | @brief Initialize structures responsible for table/column-level privilege |
7164 | checking and load information for them from tables in the 'mysql' database. |
7165 | |
7166 | @return Error status |
7167 | @retval 0 OK |
7168 | @retval 1 Could not initialize grant subsystem. |
7169 | */ |
7170 | |
7171 | bool grant_init() |
7172 | { |
7173 | THD *thd; |
7174 | bool return_val; |
7175 | DBUG_ENTER("grant_init" ); |
7176 | |
7177 | if (!(thd= new THD(0))) |
7178 | DBUG_RETURN(1); /* purecov: deadcode */ |
7179 | thd->thread_stack= (char*) &thd; |
7180 | thd->store_globals(); |
7181 | return_val= grant_reload(thd); |
7182 | delete thd; |
7183 | DBUG_RETURN(return_val); |
7184 | } |
7185 | |
7186 | |
7187 | /** |
7188 | @brief Initialize structures responsible for table/column-level privilege |
7189 | checking and load information about grants from open privilege tables. |
7190 | |
7191 | @param thd Current thread |
7192 | @param tables List containing open "mysql.tables_priv" and |
7193 | "mysql.columns_priv" tables. |
7194 | |
7195 | @see grant_reload |
7196 | |
7197 | @return Error state |
7198 | @retval FALSE Success |
7199 | @retval TRUE Error |
7200 | */ |
7201 | |
7202 | static bool grant_load(THD *thd, |
7203 | const Tables_priv_table& tables_priv, |
7204 | const Columns_priv_table& columns_priv, |
7205 | const Procs_priv_table& procs_priv) |
7206 | { |
7207 | bool return_val= 1; |
7208 | TABLE *t_table, *c_table, *p_table; |
7209 | bool check_no_resolve= specialflag & SPECIAL_NO_RESOLVE; |
7210 | MEM_ROOT *save_mem_root= thd->mem_root; |
7211 | sql_mode_t old_sql_mode= thd->variables.sql_mode; |
7212 | DBUG_ENTER("grant_load" ); |
7213 | |
7214 | thd->variables.sql_mode&= ~MODE_PAD_CHAR_TO_FULL_LENGTH; |
7215 | |
7216 | (void) my_hash_init(&column_priv_hash, &my_charset_utf8_bin, |
7217 | 0,0,0, (my_hash_get_key) get_grant_table, |
7218 | (my_hash_free_key) free_grant_table,0); |
7219 | (void) my_hash_init(&proc_priv_hash, &my_charset_utf8_bin, |
7220 | 0,0,0, (my_hash_get_key) get_grant_table, 0,0); |
7221 | (void) my_hash_init(&func_priv_hash, &my_charset_utf8_bin, |
7222 | 0,0,0, (my_hash_get_key) get_grant_table, 0,0); |
7223 | (void) my_hash_init(&package_spec_priv_hash, &my_charset_utf8_bin, |
7224 | 0,0,0, (my_hash_get_key) get_grant_table, 0,0); |
7225 | (void) my_hash_init(&package_body_priv_hash, &my_charset_utf8_bin, |
7226 | 0,0,0, (my_hash_get_key) get_grant_table, 0,0); |
7227 | init_sql_alloc(&grant_memroot, "GRANT" , ACL_ALLOC_BLOCK_SIZE, 0, MYF(0)); |
7228 | |
7229 | t_table= tables_priv.table(); |
7230 | c_table= columns_priv.table(); |
7231 | p_table= procs_priv.table(); // this can be NULL |
7232 | |
7233 | if (t_table->file->ha_index_init(0, 1)) |
7234 | goto end_index_init; |
7235 | |
7236 | t_table->use_all_columns(); |
7237 | c_table->use_all_columns(); |
7238 | |
7239 | thd->mem_root= &grant_memroot; |
7240 | |
7241 | if (!t_table->file->ha_index_first(t_table->record[0])) |
7242 | { |
7243 | do |
7244 | { |
7245 | GRANT_TABLE *mem_check; |
7246 | /* TODO(cvicentiu) convert this to use tables_priv and columns_priv. */ |
7247 | if (!(mem_check= new (&grant_memroot) GRANT_TABLE(t_table, c_table))) |
7248 | { |
7249 | /* This could only happen if we are out memory */ |
7250 | goto end_unlock; |
7251 | } |
7252 | |
7253 | if (check_no_resolve) |
7254 | { |
7255 | if (hostname_requires_resolving(mem_check->host.hostname)) |
7256 | { |
7257 | sql_print_warning("'tables_priv' entry '%s %s@%s' " |
7258 | "ignored in --skip-name-resolve mode." , |
7259 | mem_check->tname, |
7260 | safe_str(mem_check->user), |
7261 | safe_str(mem_check->host.hostname)); |
7262 | continue; |
7263 | } |
7264 | } |
7265 | |
7266 | if (! mem_check->ok()) |
7267 | delete mem_check; |
7268 | else if (my_hash_insert(&column_priv_hash,(uchar*) mem_check)) |
7269 | { |
7270 | delete mem_check; |
7271 | goto end_unlock; |
7272 | } |
7273 | } |
7274 | while (!t_table->file->ha_index_next(t_table->record[0])); |
7275 | } |
7276 | |
7277 | return_val= 0; |
7278 | |
7279 | if (p_table) |
7280 | { |
7281 | if (p_table->file->ha_index_init(0, 1)) |
7282 | goto end_unlock; |
7283 | |
7284 | p_table->use_all_columns(); |
7285 | |
7286 | if (!p_table->file->ha_index_first(p_table->record[0])) |
7287 | { |
7288 | do |
7289 | { |
7290 | GRANT_NAME *mem_check; |
7291 | HASH *hash; |
7292 | if (!(mem_check= new (&grant_memroot) GRANT_NAME(p_table, TRUE))) |
7293 | { |
7294 | /* This could only happen if we are out memory */ |
7295 | goto end_unlock_p; |
7296 | } |
7297 | |
7298 | if (check_no_resolve) |
7299 | { |
7300 | if (hostname_requires_resolving(mem_check->host.hostname)) |
7301 | { |
7302 | sql_print_warning("'procs_priv' entry '%s %s@%s' " |
7303 | "ignored in --skip-name-resolve mode." , |
7304 | mem_check->tname, mem_check->user, |
7305 | safe_str(mem_check->host.hostname)); |
7306 | continue; |
7307 | } |
7308 | } |
7309 | stored_procedure_type type= (stored_procedure_type)procs_priv.routine_type()->val_int(); |
7310 | const Sp_handler *sph= Sp_handler::handler(type); |
7311 | if (!sph || !(hash= sph->get_priv_hash())) |
7312 | { |
7313 | sql_print_warning("'procs_priv' entry '%s' " |
7314 | "ignored, bad routine type" , |
7315 | mem_check->tname); |
7316 | continue; |
7317 | } |
7318 | |
7319 | mem_check->privs= fix_rights_for_procedure(mem_check->privs); |
7320 | mem_check->init_privs= mem_check->privs; |
7321 | if (! mem_check->ok()) |
7322 | delete mem_check; |
7323 | else if (my_hash_insert(hash, (uchar*) mem_check)) |
7324 | { |
7325 | delete mem_check; |
7326 | goto end_unlock_p; |
7327 | } |
7328 | } |
7329 | while (!p_table->file->ha_index_next(p_table->record[0])); |
7330 | } |
7331 | } |
7332 | |
7333 | end_unlock_p: |
7334 | if (p_table) |
7335 | p_table->file->ha_index_end(); |
7336 | end_unlock: |
7337 | t_table->file->ha_index_end(); |
7338 | thd->mem_root= save_mem_root; |
7339 | end_index_init: |
7340 | thd->variables.sql_mode= old_sql_mode; |
7341 | DBUG_RETURN(return_val); |
7342 | } |
7343 | |
7344 | static my_bool propagate_role_grants_action(void *role_ptr, |
7345 | void *ptr __attribute__((unused))) |
7346 | { |
7347 | ACL_ROLE *role= static_cast<ACL_ROLE *>(role_ptr); |
7348 | if (role->counter) |
7349 | return 0; |
7350 | |
7351 | mysql_mutex_assert_owner(&acl_cache->lock); |
7352 | PRIVS_TO_MERGE data= { PRIVS_TO_MERGE::ALL, 0, 0 }; |
7353 | traverse_role_graph_up(role, &data, NULL, merge_role_privileges); |
7354 | return 0; |
7355 | } |
7356 | |
7357 | |
7358 | /** |
7359 | @brief Reload information about table and column level privileges if possible |
7360 | |
7361 | @param thd Current thread |
7362 | |
7363 | Locked tables are checked by acl_reload() and doesn't have to be checked |
7364 | in this call. |
7365 | This function is also used for initialization of structures responsible |
7366 | for table/column-level privilege checking. |
7367 | |
7368 | @return Error state |
7369 | @retval FALSE Success |
7370 | @retval TRUE Error |
7371 | */ |
7372 | |
7373 | bool grant_reload(THD *thd) |
7374 | { |
7375 | HASH old_column_priv_hash, old_proc_priv_hash, old_func_priv_hash; |
7376 | HASH old_package_spec_priv_hash, old_package_body_priv_hash; |
7377 | MEM_ROOT old_mem; |
7378 | int result; |
7379 | DBUG_ENTER("grant_reload" ); |
7380 | |
7381 | /* |
7382 | To avoid deadlocks we should obtain table locks before |
7383 | obtaining LOCK_grant rwlock. |
7384 | */ |
7385 | |
7386 | Grant_tables tables(Table_tables_priv | Table_columns_priv| Table_procs_priv, |
7387 | TL_READ); |
7388 | if ((result= tables.open_and_lock(thd))) |
7389 | DBUG_RETURN(result != 1); |
7390 | |
7391 | mysql_rwlock_wrlock(&LOCK_grant); |
7392 | grant_version++; |
7393 | old_column_priv_hash= column_priv_hash; |
7394 | old_proc_priv_hash= proc_priv_hash; |
7395 | old_func_priv_hash= func_priv_hash; |
7396 | old_package_spec_priv_hash= package_spec_priv_hash; |
7397 | old_package_body_priv_hash= package_body_priv_hash; |
7398 | |
7399 | /* |
7400 | Create a new memory pool but save the current memory pool to make an undo |
7401 | opertion possible in case of failure. |
7402 | */ |
7403 | old_mem= grant_memroot; |
7404 | |
7405 | if ((result= grant_load(thd, |
7406 | tables.tables_priv_table(), |
7407 | tables.columns_priv_table(), |
7408 | tables.procs_priv_table()))) |
7409 | { // Error. Revert to old hash |
7410 | DBUG_PRINT("error" ,("Reverting to old privileges" )); |
7411 | grant_free(); /* purecov: deadcode */ |
7412 | column_priv_hash= old_column_priv_hash; /* purecov: deadcode */ |
7413 | proc_priv_hash= old_proc_priv_hash; |
7414 | func_priv_hash= old_func_priv_hash; |
7415 | package_spec_priv_hash= old_package_spec_priv_hash; |
7416 | package_body_priv_hash= old_package_body_priv_hash; |
7417 | grant_memroot= old_mem; /* purecov: deadcode */ |
7418 | } |
7419 | else |
7420 | { |
7421 | my_hash_free(&old_column_priv_hash); |
7422 | my_hash_free(&old_proc_priv_hash); |
7423 | my_hash_free(&old_func_priv_hash); |
7424 | my_hash_free(&old_package_spec_priv_hash); |
7425 | my_hash_free(&old_package_body_priv_hash); |
7426 | free_root(&old_mem,MYF(0)); |
7427 | } |
7428 | |
7429 | mysql_mutex_lock(&acl_cache->lock); |
7430 | my_hash_iterate(&acl_roles, propagate_role_grants_action, NULL); |
7431 | mysql_mutex_unlock(&acl_cache->lock); |
7432 | |
7433 | mysql_rwlock_unlock(&LOCK_grant); |
7434 | |
7435 | close_mysql_tables(thd); |
7436 | |
7437 | DBUG_RETURN(result); |
7438 | } |
7439 | |
7440 | |
7441 | /** |
7442 | @brief Check table level grants |
7443 | |
7444 | @param thd Thread handler |
7445 | @param want_access Bits of privileges user needs to have. |
7446 | @param tables List of tables to check. The user should have |
7447 | 'want_access' to all tables in list. |
7448 | @param any_combination_will_do TRUE if it's enough to have any privilege for |
7449 | any combination of the table columns. |
7450 | @param number Check at most this number of tables. |
7451 | @param no_errors TRUE if no error should be sent directly to the client. |
7452 | |
7453 | If table->grant.want_privilege != 0 then the requested privileges where |
7454 | in the set of COL_ACLS but access was not granted on the table level. As |
7455 | a consequence an extra check of column privileges is required. |
7456 | |
7457 | Specifically if this function returns FALSE the user has some kind of |
7458 | privilege on a combination of columns in each table. |
7459 | |
7460 | This function is usually preceeded by check_access which establish the |
7461 | User-, Db- and Host access rights. |
7462 | |
7463 | @see check_access |
7464 | @see check_table_access |
7465 | |
7466 | @note |
7467 | This functions assumes that either number of tables to be inspected |
7468 | by it is limited explicitly (i.e. is is not UINT_MAX) or table list |
7469 | used and thd->lex->query_tables_own_last value correspond to each |
7470 | other (the latter should be either 0 or point to next_global member |
7471 | of one of elements of this table list). |
7472 | |
7473 | We delay locking of LOCK_grant until we really need it as we assume that |
7474 | most privileges be resolved with user or db level accesses. |
7475 | |
7476 | @return Access status |
7477 | @retval FALSE Access granted; But column privileges might need to be |
7478 | checked. |
7479 | @retval TRUE The user did not have the requested privileges on any of the |
7480 | tables. |
7481 | |
7482 | */ |
7483 | |
7484 | bool check_grant(THD *thd, ulong want_access, TABLE_LIST *tables, |
7485 | bool any_combination_will_do, uint number, bool no_errors) |
7486 | { |
7487 | TABLE_LIST *tl; |
7488 | TABLE_LIST *first_not_own_table= thd->lex->first_not_own_table(); |
7489 | Security_context *sctx= thd->security_ctx; |
7490 | uint i; |
7491 | ulong original_want_access= want_access; |
7492 | bool locked= 0; |
7493 | GRANT_TABLE *grant_table; |
7494 | GRANT_TABLE *grant_table_role= NULL; |
7495 | DBUG_ENTER("check_grant" ); |
7496 | DBUG_ASSERT(number > 0); |
7497 | |
7498 | /* |
7499 | Walk through the list of tables that belong to the query and save the |
7500 | requested access (orig_want_privilege) to be able to use it when |
7501 | checking access rights to the underlying tables of a view. Our grant |
7502 | system gradually eliminates checked bits from want_privilege and thus |
7503 | after all checks are done we can no longer use it. |
7504 | The check that first_not_own_table is not reached is for the case when |
7505 | the given table list refers to the list for prelocking (contains tables |
7506 | of other queries). For simple queries first_not_own_table is 0. |
7507 | */ |
7508 | for (i= 0, tl= tables; |
7509 | i < number && tl != first_not_own_table; |
7510 | tl= tl->next_global, i++) |
7511 | { |
7512 | /* |
7513 | Save a copy of the privileges without the SHOW_VIEW_ACL attribute. |
7514 | It will be checked during making view. |
7515 | */ |
7516 | tl->grant.orig_want_privilege= (want_access & ~SHOW_VIEW_ACL); |
7517 | } |
7518 | number= i; |
7519 | |
7520 | for (tl= tables; number-- ; tl= tl->next_global) |
7521 | { |
7522 | TABLE_LIST *const t_ref= |
7523 | tl->correspondent_table ? tl->correspondent_table : tl; |
7524 | sctx= t_ref->security_ctx ? t_ref->security_ctx : thd->security_ctx; |
7525 | ulong orig_want_access= original_want_access; |
7526 | |
7527 | /* |
7528 | If sequence is used as part of NEXT VALUE, PREVIOUS VALUE or SELECT, |
7529 | we need to modify the requested access rights depending on how the |
7530 | sequence is used. |
7531 | */ |
7532 | if (t_ref->sequence && |
7533 | !(want_access & ~(SELECT_ACL | INSERT_ACL | UPDATE_ACL | DELETE_ACL))) |
7534 | { |
7535 | /* |
7536 | We want to have either SELECT or INSERT rights to sequences depending |
7537 | on how they are accessed |
7538 | */ |
7539 | orig_want_access= ((t_ref->lock_type == TL_WRITE_ALLOW_WRITE) ? |
7540 | INSERT_ACL : SELECT_ACL); |
7541 | } |
7542 | |
7543 | if (tl->with || |
7544 | (tl->select_lex && |
7545 | (tl->with= tl->select_lex->find_table_def_in_with_clauses(tl)))) |
7546 | continue; |
7547 | |
7548 | const ACL_internal_table_access *access= |
7549 | get_cached_table_access(&t_ref->grant.m_internal, |
7550 | t_ref->get_db_name(), |
7551 | t_ref->get_table_name()); |
7552 | |
7553 | if (access) |
7554 | { |
7555 | switch(access->check(orig_want_access, &t_ref->grant.privilege)) |
7556 | { |
7557 | case ACL_INTERNAL_ACCESS_GRANTED: |
7558 | /* |
7559 | Currently, |
7560 | - the information_schema does not subclass ACL_internal_table_access, |
7561 | there are no per table privilege checks for I_S, |
7562 | - the performance schema does use per tables checks, but at most |
7563 | returns 'CHECK_GRANT', and never 'ACCESS_GRANTED'. |
7564 | so this branch is not used. |
7565 | */ |
7566 | DBUG_ASSERT(0); |
7567 | case ACL_INTERNAL_ACCESS_DENIED: |
7568 | goto err; |
7569 | case ACL_INTERNAL_ACCESS_CHECK_GRANT: |
7570 | break; |
7571 | } |
7572 | } |
7573 | |
7574 | want_access= orig_want_access; |
7575 | want_access&= ~sctx->master_access; |
7576 | if (!want_access) |
7577 | continue; // ok |
7578 | |
7579 | if (!(~t_ref->grant.privilege & want_access) || |
7580 | t_ref->is_anonymous_derived_table() || t_ref->schema_table) |
7581 | { |
7582 | /* |
7583 | It is subquery in the FROM clause. VIEW set t_ref->derived after |
7584 | table opening, but this function always called before table opening. |
7585 | |
7586 | NOTE: is_derived() can't be used here because subquery in this case |
7587 | the FROM clase (derived tables) can be not be marked yet. |
7588 | */ |
7589 | if (t_ref->is_anonymous_derived_table() || t_ref->schema_table) |
7590 | { |
7591 | /* |
7592 | If it's a temporary table created for a subquery in the FROM |
7593 | clause, or an INFORMATION_SCHEMA table, drop the request for |
7594 | a privilege. |
7595 | */ |
7596 | t_ref->grant.want_privilege= 0; |
7597 | } |
7598 | continue; |
7599 | } |
7600 | |
7601 | if (is_temporary_table(t_ref)) |
7602 | { |
7603 | /* |
7604 | If this table list element corresponds to a pre-opened temporary |
7605 | table skip checking of all relevant table-level privileges for it. |
7606 | Note that during creation of temporary table we still need to check |
7607 | if user has CREATE_TMP_ACL. |
7608 | */ |
7609 | t_ref->grant.privilege|= TMP_TABLE_ACLS; |
7610 | t_ref->grant.want_privilege= 0; |
7611 | continue; |
7612 | } |
7613 | |
7614 | if (!locked) |
7615 | { |
7616 | locked= 1; |
7617 | mysql_rwlock_rdlock(&LOCK_grant); |
7618 | } |
7619 | |
7620 | grant_table= table_hash_search(sctx->host, sctx->ip, |
7621 | t_ref->get_db_name(), |
7622 | sctx->priv_user, |
7623 | t_ref->get_table_name(), |
7624 | FALSE); |
7625 | if (sctx->priv_role[0]) |
7626 | grant_table_role= table_hash_search("" , NULL, t_ref->get_db_name(), |
7627 | sctx->priv_role, |
7628 | t_ref->get_table_name(), |
7629 | TRUE); |
7630 | |
7631 | if (!grant_table && !grant_table_role) |
7632 | { |
7633 | want_access&= ~t_ref->grant.privilege; |
7634 | goto err; // No grants |
7635 | } |
7636 | |
7637 | /* |
7638 | For SHOW COLUMNS, SHOW INDEX it is enough to have some |
7639 | privileges on any column combination on the table. |
7640 | */ |
7641 | if (any_combination_will_do) |
7642 | continue; |
7643 | |
7644 | t_ref->grant.grant_table_user= grant_table; // Remember for column test |
7645 | t_ref->grant.grant_table_role= grant_table_role; |
7646 | t_ref->grant.version= grant_version; |
7647 | t_ref->grant.privilege|= grant_table ? grant_table->privs : 0; |
7648 | t_ref->grant.privilege|= grant_table_role ? grant_table_role->privs : 0; |
7649 | t_ref->grant.want_privilege= ((want_access & COL_ACLS) & ~t_ref->grant.privilege); |
7650 | |
7651 | if (!(~t_ref->grant.privilege & want_access)) |
7652 | continue; |
7653 | |
7654 | if ((want_access&= ~((grant_table ? grant_table->cols : 0) | |
7655 | (grant_table_role ? grant_table_role->cols : 0) | |
7656 | t_ref->grant.privilege))) |
7657 | { |
7658 | goto err; // impossible |
7659 | } |
7660 | } |
7661 | if (locked) |
7662 | mysql_rwlock_unlock(&LOCK_grant); |
7663 | DBUG_RETURN(FALSE); |
7664 | |
7665 | err: |
7666 | if (locked) |
7667 | mysql_rwlock_unlock(&LOCK_grant); |
7668 | if (!no_errors) // Not a silent skip of table |
7669 | { |
7670 | char command[128]; |
7671 | get_privilege_desc(command, sizeof(command), want_access); |
7672 | status_var_increment(thd->status_var.access_denied_errors); |
7673 | |
7674 | my_error(ER_TABLEACCESS_DENIED_ERROR, MYF(0), |
7675 | command, |
7676 | sctx->priv_user, |
7677 | sctx->host_or_ip, |
7678 | tl ? tl->get_table_name() : "unknown" ); |
7679 | } |
7680 | DBUG_RETURN(TRUE); |
7681 | } |
7682 | |
7683 | |
7684 | /* |
7685 | Check column rights in given security context |
7686 | |
7687 | SYNOPSIS |
7688 | check_grant_column() |
7689 | thd thread handler |
7690 | grant grant information structure |
7691 | db_name db name |
7692 | table_name table name |
7693 | name column name |
7694 | length column name length |
7695 | sctx security context |
7696 | |
7697 | RETURN |
7698 | FALSE OK |
7699 | TRUE access denied |
7700 | */ |
7701 | |
7702 | bool check_grant_column(THD *thd, GRANT_INFO *grant, |
7703 | const char *db_name, const char *table_name, |
7704 | const char *name, size_t length, Security_context *sctx) |
7705 | { |
7706 | GRANT_TABLE *grant_table; |
7707 | GRANT_TABLE *grant_table_role; |
7708 | GRANT_COLUMN *grant_column; |
7709 | ulong want_access= grant->want_privilege & ~grant->privilege; |
7710 | DBUG_ENTER("check_grant_column" ); |
7711 | DBUG_PRINT("enter" , ("table: %s want_access: %lu" , table_name, want_access)); |
7712 | |
7713 | if (!want_access) |
7714 | DBUG_RETURN(0); // Already checked |
7715 | |
7716 | mysql_rwlock_rdlock(&LOCK_grant); |
7717 | |
7718 | /* reload table if someone has modified any grants */ |
7719 | |
7720 | if (grant->version != grant_version) |
7721 | { |
7722 | grant->grant_table_user= |
7723 | table_hash_search(sctx->host, sctx->ip, db_name, |
7724 | sctx->priv_user, |
7725 | table_name, 0); /* purecov: inspected */ |
7726 | grant->grant_table_role= |
7727 | sctx->priv_role[0] ? table_hash_search("" , NULL, db_name, |
7728 | sctx->priv_role, |
7729 | table_name, TRUE) : NULL; |
7730 | grant->version= grant_version; /* purecov: inspected */ |
7731 | } |
7732 | |
7733 | grant_table= grant->grant_table_user; |
7734 | grant_table_role= grant->grant_table_role; |
7735 | |
7736 | if (!grant_table && !grant_table_role) |
7737 | goto err; |
7738 | |
7739 | if (grant_table) |
7740 | { |
7741 | grant_column= column_hash_search(grant_table, name, length); |
7742 | if (grant_column) |
7743 | { |
7744 | want_access&= ~grant_column->rights; |
7745 | } |
7746 | } |
7747 | if (grant_table_role) |
7748 | { |
7749 | grant_column= column_hash_search(grant_table_role, name, length); |
7750 | if (grant_column) |
7751 | { |
7752 | want_access&= ~grant_column->rights; |
7753 | } |
7754 | } |
7755 | if (!want_access) |
7756 | { |
7757 | mysql_rwlock_unlock(&LOCK_grant); |
7758 | DBUG_RETURN(0); |
7759 | } |
7760 | |
7761 | err: |
7762 | mysql_rwlock_unlock(&LOCK_grant); |
7763 | char command[128]; |
7764 | get_privilege_desc(command, sizeof(command), want_access); |
7765 | /* TODO perhaps error should print current rolename aswell */ |
7766 | my_error(ER_COLUMNACCESS_DENIED_ERROR, MYF(0), |
7767 | command, |
7768 | sctx->priv_user, |
7769 | sctx->host_or_ip, |
7770 | name, |
7771 | table_name); |
7772 | DBUG_RETURN(1); |
7773 | } |
7774 | |
7775 | |
7776 | /* |
7777 | Check the access right to a column depending on the type of table. |
7778 | |
7779 | SYNOPSIS |
7780 | check_column_grant_in_table_ref() |
7781 | thd thread handler |
7782 | table_ref table reference where to check the field |
7783 | name name of field to check |
7784 | length length of name |
7785 | fld use fld object to check invisibility when it is |
7786 | not 0, not_found_field, view_ref_found |
7787 | |
7788 | DESCRIPTION |
7789 | Check the access rights to a column depending on the type of table |
7790 | reference where the column is checked. The function provides a |
7791 | generic interface to check column access rights that hides the |
7792 | heterogeneity of the column representation - whether it is a view |
7793 | or a stored table colum. |
7794 | |
7795 | RETURN |
7796 | FALSE OK |
7797 | TRUE access denied |
7798 | */ |
7799 | |
7800 | bool check_column_grant_in_table_ref(THD *thd, TABLE_LIST * table_ref, |
7801 | const char *name, size_t length, |
7802 | Field *fld) |
7803 | { |
7804 | GRANT_INFO *grant; |
7805 | const char *db_name; |
7806 | const char *table_name; |
7807 | Security_context *sctx= table_ref->security_ctx ? |
7808 | table_ref->security_ctx : thd->security_ctx; |
7809 | if (fld && fld != not_found_field && fld != view_ref_found |
7810 | && fld->invisible >= INVISIBLE_SYSTEM) |
7811 | return false; |
7812 | |
7813 | if (table_ref->view || table_ref->field_translation) |
7814 | { |
7815 | /* View or derived information schema table. */ |
7816 | ulong view_privs; |
7817 | grant= &(table_ref->grant); |
7818 | db_name= table_ref->view_db.str; |
7819 | table_name= table_ref->view_name.str; |
7820 | if (table_ref->belong_to_view && |
7821 | thd->lex->sql_command == SQLCOM_SHOW_FIELDS) |
7822 | { |
7823 | view_privs= get_column_grant(thd, grant, db_name, table_name, name); |
7824 | if (view_privs & VIEW_ANY_ACL) |
7825 | { |
7826 | table_ref->belong_to_view->allowed_show= TRUE; |
7827 | return FALSE; |
7828 | } |
7829 | table_ref->belong_to_view->allowed_show= FALSE; |
7830 | my_message(ER_VIEW_NO_EXPLAIN, ER_THD(thd, ER_VIEW_NO_EXPLAIN), MYF(0)); |
7831 | return TRUE; |
7832 | } |
7833 | } |
7834 | else |
7835 | { |
7836 | /* Normal or temporary table. */ |
7837 | TABLE *table= table_ref->table; |
7838 | grant= &(table->grant); |
7839 | db_name= table->s->db.str; |
7840 | table_name= table->s->table_name.str; |
7841 | } |
7842 | |
7843 | if (grant->want_privilege) |
7844 | return check_grant_column(thd, grant, db_name, table_name, name, |
7845 | length, sctx); |
7846 | else |
7847 | return FALSE; |
7848 | |
7849 | } |
7850 | |
7851 | |
7852 | /** |
7853 | @brief check if a query can access a set of columns |
7854 | |
7855 | @param thd the current thread |
7856 | @param want_access_arg the privileges requested |
7857 | @param fields an iterator over the fields of a table reference. |
7858 | @return Operation status |
7859 | @retval 0 Success |
7860 | @retval 1 Falure |
7861 | @details This function walks over the columns of a table reference |
7862 | The columns may originate from different tables, depending on the kind of |
7863 | table reference, e.g. join, view. |
7864 | For each table it will retrieve the grant information and will use it |
7865 | to check the required access privileges for the fields requested from it. |
7866 | */ |
7867 | bool check_grant_all_columns(THD *thd, ulong want_access_arg, |
7868 | Field_iterator_table_ref *fields) |
7869 | { |
7870 | Security_context *sctx= thd->security_ctx; |
7871 | ulong UNINIT_VAR(want_access); |
7872 | const char *table_name= NULL; |
7873 | const char* db_name; |
7874 | GRANT_INFO *grant; |
7875 | GRANT_TABLE *UNINIT_VAR(grant_table); |
7876 | GRANT_TABLE *UNINIT_VAR(grant_table_role); |
7877 | /* |
7878 | Flag that gets set if privilege checking has to be performed on column |
7879 | level. |
7880 | */ |
7881 | bool using_column_privileges= FALSE; |
7882 | |
7883 | mysql_rwlock_rdlock(&LOCK_grant); |
7884 | |
7885 | for (; !fields->end_of_fields(); fields->next()) |
7886 | { |
7887 | if (fields->field() && |
7888 | fields->field()->invisible >= INVISIBLE_SYSTEM) |
7889 | continue; |
7890 | LEX_CSTRING *field_name= fields->name(); |
7891 | |
7892 | if (table_name != fields->get_table_name()) |
7893 | { |
7894 | table_name= fields->get_table_name(); |
7895 | db_name= fields->get_db_name(); |
7896 | grant= fields->grant(); |
7897 | /* get a fresh one for each table */ |
7898 | want_access= want_access_arg & ~grant->privilege; |
7899 | if (want_access) |
7900 | { |
7901 | /* reload table if someone has modified any grants */ |
7902 | if (grant->version != grant_version) |
7903 | { |
7904 | grant->grant_table_user= |
7905 | table_hash_search(sctx->host, sctx->ip, db_name, |
7906 | sctx->priv_user, |
7907 | table_name, 0); /* purecov: inspected */ |
7908 | grant->grant_table_role= |
7909 | sctx->priv_role[0] ? table_hash_search("" , NULL, db_name, |
7910 | sctx->priv_role, |
7911 | table_name, TRUE) : NULL; |
7912 | grant->version= grant_version; /* purecov: inspected */ |
7913 | } |
7914 | |
7915 | grant_table= grant->grant_table_user; |
7916 | grant_table_role= grant->grant_table_role; |
7917 | DBUG_ASSERT (grant_table || grant_table_role); |
7918 | } |
7919 | } |
7920 | |
7921 | if (want_access) |
7922 | { |
7923 | ulong have_access= 0; |
7924 | if (grant_table) |
7925 | { |
7926 | GRANT_COLUMN *grant_column= |
7927 | column_hash_search(grant_table, field_name->str, field_name->length); |
7928 | if (grant_column) |
7929 | have_access= grant_column->rights; |
7930 | } |
7931 | if (grant_table_role) |
7932 | { |
7933 | GRANT_COLUMN *grant_column= |
7934 | column_hash_search(grant_table_role, field_name->str, |
7935 | field_name->length); |
7936 | if (grant_column) |
7937 | have_access|= grant_column->rights; |
7938 | } |
7939 | |
7940 | if (have_access) |
7941 | using_column_privileges= TRUE; |
7942 | if (want_access & ~have_access) |
7943 | goto err; |
7944 | } |
7945 | } |
7946 | mysql_rwlock_unlock(&LOCK_grant); |
7947 | return 0; |
7948 | |
7949 | err: |
7950 | mysql_rwlock_unlock(&LOCK_grant); |
7951 | |
7952 | char command[128]; |
7953 | get_privilege_desc(command, sizeof(command), want_access); |
7954 | /* |
7955 | Do not give an error message listing a column name unless the user has |
7956 | privilege to see all columns. |
7957 | */ |
7958 | if (using_column_privileges) |
7959 | my_error(ER_TABLEACCESS_DENIED_ERROR, MYF(0), |
7960 | command, sctx->priv_user, |
7961 | sctx->host_or_ip, table_name); |
7962 | else |
7963 | my_error(ER_COLUMNACCESS_DENIED_ERROR, MYF(0), |
7964 | command, |
7965 | sctx->priv_user, |
7966 | sctx->host_or_ip, |
7967 | fields->name()->str, |
7968 | table_name); |
7969 | return 1; |
7970 | } |
7971 | |
7972 | |
7973 | static bool check_grant_db_routine(THD *thd, const char *db, HASH *hash) |
7974 | { |
7975 | Security_context *sctx= thd->security_ctx; |
7976 | |
7977 | for (uint idx= 0; idx < hash->records; ++idx) |
7978 | { |
7979 | GRANT_NAME *item= (GRANT_NAME*) my_hash_element(hash, idx); |
7980 | |
7981 | if (strcmp(item->user, sctx->priv_user) == 0 && |
7982 | strcmp(item->db, db) == 0 && |
7983 | compare_hostname(&item->host, sctx->host, sctx->ip)) |
7984 | { |
7985 | return FALSE; |
7986 | } |
7987 | if (sctx->priv_role[0] && strcmp(item->user, sctx->priv_role) == 0 && |
7988 | strcmp(item->db, db) == 0 && |
7989 | (!item->host.hostname || !item->host.hostname[0])) |
7990 | { |
7991 | return FALSE; /* Found current role match */ |
7992 | } |
7993 | } |
7994 | |
7995 | return TRUE; |
7996 | } |
7997 | |
7998 | |
7999 | /* |
8000 | Check if a user has the right to access a database |
8001 | Access is accepted if he has a grant for any table/routine in the database |
8002 | Return 1 if access is denied |
8003 | */ |
8004 | |
8005 | bool check_grant_db(THD *thd, const char *db) |
8006 | { |
8007 | Security_context *sctx= thd->security_ctx; |
8008 | char helping [SAFE_NAME_LEN + USERNAME_LENGTH+2], *end; |
8009 | char helping2 [SAFE_NAME_LEN + USERNAME_LENGTH+2], *tmp_db; |
8010 | uint len, UNINIT_VAR(len2); |
8011 | bool error= TRUE; |
8012 | |
8013 | tmp_db= strmov(helping, sctx->priv_user) + 1; |
8014 | end= strnmov(tmp_db, db, helping + sizeof(helping) - tmp_db); |
8015 | |
8016 | if (end >= helping + sizeof(helping)) // db name was truncated |
8017 | return 1; // no privileges for an invalid db name |
8018 | |
8019 | if (lower_case_table_names) |
8020 | { |
8021 | end = tmp_db + my_casedn_str(files_charset_info, tmp_db); |
8022 | db=tmp_db; |
8023 | } |
8024 | |
8025 | len= (uint) (end - helping) + 1; |
8026 | |
8027 | /* |
8028 | If a role is set, we need to check for privileges here as well. |
8029 | */ |
8030 | if (sctx->priv_role[0]) |
8031 | { |
8032 | end= strmov(helping2, sctx->priv_role) + 1; |
8033 | end= strnmov(end, db, helping2 + sizeof(helping2) - end); |
8034 | len2= (uint) (end - helping2) + 1; |
8035 | } |
8036 | |
8037 | |
8038 | mysql_rwlock_rdlock(&LOCK_grant); |
8039 | |
8040 | for (uint idx=0 ; idx < column_priv_hash.records ; idx++) |
8041 | { |
8042 | GRANT_TABLE *grant_table= (GRANT_TABLE*) my_hash_element(&column_priv_hash, |
8043 | idx); |
8044 | if (len < grant_table->key_length && |
8045 | !memcmp(grant_table->hash_key, helping, len) && |
8046 | compare_hostname(&grant_table->host, sctx->host, sctx->ip)) |
8047 | { |
8048 | error= FALSE; /* Found match. */ |
8049 | break; |
8050 | } |
8051 | if (sctx->priv_role[0] && |
8052 | len2 < grant_table->key_length && |
8053 | !memcmp(grant_table->hash_key, helping2, len2) && |
8054 | (!grant_table->host.hostname || !grant_table->host.hostname[0])) |
8055 | { |
8056 | error= FALSE; /* Found role match */ |
8057 | break; |
8058 | } |
8059 | } |
8060 | |
8061 | if (error) |
8062 | error= check_grant_db_routine(thd, db, &proc_priv_hash) && |
8063 | check_grant_db_routine(thd, db, &func_priv_hash) && |
8064 | check_grant_db_routine(thd, db, &package_spec_priv_hash) && |
8065 | check_grant_db_routine(thd, db, &package_body_priv_hash); |
8066 | |
8067 | mysql_rwlock_unlock(&LOCK_grant); |
8068 | |
8069 | return error; |
8070 | } |
8071 | |
8072 | |
8073 | /**************************************************************************** |
8074 | Check routine level grants |
8075 | |
8076 | SYNPOSIS |
8077 | bool check_grant_routine() |
8078 | thd Thread handler |
8079 | want_access Bits of privileges user needs to have |
8080 | procs List of routines to check. The user should have 'want_access' |
8081 | sph SP handler |
8082 | no_errors If 0 then we write an error. The error is sent directly to |
8083 | the client |
8084 | |
8085 | RETURN |
8086 | 0 ok |
8087 | 1 Error: User did not have the requested privielges |
8088 | ****************************************************************************/ |
8089 | |
8090 | bool check_grant_routine(THD *thd, ulong want_access, |
8091 | TABLE_LIST *procs, const Sp_handler *sph, |
8092 | bool no_errors) |
8093 | { |
8094 | TABLE_LIST *table; |
8095 | Security_context *sctx= thd->security_ctx; |
8096 | char *user= sctx->priv_user; |
8097 | char *host= sctx->priv_host; |
8098 | char *role= sctx->priv_role; |
8099 | DBUG_ENTER("check_grant_routine" ); |
8100 | |
8101 | want_access&= ~sctx->master_access; |
8102 | if (!want_access) |
8103 | DBUG_RETURN(0); // ok |
8104 | |
8105 | mysql_rwlock_rdlock(&LOCK_grant); |
8106 | for (table= procs; table; table= table->next_global) |
8107 | { |
8108 | GRANT_NAME *grant_proc; |
8109 | if ((grant_proc= routine_hash_search(host, sctx->ip, table->db.str, user, |
8110 | table->table_name.str, sph, 0))) |
8111 | table->grant.privilege|= grant_proc->privs; |
8112 | if (role[0]) /* current role set check */ |
8113 | { |
8114 | if ((grant_proc= routine_hash_search("" , NULL, table->db.str, role, |
8115 | table->table_name.str, sph, 0))) |
8116 | table->grant.privilege|= grant_proc->privs; |
8117 | } |
8118 | |
8119 | if (want_access & ~table->grant.privilege) |
8120 | { |
8121 | want_access &= ~table->grant.privilege; |
8122 | goto err; |
8123 | } |
8124 | } |
8125 | mysql_rwlock_unlock(&LOCK_grant); |
8126 | DBUG_RETURN(0); |
8127 | err: |
8128 | mysql_rwlock_unlock(&LOCK_grant); |
8129 | if (!no_errors) |
8130 | { |
8131 | char buff[1024]; |
8132 | const char *command="" ; |
8133 | if (table) |
8134 | strxmov(buff, table->db.str, "." , table->table_name.str, NullS); |
8135 | if (want_access & EXECUTE_ACL) |
8136 | command= "execute" ; |
8137 | else if (want_access & ALTER_PROC_ACL) |
8138 | command= "alter routine" ; |
8139 | else if (want_access & GRANT_ACL) |
8140 | command= "grant" ; |
8141 | my_error(ER_PROCACCESS_DENIED_ERROR, MYF(0), |
8142 | command, user, host, table ? buff : "unknown" ); |
8143 | } |
8144 | DBUG_RETURN(1); |
8145 | } |
8146 | |
8147 | |
8148 | /* |
8149 | Check if routine has any of the |
8150 | routine level grants |
8151 | |
8152 | SYNPOSIS |
8153 | bool check_routine_level_acl() |
8154 | thd Thread handler |
8155 | db Database name |
8156 | name Routine name |
8157 | |
8158 | RETURN |
8159 | 0 Ok |
8160 | 1 error |
8161 | */ |
8162 | |
8163 | bool check_routine_level_acl(THD *thd, const char *db, const char *name, |
8164 | const Sp_handler *sph) |
8165 | { |
8166 | bool no_routine_acl= 1; |
8167 | GRANT_NAME *grant_proc; |
8168 | Security_context *sctx= thd->security_ctx; |
8169 | mysql_rwlock_rdlock(&LOCK_grant); |
8170 | if ((grant_proc= routine_hash_search(sctx->priv_host, |
8171 | sctx->ip, db, |
8172 | sctx->priv_user, |
8173 | name, sph, 0))) |
8174 | no_routine_acl= !(grant_proc->privs & SHOW_PROC_ACLS); |
8175 | |
8176 | if (no_routine_acl && sctx->priv_role[0]) /* current set role check */ |
8177 | { |
8178 | if ((grant_proc= routine_hash_search("" , |
8179 | NULL, db, |
8180 | sctx->priv_role, |
8181 | name, sph, 0))) |
8182 | no_routine_acl= !(grant_proc->privs & SHOW_PROC_ACLS); |
8183 | } |
8184 | mysql_rwlock_unlock(&LOCK_grant); |
8185 | return no_routine_acl; |
8186 | } |
8187 | |
8188 | |
8189 | /***************************************************************************** |
8190 | Functions to retrieve the grant for a table/column (for SHOW functions) |
8191 | *****************************************************************************/ |
8192 | |
8193 | ulong get_table_grant(THD *thd, TABLE_LIST *table) |
8194 | { |
8195 | ulong privilege; |
8196 | Security_context *sctx= thd->security_ctx; |
8197 | const char *db = table->db.str ? table->db.str : thd->db.str; |
8198 | GRANT_TABLE *grant_table; |
8199 | GRANT_TABLE *grant_table_role= NULL; |
8200 | |
8201 | mysql_rwlock_rdlock(&LOCK_grant); |
8202 | #ifdef EMBEDDED_LIBRARY |
8203 | grant_table= NULL; |
8204 | grant_table_role= NULL; |
8205 | #else |
8206 | grant_table= table_hash_search(sctx->host, sctx->ip, db, sctx->priv_user, |
8207 | table->table_name.str, 0); |
8208 | if (sctx->priv_role[0]) |
8209 | grant_table_role= table_hash_search("" , "" , db, sctx->priv_role, |
8210 | table->table_name.str, 0); |
8211 | #endif |
8212 | table->grant.grant_table_user= grant_table; // Remember for column test |
8213 | table->grant.grant_table_role= grant_table_role; |
8214 | table->grant.version=grant_version; |
8215 | if (grant_table) |
8216 | table->grant.privilege|= grant_table->privs; |
8217 | if (grant_table_role) |
8218 | table->grant.privilege|= grant_table_role->privs; |
8219 | privilege= table->grant.privilege; |
8220 | mysql_rwlock_unlock(&LOCK_grant); |
8221 | return privilege; |
8222 | } |
8223 | |
8224 | |
8225 | /* |
8226 | Determine the access priviliges for a field. |
8227 | |
8228 | SYNOPSIS |
8229 | get_column_grant() |
8230 | thd thread handler |
8231 | grant grants table descriptor |
8232 | db_name name of database that the field belongs to |
8233 | table_name name of table that the field belongs to |
8234 | field_name name of field |
8235 | |
8236 | DESCRIPTION |
8237 | The procedure may also modify: grant->grant_table and grant->version. |
8238 | |
8239 | RETURN |
8240 | The access priviliges for the field db_name.table_name.field_name |
8241 | */ |
8242 | |
8243 | ulong get_column_grant(THD *thd, GRANT_INFO *grant, |
8244 | const char *db_name, const char *table_name, |
8245 | const char *field_name) |
8246 | { |
8247 | GRANT_TABLE *grant_table; |
8248 | GRANT_TABLE *grant_table_role; |
8249 | GRANT_COLUMN *grant_column; |
8250 | ulong priv= 0; |
8251 | |
8252 | mysql_rwlock_rdlock(&LOCK_grant); |
8253 | /* reload table if someone has modified any grants */ |
8254 | if (grant->version != grant_version) |
8255 | { |
8256 | Security_context *sctx= thd->security_ctx; |
8257 | grant->grant_table_user= |
8258 | table_hash_search(sctx->host, sctx->ip, |
8259 | db_name, sctx->priv_user, |
8260 | table_name, 0); /* purecov: inspected */ |
8261 | grant->grant_table_role= |
8262 | sctx->priv_role[0] ? table_hash_search("" , "" , db_name, |
8263 | sctx->priv_role, |
8264 | table_name, TRUE) : NULL; |
8265 | grant->version= grant_version; /* purecov: inspected */ |
8266 | } |
8267 | |
8268 | grant_table= grant->grant_table_user; |
8269 | grant_table_role= grant->grant_table_role; |
8270 | |
8271 | if (!grant_table && !grant_table_role) |
8272 | priv= grant->privilege; |
8273 | else |
8274 | { |
8275 | if (grant_table) |
8276 | { |
8277 | grant_column= column_hash_search(grant_table, field_name, |
8278 | (uint) strlen(field_name)); |
8279 | if (!grant_column) |
8280 | priv= (grant->privilege | grant_table->privs); |
8281 | else |
8282 | priv= (grant->privilege | grant_table->privs | grant_column->rights); |
8283 | } |
8284 | |
8285 | if (grant_table_role) |
8286 | { |
8287 | grant_column= column_hash_search(grant_table_role, field_name, |
8288 | (uint) strlen(field_name)); |
8289 | if (!grant_column) |
8290 | priv|= (grant->privilege | grant_table_role->privs); |
8291 | else |
8292 | priv|= (grant->privilege | grant_table_role->privs | |
8293 | grant_column->rights); |
8294 | } |
8295 | } |
8296 | mysql_rwlock_unlock(&LOCK_grant); |
8297 | return priv; |
8298 | } |
8299 | |
8300 | |
8301 | /* Help function for mysql_show_grants */ |
8302 | |
8303 | static void add_user_option(String *grant, long value, const char *name, |
8304 | bool is_signed) |
8305 | { |
8306 | if (value) |
8307 | { |
8308 | char buff[22], *p; // just as in int2str |
8309 | grant->append(' '); |
8310 | grant->append(name, strlen(name)); |
8311 | grant->append(' '); |
8312 | p=int10_to_str(value, buff, is_signed ? -10 : 10); |
8313 | grant->append(buff,p-buff); |
8314 | } |
8315 | } |
8316 | |
8317 | |
8318 | static void add_user_option(String *grant, double value, const char *name) |
8319 | { |
8320 | if (value != 0.0 ) |
8321 | { |
8322 | char buff[FLOATING_POINT_BUFFER]; |
8323 | size_t len; |
8324 | grant->append(' '); |
8325 | grant->append(name, strlen(name)); |
8326 | grant->append(' '); |
8327 | len= my_fcvt(value, 6, buff, NULL); |
8328 | grant->append(buff, len); |
8329 | } |
8330 | } |
8331 | |
8332 | static void add_user_parameters(String *result, ACL_USER* acl_user, |
8333 | bool with_grant) |
8334 | { |
8335 | result->append(STRING_WITH_LEN("@'" )); |
8336 | result->append(acl_user->host.hostname, acl_user->hostname_length, |
8337 | system_charset_info); |
8338 | result->append('\''); |
8339 | |
8340 | if (acl_user->plugin.str == native_password_plugin_name.str || |
8341 | acl_user->plugin.str == old_password_plugin_name.str) |
8342 | { |
8343 | if (acl_user->auth_string.length) |
8344 | { |
8345 | DBUG_ASSERT(acl_user->salt_len); |
8346 | result->append(STRING_WITH_LEN(" IDENTIFIED BY PASSWORD '" )); |
8347 | result->append(&acl_user->auth_string); |
8348 | result->append('\''); |
8349 | } |
8350 | } |
8351 | else |
8352 | { |
8353 | result->append(STRING_WITH_LEN(" IDENTIFIED VIA " )); |
8354 | result->append(&acl_user->plugin); |
8355 | if (acl_user->auth_string.length) |
8356 | { |
8357 | result->append(STRING_WITH_LEN(" USING '" )); |
8358 | result->append(&acl_user->auth_string); |
8359 | result->append('\''); |
8360 | } |
8361 | } |
8362 | /* "show grants" SSL related stuff */ |
8363 | if (acl_user->ssl_type == SSL_TYPE_ANY) |
8364 | result->append(STRING_WITH_LEN(" REQUIRE SSL" )); |
8365 | else if (acl_user->ssl_type == SSL_TYPE_X509) |
8366 | result->append(STRING_WITH_LEN(" REQUIRE X509" )); |
8367 | else if (acl_user->ssl_type == SSL_TYPE_SPECIFIED) |
8368 | { |
8369 | int ssl_options = 0; |
8370 | result->append(STRING_WITH_LEN(" REQUIRE " )); |
8371 | if (acl_user->x509_issuer) |
8372 | { |
8373 | ssl_options++; |
8374 | result->append(STRING_WITH_LEN("ISSUER \'" )); |
8375 | result->append(acl_user->x509_issuer,strlen(acl_user->x509_issuer)); |
8376 | result->append('\''); |
8377 | } |
8378 | if (acl_user->x509_subject) |
8379 | { |
8380 | if (ssl_options++) |
8381 | result->append(' '); |
8382 | result->append(STRING_WITH_LEN("SUBJECT \'" )); |
8383 | result->append(acl_user->x509_subject,strlen(acl_user->x509_subject), |
8384 | system_charset_info); |
8385 | result->append('\''); |
8386 | } |
8387 | if (acl_user->ssl_cipher) |
8388 | { |
8389 | if (ssl_options++) |
8390 | result->append(' '); |
8391 | result->append(STRING_WITH_LEN("CIPHER '" )); |
8392 | result->append(acl_user->ssl_cipher,strlen(acl_user->ssl_cipher), |
8393 | system_charset_info); |
8394 | result->append('\''); |
8395 | } |
8396 | } |
8397 | if (with_grant || |
8398 | (acl_user->user_resource.questions || |
8399 | acl_user->user_resource.updates || |
8400 | acl_user->user_resource.conn_per_hour || |
8401 | acl_user->user_resource.user_conn || |
8402 | acl_user->user_resource.max_statement_time != 0.0)) |
8403 | { |
8404 | result->append(STRING_WITH_LEN(" WITH" )); |
8405 | if (with_grant) |
8406 | result->append(STRING_WITH_LEN(" GRANT OPTION" )); |
8407 | add_user_option(result, acl_user->user_resource.questions, |
8408 | "MAX_QUERIES_PER_HOUR" , false); |
8409 | add_user_option(result, acl_user->user_resource.updates, |
8410 | "MAX_UPDATES_PER_HOUR" , false); |
8411 | add_user_option(result, acl_user->user_resource.conn_per_hour, |
8412 | "MAX_CONNECTIONS_PER_HOUR" , false); |
8413 | add_user_option(result, acl_user->user_resource.user_conn, |
8414 | "MAX_USER_CONNECTIONS" , true); |
8415 | add_user_option(result, acl_user->user_resource.max_statement_time, |
8416 | "MAX_STATEMENT_TIME" ); |
8417 | } |
8418 | } |
8419 | |
8420 | static const char *command_array[]= |
8421 | { |
8422 | "SELECT" , "INSERT" , "UPDATE" , "DELETE" , "CREATE" , "DROP" , "RELOAD" , |
8423 | "SHUTDOWN" , "PROCESS" ,"FILE" , "GRANT" , "REFERENCES" , "INDEX" , |
8424 | "ALTER" , "SHOW DATABASES" , "SUPER" , "CREATE TEMPORARY TABLES" , |
8425 | "LOCK TABLES" , "EXECUTE" , "REPLICATION SLAVE" , "REPLICATION CLIENT" , |
8426 | "CREATE VIEW" , "SHOW VIEW" , "CREATE ROUTINE" , "ALTER ROUTINE" , |
8427 | "CREATE USER" , "EVENT" , "TRIGGER" , "CREATE TABLESPACE" , |
8428 | "DELETE VERSIONING ROWS" |
8429 | }; |
8430 | |
8431 | static uint command_lengths[]= |
8432 | { |
8433 | 6, 6, 6, 6, 6, 4, 6, 8, 7, 4, 5, 10, 5, 5, 14, 5, 23, 11, 7, 17, 18, 11, 9, |
8434 | 14, 13, 11, 5, 7, 17, 22, |
8435 | }; |
8436 | |
8437 | |
8438 | static bool print_grants_for_role(THD *thd, ACL_ROLE * role) |
8439 | { |
8440 | char buff[1024]; |
8441 | |
8442 | if (show_role_grants(thd, role->user.str, "" , role, buff, sizeof(buff))) |
8443 | return TRUE; |
8444 | |
8445 | if (show_global_privileges(thd, role, TRUE, buff, sizeof(buff))) |
8446 | return TRUE; |
8447 | |
8448 | if (show_database_privileges(thd, role->user.str, "" , buff, sizeof(buff))) |
8449 | return TRUE; |
8450 | |
8451 | if (show_table_and_column_privileges(thd, role->user.str, "" , buff, sizeof(buff))) |
8452 | return TRUE; |
8453 | |
8454 | if (show_routine_grants(thd, role->user.str, "" , &sp_handler_procedure, |
8455 | buff, sizeof(buff))) |
8456 | return TRUE; |
8457 | |
8458 | if (show_routine_grants(thd, role->user.str, "" , &sp_handler_function, |
8459 | buff, sizeof(buff))) |
8460 | return TRUE; |
8461 | |
8462 | if (show_routine_grants(thd, role->user.str, "" , &sp_handler_package_spec, |
8463 | buff, sizeof(buff))) |
8464 | return TRUE; |
8465 | |
8466 | if (show_routine_grants(thd, role->user.str, "" , &sp_handler_package_body, |
8467 | buff, sizeof(buff))) |
8468 | return TRUE; |
8469 | |
8470 | return FALSE; |
8471 | |
8472 | } |
8473 | |
8474 | /** checks privileges for SHOW GRANTS and SHOW CREATE USER |
8475 | |
8476 | @note that in case of SHOW CREATE USER the parser guarantees |
8477 | that a role can never happen here, so *rolename will never |
8478 | be assigned to |
8479 | */ |
8480 | static bool check_show_access(THD *thd, LEX_USER *lex_user, |
8481 | const char **username, |
8482 | const char **hostname, const char **rolename) |
8483 | { |
8484 | DBUG_ENTER("check_show_access" ); |
8485 | |
8486 | if (lex_user->user.str == current_user.str) |
8487 | { |
8488 | *username= thd->security_ctx->priv_user; |
8489 | *hostname= thd->security_ctx->priv_host; |
8490 | } |
8491 | else if (lex_user->user.str == current_role.str) |
8492 | { |
8493 | *rolename= thd->security_ctx->priv_role; |
8494 | } |
8495 | else if (lex_user->user.str == current_user_and_current_role.str) |
8496 | { |
8497 | *username= thd->security_ctx->priv_user; |
8498 | *hostname= thd->security_ctx->priv_host; |
8499 | *rolename= thd->security_ctx->priv_role; |
8500 | } |
8501 | else |
8502 | { |
8503 | Security_context *sctx= thd->security_ctx; |
8504 | bool do_check_access; |
8505 | |
8506 | lex_user= get_current_user(thd, lex_user); |
8507 | if (!lex_user) |
8508 | DBUG_RETURN(TRUE); |
8509 | |
8510 | if (lex_user->is_role()) |
8511 | { |
8512 | *rolename= lex_user->user.str; |
8513 | do_check_access= strcmp(*rolename, sctx->priv_role); |
8514 | } |
8515 | else |
8516 | { |
8517 | *username= lex_user->user.str; |
8518 | *hostname= lex_user->host.str; |
8519 | do_check_access= strcmp(*username, sctx->priv_user) || |
8520 | strcmp(*hostname, sctx->priv_host); |
8521 | } |
8522 | |
8523 | if (do_check_access && check_access(thd, SELECT_ACL, "mysql" , 0, 0, 1, 0)) |
8524 | DBUG_RETURN(TRUE); |
8525 | } |
8526 | DBUG_RETURN(FALSE); |
8527 | } |
8528 | |
8529 | bool mysql_show_create_user(THD *thd, LEX_USER *lex_user) |
8530 | { |
8531 | const char *username= NULL, *hostname= NULL; |
8532 | char buff[1024]; //Show create user should not take more than 1024 bytes. |
8533 | Protocol *protocol= thd->protocol; |
8534 | bool error= false; |
8535 | ACL_USER *acl_user; |
8536 | uint head_length; |
8537 | DBUG_ENTER("mysql_show_create_user" ); |
8538 | |
8539 | if (check_show_access(thd, lex_user, &username, &hostname, NULL)) |
8540 | DBUG_RETURN(TRUE); |
8541 | |
8542 | List<Item> field_list; |
8543 | head_length= (uint) (strxmov(buff, "CREATE USER for " , username, "@" , |
8544 | hostname, NullS) - buff); |
8545 | Item_string *field = new (thd->mem_root) Item_string_ascii(thd, "" , 0); |
8546 | if (!field) |
8547 | DBUG_RETURN(true); // Error given my my_alloc() |
8548 | |
8549 | field->name.str= buff; |
8550 | field->name.length= head_length; |
8551 | field->max_length= sizeof(buff); |
8552 | field_list.push_back(field, thd->mem_root); |
8553 | if (protocol->send_result_set_metadata(&field_list, |
8554 | Protocol::SEND_NUM_ROWS | |
8555 | Protocol::SEND_EOF)) |
8556 | DBUG_RETURN(true); |
8557 | |
8558 | String result(buff, sizeof(buff), system_charset_info); |
8559 | result.length(0); |
8560 | mysql_rwlock_rdlock(&LOCK_grant); |
8561 | mysql_mutex_lock(&acl_cache->lock); |
8562 | |
8563 | acl_user= find_user_exact(hostname, username); |
8564 | |
8565 | // User not found in the internal data structures. |
8566 | if (!acl_user) |
8567 | { |
8568 | my_error(ER_PASSWORD_NO_MATCH, MYF(0)); |
8569 | error= true; |
8570 | goto end; |
8571 | } |
8572 | |
8573 | result.append("CREATE USER '" ); |
8574 | result.append(username); |
8575 | result.append('\''); |
8576 | |
8577 | add_user_parameters(&result, acl_user, false); |
8578 | |
8579 | protocol->prepare_for_resend(); |
8580 | protocol->store(result.ptr(), result.length(), result.charset()); |
8581 | if (protocol->write()) |
8582 | { |
8583 | error= true; |
8584 | } |
8585 | my_eof(thd); |
8586 | |
8587 | end: |
8588 | mysql_rwlock_unlock(&LOCK_grant); |
8589 | mysql_mutex_unlock(&acl_cache->lock); |
8590 | |
8591 | DBUG_RETURN(error); |
8592 | } |
8593 | |
8594 | |
8595 | static int show_grants_callback(ACL_USER_BASE *role, void *data) |
8596 | { |
8597 | THD *thd= (THD *)data; |
8598 | DBUG_ASSERT(role->flags & IS_ROLE); |
8599 | if (print_grants_for_role(thd, (ACL_ROLE *)role)) |
8600 | return -1; |
8601 | return 0; |
8602 | } |
8603 | |
8604 | void mysql_show_grants_get_fields(THD *thd, List<Item> *fields, |
8605 | const char *name, size_t length) |
8606 | { |
8607 | Item_string *field=new (thd->mem_root) Item_string_ascii(thd, "" , 0); |
8608 | /* Set name explicit to avoid character set conversions */ |
8609 | field->name.str= name; |
8610 | field->name.length= length; |
8611 | field->max_length=1024; |
8612 | fields->push_back(field, thd->mem_root); |
8613 | } |
8614 | |
8615 | |
8616 | /* |
8617 | SHOW GRANTS; Send grants for a user to the client |
8618 | |
8619 | IMPLEMENTATION |
8620 | Send to client grant-like strings depicting user@host privileges |
8621 | */ |
8622 | |
8623 | bool mysql_show_grants(THD *thd, LEX_USER *lex_user) |
8624 | { |
8625 | int error = -1; |
8626 | ACL_USER *UNINIT_VAR(acl_user); |
8627 | ACL_ROLE *acl_role= NULL; |
8628 | char buff[1024]; |
8629 | Protocol *protocol= thd->protocol; |
8630 | const char *username= NULL, *hostname= NULL, *rolename= NULL, *end; |
8631 | DBUG_ENTER("mysql_show_grants" ); |
8632 | |
8633 | if (!initialized) |
8634 | { |
8635 | my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--skip-grant-tables" ); |
8636 | DBUG_RETURN(TRUE); |
8637 | } |
8638 | |
8639 | if (check_show_access(thd, lex_user, &username, &hostname, &rolename)) |
8640 | DBUG_RETURN(TRUE); |
8641 | DBUG_ASSERT(rolename || username); |
8642 | |
8643 | List<Item> field_list; |
8644 | if (!username) |
8645 | end= strxmov(buff,"Grants for " ,rolename, NullS); |
8646 | else |
8647 | end= strxmov(buff,"Grants for " ,username,"@" ,hostname, NullS); |
8648 | |
8649 | mysql_show_grants_get_fields(thd, &field_list, buff, (uint) (end-buff)); |
8650 | |
8651 | if (protocol->send_result_set_metadata(&field_list, |
8652 | Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)) |
8653 | DBUG_RETURN(TRUE); |
8654 | |
8655 | mysql_rwlock_rdlock(&LOCK_grant); |
8656 | mysql_mutex_lock(&acl_cache->lock); |
8657 | |
8658 | if (username) |
8659 | { |
8660 | acl_user= find_user_exact(hostname, username); |
8661 | if (!acl_user) |
8662 | { |
8663 | mysql_mutex_unlock(&acl_cache->lock); |
8664 | mysql_rwlock_unlock(&LOCK_grant); |
8665 | |
8666 | my_error(ER_NONEXISTING_GRANT, MYF(0), |
8667 | username, hostname); |
8668 | DBUG_RETURN(TRUE); |
8669 | } |
8670 | |
8671 | /* Show granted roles to acl_user */ |
8672 | if (show_role_grants(thd, username, hostname, acl_user, buff, sizeof(buff))) |
8673 | goto end; |
8674 | |
8675 | /* Add first global access grants */ |
8676 | if (show_global_privileges(thd, acl_user, FALSE, buff, sizeof(buff))) |
8677 | goto end; |
8678 | |
8679 | /* Add database access */ |
8680 | if (show_database_privileges(thd, username, hostname, buff, sizeof(buff))) |
8681 | goto end; |
8682 | |
8683 | /* Add table & column access */ |
8684 | if (show_table_and_column_privileges(thd, username, hostname, buff, sizeof(buff))) |
8685 | goto end; |
8686 | |
8687 | if (show_routine_grants(thd, username, hostname, &sp_handler_procedure, |
8688 | buff, sizeof(buff))) |
8689 | goto end; |
8690 | |
8691 | if (show_routine_grants(thd, username, hostname, &sp_handler_function, |
8692 | buff, sizeof(buff))) |
8693 | goto end; |
8694 | |
8695 | if (show_routine_grants(thd, username, hostname, &sp_handler_package_spec, |
8696 | buff, sizeof(buff))) |
8697 | goto end; |
8698 | |
8699 | if (show_routine_grants(thd, username, hostname, &sp_handler_package_body, |
8700 | buff, sizeof(buff))) |
8701 | goto end; |
8702 | |
8703 | if (show_proxy_grants(thd, username, hostname, buff, sizeof(buff))) |
8704 | goto end; |
8705 | } |
8706 | |
8707 | if (rolename) |
8708 | { |
8709 | acl_role= find_acl_role(rolename); |
8710 | if (acl_role) |
8711 | { |
8712 | /* get a list of all inherited roles */ |
8713 | traverse_role_graph_down(acl_role, thd, show_grants_callback, NULL); |
8714 | } |
8715 | else |
8716 | { |
8717 | if (lex_user->user.str == current_role.str) |
8718 | { |
8719 | mysql_mutex_unlock(&acl_cache->lock); |
8720 | mysql_rwlock_unlock(&LOCK_grant); |
8721 | my_error(ER_NONEXISTING_GRANT, MYF(0), |
8722 | thd->security_ctx->priv_user, |
8723 | thd->security_ctx->priv_host); |
8724 | DBUG_RETURN(TRUE); |
8725 | } |
8726 | } |
8727 | } |
8728 | |
8729 | error= 0; |
8730 | end: |
8731 | mysql_mutex_unlock(&acl_cache->lock); |
8732 | mysql_rwlock_unlock(&LOCK_grant); |
8733 | |
8734 | my_eof(thd); |
8735 | DBUG_RETURN(error); |
8736 | } |
8737 | |
8738 | static ROLE_GRANT_PAIR *find_role_grant_pair(const LEX_CSTRING *u, |
8739 | const LEX_CSTRING *h, |
8740 | const LEX_CSTRING *r) |
8741 | { |
8742 | char buf[1024]; |
8743 | String pair_key(buf, sizeof(buf), &my_charset_bin); |
8744 | |
8745 | size_t key_length= u->length + h->length + r->length + 3; |
8746 | pair_key.alloc(key_length); |
8747 | |
8748 | strmov(strmov(strmov(const_cast<char*>(pair_key.ptr()), |
8749 | safe_str(u->str)) + 1, h->str) + 1, r->str); |
8750 | |
8751 | return (ROLE_GRANT_PAIR *) |
8752 | my_hash_search(&acl_roles_mappings, (uchar*)pair_key.ptr(), key_length); |
8753 | } |
8754 | |
8755 | static bool show_role_grants(THD *thd, const char *username, |
8756 | const char *hostname, ACL_USER_BASE *acl_entry, |
8757 | char *buff, size_t buffsize) |
8758 | { |
8759 | uint counter; |
8760 | Protocol *protocol= thd->protocol; |
8761 | LEX_CSTRING host= {const_cast<char*>(hostname), strlen(hostname)}; |
8762 | |
8763 | String grant(buff,sizeof(buff),system_charset_info); |
8764 | for (counter= 0; counter < acl_entry->role_grants.elements; counter++) |
8765 | { |
8766 | grant.length(0); |
8767 | grant.append(STRING_WITH_LEN("GRANT " )); |
8768 | ACL_ROLE *acl_role= *(dynamic_element(&acl_entry->role_grants, counter, |
8769 | ACL_ROLE**)); |
8770 | grant.append(acl_role->user.str, acl_role->user.length, |
8771 | system_charset_info); |
8772 | grant.append(STRING_WITH_LEN(" TO '" )); |
8773 | grant.append(acl_entry->user.str, acl_entry->user.length, |
8774 | system_charset_info); |
8775 | if (!(acl_entry->flags & IS_ROLE)) |
8776 | { |
8777 | grant.append(STRING_WITH_LEN("'@'" )); |
8778 | grant.append(&host); |
8779 | } |
8780 | grant.append('\''); |
8781 | |
8782 | ROLE_GRANT_PAIR *pair= |
8783 | find_role_grant_pair(&acl_entry->user, &host, &acl_role->user); |
8784 | DBUG_ASSERT(pair); |
8785 | |
8786 | if (pair->with_admin) |
8787 | grant.append(STRING_WITH_LEN(" WITH ADMIN OPTION" )); |
8788 | |
8789 | protocol->prepare_for_resend(); |
8790 | protocol->store(grant.ptr(),grant.length(),grant.charset()); |
8791 | if (protocol->write()) |
8792 | { |
8793 | return TRUE; |
8794 | } |
8795 | } |
8796 | return FALSE; |
8797 | } |
8798 | |
8799 | static bool show_global_privileges(THD *thd, ACL_USER_BASE *acl_entry, |
8800 | bool handle_as_role, |
8801 | char *buff, size_t buffsize) |
8802 | { |
8803 | uint counter; |
8804 | ulong want_access; |
8805 | Protocol *protocol= thd->protocol; |
8806 | |
8807 | String global(buff,sizeof(buff),system_charset_info); |
8808 | global.length(0); |
8809 | global.append(STRING_WITH_LEN("GRANT " )); |
8810 | |
8811 | if (handle_as_role) |
8812 | want_access= ((ACL_ROLE *)acl_entry)->initial_role_access; |
8813 | else |
8814 | want_access= acl_entry->access; |
8815 | if (test_all_bits(want_access, (GLOBAL_ACLS & ~ GRANT_ACL))) |
8816 | global.append(STRING_WITH_LEN("ALL PRIVILEGES" )); |
8817 | else if (!(want_access & ~GRANT_ACL)) |
8818 | global.append(STRING_WITH_LEN("USAGE" )); |
8819 | else |
8820 | { |
8821 | bool found=0; |
8822 | ulong j,test_access= want_access & ~GRANT_ACL; |
8823 | for (counter=0, j = SELECT_ACL;j <= GLOBAL_ACLS;counter++,j <<= 1) |
8824 | { |
8825 | if (test_access & j) |
8826 | { |
8827 | if (found) |
8828 | global.append(STRING_WITH_LEN(", " )); |
8829 | found=1; |
8830 | global.append(command_array[counter],command_lengths[counter]); |
8831 | } |
8832 | } |
8833 | } |
8834 | global.append (STRING_WITH_LEN(" ON *.* TO '" )); |
8835 | global.append(acl_entry->user.str, acl_entry->user.length, |
8836 | system_charset_info); |
8837 | global.append('\''); |
8838 | |
8839 | if (!handle_as_role) |
8840 | add_user_parameters(&global, (ACL_USER *)acl_entry, (want_access & GRANT_ACL)); |
8841 | |
8842 | protocol->prepare_for_resend(); |
8843 | protocol->store(global.ptr(),global.length(),global.charset()); |
8844 | if (protocol->write()) |
8845 | return TRUE; |
8846 | |
8847 | return FALSE; |
8848 | |
8849 | } |
8850 | |
8851 | static bool show_database_privileges(THD *thd, const char *username, |
8852 | const char *hostname, |
8853 | char *buff, size_t buffsize) |
8854 | { |
8855 | ACL_DB *acl_db; |
8856 | ulong want_access; |
8857 | uint counter; |
8858 | Protocol *protocol= thd->protocol; |
8859 | |
8860 | for (counter=0 ; counter < acl_dbs.elements ; counter++) |
8861 | { |
8862 | const char *user, *host; |
8863 | |
8864 | acl_db=dynamic_element(&acl_dbs,counter,ACL_DB*); |
8865 | user= safe_str(acl_db->user); |
8866 | host=acl_db->host.hostname; |
8867 | |
8868 | /* |
8869 | We do not make SHOW GRANTS case-sensitive here (like REVOKE), |
8870 | but make it case-insensitive because that's the way they are |
8871 | actually applied, and showing fewer privileges than are applied |
8872 | would be wrong from a security point of view. |
8873 | */ |
8874 | |
8875 | if (!strcmp(username, user) && |
8876 | !my_strcasecmp(system_charset_info, hostname, host)) |
8877 | { |
8878 | /* |
8879 | do not print inherited access bits for roles, |
8880 | the role bits present in the table are what matters |
8881 | */ |
8882 | if (*hostname) // User |
8883 | want_access=acl_db->access; |
8884 | else // Role |
8885 | want_access=acl_db->initial_access; |
8886 | if (want_access) |
8887 | { |
8888 | String db(buff,sizeof(buff),system_charset_info); |
8889 | db.length(0); |
8890 | db.append(STRING_WITH_LEN("GRANT " )); |
8891 | |
8892 | if (test_all_bits(want_access,(DB_ACLS & ~GRANT_ACL))) |
8893 | db.append(STRING_WITH_LEN("ALL PRIVILEGES" )); |
8894 | else if (!(want_access & ~GRANT_ACL)) |
8895 | db.append(STRING_WITH_LEN("USAGE" )); |
8896 | else |
8897 | { |
8898 | int found=0, cnt; |
8899 | ulong j,test_access= want_access & ~GRANT_ACL; |
8900 | for (cnt=0, j = SELECT_ACL; j <= DB_ACLS; cnt++,j <<= 1) |
8901 | { |
8902 | if (test_access & j) |
8903 | { |
8904 | if (found) |
8905 | db.append(STRING_WITH_LEN(", " )); |
8906 | found = 1; |
8907 | db.append(command_array[cnt],command_lengths[cnt]); |
8908 | } |
8909 | } |
8910 | } |
8911 | db.append (STRING_WITH_LEN(" ON " )); |
8912 | append_identifier(thd, &db, acl_db->db, strlen(acl_db->db)); |
8913 | db.append (STRING_WITH_LEN(".* TO '" )); |
8914 | db.append(username, strlen(username), |
8915 | system_charset_info); |
8916 | if (*hostname) |
8917 | { |
8918 | db.append (STRING_WITH_LEN("'@'" )); |
8919 | // host and lex_user->host are equal except for case |
8920 | db.append(host, strlen(host), system_charset_info); |
8921 | } |
8922 | db.append ('\''); |
8923 | if (want_access & GRANT_ACL) |
8924 | db.append(STRING_WITH_LEN(" WITH GRANT OPTION" )); |
8925 | protocol->prepare_for_resend(); |
8926 | protocol->store(db.ptr(),db.length(),db.charset()); |
8927 | if (protocol->write()) |
8928 | { |
8929 | return TRUE; |
8930 | } |
8931 | } |
8932 | } |
8933 | } |
8934 | return FALSE; |
8935 | |
8936 | } |
8937 | |
8938 | static bool show_table_and_column_privileges(THD *thd, const char *username, |
8939 | const char *hostname, |
8940 | char *buff, size_t buffsize) |
8941 | { |
8942 | uint counter, index; |
8943 | Protocol *protocol= thd->protocol; |
8944 | |
8945 | for (index=0 ; index < column_priv_hash.records ; index++) |
8946 | { |
8947 | const char *user, *host; |
8948 | GRANT_TABLE *grant_table= (GRANT_TABLE*) |
8949 | my_hash_element(&column_priv_hash, index); |
8950 | |
8951 | user= safe_str(grant_table->user); |
8952 | host= grant_table->host.hostname; |
8953 | |
8954 | /* |
8955 | We do not make SHOW GRANTS case-sensitive here (like REVOKE), |
8956 | but make it case-insensitive because that's the way they are |
8957 | actually applied, and showing fewer privileges than are applied |
8958 | would be wrong from a security point of view. |
8959 | */ |
8960 | |
8961 | if (!strcmp(username,user) && |
8962 | !my_strcasecmp(system_charset_info, hostname, host)) |
8963 | { |
8964 | ulong table_access; |
8965 | ulong cols_access; |
8966 | if (*hostname) // User |
8967 | { |
8968 | table_access= grant_table->privs; |
8969 | cols_access= grant_table->cols; |
8970 | } |
8971 | else // Role |
8972 | { |
8973 | table_access= grant_table->init_privs; |
8974 | cols_access= grant_table->init_cols; |
8975 | } |
8976 | |
8977 | if ((table_access | cols_access) != 0) |
8978 | { |
8979 | String global(buff, sizeof(buff), system_charset_info); |
8980 | ulong test_access= (table_access | cols_access) & ~GRANT_ACL; |
8981 | |
8982 | global.length(0); |
8983 | global.append(STRING_WITH_LEN("GRANT " )); |
8984 | |
8985 | if (test_all_bits(table_access, (TABLE_ACLS & ~GRANT_ACL))) |
8986 | global.append(STRING_WITH_LEN("ALL PRIVILEGES" )); |
8987 | else if (!test_access) |
8988 | global.append(STRING_WITH_LEN("USAGE" )); |
8989 | else |
8990 | { |
8991 | /* Add specific column access */ |
8992 | int found= 0; |
8993 | ulong j; |
8994 | |
8995 | for (counter= 0, j= SELECT_ACL; j <= TABLE_ACLS; counter++, j<<= 1) |
8996 | { |
8997 | if (test_access & j) |
8998 | { |
8999 | if (found) |
9000 | global.append(STRING_WITH_LEN(", " )); |
9001 | found= 1; |
9002 | global.append(command_array[counter],command_lengths[counter]); |
9003 | |
9004 | if (grant_table->cols) |
9005 | { |
9006 | uint found_col= 0; |
9007 | HASH *hash_columns; |
9008 | hash_columns= &grant_table->hash_columns; |
9009 | |
9010 | for (uint col_index=0 ; |
9011 | col_index < hash_columns->records ; |
9012 | col_index++) |
9013 | { |
9014 | GRANT_COLUMN *grant_column = (GRANT_COLUMN*) |
9015 | my_hash_element(hash_columns,col_index); |
9016 | if (j & (*hostname ? grant_column->rights // User |
9017 | : grant_column->init_rights)) // Role |
9018 | { |
9019 | if (!found_col) |
9020 | { |
9021 | found_col= 1; |
9022 | /* |
9023 | If we have a duplicated table level privilege, we |
9024 | must write the access privilege name again. |
9025 | */ |
9026 | if (table_access & j) |
9027 | { |
9028 | global.append(STRING_WITH_LEN(", " )); |
9029 | global.append(command_array[counter], |
9030 | command_lengths[counter]); |
9031 | } |
9032 | global.append(STRING_WITH_LEN(" (" )); |
9033 | } |
9034 | else |
9035 | global.append(STRING_WITH_LEN(", " )); |
9036 | global.append(grant_column->column, |
9037 | grant_column->key_length, |
9038 | system_charset_info); |
9039 | } |
9040 | } |
9041 | if (found_col) |
9042 | global.append(')'); |
9043 | } |
9044 | } |
9045 | } |
9046 | } |
9047 | global.append(STRING_WITH_LEN(" ON " )); |
9048 | append_identifier(thd, &global, grant_table->db, |
9049 | strlen(grant_table->db)); |
9050 | global.append('.'); |
9051 | append_identifier(thd, &global, grant_table->tname, |
9052 | strlen(grant_table->tname)); |
9053 | global.append(STRING_WITH_LEN(" TO '" )); |
9054 | global.append(username, strlen(username), |
9055 | system_charset_info); |
9056 | if (*hostname) |
9057 | { |
9058 | global.append(STRING_WITH_LEN("'@'" )); |
9059 | // host and lex_user->host are equal except for case |
9060 | global.append(host, strlen(host), system_charset_info); |
9061 | } |
9062 | global.append('\''); |
9063 | if (table_access & GRANT_ACL) |
9064 | global.append(STRING_WITH_LEN(" WITH GRANT OPTION" )); |
9065 | protocol->prepare_for_resend(); |
9066 | protocol->store(global.ptr(),global.length(),global.charset()); |
9067 | if (protocol->write()) |
9068 | { |
9069 | return TRUE; |
9070 | } |
9071 | } |
9072 | } |
9073 | } |
9074 | return FALSE; |
9075 | |
9076 | } |
9077 | |
9078 | static int show_routine_grants(THD* thd, |
9079 | const char *username, const char *hostname, |
9080 | const Sp_handler *sph, |
9081 | char *buff, int buffsize) |
9082 | { |
9083 | uint counter, index; |
9084 | int error= 0; |
9085 | Protocol *protocol= thd->protocol; |
9086 | HASH *hash= sph->get_priv_hash(); |
9087 | /* Add routine access */ |
9088 | for (index=0 ; index < hash->records ; index++) |
9089 | { |
9090 | const char *user, *host; |
9091 | GRANT_NAME *grant_proc= (GRANT_NAME*) my_hash_element(hash, index); |
9092 | |
9093 | user= safe_str(grant_proc->user); |
9094 | host= grant_proc->host.hostname; |
9095 | |
9096 | /* |
9097 | We do not make SHOW GRANTS case-sensitive here (like REVOKE), |
9098 | but make it case-insensitive because that's the way they are |
9099 | actually applied, and showing fewer privileges than are applied |
9100 | would be wrong from a security point of view. |
9101 | */ |
9102 | |
9103 | if (!strcmp(username, user) && |
9104 | !my_strcasecmp(system_charset_info, hostname, host)) |
9105 | { |
9106 | ulong proc_access; |
9107 | if (*hostname) // User |
9108 | proc_access= grant_proc->privs; |
9109 | else // Role |
9110 | proc_access= grant_proc->init_privs; |
9111 | |
9112 | if (proc_access != 0) |
9113 | { |
9114 | String global(buff, buffsize, system_charset_info); |
9115 | ulong test_access= proc_access & ~GRANT_ACL; |
9116 | |
9117 | global.length(0); |
9118 | global.append(STRING_WITH_LEN("GRANT " )); |
9119 | |
9120 | if (!test_access) |
9121 | global.append(STRING_WITH_LEN("USAGE" )); |
9122 | else |
9123 | { |
9124 | /* Add specific procedure access */ |
9125 | int found= 0; |
9126 | ulong j; |
9127 | |
9128 | for (counter= 0, j= SELECT_ACL; j <= PROC_ACLS; counter++, j<<= 1) |
9129 | { |
9130 | if (test_access & j) |
9131 | { |
9132 | if (found) |
9133 | global.append(STRING_WITH_LEN(", " )); |
9134 | found= 1; |
9135 | global.append(command_array[counter],command_lengths[counter]); |
9136 | } |
9137 | } |
9138 | } |
9139 | global.append(STRING_WITH_LEN(" ON " )); |
9140 | LEX_CSTRING tmp= sph->type_lex_cstring(); |
9141 | global.append(&tmp); |
9142 | global.append(' '); |
9143 | append_identifier(thd, &global, grant_proc->db, |
9144 | strlen(grant_proc->db)); |
9145 | global.append('.'); |
9146 | append_identifier(thd, &global, grant_proc->tname, |
9147 | strlen(grant_proc->tname)); |
9148 | global.append(STRING_WITH_LEN(" TO '" )); |
9149 | global.append(username, strlen(username), |
9150 | system_charset_info); |
9151 | if (*hostname) |
9152 | { |
9153 | global.append(STRING_WITH_LEN("'@'" )); |
9154 | // host and lex_user->host are equal except for case |
9155 | global.append(host, strlen(host), system_charset_info); |
9156 | } |
9157 | global.append('\''); |
9158 | if (proc_access & GRANT_ACL) |
9159 | global.append(STRING_WITH_LEN(" WITH GRANT OPTION" )); |
9160 | protocol->prepare_for_resend(); |
9161 | protocol->store(global.ptr(),global.length(),global.charset()); |
9162 | if (protocol->write()) |
9163 | { |
9164 | error= -1; |
9165 | break; |
9166 | } |
9167 | } |
9168 | } |
9169 | } |
9170 | return error; |
9171 | } |
9172 | |
9173 | |
9174 | /* |
9175 | Make a clear-text version of the requested privilege. |
9176 | */ |
9177 | |
9178 | void get_privilege_desc(char *to, uint max_length, ulong access) |
9179 | { |
9180 | uint pos; |
9181 | char *start=to; |
9182 | DBUG_ASSERT(max_length >= 30); // For end ', ' removal |
9183 | |
9184 | if (access) |
9185 | { |
9186 | max_length--; // Reserve place for end-zero |
9187 | for (pos=0 ; access ; pos++, access>>=1) |
9188 | { |
9189 | if ((access & 1) && |
9190 | command_lengths[pos] + (uint) (to-start) < max_length) |
9191 | { |
9192 | to= strmov(to, command_array[pos]); |
9193 | *to++= ','; |
9194 | *to++= ' '; |
9195 | } |
9196 | } |
9197 | to--; // Remove end ' ' |
9198 | to--; // Remove end ',' |
9199 | } |
9200 | *to=0; |
9201 | } |
9202 | |
9203 | |
9204 | void get_mqh(const char *user, const char *host, USER_CONN *uc) |
9205 | { |
9206 | ACL_USER *acl_user; |
9207 | |
9208 | mysql_mutex_lock(&acl_cache->lock); |
9209 | |
9210 | if (initialized && (acl_user= find_user_wild(host,user))) |
9211 | uc->user_resources= acl_user->user_resource; |
9212 | else |
9213 | bzero((char*) &uc->user_resources, sizeof(uc->user_resources)); |
9214 | |
9215 | mysql_mutex_unlock(&acl_cache->lock); |
9216 | } |
9217 | |
9218 | static int check_role_is_granted_callback(ACL_USER_BASE *grantee, void *data) |
9219 | { |
9220 | LEX_CSTRING *rolename= static_cast<LEX_CSTRING *>(data); |
9221 | if (rolename->length == grantee->user.length && |
9222 | !strcmp(rolename->str, grantee->user.str)) |
9223 | return -1; // End search, we've found our role. |
9224 | |
9225 | /* Keep looking, we haven't found our role yet. */ |
9226 | return 0; |
9227 | } |
9228 | |
9229 | /* |
9230 | Modify a privilege table. |
9231 | |
9232 | SYNOPSIS |
9233 | modify_grant_table() |
9234 | table The table to modify. |
9235 | host_field The host name field. |
9236 | user_field The user name field. |
9237 | user_to The new name for the user if to be renamed, |
9238 | NULL otherwise. |
9239 | |
9240 | DESCRIPTION |
9241 | Update user/host in the current record if user_to is not NULL. |
9242 | Delete the current record if user_to is NULL. |
9243 | |
9244 | RETURN |
9245 | 0 OK. |
9246 | != 0 Error. |
9247 | */ |
9248 | |
9249 | static int modify_grant_table(TABLE *table, Field *host_field, |
9250 | Field *user_field, LEX_USER *user_to) |
9251 | { |
9252 | int error; |
9253 | DBUG_ENTER("modify_grant_table" ); |
9254 | |
9255 | if (user_to) |
9256 | { |
9257 | /* rename */ |
9258 | store_record(table, record[1]); |
9259 | host_field->store(user_to->host.str, user_to->host.length, |
9260 | system_charset_info); |
9261 | user_field->store(user_to->user.str, user_to->user.length, |
9262 | system_charset_info); |
9263 | if (unlikely(error= table->file->ha_update_row(table->record[1], |
9264 | table->record[0])) && |
9265 | error != HA_ERR_RECORD_IS_THE_SAME) |
9266 | table->file->print_error(error, MYF(0)); |
9267 | else |
9268 | error= 0; |
9269 | } |
9270 | else |
9271 | { |
9272 | /* delete */ |
9273 | if (unlikely((error=table->file->ha_delete_row(table->record[0])))) |
9274 | table->file->print_error(error, MYF(0)); |
9275 | } |
9276 | |
9277 | DBUG_RETURN(error); |
9278 | } |
9279 | |
9280 | /* |
9281 | Handle the roles_mapping privilege table |
9282 | */ |
9283 | static int handle_roles_mappings_table(TABLE *table, bool drop, |
9284 | LEX_USER *user_from, LEX_USER *user_to) |
9285 | { |
9286 | /* |
9287 | All entries (Host, User) that match user_from will be renamed, |
9288 | as well as all Role entries that match if user_from.host.str == "" |
9289 | |
9290 | Otherwise, only matching (Host, User) will be renamed. |
9291 | */ |
9292 | DBUG_ENTER("handle_roles_mappings_table" ); |
9293 | |
9294 | int error; |
9295 | int result= 0; |
9296 | THD *thd= table->in_use; |
9297 | const char *host, *user, *role; |
9298 | Field *host_field= table->field[0]; |
9299 | Field *user_field= table->field[1]; |
9300 | Field *role_field= table->field[2]; |
9301 | |
9302 | DBUG_PRINT("info" , ("Rewriting entry in roles_mapping table: %s@%s" , |
9303 | user_from->user.str, user_from->host.str)); |
9304 | table->use_all_columns(); |
9305 | |
9306 | if (unlikely(table->file->ha_rnd_init_with_error(1))) |
9307 | result= -1; |
9308 | else |
9309 | { |
9310 | while((error= table->file->ha_rnd_next(table->record[0])) != |
9311 | HA_ERR_END_OF_FILE) |
9312 | { |
9313 | if (error) |
9314 | { |
9315 | DBUG_PRINT("info" , ("scan error: %d" , error)); |
9316 | continue; |
9317 | } |
9318 | |
9319 | host= safe_str(get_field(thd->mem_root, host_field)); |
9320 | user= safe_str(get_field(thd->mem_root, user_field)); |
9321 | |
9322 | if (!(strcmp(user_from->user.str, user) || |
9323 | my_strcasecmp(system_charset_info, user_from->host.str, host))) |
9324 | result= ((drop || user_to) && |
9325 | modify_grant_table(table, host_field, user_field, user_to)) ? |
9326 | -1 : result ? result : 1; /* Error or keep result or found. */ |
9327 | else |
9328 | { |
9329 | role= safe_str(get_field(thd->mem_root, role_field)); |
9330 | |
9331 | if (!user_from->is_role() || strcmp(user_from->user.str, role)) |
9332 | continue; |
9333 | |
9334 | error= 0; |
9335 | |
9336 | if (drop) /* drop if requested */ |
9337 | { |
9338 | if (unlikely((error= table->file->ha_delete_row(table->record[0])))) |
9339 | table->file->print_error(error, MYF(0)); |
9340 | } |
9341 | else if (user_to) |
9342 | { |
9343 | store_record(table, record[1]); |
9344 | role_field->store(user_to->user.str, user_to->user.length, |
9345 | system_charset_info); |
9346 | if (unlikely(error= table->file->ha_update_row(table->record[1], |
9347 | table->record[0])) && |
9348 | error != HA_ERR_RECORD_IS_THE_SAME) |
9349 | table->file->print_error(error, MYF(0)); |
9350 | } |
9351 | |
9352 | /* Error or keep result or found. */ |
9353 | result= error ? -1 : result ? result : 1; |
9354 | } |
9355 | } |
9356 | table->file->ha_rnd_end(); |
9357 | } |
9358 | DBUG_RETURN(result); |
9359 | } |
9360 | |
9361 | /* |
9362 | Handle a privilege table. |
9363 | |
9364 | SYNOPSIS |
9365 | handle_grant_table() |
9366 | grant_table An open grant table handle. |
9367 | which_table Which grant table to handle. |
9368 | drop If user_from is to be dropped. |
9369 | user_from The the user to be searched/dropped/renamed. |
9370 | user_to The new name for the user if to be renamed, |
9371 | NULL otherwise. |
9372 | |
9373 | DESCRIPTION |
9374 | Scan through all records in a grant table and apply the requested |
9375 | operation. For the "user" table, a single index access is sufficient, |
9376 | since there is an unique index on (host, user). |
9377 | Delete from grant table if drop is true. |
9378 | Update in grant table if drop is false and user_to is not NULL. |
9379 | Search in grant table if drop is false and user_to is NULL. |
9380 | |
9381 | RETURN |
9382 | > 0 At least one record matched. |
9383 | 0 OK, but no record matched. |
9384 | < 0 Error. |
9385 | |
9386 | TODO(cvicentiu) refactor handle_grant_table to use |
9387 | Grant_table_base instead of TABLE directly. |
9388 | */ |
9389 | |
9390 | static int handle_grant_table(THD *thd, const Grant_table_base& grant_table, |
9391 | enum enum_acl_tables which_table, bool drop, |
9392 | LEX_USER *user_from, LEX_USER *user_to) |
9393 | { |
9394 | int result= 0; |
9395 | int error; |
9396 | TABLE *table= grant_table.table(); |
9397 | Field *host_field= table->field[0]; |
9398 | Field *user_field= table->field[which_table == USER_TABLE || |
9399 | which_table == PROXIES_PRIV_TABLE ? 1 : 2]; |
9400 | const char *host_str= user_from->host.str; |
9401 | const char *user_str= user_from->user.str; |
9402 | const char *host; |
9403 | const char *user; |
9404 | uchar user_key[MAX_KEY_LENGTH]; |
9405 | uint key_prefix_length; |
9406 | DBUG_ENTER("handle_grant_table" ); |
9407 | |
9408 | if (which_table == ROLES_MAPPING_TABLE) |
9409 | { |
9410 | result= handle_roles_mappings_table(table, drop, user_from, user_to); |
9411 | DBUG_RETURN(result); |
9412 | } |
9413 | |
9414 | table->use_all_columns(); |
9415 | if (which_table == USER_TABLE) // mysql.user table |
9416 | { |
9417 | /* |
9418 | The 'user' table has an unique index on (host, user). |
9419 | Thus, we can handle everything with a single index access. |
9420 | The host- and user fields are consecutive in the user table records. |
9421 | So we set host- and user fields of table->record[0] and use the |
9422 | pointer to the host field as key. |
9423 | index_read_idx() will replace table->record[0] (its first argument) |
9424 | by the searched record, if it exists. |
9425 | */ |
9426 | DBUG_PRINT("info" ,("read table: '%s' search: '%s'@'%s'" , |
9427 | table->s->table_name.str, user_str, host_str)); |
9428 | host_field->store(host_str, user_from->host.length, system_charset_info); |
9429 | user_field->store(user_str, user_from->user.length, system_charset_info); |
9430 | |
9431 | key_prefix_length= (table->key_info->key_part[0].store_length + |
9432 | table->key_info->key_part[1].store_length); |
9433 | key_copy(user_key, table->record[0], table->key_info, key_prefix_length); |
9434 | |
9435 | error= table->file->ha_index_read_idx_map(table->record[0], 0, |
9436 | user_key, (key_part_map)3, |
9437 | HA_READ_KEY_EXACT); |
9438 | if (!unlikely(error) && !*host_str) |
9439 | { |
9440 | // verify that we got a role or a user, as needed |
9441 | if (static_cast<const User_table&>(grant_table).check_is_role() != |
9442 | user_from->is_role()) |
9443 | error= HA_ERR_KEY_NOT_FOUND; |
9444 | } |
9445 | if (unlikely(error)) |
9446 | { |
9447 | if (error != HA_ERR_KEY_NOT_FOUND && error != HA_ERR_END_OF_FILE) |
9448 | { |
9449 | table->file->print_error(error, MYF(0)); |
9450 | result= -1; |
9451 | } |
9452 | } |
9453 | else |
9454 | { |
9455 | /* If requested, delete or update the record. */ |
9456 | result= ((drop || user_to) && |
9457 | modify_grant_table(table, host_field, user_field, user_to)) ? |
9458 | -1 : 1; /* Error or found. */ |
9459 | } |
9460 | DBUG_PRINT("info" ,("read result: %d" , result)); |
9461 | } |
9462 | else |
9463 | { |
9464 | /* |
9465 | The non-'user' table do not have indexes on (host, user). |
9466 | And their host- and user fields are not consecutive. |
9467 | Thus, we need to do a table scan to find all matching records. |
9468 | */ |
9469 | if (unlikely(table->file->ha_rnd_init_with_error(1))) |
9470 | result= -1; |
9471 | else |
9472 | { |
9473 | #ifdef EXTRA_DEBUG |
9474 | DBUG_PRINT("info" ,("scan table: '%s' search: '%s'@'%s'" , |
9475 | table->s->table_name.str, user_str, host_str)); |
9476 | #endif |
9477 | while ((error= table->file->ha_rnd_next(table->record[0])) != |
9478 | HA_ERR_END_OF_FILE) |
9479 | { |
9480 | if (error) |
9481 | { |
9482 | /* Most probable 'deleted record'. */ |
9483 | DBUG_PRINT("info" ,("scan error: %d" , error)); |
9484 | continue; |
9485 | } |
9486 | host= safe_str(get_field(thd->mem_root, host_field)); |
9487 | user= safe_str(get_field(thd->mem_root, user_field)); |
9488 | |
9489 | #ifdef EXTRA_DEBUG |
9490 | if (which_table != PROXIES_PRIV_TABLE) |
9491 | { |
9492 | DBUG_PRINT("loop" ,("scan fields: '%s'@'%s' '%s' '%s' '%s'" , |
9493 | user, host, |
9494 | get_field(thd->mem_root, table->field[1]) /*db*/, |
9495 | get_field(thd->mem_root, table->field[3]) /*table*/, |
9496 | get_field(thd->mem_root, |
9497 | table->field[4]) /*column*/)); |
9498 | } |
9499 | #endif |
9500 | if (strcmp(user_str, user) || |
9501 | my_strcasecmp(system_charset_info, host_str, host)) |
9502 | continue; |
9503 | |
9504 | /* If requested, delete or update the record. */ |
9505 | result= ((drop || user_to) && |
9506 | modify_grant_table(table, host_field, user_field, user_to)) ? |
9507 | -1 : result ? result : 1; /* Error or keep result or found. */ |
9508 | /* If search is requested, we do not need to search further. */ |
9509 | if (! drop && ! user_to) |
9510 | break ; |
9511 | } |
9512 | (void) table->file->ha_rnd_end(); |
9513 | DBUG_PRINT("info" ,("scan result: %d" , result)); |
9514 | } |
9515 | } |
9516 | |
9517 | DBUG_RETURN(result); |
9518 | } |
9519 | |
9520 | |
9521 | /** |
9522 | Handle an in-memory privilege structure. |
9523 | |
9524 | @param struct_no The number of the structure to handle (0..6). |
9525 | @param drop If user_from is to be dropped. |
9526 | @param user_from The the user to be searched/dropped/renamed. |
9527 | @param user_to The new name for the user if to be renamed, NULL otherwise. |
9528 | |
9529 | @note |
9530 | Scan through all elements in an in-memory grant structure and apply |
9531 | the requested operation. |
9532 | Delete from grant structure if drop is true. |
9533 | Update in grant structure if drop is false and user_to is not NULL. |
9534 | Search in grant structure if drop is false and user_to is NULL. |
9535 | |
9536 | @retval > 0 At least one element matched. |
9537 | @retval 0 OK, but no element matched. |
9538 | */ |
9539 | |
9540 | static int handle_grant_struct(enum enum_acl_lists struct_no, bool drop, |
9541 | LEX_USER *user_from, LEX_USER *user_to) |
9542 | { |
9543 | int result= 0; |
9544 | int idx; |
9545 | int elements; |
9546 | const char *UNINIT_VAR(user); |
9547 | const char *UNINIT_VAR(host); |
9548 | ACL_USER *acl_user= NULL; |
9549 | ACL_ROLE *acl_role= NULL; |
9550 | ACL_DB *acl_db= NULL; |
9551 | ACL_PROXY_USER *acl_proxy_user= NULL; |
9552 | GRANT_NAME *grant_name= NULL; |
9553 | ROLE_GRANT_PAIR *UNINIT_VAR(role_grant_pair); |
9554 | HASH *grant_name_hash= NULL; |
9555 | HASH *roles_mappings_hash= NULL; |
9556 | DBUG_ENTER("handle_grant_struct" ); |
9557 | DBUG_PRINT("info" ,("scan struct: %u search: '%s'@'%s'" , |
9558 | struct_no, user_from->user.str, user_from->host.str)); |
9559 | |
9560 | mysql_mutex_assert_owner(&acl_cache->lock); |
9561 | |
9562 | /* No point in querying ROLE ACL if user_from is not a role */ |
9563 | if (struct_no == ROLE_ACL && user_from->host.length) |
9564 | DBUG_RETURN(0); |
9565 | |
9566 | /* same. no roles in PROXY_USERS_ACL */ |
9567 | if (struct_no == PROXY_USERS_ACL && user_from->is_role()) |
9568 | DBUG_RETURN(0); |
9569 | |
9570 | if (struct_no == ROLE_ACL) //no need to scan the structures in this case |
9571 | { |
9572 | acl_role= find_acl_role(user_from->user.str); |
9573 | if (!acl_role) |
9574 | DBUG_RETURN(0); |
9575 | |
9576 | if (!drop && !user_to) //role was found |
9577 | DBUG_RETURN(1); |
9578 | |
9579 | /* this calls for a role update */ |
9580 | const char *old_key= acl_role->user.str; |
9581 | size_t old_key_length= acl_role->user.length; |
9582 | if (drop) |
9583 | { |
9584 | /* all grants must be revoked from this role by now. propagate this */ |
9585 | propagate_role_grants(acl_role, PRIVS_TO_MERGE::ALL); |
9586 | |
9587 | // delete the role from cross-reference arrays |
9588 | for (uint i=0; i < acl_role->role_grants.elements; i++) |
9589 | { |
9590 | ACL_ROLE *grant= *dynamic_element(&acl_role->role_grants, |
9591 | i, ACL_ROLE**); |
9592 | remove_ptr_from_dynarray(&grant->parent_grantee, acl_role); |
9593 | } |
9594 | |
9595 | for (uint i=0; i < acl_role->parent_grantee.elements; i++) |
9596 | { |
9597 | ACL_USER_BASE *grantee= *dynamic_element(&acl_role->parent_grantee, |
9598 | i, ACL_USER_BASE**); |
9599 | remove_ptr_from_dynarray(&grantee->role_grants, acl_role); |
9600 | } |
9601 | |
9602 | my_hash_delete(&acl_roles, (uchar*) acl_role); |
9603 | DBUG_RETURN(1); |
9604 | } |
9605 | acl_role->user.str= strdup_root(&acl_memroot, user_to->user.str); |
9606 | acl_role->user.length= user_to->user.length; |
9607 | |
9608 | my_hash_update(&acl_roles, (uchar*) acl_role, (uchar*) old_key, |
9609 | old_key_length); |
9610 | DBUG_RETURN(1); |
9611 | |
9612 | } |
9613 | |
9614 | /* Get the number of elements in the in-memory structure. */ |
9615 | switch (struct_no) { |
9616 | case USER_ACL: |
9617 | elements= acl_users.elements; |
9618 | break; |
9619 | case DB_ACL: |
9620 | elements= acl_dbs.elements; |
9621 | break; |
9622 | case COLUMN_PRIVILEGES_HASH: |
9623 | grant_name_hash= &column_priv_hash; |
9624 | elements= grant_name_hash->records; |
9625 | break; |
9626 | case PROC_PRIVILEGES_HASH: |
9627 | grant_name_hash= &proc_priv_hash; |
9628 | elements= grant_name_hash->records; |
9629 | break; |
9630 | case FUNC_PRIVILEGES_HASH: |
9631 | grant_name_hash= &func_priv_hash; |
9632 | elements= grant_name_hash->records; |
9633 | break; |
9634 | case PACKAGE_SPEC_PRIVILEGES_HASH: |
9635 | grant_name_hash= &package_spec_priv_hash; |
9636 | elements= grant_name_hash->records; |
9637 | break; |
9638 | case PACKAGE_BODY_PRIVILEGES_HASH: |
9639 | grant_name_hash= &package_body_priv_hash; |
9640 | elements= grant_name_hash->records; |
9641 | break; |
9642 | case PROXY_USERS_ACL: |
9643 | elements= acl_proxy_users.elements; |
9644 | break; |
9645 | case ROLES_MAPPINGS_HASH: |
9646 | roles_mappings_hash= &acl_roles_mappings; |
9647 | elements= roles_mappings_hash->records; |
9648 | break; |
9649 | default: |
9650 | DBUG_ASSERT(0); |
9651 | DBUG_RETURN(-1); |
9652 | } |
9653 | |
9654 | #ifdef EXTRA_DEBUG |
9655 | DBUG_PRINT("loop" ,("scan struct: %u search user: '%s' host: '%s'" , |
9656 | struct_no, user_from->user.str, user_from->host.str)); |
9657 | #endif |
9658 | /* Loop over all elements *backwards* (see the comment below). */ |
9659 | for (idx= elements - 1; idx >= 0; idx--) |
9660 | { |
9661 | /* |
9662 | Get a pointer to the element. |
9663 | */ |
9664 | switch (struct_no) { |
9665 | case USER_ACL: |
9666 | acl_user= dynamic_element(&acl_users, idx, ACL_USER*); |
9667 | user= acl_user->user.str; |
9668 | host= acl_user->host.hostname; |
9669 | break; |
9670 | |
9671 | case DB_ACL: |
9672 | acl_db= dynamic_element(&acl_dbs, idx, ACL_DB*); |
9673 | user= acl_db->user; |
9674 | host= acl_db->host.hostname; |
9675 | break; |
9676 | |
9677 | case COLUMN_PRIVILEGES_HASH: |
9678 | case PROC_PRIVILEGES_HASH: |
9679 | case FUNC_PRIVILEGES_HASH: |
9680 | case PACKAGE_SPEC_PRIVILEGES_HASH: |
9681 | case PACKAGE_BODY_PRIVILEGES_HASH: |
9682 | grant_name= (GRANT_NAME*) my_hash_element(grant_name_hash, idx); |
9683 | user= grant_name->user; |
9684 | host= grant_name->host.hostname; |
9685 | break; |
9686 | |
9687 | case PROXY_USERS_ACL: |
9688 | acl_proxy_user= dynamic_element(&acl_proxy_users, idx, ACL_PROXY_USER*); |
9689 | user= acl_proxy_user->get_user(); |
9690 | host= acl_proxy_user->get_host(); |
9691 | break; |
9692 | |
9693 | case ROLES_MAPPINGS_HASH: |
9694 | role_grant_pair= (ROLE_GRANT_PAIR *) my_hash_element(roles_mappings_hash, idx); |
9695 | user= role_grant_pair->u_uname; |
9696 | host= role_grant_pair->u_hname; |
9697 | break; |
9698 | |
9699 | default: |
9700 | DBUG_ASSERT(0); |
9701 | } |
9702 | if (! user) |
9703 | user= "" ; |
9704 | if (! host) |
9705 | host= "" ; |
9706 | |
9707 | #ifdef EXTRA_DEBUG |
9708 | DBUG_PRINT("loop" ,("scan struct: %u index: %u user: '%s' host: '%s'" , |
9709 | struct_no, idx, user, host)); |
9710 | #endif |
9711 | |
9712 | if (struct_no == ROLES_MAPPINGS_HASH) |
9713 | { |
9714 | const char* role= role_grant_pair->r_uname? role_grant_pair->r_uname: "" ; |
9715 | if (user_from->is_role()) |
9716 | { |
9717 | /* When searching for roles within the ROLES_MAPPINGS_HASH, we have |
9718 | to check both the user field as well as the role field for a match. |
9719 | |
9720 | It is possible to have a role granted to a role. If we are going |
9721 | to modify the mapping entry, it needs to be done on either on the |
9722 | "user" end (here represented by a role) or the "role" end. At least |
9723 | one part must match. |
9724 | |
9725 | If the "user" end has a not-empty host string, it can never match |
9726 | as we are searching for a role here. A role always has an empty host |
9727 | string. |
9728 | */ |
9729 | if ((*host || strcmp(user_from->user.str, user)) && |
9730 | strcmp(user_from->user.str, role)) |
9731 | continue; |
9732 | } |
9733 | else |
9734 | { |
9735 | if (strcmp(user_from->user.str, user) || |
9736 | my_strcasecmp(system_charset_info, user_from->host.str, host)) |
9737 | continue; |
9738 | } |
9739 | } |
9740 | else |
9741 | { |
9742 | if (strcmp(user_from->user.str, user) || |
9743 | my_strcasecmp(system_charset_info, user_from->host.str, host)) |
9744 | continue; |
9745 | } |
9746 | |
9747 | result= 1; /* At least one element found. */ |
9748 | if ( drop ) |
9749 | { |
9750 | elements--; |
9751 | switch ( struct_no ) { |
9752 | case USER_ACL: |
9753 | free_acl_user(dynamic_element(&acl_users, idx, ACL_USER*)); |
9754 | delete_dynamic_element(&acl_users, idx); |
9755 | break; |
9756 | |
9757 | case DB_ACL: |
9758 | delete_dynamic_element(&acl_dbs, idx); |
9759 | break; |
9760 | |
9761 | case COLUMN_PRIVILEGES_HASH: |
9762 | case PROC_PRIVILEGES_HASH: |
9763 | case FUNC_PRIVILEGES_HASH: |
9764 | case PACKAGE_SPEC_PRIVILEGES_HASH: |
9765 | case PACKAGE_BODY_PRIVILEGES_HASH: |
9766 | my_hash_delete(grant_name_hash, (uchar*) grant_name); |
9767 | /* |
9768 | In our HASH implementation on deletion one elements |
9769 | is moved into a place where a deleted element was, |
9770 | and the last element is moved into the empty space. |
9771 | Thus we need to re-examine the current element, but |
9772 | we don't have to restart the search from the beginning. |
9773 | */ |
9774 | if (idx != elements) |
9775 | idx++; |
9776 | break; |
9777 | |
9778 | case PROXY_USERS_ACL: |
9779 | delete_dynamic_element(&acl_proxy_users, idx); |
9780 | break; |
9781 | |
9782 | case ROLES_MAPPINGS_HASH: |
9783 | my_hash_delete(roles_mappings_hash, (uchar*) role_grant_pair); |
9784 | if (idx != elements) |
9785 | idx++; |
9786 | break; |
9787 | |
9788 | default: |
9789 | DBUG_ASSERT(0); |
9790 | break; |
9791 | } |
9792 | } |
9793 | else if ( user_to ) |
9794 | { |
9795 | switch ( struct_no ) { |
9796 | case USER_ACL: |
9797 | acl_user->user.str= strdup_root(&acl_memroot, user_to->user.str); |
9798 | acl_user->user.length= user_to->user.length; |
9799 | update_hostname(&acl_user->host, strdup_root(&acl_memroot, user_to->host.str)); |
9800 | acl_user->hostname_length= strlen(acl_user->host.hostname); |
9801 | break; |
9802 | |
9803 | case DB_ACL: |
9804 | acl_db->user= strdup_root(&acl_memroot, user_to->user.str); |
9805 | update_hostname(&acl_db->host, strdup_root(&acl_memroot, user_to->host.str)); |
9806 | break; |
9807 | |
9808 | case COLUMN_PRIVILEGES_HASH: |
9809 | case PROC_PRIVILEGES_HASH: |
9810 | case FUNC_PRIVILEGES_HASH: |
9811 | case PACKAGE_SPEC_PRIVILEGES_HASH: |
9812 | case PACKAGE_BODY_PRIVILEGES_HASH: |
9813 | { |
9814 | /* |
9815 | Save old hash key and its length to be able to properly update |
9816 | element position in hash. |
9817 | */ |
9818 | char *old_key= grant_name->hash_key; |
9819 | size_t old_key_length= grant_name->key_length; |
9820 | |
9821 | /* |
9822 | Update the grant structure with the new user name and host name. |
9823 | */ |
9824 | grant_name->set_user_details(user_to->host.str, grant_name->db, |
9825 | user_to->user.str, grant_name->tname, |
9826 | TRUE); |
9827 | |
9828 | /* |
9829 | Since username is part of the hash key, when the user name |
9830 | is renamed, the hash key is changed. Update the hash to |
9831 | ensure that the position matches the new hash key value |
9832 | */ |
9833 | my_hash_update(grant_name_hash, (uchar*) grant_name, (uchar*) old_key, |
9834 | old_key_length); |
9835 | /* |
9836 | hash_update() operation could have moved element from the tail or |
9837 | the head of the hash to the current position. But it can never |
9838 | move an element from the head to the tail or from the tail to the |
9839 | head over the current element. |
9840 | So we need to examine the current element once again, but |
9841 | we don't need to restart the search from the beginning. |
9842 | */ |
9843 | idx++; |
9844 | break; |
9845 | } |
9846 | |
9847 | case PROXY_USERS_ACL: |
9848 | acl_proxy_user->set_user (&acl_memroot, user_to->user.str); |
9849 | acl_proxy_user->set_host (&acl_memroot, user_to->host.str); |
9850 | break; |
9851 | |
9852 | case ROLES_MAPPINGS_HASH: |
9853 | { |
9854 | /* |
9855 | Save old hash key and its length to be able to properly update |
9856 | element position in hash. |
9857 | */ |
9858 | char *old_key= role_grant_pair->hashkey.str; |
9859 | size_t old_key_length= role_grant_pair->hashkey.length; |
9860 | bool oom; |
9861 | |
9862 | if (user_to->is_role()) |
9863 | oom= role_grant_pair->init(&acl_memroot, role_grant_pair->u_uname, |
9864 | role_grant_pair->u_hname, |
9865 | user_to->user.str, false); |
9866 | else |
9867 | oom= role_grant_pair->init(&acl_memroot, user_to->user.str, |
9868 | user_to->host.str, |
9869 | role_grant_pair->r_uname, false); |
9870 | if (oom) |
9871 | DBUG_RETURN(-1); |
9872 | |
9873 | my_hash_update(roles_mappings_hash, (uchar*) role_grant_pair, |
9874 | (uchar*) old_key, old_key_length); |
9875 | idx++; // see the comment above |
9876 | break; |
9877 | } |
9878 | |
9879 | default: |
9880 | DBUG_ASSERT(0); |
9881 | break; |
9882 | } |
9883 | |
9884 | } |
9885 | else |
9886 | { |
9887 | /* If search is requested, we do not need to search further. */ |
9888 | break; |
9889 | } |
9890 | } |
9891 | #ifdef EXTRA_DEBUG |
9892 | DBUG_PRINT("loop" ,("scan struct: %u result %d" , struct_no, result)); |
9893 | #endif |
9894 | |
9895 | DBUG_RETURN(result); |
9896 | } |
9897 | |
9898 | |
9899 | /* |
9900 | Handle all privilege tables and in-memory privilege structures. |
9901 | |
9902 | SYNOPSIS |
9903 | handle_grant_data() |
9904 | tables The array with the four open tables. |
9905 | drop If user_from is to be dropped. |
9906 | user_from The the user to be searched/dropped/renamed. |
9907 | user_to The new name for the user if to be renamed, |
9908 | NULL otherwise. |
9909 | |
9910 | DESCRIPTION |
9911 | Go through all grant tables and in-memory grant structures and apply |
9912 | the requested operation. |
9913 | Delete from grant data if drop is true. |
9914 | Update in grant data if drop is false and user_to is not NULL. |
9915 | Search in grant data if drop is false and user_to is NULL. |
9916 | |
9917 | RETURN |
9918 | > 0 At least one element matched. |
9919 | 0 OK, but no element matched. |
9920 | < 0 Error. |
9921 | */ |
9922 | |
9923 | static int handle_grant_data(THD *thd, Grant_tables& tables, bool drop, |
9924 | LEX_USER *user_from, LEX_USER *user_to) |
9925 | { |
9926 | int result= 0; |
9927 | int found; |
9928 | bool handle_as_role= user_from->is_role(); |
9929 | bool search_only= !drop && !user_to; |
9930 | DBUG_ENTER("handle_grant_data" ); |
9931 | |
9932 | if (user_to) |
9933 | DBUG_ASSERT(handle_as_role == user_to->is_role()); |
9934 | |
9935 | if (search_only) |
9936 | { |
9937 | /* quickly search in-memory structures first */ |
9938 | if (handle_as_role && find_acl_role(user_from->user.str)) |
9939 | DBUG_RETURN(1); // found |
9940 | |
9941 | if (!handle_as_role && find_user_exact(user_from->host.str, user_from->user.str)) |
9942 | DBUG_RETURN(1); // found |
9943 | } |
9944 | |
9945 | /* Handle db table. */ |
9946 | if ((found= handle_grant_table(thd, tables.db_table(), |
9947 | DB_TABLE, drop, user_from, |
9948 | user_to)) < 0) |
9949 | { |
9950 | /* Handle of table failed, don't touch the in-memory array. */ |
9951 | result= -1; |
9952 | } |
9953 | else |
9954 | { |
9955 | /* Handle db array. */ |
9956 | if ((handle_grant_struct(DB_ACL, drop, user_from, user_to) || found) |
9957 | && ! result) |
9958 | { |
9959 | result= 1; /* At least one record/element found. */ |
9960 | /* If search is requested, we do not need to search further. */ |
9961 | if (search_only) |
9962 | goto end; |
9963 | acl_cache->clear(1); |
9964 | } |
9965 | } |
9966 | |
9967 | /* Handle stored routines table. */ |
9968 | if ((found= handle_grant_table(thd, tables.procs_priv_table(), |
9969 | PROCS_PRIV_TABLE, drop, |
9970 | user_from, user_to)) < 0) |
9971 | { |
9972 | /* Handle of table failed, don't touch in-memory array. */ |
9973 | result= -1; |
9974 | } |
9975 | else |
9976 | { |
9977 | /* Handle procs array. */ |
9978 | if ((handle_grant_struct(PROC_PRIVILEGES_HASH, drop, user_from, user_to) || found) |
9979 | && ! result) |
9980 | { |
9981 | result= 1; /* At least one record/element found. */ |
9982 | /* If search is requested, we do not need to search further. */ |
9983 | if (search_only) |
9984 | goto end; |
9985 | } |
9986 | /* Handle funcs array. */ |
9987 | if ((handle_grant_struct(FUNC_PRIVILEGES_HASH, drop, user_from, user_to) || found) |
9988 | && ! result) |
9989 | { |
9990 | result= 1; /* At least one record/element found. */ |
9991 | /* If search is requested, we do not need to search further. */ |
9992 | if (search_only) |
9993 | goto end; |
9994 | } |
9995 | /* Handle package spec array. */ |
9996 | if ((handle_grant_struct(PACKAGE_SPEC_PRIVILEGES_HASH, |
9997 | drop, user_from, user_to) || found) |
9998 | && ! result) |
9999 | { |
10000 | result= 1; /* At least one record/element found. */ |
10001 | /* If search is requested, we do not need to search further. */ |
10002 | if (search_only) |
10003 | goto end; |
10004 | } |
10005 | /* Handle package body array. */ |
10006 | if ((handle_grant_struct(PACKAGE_BODY_PRIVILEGES_HASH, |
10007 | drop, user_from, user_to) || found) |
10008 | && ! result) |
10009 | { |
10010 | result= 1; /* At least one record/element found. */ |
10011 | /* If search is requested, we do not need to search further. */ |
10012 | if (search_only) |
10013 | goto end; |
10014 | } |
10015 | } |
10016 | |
10017 | /* Handle tables table. */ |
10018 | if ((found= handle_grant_table(thd, tables.tables_priv_table(), |
10019 | TABLES_PRIV_TABLE, drop, |
10020 | user_from, user_to)) < 0) |
10021 | { |
10022 | /* Handle of table failed, don't touch columns and in-memory array. */ |
10023 | result= -1; |
10024 | } |
10025 | else |
10026 | { |
10027 | if (found && ! result) |
10028 | { |
10029 | result= 1; /* At least one record found. */ |
10030 | /* If search is requested, we do not need to search further. */ |
10031 | if (search_only) |
10032 | goto end; |
10033 | } |
10034 | |
10035 | /* Handle columns table. */ |
10036 | if ((found= handle_grant_table(thd, tables.columns_priv_table(), |
10037 | COLUMNS_PRIV_TABLE, drop, |
10038 | user_from, user_to)) < 0) |
10039 | { |
10040 | /* Handle of table failed, don't touch the in-memory array. */ |
10041 | result= -1; |
10042 | } |
10043 | else |
10044 | { |
10045 | /* Handle columns hash. */ |
10046 | if ((handle_grant_struct(COLUMN_PRIVILEGES_HASH, drop, user_from, user_to) || found) |
10047 | && ! result) |
10048 | result= 1; /* At least one record/element found. */ |
10049 | if (search_only) |
10050 | goto end; |
10051 | } |
10052 | } |
10053 | |
10054 | /* Handle proxies_priv table. */ |
10055 | if (tables.proxies_priv_table().table_exists()) |
10056 | { |
10057 | if ((found= handle_grant_table(thd, tables.proxies_priv_table(), |
10058 | PROXIES_PRIV_TABLE, drop, |
10059 | user_from, user_to)) < 0) |
10060 | { |
10061 | /* Handle of table failed, don't touch the in-memory array. */ |
10062 | result= -1; |
10063 | } |
10064 | else |
10065 | { |
10066 | /* Handle proxies_priv array. */ |
10067 | if ((handle_grant_struct(PROXY_USERS_ACL, drop, user_from, user_to) || found) |
10068 | && ! result) |
10069 | result= 1; /* At least one record/element found. */ |
10070 | if (search_only) |
10071 | goto end; |
10072 | } |
10073 | } |
10074 | |
10075 | /* Handle roles_mapping table. */ |
10076 | if (tables.roles_mapping_table().table_exists()) |
10077 | { |
10078 | if ((found= handle_grant_table(thd, tables.roles_mapping_table(), |
10079 | ROLES_MAPPING_TABLE, drop, |
10080 | user_from, user_to)) < 0) |
10081 | { |
10082 | /* Handle of table failed, don't touch the in-memory array. */ |
10083 | result= -1; |
10084 | } |
10085 | else |
10086 | { |
10087 | /* Handle acl_roles_mappings array */ |
10088 | if ((handle_grant_struct(ROLES_MAPPINGS_HASH, drop, user_from, user_to) || found) |
10089 | && ! result) |
10090 | result= 1; /* At least one record/element found */ |
10091 | if (search_only) |
10092 | goto end; |
10093 | } |
10094 | } |
10095 | |
10096 | /* Handle user table. */ |
10097 | if ((found= handle_grant_table(thd, tables.user_table(), USER_TABLE, |
10098 | drop, user_from, user_to)) < 0) |
10099 | { |
10100 | /* Handle of table failed, don't touch the in-memory array. */ |
10101 | result= -1; |
10102 | } |
10103 | else |
10104 | { |
10105 | enum enum_acl_lists what= handle_as_role ? ROLE_ACL : USER_ACL; |
10106 | if (((handle_grant_struct(what, drop, user_from, user_to)) || found) && !result) |
10107 | { |
10108 | result= 1; /* At least one record/element found. */ |
10109 | DBUG_ASSERT(! search_only); |
10110 | } |
10111 | } |
10112 | |
10113 | end: |
10114 | DBUG_RETURN(result); |
10115 | } |
10116 | |
10117 | /* |
10118 | Create a list of users. |
10119 | |
10120 | SYNOPSIS |
10121 | mysql_create_user() |
10122 | thd The current thread. |
10123 | list The users to create. |
10124 | handle_as_role Handle the user list as roles if true |
10125 | |
10126 | RETURN |
10127 | FALSE OK. |
10128 | TRUE Error. |
10129 | */ |
10130 | |
10131 | bool mysql_create_user(THD *thd, List <LEX_USER> &list, bool handle_as_role) |
10132 | { |
10133 | int result; |
10134 | String wrong_users; |
10135 | LEX_USER *user_name; |
10136 | List_iterator <LEX_USER> user_list(list); |
10137 | bool binlog= false; |
10138 | DBUG_ENTER("mysql_create_user" ); |
10139 | DBUG_PRINT("entry" , ("Handle as %s" , handle_as_role ? "role" : "user" )); |
10140 | |
10141 | if (handle_as_role && sp_process_definer(thd)) |
10142 | DBUG_RETURN(TRUE); |
10143 | |
10144 | /* CREATE USER may be skipped on replication client. */ |
10145 | Grant_tables tables(Table_user | Table_db | |
10146 | Table_tables_priv | Table_columns_priv | |
10147 | Table_procs_priv | Table_proxies_priv | |
10148 | Table_roles_mapping, TL_WRITE); |
10149 | if ((result= tables.open_and_lock(thd))) |
10150 | DBUG_RETURN(result != 1); |
10151 | |
10152 | mysql_rwlock_wrlock(&LOCK_grant); |
10153 | mysql_mutex_lock(&acl_cache->lock); |
10154 | |
10155 | while ((user_name= user_list++)) |
10156 | { |
10157 | if (user_name->user.str == current_user.str) |
10158 | { |
10159 | append_str(&wrong_users, STRING_WITH_LEN("CURRENT_USER" )); |
10160 | result= TRUE; |
10161 | continue; |
10162 | } |
10163 | |
10164 | if (user_name->user.str == current_role.str) |
10165 | { |
10166 | append_str(&wrong_users, STRING_WITH_LEN("CURRENT_ROLE" )); |
10167 | result= TRUE; |
10168 | continue; |
10169 | } |
10170 | |
10171 | if (handle_as_role && is_invalid_role_name(user_name->user.str)) |
10172 | { |
10173 | append_user(thd, &wrong_users, user_name); |
10174 | result= TRUE; |
10175 | continue; |
10176 | } |
10177 | |
10178 | if (!user_name->host.str) |
10179 | user_name->host= host_not_specified; |
10180 | |
10181 | if (fix_lex_user(thd, user_name)) |
10182 | { |
10183 | append_user(thd, &wrong_users, user_name); |
10184 | result= TRUE; |
10185 | continue; |
10186 | } |
10187 | |
10188 | /* |
10189 | Search all in-memory structures and grant tables |
10190 | for a mention of the new user/role name. |
10191 | */ |
10192 | if (handle_grant_data(thd, tables, 0, user_name, NULL)) |
10193 | { |
10194 | if (thd->lex->create_info.or_replace()) |
10195 | { |
10196 | // Drop the existing user |
10197 | if (handle_grant_data(thd, tables, 1, user_name, NULL) <= 0) |
10198 | { |
10199 | // DROP failed |
10200 | append_user(thd, &wrong_users, user_name); |
10201 | result= true; |
10202 | continue; |
10203 | } |
10204 | // Proceed with the creation |
10205 | } |
10206 | else if (thd->lex->create_info.if_not_exists()) |
10207 | { |
10208 | binlog= true; |
10209 | if (handle_as_role) |
10210 | push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE, |
10211 | ER_ROLE_CREATE_EXISTS, |
10212 | ER_THD(thd, ER_ROLE_CREATE_EXISTS), |
10213 | user_name->user.str); |
10214 | else |
10215 | push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE, |
10216 | ER_USER_CREATE_EXISTS, |
10217 | ER_THD(thd, ER_USER_CREATE_EXISTS), |
10218 | user_name->user.str, user_name->host.str); |
10219 | continue; |
10220 | } |
10221 | else |
10222 | { |
10223 | // "CREATE USER user1" for an existing user |
10224 | append_user(thd, &wrong_users, user_name); |
10225 | result= true; |
10226 | continue; |
10227 | } |
10228 | } |
10229 | |
10230 | if (replace_user_table(thd, tables.user_table(), *user_name, 0, 0, 1, 0)) |
10231 | { |
10232 | append_user(thd, &wrong_users, user_name); |
10233 | result= TRUE; |
10234 | continue; |
10235 | } |
10236 | binlog= true; |
10237 | |
10238 | // every created role is automatically granted to its creator-admin |
10239 | if (handle_as_role) |
10240 | { |
10241 | ACL_USER_BASE *grantee= find_acl_user_base(thd->lex->definer->user.str, |
10242 | thd->lex->definer->host.str); |
10243 | ACL_ROLE *role= find_acl_role(user_name->user.str); |
10244 | |
10245 | /* |
10246 | just like with routines, views, triggers, and events we allow |
10247 | non-existant definers here with a warning (see sp_process_definer()) |
10248 | */ |
10249 | if (grantee) |
10250 | add_role_user_mapping(grantee, role); |
10251 | |
10252 | /* TODO(cvicentiu) refactor replace_roles_mapping_table to use |
10253 | Roles_mapping_table instead of TABLE directly. */ |
10254 | if (replace_roles_mapping_table(tables.roles_mapping_table().table(), |
10255 | &thd->lex->definer->user, |
10256 | &thd->lex->definer->host, |
10257 | &user_name->user, true, |
10258 | NULL, false)) |
10259 | { |
10260 | append_user(thd, &wrong_users, user_name); |
10261 | if (grantee) |
10262 | undo_add_role_user_mapping(grantee, role); |
10263 | result= TRUE; |
10264 | } |
10265 | else if (grantee) |
10266 | update_role_mapping(&thd->lex->definer->user, |
10267 | &thd->lex->definer->host, |
10268 | &user_name->user, true, NULL, false); |
10269 | } |
10270 | } |
10271 | |
10272 | mysql_mutex_unlock(&acl_cache->lock); |
10273 | |
10274 | if (result) |
10275 | my_error(ER_CANNOT_USER, MYF(0), |
10276 | (handle_as_role) ? "CREATE ROLE" : "CREATE USER" , |
10277 | wrong_users.c_ptr_safe()); |
10278 | |
10279 | if (binlog) |
10280 | result |= write_bin_log(thd, FALSE, thd->query(), thd->query_length()); |
10281 | |
10282 | mysql_rwlock_unlock(&LOCK_grant); |
10283 | DBUG_RETURN(result); |
10284 | } |
10285 | |
10286 | /* |
10287 | Drop a list of users and all their privileges. |
10288 | |
10289 | SYNOPSIS |
10290 | mysql_drop_user() |
10291 | thd The current thread. |
10292 | list The users to drop. |
10293 | |
10294 | RETURN |
10295 | FALSE OK. |
10296 | TRUE Error. |
10297 | */ |
10298 | |
10299 | bool mysql_drop_user(THD *thd, List <LEX_USER> &list, bool handle_as_role) |
10300 | { |
10301 | int result; |
10302 | String wrong_users; |
10303 | LEX_USER *user_name, *tmp_user_name; |
10304 | List_iterator <LEX_USER> user_list(list); |
10305 | bool binlog= false; |
10306 | sql_mode_t old_sql_mode= thd->variables.sql_mode; |
10307 | DBUG_ENTER("mysql_drop_user" ); |
10308 | DBUG_PRINT("entry" , ("Handle as %s" , handle_as_role ? "role" : "user" )); |
10309 | |
10310 | /* DROP USER may be skipped on replication client. */ |
10311 | Grant_tables tables(Table_user | Table_db | |
10312 | Table_tables_priv | Table_columns_priv | |
10313 | Table_procs_priv | Table_proxies_priv | |
10314 | Table_roles_mapping, TL_WRITE); |
10315 | if ((result= tables.open_and_lock(thd))) |
10316 | DBUG_RETURN(result != 1); |
10317 | |
10318 | thd->variables.sql_mode&= ~MODE_PAD_CHAR_TO_FULL_LENGTH; |
10319 | |
10320 | mysql_rwlock_wrlock(&LOCK_grant); |
10321 | mysql_mutex_lock(&acl_cache->lock); |
10322 | |
10323 | while ((tmp_user_name= user_list++)) |
10324 | { |
10325 | int rc; |
10326 | user_name= get_current_user(thd, tmp_user_name, false); |
10327 | if (!user_name) |
10328 | { |
10329 | thd->clear_error(); |
10330 | append_str(&wrong_users, STRING_WITH_LEN("CURRENT_ROLE" )); |
10331 | result= TRUE; |
10332 | continue; |
10333 | } |
10334 | |
10335 | if (handle_as_role != user_name->is_role()) |
10336 | { |
10337 | append_user(thd, &wrong_users, user_name); |
10338 | result= TRUE; |
10339 | continue; |
10340 | } |
10341 | |
10342 | if ((rc= handle_grant_data(thd, tables, 1, user_name, NULL)) > 0) |
10343 | { |
10344 | // The user or role was successfully deleted |
10345 | binlog= true; |
10346 | continue; |
10347 | } |
10348 | |
10349 | if (rc == 0 && thd->lex->if_exists()) |
10350 | { |
10351 | // "DROP USER IF EXISTS user1" for a non-existing user or role |
10352 | if (handle_as_role) |
10353 | push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE, |
10354 | ER_ROLE_DROP_EXISTS, |
10355 | ER_THD(thd, ER_ROLE_DROP_EXISTS), |
10356 | user_name->user.str); |
10357 | else |
10358 | push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE, |
10359 | ER_USER_DROP_EXISTS, |
10360 | ER_THD(thd, ER_USER_DROP_EXISTS), |
10361 | user_name->user.str, user_name->host.str); |
10362 | binlog= true; |
10363 | continue; |
10364 | } |
10365 | // Internal error, or "DROP USER user1" for a non-existing user |
10366 | append_user(thd, &wrong_users, user_name); |
10367 | result= TRUE; |
10368 | } |
10369 | |
10370 | if (!handle_as_role) |
10371 | { |
10372 | /* Rebuild 'acl_check_hosts' since 'acl_users' has been modified */ |
10373 | rebuild_check_host(); |
10374 | |
10375 | /* |
10376 | Rebuild every user's role_grants since 'acl_users' has been sorted |
10377 | and old pointers to ACL_USER elements are no longer valid |
10378 | */ |
10379 | rebuild_role_grants(); |
10380 | } |
10381 | |
10382 | mysql_mutex_unlock(&acl_cache->lock); |
10383 | |
10384 | if (result) |
10385 | my_error(ER_CANNOT_USER, MYF(0), |
10386 | (handle_as_role) ? "DROP ROLE" : "DROP USER" , |
10387 | wrong_users.c_ptr_safe()); |
10388 | |
10389 | if (binlog) |
10390 | result |= write_bin_log(thd, FALSE, thd->query(), thd->query_length()); |
10391 | |
10392 | mysql_rwlock_unlock(&LOCK_grant); |
10393 | thd->variables.sql_mode= old_sql_mode; |
10394 | DBUG_RETURN(result); |
10395 | } |
10396 | |
10397 | /* |
10398 | Rename a user. |
10399 | |
10400 | SYNOPSIS |
10401 | mysql_rename_user() |
10402 | thd The current thread. |
10403 | list The user name pairs: (from, to). |
10404 | |
10405 | RETURN |
10406 | FALSE OK. |
10407 | TRUE Error. |
10408 | */ |
10409 | |
10410 | bool mysql_rename_user(THD *thd, List <LEX_USER> &list) |
10411 | { |
10412 | int result; |
10413 | String wrong_users; |
10414 | LEX_USER *user_from, *tmp_user_from; |
10415 | LEX_USER *user_to, *tmp_user_to; |
10416 | List_iterator <LEX_USER> user_list(list); |
10417 | bool some_users_renamed= FALSE; |
10418 | DBUG_ENTER("mysql_rename_user" ); |
10419 | |
10420 | /* RENAME USER may be skipped on replication client. */ |
10421 | Grant_tables tables(Table_user | Table_db | |
10422 | Table_tables_priv | Table_columns_priv | |
10423 | Table_procs_priv | Table_proxies_priv | |
10424 | Table_roles_mapping, TL_WRITE); |
10425 | if ((result= tables.open_and_lock(thd))) |
10426 | DBUG_RETURN(result != 1); |
10427 | |
10428 | DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); |
10429 | |
10430 | mysql_rwlock_wrlock(&LOCK_grant); |
10431 | mysql_mutex_lock(&acl_cache->lock); |
10432 | |
10433 | while ((tmp_user_from= user_list++)) |
10434 | { |
10435 | tmp_user_to= user_list++; |
10436 | if (!(user_from= get_current_user(thd, tmp_user_from, false))) |
10437 | { |
10438 | append_user(thd, &wrong_users, user_from); |
10439 | result= TRUE; |
10440 | continue; |
10441 | } |
10442 | if (!(user_to= get_current_user(thd, tmp_user_to, false))) |
10443 | { |
10444 | append_user(thd, &wrong_users, user_to); |
10445 | result= TRUE; |
10446 | continue; |
10447 | } |
10448 | DBUG_ASSERT(!user_from->is_role()); |
10449 | DBUG_ASSERT(!user_to->is_role()); |
10450 | |
10451 | /* |
10452 | Search all in-memory structures and grant tables |
10453 | for a mention of the new user name. |
10454 | */ |
10455 | if (handle_grant_data(thd, tables, 0, user_to, NULL) || |
10456 | handle_grant_data(thd, tables, 0, user_from, user_to) <= 0) |
10457 | { |
10458 | /* NOTE TODO renaming roles is not yet implemented */ |
10459 | append_user(thd, &wrong_users, user_from); |
10460 | result= TRUE; |
10461 | continue; |
10462 | } |
10463 | some_users_renamed= TRUE; |
10464 | } |
10465 | |
10466 | /* Rebuild 'acl_check_hosts' since 'acl_users' has been modified */ |
10467 | rebuild_check_host(); |
10468 | |
10469 | /* |
10470 | Rebuild every user's role_grants since 'acl_users' has been sorted |
10471 | and old pointers to ACL_USER elements are no longer valid |
10472 | */ |
10473 | rebuild_role_grants(); |
10474 | |
10475 | mysql_mutex_unlock(&acl_cache->lock); |
10476 | |
10477 | if (result) |
10478 | my_error(ER_CANNOT_USER, MYF(0), "RENAME USER" , wrong_users.c_ptr_safe()); |
10479 | |
10480 | if (some_users_renamed && mysql_bin_log.is_open()) |
10481 | result |= write_bin_log(thd, FALSE, thd->query(), thd->query_length()); |
10482 | |
10483 | mysql_rwlock_unlock(&LOCK_grant); |
10484 | DBUG_RETURN(result); |
10485 | } |
10486 | |
10487 | /* |
10488 | Alter a user's connection and resource settings. |
10489 | |
10490 | SYNOPSIS |
10491 | mysql_alter_user() |
10492 | thd The current thread. |
10493 | list The users to alter. |
10494 | |
10495 | RETURN |
10496 | > 0 Error. Error message already sent. |
10497 | 0 OK. |
10498 | */ |
10499 | int mysql_alter_user(THD* thd, List<LEX_USER> &users_list) |
10500 | { |
10501 | DBUG_ENTER("mysql_alter_user" ); |
10502 | int result= 0; |
10503 | String wrong_users; |
10504 | |
10505 | /* The only table we're altering is the user table. */ |
10506 | Grant_tables tables(Table_user, TL_WRITE); |
10507 | if ((result= tables.open_and_lock(thd))) |
10508 | DBUG_RETURN(result != 1); |
10509 | |
10510 | /* Lock ACL data structures until we finish altering all users. */ |
10511 | mysql_rwlock_wrlock(&LOCK_grant); |
10512 | mysql_mutex_lock(&acl_cache->lock); |
10513 | |
10514 | LEX_USER *tmp_lex_user; |
10515 | List_iterator<LEX_USER> users_list_iterator(users_list); |
10516 | while ((tmp_lex_user= users_list_iterator++)) |
10517 | { |
10518 | LEX_USER* lex_user= get_current_user(thd, tmp_lex_user, false); |
10519 | if (!lex_user || |
10520 | fix_lex_user(thd, lex_user) || |
10521 | replace_user_table(thd, tables.user_table(), *lex_user, 0, |
10522 | false, false, true)) |
10523 | { |
10524 | thd->clear_error(); |
10525 | append_user(thd, &wrong_users, tmp_lex_user); |
10526 | result= TRUE; |
10527 | continue; |
10528 | } |
10529 | } |
10530 | |
10531 | /* Unlock ACL data structures. */ |
10532 | mysql_mutex_unlock(&acl_cache->lock); |
10533 | mysql_rwlock_unlock(&LOCK_grant); |
10534 | |
10535 | if (result) |
10536 | { |
10537 | /* 'if exists' flag leads to warnings instead of errors. */ |
10538 | if (thd->lex->create_info.if_exists()) |
10539 | { |
10540 | push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE, |
10541 | ER_CANNOT_USER, |
10542 | ER_THD(thd, ER_CANNOT_USER), |
10543 | "ALTER USER" , wrong_users.c_ptr_safe()); |
10544 | result= FALSE; |
10545 | } |
10546 | else |
10547 | { |
10548 | my_error(ER_CANNOT_USER, MYF(0), |
10549 | "ALTER USER" , |
10550 | wrong_users.c_ptr_safe()); |
10551 | } |
10552 | } |
10553 | DBUG_RETURN(result); |
10554 | } |
10555 | |
10556 | |
10557 | static bool |
10558 | mysql_revoke_sp_privs(THD *thd, |
10559 | Grant_tables *tables, |
10560 | const Sp_handler *sph, |
10561 | const LEX_USER *lex_user) |
10562 | { |
10563 | bool rc= false; |
10564 | uint counter, revoked; |
10565 | do { |
10566 | HASH *hash= sph->get_priv_hash(); |
10567 | for (counter= 0, revoked= 0 ; counter < hash->records ; ) |
10568 | { |
10569 | const char *user,*host; |
10570 | GRANT_NAME *grant_proc= (GRANT_NAME*) my_hash_element(hash, counter); |
10571 | user= safe_str(grant_proc->user); |
10572 | host= safe_str(grant_proc->host.hostname); |
10573 | |
10574 | if (!strcmp(lex_user->user.str, user) && |
10575 | !strcmp(lex_user->host.str, host)) |
10576 | { |
10577 | if (replace_routine_table(thd, grant_proc, |
10578 | tables->procs_priv_table().table(), |
10579 | *lex_user, |
10580 | grant_proc->db, grant_proc->tname, |
10581 | sph, ~(ulong)0, 1) == 0) |
10582 | { |
10583 | revoked= 1; |
10584 | continue; |
10585 | } |
10586 | rc= true; // Something went wrong |
10587 | } |
10588 | counter++; |
10589 | } |
10590 | } while (revoked); |
10591 | return rc; |
10592 | } |
10593 | |
10594 | |
10595 | /* |
10596 | Revoke all privileges from a list of users. |
10597 | |
10598 | SYNOPSIS |
10599 | mysql_revoke_all() |
10600 | thd The current thread. |
10601 | list The users to revoke all privileges from. |
10602 | |
10603 | RETURN |
10604 | > 0 Error. Error message already sent. |
10605 | 0 OK. |
10606 | < 0 Error. Error message not yet sent. |
10607 | */ |
10608 | |
10609 | bool mysql_revoke_all(THD *thd, List <LEX_USER> &list) |
10610 | { |
10611 | uint counter, revoked; |
10612 | int result; |
10613 | ACL_DB *acl_db; |
10614 | DBUG_ENTER("mysql_revoke_all" ); |
10615 | |
10616 | Grant_tables tables(Table_user | Table_db | |
10617 | Table_tables_priv | Table_columns_priv | |
10618 | Table_procs_priv | Table_proxies_priv | |
10619 | Table_roles_mapping, TL_WRITE); |
10620 | if ((result= tables.open_and_lock(thd))) |
10621 | DBUG_RETURN(result != 1); |
10622 | |
10623 | DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); |
10624 | |
10625 | mysql_rwlock_wrlock(&LOCK_grant); |
10626 | mysql_mutex_lock(&acl_cache->lock); |
10627 | |
10628 | LEX_USER *lex_user, *tmp_lex_user; |
10629 | List_iterator <LEX_USER> user_list(list); |
10630 | while ((tmp_lex_user= user_list++)) |
10631 | { |
10632 | if (!(lex_user= get_current_user(thd, tmp_lex_user, false))) |
10633 | { |
10634 | result= -1; |
10635 | continue; |
10636 | } |
10637 | |
10638 | /* This is not a role and the user could not be found */ |
10639 | if (!lex_user->is_role() && |
10640 | !find_user_exact(lex_user->host.str, lex_user->user.str)) |
10641 | { |
10642 | result= -1; |
10643 | continue; |
10644 | } |
10645 | |
10646 | if (replace_user_table(thd, tables.user_table(), *lex_user, |
10647 | ~(ulong)0, 1, 0, 0)) |
10648 | { |
10649 | result= -1; |
10650 | continue; |
10651 | } |
10652 | |
10653 | /* Remove db access privileges */ |
10654 | /* |
10655 | Because acl_dbs and column_priv_hash shrink and may re-order |
10656 | as privileges are removed, removal occurs in a repeated loop |
10657 | until no more privileges are revoked. |
10658 | */ |
10659 | do |
10660 | { |
10661 | for (counter= 0, revoked= 0 ; counter < acl_dbs.elements ; ) |
10662 | { |
10663 | const char *user,*host; |
10664 | |
10665 | acl_db=dynamic_element(&acl_dbs,counter,ACL_DB*); |
10666 | |
10667 | user= safe_str(acl_db->user); |
10668 | host= safe_str(acl_db->host.hostname); |
10669 | |
10670 | if (!strcmp(lex_user->user.str, user) && |
10671 | !strcmp(lex_user->host.str, host)) |
10672 | { |
10673 | /* TODO(cvicentiu) refactor replace_db_table to use |
10674 | Db_table instead of TABLE directly. */ |
10675 | if (!replace_db_table(tables.db_table().table(), acl_db->db, *lex_user, |
10676 | ~(ulong)0, 1)) |
10677 | { |
10678 | /* |
10679 | Don't increment counter as replace_db_table deleted the |
10680 | current element in acl_dbs. |
10681 | */ |
10682 | revoked= 1; |
10683 | continue; |
10684 | } |
10685 | result= -1; // Something went wrong |
10686 | } |
10687 | counter++; |
10688 | } |
10689 | } while (revoked); |
10690 | |
10691 | /* Remove column access */ |
10692 | do |
10693 | { |
10694 | for (counter= 0, revoked= 0 ; counter < column_priv_hash.records ; ) |
10695 | { |
10696 | const char *user,*host; |
10697 | GRANT_TABLE *grant_table= |
10698 | (GRANT_TABLE*) my_hash_element(&column_priv_hash, counter); |
10699 | user= safe_str(grant_table->user); |
10700 | host= safe_str(grant_table->host.hostname); |
10701 | |
10702 | if (!strcmp(lex_user->user.str,user) && |
10703 | !strcmp(lex_user->host.str, host)) |
10704 | { |
10705 | /* TODO(cvicentiu) refactor replace_db_table to use |
10706 | Db_table instead of TABLE directly. */ |
10707 | if (replace_table_table(thd, grant_table, |
10708 | tables.tables_priv_table().table(), |
10709 | *lex_user, grant_table->db, |
10710 | grant_table->tname, ~(ulong)0, 0, 1)) |
10711 | { |
10712 | result= -1; |
10713 | } |
10714 | else |
10715 | { |
10716 | if (!grant_table->cols) |
10717 | { |
10718 | revoked= 1; |
10719 | continue; |
10720 | } |
10721 | List<LEX_COLUMN> columns; |
10722 | /* TODO(cvicentiu) refactor replace_db_table to use |
10723 | Db_table instead of TABLE directly. */ |
10724 | if (!replace_column_table(grant_table, |
10725 | tables.columns_priv_table().table(), |
10726 | *lex_user, columns, grant_table->db, |
10727 | grant_table->tname, ~(ulong)0, 1)) |
10728 | { |
10729 | revoked= 1; |
10730 | continue; |
10731 | } |
10732 | result= -1; |
10733 | } |
10734 | } |
10735 | counter++; |
10736 | } |
10737 | } while (revoked); |
10738 | |
10739 | /* Remove procedure access */ |
10740 | if (mysql_revoke_sp_privs(thd, &tables, &sp_handler_function, lex_user) || |
10741 | mysql_revoke_sp_privs(thd, &tables, &sp_handler_procedure, lex_user) || |
10742 | mysql_revoke_sp_privs(thd, &tables, &sp_handler_package_spec, lex_user) || |
10743 | mysql_revoke_sp_privs(thd, &tables, &sp_handler_package_body, lex_user)) |
10744 | result= -1; |
10745 | |
10746 | ACL_USER_BASE *user_or_role; |
10747 | /* remove role grants */ |
10748 | if (lex_user->is_role()) |
10749 | { |
10750 | /* this can not fail due to get_current_user already having searched for it */ |
10751 | user_or_role= find_acl_role(lex_user->user.str); |
10752 | } |
10753 | else |
10754 | { |
10755 | user_or_role= find_user_exact(lex_user->host.str, lex_user->user.str); |
10756 | } |
10757 | /* |
10758 | Find every role grant pair matching the role_grants array and remove it, |
10759 | both from the acl_roles_mappings and the roles_mapping table |
10760 | */ |
10761 | for (counter= 0; counter < user_or_role->role_grants.elements; counter++) |
10762 | { |
10763 | ACL_ROLE *role_grant= *dynamic_element(&user_or_role->role_grants, |
10764 | counter, ACL_ROLE**); |
10765 | ROLE_GRANT_PAIR *pair = find_role_grant_pair(&lex_user->user, |
10766 | &lex_user->host, |
10767 | &role_grant->user); |
10768 | /* TODO(cvicentiu) refactor replace_roles_mapping_table to use |
10769 | Roles_mapping_table instead of TABLE directly. */ |
10770 | if (replace_roles_mapping_table(tables.roles_mapping_table().table(), |
10771 | &lex_user->user, &lex_user->host, |
10772 | &role_grant->user, false, pair, true)) |
10773 | { |
10774 | result= -1; //Something went wrong |
10775 | } |
10776 | update_role_mapping(&lex_user->user, &lex_user->host, |
10777 | &role_grant->user, false, pair, true); |
10778 | /* |
10779 | Delete from the parent_grantee array of the roles granted, |
10780 | the entry pointing to this user_or_role |
10781 | */ |
10782 | remove_ptr_from_dynarray(&role_grant->parent_grantee, user_or_role); |
10783 | } |
10784 | /* TODO |
10785 | How to handle an error in the replace_roles_mapping_table, in |
10786 | regards to the privileges held in memory |
10787 | */ |
10788 | |
10789 | /* Finally, clear the role_grants array */ |
10790 | if (counter == user_or_role->role_grants.elements) |
10791 | { |
10792 | reset_dynamic(&user_or_role->role_grants); |
10793 | } |
10794 | /* |
10795 | If we are revoking from a role, we need to update all the parent grantees |
10796 | */ |
10797 | if (lex_user->is_role()) |
10798 | { |
10799 | propagate_role_grants((ACL_ROLE *)user_or_role, PRIVS_TO_MERGE::ALL); |
10800 | } |
10801 | } |
10802 | |
10803 | mysql_mutex_unlock(&acl_cache->lock); |
10804 | |
10805 | if (result) |
10806 | my_message(ER_REVOKE_GRANTS, ER_THD(thd, ER_REVOKE_GRANTS), MYF(0)); |
10807 | |
10808 | result= result | |
10809 | write_bin_log(thd, FALSE, thd->query(), thd->query_length()); |
10810 | |
10811 | mysql_rwlock_unlock(&LOCK_grant); |
10812 | |
10813 | DBUG_RETURN(result); |
10814 | } |
10815 | |
10816 | |
10817 | |
10818 | |
10819 | /** |
10820 | If the defining user for a routine does not exist, then the ACL lookup |
10821 | code should raise two errors which we should intercept. We convert the more |
10822 | descriptive error into a warning, and consume the other. |
10823 | |
10824 | If any other errors are raised, then we set a flag that should indicate |
10825 | that there was some failure we should complain at a higher level. |
10826 | */ |
10827 | class Silence_routine_definer_errors : public Internal_error_handler |
10828 | { |
10829 | public: |
10830 | Silence_routine_definer_errors() |
10831 | : is_grave(FALSE) |
10832 | {} |
10833 | |
10834 | virtual ~Silence_routine_definer_errors() |
10835 | {} |
10836 | |
10837 | virtual bool handle_condition(THD *thd, |
10838 | uint sql_errno, |
10839 | const char* sqlstate, |
10840 | Sql_condition::enum_warning_level *level, |
10841 | const char* msg, |
10842 | Sql_condition ** cond_hdl); |
10843 | |
10844 | bool has_errors() { return is_grave; } |
10845 | |
10846 | private: |
10847 | bool is_grave; |
10848 | }; |
10849 | |
10850 | bool |
10851 | Silence_routine_definer_errors::handle_condition( |
10852 | THD *thd, |
10853 | uint sql_errno, |
10854 | const char*, |
10855 | Sql_condition::enum_warning_level *level, |
10856 | const char* msg, |
10857 | Sql_condition ** cond_hdl) |
10858 | { |
10859 | *cond_hdl= NULL; |
10860 | if (*level == Sql_condition::WARN_LEVEL_ERROR) |
10861 | { |
10862 | switch (sql_errno) |
10863 | { |
10864 | case ER_NONEXISTING_PROC_GRANT: |
10865 | /* Convert the error into a warning. */ |
10866 | push_warning(thd, Sql_condition::WARN_LEVEL_WARN, |
10867 | sql_errno, msg); |
10868 | return TRUE; |
10869 | default: |
10870 | is_grave= TRUE; |
10871 | } |
10872 | } |
10873 | |
10874 | return FALSE; |
10875 | } |
10876 | |
10877 | |
10878 | /** |
10879 | Revoke privileges for all users on a stored procedure. Use an error handler |
10880 | that converts errors about missing grants into warnings. |
10881 | |
10882 | @param |
10883 | thd The current thread. |
10884 | @param |
10885 | db DB of the stored procedure |
10886 | @param |
10887 | name Name of the stored procedure |
10888 | |
10889 | @retval |
10890 | 0 OK. |
10891 | @retval |
10892 | < 0 Error. Error message not yet sent. |
10893 | */ |
10894 | |
10895 | bool sp_revoke_privileges(THD *thd, const char *sp_db, const char *sp_name, |
10896 | const Sp_handler *sph) |
10897 | { |
10898 | uint counter, revoked; |
10899 | int result; |
10900 | HASH *hash= sph->get_priv_hash(); |
10901 | Silence_routine_definer_errors error_handler; |
10902 | DBUG_ENTER("sp_revoke_privileges" ); |
10903 | |
10904 | Grant_tables tables(Table_user | Table_db | |
10905 | Table_tables_priv | Table_columns_priv | |
10906 | Table_procs_priv | Table_proxies_priv | |
10907 | Table_roles_mapping, TL_WRITE); |
10908 | if ((result= tables.open_and_lock(thd))) |
10909 | DBUG_RETURN(result != 1); |
10910 | |
10911 | DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); |
10912 | |
10913 | /* Be sure to pop this before exiting this scope! */ |
10914 | thd->push_internal_handler(&error_handler); |
10915 | |
10916 | mysql_rwlock_wrlock(&LOCK_grant); |
10917 | mysql_mutex_lock(&acl_cache->lock); |
10918 | |
10919 | /* Remove procedure access */ |
10920 | do |
10921 | { |
10922 | for (counter= 0, revoked= 0 ; counter < hash->records ; ) |
10923 | { |
10924 | GRANT_NAME *grant_proc= (GRANT_NAME*) my_hash_element(hash, counter); |
10925 | if (!my_strcasecmp(&my_charset_utf8_bin, grant_proc->db, sp_db) && |
10926 | !my_strcasecmp(system_charset_info, grant_proc->tname, sp_name)) |
10927 | { |
10928 | LEX_USER lex_user; |
10929 | lex_user.user.str= grant_proc->user; |
10930 | lex_user.user.length= strlen(grant_proc->user); |
10931 | lex_user.host.str= safe_str(grant_proc->host.hostname); |
10932 | lex_user.host.length= strlen(lex_user.host.str); |
10933 | if (replace_routine_table(thd, grant_proc, |
10934 | tables.procs_priv_table().table(), lex_user, |
10935 | grant_proc->db, grant_proc->tname, |
10936 | sph, ~(ulong)0, 1) == 0) |
10937 | { |
10938 | revoked= 1; |
10939 | continue; |
10940 | } |
10941 | } |
10942 | counter++; |
10943 | } |
10944 | } while (revoked); |
10945 | |
10946 | mysql_mutex_unlock(&acl_cache->lock); |
10947 | mysql_rwlock_unlock(&LOCK_grant); |
10948 | |
10949 | thd->pop_internal_handler(); |
10950 | |
10951 | DBUG_RETURN(error_handler.has_errors()); |
10952 | } |
10953 | |
10954 | |
10955 | /** |
10956 | Grant EXECUTE,ALTER privilege for a stored procedure |
10957 | |
10958 | @param thd The current thread. |
10959 | @param sp_db |
10960 | @param sp_name |
10961 | @param sph |
10962 | |
10963 | @return |
10964 | @retval FALSE Success |
10965 | @retval TRUE An error occurred. Error message not yet sent. |
10966 | */ |
10967 | |
10968 | bool sp_grant_privileges(THD *thd, const char *sp_db, const char *sp_name, |
10969 | const Sp_handler *sph) |
10970 | { |
10971 | Security_context *sctx= thd->security_ctx; |
10972 | LEX_USER *combo; |
10973 | TABLE_LIST tables[1]; |
10974 | List<LEX_USER> user_list; |
10975 | bool result; |
10976 | ACL_USER *au; |
10977 | Dummy_error_handler error_handler; |
10978 | DBUG_ENTER("sp_grant_privileges" ); |
10979 | |
10980 | if (!(combo=(LEX_USER*) thd->alloc(sizeof(LEX_USER)))) |
10981 | DBUG_RETURN(TRUE); |
10982 | |
10983 | combo->user.str= sctx->user; |
10984 | |
10985 | mysql_mutex_lock(&acl_cache->lock); |
10986 | |
10987 | if ((au= find_user_wild(combo->host.str=(char*)sctx->host_or_ip, combo->user.str))) |
10988 | goto found_acl; |
10989 | if ((au= find_user_wild(combo->host.str=(char*)sctx->host, combo->user.str))) |
10990 | goto found_acl; |
10991 | if ((au= find_user_wild(combo->host.str=(char*)sctx->ip, combo->user.str))) |
10992 | goto found_acl; |
10993 | if ((au= find_user_wild(combo->host.str=(char*)"%" , combo->user.str))) |
10994 | goto found_acl; |
10995 | |
10996 | mysql_mutex_unlock(&acl_cache->lock); |
10997 | DBUG_RETURN(TRUE); |
10998 | |
10999 | found_acl: |
11000 | mysql_mutex_unlock(&acl_cache->lock); |
11001 | |
11002 | bzero((char*)tables, sizeof(TABLE_LIST)); |
11003 | user_list.empty(); |
11004 | |
11005 | tables->db.str= sp_db; |
11006 | tables->db.length= sp_db ? strlen(sp_db) : 0; |
11007 | tables->table_name.str= tables->alias.str= sp_name; |
11008 | tables->table_name.length= tables->alias.length= sp_name ? strlen(sp_name) : 0; |
11009 | |
11010 | thd->make_lex_string(&combo->user, combo->user.str, strlen(combo->user.str)); |
11011 | thd->make_lex_string(&combo->host, combo->host.str, strlen(combo->host.str)); |
11012 | |
11013 | combo->reset_auth(); |
11014 | |
11015 | if(au) |
11016 | { |
11017 | combo->plugin= au->plugin; |
11018 | combo->auth= au->auth_string; |
11019 | } |
11020 | |
11021 | if (user_list.push_back(combo, thd->mem_root)) |
11022 | DBUG_RETURN(TRUE); |
11023 | |
11024 | thd->lex->ssl_type= SSL_TYPE_NOT_SPECIFIED; |
11025 | thd->lex->ssl_cipher= thd->lex->x509_subject= thd->lex->x509_issuer= 0; |
11026 | bzero((char*) &thd->lex->mqh, sizeof(thd->lex->mqh)); |
11027 | |
11028 | /* |
11029 | Only care about whether the operation failed or succeeded |
11030 | as all errors will be handled later. |
11031 | */ |
11032 | thd->push_internal_handler(&error_handler); |
11033 | result= mysql_routine_grant(thd, tables, sph, user_list, |
11034 | DEFAULT_CREATE_PROC_ACLS, FALSE, FALSE); |
11035 | thd->pop_internal_handler(); |
11036 | DBUG_RETURN(result); |
11037 | } |
11038 | |
11039 | |
11040 | /** |
11041 | Validate if a user can proxy as another user |
11042 | |
11043 | @thd current thread |
11044 | @param user the logged in user (proxy user) |
11045 | @param authenticated_as the effective user a plugin is trying to |
11046 | impersonate as (proxied user) |
11047 | @return proxy user definition |
11048 | @retval NULL proxy user definition not found or not applicable |
11049 | @retval non-null the proxy user data |
11050 | */ |
11051 | |
11052 | static ACL_PROXY_USER * |
11053 | acl_find_proxy_user(const char *user, const char *host, const char *ip, |
11054 | const char *authenticated_as, bool *proxy_used) |
11055 | { |
11056 | uint i; |
11057 | /* if the proxied and proxy user are the same return OK */ |
11058 | DBUG_ENTER("acl_find_proxy_user" ); |
11059 | DBUG_PRINT("info" , ("user=%s host=%s ip=%s authenticated_as=%s" , |
11060 | user, host, ip, authenticated_as)); |
11061 | |
11062 | if (!strcmp(authenticated_as, user)) |
11063 | { |
11064 | DBUG_PRINT ("info" , ("user is the same as authenticated_as" )); |
11065 | DBUG_RETURN (NULL); |
11066 | } |
11067 | |
11068 | *proxy_used= TRUE; |
11069 | for (i=0; i < acl_proxy_users.elements; i++) |
11070 | { |
11071 | ACL_PROXY_USER *proxy= dynamic_element(&acl_proxy_users, i, |
11072 | ACL_PROXY_USER *); |
11073 | if (proxy->matches(host, user, ip, authenticated_as)) |
11074 | DBUG_RETURN(proxy); |
11075 | } |
11076 | |
11077 | DBUG_RETURN(NULL); |
11078 | } |
11079 | |
11080 | |
11081 | bool |
11082 | acl_check_proxy_grant_access(THD *thd, const char *host, const char *user, |
11083 | bool with_grant) |
11084 | { |
11085 | DBUG_ENTER("acl_check_proxy_grant_access" ); |
11086 | DBUG_PRINT("info" , ("user=%s host=%s with_grant=%d" , user, host, |
11087 | (int) with_grant)); |
11088 | if (!initialized) |
11089 | { |
11090 | my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--skip-grant-tables" ); |
11091 | DBUG_RETURN(1); |
11092 | } |
11093 | |
11094 | /* replication slave thread can do anything */ |
11095 | if (thd->slave_thread) |
11096 | { |
11097 | DBUG_PRINT("info" , ("replication slave" )); |
11098 | DBUG_RETURN(FALSE); |
11099 | } |
11100 | |
11101 | /* |
11102 | one can grant proxy for self to others. |
11103 | Security context in THD contains two pairs of (user,host): |
11104 | 1. (user,host) pair referring to inbound connection. |
11105 | 2. (priv_user,priv_host) pair obtained from mysql.user table after doing |
11106 | authnetication of incoming connection. |
11107 | Privileges should be checked wrt (priv_user, priv_host) tuple, because |
11108 | (user,host) pair obtained from inbound connection may have different |
11109 | values than what is actually stored in mysql.user table and while granting |
11110 | or revoking proxy privilege, user is expected to provide entries mentioned |
11111 | in mysql.user table. |
11112 | */ |
11113 | if (!strcmp(thd->security_ctx->priv_user, user) && |
11114 | !my_strcasecmp(system_charset_info, host, |
11115 | thd->security_ctx->priv_host)) |
11116 | { |
11117 | DBUG_PRINT("info" , ("strcmp (%s, %s) my_casestrcmp (%s, %s) equal" , |
11118 | thd->security_ctx->priv_user, user, |
11119 | host, thd->security_ctx->priv_host)); |
11120 | DBUG_RETURN(FALSE); |
11121 | } |
11122 | |
11123 | mysql_mutex_lock(&acl_cache->lock); |
11124 | |
11125 | /* check for matching WITH PROXY rights */ |
11126 | for (uint i=0; i < acl_proxy_users.elements; i++) |
11127 | { |
11128 | ACL_PROXY_USER *proxy= dynamic_element(&acl_proxy_users, i, |
11129 | ACL_PROXY_USER *); |
11130 | if (proxy->matches(thd->security_ctx->host, |
11131 | thd->security_ctx->user, |
11132 | thd->security_ctx->ip, |
11133 | user) && |
11134 | proxy->get_with_grant()) |
11135 | { |
11136 | DBUG_PRINT("info" , ("found" )); |
11137 | mysql_mutex_unlock(&acl_cache->lock); |
11138 | DBUG_RETURN(FALSE); |
11139 | } |
11140 | } |
11141 | |
11142 | mysql_mutex_unlock(&acl_cache->lock); |
11143 | my_error(ER_ACCESS_DENIED_NO_PASSWORD_ERROR, MYF(0), |
11144 | thd->security_ctx->user, |
11145 | thd->security_ctx->host_or_ip); |
11146 | DBUG_RETURN(TRUE); |
11147 | } |
11148 | |
11149 | |
11150 | static bool |
11151 | show_proxy_grants(THD *thd, const char *username, const char *hostname, |
11152 | char *buff, size_t buffsize) |
11153 | { |
11154 | Protocol *protocol= thd->protocol; |
11155 | int error= 0; |
11156 | |
11157 | for (uint i=0; i < acl_proxy_users.elements; i++) |
11158 | { |
11159 | ACL_PROXY_USER *proxy= dynamic_element(&acl_proxy_users, i, |
11160 | ACL_PROXY_USER *); |
11161 | if (proxy->granted_on(hostname, username)) |
11162 | { |
11163 | String global(buff, buffsize, system_charset_info); |
11164 | global.length(0); |
11165 | proxy->print_grant(&global); |
11166 | protocol->prepare_for_resend(); |
11167 | protocol->store(global.ptr(), global.length(), global.charset()); |
11168 | if (protocol->write()) |
11169 | { |
11170 | error= -1; |
11171 | break; |
11172 | } |
11173 | } |
11174 | } |
11175 | return error; |
11176 | } |
11177 | |
11178 | static int enabled_roles_insert(ACL_USER_BASE *role, void *context_data) |
11179 | { |
11180 | TABLE *table= (TABLE*) context_data; |
11181 | DBUG_ASSERT(role->flags & IS_ROLE); |
11182 | |
11183 | restore_record(table, s->default_values); |
11184 | table->field[0]->set_notnull(); |
11185 | table->field[0]->store(role->user.str, role->user.length, |
11186 | system_charset_info); |
11187 | if (schema_table_store_record(table->in_use, table)) |
11188 | return -1; |
11189 | return 0; |
11190 | } |
11191 | |
11192 | struct APPLICABLE_ROLES_DATA |
11193 | { |
11194 | TABLE *table; |
11195 | const LEX_CSTRING host; |
11196 | const LEX_CSTRING user_and_host; |
11197 | ACL_USER *user; |
11198 | }; |
11199 | |
11200 | static int |
11201 | applicable_roles_insert(ACL_USER_BASE *grantee, ACL_ROLE *role, void *ptr) |
11202 | { |
11203 | APPLICABLE_ROLES_DATA *data= (APPLICABLE_ROLES_DATA *)ptr; |
11204 | CHARSET_INFO *cs= system_charset_info; |
11205 | TABLE *table= data->table; |
11206 | bool is_role= grantee != data->user; |
11207 | const LEX_CSTRING *user_and_host= is_role ? &grantee->user |
11208 | : &data->user_and_host; |
11209 | const LEX_CSTRING *host= is_role ? &empty_clex_str : &data->host; |
11210 | |
11211 | restore_record(table, s->default_values); |
11212 | table->field[0]->store(user_and_host->str, user_and_host->length, cs); |
11213 | table->field[1]->store(role->user.str, role->user.length, cs); |
11214 | |
11215 | ROLE_GRANT_PAIR *pair= |
11216 | find_role_grant_pair(&grantee->user, host, &role->user); |
11217 | DBUG_ASSERT(pair); |
11218 | |
11219 | if (pair->with_admin) |
11220 | table->field[2]->store(STRING_WITH_LEN("YES" ), cs); |
11221 | else |
11222 | table->field[2]->store(STRING_WITH_LEN("NO" ), cs); |
11223 | |
11224 | /* Default role is only valid when looking at a role granted to a user. */ |
11225 | if (!is_role) |
11226 | { |
11227 | if (data->user->default_rolename.length && |
11228 | lex_string_eq(&data->user->default_rolename, &role->user)) |
11229 | table->field[3]->store(STRING_WITH_LEN("YES" ), cs); |
11230 | else |
11231 | table->field[3]->store(STRING_WITH_LEN("NO" ), cs); |
11232 | table->field[3]->set_notnull(); |
11233 | } |
11234 | |
11235 | if (schema_table_store_record(table->in_use, table)) |
11236 | return -1; |
11237 | return 0; |
11238 | } |
11239 | |
11240 | /** |
11241 | Hash iterate function to count the number of total column privileges granted. |
11242 | */ |
11243 | static my_bool count_column_grants(void *grant_table, |
11244 | void *current_count) |
11245 | { |
11246 | HASH hash_columns = ((GRANT_TABLE *)grant_table)->hash_columns; |
11247 | *(ulong *)current_count+= hash_columns.records; |
11248 | return 0; |
11249 | } |
11250 | |
11251 | /** |
11252 | SHOW function that computes the number of column grants. |
11253 | |
11254 | This must be performed under the mutex in order to make sure the |
11255 | iteration does not fail. |
11256 | */ |
11257 | static int show_column_grants(THD *thd, SHOW_VAR *var, char *buff, |
11258 | enum enum_var_type scope) |
11259 | { |
11260 | var->type= SHOW_ULONG; |
11261 | var->value= buff; |
11262 | *(ulong *)buff= 0; |
11263 | if (initialized) |
11264 | { |
11265 | mysql_rwlock_rdlock(&LOCK_grant); |
11266 | mysql_mutex_lock(&acl_cache->lock); |
11267 | my_hash_iterate(&column_priv_hash, count_column_grants, buff); |
11268 | mysql_mutex_unlock(&acl_cache->lock); |
11269 | mysql_rwlock_unlock(&LOCK_grant); |
11270 | } |
11271 | return 0; |
11272 | } |
11273 | |
11274 | |
11275 | #else |
11276 | bool check_grant(THD *, ulong, TABLE_LIST *, bool, uint, bool) |
11277 | { |
11278 | return 0; |
11279 | } |
11280 | #endif /*NO_EMBEDDED_ACCESS_CHECKS */ |
11281 | |
11282 | SHOW_VAR acl_statistics[] = { |
11283 | #ifndef NO_EMBEDDED_ACCESS_CHECKS |
11284 | {"column_grants" , (char*)show_column_grants, SHOW_SIMPLE_FUNC}, |
11285 | {"database_grants" , (char*)&acl_dbs.elements, SHOW_UINT}, |
11286 | {"function_grants" , (char*)&func_priv_hash.records, SHOW_ULONG}, |
11287 | {"procedure_grants" , (char*)&proc_priv_hash.records, SHOW_ULONG}, |
11288 | {"package_spec_grants" , (char*)&package_spec_priv_hash.records, SHOW_ULONG}, |
11289 | {"package_body_grants" , (char*)&package_body_priv_hash.records, SHOW_ULONG}, |
11290 | {"proxy_users" , (char*)&acl_proxy_users.elements, SHOW_UINT}, |
11291 | {"role_grants" , (char*)&acl_roles_mappings.records, SHOW_ULONG}, |
11292 | {"roles" , (char*)&acl_roles.records, SHOW_ULONG}, |
11293 | {"table_grants" , (char*)&column_priv_hash.records, SHOW_ULONG}, |
11294 | {"users" , (char*)&acl_users.elements, SHOW_UINT}, |
11295 | #endif |
11296 | {NullS, NullS, SHOW_LONG}, |
11297 | }; |
11298 | |
11299 | /* Check if a role is granted to a user/role. We traverse the role graph |
11300 | and return true if we find a match. |
11301 | |
11302 | hostname == NULL means we are looking for a role as a starting point, |
11303 | otherwise a user. |
11304 | */ |
11305 | bool check_role_is_granted(const char *username, |
11306 | const char *hostname, |
11307 | const char *rolename) |
11308 | { |
11309 | DBUG_ENTER("check_role_is_granted" ); |
11310 | bool result= false; |
11311 | #ifndef NO_EMBEDDED_ACCESS_CHECKS |
11312 | ACL_USER_BASE *root; |
11313 | mysql_mutex_lock(&acl_cache->lock); |
11314 | if (hostname) |
11315 | root= find_user_exact(username, hostname); |
11316 | else |
11317 | root= find_acl_role(username); |
11318 | |
11319 | LEX_CSTRING role_lex; |
11320 | role_lex.str= rolename; |
11321 | role_lex.length= strlen(rolename); |
11322 | |
11323 | if (root && /* No grantee, nothing to search. */ |
11324 | traverse_role_graph_down(root, &role_lex, check_role_is_granted_callback, |
11325 | NULL) == -1) |
11326 | { |
11327 | /* We have found the role during our search. */ |
11328 | result= true; |
11329 | } |
11330 | |
11331 | /* We haven't found the role or we had no initial grantee to start from. */ |
11332 | mysql_mutex_unlock(&acl_cache->lock); |
11333 | #endif |
11334 | DBUG_RETURN(result); |
11335 | } |
11336 | |
11337 | int fill_schema_enabled_roles(THD *thd, TABLE_LIST *tables, COND *cond) |
11338 | { |
11339 | TABLE *table= tables->table; |
11340 | #ifndef NO_EMBEDDED_ACCESS_CHECKS |
11341 | if (thd->security_ctx->priv_role[0]) |
11342 | { |
11343 | mysql_rwlock_rdlock(&LOCK_grant); |
11344 | mysql_mutex_lock(&acl_cache->lock); |
11345 | ACL_ROLE *acl_role= find_acl_role(thd->security_ctx->priv_role); |
11346 | if (acl_role) |
11347 | traverse_role_graph_down(acl_role, table, enabled_roles_insert, NULL); |
11348 | mysql_mutex_unlock(&acl_cache->lock); |
11349 | mysql_rwlock_unlock(&LOCK_grant); |
11350 | if (acl_role) |
11351 | return 0; |
11352 | } |
11353 | #endif |
11354 | |
11355 | restore_record(table, s->default_values); |
11356 | table->field[0]->set_null(); |
11357 | return schema_table_store_record(table->in_use, table); |
11358 | } |
11359 | |
11360 | |
11361 | /* |
11362 | This shows all roles granted to current user |
11363 | and recursively all roles granted to those roles |
11364 | */ |
11365 | int fill_schema_applicable_roles(THD *thd, TABLE_LIST *tables, COND *cond) |
11366 | { |
11367 | int res= 0; |
11368 | #ifndef NO_EMBEDDED_ACCESS_CHECKS |
11369 | if (initialized) |
11370 | { |
11371 | TABLE *table= tables->table; |
11372 | Security_context *sctx= thd->security_ctx; |
11373 | mysql_rwlock_rdlock(&LOCK_grant); |
11374 | mysql_mutex_lock(&acl_cache->lock); |
11375 | ACL_USER *user= find_user_exact(sctx->priv_host, sctx->priv_user); |
11376 | if (user) |
11377 | { |
11378 | char buff[USER_HOST_BUFF_SIZE+10]; |
11379 | DBUG_ASSERT(user->user.length + user->hostname_length +2 < sizeof(buff)); |
11380 | char *end= strxmov(buff, user->user.str, "@" , user->host.hostname, NULL); |
11381 | APPLICABLE_ROLES_DATA data= { table, |
11382 | { user->host.hostname, user->hostname_length }, |
11383 | { buff, (size_t)(end - buff) }, user |
11384 | }; |
11385 | |
11386 | res= traverse_role_graph_down(user, &data, 0, applicable_roles_insert); |
11387 | } |
11388 | |
11389 | mysql_mutex_unlock(&acl_cache->lock); |
11390 | mysql_rwlock_unlock(&LOCK_grant); |
11391 | } |
11392 | #endif |
11393 | |
11394 | return res; |
11395 | } |
11396 | |
11397 | |
11398 | int wild_case_compare(CHARSET_INFO *cs, const char *str,const char *wildstr) |
11399 | { |
11400 | int flag; |
11401 | DBUG_ENTER("wild_case_compare" ); |
11402 | DBUG_PRINT("enter" ,("str: '%s' wildstr: '%s'" ,str,wildstr)); |
11403 | while (*wildstr) |
11404 | { |
11405 | while (*wildstr && *wildstr != wild_many && *wildstr != wild_one) |
11406 | { |
11407 | if (*wildstr == wild_prefix && wildstr[1]) |
11408 | wildstr++; |
11409 | if (my_toupper(cs, *wildstr++) != |
11410 | my_toupper(cs, *str++)) DBUG_RETURN(1); |
11411 | } |
11412 | if (! *wildstr ) DBUG_RETURN (*str != 0); |
11413 | if (*wildstr++ == wild_one) |
11414 | { |
11415 | if (! *str++) DBUG_RETURN (1); /* One char; skip */ |
11416 | } |
11417 | else |
11418 | { /* Found '*' */ |
11419 | if (!*wildstr) DBUG_RETURN(0); /* '*' as last char: OK */ |
11420 | flag=(*wildstr != wild_many && *wildstr != wild_one); |
11421 | do |
11422 | { |
11423 | if (flag) |
11424 | { |
11425 | char cmp; |
11426 | if ((cmp= *wildstr) == wild_prefix && wildstr[1]) |
11427 | cmp=wildstr[1]; |
11428 | cmp=my_toupper(cs, cmp); |
11429 | while (*str && my_toupper(cs, *str) != cmp) |
11430 | str++; |
11431 | if (!*str) DBUG_RETURN (1); |
11432 | } |
11433 | if (wild_case_compare(cs, str,wildstr) == 0) DBUG_RETURN (0); |
11434 | } while (*str++); |
11435 | DBUG_RETURN(1); |
11436 | } |
11437 | } |
11438 | DBUG_RETURN (*str != '\0'); |
11439 | } |
11440 | |
11441 | |
11442 | #ifndef NO_EMBEDDED_ACCESS_CHECKS |
11443 | static bool update_schema_privilege(THD *thd, TABLE *table, char *buff, |
11444 | const char* db, const char* t_name, |
11445 | const char* column, uint col_length, |
11446 | const char *priv, uint priv_length, |
11447 | const char* is_grantable) |
11448 | { |
11449 | int i= 2; |
11450 | CHARSET_INFO *cs= system_charset_info; |
11451 | restore_record(table, s->default_values); |
11452 | table->field[0]->store(buff, (uint) strlen(buff), cs); |
11453 | table->field[1]->store(STRING_WITH_LEN("def" ), cs); |
11454 | if (db) |
11455 | table->field[i++]->store(db, (uint) strlen(db), cs); |
11456 | if (t_name) |
11457 | table->field[i++]->store(t_name, (uint) strlen(t_name), cs); |
11458 | if (column) |
11459 | table->field[i++]->store(column, col_length, cs); |
11460 | table->field[i++]->store(priv, priv_length, cs); |
11461 | table->field[i]->store(is_grantable, strlen(is_grantable), cs); |
11462 | return schema_table_store_record(thd, table); |
11463 | } |
11464 | #endif |
11465 | |
11466 | |
11467 | int fill_schema_user_privileges(THD *thd, TABLE_LIST *tables, COND *cond) |
11468 | { |
11469 | #ifndef NO_EMBEDDED_ACCESS_CHECKS |
11470 | int error= 0; |
11471 | uint counter; |
11472 | ACL_USER *acl_user; |
11473 | ulong want_access; |
11474 | char buff[100]; |
11475 | TABLE *table= tables->table; |
11476 | bool no_global_access= check_access(thd, SELECT_ACL, "mysql" , |
11477 | NULL, NULL, 1, 1); |
11478 | char *curr_host= thd->security_ctx->priv_host_name(); |
11479 | DBUG_ENTER("fill_schema_user_privileges" ); |
11480 | |
11481 | if (!initialized) |
11482 | DBUG_RETURN(0); |
11483 | mysql_mutex_lock(&acl_cache->lock); |
11484 | |
11485 | for (counter=0 ; counter < acl_users.elements ; counter++) |
11486 | { |
11487 | const char *user,*host, *is_grantable="YES" ; |
11488 | acl_user=dynamic_element(&acl_users,counter,ACL_USER*); |
11489 | user= safe_str(acl_user->user.str); |
11490 | host= safe_str(acl_user->host.hostname); |
11491 | |
11492 | if (no_global_access && |
11493 | (strcmp(thd->security_ctx->priv_user, user) || |
11494 | my_strcasecmp(system_charset_info, curr_host, host))) |
11495 | continue; |
11496 | |
11497 | want_access= acl_user->access; |
11498 | if (!(want_access & GRANT_ACL)) |
11499 | is_grantable= "NO" ; |
11500 | |
11501 | strxmov(buff,"'" ,user,"'@'" ,host,"'" ,NullS); |
11502 | if (!(want_access & ~GRANT_ACL)) |
11503 | { |
11504 | if (update_schema_privilege(thd, table, buff, 0, 0, 0, 0, |
11505 | STRING_WITH_LEN("USAGE" ), is_grantable)) |
11506 | { |
11507 | error= 1; |
11508 | goto err; |
11509 | } |
11510 | } |
11511 | else |
11512 | { |
11513 | uint priv_id; |
11514 | ulong j,test_access= want_access & ~GRANT_ACL; |
11515 | for (priv_id=0, j = SELECT_ACL;j <= GLOBAL_ACLS; priv_id++,j <<= 1) |
11516 | { |
11517 | if (test_access & j) |
11518 | { |
11519 | if (update_schema_privilege(thd, table, buff, 0, 0, 0, 0, |
11520 | command_array[priv_id], |
11521 | command_lengths[priv_id], is_grantable)) |
11522 | { |
11523 | error= 1; |
11524 | goto err; |
11525 | } |
11526 | } |
11527 | } |
11528 | } |
11529 | } |
11530 | err: |
11531 | mysql_mutex_unlock(&acl_cache->lock); |
11532 | |
11533 | DBUG_RETURN(error); |
11534 | #else |
11535 | return(0); |
11536 | #endif |
11537 | } |
11538 | |
11539 | |
11540 | int fill_schema_schema_privileges(THD *thd, TABLE_LIST *tables, COND *cond) |
11541 | { |
11542 | #ifndef NO_EMBEDDED_ACCESS_CHECKS |
11543 | int error= 0; |
11544 | uint counter; |
11545 | ACL_DB *acl_db; |
11546 | ulong want_access; |
11547 | char buff[100]; |
11548 | TABLE *table= tables->table; |
11549 | bool no_global_access= check_access(thd, SELECT_ACL, "mysql" , |
11550 | NULL, NULL, 1, 1); |
11551 | char *curr_host= thd->security_ctx->priv_host_name(); |
11552 | DBUG_ENTER("fill_schema_schema_privileges" ); |
11553 | |
11554 | if (!initialized) |
11555 | DBUG_RETURN(0); |
11556 | mysql_mutex_lock(&acl_cache->lock); |
11557 | |
11558 | for (counter=0 ; counter < acl_dbs.elements ; counter++) |
11559 | { |
11560 | const char *user, *host, *is_grantable="YES" ; |
11561 | |
11562 | acl_db=dynamic_element(&acl_dbs,counter,ACL_DB*); |
11563 | user= safe_str(acl_db->user); |
11564 | host= safe_str(acl_db->host.hostname); |
11565 | |
11566 | if (no_global_access && |
11567 | (strcmp(thd->security_ctx->priv_user, user) || |
11568 | my_strcasecmp(system_charset_info, curr_host, host))) |
11569 | continue; |
11570 | |
11571 | want_access=acl_db->access; |
11572 | if (want_access) |
11573 | { |
11574 | if (!(want_access & GRANT_ACL)) |
11575 | { |
11576 | is_grantable= "NO" ; |
11577 | } |
11578 | strxmov(buff,"'" ,user,"'@'" ,host,"'" ,NullS); |
11579 | if (!(want_access & ~GRANT_ACL)) |
11580 | { |
11581 | if (update_schema_privilege(thd, table, buff, acl_db->db, 0, 0, |
11582 | 0, STRING_WITH_LEN("USAGE" ), is_grantable)) |
11583 | { |
11584 | error= 1; |
11585 | goto err; |
11586 | } |
11587 | } |
11588 | else |
11589 | { |
11590 | int cnt; |
11591 | ulong j,test_access= want_access & ~GRANT_ACL; |
11592 | for (cnt=0, j = SELECT_ACL; j <= DB_ACLS; cnt++,j <<= 1) |
11593 | if (test_access & j) |
11594 | { |
11595 | if (update_schema_privilege(thd, table, buff, acl_db->db, 0, 0, 0, |
11596 | command_array[cnt], command_lengths[cnt], |
11597 | is_grantable)) |
11598 | { |
11599 | error= 1; |
11600 | goto err; |
11601 | } |
11602 | } |
11603 | } |
11604 | } |
11605 | } |
11606 | err: |
11607 | mysql_mutex_unlock(&acl_cache->lock); |
11608 | |
11609 | DBUG_RETURN(error); |
11610 | #else |
11611 | return (0); |
11612 | #endif |
11613 | } |
11614 | |
11615 | |
11616 | int fill_schema_table_privileges(THD *thd, TABLE_LIST *tables, COND *cond) |
11617 | { |
11618 | #ifndef NO_EMBEDDED_ACCESS_CHECKS |
11619 | int error= 0; |
11620 | uint index; |
11621 | char buff[100]; |
11622 | TABLE *table= tables->table; |
11623 | bool no_global_access= check_access(thd, SELECT_ACL, "mysql" , |
11624 | NULL, NULL, 1, 1); |
11625 | char *curr_host= thd->security_ctx->priv_host_name(); |
11626 | DBUG_ENTER("fill_schema_table_privileges" ); |
11627 | |
11628 | mysql_rwlock_rdlock(&LOCK_grant); |
11629 | |
11630 | for (index=0 ; index < column_priv_hash.records ; index++) |
11631 | { |
11632 | const char *user, *host, *is_grantable= "YES" ; |
11633 | GRANT_TABLE *grant_table= (GRANT_TABLE*) my_hash_element(&column_priv_hash, |
11634 | index); |
11635 | user= safe_str(grant_table->user); |
11636 | host= safe_str(grant_table->host.hostname); |
11637 | |
11638 | if (no_global_access && |
11639 | (strcmp(thd->security_ctx->priv_user, user) || |
11640 | my_strcasecmp(system_charset_info, curr_host, host))) |
11641 | continue; |
11642 | |
11643 | ulong table_access= grant_table->privs; |
11644 | if (table_access) |
11645 | { |
11646 | ulong test_access= table_access & ~GRANT_ACL; |
11647 | /* |
11648 | We should skip 'usage' privilege on table if |
11649 | we have any privileges on column(s) of this table |
11650 | */ |
11651 | if (!test_access && grant_table->cols) |
11652 | continue; |
11653 | if (!(table_access & GRANT_ACL)) |
11654 | is_grantable= "NO" ; |
11655 | |
11656 | strxmov(buff, "'" , user, "'@'" , host, "'" , NullS); |
11657 | if (!test_access) |
11658 | { |
11659 | if (update_schema_privilege(thd, table, buff, grant_table->db, |
11660 | grant_table->tname, 0, 0, |
11661 | STRING_WITH_LEN("USAGE" ), is_grantable)) |
11662 | { |
11663 | error= 1; |
11664 | goto err; |
11665 | } |
11666 | } |
11667 | else |
11668 | { |
11669 | ulong j; |
11670 | int cnt; |
11671 | for (cnt= 0, j= SELECT_ACL; j <= TABLE_ACLS; cnt++, j<<= 1) |
11672 | { |
11673 | if (test_access & j) |
11674 | { |
11675 | if (update_schema_privilege(thd, table, buff, grant_table->db, |
11676 | grant_table->tname, 0, 0, |
11677 | command_array[cnt], |
11678 | command_lengths[cnt], is_grantable)) |
11679 | { |
11680 | error= 1; |
11681 | goto err; |
11682 | } |
11683 | } |
11684 | } |
11685 | } |
11686 | } |
11687 | } |
11688 | err: |
11689 | mysql_rwlock_unlock(&LOCK_grant); |
11690 | |
11691 | DBUG_RETURN(error); |
11692 | #else |
11693 | return (0); |
11694 | #endif |
11695 | } |
11696 | |
11697 | |
11698 | int fill_schema_column_privileges(THD *thd, TABLE_LIST *tables, COND *cond) |
11699 | { |
11700 | #ifndef NO_EMBEDDED_ACCESS_CHECKS |
11701 | int error= 0; |
11702 | uint index; |
11703 | char buff[100]; |
11704 | TABLE *table= tables->table; |
11705 | bool no_global_access= check_access(thd, SELECT_ACL, "mysql" , |
11706 | NULL, NULL, 1, 1); |
11707 | char *curr_host= thd->security_ctx->priv_host_name(); |
11708 | DBUG_ENTER("fill_schema_table_privileges" ); |
11709 | |
11710 | mysql_rwlock_rdlock(&LOCK_grant); |
11711 | |
11712 | for (index=0 ; index < column_priv_hash.records ; index++) |
11713 | { |
11714 | const char *user, *host, *is_grantable= "YES" ; |
11715 | GRANT_TABLE *grant_table= (GRANT_TABLE*) my_hash_element(&column_priv_hash, |
11716 | index); |
11717 | user= safe_str(grant_table->user); |
11718 | host= safe_str(grant_table->host.hostname); |
11719 | |
11720 | if (no_global_access && |
11721 | (strcmp(thd->security_ctx->priv_user, user) || |
11722 | my_strcasecmp(system_charset_info, curr_host, host))) |
11723 | continue; |
11724 | |
11725 | ulong table_access= grant_table->cols; |
11726 | if (table_access != 0) |
11727 | { |
11728 | if (!(grant_table->privs & GRANT_ACL)) |
11729 | is_grantable= "NO" ; |
11730 | |
11731 | ulong test_access= table_access & ~GRANT_ACL; |
11732 | strxmov(buff, "'" , user, "'@'" , host, "'" , NullS); |
11733 | if (!test_access) |
11734 | continue; |
11735 | else |
11736 | { |
11737 | ulong j; |
11738 | int cnt; |
11739 | for (cnt= 0, j= SELECT_ACL; j <= TABLE_ACLS; cnt++, j<<= 1) |
11740 | { |
11741 | if (test_access & j) |
11742 | { |
11743 | for (uint col_index=0 ; |
11744 | col_index < grant_table->hash_columns.records ; |
11745 | col_index++) |
11746 | { |
11747 | GRANT_COLUMN *grant_column = (GRANT_COLUMN*) |
11748 | my_hash_element(&grant_table->hash_columns,col_index); |
11749 | if ((grant_column->rights & j) && (table_access & j)) |
11750 | { |
11751 | if (update_schema_privilege(thd, table, buff, grant_table->db, |
11752 | grant_table->tname, |
11753 | grant_column->column, |
11754 | grant_column->key_length, |
11755 | command_array[cnt], |
11756 | command_lengths[cnt], is_grantable)) |
11757 | { |
11758 | error= 1; |
11759 | goto err; |
11760 | } |
11761 | } |
11762 | } |
11763 | } |
11764 | } |
11765 | } |
11766 | } |
11767 | } |
11768 | err: |
11769 | mysql_rwlock_unlock(&LOCK_grant); |
11770 | |
11771 | DBUG_RETURN(error); |
11772 | #else |
11773 | return (0); |
11774 | #endif |
11775 | } |
11776 | |
11777 | |
11778 | #ifndef NO_EMBEDDED_ACCESS_CHECKS |
11779 | /* |
11780 | fill effective privileges for table |
11781 | |
11782 | SYNOPSIS |
11783 | fill_effective_table_privileges() |
11784 | thd thread handler |
11785 | grant grants table descriptor |
11786 | db db name |
11787 | table table name |
11788 | */ |
11789 | |
11790 | void fill_effective_table_privileges(THD *thd, GRANT_INFO *grant, |
11791 | const char *db, const char *table) |
11792 | { |
11793 | Security_context *sctx= thd->security_ctx; |
11794 | DBUG_ENTER("fill_effective_table_privileges" ); |
11795 | DBUG_PRINT("enter" , ("Host: '%s', Ip: '%s', User: '%s', table: `%s`.`%s`" , |
11796 | sctx->priv_host, sctx->ip, sctx->priv_user, db, table)); |
11797 | /* --skip-grants */ |
11798 | if (!initialized) |
11799 | { |
11800 | DBUG_PRINT("info" , ("skip grants" )); |
11801 | grant->privilege= ~NO_ACCESS; // everything is allowed |
11802 | DBUG_PRINT("info" , ("privilege 0x%lx" , grant->privilege)); |
11803 | DBUG_VOID_RETURN; |
11804 | } |
11805 | |
11806 | /* global privileges */ |
11807 | grant->privilege= sctx->master_access; |
11808 | |
11809 | if (!thd->db.str || strcmp(db, thd->db.str)) |
11810 | { |
11811 | /* db privileges */ |
11812 | grant->privilege|= acl_get(sctx->host, sctx->ip, sctx->priv_user, db, 0); |
11813 | /* db privileges for role */ |
11814 | if (sctx->priv_role[0]) |
11815 | grant->privilege|= acl_get("" , "" , sctx->priv_role, db, 0); |
11816 | } |
11817 | else |
11818 | { |
11819 | grant->privilege|= sctx->db_access; |
11820 | } |
11821 | |
11822 | /* table privileges */ |
11823 | mysql_rwlock_rdlock(&LOCK_grant); |
11824 | if (grant->version != grant_version) |
11825 | { |
11826 | grant->grant_table_user= |
11827 | table_hash_search(sctx->host, sctx->ip, db, |
11828 | sctx->priv_user, |
11829 | table, 0); /* purecov: inspected */ |
11830 | grant->grant_table_role= |
11831 | sctx->priv_role[0] ? table_hash_search("" , "" , db, |
11832 | sctx->priv_role, |
11833 | table, TRUE) : NULL; |
11834 | grant->version= grant_version; /* purecov: inspected */ |
11835 | } |
11836 | if (grant->grant_table_user != 0) |
11837 | { |
11838 | grant->privilege|= grant->grant_table_user->privs; |
11839 | } |
11840 | if (grant->grant_table_role != 0) |
11841 | { |
11842 | grant->privilege|= grant->grant_table_role->privs; |
11843 | } |
11844 | mysql_rwlock_unlock(&LOCK_grant); |
11845 | |
11846 | DBUG_PRINT("info" , ("privilege 0x%lx" , grant->privilege)); |
11847 | DBUG_VOID_RETURN; |
11848 | } |
11849 | |
11850 | #else /* NO_EMBEDDED_ACCESS_CHECKS */ |
11851 | |
11852 | /**************************************************************************** |
11853 | Dummy wrappers when we don't have any access checks |
11854 | ****************************************************************************/ |
11855 | |
11856 | bool check_routine_level_acl(THD *thd, const char *db, const char *name, |
11857 | const Sp_handler *sph) |
11858 | { |
11859 | return FALSE; |
11860 | } |
11861 | |
11862 | #endif |
11863 | |
11864 | /** |
11865 | Return information about user or current user. |
11866 | |
11867 | @param[in] thd thread handler |
11868 | @param[in] user user |
11869 | @param[in] lock whether &acl_cache->lock mutex needs to be locked |
11870 | |
11871 | @return |
11872 | - On success, return a valid pointer to initialized |
11873 | LEX_USER, which contains user information. |
11874 | - On error, return 0. |
11875 | */ |
11876 | |
11877 | LEX_USER *get_current_user(THD *thd, LEX_USER *user, bool lock) |
11878 | { |
11879 | if (user->user.str == current_user.str) // current_user |
11880 | return create_default_definer(thd, false); |
11881 | |
11882 | if (user->user.str == current_role.str) // current_role |
11883 | return create_default_definer(thd, true); |
11884 | |
11885 | if (user->host.str == NULL) // Possibly a role |
11886 | { |
11887 | // to be reexecution friendly we have to make a copy |
11888 | LEX_USER *dup= (LEX_USER*) thd->memdup(user, sizeof(*user)); |
11889 | if (!dup) |
11890 | return 0; |
11891 | |
11892 | #ifndef NO_EMBEDDED_ACCESS_CHECKS |
11893 | if (has_auth(user, thd->lex)) |
11894 | { |
11895 | dup->host= host_not_specified; |
11896 | return dup; |
11897 | } |
11898 | |
11899 | if (is_invalid_role_name(user->user.str)) |
11900 | return 0; |
11901 | |
11902 | if (lock) |
11903 | mysql_mutex_lock(&acl_cache->lock); |
11904 | if (find_acl_role(dup->user.str)) |
11905 | dup->host= empty_clex_str; |
11906 | else |
11907 | dup->host= host_not_specified; |
11908 | if (lock) |
11909 | mysql_mutex_unlock(&acl_cache->lock); |
11910 | #endif |
11911 | |
11912 | return dup; |
11913 | } |
11914 | |
11915 | return user; |
11916 | } |
11917 | |
11918 | struct ACL_internal_schema_registry_entry |
11919 | { |
11920 | const LEX_CSTRING *m_name; |
11921 | const ACL_internal_schema_access *m_access; |
11922 | }; |
11923 | |
11924 | /** |
11925 | Internal schema registered. |
11926 | Currently, this is only: |
11927 | - performance_schema |
11928 | - information_schema, |
11929 | This can be reused later for: |
11930 | - mysql |
11931 | */ |
11932 | static ACL_internal_schema_registry_entry registry_array[2]; |
11933 | static uint m_registry_array_size= 0; |
11934 | |
11935 | /** |
11936 | Add an internal schema to the registry. |
11937 | @param name the schema name |
11938 | @param access the schema ACL specific rules |
11939 | */ |
11940 | void ACL_internal_schema_registry::register_schema |
11941 | (const LEX_CSTRING *name, const ACL_internal_schema_access *access) |
11942 | { |
11943 | DBUG_ASSERT(m_registry_array_size < array_elements(registry_array)); |
11944 | |
11945 | /* Not thread safe, and does not need to be. */ |
11946 | registry_array[m_registry_array_size].m_name= name; |
11947 | registry_array[m_registry_array_size].m_access= access; |
11948 | m_registry_array_size++; |
11949 | } |
11950 | |
11951 | /** |
11952 | Search per internal schema ACL by name. |
11953 | @param name a schema name |
11954 | @return per schema rules, or NULL |
11955 | */ |
11956 | const ACL_internal_schema_access * |
11957 | ACL_internal_schema_registry::lookup(const char *name) |
11958 | { |
11959 | DBUG_ASSERT(name != NULL); |
11960 | |
11961 | uint i; |
11962 | |
11963 | for (i= 0; i<m_registry_array_size; i++) |
11964 | { |
11965 | if (my_strcasecmp(system_charset_info, registry_array[i].m_name->str, |
11966 | name) == 0) |
11967 | return registry_array[i].m_access; |
11968 | } |
11969 | return NULL; |
11970 | } |
11971 | |
11972 | /** |
11973 | Get a cached internal schema access. |
11974 | @param grant_internal_info the cache |
11975 | @param schema_name the name of the internal schema |
11976 | */ |
11977 | const ACL_internal_schema_access * |
11978 | get_cached_schema_access(GRANT_INTERNAL_INFO *grant_internal_info, |
11979 | const char *schema_name) |
11980 | { |
11981 | if (grant_internal_info) |
11982 | { |
11983 | if (! grant_internal_info->m_schema_lookup_done) |
11984 | { |
11985 | grant_internal_info->m_schema_access= |
11986 | ACL_internal_schema_registry::lookup(schema_name); |
11987 | grant_internal_info->m_schema_lookup_done= TRUE; |
11988 | } |
11989 | return grant_internal_info->m_schema_access; |
11990 | } |
11991 | return ACL_internal_schema_registry::lookup(schema_name); |
11992 | } |
11993 | |
11994 | /** |
11995 | Get a cached internal table access. |
11996 | @param grant_internal_info the cache |
11997 | @param schema_name the name of the internal schema |
11998 | @param table_name the name of the internal table |
11999 | */ |
12000 | const ACL_internal_table_access * |
12001 | get_cached_table_access(GRANT_INTERNAL_INFO *grant_internal_info, |
12002 | const char *schema_name, |
12003 | const char *table_name) |
12004 | { |
12005 | DBUG_ASSERT(grant_internal_info); |
12006 | if (! grant_internal_info->m_table_lookup_done) |
12007 | { |
12008 | const ACL_internal_schema_access *schema_access; |
12009 | schema_access= get_cached_schema_access(grant_internal_info, schema_name); |
12010 | if (schema_access) |
12011 | grant_internal_info->m_table_access= schema_access->lookup(table_name); |
12012 | grant_internal_info->m_table_lookup_done= TRUE; |
12013 | } |
12014 | return grant_internal_info->m_table_access; |
12015 | } |
12016 | |
12017 | |
12018 | /**************************************************************************** |
12019 | AUTHENTICATION CODE |
12020 | including initial connect handshake, invoking appropriate plugins, |
12021 | client-server plugin negotiation, COM_CHANGE_USER, and native |
12022 | MySQL authentication plugins. |
12023 | ****************************************************************************/ |
12024 | |
12025 | /* few defines to have less ifdef's in the code below */ |
12026 | #ifdef EMBEDDED_LIBRARY |
12027 | #undef HAVE_OPENSSL |
12028 | #ifdef NO_EMBEDDED_ACCESS_CHECKS |
12029 | #define initialized 0 |
12030 | #define check_for_max_user_connections(X,Y) 0 |
12031 | #define get_or_create_user_conn(A,B,C,D) 0 |
12032 | #endif |
12033 | #endif |
12034 | #ifndef HAVE_OPENSSL |
12035 | #define ssl_acceptor_fd 0 |
12036 | #define sslaccept(A,B,C,D) 1 |
12037 | #endif |
12038 | |
12039 | /** |
12040 | The internal version of what plugins know as MYSQL_PLUGIN_VIO, |
12041 | basically the context of the authentication session |
12042 | */ |
12043 | struct MPVIO_EXT :public MYSQL_PLUGIN_VIO |
12044 | { |
12045 | MYSQL_SERVER_AUTH_INFO auth_info; |
12046 | ACL_USER *acl_user; ///< a copy, independent from acl_users array |
12047 | plugin_ref plugin; ///< what plugin we're under |
12048 | LEX_CSTRING db; ///< db name from the handshake packet |
12049 | /** when restarting a plugin this caches the last client reply */ |
12050 | struct { |
12051 | const char *plugin; |
12052 | char *pkt; ///< pointer into NET::buff |
12053 | uint pkt_len; |
12054 | } cached_client_reply; |
12055 | /** this caches the first plugin packet for restart request on the client */ |
12056 | struct { |
12057 | char *pkt; |
12058 | uint pkt_len; |
12059 | } cached_server_packet; |
12060 | int packets_read, packets_written; ///< counters for send/received packets |
12061 | bool make_it_fail; |
12062 | /** when plugin returns a failure this tells us what really happened */ |
12063 | enum { SUCCESS, FAILURE, RESTART } status; |
12064 | }; |
12065 | |
12066 | /** |
12067 | a helper function to report an access denied error in all the proper places |
12068 | */ |
12069 | static void login_failed_error(THD *thd) |
12070 | { |
12071 | my_error(access_denied_error_code(thd->password), MYF(0), |
12072 | thd->main_security_ctx.user, |
12073 | thd->main_security_ctx.host_or_ip, |
12074 | thd->password ? ER_THD(thd, ER_YES) : ER_THD(thd, ER_NO)); |
12075 | general_log_print(thd, COM_CONNECT, |
12076 | ER_THD(thd, access_denied_error_code(thd->password)), |
12077 | thd->main_security_ctx.user, |
12078 | thd->main_security_ctx.host_or_ip, |
12079 | thd->password ? ER_THD(thd, ER_YES) : ER_THD(thd, ER_NO)); |
12080 | status_var_increment(thd->status_var.access_denied_errors); |
12081 | /* |
12082 | Log access denied messages to the error log when log-warnings = 2 |
12083 | so that the overhead of the general query log is not required to track |
12084 | failed connections. |
12085 | */ |
12086 | if (global_system_variables.log_warnings > 1) |
12087 | { |
12088 | sql_print_warning(ER_THD(thd, access_denied_error_code(thd->password)), |
12089 | thd->main_security_ctx.user, |
12090 | thd->main_security_ctx.host_or_ip, |
12091 | thd->password ? ER_THD(thd, ER_YES) : ER_THD(thd, ER_NO)); |
12092 | } |
12093 | } |
12094 | |
12095 | /** |
12096 | sends a server handshake initialization packet, the very first packet |
12097 | after the connection was established |
12098 | |
12099 | Packet format: |
12100 | |
12101 | Bytes Content |
12102 | ----- ---- |
12103 | 1 protocol version (always 10) |
12104 | n server version string, \0-terminated |
12105 | 4 thread id |
12106 | 8 first 8 bytes of the plugin provided data (scramble) |
12107 | 1 \0 byte, terminating the first part of a scramble |
12108 | 2 server capabilities (two lower bytes) |
12109 | 1 server character set |
12110 | 2 server status |
12111 | 2 server capabilities (two upper bytes) |
12112 | 1 length of the scramble |
12113 | 10 reserved, always 0 |
12114 | n rest of the plugin provided data (at least 12 bytes) |
12115 | 1 \0 byte, terminating the second part of a scramble |
12116 | |
12117 | @retval 0 ok |
12118 | @retval 1 error |
12119 | */ |
12120 | static bool send_server_handshake_packet(MPVIO_EXT *mpvio, |
12121 | const char *data, uint data_len) |
12122 | { |
12123 | DBUG_ASSERT(mpvio->status == MPVIO_EXT::FAILURE); |
12124 | DBUG_ASSERT(data_len <= 255); |
12125 | |
12126 | THD *thd= mpvio->auth_info.thd; |
12127 | char *buff= (char *) my_alloca(1 + SERVER_VERSION_LENGTH + 1 + data_len + 64); |
12128 | char scramble_buf[SCRAMBLE_LENGTH]; |
12129 | char *end= buff; |
12130 | DBUG_ENTER("send_server_handshake_packet" ); |
12131 | |
12132 | *end++= protocol_version; |
12133 | |
12134 | thd->client_capabilities= CLIENT_BASIC_FLAGS; |
12135 | |
12136 | if (opt_using_transactions) |
12137 | thd->client_capabilities|= CLIENT_TRANSACTIONS; |
12138 | |
12139 | thd->client_capabilities|= CAN_CLIENT_COMPRESS; |
12140 | |
12141 | if (ssl_acceptor_fd) |
12142 | { |
12143 | thd->client_capabilities |= CLIENT_SSL; |
12144 | thd->client_capabilities |= CLIENT_SSL_VERIFY_SERVER_CERT; |
12145 | } |
12146 | |
12147 | if (data_len) |
12148 | { |
12149 | mpvio->cached_server_packet.pkt= (char*)thd->memdup(data, data_len); |
12150 | mpvio->cached_server_packet.pkt_len= data_len; |
12151 | } |
12152 | |
12153 | if (data_len < SCRAMBLE_LENGTH) |
12154 | { |
12155 | if (data_len) |
12156 | { |
12157 | /* |
12158 | the first packet *must* have at least 20 bytes of a scramble. |
12159 | if a plugin provided less, we pad it to 20 with zeros |
12160 | */ |
12161 | memcpy(scramble_buf, data, data_len); |
12162 | bzero(scramble_buf + data_len, SCRAMBLE_LENGTH - data_len); |
12163 | data= scramble_buf; |
12164 | } |
12165 | else |
12166 | { |
12167 | /* |
12168 | if the default plugin does not provide the data for the scramble at |
12169 | all, we generate a scramble internally anyway, just in case the |
12170 | user account (that will be known only later) uses a |
12171 | native_password_plugin (which needs a scramble). If we don't send a |
12172 | scramble now - wasting 20 bytes in the packet - |
12173 | native_password_plugin will have to send it in a separate packet, |
12174 | adding one more round trip. |
12175 | */ |
12176 | thd_create_random_password(thd, thd->scramble, SCRAMBLE_LENGTH); |
12177 | data= thd->scramble; |
12178 | } |
12179 | data_len= SCRAMBLE_LENGTH; |
12180 | } |
12181 | |
12182 | /* When server version is specified in config file, don't include |
12183 | the replication hack prefix. */ |
12184 | if (using_custom_server_version) |
12185 | end= strnmov(end, server_version, SERVER_VERSION_LENGTH) + 1; |
12186 | else |
12187 | end= strxnmov(end, SERVER_VERSION_LENGTH, RPL_VERSION_HACK, server_version, NullS) + 1; |
12188 | |
12189 | int4store((uchar*) end, mpvio->auth_info.thd->thread_id); |
12190 | end+= 4; |
12191 | |
12192 | /* |
12193 | Old clients does not understand long scrambles, but can ignore packet |
12194 | tail: that's why first part of the scramble is placed here, and second |
12195 | part at the end of packet. |
12196 | */ |
12197 | end= (char*) memcpy(end, data, SCRAMBLE_LENGTH_323); |
12198 | end+= SCRAMBLE_LENGTH_323; |
12199 | *end++= 0; |
12200 | |
12201 | int2store(end, thd->client_capabilities); |
12202 | /* write server characteristics: up to 16 bytes allowed */ |
12203 | end[2]= (char) default_charset_info->number; |
12204 | int2store(end+3, mpvio->auth_info.thd->server_status); |
12205 | int2store(end+5, thd->client_capabilities >> 16); |
12206 | end[7]= data_len; |
12207 | DBUG_EXECUTE_IF("poison_srv_handshake_scramble_len" , end[7]= -100;); |
12208 | bzero(end + 8, 6); |
12209 | int4store(end + 14, thd->client_capabilities >> 32); |
12210 | end+= 18; |
12211 | /* write scramble tail */ |
12212 | end= (char*) memcpy(end, data + SCRAMBLE_LENGTH_323, |
12213 | data_len - SCRAMBLE_LENGTH_323); |
12214 | end+= data_len - SCRAMBLE_LENGTH_323; |
12215 | end= strmake(end, plugin_name(mpvio->plugin)->str, |
12216 | plugin_name(mpvio->plugin)->length); |
12217 | |
12218 | int res= my_net_write(&mpvio->auth_info.thd->net, (uchar*) buff, |
12219 | (size_t) (end - buff + 1)) || |
12220 | net_flush(&mpvio->auth_info.thd->net); |
12221 | my_afree(buff); |
12222 | DBUG_RETURN (res); |
12223 | } |
12224 | |
12225 | static bool secure_auth(THD *thd) |
12226 | { |
12227 | if (!opt_secure_auth) |
12228 | return 0; |
12229 | |
12230 | /* |
12231 | If the server is running in secure auth mode, short scrambles are |
12232 | forbidden. Extra juggling to report the same error as the old code. |
12233 | */ |
12234 | if (thd->client_capabilities & CLIENT_PROTOCOL_41) |
12235 | { |
12236 | my_error(ER_SERVER_IS_IN_SECURE_AUTH_MODE, MYF(0), |
12237 | thd->security_ctx->user, |
12238 | thd->security_ctx->host_or_ip); |
12239 | general_log_print(thd, COM_CONNECT, |
12240 | ER_THD(thd, ER_SERVER_IS_IN_SECURE_AUTH_MODE), |
12241 | thd->security_ctx->user, |
12242 | thd->security_ctx->host_or_ip); |
12243 | } |
12244 | else |
12245 | { |
12246 | my_error(ER_NOT_SUPPORTED_AUTH_MODE, MYF(0)); |
12247 | general_log_print(thd, COM_CONNECT, |
12248 | ER_THD(thd, ER_NOT_SUPPORTED_AUTH_MODE)); |
12249 | } |
12250 | return 1; |
12251 | } |
12252 | |
12253 | /** |
12254 | sends a "change plugin" packet, requesting a client to restart authentication |
12255 | using a different authentication plugin |
12256 | |
12257 | Packet format: |
12258 | |
12259 | Bytes Content |
12260 | ----- ---- |
12261 | 1 byte with the value 254 |
12262 | n client plugin to use, \0-terminated |
12263 | n plugin provided data |
12264 | |
12265 | In a special case of switching from native_password_plugin to |
12266 | old_password_plugin, the packet contains only one - the first - byte, |
12267 | plugin name is omitted, plugin data aren't needed as the scramble was |
12268 | already sent. This one-byte packet is identical to the "use the short |
12269 | scramble" packet in the protocol before plugins were introduced. |
12270 | |
12271 | @retval 0 ok |
12272 | @retval 1 error |
12273 | */ |
12274 | static bool send_plugin_request_packet(MPVIO_EXT *mpvio, |
12275 | const uchar *data, uint data_len) |
12276 | { |
12277 | DBUG_ASSERT(mpvio->packets_written == 1); |
12278 | DBUG_ASSERT(mpvio->packets_read == 1); |
12279 | NET *net= &mpvio->auth_info.thd->net; |
12280 | static uchar switch_plugin_request_buf[]= { 254 }; |
12281 | DBUG_ENTER("send_plugin_request_packet" ); |
12282 | |
12283 | mpvio->status= MPVIO_EXT::FAILURE; // the status is no longer RESTART |
12284 | |
12285 | const char *client_auth_plugin= |
12286 | ((st_mysql_auth *) (plugin_decl(mpvio->plugin)->info))->client_auth_plugin; |
12287 | |
12288 | DBUG_EXECUTE_IF("auth_disconnect" , { vio_close(net->vio); DBUG_RETURN(1); }); |
12289 | DBUG_ASSERT(client_auth_plugin); |
12290 | |
12291 | /* |
12292 | we send an old "short 4.0 scramble request", if we need to request a |
12293 | client to use 4.0 auth plugin (short scramble) and the scramble was |
12294 | already sent to the client |
12295 | |
12296 | below, cached_client_reply.plugin is the plugin name that client has used, |
12297 | client_auth_plugin is derived from mysql.user table, for the given |
12298 | user account, it's the plugin that the client need to use to login. |
12299 | */ |
12300 | bool switch_from_long_to_short_scramble= |
12301 | native_password_plugin_name.str == mpvio->cached_client_reply.plugin && |
12302 | client_auth_plugin == old_password_plugin_name.str; |
12303 | |
12304 | if (switch_from_long_to_short_scramble) |
12305 | DBUG_RETURN (secure_auth(mpvio->auth_info.thd) || |
12306 | my_net_write(net, switch_plugin_request_buf, 1) || |
12307 | net_flush(net)); |
12308 | |
12309 | /* |
12310 | We never request a client to switch from a short to long scramble. |
12311 | Plugin-aware clients can do that, but traditionally it meant to |
12312 | ask an old 4.0 client to use the new 4.1 authentication protocol. |
12313 | */ |
12314 | bool switch_from_short_to_long_scramble= |
12315 | old_password_plugin_name.str == mpvio->cached_client_reply.plugin && |
12316 | client_auth_plugin == native_password_plugin_name.str; |
12317 | |
12318 | if (switch_from_short_to_long_scramble) |
12319 | { |
12320 | my_error(ER_NOT_SUPPORTED_AUTH_MODE, MYF(0)); |
12321 | general_log_print(mpvio->auth_info.thd, COM_CONNECT, |
12322 | ER_THD(mpvio->auth_info.thd, ER_NOT_SUPPORTED_AUTH_MODE)); |
12323 | DBUG_RETURN (1); |
12324 | } |
12325 | |
12326 | DBUG_PRINT("info" , ("requesting client to use the %s plugin" , |
12327 | client_auth_plugin)); |
12328 | DBUG_RETURN(net_write_command(net, switch_plugin_request_buf[0], |
12329 | (uchar*) client_auth_plugin, |
12330 | strlen(client_auth_plugin) + 1, |
12331 | (uchar*) data, data_len)); |
12332 | } |
12333 | |
12334 | #ifndef NO_EMBEDDED_ACCESS_CHECKS |
12335 | /** |
12336 | Finds acl entry in user database for authentication purposes. |
12337 | |
12338 | Finds a user and copies it into mpvio. Creates a fake user |
12339 | if no matching user account is found. |
12340 | |
12341 | @retval 0 found |
12342 | @retval 1 error |
12343 | */ |
12344 | static bool find_mpvio_user(MPVIO_EXT *mpvio) |
12345 | { |
12346 | Security_context *sctx= mpvio->auth_info.thd->security_ctx; |
12347 | DBUG_ENTER("find_mpvio_user" ); |
12348 | DBUG_ASSERT(mpvio->acl_user == 0); |
12349 | |
12350 | mysql_mutex_lock(&acl_cache->lock); |
12351 | |
12352 | ACL_USER *user= find_user_or_anon(sctx->host, sctx->user, sctx->ip); |
12353 | if (user) |
12354 | mpvio->acl_user= user->copy(mpvio->auth_info.thd->mem_root); |
12355 | |
12356 | mysql_mutex_unlock(&acl_cache->lock); |
12357 | |
12358 | if (!mpvio->acl_user) |
12359 | { |
12360 | /* |
12361 | A matching user was not found. Fake it. Take any user, make the |
12362 | authentication fail later. |
12363 | This way we get a realistically looking failure, with occasional |
12364 | "change auth plugin" requests even for nonexistent users. The ratio |
12365 | of "change auth plugin" request will be the same for real and |
12366 | nonexistent users. |
12367 | Note, that we cannot pick any user at random, it must always be |
12368 | the same user account for the incoming sctx->user name. |
12369 | */ |
12370 | ulong nr1=1, nr2=4; |
12371 | CHARSET_INFO *cs= &my_charset_latin1; |
12372 | cs->coll->hash_sort(cs, (uchar*) sctx->user, strlen(sctx->user), &nr1, &nr2); |
12373 | |
12374 | mysql_mutex_lock(&acl_cache->lock); |
12375 | if (!acl_users.elements) |
12376 | { |
12377 | mysql_mutex_unlock(&acl_cache->lock); |
12378 | login_failed_error(mpvio->auth_info.thd); |
12379 | DBUG_RETURN(1); |
12380 | } |
12381 | uint i= nr1 % acl_users.elements; |
12382 | ACL_USER *acl_user_tmp= dynamic_element(&acl_users, i, ACL_USER*); |
12383 | mpvio->acl_user= acl_user_tmp->copy(mpvio->auth_info.thd->mem_root); |
12384 | mysql_mutex_unlock(&acl_cache->lock); |
12385 | |
12386 | mpvio->make_it_fail= true; |
12387 | } |
12388 | |
12389 | /* user account requires non-default plugin and the client is too old */ |
12390 | if (mpvio->acl_user->plugin.str != native_password_plugin_name.str && |
12391 | mpvio->acl_user->plugin.str != old_password_plugin_name.str && |
12392 | !(mpvio->auth_info.thd->client_capabilities & CLIENT_PLUGIN_AUTH)) |
12393 | { |
12394 | DBUG_ASSERT(my_strcasecmp(system_charset_info, mpvio->acl_user->plugin.str, |
12395 | native_password_plugin_name.str)); |
12396 | DBUG_ASSERT(my_strcasecmp(system_charset_info, mpvio->acl_user->plugin.str, |
12397 | old_password_plugin_name.str)); |
12398 | my_error(ER_NOT_SUPPORTED_AUTH_MODE, MYF(0)); |
12399 | general_log_print(mpvio->auth_info.thd, COM_CONNECT, |
12400 | ER_THD(mpvio->auth_info.thd, ER_NOT_SUPPORTED_AUTH_MODE)); |
12401 | DBUG_RETURN (1); |
12402 | } |
12403 | |
12404 | mpvio->auth_info.user_name= sctx->user; |
12405 | mpvio->auth_info.user_name_length= (uint)strlen(sctx->user); |
12406 | mpvio->auth_info.auth_string= mpvio->acl_user->auth_string.str; |
12407 | mpvio->auth_info.auth_string_length= (unsigned long) mpvio->acl_user->auth_string.length; |
12408 | strmake_buf(mpvio->auth_info.authenticated_as, safe_str(mpvio->acl_user->user.str)); |
12409 | |
12410 | DBUG_PRINT("info" , ("exit: user=%s, auth_string=%s, authenticated as=%s" |
12411 | "plugin=%s" , |
12412 | mpvio->auth_info.user_name, |
12413 | mpvio->auth_info.auth_string, |
12414 | mpvio->auth_info.authenticated_as, |
12415 | mpvio->acl_user->plugin.str)); |
12416 | DBUG_RETURN(0); |
12417 | } |
12418 | |
12419 | static bool |
12420 | read_client_connect_attrs(char **ptr, char *end, CHARSET_INFO *from_cs) |
12421 | { |
12422 | ulonglong length; |
12423 | char *ptr_save= *ptr; |
12424 | |
12425 | /* not enough bytes to hold the length */ |
12426 | if (ptr_save >= end) |
12427 | return true; |
12428 | |
12429 | length= safe_net_field_length_ll((uchar **) ptr, end - ptr_save); |
12430 | |
12431 | /* cannot even read the length */ |
12432 | if (*ptr == NULL) |
12433 | return true; |
12434 | |
12435 | /* length says there're more data than can fit into the packet */ |
12436 | if (*ptr + length > end) |
12437 | return true; |
12438 | |
12439 | /* impose an artificial length limit of 64k */ |
12440 | if (length > 65535) |
12441 | return true; |
12442 | |
12443 | if (PSI_CALL_set_thread_connect_attrs(*ptr, (uint)length, from_cs) && |
12444 | current_thd->variables.log_warnings) |
12445 | sql_print_warning("Connection attributes of length %llu were truncated" , |
12446 | length); |
12447 | return false; |
12448 | } |
12449 | |
12450 | #endif |
12451 | |
12452 | /* the packet format is described in send_change_user_packet() */ |
12453 | static bool parse_com_change_user_packet(MPVIO_EXT *mpvio, uint packet_length) |
12454 | { |
12455 | THD *thd= mpvio->auth_info.thd; |
12456 | NET *net= &thd->net; |
12457 | Security_context *sctx= thd->security_ctx; |
12458 | |
12459 | char *user= (char*) net->read_pos; |
12460 | char *end= user + packet_length; |
12461 | /* Safe because there is always a trailing \0 at the end of the packet */ |
12462 | char *passwd= strend(user) + 1; |
12463 | uint user_len= (uint)(passwd - user - 1); |
12464 | char *db= passwd; |
12465 | char db_buff[SAFE_NAME_LEN + 1]; // buffer to store db in utf8 |
12466 | char user_buff[USERNAME_LENGTH + 1]; // buffer to store user in utf8 |
12467 | uint dummy_errors; |
12468 | DBUG_ENTER ("parse_com_change_user_packet" ); |
12469 | |
12470 | if (passwd >= end) |
12471 | { |
12472 | my_message(ER_UNKNOWN_COM_ERROR, ER_THD(thd, ER_UNKNOWN_COM_ERROR), |
12473 | MYF(0)); |
12474 | DBUG_RETURN (1); |
12475 | } |
12476 | |
12477 | /* |
12478 | Old clients send null-terminated string as password; new clients send |
12479 | the size (1 byte) + string (not null-terminated). Hence in case of empty |
12480 | password both send '\0'. |
12481 | |
12482 | This strlen() can't be easily deleted without changing protocol. |
12483 | |
12484 | Cast *passwd to an unsigned char, so that it doesn't extend the sign for |
12485 | *passwd > 127 and become 2**32-127+ after casting to uint. |
12486 | */ |
12487 | uint passwd_len= (thd->client_capabilities & CLIENT_SECURE_CONNECTION ? |
12488 | (uchar) (*passwd++) : (uint)strlen(passwd)); |
12489 | |
12490 | db+= passwd_len + 1; |
12491 | /* |
12492 | Database name is always NUL-terminated, so in case of empty database |
12493 | the packet must contain at least the trailing '\0'. |
12494 | */ |
12495 | if (db >= end) |
12496 | { |
12497 | my_message(ER_UNKNOWN_COM_ERROR, ER_THD(thd, ER_UNKNOWN_COM_ERROR), |
12498 | MYF(0)); |
12499 | DBUG_RETURN (1); |
12500 | } |
12501 | |
12502 | size_t db_len= strlen(db); |
12503 | |
12504 | char *next_field= db + db_len + 1; |
12505 | |
12506 | if (next_field + 1 < end) |
12507 | { |
12508 | if (thd_init_client_charset(thd, uint2korr(next_field))) |
12509 | DBUG_RETURN(1); |
12510 | next_field+= 2; |
12511 | } |
12512 | |
12513 | /* Convert database and user names to utf8 */ |
12514 | db_len= copy_and_convert(db_buff, sizeof(db_buff) - 1, system_charset_info, |
12515 | db, db_len, thd->charset(), &dummy_errors); |
12516 | |
12517 | user_len= copy_and_convert(user_buff, sizeof(user_buff) - 1, |
12518 | system_charset_info, user, user_len, |
12519 | thd->charset(), &dummy_errors); |
12520 | |
12521 | if (!(sctx->user= my_strndup(user_buff, user_len, MYF(MY_WME)))) |
12522 | DBUG_RETURN(1); |
12523 | |
12524 | /* Clear variables that are allocated */ |
12525 | thd->user_connect= 0; |
12526 | strmake_buf(sctx->priv_user, sctx->user); |
12527 | |
12528 | if (thd->make_lex_string(&mpvio->db, db_buff, db_len) == 0) |
12529 | DBUG_RETURN(1); /* The error is set by make_lex_string(). */ |
12530 | |
12531 | /* |
12532 | Clear thd->db as it points to something, that will be freed when |
12533 | connection is closed. We don't want to accidentally free a wrong |
12534 | pointer if connect failed. |
12535 | */ |
12536 | thd->reset_db(&null_clex_str); |
12537 | |
12538 | if (!initialized) |
12539 | { |
12540 | // if mysqld's been started with --skip-grant-tables option |
12541 | mpvio->status= MPVIO_EXT::SUCCESS; |
12542 | DBUG_RETURN(0); |
12543 | } |
12544 | |
12545 | #ifndef NO_EMBEDDED_ACCESS_CHECKS |
12546 | thd->password= passwd_len > 0; |
12547 | if (find_mpvio_user(mpvio)) |
12548 | DBUG_RETURN(1); |
12549 | |
12550 | const char *client_plugin; |
12551 | if (thd->client_capabilities & CLIENT_PLUGIN_AUTH) |
12552 | { |
12553 | if (next_field >= end) |
12554 | { |
12555 | my_message(ER_UNKNOWN_COM_ERROR, ER_THD(thd, ER_UNKNOWN_COM_ERROR), |
12556 | MYF(0)); |
12557 | DBUG_RETURN(1); |
12558 | } |
12559 | client_plugin= fix_plugin_ptr(next_field); |
12560 | next_field+= strlen(next_field) + 1; |
12561 | } |
12562 | else |
12563 | { |
12564 | if (thd->client_capabilities & CLIENT_SECURE_CONNECTION) |
12565 | client_plugin= native_password_plugin_name.str; |
12566 | else |
12567 | { |
12568 | client_plugin= old_password_plugin_name.str; |
12569 | /* |
12570 | For a passwordless accounts we use native_password_plugin. |
12571 | But when an old 4.0 client connects to it, we change it to |
12572 | old_password_plugin, otherwise MySQL will think that server |
12573 | and client plugins don't match. |
12574 | */ |
12575 | if (mpvio->acl_user->auth_string.length == 0) |
12576 | mpvio->acl_user->plugin= old_password_plugin_name; |
12577 | } |
12578 | } |
12579 | |
12580 | if ((thd->client_capabilities & CLIENT_CONNECT_ATTRS) && |
12581 | read_client_connect_attrs(&next_field, end, |
12582 | thd->charset())) |
12583 | { |
12584 | my_message(ER_UNKNOWN_COM_ERROR, ER_THD(thd, ER_UNKNOWN_COM_ERROR), |
12585 | MYF(0)); |
12586 | DBUG_RETURN(1); |
12587 | } |
12588 | |
12589 | DBUG_PRINT("info" , ("client_plugin=%s, restart" , client_plugin)); |
12590 | /* |
12591 | Remember the data part of the packet, to present it to plugin in |
12592 | read_packet() |
12593 | */ |
12594 | mpvio->cached_client_reply.pkt= passwd; |
12595 | mpvio->cached_client_reply.pkt_len= passwd_len; |
12596 | mpvio->cached_client_reply.plugin= client_plugin; |
12597 | mpvio->status= MPVIO_EXT::RESTART; |
12598 | #endif |
12599 | |
12600 | DBUG_RETURN (0); |
12601 | } |
12602 | |
12603 | |
12604 | /* the packet format is described in send_client_reply_packet() */ |
12605 | static ulong parse_client_handshake_packet(MPVIO_EXT *mpvio, |
12606 | uchar **buff, ulong pkt_len) |
12607 | { |
12608 | #ifndef EMBEDDED_LIBRARY |
12609 | THD *thd= mpvio->auth_info.thd; |
12610 | NET *net= &thd->net; |
12611 | char *end; |
12612 | DBUG_ASSERT(mpvio->status == MPVIO_EXT::FAILURE); |
12613 | |
12614 | if (pkt_len < MIN_HANDSHAKE_SIZE) |
12615 | return packet_error; |
12616 | |
12617 | /* |
12618 | Protocol buffer is guaranteed to always end with \0. (see my_net_read()) |
12619 | As the code below depends on this, lets check that. |
12620 | */ |
12621 | DBUG_ASSERT(net->read_pos[pkt_len] == 0); |
12622 | |
12623 | ulonglong client_capabilities= uint2korr(net->read_pos); |
12624 | compile_time_assert(sizeof(client_capabilities) >= 8); |
12625 | if (client_capabilities & CLIENT_PROTOCOL_41) |
12626 | { |
12627 | if (pkt_len < 32) |
12628 | return packet_error; |
12629 | client_capabilities|= ((ulong) uint2korr(net->read_pos+2)) << 16; |
12630 | if (!(client_capabilities & CLIENT_MYSQL)) |
12631 | { |
12632 | // it is client with mariadb extensions |
12633 | ulonglong ext_client_capabilities= |
12634 | (((ulonglong)uint4korr(net->read_pos + 28)) << 32); |
12635 | client_capabilities|= ext_client_capabilities; |
12636 | } |
12637 | } |
12638 | |
12639 | /* Disable those bits which are not supported by the client. */ |
12640 | compile_time_assert(sizeof(thd->client_capabilities) >= 8); |
12641 | thd->client_capabilities&= client_capabilities; |
12642 | |
12643 | DBUG_PRINT("info" , ("client capabilities: %llu" , thd->client_capabilities)); |
12644 | if (thd->client_capabilities & CLIENT_SSL) |
12645 | { |
12646 | unsigned long errptr __attribute__((unused)); |
12647 | |
12648 | /* Do the SSL layering. */ |
12649 | if (!ssl_acceptor_fd) |
12650 | return packet_error; |
12651 | |
12652 | DBUG_PRINT("info" , ("IO layer change in progress..." )); |
12653 | if (sslaccept(ssl_acceptor_fd, net->vio, net->read_timeout, &errptr)) |
12654 | { |
12655 | DBUG_PRINT("error" , ("Failed to accept new SSL connection" )); |
12656 | return packet_error; |
12657 | } |
12658 | |
12659 | DBUG_PRINT("info" , ("Reading user information over SSL layer" )); |
12660 | pkt_len= my_net_read(net); |
12661 | if (unlikely(pkt_len == packet_error || pkt_len < NORMAL_HANDSHAKE_SIZE)) |
12662 | { |
12663 | DBUG_PRINT("error" , ("Failed to read user information (pkt_len= %lu)" , |
12664 | pkt_len)); |
12665 | return packet_error; |
12666 | } |
12667 | } |
12668 | |
12669 | if (client_capabilities & CLIENT_PROTOCOL_41) |
12670 | { |
12671 | thd->max_client_packet_length= uint4korr(net->read_pos+4); |
12672 | DBUG_PRINT("info" , ("client_character_set: %d" , (uint) net->read_pos[8])); |
12673 | if (thd_init_client_charset(thd, (uint) net->read_pos[8])) |
12674 | return packet_error; |
12675 | end= (char*) net->read_pos+32; |
12676 | } |
12677 | else |
12678 | { |
12679 | if (pkt_len < 5) |
12680 | return packet_error; |
12681 | thd->max_client_packet_length= uint3korr(net->read_pos+2); |
12682 | end= (char*) net->read_pos+5; |
12683 | } |
12684 | |
12685 | if (end >= (char*) net->read_pos+ pkt_len +2) |
12686 | return packet_error; |
12687 | |
12688 | if (thd->client_capabilities & CLIENT_IGNORE_SPACE) |
12689 | thd->variables.sql_mode|= MODE_IGNORE_SPACE; |
12690 | if (thd->client_capabilities & CLIENT_INTERACTIVE) |
12691 | thd->variables.net_wait_timeout= thd->variables.net_interactive_timeout; |
12692 | |
12693 | if (end >= (char*) net->read_pos+ pkt_len +2) |
12694 | return packet_error; |
12695 | |
12696 | if ((thd->client_capabilities & CLIENT_TRANSACTIONS) && |
12697 | opt_using_transactions) |
12698 | net->return_status= &thd->server_status; |
12699 | |
12700 | char *user= end; |
12701 | char *passwd= strend(user)+1; |
12702 | size_t user_len= (size_t)(passwd - user - 1), db_len; |
12703 | char *db= passwd; |
12704 | char user_buff[USERNAME_LENGTH + 1]; // buffer to store user in utf8 |
12705 | uint dummy_errors; |
12706 | |
12707 | /* |
12708 | Old clients send null-terminated string as password; new clients send |
12709 | the size (1 byte) + string (not null-terminated). Hence in case of empty |
12710 | password both send '\0'. |
12711 | |
12712 | This strlen() can't be easily deleted without changing protocol. |
12713 | |
12714 | Cast *passwd to an unsigned char, so that it doesn't extend the sign for |
12715 | *passwd > 127 and become 2**32-127+ after casting to uint. |
12716 | */ |
12717 | ulonglong len; |
12718 | size_t passwd_len; |
12719 | |
12720 | if (!(thd->client_capabilities & CLIENT_SECURE_CONNECTION)) |
12721 | len= strlen(passwd); |
12722 | else if (!(thd->client_capabilities & CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA)) |
12723 | len= (uchar)(*passwd++); |
12724 | else |
12725 | { |
12726 | len= safe_net_field_length_ll((uchar**)&passwd, |
12727 | net->read_pos + pkt_len - (uchar*)passwd); |
12728 | if (len > pkt_len) |
12729 | return packet_error; |
12730 | } |
12731 | |
12732 | passwd_len= (size_t)len; |
12733 | db= thd->client_capabilities & CLIENT_CONNECT_WITH_DB ? |
12734 | db + passwd_len + 1 : 0; |
12735 | |
12736 | if (passwd == NULL || |
12737 | passwd + passwd_len + MY_TEST(db) > (char*) net->read_pos + pkt_len) |
12738 | return packet_error; |
12739 | |
12740 | /* strlen() can't be easily deleted without changing protocol */ |
12741 | db_len= safe_strlen(db); |
12742 | |
12743 | char *next_field; |
12744 | const char *client_plugin= next_field= passwd + passwd_len + (db ? db_len + 1 : 0); |
12745 | |
12746 | /* |
12747 | Since 4.1 all database names are stored in utf8 |
12748 | The cast is ok as copy_with_error will create a new area for db |
12749 | */ |
12750 | if (unlikely(thd->copy_with_error(system_charset_info, |
12751 | (LEX_STRING*) &mpvio->db, |
12752 | thd->charset(), db, db_len))) |
12753 | return packet_error; |
12754 | |
12755 | user_len= copy_and_convert(user_buff, sizeof(user_buff) - 1, |
12756 | system_charset_info, user, user_len, |
12757 | thd->charset(), &dummy_errors); |
12758 | user= user_buff; |
12759 | |
12760 | /* If username starts and ends in "'", chop them off */ |
12761 | if (user_len > 1 && user[0] == '\'' && user[user_len - 1] == '\'') |
12762 | { |
12763 | user++; |
12764 | user_len-= 2; |
12765 | } |
12766 | |
12767 | /* |
12768 | Clip username to allowed length in characters (not bytes). This is |
12769 | mostly for backward compatibility (to truncate long usernames, as |
12770 | old 5.1 did) |
12771 | */ |
12772 | user_len= Well_formed_prefix(system_charset_info, user, user_len, |
12773 | username_char_length).length(); |
12774 | user[user_len]= '\0'; |
12775 | |
12776 | Security_context *sctx= thd->security_ctx; |
12777 | |
12778 | my_free((char*) sctx->user); |
12779 | if (!(sctx->user= my_strndup(user, user_len, MYF(MY_WME)))) |
12780 | return packet_error; /* The error is set by my_strdup(). */ |
12781 | |
12782 | |
12783 | /* |
12784 | Clear thd->db as it points to something, that will be freed when |
12785 | connection is closed. We don't want to accidentally free a wrong |
12786 | pointer if connect failed. |
12787 | */ |
12788 | thd->reset_db(&null_clex_str); |
12789 | |
12790 | if (!initialized) |
12791 | { |
12792 | // if mysqld's been started with --skip-grant-tables option |
12793 | mpvio->status= MPVIO_EXT::SUCCESS; |
12794 | return packet_error; |
12795 | } |
12796 | |
12797 | thd->password= passwd_len > 0; |
12798 | if (find_mpvio_user(mpvio)) |
12799 | return packet_error; |
12800 | |
12801 | if ((thd->client_capabilities & CLIENT_PLUGIN_AUTH) && |
12802 | (client_plugin < (char *)net->read_pos + pkt_len)) |
12803 | { |
12804 | client_plugin= fix_plugin_ptr(client_plugin); |
12805 | next_field+= strlen(next_field) + 1; |
12806 | } |
12807 | else |
12808 | { |
12809 | /* Some clients lie. Sad, but true */ |
12810 | thd->client_capabilities &= ~CLIENT_PLUGIN_AUTH; |
12811 | |
12812 | if (thd->client_capabilities & CLIENT_SECURE_CONNECTION) |
12813 | client_plugin= native_password_plugin_name.str; |
12814 | else |
12815 | { |
12816 | client_plugin= old_password_plugin_name.str; |
12817 | /* |
12818 | For a passwordless accounts we use native_password_plugin. |
12819 | But when an old 4.0 client connects to it, we change it to |
12820 | old_password_plugin, otherwise MySQL will think that server |
12821 | and client plugins don't match. |
12822 | */ |
12823 | if (mpvio->acl_user->auth_string.length == 0) |
12824 | mpvio->acl_user->plugin= old_password_plugin_name; |
12825 | } |
12826 | } |
12827 | |
12828 | if ((thd->client_capabilities & CLIENT_CONNECT_ATTRS) && |
12829 | read_client_connect_attrs(&next_field, ((char *)net->read_pos) + pkt_len, |
12830 | mpvio->auth_info.thd->charset())) |
12831 | return packet_error; |
12832 | |
12833 | /* |
12834 | if the acl_user needs a different plugin to authenticate |
12835 | (specified in GRANT ... AUTHENTICATED VIA plugin_name ..) |
12836 | we need to restart the authentication in the server. |
12837 | But perhaps the client has already used the correct plugin - |
12838 | in that case the authentication on the client may not need to be |
12839 | restarted and a server auth plugin will read the data that the client |
12840 | has just send. Cache them to return in the next server_mpvio_read_packet(). |
12841 | */ |
12842 | if (!lex_string_eq(&mpvio->acl_user->plugin, plugin_name(mpvio->plugin))) |
12843 | { |
12844 | mpvio->cached_client_reply.pkt= passwd; |
12845 | mpvio->cached_client_reply.pkt_len= (uint)passwd_len; |
12846 | mpvio->cached_client_reply.plugin= client_plugin; |
12847 | mpvio->status= MPVIO_EXT::RESTART; |
12848 | return packet_error; |
12849 | } |
12850 | |
12851 | /* |
12852 | ok, we don't need to restart the authentication on the server. |
12853 | but if the client used the wrong plugin, we need to restart |
12854 | the authentication on the client. Do it here, the server plugin |
12855 | doesn't need to know. |
12856 | */ |
12857 | const char *client_auth_plugin= |
12858 | ((st_mysql_auth *) (plugin_decl(mpvio->plugin)->info))->client_auth_plugin; |
12859 | |
12860 | if (client_auth_plugin && |
12861 | my_strcasecmp(system_charset_info, client_plugin, client_auth_plugin)) |
12862 | { |
12863 | mpvio->cached_client_reply.plugin= client_plugin; |
12864 | if (send_plugin_request_packet(mpvio, |
12865 | (uchar*) mpvio->cached_server_packet.pkt, |
12866 | mpvio->cached_server_packet.pkt_len)) |
12867 | return packet_error; |
12868 | |
12869 | passwd_len= my_net_read(&thd->net); |
12870 | passwd= (char*)thd->net.read_pos; |
12871 | } |
12872 | |
12873 | *buff= (uchar*) passwd; |
12874 | return (ulong)passwd_len; |
12875 | #else |
12876 | return 0; |
12877 | #endif |
12878 | } |
12879 | |
12880 | |
12881 | /** |
12882 | vio->write_packet() callback method for server authentication plugins |
12883 | |
12884 | This function is called by a server authentication plugin, when it wants |
12885 | to send data to the client. |
12886 | |
12887 | It transparently wraps the data into a handshake packet, |
12888 | and handles plugin negotiation with the client. If necessary, |
12889 | it escapes the plugin data, if it starts with a mysql protocol packet byte. |
12890 | */ |
12891 | static int server_mpvio_write_packet(MYSQL_PLUGIN_VIO *param, |
12892 | const uchar *packet, int packet_len) |
12893 | { |
12894 | MPVIO_EXT *mpvio= (MPVIO_EXT *) param; |
12895 | int res; |
12896 | DBUG_ENTER("server_mpvio_write_packet" ); |
12897 | |
12898 | /* reset cached_client_reply */ |
12899 | mpvio->cached_client_reply.pkt= 0; |
12900 | |
12901 | /* for the 1st packet we wrap plugin data into the handshake packet */ |
12902 | if (mpvio->packets_written == 0) |
12903 | res= send_server_handshake_packet(mpvio, (char*) packet, packet_len); |
12904 | else if (mpvio->status == MPVIO_EXT::RESTART) |
12905 | res= send_plugin_request_packet(mpvio, packet, packet_len); |
12906 | else if (packet_len > 0 && (*packet == 1 || *packet == 255 || *packet == 254)) |
12907 | { |
12908 | /* |
12909 | we cannot allow plugin data packet to start from 255 or 254 - |
12910 | as the client will treat it as an error or "change plugin" packet. |
12911 | We'll escape these bytes with \1. Consequently, we |
12912 | have to escape \1 byte too. |
12913 | */ |
12914 | res= net_write_command(&mpvio->auth_info.thd->net, 1, (uchar*)"" , 0, |
12915 | packet, packet_len); |
12916 | } |
12917 | else |
12918 | { |
12919 | res= my_net_write(&mpvio->auth_info.thd->net, packet, packet_len) || |
12920 | net_flush(&mpvio->auth_info.thd->net); |
12921 | } |
12922 | mpvio->packets_written++; |
12923 | DBUG_RETURN(res); |
12924 | } |
12925 | |
12926 | /** |
12927 | vio->read_packet() callback method for server authentication plugins |
12928 | |
12929 | This function is called by a server authentication plugin, when it wants |
12930 | to read data from the client. |
12931 | |
12932 | It transparently extracts the client plugin data, if embedded into |
12933 | a client authentication handshake packet, and handles plugin negotiation |
12934 | with the client, if necessary. |
12935 | */ |
12936 | static int server_mpvio_read_packet(MYSQL_PLUGIN_VIO *param, uchar **buf) |
12937 | { |
12938 | MPVIO_EXT *mpvio= (MPVIO_EXT *) param; |
12939 | ulong pkt_len; |
12940 | DBUG_ENTER("server_mpvio_read_packet" ); |
12941 | if (mpvio->packets_written == 0) |
12942 | { |
12943 | /* |
12944 | plugin wants to read the data without sending anything first. |
12945 | send an empty packet to force a server handshake packet to be sent |
12946 | */ |
12947 | if (server_mpvio_write_packet(mpvio, 0, 0)) |
12948 | pkt_len= packet_error; |
12949 | else |
12950 | pkt_len= my_net_read(&mpvio->auth_info.thd->net); |
12951 | } |
12952 | else if (mpvio->cached_client_reply.pkt) |
12953 | { |
12954 | DBUG_ASSERT(mpvio->status == MPVIO_EXT::RESTART); |
12955 | DBUG_ASSERT(mpvio->packets_read > 0); |
12956 | /* |
12957 | if the have the data cached from the last server_mpvio_read_packet |
12958 | (which can be the case if it's a restarted authentication) |
12959 | and a client has used the correct plugin, then we can return the |
12960 | cached data straight away and avoid one round trip. |
12961 | */ |
12962 | const char *client_auth_plugin= |
12963 | ((st_mysql_auth *) (plugin_decl(mpvio->plugin)->info))->client_auth_plugin; |
12964 | if (client_auth_plugin == 0 || |
12965 | my_strcasecmp(system_charset_info, mpvio->cached_client_reply.plugin, |
12966 | client_auth_plugin) == 0) |
12967 | { |
12968 | mpvio->status= MPVIO_EXT::FAILURE; |
12969 | *buf= (uchar*) mpvio->cached_client_reply.pkt; |
12970 | mpvio->cached_client_reply.pkt= 0; |
12971 | mpvio->packets_read++; |
12972 | |
12973 | DBUG_RETURN ((int) mpvio->cached_client_reply.pkt_len); |
12974 | } |
12975 | |
12976 | /* |
12977 | But if the client has used the wrong plugin, the cached data are |
12978 | useless. Furthermore, we have to send a "change plugin" request |
12979 | to the client. |
12980 | */ |
12981 | if (server_mpvio_write_packet(mpvio, 0, 0)) |
12982 | pkt_len= packet_error; |
12983 | else |
12984 | pkt_len= my_net_read(&mpvio->auth_info.thd->net); |
12985 | } |
12986 | else |
12987 | pkt_len= my_net_read(&mpvio->auth_info.thd->net); |
12988 | |
12989 | if (unlikely(pkt_len == packet_error)) |
12990 | goto err; |
12991 | |
12992 | mpvio->packets_read++; |
12993 | |
12994 | /* |
12995 | the 1st packet has the plugin data wrapped into the client authentication |
12996 | handshake packet |
12997 | */ |
12998 | if (mpvio->packets_read == 1) |
12999 | { |
13000 | pkt_len= parse_client_handshake_packet(mpvio, buf, pkt_len); |
13001 | if (unlikely(pkt_len == packet_error)) |
13002 | goto err; |
13003 | } |
13004 | else |
13005 | *buf= mpvio->auth_info.thd->net.read_pos; |
13006 | |
13007 | DBUG_RETURN((int)pkt_len); |
13008 | |
13009 | err: |
13010 | if (mpvio->status == MPVIO_EXT::FAILURE) |
13011 | { |
13012 | if (!mpvio->auth_info.thd->is_error()) |
13013 | my_error(ER_HANDSHAKE_ERROR, MYF(0)); |
13014 | } |
13015 | DBUG_RETURN(-1); |
13016 | } |
13017 | |
13018 | /** |
13019 | fills MYSQL_PLUGIN_VIO_INFO structure with the information about the |
13020 | connection |
13021 | */ |
13022 | static void server_mpvio_info(MYSQL_PLUGIN_VIO *vio, |
13023 | MYSQL_PLUGIN_VIO_INFO *info) |
13024 | { |
13025 | MPVIO_EXT *mpvio= (MPVIO_EXT *) vio; |
13026 | mpvio_info(mpvio->auth_info.thd->net.vio, info); |
13027 | } |
13028 | |
13029 | static bool acl_check_ssl(THD *thd, const ACL_USER *acl_user) |
13030 | { |
13031 | #ifdef HAVE_OPENSSL |
13032 | Vio *vio= thd->net.vio; |
13033 | SSL *ssl= (SSL *) vio->ssl_arg; |
13034 | X509 *cert; |
13035 | #endif |
13036 | |
13037 | /* |
13038 | At this point we know that user is allowed to connect |
13039 | from given host by given username/password pair. Now |
13040 | we check if SSL is required, if user is using SSL and |
13041 | if X509 certificate attributes are OK |
13042 | */ |
13043 | switch (acl_user->ssl_type) { |
13044 | case SSL_TYPE_NOT_SPECIFIED: // Impossible |
13045 | case SSL_TYPE_NONE: // SSL is not required |
13046 | return 0; |
13047 | #ifdef HAVE_OPENSSL |
13048 | case SSL_TYPE_ANY: // Any kind of SSL is ok |
13049 | return vio_type(vio) != VIO_TYPE_SSL; |
13050 | case SSL_TYPE_X509: /* Client should have any valid certificate. */ |
13051 | /* |
13052 | Connections with non-valid certificates are dropped already |
13053 | in sslaccept() anyway, so we do not check validity here. |
13054 | |
13055 | We need to check for absence of SSL because without SSL |
13056 | we should reject connection. |
13057 | */ |
13058 | if (vio_type(vio) == VIO_TYPE_SSL && |
13059 | SSL_get_verify_result(ssl) == X509_V_OK && |
13060 | (cert= SSL_get_peer_certificate(ssl))) |
13061 | { |
13062 | X509_free(cert); |
13063 | return 0; |
13064 | } |
13065 | return 1; |
13066 | case SSL_TYPE_SPECIFIED: /* Client should have specified attrib */ |
13067 | /* If a cipher name is specified, we compare it to actual cipher in use. */ |
13068 | if (vio_type(vio) != VIO_TYPE_SSL || |
13069 | SSL_get_verify_result(ssl) != X509_V_OK) |
13070 | return 1; |
13071 | if (acl_user->ssl_cipher) |
13072 | { |
13073 | DBUG_PRINT("info" , ("comparing ciphers: '%s' and '%s'" , |
13074 | acl_user->ssl_cipher, SSL_get_cipher(ssl))); |
13075 | if (strcmp(acl_user->ssl_cipher, SSL_get_cipher(ssl))) |
13076 | { |
13077 | if (global_system_variables.log_warnings) |
13078 | sql_print_information("X509 ciphers mismatch: should be '%s' but is '%s'" , |
13079 | acl_user->ssl_cipher, SSL_get_cipher(ssl)); |
13080 | return 1; |
13081 | } |
13082 | } |
13083 | if (!acl_user->x509_issuer && !acl_user->x509_subject) |
13084 | return 0; // all done |
13085 | |
13086 | /* Prepare certificate (if exists) */ |
13087 | if (!(cert= SSL_get_peer_certificate(ssl))) |
13088 | return 1; |
13089 | /* If X509 issuer is specified, we check it... */ |
13090 | if (acl_user->x509_issuer) |
13091 | { |
13092 | char *ptr= X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0); |
13093 | DBUG_PRINT("info" , ("comparing issuers: '%s' and '%s'" , |
13094 | acl_user->x509_issuer, ptr)); |
13095 | if (strcmp(acl_user->x509_issuer, ptr)) |
13096 | { |
13097 | if (global_system_variables.log_warnings) |
13098 | sql_print_information("X509 issuer mismatch: should be '%s' " |
13099 | "but is '%s'" , acl_user->x509_issuer, ptr); |
13100 | free(ptr); |
13101 | X509_free(cert); |
13102 | return 1; |
13103 | } |
13104 | free(ptr); |
13105 | } |
13106 | /* X509 subject is specified, we check it .. */ |
13107 | if (acl_user->x509_subject) |
13108 | { |
13109 | char *ptr= X509_NAME_oneline(X509_get_subject_name(cert), 0, 0); |
13110 | DBUG_PRINT("info" , ("comparing subjects: '%s' and '%s'" , |
13111 | acl_user->x509_subject, ptr)); |
13112 | if (strcmp(acl_user->x509_subject, ptr)) |
13113 | { |
13114 | if (global_system_variables.log_warnings) |
13115 | sql_print_information("X509 subject mismatch: should be '%s' but is '%s'" , |
13116 | acl_user->x509_subject, ptr); |
13117 | free(ptr); |
13118 | X509_free(cert); |
13119 | return 1; |
13120 | } |
13121 | free(ptr); |
13122 | } |
13123 | X509_free(cert); |
13124 | return 0; |
13125 | #else /* HAVE_OPENSSL */ |
13126 | default: |
13127 | /* |
13128 | If we don't have SSL but SSL is required for this user the |
13129 | authentication should fail. |
13130 | */ |
13131 | return 1; |
13132 | #endif /* HAVE_OPENSSL */ |
13133 | } |
13134 | return 1; |
13135 | } |
13136 | |
13137 | |
13138 | static int do_auth_once(THD *thd, const LEX_CSTRING *auth_plugin_name, |
13139 | MPVIO_EXT *mpvio) |
13140 | { |
13141 | int res= CR_OK, old_status= MPVIO_EXT::FAILURE; |
13142 | bool unlock_plugin= false; |
13143 | plugin_ref plugin= NULL; |
13144 | |
13145 | if (auth_plugin_name->str == native_password_plugin_name.str) |
13146 | plugin= native_password_plugin; |
13147 | #ifndef EMBEDDED_LIBRARY |
13148 | else if (auth_plugin_name->str == old_password_plugin_name.str) |
13149 | plugin= old_password_plugin; |
13150 | else if ((plugin= my_plugin_lock_by_name(thd, auth_plugin_name, |
13151 | MYSQL_AUTHENTICATION_PLUGIN))) |
13152 | unlock_plugin= true; |
13153 | #endif |
13154 | |
13155 | mpvio->plugin= plugin; |
13156 | old_status= mpvio->status; |
13157 | |
13158 | if (plugin) |
13159 | { |
13160 | st_mysql_auth *auth= (st_mysql_auth *) plugin_decl(plugin)->info; |
13161 | switch (auth->interface_version >> 8) { |
13162 | case 0x02: |
13163 | res= auth->authenticate_user(mpvio, &mpvio->auth_info); |
13164 | break; |
13165 | case 0x01: |
13166 | { |
13167 | MYSQL_SERVER_AUTH_INFO_0x0100 compat; |
13168 | compat.downgrade(&mpvio->auth_info); |
13169 | res= auth->authenticate_user(mpvio, (MYSQL_SERVER_AUTH_INFO *)&compat); |
13170 | compat.upgrade(&mpvio->auth_info); |
13171 | } |
13172 | break; |
13173 | default: DBUG_ASSERT(0); |
13174 | } |
13175 | |
13176 | if (unlock_plugin) |
13177 | plugin_unlock(thd, plugin); |
13178 | } |
13179 | else |
13180 | { |
13181 | /* Server cannot load the required plugin. */ |
13182 | Host_errors errors; |
13183 | errors.m_no_auth_plugin= 1; |
13184 | inc_host_errors(mpvio->auth_info.thd->security_ctx->ip, &errors); |
13185 | my_error(ER_PLUGIN_IS_NOT_LOADED, MYF(0), auth_plugin_name->str); |
13186 | res= CR_ERROR; |
13187 | } |
13188 | |
13189 | /* |
13190 | If the status was MPVIO_EXT::RESTART before the authenticate_user() call |
13191 | it can never be MPVIO_EXT::RESTART after the call, because any call |
13192 | to write_packet() or read_packet() will reset the status. |
13193 | |
13194 | But (!) if a plugin never called a read_packet() or write_packet(), the |
13195 | status will stay unchanged. We'll fix it, by resetting the status here. |
13196 | */ |
13197 | if (old_status == MPVIO_EXT::RESTART && mpvio->status == MPVIO_EXT::RESTART) |
13198 | mpvio->status= MPVIO_EXT::FAILURE; // reset to the default |
13199 | |
13200 | return res; |
13201 | } |
13202 | |
13203 | |
13204 | /** |
13205 | Perform the handshake, authorize the client and update thd sctx variables. |
13206 | |
13207 | @param thd thread handle |
13208 | @param com_change_user_pkt_len size of the COM_CHANGE_USER packet |
13209 | (without the first, command, byte) or 0 |
13210 | if it's not a COM_CHANGE_USER (that is, if |
13211 | it's a new connection) |
13212 | |
13213 | @retval 0 success, thd is updated. |
13214 | @retval 1 error |
13215 | */ |
13216 | bool acl_authenticate(THD *thd, uint com_change_user_pkt_len) |
13217 | { |
13218 | int res= CR_OK; |
13219 | MPVIO_EXT mpvio; |
13220 | const LEX_CSTRING *auth_plugin_name= default_auth_plugin_name; |
13221 | enum enum_server_command command= com_change_user_pkt_len ? COM_CHANGE_USER |
13222 | : COM_CONNECT; |
13223 | DBUG_ENTER("acl_authenticate" ); |
13224 | |
13225 | bzero(&mpvio, sizeof(mpvio)); |
13226 | mpvio.read_packet= server_mpvio_read_packet; |
13227 | mpvio.write_packet= server_mpvio_write_packet; |
13228 | mpvio.info= server_mpvio_info; |
13229 | mpvio.status= MPVIO_EXT::FAILURE; |
13230 | mpvio.make_it_fail= false; |
13231 | mpvio.auth_info.thd= thd; |
13232 | mpvio.auth_info.host_or_ip= thd->security_ctx->host_or_ip; |
13233 | mpvio.auth_info.host_or_ip_length= |
13234 | (unsigned int) strlen(thd->security_ctx->host_or_ip); |
13235 | |
13236 | DBUG_PRINT("info" , ("com_change_user_pkt_len=%u" , com_change_user_pkt_len)); |
13237 | |
13238 | if (command == COM_CHANGE_USER) |
13239 | { |
13240 | mpvio.packets_written++; // pretend that a server handshake packet was sent |
13241 | mpvio.packets_read++; // take COM_CHANGE_USER packet into account |
13242 | |
13243 | if (parse_com_change_user_packet(&mpvio, com_change_user_pkt_len)) |
13244 | DBUG_RETURN(1); |
13245 | |
13246 | DBUG_ASSERT(mpvio.status == MPVIO_EXT::RESTART || |
13247 | mpvio.status == MPVIO_EXT::SUCCESS); |
13248 | } |
13249 | else |
13250 | { |
13251 | /* mark the thd as having no scramble yet */ |
13252 | thd->scramble[SCRAMBLE_LENGTH]= 1; |
13253 | |
13254 | /* |
13255 | perform the first authentication attempt, with the default plugin. |
13256 | This sends the server handshake packet, reads the client reply |
13257 | with a user name, and performs the authentication if everyone has used |
13258 | the correct plugin. |
13259 | */ |
13260 | |
13261 | res= do_auth_once(thd, auth_plugin_name, &mpvio); |
13262 | } |
13263 | |
13264 | /* |
13265 | retry the authentication, if - after receiving the user name - |
13266 | we found that we need to switch to a non-default plugin |
13267 | */ |
13268 | if (mpvio.status == MPVIO_EXT::RESTART) |
13269 | { |
13270 | DBUG_ASSERT(mpvio.acl_user); |
13271 | DBUG_ASSERT(command == COM_CHANGE_USER || |
13272 | !lex_string_eq(auth_plugin_name, &mpvio.acl_user->plugin)); |
13273 | auth_plugin_name= &mpvio.acl_user->plugin; |
13274 | res= do_auth_once(thd, auth_plugin_name, &mpvio); |
13275 | } |
13276 | if (mpvio.make_it_fail && res == CR_OK) |
13277 | { |
13278 | mpvio.status= MPVIO_EXT::FAILURE; |
13279 | res= CR_ERROR; |
13280 | } |
13281 | |
13282 | Security_context *sctx= thd->security_ctx; |
13283 | const ACL_USER *acl_user= mpvio.acl_user; |
13284 | |
13285 | thd->password= mpvio.auth_info.password_used; // remember for error messages |
13286 | |
13287 | /* |
13288 | Log the command here so that the user can check the log |
13289 | for the tried logins and also to detect break-in attempts. |
13290 | |
13291 | if sctx->user is unset it's protocol failure, bad packet. |
13292 | */ |
13293 | if (sctx->user) |
13294 | { |
13295 | if (strcmp(sctx->priv_user, sctx->user)) |
13296 | { |
13297 | general_log_print(thd, command, "%s@%s as %s on %s" , |
13298 | sctx->user, sctx->host_or_ip, |
13299 | sctx->priv_user[0] ? sctx->priv_user : "anonymous" , |
13300 | safe_str(mpvio.db.str)); |
13301 | } |
13302 | else |
13303 | general_log_print(thd, command, (char*) "%s@%s on %s" , |
13304 | sctx->user, sctx->host_or_ip, |
13305 | safe_str(mpvio.db.str)); |
13306 | } |
13307 | |
13308 | if (res > CR_OK && mpvio.status != MPVIO_EXT::SUCCESS) |
13309 | { |
13310 | Host_errors errors; |
13311 | DBUG_ASSERT(mpvio.status == MPVIO_EXT::FAILURE); |
13312 | switch (res) |
13313 | { |
13314 | case CR_AUTH_PLUGIN_ERROR: |
13315 | errors.m_auth_plugin= 1; |
13316 | break; |
13317 | case CR_AUTH_HANDSHAKE: |
13318 | errors.m_handshake= 1; |
13319 | break; |
13320 | case CR_AUTH_USER_CREDENTIALS: |
13321 | errors.m_authentication= 1; |
13322 | break; |
13323 | case CR_ERROR: |
13324 | default: |
13325 | /* Unknown of unspecified auth plugin error. */ |
13326 | errors.m_auth_plugin= 1; |
13327 | break; |
13328 | } |
13329 | inc_host_errors(mpvio.auth_info.thd->security_ctx->ip, &errors); |
13330 | if (!thd->is_error()) |
13331 | login_failed_error(thd); |
13332 | DBUG_RETURN(1); |
13333 | } |
13334 | |
13335 | sctx->proxy_user[0]= 0; |
13336 | |
13337 | if (initialized) // if not --skip-grant-tables |
13338 | { |
13339 | #ifndef NO_EMBEDDED_ACCESS_CHECKS |
13340 | bool is_proxy_user= FALSE; |
13341 | const char *auth_user = safe_str(acl_user->user.str); |
13342 | ACL_PROXY_USER *proxy_user; |
13343 | /* check if the user is allowed to proxy as another user */ |
13344 | proxy_user= acl_find_proxy_user(auth_user, sctx->host, sctx->ip, |
13345 | mpvio.auth_info.authenticated_as, |
13346 | &is_proxy_user); |
13347 | if (is_proxy_user) |
13348 | { |
13349 | ACL_USER *acl_proxy_user; |
13350 | |
13351 | /* we need to find the proxy user, but there was none */ |
13352 | if (!proxy_user) |
13353 | { |
13354 | Host_errors errors; |
13355 | errors.m_proxy_user= 1; |
13356 | inc_host_errors(mpvio.auth_info.thd->security_ctx->ip, &errors); |
13357 | if (!thd->is_error()) |
13358 | login_failed_error(thd); |
13359 | DBUG_RETURN(1); |
13360 | } |
13361 | |
13362 | my_snprintf(sctx->proxy_user, sizeof(sctx->proxy_user) - 1, |
13363 | "'%s'@'%s'" , auth_user, |
13364 | safe_str(acl_user->host.hostname)); |
13365 | |
13366 | /* we're proxying : find the proxy user definition */ |
13367 | mysql_mutex_lock(&acl_cache->lock); |
13368 | acl_proxy_user= find_user_exact(safe_str(proxy_user->get_proxied_host()), |
13369 | mpvio.auth_info.authenticated_as); |
13370 | if (!acl_proxy_user) |
13371 | { |
13372 | mysql_mutex_unlock(&acl_cache->lock); |
13373 | |
13374 | Host_errors errors; |
13375 | errors.m_proxy_user_acl= 1; |
13376 | inc_host_errors(mpvio.auth_info.thd->security_ctx->ip, &errors); |
13377 | if (!thd->is_error()) |
13378 | login_failed_error(thd); |
13379 | DBUG_RETURN(1); |
13380 | } |
13381 | acl_user= acl_proxy_user->copy(thd->mem_root); |
13382 | mysql_mutex_unlock(&acl_cache->lock); |
13383 | } |
13384 | #endif |
13385 | |
13386 | sctx->master_access= acl_user->access; |
13387 | if (acl_user->user.str) |
13388 | strmake_buf(sctx->priv_user, acl_user->user.str); |
13389 | else |
13390 | *sctx->priv_user= 0; |
13391 | |
13392 | if (acl_user->host.hostname) |
13393 | strmake_buf(sctx->priv_host, acl_user->host.hostname); |
13394 | else |
13395 | *sctx->priv_host= 0; |
13396 | |
13397 | /* |
13398 | OK. Let's check the SSL. Historically it was checked after the password, |
13399 | as an additional layer, not instead of the password |
13400 | (in which case it would've been a plugin too). |
13401 | */ |
13402 | if (acl_check_ssl(thd, acl_user)) |
13403 | { |
13404 | Host_errors errors; |
13405 | errors.m_ssl= 1; |
13406 | inc_host_errors(mpvio.auth_info.thd->security_ctx->ip, &errors); |
13407 | login_failed_error(thd); |
13408 | DBUG_RETURN(1); |
13409 | } |
13410 | |
13411 | /* |
13412 | Don't allow the user to connect if he has done too many queries. |
13413 | As we are testing max_user_connections == 0 here, it means that we |
13414 | can't let the user change max_user_connections from 0 in the server |
13415 | without a restart as it would lead to wrong connect counting. |
13416 | */ |
13417 | if ((acl_user->user_resource.questions || |
13418 | acl_user->user_resource.updates || |
13419 | acl_user->user_resource.conn_per_hour || |
13420 | acl_user->user_resource.user_conn || |
13421 | acl_user->user_resource.max_statement_time != 0.0 || |
13422 | max_user_connections_checking) && |
13423 | get_or_create_user_conn(thd, |
13424 | (opt_old_style_user_limits ? sctx->user : sctx->priv_user), |
13425 | (opt_old_style_user_limits ? sctx->host_or_ip : sctx->priv_host), |
13426 | &acl_user->user_resource)) |
13427 | DBUG_RETURN(1); // The error is set by get_or_create_user_conn() |
13428 | |
13429 | if (acl_user->user_resource.max_statement_time != 0.0) |
13430 | { |
13431 | thd->variables.max_statement_time_double= |
13432 | acl_user->user_resource.max_statement_time; |
13433 | thd->variables.max_statement_time= |
13434 | (ulonglong) (thd->variables.max_statement_time_double * 1e6 + 0.1); |
13435 | } |
13436 | } |
13437 | else |
13438 | sctx->skip_grants(); |
13439 | |
13440 | if (thd->user_connect && |
13441 | (thd->user_connect->user_resources.conn_per_hour || |
13442 | thd->user_connect->user_resources.user_conn || |
13443 | max_user_connections_checking) && |
13444 | check_for_max_user_connections(thd, thd->user_connect)) |
13445 | { |
13446 | /* Ensure we don't decrement thd->user_connections->connections twice */ |
13447 | thd->user_connect= 0; |
13448 | status_var_increment(denied_connections); |
13449 | DBUG_RETURN(1); // The error is set in check_for_max_user_connections() |
13450 | } |
13451 | |
13452 | DBUG_PRINT("info" , |
13453 | ("Capabilities: %llu packet_length: %ld Host: '%s' " |
13454 | "Login user: '%s' Priv_user: '%s' Using password: %s " |
13455 | "Access: %lu db: '%s'" , |
13456 | thd->client_capabilities, thd->max_client_packet_length, |
13457 | sctx->host_or_ip, sctx->user, sctx->priv_user, |
13458 | thd->password ? "yes" : "no" , |
13459 | sctx->master_access, mpvio.db.str)); |
13460 | |
13461 | if (command == COM_CONNECT && |
13462 | !(thd->main_security_ctx.master_access & SUPER_ACL)) |
13463 | { |
13464 | mysql_mutex_lock(&LOCK_connection_count); |
13465 | bool count_ok= (*thd->scheduler->connection_count <= |
13466 | *thd->scheduler->max_connections); |
13467 | mysql_mutex_unlock(&LOCK_connection_count); |
13468 | if (!count_ok) |
13469 | { // too many connections |
13470 | my_error(ER_CON_COUNT_ERROR, MYF(0)); |
13471 | DBUG_RETURN(1); |
13472 | } |
13473 | } |
13474 | |
13475 | /* |
13476 | This is the default access rights for the current database. It's |
13477 | set to 0 here because we don't have an active database yet (and we |
13478 | may not have an active database to set. |
13479 | */ |
13480 | sctx->db_access=0; |
13481 | |
13482 | #ifndef NO_EMBEDDED_ACCESS_CHECKS |
13483 | /* |
13484 | In case the user has a default role set, attempt to set that role |
13485 | */ |
13486 | if (initialized && acl_user->default_rolename.length) { |
13487 | ulonglong access= 0; |
13488 | int result; |
13489 | result= acl_check_setrole(thd, acl_user->default_rolename.str, &access); |
13490 | if (!result) |
13491 | result= acl_setrole(thd, acl_user->default_rolename.str, access); |
13492 | if (result) |
13493 | thd->clear_error(); // even if the default role was not granted, do not |
13494 | // close the connection |
13495 | } |
13496 | #endif |
13497 | |
13498 | /* Change a database if necessary */ |
13499 | if (mpvio.db.length) |
13500 | { |
13501 | if (mysql_change_db(thd, &mpvio.db, FALSE)) |
13502 | { |
13503 | /* mysql_change_db() has pushed the error message. */ |
13504 | status_var_increment(thd->status_var.access_denied_errors); |
13505 | DBUG_RETURN(1); |
13506 | } |
13507 | } |
13508 | |
13509 | thd->net.net_skip_rest_factor= 2; // skip at most 2*max_packet_size |
13510 | |
13511 | if (mpvio.auth_info.external_user[0]) |
13512 | sctx->external_user= my_strdup(mpvio.auth_info.external_user, MYF(0)); |
13513 | |
13514 | if (res == CR_OK_HANDSHAKE_COMPLETE) |
13515 | thd->get_stmt_da()->disable_status(); |
13516 | else |
13517 | my_ok(thd); |
13518 | |
13519 | PSI_CALL_set_thread_user_host |
13520 | (thd->main_security_ctx.user, (uint)strlen(thd->main_security_ctx.user), |
13521 | thd->main_security_ctx.host_or_ip, (uint)strlen(thd->main_security_ctx.host_or_ip)); |
13522 | |
13523 | /* Ready to handle queries */ |
13524 | DBUG_RETURN(0); |
13525 | } |
13526 | |
13527 | /** |
13528 | MySQL Server Password Authentication Plugin |
13529 | |
13530 | In the MySQL authentication protocol: |
13531 | 1. the server sends the random scramble to the client |
13532 | 2. client sends the encrypted password back to the server |
13533 | 3. the server checks the password. |
13534 | */ |
13535 | static int native_password_authenticate(MYSQL_PLUGIN_VIO *vio, |
13536 | MYSQL_SERVER_AUTH_INFO *info) |
13537 | { |
13538 | uchar *pkt; |
13539 | int pkt_len; |
13540 | MPVIO_EXT *mpvio= (MPVIO_EXT *) vio; |
13541 | THD *thd=info->thd; |
13542 | DBUG_ENTER("native_password_authenticate" ); |
13543 | |
13544 | /* generate the scramble, or reuse the old one */ |
13545 | if (thd->scramble[SCRAMBLE_LENGTH]) |
13546 | { |
13547 | thd_create_random_password(thd, thd->scramble, SCRAMBLE_LENGTH); |
13548 | /* and send it to the client */ |
13549 | if (mpvio->write_packet(mpvio, (uchar*)thd->scramble, SCRAMBLE_LENGTH + 1)) |
13550 | DBUG_RETURN(CR_AUTH_HANDSHAKE); |
13551 | } |
13552 | |
13553 | /* reply and authenticate */ |
13554 | |
13555 | /* |
13556 | <digression> |
13557 | This is more complex than it looks. |
13558 | |
13559 | The plugin (we) may be called right after the client was connected - |
13560 | and will need to send a scramble, read reply, authenticate. |
13561 | |
13562 | Or the plugin may be called after another plugin has sent a scramble, |
13563 | and read the reply. If the client has used the correct client-plugin, |
13564 | we won't need to read anything here from the client, the client |
13565 | has already sent a reply with everything we need for authentication. |
13566 | |
13567 | Or the plugin may be called after another plugin has sent a scramble, |
13568 | and read the reply, but the client has used the wrong client-plugin. |
13569 | We'll need to sent a "switch to another plugin" packet to the |
13570 | client and read the reply. "Use the short scramble" packet is a special |
13571 | case of "switch to another plugin" packet. |
13572 | |
13573 | Or, perhaps, the plugin may be called after another plugin has |
13574 | done the handshake but did not send a useful scramble. We'll need |
13575 | to send a scramble (and perhaps a "switch to another plugin" packet) |
13576 | and read the reply. |
13577 | |
13578 | Besides, a client may be an old one, that doesn't understand plugins. |
13579 | Or doesn't even understand 4.0 scramble. |
13580 | |
13581 | And we want to keep the same protocol on the wire unless non-native |
13582 | plugins are involved. |
13583 | |
13584 | Anyway, it still looks simple from a plugin point of view: |
13585 | "send the scramble, read the reply and authenticate". |
13586 | All the magic is transparently handled by the server. |
13587 | </digression> |
13588 | */ |
13589 | |
13590 | /* read the reply with the encrypted password */ |
13591 | if ((pkt_len= mpvio->read_packet(mpvio, &pkt)) < 0) |
13592 | DBUG_RETURN(CR_AUTH_HANDSHAKE); |
13593 | DBUG_PRINT("info" , ("reply read : pkt_len=%d" , pkt_len)); |
13594 | |
13595 | #ifdef NO_EMBEDDED_ACCESS_CHECKS |
13596 | DBUG_RETURN(CR_OK); |
13597 | #endif |
13598 | |
13599 | DBUG_EXECUTE_IF("native_password_bad_reply" , { pkt_len= 12; }); |
13600 | |
13601 | if (pkt_len == 0) /* no password */ |
13602 | DBUG_RETURN(mpvio->acl_user->salt_len != 0 ? CR_AUTH_USER_CREDENTIALS : CR_OK); |
13603 | |
13604 | info->password_used= PASSWORD_USED_YES; |
13605 | if (pkt_len == SCRAMBLE_LENGTH) |
13606 | { |
13607 | if (!mpvio->acl_user->salt_len) |
13608 | DBUG_RETURN(CR_AUTH_USER_CREDENTIALS); |
13609 | |
13610 | if (check_scramble(pkt, thd->scramble, mpvio->acl_user->salt)) |
13611 | DBUG_RETURN(CR_AUTH_USER_CREDENTIALS); |
13612 | else |
13613 | DBUG_RETURN(CR_OK); |
13614 | } |
13615 | |
13616 | my_error(ER_HANDSHAKE_ERROR, MYF(0)); |
13617 | DBUG_RETURN(CR_AUTH_HANDSHAKE); |
13618 | } |
13619 | |
13620 | static int old_password_authenticate(MYSQL_PLUGIN_VIO *vio, |
13621 | MYSQL_SERVER_AUTH_INFO *info) |
13622 | { |
13623 | uchar *pkt; |
13624 | int pkt_len; |
13625 | MPVIO_EXT *mpvio= (MPVIO_EXT *) vio; |
13626 | THD *thd=info->thd; |
13627 | |
13628 | /* generate the scramble, or reuse the old one */ |
13629 | if (thd->scramble[SCRAMBLE_LENGTH]) |
13630 | { |
13631 | thd_create_random_password(thd, thd->scramble, SCRAMBLE_LENGTH); |
13632 | /* and send it to the client */ |
13633 | if (mpvio->write_packet(mpvio, (uchar*)thd->scramble, SCRAMBLE_LENGTH + 1)) |
13634 | return CR_AUTH_HANDSHAKE; |
13635 | } |
13636 | |
13637 | /* read the reply and authenticate */ |
13638 | if ((pkt_len= mpvio->read_packet(mpvio, &pkt)) < 0) |
13639 | return CR_AUTH_HANDSHAKE; |
13640 | |
13641 | #ifdef NO_EMBEDDED_ACCESS_CHECKS |
13642 | return CR_OK; |
13643 | #endif |
13644 | |
13645 | /* |
13646 | legacy: if switch_from_long_to_short_scramble, |
13647 | the password is sent \0-terminated, the pkt_len is always 9 bytes. |
13648 | We need to figure out the correct scramble length here. |
13649 | */ |
13650 | if (pkt_len == SCRAMBLE_LENGTH_323 + 1) |
13651 | pkt_len= (int)strnlen((char*)pkt, pkt_len); |
13652 | |
13653 | if (pkt_len == 0) /* no password */ |
13654 | return info->auth_string[0] ? CR_AUTH_USER_CREDENTIALS : CR_OK; |
13655 | |
13656 | if (secure_auth(thd)) |
13657 | return CR_AUTH_HANDSHAKE; |
13658 | |
13659 | info->password_used= PASSWORD_USED_YES; |
13660 | |
13661 | if (pkt_len == SCRAMBLE_LENGTH_323) |
13662 | { |
13663 | if (!mpvio->acl_user->salt_len) |
13664 | return CR_AUTH_USER_CREDENTIALS; |
13665 | |
13666 | return check_scramble_323(pkt, thd->scramble, |
13667 | (ulong *) mpvio->acl_user->salt) ? |
13668 | CR_AUTH_USER_CREDENTIALS : CR_OK; |
13669 | } |
13670 | |
13671 | my_error(ER_HANDSHAKE_ERROR, MYF(0)); |
13672 | return CR_AUTH_HANDSHAKE; |
13673 | } |
13674 | |
13675 | static struct st_mysql_auth native_password_handler= |
13676 | { |
13677 | MYSQL_AUTHENTICATION_INTERFACE_VERSION, |
13678 | native_password_plugin_name.str, |
13679 | native_password_authenticate |
13680 | }; |
13681 | |
13682 | static struct st_mysql_auth old_password_handler= |
13683 | { |
13684 | MYSQL_AUTHENTICATION_INTERFACE_VERSION, |
13685 | old_password_plugin_name.str, |
13686 | old_password_authenticate |
13687 | }; |
13688 | |
13689 | maria_declare_plugin(mysql_password) |
13690 | { |
13691 | MYSQL_AUTHENTICATION_PLUGIN, /* type constant */ |
13692 | &native_password_handler, /* type descriptor */ |
13693 | native_password_plugin_name.str, /* Name */ |
13694 | "R.J.Silk, Sergei Golubchik" , /* Author */ |
13695 | "Native MySQL authentication" , /* Description */ |
13696 | PLUGIN_LICENSE_GPL, /* License */ |
13697 | NULL, /* Init function */ |
13698 | NULL, /* Deinit function */ |
13699 | 0x0100, /* Version (1.0) */ |
13700 | NULL, /* status variables */ |
13701 | NULL, /* system variables */ |
13702 | "1.0" , /* String version */ |
13703 | MariaDB_PLUGIN_MATURITY_STABLE /* Maturity */ |
13704 | }, |
13705 | { |
13706 | MYSQL_AUTHENTICATION_PLUGIN, /* type constant */ |
13707 | &old_password_handler, /* type descriptor */ |
13708 | old_password_plugin_name.str, /* Name */ |
13709 | "R.J.Silk, Sergei Golubchik" , /* Author */ |
13710 | "Old MySQL-4.0 authentication" , /* Description */ |
13711 | PLUGIN_LICENSE_GPL, /* License */ |
13712 | NULL, /* Init function */ |
13713 | NULL, /* Deinit function */ |
13714 | 0x0100, /* Version (1.0) */ |
13715 | NULL, /* status variables */ |
13716 | NULL, /* system variables */ |
13717 | "1.0" , /* String version */ |
13718 | MariaDB_PLUGIN_MATURITY_STABLE /* Maturity */ |
13719 | } |
13720 | maria_declare_plugin_end; |
13721 | |