18 #include <mysql/plugin_validate_password.h>
29 #if !defined(__attribute__) && (defined(__cplusplus) || !defined(__GNUC__) || __GNUC__ == 2 && __GNUC_MINOR__ < 8)
30 #define __attribute__(A)
33 #define MAX_DICTIONARY_FILE_LENGTH 1024 * 1024
34 #define PASSWORD_SCORE 25
35 #define MIN_DICTIONARY_WORD_LENGTH 4
36 #define MAX_PASSWORD_LENGTH 100
43 static MYSQL_PLUGIN plugin_info_ptr;
49 enum password_policy_enum { PASSWORD_POLICY_LOW,
50 PASSWORD_POLICY_MEDIUM,
51 PASSWORD_POLICY_STRONG
54 static const char* policy_names[] = {
"LOW",
"MEDIUM",
"STRONG", NullS };
56 static TYPELIB password_policy_typelib_t = {
57 array_elements(policy_names) - 1,
58 "password_policy_typelib_t",
63 typedef std::string string_type;
64 typedef std::set<string_type> set_type;
65 static set_type dictionary_words;
67 static int validate_password_length;
68 static int validate_password_number_count;
69 static int validate_password_mixed_case_count;
70 static int validate_password_special_char_count;
71 static ulong validate_password_policy;
72 static char *validate_password_dictionary_file;
75 static void read_dictionary_file()
80 if (validate_password_dictionary_file == NULL)
82 my_plugin_log_message(&plugin_info_ptr, MY_WARNING_LEVEL,
83 "Dictionary file not specified");
86 std::ifstream dictionary_stream(validate_password_dictionary_file);
87 if (!dictionary_stream)
89 my_plugin_log_message(&plugin_info_ptr, MY_WARNING_LEVEL,
90 "Dictionary file not loaded");
93 dictionary_stream.seekg(0, std::ios::end);
94 file_length= dictionary_stream.tellg();
95 dictionary_stream.seekg(0, std::ios::beg);
96 if (file_length > MAX_DICTIONARY_FILE_LENGTH)
98 dictionary_stream.close();
99 my_plugin_log_message(&plugin_info_ptr, MY_WARNING_LEVEL,
100 "Dictionary file size exceed",
101 "MAX_DICTIONARY_FILE_LENGTH, not loaded");
104 while (dictionary_stream.good())
106 std::getline(dictionary_stream, words);
107 dictionary_words.insert(words);
109 dictionary_stream.close();
113 static void free_dictionary_file()
115 if (!dictionary_words.empty())
116 dictionary_words.clear();
123 static int validate_dictionary_check(mysql_string_handle password)
128 mysql_string_handle lower_string_handle= mysql_string_to_lowercase(password);
129 if (!(buffer= (
char*) malloc(MAX_PASSWORD_LENGTH)))
132 length= mysql_string_convert_to_char_ptr(lower_string_handle,
"utf8",
133 buffer, MAX_PASSWORD_LENGTH,
136 int substr_length= length;
137 string_type password_str= (
const char *)buffer;
138 string_type password_substr;
139 set_type::iterator itr;
144 if (!dictionary_words.empty())
146 while (substr_length >= MIN_DICTIONARY_WORD_LENGTH)
149 while (substr_pos + substr_length <= length)
151 password_substr= password_str.substr(substr_pos, substr_length);
152 itr= dictionary_words.find(password_substr);
153 if (itr != dictionary_words.end())
167 static int validate_password_policy_strength(mysql_string_handle password,
173 int has_special_chars= 0;
175 mysql_string_iterator_handle iter;
177 iter = mysql_string_get_iterator(password);
178 while(mysql_string_iterator_next(iter))
181 if (policy > PASSWORD_POLICY_LOW)
183 if (mysql_string_iterator_islower(iter))
185 else if (mysql_string_iterator_isupper(iter))
187 else if (mysql_string_iterator_isdigit(iter))
194 mysql_string_iterator_free(iter);
195 if (n_chars >= validate_password_length)
197 if (policy == PASSWORD_POLICY_LOW)
199 if (has_upper >= validate_password_mixed_case_count &&
200 has_lower >= validate_password_mixed_case_count &&
201 has_special_chars >= validate_password_special_char_count &&
202 has_digit >= validate_password_number_count)
204 if (policy == PASSWORD_POLICY_MEDIUM || validate_dictionary_check
213 static int validate_password(mysql_string_handle password)
215 return validate_password_policy_strength(password, validate_password_policy);
219 static int get_password_strength(mysql_string_handle password)
223 mysql_string_iterator_handle iter;
225 iter = mysql_string_get_iterator(password);
226 while(mysql_string_iterator_next(iter))
229 mysql_string_iterator_free(iter);
230 if (n_chars < MIN_DICTIONARY_WORD_LENGTH)
232 if (n_chars < validate_password_length)
233 return (PASSWORD_SCORE);
236 policy= PASSWORD_POLICY_LOW;
237 if (validate_password_policy_strength(password, PASSWORD_POLICY_MEDIUM))
239 policy= PASSWORD_POLICY_MEDIUM;
240 if (validate_dictionary_check(password))
241 policy= PASSWORD_POLICY_STRONG;
244 return ((policy+1) * PASSWORD_SCORE + PASSWORD_SCORE);
250 MYSQL_VALIDATE_PASSWORD_INTERFACE_VERSION,
252 get_password_strength
260 static int validate_password_init(MYSQL_PLUGIN plugin_info)
262 plugin_info_ptr= plugin_info;
263 read_dictionary_file();
272 static int validate_password_deinit(
void *arg __attribute__((unused)))
274 free_dictionary_file();
287 length_update(MYSQL_THD thd __attribute__((unused)),
289 void *var_ptr,
const void *save)
291 int new_validate_password_length;
294 if (*((
int *)var_ptr) == *((
int *)save))
304 *((
int *)var_ptr)= *((
int *)save);
317 new_validate_password_length= (validate_password_number_count +
318 (2 * validate_password_mixed_case_count) +
319 validate_password_special_char_count);
321 if (validate_password_length < new_validate_password_length)
327 my_plugin_log_message(&plugin_info_ptr, MY_WARNING_LEVEL,
328 "Effective value of validate_password_length is changed. New value is %d",
329 new_validate_password_length);
331 validate_password_length= new_validate_password_length;
339 static MYSQL_SYSVAR_INT(length, validate_password_length,
341 "Password validate length to check for minimum password_length",
342 NULL, length_update, 8, 0, 0, 0);
344 static MYSQL_SYSVAR_INT(number_count, validate_password_number_count,
346 "password validate digit to ensure minimum numeric character in password",
347 NULL, length_update, 1, 0, 0, 0);
349 static MYSQL_SYSVAR_INT(mixed_case_count, validate_password_mixed_case_count,
351 "Password validate mixed case to ensure minimum upper/lower case in password",
352 NULL, length_update, 1, 0, 0, 0);
354 static MYSQL_SYSVAR_INT(special_char_count,
355 validate_password_special_char_count, PLUGIN_VAR_RQCMDARG,
356 "password validate special to ensure minimum special character in password",
357 NULL, length_update, 1, 0, 0, 0);
359 static MYSQL_SYSVAR_ENUM(policy, validate_password_policy,
361 "password_validate_policy choosen policy to validate password"
362 "possible values are LOW MEDIUM (default), STRONG",
363 NULL, NULL, PASSWORD_POLICY_MEDIUM, &password_policy_typelib_t);
365 static MYSQL_SYSVAR_STR(dictionary_file, validate_password_dictionary_file,
367 "password_validate_dictionary file to be loaded and check for password",
371 MYSQL_SYSVAR(length),
372 MYSQL_SYSVAR(number_count),
373 MYSQL_SYSVAR(mixed_case_count),
374 MYSQL_SYSVAR(special_char_count),
375 MYSQL_SYSVAR(policy),
376 MYSQL_SYSVAR(dictionary_file),
380 mysql_declare_plugin(validate_password)
382 MYSQL_VALIDATE_PASSWORD_PLUGIN,
383 &validate_password_descriptor,
385 "Oracle Corporation",
386 "check password strength",
388 validate_password_init,
389 validate_password_deinit,
392 validate_password_system_variables,
396 mysql_declare_plugin_end;