155682Smarkm/*
2178825Sdfr * Copyright (c) 1997-2000, 2003-2005 Kungliga Tekniska H�gskolan
355682Smarkm * (Royal Institute of Technology, Stockholm, Sweden).
455682Smarkm * All rights reserved.
555682Smarkm *
655682Smarkm * Redistribution and use in source and binary forms, with or without
755682Smarkm * modification, are permitted provided that the following conditions
855682Smarkm * are met:
955682Smarkm *
1055682Smarkm * 1. Redistributions of source code must retain the above copyright
1155682Smarkm *    notice, this list of conditions and the following disclaimer.
1255682Smarkm *
1355682Smarkm * 2. Redistributions in binary form must reproduce the above copyright
1455682Smarkm *    notice, this list of conditions and the following disclaimer in the
1555682Smarkm *    documentation and/or other materials provided with the distribution.
1655682Smarkm *
1755682Smarkm * 3. Neither the name of the Institute nor the names of its contributors
1855682Smarkm *    may be used to endorse or promote products derived from this software
1955682Smarkm *    without specific prior written permission.
2055682Smarkm *
2155682Smarkm * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
2255682Smarkm * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
2355682Smarkm * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
2455682Smarkm * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
2555682Smarkm * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2655682Smarkm * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2755682Smarkm * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2855682Smarkm * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2955682Smarkm * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
3055682Smarkm * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
3155682Smarkm * SUCH DAMAGE.
3255682Smarkm */
3355682Smarkm
3455682Smarkm#include "kadm5_locl.h"
35178825Sdfr#include "kadm5-pwcheck.h"
3655682Smarkm
37178825SdfrRCSID("$Id: password_quality.c 17595 2006-05-30 21:51:55Z lha $");
3855682Smarkm
39178825Sdfr#ifdef HAVE_SYS_WAIT_H
40178825Sdfr#include <sys/wait.h>
41178825Sdfr#endif
4255682Smarkm#ifdef HAVE_DLFCN_H
4355682Smarkm#include <dlfcn.h>
4455682Smarkm#endif
4555682Smarkm
46178825Sdfrstatic int
47178825Sdfrmin_length_passwd_quality (krb5_context context,
48178825Sdfr			   krb5_principal principal,
49178825Sdfr			   krb5_data *pwd,
50178825Sdfr			   const char *opaque,
51178825Sdfr			   char *message,
52178825Sdfr			   size_t length)
53178825Sdfr{
54178825Sdfr    uint32_t min_length = krb5_config_get_int_default(context, NULL, 6,
55178825Sdfr						      "password_quality",
56178825Sdfr						      "min_length",
57178825Sdfr						      NULL);
58178825Sdfr
59178825Sdfr    if (pwd->length < min_length) {
60178825Sdfr	strlcpy(message, "Password too short", length);
61178825Sdfr	return 1;
62178825Sdfr    } else
63178825Sdfr	return 0;
64178825Sdfr}
65178825Sdfr
6655682Smarkmstatic const char *
67178825Sdfrmin_length_passwd_quality_v0 (krb5_context context,
68178825Sdfr			      krb5_principal principal,
69178825Sdfr			      krb5_data *pwd)
7055682Smarkm{
71178825Sdfr    static char message[1024];
72178825Sdfr    int ret;
73178825Sdfr
74178825Sdfr    message[0] = '\0';
75178825Sdfr
76178825Sdfr    ret = min_length_passwd_quality(context, principal, pwd, NULL,
77178825Sdfr				    message, sizeof(message));
78178825Sdfr    if (ret)
79178825Sdfr	return message;
80178825Sdfr    return NULL;
8155682Smarkm}
8255682Smarkm
8355682Smarkm
84178825Sdfrstatic int
85178825Sdfrchar_class_passwd_quality (krb5_context context,
86178825Sdfr			   krb5_principal principal,
87178825Sdfr			   krb5_data *pwd,
88178825Sdfr			   const char *opaque,
89178825Sdfr			   char *message,
90178825Sdfr			   size_t length)
91178825Sdfr{
92178825Sdfr    const char *classes[] = {
93178825Sdfr	"ABCDEFGHIJKLMNOPQRSTUVWXYZ",
94178825Sdfr	"abcdefghijklmnopqrstuvwxyz",
95178825Sdfr	"1234567890",
96178825Sdfr	"!@#$%^&*()/?<>,.{[]}\\|'~`\" "
97178825Sdfr    };
98178825Sdfr    int i, counter = 0, req_classes;
99178825Sdfr    size_t len;
100178825Sdfr    char *pw;
10155682Smarkm
102178825Sdfr    req_classes = krb5_config_get_int_default(context, NULL, 3,
103178825Sdfr					      "password_quality",
104178825Sdfr					      "min_classes",
105178825Sdfr					      NULL);
10655682Smarkm
107178825Sdfr    len = pwd->length + 1;
108178825Sdfr    pw = malloc(len);
109178825Sdfr    if (pw == NULL) {
110178825Sdfr	strlcpy(message, "out of memory", length);
111178825Sdfr	return 1;
112178825Sdfr    }
113178825Sdfr    strlcpy(pw, pwd->data, len);
114178825Sdfr    len = strlen(pw);
11555682Smarkm
116178825Sdfr    for (i = 0; i < sizeof(classes)/sizeof(classes[0]); i++) {
117178825Sdfr	if (strcspn(pw, classes[i]) < len)
118178825Sdfr	    counter++;
119178825Sdfr    }
120178825Sdfr    memset(pw, 0, pwd->length + 1);
121178825Sdfr    free(pw);
122178825Sdfr    if (counter < req_classes) {
123178825Sdfr	snprintf(message, length,
124178825Sdfr	    "Password doesn't meet complexity requirement.\n"
125178825Sdfr	    "Add more characters from the following classes:\n"
126178825Sdfr	    "1. English uppercase characters (A through Z)\n"
127178825Sdfr	    "2. English lowercase characters (a through z)\n"
128178825Sdfr	    "3. Base 10 digits (0 through 9)\n"
129178825Sdfr	    "4. Nonalphanumeric characters (e.g., !, $, #, %%)");
130178825Sdfr	return 1;
131178825Sdfr    }
132178825Sdfr    return 0;
133178825Sdfr}
13455682Smarkm
135178825Sdfrstatic int
136178825Sdfrexternal_passwd_quality (krb5_context context,
137178825Sdfr			 krb5_principal principal,
138178825Sdfr			 krb5_data *pwd,
139178825Sdfr			 const char *opaque,
140178825Sdfr			 char *message,
141178825Sdfr			 size_t length)
142178825Sdfr{
143178825Sdfr    krb5_error_code ret;
144178825Sdfr    const char *program;
145178825Sdfr    char *p;
146178825Sdfr    pid_t child;
147178825Sdfr    int status;
148178825Sdfr    char reply[1024];
149178825Sdfr    FILE *in = NULL, *out = NULL, *error = NULL;
150178825Sdfr
151178825Sdfr    if (memchr(pwd->data, pwd->length, '\n') != NULL) {
152178825Sdfr	snprintf(message, length, "password contains newline, "
153178825Sdfr		 "not valid for external test");
154178825Sdfr	return 1;
155178825Sdfr    }
156178825Sdfr
157178825Sdfr    program = krb5_config_get_string(context, NULL,
158178825Sdfr				     "password_quality",
159178825Sdfr				     "external_program",
160178825Sdfr				     NULL);
161178825Sdfr    if (program == NULL) {
162178825Sdfr	snprintf(message, length, "external password quality "
163178825Sdfr		 "program not configured");
164178825Sdfr	return 1;
165178825Sdfr    }
166178825Sdfr
167178825Sdfr    ret = krb5_unparse_name(context, principal, &p);
168178825Sdfr    if (ret) {
169178825Sdfr	strlcpy(message, "out of memory", length);
170178825Sdfr	return 1;
171178825Sdfr    }
172178825Sdfr
173178825Sdfr    child = pipe_execv(&in, &out, &error, program, p, NULL);
174178825Sdfr    if (child < 0) {
175178825Sdfr	snprintf(message, length, "external password quality "
176178825Sdfr		 "program failed to execute for principal %s", p);
177178825Sdfr	free(p);
178178825Sdfr	return 1;
179178825Sdfr    }
180178825Sdfr
181178825Sdfr    fprintf(in, "principal: %s\n"
182178825Sdfr	    "new-password: %.*s\n"
183178825Sdfr	    "end\n",
184178825Sdfr	    p, (int)pwd->length, (char *)pwd->data);
185178825Sdfr
186178825Sdfr    fclose(in);
187178825Sdfr
188178825Sdfr    if (fgets(reply, sizeof(reply), out) == NULL) {
189178825Sdfr
190178825Sdfr	if (fgets(reply, sizeof(reply), error) == NULL) {
191178825Sdfr	    snprintf(message, length, "external password quality "
192178825Sdfr		     "program failed without error");
193178825Sdfr
194178825Sdfr	} else {
195178825Sdfr	    reply[strcspn(reply, "\n")] = '\0';
196178825Sdfr	    snprintf(message, length, "External password quality "
197178825Sdfr		     "program failed: %s", reply);
198178825Sdfr	}
199178825Sdfr
200178825Sdfr	fclose(out);
201178825Sdfr	fclose(error);
202178825Sdfr	waitpid(child, &status, 0);
203178825Sdfr	return 1;
204178825Sdfr    }
205178825Sdfr    reply[strcspn(reply, "\n")] = '\0';
206178825Sdfr
207178825Sdfr    fclose(out);
208178825Sdfr    fclose(error);
209178825Sdfr
210178825Sdfr    if (waitpid(child, &status, 0) < 0) {
211178825Sdfr	snprintf(message, length, "external program failed: %s", reply);
212178825Sdfr	free(p);
213178825Sdfr	return 1;
214178825Sdfr    }
215178825Sdfr    if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
216178825Sdfr	snprintf(message, length, "external program failed: %s", reply);
217178825Sdfr	free(p);
218178825Sdfr	return 1;
219178825Sdfr    }
220178825Sdfr
221178825Sdfr    if (strcmp(reply, "APPROVED") != 0) {
222178825Sdfr	snprintf(message, length, "%s", reply);
223178825Sdfr	free(p);
224178825Sdfr	return 1;
225178825Sdfr    }
226178825Sdfr
227178825Sdfr    free(p);
228178825Sdfr
229178825Sdfr    return 0;
230178825Sdfr}
231178825Sdfr
232178825Sdfr
233178825Sdfrstatic kadm5_passwd_quality_check_func_v0 passwd_quality_check =
234178825Sdfr	min_length_passwd_quality_v0;
235178825Sdfr
236178825Sdfrstruct kadm5_pw_policy_check_func builtin_funcs[] = {
237178825Sdfr    { "minimum-length", min_length_passwd_quality },
238178825Sdfr    { "character-class", char_class_passwd_quality },
239178825Sdfr    { "external-check", external_passwd_quality },
240178825Sdfr    { NULL }
241178825Sdfr};
242178825Sdfrstruct kadm5_pw_policy_verifier builtin_verifier = {
243178825Sdfr    "builtin",
244178825Sdfr    KADM5_PASSWD_VERSION_V1,
245178825Sdfr    "Heimdal builtin",
246178825Sdfr    builtin_funcs
247178825Sdfr};
248178825Sdfr
249178825Sdfrstatic struct kadm5_pw_policy_verifier **verifiers;
250178825Sdfrstatic int num_verifiers;
251178825Sdfr
25255682Smarkm/*
25355682Smarkm * setup the password quality hook
25455682Smarkm */
25555682Smarkm
256178825Sdfr#ifndef RTLD_NOW
257178825Sdfr#define RTLD_NOW 0
258178825Sdfr#endif
259178825Sdfr
26055682Smarkmvoid
26155682Smarkmkadm5_setup_passwd_quality_check(krb5_context context,
26255682Smarkm				 const char *check_library,
26355682Smarkm				 const char *check_function)
26455682Smarkm{
26555682Smarkm#ifdef HAVE_DLOPEN
26655682Smarkm    void *handle;
26755682Smarkm    void *sym;
26855682Smarkm    int *version;
26955682Smarkm    const char *tmp;
27055682Smarkm
27155682Smarkm    if(check_library == NULL) {
27255682Smarkm	tmp = krb5_config_get_string(context, NULL,
27355682Smarkm				     "password_quality",
27455682Smarkm				     "check_library",
27555682Smarkm				     NULL);
27655682Smarkm	if(tmp != NULL)
27755682Smarkm	    check_library = tmp;
27855682Smarkm    }
27955682Smarkm    if(check_function == NULL) {
28055682Smarkm	tmp = krb5_config_get_string(context, NULL,
28155682Smarkm				     "password_quality",
28255682Smarkm				     "check_function",
28355682Smarkm				     NULL);
28455682Smarkm	if(tmp != NULL)
28555682Smarkm	    check_function = tmp;
28655682Smarkm    }
28755682Smarkm    if(check_library != NULL && check_function == NULL)
28855682Smarkm	check_function = "passwd_check";
28955682Smarkm
29055682Smarkm    if(check_library == NULL)
29155682Smarkm	return;
292178825Sdfr    handle = dlopen(check_library, RTLD_NOW);
29355682Smarkm    if(handle == NULL) {
29455682Smarkm	krb5_warnx(context, "failed to open `%s'", check_library);
29555682Smarkm	return;
29655682Smarkm    }
29755682Smarkm    version = dlsym(handle, "version");
29855682Smarkm    if(version == NULL) {
29955682Smarkm	krb5_warnx(context,
30055682Smarkm		   "didn't find `version' symbol in `%s'", check_library);
30155682Smarkm	dlclose(handle);
30255682Smarkm	return;
30355682Smarkm    }
304178825Sdfr    if(*version != KADM5_PASSWD_VERSION_V0) {
30555682Smarkm	krb5_warnx(context,
30655682Smarkm		   "version of loaded library is %d (expected %d)",
307178825Sdfr		   *version, KADM5_PASSWD_VERSION_V0);
30855682Smarkm	dlclose(handle);
30955682Smarkm	return;
31055682Smarkm    }
31155682Smarkm    sym = dlsym(handle, check_function);
31255682Smarkm    if(sym == NULL) {
31355682Smarkm	krb5_warnx(context,
31455682Smarkm		   "didn't find `%s' symbol in `%s'",
31555682Smarkm		   check_function, check_library);
31655682Smarkm	dlclose(handle);
31755682Smarkm	return;
31855682Smarkm    }
319178825Sdfr    passwd_quality_check = (kadm5_passwd_quality_check_func_v0) sym;
32055682Smarkm#endif /* HAVE_DLOPEN */
32155682Smarkm}
32255682Smarkm
323178825Sdfr#ifdef HAVE_DLOPEN
324178825Sdfr
325178825Sdfrstatic krb5_error_code
326178825Sdfradd_verifier(krb5_context context, const char *check_library)
327178825Sdfr{
328178825Sdfr    struct kadm5_pw_policy_verifier *v, **tmp;
329178825Sdfr    void *handle;
330178825Sdfr    int i;
331178825Sdfr
332178825Sdfr    handle = dlopen(check_library, RTLD_NOW);
333178825Sdfr    if(handle == NULL) {
334178825Sdfr	krb5_warnx(context, "failed to open `%s'", check_library);
335178825Sdfr	return ENOENT;
336178825Sdfr    }
337178825Sdfr    v = dlsym(handle, "kadm5_password_verifier");
338178825Sdfr    if(v == NULL) {
339178825Sdfr	krb5_warnx(context,
340178825Sdfr		   "didn't find `kadm5_password_verifier' symbol "
341178825Sdfr		   "in `%s'", check_library);
342178825Sdfr	dlclose(handle);
343178825Sdfr	return ENOENT;
344178825Sdfr    }
345178825Sdfr    if(v->version != KADM5_PASSWD_VERSION_V1) {
346178825Sdfr	krb5_warnx(context,
347178825Sdfr		   "version of loaded library is %d (expected %d)",
348178825Sdfr		   v->version, KADM5_PASSWD_VERSION_V1);
349178825Sdfr	dlclose(handle);
350178825Sdfr	return EINVAL;
351178825Sdfr    }
352178825Sdfr    for (i = 0; i < num_verifiers; i++) {
353178825Sdfr	if (strcmp(v->name, verifiers[i]->name) == 0)
354178825Sdfr	    break;
355178825Sdfr    }
356178825Sdfr    if (i < num_verifiers) {
357178825Sdfr	krb5_warnx(context, "password verifier library `%s' is already loaded",
358178825Sdfr		   v->name);
359178825Sdfr	dlclose(handle);
360178825Sdfr	return 0;
361178825Sdfr    }
362178825Sdfr
363178825Sdfr    tmp = realloc(verifiers, (num_verifiers + 1) * sizeof(*verifiers));
364178825Sdfr    if (tmp == NULL) {
365178825Sdfr	krb5_warnx(context, "out of memory");
366178825Sdfr	dlclose(handle);
367178825Sdfr	return 0;
368178825Sdfr    }
369178825Sdfr    verifiers = tmp;
370178825Sdfr    verifiers[num_verifiers] = v;
371178825Sdfr    num_verifiers++;
372178825Sdfr
373178825Sdfr    return 0;
374178825Sdfr}
375178825Sdfr
376178825Sdfr#endif
377178825Sdfr
378178825Sdfrkrb5_error_code
379178825Sdfrkadm5_add_passwd_quality_verifier(krb5_context context,
380178825Sdfr				  const char *check_library)
381178825Sdfr{
382178825Sdfr#ifdef HAVE_DLOPEN
383178825Sdfr
384178825Sdfr    if(check_library == NULL) {
385178825Sdfr	krb5_error_code ret;
386178825Sdfr	char **tmp;
387178825Sdfr
388178825Sdfr	tmp = krb5_config_get_strings(context, NULL,
389178825Sdfr				      "password_quality",
390178825Sdfr				      "policy_libraries",
391178825Sdfr				      NULL);
392178825Sdfr	if(tmp == NULL)
393178825Sdfr	    return 0;
394178825Sdfr
395178825Sdfr	while(tmp) {
396178825Sdfr	    ret = add_verifier(context, *tmp);
397178825Sdfr	    if (ret)
398178825Sdfr		return ret;
399178825Sdfr	    tmp++;
400178825Sdfr	}
401178825Sdfr    }
402178825Sdfr    return add_verifier(context, check_library);
403178825Sdfr#else
404178825Sdfr    return 0;
405178825Sdfr#endif /* HAVE_DLOPEN */
406178825Sdfr}
407178825Sdfr
408178825Sdfr/*
409178825Sdfr *
410178825Sdfr */
411178825Sdfr
412178825Sdfrstatic const struct kadm5_pw_policy_check_func *
413178825Sdfrfind_func(krb5_context context, const char *name)
414178825Sdfr{
415178825Sdfr    const struct kadm5_pw_policy_check_func *f;
416178825Sdfr    char *module = NULL;
417178825Sdfr    const char *p, *func;
418178825Sdfr    int i;
419178825Sdfr
420178825Sdfr    p = strchr(name, ':');
421178825Sdfr    if (p) {
422178825Sdfr	func = p + 1;
423178825Sdfr	module = strndup(name, p - name);
424178825Sdfr	if (module == NULL)
425178825Sdfr	    return NULL;
426178825Sdfr    } else
427178825Sdfr	func = name;
428178825Sdfr
429178825Sdfr    /* Find module in loaded modules first */
430178825Sdfr    for (i = 0; i < num_verifiers; i++) {
431178825Sdfr	if (module && strcmp(module, verifiers[i]->name) != 0)
432178825Sdfr	    continue;
433178825Sdfr	for (f = verifiers[i]->funcs; f->name ; f++)
434178825Sdfr	    if (strcmp(name, f->name) == 0) {
435178825Sdfr		if (module)
436178825Sdfr		    free(module);
437178825Sdfr		return f;
438178825Sdfr	    }
439178825Sdfr    }
440178825Sdfr    /* Lets try try the builtin modules */
441178825Sdfr    if (module == NULL || strcmp(module, "builtin") == 0) {
442178825Sdfr	for (f = builtin_verifier.funcs; f->name ; f++)
443178825Sdfr	    if (strcmp(func, f->name) == 0) {
444178825Sdfr		if (module)
445178825Sdfr		    free(module);
446178825Sdfr		return f;
447178825Sdfr	    }
448178825Sdfr    }
449178825Sdfr    if (module)
450178825Sdfr	free(module);
451178825Sdfr    return NULL;
452178825Sdfr}
453178825Sdfr
45455682Smarkmconst char *
45555682Smarkmkadm5_check_password_quality (krb5_context context,
45655682Smarkm			      krb5_principal principal,
45755682Smarkm			      krb5_data *pwd_data)
45855682Smarkm{
459178825Sdfr    const struct kadm5_pw_policy_check_func *proc;
460178825Sdfr    static char error_msg[1024];
461178825Sdfr    const char *msg;
462178825Sdfr    char **v, **vp;
463178825Sdfr    int ret;
464178825Sdfr
465178825Sdfr    /*
466178825Sdfr     * Check if we should use the old version of policy function.
467178825Sdfr     */
468178825Sdfr
469178825Sdfr    v = krb5_config_get_strings(context, NULL,
470178825Sdfr				"password_quality",
471178825Sdfr				"policies",
472178825Sdfr				NULL);
473178825Sdfr    if (v == NULL) {
474178825Sdfr	msg = (*passwd_quality_check) (context, principal, pwd_data);
475178825Sdfr	krb5_set_error_string(context, "password policy failed: %s", msg);
476178825Sdfr	return msg;
477178825Sdfr    }
478178825Sdfr
479178825Sdfr    error_msg[0] = '\0';
480178825Sdfr
481178825Sdfr    msg = NULL;
482178825Sdfr    for(vp = v; *vp; vp++) {
483178825Sdfr	proc = find_func(context, *vp);
484178825Sdfr	if (proc == NULL) {
485178825Sdfr	    msg = "failed to find password verifier function";
486178825Sdfr	    krb5_set_error_string(context, "Failed to find password policy "
487178825Sdfr				  "function: %s", *vp);
488178825Sdfr	    break;
489178825Sdfr	}
490178825Sdfr	ret = (proc->func)(context, principal, pwd_data, NULL,
491178825Sdfr			   error_msg, sizeof(error_msg));
492178825Sdfr	if (ret) {
493178825Sdfr	    krb5_set_error_string(context, "Password policy "
494178825Sdfr				  "%s failed with %s",
495178825Sdfr				  proc->name, error_msg);
496178825Sdfr	    msg = error_msg;
497178825Sdfr	    break;
498178825Sdfr	}
499178825Sdfr    }
500178825Sdfr    krb5_config_free_strings(v);
501178825Sdfr
502178825Sdfr    /* If the default quality check isn't used, lets check that the
503178825Sdfr     * old quality function the user have set too */
504178825Sdfr    if (msg == NULL && passwd_quality_check != min_length_passwd_quality_v0) {
505178825Sdfr	msg = (*passwd_quality_check) (context, principal, pwd_data);
506178825Sdfr	if (msg)
507178825Sdfr	    krb5_set_error_string(context, "(old) password policy "
508178825Sdfr				  "failed with %s", msg);
509178825Sdfr
510178825Sdfr    }
511178825Sdfr    return msg;
51255682Smarkm}
513