authtok_check.c revision 36e852a172cba914383d7341c988128b2c667fbd
/*
* 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
* 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.
*/
#include <sys/sysmacros.h>
#include <stdio.h>
#include <stdlib.h>
#include <deflt.h>
#include <security/pam_appl.h>
#include <security/pam_modules.h>
#include <security/pam_impl.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <syslog.h>
#include <libintl.h>
#include <errno.h>
#include <pwd.h>
#include "packer.h"
#include <passwdutil.h>
#define MINLENGTH 6
#define MINDIFF 3
#define MINALPHA 2
#define MINNONALPHA 1
/*
* We implement:
* PASSLENGTH (int) minimum password length
* 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
*
* 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 {
char *dicts; /* list of dictionaries configured */
};
/*PRINTFLIKE3*/
void
{
if ((flags & PAM_SILENT) == 0)
}
int
{
char *q;
int r = 0;
if (!isdigit(*q)) {
"non-integer value for %s: %s. "
} else {
r = 1;
}
}
return (r);
}
/*
* fill in static defaults, and augment with settings from PWADMIN
* get system defaults with regard to maximum password length
*/
int
{
char *q;
struct pam_repository *pam_rep;
int result;
char *progname;
void *defp;
/* Module defaults */
p->do_namecheck = B_TRUE;
p->do_dictcheck = B_FALSE;
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;
return (PAM_SUCCESS);
strcasecmp(q, "NO") == 0)
p->do_namecheck = B_FALSE;
return (PAM_BUF_ERR);
}
p->do_dictcheck = B_TRUE;
} else {
}
sizeof (p->db_location)) {
"DICTIONDBDIR too large.");
return (PAM_SYSTEM_ERR);
}
p->do_dictcheck = B_TRUE;
} else {
sizeof (p->db_location));
}
if (minnonalpha_defined) {
"definition for MINNONALPHA and for MINSPECIAL. "
"These options are mutually exclusive.", PWADMIN);
return (PAM_SYSTEM_ERR);
}
p->minnonalpha = 0;
}
if (minnonalpha_defined) {
"definition for MINNONALPHA and for MINDIGIT. "
"These options are mutually exclusive.", PWADMIN);
return (PAM_SYSTEM_ERR);
}
p->minnonalpha = 0;
}
p->whitespace =
/*
* 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).
*
* the module-flag "server_policy" means that we don't perform
* any checks on the user, but let the repository decide instead.
*/
return (PAM_BUF_ERR);
} else {
}
if (pwu_rep != PWU_DEFAULT_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 {
p->server_policy == B_FALSE) {
char *salt;
/*
* We currently need to supply this dummy to
* crypt_gensalt(). This will change RSN.
*/
else
p->maxlength = _PASS_MAX_XPG;
} else {
/* not files or nis AND server_policy is set */
}
}
/* sanity check of the configured parameters */
p->minalpha) {
"password length (PASSLENGTH=%d) is less then minimum "
"characters in the various classes (%d)", progname,
p->minlength,
p->minalpha;
/* this won't lead to failure */
}
"minimum password length (PASSLENGTH=%d) is larger than "
"the number of significant characters the current "
"encryption algorithm uses (%d). See policy.conf(4) 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)
}
/*
* 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;
{
unsigned int i, j, k, l, m;
int ret = 0;
i = strlen(s);
l = strlen(t);
if (i != l)
return (0);
len = i + 1;
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];
ret = 1;
goto out;
}
}
p = p + i;
r = &ubuff[0];
j = i;
while (j--)
*--p = *r++; /* reverse test-string for m==0 pass */
}
out:
return (ret);
}
/*
* count the different character classes present in the password.
*/
int
int flags)
{
uint_t special_cnt = 0;
uint_t whitespace_cnt = 0;
int ret = 0;
char *progname;
char errmsg[256];
char lastc = '\0';
char *w;
/* go over the password gathering statistics */
if (isalpha(*w)) {
alpha_cnt++;
if (isupper(*w)) {
upper_cnt++;
} else {
lower_cnt++;
}
} else if (isspace(*w))
else if (isdigit(*w))
digit_cnt++;
else
special_cnt++;
if (*w == lastc) {
} 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 he's told that
* "The password should contain at least 4 digits"...
* Instead, we now well him
* "The first 8 characters of the password should contain at least
* 4 digits."
*/
/*
* 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)"
*/
"%s: The first %d characters of the password must "
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)"
*/
"%s: The password must contain at least %%d %%s."),
progname);
/* Check for whitespace first since it influences special counts */
"%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.
*/
ret = 1;
goto out;
}
if (pwdef->minnonalpha > 0) {
/* specials are defined by MINNONALPHA */
/* nonalpha = special+whitespace+digit */
pwdef->minnonalpha) {
"numeric or special character(s)"));
ret = 1;
goto out;
}
} else {
ret = 1;
goto out;
}
ret = 1;
goto out;
}
}
ret = 1;
goto out;
}
ret = 1;
goto out;
}
"%s: Too many consecutively repeating characters. "
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
int flags)
{
unsigned int diff; /* difference between old and new */
opw = "";
else
diff++;
opw++;
pw++;
}
char *progname;
"%s: The first %d characters of the old and new passwords "
"must differ by at least %d positions."), progname,
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
int flags)
{
int crack_ret;
int ret;
char *progname;
/* dictionary check isn't MT-safe */
(void) mutex_lock(&dictlock);
(void) mutex_unlock(&dictlock);
"Dictionary database not present.");
"%s: password dictionary missing."), progname);
return (PAM_SYSTEM_ERR);
}
(void) mutex_unlock(&dictlock);
switch (crack_ret) {
case DATABASE_OPEN_FAIL:
"%s: failed to open dictionary database."), progname);
break;
case DICTIONARY_WORD:
"%s: password is based on a dictionary word."), progname);
break;
case REVERSE_DICTIONARY_WORD:
"%s: password is based on a reversed dictionary word."),
progname);
break;
default:
ret = PAM_SUCCESS;
break;
}
return (ret);
}
int
{
int debug = 0;
int retcode = 0;
int force_check = 0;
int i;
char *usrname;
struct pwdefaults pwdef;
char *progname;
/* needs to be set before option processing */
for (i = 0; i < argc; i++) {
debug = 1;
force_check = 1;
}
if (debug)
"pam_authtok_check: pam_sm_chauthok called(%x) "
if ((flags & PAM_PRELIM_CHECK) == 0)
return (PAM_IGNORE);
return (PAM_USER_UNKNOWN);
}
return (PAM_AUTHTOK_ERR);
/* none of these checks holds if caller say so */
return (PAM_SUCCESS);
/* read system-defaults */
if (retcode != PAM_SUCCESS)
return (retcode);
if (debug) {
"pam_authtok_check: MAXLENGTH= %d, server_policy = %s",
"pam_authtok_check: do_dictcheck = %s\n",
if (pwdef.do_dictcheck) {
"pam_authtok_check: DICTIONLIST=%s",
"pam_authtok_check: DICTIONDBDIR=%s",
}
"pam_authtok_check: MINALPHA=%d, MINNONALPHA=%d",
"pam_authtok_check: MINSPECIAL=%d, MINDIGIT=%d",
"pam_authtok_check: MINUPPER=%d, MINLOWER=%d",
}
/*
* If server policy is still true (might be changed from the
* we return ignore and let the server do all the checks.
*/
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
*/
"%s: Password too short - must be at least %d "
return (PAM_AUTHTOK_ERR);
}
/* Make sure the password doesn't equal--a shift of--the username */
if (pwdef.do_namecheck) {
case 1:
"%s: Password cannot be circular shift of "
"logonid."), progname);
return (PAM_AUTHTOK_ERR);
case -1:
return (PAM_BUF_ERR);
default:
break;
}
}
/* Check if new password is in history list. */
return (PAM_BUF_ERR);
}
/* password found in history */
"%s: Password in history list."), progname);
if (pwu_rep != PWU_DEFAULT_REP)
return (PAM_AUTHTOK_ERR);
}
if (pwu_rep != PWU_DEFAULT_REP)
/* check MINALPHA, MINLOWER, etc. */
return (PAM_AUTHTOK_ERR);
}
/* make sure the old and new password are not too much alike */
return (PAM_AUTHTOK_ERR);
}
/* dictionary check */
if (pwdef.do_dictcheck) {
if (retcode != PAM_SUCCESS) {
return (retcode);
}
}
/* password has passed all tests: it's strong enough */
return (PAM_SUCCESS);
}