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