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