/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * Copyright (c) 2016 by Delphix. All rights reserved. * Copyright 2023 OmniOS Community Edition (OmniOSce) Association. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "packer.h" #include #define PWADMIN "/etc/default/passwd" #define MINLENGTH 6 #define MINDIFF 3 #define MINALPHA 2 #define MINNONALPHA 1 mutex_t dictlock = DEFAULTMUTEX; /* * We implement: * PASSLENGTH (int) minimum password length * NAMECHECK (yes/no) perform comparison of password and loginname * MINDIFF (int) minimum number of character-positions in which * the old and the new password should differ. * MINALPHA (int) minimum number of Alpha characters * MINUPPER (int) minimum number of upper-case characters * MINLOWER (int) minimum number of lower-case characters * MAXREPEATS (int) maximum number of consecutively repeating chars * WHITESPACE (yes/no) Are whitespaces allowed? * * Furthermore, these two mutualy exclusive groups of options are allowed: * * MINNONALPHA (int) minimum number of characters from the * character classes [ punct, space, digit ] * if WHITESPACE == NO, whitespaces don't count. * and * MINSPECIAL (int) minimum number of punctuation characters. * if WHITESPACE != NO, whitespace is seen as * a "special" character. * MINDIGIT (int) minimum number of digits * * specifying options from both groups results in an error to syslog and * failure to change the password. * * NOTE: * HISTORY is implemented at the repository level (passwdutil). */ /* * default password-strength-values, compiled-in or stored in PWADMIN * are kept in here */ struct pwdefaults { boolean_t server_policy; /* server policy flag from pam.conf */ uint_t minlength; /* minimum password lenght */ uint_t maxlength; /* maximum (significant) length */ boolean_t do_namecheck; /* check password against user's gecos */ char db_location[MAXPATHLEN]; /* location of the generated database */ boolean_t do_dictcheck; /* perform dictionary lookup */ char *dicts; /* list of dictionaries configured */ uint_t mindiff; /* old and new should differ by this much */ uint_t minalpha; /* minimum alpha characters required */ uint_t minupper; /* minimum uppercase characters required */ uint_t minlower; /* minimum lowercase characters required */ uint_t minnonalpha; /* minimum special (non alpha) required */ uint_t maxrepeat; /* maximum number of repeating chars allowed */ uint_t minspecial; /* punctuation characters */ uint_t mindigit; /* minimum number of digits required */ boolean_t whitespace; /* is whitespace allowed in a password */ }; /*PRINTFLIKE3*/ void error(pam_handle_t *pamh, int flags, char *fmt, ...) { va_list ap; char msg[1][PAM_MAX_MSG_SIZE]; va_start(ap, fmt); (void) vsnprintf(msg[0], sizeof (msg[0]), fmt, ap); va_end(ap); if ((flags & PAM_SILENT) == 0) (void) __pam_display_msg(pamh, PAM_ERROR_MSG, 1, msg, NULL); } int defread_int(char *name, uint_t *ip, void *defp) { char *q; int r = 0; if ((q = defread_r(name, defp)) != NULL) { if (!isdigit(*q)) { syslog(LOG_ERR, "pam_authtok_check: %s contains " "non-integer value for %s: %s. " "Using default instead.", PWADMIN, name, q); } else { *ip = atoi(q); r = 1; } } return (r); } /* * fill in static defaults, and augment with settings from PWADMIN * get system defaults with regard to maximum password length */ int get_passwd_defaults(pam_handle_t *pamh, const char *user, struct pwdefaults *p) { char *q; boolean_t minnonalpha_defined = B_FALSE; pwu_repository_t *pwu_rep; const struct pam_repository *pam_rep; attrlist attr[2]; int result; const char *progname; void *defp; (void) pam_get_item(pamh, PAM_SERVICE, (const void **)&progname); /* Module defaults */ p->minlength = MINLENGTH; p->do_namecheck = B_TRUE; p->do_dictcheck = B_FALSE; p->dicts = NULL; p->mindiff = MINDIFF; p->minalpha = MINALPHA; p->minnonalpha = MINNONALPHA; p->minupper = 0; /* not configured by default */ p->minlower = 0; /* not configured by default */ p->maxrepeat = 0; /* not configured by default */ p->minspecial = 0; p->mindigit = 0; p->whitespace = B_TRUE; if ((defp = defopen_r(PWADMIN)) == NULL) return (PAM_SUCCESS); (void) defread_int("PASSLENGTH=", &p->minlength, defp); if ((q = defread_r("NAMECHECK=", defp)) != NULL && strcasecmp(q, "NO") == 0) p->do_namecheck = B_FALSE; if ((q = defread_r("DICTIONLIST=", defp)) != NULL) { if ((p->dicts = strdup(q)) == NULL) { syslog(LOG_ERR, "pam_authtok_check: out of memory"); defclose_r(defp); return (PAM_BUF_ERR); } p->do_dictcheck = B_TRUE; } else { p->dicts = NULL; } if ((q = defread_r("DICTIONDBDIR=", defp)) != NULL) { if (strlcpy(p->db_location, q, sizeof (p->db_location)) >= sizeof (p->db_location)) { syslog(LOG_ERR, "pam_authtok_check: value for " "DICTIONDBDIR too large."); defclose_r(defp); return (PAM_SYSTEM_ERR); } p->do_dictcheck = B_TRUE; } else { (void) strlcpy(p->db_location, CRACK_DIR, sizeof (p->db_location)); } (void) defread_int("MINDIFF=", &p->mindiff, defp); (void) defread_int("MINALPHA=", &p->minalpha, defp); (void) defread_int("MINUPPER=", &p->minupper, defp); (void) defread_int("MINLOWER=", &p->minlower, defp); if (defread_int("MINNONALPHA=", &p->minnonalpha, defp)) minnonalpha_defined = B_TRUE; (void) defread_int("MAXREPEATS=", &p->maxrepeat, defp); if (defread_int("MINSPECIAL=", &p->minspecial, defp)) { if (minnonalpha_defined) { syslog(LOG_ERR, "pam_authtok_check: %s contains " "definition for MINNONALPHA and for MINSPECIAL. " "These options are mutually exclusive.", PWADMIN); defclose_r(defp); return (PAM_SYSTEM_ERR); } p->minnonalpha = 0; } if (defread_int("MINDIGIT=", &p->mindigit, defp)) { if (minnonalpha_defined) { syslog(LOG_ERR, "pam_authtok_check: %s contains " "definition for MINNONALPHA and for MINDIGIT. " "These options are mutually exclusive.", PWADMIN); defclose_r(defp); return (PAM_SYSTEM_ERR); } p->minnonalpha = 0; } if ((q = defread_r("WHITESPACE=", defp)) != NULL) p->whitespace = (strcasecmp(q, "no") == 0 || strcmp(q, "0") == 0) ? B_FALSE : B_TRUE; defclose_r(defp); /* * Determine the number of significant characters in a password * * we find out where the user information came from (which repository), * and which password-crypt-algorithm is to be used (based on the * old password, or the system default). * * If the user comes from a repository other than FILES/NIS * the module-flag "server_policy" means that we don't perform * any checks on the user, but let the repository decide instead. */ (void) pam_get_item(pamh, PAM_REPOSITORY, (const void **)&pam_rep); if (pam_rep != NULL) { if ((pwu_rep = calloc(1, sizeof (*pwu_rep))) == NULL) return (PAM_BUF_ERR); pwu_rep->type = pam_rep->type; pwu_rep->scope = pam_rep->scope; pwu_rep->scope_len = pam_rep->scope_len; } else { pwu_rep = PWU_DEFAULT_REP; } attr[0].type = ATTR_PASSWD; attr[0].next = &attr[1]; attr[1].type = ATTR_REP_NAME; attr[1].next = NULL; result = __get_authtoken_attr(user, pwu_rep, attr); if (pwu_rep != PWU_DEFAULT_REP) free(pwu_rep); if (result != PWU_SUCCESS) { /* * In the unlikely event that we can't obtain any info about * the users password, we assume the most strict scenario. */ p->maxlength = _PASS_MAX_XPG; } else { char *oldpw = attr[0].data.val_s; char *repository = attr[1].data.val_s; if ((strcmp(repository, "files") == 0 || strcmp(repository, "nis") == 0) || p->server_policy == B_FALSE) { char *salt; /* * We currently need to supply this dummy to * crypt_gensalt(). This will change RSN. */ struct passwd dummy; dummy.pw_name = strdup(user); if (dummy.pw_name == NULL) { free(attr[0].data.val_s); free(attr[1].data.val_s); return (PAM_BUF_ERR); } salt = crypt_gensalt(oldpw, &dummy); free(dummy.pw_name); if (salt && *salt == '$') p->maxlength = _PASS_MAX; else p->maxlength = _PASS_MAX_XPG; free(salt); p->server_policy = B_FALSE; /* we perform checks */ } else { /* not files or nis AND server_policy is set */ p->maxlength = _PASS_MAX; } free(attr[0].data.val_s); free(attr[1].data.val_s); } /* sanity check of the configured parameters */ if (p->minlength < p->mindigit + p->minspecial + p->minnonalpha + p->minalpha) { syslog(LOG_ERR, "%s: pam_authtok_check: Defined minimum " "password length (PASSLENGTH=%d) is less then minimum " "characters in the various classes (%d)", progname, p->minlength, p->mindigit + p->minspecial + p->minnonalpha + p->minalpha); p->minlength = p->mindigit + p->minspecial + p->minnonalpha + p->minalpha; syslog(LOG_ERR, "%s: pam_authtok_check: effective " "PASSLENGTH set to %d.", progname, p->minlength); /* this won't lead to failure */ } if (p->maxlength < p->minlength) { syslog(LOG_ERR, "%s: pam_authtok_check: The configured " "minimum password length (PASSLENGTH=%d) is larger than " "the number of significant characters the current " "encryption algorithm uses (%d). See policy.conf(5) for " "alternative password encryption algorithms.", progname); /* this won't lead to failure */ } return (PAM_SUCCESS); } /* * free_passwd_defaults(struct pwdefaults *p) * * free space occupied by the defaults read from PWADMIN */ void free_passwd_defaults(struct pwdefaults *p) { if (p && p->dicts) free(p->dicts); } /* * check_circular(): * This function return 1 if string "t" is a circular shift of * string "s", else it returns 0. -1 is returned on failure. * We also check to see if string "t" is a reversed-circular shift * of string "s", i.e. "ABCDE" vs. "DCBAE". */ static int check_circular(s, t) char *s, *t; { char c, *p, *o, *r, *buff, *ubuff, *pubuff; unsigned int i, j, k, l, m; size_t len; int ret = 0; i = strlen(s); l = strlen(t); if (i != l) return (0); len = i + 1; buff = malloc(len); ubuff = malloc(len); pubuff = malloc(len); if (buff == NULL || ubuff == NULL || pubuff == NULL) { syslog(LOG_ERR, "pam_authtok_check: out of memory."); return (-1); } m = 2; o = &ubuff[0]; for (p = s; c = *p++; *o++ = c) if (islower(c)) c = toupper(c); *o = '\0'; o = &pubuff[0]; for (p = t; c = *p++; *o++ = c) if (islower(c)) c = toupper(c); *o = '\0'; p = &ubuff[0]; while (m--) { for (k = 0; k < i; k++) { c = *p++; o = p; l = i; r = &buff[0]; while (--l) *r++ = *o++; *r++ = c; *r = '\0'; p = &buff[0]; if (strcmp(p, pubuff) == 0) { ret = 1; goto out; } } p = p + i; r = &ubuff[0]; j = i; while (j--) *--p = *r++; /* reverse test-string for m==0 pass */ } out: (void) memset(buff, 0, len); (void) memset(ubuff, 0, len); (void) memset(pubuff, 0, len); free(buff); free(ubuff); free(pubuff); return (ret); } /* * count the different character classes present in the password. */ int check_composition(const char *pw, struct pwdefaults *pwdef, pam_handle_t *pamh, int flags) { uint_t alpha_cnt = 0; uint_t upper_cnt = 0; uint_t lower_cnt = 0; uint_t special_cnt = 0; uint_t whitespace_cnt = 0; uint_t digit_cnt = 0; uint_t maxrepeat = 0; uint_t repeat = 1; int ret = 0; const char *progname; char errmsg[256]; char lastc = '\0'; uint_t significant = pwdef->maxlength; const char *w; (void) pam_get_item(pamh, PAM_SERVICE, (const void **)&progname); /* go over the password gathering statistics */ for (w = pw; significant != 0 && *w != '\0'; w++, significant--) { if (isalpha(*w)) { alpha_cnt++; if (isupper(*w)) { upper_cnt++; } else { lower_cnt++; } } else if (isspace(*w)) whitespace_cnt++; else if (isdigit(*w)) digit_cnt++; else special_cnt++; if (*w == lastc) { if (++repeat > maxrepeat) maxrepeat = repeat; } else { repeat = 1; } lastc = *w; } /* * If we only consider part of the password (the first maxlength * characters) we give a modified error message. Otherwise, a * user entering FooBar1234 with PASSLENGTH=6, MINDIGIT=4, while * we're using the default UNIX crypt (8 chars significant), * would not understand what's going on when they're told that * "The password should contain at least 4 digits"... * Instead, we now tell them * "The first 8 characters of the password should contain at least * 4 digits." */ if (pwdef->maxlength < strlen(pw)) /* * TRANSLATION_NOTE * - Make sure the % and %% come over intact * - The last %%s will be replaced by strings like * "alphabetic character(s)" * "numeric or special character(s)" * "special character(s)" * "digit(s)" * "uppercase alpha character(s)" * "lowercase alpha character(s)" * So the final string written to the user might become * "passwd: The first 8 characters of the password must contain * at least 4 uppercase alpha characters(s)" */ (void) snprintf(errmsg, sizeof (errmsg), dgettext(TEXT_DOMAIN, "%s: The first %d characters of the password must " "contain at least %%d %%s."), progname, pwdef->maxlength); else /* * TRANSLATION_NOTE * - Make sure the % and %% come over intact * - The last %%s will be replaced by strings like * "alphabetic character(s)" * "numeric or special character(s)" * "special character(s)" * "digit(s)" * "uppercase alpha character(s)" * "lowercase alpha character(s)" * So the final string written to the user might become * "passwd: The password must contain at least 4 uppercase * alpha characters(s)" */ (void) snprintf(errmsg, sizeof (errmsg), dgettext(TEXT_DOMAIN, "%s: The password must contain at least %%d %%s."), progname); /* Check for whitespace first since it influences special counts */ if (whitespace_cnt > 0 && pwdef->whitespace == B_FALSE) { error(pamh, flags, dgettext(TEXT_DOMAIN, "%s: Whitespace characters are not allowed."), progname); ret = 1; goto out; } /* * Once we get here, whitespace_cnt is either 0, or whitespaces are * to be treated a special characters. */ if (alpha_cnt < pwdef->minalpha) { error(pamh, flags, errmsg, pwdef->minalpha, dgettext(TEXT_DOMAIN, "alphabetic character(s)")); ret = 1; goto out; } if (pwdef->minnonalpha > 0) { /* specials are defined by MINNONALPHA */ /* nonalpha = special+whitespace+digit */ if ((special_cnt + whitespace_cnt + digit_cnt) < pwdef->minnonalpha) { error(pamh, flags, errmsg, pwdef->minnonalpha, dgettext(TEXT_DOMAIN, "numeric or special character(s)")); ret = 1; goto out; } } else { /* specials are defined by MINSPECIAL and/or MINDIGIT */ if ((special_cnt + whitespace_cnt) < pwdef->minspecial) { error(pamh, flags, errmsg, pwdef->minspecial, dgettext(TEXT_DOMAIN, "special character(s)")); ret = 1; goto out; } if (digit_cnt < pwdef->mindigit) { error(pamh, flags, errmsg, pwdef->mindigit, dgettext(TEXT_DOMAIN, "digit(s)")); ret = 1; goto out; } } if (upper_cnt < pwdef->minupper) { error(pamh, flags, errmsg, pwdef->minupper, dgettext(TEXT_DOMAIN, "uppercase alpha character(s)")); ret = 1; goto out; } if (lower_cnt < pwdef->minlower) { error(pamh, flags, errmsg, pwdef->minlower, dgettext(TEXT_DOMAIN, "lowercase alpha character(s)")); ret = 1; goto out; } if (pwdef->maxrepeat > 0 && maxrepeat > pwdef->maxrepeat) { error(pamh, flags, dgettext(TEXT_DOMAIN, "%s: Too many consecutively repeating characters. " "Maximum allowed is %d."), progname, pwdef->maxrepeat); ret = 1; } out: return (ret); } /* * make sure that old and new password differ by at least 'mindiff' * positions. Return 0 if OK, 1 otherwise */ int check_diff(const char *pw, const char *opw, struct pwdefaults *pwdef, pam_handle_t *pamh, int flags) { size_t pwlen, opwlen, max; unsigned int diff; /* difference between old and new */ if (opw == NULL) opw = ""; max = pwdef->maxlength; pwlen = MIN(strlen(pw), max); opwlen = MIN(strlen(opw), max); if (pwlen > opwlen) diff = pwlen - opwlen; else diff = opwlen - pwlen; while (*opw != '\0' && *pw != '\0' && max-- != 0) { if (*opw != *pw) diff++; opw++; pw++; } if (diff < pwdef->mindiff) { const char *progname; (void) pam_get_item(pamh, PAM_SERVICE, (const void **)&progname); error(pamh, flags, dgettext(TEXT_DOMAIN, "%s: The first %d characters of the old and new passwords " "must differ by at least %d positions."), progname, pwdef->maxlength, pwdef->mindiff); return (1); } return (0); } /* * check to see if password is in one way or another based on a * dictionary word. Returns 0 if password is OK, 1 if it is based * on a dictionary word and hence should be rejected. */ int check_dictionary(const char *pw, struct pwdefaults *pwdef, pam_handle_t *pamh, int flags) { int crack_ret; int ret; const char *progname; (void) pam_get_item(pamh, PAM_SERVICE, (const void **)&progname); /* dictionary check isn't MT-safe */ (void) mutex_lock(&dictlock); if (pwdef->dicts && make_dict_database(pwdef->dicts, pwdef->db_location) != 0) { (void) mutex_unlock(&dictlock); syslog(LOG_ERR, "pam_authtok_check:pam_sm_chauthtok: " "Dictionary database not present."); error(pamh, flags, dgettext(TEXT_DOMAIN, "%s: password dictionary missing."), progname); return (PAM_SYSTEM_ERR); } crack_ret = DictCheck(pw, pwdef->db_location); (void) mutex_unlock(&dictlock); switch (crack_ret) { case DATABASE_OPEN_FAIL: syslog(LOG_ERR, "pam_authtok_check:pam_sm_chauthtok: " "dictionary database open failure: %s", strerror(errno)); error(pamh, flags, dgettext(TEXT_DOMAIN, "%s: failed to open dictionary database."), progname); ret = PAM_SYSTEM_ERR; break; case DICTIONARY_WORD: error(pamh, flags, dgettext(TEXT_DOMAIN, "%s: password is based on a dictionary word."), progname); ret = PAM_AUTHTOK_ERR; break; case REVERSE_DICTIONARY_WORD: error(pamh, flags, dgettext(TEXT_DOMAIN, "%s: password is based on a reversed dictionary word."), progname); ret = PAM_AUTHTOK_ERR; break; default: ret = PAM_SUCCESS; break; } return (ret); } int pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, const char **argv) { int debug = 0; int retcode = 0; int force_check = 0; int i; size_t pwlen; const char *usrname; const char *pwbuf, *opwbuf; pwu_repository_t *pwu_rep = PWU_DEFAULT_REP; const pam_repository_t *pwd_rep = NULL; struct pwdefaults pwdef; const char *progname; /* needs to be set before option processing */ pwdef.server_policy = B_FALSE; for (i = 0; i < argc; i++) { if (strcmp(argv[i], "debug") == 0) debug = 1; if (strcmp(argv[i], "force_check") == 0) force_check = 1; if (strcmp(argv[i], "server_policy") == 0) pwdef.server_policy = B_TRUE; } if (debug) syslog(LOG_AUTH | LOG_DEBUG, "pam_authtok_check: pam_sm_chauthok called(%x) " "force_check = %d", flags, force_check); if ((flags & PAM_PRELIM_CHECK) == 0) return (PAM_IGNORE); (void) pam_get_item(pamh, PAM_SERVICE, (const void **)&progname); (void) pam_get_item(pamh, PAM_USER, (const void **)&usrname); if (usrname == NULL || *usrname == '\0') { syslog(LOG_ERR, "pam_authtok_check: username name is empty"); return (PAM_USER_UNKNOWN); } (void) pam_get_item(pamh, PAM_AUTHTOK, (const void **)&pwbuf); (void) pam_get_item(pamh, PAM_OLDAUTHTOK, (const void **)&opwbuf); if (pwbuf == NULL) return (PAM_AUTHTOK_ERR); /* none of these checks holds if caller say so */ if ((flags & PAM_NO_AUTHTOK_CHECK) != 0 && force_check == 0) return (PAM_SUCCESS); /* read system-defaults */ retcode = get_passwd_defaults(pamh, usrname, &pwdef); if (retcode != PAM_SUCCESS) return (retcode); if (debug) { syslog(LOG_AUTH | LOG_DEBUG, "pam_authtok_check: MAXLENGTH= %d, server_policy = %s", pwdef.maxlength, pwdef.server_policy ? "true" : "false"); syslog(LOG_AUTH | LOG_DEBUG, "pam_authtok_check: PASSLENGTH= %d", pwdef.minlength); syslog(LOG_AUTH | LOG_DEBUG, "pam_authtok_check: NAMECHECK=%s", pwdef.do_namecheck == B_TRUE ? "Yes" : "No"); syslog(LOG_AUTH | LOG_DEBUG, "pam_authtok_check: do_dictcheck = %s\n", pwdef.do_dictcheck ? "true" : "false"); if (pwdef.do_dictcheck) { syslog(LOG_AUTH | LOG_DEBUG, "pam_authtok_check: DICTIONLIST=%s", (pwdef.dicts != NULL) ? pwdef.dicts : ""); syslog(LOG_AUTH | LOG_DEBUG, "pam_authtok_check: DICTIONDBDIR=%s", pwdef.db_location); } syslog(LOG_AUTH | LOG_DEBUG, "pam_authtok_check: MINDIFF=%d", pwdef.mindiff); syslog(LOG_AUTH | LOG_DEBUG, "pam_authtok_check: MINALPHA=%d, MINNONALPHA=%d", pwdef.minalpha, pwdef.minnonalpha); syslog(LOG_AUTH | LOG_DEBUG, "pam_authtok_check: MINSPECIAL=%d, MINDIGIT=%d", pwdef.minspecial, pwdef.mindigit); syslog(LOG_AUTH | LOG_DEBUG, "pam_authtok_check: WHITESPACE=%s", pwdef.whitespace ? "YES" : "NO"); syslog(LOG_AUTH | LOG_DEBUG, "pam_authtok_check: MINUPPER=%d, MINLOWER=%d", pwdef.minupper, pwdef.minlower); syslog(LOG_AUTH | LOG_DEBUG, "pam_authtok_check: MAXREPEATS=%d", pwdef.maxrepeat); } /* * If server policy is still true (might be changed from the * value specified in /etc/pam.conf by get_passwd_defaults()), * we return ignore and let the server do all the checks. */ if (pwdef.server_policy == B_TRUE) { free_passwd_defaults(&pwdef); return (PAM_IGNORE); } /* * XXX: JV: we can't really make any assumption on the length of * the password that will be used by the crypto algorithm. * for UNIX-style encryption, minalpha=5,minnonalpha=5 might * be impossible, but not for MD5 style hashes... what to do? * * since we don't know what alg. will be used, we operate on * the password as entered, so we don't sanity check anything * for now. */ /* * Make sure new password is long enough */ pwlen = strlen(pwbuf); if (pwlen < pwdef.minlength) { error(pamh, flags, dgettext(TEXT_DOMAIN, "%s: Password too short - must be at least %d " "characters."), progname, pwdef.minlength); free_passwd_defaults(&pwdef); return (PAM_AUTHTOK_ERR); } /* Make sure the password doesn't equal--a shift of--the username */ if (pwdef.do_namecheck) { switch (check_circular(usrname, pwbuf)) { case 1: error(pamh, flags, dgettext(TEXT_DOMAIN, "%s: Password cannot be circular shift of " "logonid."), progname); free_passwd_defaults(&pwdef); return (PAM_AUTHTOK_ERR); case -1: free_passwd_defaults(&pwdef); return (PAM_BUF_ERR); default: break; } } /* Check if new password is in history list. */ (void) pam_get_item(pamh, PAM_REPOSITORY, (const void **)&pwd_rep); if (pwd_rep != NULL) { if ((pwu_rep = calloc(1, sizeof (*pwu_rep))) == NULL) return (PAM_BUF_ERR); pwu_rep->type = pwd_rep->type; pwu_rep->scope = pwd_rep->scope; pwu_rep->scope_len = pwd_rep->scope_len; } if (__check_history(usrname, pwbuf, pwu_rep) == PWU_SUCCESS) { /* password found in history */ error(pamh, flags, dgettext(TEXT_DOMAIN, "%s: Password in history list."), progname); if (pwu_rep != PWU_DEFAULT_REP) free(pwu_rep); free_passwd_defaults(&pwdef); return (PAM_AUTHTOK_ERR); } if (pwu_rep != PWU_DEFAULT_REP) free(pwu_rep); /* check MINALPHA, MINLOWER, etc. */ if (check_composition(pwbuf, &pwdef, pamh, flags) != 0) { free_passwd_defaults(&pwdef); return (PAM_AUTHTOK_ERR); } /* make sure the old and new password are not too much alike */ if (check_diff(pwbuf, opwbuf, &pwdef, pamh, flags) != 0) { free_passwd_defaults(&pwdef); return (PAM_AUTHTOK_ERR); } /* dictionary check */ if (pwdef.do_dictcheck) { retcode = check_dictionary(pwbuf, &pwdef, pamh, flags); if (retcode != PAM_SUCCESS) { free_passwd_defaults(&pwdef); return (retcode); } } free_passwd_defaults(&pwdef); /* password has passed all tests: it's strong enough */ return (PAM_SUCCESS); }