Home | History | Annotate | Download | only in authtok_check
      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  */
     25 
     26 #include <sys/types.h>
     27 #include <sys/varargs.h>
     28 #include <sys/param.h>
     29 #include <sys/sysmacros.h>
     30 #include <stdio.h>
     31 #include <stdlib.h>
     32 #include <deflt.h>
     33 #include <security/pam_appl.h>
     34 #include <security/pam_modules.h>
     35 #include <security/pam_impl.h>
     36 #include <string.h>
     37 #include <ctype.h>
     38 #include <unistd.h>
     39 #include <syslog.h>
     40 #include <libintl.h>
     41 #include <errno.h>
     42 #include <pwd.h>
     43 #include "packer.h"
     44 
     45 #include <passwdutil.h>
     46 
     47 #define	PWADMIN "/etc/default/passwd"
     48 
     49 #define	MINLENGTH	6
     50 #define	MINDIFF		3
     51 #define	MINALPHA	2
     52 #define	MINNONALPHA	1
     53 
     54 mutex_t dictlock = DEFAULTMUTEX;
     55 
     56 /*
     57  * We implement:
     58  *	PASSLENGTH (int)	minimum password length
     59  *	NAMECHECK (yes/no)	perform comparison of password and loginname
     60  *	MINDIFF (int)		minimum number of character-positions in which
     61  *				the old	and the new password should differ.
     62  *	MINALPHA (int)		minimum number of Alpha characters
     63  *	MINUPPER (int)		minimum number of upper-case characters
     64  *	MINLOWER (int)		minimum number of lower-case characters
     65  *	MAXREPEATS (int)	maximum number of consecutively repeating chars
     66  *	WHITESPACE (yes/no)	Are whitespaces allowed?
     67  *
     68  * Furthermore, these two mutualy exclusive groups of options are allowed:
     69  *
     70  *	MINNONALPHA (int)	minimum number of characters from the
     71  *				character classes [ punct, space, digit ]
     72  *				if WHITESPACE == NO, whitespaces don't count.
     73  * and
     74  *	MINSPECIAL (int)	minimum number of punctuation characters.
     75  *				if WHITESPACE != NO, whitespace is seen as
     76  *				a "special" character.
     77  *	MINDIGIT (int)		minimum number of digits
     78  *
     79  * specifying options from both groups results in an error to syslog and
     80  * failure to change the password.
     81  *
     82  * NOTE:
     83  *	HISTORY is implemented at the repository level (passwdutil).
     84  */
     85 
     86 /*
     87  * default password-strength-values, compiled-in or stored in PWADMIN
     88  * are kept in here
     89  */
     90 struct pwdefaults {
     91 	boolean_t server_policy;	/* server policy flag from pam.conf */
     92 	uint_t minlength;	/* minimum password lenght */
     93 	uint_t maxlength;	/* maximum (significant) length */
     94 	boolean_t do_namecheck;	/* check password against user's gecos */
     95 	char db_location[MAXPATHLEN]; /* location of the generated database */
     96 	boolean_t do_dictcheck;	/* perform dictionary lookup */
     97 	char *dicts;		/* list of dictionaries configured */
     98 	uint_t mindiff;		/* old and new should differ by this much */
     99 	uint_t minalpha;	/* minimum alpha characters required */
    100 	uint_t minupper;	/* minimum uppercase characters required */
    101 	uint_t minlower;	/* minimum lowercase characters required */
    102 	uint_t minnonalpha; 	/* minimum special (non alpha) required */
    103 	uint_t maxrepeat;	/* maximum number of repeating chars allowed */
    104 	uint_t minspecial;	/* punctuation characters */
    105 	uint_t mindigit;	/* minimum number of digits required */
    106 	boolean_t whitespace;	/* is whitespace allowed in a password */
    107 };
    108 
    109 
    110 /*PRINTFLIKE3*/
    111 void
    112 error(pam_handle_t *pamh, int flags, char *fmt, ...)
    113 {
    114 	va_list ap;
    115 	char msg[1][PAM_MAX_MSG_SIZE];
    116 
    117 	va_start(ap, fmt);
    118 	(void) vsnprintf(msg[0], sizeof (msg[0]), fmt, ap);
    119 	va_end(ap);
    120 	if ((flags & PAM_SILENT) == 0)
    121 		(void) __pam_display_msg(pamh, PAM_ERROR_MSG, 1, msg, NULL);
    122 }
    123 
    124 int
    125 defread_int(char *name, uint_t *ip, void *defp)
    126 {
    127 	char *q;
    128 	int r = 0;
    129 	if ((q = defread_r(name, defp)) != NULL) {
    130 		if (!isdigit(*q)) {
    131 			syslog(LOG_ERR, "pam_authtok_check: %s contains "
    132 			    "non-integer value for %s: %s. "
    133 			    "Using default instead.", PWADMIN, name, q);
    134 		} else {
    135 			*ip = atoi(q);
    136 			r = 1;
    137 		}
    138 	}
    139 	return (r);
    140 }
    141 
    142 /*
    143  * fill in static defaults, and augment with settings from PWADMIN
    144  * get system defaults with regard to maximum password length
    145  */
    146 int
    147 get_passwd_defaults(pam_handle_t *pamh, char *user, struct pwdefaults *p)
    148 {
    149 	char *q;
    150 	boolean_t minnonalpha_defined = B_FALSE;
    151 	pwu_repository_t *pwu_rep;
    152 	struct pam_repository *pam_rep;
    153 	attrlist attr[2];
    154 	int result;
    155 	char *progname;
    156 	void	*defp;
    157 
    158 	(void) pam_get_item(pamh, PAM_SERVICE, (void **)&progname);
    159 
    160 	/* Module defaults */
    161 	p->minlength = MINLENGTH;
    162 	p->do_namecheck = B_TRUE;
    163 	p->do_dictcheck = B_FALSE;
    164 	p->dicts = NULL;
    165 	p->mindiff = MINDIFF;
    166 	p->minalpha = MINALPHA;
    167 	p->minnonalpha = MINNONALPHA;
    168 	p->minupper = 0;	/* not configured by default */
    169 	p->minlower = 0;	/* not configured by default */
    170 	p->maxrepeat = 0;	/* not configured by default */
    171 
    172 	p->minspecial = 0;
    173 	p->mindigit = 0;
    174 	p->whitespace = B_TRUE;
    175 
    176 	if ((defp = defopen_r(PWADMIN)) == NULL)
    177 		return (PAM_SUCCESS);
    178 
    179 	(void) defread_int("PASSLENGTH=", &p->minlength, defp);
    180 
    181 	if ((q = defread_r("NAMECHECK=", defp)) != NULL &&
    182 	    strcasecmp(q, "NO") == 0)
    183 		p->do_namecheck = B_FALSE;
    184 
    185 	if ((q = defread_r("DICTIONLIST=", defp)) != NULL) {
    186 		if ((p->dicts = strdup(q)) == NULL) {
    187 			syslog(LOG_ERR, "pam_authtok_check: out of memory");
    188 			defclose_r(defp);
    189 			return (PAM_BUF_ERR);
    190 
    191 		}
    192 		p->do_dictcheck = B_TRUE;
    193 	} else {
    194 		p->dicts = NULL;
    195 	}
    196 
    197 	if ((q = defread_r("DICTIONDBDIR=", defp)) != NULL) {
    198 		if (strlcpy(p->db_location, q, sizeof (p->db_location)) >=
    199 		    sizeof (p->db_location)) {
    200 			syslog(LOG_ERR, "pam_authtok_check: value for "
    201 			    "DICTIONDBDIR too large.");
    202 			defclose_r(defp);
    203 			return (PAM_SYSTEM_ERR);
    204 		}
    205 		p->do_dictcheck = B_TRUE;
    206 	} else {
    207 		(void) strlcpy(p->db_location, CRACK_DIR,
    208 		    sizeof (p->db_location));
    209 	}
    210 
    211 	(void) defread_int("MINDIFF=", &p->mindiff, defp);
    212 	(void) defread_int("MINALPHA=", &p->minalpha, defp);
    213 	(void) defread_int("MINUPPER=", &p->minupper, defp);
    214 	(void) defread_int("MINLOWER=", &p->minlower, defp);
    215 	if (defread_int("MINNONALPHA=", &p->minnonalpha, defp))
    216 		minnonalpha_defined = B_TRUE;
    217 	(void) defread_int("MAXREPEATS=", &p->maxrepeat, defp);
    218 
    219 	if (defread_int("MINSPECIAL=", &p->minspecial, defp)) {
    220 		if (minnonalpha_defined) {
    221 			syslog(LOG_ERR, "pam_authtok_check: %s contains "
    222 			    "definition for MINNONALPHA and for MINSPECIAL. "
    223 			    "These options are mutually exclusive.", PWADMIN);
    224 			defclose_r(defp);
    225 			return (PAM_SYSTEM_ERR);
    226 		}
    227 		p->minnonalpha = 0;
    228 	}
    229 
    230 	if (defread_int("MINDIGIT=", &p->mindigit, defp)) {
    231 		if (minnonalpha_defined) {
    232 			syslog(LOG_ERR, "pam_authtok_check: %s contains "
    233 			    "definition for MINNONALPHA and for MINDIGIT. "
    234 			    "These options are mutually exclusive.", PWADMIN);
    235 			defclose_r(defp);
    236 			return (PAM_SYSTEM_ERR);
    237 		}
    238 		p->minnonalpha = 0;
    239 	}
    240 
    241 	if ((q = defread_r("WHITESPACE=", defp)) != NULL)
    242 		p->whitespace =
    243 		    (strcasecmp(q, "no") == 0 || strcmp(q, "0") == 0)
    244 		    ? B_FALSE : B_TRUE;
    245 
    246 	defclose_r(defp);
    247 
    248 	/*
    249 	 * Determine the number of significant characters in a password
    250 	 *
    251 	 * we find out where the user information came from (which repository),
    252 	 * and which password-crypt-algorithm is to be used (based on the
    253 	 * old password, or the system default).
    254 	 *
    255 	 * If the user comes from a repository other than FILES/NIS/NIS+,
    256 	 * the module-flag "server_policy" means that we don't perform
    257 	 * any checks on the user, but let the repository decide instead.
    258 	 */
    259 
    260 	(void) pam_get_item(pamh, PAM_REPOSITORY, (void **)&pam_rep);
    261 	if (pam_rep != NULL) {
    262 		if ((pwu_rep = calloc(1, sizeof (*pwu_rep))) == NULL)
    263 			return (PAM_BUF_ERR);
    264 		pwu_rep->type = pam_rep->type;
    265 		pwu_rep->scope = pam_rep->scope;
    266 		pwu_rep->scope_len = pam_rep->scope_len;
    267 	} else {
    268 		pwu_rep = PWU_DEFAULT_REP;
    269 	}
    270 
    271 	attr[0].type = ATTR_PASSWD; attr[0].next = &attr[1];
    272 	attr[1].type = ATTR_REP_NAME; attr[1].next = NULL;
    273 	result = __get_authtoken_attr(user, pwu_rep, attr);
    274 	if (pwu_rep != PWU_DEFAULT_REP)
    275 		free(pwu_rep);
    276 
    277 	if (result != PWU_SUCCESS) {
    278 		/*
    279 		 * In the unlikely event that we can't obtain any info about
    280 		 * the users password, we assume the most strict scenario.
    281 		 */
    282 		p->maxlength = _PASS_MAX_XPG;
    283 	} else {
    284 		char *oldpw = attr[0].data.val_s;
    285 		char *repository = attr[1].data.val_s;
    286 		if ((strcmp(repository, "files") == 0 ||
    287 		    strcmp(repository, "nis") == 0 ||
    288 		    strcmp(repository, "nisplus") == 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, nis or nisplus 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 he's told that
    487 	 * "The password should contain at least 4 digits"...
    488 	 * Instead, we now well him
    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