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$");
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 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
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, '\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
229static int
230no_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
240static kadm5_passwd_quality_check_func_v0 passwd_quality_check =
241	min_length_passwd_quality_v0;
242
243struct 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};
250struct kadm5_pw_policy_verifier builtin_verifier = {
251    "builtin",
252    KADM5_PASSWD_VERSION_V1,
253    "Heimdal builtin",
254    builtin_funcs
255};
256
257static struct kadm5_pw_policy_verifier **verifiers;
258static 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
268void
269kadm5_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
333static krb5_error_code
334add_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
386krb5_error_code
387kadm5_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
422static const struct kadm5_pw_policy_check_func *
423find_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
466const char *
467kadm5_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}
525