summaryrefslogtreecommitdiff
path: root/sql/sql_acl.cc
diff options
context:
space:
mode:
authorVicentiu Ciorbaru <cvicentiu@gmail.com>2014-07-13 23:57:10 +0000
committerSergei Golubchik <serg@mariadb.org>2014-07-23 14:48:12 +0200
commit64b27c734eed91e2b79701c9c53283d9411f702f (patch)
tree6f7e49f44ad916637fb214fc93df1a59bcf76553 /sql/sql_acl.cc
parent43351faf2b229fb2e87331227efb2daf554647a7 (diff)
downloadmariadb-git-64b27c734eed91e2b79701c9c53283d9411f702f.tar.gz
Added default role implementation
Diffstat (limited to 'sql/sql_acl.cc')
-rw-r--r--sql/sql_acl.cc252
1 files changed, 230 insertions, 22 deletions
diff --git a/sql/sql_acl.cc b/sql/sql_acl.cc
index eff05745628..9ad78115604 100644
--- a/sql/sql_acl.cc
+++ b/sql/sql_acl.cc
@@ -265,6 +265,7 @@ public:
const char *ssl_cipher, *x509_issuer, *x509_subject;
LEX_STRING plugin;
LEX_STRING auth_string;
+ LEX_STRING default_rolename;
ACL_USER *copy(MEM_ROOT *root)
{
@@ -284,6 +285,8 @@ public:
dst->plugin.str= strmake_root(root, plugin.str, plugin.length);
dst->auth_string.str= safe_strdup_root(root, auth_string.str);
dst->host.hostname= safe_strdup_root(root, host.hostname);
+ dst->default_rolename.str= safe_strdup_root(root, default_rolename.str);
+ dst->default_rolename.length= default_rolename.length;
bzero(&dst->role_grants, sizeof(role_grants));
return dst;
}
@@ -702,6 +705,7 @@ bool ROLE_GRANT_PAIR::init(MEM_ROOT *mem, char *username,
#define NORMAL_HANDSHAKE_SIZE 6
#define ROLE_ASSIGN_COLUMN_IDX 43
+#define DEFAULT_ROLE_COLUMN_IDX 44
/* various flags valid for ACL_USER */
#define IS_ROLE (1L << 0)
/* Flag to mark that a ROLE is on the recursive DEPTH_FIRST_SEARCH stack */
@@ -1347,6 +1351,14 @@ static bool acl_load(THD *thd, TABLE_LIST *tables)
(void) my_init_dynamic_array(&user.role_grants,sizeof(ACL_ROLE *),
8, 8, MYF(0));
+ /* check default role, if any */
+ if (!is_role && table->s->fields >= 45)
+ {
+ user.default_rolename.str= get_field(&acl_memroot, table->field[44]);
+ user.default_rolename.length= user.default_rolename.str ?
+ strlen(user.default_rolename.str) : 0;
+ }
+
if (is_role)
{
DBUG_PRINT("info", ("Found role %s", user.user.str));
@@ -1867,7 +1879,8 @@ bool acl_getroot(Security_context *sctx, char *user, char *host,
DBUG_RETURN(res);
}
-int acl_check_setrole(THD *thd, char *rolename, ulonglong *access)
+int check_user_can_set_role(const char *host, const char *user,
+ const char *rolename, ulonglong *access)
{
ACL_ROLE *role;
ACL_USER_BASE *acl_user_base;
@@ -1882,8 +1895,7 @@ int acl_check_setrole(THD *thd, char *rolename, ulonglong *access)
{
/* have to clear the privileges */
/* get the current user */
- acl_user= find_user_exact(thd->security_ctx->priv_host,
- thd->security_ctx->priv_user);
+ acl_user= find_user_exact(host, user);
if (acl_user == NULL)
{
my_error(ER_INVALID_CURRENT_USER, MYF(0), rolename);
@@ -1911,9 +1923,7 @@ int acl_check_setrole(THD *thd, char *rolename, ulonglong *access)
continue;
acl_user= (ACL_USER *)acl_user_base;
- /* Yes! priv_user@host. Don't ask why - that's what check_access() does. */
- if (acl_user->wild_eq(thd->security_ctx->priv_user,
- thd->security_ctx->host))
+ if (acl_user->wild_eq(user, host))
{
is_granted= TRUE;
break;
@@ -1935,6 +1945,15 @@ int acl_check_setrole(THD *thd, char *rolename, ulonglong *access)
end:
mysql_mutex_unlock(&acl_cache->lock);
return result;
+
+}
+
+int acl_check_setrole(THD *thd, char *rolename, ulonglong *access)
+{
+ /* Yes! priv_user@host. Don't ask why - that's what check_access() does. */
+ return check_user_can_set_role(thd->security_ctx->host,
+ thd->security_ctx->priv_user,
+ rolename, access);
}
@@ -1960,7 +1979,6 @@ int acl_setrole(THD *thd, char *rolename, ulonglong access)
return 0;
}
-
static uchar* check_get_key(ACL_USER *buff, size_t *length,
my_bool not_used __attribute__((unused)))
{
@@ -2068,6 +2086,7 @@ static void acl_insert_user(const char *user, const char *host,
mysql_mutex_assert_owner(&acl_cache->lock);
+ bzero(&acl_user, sizeof(acl_user));
acl_user.user.str=*user ? strdup_root(&acl_memroot,user) : 0;
acl_user.user.length= strlen(user);
update_hostname(&acl_user.host, safe_strdup_root(&acl_memroot, host));
@@ -2518,41 +2537,40 @@ bool acl_check_host(const char *host, const char *ip)
return 1; // Host is not allowed
}
-
/**
- Check if the user is allowed to change password
+ Check if the user is allowed to alter the mysql.user table
@param thd THD
@param host Hostname for the user
@param user User name
- @param new_password New password
- @param new_password_len The length of the new password
-
- new_password cannot be NULL
@return Error status
@retval 0 OK
- @retval 1 ERROR; In this case the error is sent to the client.
+ @retval 1 skip grant tables error
+ @retval 2 ANONYMOUS user error
+ @retval 3 the entry to change is a role, not a user
+ @retval 4 no UPDATE_ACL error
+
*/
-int check_change_password(THD *thd, const char *host, const char *user,
- char *new_password, uint new_password_len)
+int check_alter_user(THD *thd, const char *host, const char *user)
{
+ int error = 1;
if (!initialized)
{
my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--skip-grant-tables");
- return(1);
+ goto end;
}
if (!thd->slave_thread && !thd->security_ctx->priv_user[0])
{
my_message(ER_PASSWORD_ANONYMOUS_USER, ER(ER_PASSWORD_ANONYMOUS_USER),
MYF(0));
- return(1);
+ goto end;
}
if (!host) // Role
{
my_error(ER_PASSWORD_NO_MATCH, MYF(0));
- return 1;
+ goto end;
}
if (!thd->slave_thread &&
(strcmp(thd->security_ctx->priv_user, user) ||
@@ -2560,16 +2578,44 @@ int check_change_password(THD *thd, const char *host, const char *user,
thd->security_ctx->priv_host)))
{
if (check_access(thd, UPDATE_ACL, "mysql", NULL, NULL, 1, 0))
- return(1);
+ goto end;
}
+
+ error = 0;
+
+end:
+ return error;
+}
+/**
+ Check if the user is allowed to change password
+
+ @param thd THD
+ @param host Hostname for the user
+ @param user User name
+ @param new_password New password
+ @param new_password_len The length of the new password
+
+ new_password cannot be NULL
+
+ @return Error status
+ @retval 0 OK
+ @retval 1 ERROR; In this case the error is sent to the client.
+*/
+
+int check_change_password(THD *thd, const char *host, const char *user,
+ char *new_password, uint new_password_len)
+{
+ if (check_alter_user(thd, host, user))
+ return 1;
+
size_t len= strlen(new_password);
if (len && len != SCRAMBLED_PASSWORD_CHAR_LENGTH &&
len != SCRAMBLED_PASSWORD_CHAR_LENGTH_323)
{
my_error(ER_PASSWD_LENGTH, MYF(0), SCRAMBLED_PASSWORD_CHAR_LENGTH);
- return -1;
+ return 1;
}
- return(0);
+ return 0;
}
@@ -2669,6 +2715,152 @@ end:
DBUG_RETURN(result);
}
+int acl_check_set_default_role(THD *thd, const char *host, const char *user)
+{
+ return check_alter_user(thd, host, user);
+}
+
+int acl_set_default_role(THD *thd, const char *host, const char *user,
+ const char *rolename)
+{
+ TABLE_LIST tables;
+ TABLE *table;
+ char user_key[MAX_KEY_LENGTH];
+ int result= 1;
+ int error;
+ bool clear_role= FALSE;
+ Rpl_filter *rpl_filter;
+ enum_binlog_format save_binlog_format;
+
+
+ DBUG_ENTER("acl_set_default_role");
+ DBUG_PRINT("enter",("host: '%s' user: '%s' rolename: '%s'",
+ safe_str(user), safe_str(host), safe_str(rolename)));
+
+ if (rolename == current_role.str) {
+ if (!thd->security_ctx->priv_role[0])
+ rolename= "NONE";
+ else
+ rolename= thd->security_ctx->priv_role;
+ }
+
+ if (check_user_can_set_role(host, user, rolename, NULL))
+ DBUG_RETURN(result);
+
+ if (!strcasecmp(rolename, "NONE"))
+ clear_role= TRUE;
+
+ tables.init_one_table("mysql", 5, "user", 4, "user", TL_WRITE);
+
+#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 &&
+ (rpl_filter= thd->system_thread_info.rpl_sql_info->rpl_filter)->is_on())
+ {
+ /*
+ The tables must be marked "updating" so that tables_ok() takes them into
+ account in tests. It's ok to leave 'updating' set after tables_ok.
+ */
+ tables.updating= 1;
+ /* Thanks to bzero, tables.next==0 */
+ if (!(thd->spcont || rpl_filter->tables_ok(0, &tables)))
+ DBUG_RETURN(0);
+ }
+#endif
+
+ if (!(table= open_ltable(thd, &tables, TL_WRITE, MYSQL_LOCK_IGNORE_TIMEOUT)))
+ DBUG_RETURN(1);
+
+ /*
+ This statement will be replicated as a statement, even when using
+ row-based replication. The flag will be reset at the end of the
+ statement.
+ This has to be handled here as it's called by set_var.cc, which is
+ not automaticly handled by sql_parse.cc
+ */
+ save_binlog_format= thd->set_current_stmt_binlog_format_stmt();
+
+ mysql_mutex_lock(&acl_cache->lock);
+ ACL_USER *acl_user;
+ if (!(acl_user= find_user_exact(host, user)))
+ {
+ mysql_mutex_unlock(&acl_cache->lock);
+ my_message(ER_PASSWORD_NO_MATCH, ER(ER_PASSWORD_NO_MATCH), MYF(0));
+ goto end;
+ }
+
+ if (!clear_role) {
+ /* set new default_rolename */
+ acl_user->default_rolename.str= safe_strdup_root(&acl_memroot, rolename);
+ acl_user->default_rolename.length= strlen(rolename);
+ }
+ else
+ {
+ /* clear the default_rolename */
+ acl_user->default_rolename.str = NULL;
+ acl_user->default_rolename.length = 0;
+ }
+
+ /* update the mysql.user table with the new default role */
+ table->use_all_columns();
+ if (table->s->fields < 45)
+ {
+ my_error(ER_COL_COUNT_DOESNT_MATCH_PLEASE_UPDATE, MYF(0),
+ table->alias.c_ptr(), DEFAULT_ROLE_COLUMN_IDX + 1, table->s->fields,
+ static_cast<int>(table->s->mysql_version), MYSQL_VERSION_ID);
+ mysql_mutex_unlock(&acl_cache->lock);
+ goto end;
+ }
+ table->field[0]->store(host,(uint) strlen(host), system_charset_info);
+ table->field[1]->store(user,(uint) strlen(user), system_charset_info);
+ key_copy((uchar *) user_key, table->record[0], table->key_info,
+ table->key_info->key_length);
+
+ if (table->file->ha_index_read_idx_map(table->record[0], 0,
+ (uchar *) user_key, HA_WHOLE_KEY,
+ HA_READ_KEY_EXACT))
+ {
+ mysql_mutex_unlock(&acl_cache->lock);
+ my_message(ER_PASSWORD_NO_MATCH, ER(ER_PASSWORD_NO_MATCH), MYF(0));
+ goto end;
+ }
+ store_record(table, record[1]);
+ table->field[DEFAULT_ROLE_COLUMN_IDX]->store(acl_user->default_rolename.str,
+ acl_user->default_rolename.length,
+ system_charset_info);
+ if ((error=table->file->ha_update_row(table->record[1],table->record[0])) &&
+ error != HA_ERR_RECORD_IS_THE_SAME)
+ {
+ mysql_mutex_unlock(&acl_cache->lock);
+ table->file->print_error(error,MYF(0)); /* purecov: deadcode */
+ goto end;
+ }
+
+ acl_cache->clear(1);
+ mysql_mutex_unlock(&acl_cache->lock);
+ result= 0;
+ if (mysql_bin_log.is_open())
+ {
+ char buff[512];
+ int query_length=
+ sprintf(buff,"SET DEFAULT ROLE '%-.120s' FOR '%-.120s'@'%-.120s'",
+ safe_str(acl_user->default_rolename.str),
+ safe_str(acl_user->user.str),
+ safe_str(acl_user->host.hostname));
+ thd->clear_error();
+ result= thd->binlog_query(THD::STMT_QUERY_TYPE, buff, query_length,
+ FALSE, FALSE, FALSE, 0);
+ }
+end:
+ close_mysql_tables(thd);
+ thd->restore_stmt_binlog_format(save_binlog_format);
+
+ DBUG_RETURN(result);
+}
+
/*
Find user in ACL
@@ -12075,6 +12267,22 @@ bool acl_authenticate(THD *thd, uint com_change_user_pkt_len)
*/
sctx->db_access=0;
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ /*
+ In case the user has a default role set, attempt to set that role
+ */
+ if (acl_user->default_rolename.length) {
+ ulonglong access= 0;
+ int result;
+ result= acl_check_setrole(thd, acl_user->default_rolename.str, &access);
+ if (!result)
+ result= acl_setrole(thd, acl_user->default_rolename.str, access);
+ if (result)
+ thd->clear_error(); // even if the default role was not granted, do not
+ // close the connection
+ }
+#endif
+
/* Change a database if necessary */
if (mpvio.db.length)
{