From b81404671998009c25a56cc83aa7bf098ef9d964 Mon Sep 17 00:00:00 2001 From: Sergei Golubchik Date: Tue, 25 Nov 2014 10:07:59 +0100 Subject: [PATCH] validate SET PASSWORD --- .../plugins/r/simple_password_check.result | 11 +++ .../plugins/t/simple_password_check.test | 12 +++ sql/set_var.cc | 9 +- sql/set_var.h | 4 +- sql/sql_acl.cc | 99 ++++++++++--------- sql/sql_acl.h | 6 +- sql/sql_yacc.yy | 77 ++++++--------- 7 files changed, 113 insertions(+), 105 deletions(-) diff --git a/mysql-test/suite/plugins/r/simple_password_check.result b/mysql-test/suite/plugins/r/simple_password_check.result index 6682cc5bbbd..3c0e01ed0a3 100644 --- a/mysql-test/suite/plugins/r/simple_password_check.result +++ b/mysql-test/suite/plugins/r/simple_password_check.result @@ -99,6 +99,17 @@ create user foo1 identified by '123:qwe:4SD!'; ERROR HY000: Your password does not satisfy the current policy requirements create user foo1 identified by '123:qwe:ASD4'; ERROR HY000: Your password does not satisfy the current policy requirements +create user foo1 identified by '123:qwe:ASD!'; +set password for foo1 = password('qwe:-23:ASD!'); +ERROR HY000: Your password does not satisfy the current policy requirements +set password for foo1 = old_password('4we:123:ASD!'); +ERROR HY000: Your password does not satisfy the current policy requirements +set password for foo1 = password('qwe:123:4SD!'); +ERROR HY000: Your password does not satisfy the current policy requirements +set password for foo1 = old_password('qwe:123:ASD4'); +ERROR HY000: Your password does not satisfy the current policy requirements +set password for foo1 = password('qwe:123:ASD!'); +drop user foo1; uninstall plugin simple_password_check; create user foo1 identified by 'pwd'; drop user foo1; diff --git a/mysql-test/suite/plugins/t/simple_password_check.test b/mysql-test/suite/plugins/t/simple_password_check.test index 882fe0afe39..c322bebdfe3 100644 --- a/mysql-test/suite/plugins/t/simple_password_check.test +++ b/mysql-test/suite/plugins/t/simple_password_check.test @@ -48,6 +48,18 @@ create user foo1 identified by '123:qwe:4SD!'; --error ER_NOT_VALID_PASSWORD create user foo1 identified by '123:qwe:ASD4'; +create user foo1 identified by '123:qwe:ASD!'; +--error ER_NOT_VALID_PASSWORD +set password for foo1 = password('qwe:-23:ASD!'); +--error ER_NOT_VALID_PASSWORD +set password for foo1 = old_password('4we:123:ASD!'); +--error ER_NOT_VALID_PASSWORD +set password for foo1 = password('qwe:123:4SD!'); +--error ER_NOT_VALID_PASSWORD +set password for foo1 = old_password('qwe:123:ASD4'); +set password for foo1 = password('qwe:123:ASD!'); +drop user foo1; + uninstall plugin simple_password_check; create user foo1 identified by 'pwd'; diff --git a/sql/set_var.cc b/sql/set_var.cc index 8a34d6a5af1..faeabcd074f 100644 --- a/sql/set_var.cc +++ b/sql/set_var.cc @@ -851,10 +851,7 @@ int set_var_user::update(THD *thd) int set_var_password::check(THD *thd) { #ifndef NO_EMBEDDED_ACCESS_CHECKS - user= get_current_user(thd, user); - /* Returns 1 as the function sends error to client */ - return check_change_password(thd, user->host.str, user->user.str, - password, strlen(password)) ? 1 : 0; + return check_change_password(thd, user); #else return 0; #endif @@ -863,9 +860,7 @@ int set_var_password::check(THD *thd) int set_var_password::update(THD *thd) { #ifndef NO_EMBEDDED_ACCESS_CHECKS - /* Returns 1 as the function sends error to client */ - return change_password(thd, user->host.str, user->user.str, password) ? - 1 : 0; + return change_password(thd, user); #else return 0; #endif diff --git a/sql/set_var.h b/sql/set_var.h index 47f19f0ff96..66d1bc4e983 100644 --- a/sql/set_var.h +++ b/sql/set_var.h @@ -321,10 +321,8 @@ public: class set_var_password: public set_var_base { LEX_USER *user; - char *password; public: - set_var_password(LEX_USER *user_arg,char *password_arg) - :user(user_arg), password(password_arg) + set_var_password(LEX_USER *user_arg) :user(user_arg) {} int check(THD *thd); int update(THD *thd); diff --git a/sql/sql_acl.cc b/sql/sql_acl.cc index 1a27fa8209b..a8e7ef5db30 100644 --- a/sql/sql_acl.cc +++ b/sql/sql_acl.cc @@ -349,6 +349,7 @@ ulong role_global_merges= 0, role_db_merges= 0, role_table_merges= 0, #endif #ifndef NO_EMBEDDED_ACCESS_CHECKS +static bool fix_and_copy_user(LEX_USER *to, LEX_USER *from, THD *thd); static void update_hostname(acl_host_and_ip *host, const char *hostname); static ulong get_sort(uint count,...); static bool show_proxy_grants (THD *, const char *, const char *, @@ -965,10 +966,25 @@ static bool fix_user_plugin_ptr(ACL_USER *user) /* - transform equivalent LEX_USER values to one: - username IDENTIFIED BY PASSWORD xxx - username IDENTIFIED VIA mysql_native_password USING xxx - etc + Validates the password, calculates password hash, transforms + equivalent LEX_USER representations. + + Upon entering this function: + + - if user->plugin is specified, user->auth is the plugin auth data. + - if user->plugin is mysql_native_password or mysql_old_password, + user->auth if the password hash, and LEX_USER is transformed + to match the next case (that is, user->plugin is cleared). + - if user->plugin is NOT specified, built-in auth is assumed, that is + mysql_native_password or mysql_old_password. In that case, + user->auth is the password hash. And user->password is the original + plain-text password. Either one can be set or even both. + + Upon exiting this function: + + - user->password is the password hash, as the mysql.user.password column + - user->plugin is the plugin name, as the mysql.user.plugin column + - user->auth is the plugin auth data, as the mysql.user.authentication_string column */ static bool fix_lex_user(THD *thd, LEX_USER *user) { @@ -1005,7 +1021,7 @@ static bool fix_lex_user(THD *thd, LEX_USER *user) } } - if (user->password.length) + if (user->password.length && !user->auth.length) { size_t scramble_length; void (*make_scramble)(char *, const char *, size_t); @@ -2691,32 +2707,23 @@ end: 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 + @param user User, hostname, new password or password hash @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) +bool check_change_password(THD *thd, LEX_USER *user) { - if (check_alter_user(thd, host, user)) - return 1; + LEX_USER *real_user= get_current_user(thd, user); - 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 0; + if (fix_and_copy_user(real_user, user, thd)) + return true; + + *user= *real_user; + + return check_alter_user(thd, user->host.str, user->user.str); } @@ -2724,39 +2731,33 @@ int check_change_password(THD *thd, const char *host, const char *user, Change a password for a user. @param thd THD - @param host Hostname - @param user User name - @param new_password New password hash for host@user + @param user User, hostname, new password hash @return Error code @retval 0 ok @retval 1 ERROR; In this case the error is sent to the client. */ -bool change_password(THD *thd, const char *host, const char *user, - char *new_password) +bool change_password(THD *thd, LEX_USER *user) { TABLE_LIST tables[TABLES_MAX]; /* Buffer should be extended when password length is extended. */ char buff[512]; ulong query_length= 0; enum_binlog_format save_binlog_format; - uint new_password_len= (uint) strlen(new_password); int result=0; const CSET_STRING query_save __attribute__((unused)) = thd->query_string; DBUG_ENTER("change_password"); DBUG_PRINT("enter",("host: '%s' user: '%s' new_password: '%s'", - host,user,new_password)); - DBUG_ASSERT(host != 0); // Ensured by parent - - if (check_change_password(thd, host, user, new_password, new_password_len)) - DBUG_RETURN(1); + user->host.str, user->user.str, user->password.str)); + DBUG_ASSERT(user->host.str != 0); // Ensured by parent if (mysql_bin_log.is_open() || (WSREP(thd) && !IF_WSREP(thd->wsrep_applier, 0))) { query_length= sprintf(buff, "SET PASSWORD FOR '%-.120s'@'%-.120s'='%-.120s'", - safe_str(user), safe_str(host), new_password); + safe_str(user->user.str), safe_str(user->host.str), + safe_str(user->password.str)); } if (WSREP(thd) && !IF_WSREP(thd->wsrep_applier, 0)) @@ -2781,7 +2782,7 @@ bool change_password(THD *thd, const char *host, const char *user, mysql_mutex_lock(&acl_cache->lock); ACL_USER *acl_user; - if (!(acl_user= find_user_exact(host, user))) + if (!(acl_user= find_user_exact(user->host.str, user->user.str))) { mysql_mutex_unlock(&acl_cache->lock); my_message(ER_PASSWORD_NO_MATCH, ER(ER_PASSWORD_NO_MATCH), MYF(0)); @@ -2792,10 +2793,10 @@ bool change_password(THD *thd, const char *host, const char *user, if (acl_user->plugin.str == native_password_plugin_name.str || acl_user->plugin.str == old_password_plugin_name.str) { - acl_user->auth_string.str= strmake_root(&acl_memroot, new_password, new_password_len); - acl_user->auth_string.length= new_password_len; - set_user_salt(acl_user, new_password, new_password_len); - set_user_plugin(acl_user, new_password_len); + acl_user->auth_string.str= strmake_root(&acl_memroot, user->password.str, user->password.length); + acl_user->auth_string.length= user->password.length; + set_user_salt(acl_user, user->password.str, user->password.length); + set_user_plugin(acl_user, user->password.length); } else push_warning(thd, Sql_condition::WARN_LEVEL_NOTE, @@ -2804,7 +2805,7 @@ bool change_password(THD *thd, const char *host, const char *user, if (update_user_table(thd, tables[USER_TABLE].table, safe_str(acl_user->host.hostname), safe_str(acl_user->user.str), - new_password, new_password_len)) + user->password.str, user->password.length)) { mysql_mutex_unlock(&acl_cache->lock); /* purecov: deadcode */ goto end; @@ -5660,11 +5661,8 @@ static bool has_auth(LEX_USER *user, LEX *lex) lex->mqh.specified_limits; } -static bool copy_and_check_auth(LEX_USER *to, LEX_USER *from, THD *thd) +static bool fix_and_copy_user(LEX_USER *to, LEX_USER *from, THD *thd) { - if (fix_lex_user(thd, from)) - return true; - if (to != from) { /* preserve authentication information, if LEX_USER was reallocated */ @@ -5673,6 +5671,17 @@ static bool copy_and_check_auth(LEX_USER *to, LEX_USER *from, THD *thd) to->auth= from->auth; } + if (fix_lex_user(thd, to)) + return true; + + return false; +} + +static bool copy_and_check_auth(LEX_USER *to, LEX_USER *from, THD *thd) +{ + if (fix_and_copy_user(to, from, thd)) + return true; + // if changing auth for an existing user if (has_auth(to, thd->lex) && find_user_exact(to->host.str, to->user.str)) { diff --git a/sql/sql_acl.h b/sql/sql_acl.h index 1bdf2820086..55b00a9494f 100644 --- a/sql/sql_acl.h +++ b/sql/sql_acl.h @@ -206,10 +206,8 @@ bool acl_authenticate(THD *thd, uint com_change_user_pkt_len); bool acl_getroot(Security_context *sctx, char *user, char *host, char *ip, char *db); bool acl_check_host(const char *host, const char *ip); -int check_change_password(THD *thd, const char *host, const char *user, - char *password, uint password_len); -bool change_password(THD *thd, const char *host, const char *user, - char *password); +bool check_change_password(THD *thd, LEX_USER *user); +bool change_password(THD *thd, LEX_USER *user); bool mysql_grant_role(THD *thd, List &user_list, bool revoke); bool mysql_grant(THD *thd, const char *db, List &user_list, diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 298301fbcef..52b7db7e617 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -1637,7 +1637,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); table_ident_opt_wild create_like %type - remember_name remember_end opt_db text_or_password remember_tok_start + remember_name remember_end opt_db remember_tok_start wild_and_where %type @@ -14045,7 +14045,6 @@ user_maybe_role: if (!($$=(LEX_USER*)thd->calloc(sizeof(LEX_USER)))) MYSQL_YYABORT; $$->user= current_user; - $$->password= null_lex_str; $$->plugin= empty_lex_str; $$->auth= empty_lex_str; } @@ -14753,41 +14752,17 @@ option_value_no_option_type: MYSQL_YYABORT; lex->var_list.push_back(var); } - | PASSWORD_SYM equal text_or_password + | PASSWORD_SYM opt_for_user text_or_password { - LEX *lex= thd->lex; - LEX_USER *user; - sp_pcontext *spc= lex->spcont; - LEX_STRING pw; - - pw.str= (char *)"password"; - pw.length= 8; - if (spc && spc->find_variable(pw, false)) - { - my_error(ER_SP_BAD_VAR_SHADOW, MYF(0), pw.str); - MYSQL_YYABORT; - } - if (!(user=(LEX_USER*) thd->calloc(sizeof(LEX_USER)))) - MYSQL_YYABORT; - user->user= current_user; - set_var_password *var= new set_var_password(user, $3); + LEX *lex = Lex; + set_var_password *var= new set_var_password(lex->definer); if (var == NULL) MYSQL_YYABORT; - thd->lex->var_list.push_back(var); - thd->lex->autocommit= TRUE; + lex->var_list.push_back(var); + lex->autocommit= TRUE; if (lex->sphead) lex->sphead->m_flags|= sp_head::HAS_SET_AUTOCOMMIT_STMT; } - | PASSWORD_SYM FOR_SYM user equal text_or_password - { - set_var_password *var= new set_var_password($3,$5); - if (var == NULL) - MYSQL_YYABORT; - Lex->var_list.push_back(var); - Lex->autocommit= TRUE; - if (Lex->sphead) - Lex->sphead->m_flags|= sp_head::HAS_SET_AUTOCOMMIT_STMT; - } ; @@ -14927,26 +14902,36 @@ isolation_types: | SERIALIZABLE_SYM { $$= ISO_SERIALIZABLE; } ; -text_or_password: - TEXT_STRING { $$=$1.str;} - | PASSWORD_SYM '(' TEXT_STRING ')' +opt_for_user: + equal { - $$= $3.length ? - Item_func_password::alloc(thd, $3.str, $3.length, - thd->variables.old_passwords ? - Item_func_password::OLD : - Item_func_password::NEW) : - $3.str; - if ($$ == NULL) + LEX *lex= thd->lex; + sp_pcontext *spc= lex->spcont; + LEX_STRING pw= { C_STRING_WITH_LEN("password") }; + + if (spc && spc->find_variable(pw, false)) + { + my_error(ER_SP_BAD_VAR_SHADOW, MYF(0), pw.str); MYSQL_YYABORT; + } + if (!(lex->definer= (LEX_USER*) thd->calloc(sizeof(LEX_USER)))) + MYSQL_YYABORT; + lex->definer->user= current_user; + lex->definer->plugin= empty_lex_str; + lex->definer->auth= empty_lex_str; } + | FOR_SYM user equal { Lex->definer= $2; } + ; + +text_or_password: + TEXT_STRING { Lex->definer->auth= $1;} + | PASSWORD_SYM '(' TEXT_STRING ')' { Lex->definer->password= $3; } | OLD_PASSWORD_SYM '(' TEXT_STRING ')' { - $$= $3.length ? Item_func_password:: - alloc(thd, $3.str, $3.length, Item_func_password::OLD) : - $3.str; - if ($$ == NULL) - MYSQL_YYABORT; + Lex->definer->password= $3; + Lex->definer->auth.str= Item_func_password::alloc(thd, + $3.str, $3.length, Item_func_password::OLD); + Lex->definer->auth.length= SCRAMBLED_PASSWORD_CHAR_LENGTH_323; } ;