Vendor import of Solar Designer's pam_passwdqc module.

This commit is contained in:
Dag-Erling Smørgrav 2002-04-04 15:50:47 +00:00
commit 0b0ecb56f2
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/vendor/pam_modules/dist/; revision=93787
svn path=/vendor/pam_modules/0.4/; revision=93789; tag=vendor/pam_modules/0.4
12 changed files with 5451 additions and 0 deletions

View File

@ -0,0 +1,2 @@
The functions defined in passwdqc.h may be used without PAM at all.
They will eventually be moved into a libpasswdqc.

View File

@ -0,0 +1,9 @@
You're allowed to do whatever you like with this software (including
re-distribution in source and/or binary form, with or without
modification), provided that credit is given where it is due and any
modified versions are marked as such. There's absolutely no warranty.
Note that you don't have to re-distribute this software under these
same relaxed terms. In particular, you're free to place modified
versions under (L)GPL, thus disallowing further re-distribution in
binary-only form.

View File

@ -0,0 +1,48 @@
#
# Copyright (c) 2000,2001 by Solar Designer. See LICENSE.
#
CC = gcc
LD = ld
RM = rm -f
MKDIR = mkdir -p
INSTALL = install
CFLAGS = -c -Wall -fPIC -DHAVE_SHADOW -O2
LDFLAGS = -s -lpam -lcrypt --shared
LDFLAGS_SUN = -s -lpam -lcrypt -G
TITLE = pam_passwdqc
LIBSHARED = $(TITLE).so
SHLIBMODE = 755
SECUREDIR = /lib/security
FAKEROOT =
PROJ = $(LIBSHARED)
OBJS = pam_passwdqc.o passwdqc_check.o passwdqc_random.o wordset_4k.o
all:
if [ "`uname -s`" = "SunOS" ]; then \
make LDFLAGS="$(LDFLAGS_SUN)" $(PROJ); \
else \
make $(PROJ); \
fi
$(LIBSHARED): $(OBJS)
$(LD) $(LDFLAGS) $(OBJS) -o $(LIBSHARED)
.c.o:
$(CC) $(CFLAGS) $*.c
pam_passwdqc.o: passwdqc.h pam_macros.h
passwdqc_check.o: passwdqc.h
passwdqc_random.o: passwdqc.h
install:
$(MKDIR) $(FAKEROOT)$(SECUREDIR)
$(INSTALL) -m $(SHLIBMODE) $(LIBSHARED) $(FAKEROOT)$(SECUREDIR)
remove:
$(RM) $(FAKEROOT)$(SECUREDIR)/$(TITLE).so
clean:
$(RM) $(PROJ) *.o

View File

@ -0,0 +1,27 @@
Please see the README for instructions common to all platforms and
descriptions of the options mentioned here.
Linux.
Most modern Linux distributions use Linux-PAM with a password changing
module which understands "use_authtok". Thus, you may choose which
module prompts for the old password, things should work either way.
FreeBSD.
Currently, FreeBSD doesn't use PAM for password changing. This means
you won't be able to use pam_passwdqc with FreeBSD.
Solaris.
pam_passwdqc has to ask for the old password during the update phase.
Use "ask_oldauthtok=update check_oldauthtok" with pam_passwdqc and
"use_first_pass" with pam_unix.
You will likely also need to set "max=8" in order to actually enforce
not-so-weak passwords with the obsolete "traditional" crypt(3) hashes
that most Solaris systems use. Of course this way you only get about
one third of the functionality of pam_passwdqc.

View File

@ -0,0 +1,143 @@
pam_passwdqc is a simple password strength checking module for
PAM-aware password changing programs, such as passwd(1). In addition
to checking regular passwords, it offers support for passphrases and
can provide randomly generated passwords. All features are optional
and can be (re-)configured without rebuilding.
This module should be stacked before your usual password changing
module (such as pam_unix or pam_pwdb) in the password management group
(the "password" lines in /etc/pam.d/passwd or /etc/pam.conf). The
password changing module should then be told to use the provided new
authentication token (new password) rather than request it from the
user. There's usually the "use_authtok" option to do that. If your
password changing module lacks the "use_authtok" option or its prompts
are inconsistent with pam_passwdqc's, you may tell pam_passwdqc to ask
for the old password as well, with "ask_oldauthtok". In that case the
option to use with the password changing module is "use_first_pass".
There's a number of supported options which can be used to modify the
behavior of pam_passwdqc (defaults are given in square brackets):
min=N0,N1,N2,N3,N4 [min=disabled,24,12,8,7]
The minimum allowed password lengths, separately for different kinds
of passwords/passphrases. The special word "disabled" can be used to
disallow passwords of a given kind regardless of their length. Each
subsequent number is required to be no larger than the preceding one.
N0 is used for passwords consisting of characters from one character
class only. (The character classes are: digits, lower-case letters,
upper-case letters, and other characters. There's also the special
class for non-ASCII characters which couldn't be classified, but are
assumed to be non-digits.)
N1 is used for passwords consisting of characters from two character
classes, which don't meet the requirements for a passphrase.
N2 is used for passphrases. A passphrase must consist of sufficient
words (see the "passphrase" option, below).
N3 and N4 are used for passwords consisting of characters from three
and four character classes, respectively.
When calculating the number of character classes, upper-case letters
used as the first character and digits used as the last character of a
password are not counted.
In addition to being sufficiently long, passwords are required to
contain enough different characters for the character classes and
the minimum length they've been checked against.
max=N [max=40]
The maximum allowed password length. This can be used to prevent
users from setting passwords which may be too long for some system
services.
The value 8 is treated specially. Passwords longer than 8 characters
will not be rejected, but will be truncated to 8 characters for the
strength checks and the user will be warned. This is to be used with
the traditional crypt(3) password hashes.
It is important that you do set max=8 if you're using the traditional
hashes, or some weak passwords will pass the checks.
passphrase=N [passphrase=3]
The number of words required for a passphrase, or 0 to disable the
support for passphrases.
match=N [match=4]
The length of common substring required to conclude that a password is
at least partially based on information found in a character string,
or 0 to disable the substring search. Note that the password will not
be rejected once a weak substring is found. Instead, the password
will be subjected to the usual strength requirements with the weak
substring removed.
The substring search is case-insensitive and is able to detect and
remove a common substring spelled backwards.
similar=permit|deny [similar=deny]
Whether a new password is allowed to be similar to the old one. The
passwords are considered to be similar when there's a sufficiently
long common substring and the new password with the substring removed
would be weak.
random=N[,only] [random=42]
The size of randomly-generated passwords in bits, or 0 to disable this
feature. Passwords that contain the offered randomly-generated string
will be allowed regardless of other possible restrictions.
The "only" modifier can be used to disallow user-chosen passwords.
enforce=none|users|everyone [enforce=everyone]
The module can be configured to warn of weak passwords only, but not
actually enforce strong passwords. The "users" setting will enforce
strong passwords for non-root users only.
non-unix []
By default, the module uses getpwnam(3) to obtain the user's personal
login information and use that during the password strength checks.
This behavior can be disabled with "non-unix".
retry=N [retry=3]
The number of times the module will ask for a new password if the user
fails to provide a sufficiently strong password and enter it twice the
first time.
ask_oldauthtok[=update] []
Ask for the old password as well. Normally, pam_passwdqc leaves this
task for the password changing module. A simple "ask_oldauthtok" will
cause pam_passwdqc to ask for the old password during the preliminary
check phase. With "ask_oldauthtok=update", pam_passwdqc will do that
during the update phase.
check_oldauthtok []
This tells pam_passwdqc to validate the old password before giving a
new password prompt. Normally, this task is left for the password
changing module.
The primary use for this option is with "ask_oldauthtok=update" in
which case no other modules have a chance to run and validate the
password between the prompts. Of course, this will only work with
Unix passwords.
use_first_pass []
use_authtok []
Use the new password obtained by modules stacked before pam_passwdqc.
This disables user interaction within pam_passwdqc. With this module,
the only difference between "use_first_pass" and "use_authtok" is that
the former is incompatible with "ask_oldauthtok".
--
Solar Designer <solar@openwall.com>

View File

@ -0,0 +1,28 @@
/*
* These macros are partially based on Linux-PAM's <security/_pam_macros.h>,
* which were organized by Cristian Gafton and I believe are in the public
* domain.
*/
#if !defined(_PAM_MACROS_H) && !defined(_pam_overwrite)
#define _PAM_MACROS_H
#include <string.h>
#include <stdlib.h>
#define _pam_overwrite(x) \
memset((x), 0, strlen((x)))
#define _pam_drop_reply(/* struct pam_response * */ reply, /* int */ replies) \
do { \
int i; \
\
for (i = 0; i < (replies); i++) \
if ((reply)[i].resp) { \
_pam_overwrite((reply)[i].resp); \
free((reply)[i].resp); \
} \
if ((reply)) free((reply)); \
} while (0)
#endif

View File

@ -0,0 +1,553 @@
/*
* Copyright (c) 2000,2001 by Solar Designer. See LICENSE.
*/
#define _XOPEN_SOURCE 500
#define _XOPEN_SOURCE_EXTENDED
#define _XOPEN_VERSION 500
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <limits.h>
#include <unistd.h>
#include <pwd.h>
#ifdef HAVE_SHADOW
#include <shadow.h>
#endif
#define PAM_SM_PASSWORD
#ifndef LINUX_PAM
#include <security/pam_appl.h>
#endif
#include <security/pam_modules.h>
#include "pam_macros.h"
#if !defined(PAM_EXTERN) && !defined(PAM_STATIC)
#define PAM_EXTERN extern
#endif
#if !defined(PAM_AUTHTOK_RECOVER_ERR) && defined(PAM_AUTHTOK_RECOVERY_ERR)
#define PAM_AUTHTOK_RECOVER_ERR PAM_AUTHTOK_RECOVERY_ERR
#endif
#if defined(__sun__) && !defined(LINUX_PAM)
#define linux_const /* Sun's PAM doesn't use const here */
#else
#define linux_const const
#endif
typedef linux_const void *pam_item_t;
#include "passwdqc.h"
#define F_ENFORCE_MASK 0x00000003
#define F_ENFORCE_USERS 0x00000001
#define F_ENFORCE_ROOT 0x00000002
#define F_ENFORCE_EVERYONE F_ENFORCE_MASK
#define F_NON_UNIX 0x00000004
#define F_ASK_OLDAUTHTOK_MASK 0x00000030
#define F_ASK_OLDAUTHTOK_PRELIM 0x00000010
#define F_ASK_OLDAUTHTOK_UPDATE 0x00000020
#define F_CHECK_OLDAUTHTOK 0x00000040
#define F_USE_FIRST_PASS 0x00000100
#define F_USE_AUTHTOK 0x00000200
typedef struct {
passwdqc_params_t qc;
int flags;
int retry;
} params_t;
static params_t defaults = {
{
{INT_MAX, 24, 12, 8, 7}, /* min */
40, /* max */
3, /* passphrase_words */
4, /* match_length */
1, /* similar_deny */
42 /* random_bits */
},
F_ENFORCE_EVERYONE, /* flags */
3 /* retry */
};
#define PROMPT_OLDPASS \
"Enter current password: "
#define PROMPT_NEWPASS1 \
"Enter new password: "
#define PROMPT_NEWPASS2 \
"Re-type new password: "
#define MESSAGE_MISCONFIGURED \
"System configuration error. Please contact your administrator."
#define MESSAGE_INVALID_OPTION \
"pam_passwdqc: Invalid option: \"%s\"."
#define MESSAGE_INTRO_PASSWORD \
"\nYou can now choose the new password.\n"
#define MESSAGE_INTRO_BOTH \
"\nYou can now choose the new password or passphrase.\n"
#define MESSAGE_EXPLAIN_PASSWORD_1 \
"A valid password should be a mix of upper and lower case letters,\n" \
"digits and other characters. You can use a%s %d character long\n" \
"password with characters from at least 3 of these 4 classes.\n" \
"Characters that form a common pattern are discarded by the check.\n"
#define MESSAGE_EXPLAIN_PASSWORD_2 \
"A valid password should be a mix of upper and lower case letters,\n" \
"digits and other characters. You can use a%s %d character long\n" \
"password with characters from at least 3 of these 4 classes, or\n" \
"a%s %d character long password containing characters from all the\n" \
"classes. Characters that form a common pattern are discarded by\n" \
"the check.\n"
#define MESSAGE_EXPLAIN_PASSPHRASE \
"A passphrase should be of at least %d words, %d to %d characters\n" \
"long and contain enough different characters.\n"
#define MESSAGE_RANDOM \
"Alternatively, if noone else can see your terminal now, you can\n" \
"pick this as your password: \"%s\".\n"
#define MESSAGE_RANDOMONLY \
"This system is configured to permit randomly generated passwords\n" \
"only. If noone else can see your terminal now, you can pick this\n" \
"as your password: \"%s\". Otherwise, come back later.\n"
#define MESSAGE_RANDOMFAILED \
"This system is configured to use randomly generated passwords\n" \
"only, but the attempt to generate a password has failed. This\n" \
"could happen for a number of reasons: you could have requested\n" \
"an impossible password length, or the access to kernel random\n" \
"number pool could have failed."
#define MESSAGE_TOOLONG \
"This password may be too long for some services. Choose another."
#define MESSAGE_TRUNCATED \
"Warning: your longer password will be truncated to 8 characters."
#define MESSAGE_WEAKPASS \
"Weak password: %s."
#define MESSAGE_NOTRANDOM \
"Sorry, you've mistyped the password that was generated for you."
#define MESSAGE_MISTYPED \
"Sorry, passwords do not match."
#define MESSAGE_RETRY \
"Try again."
static int converse(pam_handle_t *pamh, int style, char *text,
struct pam_response **resp)
{
struct pam_conv *conv;
struct pam_message msg, *pmsg;
int status;
status = pam_get_item(pamh, PAM_CONV, (pam_item_t *)&conv);
if (status != PAM_SUCCESS)
return status;
pmsg = &msg;
msg.msg_style = style;
msg.msg = text;
*resp = NULL;
return conv->conv(1, (linux_const struct pam_message **)&pmsg, resp,
conv->appdata_ptr);
}
#ifdef __GNUC__
__attribute__ ((format (printf, 3, 4)))
#endif
static int say(pam_handle_t *pamh, int style, const char *format, ...)
{
va_list args;
char buffer[0x800];
int needed;
struct pam_response *resp;
int status;
va_start(args, format);
needed = vsnprintf(buffer, sizeof(buffer), format, args);
va_end(args);
if (needed > 0 && needed < sizeof(buffer)) {
status = converse(pamh, style, buffer, &resp);
_pam_overwrite(buffer);
} else {
status = PAM_ABORT;
memset(buffer, 0, sizeof(buffer));
}
return status;
}
static int check_max(params_t *params, pam_handle_t *pamh, char *newpass)
{
if (strlen(newpass) > params->qc.max) {
if (params->qc.max != 8) {
say(pamh, PAM_ERROR_MSG, MESSAGE_TOOLONG);
return -1;
}
say(pamh, PAM_TEXT_INFO, MESSAGE_TRUNCATED);
}
return 0;
}
static int parse(params_t *params, pam_handle_t *pamh,
int argc, const char **argv)
{
const char *p;
int i;
unsigned long v;
while (argc) {
if (!strncmp(*argv, "min=", 4)) {
p = *argv + 4;
for (i = 0; i < 5; i++) {
if (!strncmp(p, "disabled", 8)) {
v = INT_MAX;
p += 8;
} else
v = strtoul(p, (char **)&p, 10);
if (i < 4 && *p++ != ',') break;
if (v > INT_MAX) break;
if (i && v > params->qc.min[i - 1]) break;
params->qc.min[i] = v;
}
if (*p) break;
} else
if (!strncmp(*argv, "max=", 4)) {
v = strtoul(*argv + 4, (char **)&p, 10);
if (*p || v < 8 || v > INT_MAX) break;
params->qc.max = v;
} else
if (!strncmp(*argv, "passphrase=", 11)) {
v = strtoul(*argv + 11, (char **)&p, 10);
if (*p || v > INT_MAX) break;
params->qc.passphrase_words = v;
} else
if (!strncmp(*argv, "match=", 6)) {
v = strtoul(*argv + 6, (char **)&p, 10);
if (*p || v > INT_MAX) break;
params->qc.match_length = v;
} else
if (!strncmp(*argv, "similar=", 8)) {
if (!strcmp(*argv + 8, "permit"))
params->qc.similar_deny = 0;
else
if (!strcmp(*argv + 8, "deny"))
params->qc.similar_deny = 1;
else
break;
} else
if (!strncmp(*argv, "random=", 7)) {
v = strtoul(*argv + 7, (char **)&p, 10);
if (!strcmp(p, ",only")) {
p += 5;
params->qc.min[4] = INT_MAX;
}
if (*p || v > INT_MAX) break;
params->qc.random_bits = v;
} else
if (!strncmp(*argv, "enforce=", 8)) {
params->flags &= ~F_ENFORCE_MASK;
if (!strcmp(*argv + 8, "users"))
params->flags |= F_ENFORCE_USERS;
else
if (!strcmp(*argv + 8, "everyone"))
params->flags |= F_ENFORCE_EVERYONE;
else
if (strcmp(*argv + 8, "none"))
break;
} else
if (!strcmp(*argv, "non-unix")) {
if (params->flags & F_CHECK_OLDAUTHTOK) break;
params->flags |= F_NON_UNIX;
} else
if (!strncmp(*argv, "retry=", 6)) {
v = strtoul(*argv + 6, (char **)&p, 10);
if (*p || v > INT_MAX) break;
params->retry = v;
} else
if (!strncmp(*argv, "ask_oldauthtok", 14)) {
params->flags &= ~F_ASK_OLDAUTHTOK_MASK;
if (params->flags & F_USE_FIRST_PASS) break;
if (!strcmp(*argv + 14, "=update"))
params->flags |= F_ASK_OLDAUTHTOK_UPDATE;
else
if (!(*argv)[14])
params->flags |= F_ASK_OLDAUTHTOK_PRELIM;
else
break;
} else
if (!strcmp(*argv, "check_oldauthtok")) {
if (params->flags & F_NON_UNIX) break;
params->flags |= F_CHECK_OLDAUTHTOK;
} else
if (!strcmp(*argv, "use_first_pass")) {
if (params->flags & F_ASK_OLDAUTHTOK_MASK) break;
params->flags |= F_USE_FIRST_PASS | F_USE_AUTHTOK;
} else
if (!strcmp(*argv, "use_authtok")) {
params->flags |= F_USE_AUTHTOK;
} else
break;
argc--; argv++;
}
if (argc) {
say(pamh, PAM_ERROR_MSG, getuid() != 0 ?
MESSAGE_MISCONFIGURED : MESSAGE_INVALID_OPTION, *argv);
return PAM_ABORT;
}
return PAM_SUCCESS;
}
PAM_EXTERN int pam_sm_chauthtok(pam_handle_t *pamh, int flags,
int argc, const char **argv)
{
params_t params;
struct pam_response *resp;
struct passwd *pw, fake_pw;
#ifdef HAVE_SHADOW
struct spwd *spw;
#endif
char *user, *oldpass, *newpass, *randompass;
char *reason;
int ask_oldauthtok;
int randomonly, enforce, retries_left, retry_wanted;
int status;
params = defaults;
status = parse(&params, pamh, argc, argv);
if (status != PAM_SUCCESS)
return status;
ask_oldauthtok = 0;
if (flags & PAM_PRELIM_CHECK) {
if (params.flags & F_ASK_OLDAUTHTOK_PRELIM)
ask_oldauthtok = 1;
} else
if (flags & PAM_UPDATE_AUTHTOK) {
if (params.flags & F_ASK_OLDAUTHTOK_UPDATE)
ask_oldauthtok = 1;
} else
return PAM_SERVICE_ERR;
if (ask_oldauthtok && getuid() != 0) {
status = converse(pamh, PAM_PROMPT_ECHO_OFF,
PROMPT_OLDPASS, &resp);
if (status == PAM_SUCCESS) {
if (resp && resp->resp) {
status = pam_set_item(pamh,
PAM_OLDAUTHTOK, resp->resp);
_pam_drop_reply(resp, 1);
} else
status = PAM_AUTHTOK_RECOVER_ERR;
}
if (status != PAM_SUCCESS)
return status;
}
if (flags & PAM_PRELIM_CHECK)
return status;
status = pam_get_item(pamh, PAM_USER, (pam_item_t *)&user);
if (status != PAM_SUCCESS)
return status;
status = pam_get_item(pamh, PAM_OLDAUTHTOK, (pam_item_t *)&oldpass);
if (status != PAM_SUCCESS)
return status;
if (params.flags & F_NON_UNIX) {
pw = &fake_pw;
pw->pw_name = user;
pw->pw_gecos = "";
} else {
pw = getpwnam(user);
endpwent();
if (!pw)
return PAM_USER_UNKNOWN;
if ((params.flags & F_CHECK_OLDAUTHTOK) && getuid() != 0) {
if (!oldpass)
status = PAM_AUTH_ERR;
else
#ifdef HAVE_SHADOW
if (!strcmp(pw->pw_passwd, "x")) {
spw = getspnam(user);
endspent();
if (spw) {
if (strcmp(crypt(oldpass, spw->sp_pwdp),
spw->sp_pwdp))
status = PAM_AUTH_ERR;
memset(spw->sp_pwdp, 0,
strlen(spw->sp_pwdp));
} else
status = PAM_AUTH_ERR;
} else
#endif
if (strcmp(crypt(oldpass, pw->pw_passwd),
pw->pw_passwd))
status = PAM_AUTH_ERR;
}
memset(pw->pw_passwd, 0, strlen(pw->pw_passwd));
if (status != PAM_SUCCESS)
return status;
}
randomonly = params.qc.min[4] > params.qc.max;
if (getuid() != 0)
enforce = params.flags & F_ENFORCE_USERS;
else
enforce = params.flags & F_ENFORCE_ROOT;
if (params.flags & F_USE_AUTHTOK) {
status = pam_get_item(pamh, PAM_AUTHTOK,
(pam_item_t *)&newpass);
if (status != PAM_SUCCESS)
return status;
if (!newpass || (check_max(&params, pamh, newpass) && enforce))
return PAM_AUTHTOK_RECOVER_ERR;
reason = _passwdqc_check(&params.qc, newpass, oldpass, pw);
if (reason) {
say(pamh, PAM_ERROR_MSG, MESSAGE_WEAKPASS, reason);
if (enforce)
status = PAM_AUTHTOK_RECOVER_ERR;
}
return status;
}
retries_left = params.retry;
retry:
retry_wanted = 0;
if (!randomonly &&
params.qc.passphrase_words && params.qc.min[2] <= params.qc.max)
status = say(pamh, PAM_TEXT_INFO, MESSAGE_INTRO_BOTH);
else
status = say(pamh, PAM_TEXT_INFO, MESSAGE_INTRO_PASSWORD);
if (status != PAM_SUCCESS)
return status;
if (!randomonly && params.qc.min[3] <= params.qc.min[4])
status = say(pamh, PAM_TEXT_INFO, MESSAGE_EXPLAIN_PASSWORD_1,
params.qc.min[3] == 8 || params.qc.min[3] == 11 ? "n" : "",
params.qc.min[3]);
else
if (!randomonly)
status = say(pamh, PAM_TEXT_INFO, MESSAGE_EXPLAIN_PASSWORD_2,
params.qc.min[3] == 8 || params.qc.min[3] == 11 ? "n" : "",
params.qc.min[3],
params.qc.min[4] == 8 || params.qc.min[4] == 11 ? "n" : "",
params.qc.min[4]);
if (status != PAM_SUCCESS)
return status;
if (!randomonly &&
params.qc.passphrase_words &&
params.qc.min[2] <= params.qc.max) {
status = say(pamh, PAM_TEXT_INFO, MESSAGE_EXPLAIN_PASSPHRASE,
params.qc.passphrase_words,
params.qc.min[2], params.qc.max);
if (status != PAM_SUCCESS)
return status;
}
randompass = _passwdqc_random(&params.qc);
if (randompass) {
status = say(pamh, PAM_TEXT_INFO, randomonly ?
MESSAGE_RANDOMONLY : MESSAGE_RANDOM, randompass);
if (status != PAM_SUCCESS) {
_pam_overwrite(randompass);
randompass = NULL;
}
} else
if (randomonly) {
say(pamh, PAM_ERROR_MSG, getuid() != 0 ?
MESSAGE_MISCONFIGURED : MESSAGE_RANDOMFAILED);
return PAM_AUTHTOK_RECOVER_ERR;
}
status = converse(pamh, PAM_PROMPT_ECHO_OFF, PROMPT_NEWPASS1, &resp);
if (status == PAM_SUCCESS && (!resp || !resp->resp))
status = PAM_AUTHTOK_RECOVER_ERR;
if (status != PAM_SUCCESS) {
if (randompass) _pam_overwrite(randompass);
return status;
}
newpass = strdup(resp->resp);
_pam_drop_reply(resp, 1);
if (!newpass) {
if (randompass) _pam_overwrite(randompass);
return PAM_AUTHTOK_RECOVER_ERR;
}
if (check_max(&params, pamh, newpass) && enforce) {
status = PAM_AUTHTOK_RECOVER_ERR;
retry_wanted = 1;
}
reason = NULL;
if (status == PAM_SUCCESS &&
(!randompass || !strstr(newpass, randompass)) &&
(randomonly ||
(reason = _passwdqc_check(&params.qc, newpass, oldpass, pw)))) {
if (randomonly)
say(pamh, PAM_ERROR_MSG, MESSAGE_NOTRANDOM);
else
say(pamh, PAM_ERROR_MSG, MESSAGE_WEAKPASS, reason);
if (enforce) {
status = PAM_AUTHTOK_RECOVER_ERR;
retry_wanted = 1;
}
}
if (status == PAM_SUCCESS)
status = converse(pamh, PAM_PROMPT_ECHO_OFF,
PROMPT_NEWPASS2, &resp);
if (status == PAM_SUCCESS) {
if (resp && resp->resp) {
if (strcmp(newpass, resp->resp)) {
status = say(pamh,
PAM_ERROR_MSG, MESSAGE_MISTYPED);
if (status == PAM_SUCCESS) {
status = PAM_AUTHTOK_RECOVER_ERR;
retry_wanted = 1;
}
}
_pam_drop_reply(resp, 1);
} else
status = PAM_AUTHTOK_RECOVER_ERR;
}
if (status == PAM_SUCCESS)
status = pam_set_item(pamh, PAM_AUTHTOK, newpass);
if (randompass) _pam_overwrite(randompass);
_pam_overwrite(newpass);
free(newpass);
if (retry_wanted && --retries_left > 0) {
status = say(pamh, PAM_TEXT_INFO, MESSAGE_RETRY);
if (status == PAM_SUCCESS)
goto retry;
}
return status;
}
#ifdef PAM_STATIC
struct pam_module _pam_passwdqc_modstruct = {
"pam_passwdqc",
NULL,
NULL,
NULL,
NULL,
NULL,
pam_sm_chauthtok
};
#endif

View File

@ -0,0 +1,58 @@
Summary: Pluggable password "quality check"
Name: pam_passwdqc
Version: 0.4
Release: 1owl
License: relaxed BSD and (L)GPL-compatible
Group: System Environment/Base
Source: pam_passwdqc-%version.tar.gz
BuildRoot: /override/%name-%version
%description
pam_passwdqc is a simple password strength checking module for
PAM-aware password changing programs, such as passwd(1). In addition
to checking regular passwords, it offers support for passphrases and
can provide randomly generated passwords. All features are optional
and can be (re-)configured without rebuilding.
%prep
%setup -q
%build
make CFLAGS="-c -Wall -fPIC -DHAVE_SHADOW -DLINUX_PAM $RPM_OPT_FLAGS"
%install
rm -rf $RPM_BUILD_ROOT
make install FAKEROOT=$RPM_BUILD_ROOT
%clean
rm -rf $RPM_BUILD_ROOT
%files
%defattr(-,root,root)
%doc LICENSE README
/lib/security/pam_passwdqc.so
%changelog
* Sun Nov 04 2001 Solar Designer <solar@owl.openwall.com>
- Updated to 0.4:
- Added "ask_oldauthtok" and "check_oldauthtok" as needed for stacking with
the Solaris pam_unix;
- Permit for stacking of more than one instance of this module (no statics).
* Tue Feb 13 2001 Solar Designer <solar@owl.openwall.com>
- Install the module as mode 755.
* Tue Dec 19 2000 Solar Designer <solar@owl.openwall.com>
- Added "-Wall -fPIC" to the CFLAGS.
* Mon Oct 30 2000 Solar Designer <solar@owl.openwall.com>
- 0.3: portability fixes (this might build on non-Linux-PAM now).
* Fri Sep 22 2000 Solar Designer <solar@owl.openwall.com>
- 0.2: added "use_authtok", added README.
* Fri Aug 18 2000 Solar Designer <solar@owl.openwall.com>
- 0.1, "retry_wanted" bugfix.
* Sun Jul 02 2000 Solar Designer <solar@owl.openwall.com>
- Initial version (non-public).

View File

@ -0,0 +1,24 @@
/*
* Copyright (c) 2000,2001 by Solar Designer. See LICENSE.
*/
#ifndef _PASSWDQC_H
#define _PASSWDQC_H
#include <pwd.h>
typedef struct {
int min[5], max;
int passphrase_words;
int match_length;
int similar_deny;
int random_bits;
} passwdqc_params_t;
extern char _passwdqc_wordset_4k[0x1000][6];
extern char *_passwdqc_check(passwdqc_params_t *params,
char *newpass, char *oldpass, struct passwd *pw);
extern char *_passwdqc_random(passwdqc_params_t *params);
#endif

View File

@ -0,0 +1,361 @@
/*
* Copyright (c) 2000,2001 by Solar Designer. See LICENSE.
*/
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <pwd.h>
#include "passwdqc.h"
#define REASON_ERROR \
"check failed"
#define REASON_SAME \
"is the same as the old one"
#define REASON_SIMILAR \
"is based on the old one"
#define REASON_SHORT \
"too short"
#define REASON_LONG \
"too long"
#define REASON_SIMPLESHORT \
"not enough different characters or classes for this length"
#define REASON_SIMPLE \
"not enough different characters or classes"
#define REASON_PERSONAL \
"based on personal login information"
#define REASON_WORD \
"based on a dictionary word and not a passphrase"
#define FIXED_BITS 15
typedef unsigned long fixed;
/*
* Calculates the expected number of different characters for a random
* password of a given length. The result is rounded down. We use this
* with the _requested_ minimum length (so longer passwords don't have
* to meet this strict requirement for their length).
*/
static int expected_different(int charset, int length)
{
fixed x, y, z;
x = ((fixed)(charset - 1) << FIXED_BITS) / charset;
y = x;
while (--length > 0) y = (y * x) >> FIXED_BITS;
z = (fixed)charset * (((fixed)1 << FIXED_BITS) - y);
return (int)(z >> FIXED_BITS);
}
/*
* A password is too simple if it is too short for its class, or doesn't
* contain enough different characters for its class, or doesn't contain
* enough words for a passphrase.
*/
static int is_simple(passwdqc_params_t *params, char *newpass)
{
int length, classes, words, chars;
int digits, lowers, uppers, others, unknowns;
int c, p;
length = classes = words = chars = 0;
digits = lowers = uppers = others = unknowns = 0;
p = ' ';
while ((c = (unsigned char)newpass[length])) {
length++;
if (!isascii(c)) unknowns++; else
if (isdigit(c)) digits++; else
if (islower(c)) lowers++; else
if (isupper(c)) uppers++; else
others++;
if (isascii(c) && isalpha(c) && isascii(p) && !isalpha(p))
words++;
p = c;
if (!strchr(&newpass[length], c))
chars++;
}
if (!length) return 1;
/* Upper case characters and digits used in common ways don't increase the
* strength of a password */
c = (unsigned char)newpass[0];
if (uppers && isascii(c) && isupper(c)) uppers--;
c = (unsigned char)newpass[length - 1];
if (digits && isascii(c) && isdigit(c)) digits--;
/* Count the number of different character classes we've seen. We assume
* that there're no non-ASCII characters for digits. */
classes = 0;
if (digits) classes++;
if (lowers) classes++;
if (uppers) classes++;
if (others) classes++;
if (unknowns && (!classes || (digits && classes == 1))) classes++;
for (; classes > 0; classes--)
switch (classes) {
case 1:
if (length >= params->min[0] &&
chars >= expected_different(10, params->min[0]) - 1)
return 0;
return 1;
case 2:
if (length >= params->min[1] &&
chars >= expected_different(36, params->min[1]) - 1)
return 0;
if (!params->passphrase_words ||
words < params->passphrase_words)
continue;
if (length >= params->min[2] &&
chars >= expected_different(27, params->min[2]) - 1)
return 0;
continue;
case 3:
if (length >= params->min[3] &&
chars >= expected_different(62, params->min[3]) - 1)
return 0;
continue;
case 4:
if (length >= params->min[4] &&
chars >= expected_different(95, params->min[4]) - 1)
return 0;
continue;
}
return 1;
}
static char *unify(char *src)
{
char *dst;
char *sptr, *dptr;
int c;
if (!(dst = malloc(strlen(src) + 1)))
return NULL;
sptr = src;
dptr = dst;
do {
c = (unsigned char)*sptr;
if (isascii(c) && isupper(c))
*dptr++ = tolower(c);
else
*dptr++ = *sptr;
} while (*sptr++);
return dst;
}
static char *reverse(char *src)
{
char *dst;
char *sptr, *dptr;
if (!(dst = malloc(strlen(src) + 1)))
return NULL;
sptr = &src[strlen(src)];
dptr = dst;
while (sptr > src)
*dptr++ = *--sptr;
*dptr = '\0';
return dst;
}
static void clean(char *dst)
{
if (dst) {
memset(dst, 0, strlen(dst));
free(dst);
}
}
/*
* Needle is based on haystack if both contain a long enough common
* substring and needle would be too simple for a password with the
* substring removed.
*/
static int is_based(passwdqc_params_t *params,
char *haystack, char *needle, char *original)
{
char *scratch;
int length;
int i, j;
char *p;
int match;
if (!params->match_length) /* disabled */
return 0;
if (params->match_length < 0) /* misconfigured */
return 1;
if (strstr(haystack, needle)) /* based on haystack entirely */
return 1;
scratch = NULL;
length = strlen(needle);
for (i = 0; i <= length - params->match_length; i++)
for (j = params->match_length; i + j <= length; j++) {
match = 0;
for (p = haystack; *p; p++)
if (*p == needle[i] && !strncmp(p, &needle[i], j)) {
match = 1;
if (!scratch) {
if (!(scratch = malloc(length + 1)))
return 1;
}
memcpy(scratch, original, i);
memcpy(&scratch[i], &original[i + j],
length + 1 - (i + j));
if (is_simple(params, scratch)) {
clean(scratch);
return 1;
}
}
if (!match) break;
}
clean(scratch);
return 0;
}
/*
* This wordlist check is now the least important given the checks above
* and the support for passphrases (which are based on dictionary words,
* and checked by other means). It is still useful to trap simple short
* passwords (if short passwords are allowed) that are word-based, but
* passed the other checks due to uncommon capitalization, digits, and
* special characters. We (mis)use the same set of words that are used
* to generate random passwords. This list is much smaller than those
* used for password crackers, and it doesn't contain common passwords
* that aren't short English words. Perhaps support for large wordlists
* should still be added, even though this is now of little importance.
*/
static int is_word_based(passwdqc_params_t *params,
char *needle, char *original)
{
char word[7];
char *unified;
int index;
word[6] = '\0';
for (index = 0; index < 0x1000; index++) {
memcpy(word, _passwdqc_wordset_4k[index], 6);
if (strlen(word) < params->match_length) continue;
unified = unify(word);
if (is_based(params, unified, needle, original)) {
clean(unified);
return 1;
}
clean(unified);
}
return 0;
}
char *_passwdqc_check(passwdqc_params_t *params,
char *newpass, char *oldpass, struct passwd *pw)
{
char truncated[9], *reversed;
char *u_newpass, *u_reversed;
char *u_oldpass;
char *u_name, *u_gecos;
char *reason;
int length;
reversed = NULL;
u_newpass = u_reversed = NULL;
u_oldpass = NULL;
u_name = u_gecos = NULL;
reason = NULL;
if (oldpass && !strcmp(oldpass, newpass))
reason = REASON_SAME;
length = strlen(newpass);
if (!reason && length < params->min[4])
reason = REASON_SHORT;
if (!reason && length > params->max) {
if (params->max == 8) {
truncated[0] = '\0';
strncat(truncated, newpass, 8);
newpass = truncated;
if (oldpass && !strncmp(oldpass, newpass, 8))
reason = REASON_SAME;
} else
reason = REASON_LONG;
}
if (!reason && is_simple(params, newpass)) {
if (length < params->min[1] && params->min[1] <= params->max)
reason = REASON_SIMPLESHORT;
else
reason = REASON_SIMPLE;
}
if (!reason) {
if ((reversed = reverse(newpass))) {
u_newpass = unify(newpass);
u_reversed = unify(reversed);
if (oldpass)
u_oldpass = unify(oldpass);
if (pw) {
u_name = unify(pw->pw_name);
u_gecos = unify(pw->pw_gecos);
}
}
if (!reversed ||
!u_newpass || !u_reversed ||
(oldpass && !u_oldpass) ||
(pw && (!u_name || !u_gecos)))
reason = REASON_ERROR;
}
if (!reason && oldpass && params->similar_deny &&
(is_based(params, u_oldpass, u_newpass, newpass) ||
is_based(params, u_oldpass, u_reversed, reversed)))
reason = REASON_SIMILAR;
if (!reason && pw &&
(is_based(params, u_name, u_newpass, newpass) ||
is_based(params, u_name, u_reversed, reversed) ||
is_based(params, u_gecos, u_newpass, newpass) ||
is_based(params, u_gecos, u_reversed, reversed)))
reason = REASON_PERSONAL;
if (!reason && strlen(newpass) < params->min[2] &&
(is_word_based(params, u_newpass, newpass) ||
is_word_based(params, u_reversed, reversed)))
reason = REASON_WORD;
memset(truncated, 0, sizeof(truncated));
clean(reversed);
clean(u_newpass); clean(u_reversed);
clean(u_oldpass);
clean(u_name); clean(u_gecos);
return reason;
}

View File

@ -0,0 +1,90 @@
/*
* Copyright (c) 2000,2001 by Solar Designer. See LICENSE.
*/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include "passwdqc.h"
#define SEPARATORS "_,.;:-!&"
static int read_loop(int fd, char *buffer, int count)
{
int offset, block;
offset = 0;
while (count > 0) {
block = read(fd, &buffer[offset], count);
if (block < 0) {
if (errno == EINTR) continue;
return block;
}
if (!block) return offset;
offset += block;
count -= block;
}
return offset;
}
char *_passwdqc_random(passwdqc_params_t *params)
{
static char output[0x100];
int bits;
int use_separators, count, length, index;
char *start, *end;
int fd;
unsigned char bytes[2];
if (!(bits = params->random_bits))
return NULL;
count = 1 + ((bits - 12) + 14) / 15;
use_separators = ((bits + 11) / 12 != count);
length = count * 7 - 1;
if (length >= sizeof(output) || length > params->max) return NULL;
if ((fd = open("/dev/urandom", O_RDONLY)) < 0) return NULL;
length = 0;
do {
if (read_loop(fd, bytes, sizeof(bytes)) != sizeof(bytes)) {
close(fd);
return NULL;
}
index = (((int)bytes[1] & 0x0f) << 8) | (int)bytes[0];
start = _passwdqc_wordset_4k[index];
end = memchr(start, '\0', 6);
if (!end) end = start + 6;
if (length + (end - start) >= sizeof(output) - 1) {
close(fd);
return NULL;
}
memcpy(&output[length], start, end - start);
length += end - start;
bits -= 12;
if (use_separators && bits > 3) {
index = ((int)bytes[1] & 0x70) >> 4;
output[length++] = SEPARATORS[index];
bits -= 3;
} else
if (bits > 0)
output[length++] = ' ';
} while (bits > 0);
memset(bytes, 0, sizeof(bytes));
output[length] = '\0';
close(fd);
return output;
}

File diff suppressed because it is too large Load Diff