1/*	$NetBSD$	*/
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$");
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
48static int
49min_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
68static const char *
69min_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
86static int
87char_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
137static int
138external_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
232static kadm5_passwd_quality_check_func_v0 passwd_quality_check =
233	min_length_passwd_quality_v0;
234
235struct 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};
241struct kadm5_pw_policy_verifier builtin_verifier = {
242    "builtin",
243    KADM5_PASSWD_VERSION_V1,
244    "Heimdal builtin",
245    builtin_funcs
246};
247
248static struct kadm5_pw_policy_verifier **verifiers;
249static 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
259void
260kadm5_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
324static krb5_error_code
325add_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
377krb5_error_code
378kadm5_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
413static const struct kadm5_pw_policy_check_func *
414find_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
457const char *
458kadm5_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