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
37RCSID("$Id: password_quality.c 17595 2006-05-30 21:51:55Z lha $");
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
46static int
47min_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
66static const char *
67min_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
84static int
85char_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 i, counter = 0, req_classes;
99    size_t 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
135static int
136external_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, pwd->length, '\n') != 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, 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	waitpid(child, &status, 0);
203	return 1;
204    }
205    reply[strcspn(reply, "\n")] = '\0';
206
207    fclose(out);
208    fclose(error);
209
210    if (waitpid(child, &status, 0) < 0) {
211	snprintf(message, length, "external program failed: %s", reply);
212	free(p);
213	return 1;
214    }
215    if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
216	snprintf(message, length, "external program failed: %s", reply);
217	free(p);
218	return 1;
219    }
220
221    if (strcmp(reply, "APPROVED") != 0) {
222	snprintf(message, length, "%s", reply);
223	free(p);
224	return 1;
225    }
226
227    free(p);
228
229    return 0;
230}
231
232
233static kadm5_passwd_quality_check_func_v0 passwd_quality_check =
234	min_length_passwd_quality_v0;
235
236struct kadm5_pw_policy_check_func builtin_funcs[] = {
237    { "minimum-length", min_length_passwd_quality },
238    { "character-class", char_class_passwd_quality },
239    { "external-check", external_passwd_quality },
240    { NULL }
241};
242struct kadm5_pw_policy_verifier builtin_verifier = {
243    "builtin",
244    KADM5_PASSWD_VERSION_V1,
245    "Heimdal builtin",
246    builtin_funcs
247};
248
249static struct kadm5_pw_policy_verifier **verifiers;
250static int num_verifiers;
251
252/*
253 * setup the password quality hook
254 */
255
256#ifndef RTLD_NOW
257#define RTLD_NOW 0
258#endif
259
260void
261kadm5_setup_passwd_quality_check(krb5_context context,
262				 const char *check_library,
263				 const char *check_function)
264{
265#ifdef HAVE_DLOPEN
266    void *handle;
267    void *sym;
268    int *version;
269    const char *tmp;
270
271    if(check_library == NULL) {
272	tmp = krb5_config_get_string(context, NULL,
273				     "password_quality",
274				     "check_library",
275				     NULL);
276	if(tmp != NULL)
277	    check_library = tmp;
278    }
279    if(check_function == NULL) {
280	tmp = krb5_config_get_string(context, NULL,
281				     "password_quality",
282				     "check_function",
283				     NULL);
284	if(tmp != NULL)
285	    check_function = tmp;
286    }
287    if(check_library != NULL && check_function == NULL)
288	check_function = "passwd_check";
289
290    if(check_library == NULL)
291	return;
292    handle = dlopen(check_library, RTLD_NOW);
293    if(handle == NULL) {
294	krb5_warnx(context, "failed to open `%s'", check_library);
295	return;
296    }
297    version = dlsym(handle, "version");
298    if(version == NULL) {
299	krb5_warnx(context,
300		   "didn't find `version' symbol in `%s'", check_library);
301	dlclose(handle);
302	return;
303    }
304    if(*version != KADM5_PASSWD_VERSION_V0) {
305	krb5_warnx(context,
306		   "version of loaded library is %d (expected %d)",
307		   *version, KADM5_PASSWD_VERSION_V0);
308	dlclose(handle);
309	return;
310    }
311    sym = dlsym(handle, check_function);
312    if(sym == NULL) {
313	krb5_warnx(context,
314		   "didn't find `%s' symbol in `%s'",
315		   check_function, check_library);
316	dlclose(handle);
317	return;
318    }
319    passwd_quality_check = (kadm5_passwd_quality_check_func_v0) sym;
320#endif /* HAVE_DLOPEN */
321}
322
323#ifdef HAVE_DLOPEN
324
325static krb5_error_code
326add_verifier(krb5_context context, const char *check_library)
327{
328    struct kadm5_pw_policy_verifier *v, **tmp;
329    void *handle;
330    int i;
331
332    handle = dlopen(check_library, RTLD_NOW);
333    if(handle == NULL) {
334	krb5_warnx(context, "failed to open `%s'", check_library);
335	return ENOENT;
336    }
337    v = dlsym(handle, "kadm5_password_verifier");
338    if(v == NULL) {
339	krb5_warnx(context,
340		   "didn't find `kadm5_password_verifier' symbol "
341		   "in `%s'", check_library);
342	dlclose(handle);
343	return ENOENT;
344    }
345    if(v->version != KADM5_PASSWD_VERSION_V1) {
346	krb5_warnx(context,
347		   "version of loaded library is %d (expected %d)",
348		   v->version, KADM5_PASSWD_VERSION_V1);
349	dlclose(handle);
350	return EINVAL;
351    }
352    for (i = 0; i < num_verifiers; i++) {
353	if (strcmp(v->name, verifiers[i]->name) == 0)
354	    break;
355    }
356    if (i < num_verifiers) {
357	krb5_warnx(context, "password verifier library `%s' is already loaded",
358		   v->name);
359	dlclose(handle);
360	return 0;
361    }
362
363    tmp = realloc(verifiers, (num_verifiers + 1) * sizeof(*verifiers));
364    if (tmp == NULL) {
365	krb5_warnx(context, "out of memory");
366	dlclose(handle);
367	return 0;
368    }
369    verifiers = tmp;
370    verifiers[num_verifiers] = v;
371    num_verifiers++;
372
373    return 0;
374}
375
376#endif
377
378krb5_error_code
379kadm5_add_passwd_quality_verifier(krb5_context context,
380				  const char *check_library)
381{
382#ifdef HAVE_DLOPEN
383
384    if(check_library == NULL) {
385	krb5_error_code ret;
386	char **tmp;
387
388	tmp = krb5_config_get_strings(context, NULL,
389				      "password_quality",
390				      "policy_libraries",
391				      NULL);
392	if(tmp == NULL)
393	    return 0;
394
395	while(tmp) {
396	    ret = add_verifier(context, *tmp);
397	    if (ret)
398		return ret;
399	    tmp++;
400	}
401    }
402    return add_verifier(context, check_library);
403#else
404    return 0;
405#endif /* HAVE_DLOPEN */
406}
407
408/*
409 *
410 */
411
412static const struct kadm5_pw_policy_check_func *
413find_func(krb5_context context, const char *name)
414{
415    const struct kadm5_pw_policy_check_func *f;
416    char *module = NULL;
417    const char *p, *func;
418    int i;
419
420    p = strchr(name, ':');
421    if (p) {
422	func = p + 1;
423	module = strndup(name, p - name);
424	if (module == NULL)
425	    return NULL;
426    } else
427	func = name;
428
429    /* Find module in loaded modules first */
430    for (i = 0; i < num_verifiers; i++) {
431	if (module && strcmp(module, verifiers[i]->name) != 0)
432	    continue;
433	for (f = verifiers[i]->funcs; f->name ; f++)
434	    if (strcmp(name, f->name) == 0) {
435		if (module)
436		    free(module);
437		return f;
438	    }
439    }
440    /* Lets try try the builtin modules */
441    if (module == NULL || strcmp(module, "builtin") == 0) {
442	for (f = builtin_verifier.funcs; f->name ; f++)
443	    if (strcmp(func, f->name) == 0) {
444		if (module)
445		    free(module);
446		return f;
447	    }
448    }
449    if (module)
450	free(module);
451    return NULL;
452}
453
454const char *
455kadm5_check_password_quality (krb5_context context,
456			      krb5_principal principal,
457			      krb5_data *pwd_data)
458{
459    const struct kadm5_pw_policy_check_func *proc;
460    static char error_msg[1024];
461    const char *msg;
462    char **v, **vp;
463    int ret;
464
465    /*
466     * Check if we should use the old version of policy function.
467     */
468
469    v = krb5_config_get_strings(context, NULL,
470				"password_quality",
471				"policies",
472				NULL);
473    if (v == NULL) {
474	msg = (*passwd_quality_check) (context, principal, pwd_data);
475	krb5_set_error_string(context, "password policy failed: %s", msg);
476	return msg;
477    }
478
479    error_msg[0] = '\0';
480
481    msg = NULL;
482    for(vp = v; *vp; vp++) {
483	proc = find_func(context, *vp);
484	if (proc == NULL) {
485	    msg = "failed to find password verifier function";
486	    krb5_set_error_string(context, "Failed to find password policy "
487				  "function: %s", *vp);
488	    break;
489	}
490	ret = (proc->func)(context, principal, pwd_data, NULL,
491			   error_msg, sizeof(error_msg));
492	if (ret) {
493	    krb5_set_error_string(context, "Password policy "
494				  "%s failed with %s",
495				  proc->name, error_msg);
496	    msg = error_msg;
497	    break;
498	}
499    }
500    krb5_config_free_strings(v);
501
502    /* If the default quality check isn't used, lets check that the
503     * old quality function the user have set too */
504    if (msg == NULL && passwd_quality_check != min_length_passwd_quality_v0) {
505	msg = (*passwd_quality_check) (context, principal, pwd_data);
506	if (msg)
507	    krb5_set_error_string(context, "(old) password policy "
508				  "failed with %s", msg);
509
510    }
511    return msg;
512}
513