7f16b85d54
- add inactive account lock out functionality to pam_lastlog - fix pam_unix remember user name matching - add gecoscheck and maxclassrepeat functionality to pam_cracklib - correctly check for crypt() returning NULL in pam_unix - pam_unix - do not fallback to MD5 on password change if requested algorithm not supported by crypt() (#818741)
374 lines
12 KiB
Diff
374 lines
12 KiB
Diff
From 422c19520fb814cfd8edd84d7989f4c52acbfa03 Mon Sep 17 00:00:00 2001
|
|
From: Tomas Mraz <tmraz@fedoraproject.org>
|
|
Date: Mon, 30 Apr 2012 15:03:32 +0200
|
|
Subject: [PATCH] pam_cracklib: Add maxclassrepeat, gecoscheck checks and
|
|
remove unused difignore.
|
|
|
|
modules/pam_cracklib/pam_cracklib.c (_pam_parse): Recognize the maxclassrepeat, gecoscheck options. Ignore difignore option.
|
|
(simple): Add the check for the same class repetition.
|
|
(usercheck): Refactor into wordcheck().
|
|
(gecoscheck): New test for words from the GECOS field.
|
|
(password_check): Call the gecoscheck().
|
|
(pam_sm_chauthtok): Drop the diff_ignore from options struct.
|
|
modules/pam_cracklib/pam_cracklib.8.xml: Document the maxclassrepeat and gecoscheck checks, update the documentation of the difok test.
|
|
---
|
|
modules/pam_cracklib/pam_cracklib.8.xml | 66 +++++++++-------
|
|
modules/pam_cracklib/pam_cracklib.c | 129 +++++++++++++++++++++++++------
|
|
2 files changed, 142 insertions(+), 53 deletions(-)
|
|
|
|
diff --git a/modules/pam_cracklib/pam_cracklib.8.xml b/modules/pam_cracklib/pam_cracklib.8.xml
|
|
index 29e00c0..5022c75 100644
|
|
--- a/modules/pam_cracklib/pam_cracklib.8.xml
|
|
+++ b/modules/pam_cracklib/pam_cracklib.8.xml
|
|
@@ -77,17 +77,10 @@
|
|
<para>
|
|
Is the new password too much like the old one?
|
|
This is primarily controlled by one argument,
|
|
- <option>difok</option> which is a number of characters
|
|
- that if different between the old and new are enough to accept
|
|
- the new password, this defaults to 10 or 1/2 the size of the
|
|
- new password whichever is smaller.
|
|
- </para>
|
|
- <para>
|
|
- To avoid the lockup associated with trying to change a long and
|
|
- complicated password, <option>difignore</option> is available.
|
|
- This argument can be used to specify the minimum length a new
|
|
- password needs to be before the <option>difok</option> value is
|
|
- ignored. The default value for <option>difignore</option> is 23.
|
|
+ <option>difok</option> which is a number of character changes
|
|
+ (inserts, removals, or replacements) between the old and new
|
|
+ password that are enough to accept the new password.
|
|
+ This defaults to 5 changes.
|
|
</para>
|
|
</listitem>
|
|
</varlistentry>
|
|
@@ -96,7 +89,8 @@
|
|
<listitem>
|
|
<para>
|
|
Is the new password too small?
|
|
- This is controlled by 5 arguments <option>minlen</option>,
|
|
+ This is controlled by 6 arguments <option>minlen</option>,
|
|
+ <option>maxclassrepeat</option>,
|
|
<option>dcredit</option>, <option>ucredit</option>,
|
|
<option>lcredit</option>, and <option>ocredit</option>. See the section
|
|
on the arguments for the details of how these work and there defaults.
|
|
@@ -204,24 +198,9 @@
|
|
<listitem>
|
|
<para>
|
|
This argument will change the default of
|
|
- <emphasis>5</emphasis> for the number of characters in
|
|
- the new password that must not be present in the old
|
|
- password. In addition, if 1/2 of the characters in the
|
|
- new password are different then the new password will
|
|
- be accepted anyway.
|
|
- </para>
|
|
- </listitem>
|
|
- </varlistentry>
|
|
-
|
|
- <varlistentry>
|
|
- <term>
|
|
- <option>difignore=<replaceable>N</replaceable></option>
|
|
- </term>
|
|
- <listitem>
|
|
- <para>
|
|
- How many characters should the password have before
|
|
- difok will be ignored. The default is
|
|
- <emphasis>23</emphasis>.
|
|
+ <emphasis>5</emphasis> for the number of character
|
|
+ changes in the new password that differentiate it
|
|
+ from the old password.
|
|
</para>
|
|
</listitem>
|
|
</varlistentry>
|
|
@@ -370,6 +349,19 @@
|
|
|
|
<varlistentry>
|
|
<term>
|
|
+ <option>maxclassrepeat=<replaceable>N</replaceable></option>
|
|
+ </term>
|
|
+ <listitem>
|
|
+ <para>
|
|
+ Reject passwords which contain more than N consecutive
|
|
+ characters of the same class. The default is 0 which means
|
|
+ that this check is disabled.
|
|
+ </para>
|
|
+ </listitem>
|
|
+ </varlistentry>
|
|
+
|
|
+ <varlistentry>
|
|
+ <term>
|
|
<option>reject_username</option>
|
|
</term>
|
|
<listitem>
|
|
@@ -383,6 +375,20 @@
|
|
|
|
<varlistentry>
|
|
<term>
|
|
+ <option>gecoscheck</option>
|
|
+ </term>
|
|
+ <listitem>
|
|
+ <para>
|
|
+ Check whether the words from the GECOS field (usualy full name
|
|
+ of the user) longer than 3 characters in straight or reversed
|
|
+ form are contained in the new password. If any such word is
|
|
+ found the new password is rejected.
|
|
+ </para>
|
|
+ </listitem>
|
|
+ </varlistentry>
|
|
+
|
|
+ <varlistentry>
|
|
+ <term>
|
|
<option>use_authtok</option>
|
|
</term>
|
|
<listitem>
|
|
diff --git a/modules/pam_cracklib/pam_cracklib.c b/modules/pam_cracklib/pam_cracklib.c
|
|
index 1955b83..96ee995 100644
|
|
--- a/modules/pam_cracklib/pam_cracklib.c
|
|
+++ b/modules/pam_cracklib/pam_cracklib.c
|
|
@@ -51,6 +51,8 @@
|
|
#include <sys/stat.h>
|
|
#include <ctype.h>
|
|
#include <limits.h>
|
|
+#include <pwd.h>
|
|
+#include <security/pam_modutil.h>
|
|
|
|
#ifdef HAVE_CRACK_H
|
|
#include <crack.h>
|
|
@@ -92,7 +94,6 @@ extern char *FascistCheck(char *pw, const char *dictpath);
|
|
struct cracklib_options {
|
|
int retry_times;
|
|
int diff_ok;
|
|
- int diff_ignore;
|
|
int min_length;
|
|
int dig_credit;
|
|
int up_credit;
|
|
@@ -100,19 +101,21 @@ struct cracklib_options {
|
|
int oth_credit;
|
|
int min_class;
|
|
int max_repeat;
|
|
+ int max_class_repeat;
|
|
int reject_user;
|
|
+ int gecos_check;
|
|
const char *cracklib_dictpath;
|
|
};
|
|
|
|
#define CO_RETRY_TIMES 1
|
|
#define CO_DIFF_OK 5
|
|
-#define CO_DIFF_IGNORE 23
|
|
#define CO_MIN_LENGTH 9
|
|
# define CO_MIN_LENGTH_BASE 5
|
|
#define CO_DIG_CREDIT 1
|
|
#define CO_UP_CREDIT 1
|
|
#define CO_LOW_CREDIT 1
|
|
#define CO_OTH_CREDIT 1
|
|
+#define CO_MIN_WORD_LENGTH 4
|
|
|
|
static int
|
|
_pam_parse (pam_handle_t *pamh, struct cracklib_options *opt,
|
|
@@ -139,9 +142,7 @@ _pam_parse (pam_handle_t *pamh, struct cracklib_options *opt,
|
|
if (!ep || (opt->diff_ok < 0))
|
|
opt->diff_ok = CO_DIFF_OK;
|
|
} else if (!strncmp(*argv,"difignore=",10)) {
|
|
- opt->diff_ignore = strtol(*argv+10,&ep,10);
|
|
- if (!ep || (opt->diff_ignore < 0))
|
|
- opt->diff_ignore = CO_DIFF_IGNORE;
|
|
+ /* just ignore */
|
|
} else if (!strncmp(*argv,"minlen=",7)) {
|
|
opt->min_length = strtol(*argv+7,&ep,10);
|
|
if (!ep || (opt->min_length < CO_MIN_LENGTH_BASE))
|
|
@@ -172,8 +173,14 @@ _pam_parse (pam_handle_t *pamh, struct cracklib_options *opt,
|
|
opt->max_repeat = strtol(*argv+10,&ep,10);
|
|
if (!ep)
|
|
opt->max_repeat = 0;
|
|
+ } else if (!strncmp(*argv,"maxclassrepeat=",15)) {
|
|
+ opt->max_class_repeat = strtol(*argv+15,&ep,10);
|
|
+ if (!ep)
|
|
+ opt->max_class_repeat = 0;
|
|
} else if (!strncmp(*argv,"reject_username",15)) {
|
|
opt->reject_user = 1;
|
|
+ } else if (!strncmp(*argv,"gecoscheck",10)) {
|
|
+ opt->gecos_check = 1;
|
|
} else if (!strncmp(*argv,"authtok_type",12)) {
|
|
/* for pam_get_authtok, ignore */;
|
|
} else if (!strncmp(*argv,"use_authtok",11)) {
|
|
@@ -357,16 +364,45 @@ static int simple(struct cracklib_options *opt, const char *new)
|
|
int others = 0;
|
|
int size;
|
|
int i;
|
|
+ enum { NONE, DIGIT, UCASE, LCASE, OTHER } prevclass = NONE;
|
|
+ int sameclass = 0;
|
|
|
|
for (i = 0;new[i];i++) {
|
|
- if (isdigit (new[i]))
|
|
+ if (isdigit (new[i])) {
|
|
digits++;
|
|
- else if (isupper (new[i]))
|
|
+ if (prevclass != DIGIT) {
|
|
+ prevclass = DIGIT;
|
|
+ sameclass = 1;
|
|
+ } else
|
|
+ sameclass++;
|
|
+ }
|
|
+ else if (isupper (new[i])) {
|
|
uppers++;
|
|
- else if (islower (new[i]))
|
|
+ if (prevclass != UCASE) {
|
|
+ prevclass = UCASE;
|
|
+ sameclass = 1;
|
|
+ } else
|
|
+ sameclass++;
|
|
+ }
|
|
+ else if (islower (new[i])) {
|
|
lowers++;
|
|
- else
|
|
+ if (prevclass != LCASE) {
|
|
+ prevclass = LCASE;
|
|
+ sameclass = 1;
|
|
+ } else
|
|
+ sameclass++;
|
|
+ }
|
|
+ else {
|
|
others++;
|
|
+ if (prevclass != OTHER) {
|
|
+ prevclass = OTHER;
|
|
+ sameclass = 1;
|
|
+ } else
|
|
+ sameclass++;
|
|
+ }
|
|
+ if (opt->max_class_repeat > 1 && sameclass > opt->max_class_repeat) {
|
|
+ return 1;
|
|
+ }
|
|
}
|
|
|
|
/*
|
|
@@ -439,21 +475,17 @@ static int consecutive(struct cracklib_options *opt, const char *new)
|
|
return 0;
|
|
}
|
|
|
|
-static int usercheck(struct cracklib_options *opt, const char *new,
|
|
- char *user)
|
|
+static int wordcheck(const char *new, char *word)
|
|
{
|
|
char *f, *b;
|
|
|
|
- if (!opt->reject_user)
|
|
- return 0;
|
|
-
|
|
- if (strstr(new, user) != NULL)
|
|
+ if (strstr(new, word) != NULL)
|
|
return 1;
|
|
|
|
- /* now reverse the username, we can do that in place
|
|
+ /* now reverse the word, we can do that in place
|
|
as it is strdup-ed */
|
|
- f = user;
|
|
- b = user+strlen(user)-1;
|
|
+ f = word;
|
|
+ b = word+strlen(word)-1;
|
|
while (f < b) {
|
|
char c;
|
|
|
|
@@ -464,11 +496,20 @@ static int usercheck(struct cracklib_options *opt, const char *new,
|
|
++f;
|
|
}
|
|
|
|
- if (strstr(new, user) != NULL)
|
|
+ if (strstr(new, word) != NULL)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
+static int usercheck(struct cracklib_options *opt, const char *new,
|
|
+ char *user)
|
|
+{
|
|
+ if (!opt->reject_user)
|
|
+ return 0;
|
|
+
|
|
+ return wordcheck(new, user);
|
|
+}
|
|
+
|
|
static char * str_lower(char *string)
|
|
{
|
|
char *cp;
|
|
@@ -481,7 +522,50 @@ static char * str_lower(char *string)
|
|
return string;
|
|
}
|
|
|
|
-static const char *password_check(struct cracklib_options *opt,
|
|
+static int gecoscheck(pam_handle_t *pamh, struct cracklib_options *opt, const char *new,
|
|
+ const char *user)
|
|
+{
|
|
+ struct passwd *pwd;
|
|
+ char *list;
|
|
+ char *p;
|
|
+ char *next;
|
|
+
|
|
+ if (!opt->gecos_check)
|
|
+ return 0;
|
|
+
|
|
+ if ((pwd = pam_modutil_getpwnam(pamh, user)) == NULL) {
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ list = strdup(pwd->pw_gecos);
|
|
+
|
|
+ if (list == NULL || *list == '\0') {
|
|
+ free(list);
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ for (p = list;;p = next + 1) {
|
|
+ next = strchr(p, ' ');
|
|
+ if (next)
|
|
+ *next = '\0';
|
|
+
|
|
+ if (strlen(p) >= CO_MIN_WORD_LENGTH) {
|
|
+ str_lower(p);
|
|
+ if (wordcheck(new, p)) {
|
|
+ free(list);
|
|
+ return 1;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (!next)
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ free(list);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const char *password_check(pam_handle_t *pamh, struct cracklib_options *opt,
|
|
const char *old, const char *new,
|
|
const char *user)
|
|
{
|
|
@@ -535,7 +619,7 @@ static const char *password_check(struct cracklib_options *opt,
|
|
if (!msg && consecutive(opt, new))
|
|
msg = _("contains too many same characters consecutively");
|
|
|
|
- if (!msg && usercheck(opt, newmono, usermono))
|
|
+ if (!msg && (usercheck(opt, newmono, usermono) || gecoscheck(pamh, opt, newmono, user)))
|
|
msg = _("contains the user name in some form");
|
|
|
|
free(usermono);
|
|
@@ -584,7 +668,7 @@ static int _pam_unix_approve_pass(pam_handle_t *pamh,
|
|
* if one wanted to hardwire authentication token strength
|
|
* checking this would be the place
|
|
*/
|
|
- msg = password_check(opt, pass_old, pass_new, user);
|
|
+ msg = password_check(pamh, opt, pass_old, pass_new, user);
|
|
|
|
if (msg) {
|
|
if (ctrl & PAM_DEBUG_ARG)
|
|
@@ -611,7 +695,6 @@ PAM_EXTERN int pam_sm_chauthtok(pam_handle_t *pamh, int flags,
|
|
memset(&options, 0, sizeof(options));
|
|
options.retry_times = CO_RETRY_TIMES;
|
|
options.diff_ok = CO_DIFF_OK;
|
|
- options.diff_ignore = CO_DIFF_IGNORE;
|
|
options.min_length = CO_MIN_LENGTH;
|
|
options.dig_credit = CO_DIG_CREDIT;
|
|
options.up_credit = CO_UP_CREDIT;
|
|
--
|
|
1.7.7.6
|
|
|