xref: /onnv-gate/usr/src/lib/pam_modules/authtok_check/authtok_check.c (revision 11262:b7ebfbf2359e)
10Sstevel@tonic-gate /*
20Sstevel@tonic-gate  * CDDL HEADER START
30Sstevel@tonic-gate  *
40Sstevel@tonic-gate  * The contents of this file are subject to the terms of the
58563SKenjiro.Tsuji@Sun.COM  * Common Development and Distribution License (the "License").
68563SKenjiro.Tsuji@Sun.COM  * You may not use this file except in compliance with the License.
70Sstevel@tonic-gate  *
80Sstevel@tonic-gate  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
90Sstevel@tonic-gate  * or http://www.opensolaris.org/os/licensing.
100Sstevel@tonic-gate  * See the License for the specific language governing permissions
110Sstevel@tonic-gate  * and limitations under the License.
120Sstevel@tonic-gate  *
130Sstevel@tonic-gate  * When distributing Covered Code, include this CDDL HEADER in each
140Sstevel@tonic-gate  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
150Sstevel@tonic-gate  * If applicable, add the following below this CDDL HEADER, with the
160Sstevel@tonic-gate  * fields enclosed by brackets "[]" replaced with your own identifying
170Sstevel@tonic-gate  * information: Portions Copyright [yyyy] [name of copyright owner]
180Sstevel@tonic-gate  *
190Sstevel@tonic-gate  * CDDL HEADER END
200Sstevel@tonic-gate  */
210Sstevel@tonic-gate /*
228563SKenjiro.Tsuji@Sun.COM  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
230Sstevel@tonic-gate  * Use is subject to license terms.
240Sstevel@tonic-gate  */
250Sstevel@tonic-gate 
260Sstevel@tonic-gate #include <sys/types.h>
270Sstevel@tonic-gate #include <sys/varargs.h>
280Sstevel@tonic-gate #include <sys/param.h>
290Sstevel@tonic-gate #include <sys/sysmacros.h>
300Sstevel@tonic-gate #include <stdio.h>
310Sstevel@tonic-gate #include <stdlib.h>
320Sstevel@tonic-gate #include <deflt.h>
330Sstevel@tonic-gate #include <security/pam_appl.h>
340Sstevel@tonic-gate #include <security/pam_modules.h>
350Sstevel@tonic-gate #include <security/pam_impl.h>
360Sstevel@tonic-gate #include <string.h>
370Sstevel@tonic-gate #include <ctype.h>
380Sstevel@tonic-gate #include <unistd.h>
390Sstevel@tonic-gate #include <syslog.h>
400Sstevel@tonic-gate #include <libintl.h>
410Sstevel@tonic-gate #include <errno.h>
420Sstevel@tonic-gate #include <pwd.h>
430Sstevel@tonic-gate #include "packer.h"
440Sstevel@tonic-gate 
450Sstevel@tonic-gate #include <passwdutil.h>
460Sstevel@tonic-gate 
470Sstevel@tonic-gate #define	PWADMIN "/etc/default/passwd"
480Sstevel@tonic-gate 
490Sstevel@tonic-gate #define	MINLENGTH	6
500Sstevel@tonic-gate #define	MINDIFF		3
510Sstevel@tonic-gate #define	MINALPHA	2
520Sstevel@tonic-gate #define	MINNONALPHA	1
530Sstevel@tonic-gate 
540Sstevel@tonic-gate mutex_t dictlock = DEFAULTMUTEX;
550Sstevel@tonic-gate 
560Sstevel@tonic-gate /*
570Sstevel@tonic-gate  * We implement:
580Sstevel@tonic-gate  *	PASSLENGTH (int)	minimum password length
590Sstevel@tonic-gate  *	NAMECHECK (yes/no)	perform comparison of password and loginname
600Sstevel@tonic-gate  *	MINDIFF (int)		minimum number of character-positions in which
610Sstevel@tonic-gate  *				the old	and the new password should differ.
620Sstevel@tonic-gate  *	MINALPHA (int)		minimum number of Alpha characters
630Sstevel@tonic-gate  *	MINUPPER (int)		minimum number of upper-case characters
640Sstevel@tonic-gate  *	MINLOWER (int)		minimum number of lower-case characters
650Sstevel@tonic-gate  *	MAXREPEATS (int)	maximum number of consecutively repeating chars
660Sstevel@tonic-gate  *	WHITESPACE (yes/no)	Are whitespaces allowed?
670Sstevel@tonic-gate  *
680Sstevel@tonic-gate  * Furthermore, these two mutualy exclusive groups of options are allowed:
690Sstevel@tonic-gate  *
700Sstevel@tonic-gate  *	MINNONALPHA (int)	minimum number of characters from the
710Sstevel@tonic-gate  *				character classes [ punct, space, digit ]
720Sstevel@tonic-gate  *				if WHITESPACE == NO, whitespaces don't count.
730Sstevel@tonic-gate  * and
740Sstevel@tonic-gate  *	MINSPECIAL (int)	minimum number of punctuation characters.
750Sstevel@tonic-gate  *				if WHITESPACE != NO, whitespace is seen as
760Sstevel@tonic-gate  *				a "special" character.
770Sstevel@tonic-gate  *	MINDIGIT (int)		minimum number of digits
780Sstevel@tonic-gate  *
790Sstevel@tonic-gate  * specifying options from both groups results in an error to syslog and
800Sstevel@tonic-gate  * failure to change the password.
810Sstevel@tonic-gate  *
820Sstevel@tonic-gate  * NOTE:
830Sstevel@tonic-gate  *	HISTORY is implemented at the repository level (passwdutil).
840Sstevel@tonic-gate  */
850Sstevel@tonic-gate 
860Sstevel@tonic-gate /*
870Sstevel@tonic-gate  * default password-strength-values, compiled-in or stored in PWADMIN
880Sstevel@tonic-gate  * are kept in here
890Sstevel@tonic-gate  */
900Sstevel@tonic-gate struct pwdefaults {
910Sstevel@tonic-gate 	boolean_t server_policy;	/* server policy flag from pam.conf */
920Sstevel@tonic-gate 	uint_t minlength;	/* minimum password lenght */
930Sstevel@tonic-gate 	uint_t maxlength;	/* maximum (significant) length */
940Sstevel@tonic-gate 	boolean_t do_namecheck;	/* check password against user's gecos */
950Sstevel@tonic-gate 	char db_location[MAXPATHLEN]; /* location of the generated database */
960Sstevel@tonic-gate 	boolean_t do_dictcheck;	/* perform dictionary lookup */
970Sstevel@tonic-gate 	char *dicts;		/* list of dictionaries configured */
980Sstevel@tonic-gate 	uint_t mindiff;		/* old and new should differ by this much */
990Sstevel@tonic-gate 	uint_t minalpha;	/* minimum alpha characters required */
1000Sstevel@tonic-gate 	uint_t minupper;	/* minimum uppercase characters required */
1010Sstevel@tonic-gate 	uint_t minlower;	/* minimum lowercase characters required */
1020Sstevel@tonic-gate 	uint_t minnonalpha; 	/* minimum special (non alpha) required */
1030Sstevel@tonic-gate 	uint_t maxrepeat;	/* maximum number of repeating chars allowed */
1040Sstevel@tonic-gate 	uint_t minspecial;	/* punctuation characters */
1050Sstevel@tonic-gate 	uint_t mindigit;	/* minimum number of digits required */
1060Sstevel@tonic-gate 	boolean_t whitespace;	/* is whitespace allowed in a password */
1070Sstevel@tonic-gate };
1080Sstevel@tonic-gate 
1090Sstevel@tonic-gate 
1100Sstevel@tonic-gate /*PRINTFLIKE3*/
1110Sstevel@tonic-gate void
error(pam_handle_t * pamh,int flags,char * fmt,...)1120Sstevel@tonic-gate error(pam_handle_t *pamh, int flags, char *fmt, ...)
1130Sstevel@tonic-gate {
1140Sstevel@tonic-gate 	va_list ap;
1150Sstevel@tonic-gate 	char msg[1][PAM_MAX_MSG_SIZE];
1160Sstevel@tonic-gate 
1170Sstevel@tonic-gate 	va_start(ap, fmt);
1180Sstevel@tonic-gate 	(void) vsnprintf(msg[0], sizeof (msg[0]), fmt, ap);
1190Sstevel@tonic-gate 	va_end(ap);
1200Sstevel@tonic-gate 	if ((flags & PAM_SILENT) == 0)
1210Sstevel@tonic-gate 		(void) __pam_display_msg(pamh, PAM_ERROR_MSG, 1, msg, NULL);
1220Sstevel@tonic-gate }
1230Sstevel@tonic-gate 
1240Sstevel@tonic-gate int
defread_int(char * name,uint_t * ip,void * defp)1258563SKenjiro.Tsuji@Sun.COM defread_int(char *name, uint_t *ip, void *defp)
1260Sstevel@tonic-gate {
1270Sstevel@tonic-gate 	char *q;
1280Sstevel@tonic-gate 	int r = 0;
1298563SKenjiro.Tsuji@Sun.COM 	if ((q = defread_r(name, defp)) != NULL) {
1300Sstevel@tonic-gate 		if (!isdigit(*q)) {
1310Sstevel@tonic-gate 			syslog(LOG_ERR, "pam_authtok_check: %s contains "
1320Sstevel@tonic-gate 			    "non-integer value for %s: %s. "
1330Sstevel@tonic-gate 			    "Using default instead.", PWADMIN, name, q);
1340Sstevel@tonic-gate 		} else {
1350Sstevel@tonic-gate 			*ip = atoi(q);
1360Sstevel@tonic-gate 			r = 1;
1370Sstevel@tonic-gate 		}
1380Sstevel@tonic-gate 	}
1390Sstevel@tonic-gate 	return (r);
1400Sstevel@tonic-gate }
1410Sstevel@tonic-gate 
1420Sstevel@tonic-gate /*
1430Sstevel@tonic-gate  * fill in static defaults, and augment with settings from PWADMIN
1440Sstevel@tonic-gate  * get system defaults with regard to maximum password length
1450Sstevel@tonic-gate  */
1460Sstevel@tonic-gate int
get_passwd_defaults(pam_handle_t * pamh,char * user,struct pwdefaults * p)1470Sstevel@tonic-gate get_passwd_defaults(pam_handle_t *pamh, char *user, struct pwdefaults *p)
1480Sstevel@tonic-gate {
1490Sstevel@tonic-gate 	char *q;
1500Sstevel@tonic-gate 	boolean_t minnonalpha_defined = B_FALSE;
1510Sstevel@tonic-gate 	pwu_repository_t *pwu_rep;
1520Sstevel@tonic-gate 	struct pam_repository *pam_rep;
1530Sstevel@tonic-gate 	attrlist attr[2];
1540Sstevel@tonic-gate 	int result;
1550Sstevel@tonic-gate 	char *progname;
1568563SKenjiro.Tsuji@Sun.COM 	void	*defp;
1570Sstevel@tonic-gate 
1580Sstevel@tonic-gate 	(void) pam_get_item(pamh, PAM_SERVICE, (void **)&progname);
1590Sstevel@tonic-gate 
1600Sstevel@tonic-gate 	/* Module defaults */
1610Sstevel@tonic-gate 	p->minlength = MINLENGTH;
1620Sstevel@tonic-gate 	p->do_namecheck = B_TRUE;
1630Sstevel@tonic-gate 	p->do_dictcheck = B_FALSE;
1640Sstevel@tonic-gate 	p->dicts = NULL;
1650Sstevel@tonic-gate 	p->mindiff = MINDIFF;
1660Sstevel@tonic-gate 	p->minalpha = MINALPHA;
1670Sstevel@tonic-gate 	p->minnonalpha = MINNONALPHA;
1680Sstevel@tonic-gate 	p->minupper = 0;	/* not configured by default */
1690Sstevel@tonic-gate 	p->minlower = 0;	/* not configured by default */
1700Sstevel@tonic-gate 	p->maxrepeat = 0;	/* not configured by default */
1710Sstevel@tonic-gate 
1720Sstevel@tonic-gate 	p->minspecial = 0;
1730Sstevel@tonic-gate 	p->mindigit = 0;
1740Sstevel@tonic-gate 	p->whitespace = B_TRUE;
1750Sstevel@tonic-gate 
1768563SKenjiro.Tsuji@Sun.COM 	if ((defp = defopen_r(PWADMIN)) == NULL)
1770Sstevel@tonic-gate 		return (PAM_SUCCESS);
1780Sstevel@tonic-gate 
1798563SKenjiro.Tsuji@Sun.COM 	(void) defread_int("PASSLENGTH=", &p->minlength, defp);
1800Sstevel@tonic-gate 
1818563SKenjiro.Tsuji@Sun.COM 	if ((q = defread_r("NAMECHECK=", defp)) != NULL &&
1828563SKenjiro.Tsuji@Sun.COM 	    strcasecmp(q, "NO") == 0)
1830Sstevel@tonic-gate 		p->do_namecheck = B_FALSE;
1840Sstevel@tonic-gate 
1858563SKenjiro.Tsuji@Sun.COM 	if ((q = defread_r("DICTIONLIST=", defp)) != NULL) {
1860Sstevel@tonic-gate 		if ((p->dicts = strdup(q)) == NULL) {
1870Sstevel@tonic-gate 			syslog(LOG_ERR, "pam_authtok_check: out of memory");
1888563SKenjiro.Tsuji@Sun.COM 			defclose_r(defp);
1890Sstevel@tonic-gate 			return (PAM_BUF_ERR);
1900Sstevel@tonic-gate 
1910Sstevel@tonic-gate 		}
1920Sstevel@tonic-gate 		p->do_dictcheck = B_TRUE;
1938563SKenjiro.Tsuji@Sun.COM 	} else {
1940Sstevel@tonic-gate 		p->dicts = NULL;
1958563SKenjiro.Tsuji@Sun.COM 	}
1960Sstevel@tonic-gate 
1978563SKenjiro.Tsuji@Sun.COM 	if ((q = defread_r("DICTIONDBDIR=", defp)) != NULL) {
1980Sstevel@tonic-gate 		if (strlcpy(p->db_location, q, sizeof (p->db_location)) >=
1990Sstevel@tonic-gate 		    sizeof (p->db_location)) {
2000Sstevel@tonic-gate 			syslog(LOG_ERR, "pam_authtok_check: value for "
2010Sstevel@tonic-gate 			    "DICTIONDBDIR too large.");
2028563SKenjiro.Tsuji@Sun.COM 			defclose_r(defp);
2030Sstevel@tonic-gate 			return (PAM_SYSTEM_ERR);
2040Sstevel@tonic-gate 		}
2050Sstevel@tonic-gate 		p->do_dictcheck = B_TRUE;
2068563SKenjiro.Tsuji@Sun.COM 	} else {
2070Sstevel@tonic-gate 		(void) strlcpy(p->db_location, CRACK_DIR,
2080Sstevel@tonic-gate 		    sizeof (p->db_location));
2098563SKenjiro.Tsuji@Sun.COM 	}
2100Sstevel@tonic-gate 
2118563SKenjiro.Tsuji@Sun.COM 	(void) defread_int("MINDIFF=", &p->mindiff, defp);
2128563SKenjiro.Tsuji@Sun.COM 	(void) defread_int("MINALPHA=", &p->minalpha, defp);
2138563SKenjiro.Tsuji@Sun.COM 	(void) defread_int("MINUPPER=", &p->minupper, defp);
2148563SKenjiro.Tsuji@Sun.COM 	(void) defread_int("MINLOWER=", &p->minlower, defp);
2158563SKenjiro.Tsuji@Sun.COM 	if (defread_int("MINNONALPHA=", &p->minnonalpha, defp))
2160Sstevel@tonic-gate 		minnonalpha_defined = B_TRUE;
2178563SKenjiro.Tsuji@Sun.COM 	(void) defread_int("MAXREPEATS=", &p->maxrepeat, defp);
2180Sstevel@tonic-gate 
2198563SKenjiro.Tsuji@Sun.COM 	if (defread_int("MINSPECIAL=", &p->minspecial, defp)) {
2200Sstevel@tonic-gate 		if (minnonalpha_defined) {
2210Sstevel@tonic-gate 			syslog(LOG_ERR, "pam_authtok_check: %s contains "
2220Sstevel@tonic-gate 			    "definition for MINNONALPHA and for MINSPECIAL. "
2230Sstevel@tonic-gate 			    "These options are mutually exclusive.", PWADMIN);
2248563SKenjiro.Tsuji@Sun.COM 			defclose_r(defp);
2250Sstevel@tonic-gate 			return (PAM_SYSTEM_ERR);
2260Sstevel@tonic-gate 		}
2270Sstevel@tonic-gate 		p->minnonalpha = 0;
2280Sstevel@tonic-gate 	}
2290Sstevel@tonic-gate 
2308563SKenjiro.Tsuji@Sun.COM 	if (defread_int("MINDIGIT=", &p->mindigit, defp)) {
2310Sstevel@tonic-gate 		if (minnonalpha_defined) {
2320Sstevel@tonic-gate 			syslog(LOG_ERR, "pam_authtok_check: %s contains "
2330Sstevel@tonic-gate 			    "definition for MINNONALPHA and for MINDIGIT. "
2340Sstevel@tonic-gate 			    "These options are mutually exclusive.", PWADMIN);
2358563SKenjiro.Tsuji@Sun.COM 			defclose_r(defp);
2360Sstevel@tonic-gate 			return (PAM_SYSTEM_ERR);
2370Sstevel@tonic-gate 		}
2380Sstevel@tonic-gate 		p->minnonalpha = 0;
2390Sstevel@tonic-gate 	}
2400Sstevel@tonic-gate 
2418563SKenjiro.Tsuji@Sun.COM 	if ((q = defread_r("WHITESPACE=", defp)) != NULL)
2420Sstevel@tonic-gate 		p->whitespace =
2430Sstevel@tonic-gate 		    (strcasecmp(q, "no") == 0 || strcmp(q, "0") == 0)
2440Sstevel@tonic-gate 		    ? B_FALSE : B_TRUE;
2450Sstevel@tonic-gate 
2468563SKenjiro.Tsuji@Sun.COM 	defclose_r(defp);
2470Sstevel@tonic-gate 
2480Sstevel@tonic-gate 	/*
2490Sstevel@tonic-gate 	 * Determine the number of significant characters in a password
2500Sstevel@tonic-gate 	 *
2510Sstevel@tonic-gate 	 * we find out where the user information came from (which repository),
2520Sstevel@tonic-gate 	 * and which password-crypt-algorithm is to be used (based on the
2530Sstevel@tonic-gate 	 * old password, or the system default).
2540Sstevel@tonic-gate 	 *
255*11262SRajagopal.Andra@Sun.COM 	 * If the user comes from a repository other than FILES/NIS
2560Sstevel@tonic-gate 	 * the module-flag "server_policy" means that we don't perform
2570Sstevel@tonic-gate 	 * any checks on the user, but let the repository decide instead.
2580Sstevel@tonic-gate 	 */
2590Sstevel@tonic-gate 
2600Sstevel@tonic-gate 	(void) pam_get_item(pamh, PAM_REPOSITORY, (void **)&pam_rep);
2610Sstevel@tonic-gate 	if (pam_rep != NULL) {
2620Sstevel@tonic-gate 		if ((pwu_rep = calloc(1, sizeof (*pwu_rep))) == NULL)
2630Sstevel@tonic-gate 			return (PAM_BUF_ERR);
2640Sstevel@tonic-gate 		pwu_rep->type = pam_rep->type;
2650Sstevel@tonic-gate 		pwu_rep->scope = pam_rep->scope;
2660Sstevel@tonic-gate 		pwu_rep->scope_len = pam_rep->scope_len;
2670Sstevel@tonic-gate 	} else {
2680Sstevel@tonic-gate 		pwu_rep = PWU_DEFAULT_REP;
2690Sstevel@tonic-gate 	}
2700Sstevel@tonic-gate 
2710Sstevel@tonic-gate 	attr[0].type = ATTR_PASSWD; attr[0].next = &attr[1];
2720Sstevel@tonic-gate 	attr[1].type = ATTR_REP_NAME; attr[1].next = NULL;
2730Sstevel@tonic-gate 	result = __get_authtoken_attr(user, pwu_rep, attr);
2740Sstevel@tonic-gate 	if (pwu_rep != PWU_DEFAULT_REP)
2750Sstevel@tonic-gate 		free(pwu_rep);
2760Sstevel@tonic-gate 
2770Sstevel@tonic-gate 	if (result != PWU_SUCCESS) {
2780Sstevel@tonic-gate 		/*
2790Sstevel@tonic-gate 		 * In the unlikely event that we can't obtain any info about
2800Sstevel@tonic-gate 		 * the users password, we assume the most strict scenario.
2810Sstevel@tonic-gate 		 */
2820Sstevel@tonic-gate 		p->maxlength = _PASS_MAX_XPG;
2830Sstevel@tonic-gate 	} else {
2840Sstevel@tonic-gate 		char *oldpw = attr[0].data.val_s;
2850Sstevel@tonic-gate 		char *repository = attr[1].data.val_s;
2860Sstevel@tonic-gate 		if ((strcmp(repository, "files") == 0 ||
287*11262SRajagopal.Andra@Sun.COM 		    strcmp(repository, "nis") == 0) ||
2880Sstevel@tonic-gate 		    p->server_policy == B_FALSE) {
2890Sstevel@tonic-gate 			char *salt;
2900Sstevel@tonic-gate 			/*
2910Sstevel@tonic-gate 			 * We currently need to supply this dummy to
2920Sstevel@tonic-gate 			 * crypt_gensalt(). This will change RSN.
2930Sstevel@tonic-gate 			 */
2940Sstevel@tonic-gate 			struct passwd dummy;
2950Sstevel@tonic-gate 
2960Sstevel@tonic-gate 			dummy.pw_name = user;
2970Sstevel@tonic-gate 
2980Sstevel@tonic-gate 			salt = crypt_gensalt(oldpw, &dummy);
2990Sstevel@tonic-gate 			if (salt && *salt == '$')
3000Sstevel@tonic-gate 				p->maxlength = _PASS_MAX;
3010Sstevel@tonic-gate 			else
3020Sstevel@tonic-gate 				p->maxlength = _PASS_MAX_XPG;
3030Sstevel@tonic-gate 
3040Sstevel@tonic-gate 			free(salt);
3050Sstevel@tonic-gate 
3060Sstevel@tonic-gate 			p->server_policy = B_FALSE; /* we perform checks */
3070Sstevel@tonic-gate 		} else {
308*11262SRajagopal.Andra@Sun.COM 			/* not files or nis AND server_policy is set */
3090Sstevel@tonic-gate 			p->maxlength = _PASS_MAX;
3100Sstevel@tonic-gate 		}
3110Sstevel@tonic-gate 		free(attr[0].data.val_s);
3120Sstevel@tonic-gate 		free(attr[1].data.val_s);
3130Sstevel@tonic-gate 	}
3140Sstevel@tonic-gate 
3150Sstevel@tonic-gate 	/* sanity check of the configured parameters */
3160Sstevel@tonic-gate 	if (p->minlength < p->mindigit + p->minspecial + p->minnonalpha +
3170Sstevel@tonic-gate 	    p->minalpha) {
3180Sstevel@tonic-gate 		syslog(LOG_ERR, "%s: pam_authtok_check: Defined minimum "
3190Sstevel@tonic-gate 		    "password length (PASSLENGTH=%d) is less then minimum "
3200Sstevel@tonic-gate 		    "characters in the various classes (%d)", progname,
3210Sstevel@tonic-gate 		    p->minlength,
3220Sstevel@tonic-gate 		    p->mindigit + p->minspecial + p->minnonalpha + p->minalpha);
3230Sstevel@tonic-gate 		p->minlength = p->mindigit + p->minspecial + p->minnonalpha +
3240Sstevel@tonic-gate 		    p->minalpha;
3250Sstevel@tonic-gate 		syslog(LOG_ERR, "%s: pam_authtok_check: effective "
3260Sstevel@tonic-gate 		    "PASSLENGTH set to %d.", progname, p->minlength);
3270Sstevel@tonic-gate 		/* this won't lead to failure */
3280Sstevel@tonic-gate 	}
3290Sstevel@tonic-gate 
3300Sstevel@tonic-gate 	if (p->maxlength < p->minlength) {
3310Sstevel@tonic-gate 		syslog(LOG_ERR, "%s: pam_authtok_check: The configured "
3320Sstevel@tonic-gate 		    "minimum password length (PASSLENGTH=%d) is larger than "
3330Sstevel@tonic-gate 		    "the number of significant characters the current "
3340Sstevel@tonic-gate 		    "encryption algorithm uses (%d). See policy.conf(4) for "
3350Sstevel@tonic-gate 		    "alternative password encryption algorithms.", progname);
3360Sstevel@tonic-gate 		/* this won't lead to failure */
3370Sstevel@tonic-gate 	}
3380Sstevel@tonic-gate 
3390Sstevel@tonic-gate 	return (PAM_SUCCESS);
3400Sstevel@tonic-gate }
3410Sstevel@tonic-gate 
3420Sstevel@tonic-gate /*
3430Sstevel@tonic-gate  * free_passwd_defaults(struct pwdefaults *p)
3440Sstevel@tonic-gate  *
3450Sstevel@tonic-gate  * free space occupied by the defaults read from PWADMIN
3460Sstevel@tonic-gate  */
3470Sstevel@tonic-gate void
free_passwd_defaults(struct pwdefaults * p)3480Sstevel@tonic-gate free_passwd_defaults(struct pwdefaults *p)
3490Sstevel@tonic-gate {
3500Sstevel@tonic-gate 	if (p && p->dicts)
3510Sstevel@tonic-gate 		free(p->dicts);
3520Sstevel@tonic-gate }
3530Sstevel@tonic-gate 
3540Sstevel@tonic-gate /*
3550Sstevel@tonic-gate  * check_circular():
3560Sstevel@tonic-gate  * This function return 1 if string "t" is a circular shift of
3570Sstevel@tonic-gate  * string "s", else it returns 0. -1 is returned on failure.
3580Sstevel@tonic-gate  * We also check to see if string "t" is a reversed-circular shift
3590Sstevel@tonic-gate  * of string "s", i.e. "ABCDE" vs. "DCBAE".
3600Sstevel@tonic-gate  */
3610Sstevel@tonic-gate static int
check_circular(s,t)3620Sstevel@tonic-gate check_circular(s, t)
3630Sstevel@tonic-gate 	char *s, *t;
3640Sstevel@tonic-gate {
3650Sstevel@tonic-gate 	char c, *p, *o, *r, *buff, *ubuff, *pubuff;
3660Sstevel@tonic-gate 	unsigned int i, j, k, l, m;
3670Sstevel@tonic-gate 	size_t len;
3680Sstevel@tonic-gate 	int ret = 0;
3690Sstevel@tonic-gate 
3700Sstevel@tonic-gate 	i = strlen(s);
3710Sstevel@tonic-gate 	l = strlen(t);
3720Sstevel@tonic-gate 	if (i != l)
3730Sstevel@tonic-gate 		return (0);
3740Sstevel@tonic-gate 	len = i + 1;
3750Sstevel@tonic-gate 
3760Sstevel@tonic-gate 	buff = malloc(len);
3770Sstevel@tonic-gate 	ubuff = malloc(len);
3780Sstevel@tonic-gate 	pubuff = malloc(len);
3790Sstevel@tonic-gate 
3800Sstevel@tonic-gate 	if (buff == NULL || ubuff == NULL || pubuff == NULL) {
3810Sstevel@tonic-gate 		syslog(LOG_ERR, "pam_authtok_check: out of memory.");
3820Sstevel@tonic-gate 		return (-1);
3830Sstevel@tonic-gate 	}
3840Sstevel@tonic-gate 
3850Sstevel@tonic-gate 	m = 2;
3860Sstevel@tonic-gate 	o = &ubuff[0];
3870Sstevel@tonic-gate 	for (p = s; c = *p++; *o++ = c)
3880Sstevel@tonic-gate 		if (islower(c))
3890Sstevel@tonic-gate 			c = toupper(c);
3900Sstevel@tonic-gate 	*o = '\0';
3910Sstevel@tonic-gate 	o = &pubuff[0];
3920Sstevel@tonic-gate 	for (p = t; c = *p++; *o++ = c)
3930Sstevel@tonic-gate 		if (islower(c))
3940Sstevel@tonic-gate 			c = toupper(c);
3950Sstevel@tonic-gate 
3960Sstevel@tonic-gate 	*o = '\0';
3970Sstevel@tonic-gate 
3980Sstevel@tonic-gate 	p = &ubuff[0];
3990Sstevel@tonic-gate 	while (m--) {
4000Sstevel@tonic-gate 		for (k = 0; k  <  i; k++) {
4010Sstevel@tonic-gate 			c = *p++;
4020Sstevel@tonic-gate 			o = p;
4030Sstevel@tonic-gate 			l = i;
4040Sstevel@tonic-gate 			r = &buff[0];
4050Sstevel@tonic-gate 			while (--l)
4060Sstevel@tonic-gate 				*r++ = *o++;
4070Sstevel@tonic-gate 			*r++ = c;
4080Sstevel@tonic-gate 			*r = '\0';
4090Sstevel@tonic-gate 			p = &buff[0];
4100Sstevel@tonic-gate 			if (strcmp(p, pubuff) == 0) {
4110Sstevel@tonic-gate 				ret = 1;
4120Sstevel@tonic-gate 				goto out;
4130Sstevel@tonic-gate 			}
4140Sstevel@tonic-gate 		}
4150Sstevel@tonic-gate 		p = p + i;
4160Sstevel@tonic-gate 		r = &ubuff[0];
4170Sstevel@tonic-gate 		j = i;
4180Sstevel@tonic-gate 		while (j--)
4190Sstevel@tonic-gate 			*--p = *r++;	/* reverse test-string for m==0 pass */
4200Sstevel@tonic-gate 	}
4210Sstevel@tonic-gate out:
4220Sstevel@tonic-gate 	(void) memset(buff, 0, len);
4230Sstevel@tonic-gate 	(void) memset(ubuff, 0, len);
4240Sstevel@tonic-gate 	(void) memset(pubuff, 0, len);
4250Sstevel@tonic-gate 	free(buff);
4260Sstevel@tonic-gate 	free(ubuff);
4270Sstevel@tonic-gate 	free(pubuff);
4280Sstevel@tonic-gate 	return (ret);
4290Sstevel@tonic-gate }
4300Sstevel@tonic-gate 
4310Sstevel@tonic-gate 
4320Sstevel@tonic-gate /*
4330Sstevel@tonic-gate  * count the different character classes present in the password.
4340Sstevel@tonic-gate  */
4350Sstevel@tonic-gate int
check_composition(char * pw,struct pwdefaults * pwdef,pam_handle_t * pamh,int flags)4360Sstevel@tonic-gate check_composition(char *pw, struct pwdefaults *pwdef, pam_handle_t *pamh,
4370Sstevel@tonic-gate     int flags)
4380Sstevel@tonic-gate {
4390Sstevel@tonic-gate 	uint_t alpha_cnt = 0;
4400Sstevel@tonic-gate 	uint_t upper_cnt = 0;
4410Sstevel@tonic-gate 	uint_t lower_cnt = 0;
4420Sstevel@tonic-gate 	uint_t special_cnt = 0;
4430Sstevel@tonic-gate 	uint_t whitespace_cnt = 0;
4440Sstevel@tonic-gate 	uint_t digit_cnt = 0;
4450Sstevel@tonic-gate 	uint_t maxrepeat = 0;
4460Sstevel@tonic-gate 	uint_t repeat = 1;
4470Sstevel@tonic-gate 	int ret = 0;
4480Sstevel@tonic-gate 	char *progname;
4490Sstevel@tonic-gate 	char errmsg[256];
4500Sstevel@tonic-gate 	char lastc = '\0';
4510Sstevel@tonic-gate 	uint_t significant = pwdef->maxlength;
4520Sstevel@tonic-gate 	char *w;
4530Sstevel@tonic-gate 
4540Sstevel@tonic-gate 	(void) pam_get_item(pamh, PAM_SERVICE, (void **)&progname);
4550Sstevel@tonic-gate 
4560Sstevel@tonic-gate 	/* go over the password gathering statistics */
4570Sstevel@tonic-gate 	for (w = pw; significant != 0 && *w != '\0'; w++, significant--) {
4580Sstevel@tonic-gate 		if (isalpha(*w)) {
4590Sstevel@tonic-gate 			alpha_cnt++;
4600Sstevel@tonic-gate 			if (isupper(*w)) {
4610Sstevel@tonic-gate 				upper_cnt++;
4620Sstevel@tonic-gate 			} else {
4630Sstevel@tonic-gate 				lower_cnt++;
4640Sstevel@tonic-gate 			}
4650Sstevel@tonic-gate 		} else if (isspace(*w))
4660Sstevel@tonic-gate 			whitespace_cnt++;
4670Sstevel@tonic-gate 		else if (isdigit(*w))
4680Sstevel@tonic-gate 			digit_cnt++;
4690Sstevel@tonic-gate 		else
4700Sstevel@tonic-gate 			special_cnt++;
4710Sstevel@tonic-gate 		if (*w == lastc) {
4720Sstevel@tonic-gate 			if (++repeat > maxrepeat)
4730Sstevel@tonic-gate 				maxrepeat = repeat;
4740Sstevel@tonic-gate 		} else {
4750Sstevel@tonic-gate 			repeat = 1;
4760Sstevel@tonic-gate 		}
4770Sstevel@tonic-gate 		lastc = *w;
4780Sstevel@tonic-gate 	}
4790Sstevel@tonic-gate 
4800Sstevel@tonic-gate 	/*
4810Sstevel@tonic-gate 	 * If we only consider part of the password (the first maxlength
4820Sstevel@tonic-gate 	 * characters) we give a modified error message. Otherwise, a
4830Sstevel@tonic-gate 	 * user entering FooBar1234 with PASSLENGTH=6, MINDIGIT=4, while
4840Sstevel@tonic-gate 	 * we're using the default UNIX crypt (8 chars significant),
4850Sstevel@tonic-gate 	 * would not understand what's going on when he's told that
4860Sstevel@tonic-gate 	 * "The password should contain at least 4 digits"...
4870Sstevel@tonic-gate 	 * Instead, we now well him
4880Sstevel@tonic-gate 	 * "The first 8 characters of the password should contain at least
4890Sstevel@tonic-gate 	 *  4 digits."
4900Sstevel@tonic-gate 	 */
4910Sstevel@tonic-gate 	if (pwdef->maxlength < strlen(pw))
4920Sstevel@tonic-gate 		/*
4930Sstevel@tonic-gate 		 * TRANSLATION_NOTE
4940Sstevel@tonic-gate 		 * - Make sure the % and %% come over intact
4950Sstevel@tonic-gate 		 * - The last %%s will be replaced by strings like
4960Sstevel@tonic-gate 		 *	"alphabetic character(s)"
4970Sstevel@tonic-gate 		 *	"numeric or special character(s)"
4980Sstevel@tonic-gate 		 *	"special character(s)"
4990Sstevel@tonic-gate 		 *	"digit(s)"
5000Sstevel@tonic-gate 		 *	"uppercase alpha character(s)"
5010Sstevel@tonic-gate 		 *	"lowercase alpha character(s)"
5020Sstevel@tonic-gate 		 *   So the final string written to the user might become
5030Sstevel@tonic-gate 		 * "passwd: The first 8 characters of the password must contain
5040Sstevel@tonic-gate 		 *   at least 4 uppercase alpha characters(s)"
5050Sstevel@tonic-gate 		 */
5060Sstevel@tonic-gate 		(void) snprintf(errmsg, sizeof (errmsg), dgettext(TEXT_DOMAIN,
5070Sstevel@tonic-gate 		    "%s: The first %d characters of the password must "
5080Sstevel@tonic-gate 		    "contain at least %%d %%s."), progname, pwdef->maxlength);
5090Sstevel@tonic-gate 	else
5100Sstevel@tonic-gate 		/*
5110Sstevel@tonic-gate 		 * TRANSLATION_NOTE
5120Sstevel@tonic-gate 		 * - Make sure the % and %% come over intact
5130Sstevel@tonic-gate 		 * - The last %%s will be replaced by strings like
5140Sstevel@tonic-gate 		 *	"alphabetic character(s)"
5150Sstevel@tonic-gate 		 *	"numeric or special character(s)"
5160Sstevel@tonic-gate 		 *	"special character(s)"
5170Sstevel@tonic-gate 		 *	"digit(s)"
5180Sstevel@tonic-gate 		 *	"uppercase alpha character(s)"
5190Sstevel@tonic-gate 		 *	"lowercase alpha character(s)"
5200Sstevel@tonic-gate 		 *   So the final string written to the user might become
5210Sstevel@tonic-gate 		 * "passwd: The password must contain at least 4 uppercase
5220Sstevel@tonic-gate 		 *   alpha characters(s)"
5230Sstevel@tonic-gate 		 */
5240Sstevel@tonic-gate 		(void) snprintf(errmsg, sizeof (errmsg), dgettext(TEXT_DOMAIN,
5250Sstevel@tonic-gate 		    "%s: The password must contain at least %%d %%s."),
5260Sstevel@tonic-gate 		    progname);
5270Sstevel@tonic-gate 
5280Sstevel@tonic-gate 	/* Check for whitespace first since it influences special counts */
5290Sstevel@tonic-gate 	if (whitespace_cnt > 0 && pwdef->whitespace == B_FALSE) {
5300Sstevel@tonic-gate 		error(pamh, flags, dgettext(TEXT_DOMAIN,
5310Sstevel@tonic-gate 		    "%s: Whitespace characters are not allowed."), progname);
5320Sstevel@tonic-gate 		ret = 1;
5330Sstevel@tonic-gate 		goto out;
5340Sstevel@tonic-gate 	}
5350Sstevel@tonic-gate 
5360Sstevel@tonic-gate 	/*
5370Sstevel@tonic-gate 	 * Once we get here, whitespace_cnt is either 0, or whitespaces are
5380Sstevel@tonic-gate 	 * to be treated a special characters.
5390Sstevel@tonic-gate 	 */
5400Sstevel@tonic-gate 
5410Sstevel@tonic-gate 	if (alpha_cnt < pwdef->minalpha) {
5420Sstevel@tonic-gate 		error(pamh, flags, errmsg, pwdef->minalpha,
5430Sstevel@tonic-gate 		    dgettext(TEXT_DOMAIN, "alphabetic character(s)"));
5440Sstevel@tonic-gate 		ret = 1;
5450Sstevel@tonic-gate 		goto out;
5460Sstevel@tonic-gate 	}
5470Sstevel@tonic-gate 
5480Sstevel@tonic-gate 	if (pwdef->minnonalpha > 0) {
5490Sstevel@tonic-gate 		/* specials are defined by MINNONALPHA */
5500Sstevel@tonic-gate 		/* nonalpha = special+whitespace+digit */
5510Sstevel@tonic-gate 		if ((special_cnt + whitespace_cnt + digit_cnt) <
5520Sstevel@tonic-gate 		    pwdef->minnonalpha) {
5530Sstevel@tonic-gate 			error(pamh, flags, errmsg, pwdef->minnonalpha,
5540Sstevel@tonic-gate 			    dgettext(TEXT_DOMAIN,
5558563SKenjiro.Tsuji@Sun.COM 			    "numeric or special character(s)"));
5560Sstevel@tonic-gate 			ret = 1;
5570Sstevel@tonic-gate 			goto out;
5580Sstevel@tonic-gate 		}
5590Sstevel@tonic-gate 	} else {
5600Sstevel@tonic-gate 		/* specials are defined by MINSPECIAL and/or MINDIGIT */
5610Sstevel@tonic-gate 		if ((special_cnt + whitespace_cnt) < pwdef->minspecial) {
5620Sstevel@tonic-gate 			error(pamh, flags, errmsg, pwdef->minspecial,
5630Sstevel@tonic-gate 			    dgettext(TEXT_DOMAIN, "special character(s)"));
5640Sstevel@tonic-gate 			ret = 1;
5650Sstevel@tonic-gate 			goto out;
5660Sstevel@tonic-gate 		}
5670Sstevel@tonic-gate 		if (digit_cnt < pwdef->mindigit) {
5680Sstevel@tonic-gate 			error(pamh, flags, errmsg, pwdef->mindigit,
5690Sstevel@tonic-gate 			    dgettext(TEXT_DOMAIN, "digit(s)"));
5700Sstevel@tonic-gate 			ret = 1;
5710Sstevel@tonic-gate 			goto out;
5720Sstevel@tonic-gate 		}
5730Sstevel@tonic-gate 	}
5740Sstevel@tonic-gate 
5750Sstevel@tonic-gate 	if (upper_cnt < pwdef->minupper) {
5760Sstevel@tonic-gate 		error(pamh, flags, errmsg, pwdef->minupper,
5770Sstevel@tonic-gate 		    dgettext(TEXT_DOMAIN, "uppercase alpha character(s)"));
5780Sstevel@tonic-gate 		ret = 1;
5790Sstevel@tonic-gate 		goto out;
5800Sstevel@tonic-gate 	}
5810Sstevel@tonic-gate 	if (lower_cnt < pwdef->minlower) {
5820Sstevel@tonic-gate 		error(pamh, flags, errmsg, pwdef->minlower,
5830Sstevel@tonic-gate 		    dgettext(TEXT_DOMAIN, "lowercase alpha character(s)"));
5840Sstevel@tonic-gate 		ret = 1;
5850Sstevel@tonic-gate 		goto out;
5860Sstevel@tonic-gate 	}
5870Sstevel@tonic-gate 
5880Sstevel@tonic-gate 	if (pwdef->maxrepeat > 0 && maxrepeat > pwdef->maxrepeat) {
5890Sstevel@tonic-gate 		error(pamh, flags, dgettext(TEXT_DOMAIN,
5900Sstevel@tonic-gate 		    "%s: Too many consecutively repeating characters. "
5910Sstevel@tonic-gate 		    "Maximum allowed is %d."), progname, pwdef->maxrepeat);
5920Sstevel@tonic-gate 		ret = 1;
5930Sstevel@tonic-gate 	}
5940Sstevel@tonic-gate out:
5950Sstevel@tonic-gate 	return (ret);
5960Sstevel@tonic-gate }
5970Sstevel@tonic-gate 
5980Sstevel@tonic-gate /*
5990Sstevel@tonic-gate  * make sure that old and new password differ by at least 'mindiff'
6000Sstevel@tonic-gate  * positions. Return 0 if OK, 1 otherwise
6010Sstevel@tonic-gate  */
6020Sstevel@tonic-gate int
check_diff(char * pw,char * opw,struct pwdefaults * pwdef,pam_handle_t * pamh,int flags)6030Sstevel@tonic-gate check_diff(char *pw, char *opw, struct pwdefaults *pwdef, pam_handle_t *pamh,
6040Sstevel@tonic-gate     int flags)
6050Sstevel@tonic-gate {
6060Sstevel@tonic-gate 	size_t pwlen, opwlen, max;
6070Sstevel@tonic-gate 	unsigned int diff;	/* difference between old and new */
6080Sstevel@tonic-gate 
6090Sstevel@tonic-gate 	if (opw == NULL)
6100Sstevel@tonic-gate 		opw = "";
6110Sstevel@tonic-gate 
6120Sstevel@tonic-gate 	max = pwdef->maxlength;
6130Sstevel@tonic-gate 	pwlen = MIN(strlen(pw), max);
6140Sstevel@tonic-gate 	opwlen = MIN(strlen(opw), max);
6150Sstevel@tonic-gate 
6160Sstevel@tonic-gate 	if (pwlen > opwlen)
6170Sstevel@tonic-gate 		diff = pwlen - opwlen;
6180Sstevel@tonic-gate 	else
6190Sstevel@tonic-gate 		diff = opwlen - pwlen;
6200Sstevel@tonic-gate 
6210Sstevel@tonic-gate 	while (*opw != '\0' && *pw != '\0' && max-- != 0) {
6220Sstevel@tonic-gate 		if (*opw != *pw)
6230Sstevel@tonic-gate 			diff++;
6240Sstevel@tonic-gate 		opw++;
6250Sstevel@tonic-gate 		pw++;
6260Sstevel@tonic-gate 	}
6270Sstevel@tonic-gate 
6280Sstevel@tonic-gate 	if (diff  < pwdef->mindiff) {
6290Sstevel@tonic-gate 		char *progname;
6300Sstevel@tonic-gate 
6310Sstevel@tonic-gate 		(void) pam_get_item(pamh, PAM_SERVICE, (void **)&progname);
6320Sstevel@tonic-gate 
6330Sstevel@tonic-gate 		error(pamh, flags, dgettext(TEXT_DOMAIN,
6340Sstevel@tonic-gate 		    "%s: The first %d characters of the old and new passwords "
6350Sstevel@tonic-gate 		    "must differ by at least %d positions."), progname,
6360Sstevel@tonic-gate 		    pwdef->maxlength, pwdef->mindiff);
6370Sstevel@tonic-gate 		return (1);
6380Sstevel@tonic-gate 	}
6390Sstevel@tonic-gate 
6400Sstevel@tonic-gate 	return (0);
6410Sstevel@tonic-gate }
6420Sstevel@tonic-gate 
6430Sstevel@tonic-gate /*
6440Sstevel@tonic-gate  * check to see if password is in one way or another based on a
6450Sstevel@tonic-gate  * dictionary word. Returns 0 if password is OK, 1 if it is based
6460Sstevel@tonic-gate  * on a dictionary word and hence should be rejected.
6470Sstevel@tonic-gate  */
6480Sstevel@tonic-gate int
check_dictionary(char * pw,struct pwdefaults * pwdef,pam_handle_t * pamh,int flags)6490Sstevel@tonic-gate check_dictionary(char *pw, struct pwdefaults *pwdef, pam_handle_t *pamh,
6500Sstevel@tonic-gate     int flags)
6510Sstevel@tonic-gate {
6520Sstevel@tonic-gate 	int crack_ret;
6530Sstevel@tonic-gate 	int ret;
6540Sstevel@tonic-gate 	char *progname;
6550Sstevel@tonic-gate 
6560Sstevel@tonic-gate 	(void) pam_get_item(pamh, PAM_SERVICE, (void **)&progname);
6570Sstevel@tonic-gate 
6580Sstevel@tonic-gate 	/* dictionary check isn't MT-safe */
6590Sstevel@tonic-gate 	(void) mutex_lock(&dictlock);
6600Sstevel@tonic-gate 
6610Sstevel@tonic-gate 	if (pwdef->dicts &&
6620Sstevel@tonic-gate 	    make_dict_database(pwdef->dicts, pwdef->db_location) != 0) {
6630Sstevel@tonic-gate 		(void) mutex_unlock(&dictlock);
6640Sstevel@tonic-gate 		syslog(LOG_ERR, "pam_authtok_check:pam_sm_chauthtok: "
6650Sstevel@tonic-gate 		    "Dictionary database not present.");
6660Sstevel@tonic-gate 		error(pamh, flags, dgettext(TEXT_DOMAIN,
6670Sstevel@tonic-gate 		    "%s: password dictionary missing."), progname);
6680Sstevel@tonic-gate 		return (PAM_SYSTEM_ERR);
6690Sstevel@tonic-gate 	}
6700Sstevel@tonic-gate 
6710Sstevel@tonic-gate 	crack_ret = DictCheck(pw, pwdef->db_location);
6720Sstevel@tonic-gate 
6730Sstevel@tonic-gate 	(void) mutex_unlock(&dictlock);
6740Sstevel@tonic-gate 
6750Sstevel@tonic-gate 	switch (crack_ret) {
6760Sstevel@tonic-gate 	case DATABASE_OPEN_FAIL:
6770Sstevel@tonic-gate 		syslog(LOG_ERR, "pam_authtok_check:pam_sm_chauthtok: "
6780Sstevel@tonic-gate 		    "dictionary database open failure: %s", strerror(errno));
6790Sstevel@tonic-gate 		error(pamh, flags, dgettext(TEXT_DOMAIN,
6800Sstevel@tonic-gate 		    "%s: failed to open dictionary database."), progname);
6810Sstevel@tonic-gate 		ret = PAM_SYSTEM_ERR;
6820Sstevel@tonic-gate 		break;
6830Sstevel@tonic-gate 	case DICTIONARY_WORD:
6840Sstevel@tonic-gate 		error(pamh, flags, dgettext(TEXT_DOMAIN,
6850Sstevel@tonic-gate 		    "%s: password is based on a dictionary word."), progname);
6860Sstevel@tonic-gate 		ret = PAM_AUTHTOK_ERR;
6870Sstevel@tonic-gate 		break;
6880Sstevel@tonic-gate 	case REVERSE_DICTIONARY_WORD:
6890Sstevel@tonic-gate 		error(pamh, flags, dgettext(TEXT_DOMAIN,
6900Sstevel@tonic-gate 		    "%s: password is based on a reversed dictionary word."),
6910Sstevel@tonic-gate 		    progname);
6920Sstevel@tonic-gate 		ret = PAM_AUTHTOK_ERR;
6930Sstevel@tonic-gate 		break;
6940Sstevel@tonic-gate 	default:
6950Sstevel@tonic-gate 		ret = PAM_SUCCESS;
6960Sstevel@tonic-gate 		break;
6970Sstevel@tonic-gate 	}
6980Sstevel@tonic-gate 	return (ret);
6990Sstevel@tonic-gate }
7000Sstevel@tonic-gate 
7010Sstevel@tonic-gate int
pam_sm_chauthtok(pam_handle_t * pamh,int flags,int argc,const char ** argv)7020Sstevel@tonic-gate pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, const char **argv)
7030Sstevel@tonic-gate {
7040Sstevel@tonic-gate 	int	debug = 0;
7050Sstevel@tonic-gate 	int	retcode = 0;
7060Sstevel@tonic-gate 	int	force_check = 0;
7070Sstevel@tonic-gate 	int 	i;
7080Sstevel@tonic-gate 	size_t	pwlen;
7090Sstevel@tonic-gate 	char	*usrname;
7100Sstevel@tonic-gate 	char	*pwbuf, *opwbuf;
7110Sstevel@tonic-gate 	pwu_repository_t *pwu_rep = PWU_DEFAULT_REP;
7120Sstevel@tonic-gate 	pam_repository_t *pwd_rep = NULL;
7130Sstevel@tonic-gate 	struct pwdefaults pwdef;
7140Sstevel@tonic-gate 	char *progname;
7150Sstevel@tonic-gate 
7160Sstevel@tonic-gate 	/* needs to be set before option processing */
7170Sstevel@tonic-gate 	pwdef.server_policy = B_FALSE;
7180Sstevel@tonic-gate 
7190Sstevel@tonic-gate 	for (i = 0; i < argc; i++) {
7200Sstevel@tonic-gate 		if (strcmp(argv[i], "debug") == 0)
7210Sstevel@tonic-gate 			debug = 1;
7220Sstevel@tonic-gate 		if (strcmp(argv[i], "force_check") == 0)
7230Sstevel@tonic-gate 			force_check = 1;
7240Sstevel@tonic-gate 		if (strcmp(argv[i], "server_policy") == 0)
7250Sstevel@tonic-gate 			pwdef.server_policy = B_TRUE;
7260Sstevel@tonic-gate 	}
7270Sstevel@tonic-gate 
7280Sstevel@tonic-gate 	if (debug)
7290Sstevel@tonic-gate 		syslog(LOG_AUTH | LOG_DEBUG,
7300Sstevel@tonic-gate 		    "pam_authtok_check: pam_sm_chauthok called(%x) "
7310Sstevel@tonic-gate 		    "force_check = %d", flags, force_check);
7320Sstevel@tonic-gate 
7330Sstevel@tonic-gate 	if ((flags & PAM_PRELIM_CHECK) == 0)
7340Sstevel@tonic-gate 		return (PAM_IGNORE);
7350Sstevel@tonic-gate 
7360Sstevel@tonic-gate 	(void) pam_get_item(pamh, PAM_SERVICE, (void **)&progname);
7370Sstevel@tonic-gate 	(void) pam_get_item(pamh, PAM_USER, (void **)&usrname);
7380Sstevel@tonic-gate 	if (usrname == NULL || *usrname == '\0') {
7390Sstevel@tonic-gate 		syslog(LOG_ERR, "pam_authtok_check: username name is empty");
7400Sstevel@tonic-gate 		return (PAM_USER_UNKNOWN);
7410Sstevel@tonic-gate 	}
7420Sstevel@tonic-gate 
7430Sstevel@tonic-gate 	(void) pam_get_item(pamh, PAM_AUTHTOK, (void **)&pwbuf);
7440Sstevel@tonic-gate 	(void) pam_get_item(pamh, PAM_OLDAUTHTOK, (void **)&opwbuf);
7450Sstevel@tonic-gate 	if (pwbuf == NULL)
7460Sstevel@tonic-gate 		return (PAM_AUTHTOK_ERR);
7470Sstevel@tonic-gate 
7480Sstevel@tonic-gate 	/* none of these checks holds if caller say so */
7490Sstevel@tonic-gate 	if ((flags & PAM_NO_AUTHTOK_CHECK) != 0 && force_check == 0)
7500Sstevel@tonic-gate 		return (PAM_SUCCESS);
7510Sstevel@tonic-gate 
7520Sstevel@tonic-gate 	/* read system-defaults */
7530Sstevel@tonic-gate 	retcode = get_passwd_defaults(pamh, usrname, &pwdef);
7540Sstevel@tonic-gate 	if (retcode != PAM_SUCCESS)
7550Sstevel@tonic-gate 		return (retcode);
7560Sstevel@tonic-gate 
7570Sstevel@tonic-gate 	if (debug) {
7580Sstevel@tonic-gate 		syslog(LOG_AUTH | LOG_DEBUG,
7590Sstevel@tonic-gate 		    "pam_authtok_check: MAXLENGTH= %d, server_policy = %s",
7600Sstevel@tonic-gate 		    pwdef.maxlength, pwdef.server_policy ? "true" : "false");
7610Sstevel@tonic-gate 		syslog(LOG_AUTH | LOG_DEBUG,
7620Sstevel@tonic-gate 		    "pam_authtok_check: PASSLENGTH= %d", pwdef.minlength);
7630Sstevel@tonic-gate 		syslog(LOG_AUTH | LOG_DEBUG, "pam_authtok_check: NAMECHECK=%s",
7640Sstevel@tonic-gate 		    pwdef.do_namecheck == B_TRUE ? "Yes" : "No");
7650Sstevel@tonic-gate 		syslog(LOG_AUTH | LOG_DEBUG,
7660Sstevel@tonic-gate 		    "pam_authtok_check: do_dictcheck = %s\n",
7670Sstevel@tonic-gate 		    pwdef.do_dictcheck ? "true" : "false");
7680Sstevel@tonic-gate 		if (pwdef.do_dictcheck) {
7690Sstevel@tonic-gate 			syslog(LOG_AUTH | LOG_DEBUG,
7700Sstevel@tonic-gate 			    "pam_authtok_check: DICTIONLIST=%s",
7710Sstevel@tonic-gate 			    (pwdef.dicts != NULL) ? pwdef.dicts : "<not set>");
7720Sstevel@tonic-gate 			syslog(LOG_AUTH | LOG_DEBUG,
7730Sstevel@tonic-gate 			    "pam_authtok_check: DICTIONDBDIR=%s",
7740Sstevel@tonic-gate 			    pwdef.db_location);
7750Sstevel@tonic-gate 		}
7760Sstevel@tonic-gate 		syslog(LOG_AUTH | LOG_DEBUG, "pam_authtok_check: MINDIFF=%d",
7770Sstevel@tonic-gate 		    pwdef.mindiff);
7780Sstevel@tonic-gate 		syslog(LOG_AUTH | LOG_DEBUG,
7790Sstevel@tonic-gate 		    "pam_authtok_check: MINALPHA=%d, MINNONALPHA=%d",
7800Sstevel@tonic-gate 		    pwdef.minalpha, pwdef.minnonalpha);
7810Sstevel@tonic-gate 		syslog(LOG_AUTH | LOG_DEBUG,
7820Sstevel@tonic-gate 		    "pam_authtok_check: MINSPECIAL=%d, MINDIGIT=%d",
7830Sstevel@tonic-gate 		    pwdef.minspecial, pwdef.mindigit);
7840Sstevel@tonic-gate 		syslog(LOG_AUTH | LOG_DEBUG, "pam_authtok_check: WHITESPACE=%s",
7850Sstevel@tonic-gate 		    pwdef.whitespace ? "YES" : "NO");
7860Sstevel@tonic-gate 		syslog(LOG_AUTH | LOG_DEBUG,
7870Sstevel@tonic-gate 		    "pam_authtok_check: MINUPPER=%d, MINLOWER=%d",
7880Sstevel@tonic-gate 		    pwdef.minupper, pwdef.minlower);
7890Sstevel@tonic-gate 		syslog(LOG_AUTH | LOG_DEBUG, "pam_authtok_check: MAXREPEATS=%d",
7900Sstevel@tonic-gate 		    pwdef.maxrepeat);
7910Sstevel@tonic-gate 	}
7920Sstevel@tonic-gate 
7930Sstevel@tonic-gate 	/*
7940Sstevel@tonic-gate 	 * If server policy is still true (might be changed from the
7950Sstevel@tonic-gate 	 * value specified in /etc/pam.conf by get_passwd_defaults()),
7960Sstevel@tonic-gate 	 * we return ignore and let the server do all the checks.
7970Sstevel@tonic-gate 	 */
7980Sstevel@tonic-gate 	if (pwdef.server_policy == B_TRUE) {
7990Sstevel@tonic-gate 		free_passwd_defaults(&pwdef);
8000Sstevel@tonic-gate 		return (PAM_IGNORE);
8010Sstevel@tonic-gate 	}
8020Sstevel@tonic-gate 
8030Sstevel@tonic-gate 	/*
8040Sstevel@tonic-gate 	 * XXX: JV: we can't really make any assumption on the length of
8050Sstevel@tonic-gate 	 *	the password that will be used by the crypto algorithm.
8060Sstevel@tonic-gate 	 *	for UNIX-style encryption, minalpha=5,minnonalpha=5 might
8070Sstevel@tonic-gate 	 *	be impossible, but not for MD5 style hashes... what to do?
8080Sstevel@tonic-gate 	 *
8090Sstevel@tonic-gate 	 *	since we don't know what alg. will be used, we operate on
8100Sstevel@tonic-gate 	 *	the password as entered, so we don't sanity check anything
8110Sstevel@tonic-gate 	 *	for now.
8120Sstevel@tonic-gate 	 */
8130Sstevel@tonic-gate 
8140Sstevel@tonic-gate 	/*
8150Sstevel@tonic-gate 	 * Make sure new password is long enough
8160Sstevel@tonic-gate 	 */
8170Sstevel@tonic-gate 	pwlen = strlen(pwbuf);
8180Sstevel@tonic-gate 
8190Sstevel@tonic-gate 	if (pwlen < pwdef.minlength) {
8200Sstevel@tonic-gate 		error(pamh, flags, dgettext(TEXT_DOMAIN,
8210Sstevel@tonic-gate 		    "%s: Password too short - must be at least %d "
8220Sstevel@tonic-gate 		    "characters."), progname, pwdef.minlength);
8230Sstevel@tonic-gate 		free_passwd_defaults(&pwdef);
8240Sstevel@tonic-gate 		return (PAM_AUTHTOK_ERR);
8250Sstevel@tonic-gate 	}
8260Sstevel@tonic-gate 
8270Sstevel@tonic-gate 	/* Make sure the password doesn't equal--a shift of--the username */
8280Sstevel@tonic-gate 	if (pwdef.do_namecheck) {
8290Sstevel@tonic-gate 		switch (check_circular(usrname, pwbuf)) {
8300Sstevel@tonic-gate 		case 1:
8310Sstevel@tonic-gate 			error(pamh, flags, dgettext(TEXT_DOMAIN,
8320Sstevel@tonic-gate 			    "%s: Password cannot be circular shift of "
8330Sstevel@tonic-gate 			    "logonid."), progname);
8340Sstevel@tonic-gate 			free_passwd_defaults(&pwdef);
8350Sstevel@tonic-gate 			return (PAM_AUTHTOK_ERR);
8360Sstevel@tonic-gate 		case -1:
8370Sstevel@tonic-gate 			free_passwd_defaults(&pwdef);
8380Sstevel@tonic-gate 			return (PAM_BUF_ERR);
8390Sstevel@tonic-gate 		default:
8400Sstevel@tonic-gate 			break;
8410Sstevel@tonic-gate 		}
8420Sstevel@tonic-gate 	}
8430Sstevel@tonic-gate 
8440Sstevel@tonic-gate 	/* Check if new password is in history list. */
8450Sstevel@tonic-gate 	(void) pam_get_item(pamh, PAM_REPOSITORY, (void **)&pwd_rep);
8460Sstevel@tonic-gate 	if (pwd_rep != NULL) {
8470Sstevel@tonic-gate 		if ((pwu_rep = calloc(1, sizeof (*pwu_rep))) == NULL)
8480Sstevel@tonic-gate 			return (PAM_BUF_ERR);
8490Sstevel@tonic-gate 		pwu_rep->type = pwd_rep->type;
8500Sstevel@tonic-gate 		pwu_rep->scope = pwd_rep->scope;
8510Sstevel@tonic-gate 		pwu_rep->scope_len = pwd_rep->scope_len;
8520Sstevel@tonic-gate 	}
8530Sstevel@tonic-gate 
8540Sstevel@tonic-gate 	if (__check_history(usrname, pwbuf, pwu_rep) == PWU_SUCCESS) {
8550Sstevel@tonic-gate 		/* password found in history */
8560Sstevel@tonic-gate 		error(pamh, flags, dgettext(TEXT_DOMAIN,
8570Sstevel@tonic-gate 		    "%s: Password in history list."), progname);
8580Sstevel@tonic-gate 		if (pwu_rep != PWU_DEFAULT_REP)
8590Sstevel@tonic-gate 			free(pwu_rep);
8600Sstevel@tonic-gate 		free_passwd_defaults(&pwdef);
8610Sstevel@tonic-gate 		return (PAM_AUTHTOK_ERR);
8620Sstevel@tonic-gate 	}
8630Sstevel@tonic-gate 
8640Sstevel@tonic-gate 	if (pwu_rep != PWU_DEFAULT_REP)
8650Sstevel@tonic-gate 		free(pwu_rep);
8660Sstevel@tonic-gate 
8670Sstevel@tonic-gate 	/* check MINALPHA, MINLOWER, etc. */
8680Sstevel@tonic-gate 	if (check_composition(pwbuf, &pwdef, pamh, flags) != 0) {
8690Sstevel@tonic-gate 		free_passwd_defaults(&pwdef);
8700Sstevel@tonic-gate 		return (PAM_AUTHTOK_ERR);
8710Sstevel@tonic-gate 	}
8720Sstevel@tonic-gate 
8730Sstevel@tonic-gate 	/* make sure the old and new password are not too much alike */
8740Sstevel@tonic-gate 	if (check_diff(pwbuf, opwbuf, &pwdef, pamh, flags) != 0) {
8750Sstevel@tonic-gate 		free_passwd_defaults(&pwdef);
8760Sstevel@tonic-gate 		return (PAM_AUTHTOK_ERR);
8770Sstevel@tonic-gate 	}
8780Sstevel@tonic-gate 
8790Sstevel@tonic-gate 	/* dictionary check */
8800Sstevel@tonic-gate 	if (pwdef.do_dictcheck) {
8810Sstevel@tonic-gate 		retcode = check_dictionary(pwbuf, &pwdef, pamh, flags);
8820Sstevel@tonic-gate 		if (retcode != PAM_SUCCESS) {
8830Sstevel@tonic-gate 			free_passwd_defaults(&pwdef);
8840Sstevel@tonic-gate 			return (retcode);
8850Sstevel@tonic-gate 		}
8860Sstevel@tonic-gate 	}
8870Sstevel@tonic-gate 
8880Sstevel@tonic-gate 	free_passwd_defaults(&pwdef);
8890Sstevel@tonic-gate 	/* password has passed all tests: it's strong enough */
8900Sstevel@tonic-gate 	return (PAM_SUCCESS);
8910Sstevel@tonic-gate }
892