summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDanny Sauer <dsauer@suse.com>2020-08-03 05:28:42 -0500
committerGitHub <noreply@github.com>2020-08-03 12:28:42 +0200
commitbfef79dbe6aa525e9557bf4b0a61e6dde12749c4 (patch)
tree5c277405a7257799623f4011fca391c6aced5456
parent0c9cc1ffa122f80fd567be6000f8416fe50bc934 (diff)
downloadlibpwquality-git-bfef79dbe6aa525e9557bf4b0a61e6dde12749c4.tar.gz
Add usersubstr check
Check for substrings of N characters from the username. Potentially useful for sites with usernames like firstnamelastname or similar. Also rename usercheck to "wordcheck" since it's also used for arbitrary words :) Signed-off-by: Danny Sauer <dsauer@suse.com>
-rw-r--r--doc/man/pam_pwquality.8.pod6
-rw-r--r--doc/man/pwquality.conf.5.pod6
-rw-r--r--src/check.c75
-rw-r--r--src/pwqprivate.h2
-rw-r--r--src/pwquality.conf4
-rw-r--r--src/pwquality.h1
-rw-r--r--src/settings.c5
7 files changed, 78 insertions, 21 deletions
diff --git a/doc/man/pam_pwquality.8.pod b/doc/man/pam_pwquality.8.pod
index 017c4d0..7a74b48 100644
--- a/doc/man/pam_pwquality.8.pod
+++ b/doc/man/pam_pwquality.8.pod
@@ -206,6 +206,12 @@ contains the user name in some form. The default is 1 which means that
this check is enabled. It is not performed for user names shorter
than 3 characters.
+=item B<usersubstr=>I<N>
+
+If greater than 3 (due to the minimum length in usercheck), check whether the
+password contains a substring of at least I<N> length in some form.
+The default is 0, which means this check is disabled.
+
=item B<enforcing=>I<N>
If nonzero, reject the password if it fails the checks, otherwise
diff --git a/doc/man/pwquality.conf.5.pod b/doc/man/pwquality.conf.5.pod
index 47d6fd0..ada22d0 100644
--- a/doc/man/pwquality.conf.5.pod
+++ b/doc/man/pwquality.conf.5.pod
@@ -111,6 +111,12 @@ If nonzero, check whether the password (with possible modifications)
contains the user name in some form. It is not performed for user names shorter
than 3 characters. (default 1)
+=item B<usersubstr=>I<N>
+
+If greater than 3 (due to the minimum length in usercheck), check whether the
+password contains a substring of at least I<N> length in some form.
+(default 0)
+
=item B<enforcing=>I<N>
If nonzero, reject the password if it fails the checks, otherwise
diff --git a/src/check.c b/src/check.c
index 44983c6..6560535 100644
--- a/src/check.c
+++ b/src/check.c
@@ -368,28 +368,28 @@ static int sequence(pwquality_settings_t *pwq, const char *new, void **auxerror)
}
static int
-usercheck(pwquality_settings_t *pwq, const char *new,
- char *user)
+wordcheck(pwquality_settings_t *pwq, const char *new,
+ char *word)
{
char *f, *b;
- int dist, userlen = strlen(user);
+ int dist, wordlen = strlen(word);
- /* No point to check for username in password in 1-3 char
- * usernames; it will be contained one way or another anyway. */
- if (userlen < PWQ_MIN_WORD_LENGTH)
+ /* No point to check for word in password for 1-3 char
+ * words; it will be contained one way or another anyway. */
+ if (wordlen < PWQ_MIN_WORD_LENGTH)
return 0;
- if (strstr(new, user) != NULL)
- return 1;
+ if (strstr(new, word) != NULL)
+ return PWQ_ERROR_BAD_WORDS;
- dist = distance(new, user);
+ dist = distance(new, word);
if (dist >= 0 && dist < PWQ_DEFAULT_DIFF_OK)
- return 1;
+ return PWQ_ERROR_BAD_WORDS;
- /* now reverse the username, we can do that in place
+ /* now reverse the wordname, we can do that in place
as it is strdup-ed */
- f = user;
- b = user + userlen - 1;
+ f = word;
+ b = word + wordlen - 1;
while (f < b) {
char c;
@@ -400,16 +400,50 @@ usercheck(pwquality_settings_t *pwq, const char *new,
++f;
}
- if (strstr(new, user) != NULL)
- return 1;
+ if (strstr(new, word) != NULL)
+ return PWQ_ERROR_BAD_WORDS;
- dist = distance(new, user);
+ dist = distance(new, word);
if (dist >= 0 && dist < PWQ_DEFAULT_DIFF_OK)
- return 1;
+ return PWQ_ERROR_BAD_WORDS;
return 0;
}
+static int
+usercheck(pwquality_settings_t *pwq, const char *new,
+ char *user)
+{
+ int i, userlen;
+ int rv = 0;
+ char *subuser = calloc(pwq->user_substr+1, sizeof(char));
+
+ if (subuser == NULL) {
+ return PWQ_ERROR_MEM_ALLOC;
+ }
+
+ userlen = strlen(user);
+ if (pwq->user_substr >= PWQ_MIN_WORD_LENGTH &&
+ userlen > pwq->user_substr) {
+ for(i = 0; !rv && (i <= userlen - pwq->user_substr); i++) {
+ strncpy(subuser, user+i, pwq->user_substr+1);
+ subuser[pwq->user_substr] = '\0';
+ rv = wordcheck(pwq, new, subuser);
+ }
+ }
+ else {
+ // if we already tested substrings, there's no need to test
+ // the whole username; all substrings would've been found :)
+ if (!rv)
+ rv = wordcheck(pwq, new, user);
+ }
+ // translate wordcheck return
+ if (rv == PWQ_ERROR_BAD_WORDS)
+ rv = PWQ_ERROR_USER_CHECK;
+ free(subuser);
+ return rv;
+}
+
static char *
str_lower(char *string)
{
@@ -445,7 +479,7 @@ wordlistcheck(pwquality_settings_t *pwq, const char *new,
if (strlen(p) >= PWQ_MIN_WORD_LENGTH) {
str_lower(p);
- if (usercheck(pwq, new, p)) {
+ if (wordcheck(pwq, new, p)) {
free(list);
return PWQ_ERROR_BAD_WORDS;
}
@@ -557,9 +591,8 @@ password_check(pwquality_settings_t *pwq,
if (!rv && sequence(pwq, new, auxerror))
rv = PWQ_ERROR_MAX_SEQUENCE;
- if (!rv && usermono && pwq->user_check &&
- usercheck(pwq, newmono, usermono))
- rv = PWQ_ERROR_USER_CHECK;
+ if (!rv && usermono && pwq->user_check)
+ rv = usercheck(pwq, newmono, usermono);
if (!rv && user && pwq->gecos_check)
rv = gecoscheck(pwq, newmono, user);
diff --git a/src/pwqprivate.h b/src/pwqprivate.h
index 38fc93f..defdaca 100644
--- a/src/pwqprivate.h
+++ b/src/pwqprivate.h
@@ -26,6 +26,7 @@ struct pwquality_settings {
int gecos_check;
int dict_check;
int user_check;
+ int user_substr;
int enforcing;
int retry_times;
int enforce_for_root;
@@ -48,6 +49,7 @@ struct setting_mapping {
#define PWQ_DEFAULT_OTH_CREDIT 0
#define PWQ_DEFAULT_DICT_CHECK 1
#define PWQ_DEFAULT_USER_CHECK 1
+#define PWQ_DEFAULT_USER_SUBSTR 0
#define PWQ_DEFAULT_ENFORCING 1
#define PWQ_DEFAULT_RETRY_TIMES 1
#define PWQ_DEFAULT_ENFORCE_ROOT 0
diff --git a/src/pwquality.conf b/src/pwquality.conf
index 1b05cb4..63eb315 100644
--- a/src/pwquality.conf
+++ b/src/pwquality.conf
@@ -54,6 +54,10 @@
# The check is enabled if the value is not 0.
# usercheck = 1
#
+# Length of substrings from the username to check for in the password
+# The check is enabled if the value is greater than 0 and usercheck is enabled.
+# usersubstr = 0
+#
# Whether the check is enforced by the PAM module and possibly other
# applications.
# The new password is rejected if it fails the check and the value is not 0.
diff --git a/src/pwquality.h b/src/pwquality.h
index 73474cd..8f163bb 100644
--- a/src/pwquality.h
+++ b/src/pwquality.h
@@ -35,6 +35,7 @@ extern "C" {
#define PWQ_SETTING_RETRY_TIMES 18
#define PWQ_SETTING_ENFORCE_ROOT 19
#define PWQ_SETTING_LOCAL_USERS 20
+#define PWQ_SETTING_USER_SUBSTR 21
#define PWQ_MAX_ENTROPY_BITS 256
#define PWQ_MIN_ENTROPY_BITS 56
diff --git a/src/settings.c b/src/settings.c
index 3c428cc..6a5f832 100644
--- a/src/settings.c
+++ b/src/settings.c
@@ -36,6 +36,7 @@ pwquality_default_settings(void)
pwq->oth_credit = PWQ_DEFAULT_OTH_CREDIT;
pwq->dict_check = PWQ_DEFAULT_DICT_CHECK;
pwq->user_check = PWQ_DEFAULT_USER_CHECK;
+ pwq->user_substr = PWQ_DEFAULT_USER_SUBSTR;
pwq->enforcing = PWQ_DEFAULT_ENFORCING;
pwq->retry_times = PWQ_DEFAULT_RETRY_TIMES;
pwq->enforce_for_root = PWQ_DEFAULT_ENFORCE_ROOT;
@@ -70,6 +71,7 @@ static const struct setting_mapping s_map[] = {
{ "gecoscheck", PWQ_SETTING_GECOS_CHECK, PWQ_TYPE_INT},
{ "dictcheck", PWQ_SETTING_DICT_CHECK, PWQ_TYPE_INT},
{ "usercheck", PWQ_SETTING_USER_CHECK, PWQ_TYPE_INT},
+ { "usersubstr", PWQ_SETTING_USER_SUBSTR, PWQ_TYPE_INT},
{ "enforcing", PWQ_SETTING_ENFORCING, PWQ_TYPE_INT},
{ "badwords", PWQ_SETTING_BAD_WORDS, PWQ_TYPE_STR},
{ "dictpath", PWQ_SETTING_DICT_PATH, PWQ_TYPE_STR},
@@ -349,6 +351,9 @@ pwquality_set_int_value(pwquality_settings_t *pwq, int setting, int value)
case PWQ_SETTING_USER_CHECK:
pwq->user_check = value;
break;
+ case PWQ_SETTING_USER_SUBSTR:
+ pwq->user_substr = value;
+ break;
case PWQ_SETTING_ENFORCING:
pwq->enforcing = value;
break;