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#ifdef HAVE_SYS_WAIT_H
38#include <sys/wait.h>
39#endif
40#ifdef HAVE_DLFCN_H
41#include <dlfcn.h>
42#endif
43
44static int
45min_length_passwd_quality (krb5_context context,
46			   krb5_principal principal,
47			   krb5_data *pwd,
48			   const char *opaque,
49			   char *message,
50			   size_t length)
51{
52    uint32_t min_length = krb5_config_get_int_default(context, NULL, 6,
53						      "password_quality",
54						      "min_length",
55						      NULL);
56
57    if (pwd->length < min_length) {
58	strlcpy(message, "Password too short", length);
59	return 1;
60    } else
61	return 0;
62}
63
64static const char *
65min_length_passwd_quality_v0 (krb5_context context,
66			      krb5_principal principal,
67			      krb5_data *pwd)
68{
69    static char message[1024];
70    int ret;
71
72    message[0] = '\0';
73
74    ret = min_length_passwd_quality(context, principal, pwd, NULL,
75				    message, sizeof(message));
76    if (ret)
77	return message;
78    return NULL;
79}
80
81
82static int
83char_class_passwd_quality (krb5_context context,
84			   krb5_principal principal,
85			   krb5_data *pwd,
86			   const char *opaque,
87			   char *message,
88			   size_t length)
89{
90    const char *classes[] = {
91	"ABCDEFGHIJKLMNOPQRSTUVWXYZ",
92	"abcdefghijklmnopqrstuvwxyz",
93	"1234567890",
94	"!@#$%^&*()/?<>,.{[]}\\|'~`\" "
95    };
96    int counter = 0, req_classes;
97    size_t i, len;
98    char *pw;
99
100    req_classes = krb5_config_get_int_default(context, NULL, 3,
101					      "password_quality",
102					      "min_classes",
103					      NULL);
104
105    len = pwd->length + 1;
106    pw = malloc(len);
107    if (pw == NULL) {
108	strlcpy(message, "out of memory", length);
109	return 1;
110    }
111    strlcpy(pw, pwd->data, len);
112    len = strlen(pw);
113
114    for (i = 0; i < sizeof(classes)/sizeof(classes[0]); i++) {
115	if (strcspn(pw, classes[i]) < len)
116	    counter++;
117    }
118    memset(pw, 0, pwd->length + 1);
119    free(pw);
120    if (counter < req_classes) {
121	snprintf(message, length,
122	    "Password doesn't meet complexity requirement.\n"
123	    "Add more characters from the following classes:\n"
124	    "1. English uppercase characters (A through Z)\n"
125	    "2. English lowercase characters (a through z)\n"
126	    "3. Base 10 digits (0 through 9)\n"
127	    "4. Nonalphanumeric characters (e.g., !, $, #, %%)");
128	return 1;
129    }
130    return 0;
131}
132
133static int
134external_passwd_quality (krb5_context context,
135			 krb5_principal principal,
136			 krb5_data *pwd,
137			 const char *opaque,
138			 char *message,
139			 size_t length)
140{
141    krb5_error_code ret;
142    const char *program;
143    char *p;
144    pid_t child;
145    int status;
146    char reply[1024];
147    FILE *in = NULL, *out = NULL, *error = NULL;
148
149    if (memchr(pwd->data, '\n', pwd->length) != NULL) {
150	snprintf(message, length, "password contains newline, "
151		 "not valid for external test");
152	return 1;
153    }
154
155    program = krb5_config_get_string(context, NULL,
156				     "password_quality",
157				     "external_program",
158				     NULL);
159    if (program == NULL) {
160	snprintf(message, length, "external password quality "
161		 "program not configured");
162	return 1;
163    }
164
165    ret = krb5_unparse_name(context, principal, &p);
166    if (ret) {
167	strlcpy(message, "out of memory", length);
168	return 1;
169    }
170
171    child = pipe_execv(&in, &out, &error, program, program, p, NULL);
172    if (child < 0) {
173	snprintf(message, length, "external password quality "
174		 "program failed to execute for principal %s", p);
175	free(p);
176	return 1;
177    }
178
179    fprintf(in, "principal: %s\n"
180	    "new-password: %.*s\n"
181	    "end\n",
182	    p, (int)pwd->length, (char *)pwd->data);
183
184    fclose(in);
185
186    if (fgets(reply, sizeof(reply), out) == NULL) {
187
188	if (fgets(reply, sizeof(reply), error) == NULL) {
189	    snprintf(message, length, "external password quality "
190		     "program failed without error");
191
192	} else {
193	    reply[strcspn(reply, "\n")] = '\0';
194	    snprintf(message, length, "External password quality "
195		     "program failed: %s", reply);
196	}
197
198	fclose(out);
199	fclose(error);
200	wait_for_process(child);
201	return 1;
202    }
203    reply[strcspn(reply, "\n")] = '\0';
204
205    fclose(out);
206    fclose(error);
207
208    status = wait_for_process(child);
209
210    if (SE_IS_ERROR(status) || SE_PROCSTATUS(status) != 0) {
211	snprintf(message, length, "external program failed: %s", reply);
212	free(p);
213	return 1;
214    }
215
216    if (strcmp(reply, "APPROVED") != 0) {
217	snprintf(message, length, "%s", reply);
218	free(p);
219	return 1;
220    }
221
222    free(p);
223
224    return 0;
225}
226
227
228static kadm5_passwd_quality_check_func_v0 passwd_quality_check =
229	min_length_passwd_quality_v0;
230
231struct kadm5_pw_policy_check_func builtin_funcs[] = {
232    { "minimum-length", min_length_passwd_quality },
233    { "character-class", char_class_passwd_quality },
234    { "external-check", external_passwd_quality },
235    { NULL, NULL }
236};
237struct kadm5_pw_policy_verifier builtin_verifier = {
238    "builtin",
239    KADM5_PASSWD_VERSION_V1,
240    "Heimdal builtin",
241    builtin_funcs
242};
243
244static struct kadm5_pw_policy_verifier **verifiers;
245static int num_verifiers;
246
247/*
248 * setup the password quality hook
249 */
250
251#ifndef RTLD_NOW
252#define RTLD_NOW 0
253#endif
254
255void
256kadm5_setup_passwd_quality_check(krb5_context context,
257				 const char *check_library,
258				 const char *check_function)
259{
260#ifdef HAVE_DLOPEN
261    void *handle;
262    void *sym;
263    int *version;
264    const char *tmp;
265
266    if(check_library == NULL) {
267	tmp = krb5_config_get_string(context, NULL,
268				     "password_quality",
269				     "check_library",
270				     NULL);
271	if(tmp != NULL)
272	    check_library = tmp;
273    }
274    if(check_function == NULL) {
275	tmp = krb5_config_get_string(context, NULL,
276				     "password_quality",
277				     "check_function",
278				     NULL);
279	if(tmp != NULL)
280	    check_function = tmp;
281    }
282    if(check_library != NULL && check_function == NULL)
283	check_function = "passwd_check";
284
285    if(check_library == NULL)
286	return;
287    handle = dlopen(check_library, RTLD_NOW);
288    if(handle == NULL) {
289	krb5_warnx(context, "failed to open `%s'", check_library);
290	return;
291    }
292    version = (int *) dlsym(handle, "version");
293    if(version == NULL) {
294	krb5_warnx(context,
295		   "didn't find `version' symbol in `%s'", check_library);
296	dlclose(handle);
297	return;
298    }
299    if(*version != KADM5_PASSWD_VERSION_V0) {
300	krb5_warnx(context,
301		   "version of loaded library is %d (expected %d)",
302		   *version, KADM5_PASSWD_VERSION_V0);
303	dlclose(handle);
304	return;
305    }
306    sym = dlsym(handle, check_function);
307    if(sym == NULL) {
308	krb5_warnx(context,
309		   "didn't find `%s' symbol in `%s'",
310		   check_function, check_library);
311	dlclose(handle);
312	return;
313    }
314    passwd_quality_check = (kadm5_passwd_quality_check_func_v0) sym;
315#endif /* HAVE_DLOPEN */
316}
317
318#ifdef HAVE_DLOPEN
319
320static krb5_error_code
321add_verifier(krb5_context context, const char *check_library)
322{
323    struct kadm5_pw_policy_verifier *v, **tmp;
324    void *handle;
325    int i;
326
327    handle = dlopen(check_library, RTLD_NOW);
328    if(handle == NULL) {
329	krb5_warnx(context, "failed to open `%s'", check_library);
330	return ENOENT;
331    }
332    v = (struct kadm5_pw_policy_verifier *) dlsym(handle, "kadm5_password_verifier");
333    if(v == NULL) {
334	krb5_warnx(context,
335		   "didn't find `kadm5_password_verifier' symbol "
336		   "in `%s'", check_library);
337	dlclose(handle);
338	return ENOENT;
339    }
340    if(v->version != KADM5_PASSWD_VERSION_V1) {
341	krb5_warnx(context,
342		   "version of loaded library is %d (expected %d)",
343		   v->version, KADM5_PASSWD_VERSION_V1);
344	dlclose(handle);
345	return EINVAL;
346    }
347    for (i = 0; i < num_verifiers; i++) {
348	if (strcmp(v->name, verifiers[i]->name) == 0)
349	    break;
350    }
351    if (i < num_verifiers) {
352	krb5_warnx(context, "password verifier library `%s' is already loaded",
353		   v->name);
354	dlclose(handle);
355	return 0;
356    }
357
358    tmp = realloc(verifiers, (num_verifiers + 1) * sizeof(*verifiers));
359    if (tmp == NULL) {
360	krb5_warnx(context, "out of memory");
361	dlclose(handle);
362	return 0;
363    }
364    verifiers = tmp;
365    verifiers[num_verifiers] = v;
366    num_verifiers++;
367
368    return 0;
369}
370
371#endif
372
373krb5_error_code
374kadm5_add_passwd_quality_verifier(krb5_context context,
375				  const char *check_library)
376{
377#ifdef HAVE_DLOPEN
378
379    if(check_library == NULL) {
380	krb5_error_code ret;
381	char **tmp;
382
383	tmp = krb5_config_get_strings(context, NULL,
384				      "password_quality",
385				      "policy_libraries",
386				      NULL);
387	if(tmp == NULL || *tmp == NULL)
388	    return 0;
389
390	while (*tmp) {
391	    ret = add_verifier(context, *tmp);
392	    if (ret)
393		return ret;
394	    tmp++;
395	}
396	return 0;
397    } else {
398	return add_verifier(context, check_library);
399    }
400#else
401    return 0;
402#endif /* HAVE_DLOPEN */
403}
404
405/*
406 *
407 */
408
409static const struct kadm5_pw_policy_check_func *
410find_func(krb5_context context, const char *name)
411{
412    const struct kadm5_pw_policy_check_func *f;
413    char *module = NULL;
414    const char *p, *func;
415    int i;
416
417    p = strchr(name, ':');
418    if (p) {
419	size_t len = p - name + 1;
420	func = p + 1;
421	module = malloc(len);
422	if (module == NULL)
423	    return NULL;
424	strlcpy(module, name, len);
425    } else
426	func = name;
427
428    /* Find module in loaded modules first */
429    for (i = 0; i < num_verifiers; i++) {
430	if (module && strcmp(module, verifiers[i]->name) != 0)
431	    continue;
432	for (f = verifiers[i]->funcs; f->name ; f++)
433	    if (strcmp(func, f->name) == 0) {
434		if (module)
435		    free(module);
436		return f;
437	    }
438    }
439    /* Lets try try the builtin modules */
440    if (module == NULL || strcmp(module, "builtin") == 0) {
441	for (f = builtin_verifier.funcs; f->name ; f++)
442	    if (strcmp(func, f->name) == 0) {
443		if (module)
444		    free(module);
445		return f;
446	    }
447    }
448    if (module)
449	free(module);
450    return NULL;
451}
452
453const char *
454kadm5_check_password_quality (krb5_context context,
455			      krb5_principal principal,
456			      krb5_data *pwd_data)
457{
458    const struct kadm5_pw_policy_check_func *proc;
459    static char error_msg[1024];
460    const char *msg;
461    char **v, **vp;
462    int ret;
463
464    /*
465     * Check if we should use the old version of policy function.
466     */
467
468    v = krb5_config_get_strings(context, NULL,
469				"password_quality",
470				"policies",
471				NULL);
472    if (v == NULL) {
473	msg = (*passwd_quality_check) (context, principal, pwd_data);
474	if (msg)
475	    krb5_set_error_message(context, 0, "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_message(context, 0, "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_message(context, 0, "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_message(context, 0, "(old) password policy "
508				   "failed with %s", msg);
509
510    }
511    return msg;
512}
513