MySQL 5.6.14 Source Code Document
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
validate_password.cc
1 /* Copyright © 2012, Oracle and/or its affiliates. All rights reserved.
2 
3  This program is free software; you can redistribute it and/or modify
4  it under the terms of the GNU General Public License as published by
5  the Free Software Foundation; version 2 of the License.
6 
7  This program is distributed in the hope that it will be useful,
8  but WITHOUT ANY WARRANTY; without even the implied warranty of
9  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10  GNU General Public License for more details.
11 
12  You should have received a copy of the GNU General Public License
13  along with this program; if not, write to the Free Software
14  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
15 
16 #include <my_sys.h>
17 #include <string>
18 #include <mysql/plugin_validate_password.h>
19 #include <set>
20 #include <iostream>
21 #include <fstream>
22 
23 
24 /*
25  __attribute__(A) needs to be defined for Windows else complier
26  do not recognise it. Argument in plugin_init and plugin_deinit
27  Used in other plugins as well.
28 */
29 #if !defined(__attribute__) && (defined(__cplusplus) || !defined(__GNUC__) || __GNUC__ == 2 && __GNUC_MINOR__ < 8)
30 #define __attribute__(A)
31 #endif
32 
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
37 
38 /*
39  Handle assigned when loading the plugin.
40  Used with the error reporting functions.
41 */
42 
43 static MYSQL_PLUGIN plugin_info_ptr;
44 /*
45  These are the 3 password policies that this plugin allow to set
46  and configure as per the requirements.
47 */
48 
49 enum password_policy_enum { PASSWORD_POLICY_LOW,
50  PASSWORD_POLICY_MEDIUM,
51  PASSWORD_POLICY_STRONG
52 };
53 
54 static const char* policy_names[] = { "LOW", "MEDIUM", "STRONG", NullS };
55 
56 static TYPELIB password_policy_typelib_t = {
57  array_elements(policy_names) - 1,
58  "password_policy_typelib_t",
59  policy_names,
60  NULL
61 };
62 
63 typedef std::string string_type;
64 typedef std::set<string_type> set_type;
65 static set_type dictionary_words;
66 
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;
73 
74 /* To read dictionary file into std::set */
75 static void read_dictionary_file()
76 {
77  string_type words;
78  long file_length;
79 
80  if (validate_password_dictionary_file == NULL)
81  {
82  my_plugin_log_message(&plugin_info_ptr, MY_WARNING_LEVEL,
83  "Dictionary file not specified");
84  return;
85  }
86  std::ifstream dictionary_stream(validate_password_dictionary_file);
87  if (!dictionary_stream)
88  {
89  my_plugin_log_message(&plugin_info_ptr, MY_WARNING_LEVEL,
90  "Dictionary file not loaded");
91  return;
92  }
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)
97  {
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");
102  return;
103  }
104  while (dictionary_stream.good())
105  {
106  std::getline(dictionary_stream, words);
107  dictionary_words.insert(words);
108  }
109  dictionary_stream.close();
110 }
111 
112 /* Clear words from std::set */
113 static void free_dictionary_file()
114 {
115  if (!dictionary_words.empty())
116  dictionary_words.clear();
117 }
118 
119 /*
120  Checks whether password or substring of password
121  is present in dictionary file stored as std::set
122 */
123 static int validate_dictionary_check(mysql_string_handle password)
124 {
125  int length;
126  int error= 0;
127  char *buffer;
128  mysql_string_handle lower_string_handle= mysql_string_to_lowercase(password);
129  if (!(buffer= (char*) malloc(MAX_PASSWORD_LENGTH)))
130  return (0);
131 
132  length= mysql_string_convert_to_char_ptr(lower_string_handle, "utf8",
133  buffer, MAX_PASSWORD_LENGTH,
134  &error);
135  int substr_pos= 0;
136  int substr_length= length;
137  string_type password_str= (const char *)buffer;
138  string_type password_substr;
139  set_type::iterator itr;
140  /*
141  std::set as container stores the dictionary words,
142  binary comparison between dictionary words and password
143  */
144  if (!dictionary_words.empty())
145  {
146  while (substr_length >= MIN_DICTIONARY_WORD_LENGTH)
147  {
148  substr_pos= 0;
149  while (substr_pos + substr_length <= length)
150  {
151  password_substr= password_str.substr(substr_pos, substr_length);
152  itr= dictionary_words.find(password_substr);
153  if (itr != dictionary_words.end())
154  {
155  free(buffer);
156  return (0);
157  }
158  substr_pos++;
159  }
160  substr_length--;
161  }
162  }
163  free(buffer);
164  return (1);
165 }
166 
167 static int validate_password_policy_strength(mysql_string_handle password,
168  int policy)
169 {
170  int has_digit= 0;
171  int has_lower= 0;
172  int has_upper= 0;
173  int has_special_chars= 0;
174  int n_chars= 0;
175  mysql_string_iterator_handle iter;
176 
177  iter = mysql_string_get_iterator(password);
178  while(mysql_string_iterator_next(iter))
179  {
180  n_chars++;
181  if (policy > PASSWORD_POLICY_LOW)
182  {
183  if (mysql_string_iterator_islower(iter))
184  has_lower++;
185  else if (mysql_string_iterator_isupper(iter))
186  has_upper++;
187  else if (mysql_string_iterator_isdigit(iter))
188  has_digit++;
189  else
190  has_special_chars++;
191  }
192  }
193 
194  mysql_string_iterator_free(iter);
195  if (n_chars >= validate_password_length)
196  {
197  if (policy == PASSWORD_POLICY_LOW)
198  return (1);
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)
203  {
204  if (policy == PASSWORD_POLICY_MEDIUM || validate_dictionary_check
205  (password))
206  return (1);
207  }
208  }
209  return (0);
210 }
211 
212 /* Actual plugin function which acts as a wrapper */
213 static int validate_password(mysql_string_handle password)
214 {
215  return validate_password_policy_strength(password, validate_password_policy);
216 }
217 
218 /* Password strength between (0-100) */
219 static int get_password_strength(mysql_string_handle password)
220 {
221  int policy= 0;
222  int n_chars= 0;
223  mysql_string_iterator_handle iter;
224 
225  iter = mysql_string_get_iterator(password);
226  while(mysql_string_iterator_next(iter))
227  n_chars++;
228 
229  mysql_string_iterator_free(iter);
230  if (n_chars < MIN_DICTIONARY_WORD_LENGTH)
231  return (policy);
232  if (n_chars < validate_password_length)
233  return (PASSWORD_SCORE);
234  else
235  {
236  policy= PASSWORD_POLICY_LOW;
237  if (validate_password_policy_strength(password, PASSWORD_POLICY_MEDIUM))
238  {
239  policy= PASSWORD_POLICY_MEDIUM;
240  if (validate_dictionary_check(password))
241  policy= PASSWORD_POLICY_STRONG;
242  }
243  }
244  return ((policy+1) * PASSWORD_SCORE + PASSWORD_SCORE);
245 }
246 
247 /* Plugin type-specific descriptor */
248 static struct st_mysql_validate_password validate_password_descriptor=
249 {
250  MYSQL_VALIDATE_PASSWORD_INTERFACE_VERSION,
251  validate_password, /* validate function */
252  get_password_strength /* validate strength function */
253 };
254 
255 /*
256  Initialize the password plugin at server start or plugin installation,
257  read dictionary file into std::set.
258 */
259 
260 static int validate_password_init(MYSQL_PLUGIN plugin_info)
261 {
262  plugin_info_ptr= plugin_info;
263  read_dictionary_file();
264  return (0);
265 }
266 
267 /*
268  Terminate the password plugin at server shutdown or plugin deinstallation.
269  It empty the std::set and returns 0
270 */
271 
272 static int validate_password_deinit(void *arg __attribute__((unused)))
273 {
274  free_dictionary_file();
275  return (0);
276 }
277 
278 
279 /*
280  update function for:
281  1. validate_password_length
282  2. validate_password_number_count
283  3. validate_password_mixed_case_count
284  4. validate_password_special_char_count
285 */
286 static void
287 length_update(MYSQL_THD thd __attribute__((unused)),
288  struct st_mysql_sys_var *var __attribute__((unused)),
289  void *var_ptr, const void *save)
290 {
291  int new_validate_password_length;
292 
293  /* check if there is an actual change */
294  if (*((int *)var_ptr) == *((int *)save))
295  return;
296 
297  /*
298  set new value for system variable.
299  Note that we need not know for which of the above mentioned
300  variables, length_update() is called because var_ptr points
301  to the location at which corresponding static variable is
302  declared in this file.
303  */
304  *((int *)var_ptr)= *((int *)save);
305 
306  /*
307  Any change in above mentioned system variables can trigger a change in
308  actual password length restriction applied by validate password plugin.
309  actual restriction on password length can be described as:
310 
311  MAX(validate_password_length,
312  (validate_password_number_count +
313  2*validate_password_mixed_case_count +
314  validate_password_special_char_count))
315  */
316 
317  new_validate_password_length= (validate_password_number_count +
318  (2 * validate_password_mixed_case_count) +
319  validate_password_special_char_count);
320 
321  if (validate_password_length < new_validate_password_length)
322  {
323  /*
324  Raise a warning that effective restriction on password
325  length is changed.
326  */
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);
330 
331  validate_password_length= new_validate_password_length;
332  }
333 }
334 
335 
336 
337 /* Plugin system variables */
338 
339 static MYSQL_SYSVAR_INT(length, validate_password_length,
340  PLUGIN_VAR_RQCMDARG,
341  "Password validate length to check for minimum password_length",
342  NULL, length_update, 8, 0, 0, 0);
343 
344 static MYSQL_SYSVAR_INT(number_count, validate_password_number_count,
345  PLUGIN_VAR_RQCMDARG,
346  "password validate digit to ensure minimum numeric character in password",
347  NULL, length_update, 1, 0, 0, 0);
348 
349 static MYSQL_SYSVAR_INT(mixed_case_count, validate_password_mixed_case_count,
350  PLUGIN_VAR_RQCMDARG,
351  "Password validate mixed case to ensure minimum upper/lower case in password",
352  NULL, length_update, 1, 0, 0, 0);
353 
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);
358 
359 static MYSQL_SYSVAR_ENUM(policy, validate_password_policy,
360  PLUGIN_VAR_RQCMDARG,
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);
364 
365 static MYSQL_SYSVAR_STR(dictionary_file, validate_password_dictionary_file,
366  PLUGIN_VAR_READONLY,
367  "password_validate_dictionary file to be loaded and check for password",
368  NULL, NULL, NULL);
369 
370 static struct st_mysql_sys_var* validate_password_system_variables[]= {
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),
377  NULL
378 };
379 
380 mysql_declare_plugin(validate_password)
381 {
382  MYSQL_VALIDATE_PASSWORD_PLUGIN, /* type */
383  &validate_password_descriptor, /* descriptor */
384  "validate_password", /* name */
385  "Oracle Corporation", /* author */
386  "check password strength", /* description */
387  PLUGIN_LICENSE_GPL,
388  validate_password_init, /* init function (when loaded) */
389  validate_password_deinit, /* deinit function (when unloaded) */
390  0x0100, /* version */
391  NULL,
392  validate_password_system_variables, /* system variables */
393  NULL,
394  0,
395 }
396 mysql_declare_plugin_end;