diff options
Diffstat (limited to 'sql/sql_acl.cc')
-rw-r--r-- | sql/sql_acl.cc | 3496 |
1 files changed, 2850 insertions, 646 deletions
diff --git a/sql/sql_acl.cc b/sql/sql_acl.cc index 6cbe6554235..50cee6eeca4 100644 --- a/sql/sql_acl.cc +++ b/sql/sql_acl.cc @@ -17,8 +17,8 @@ /* The privileges are saved in the following tables: - mysql/user ; super user who are allowed to do almoust anything - mysql/host ; host priviliges. This is used if host is empty in mysql/db. + mysql/user ; super user who are allowed to do almost anything + mysql/host ; host privileges. This is used if host is empty in mysql/db. mysql/db ; database privileges / user data in tables is sorted according to how many not-wild-cards there is @@ -32,6 +32,8 @@ #endif #include <m_ctype.h> #include <stdarg.h> +#include "sp_head.h" +#include "sp.h" #ifndef NO_EMBEDDED_ACCESS_CHECKS @@ -58,15 +60,15 @@ static DYNAMIC_ARRAY acl_hosts,acl_users,acl_dbs; static MEM_ROOT mem, memex; static bool initialized=0; static bool allow_all_hosts=1; -static HASH acl_check_hosts, column_priv_hash; +static HASH acl_check_hosts, column_priv_hash, proc_priv_hash, func_priv_hash; static DYNAMIC_ARRAY acl_wild_hosts; static hash_filo *acl_cache; -static uint grant_version=0; -static uint priv_version=0; /* Version of priv tables. incremented by acl_load */ +static uint grant_version=0; /* Version of priv tables. incremented by acl_load */ static ulong get_access(TABLE *form,uint fieldnr, uint *next_field=0); static int acl_compare(ACL_ACCESS *a,ACL_ACCESS *b); static ulong get_sort(uint count,...); static void init_check_host(void); +static void rebuild_check_host(void); static ACL_USER *find_acl_user(const char *host, const char *user, my_bool exact); static bool update_user_table(THD *thd, TABLE *table, @@ -159,6 +161,7 @@ my_bool acl_init(bool dont_read_acl_tables) */ if (!(thd=new THD)) DBUG_RETURN(1); /* purecov: inspected */ + thd->thread_stack= (char*) &thd; thd->store_globals(); /* It is safe to call acl_reload() since acl_* arrays and hashes which @@ -198,7 +201,7 @@ static my_bool acl_load(THD *thd, TABLE_LIST *tables) int password_length; DBUG_ENTER("acl_load"); - priv_version++; /* Privileges updated */ + grant_version++; /* Privileges updated */ acl_cache->clear(1); // Clear locked hostname cache @@ -213,19 +216,18 @@ static my_bool acl_load(THD *thd, TABLE_LIST *tables) if (lower_case_table_names && host.db) { /* - We make a temporary copy of the database, force it to lower case, - and then check it against the original name. + convert db to lower case and give a warning if the db wasn't + already in lower case */ - (void)strnmov(tmp_name, host.db, sizeof(tmp_name)); + (void) strmov(tmp_name, host.db); my_casedn_str(files_charset_info, host.db); if (strcmp(host.db, tmp_name) != 0) - { sql_print_warning("'host' entry '%s|%s' had database in mixed " "case that has been forced to lowercase because " "lower_case_table_names is set. It will not be " "possible to remove this privilege using REVOKE.", - host.host.hostname, host.db); - } + host.host.hostname ? host.host.hostname : "", + host.db ? host.db : ""); } host.access= get_access(table,2); host.access= fix_rights_for_db(host.access); @@ -234,11 +236,12 @@ static my_bool acl_load(THD *thd, TABLE_LIST *tables) { sql_print_warning("'host' entry '%s|%s' " "ignored in --skip-name-resolve mode.", - host.host.hostname, host.db?host.db:""); + host.host.hostname ? host.host.hostname : "", + host.db ? host.db : ""); continue; } #ifndef TO_BE_REMOVED - if (table->fields == 8) + if (table->s->fields == 8) { // Without grant if (host.access & CREATE_ACL) host.access|=REFERENCES_ACL | INDEX_ACL | ALTER_ACL | CREATE_TMP_ACL; @@ -263,8 +266,8 @@ static my_bool acl_load(THD *thd, TABLE_LIST *tables) } DBUG_PRINT("info",("user table fields: %d, password length: %d", - table->fields, password_length)); - + table->s->fields, password_length)); + pthread_mutex_lock(&LOCK_global_system_variables); if (password_length < SCRAMBLED_PASSWORD_CHAR_LENGTH) { @@ -304,7 +307,8 @@ static my_bool acl_load(THD *thd, TABLE_LIST *tables) { sql_print_warning("'user' entry '%s@%s' " "ignored in --skip-name-resolve mode.", - user.user, user.host.hostname); + user.user ? user.user : "", + user.host.hostname ? user.host.hostname : ""); continue; } @@ -332,10 +336,34 @@ static my_bool acl_load(THD *thd, TABLE_LIST *tables) { uint next_field; user.access= get_access(table,3,&next_field) & GLOBAL_ACLS; + /* + if it is pre 5.0.1 privilege table then map CREATE privilege on + CREATE VIEW & SHOW VIEW privileges + */ + if (table->s->fields <= 31 && (user.access & CREATE_ACL)) + user.access|= (CREATE_VIEW_ACL | SHOW_VIEW_ACL); + + /* + if it is pre 5.0.2 privilege table then map CREATE/ALTER privilege on + CREATE PROCEDURE & ALTER PROCEDURE privileges + */ + if (table->s->fields <= 33 && (user.access & CREATE_ACL)) + user.access|= CREATE_PROC_ACL; + if (table->s->fields <= 33 && (user.access & ALTER_ACL)) + user.access|= ALTER_PROC_ACL; + + /* + pre 5.0.3 did not have CREATE_USER_ACL + */ + if (table->s->fields <= 36 && (user.access & GRANT_ACL)) + user.access|= CREATE_USER_ACL; + user.sort= get_sort(2,user.host.hostname,user.user); user.hostname_length= (user.host.hostname ? (uint) strlen(user.host.hostname) : 0); - if (table->fields >= 31) /* Starting from 4.0.2 we have more fields */ + + /* Starting from 4.0.2 we have more fields */ + if (table->s->fields >= 31) { char *ssl_type=get_field(&mem, table->field[next_field++]); if (!ssl_type) @@ -356,17 +384,26 @@ static my_bool acl_load(THD *thd, TABLE_LIST *tables) ptr = get_field(&mem, table->field[next_field++]); user.user_resource.updates=ptr ? atoi(ptr) : 0; ptr = get_field(&mem, table->field[next_field++]); - user.user_resource.connections=ptr ? atoi(ptr) : 0; + user.user_resource.conn_per_hour= ptr ? atoi(ptr) : 0; if (user.user_resource.questions || user.user_resource.updates || - user.user_resource.connections) + user.user_resource.conn_per_hour) mqh_used=1; + + if (table->s->fields >= 36) + { + /* Starting from 5.0.3 we have max_user_connections field */ + ptr= get_field(&mem, table->field[next_field++]); + user.user_resource.user_conn= ptr ? atoi(ptr) : 0; + } + else + user.user_resource.user_conn= 0; } else { user.ssl_type=SSL_TYPE_NONE; bzero((char *)&(user.user_resource),sizeof(user.user_resource)); #ifndef TO_BE_REMOVED - if (table->fields <= 13) + if (table->s->fields <= 13) { // Without grant if (user.access & CREATE_ACL) user.access|=REFERENCES_ACL | INDEX_ACL | ALTER_ACL; @@ -380,8 +417,8 @@ static my_bool acl_load(THD *thd, TABLE_LIST *tables) #endif } VOID(push_dynamic(&acl_users,(gptr) &user)); - if (!user.host.hostname || user.host.hostname[0] == wild_many && - !user.host.hostname[1]) + if (!user.host.hostname || + (user.host.hostname[0] == wild_many && !user.host.hostname[1])) allow_all_hosts=1; // Anyone can connect } } @@ -407,7 +444,9 @@ static my_bool acl_load(THD *thd, TABLE_LIST *tables) { sql_print_warning("'db' entry '%s %s@%s' " "ignored in --skip-name-resolve mode.", - db.db, db.user, db.host.hostname); + db.db, + db.user ? db.user : "", + db.host.hostname ? db.host.hostname : ""); continue; } db.access=get_access(table,3); @@ -415,10 +454,10 @@ static my_bool acl_load(THD *thd, TABLE_LIST *tables) if (lower_case_table_names) { /* - We make a temporary copy of the database, force it to lower case, - and then check it against the original name. + convert db to lower case and give a warning if the db wasn't + already in lower case */ - (void)strnmov(tmp_name, db.db, sizeof(tmp_name)); + (void)strmov(tmp_name, db.db); my_casedn_str(files_charset_info, db.db); if (strcmp(db.db, tmp_name) != 0) { @@ -426,12 +465,14 @@ static my_bool acl_load(THD *thd, TABLE_LIST *tables) "case that has been forced to lowercase because " "lower_case_table_names is set. It will not be " "possible to remove this privilege using REVOKE.", - db.db, db.user, db.host.hostname); + db.db, + db.user ? db.user : "", + db.host.hostname ? db.host.hostname : ""); } } db.sort=get_sort(3,db.host.hostname,db.db,db.user); #ifndef TO_BE_REMOVED - if (table->fields <= 9) + if (table->s->fields <= 9) { // Without grant if (db.access & CREATE_ACL) db.access|=REFERENCES_ACL | INDEX_ACL | ALTER_ACL; @@ -511,12 +552,12 @@ my_bool acl_reload(THD *thd) obtaining acl_cache->lock mutex. */ bzero((char*) tables, sizeof(tables)); - tables[0].alias=tables[0].real_name=(char*) "host"; - tables[1].alias=tables[1].real_name=(char*) "user"; - tables[2].alias=tables[2].real_name=(char*) "db"; - tables[0].db=tables[1].db=tables[2].db= (char*) "mysql"; - tables[0].next= tables+1; - tables[1].next= tables+2; + tables[0].alias= tables[0].table_name= (char*) "host"; + tables[1].alias= tables[1].table_name= (char*) "user"; + tables[2].alias= tables[2].table_name= (char*) "db"; + tables[0].db=tables[1].db=tables[2].db=(char*) "mysql"; + tables[0].next_local= tables[0].next_global= tables+1; + tables[1].next_local= tables[1].next_global= tables+2; tables[0].lock_type=tables[1].lock_type=tables[2].lock_type=TL_READ; if (simple_open_n_lock_tables(thd, tables)) @@ -565,8 +606,8 @@ end: Get all access bits from table after fieldnr IMPLEMENTATION - We know that the access privileges ends when there is no more fields - or the field is not an enum with two elements. + We know that the access privileges ends when there is no more fields + or the field is not an enum with two elements. SYNOPSIS get_access() @@ -665,11 +706,11 @@ static int acl_compare(ACL_ACCESS *a,ACL_ACCESS *b) SYNOPSIS acl_getroot() thd thread handle. If all checks are OK, - thd->priv_user, thd->master_access are updated. - thd->host, thd->ip, thd->user are used for checks. + thd->security_ctx->priv_user/master_access are updated. + thd->security_ctx->host/ip/user are used for checks. mqh user resources; on success mqh is reset, else unchanged - passwd scrambled & crypted password, recieved from client + passwd scrambled & crypted password, received from client (to check): thd->scramble or thd->scramble_323 is used to decrypt passwd, so they must contain original random string, @@ -680,7 +721,7 @@ static int acl_compare(ACL_ACCESS *a,ACL_ACCESS *b) RETURN VALUE 0 success: thd->priv_user, thd->priv_host, thd->master_access, mqh are updated - 1 user not found or authentification failure + 1 user not found or authentication failure 2 user found, has long (4.1.1) salt, but passwd is in old (3.23) format. -1 user found, has short (3.23) salt, but passwd is in new (4.1.1) format. */ @@ -691,6 +732,7 @@ int acl_getroot(THD *thd, USER_RESOURCES *mqh, ulong user_access= NO_ACCESS; int res= 1; ACL_USER *acl_user= 0; + Security_context *sctx= thd->security_ctx; DBUG_ENTER("acl_getroot"); if (!initialized) @@ -698,9 +740,7 @@ int acl_getroot(THD *thd, USER_RESOURCES *mqh, /* here if mysqld's been started with --skip-grant-tables option. */ - thd->priv_user= (char *) ""; // privileges for - *thd->priv_host= '\0'; // the user are unknown - thd->master_access= ~NO_ACCESS; // everything is allowed + sctx->skip_grants(); bzero((char*) mqh, sizeof(*mqh)); DBUG_RETURN(0); } @@ -716,9 +756,9 @@ int acl_getroot(THD *thd, USER_RESOURCES *mqh, for (uint i=0 ; i < acl_users.elements ; i++) { ACL_USER *acl_user_tmp= dynamic_element(&acl_users,i,ACL_USER*); - if (!acl_user_tmp->user || !strcmp(thd->user, acl_user_tmp->user)) + if (!acl_user_tmp->user || !strcmp(sctx->user, acl_user_tmp->user)) { - if (compare_hostname(&acl_user_tmp->host, thd->host, thd->ip)) + if (compare_hostname(&acl_user_tmp->host, sctx->host, sctx->ip)) { /* check password: it should be empty or valid */ if (passwd_len == acl_user_tmp->salt_len) @@ -820,12 +860,12 @@ int acl_getroot(THD *thd, USER_RESOURCES *mqh, break; } DBUG_PRINT("info",("checkpoint 2")); - /* If X509 issuer is speified, we check it... */ + /* If X509 issuer is specified, we check it... */ if (acl_user->x509_issuer) { DBUG_PRINT("info",("checkpoint 3")); - char *ptr = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0); - DBUG_PRINT("info",("comparing issuers: '%s' and '%s'", + char *ptr = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0); + DBUG_PRINT("info",("comparing issuers: '%s' and '%s'", acl_user->x509_issuer, ptr)); if (strcmp(acl_user->x509_issuer, ptr)) { @@ -833,6 +873,7 @@ int acl_getroot(THD *thd, USER_RESOURCES *mqh, sql_print_information("X509 issuer mismatch: should be '%s' " "but is '%s'", acl_user->x509_issuer, ptr); free(ptr); + user_access=NO_ACCESS; break; } user_access= acl_user->access; @@ -848,11 +889,13 @@ int acl_getroot(THD *thd, USER_RESOURCES *mqh, if (strcmp(acl_user->x509_subject,ptr)) { if (global_system_variables.log_warnings) - sql_print_information("X509 subject mismatch: '%s' vs '%s'", + sql_print_information("X509 subject mismatch: should be '%s' but is '%s'", acl_user->x509_subject, ptr); + free(ptr); + user_access=NO_ACCESS; + break; } - else - user_access= acl_user->access; + user_access= acl_user->access; free(ptr); } break; @@ -865,20 +908,119 @@ int acl_getroot(THD *thd, USER_RESOURCES *mqh, break; #endif /* HAVE_OPENSSL */ } - thd->master_access= user_access; - thd->priv_user= acl_user->user ? thd->user : (char *) ""; + sctx->master_access= user_access; + sctx->priv_user= acl_user->user ? sctx->user : (char *) ""; *mqh= acl_user->user_resource; if (acl_user->host.hostname) - strmake(thd->priv_host, acl_user->host.hostname, MAX_HOSTNAME); + strmake(sctx->priv_host, acl_user->host.hostname, MAX_HOSTNAME); else - *thd->priv_host= 0; + *sctx->priv_host= 0; } VOID(pthread_mutex_unlock(&acl_cache->lock)); DBUG_RETURN(res); } +/* + This is like acl_getroot() above, but it doesn't check password, + and we don't care about the user resources. + + SYNOPSIS + acl_getroot_no_password() + sctx Context which should be initialized + user user name + host host name + ip IP + db current data base name + + RETURN + FALSE OK + TRUE Error +*/ + +bool acl_getroot_no_password(Security_context *sctx, char *user, char *host, + char *ip, char *db) +{ + int res= 1; + uint i; + ACL_USER *acl_user= 0; + DBUG_ENTER("acl_getroot_no_password"); + + DBUG_PRINT("enter", ("Host: '%s', Ip: '%s', User: '%s', db: '%s'", + (host ? host : "(NULL)"), (ip ? ip : "(NULL)"), + user, (db ? db : "(NULL)"))); + sctx->user= user; + sctx->host= host; + sctx->ip= ip; + sctx->host_or_ip= host ? host : (ip ? ip : ""); + + if (!initialized) + { + /* + here if mysqld's been started with --skip-grant-tables option. + */ + sctx->skip_grants(); + DBUG_RETURN(FALSE); + } + + VOID(pthread_mutex_lock(&acl_cache->lock)); + + sctx->master_access= 0; + sctx->db_access= 0; + sctx->priv_user= (char *) ""; + *sctx->priv_host= 0; + + /* + Find acl entry in user database. + This is specially tailored to suit the check we do for CALL of + a stored procedure; user is set to what is actually a + priv_user, which can be ''. + */ + for (i=0 ; i < acl_users.elements ; i++) + { + acl_user= dynamic_element(&acl_users,i,ACL_USER*); + if ((!acl_user->user && !user[0]) || + (acl_user->user && strcmp(user, acl_user->user) == 0)) + { + if (compare_hostname(&acl_user->host, host, ip)) + { + res= 0; + break; + } + } + } + + if (acl_user) + { + for (i=0 ; i < acl_dbs.elements ; i++) + { + ACL_DB *acl_db= dynamic_element(&acl_dbs, i, ACL_DB*); + if (!acl_db->user || + (user && user[0] && !strcmp(user, acl_db->user))) + { + if (compare_hostname(&acl_db->host, host, ip)) + { + if (!acl_db->db || (db && !wild_compare(db, acl_db->db, 0))) + { + sctx->db_access= acl_db->access; + break; + } + } + } + } + sctx->master_access= acl_user->access; + sctx->priv_user= acl_user->user ? user : (char *) ""; + + if (acl_user->host.hostname) + strmake(sctx->priv_host, acl_user->host.hostname, MAX_HOSTNAME); + else + *sctx->priv_host= 0; + } + VOID(pthread_mutex_unlock(&acl_cache->lock)); + DBUG_RETURN(res); +} + static byte* check_get_key(ACL_USER *buff,uint *length, my_bool not_used __attribute__((unused))) { @@ -902,20 +1044,21 @@ static void acl_update_user(const char *user, const char *host, { ACL_USER *acl_user=dynamic_element(&acl_users,i,ACL_USER*); if (!acl_user->user && !user[0] || - acl_user->user && - !strcmp(user,acl_user->user)) + acl_user->user && !strcmp(user,acl_user->user)) { if (!acl_user->host.hostname && !host[0] || acl_user->host.hostname && !my_strcasecmp(system_charset_info, host, acl_user->host.hostname)) { acl_user->access=privileges; - if (mqh->bits & 1) + if (mqh->specified_limits & USER_RESOURCES::QUERIES_PER_HOUR) acl_user->user_resource.questions=mqh->questions; - if (mqh->bits & 2) + if (mqh->specified_limits & USER_RESOURCES::UPDATES_PER_HOUR) acl_user->user_resource.updates=mqh->updates; - if (mqh->bits & 4) - acl_user->user_resource.connections=mqh->connections; + if (mqh->specified_limits & USER_RESOURCES::CONNECTIONS_PER_HOUR) + acl_user->user_resource.conn_per_hour= mqh->conn_per_hour; + if (mqh->specified_limits & USER_RESOURCES::USER_CONNECTIONS) + acl_user->user_resource.user_conn= mqh->user_conn; if (ssl_type != SSL_TYPE_NOT_SPECIFIED) { acl_user->ssl_type= ssl_type; @@ -964,16 +1107,14 @@ static void acl_insert_user(const char *user, const char *host, set_user_salt(&acl_user, password, password_len); VOID(push_dynamic(&acl_users,(gptr) &acl_user)); - if (!acl_user.host.hostname || acl_user.host.hostname[0] == wild_many - && !acl_user.host.hostname[1]) + if (!acl_user.host.hostname || + (acl_user.host.hostname[0] == wild_many && !acl_user.host.hostname[1])) allow_all_hosts=1; // Anyone can connect /* purecov: tested */ qsort((gptr) dynamic_element(&acl_users,0,ACL_USER*),acl_users.elements, sizeof(ACL_USER),(qsort_cmp) acl_compare); - /* We must free acl_check_hosts as its memory is mapped to acl_user */ - delete_dynamic(&acl_wild_hosts); - hash_free(&acl_check_hosts); - init_check_host(); + /* Rebuild 'acl_check_hosts' since 'acl_users' has been modified */ + rebuild_check_host(); } @@ -1027,7 +1168,7 @@ static void acl_insert_db(const char *user, const char *host, const char *db, ACL_DB acl_db; safe_mutex_assert_owner(&acl_cache->lock); acl_db.user=strdup_root(&mem,user); - update_hostname(&acl_db.host,strdup_root(&mem,host)); + update_hostname(&acl_db.host, *host ? strdup_root(&mem,host) : 0); acl_db.db=strdup_root(&mem,db); acl_db.access=privileges; acl_db.sort=get_sort(3,acl_db.host.hostname,acl_db.db,acl_db.user); @@ -1048,7 +1189,7 @@ static void acl_insert_db(const char *user, const char *host, const char *db, ulong acl_get(const char *host, const char *ip, const char *user, const char *db, my_bool db_is_pattern) { - ulong host_access= ~(ulong)0,db_access= 0; + ulong host_access= ~(ulong)0, db_access= 0; uint i,key_length; char key[ACL_KEY_LENGTH],*tmp_db,*end; acl_entry *entry; @@ -1177,6 +1318,22 @@ static void init_check_host(void) } +/* + Rebuild lists used for checking of allowed hosts + + We need to rebuild 'acl_check_hosts' and 'acl_wild_hosts' after adding, + dropping or renaming user, since they contain pointers to elements of + 'acl_user' array, which are invalidated by drop operation, and use + ACL_USER::host::hostname as a key, which is changed by rename. +*/ +void rebuild_check_host(void) +{ + delete_dynamic(&acl_wild_hosts); + hash_free(&acl_check_hosts); + init_check_host(); +} + + /* Return true if there is no users that can match the given host */ bool acl_check_host(const char *host, const char *ip) @@ -1228,29 +1385,28 @@ bool check_change_password(THD *thd, const char *host, const char *user, { if (!initialized) { - net_printf(thd,ER_OPTION_PREVENTS_STATEMENT, - "--skip-grant-tables"); + my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--skip-grant-tables"); return(1); } if (!thd->slave_thread && - (strcmp(thd->user,user) || - my_strcasecmp(system_charset_info, host, thd->priv_host))) + (strcmp(thd->security_ctx->user, user) || + my_strcasecmp(system_charset_info, host, + thd->security_ctx->priv_host))) { - if (check_access(thd, UPDATE_ACL, "mysql",0,1,0)) + if (check_access(thd, UPDATE_ACL, "mysql",0,1,0,0)) return(1); } - if (!thd->slave_thread && !thd->user[0]) + if (!thd->slave_thread && !thd->security_ctx->user[0]) { - send_error(thd, ER_PASSWORD_ANONYMOUS_USER); + my_message(ER_PASSWORD_ANONYMOUS_USER, ER(ER_PASSWORD_ANONYMOUS_USER), + MYF(0)); return(1); } uint len=strlen(new_password); if (len && len != SCRAMBLED_PASSWORD_CHAR_LENGTH && len != SCRAMBLED_PASSWORD_CHAR_LENGTH_323) { - net_printf(thd, 0, - "Password hash should be a %d-digit hexadecimal number", - SCRAMBLED_PASSWORD_CHAR_LENGTH); + my_error(ER_PASSWD_LENGTH, MYF(0), SCRAMBLED_PASSWORD_CHAR_LENGTH); return -1; } return(0); @@ -1291,7 +1447,7 @@ bool change_password(THD *thd, const char *host, const char *user, DBUG_RETURN(1); bzero((char*) &tables, sizeof(tables)); - tables.alias=tables.real_name= (char*) "user"; + tables.alias= tables.table_name= (char*) "user"; tables.db= (char*) "mysql"; #ifdef HAVE_REPLICATION @@ -1307,7 +1463,7 @@ bool change_password(THD *thd, const char *host, const char *user, */ tables.updating= 1; /* Thanks to bzero, tables.next==0 */ - if (!tables_ok(0, &tables)) + if (!tables_ok(thd, &tables)) DBUG_RETURN(0); } #endif @@ -1320,7 +1476,7 @@ bool change_password(THD *thd, const char *host, const char *user, if (!(acl_user= find_acl_user(host, user, TRUE))) { VOID(pthread_mutex_unlock(&acl_cache->lock)); - send_error(thd, ER_PASSWORD_NO_MATCH); + my_message(ER_PASSWORD_NO_MATCH, ER(ER_PASSWORD_NO_MATCH), MYF(0)); goto end; } /* update loaded acl entry: */ @@ -1332,22 +1488,20 @@ bool change_password(THD *thd, const char *host, const char *user, new_password, new_password_len)) { VOID(pthread_mutex_unlock(&acl_cache->lock)); /* purecov: deadcode */ - send_error(thd,0); /* purecov: deadcode */ goto end; } acl_cache->clear(1); // Clear locked hostname cache VOID(pthread_mutex_unlock(&acl_cache->lock)); result= 0; - query_length= - my_sprintf(buff, - (buff,"SET PASSWORD FOR \"%-.120s\"@\"%-.120s\"=\"%-.120s\"", - acl_user->user ? acl_user->user : "", - acl_user->host.hostname ? acl_user->host.hostname : "", - new_password)); - mysql_update_log.write(thd, buff, query_length); if (mysql_bin_log.is_open()) { + query_length= + my_sprintf(buff, + (buff,"SET PASSWORD FOR \"%-.120s\"@\"%-.120s\"=\"%-.120s\"", + acl_user->user ? acl_user->user : "", + acl_user->host.hostname ? acl_user->host.hostname : "", + new_password)); thd->clear_error(); Query_log_event qinfo(thd, buff, query_length, 0, FALSE); mysql_bin_log.write(&qinfo); @@ -1359,6 +1513,34 @@ end: /* + Find user in ACL + + SYNOPSIS + is_acl_user() + host host name + user user name + + RETURN + FALSE user not fond + TRUE there are such user +*/ + +bool is_acl_user(const char *host, const char *user) +{ + bool res; + + /* --skip-grants */ + if (!initialized) + return TRUE; + + VOID(pthread_mutex_lock(&acl_cache->lock)); + res= find_acl_user(host, user, TRUE) != NULL; + VOID(pthread_mutex_unlock(&acl_cache->lock)); + return res; +} + + +/* Find first entry that matches the current user */ @@ -1374,11 +1556,10 @@ find_acl_user(const char *host, const char *user, my_bool exact) { ACL_USER *acl_user=dynamic_element(&acl_users,i,ACL_USER*); DBUG_PRINT("info",("strcmp('%s','%s'), compare_hostname('%s','%s'),", - user, - acl_user->user ? acl_user->user : "", - host, - acl_user->host.hostname ? acl_user->host.hostname : - "")); + user, acl_user->user ? acl_user->user : "", + host, + acl_user->host.hostname ? acl_user->host.hostname : + "")); if (!acl_user->user && !user[0] || acl_user->user && !strcmp(user,acl_user->user)) { @@ -1428,7 +1609,7 @@ static const char *calc_ip(const char *ip, long *val, char end) static void update_hostname(acl_host_and_ip *host, const char *hostname) { - host->hostname=(char*) hostname; // This will not be modified! + host->hostname=(char*) hostname; // This will not be modified! if (!hostname || (!(hostname=calc_ip(hostname,&host->ip,'/')) || !(hostname=calc_ip(hostname+1,&host->ip_mask,'\0')))) @@ -1448,8 +1629,8 @@ static bool compare_hostname(const acl_host_and_ip *host, const char *hostname, } return (!host->hostname || (hostname && !wild_case_compare(system_charset_info, - hostname,host->hostname)) || - (ip && !wild_compare(ip,host->hostname,0))); + hostname, host->hostname)) || + (ip && !wild_compare(ip, host->hostname, 0))); } bool hostname_requires_resolving(const char *hostname) @@ -1490,20 +1671,23 @@ static bool update_user_table(THD *thd, TABLE *table, const char *host, const char *user, const char *new_password, uint new_password_len) { + char user_key[MAX_KEY_LENGTH]; int error; DBUG_ENTER("update_user_table"); DBUG_PRINT("enter",("user: %s host: %s",user,host)); table->field[0]->store(host,(uint) strlen(host), system_charset_info); table->field[1]->store(user,(uint) strlen(user), system_charset_info); + key_copy((byte *) user_key, table->record[0], table->key_info, + table->key_info->key_length); table->file->extra(HA_EXTRA_RETRIEVE_ALL_COLS); - if (table->file->index_read_idx(table->record[0],0, - (byte*) table->field[0]->ptr, - table->key_info[0].key_length, + if (table->file->index_read_idx(table->record[0], 0, + (byte *) user_key, table->key_info->key_length, HA_READ_KEY_EXACT)) { - my_error(ER_PASSWORD_NO_MATCH,MYF(0)); /* purecov: deadcode */ + my_message(ER_PASSWORD_NO_MATCH, ER(ER_PASSWORD_NO_MATCH), + MYF(0)); /* purecov: deadcode */ DBUG_RETURN(1); /* purecov: deadcode */ } store_record(table,record[1]); @@ -1517,21 +1701,30 @@ static bool update_user_table(THD *thd, TABLE *table, } -/* Return 1 if we are allowed to create new users */ +/* + Return 1 if we are allowed to create new users + the logic here is: INSERT_ACL is sufficient. + It's also a requirement in opt_safe_user_create, + otherwise CREATE_USER_ACL is enough. +*/ static bool test_if_create_new_users(THD *thd) { - bool create_new_users=1; // Assume that we are allowed to create new users - if (opt_safe_user_create && !(thd->master_access & INSERT_ACL)) + Security_context *sctx= thd->security_ctx; + bool create_new_users= test(sctx->master_access & INSERT_ACL) || + (!opt_safe_user_create && + test(sctx->master_access & CREATE_USER_ACL)); + if (!create_new_users) { TABLE_LIST tl; ulong db_access; bzero((char*) &tl,sizeof(tl)); tl.db= (char*) "mysql"; - tl.real_name= (char*) "user"; + tl.table_name= (char*) "user"; + create_new_users= 1; - db_access=acl_get(thd->host, thd->ip, - thd->priv_user, tl.db, 0); + db_access=acl_get(sctx->host, sctx->ip, + sctx->priv_user, tl.db, 0); if (!(db_access & INSERT_ACL)) { if (check_grant(thd, INSERT_ACL, &tl, 0, UINT_MAX, 1)) @@ -1548,14 +1741,17 @@ static bool test_if_create_new_users(THD *thd) static int replace_user_table(THD *thd, TABLE *table, const LEX_USER &combo, ulong rights, bool revoke_grant, - bool create_user) + bool can_create_user, bool no_auto_create) { int error = -1; bool old_row_exists=0; const char *password= ""; uint password_len= 0; char what= (revoke_grant) ? 'N' : 'Y'; + byte user_key[MAX_KEY_LENGTH]; + LEX *lex= thd->lex; DBUG_ENTER("replace_user_table"); + safe_mutex_assert_owner(&acl_cache->lock); if (combo.password.str && combo.password.str[0]) @@ -1563,9 +1759,7 @@ static int replace_user_table(THD *thd, TABLE *table, const LEX_USER &combo, if (combo.password.length != SCRAMBLED_PASSWORD_CHAR_LENGTH && combo.password.length != SCRAMBLED_PASSWORD_CHAR_LENGTH_323) { - my_printf_error(ER_UNKNOWN_ERROR, - "Password hash should be a %d-digit hexadecimal number", - MYF(0), SCRAMBLED_PASSWORD_CHAR_LENGTH); + my_error(ER_PASSWD_LENGTH, MYF(0), SCRAMBLED_PASSWORD_CHAR_LENGTH); DBUG_RETURN(-1); } password_len= combo.password.length; @@ -1574,23 +1768,46 @@ static int replace_user_table(THD *thd, TABLE *table, const LEX_USER &combo, table->field[0]->store(combo.host.str,combo.host.length, system_charset_info); table->field[1]->store(combo.user.str,combo.user.length, system_charset_info); + key_copy(user_key, table->record[0], table->key_info, + table->key_info->key_length); + table->file->extra(HA_EXTRA_RETRIEVE_ALL_COLS); if (table->file->index_read_idx(table->record[0], 0, - (byte*) table->field[0]->ptr, - table->key_info[0].key_length, - HA_READ_KEY_EXACT)) + user_key, table->key_info->key_length, + HA_READ_KEY_EXACT)) { - if (!create_user) + /* what == 'N' means revoke */ + if (what == 'N') { - if (what == 'N') - my_error(ER_NONEXISTING_GRANT, MYF(0), combo.user.str, combo.host.str); - else - my_error(ER_NO_PERMISSION_TO_CREATE_USER, MYF(0), - thd->user, thd->host_or_ip); + my_error(ER_NONEXISTING_GRANT, MYF(0), combo.user.str, combo.host.str); + goto end; + } + /* + There are four options which affect the process of creation of + a new user (mysqld option --safe-create-user, 'insert' privilege + on 'mysql.user' table, using 'GRANT' with 'IDENTIFIED BY' and + SQL_MODE flag NO_AUTO_CREATE_USER). Below is the simplified rule + how it should work. + if (safe-user-create && ! INSERT_priv) => reject + else if (identified_by) => create + else if (no_auto_create_user) => reject + else create + + see also test_if_create_new_users() + */ + else if (!password_len && no_auto_create) + { + my_error(ER_PASSWORD_NO_MATCH, MYF(0), combo.user.str, combo.host.str); + goto end; + } + else if (!can_create_user) + { + my_error(ER_CANT_CREATE_USER_WITH_GRANT, MYF(0), + thd->security_ctx->user, thd->security_ctx->host_or_ip); goto end; } old_row_exists = 0; - restore_record(table,default_values); // cp empty row from default_values + restore_record(table,s->default_values); table->field[0]->store(combo.host.str,combo.host.length, system_charset_info); table->field[1]->store(combo.user.str,combo.user.length, @@ -1604,8 +1821,9 @@ static int replace_user_table(THD *thd, TABLE *table, const LEX_USER &combo, store_record(table,record[1]); // Save copy for update if (combo.password.str) // If password given table->field[2]->store(password, password_len, system_charset_info); - else if (!rights && !revoke_grant && thd->lex->ssl_type == SSL_TYPE_NOT_SPECIFIED && - !thd->lex->mqh.bits) + else if (!rights && !revoke_grant && + lex->ssl_type == SSL_TYPE_NOT_SPECIFIED && + !lex->mqh.specified_limits) { DBUG_RETURN(0); } @@ -1625,40 +1843,40 @@ static int replace_user_table(THD *thd, TABLE *table, const LEX_USER &combo, (*tmp_field)->store(&what, 1, &my_charset_latin1); } rights= get_access(table, 3, &next_field); - DBUG_PRINT("info",("table->fields: %d",table->fields)); - if (table->fields >= 31) /* From 4.0.0 we have more fields */ + DBUG_PRINT("info",("table fields: %d",table->s->fields)); + if (table->s->fields >= 31) /* From 4.0.0 we have more fields */ { /* We write down SSL related ACL stuff */ - switch (thd->lex->ssl_type) { + switch (lex->ssl_type) { case SSL_TYPE_ANY: - table->field[next_field]->store("ANY", 3, &my_charset_latin1); + table->field[next_field]->store(STRING_WITH_LEN("ANY"), + &my_charset_latin1); table->field[next_field+1]->store("", 0, &my_charset_latin1); table->field[next_field+2]->store("", 0, &my_charset_latin1); table->field[next_field+3]->store("", 0, &my_charset_latin1); break; case SSL_TYPE_X509: - table->field[next_field]->store("X509", 4, &my_charset_latin1); + table->field[next_field]->store(STRING_WITH_LEN("X509"), + &my_charset_latin1); table->field[next_field+1]->store("", 0, &my_charset_latin1); table->field[next_field+2]->store("", 0, &my_charset_latin1); table->field[next_field+3]->store("", 0, &my_charset_latin1); break; case SSL_TYPE_SPECIFIED: - table->field[next_field]->store("SPECIFIED", 9, &my_charset_latin1); + table->field[next_field]->store(STRING_WITH_LEN("SPECIFIED"), + &my_charset_latin1); table->field[next_field+1]->store("", 0, &my_charset_latin1); table->field[next_field+2]->store("", 0, &my_charset_latin1); table->field[next_field+3]->store("", 0, &my_charset_latin1); - if (thd->lex->ssl_cipher) - table->field[next_field+1]->store(thd->lex->ssl_cipher, - strlen(thd->lex->ssl_cipher), - system_charset_info); - if (thd->lex->x509_issuer) - table->field[next_field+2]->store(thd->lex->x509_issuer, - strlen(thd->lex->x509_issuer), - system_charset_info); - if (thd->lex->x509_subject) - table->field[next_field+3]->store(thd->lex->x509_subject, - strlen(thd->lex->x509_subject), - system_charset_info); + if (lex->ssl_cipher) + table->field[next_field+1]->store(lex->ssl_cipher, + strlen(lex->ssl_cipher), system_charset_info); + if (lex->x509_issuer) + table->field[next_field+2]->store(lex->x509_issuer, + strlen(lex->x509_issuer), system_charset_info); + if (lex->x509_subject) + table->field[next_field+3]->store(lex->x509_subject, + strlen(lex->x509_subject), system_charset_info); break; case SSL_TYPE_NOT_SPECIFIED: break; @@ -1669,18 +1887,19 @@ static int replace_user_table(THD *thd, TABLE *table, const LEX_USER &combo, table->field[next_field+3]->store("", 0, &my_charset_latin1); break; } - - /* Skip over SSL related fields to first user limits related field */ - next_field+= 4; - - USER_RESOURCES mqh= thd->lex->mqh; - if (mqh.bits & 1) - table->field[next_field]->store((longlong) mqh.questions); - if (mqh.bits & 2) - table->field[next_field+1]->store((longlong) mqh.updates); - if (mqh.bits & 4) - table->field[next_field+2]->store((longlong) mqh.connections); - mqh_used = mqh_used || mqh.questions || mqh.updates || mqh.connections; + next_field+=4; + + USER_RESOURCES mqh= lex->mqh; + if (mqh.specified_limits & USER_RESOURCES::QUERIES_PER_HOUR) + table->field[next_field]->store((longlong) mqh.questions, TRUE); + if (mqh.specified_limits & USER_RESOURCES::UPDATES_PER_HOUR) + table->field[next_field+1]->store((longlong) mqh.updates, TRUE); + if (mqh.specified_limits & USER_RESOURCES::CONNECTIONS_PER_HOUR) + table->field[next_field+2]->store((longlong) mqh.conn_per_hour, TRUE); + if (table->s->fields >= 36 && + (mqh.specified_limits & USER_RESOURCES::USER_CONNECTIONS)) + table->field[next_field+3]->store((longlong) mqh.user_conn); + mqh_used= mqh_used || mqh.questions || mqh.updates || mqh.conn_per_hour; } if (old_row_exists) { @@ -1716,19 +1935,19 @@ end: if (old_row_exists) acl_update_user(combo.user.str, combo.host.str, combo.password.str, password_len, - thd->lex->ssl_type, - thd->lex->ssl_cipher, - thd->lex->x509_issuer, - thd->lex->x509_subject, - &thd->lex->mqh, + lex->ssl_type, + lex->ssl_cipher, + lex->x509_issuer, + lex->x509_subject, + &lex->mqh, rights); else acl_insert_user(combo.user.str, combo.host.str, password, password_len, - thd->lex->ssl_type, - thd->lex->ssl_cipher, - thd->lex->x509_issuer, - thd->lex->x509_subject, - &thd->lex->mqh, + lex->ssl_type, + lex->ssl_cipher, + lex->x509_issuer, + lex->x509_subject, + &lex->mqh, rights); } DBUG_RETURN(error); @@ -1748,6 +1967,7 @@ static int replace_db_table(TABLE *table, const char *db, bool old_row_exists=0; int error; char what= (revoke_grant) ? 'N' : 'Y'; + byte user_key[MAX_KEY_LENGTH]; DBUG_ENTER("replace_db_table"); if (!initialized) @@ -1759,18 +1979,20 @@ static int replace_db_table(TABLE *table, const char *db, /* Check if there is such a user in user table in memory? */ if (!find_acl_user(combo.host.str,combo.user.str, FALSE)) { - my_error(ER_PASSWORD_NO_MATCH,MYF(0)); + my_message(ER_PASSWORD_NO_MATCH, ER(ER_PASSWORD_NO_MATCH), MYF(0)); DBUG_RETURN(-1); } table->field[0]->store(combo.host.str,combo.host.length, system_charset_info); table->field[1]->store(db,(uint) strlen(db), system_charset_info); table->field[2]->store(combo.user.str,combo.user.length, system_charset_info); + key_copy(user_key, table->record[0], table->key_info, + table->key_info->key_length); + table->file->extra(HA_EXTRA_RETRIEVE_ALL_COLS); if (table->file->index_read_idx(table->record[0],0, - (byte*) table->field[0]->ptr, - table->key_info[0].key_length, - HA_READ_KEY_EXACT)) + user_key, table->key_info->key_length, + HA_READ_KEY_EXACT)) { if (what == 'N') { // no row, no revoke @@ -1778,7 +2000,7 @@ static int replace_db_table(TABLE *table, const char *db, goto abort; } old_row_exists = 0; - restore_record(table,default_values); // cp empty row from default_values + restore_record(table, s->default_values); table->field[0]->store(combo.host.str,combo.host.length, system_charset_info); table->field[1]->store(db,(uint) strlen(db), system_charset_info); table->field[2]->store(combo.user.str,combo.user.length, system_charset_info); @@ -1790,7 +2012,7 @@ static int replace_db_table(TABLE *table, const char *db, } store_rights=get_rights_for_db(rights); - for (i= 3, priv= 1; i < table->fields; i++, priv <<= 1) + for (i= 3, priv= 1; i < table->s->fields; i++, priv <<= 1) { if (priv & store_rights) // do it if priv is chosen table->field [i]->store(&what,1, &my_charset_latin1);// set requested privileges @@ -1857,27 +2079,40 @@ static byte* get_key_column(GRANT_COLUMN *buff,uint *length, } -class GRANT_TABLE :public Sql_alloc +class GRANT_NAME :public Sql_alloc { public: acl_host_and_ip host; char *db, *user, *tname, *hash_key; - ulong privs, cols; + ulong privs; ulong sort; uint key_length; + GRANT_NAME(const char *h, const char *d,const char *u, + const char *t, ulong p); + GRANT_NAME (TABLE *form); + virtual ~GRANT_NAME() {}; + virtual bool ok() { return privs != 0; } +}; + + +class GRANT_TABLE :public GRANT_NAME +{ +public: + ulong cols; HASH hash_columns; GRANT_TABLE(const char *h, const char *d,const char *u, const char *t, ulong p, ulong c); GRANT_TABLE (TABLE *form, TABLE *col_privs); + ~GRANT_TABLE(); bool ok() { return privs != 0 || cols != 0; } }; -GRANT_TABLE::GRANT_TABLE(const char *h, const char *d,const char *u, - const char *t, ulong p, ulong c) - :privs(p), cols(c) +GRANT_NAME::GRANT_NAME(const char *h, const char *d,const char *u, + const char *t, ulong p) + :privs(p) { /* Host given by user */ update_hostname(&host, strdup_root(&memex, h)); @@ -1893,15 +2128,20 @@ GRANT_TABLE::GRANT_TABLE(const char *h, const char *d,const char *u, key_length =(uint) strlen(d)+(uint) strlen(u)+(uint) strlen(t)+3; hash_key = (char*) alloc_root(&memex,key_length); strmov(strmov(strmov(hash_key,user)+1,db)+1,tname); +} + + +GRANT_TABLE::GRANT_TABLE(const char *h, const char *d,const char *u, + const char *t, ulong p, ulong c) + :GRANT_NAME(h,d,u,t,p), cols(c) +{ (void) hash_init(&hash_columns,system_charset_info, 0,0,0, (hash_get_key) get_key_column,0,0); } -GRANT_TABLE::GRANT_TABLE(TABLE *form, TABLE *col_privs) +GRANT_NAME::GRANT_NAME(TABLE *form) { - byte key[MAX_KEY_LENGTH]; - update_hostname(&host, get_field(&memex, form->field[0])); db= get_field(&memex,form->field[1]); user= get_field(&memex,form->field[2]); @@ -1912,7 +2152,7 @@ GRANT_TABLE::GRANT_TABLE(TABLE *form, TABLE *col_privs) if (!db || !tname) { /* Wrong table row; Ignore it */ - privs = cols = 0; /* purecov: inspected */ + privs= 0; return; /* purecov: inspected */ } if (lower_case_table_names) @@ -1925,31 +2165,49 @@ GRANT_TABLE::GRANT_TABLE(TABLE *form, TABLE *col_privs) hash_key = (char*) alloc_root(&memex,key_length); strmov(strmov(strmov(hash_key,user)+1,db)+1,tname); privs = (ulong) form->field[6]->val_int(); - cols = (ulong) form->field[7]->val_int(); privs = fix_rights_for_table(privs); +} + + +GRANT_TABLE::GRANT_TABLE(TABLE *form, TABLE *col_privs) + :GRANT_NAME(form) +{ + byte key[MAX_KEY_LENGTH]; + + if (!db || !tname) + { + /* Wrong table row; Ignore it */ + hash_clear(&hash_columns); /* allow for destruction */ + cols= 0; + return; + } + cols= (ulong) form->field[7]->val_int(); cols = fix_rights_for_column(cols); (void) hash_init(&hash_columns,system_charset_info, 0,0,0, (hash_get_key) get_key_column,0,0); if (cols) { - int key_len; + uint key_prefix_len; + KEY_PART_INFO *key_part= col_privs->key_info->key_part; col_privs->field[0]->store(host.hostname, host.hostname ? (uint) strlen(host.hostname) : 0, system_charset_info); col_privs->field[1]->store(db,(uint) strlen(db), system_charset_info); col_privs->field[2]->store(user,(uint) strlen(user), system_charset_info); col_privs->field[3]->store(tname,(uint) strlen(tname), system_charset_info); - key_len=(col_privs->field[0]->pack_length()+ - col_privs->field[1]->pack_length()+ - col_privs->field[2]->pack_length()+ - col_privs->field[3]->pack_length()); - key_copy(key,col_privs,0,key_len); + + key_prefix_len= (key_part[0].store_length + + key_part[1].store_length + + key_part[2].store_length + + key_part[3].store_length); + key_copy(key, col_privs->record[0], col_privs->key_info, key_prefix_len); col_privs->field[4]->store("",0, &my_charset_latin1); + col_privs->file->ha_index_init(0); if (col_privs->file->index_read(col_privs->record[0], - (byte*) col_privs->field[0]->ptr, - key_len, HA_READ_KEY_EXACT)) + (byte*) key, + key_prefix_len, HA_READ_KEY_EXACT)) { cols = 0; /* purecov: deadcode */ col_privs->file->ha_index_end(); @@ -1971,13 +2229,19 @@ GRANT_TABLE::GRANT_TABLE(TABLE *form, TABLE *col_privs) } my_hash_insert(&hash_columns, (byte *) mem_check); } while (!col_privs->file->index_next(col_privs->record[0]) && - !key_cmp_if_same(col_privs,key,0,key_len)); + !key_cmp_if_same(col_privs,key,0,key_prefix_len)); col_privs->file->ha_index_end(); } } -static byte* get_grant_table(GRANT_TABLE *buff,uint *length, +GRANT_TABLE::~GRANT_TABLE() +{ + hash_free(&hash_columns); +} + + +static byte* get_grant_table(GRANT_NAME *buff,uint *length, my_bool not_used __attribute__((unused))) { *length=buff->key_length; @@ -1993,43 +2257,62 @@ void free_grant_table(GRANT_TABLE *grant_table) /* Search after a matching grant. Prefer exact grants before not exact ones */ -static GRANT_TABLE *table_hash_search(const char *host,const char* ip, +static GRANT_NAME *name_hash_search(HASH *name_hash, + const char *host,const char* ip, const char *db, const char *user, const char *tname, bool exact) { char helping [NAME_LEN*2+USERNAME_LENGTH+3]; uint len; - GRANT_TABLE *grant_table,*found=0; + GRANT_NAME *grant_name,*found=0; HASH_SEARCH_STATE state; len = (uint) (strmov(strmov(strmov(helping,user)+1,db)+1,tname)-helping)+ 1; - for (grant_table=(GRANT_TABLE*) hash_first(&column_priv_hash, - (byte*) helping, - len, &state) ; - grant_table ; - grant_table= (GRANT_TABLE*) hash_next(&column_priv_hash,(byte*) helping, - len, &state)) + for (grant_name= (GRANT_NAME*) hash_first(name_hash, (byte*) helping, + len, &state); + grant_name ; + grant_name= (GRANT_NAME*) hash_next(name_hash,(byte*) helping, + len, &state)) { if (exact) { - if ((host && + if (!grant_name->host.hostname || + (host && !my_strcasecmp(system_charset_info, host, - grant_table->host.hostname)) || - (ip && !strcmp(ip, grant_table->host.hostname))) - return grant_table; + grant_name->host.hostname)) || + (ip && !strcmp(ip, grant_name->host.hostname))) + return grant_name; } else { - if (compare_hostname(&grant_table->host, host, ip) && - (!found || found->sort < grant_table->sort)) - found=grant_table; // Host ok + if (compare_hostname(&grant_name->host, host, ip) && + (!found || found->sort < grant_name->sort)) + found=grant_name; // Host ok } } return found; } +inline GRANT_NAME * +routine_hash_search(const char *host, const char *ip, const char *db, + const char *user, const char *tname, bool proc, bool exact) +{ + return (GRANT_TABLE*) + name_hash_search(proc ? &proc_priv_hash : &func_priv_hash, + host, ip, db, user, tname, exact); +} + + +inline GRANT_TABLE * +table_hash_search(const char *host, const char *ip, const char *db, + const char *user, const char *tname, bool exact) +{ + return (GRANT_TABLE*) name_hash_search(&column_priv_hash, host, ip, db, + user, tname, exact); +} + inline GRANT_COLUMN * column_hash_search(GRANT_TABLE *t, const char *cname, uint length) @@ -2045,49 +2328,64 @@ static int replace_column_table(GRANT_TABLE *g_t, ulong rights, bool revoke_grant) { int error=0,result=0; - uint key_length; byte key[MAX_KEY_LENGTH]; + uint key_prefix_length; + KEY_PART_INFO *key_part= table->key_info->key_part; DBUG_ENTER("replace_column_table"); - table->field[0]->store(combo.host.str,combo.host.length, system_charset_info); - table->field[1]->store(db,(uint) strlen(db), system_charset_info); - table->field[2]->store(combo.user.str,combo.user.length, system_charset_info); - table->field[3]->store(table_name,(uint) strlen(table_name), system_charset_info); - key_length=(table->field[0]->pack_length()+ table->field[1]->pack_length()+ - table->field[2]->pack_length()+ table->field[3]->pack_length()); - key_copy(key,table,0,key_length); + table->field[0]->store(combo.host.str,combo.host.length, + system_charset_info); + table->field[1]->store(db,(uint) strlen(db), + system_charset_info); + table->field[2]->store(combo.user.str,combo.user.length, + system_charset_info); + table->field[3]->store(table_name,(uint) strlen(table_name), + system_charset_info); + + /* Get length of 3 first key parts */ + key_prefix_length= (key_part[0].store_length + key_part[1].store_length + + key_part[2].store_length + key_part[3].store_length); + key_copy(key, table->record[0], table->key_info, key_prefix_length); - rights &= COL_ACLS; // Only ACL for columns + rights&= COL_ACLS; // Only ACL for columns /* first fix privileges for all columns in column list */ List_iterator <LEX_COLUMN> iter(columns); - class LEX_COLUMN *xx; + class LEX_COLUMN *column; table->file->ha_index_init(0); - while ((xx=iter++)) + while ((column= iter++)) { - ulong privileges = xx->rights; + ulong privileges= column->rights; bool old_row_exists=0; - key_restore(table,key,0,key_length); - table->field[4]->store(xx->column.ptr(),xx->column.length(), + byte user_key[MAX_KEY_LENGTH]; + + key_restore(table->record[0],key,table->key_info, + key_prefix_length); + table->field[4]->store(column->column.ptr(), column->column.length(), system_charset_info); + /* Get key for the first 4 columns */ + key_copy(user_key, table->record[0], table->key_info, + table->key_info->key_length); table->file->extra(HA_EXTRA_RETRIEVE_ALL_COLS); - if (table->file->index_read(table->record[0],(byte*) table->field[0]->ptr, - table->key_info[0].key_length, - HA_READ_KEY_EXACT)) + if (table->file->index_read(table->record[0], user_key, + table->key_info->key_length, + HA_READ_KEY_EXACT)) { if (revoke_grant) { my_error(ER_NONEXISTING_TABLE_GRANT, MYF(0), - combo.user.str, combo.host.str, table_name); /* purecov: inspected */ - result= -1; /* purecov: inspected */ - continue; /* purecov: inspected */ + combo.user.str, combo.host.str, + table_name); /* purecov: inspected */ + result= -1; /* purecov: inspected */ + continue; /* purecov: inspected */ } old_row_exists = 0; - restore_record(table,default_values); // Get empty record - key_restore(table,key,0,key_length); - table->field[4]->store(xx->column.ptr(),xx->column.length(), + restore_record(table, s->default_values); // Get empty record + key_restore(table->record[0],key,table->key_info, + key_prefix_length); + table->field[4]->store(column->column.ptr(),column->column.length(), system_charset_info); } else @@ -2103,10 +2401,11 @@ static int replace_column_table(GRANT_TABLE *g_t, store_record(table,record[1]); // copy original row } - table->field[6]->store((longlong) get_rights_for_column(privileges)); + table->field[6]->store((longlong) get_rights_for_column(privileges), TRUE); if (old_row_exists) { + GRANT_COLUMN *grant_column; if (privileges) error=table->file->update_row(table->record[1],table->record[0]); else @@ -2117,21 +2416,21 @@ static int replace_column_table(GRANT_TABLE *g_t, result= -1; /* purecov: inspected */ goto end; /* purecov: inspected */ } - GRANT_COLUMN *grant_column = column_hash_search(g_t, - xx->column.ptr(), - xx->column.length()); + grant_column= column_hash_search(g_t, column->column.ptr(), + column->column.length()); if (grant_column) // Should always be true - grant_column->rights = privileges; // Update hash + grant_column->rights= privileges; // Update hash } else // new grant { + GRANT_COLUMN *grant_column; if ((error=table->file->write_row(table->record[0]))) { table->file->print_error(error,MYF(0)); /* purecov: inspected */ result= -1; /* purecov: inspected */ goto end; /* purecov: inspected */ } - GRANT_COLUMN *grant_column = new GRANT_COLUMN(xx->column,privileges); + grant_column= new GRANT_COLUMN(column->column,privileges); my_hash_insert(&g_t->hash_columns,(byte*) grant_column); } } @@ -2143,10 +2442,14 @@ static int replace_column_table(GRANT_TABLE *g_t, if (revoke_grant) { + byte user_key[MAX_KEY_LENGTH]; + key_copy(user_key, table->record[0], table->key_info, + key_prefix_length); + table->file->extra(HA_EXTRA_RETRIEVE_ALL_COLS); - if (table->file->index_read(table->record[0], (byte*) table->field[0]->ptr, - key_length, - HA_READ_KEY_EXACT)) + if (table->file->index_read(table->record[0], user_key, + key_prefix_length, + HA_READ_KEY_EXACT)) goto end; /* Scan through all rows with the same host,db,user and table */ @@ -2165,7 +2468,7 @@ static int replace_column_table(GRANT_TABLE *g_t, privileges&= ~rights; table->field[6]->store((longlong) - get_rights_for_column(privileges)); + get_rights_for_column(privileges), TRUE); table->field[4]->val_str(&column_name); grant_column = column_hash_search(g_t, column_name.ptr(), @@ -2197,7 +2500,7 @@ static int replace_column_table(GRANT_TABLE *g_t, } } } while (!table->file->index_next(table->record[0]) && - !key_cmp_if_same(table,key,0,key_length)); + !key_cmp_if_same(table, key, 0, key_prefix_length)); } end: @@ -2212,13 +2515,15 @@ static int replace_table_table(THD *thd, GRANT_TABLE *grant_table, ulong rights, ulong col_rights, bool revoke_grant) { - char grantor[HOSTNAME_LENGTH+USERNAME_LENGTH+2]; + char grantor[USER_HOST_BUFF_SIZE]; int old_row_exists = 1; int error=0; ulong store_table_rights, store_col_rights; + byte user_key[MAX_KEY_LENGTH]; DBUG_ENTER("replace_table_table"); - strxmov(grantor, thd->user, "@", thd->host_or_ip, NullS); + strxmov(grantor, thd->security_ctx->user, "@", + thd->security_ctx->host_or_ip, NullS); /* The following should always succeed as new users are created before @@ -2226,20 +2531,23 @@ static int replace_table_table(THD *thd, GRANT_TABLE *grant_table, */ if (!find_acl_user(combo.host.str,combo.user.str, FALSE)) { - my_error(ER_PASSWORD_NO_MATCH,MYF(0)); /* purecov: deadcode */ + my_message(ER_PASSWORD_NO_MATCH, ER(ER_PASSWORD_NO_MATCH), + MYF(0)); /* purecov: deadcode */ DBUG_RETURN(-1); /* purecov: deadcode */ } - restore_record(table,default_values); // Get empty record + restore_record(table, s->default_values); // Get empty record table->field[0]->store(combo.host.str,combo.host.length, system_charset_info); table->field[1]->store(db,(uint) strlen(db), system_charset_info); table->field[2]->store(combo.user.str,combo.user.length, system_charset_info); table->field[3]->store(table_name,(uint) strlen(table_name), system_charset_info); store_record(table,record[1]); // store at pos 1 + key_copy(user_key, table->record[0], table->key_info, + table->key_info->key_length); + table->file->extra(HA_EXTRA_RETRIEVE_ALL_COLS); - if (table->file->index_read_idx(table->record[0],0, - (byte*) table->field[0]->ptr, - table->key_info[0].key_length, + if (table->file->index_read_idx(table->record[0], 0, + user_key, table->key_info->key_length, HA_READ_KEY_EXACT)) { /* @@ -2251,7 +2559,7 @@ static int replace_table_table(THD *thd, GRANT_TABLE *grant_table, { // no row, no revoke my_error(ER_NONEXISTING_TABLE_GRANT, MYF(0), combo.user.str, combo.host.str, - table_name); /* purecov: deadcode */ + table_name); /* purecov: deadcode */ DBUG_RETURN(-1); /* purecov: deadcode */ } old_row_exists = 0; @@ -2280,8 +2588,8 @@ static int replace_table_table(THD *thd, GRANT_TABLE *grant_table, } table->field[4]->store(grantor,(uint) strlen(grantor), system_charset_info); - table->field[6]->store((longlong) store_table_rights); - table->field[7]->store((longlong) store_col_rights); + table->field[6]->store((longlong) store_table_rights, TRUE); + table->field[7]->store((longlong) store_col_rights, TRUE); rights=fix_rights_for_table(store_table_rights); col_rights=fix_rights_for_column(store_col_rights); @@ -2320,6 +2628,122 @@ table_error: } +static int replace_routine_table(THD *thd, GRANT_NAME *grant_name, + TABLE *table, const LEX_USER &combo, + const char *db, const char *routine_name, + bool is_proc, ulong rights, bool revoke_grant) +{ + char grantor[USER_HOST_BUFF_SIZE]; + int old_row_exists= 1; + int error=0; + ulong store_proc_rights; + DBUG_ENTER("replace_routine_table"); + + if (!initialized) + { + my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--skip-grant-tables"); + DBUG_RETURN(-1); + } + + strxmov(grantor, thd->security_ctx->user, "@", + thd->security_ctx->host_or_ip, NullS); + + /* + The following should always succeed as new users are created before + this function is called! + */ + if (!find_acl_user(combo.host.str, combo.user.str, FALSE)) + { + my_error(ER_PASSWORD_NO_MATCH,MYF(0)); + DBUG_RETURN(-1); + } + + restore_record(table, s->default_values); // Get empty record + table->field[0]->store(combo.host.str,combo.host.length, &my_charset_latin1); + table->field[1]->store(db,(uint) strlen(db), &my_charset_latin1); + table->field[2]->store(combo.user.str,combo.user.length, &my_charset_latin1); + table->field[3]->store(routine_name,(uint) strlen(routine_name), + &my_charset_latin1); + table->field[4]->store((longlong)(is_proc ? + TYPE_ENUM_PROCEDURE : TYPE_ENUM_FUNCTION), + TRUE); + store_record(table,record[1]); // store at pos 1 + + if (table->file->index_read_idx(table->record[0],0, + (byte*) table->field[0]->ptr,0, + HA_READ_KEY_EXACT)) + { + /* + The following should never happen as we first check the in memory + grant tables for the user. There is however always a small change that + the user has modified the grant tables directly. + */ + if (revoke_grant) + { // no row, no revoke + my_error(ER_NONEXISTING_PROC_GRANT, MYF(0), + combo.user.str, combo.host.str, routine_name); + DBUG_RETURN(-1); + } + old_row_exists= 0; + restore_record(table,record[1]); // Get saved record + } + + store_proc_rights= get_rights_for_procedure(rights); + if (old_row_exists) + { + ulong j; + store_record(table,record[1]); + j= (ulong) table->field[6]->val_int(); + + if (revoke_grant) + { + /* column rights are already fixed in mysql_table_grant */ + store_proc_rights=j & ~store_proc_rights; + } + else + { + store_proc_rights|= j; + } + } + + table->field[5]->store(grantor,(uint) strlen(grantor), &my_charset_latin1); + table->field[6]->store((longlong) store_proc_rights, TRUE); + rights=fix_rights_for_procedure(store_proc_rights); + + if (old_row_exists) + { + if (store_proc_rights) + { + if ((error=table->file->update_row(table->record[1],table->record[0]))) + goto table_error; + } + else if ((error= table->file->delete_row(table->record[1]))) + goto table_error; + } + else + { + error=table->file->write_row(table->record[0]); + if (error && error != HA_ERR_FOUND_DUPP_KEY) + goto table_error; + } + + if (rights) + { + grant_name->privs= rights; + } + else + { + hash_delete(is_proc ? &proc_priv_hash : &func_priv_hash,(byte*) grant_name); + } + DBUG_RETURN(0); + + /* This should never happen */ +table_error: + table->file->print_error(error,MYF(0)); + DBUG_RETURN(-1); +} + + /* Store table level and column level grants in the privilege tables @@ -2333,59 +2757,63 @@ table_error: revoke_grant Set to 1 if this is a REVOKE command RETURN - 0 ok - 1 error + FALSE ok + TRUE error */ -int mysql_table_grant(THD *thd, TABLE_LIST *table_list, +bool mysql_table_grant(THD *thd, TABLE_LIST *table_list, List <LEX_USER> &user_list, List <LEX_COLUMN> &columns, ulong rights, bool revoke_grant) { ulong column_priv= 0; List_iterator <LEX_USER> str_list (user_list); - LEX_USER *Str; + LEX_USER *Str, *tmp_Str; TABLE_LIST tables[3]; bool create_new_users=0; + char *db_name, *table_name; DBUG_ENTER("mysql_table_grant"); if (!initialized) { my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--skip-grant-tables"); /* purecov: inspected */ - DBUG_RETURN(-1); /* purecov: inspected */ + DBUG_RETURN(TRUE); /* purecov: inspected */ } if (rights & ~TABLE_ACLS) { - my_error(ER_ILLEGAL_GRANT_FOR_TABLE,MYF(0)); - DBUG_RETURN(-1); + my_message(ER_ILLEGAL_GRANT_FOR_TABLE, ER(ER_ILLEGAL_GRANT_FOR_TABLE), + MYF(0)); + DBUG_RETURN(TRUE); } if (!revoke_grant) { - if (columns.elements && !revoke_grant) + if (columns.elements) { - TABLE *table; class LEX_COLUMN *column; List_iterator <LEX_COLUMN> column_iter(columns); - if (!(table=open_ltable(thd,table_list,TL_READ))) - DBUG_RETURN(-1); + if (open_and_lock_tables(thd, table_list)) + DBUG_RETURN(TRUE); + while ((column = column_iter++)) { uint unused_field_idx= NO_CACHED_FIELD_INDEX; - Field *f= find_field_in_table(thd,table,column->column.ptr(), - column->column.length(),1,0,&unused_field_idx); - if (!f) + TABLE_LIST *dummy; + Field *f=find_field_in_table_ref(thd, table_list, column->column.ptr(), + column->column.length(), + column->column.ptr(), NULL, NULL, + NULL, TRUE, FALSE, + &unused_field_idx, FALSE, &dummy); + if (f == (Field*)0) { my_error(ER_BAD_FIELD_ERROR, MYF(0), column->column.c_ptr(), table_list->alias); - DBUG_RETURN(-1); - } - if (f == (Field*)-1) - { - DBUG_RETURN(-1); + DBUG_RETURN(TRUE); } + if (f == (Field *)-1) + DBUG_RETURN(TRUE); column_priv|= column->rights; } close_thread_tables(thd); @@ -2396,12 +2824,12 @@ int mysql_table_grant(THD *thd, TABLE_LIST *table_list, { char buf[FN_REFLEN]; sprintf(buf,"%s/%s/%s.frm",mysql_data_home, table_list->db, - table_list->real_name); + table_list->table_name); fn_format(buf,buf,"","",4+16+32); if (access(buf,F_OK)) { my_error(ER_NO_SUCH_TABLE, MYF(0), table_list->db, table_list->alias); - DBUG_RETURN(-1); + DBUG_RETURN(TRUE); } } if (table_list->grant.want_privilege) @@ -2410,7 +2838,8 @@ int mysql_table_grant(THD *thd, TABLE_LIST *table_list, get_privilege_desc(command, sizeof(command), table_list->grant.want_privilege); my_error(ER_TABLEACCESS_DENIED_ERROR, MYF(0), - command, thd->priv_user, thd->host_or_ip, table_list->alias); + command, thd->security_ctx->priv_user, + thd->security_ctx->host_or_ip, table_list->alias); DBUG_RETURN(-1); } } @@ -2419,14 +2848,16 @@ int mysql_table_grant(THD *thd, TABLE_LIST *table_list, /* open the mysql.tables_priv and mysql.columns_priv tables */ bzero((char*) &tables,sizeof(tables)); - tables[0].alias=tables[0].real_name= (char*) "user"; - tables[1].alias=tables[1].real_name= (char*) "tables_priv"; - tables[2].alias=tables[2].real_name= (char*) "columns_priv"; - tables[0].next=tables+1; + tables[0].alias=tables[0].table_name= (char*) "user"; + tables[1].alias=tables[1].table_name= (char*) "tables_priv"; + tables[2].alias=tables[2].table_name= (char*) "columns_priv"; + tables[0].next_local= tables[0].next_global= tables+1; /* Don't open column table if we don't need it ! */ - tables[1].next=((column_priv || - (revoke_grant && ((rights & COL_ACLS) || columns.elements))) - ? tables+2 : 0); + tables[1].next_local= + tables[1].next_global= ((column_priv || + (revoke_grant && + ((rights & COL_ACLS) || columns.elements))) + ? tables+2 : 0); tables[0].lock_type=tables[1].lock_type=tables[2].lock_type=TL_WRITE; tables[0].db=tables[1].db=tables[2].db=(char*) "mysql"; @@ -2442,66 +2873,72 @@ int mysql_table_grant(THD *thd, TABLE_LIST *table_list, account in tests. */ tables[0].updating= tables[1].updating= tables[2].updating= 1; - if (!tables_ok(0, tables)) - DBUG_RETURN(0); + if (!tables_ok(thd, tables)) + DBUG_RETURN(FALSE); } #endif if (simple_open_n_lock_tables(thd,tables)) { // Should never happen close_thread_tables(thd); /* purecov: deadcode */ - DBUG_RETURN(-1); /* purecov: deadcode */ + DBUG_RETURN(TRUE); /* purecov: deadcode */ } if (!revoke_grant) create_new_users= test_if_create_new_users(thd); - int result=0; + bool result= FALSE; rw_wrlock(&LOCK_grant); pthread_mutex_lock(&acl_cache->lock); MEM_ROOT *old_root= thd->mem_root; thd->mem_root= &memex; + grant_version++; - while ((Str = str_list++)) + while ((tmp_Str = str_list++)) { int error; GRANT_TABLE *grant_table; - if (Str->host.length > HOSTNAME_LENGTH || - Str->user.length > USERNAME_LENGTH) + if (!(Str= get_current_user(thd, tmp_Str))) { - my_error(ER_GRANT_WRONG_HOST_OR_USER,MYF(0)); - result= -1; + result= TRUE; continue; - } + } /* Create user if needed */ error=replace_user_table(thd, tables[0].table, *Str, - 0, revoke_grant, create_new_users); + 0, revoke_grant, create_new_users, + test(thd->variables.sql_mode & + MODE_NO_AUTO_CREATE_USER)); if (error) { - result= -1; // Remember error + result= TRUE; // Remember error continue; // Add next user } + db_name= (table_list->view_db.length ? + table_list->view_db.str : + table_list->db); + table_name= (table_list->view_name.length ? + table_list->view_name.str : + table_list->table_name); + /* Find/create cached table grant */ - grant_table= table_hash_search(Str->host.str,NullS,table_list->db, - Str->user.str, - table_list->real_name,1); + grant_table= table_hash_search(Str->host.str, NullS, db_name, + Str->user.str, table_name, 1); if (!grant_table) { if (revoke_grant) { my_error(ER_NONEXISTING_TABLE_GRANT, MYF(0), - Str->user.str, Str->host.str, table_list->real_name); - result= -1; + Str->user.str, Str->host.str, table_list->table_name); + result= TRUE; continue; } - grant_table = new GRANT_TABLE (Str->host.str,table_list->db, - Str->user.str, - table_list->real_name, + grant_table = new GRANT_TABLE (Str->host.str, db_name, + Str->user.str, table_name, rights, column_priv); if (!grant_table) // end of memory { - result= -1; /* purecov: deadcode */ + result= TRUE; /* purecov: deadcode */ continue; /* purecov: deadcode */ } my_hash_insert(&column_priv_hash,(byte*) grant_table); @@ -2541,23 +2978,21 @@ int mysql_table_grant(THD *thd, TABLE_LIST *table_list, /* update table and columns */ - if (replace_table_table(thd,grant_table,tables[1].table,*Str, - table_list->db, - table_list->real_name, + if (replace_table_table(thd, grant_table, tables[1].table, *Str, + db_name, table_name, rights, column_priv, revoke_grant)) { /* Should only happen if table is crashed */ - result= -1; /* purecov: deadcode */ + result= TRUE; /* purecov: deadcode */ } else if (tables[2].table) { - if ((replace_column_table(grant_table,tables[2].table, *Str, + if ((replace_column_table(grant_table, tables[2].table, *Str, columns, - table_list->db, - table_list->real_name, + db_name, table_name, rights, revoke_grant))) { - result= -1; + result= TRUE; } } } @@ -2572,11 +3007,165 @@ int mysql_table_grant(THD *thd, TABLE_LIST *table_list, } -int mysql_grant(THD *thd, const char *db, List <LEX_USER> &list, - ulong rights, bool revoke_grant) +/* + Store routine level grants in the privilege tables + + SYNOPSIS + mysql_routine_grant() + thd Thread handle + table_list List of routines to give grant + is_proc true indicates routine list are procedures + user_list List of users to give grant + rights Table level grant + revoke_grant Set to 1 if this is a REVOKE command + + RETURN + 0 ok + 1 error +*/ + +bool mysql_routine_grant(THD *thd, TABLE_LIST *table_list, bool is_proc, + List <LEX_USER> &user_list, ulong rights, + bool revoke_grant, bool no_error) +{ + List_iterator <LEX_USER> str_list (user_list); + LEX_USER *Str, *tmp_Str; + TABLE_LIST tables[2]; + bool create_new_users=0, result=0; + char *db_name, *table_name; + DBUG_ENTER("mysql_routine_grant"); + + if (!initialized) + { + if (!no_error) + my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), + "--skip-grant-tables"); + DBUG_RETURN(TRUE); + } + if (rights & ~PROC_ACLS) + { + if (!no_error) + my_message(ER_ILLEGAL_GRANT_FOR_TABLE, ER(ER_ILLEGAL_GRANT_FOR_TABLE), + MYF(0)); + DBUG_RETURN(TRUE); + } + + if (!revoke_grant) + { + if (sp_exist_routines(thd, table_list, is_proc, no_error)<0) + DBUG_RETURN(TRUE); + } + + /* open the mysql.user and mysql.procs_priv tables */ + + bzero((char*) &tables,sizeof(tables)); + tables[0].alias=tables[0].table_name= (char*) "user"; + tables[1].alias=tables[1].table_name= (char*) "procs_priv"; + tables[0].next_local= tables[0].next_global= tables+1; + tables[0].lock_type=tables[1].lock_type=TL_WRITE; + tables[0].db=tables[1].db=(char*) "mysql"; + +#ifdef HAVE_REPLICATION + /* + GRANT and REVOKE are applied the slave in/exclusion rules as they are + some kind of updates to the mysql.% tables. + */ + if (thd->slave_thread && table_rules_on) + { + /* + The tables must be marked "updating" so that tables_ok() takes them into + account in tests. + */ + tables[0].updating= tables[1].updating= 1; + if (!tables_ok(thd, tables)) + DBUG_RETURN(FALSE); + } +#endif + + if (simple_open_n_lock_tables(thd,tables)) + { // Should never happen + close_thread_tables(thd); + DBUG_RETURN(TRUE); + } + + if (!revoke_grant) + create_new_users= test_if_create_new_users(thd); + rw_wrlock(&LOCK_grant); + pthread_mutex_lock(&acl_cache->lock); + MEM_ROOT *old_root= thd->mem_root; + thd->mem_root= &memex; + + DBUG_PRINT("info",("now time to iterate and add users")); + + while ((tmp_Str= str_list++)) + { + int error; + GRANT_NAME *grant_name; + if (!(Str= get_current_user(thd, tmp_Str))) + { + result= TRUE; + continue; + } + /* Create user if needed */ + error=replace_user_table(thd, tables[0].table, *Str, + 0, revoke_grant, create_new_users, + test(thd->variables.sql_mode & + MODE_NO_AUTO_CREATE_USER)); + if (error) + { + result= TRUE; // Remember error + continue; // Add next user + } + + db_name= table_list->db; + table_name= table_list->table_name; + + grant_name= routine_hash_search(Str->host.str, NullS, db_name, + Str->user.str, table_name, is_proc, 1); + if (!grant_name) + { + if (revoke_grant) + { + if (!no_error) + my_error(ER_NONEXISTING_PROC_GRANT, MYF(0), + Str->user.str, Str->host.str, table_name); + result= TRUE; + continue; + } + grant_name= new GRANT_NAME(Str->host.str, db_name, + Str->user.str, table_name, + rights); + if (!grant_name) + { + result= TRUE; + continue; + } + my_hash_insert(is_proc ? &proc_priv_hash : &func_priv_hash,(byte*) grant_name); + } + + if (replace_routine_table(thd, grant_name, tables[1].table, *Str, + db_name, table_name, is_proc, rights, revoke_grant)) + { + result= TRUE; + continue; + } + } + grant_option=TRUE; + thd->mem_root= old_root; + pthread_mutex_unlock(&acl_cache->lock); + rw_unlock(&LOCK_grant); + if (!result && !no_error) + send_ok(thd); + /* Tables are automatically closed */ + DBUG_RETURN(result); +} + + +bool mysql_grant(THD *thd, const char *db, List <LEX_USER> &list, + ulong rights, bool revoke_grant) { List_iterator <LEX_USER> str_list (list); - LEX_USER *Str; + LEX_USER *Str, *tmp_Str; char tmp_db[NAME_LEN+1]; bool create_new_users=0; TABLE_LIST tables[2]; @@ -2585,7 +3174,7 @@ int mysql_grant(THD *thd, const char *db, List <LEX_USER> &list, { my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--skip-grant-tables"); /* purecov: tested */ - DBUG_RETURN(-1); /* purecov: tested */ + DBUG_RETURN(TRUE); /* purecov: tested */ } if (lower_case_table_names && db) @@ -2597,13 +3186,11 @@ int mysql_grant(THD *thd, const char *db, List <LEX_USER> &list, /* open the mysql.user and mysql.db tables */ bzero((char*) &tables,sizeof(tables)); - tables[0].alias=tables[0].real_name=(char*) "user"; - tables[1].alias=tables[1].real_name=(char*) "db"; - tables[0].next=tables+1; - tables[1].next=0; + tables[0].alias=tables[0].table_name=(char*) "user"; + tables[1].alias=tables[1].table_name=(char*) "db"; + tables[0].next_local= tables[0].next_global= tables+1; tables[0].lock_type=tables[1].lock_type=TL_WRITE; tables[0].db=tables[1].db=(char*) "mysql"; - tables[0].table=tables[1].table=0; #ifdef HAVE_REPLICATION /* @@ -2617,15 +3204,15 @@ int mysql_grant(THD *thd, const char *db, List <LEX_USER> &list, account in tests. */ tables[0].updating= tables[1].updating= 1; - if (!tables_ok(0, tables)) - DBUG_RETURN(0); + if (!tables_ok(thd, tables)) + DBUG_RETURN(FALSE); } #endif if (simple_open_n_lock_tables(thd,tables)) { // This should never happen close_thread_tables(thd); /* purecov: deadcode */ - DBUG_RETURN(-1); /* purecov: deadcode */ + DBUG_RETURN(TRUE); /* purecov: deadcode */ } if (!revoke_grant) @@ -2637,20 +3224,17 @@ int mysql_grant(THD *thd, const char *db, List <LEX_USER> &list, grant_version++; int result=0; - while ((Str = str_list++)) + while ((tmp_Str = str_list++)) { - if (Str->host.length > HOSTNAME_LENGTH || - Str->user.length > USERNAME_LENGTH) + if (!(Str= get_current_user(thd, tmp_Str))) { - my_error(ER_GRANT_WRONG_HOST_OR_USER,MYF(0)); - result= -1; + result= TRUE; continue; } - if ((replace_user_table(thd, - tables[0].table, - *Str, - (!db ? rights : 0), revoke_grant, - create_new_users))) + if (replace_user_table(thd, tables[0].table, *Str, + (!db ? rights : 0), revoke_grant, create_new_users, + test(thd->variables.sql_mode & + MODE_NO_AUTO_CREATE_USER))) result= -1; else if (db) { @@ -2685,6 +3269,8 @@ void grant_free(void) DBUG_ENTER("grant_free"); grant_option = FALSE; hash_free(&column_priv_hash); + hash_free(&proc_priv_hash); + hash_free(&func_priv_hash); free_root(&memex,MYF(0)); DBUG_VOID_RETURN; } @@ -2710,6 +3296,7 @@ my_bool grant_init() if (!(thd= new THD)) DBUG_RETURN(1); /* purecov: deadcode */ + thd->thread_stack= (char*) &thd; thd->store_globals(); return_val= grant_reload(thd); delete thd; @@ -2738,7 +3325,7 @@ static my_bool grant_load(TABLE_LIST *tables) { MEM_ROOT *memex_ptr; my_bool return_val= 1; - TABLE *t_table, *c_table; + TABLE *t_table, *c_table, *p_table; bool check_no_resolve= specialflag & SPECIAL_NO_RESOLVE; MEM_ROOT **save_mem_root_ptr= my_pthread_getspecific_ptr(MEM_ROOT**, THR_MALLOC); @@ -2748,53 +3335,119 @@ static my_bool grant_load(TABLE_LIST *tables) (void) hash_init(&column_priv_hash,system_charset_info, 0,0,0, (hash_get_key) get_grant_table, (hash_free_key) free_grant_table,0); + (void) hash_init(&proc_priv_hash,system_charset_info, + 0,0,0, (hash_get_key) get_grant_table, + 0,0); + (void) hash_init(&func_priv_hash,system_charset_info, + 0,0,0, (hash_get_key) get_grant_table, + 0,0); init_sql_alloc(&memex, ACL_ALLOC_BLOCK_SIZE, 0); t_table = tables[0].table; c_table = tables[1].table; + p_table= tables[2].table; t_table->file->ha_index_init(0); - if (t_table->file->index_first(t_table->record[0])) + p_table->file->ha_index_init(0); + if (!t_table->file->index_first(t_table->record[0])) { - return_val= 0; - goto end_unlock; - } - grant_option= TRUE; + memex_ptr= &memex; + my_pthread_setspecific_ptr(THR_MALLOC, &memex_ptr); + do + { + GRANT_TABLE *mem_check; + if (!(mem_check=new GRANT_TABLE(t_table,c_table))) + { + /* This could only happen if we are out memory */ + grant_option= FALSE; + goto end_unlock; + } - memex_ptr= &memex; - my_pthread_setspecific_ptr(THR_MALLOC, &memex_ptr); - do + if (check_no_resolve) + { + if (hostname_requires_resolving(mem_check->host.hostname)) + { + sql_print_warning("'tables_priv' entry '%s %s@%s' " + "ignored in --skip-name-resolve mode.", + mem_check->tname, + mem_check->user ? mem_check->user : "", + mem_check->host.hostname ? + mem_check->host.hostname : ""); + continue; + } + } + + if (! mem_check->ok()) + delete mem_check; + else if (my_hash_insert(&column_priv_hash,(byte*) mem_check)) + { + delete mem_check; + grant_option= FALSE; + goto end_unlock; + } + } + while (!t_table->file->index_next(t_table->record[0])); + } + if (!p_table->file->index_first(p_table->record[0])) { - GRANT_TABLE *mem_check; - if (!(mem_check=new GRANT_TABLE(t_table,c_table))) + memex_ptr= &memex; + my_pthread_setspecific_ptr(THR_MALLOC, &memex_ptr); + do { - /* This could only happen if we are out memory */ - grant_option= FALSE; /* purecov: deadcode */ - goto end_unlock; - } + GRANT_NAME *mem_check; + HASH *hash; + if (!(mem_check=new GRANT_NAME(p_table))) + { + /* This could only happen if we are out memory */ + grant_option= FALSE; + goto end_unlock; + } - if (check_no_resolve) - { - if (hostname_requires_resolving(mem_check->host.hostname)) + if (check_no_resolve) + { + if (hostname_requires_resolving(mem_check->host.hostname)) + { + sql_print_warning("'procs_priv' entry '%s %s@%s' " + "ignored in --skip-name-resolve mode.", + mem_check->tname, mem_check->user, + mem_check->host.hostname ? + mem_check->host.hostname : ""); + continue; + } + } + if (p_table->field[4]->val_int() == TYPE_ENUM_PROCEDURE) + { + hash= &proc_priv_hash; + } + else + if (p_table->field[4]->val_int() == TYPE_ENUM_FUNCTION) { - sql_print_warning("'tables_priv' entry '%s %s@%s' " - "ignored in --skip-name-resolve mode.", - mem_check->tname, mem_check->user, - mem_check->host.hostname); + hash= &func_priv_hash; + } + else + { + sql_print_warning("'procs_priv' entry '%s' " + "ignored, bad routine type", + mem_check->tname); continue; } - } - if (mem_check->ok() && my_hash_insert(&column_priv_hash,(byte*) mem_check)) - { - grant_option= FALSE; - goto end_unlock; + mem_check->privs= fix_rights_for_procedure(mem_check->privs); + if (! mem_check->ok()) + delete mem_check; + else if (my_hash_insert(hash, (byte*) mem_check)) + { + delete mem_check; + grant_option= FALSE; + goto end_unlock; + } } + while (!p_table->file->index_next(p_table->record[0])); } - while (!t_table->file->index_next(t_table->record[0])); - + grant_option= TRUE; return_val=0; // Return ok end_unlock: t_table->file->ha_index_end(); + p_table->file->ha_index_end(); my_pthread_setspecific_ptr(THR_MALLOC, save_mem_root_ptr); DBUG_RETURN(return_val); } @@ -2820,8 +3473,8 @@ end_unlock: my_bool grant_reload(THD *thd) { - TABLE_LIST tables[2]; - HASH old_column_priv_hash; + TABLE_LIST tables[3]; + HASH old_column_priv_hash, old_proc_priv_hash, old_func_priv_hash; bool old_grant_option; MEM_ROOT old_mem; my_bool return_val= 1; @@ -2832,11 +3485,13 @@ my_bool grant_reload(THD *thd) DBUG_RETURN(0); bzero((char*) tables, sizeof(tables)); - tables[0].alias=tables[0].real_name= (char*) "tables_priv"; - tables[1].alias=tables[1].real_name= (char*) "columns_priv"; - tables[0].db=tables[1].db= (char *) "mysql"; - tables[0].next=tables+1; - tables[0].lock_type=tables[1].lock_type=TL_READ; + tables[0].alias= tables[0].table_name= (char*) "tables_priv"; + tables[1].alias= tables[1].table_name= (char*) "columns_priv"; + tables[2].alias= tables[2].table_name= (char*) "procs_priv"; + tables[0].db= tables[1].db= tables[2].db= (char *) "mysql"; + tables[0].next_local= tables[0].next_global= tables+1; + tables[1].next_local= tables[1].next_global= tables+2; + tables[0].lock_type= tables[1].lock_type= tables[2].lock_type= TL_READ; /* To avoid deadlocks we should obtain table locks before @@ -2848,6 +3503,8 @@ my_bool grant_reload(THD *thd) rw_wrlock(&LOCK_grant); grant_version++; old_column_priv_hash= column_priv_hash; + old_proc_priv_hash= proc_priv_hash; + old_func_priv_hash= func_priv_hash; old_grant_option= grant_option; old_mem= memex; @@ -2856,12 +3513,16 @@ my_bool grant_reload(THD *thd) DBUG_PRINT("error",("Reverting to old privileges")); grant_free(); /* purecov: deadcode */ column_priv_hash= old_column_priv_hash; /* purecov: deadcode */ + proc_priv_hash= old_proc_priv_hash; + func_priv_hash= old_func_priv_hash; grant_option= old_grant_option; /* purecov: deadcode */ memex= old_mem; /* purecov: deadcode */ } else { hash_free(&old_column_priv_hash); + hash_free(&old_proc_priv_hash); + hash_free(&old_func_priv_hash); free_root(&old_mem,MYF(0)); } rw_unlock(&LOCK_grant); @@ -2873,32 +3534,94 @@ end: /**************************************************************************** Check table level grants - All errors are written directly to the client if no_errors is given ! + + SYNOPSIS + bool check_grant() + thd Thread handler + want_access Bits of privileges user needs to have + tables List of tables to check. The user should have 'want_access' + to all tables in list. + show_table <> 0 if we are in show table. In this case it's enough to have + any privilege for the table + number Check at most this number of tables. + no_errors If 0 then we write an error. The error is sent directly to + the client + + RETURN + 0 ok + 1 Error: User did not have the requested privileges + + NOTE + This functions assumes that either number of tables to be inspected + by it is limited explicitly (i.e. is is not UINT_MAX) or table list + used and thd->lex->query_tables_own_last value correspond to each + other (the latter should be either 0 or point to next_global member + of one of elements of this table list). ****************************************************************************/ bool check_grant(THD *thd, ulong want_access, TABLE_LIST *tables, uint show_table, uint number, bool no_errors) { - TABLE_LIST *table; - char *user = thd->priv_user; + TABLE_LIST *table, *first_not_own_table= thd->lex->first_not_own_table(); + Security_context *sctx= thd->security_ctx; + uint i; + ulong orig_want_access= want_access; DBUG_ENTER("check_grant"); + DBUG_ASSERT(number > 0); - want_access &= ~thd->master_access; - if (!want_access) - DBUG_RETURN(0); // ok + /* + Walk through the list of tables that belong to the query and save the + requested access (orig_want_privilege) to be able to use it when + checking access rights to the underlying tables of a view. Our grant + system gradually eliminates checked bits from want_privilege and thus + after all checks are done we can no longer use it. + The check that first_not_own_table is not reached is for the case when + the given table list refers to the list for prelocking (contains tables + of other queries). For simple queries first_not_own_table is 0. + */ + for (i= 0, table= tables; + table != first_not_own_table && i < number; + table= table->next_global, i++) + { + /* Remove SHOW_VIEW_ACL, because it will be checked during making view */ + table->grant.orig_want_privilege= (want_access & ~SHOW_VIEW_ACL); + } rw_rdlock(&LOCK_grant); - for (table= tables; table && number--; table= table->next) + for (table= tables; + table && number-- && table != first_not_own_table; + table= table->next_global) { - if (!(~table->grant.privilege & want_access) || table->derived) + GRANT_TABLE *grant_table; + sctx = test(table->security_ctx) ? + table->security_ctx : thd->security_ctx; + + want_access= orig_want_access; + want_access&= ~sctx->master_access; + if (!want_access) + continue; // ok + + if (!(~table->grant.privilege & want_access) || + table->derived || table->schema_table) { - table->grant.want_privilege=0; - continue; // Already checked + /* + It is subquery in the FROM clause. VIEW set table->derived after + table opening, but this function always called before table opening. + */ + if (!table->referencing_view) + { + /* + If it's a temporary table created for a subquery in the FROM + clause, or an INFORMATION_SCHEMA table, drop the request for + a privilege. + */ + table->grant.want_privilege= 0; + } + continue; } - GRANT_TABLE *grant_table = table_hash_search(thd->host,thd->ip, - table->db,user, - table->real_name,0); - if (!grant_table) + if (!(grant_table= table_hash_search(sctx->host, sctx->ip, + table->db, sctx->priv_user, + table->table_name,0))) { want_access &= ~table->grant.privilege; goto err; // No grants @@ -2930,147 +3653,224 @@ err: { char command[128]; get_privilege_desc(command, sizeof(command), want_access); - net_printf(thd,ER_TABLEACCESS_DENIED_ERROR, - command, - thd->priv_user, - thd->host_or_ip, - table ? table->real_name : "unknown"); + my_error(ER_TABLEACCESS_DENIED_ERROR, MYF(0), + command, + sctx->priv_user, + sctx->host_or_ip, + table ? table->table_name : "unknown"); } DBUG_RETURN(1); } -bool check_grant_column(THD *thd,TABLE *table, const char *name, - uint length, uint show_tables) +/* + Check column rights in given security context + + SYNOPSIS + check_grant_column() + thd thread handler + grant grant information structure + db_name db name + table_name table name + name column name + length column name length + sctx security context + + RETURN + FALSE OK + TRUE access denied +*/ + +bool check_grant_column(THD *thd, GRANT_INFO *grant, + const char *db_name, const char *table_name, + const char *name, uint length, Security_context *sctx) { GRANT_TABLE *grant_table; GRANT_COLUMN *grant_column; + ulong want_access= grant->want_privilege & ~grant->privilege; + DBUG_ENTER("check_grant_column"); + DBUG_PRINT("enter", ("table: %s want_access: %u", table_name, want_access)); - ulong want_access=table->grant.want_privilege; if (!want_access) - return 0; // Already checked + DBUG_RETURN(0); // Already checked rw_rdlock(&LOCK_grant); /* reload table if someone has modified any grants */ - if (table->grant.version != grant_version) + if (grant->version != grant_version) { - table->grant.grant_table= - table_hash_search(thd->host, thd->ip, table->table_cache_key, - thd->priv_user, - table->real_name, 0); /* purecov: inspected */ - table->grant.version=grant_version; /* purecov: inspected */ + grant->grant_table= + table_hash_search(sctx->host, sctx->ip, db_name, + sctx->priv_user, + table_name, 0); /* purecov: inspected */ + grant->version= grant_version; /* purecov: inspected */ } - if (!(grant_table=table->grant.grant_table)) + if (!(grant_table= grant->grant_table)) goto err; /* purecov: deadcode */ grant_column=column_hash_search(grant_table, name, length); if (grant_column && !(~grant_column->rights & want_access)) { rw_unlock(&LOCK_grant); - return 0; - } -#ifdef NOT_USED - if (show_tables && (grant_column || table->grant.privilege & COL_ACLS)) - { - rw_unlock(&LOCK_grant); /* purecov: deadcode */ - return 0; /* purecov: deadcode */ + DBUG_RETURN(0); } -#endif - /* We must use my_printf_error() here! */ err: rw_unlock(&LOCK_grant); - if (!show_tables) + char command[128]; + get_privilege_desc(command, sizeof(command), want_access); + my_error(ER_COLUMNACCESS_DENIED_ERROR, MYF(0), + command, + sctx->priv_user, + sctx->host_or_ip, + name, + table_name); + DBUG_RETURN(1); +} + + +/* + Check the access right to a column depending on the type of table. + + SYNOPSIS + check_column_grant_in_table_ref() + thd thread handler + table_ref table reference where to check the field + name name of field to check + length length of name + + DESCRIPTION + Check the access rights to a column depending on the type of table + reference where the column is checked. The function provides a + generic interface to check column access rights that hides the + heterogeneity of the column representation - whether it is a view + or a stored table colum. + + RETURN + FALSE OK + TRUE access denied +*/ + +bool check_column_grant_in_table_ref(THD *thd, TABLE_LIST * table_ref, + const char *name, uint length) +{ + GRANT_INFO *grant; + const char *db_name; + const char *table_name; + Security_context *sctx= test(table_ref->security_ctx) ? + table_ref->security_ctx : thd->security_ctx; + + if (table_ref->view || table_ref->field_translation) { - char command[128]; - get_privilege_desc(command, sizeof(command), want_access); - my_printf_error(ER_COLUMNACCESS_DENIED_ERROR, - ER(ER_COLUMNACCESS_DENIED_ERROR), - MYF(0), - command, - thd->priv_user, - thd->host_or_ip, - name, - table ? table->real_name : "unknown"); + /* View or derived information schema table. */ + ulong view_privs; + grant= &(table_ref->grant); + db_name= table_ref->view_db.str; + table_name= table_ref->view_name.str; + if (table_ref->belong_to_view && + (thd->lex->sql_command == SQLCOM_SHOW_FIELDS || + thd->lex->sql_command == SQLCOM_SHOW_CREATE)) + { + view_privs= get_column_grant(thd, grant, db_name, table_name, name); + if (view_privs & VIEW_ANY_ACL) + { + table_ref->belong_to_view->allowed_show= TRUE; + return FALSE; + } + table_ref->belong_to_view->allowed_show= FALSE; + my_message(ER_VIEW_NO_EXPLAIN, ER(ER_VIEW_NO_EXPLAIN), MYF(0)); + return TRUE; + } } - return 1; + else + { + /* Normal or temporary table. */ + TABLE *table= table_ref->table; + grant= &(table->grant); + db_name= table->s->db; + table_name= table->s->table_name; + } + + if (grant->want_privilege) + return check_grant_column(thd, grant, db_name, table_name, name, + length, sctx); + else + return FALSE; + } -bool check_grant_all_columns(THD *thd, ulong want_access, TABLE *table) +bool check_grant_all_columns(THD *thd, ulong want_access, GRANT_INFO *grant, + const char* db_name, const char *table_name, + Field_iterator *fields) { + Security_context *sctx= thd->security_ctx; GRANT_TABLE *grant_table; GRANT_COLUMN *grant_column; - Field *field=0,**ptr; - want_access &= ~table->grant.privilege; + want_access &= ~grant->privilege; if (!want_access) return 0; // Already checked if (!grant_option) - { - field= table->field[0]; // To give a meaningful error message goto err2; - } rw_rdlock(&LOCK_grant); /* reload table if someone has modified any grants */ - if (table->grant.version != grant_version) + if (grant->version != grant_version) { - table->grant.grant_table= - table_hash_search(thd->host, thd->ip, table->table_cache_key, - thd->priv_user, - table->real_name,0); /* purecov: inspected */ - table->grant.version=grant_version; /* purecov: inspected */ + grant->grant_table= + table_hash_search(sctx->host, sctx->ip, db_name, + sctx->priv_user, + table_name, 0); /* purecov: inspected */ + grant->version= grant_version; /* purecov: inspected */ } /* The following should always be true */ - if (!(grant_table=table->grant.grant_table)) + if (!(grant_table= grant->grant_table)) goto err; /* purecov: inspected */ - for (ptr=table->field; (field= *ptr) ; ptr++) + for (; !fields->end_of_fields(); fields->next()) { - grant_column=column_hash_search(grant_table, field->field_name, - (uint) strlen(field->field_name)); + const char *field_name= fields->name(); + grant_column= column_hash_search(grant_table, field_name, + (uint) strlen(field_name)); if (!grant_column || (~grant_column->rights & want_access)) goto err; } rw_unlock(&LOCK_grant); return 0; - /* We must use my_printf_error() here! */ err: rw_unlock(&LOCK_grant); err2: char command[128]; get_privilege_desc(command, sizeof(command), want_access); - my_printf_error(ER_COLUMNACCESS_DENIED_ERROR, - ER(ER_COLUMNACCESS_DENIED_ERROR), - MYF(0), - command, - thd->priv_user, - thd->host_or_ip, - field ? field->field_name : "unknown", - table->real_name); + my_error(ER_COLUMNACCESS_DENIED_ERROR, MYF(0), + command, + sctx->priv_user, + sctx->host_or_ip, + fields->name(), + table_name); return 1; } /* Check if a user has the right to access a database - Access is accepted if he has a grant for any table in the database + Access is accepted if he has a grant for any table/routine in the database Return 1 if access is denied */ bool check_grant_db(THD *thd,const char *db) { + Security_context *sctx= thd->security_ctx; char helping [NAME_LEN+USERNAME_LENGTH+2]; uint len; - bool error=1; + bool error= 1; - len = (uint) (strmov(strmov(helping,thd->priv_user)+1,db)-helping)+ 1; + len= (uint) (strmov(strmov(helping, sctx->priv_user) + 1, db) - helping) + 1; rw_rdlock(&LOCK_grant); for (uint idx=0 ; idx < column_priv_hash.records ; idx++) @@ -3079,7 +3879,7 @@ bool check_grant_db(THD *thd,const char *db) idx); if (len < grant_table->key_length && !memcmp(grant_table->hash_key,helping,len) && - compare_hostname(&grant_table->host, thd->host, thd->ip)) + compare_hostname(&grant_table->host, sctx->host, sctx->ip)) { error=0; // Found match break; @@ -3089,6 +3889,109 @@ bool check_grant_db(THD *thd,const char *db) return error; } + +/**************************************************************************** + Check routine level grants + + SYNPOSIS + bool check_grant_routine() + thd Thread handler + want_access Bits of privileges user needs to have + procs List of routines to check. The user should have 'want_access' + is_proc True if the list is all procedures, else functions + no_errors If 0 then we write an error. The error is sent directly to + the client + + RETURN + 0 ok + 1 Error: User did not have the requested privielges +****************************************************************************/ + +bool check_grant_routine(THD *thd, ulong want_access, + TABLE_LIST *procs, bool is_proc, bool no_errors) +{ + TABLE_LIST *table; + Security_context *sctx= thd->security_ctx; + char *user= sctx->priv_user; + char *host= sctx->priv_host; + DBUG_ENTER("check_grant_routine"); + + want_access&= ~sctx->master_access; + if (!want_access) + DBUG_RETURN(0); // ok + + rw_rdlock(&LOCK_grant); + for (table= procs; table; table= table->next_global) + { + GRANT_NAME *grant_proc; + if ((grant_proc= routine_hash_search(host, sctx->ip, table->db, user, + table->table_name, is_proc, 0))) + table->grant.privilege|= grant_proc->privs; + + if (want_access & ~table->grant.privilege) + { + want_access &= ~table->grant.privilege; + goto err; + } + } + rw_unlock(&LOCK_grant); + DBUG_RETURN(0); +err: + rw_unlock(&LOCK_grant); + if (!no_errors) + { + char buff[1024]; + const char *command=""; + if (table) + strxmov(buff, table->db, ".", table->table_name, NullS); + if (want_access & EXECUTE_ACL) + command= "execute"; + else if (want_access & ALTER_PROC_ACL) + command= "alter routine"; + else if (want_access & GRANT_ACL) + command= "grant"; + my_error(ER_PROCACCESS_DENIED_ERROR, MYF(0), + command, user, host, table ? buff : "unknown"); + } + DBUG_RETURN(1); +} + + +/* + Check if routine has any of the + routine level grants + + SYNPOSIS + bool check_routine_level_acl() + thd Thread handler + db Database name + name Routine name + + RETURN + 0 Ok + 1 error +*/ + +bool check_routine_level_acl(THD *thd, const char *db, const char *name, + bool is_proc) +{ + bool no_routine_acl= 1; + if (grant_option) + { + GRANT_NAME *grant_proc; + Security_context *sctx= thd->security_ctx; + rw_rdlock(&LOCK_grant); + if ((grant_proc= routine_hash_search(sctx->priv_host, + sctx->ip, db, + sctx->priv_user, + name, is_proc, 0))) + no_routine_acl= !(grant_proc->privs & SHOW_PROC_ACLS); + rw_unlock(&LOCK_grant); + } + return no_routine_acl; +} + + /***************************************************************************** Functions to retrieve the grant for a table/column (for SHOW functions) *****************************************************************************/ @@ -3096,7 +3999,7 @@ bool check_grant_db(THD *thd,const char *db) ulong get_table_grant(THD *thd, TABLE_LIST *table) { ulong privilege; - char *user = thd->priv_user; + Security_context *sctx= thd->security_ctx; const char *db = table->db ? table->db : thd->db; GRANT_TABLE *grant_table; @@ -3104,8 +4007,8 @@ ulong get_table_grant(THD *thd, TABLE_LIST *table) #ifdef EMBEDDED_LIBRARY grant_table= NULL; #else - grant_table= table_hash_search(thd->host, thd->ip, db, user, - table->real_name, 0); + grant_table= table_hash_search(sctx->host, sctx->ip, db, sctx->priv_user, + table->table_name, 0); #endif table->grant.grant_table=grant_table; // Remember for column test table->grant.version=grant_version; @@ -3117,7 +4020,27 @@ ulong get_table_grant(THD *thd, TABLE_LIST *table) } -ulong get_column_grant(THD *thd, TABLE_LIST *table, Field *field) +/* + Determine the access priviliges for a field. + + SYNOPSIS + get_column_grant() + thd thread handler + grant grants table descriptor + db_name name of database that the field belongs to + table_name name of table that the field belongs to + field_name name of field + + DESCRIPTION + The procedure may also modify: grant->grant_table and grant->version. + + RETURN + The access priviliges for the field db_name.table_name.field_name +*/ + +ulong get_column_grant(THD *thd, GRANT_INFO *grant, + const char *db_name, const char *table_name, + const char *field_name) { GRANT_TABLE *grant_table; GRANT_COLUMN *grant_column; @@ -3125,30 +4048,32 @@ ulong get_column_grant(THD *thd, TABLE_LIST *table, Field *field) rw_rdlock(&LOCK_grant); /* reload table if someone has modified any grants */ - if (table->grant.version != grant_version) + if (grant->version != grant_version) { - table->grant.grant_table= - table_hash_search(thd->host, thd->ip, table->db, - thd->priv_user, - table->real_name,0); /* purecov: inspected */ - table->grant.version=grant_version; /* purecov: inspected */ + Security_context *sctx= thd->security_ctx; + grant->grant_table= + table_hash_search(sctx->host, sctx->ip, + db_name, sctx->priv_user, + table_name, 0); /* purecov: inspected */ + grant->version= grant_version; /* purecov: inspected */ } - if (!(grant_table=table->grant.grant_table)) - priv=table->grant.privilege; + if (!(grant_table= grant->grant_table)) + priv= grant->privilege; else { - grant_column=column_hash_search(grant_table, field->field_name, - (uint) strlen(field->field_name)); + grant_column= column_hash_search(grant_table, field_name, + (uint) strlen(field_name)); if (!grant_column) - priv=table->grant.privilege; + priv= (grant->privilege | grant_table->privs); else - priv=table->grant.privilege | grant_column->rights; + priv= (grant->privilege | grant_table->privs | grant_column->rights); } rw_unlock(&LOCK_grant); return priv; } + /* Help function for mysql_show_grants */ static void add_user_option(String *grant, ulong value, const char *name) @@ -3166,18 +4091,26 @@ static void add_user_option(String *grant, ulong value, const char *name) static const char *command_array[]= { - "SELECT", "INSERT","UPDATE","DELETE","CREATE", "DROP", "RELOAD","SHUTDOWN", - "PROCESS","FILE","GRANT","REFERENCES","INDEX", "ALTER", "SHOW DATABASES", - "SUPER", "CREATE TEMPORARY TABLES", "LOCK TABLES", "EXECUTE", - "REPLICATION SLAVE", "REPLICATION CLIENT", + "SELECT", "INSERT", "UPDATE", "DELETE", "CREATE", "DROP", "RELOAD", + "SHUTDOWN", "PROCESS","FILE", "GRANT", "REFERENCES", "INDEX", + "ALTER", "SHOW DATABASES", "SUPER", "CREATE TEMPORARY TABLES", + "LOCK TABLES", "EXECUTE", "REPLICATION SLAVE", "REPLICATION CLIENT", + "CREATE VIEW", "SHOW VIEW", "CREATE ROUTINE", "ALTER ROUTINE", + "CREATE USER" }; static uint command_lengths[]= { - 6,6,6,6,6,4,6,8,7,4,5,10,5,5,14,5,23,11,7,17,18 + 6, 6, 6, 6, 6, 4, 6, 8, 7, 4, 5, 10, 5, 5, 14, 5, 23, 11, 7, 17, 18, 11, 9, + 14, 13, 11 }; +static int show_routine_grants(THD *thd, LEX_USER *lex_user, HASH *hash, + const char *type, int typelen, + char *buff, int buffsize); + + /* SHOW GRANTS; Send grants for a user to the client @@ -3185,7 +4118,7 @@ static uint command_lengths[]= Send to client grant-like strings depicting user@host privileges */ -int mysql_show_grants(THD *thd,LEX_USER *lex_user) +bool mysql_show_grants(THD *thd,LEX_USER *lex_user) { ulong want_access; uint counter,index; @@ -3200,44 +4133,21 @@ int mysql_show_grants(THD *thd,LEX_USER *lex_user) if (!initialized) { my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--skip-grant-tables"); - DBUG_RETURN(-1); - } - - if (!lex_user->host.str) - { - lex_user->host.str= (char*) "%"; - lex_user->host.length=1; - } - if (lex_user->host.length > HOSTNAME_LENGTH || - lex_user->user.length > USERNAME_LENGTH) - { - my_error(ER_GRANT_WRONG_HOST_OR_USER,MYF(0)); - DBUG_RETURN(-1); + DBUG_RETURN(TRUE); } rw_rdlock(&LOCK_grant); VOID(pthread_mutex_lock(&acl_cache->lock)); - for (counter=0 ; counter < acl_users.elements ; counter++) - { - const char *user,*host; - acl_user=dynamic_element(&acl_users,counter,ACL_USER*); - if (!(user=acl_user->user)) - user= ""; - if (!(host=acl_user->host.hostname)) - host= ""; - if (!strcmp(lex_user->user.str,user) && - !my_strcasecmp(system_charset_info, lex_user->host.str, host)) - break; - } - if (counter == acl_users.elements) + acl_user= find_acl_user(lex_user->host.str, lex_user->user.str, TRUE); + if (!acl_user) { VOID(pthread_mutex_unlock(&acl_cache->lock)); rw_unlock(&LOCK_grant); my_error(ER_NONEXISTING_GRANT, MYF(0), lex_user->user.str, lex_user->host.str); - DBUG_RETURN(-1); + DBUG_RETURN(TRUE); } Item_string *field=new Item_string("",0,&my_charset_latin1); @@ -3247,25 +4157,26 @@ int mysql_show_grants(THD *thd,LEX_USER *lex_user) strxmov(buff,"Grants for ",lex_user->user.str,"@", lex_user->host.str,NullS); field_list.push_back(field); - if (protocol->send_fields(&field_list,1)) + if (protocol->send_fields(&field_list, + Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)) { VOID(pthread_mutex_unlock(&acl_cache->lock)); rw_unlock(&LOCK_grant); - DBUG_RETURN(-1); + DBUG_RETURN(TRUE); } /* Add first global access grants */ { String global(buff,sizeof(buff),system_charset_info); global.length(0); - global.append("GRANT ",6); + global.append(STRING_WITH_LEN("GRANT ")); want_access= acl_user->access; if (test_all_bits(want_access, (GLOBAL_ACLS & ~ GRANT_ACL))) - global.append("ALL PRIVILEGES",14); + global.append(STRING_WITH_LEN("ALL PRIVILEGES")); else if (!(want_access & ~GRANT_ACL)) - global.append("USAGE",5); + global.append(STRING_WITH_LEN("USAGE")); else { bool found=0; @@ -3275,17 +4186,18 @@ int mysql_show_grants(THD *thd,LEX_USER *lex_user) if (test_access & j) { if (found) - global.append(", ",2); + global.append(STRING_WITH_LEN(", ")); found=1; global.append(command_array[counter],command_lengths[counter]); } } } - global.append (" ON *.* TO '",12); + global.append (STRING_WITH_LEN(" ON *.* TO '")); global.append(lex_user->user.str, lex_user->user.length, system_charset_info); - global.append ("'@'",3); - global.append(lex_user->host.str,lex_user->host.length); + global.append (STRING_WITH_LEN("'@'")); + global.append(lex_user->host.str,lex_user->host.length, + system_charset_info); global.append ('\''); if (acl_user->salt_len) { @@ -3294,23 +4206,23 @@ int mysql_show_grants(THD *thd,LEX_USER *lex_user) make_password_from_salt(passwd_buff, acl_user->salt); else make_password_from_salt_323(passwd_buff, (ulong *) acl_user->salt); - global.append(" IDENTIFIED BY PASSWORD '",25); + global.append(STRING_WITH_LEN(" IDENTIFIED BY PASSWORD '")); global.append(passwd_buff); global.append('\''); } /* "show grants" SSL related stuff */ if (acl_user->ssl_type == SSL_TYPE_ANY) - global.append(" REQUIRE SSL",12); + global.append(STRING_WITH_LEN(" REQUIRE SSL")); else if (acl_user->ssl_type == SSL_TYPE_X509) - global.append(" REQUIRE X509",13); + global.append(STRING_WITH_LEN(" REQUIRE X509")); else if (acl_user->ssl_type == SSL_TYPE_SPECIFIED) { int ssl_options = 0; - global.append(" REQUIRE ",9); + global.append(STRING_WITH_LEN(" REQUIRE ")); if (acl_user->x509_issuer) { ssl_options++; - global.append("ISSUER \'",8); + global.append(STRING_WITH_LEN("ISSUER \'")); global.append(acl_user->x509_issuer,strlen(acl_user->x509_issuer)); global.append('\''); } @@ -3318,32 +4230,38 @@ int mysql_show_grants(THD *thd,LEX_USER *lex_user) { if (ssl_options++) global.append(' '); - global.append("SUBJECT \'",9); - global.append(acl_user->x509_subject,strlen(acl_user->x509_subject)); + global.append(STRING_WITH_LEN("SUBJECT \'")); + global.append(acl_user->x509_subject,strlen(acl_user->x509_subject), + system_charset_info); global.append('\''); } if (acl_user->ssl_cipher) { if (ssl_options++) global.append(' '); - global.append("CIPHER '",8); - global.append(acl_user->ssl_cipher,strlen(acl_user->ssl_cipher)); + global.append(STRING_WITH_LEN("CIPHER '")); + global.append(acl_user->ssl_cipher,strlen(acl_user->ssl_cipher), + system_charset_info); global.append('\''); } } if ((want_access & GRANT_ACL) || - (acl_user->user_resource.questions | acl_user->user_resource.updates | - acl_user->user_resource.connections)) + (acl_user->user_resource.questions || + acl_user->user_resource.updates || + acl_user->user_resource.conn_per_hour || + acl_user->user_resource.user_conn)) { - global.append(" WITH",5); + global.append(STRING_WITH_LEN(" WITH")); if (want_access & GRANT_ACL) - global.append(" GRANT OPTION",13); + global.append(STRING_WITH_LEN(" GRANT OPTION")); add_user_option(&global, acl_user->user_resource.questions, "MAX_QUERIES_PER_HOUR"); add_user_option(&global, acl_user->user_resource.updates, "MAX_UPDATES_PER_HOUR"); - add_user_option(&global, acl_user->user_resource.connections, + add_user_option(&global, acl_user->user_resource.conn_per_hour, "MAX_CONNECTIONS_PER_HOUR"); + add_user_option(&global, acl_user->user_resource.user_conn, + "MAX_USER_CONNECTIONS"); } protocol->prepare_for_resend(); protocol->store(global.ptr(),global.length(),global.charset()); @@ -3373,12 +4291,12 @@ int mysql_show_grants(THD *thd,LEX_USER *lex_user) { String db(buff,sizeof(buff),system_charset_info); db.length(0); - db.append("GRANT ",6); + db.append(STRING_WITH_LEN("GRANT ")); if (test_all_bits(want_access,(DB_ACLS & ~GRANT_ACL))) - db.append("ALL PRIVILEGES",14); + db.append(STRING_WITH_LEN("ALL PRIVILEGES")); else if (!(want_access & ~GRANT_ACL)) - db.append("USAGE",5); + db.append(STRING_WITH_LEN("USAGE")); else { int found=0, cnt; @@ -3388,22 +4306,23 @@ int mysql_show_grants(THD *thd,LEX_USER *lex_user) if (test_access & j) { if (found) - db.append(", ",2); + db.append(STRING_WITH_LEN(", ")); found = 1; db.append(command_array[cnt],command_lengths[cnt]); } } } - db.append (" ON ",4); + db.append (STRING_WITH_LEN(" ON ")); append_identifier(thd, &db, acl_db->db, strlen(acl_db->db)); - db.append (".* TO '",7); + db.append (STRING_WITH_LEN(".* TO '")); db.append(lex_user->user.str, lex_user->user.length, system_charset_info); - db.append ("'@'",3); - db.append(lex_user->host.str, lex_user->host.length); + db.append (STRING_WITH_LEN("'@'")); + db.append(lex_user->host.str, lex_user->host.length, + system_charset_info); db.append ('\''); if (want_access & GRANT_ACL) - db.append(" WITH GRANT OPTION",18); + db.append(STRING_WITH_LEN(" WITH GRANT OPTION")); protocol->prepare_for_resend(); protocol->store(db.ptr(),db.length(),db.charset()); if (protocol->write()) @@ -3418,16 +4337,17 @@ int mysql_show_grants(THD *thd,LEX_USER *lex_user) /* Add table & column access */ for (index=0 ; index < column_priv_hash.records ; index++) { - const char *user; + const char *user, *host; GRANT_TABLE *grant_table= (GRANT_TABLE*) hash_element(&column_priv_hash, index); if (!(user=grant_table->user)) user= ""; + if (!(host= grant_table->host.hostname)) + host= ""; if (!strcmp(lex_user->user.str,user) && - !my_strcasecmp(system_charset_info, lex_user->host.str, - grant_table->host.hostname)) + !my_strcasecmp(system_charset_info, lex_user->host.str, host)) { ulong table_access= grant_table->privs; if ((table_access | grant_table->cols) != 0) @@ -3436,12 +4356,12 @@ int mysql_show_grants(THD *thd,LEX_USER *lex_user) ulong test_access= (table_access | grant_table->cols) & ~GRANT_ACL; global.length(0); - global.append("GRANT ",6); + global.append(STRING_WITH_LEN("GRANT ")); if (test_all_bits(table_access, (TABLE_ACLS & ~GRANT_ACL))) - global.append("ALL PRIVILEGES",14); + global.append(STRING_WITH_LEN("ALL PRIVILEGES")); else if (!test_access) - global.append("USAGE",5); + global.append(STRING_WITH_LEN("USAGE")); else { /* Add specific column access */ @@ -3453,7 +4373,7 @@ int mysql_show_grants(THD *thd,LEX_USER *lex_user) if (test_access & j) { if (found) - global.append(", ",2); + global.append(STRING_WITH_LEN(", ")); found= 1; global.append(command_array[counter],command_lengths[counter]); @@ -3477,14 +4397,14 @@ int mysql_show_grants(THD *thd,LEX_USER *lex_user) */ if (table_access & j) { - global.append(", ", 2); + global.append(STRING_WITH_LEN(", ")); global.append(command_array[counter], command_lengths[counter]); } - global.append(" (",2); + global.append(STRING_WITH_LEN(" (")); } else - global.append(", ",2); + global.append(STRING_WITH_LEN(", ")); global.append(grant_column->column, grant_column->key_length, system_charset_info); @@ -3496,20 +4416,21 @@ int mysql_show_grants(THD *thd,LEX_USER *lex_user) } } } - global.append(" ON ",4); + global.append(STRING_WITH_LEN(" ON ")); append_identifier(thd, &global, grant_table->db, strlen(grant_table->db)); global.append('.'); append_identifier(thd, &global, grant_table->tname, strlen(grant_table->tname)); - global.append(" TO '",5); + global.append(STRING_WITH_LEN(" TO '")); global.append(lex_user->user.str, lex_user->user.length, system_charset_info); - global.append("'@'",3); - global.append(lex_user->host.str,lex_user->host.length); + global.append(STRING_WITH_LEN("'@'")); + global.append(lex_user->host.str,lex_user->host.length, + system_charset_info); global.append('\''); if (table_access & GRANT_ACL) - global.append(" WITH GRANT OPTION",18); + global.append(STRING_WITH_LEN(" WITH GRANT OPTION")); protocol->prepare_for_resend(); protocol->store(global.ptr(),global.length(),global.charset()); if (protocol->write()) @@ -3520,6 +4441,21 @@ int mysql_show_grants(THD *thd,LEX_USER *lex_user) } } } + + if (show_routine_grants(thd, lex_user, &proc_priv_hash, + STRING_WITH_LEN("PROCEDURE"), buff, sizeof(buff))) + { + error= -1; + goto end; + } + + if (show_routine_grants(thd, lex_user, &func_priv_hash, + STRING_WITH_LEN("FUNCTION"), buff, sizeof(buff))) + { + error= -1; + goto end; + } + end: VOID(pthread_mutex_unlock(&acl_cache->lock)); rw_unlock(&LOCK_grant); @@ -3528,6 +4464,84 @@ end: DBUG_RETURN(error); } +static int show_routine_grants(THD* thd, LEX_USER *lex_user, HASH *hash, + const char *type, int typelen, + char *buff, int buffsize) +{ + uint counter, index; + int error= 0; + Protocol *protocol= thd->protocol; + /* Add routine access */ + for (index=0 ; index < hash->records ; index++) + { + const char *user, *host; + GRANT_NAME *grant_proc= (GRANT_NAME*) hash_element(hash, index); + + if (!(user=grant_proc->user)) + user= ""; + if (!(host= grant_proc->host.hostname)) + host= ""; + + if (!strcmp(lex_user->user.str,user) && + !my_strcasecmp(system_charset_info, lex_user->host.str, host)) + { + ulong proc_access= grant_proc->privs; + if (proc_access != 0) + { + String global(buff, buffsize, system_charset_info); + ulong test_access= proc_access & ~GRANT_ACL; + + global.length(0); + global.append(STRING_WITH_LEN("GRANT ")); + + if (!test_access) + global.append(STRING_WITH_LEN("USAGE")); + else + { + /* Add specific procedure access */ + int found= 0; + ulong j; + + for (counter= 0, j= SELECT_ACL; j <= PROC_ACLS; counter++, j<<= 1) + { + if (test_access & j) + { + if (found) + global.append(STRING_WITH_LEN(", ")); + found= 1; + global.append(command_array[counter],command_lengths[counter]); + } + } + } + global.append(STRING_WITH_LEN(" ON ")); + global.append(type,typelen); + global.append(' '); + append_identifier(thd, &global, grant_proc->db, + strlen(grant_proc->db)); + global.append('.'); + append_identifier(thd, &global, grant_proc->tname, + strlen(grant_proc->tname)); + global.append(STRING_WITH_LEN(" TO '")); + global.append(lex_user->user.str, lex_user->user.length, + system_charset_info); + global.append(STRING_WITH_LEN("'@'")); + global.append(lex_user->host.str,lex_user->host.length, + system_charset_info); + global.append('\''); + if (proc_access & GRANT_ACL) + global.append(STRING_WITH_LEN(" WITH GRANT OPTION")); + protocol->prepare_for_resend(); + protocol->store(global.ptr(),global.length(),global.charset()); + if (protocol->write()) + { + error= -1; + break; + } + } + } + } + return error; +} /* Make a clear-text version of the requested privilege. @@ -3571,28 +4585,53 @@ void get_mqh(const char *user, const char *host, USER_CONN *uc) pthread_mutex_unlock(&acl_cache->lock); } +/* + Open the grant tables. + + SYNOPSIS + open_grant_tables() + thd The current thread. + tables (out) The 4 elements array for the opened tables. + + DESCRIPTION + Tables are numbered as follows: + 0 user + 1 db + 2 tables_priv + 3 columns_priv + + RETURN + 1 Skip GRANT handling during replication. + 0 OK. + < 0 Error. +*/ + +#define GRANT_TABLES 5 int open_grant_tables(THD *thd, TABLE_LIST *tables) { DBUG_ENTER("open_grant_tables"); if (!initialized) { - net_printf(thd,ER_OPTION_PREVENTS_STATEMENT, "--skip-grant-tables"); + my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--skip-grant-tables"); DBUG_RETURN(-1); } - bzero((char*) tables, 4*sizeof(*tables)); - tables->alias= tables->real_name= (char*) "user"; - (tables+1)->alias= (tables+1)->real_name= (char*) "db"; - (tables+2)->alias= (tables+2)->real_name= (char*) "tables_priv"; - (tables+3)->alias= (tables+3)->real_name= (char*) "columns_priv"; - tables->next= tables+1; - (tables+1)->next= tables+2; - (tables+2)->next= tables+3; - (tables+3)->next= 0; + bzero((char*) tables, GRANT_TABLES*sizeof(*tables)); + tables->alias= tables->table_name= (char*) "user"; + (tables+1)->alias= (tables+1)->table_name= (char*) "db"; + (tables+2)->alias= (tables+2)->table_name= (char*) "tables_priv"; + (tables+3)->alias= (tables+3)->table_name= (char*) "columns_priv"; + (tables+4)->alias= (tables+4)->table_name= (char*) "procs_priv"; + tables->next_local= tables->next_global= tables+1; + (tables+1)->next_local= (tables+1)->next_global= tables+2; + (tables+2)->next_local= (tables+2)->next_global= tables+3; + (tables+3)->next_local= (tables+3)->next_global= tables+4; tables->lock_type= (tables+1)->lock_type= - (tables+2)->lock_type= (tables+3)->lock_type= TL_WRITE; - tables->db= (tables+1)->db= (tables+2)->db= (tables+3)->db=(char*) "mysql"; + (tables+2)->lock_type= (tables+3)->lock_type= + (tables+4)->lock_type= TL_WRITE; + tables->db= (tables+1)->db= (tables+2)->db= + (tables+3)->db= (tables+4)->db= (char*) "mysql"; #ifdef HAVE_REPLICATION /* @@ -3605,10 +4644,12 @@ int open_grant_tables(THD *thd, TABLE_LIST *tables) The tables must be marked "updating" so that tables_ok() takes them into account in tests. */ - tables[0].updating=tables[1].updating=tables[2].updating=tables[3].updating=1; - if (!tables_ok(0, tables)) + tables[0].updating=tables[1].updating=tables[2].updating= + tables[3].updating=tables[4].updating=1; + if (!tables_ok(thd, tables)) DBUG_RETURN(1); - tables[0].updating=tables[1].updating=tables[2].updating=tables[3].updating=0; + tables[0].updating=tables[1].updating=tables[2].updating= + tables[3].updating=tables[4].updating=0;; } #endif @@ -3648,145 +4689,746 @@ ACL_USER *check_acl_user(LEX_USER *user_name, return acl_user; } +/* + Modify a privilege table. + + SYNOPSIS + modify_grant_table() + table The table to modify. + host_field The host name field. + user_field The user name field. + user_to The new name for the user if to be renamed, + NULL otherwise. -int mysql_drop_user(THD *thd, List <LEX_USER> &list) + DESCRIPTION + Update user/host in the current record if user_to is not NULL. + Delete the current record if user_to is NULL. + + RETURN + 0 OK. + != 0 Error. +*/ + +static int modify_grant_table(TABLE *table, Field *host_field, + Field *user_field, LEX_USER *user_to) { - uint counter, acl_userd; - int result; - ACL_USER *acl_user; - ACL_DB *acl_db; - TABLE_LIST tables[4]; + int error; + DBUG_ENTER("modify_grant_table"); - DBUG_ENTER("mysql_drop_user"); + if (user_to) + { + /* rename */ + store_record(table, record[1]); + host_field->store(user_to->host.str, user_to->host.length, + system_charset_info); + user_field->store(user_to->user.str, user_to->user.length, + system_charset_info); + if ((error= table->file->update_row(table->record[1], table->record[0]))) + table->file->print_error(error, MYF(0)); + } + else + { + /* delete */ + if ((error=table->file->delete_row(table->record[0]))) + table->file->print_error(error, MYF(0)); + } - if ((result= open_grant_tables(thd, tables))) - DBUG_RETURN(result == 1 ? 0 : 1); + DBUG_RETURN(error); +} - rw_wrlock(&LOCK_grant); - VOID(pthread_mutex_lock(&acl_cache->lock)); +/* + Handle a privilege table. - LEX_USER *user_name; - List_iterator <LEX_USER> user_list(list); - while ((user_name=user_list++)) + SYNOPSIS + handle_grant_table() + tables The array with the four open tables. + table_no The number of the table to handle (0..4). + drop If user_from is to be dropped. + user_from The the user to be searched/dropped/renamed. + user_to The new name for the user if to be renamed, + NULL otherwise. + + DESCRIPTION + Scan through all records in a grant table and apply the requested + operation. For the "user" table, a single index access is sufficient, + since there is an unique index on (host, user). + Delete from grant table if drop is true. + Update in grant table if drop is false and user_to is not NULL. + Search in grant table if drop is false and user_to is NULL. + Tables are numbered as follows: + 0 user + 1 db + 2 tables_priv + 3 columns_priv + 4 procs_priv + + RETURN + > 0 At least one record matched. + 0 OK, but no record matched. + < 0 Error. +*/ + +static int handle_grant_table(TABLE_LIST *tables, uint table_no, bool drop, + LEX_USER *user_from, LEX_USER *user_to) +{ + int result= 0; + int error; + TABLE *table= tables[table_no].table; + Field *host_field= table->field[0]; + Field *user_field= table->field[table_no ? 2 : 1]; + char *host_str= user_from->host.str; + char *user_str= user_from->user.str; + const char *host; + const char *user; + byte user_key[MAX_KEY_LENGTH]; + uint key_prefix_length; + DBUG_ENTER("handle_grant_table"); + + if (! table_no) // mysql.user table { - if (!(acl_user= check_acl_user(user_name, &counter))) + /* + The 'user' table has an unique index on (host, user). + Thus, we can handle everything with a single index access. + The host- and user fields are consecutive in the user table records. + So we set host- and user fields of table->record[0] and use the + pointer to the host field as key. + index_read_idx() will replace table->record[0] (its first argument) + by the searched record, if it exists. + */ + DBUG_PRINT("info",("read table: '%s' search: '%s'@'%s'", + table->s->table_name, user_str, host_str)); + host_field->store(host_str, user_from->host.length, system_charset_info); + user_field->store(user_str, user_from->user.length, system_charset_info); + + key_prefix_length= (table->key_info->key_part[0].store_length + + table->key_info->key_part[1].store_length); + key_copy(user_key, table->record[0], table->key_info, key_prefix_length); + + if ((error= table->file->index_read_idx(table->record[0], 0, + user_key, key_prefix_length, + HA_READ_KEY_EXACT))) { - result= -1; - continue; + if (error != HA_ERR_KEY_NOT_FOUND) + { + table->file->print_error(error, MYF(0)); + result= -1; + } } - if ((acl_user->access & ~0)) + else + { + /* If requested, delete or update the record. */ + result= ((drop || user_to) && + modify_grant_table(table, host_field, user_field, user_to)) ? + -1 : 1; /* Error or found. */ + } + DBUG_PRINT("info",("read result: %d", result)); + } + else + { + /* + The non-'user' table do not have indexes on (host, user). + And their host- and user fields are not consecutive. + Thus, we need to do a table scan to find all matching records. + */ + if ((error= table->file->ha_rnd_init(1))) { + table->file->print_error(error, MYF(0)); result= -1; - continue; } - acl_userd= counter; + else + { +#ifdef EXTRA_DEBUG + DBUG_PRINT("info",("scan table: '%s' search: '%s'@'%s'", + table->s->table_name, user_str, host_str)); +#endif + while ((error= table->file->rnd_next(table->record[0])) != + HA_ERR_END_OF_FILE) + { + if (error) + { + /* Most probable 'deleted record'. */ + DBUG_PRINT("info",("scan error: %d", error)); + continue; + } + if (! (host= get_field(&mem, host_field))) + host= ""; + if (! (user= get_field(&mem, user_field))) + user= ""; + +#ifdef EXTRA_DEBUG + DBUG_PRINT("loop",("scan fields: '%s'@'%s' '%s' '%s' '%s'", + user, host, + get_field(&mem, table->field[1]) /*db*/, + get_field(&mem, table->field[3]) /*table*/, + get_field(&mem, table->field[4]) /*column*/)); +#endif + if (strcmp(user_str, user) || + my_strcasecmp(system_charset_info, host_str, host)) + continue; + + /* If requested, delete or update the record. */ + result= ((drop || user_to) && + modify_grant_table(table, host_field, user_field, user_to)) ? + -1 : result ? result : 1; /* Error or keep result or found. */ + /* If search is requested, we do not need to search further. */ + if (! drop && ! user_to) + break ; + } + (void) table->file->ha_rnd_end(); + DBUG_PRINT("info",("scan result: %d", result)); + } + } + + DBUG_RETURN(result); +} + + +/* + Handle an in-memory privilege structure. + + SYNOPSIS + handle_grant_struct() + struct_no The number of the structure to handle (0..3). + drop If user_from is to be dropped. + user_from The the user to be searched/dropped/renamed. + user_to The new name for the user if to be renamed, + NULL otherwise. + + DESCRIPTION + Scan through all elements in an in-memory grant structure and apply + the requested operation. + Delete from grant structure if drop is true. + Update in grant structure if drop is false and user_to is not NULL. + Search in grant structure if drop is false and user_to is NULL. + Structures are numbered as follows: + 0 acl_users + 1 acl_dbs + 2 column_priv_hash + 3 procs_priv_hash + + RETURN + > 0 At least one element matched. + 0 OK, but no element matched. + -1 Wrong arguments to function +*/ + +static int handle_grant_struct(uint struct_no, bool drop, + LEX_USER *user_from, LEX_USER *user_to) +{ + int result= 0; + uint idx; + uint elements; + const char *user; + const char *host; + ACL_USER *acl_user; + ACL_DB *acl_db; + GRANT_NAME *grant_name; + DBUG_ENTER("handle_grant_struct"); + DBUG_PRINT("info",("scan struct: %u search: '%s'@'%s'", + struct_no, user_from->user.str, user_from->host.str)); + + LINT_INIT(acl_user); + LINT_INIT(acl_db); + LINT_INIT(grant_name); + + safe_mutex_assert_owner(&acl_cache->lock); + + /* Get the number of elements in the in-memory structure. */ + switch (struct_no) { + case 0: + elements= acl_users.elements; + break; + case 1: + elements= acl_dbs.elements; + break; + case 2: + elements= column_priv_hash.records; + break; + case 3: + elements= proc_priv_hash.records; + break; + default: + return -1; + } + +#ifdef EXTRA_DEBUG + DBUG_PRINT("loop",("scan struct: %u search user: '%s' host: '%s'", + struct_no, user_from->user.str, user_from->host.str)); +#endif + /* Loop over all elements. */ + for (idx= 0; idx < elements; idx++) + { + /* + Get a pointer to the element. + */ + switch (struct_no) { + case 0: + acl_user= dynamic_element(&acl_users, idx, ACL_USER*); + user= acl_user->user; + host= acl_user->host.hostname; + break; + + case 1: + acl_db= dynamic_element(&acl_dbs, idx, ACL_DB*); + user= acl_db->user; + host= acl_db->host.hostname; + break; + + case 2: + grant_name= (GRANT_NAME*) hash_element(&column_priv_hash, idx); + user= grant_name->user; + host= grant_name->host.hostname; + break; + + case 3: + grant_name= (GRANT_NAME*) hash_element(&proc_priv_hash, idx); + user= grant_name->user; + host= grant_name->host.hostname; + break; + } + if (! user) + user= ""; + if (! host) + host= ""; + +#ifdef EXTRA_DEBUG + DBUG_PRINT("loop",("scan struct: %u index: %u user: '%s' host: '%s'", + struct_no, idx, user, host)); +#endif + if (strcmp(user_from->user.str, user) || + my_strcasecmp(system_charset_info, user_from->host.str, host)) + continue; - for (counter= 0 ; counter < acl_dbs.elements ; counter++) + result= 1; /* At least one element found. */ + if ( drop ) { - const char *user,*host; - acl_db=dynamic_element(&acl_dbs,counter,ACL_DB*); - if (!(user= acl_db->user)) - user= ""; - if (!(host= acl_db->host.hostname)) - host= ""; + switch ( struct_no ) + { + case 0: + delete_dynamic_element(&acl_users, idx); + break; + + case 1: + delete_dynamic_element(&acl_dbs, idx); + break; - if (!strcmp(user_name->user.str,user) && - !my_strcasecmp(system_charset_info, user_name->host.str, host)) + case 2: + hash_delete(&column_priv_hash, (byte*) grant_name); break; + + case 3: + hash_delete(&proc_priv_hash, (byte*) grant_name); + break; + } + elements--; + idx--; } - if (counter != acl_dbs.elements) + else if ( user_to ) { - result= -1; - continue; + switch ( struct_no ) { + case 0: + acl_user->user= strdup_root(&mem, user_to->user.str); + acl_user->host.hostname= strdup_root(&mem, user_to->host.str); + break; + + case 1: + acl_db->user= strdup_root(&mem, user_to->user.str); + acl_db->host.hostname= strdup_root(&mem, user_to->host.str); + break; + + case 2: + case 3: + grant_name->user= strdup_root(&mem, user_to->user.str); + update_hostname(&grant_name->host, + strdup_root(&mem, user_to->host.str)); + break; + } } + else + { + /* If search is requested, we do not need to search further. */ + break; + } + } +#ifdef EXTRA_DEBUG + DBUG_PRINT("loop",("scan struct: %u result %d", struct_no, result)); +#endif + + DBUG_RETURN(result); +} + + +/* + Handle all privilege tables and in-memory privilege structures. - for (counter= 0 ; counter < column_priv_hash.records ; counter++) + SYNOPSIS + handle_grant_data() + tables The array with the four open tables. + drop If user_from is to be dropped. + user_from The the user to be searched/dropped/renamed. + user_to The new name for the user if to be renamed, + NULL otherwise. + + DESCRIPTION + Go through all grant tables and in-memory grant structures and apply + the requested operation. + Delete from grant data if drop is true. + Update in grant data if drop is false and user_to is not NULL. + Search in grant data if drop is false and user_to is NULL. + + RETURN + > 0 At least one element matched. + 0 OK, but no element matched. + < 0 Error. +*/ + +static int handle_grant_data(TABLE_LIST *tables, bool drop, + LEX_USER *user_from, LEX_USER *user_to) +{ + int result= 0; + int found; + DBUG_ENTER("handle_grant_data"); + + /* Handle user table. */ + if ((found= handle_grant_table(tables, 0, drop, user_from, user_to)) < 0) + { + /* Handle of table failed, don't touch the in-memory array. */ + result= -1; + } + else + { + /* Handle user array. */ + if ((handle_grant_struct(0, drop, user_from, user_to) && ! result) || + found) { - const char *user,*host; - GRANT_TABLE *grant_table= (GRANT_TABLE*) hash_element(&column_priv_hash, - counter); - if (!(user=grant_table->user)) - user= ""; - if (!(host=grant_table->host.hostname)) - host= ""; + result= 1; /* At least one record/element found. */ + /* If search is requested, we do not need to search further. */ + if (! drop && ! user_to) + goto end; + } + } - if (!strcmp(user_name->user.str,user) && - !my_strcasecmp(system_charset_info, user_name->host.str, host)) - break; + /* Handle db table. */ + if ((found= handle_grant_table(tables, 1, drop, user_from, user_to)) < 0) + { + /* Handle of table failed, don't touch the in-memory array. */ + result= -1; + } + else + { + /* Handle db array. */ + if (((handle_grant_struct(1, drop, user_from, user_to) && ! result) || + found) && ! result) + { + result= 1; /* At least one record/element found. */ + /* If search is requested, we do not need to search further. */ + if (! drop && ! user_to) + goto end; } - if (counter != column_priv_hash.records) + } + + /* Handle procedures table. */ + if ((found= handle_grant_table(tables, 4, drop, user_from, user_to)) < 0) + { + /* Handle of table failed, don't touch in-memory array. */ + result= -1; + } + else + { + /* Handle procs array. */ + if (((handle_grant_struct(3, drop, user_from, user_to) && ! result) || + found) && ! result) + { + result= 1; /* At least one record/element found. */ + /* If search is requested, we do not need to search further. */ + if (! drop && ! user_to) + goto end; + } + } + + /* Handle tables table. */ + if ((found= handle_grant_table(tables, 2, drop, user_from, user_to)) < 0) + { + /* Handle of table failed, don't touch columns and in-memory array. */ + result= -1; + } + else + { + if (found && ! result) { + result= 1; /* At least one record found. */ + /* If search is requested, we do not need to search further. */ + if (! drop && ! user_to) + goto end; + } + + /* Handle columns table. */ + if ((found= handle_grant_table(tables, 3, drop, user_from, user_to)) < 0) + { + /* Handle of table failed, don't touch the in-memory array. */ result= -1; + } + else + { + /* Handle columns hash. */ + if (((handle_grant_struct(2, drop, user_from, user_to) && ! result) || + found) && ! result) + result= 1; /* At least one record/element found. */ + } + } + end: + DBUG_RETURN(result); +} + + +static void append_user(String *str, LEX_USER *user) +{ + if (str->length()) + str->append(','); + str->append('\''); + str->append(user->user.str); + str->append(STRING_WITH_LEN("'@'")); + str->append(user->host.str); + str->append('\''); +} + + +/* + Create a list of users. + + SYNOPSIS + mysql_create_user() + thd The current thread. + list The users to create. + + RETURN + FALSE OK. + TRUE Error. +*/ + +bool mysql_create_user(THD *thd, List <LEX_USER> &list) +{ + int result; + String wrong_users; + ulong sql_mode; + LEX_USER *user_name, *tmp_user_name; + List_iterator <LEX_USER> user_list(list); + TABLE_LIST tables[GRANT_TABLES]; + DBUG_ENTER("mysql_create_user"); + + /* CREATE USER may be skipped on replication client. */ + if ((result= open_grant_tables(thd, tables))) + DBUG_RETURN(result != 1); + + rw_wrlock(&LOCK_grant); + VOID(pthread_mutex_lock(&acl_cache->lock)); + + while ((tmp_user_name= user_list++)) + { + if (!(user_name= get_current_user(thd, tmp_user_name))) + { + result= TRUE; continue; } - tables[0].table->field[0]->store(user_name->host.str,(uint) - user_name->host.length, - system_charset_info); - tables[0].table->field[1]->store(user_name->user.str,(uint) - user_name->user.length, - system_charset_info); - tables[0].table->file->extra(HA_EXTRA_RETRIEVE_ALL_COLS); - if (!tables[0].table->file->index_read_idx(tables[0].table->record[0],0, - (byte*) tables[0].table-> - field[0]->ptr, - tables[0].table-> - key_info[0].key_length, - HA_READ_KEY_EXACT)) - { - int error; - if ((error = tables[0].table->file->delete_row(tables[0].table-> - record[0]))) - { - tables[0].table->file->print_error(error, MYF(0)); - result= -1; - goto end; - } - delete_dynamic_element(&acl_users, acl_userd); + /* + Search all in-memory structures and grant tables + for a mention of the new user name. + */ + if (handle_grant_data(tables, 0, user_name, NULL)) + { + append_user(&wrong_users, user_name); + result= TRUE; + continue; + } + + sql_mode= thd->variables.sql_mode; + if (replace_user_table(thd, tables[0].table, *user_name, 0, 0, 1, 0)) + { + append_user(&wrong_users, user_name); + result= TRUE; } } + VOID(pthread_mutex_unlock(&acl_cache->lock)); + rw_unlock(&LOCK_grant); + close_thread_tables(thd); if (result) - my_error(ER_DROP_USER, MYF(0)); + my_error(ER_CANNOT_USER, MYF(0), "CREATE USER", wrong_users.c_ptr_safe()); + DBUG_RETURN(result); +} -end: - /* Reload acl_check_hosts as its memory is mapped to acl_user */ - delete_dynamic(&acl_wild_hosts); - hash_free(&acl_check_hosts); - init_check_host(); + +/* + Drop a list of users and all their privileges. + + SYNOPSIS + mysql_drop_user() + thd The current thread. + list The users to drop. + + RETURN + FALSE OK. + TRUE Error. +*/ + +bool mysql_drop_user(THD *thd, List <LEX_USER> &list) +{ + int result; + String wrong_users; + LEX_USER *user_name, *tmp_user_name; + List_iterator <LEX_USER> user_list(list); + TABLE_LIST tables[GRANT_TABLES]; + DBUG_ENTER("mysql_drop_user"); + + /* DROP USER may be skipped on replication client. */ + if ((result= open_grant_tables(thd, tables))) + DBUG_RETURN(result != 1); + + rw_wrlock(&LOCK_grant); + VOID(pthread_mutex_lock(&acl_cache->lock)); + + while ((tmp_user_name= user_list++)) + { + user_name= get_current_user(thd, tmp_user_name); + if (!(user_name= get_current_user(thd, tmp_user_name))) + { + result= TRUE; + continue; + } + if (handle_grant_data(tables, 1, user_name, NULL) <= 0) + { + append_user(&wrong_users, user_name); + result= TRUE; + } + } + + /* Rebuild 'acl_check_hosts' since 'acl_users' has been modified */ + rebuild_check_host(); VOID(pthread_mutex_unlock(&acl_cache->lock)); rw_unlock(&LOCK_grant); close_thread_tables(thd); + if (result) + my_error(ER_CANNOT_USER, MYF(0), "DROP USER", wrong_users.c_ptr_safe()); DBUG_RETURN(result); } -int mysql_revoke_all(THD *thd, List <LEX_USER> &list) + +/* + Rename a user. + + SYNOPSIS + mysql_rename_user() + thd The current thread. + list The user name pairs: (from, to). + + RETURN + FALSE OK. + TRUE Error. +*/ + +bool mysql_rename_user(THD *thd, List <LEX_USER> &list) { - uint counter, revoked; + int result; + String wrong_users; + LEX_USER *user_from, *tmp_user_from; + LEX_USER *user_to, *tmp_user_to; + List_iterator <LEX_USER> user_list(list); + TABLE_LIST tables[GRANT_TABLES]; + DBUG_ENTER("mysql_rename_user"); + + /* RENAME USER may be skipped on replication client. */ + if ((result= open_grant_tables(thd, tables))) + DBUG_RETURN(result != 1); + + rw_wrlock(&LOCK_grant); + VOID(pthread_mutex_lock(&acl_cache->lock)); + + while ((tmp_user_from= user_list++)) + { + if (!(user_from= get_current_user(thd, tmp_user_from))) + { + result= TRUE; + continue; + } + tmp_user_to= user_list++; + if (!(user_to= get_current_user(thd, tmp_user_to))) + { + result= TRUE; + continue; + } + DBUG_ASSERT(user_to != 0); /* Syntax enforces pairs of users. */ + + /* + Search all in-memory structures and grant tables + for a mention of the new user name. + */ + if (handle_grant_data(tables, 0, user_to, NULL) || + handle_grant_data(tables, 0, user_from, user_to) <= 0) + { + append_user(&wrong_users, user_from); + result= TRUE; + } + } + + /* Rebuild 'acl_check_hosts' since 'acl_users' has been modified */ + rebuild_check_host(); + + VOID(pthread_mutex_unlock(&acl_cache->lock)); + rw_unlock(&LOCK_grant); + close_thread_tables(thd); + if (result) + my_error(ER_CANNOT_USER, MYF(0), "RENAME USER", wrong_users.c_ptr_safe()); + DBUG_RETURN(result); +} + + +/* + Revoke all privileges from a list of users. + + SYNOPSIS + mysql_revoke_all() + thd The current thread. + list The users to revoke all privileges from. + + RETURN + > 0 Error. Error message already sent. + 0 OK. + < 0 Error. Error message not yet sent. +*/ + +bool mysql_revoke_all(THD *thd, List <LEX_USER> &list) +{ + uint counter, revoked, is_proc; int result; ACL_DB *acl_db; - TABLE_LIST tables[4]; + TABLE_LIST tables[GRANT_TABLES]; DBUG_ENTER("mysql_revoke_all"); if ((result= open_grant_tables(thd, tables))) - DBUG_RETURN(result == 1 ? 0 : 1); + DBUG_RETURN(result != 1); rw_wrlock(&LOCK_grant); VOID(pthread_mutex_lock(&acl_cache->lock)); - LEX_USER *lex_user; + LEX_USER *lex_user, *tmp_lex_user; List_iterator <LEX_USER> user_list(list); - while ((lex_user=user_list++)) + while ((tmp_lex_user= user_list++)) { - if (!check_acl_user(lex_user, &counter)) + if (!(lex_user= get_current_user(thd, tmp_lex_user))) + { + result= -1; + continue; + } + if (!find_acl_user(lex_user->host.str, lex_user->user.str, TRUE)) { result= -1; continue; } if (replace_user_table(thd, tables[0].table, - *lex_user, ~(ulong)0, 1, 0)) + *lex_user, ~(ulong)0, 1, 0, 0)) { result= -1; continue; @@ -3803,13 +5445,13 @@ int mysql_revoke_all(THD *thd, List <LEX_USER> &list) for (counter= 0, revoked= 0 ; counter < acl_dbs.elements ; ) { const char *user,*host; - + acl_db=dynamic_element(&acl_dbs,counter,ACL_DB*); if (!(user=acl_db->user)) user= ""; if (!(host=acl_db->host.hostname)) host= ""; - + if (!strcmp(lex_user->user.str,user) && !my_strcasecmp(system_charset_info, lex_user->host.str, host)) { @@ -3840,7 +5482,7 @@ int mysql_revoke_all(THD *thd, List <LEX_USER> &list) user= ""; if (!(host=grant_table->host.hostname)) host= ""; - + if (!strcmp(lex_user->user.str,user) && !my_strcasecmp(system_charset_info, lex_user->host.str, host)) { @@ -3874,15 +5516,211 @@ int mysql_revoke_all(THD *thd, List <LEX_USER> &list) counter++; } } while (revoked); + + /* Remove procedure access */ + for (is_proc=0; is_proc<2; is_proc++) do { + HASH *hash= is_proc ? &proc_priv_hash : &func_priv_hash; + for (counter= 0, revoked= 0 ; counter < hash->records ; ) + { + const char *user,*host; + GRANT_NAME *grant_proc= (GRANT_NAME*) hash_element(hash, counter); + if (!(user=grant_proc->user)) + user= ""; + if (!(host=grant_proc->host.hostname)) + host= ""; + + if (!strcmp(lex_user->user.str,user) && + !my_strcasecmp(system_charset_info, lex_user->host.str, host)) + { + if (!replace_routine_table(thd,grant_proc,tables[4].table,*lex_user, + grant_proc->db, + grant_proc->tname, + is_proc, + ~(ulong)0, 1)) + { + revoked= 1; + continue; + } + result= -1; // Something went wrong + } + counter++; + } + } while (revoked); } - + VOID(pthread_mutex_unlock(&acl_cache->lock)); rw_unlock(&LOCK_grant); close_thread_tables(thd); - + if (result) - my_error(ER_REVOKE_GRANTS, MYF(0)); - + my_message(ER_REVOKE_GRANTS, ER(ER_REVOKE_GRANTS), MYF(0)); + + DBUG_RETURN(result); +} + + +/* + Revoke privileges for all users on a stored procedure + + SYNOPSIS + sp_revoke_privileges() + thd The current thread. + db DB of the stored procedure + name Name of the stored procedure + + RETURN + 0 OK. + < 0 Error. Error message not yet sent. +*/ + +bool sp_revoke_privileges(THD *thd, const char *sp_db, const char *sp_name, + bool is_proc) +{ + uint counter, revoked; + int result; + TABLE_LIST tables[GRANT_TABLES]; + HASH *hash= is_proc ? &proc_priv_hash : &func_priv_hash; + DBUG_ENTER("sp_revoke_privileges"); + + if ((result= open_grant_tables(thd, tables))) + DBUG_RETURN(result != 1); + + rw_wrlock(&LOCK_grant); + VOID(pthread_mutex_lock(&acl_cache->lock)); + + /* Remove procedure access */ + do + { + for (counter= 0, revoked= 0 ; counter < hash->records ; ) + { + GRANT_NAME *grant_proc= (GRANT_NAME*) hash_element(hash, counter); + if (!my_strcasecmp(system_charset_info, grant_proc->db, sp_db) && + !my_strcasecmp(system_charset_info, grant_proc->tname, sp_name)) + { + LEX_USER lex_user; + lex_user.user.str= grant_proc->user; + lex_user.user.length= strlen(grant_proc->user); + lex_user.host.str= grant_proc->host.hostname ? + grant_proc->host.hostname : (char*)""; + lex_user.host.length= grant_proc->host.hostname ? + strlen(grant_proc->host.hostname) : 0; + if (!replace_routine_table(thd,grant_proc,tables[4].table,lex_user, + grant_proc->db, grant_proc->tname, + is_proc, ~(ulong)0, 1)) + { + revoked= 1; + continue; + } + result= -1; // Something went wrong + } + counter++; + } + } while (revoked); + + VOID(pthread_mutex_unlock(&acl_cache->lock)); + rw_unlock(&LOCK_grant); + close_thread_tables(thd); + + if (result) + my_message(ER_REVOKE_GRANTS, ER(ER_REVOKE_GRANTS), MYF(0)); + + DBUG_RETURN(result); +} + + +/* + Grant EXECUTE,ALTER privilege for a stored procedure + + SYNOPSIS + sp_grant_privileges() + thd The current thread. + db DB of the stored procedure + name Name of the stored procedure + + RETURN + 0 OK. + < 0 Error. Error message not yet sent. +*/ + +bool sp_grant_privileges(THD *thd, const char *sp_db, const char *sp_name, + bool is_proc) +{ + Security_context *sctx= thd->security_ctx; + LEX_USER *combo; + TABLE_LIST tables[1]; + List<LEX_USER> user_list; + bool result; + ACL_USER *au; + char passwd_buff[SCRAMBLED_PASSWORD_CHAR_LENGTH+1]; + DBUG_ENTER("sp_grant_privileges"); + + if (!(combo=(LEX_USER*) thd->alloc(sizeof(st_lex_user)))) + DBUG_RETURN(TRUE); + + combo->user.str= sctx->user; + + VOID(pthread_mutex_lock(&acl_cache->lock)); + + if ((au= find_acl_user(combo->host.str=(char*)sctx->host_or_ip,combo->user.str,FALSE))) + goto found_acl; + if ((au= find_acl_user(combo->host.str=(char*)sctx->host, combo->user.str,FALSE))) + goto found_acl; + if ((au= find_acl_user(combo->host.str=(char*)sctx->ip, combo->user.str,FALSE))) + goto found_acl; + if((au= find_acl_user(combo->host.str=(char*)"%", combo->user.str, FALSE))) + goto found_acl; + + VOID(pthread_mutex_unlock(&acl_cache->lock)); + DBUG_RETURN(TRUE); + + found_acl: + VOID(pthread_mutex_unlock(&acl_cache->lock)); + + bzero((char*)tables, sizeof(TABLE_LIST)); + user_list.empty(); + + tables->db= (char*)sp_db; + tables->table_name= tables->alias= (char*)sp_name; + + combo->host.length= strlen(combo->host.str); + combo->user.length= strlen(combo->user.str); + combo->host.str= thd->strmake(combo->host.str,combo->host.length); + combo->user.str= thd->strmake(combo->user.str,combo->user.length); + + + if(au && au->salt_len) + { + if (au->salt_len == SCRAMBLE_LENGTH) + { + make_password_from_salt(passwd_buff, au->salt); + combo->password.length= SCRAMBLED_PASSWORD_CHAR_LENGTH; + } + else if (au->salt_len == SCRAMBLE_LENGTH_323) + { + make_password_from_salt_323(passwd_buff, (ulong *) au->salt); + combo->password.length= SCRAMBLED_PASSWORD_CHAR_LENGTH_323; + } + else + { + my_error(ER_PASSWD_LENGTH, MYF(0), SCRAMBLED_PASSWORD_CHAR_LENGTH); + return -1; + } + combo->password.str= passwd_buff; + } + else + { + combo->password.str= (char*)""; + combo->password.length= 0; + } + + if (user_list.push_back(combo)) + DBUG_RETURN(TRUE); + + thd->lex->ssl_type= SSL_TYPE_NOT_SPECIFIED; + bzero((char*) &thd->lex->mqh, sizeof(thd->lex->mqh)); + + result= mysql_routine_grant(thd, tables, is_proc, user_list, + DEFAULT_CREATE_PROC_ACLS, 0, 1); DBUG_RETURN(result); } @@ -3891,7 +5729,7 @@ int mysql_revoke_all(THD *thd, List <LEX_USER> &list) Instantiate used templates *****************************************************************************/ -#ifdef __GNUC__ +#ifdef HAVE_EXPLICIT_TEMPLATE_INSTANTIATION template class List_iterator<LEX_COLUMN>; template class List_iterator<LEX_USER>; template class List<LEX_COLUMN>; @@ -3944,3 +5782,369 @@ int wild_case_compare(CHARSET_INFO *cs, const char *str,const char *wildstr) DBUG_RETURN (*str != '\0'); } + +void update_schema_privilege(TABLE *table, char *buff, const char* db, + const char* t_name, const char* column, + uint col_length, const char *priv, + uint priv_length, const char* is_grantable) +{ + int i= 2; + CHARSET_INFO *cs= system_charset_info; + restore_record(table, s->default_values); + table->field[0]->store(buff, strlen(buff), cs); + if (db) + table->field[i++]->store(db, strlen(db), cs); + if (t_name) + table->field[i++]->store(t_name, strlen(t_name), cs); + if (column) + table->field[i++]->store(column, col_length, cs); + table->field[i++]->store(priv, priv_length, cs); + table->field[i]->store(is_grantable, strlen(is_grantable), cs); + table->file->write_row(table->record[0]); +} + + +int fill_schema_user_privileges(THD *thd, TABLE_LIST *tables, COND *cond) +{ +#ifndef NO_EMBEDDED_ACCESS_CHECKS + uint counter; + ACL_USER *acl_user; + ulong want_access; + char buff[100]; + TABLE *table= tables->table; + bool no_global_access= check_access(thd, SELECT_ACL, "mysql",0,1,1,0); + char *curr_host= thd->security_ctx->priv_host_name(); + DBUG_ENTER("fill_schema_user_privileges"); + + pthread_mutex_lock(&acl_cache->lock); + + for (counter=0 ; counter < acl_users.elements ; counter++) + { + const char *user,*host, *is_grantable="YES"; + acl_user=dynamic_element(&acl_users,counter,ACL_USER*); + if (!(user=acl_user->user)) + user= ""; + if (!(host=acl_user->host.hostname)) + host= ""; + + if (no_global_access && + (strcmp(thd->security_ctx->priv_user, user) || + my_strcasecmp(system_charset_info, curr_host, host))) + continue; + + want_access= acl_user->access; + if (!(want_access & GRANT_ACL)) + is_grantable= "NO"; + + strxmov(buff,"'",user,"'@'",host,"'",NullS); + if (!(want_access & ~GRANT_ACL)) + update_schema_privilege(table, buff, 0, 0, 0, 0, + STRING_WITH_LEN("USAGE"), is_grantable); + else + { + uint priv_id; + ulong j,test_access= want_access & ~GRANT_ACL; + for (priv_id=0, j = SELECT_ACL;j <= GLOBAL_ACLS; priv_id++,j <<= 1) + { + if (test_access & j) + update_schema_privilege(table, buff, 0, 0, 0, 0, + command_array[priv_id], + command_lengths[priv_id], is_grantable); + } + } + } + + pthread_mutex_unlock(&acl_cache->lock); + + DBUG_RETURN(0); +#else + return(0); +#endif +} + + +int fill_schema_schema_privileges(THD *thd, TABLE_LIST *tables, COND *cond) +{ +#ifndef NO_EMBEDDED_ACCESS_CHECKS + uint counter; + ACL_DB *acl_db; + ulong want_access; + char buff[100]; + TABLE *table= tables->table; + bool no_global_access= check_access(thd, SELECT_ACL, "mysql",0,1,1,0); + char *curr_host= thd->security_ctx->priv_host_name(); + DBUG_ENTER("fill_schema_schema_privileges"); + + pthread_mutex_lock(&acl_cache->lock); + + for (counter=0 ; counter < acl_dbs.elements ; counter++) + { + const char *user, *host, *is_grantable="YES"; + + acl_db=dynamic_element(&acl_dbs,counter,ACL_DB*); + if (!(user=acl_db->user)) + user= ""; + if (!(host=acl_db->host.hostname)) + host= ""; + + if (no_global_access && + (strcmp(thd->security_ctx->priv_user, user) || + my_strcasecmp(system_charset_info, curr_host, host))) + continue; + + want_access=acl_db->access; + if (want_access) + { + if (!(want_access & GRANT_ACL)) + { + is_grantable= "NO"; + } + strxmov(buff,"'",user,"'@'",host,"'",NullS); + if (!(want_access & ~GRANT_ACL)) + update_schema_privilege(table, buff, acl_db->db, 0, 0, + 0, STRING_WITH_LEN("USAGE"), is_grantable); + else + { + int cnt; + ulong j,test_access= want_access & ~GRANT_ACL; + for (cnt=0, j = SELECT_ACL; j <= DB_ACLS; cnt++,j <<= 1) + if (test_access & j) + update_schema_privilege(table, buff, acl_db->db, 0, 0, 0, + command_array[cnt], command_lengths[cnt], + is_grantable); + } + } + } + + pthread_mutex_unlock(&acl_cache->lock); + + DBUG_RETURN(0); +#else + return (0); +#endif +} + + +int fill_schema_table_privileges(THD *thd, TABLE_LIST *tables, COND *cond) +{ +#ifndef NO_EMBEDDED_ACCESS_CHECKS + uint index; + char buff[100]; + TABLE *table= tables->table; + bool no_global_access= check_access(thd, SELECT_ACL, "mysql",0,1,1,0); + char *curr_host= thd->security_ctx->priv_host_name(); + DBUG_ENTER("fill_schema_table_privileges"); + + rw_rdlock(&LOCK_grant); + + for (index=0 ; index < column_priv_hash.records ; index++) + { + const char *user, *host, *is_grantable= "YES"; + GRANT_TABLE *grant_table= (GRANT_TABLE*) hash_element(&column_priv_hash, + index); + if (!(user=grant_table->user)) + user= ""; + if (!(host= grant_table->host.hostname)) + host= ""; + + if (no_global_access && + (strcmp(thd->security_ctx->priv_user, user) || + my_strcasecmp(system_charset_info, curr_host, host))) + continue; + + ulong table_access= grant_table->privs; + if (table_access) + { + ulong test_access= table_access & ~GRANT_ACL; + /* + We should skip 'usage' privilege on table if + we have any privileges on column(s) of this table + */ + if (!test_access && grant_table->cols) + continue; + if (!(table_access & GRANT_ACL)) + is_grantable= "NO"; + + strxmov(buff, "'", user, "'@'", host, "'", NullS); + if (!test_access) + update_schema_privilege(table, buff, grant_table->db, grant_table->tname, + 0, 0, STRING_WITH_LEN("USAGE"), is_grantable); + else + { + ulong j; + int cnt; + for (cnt= 0, j= SELECT_ACL; j <= TABLE_ACLS; cnt++, j<<= 1) + { + if (test_access & j) + update_schema_privilege(table, buff, grant_table->db, + grant_table->tname, 0, 0, command_array[cnt], + command_lengths[cnt], is_grantable); + } + } + } + } + + rw_unlock(&LOCK_grant); + + DBUG_RETURN(0); +#else + return (0); +#endif +} + + +int fill_schema_column_privileges(THD *thd, TABLE_LIST *tables, COND *cond) +{ +#ifndef NO_EMBEDDED_ACCESS_CHECKS + uint index; + char buff[100]; + TABLE *table= tables->table; + bool no_global_access= check_access(thd, SELECT_ACL, "mysql",0,1,1,0); + char *curr_host= thd->security_ctx->priv_host_name(); + DBUG_ENTER("fill_schema_table_privileges"); + + rw_rdlock(&LOCK_grant); + + for (index=0 ; index < column_priv_hash.records ; index++) + { + const char *user, *host, *is_grantable= "YES"; + GRANT_TABLE *grant_table= (GRANT_TABLE*) hash_element(&column_priv_hash, + index); + if (!(user=grant_table->user)) + user= ""; + if (!(host= grant_table->host.hostname)) + host= ""; + + if (no_global_access && + (strcmp(thd->security_ctx->priv_user, user) || + my_strcasecmp(system_charset_info, curr_host, host))) + continue; + + ulong table_access= grant_table->cols; + if (table_access != 0) + { + if (!(grant_table->privs & GRANT_ACL)) + is_grantable= "NO"; + + ulong test_access= table_access & ~GRANT_ACL; + strxmov(buff, "'", user, "'@'", host, "'", NullS); + if (!test_access) + continue; + else + { + ulong j; + int cnt; + for (cnt= 0, j= SELECT_ACL; j <= TABLE_ACLS; cnt++, j<<= 1) + { + if (test_access & j) + { + for (uint col_index=0 ; + col_index < grant_table->hash_columns.records ; + col_index++) + { + GRANT_COLUMN *grant_column = (GRANT_COLUMN*) + hash_element(&grant_table->hash_columns,col_index); + if ((grant_column->rights & j) && (table_access & j)) + update_schema_privilege(table, buff, grant_table->db, + grant_table->tname, + grant_column->column, + grant_column->key_length, + command_array[cnt], + command_lengths[cnt], is_grantable); + } + } + } + } + } + } + + rw_unlock(&LOCK_grant); + + DBUG_RETURN(0); +#else + return (0); +#endif +} + + +#ifndef NO_EMBEDDED_ACCESS_CHECKS +/* + fill effective privileges for table + + SYNOPSIS + fill_effective_table_privileges() + thd thread handler + grant grants table descriptor + db db name + table table name +*/ + +void fill_effective_table_privileges(THD *thd, GRANT_INFO *grant, + const char *db, const char *table) +{ + Security_context *sctx= thd->security_ctx; + DBUG_ENTER("fill_effective_table_privileges"); + DBUG_PRINT("enter", ("Host: '%s', Ip: '%s', User: '%s', table: `%s`.`%s`", + sctx->priv_host, (sctx->ip ? sctx->ip : "(NULL)"), + (sctx->priv_user ? sctx->priv_user : "(NULL)"), + db, table)); + /* --skip-grants */ + if (!initialized) + { + DBUG_PRINT("info", ("skip grants")); + grant->privilege= ~NO_ACCESS; // everything is allowed + DBUG_PRINT("info", ("privilege 0x%lx", grant->privilege)); + DBUG_VOID_RETURN; + } + + /* global privileges */ + grant->privilege= sctx->master_access; + + if (!sctx->priv_user) + { + DBUG_PRINT("info", ("privilege 0x%lx", grant->privilege)); + DBUG_VOID_RETURN; // it is slave + } + + /* db privileges */ + grant->privilege|= acl_get(sctx->host, sctx->ip, sctx->priv_user, db, 0); + + if (!grant_option) + { + DBUG_PRINT("info", ("privilege 0x%lx", grant->privilege)); + DBUG_VOID_RETURN; + } + + /* table privileges */ + rw_rdlock(&LOCK_grant); + if (grant->version != grant_version) + { + grant->grant_table= + table_hash_search(sctx->host, sctx->ip, db, + sctx->priv_user, + table, 0); /* purecov: inspected */ + grant->version= grant_version; /* purecov: inspected */ + } + if (grant->grant_table != 0) + { + grant->privilege|= grant->grant_table->privs; + } + rw_unlock(&LOCK_grant); + + DBUG_PRINT("info", ("privilege 0x%lx", grant->privilege)); + DBUG_VOID_RETURN; +} + +#else /* NO_EMBEDDED_ACCESS_CHECKS */ + +/**************************************************************************** + Dummy wrappers when we don't have any access checks +****************************************************************************/ + +bool check_routine_level_acl(THD *thd, const char *db, const char *name, + bool is_proc) +{ + return FALSE; +} + +#endif |