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