#include <sys/types.h>
#include <sys/varargs.h>
#include <sys/param.h>
#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 PWADMIN "/etc/default/passwd"
#define MINLENGTH 6
#define MINDIFF 3
#define MINALPHA 2
#define MINNONALPHA 1
mutex_t dictlock = DEFAULTMUTEX;
struct pwdefaults {
boolean_t server_policy;
uint_t minlength;
uint_t maxlength;
boolean_t do_namecheck;
char db_location[MAXPATHLEN];
boolean_t do_dictcheck;
char *dicts;
uint_t mindiff;
uint_t minalpha;
uint_t minupper;
uint_t minlower;
uint_t minnonalpha;
uint_t maxrepeat;
uint_t minspecial;
uint_t mindigit;
boolean_t whitespace;
};
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);
}
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);
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;
p->minlower = 0;
p->maxrepeat = 0;
p->minspecial = 0;
p->mindigit = 0;
p->whitespace = B_TRUE;
(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) {
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;
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;
} else {
p->maxlength = _PASS_MAX;
}
free(attr[0].data.val_s);
free(attr[1].data.val_s);
}
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);
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);
}
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);
}
return (PAM_SUCCESS);
}
void
free_passwd_defaults(struct pwdefaults *p)
{
if (p && p->dicts)
free(p->dicts);
}
static int
check_circular(const char *s, const char *t)
{
const char *p;
char c, *u, *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.");
free(buff);
free(ubuff);
free(pubuff);
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';
u = &ubuff[0];
while (m--) {
for (k = 0; k < i; k++) {
c = *u++;
o = u;
l = i;
r = &buff[0];
while (--l)
*r++ = *o++;
*r++ = c;
*r = '\0';
u = &buff[0];
if (strcmp(u, pubuff) == 0) {
ret = 1;
goto out;
}
}
u = u + i;
r = &ubuff[0];
j = i;
while (j--)
*--u = *r++;
}
out:
(void) memset(buff, 0, len);
(void) memset(ubuff, 0, len);
(void) memset(pubuff, 0, len);
free(buff);
free(ubuff);
free(pubuff);
return (ret);
}
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);
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 (pwdef->maxlength < strlen(pw))
(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
(void) snprintf(errmsg, sizeof (errmsg), dgettext(TEXT_DOMAIN,
"%s: The password must contain at least %%d %%s."),
progname);
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;
}
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) {
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 {
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);
}
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;
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);
}
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);
(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;
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);
if ((flags & PAM_NO_AUTHTOK_CHECK) != 0 && force_check == 0)
return (PAM_SUCCESS);
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 : "<not set>");
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 (pwdef.server_policy == B_TRUE) {
free_passwd_defaults(&pwdef);
return (PAM_IGNORE);
}
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);
}
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;
}
}
(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) {
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);
if (check_composition(pwbuf, &pwdef, pamh, flags) != 0) {
free_passwd_defaults(&pwdef);
return (PAM_AUTHTOK_ERR);
}
if (check_diff(pwbuf, opwbuf, &pwdef, pamh, flags) != 0) {
free_passwd_defaults(&pwdef);
return (PAM_AUTHTOK_ERR);
}
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);
return (PAM_SUCCESS);
}