xref: /illumos-gate/usr/src/lib/pam_modules/authtok_check/authtok_check.c (revision 66582b606a8194f7f3ba5b3a3a6dca5b0d346361)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  * Copyright (c) 2016 by Delphix. All rights reserved.
25  */
26 
27 #include <sys/types.h>
28 #include <sys/varargs.h>
29 #include <sys/param.h>
30 #include <sys/sysmacros.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <deflt.h>
34 #include <security/pam_appl.h>
35 #include <security/pam_modules.h>
36 #include <security/pam_impl.h>
37 #include <string.h>
38 #include <ctype.h>
39 #include <unistd.h>
40 #include <syslog.h>
41 #include <libintl.h>
42 #include <errno.h>
43 #include <pwd.h>
44 #include "packer.h"
45 
46 #include <passwdutil.h>
47 
48 #define	PWADMIN "/etc/default/passwd"
49 
50 #define	MINLENGTH	6
51 #define	MINDIFF		3
52 #define	MINALPHA	2
53 #define	MINNONALPHA	1
54 
55 mutex_t dictlock = DEFAULTMUTEX;
56 
57 /*
58  * We implement:
59  *	PASSLENGTH (int)	minimum password length
60  *	NAMECHECK (yes/no)	perform comparison of password and loginname
61  *	MINDIFF (int)		minimum number of character-positions in which
62  *				the old	and the new password should differ.
63  *	MINALPHA (int)		minimum number of Alpha characters
64  *	MINUPPER (int)		minimum number of upper-case characters
65  *	MINLOWER (int)		minimum number of lower-case characters
66  *	MAXREPEATS (int)	maximum number of consecutively repeating chars
67  *	WHITESPACE (yes/no)	Are whitespaces allowed?
68  *
69  * Furthermore, these two mutualy exclusive groups of options are allowed:
70  *
71  *	MINNONALPHA (int)	minimum number of characters from the
72  *				character classes [ punct, space, digit ]
73  *				if WHITESPACE == NO, whitespaces don't count.
74  * and
75  *	MINSPECIAL (int)	minimum number of punctuation characters.
76  *				if WHITESPACE != NO, whitespace is seen as
77  *				a "special" character.
78  *	MINDIGIT (int)		minimum number of digits
79  *
80  * specifying options from both groups results in an error to syslog and
81  * failure to change the password.
82  *
83  * NOTE:
84  *	HISTORY is implemented at the repository level (passwdutil).
85  */
86 
87 /*
88  * default password-strength-values, compiled-in or stored in PWADMIN
89  * are kept in here
90  */
91 struct pwdefaults {
92 	boolean_t server_policy;	/* server policy flag from pam.conf */
93 	uint_t minlength;	/* minimum password lenght */
94 	uint_t maxlength;	/* maximum (significant) length */
95 	boolean_t do_namecheck;	/* check password against user's gecos */
96 	char db_location[MAXPATHLEN]; /* location of the generated database */
97 	boolean_t do_dictcheck;	/* perform dictionary lookup */
98 	char *dicts;		/* list of dictionaries configured */
99 	uint_t mindiff;		/* old and new should differ by this much */
100 	uint_t minalpha;	/* minimum alpha characters required */
101 	uint_t minupper;	/* minimum uppercase characters required */
102 	uint_t minlower;	/* minimum lowercase characters required */
103 	uint_t minnonalpha; 	/* minimum special (non alpha) required */
104 	uint_t maxrepeat;	/* maximum number of repeating chars allowed */
105 	uint_t minspecial;	/* punctuation characters */
106 	uint_t mindigit;	/* minimum number of digits required */
107 	boolean_t whitespace;	/* is whitespace allowed in a password */
108 };
109 
110 
111 /*PRINTFLIKE3*/
112 void
113 error(pam_handle_t *pamh, int flags, char *fmt, ...)
114 {
115 	va_list ap;
116 	char msg[1][PAM_MAX_MSG_SIZE];
117 
118 	va_start(ap, fmt);
119 	(void) vsnprintf(msg[0], sizeof (msg[0]), fmt, ap);
120 	va_end(ap);
121 	if ((flags & PAM_SILENT) == 0)
122 		(void) __pam_display_msg(pamh, PAM_ERROR_MSG, 1, msg, NULL);
123 }
124 
125 int
126 defread_int(char *name, uint_t *ip, void *defp)
127 {
128 	char *q;
129 	int r = 0;
130 	if ((q = defread_r(name, defp)) != NULL) {
131 		if (!isdigit(*q)) {
132 			syslog(LOG_ERR, "pam_authtok_check: %s contains "
133 			    "non-integer value for %s: %s. "
134 			    "Using default instead.", PWADMIN, name, q);
135 		} else {
136 			*ip = atoi(q);
137 			r = 1;
138 		}
139 	}
140 	return (r);
141 }
142 
143 /*
144  * fill in static defaults, and augment with settings from PWADMIN
145  * get system defaults with regard to maximum password length
146  */
147 int
148 get_passwd_defaults(pam_handle_t *pamh, char *user, struct pwdefaults *p)
149 {
150 	char *q;
151 	boolean_t minnonalpha_defined = B_FALSE;
152 	pwu_repository_t *pwu_rep;
153 	struct pam_repository *pam_rep;
154 	attrlist attr[2];
155 	int result;
156 	char *progname;
157 	void	*defp;
158 
159 	(void) pam_get_item(pamh, PAM_SERVICE, (void **)&progname);
160 
161 	/* Module defaults */
162 	p->minlength = MINLENGTH;
163 	p->do_namecheck = B_TRUE;
164 	p->do_dictcheck = B_FALSE;
165 	p->dicts = NULL;
166 	p->mindiff = MINDIFF;
167 	p->minalpha = MINALPHA;
168 	p->minnonalpha = MINNONALPHA;
169 	p->minupper = 0;	/* not configured by default */
170 	p->minlower = 0;	/* not configured by default */
171 	p->maxrepeat = 0;	/* not configured by default */
172 
173 	p->minspecial = 0;
174 	p->mindigit = 0;
175 	p->whitespace = B_TRUE;
176 
177 	if ((defp = defopen_r(PWADMIN)) == NULL)
178 		return (PAM_SUCCESS);
179 
180 	(void) defread_int("PASSLENGTH=", &p->minlength, defp);
181 
182 	if ((q = defread_r("NAMECHECK=", defp)) != NULL &&
183 	    strcasecmp(q, "NO") == 0)
184 		p->do_namecheck = B_FALSE;
185 
186 	if ((q = defread_r("DICTIONLIST=", defp)) != NULL) {
187 		if ((p->dicts = strdup(q)) == NULL) {
188 			syslog(LOG_ERR, "pam_authtok_check: out of memory");
189 			defclose_r(defp);
190 			return (PAM_BUF_ERR);
191 
192 		}
193 		p->do_dictcheck = B_TRUE;
194 	} else {
195 		p->dicts = NULL;
196 	}
197 
198 	if ((q = defread_r("DICTIONDBDIR=", defp)) != NULL) {
199 		if (strlcpy(p->db_location, q, sizeof (p->db_location)) >=
200 		    sizeof (p->db_location)) {
201 			syslog(LOG_ERR, "pam_authtok_check: value for "
202 			    "DICTIONDBDIR too large.");
203 			defclose_r(defp);
204 			return (PAM_SYSTEM_ERR);
205 		}
206 		p->do_dictcheck = B_TRUE;
207 	} else {
208 		(void) strlcpy(p->db_location, CRACK_DIR,
209 		    sizeof (p->db_location));
210 	}
211 
212 	(void) defread_int("MINDIFF=", &p->mindiff, defp);
213 	(void) defread_int("MINALPHA=", &p->minalpha, defp);
214 	(void) defread_int("MINUPPER=", &p->minupper, defp);
215 	(void) defread_int("MINLOWER=", &p->minlower, defp);
216 	if (defread_int("MINNONALPHA=", &p->minnonalpha, defp))
217 		minnonalpha_defined = B_TRUE;
218 	(void) defread_int("MAXREPEATS=", &p->maxrepeat, defp);
219 
220 	if (defread_int("MINSPECIAL=", &p->minspecial, defp)) {
221 		if (minnonalpha_defined) {
222 			syslog(LOG_ERR, "pam_authtok_check: %s contains "
223 			    "definition for MINNONALPHA and for MINSPECIAL. "
224 			    "These options are mutually exclusive.", PWADMIN);
225 			defclose_r(defp);
226 			return (PAM_SYSTEM_ERR);
227 		}
228 		p->minnonalpha = 0;
229 	}
230 
231 	if (defread_int("MINDIGIT=", &p->mindigit, defp)) {
232 		if (minnonalpha_defined) {
233 			syslog(LOG_ERR, "pam_authtok_check: %s contains "
234 			    "definition for MINNONALPHA and for MINDIGIT. "
235 			    "These options are mutually exclusive.", PWADMIN);
236 			defclose_r(defp);
237 			return (PAM_SYSTEM_ERR);
238 		}
239 		p->minnonalpha = 0;
240 	}
241 
242 	if ((q = defread_r("WHITESPACE=", defp)) != NULL)
243 		p->whitespace =
244 		    (strcasecmp(q, "no") == 0 || strcmp(q, "0") == 0)
245 		    ? B_FALSE : B_TRUE;
246 
247 	defclose_r(defp);
248 
249 	/*
250 	 * Determine the number of significant characters in a password
251 	 *
252 	 * we find out where the user information came from (which repository),
253 	 * and which password-crypt-algorithm is to be used (based on the
254 	 * old password, or the system default).
255 	 *
256 	 * If the user comes from a repository other than FILES/NIS
257 	 * the module-flag "server_policy" means that we don't perform
258 	 * any checks on the user, but let the repository decide instead.
259 	 */
260 
261 	(void) pam_get_item(pamh, PAM_REPOSITORY, (void **)&pam_rep);
262 	if (pam_rep != NULL) {
263 		if ((pwu_rep = calloc(1, sizeof (*pwu_rep))) == NULL)
264 			return (PAM_BUF_ERR);
265 		pwu_rep->type = pam_rep->type;
266 		pwu_rep->scope = pam_rep->scope;
267 		pwu_rep->scope_len = pam_rep->scope_len;
268 	} else {
269 		pwu_rep = PWU_DEFAULT_REP;
270 	}
271 
272 	attr[0].type = ATTR_PASSWD; attr[0].next = &attr[1];
273 	attr[1].type = ATTR_REP_NAME; attr[1].next = NULL;
274 	result = __get_authtoken_attr(user, pwu_rep, attr);
275 	if (pwu_rep != PWU_DEFAULT_REP)
276 		free(pwu_rep);
277 
278 	if (result != PWU_SUCCESS) {
279 		/*
280 		 * In the unlikely event that we can't obtain any info about
281 		 * the users password, we assume the most strict scenario.
282 		 */
283 		p->maxlength = _PASS_MAX_XPG;
284 	} else {
285 		char *oldpw = attr[0].data.val_s;
286 		char *repository = attr[1].data.val_s;
287 		if ((strcmp(repository, "files") == 0 ||
288 		    strcmp(repository, "nis") == 0) ||
289 		    p->server_policy == B_FALSE) {
290 			char *salt;
291 			/*
292 			 * We currently need to supply this dummy to
293 			 * crypt_gensalt(). This will change RSN.
294 			 */
295 			struct passwd dummy;
296 
297 			dummy.pw_name = user;
298 
299 			salt = crypt_gensalt(oldpw, &dummy);
300 			if (salt && *salt == '$')
301 				p->maxlength = _PASS_MAX;
302 			else
303 				p->maxlength = _PASS_MAX_XPG;
304 
305 			free(salt);
306 
307 			p->server_policy = B_FALSE; /* we perform checks */
308 		} else {
309 			/* not files or nis AND server_policy is set */
310 			p->maxlength = _PASS_MAX;
311 		}
312 		free(attr[0].data.val_s);
313 		free(attr[1].data.val_s);
314 	}
315 
316 	/* sanity check of the configured parameters */
317 	if (p->minlength < p->mindigit + p->minspecial + p->minnonalpha +
318 	    p->minalpha) {
319 		syslog(LOG_ERR, "%s: pam_authtok_check: Defined minimum "
320 		    "password length (PASSLENGTH=%d) is less then minimum "
321 		    "characters in the various classes (%d)", progname,
322 		    p->minlength,
323 		    p->mindigit + p->minspecial + p->minnonalpha + p->minalpha);
324 		p->minlength = p->mindigit + p->minspecial + p->minnonalpha +
325 		    p->minalpha;
326 		syslog(LOG_ERR, "%s: pam_authtok_check: effective "
327 		    "PASSLENGTH set to %d.", progname, p->minlength);
328 		/* this won't lead to failure */
329 	}
330 
331 	if (p->maxlength < p->minlength) {
332 		syslog(LOG_ERR, "%s: pam_authtok_check: The configured "
333 		    "minimum password length (PASSLENGTH=%d) is larger than "
334 		    "the number of significant characters the current "
335 		    "encryption algorithm uses (%d). See policy.conf(4) for "
336 		    "alternative password encryption algorithms.", progname);
337 		/* this won't lead to failure */
338 	}
339 
340 	return (PAM_SUCCESS);
341 }
342 
343 /*
344  * free_passwd_defaults(struct pwdefaults *p)
345  *
346  * free space occupied by the defaults read from PWADMIN
347  */
348 void
349 free_passwd_defaults(struct pwdefaults *p)
350 {
351 	if (p && p->dicts)
352 		free(p->dicts);
353 }
354 
355 /*
356  * check_circular():
357  * This function return 1 if string "t" is a circular shift of
358  * string "s", else it returns 0. -1 is returned on failure.
359  * We also check to see if string "t" is a reversed-circular shift
360  * of string "s", i.e. "ABCDE" vs. "DCBAE".
361  */
362 static int
363 check_circular(s, t)
364 	char *s, *t;
365 {
366 	char c, *p, *o, *r, *buff, *ubuff, *pubuff;
367 	unsigned int i, j, k, l, m;
368 	size_t len;
369 	int ret = 0;
370 
371 	i = strlen(s);
372 	l = strlen(t);
373 	if (i != l)
374 		return (0);
375 	len = i + 1;
376 
377 	buff = malloc(len);
378 	ubuff = malloc(len);
379 	pubuff = malloc(len);
380 
381 	if (buff == NULL || ubuff == NULL || pubuff == NULL) {
382 		syslog(LOG_ERR, "pam_authtok_check: out of memory.");
383 		return (-1);
384 	}
385 
386 	m = 2;
387 	o = &ubuff[0];
388 	for (p = s; c = *p++; *o++ = c)
389 		if (islower(c))
390 			c = toupper(c);
391 	*o = '\0';
392 	o = &pubuff[0];
393 	for (p = t; c = *p++; *o++ = c)
394 		if (islower(c))
395 			c = toupper(c);
396 
397 	*o = '\0';
398 
399 	p = &ubuff[0];
400 	while (m--) {
401 		for (k = 0; k  <  i; k++) {
402 			c = *p++;
403 			o = p;
404 			l = i;
405 			r = &buff[0];
406 			while (--l)
407 				*r++ = *o++;
408 			*r++ = c;
409 			*r = '\0';
410 			p = &buff[0];
411 			if (strcmp(p, pubuff) == 0) {
412 				ret = 1;
413 				goto out;
414 			}
415 		}
416 		p = p + i;
417 		r = &ubuff[0];
418 		j = i;
419 		while (j--)
420 			*--p = *r++;	/* reverse test-string for m==0 pass */
421 	}
422 out:
423 	(void) memset(buff, 0, len);
424 	(void) memset(ubuff, 0, len);
425 	(void) memset(pubuff, 0, len);
426 	free(buff);
427 	free(ubuff);
428 	free(pubuff);
429 	return (ret);
430 }
431 
432 
433 /*
434  * count the different character classes present in the password.
435  */
436 int
437 check_composition(char *pw, struct pwdefaults *pwdef, pam_handle_t *pamh,
438     int flags)
439 {
440 	uint_t alpha_cnt = 0;
441 	uint_t upper_cnt = 0;
442 	uint_t lower_cnt = 0;
443 	uint_t special_cnt = 0;
444 	uint_t whitespace_cnt = 0;
445 	uint_t digit_cnt = 0;
446 	uint_t maxrepeat = 0;
447 	uint_t repeat = 1;
448 	int ret = 0;
449 	char *progname;
450 	char errmsg[256];
451 	char lastc = '\0';
452 	uint_t significant = pwdef->maxlength;
453 	char *w;
454 
455 	(void) pam_get_item(pamh, PAM_SERVICE, (void **)&progname);
456 
457 	/* go over the password gathering statistics */
458 	for (w = pw; significant != 0 && *w != '\0'; w++, significant--) {
459 		if (isalpha(*w)) {
460 			alpha_cnt++;
461 			if (isupper(*w)) {
462 				upper_cnt++;
463 			} else {
464 				lower_cnt++;
465 			}
466 		} else if (isspace(*w))
467 			whitespace_cnt++;
468 		else if (isdigit(*w))
469 			digit_cnt++;
470 		else
471 			special_cnt++;
472 		if (*w == lastc) {
473 			if (++repeat > maxrepeat)
474 				maxrepeat = repeat;
475 		} else {
476 			repeat = 1;
477 		}
478 		lastc = *w;
479 	}
480 
481 	/*
482 	 * If we only consider part of the password (the first maxlength
483 	 * characters) we give a modified error message. Otherwise, a
484 	 * user entering FooBar1234 with PASSLENGTH=6, MINDIGIT=4, while
485 	 * we're using the default UNIX crypt (8 chars significant),
486 	 * would not understand what's going on when they're told that
487 	 * "The password should contain at least 4 digits"...
488 	 * Instead, we now tell them
489 	 * "The first 8 characters of the password should contain at least
490 	 *  4 digits."
491 	 */
492 	if (pwdef->maxlength < strlen(pw))
493 		/*
494 		 * TRANSLATION_NOTE
495 		 * - Make sure the % and %% come over intact
496 		 * - The last %%s will be replaced by strings like
497 		 *	"alphabetic character(s)"
498 		 *	"numeric or special character(s)"
499 		 *	"special character(s)"
500 		 *	"digit(s)"
501 		 *	"uppercase alpha character(s)"
502 		 *	"lowercase alpha character(s)"
503 		 *   So the final string written to the user might become
504 		 * "passwd: The first 8 characters of the password must contain
505 		 *   at least 4 uppercase alpha characters(s)"
506 		 */
507 		(void) snprintf(errmsg, sizeof (errmsg), dgettext(TEXT_DOMAIN,
508 		    "%s: The first %d characters of the password must "
509 		    "contain at least %%d %%s."), progname, pwdef->maxlength);
510 	else
511 		/*
512 		 * TRANSLATION_NOTE
513 		 * - Make sure the % and %% come over intact
514 		 * - The last %%s will be replaced by strings like
515 		 *	"alphabetic character(s)"
516 		 *	"numeric or special character(s)"
517 		 *	"special character(s)"
518 		 *	"digit(s)"
519 		 *	"uppercase alpha character(s)"
520 		 *	"lowercase alpha character(s)"
521 		 *   So the final string written to the user might become
522 		 * "passwd: The password must contain at least 4 uppercase
523 		 *   alpha characters(s)"
524 		 */
525 		(void) snprintf(errmsg, sizeof (errmsg), dgettext(TEXT_DOMAIN,
526 		    "%s: The password must contain at least %%d %%s."),
527 		    progname);
528 
529 	/* Check for whitespace first since it influences special counts */
530 	if (whitespace_cnt > 0 && pwdef->whitespace == B_FALSE) {
531 		error(pamh, flags, dgettext(TEXT_DOMAIN,
532 		    "%s: Whitespace characters are not allowed."), progname);
533 		ret = 1;
534 		goto out;
535 	}
536 
537 	/*
538 	 * Once we get here, whitespace_cnt is either 0, or whitespaces are
539 	 * to be treated a special characters.
540 	 */
541 
542 	if (alpha_cnt < pwdef->minalpha) {
543 		error(pamh, flags, errmsg, pwdef->minalpha,
544 		    dgettext(TEXT_DOMAIN, "alphabetic character(s)"));
545 		ret = 1;
546 		goto out;
547 	}
548 
549 	if (pwdef->minnonalpha > 0) {
550 		/* specials are defined by MINNONALPHA */
551 		/* nonalpha = special+whitespace+digit */
552 		if ((special_cnt + whitespace_cnt + digit_cnt) <
553 		    pwdef->minnonalpha) {
554 			error(pamh, flags, errmsg, pwdef->minnonalpha,
555 			    dgettext(TEXT_DOMAIN,
556 			    "numeric or special character(s)"));
557 			ret = 1;
558 			goto out;
559 		}
560 	} else {
561 		/* specials are defined by MINSPECIAL and/or MINDIGIT */
562 		if ((special_cnt + whitespace_cnt) < pwdef->minspecial) {
563 			error(pamh, flags, errmsg, pwdef->minspecial,
564 			    dgettext(TEXT_DOMAIN, "special character(s)"));
565 			ret = 1;
566 			goto out;
567 		}
568 		if (digit_cnt < pwdef->mindigit) {
569 			error(pamh, flags, errmsg, pwdef->mindigit,
570 			    dgettext(TEXT_DOMAIN, "digit(s)"));
571 			ret = 1;
572 			goto out;
573 		}
574 	}
575 
576 	if (upper_cnt < pwdef->minupper) {
577 		error(pamh, flags, errmsg, pwdef->minupper,
578 		    dgettext(TEXT_DOMAIN, "uppercase alpha character(s)"));
579 		ret = 1;
580 		goto out;
581 	}
582 	if (lower_cnt < pwdef->minlower) {
583 		error(pamh, flags, errmsg, pwdef->minlower,
584 		    dgettext(TEXT_DOMAIN, "lowercase alpha character(s)"));
585 		ret = 1;
586 		goto out;
587 	}
588 
589 	if (pwdef->maxrepeat > 0 && maxrepeat > pwdef->maxrepeat) {
590 		error(pamh, flags, dgettext(TEXT_DOMAIN,
591 		    "%s: Too many consecutively repeating characters. "
592 		    "Maximum allowed is %d."), progname, pwdef->maxrepeat);
593 		ret = 1;
594 	}
595 out:
596 	return (ret);
597 }
598 
599 /*
600  * make sure that old and new password differ by at least 'mindiff'
601  * positions. Return 0 if OK, 1 otherwise
602  */
603 int
604 check_diff(char *pw, char *opw, struct pwdefaults *pwdef, pam_handle_t *pamh,
605     int flags)
606 {
607 	size_t pwlen, opwlen, max;
608 	unsigned int diff;	/* difference between old and new */
609 
610 	if (opw == NULL)
611 		opw = "";
612 
613 	max = pwdef->maxlength;
614 	pwlen = MIN(strlen(pw), max);
615 	opwlen = MIN(strlen(opw), max);
616 
617 	if (pwlen > opwlen)
618 		diff = pwlen - opwlen;
619 	else
620 		diff = opwlen - pwlen;
621 
622 	while (*opw != '\0' && *pw != '\0' && max-- != 0) {
623 		if (*opw != *pw)
624 			diff++;
625 		opw++;
626 		pw++;
627 	}
628 
629 	if (diff  < pwdef->mindiff) {
630 		char *progname;
631 
632 		(void) pam_get_item(pamh, PAM_SERVICE, (void **)&progname);
633 
634 		error(pamh, flags, dgettext(TEXT_DOMAIN,
635 		    "%s: The first %d characters of the old and new passwords "
636 		    "must differ by at least %d positions."), progname,
637 		    pwdef->maxlength, pwdef->mindiff);
638 		return (1);
639 	}
640 
641 	return (0);
642 }
643 
644 /*
645  * check to see if password is in one way or another based on a
646  * dictionary word. Returns 0 if password is OK, 1 if it is based
647  * on a dictionary word and hence should be rejected.
648  */
649 int
650 check_dictionary(char *pw, struct pwdefaults *pwdef, pam_handle_t *pamh,
651     int flags)
652 {
653 	int crack_ret;
654 	int ret;
655 	char *progname;
656 
657 	(void) pam_get_item(pamh, PAM_SERVICE, (void **)&progname);
658 
659 	/* dictionary check isn't MT-safe */
660 	(void) mutex_lock(&dictlock);
661 
662 	if (pwdef->dicts &&
663 	    make_dict_database(pwdef->dicts, pwdef->db_location) != 0) {
664 		(void) mutex_unlock(&dictlock);
665 		syslog(LOG_ERR, "pam_authtok_check:pam_sm_chauthtok: "
666 		    "Dictionary database not present.");
667 		error(pamh, flags, dgettext(TEXT_DOMAIN,
668 		    "%s: password dictionary missing."), progname);
669 		return (PAM_SYSTEM_ERR);
670 	}
671 
672 	crack_ret = DictCheck(pw, pwdef->db_location);
673 
674 	(void) mutex_unlock(&dictlock);
675 
676 	switch (crack_ret) {
677 	case DATABASE_OPEN_FAIL:
678 		syslog(LOG_ERR, "pam_authtok_check:pam_sm_chauthtok: "
679 		    "dictionary database open failure: %s", strerror(errno));
680 		error(pamh, flags, dgettext(TEXT_DOMAIN,
681 		    "%s: failed to open dictionary database."), progname);
682 		ret = PAM_SYSTEM_ERR;
683 		break;
684 	case DICTIONARY_WORD:
685 		error(pamh, flags, dgettext(TEXT_DOMAIN,
686 		    "%s: password is based on a dictionary word."), progname);
687 		ret = PAM_AUTHTOK_ERR;
688 		break;
689 	case REVERSE_DICTIONARY_WORD:
690 		error(pamh, flags, dgettext(TEXT_DOMAIN,
691 		    "%s: password is based on a reversed dictionary word."),
692 		    progname);
693 		ret = PAM_AUTHTOK_ERR;
694 		break;
695 	default:
696 		ret = PAM_SUCCESS;
697 		break;
698 	}
699 	return (ret);
700 }
701 
702 int
703 pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, const char **argv)
704 {
705 	int	debug = 0;
706 	int	retcode = 0;
707 	int	force_check = 0;
708 	int 	i;
709 	size_t	pwlen;
710 	char	*usrname;
711 	char	*pwbuf, *opwbuf;
712 	pwu_repository_t *pwu_rep = PWU_DEFAULT_REP;
713 	pam_repository_t *pwd_rep = NULL;
714 	struct pwdefaults pwdef;
715 	char *progname;
716 
717 	/* needs to be set before option processing */
718 	pwdef.server_policy = B_FALSE;
719 
720 	for (i = 0; i < argc; i++) {
721 		if (strcmp(argv[i], "debug") == 0)
722 			debug = 1;
723 		if (strcmp(argv[i], "force_check") == 0)
724 			force_check = 1;
725 		if (strcmp(argv[i], "server_policy") == 0)
726 			pwdef.server_policy = B_TRUE;
727 	}
728 
729 	if (debug)
730 		syslog(LOG_AUTH | LOG_DEBUG,
731 		    "pam_authtok_check: pam_sm_chauthok called(%x) "
732 		    "force_check = %d", flags, force_check);
733 
734 	if ((flags & PAM_PRELIM_CHECK) == 0)
735 		return (PAM_IGNORE);
736 
737 	(void) pam_get_item(pamh, PAM_SERVICE, (void **)&progname);
738 	(void) pam_get_item(pamh, PAM_USER, (void **)&usrname);
739 	if (usrname == NULL || *usrname == '\0') {
740 		syslog(LOG_ERR, "pam_authtok_check: username name is empty");
741 		return (PAM_USER_UNKNOWN);
742 	}
743 
744 	(void) pam_get_item(pamh, PAM_AUTHTOK, (void **)&pwbuf);
745 	(void) pam_get_item(pamh, PAM_OLDAUTHTOK, (void **)&opwbuf);
746 	if (pwbuf == NULL)
747 		return (PAM_AUTHTOK_ERR);
748 
749 	/* none of these checks holds if caller say so */
750 	if ((flags & PAM_NO_AUTHTOK_CHECK) != 0 && force_check == 0)
751 		return (PAM_SUCCESS);
752 
753 	/* read system-defaults */
754 	retcode = get_passwd_defaults(pamh, usrname, &pwdef);
755 	if (retcode != PAM_SUCCESS)
756 		return (retcode);
757 
758 	if (debug) {
759 		syslog(LOG_AUTH | LOG_DEBUG,
760 		    "pam_authtok_check: MAXLENGTH= %d, server_policy = %s",
761 		    pwdef.maxlength, pwdef.server_policy ? "true" : "false");
762 		syslog(LOG_AUTH | LOG_DEBUG,
763 		    "pam_authtok_check: PASSLENGTH= %d", pwdef.minlength);
764 		syslog(LOG_AUTH | LOG_DEBUG, "pam_authtok_check: NAMECHECK=%s",
765 		    pwdef.do_namecheck == B_TRUE ? "Yes" : "No");
766 		syslog(LOG_AUTH | LOG_DEBUG,
767 		    "pam_authtok_check: do_dictcheck = %s\n",
768 		    pwdef.do_dictcheck ? "true" : "false");
769 		if (pwdef.do_dictcheck) {
770 			syslog(LOG_AUTH | LOG_DEBUG,
771 			    "pam_authtok_check: DICTIONLIST=%s",
772 			    (pwdef.dicts != NULL) ? pwdef.dicts : "<not set>");
773 			syslog(LOG_AUTH | LOG_DEBUG,
774 			    "pam_authtok_check: DICTIONDBDIR=%s",
775 			    pwdef.db_location);
776 		}
777 		syslog(LOG_AUTH | LOG_DEBUG, "pam_authtok_check: MINDIFF=%d",
778 		    pwdef.mindiff);
779 		syslog(LOG_AUTH | LOG_DEBUG,
780 		    "pam_authtok_check: MINALPHA=%d, MINNONALPHA=%d",
781 		    pwdef.minalpha, pwdef.minnonalpha);
782 		syslog(LOG_AUTH | LOG_DEBUG,
783 		    "pam_authtok_check: MINSPECIAL=%d, MINDIGIT=%d",
784 		    pwdef.minspecial, pwdef.mindigit);
785 		syslog(LOG_AUTH | LOG_DEBUG, "pam_authtok_check: WHITESPACE=%s",
786 		    pwdef.whitespace ? "YES" : "NO");
787 		syslog(LOG_AUTH | LOG_DEBUG,
788 		    "pam_authtok_check: MINUPPER=%d, MINLOWER=%d",
789 		    pwdef.minupper, pwdef.minlower);
790 		syslog(LOG_AUTH | LOG_DEBUG, "pam_authtok_check: MAXREPEATS=%d",
791 		    pwdef.maxrepeat);
792 	}
793 
794 	/*
795 	 * If server policy is still true (might be changed from the
796 	 * value specified in /etc/pam.conf by get_passwd_defaults()),
797 	 * we return ignore and let the server do all the checks.
798 	 */
799 	if (pwdef.server_policy == B_TRUE) {
800 		free_passwd_defaults(&pwdef);
801 		return (PAM_IGNORE);
802 	}
803 
804 	/*
805 	 * XXX: JV: we can't really make any assumption on the length of
806 	 *	the password that will be used by the crypto algorithm.
807 	 *	for UNIX-style encryption, minalpha=5,minnonalpha=5 might
808 	 *	be impossible, but not for MD5 style hashes... what to do?
809 	 *
810 	 *	since we don't know what alg. will be used, we operate on
811 	 *	the password as entered, so we don't sanity check anything
812 	 *	for now.
813 	 */
814 
815 	/*
816 	 * Make sure new password is long enough
817 	 */
818 	pwlen = strlen(pwbuf);
819 
820 	if (pwlen < pwdef.minlength) {
821 		error(pamh, flags, dgettext(TEXT_DOMAIN,
822 		    "%s: Password too short - must be at least %d "
823 		    "characters."), progname, pwdef.minlength);
824 		free_passwd_defaults(&pwdef);
825 		return (PAM_AUTHTOK_ERR);
826 	}
827 
828 	/* Make sure the password doesn't equal--a shift of--the username */
829 	if (pwdef.do_namecheck) {
830 		switch (check_circular(usrname, pwbuf)) {
831 		case 1:
832 			error(pamh, flags, dgettext(TEXT_DOMAIN,
833 			    "%s: Password cannot be circular shift of "
834 			    "logonid."), progname);
835 			free_passwd_defaults(&pwdef);
836 			return (PAM_AUTHTOK_ERR);
837 		case -1:
838 			free_passwd_defaults(&pwdef);
839 			return (PAM_BUF_ERR);
840 		default:
841 			break;
842 		}
843 	}
844 
845 	/* Check if new password is in history list. */
846 	(void) pam_get_item(pamh, PAM_REPOSITORY, (void **)&pwd_rep);
847 	if (pwd_rep != NULL) {
848 		if ((pwu_rep = calloc(1, sizeof (*pwu_rep))) == NULL)
849 			return (PAM_BUF_ERR);
850 		pwu_rep->type = pwd_rep->type;
851 		pwu_rep->scope = pwd_rep->scope;
852 		pwu_rep->scope_len = pwd_rep->scope_len;
853 	}
854 
855 	if (__check_history(usrname, pwbuf, pwu_rep) == PWU_SUCCESS) {
856 		/* password found in history */
857 		error(pamh, flags, dgettext(TEXT_DOMAIN,
858 		    "%s: Password in history list."), progname);
859 		if (pwu_rep != PWU_DEFAULT_REP)
860 			free(pwu_rep);
861 		free_passwd_defaults(&pwdef);
862 		return (PAM_AUTHTOK_ERR);
863 	}
864 
865 	if (pwu_rep != PWU_DEFAULT_REP)
866 		free(pwu_rep);
867 
868 	/* check MINALPHA, MINLOWER, etc. */
869 	if (check_composition(pwbuf, &pwdef, pamh, flags) != 0) {
870 		free_passwd_defaults(&pwdef);
871 		return (PAM_AUTHTOK_ERR);
872 	}
873 
874 	/* make sure the old and new password are not too much alike */
875 	if (check_diff(pwbuf, opwbuf, &pwdef, pamh, flags) != 0) {
876 		free_passwd_defaults(&pwdef);
877 		return (PAM_AUTHTOK_ERR);
878 	}
879 
880 	/* dictionary check */
881 	if (pwdef.do_dictcheck) {
882 		retcode = check_dictionary(pwbuf, &pwdef, pamh, flags);
883 		if (retcode != PAM_SUCCESS) {
884 			free_passwd_defaults(&pwdef);
885 			return (retcode);
886 		}
887 	}
888 
889 	free_passwd_defaults(&pwdef);
890 	/* password has passed all tests: it's strong enough */
891 	return (PAM_SUCCESS);
892 }
893