/ lib / kadm5 / password_quality.c
password_quality.c
  1  /*
  2   * Copyright (c) 1997-2000, 2003-2005 Kungliga Tekniska Högskolan
  3   * (Royal Institute of Technology, Stockholm, Sweden).
  4   * All rights reserved.
  5   *
  6   * Redistribution and use in source and binary forms, with or without
  7   * modification, are permitted provided that the following conditions
  8   * are met:
  9   *
 10   * 1. Redistributions of source code must retain the above copyright
 11   *    notice, this list of conditions and the following disclaimer.
 12   *
 13   * 2. Redistributions in binary form must reproduce the above copyright
 14   *    notice, this list of conditions and the following disclaimer in the
 15   *    documentation and/or other materials provided with the distribution.
 16   *
 17   * 3. Neither the name of the Institute nor the names of its contributors
 18   *    may be used to endorse or promote products derived from this software
 19   *    without specific prior written permission.
 20   *
 21   * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
 22   * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 23   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 24   * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
 25   * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 26   * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 27   * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 28   * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 29   * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 30   * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 31   * SUCH DAMAGE.
 32   */
 33  
 34  #include "kadm5_locl.h"
 35  #include "kadm5-pwcheck.h"
 36  
 37  RCSID("$Id$");
 38  
 39  #ifdef HAVE_SYS_WAIT_H
 40  #include <sys/wait.h>
 41  #endif
 42  #ifdef HAVE_DLFCN_H
 43  #include <dlfcn.h>
 44  #endif
 45  
 46  static int
 47  min_length_passwd_quality (krb5_context context,
 48  			   krb5_principal principal,
 49  			   krb5_data *pwd,
 50  			   const char *opaque,
 51  			   char *message,
 52  			   size_t length)
 53  {
 54      uint32_t min_length = krb5_config_get_int_default(context, NULL, 6,
 55  						      "password_quality",
 56  						      "min_length",
 57  						      NULL);
 58  
 59      if (pwd->length < min_length) {
 60  	strlcpy(message, "Password too short", length);
 61  	return 1;
 62      } else
 63  	return 0;
 64  }
 65  
 66  static const char *
 67  min_length_passwd_quality_v0 (krb5_context context,
 68  			      krb5_principal principal,
 69  			      krb5_data *pwd)
 70  {
 71      static char message[1024];
 72      int ret;
 73  
 74      message[0] = '\0';
 75  
 76      ret = min_length_passwd_quality(context, principal, pwd, NULL,
 77  				    message, sizeof(message));
 78      if (ret)
 79  	return message;
 80      return NULL;
 81  }
 82  
 83  
 84  static int
 85  char_class_passwd_quality (krb5_context context,
 86  			   krb5_principal principal,
 87  			   krb5_data *pwd,
 88  			   const char *opaque,
 89  			   char *message,
 90  			   size_t length)
 91  {
 92      const char *classes[] = {
 93  	"ABCDEFGHIJKLMNOPQRSTUVWXYZ",
 94  	"abcdefghijklmnopqrstuvwxyz",
 95  	"1234567890",
 96  	"!@#$%^&*()/?<>,.{[]}\\|'~`\" "
 97      };
 98      int counter = 0, req_classes;
 99      size_t i, len;
100      char *pw;
101  
102      req_classes = krb5_config_get_int_default(context, NULL, 3,
103  					      "password_quality",
104  					      "min_classes",
105  					      NULL);
106  
107      len = pwd->length + 1;
108      pw = malloc(len);
109      if (pw == NULL) {
110  	strlcpy(message, "out of memory", length);
111  	return 1;
112      }
113      strlcpy(pw, pwd->data, len);
114      len = strlen(pw);
115  
116      for (i = 0; i < sizeof(classes)/sizeof(classes[0]); i++) {
117  	if (strcspn(pw, classes[i]) < len)
118  	    counter++;
119      }
120      memset(pw, 0, pwd->length + 1);
121      free(pw);
122      if (counter < req_classes) {
123  	snprintf(message, length,
124  	    "Password doesn't meet complexity requirement.\n"
125  	    "Add more characters from the following classes:\n"
126  	    "1. English uppercase characters (A through Z)\n"
127  	    "2. English lowercase characters (a through z)\n"
128  	    "3. Base 10 digits (0 through 9)\n"
129  	    "4. Nonalphanumeric characters (e.g., !, $, #, %%)");
130  	return 1;
131      }
132      return 0;
133  }
134  
135  static int
136  external_passwd_quality (krb5_context context,
137  			 krb5_principal principal,
138  			 krb5_data *pwd,
139  			 const char *opaque,
140  			 char *message,
141  			 size_t length)
142  {
143      krb5_error_code ret;
144      const char *program;
145      char *p;
146      pid_t child;
147      int status;
148      char reply[1024];
149      FILE *in = NULL, *out = NULL, *error = NULL;
150  
151      if (memchr(pwd->data, '\n', pwd->length) != NULL) {
152  	snprintf(message, length, "password contains newline, "
153  		 "not valid for external test");
154  	return 1;
155      }
156  
157      program = krb5_config_get_string(context, NULL,
158  				     "password_quality",
159  				     "external_program",
160  				     NULL);
161      if (program == NULL) {
162  	snprintf(message, length, "external password quality "
163  		 "program not configured");
164  	return 1;
165      }
166  
167      ret = krb5_unparse_name(context, principal, &p);
168      if (ret) {
169  	strlcpy(message, "out of memory", length);
170  	return 1;
171      }
172  
173      child = pipe_execv(&in, &out, &error, program, program, p, NULL);
174      if (child < 0) {
175  	snprintf(message, length, "external password quality "
176  		 "program failed to execute for principal %s", p);
177  	free(p);
178  	return 1;
179      }
180  
181      fprintf(in, "principal: %s\n"
182  	    "new-password: %.*s\n"
183  	    "end\n",
184  	    p, (int)pwd->length, (char *)pwd->data);
185  
186      fclose(in);
187  
188      if (fgets(reply, sizeof(reply), out) == NULL) {
189  
190  	if (fgets(reply, sizeof(reply), error) == NULL) {
191  	    snprintf(message, length, "external password quality "
192  		     "program failed without error");
193  
194  	} else {
195  	    reply[strcspn(reply, "\n")] = '\0';
196  	    snprintf(message, length, "External password quality "
197  		     "program failed: %s", reply);
198  	}
199  
200  	fclose(out);
201  	fclose(error);
202  	wait_for_process(child);
203  	return 1;
204      }
205      reply[strcspn(reply, "\n")] = '\0';
206  
207      fclose(out);
208      fclose(error);
209  
210      status = wait_for_process(child);
211  
212      if (SE_IS_ERROR(status) || SE_PROCSTATUS(status) != 0) {
213  	snprintf(message, length, "external program failed: %s", reply);
214  	free(p);
215  	return 1;
216      }
217  
218      if (strcmp(reply, "APPROVED") != 0) {
219  	snprintf(message, length, "%s", reply);
220  	free(p);
221  	return 1;
222      }
223  
224      free(p);
225  
226      return 0;
227  }
228  
229  static int
230  no_passwd_quality_check(krb5_context context,
231  			krb5_principal principal,
232  			krb5_data *pwd,
233  			const char *opaque,
234  			char *message,
235  			size_t length)
236  {
237      return 0;
238  }
239  
240  static kadm5_passwd_quality_check_func_v0 passwd_quality_check =
241  	min_length_passwd_quality_v0;
242  
243  struct kadm5_pw_policy_check_func builtin_funcs[] = {
244      { "no-check", no_passwd_quality_check },
245      { "minimum-length", min_length_passwd_quality },
246      { "character-class", char_class_passwd_quality },
247      { "external-check", external_passwd_quality },
248      { NULL, NULL }
249  };
250  struct kadm5_pw_policy_verifier builtin_verifier = {
251      "builtin",
252      KADM5_PASSWD_VERSION_V1,
253      "Heimdal builtin",
254      builtin_funcs
255  };
256  
257  static struct kadm5_pw_policy_verifier **verifiers;
258  static int num_verifiers;
259  
260  /*
261   * setup the password quality hook
262   */
263  
264  #ifndef RTLD_NOW
265  #define RTLD_NOW 0
266  #endif
267  
268  void
269  kadm5_setup_passwd_quality_check(krb5_context context,
270  				 const char *check_library,
271  				 const char *check_function)
272  {
273  #ifdef HAVE_DLOPEN
274      void *handle;
275      void *sym;
276      int *version;
277      const char *tmp;
278  
279      if(check_library == NULL) {
280  	tmp = krb5_config_get_string(context, NULL,
281  				     "password_quality",
282  				     "check_library",
283  				     NULL);
284  	if(tmp != NULL)
285  	    check_library = tmp;
286      }
287      if(check_function == NULL) {
288  	tmp = krb5_config_get_string(context, NULL,
289  				     "password_quality",
290  				     "check_function",
291  				     NULL);
292  	if(tmp != NULL)
293  	    check_function = tmp;
294      }
295      if(check_library != NULL && check_function == NULL)
296  	check_function = "passwd_check";
297  
298      if(check_library == NULL)
299  	return;
300      handle = dlopen(check_library, RTLD_NOW);
301      if(handle == NULL) {
302  	krb5_warnx(context, "failed to open `%s'", check_library);
303  	return;
304      }
305      version = (int *) dlsym(handle, "version");
306      if(version == NULL) {
307  	krb5_warnx(context,
308  		   "didn't find `version' symbol in `%s'", check_library);
309  	dlclose(handle);
310  	return;
311      }
312      if(*version != KADM5_PASSWD_VERSION_V0) {
313  	krb5_warnx(context,
314  		   "version of loaded library is %d (expected %d)",
315  		   *version, KADM5_PASSWD_VERSION_V0);
316  	dlclose(handle);
317  	return;
318      }
319      sym = dlsym(handle, check_function);
320      if(sym == NULL) {
321  	krb5_warnx(context,
322  		   "didn't find `%s' symbol in `%s'",
323  		   check_function, check_library);
324  	dlclose(handle);
325  	return;
326      }
327      passwd_quality_check = (kadm5_passwd_quality_check_func_v0) sym;
328  #endif /* HAVE_DLOPEN */
329  }
330  
331  #ifdef HAVE_DLOPEN
332  
333  static krb5_error_code
334  add_verifier(krb5_context context, const char *check_library)
335  {
336      struct kadm5_pw_policy_verifier *v, **tmp;
337      void *handle;
338      int i;
339  
340      handle = dlopen(check_library, RTLD_NOW);
341      if(handle == NULL) {
342  	krb5_warnx(context, "failed to open `%s'", check_library);
343  	return ENOENT;
344      }
345      v = (struct kadm5_pw_policy_verifier *) dlsym(handle, "kadm5_password_verifier");
346      if(v == NULL) {
347  	krb5_warnx(context,
348  		   "didn't find `kadm5_password_verifier' symbol "
349  		   "in `%s'", check_library);
350  	dlclose(handle);
351  	return ENOENT;
352      }
353      if(v->version != KADM5_PASSWD_VERSION_V1) {
354  	krb5_warnx(context,
355  		   "version of loaded library is %d (expected %d)",
356  		   v->version, KADM5_PASSWD_VERSION_V1);
357  	dlclose(handle);
358  	return EINVAL;
359      }
360      for (i = 0; i < num_verifiers; i++) {
361  	if (strcmp(v->name, verifiers[i]->name) == 0)
362  	    break;
363      }
364      if (i < num_verifiers) {
365  	krb5_warnx(context, "password verifier library `%s' is already loaded",
366  		   v->name);
367  	dlclose(handle);
368  	return 0;
369      }
370  
371      tmp = realloc(verifiers, (num_verifiers + 1) * sizeof(*verifiers));
372      if (tmp == NULL) {
373  	krb5_warnx(context, "out of memory");
374  	dlclose(handle);
375  	return 0;
376      }
377      verifiers = tmp;
378      verifiers[num_verifiers] = v;
379      num_verifiers++;
380  
381      return 0;
382  }
383  
384  #endif
385  
386  krb5_error_code
387  kadm5_add_passwd_quality_verifier(krb5_context context,
388  				  const char *check_library)
389  {
390  #ifdef HAVE_DLOPEN
391  
392      if(check_library == NULL) {
393  	krb5_error_code ret;
394  	char **tmp;
395  
396  	tmp = krb5_config_get_strings(context, NULL,
397  				      "password_quality",
398  				      "policy_libraries",
399  				      NULL);
400  	if(tmp == NULL || *tmp == NULL)
401  	    return 0;
402  
403  	while (*tmp) {
404  	    ret = add_verifier(context, *tmp);
405  	    if (ret)
406  		return ret;
407  	    tmp++;
408  	}
409  	return 0;
410      } else {
411  	return add_verifier(context, check_library);
412      }
413  #else
414      return 0;
415  #endif /* HAVE_DLOPEN */
416  }
417  
418  /*
419   *
420   */
421  
422  static const struct kadm5_pw_policy_check_func *
423  find_func(krb5_context context, const char *name)
424  {
425      const struct kadm5_pw_policy_check_func *f;
426      char *module = NULL;
427      const char *p, *func;
428      int i;
429  
430      p = strchr(name, ':');
431      if (p) {
432  	size_t len = p - name + 1;
433  	func = p + 1;
434  	module = malloc(len);
435  	if (module == NULL)
436  	    return NULL;
437  	strlcpy(module, name, len);
438      } else
439  	func = name;
440  
441      /* Find module in loaded modules first */
442      for (i = 0; i < num_verifiers; i++) {
443  	if (module && strcmp(module, verifiers[i]->name) != 0)
444  	    continue;
445  	for (f = verifiers[i]->funcs; f->name ; f++)
446  	    if (strcmp(func, f->name) == 0) {
447  		if (module)
448  		    free(module);
449  		return f;
450  	    }
451      }
452      /* Lets try try the builtin modules */
453      if (module == NULL || strcmp(module, "builtin") == 0) {
454  	for (f = builtin_verifier.funcs; f->name ; f++)
455  	    if (strcmp(func, f->name) == 0) {
456  		if (module)
457  		    free(module);
458  		return f;
459  	    }
460      }
461      if (module)
462  	free(module);
463      return NULL;
464  }
465  
466  const char *
467  kadm5_check_password_quality (krb5_context context,
468  			      krb5_principal principal,
469  			      krb5_data *pwd_data)
470  {
471      const struct kadm5_pw_policy_check_func *proc;
472      static char error_msg[1024];
473      const char *msg;
474      char **v, **vp;
475      int ret;
476  
477      /*
478       * Check if we should use the old version of policy function.
479       */
480  
481      v = krb5_config_get_strings(context, NULL,
482  				"password_quality",
483  				"policies",
484  				NULL);
485      if (v == NULL) {
486  	msg = (*passwd_quality_check) (context, principal, pwd_data);
487  	krb5_set_error_message(context, 0, "password policy failed: %s", msg);
488  	return msg;
489      }
490  
491      error_msg[0] = '\0';
492  
493      msg = NULL;
494      for(vp = v; *vp; vp++) {
495  	proc = find_func(context, *vp);
496  	if (proc == NULL) {
497  	    msg = "failed to find password verifier function";
498  	    krb5_set_error_message(context, 0, "Failed to find password policy "
499  				   "function: %s", *vp);
500  	    break;
501  	}
502  	ret = (proc->func)(context, principal, pwd_data, NULL,
503  			   error_msg, sizeof(error_msg));
504  	if (ret) {
505  	    krb5_set_error_message(context, 0, "Password policy "
506  				   "%s failed with %s",
507  				   proc->name, error_msg);
508  	    msg = error_msg;
509  	    break;
510  	}
511      }
512      krb5_config_free_strings(v);
513  
514      /* If the default quality check isn't used, lets check that the
515       * old quality function the user have set too */
516      if (msg == NULL && passwd_quality_check != min_length_passwd_quality_v0) {
517  	msg = (*passwd_quality_check) (context, principal, pwd_data);
518  	if (msg)
519  	    krb5_set_error_message(context, 0, "(old) password policy "
520  				   "failed with %s", msg);
521  
522      }
523      return msg;
524  }