Vendor import of Solar Designer's pam_passwdqc module.
This commit is contained in:
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
2
contrib/pam_modules/pam_passwdqc/INTERNALS
Normal file
2
contrib/pam_modules/pam_passwdqc/INTERNALS
Normal 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.
|
9
contrib/pam_modules/pam_passwdqc/LICENSE
Normal file
9
contrib/pam_modules/pam_passwdqc/LICENSE
Normal 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.
|
48
contrib/pam_modules/pam_passwdqc/Makefile
Normal file
48
contrib/pam_modules/pam_passwdqc/Makefile
Normal 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
|
27
contrib/pam_modules/pam_passwdqc/PLATFORMS
Normal file
27
contrib/pam_modules/pam_passwdqc/PLATFORMS
Normal 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.
|
143
contrib/pam_modules/pam_passwdqc/README
Normal file
143
contrib/pam_modules/pam_passwdqc/README
Normal 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>
|
28
contrib/pam_modules/pam_passwdqc/pam_macros.h
Normal file
28
contrib/pam_modules/pam_passwdqc/pam_macros.h
Normal 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
|
553
contrib/pam_modules/pam_passwdqc/pam_passwdqc.c
Normal file
553
contrib/pam_modules/pam_passwdqc/pam_passwdqc.c
Normal 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(¶ms, 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(¶ms, pamh, newpass) && enforce))
|
||||||
|
return PAM_AUTHTOK_RECOVER_ERR;
|
||||||
|
reason = _passwdqc_check(¶ms.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(¶ms.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(¶ms, 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(¶ms.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
|
58
contrib/pam_modules/pam_passwdqc/pam_passwdqc.spec
Normal file
58
contrib/pam_modules/pam_passwdqc/pam_passwdqc.spec
Normal 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).
|
24
contrib/pam_modules/pam_passwdqc/passwdqc.h
Normal file
24
contrib/pam_modules/pam_passwdqc/passwdqc.h
Normal 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
|
361
contrib/pam_modules/pam_passwdqc/passwdqc_check.c
Normal file
361
contrib/pam_modules/pam_passwdqc/passwdqc_check.c
Normal 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;
|
||||||
|
}
|
90
contrib/pam_modules/pam_passwdqc/passwdqc_random.c
Normal file
90
contrib/pam_modules/pam_passwdqc/passwdqc_random.c
Normal 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;
|
||||||
|
}
|
4108
contrib/pam_modules/pam_passwdqc/wordset_4k.c
Normal file
4108
contrib/pam_modules/pam_passwdqc/wordset_4k.c
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user