From: Ashish Agarwal Date: April 30 2012 6:36am Subject: bzr push into mysql-trunk branch (ashish.y.agarwal:3879 to 3880) List-Archive: http://lists.mysql.com/commits/143676 Message-Id: <201204300636.q3U6avRv021782@acsmt357.oracle.com> MIME-Version: 1.0 Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit 3880 Ashish Agarwal 2012-04-30 WL2739: Including password validation on multiple character set. modified: include/mysql/plugin_validate_password.h mysql-test/r/validate_password_plugin.result mysql-test/t/validate_password_plugin.test plugin/password_validation/validate_password.cc sql/item_func.cc sql/item_strfunc.cc sql/sql_acl.cc sql/sql_acl.h sql/sql_plugin.h sql/sql_yacc.yy 3879 Ashish Agarwal 2012-04-24 wl2739: Build failure in windows and some additional changes. modified: plugin/password_validation/validate_password.cc sql/sql_plugin.h === modified file 'include/mysql/plugin_validate_password.h' --- a/include/mysql/plugin_validate_password.h 2012-04-24 11:58:02 +0000 +++ b/include/mysql/plugin_validate_password.h 2012-04-30 06:36:01 +0000 @@ -20,6 +20,7 @@ #define MYSQL_PLUGIN_VALIDATE_PASSWORD_INCLUDED #include +#include #define MYSQL_VALIDATE_PASSWORD_INTERFACE_VERSION 0x0100 @@ -36,11 +37,11 @@ struct st_mysql_validate_password policy (as choosen by plugin variable) and FALSE for all other password */ - bool (*validate_password)(const char *password); + bool (*validate_password)(String *password); /** This function returns the highest policy number which the password satisfy. */ - uint (*validate_password_strength)(const char *password); + uint (*validate_password_strength)(String *password); }; #endif === modified file 'mysql-test/r/validate_password_plugin.result' --- a/mysql-test/r/validate_password_plugin.result 2012-04-23 15:41:09 +0000 +++ b/mysql-test/r/validate_password_plugin.result 2012-04-30 06:36:01 +0000 @@ -1,10 +1,11 @@ +CALL mtr.add_suppression("dictionary file not found"); +CALL mtr.add_suppression("dictionary file size too large"); CREATE USER 'base_user'@'localhost' IDENTIFIED BY ''; INSTALL PLUGIN validate_password SONAME 'validate_password.so'; INSTALL PLUGIN validate_password SONAME 'validate_password.so'; ERROR HY000: Function 'validate_password' already exists -policy: low= 1, medium= 2, strong= 3 -password policy low (which only check for password length) -default case: password length should be minimum 8 +# password policy low (which only check for password length) +# default case: password length should be minimum 8 SET @@global.validate_password_policy_number= 1; CREATE USER 'user'@'localhost' IDENTIFIED BY ''; ERROR HY000: not a valid password '' @@ -14,8 +15,8 @@ UPDATE mysql.user SET PASSWORD= PASSWORD ERROR HY000: not a valid password 'password' GRANT USAGE ON *.* TO 'base_user'@'localhost' IDENTIFIED BY 'password1234'; SET @@global.validate_password_length= 8; -password policy medium (check for mixed_case, digits, special_chars) -default case : atleast 1 mixed_case, 1 digit, 1 special_char +# password policy medium (check for mixed_case, digits, special_chars) +# default case : atleast 1 mixed_case, 1 digit, 1 special_char SET @@global.validate_password_policy_number= 2; CREATE USER 'user'@'localhost' IDENTIFIED BY 'password'; ERROR HY000: not a valid password 'password' @@ -41,9 +42,9 @@ SET @@global.validate_password_policy_nu CREATE USER 'user'@'localhost' IDENTIFIED BY 'password'; ERROR HY000: not a valid password 'password' SET PASSWORD FOR 'base_user'@'localhost'= PASSWORD('password1A#'); -ERROR HY000: not a valid password 'password1A#' +ERROR HY000: not a valid password 'password1a#' UPDATE mysql.user SET PASSWORD= PASSWORD('pass12345A#') WHERE user='base_user'; -ERROR HY000: not a valid password 'pass12345A#' +ERROR HY000: not a valid password 'pass12345a#' GRANT USAGE ON *.* TO 'base_user'@'localhost' IDENTIFIED BY 'PA12wrd!#'; # test for password_validate_strength function SELECT VALIDATE_PASSWORD_STRENGTH('password', 0); @@ -51,9 +52,11 @@ ERROR 42000: Incorrect parameter count i SELECT VALIDATE_PASSWORD_STRENGTH(); ERROR 42000: Incorrect parameter count in the call to native function 'VALIDATE_PASSWORD_STRENGTH' SELECT VALIDATE_PASSWORD_STRENGTH(''); -ERROR HY000: not a valid password '' +VALIDATE_PASSWORD_STRENGTH('') +0 SELECT VALIDATE_PASSWORD_STRENGTH('pass'); -ERROR HY000: not a valid password 'pass' +VALIDATE_PASSWORD_STRENGTH('pass') +0 SELECT VALIDATE_PASSWORD_STRENGTH('password'); VALIDATE_PASSWORD_STRENGTH('password') 1 @@ -63,5 +66,20 @@ VALIDATE_PASSWORD_STRENGTH('password1A#' SELECT VALIDATE_PASSWORD_STRENGTH('PA12wrd!#'); VALIDATE_PASSWORD_STRENGTH('PA12wrd!#') 3 +SET NAMES 'ujis'; +SELECT VALIDATE_PASSWORD_STRENGTH('PA12wrd!#'); +VALIDATE_PASSWORD_STRENGTH('PA12wrd!#') +3 +UNINSTALL PLUGIN validate_password; +# restarting the server with no dictionary file. +# Restart server. +INSTALL PLUGIN validate_password SONAME 'validate_password.so'; +SET @@global.validate_password_policy_number= 3; +# same password was not accepted as it was present in the dictionary file +SET PASSWORD FOR 'base_user'@'localhost'= PASSWORD('password1A#'); +# As no dictionary file is present password strength is 3 +SELECT VALIDATE_PASSWORD_STRENGTH('password1A#'); +VALIDATE_PASSWORD_STRENGTH('password1A#') +3 DROP USER 'base_user'@'localhost'; UNINSTALL PLUGIN validate_password; === modified file 'mysql-test/t/validate_password_plugin.test' --- a/mysql-test/t/validate_password_plugin.test 2012-04-23 15:41:09 +0000 +++ b/mysql-test/t/validate_password_plugin.test 2012-04-30 06:36:01 +0000 @@ -1,5 +1,7 @@ --source include/not_embedded.inc --source include/have_validate_password_plugin.inc +CALL mtr.add_suppression("dictionary file not found"); +CALL mtr.add_suppression("dictionary file size too large"); CREATE USER 'base_user'@'localhost' IDENTIFIED BY ''; --replace_regex /\.dll/.so/ @@ -8,10 +10,10 @@ eval INSTALL PLUGIN validate_password SO eval INSTALL PLUGIN validate_password SONAME '$VALIDATE_PASSWORD'; # test for all the three password policy ---echo policy: low= 1, medium= 2, strong= 3 +# policy: low= 1, medium= 2, strong= 3 ---echo password policy low (which only check for password length) ---echo default case: password length should be minimum 8 +--echo # password policy low (which only check for password length) +--echo # default case: password length should be minimum 8 SET @@global.validate_password_policy_number= 1; --error ER_NOT_VALID_PASSWORD @@ -23,8 +25,8 @@ UPDATE mysql.user SET PASSWORD= PASSWORD GRANT USAGE ON *.* TO 'base_user'@'localhost' IDENTIFIED BY 'password1234'; SET @@global.validate_password_length= 8; ---echo password policy medium (check for mixed_case, digits, special_chars) ---echo default case : atleast 1 mixed_case, 1 digit, 1 special_char +--echo # password policy medium (check for mixed_case, digits, special_chars) +--echo # default case : atleast 1 mixed_case, 1 digit, 1 special_char SET @@global.validate_password_policy_number= 2; --error ER_NOT_VALID_PASSWORD @@ -59,19 +61,59 @@ SET PASSWORD FOR 'base_user'@'localhost' UPDATE mysql.user SET PASSWORD= PASSWORD('pass12345A#') WHERE user='base_user'; GRANT USAGE ON *.* TO 'base_user'@'localhost' IDENTIFIED BY 'PA12wrd!#'; + --echo # test for password_validate_strength function --error ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT SELECT VALIDATE_PASSWORD_STRENGTH('password', 0); --error ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT SELECT VALIDATE_PASSWORD_STRENGTH(); ---error ER_NOT_VALID_PASSWORD SELECT VALIDATE_PASSWORD_STRENGTH(''); ---error ER_NOT_VALID_PASSWORD SELECT VALIDATE_PASSWORD_STRENGTH('pass'); SELECT VALIDATE_PASSWORD_STRENGTH('password'); SELECT VALIDATE_PASSWORD_STRENGTH('password1A#'); SELECT VALIDATE_PASSWORD_STRENGTH('PA12wrd!#'); +# Multibyte character set that have greater size when converted +# from uppercase to lowercase. + +SET NAMES 'ujis'; +SELECT VALIDATE_PASSWORD_STRENGTH('PA12wrd!#'); + +UNINSTALL PLUGIN validate_password; + +--echo # restarting the server with no dictionary file. + +# Write file to make mysql-test-run.pl wait for the server to stop +-- exec echo "wait" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect + +# Request shutdown +-- send_shutdown + +# Call script that will poll the server waiting for it to disapear +-- source include/wait_until_disconnected.inc + +--echo # Restart server. +--exec echo "restart:--loose-validate_password_dictionary_file='NO'" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect + +# Turn on reconnect +--enable_reconnect + +# Call script that will poll the server waiting for it to be back online again +--source include/wait_until_connected_again.inc + +# Turn off reconnect again +--disable_reconnect + +--replace_regex /\.dll/.so/ +eval INSTALL PLUGIN validate_password SONAME '$VALIDATE_PASSWORD'; + +SET @@global.validate_password_policy_number= 3; +--echo # same password was not accepted as it was present in the dictionary file +SET PASSWORD FOR 'base_user'@'localhost'= PASSWORD('password1A#'); + +--echo # As no dictionary file is present password strength is 3 +SELECT VALIDATE_PASSWORD_STRENGTH('password1A#'); + DROP USER 'base_user'@'localhost'; UNINSTALL PLUGIN validate_password; === modified file 'plugin/password_validation/validate_password.cc' --- a/plugin/password_validation/validate_password.cc 2012-04-24 15:36:30 +0000 +++ b/plugin/password_validation/validate_password.cc 2012-04-30 06:36:01 +0000 @@ -14,23 +14,22 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include -#include -#include #include #include #include -#include +#include /* dictionary file read */ #include -#include "sql_plugin.h" +#include "sql_plugin.h" /* for opt_plugin_dir */ +#include "log.h" /* for warning */ #define MAX_DICTIONARY_FILE_LENGTH 1024 * 1024 -#define MIN_DICTIONARY_WORD_LENGTH 3 +#define MIN_DICTIONARY_WORD_LENGTH 4 enum PASSWORD_POLICY { PASSWORD_STRENGTH_REJECTED, PASSWORD_STRENGTH_LOW, PASSWORD_STRENGTH_MEDIUM, PASSWORD_STRENGTH_STRONG }; typedef std::string string_type; -typedef std::set set_type; +typedef std::set set_type; set_type dictionary_words; static uint validate_password_length; @@ -40,63 +39,83 @@ static uint validate_password_special_ch static uint validate_password_policy_number; static char *validate_password_dictionary_file; -static bool validate_dictionary_check(const char *password) +static bool validate_dictionary_check(String *password) { - string_type password_str(password); + const CHARSET_INFO *cs= password->charset(); + String tmp_password_str; + if (cs->casedn_multiply == 1) + { + uint len; + len= cs->cset->casedn(cs, (char*) password->ptr(), password->length(), + (char*) password->ptr(), password->length()); + password->length(len); + } + else + { + uint len= password->length() * cs->casedn_multiply; + tmp_password_str.alloc(len); + tmp_password_str.set_charset(cs); + len= cs->cset->casedn(cs, (char*) password->ptr(), password->length(), + (char*) tmp_password_str.ptr(), len); + tmp_password_str.length(len); + password= &tmp_password_str; + } + uint substr_pos= 0; + uint substr_length= password->length(); + string_type password_str(password->ptr()); string_type password_substr; set_type::iterator itr; - uint password_length= strlen(password); - uint substr_length= password_length; - uint substr_pos= 0; + while (substr_length >= MIN_DICTIONARY_WORD_LENGTH) { substr_pos= 0; - while (substr_pos + substr_length <= password_length) + while (substr_pos + substr_length <= password->length()) { password_substr= password_str.substr(substr_pos, substr_length); - std::transform(password_substr.begin(), password_substr.end(), - password_substr.begin(), (int(*)(int)) std::toupper); itr= dictionary_words.find(password_substr); if (itr != dictionary_words.end()) return 0; substr_pos++; - } - substr_length--; - } + } + substr_length--; + } return 1; } -static bool validate_password_policy(const char *password, uint policy) +static bool validate_password_policy(String *password, uint policy) { uint has_numbers= 0; uint has_special_chars= 0; uint has_lower= 0; uint has_upper= 0; - uint password_length= strlen(password); - const char *c= password; - if (password_length >= validate_password_length) + char *beg= (char*) password->ptr(); + char *end= (char*) password->ptr() + password->length(); + const CHARSET_INFO *cs= password->charset(); + if (password->length() >= validate_password_length) { if (policy == PASSWORD_STRENGTH_LOW) return 1; - while (*c != '\0') + while (beg < end) { - if (my_isdigit(&my_charset_latin1, *c)) + int ctype, char_len; + char_len= cs->cset->ctype(cs, &ctype, (uchar*) beg, (uchar*) end); + if (ctype & _MY_NMR) has_numbers++; - else if (my_isupper(&my_charset_latin1, *c)) + else if (ctype & _MY_U) has_upper++; - else if (my_islower(&my_charset_latin1, *c)) + else if (ctype & _MY_L) has_lower++; else has_special_chars++; - c++; + beg+= (char_len > 0 ? char_len : (char_len < 0 ? -char_len : 1)); } - if (has_upper >= validate_password_mixed_case_count && has_lower >= - validate_password_mixed_case_count && has_special_chars >= - validate_password_special_char_count && has_numbers >= - validate_password_number_count) + if (has_upper >= validate_password_mixed_case_count && + has_lower >= validate_password_mixed_case_count && + has_special_chars >= validate_password_special_char_count && + has_numbers >= validate_password_number_count) { - if (policy == PASSWORD_STRENGTH_MEDIUM || + if (policy == PASSWORD_STRENGTH_MEDIUM || dictionary_words.empty() || validate_dictionary_check(password)) return 1; } @@ -104,12 +123,12 @@ static bool validate_password_policy(con return 0; } -static bool validate_password(const char *password) +static bool validate_password(String *password) { return validate_password_policy(password, validate_password_policy_number); } -static uint validate_password_strength(const char *password) +static uint validate_password_strength(String *password) { if (validate_password_policy(password, PASSWORD_STRENGTH_LOW)) { @@ -117,7 +136,7 @@ static uint validate_password_strength(c if (validate_password_policy(password, PASSWORD_STRENGTH_MEDIUM)) { policy= PASSWORD_STRENGTH_MEDIUM; - if (validate_password_policy(password, PASSWORD_STRENGTH_STRONG)) + if (dictionary_words.empty() || validate_dictionary_check(password)) policy= PASSWORD_STRENGTH_STRONG; } return policy; @@ -141,29 +160,31 @@ static int validate_password_init(void * char *dictionary_file; char default_dictionary_file[FN_REFLEN]; string_type words; - long file_length; - fn_format(default_dictionary_file, "dictionary.txt", opt_plugin_dir, + ulonglong file_length; + fn_format(default_dictionary_file, "dictionary.txt", opt_plugin_dir_ptr, "", MYF(0)); dictionary_file= (validate_password_dictionary_file ? validate_password_dictionary_file : default_dictionary_file); - std::ifstream dictionary_stream(dictionary_file); if (!dictionary_stream) - return 1; + { + if (validate_password_dictionary_file) + sql_print_warning("dictionary file not found"); + return 0; + } dictionary_stream.seekg (0, std::ios::end); - file_length = dictionary_stream.tellg(); + file_length= dictionary_stream.tellg(); dictionary_stream.seekg (0, std::ios::beg); if (file_length > MAX_DICTIONARY_FILE_LENGTH) { dictionary_stream.close(); - return 1; + sql_print_warning("dictionary file size too large"); + return 0; } while (dictionary_stream.good()) { std::getline(dictionary_stream, words); - std::transform(words.begin(), words.end(), words.begin(), - (int(*)(int)) std::toupper); dictionary_words.insert(words); } dictionary_stream.close(); @@ -183,7 +204,7 @@ static int validate_password_deinit(void static MYSQL_SYSVAR_UINT(length, validate_password_length, PLUGIN_VAR_RQCMDARG, - "Password_validate_length to check for minimum password_length", + "Password validate length to check for minimum password_length", NULL, NULL, 8, 0, 0, 0); static MYSQL_SYSVAR_UINT(number_count, validate_password_number_count, === modified file 'sql/item_func.cc' --- a/sql/item_func.cc 2012-04-16 12:25:21 +0000 +++ b/sql/item_func.cc 2012-04-30 06:36:01 +0000 @@ -3233,8 +3233,7 @@ longlong Item_func_validate_password_str String *field; if (!(field= args[0]->val_str(&value))) return 0; - const char * password= field->ptr(); - return (check_password_strength(password)); + return (check_password_strength(field)); } === modified file 'sql/item_strfunc.cc' --- a/sql/item_strfunc.cc 2012-04-23 15:41:09 +0000 +++ b/sql/item_strfunc.cc 2012-04-30 06:36:01 +0000 @@ -1925,7 +1925,7 @@ String *Item_func_password::val_str_asci String *res= args[0]->val_str(str); if ((null_value=args[0]->null_value)) return 0; - check_password_policy(res->ptr()); + check_password_policy(res); if (res->length() == 0) return make_empty_result(); my_make_scrambled_password(tmp_value, res->ptr(), res->length()); === modified file 'sql/sql_acl.cc' --- a/sql/sql_acl.cc 2012-04-23 15:41:09 +0000 +++ b/sql/sql_acl.cc 2012-04-30 06:36:01 +0000 @@ -9917,7 +9917,7 @@ mysql_declare_plugin_end; /* for validate_password_strength SQL function */ -int check_password_strength(const char *password) +int check_password_strength(String *password) { int res= 0; plugin_ref plugin= my_plugin_lock_by_name(0, &validate_password_plugin_name, @@ -9927,8 +9927,7 @@ int check_password_strength(const char * st_mysql_validate_password *password_strength= (st_mysql_validate_password *) plugin_decl(plugin)->info; - if (!(res= password_strength->validate_password_strength(password))) - my_error(ER_NOT_VALID_PASSWORD, MYF(0), password); + res= password_strength->validate_password_strength(password); plugin_unlock(0, plugin); } return(res); @@ -9936,7 +9935,7 @@ int check_password_strength(const char * /* called when new user is created or exsisting password is changed */ -void check_password_policy(const char *password) +void check_password_policy(String *password) { plugin_ref plugin= my_plugin_lock_by_name(0, &validate_password_plugin_name, MYSQL_VALIDATE_PASSWORD_PLUGIN); @@ -9946,7 +9945,7 @@ void check_password_policy(const char *p (st_mysql_validate_password *) plugin_decl(plugin)->info; if (!password_validate->validate_password(password)) - my_error(ER_NOT_VALID_PASSWORD, MYF(0), password); + my_error(ER_NOT_VALID_PASSWORD, MYF(0), password->ptr()); plugin_unlock(0, plugin); } } === modified file 'sql/sql_acl.h' --- a/sql/sql_acl.h 2012-04-23 15:41:09 +0000 +++ b/sql/sql_acl.h 2012-04-30 06:36:01 +0000 @@ -251,8 +251,8 @@ int fill_schema_schema_privileges(THD *t int fill_schema_table_privileges(THD *thd, TABLE_LIST *tables, Item *cond); int fill_schema_column_privileges(THD *thd, TABLE_LIST *tables, Item *cond); int wild_case_compare(CHARSET_INFO *cs, const char *str,const char *wildstr); -int check_password_strength(const char *password); -void check_password_policy(const char *password); +int check_password_strength(String *password); +void check_password_policy(String *password); #ifdef NO_EMBEDDED_ACCESS_CHECKS #define check_grant(A,B,C,D,E,F) 0 === modified file 'sql/sql_plugin.h' --- a/sql/sql_plugin.h 2012-04-24 15:36:30 +0000 +++ b/sql/sql_plugin.h 2012-04-30 06:36:01 +0000 @@ -135,8 +135,8 @@ typedef struct st_plugin_int **plugin_re typedef int (*plugin_type_init)(struct st_plugin_int *); extern I_List *opt_plugin_load_list_ptr; -extern char *opt_plugin_dir_ptr; -extern MYSQL_PLUGIN_IMPORT char opt_plugin_dir[FN_REFLEN]; +extern MYSQL_PLUGIN_IMPORT char *opt_plugin_dir_ptr; +extern char opt_plugin_dir[FN_REFLEN]; extern const LEX_STRING plugin_type_names[]; extern int plugin_init(int *argc, char **argv, int init_flags); extern void plugin_shutdown(void); === modified file 'sql/sql_yacc.yy' --- a/sql/sql_yacc.yy 2012-04-23 15:41:09 +0000 +++ b/sql/sql_yacc.yy 2012-04-30 06:36:01 +0000 @@ -14199,7 +14199,9 @@ text_or_password: TEXT_STRING { $$=$1.str;} | PASSWORD '(' TEXT_STRING ')' { - check_password_policy($3.str); + String *password = new (YYTHD->mem_root) String((const char*)$3.str, + YYTHD->variables.character_set_client); + check_password_policy(password); $$= $3.length ? YYTHD->variables.old_passwords ? Item_func_old_password::alloc(YYTHD, $3.str, $3.length) : Item_func_password::alloc(YYTHD, $3.str, $3.length) : @@ -14708,7 +14710,9 @@ grant_user: $$=$1; $1->password=$4; if (Lex->sql_command == SQLCOM_REVOKE) MYSQL_YYABORT; - check_password_policy($4.str); + String *password = new (YYTHD->mem_root) String((const char*)$4.str, + YYTHD->variables.character_set_client); + check_password_policy(password); if ($4.length) { if (YYTHD->variables.old_passwords) No bundle (reason: useless for push emails).