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