Add allowing/disabling character classes

This commit is contained in:
Ganna Starovoytova 2026-06-23 13:41:49 +02:00
parent 7458aa15bd
commit 08459a98b3
2 changed files with 301 additions and 1 deletions

View File

@ -0,0 +1,294 @@
diff -ruN '--exclude=*.rej' '--exclude=*.orig' a/doc/man/pam_pwquality.8.pod b/doc/man/pam_pwquality.8.pod
--- a/doc/man/pam_pwquality.8.pod 2026-06-24 13:39:51.505182998 +0200
+++ b/doc/man/pam_pwquality.8.pod 2026-06-24 13:47:32.042603712 +0200
@@ -250,6 +250,13 @@
a new password but use the one provided by the previously stacked
B<password> module.
+=item B<allowclasses=>I<< "ludo" >>
+
+The list of letters that represent allowed character classes that
+are allowed in a password (digits, uppercase, lowercase, others).
+"d" is for digits, "u" is for uppercase letters, "l" is for
+lowercase letters, "o" is for other characters.
+
=back
=head1 MODULE TYPES PROVIDED
diff -ruN '--exclude=*.rej' '--exclude=*.orig' a/doc/man/pwquality.conf.5.pod b/doc/man/pwquality.conf.5.pod
--- a/doc/man/pwquality.conf.5.pod 2026-06-24 13:39:51.505235241 +0200
+++ b/doc/man/pwquality.conf.5.pod 2026-06-24 13:50:30.694360330 +0200
@@ -85,7 +85,7 @@
Examples of such sequence are '12345' or 'fedcb'. Note
that most such passwords will not pass the simplicity check unless
the sequence is only a minor part of the password.
-The check is disabled if the value is 0. (default 0)
+The check is disabled if the value is 0. (default 0)
=item B<maxclassrepeat>
@@ -155,6 +155,13 @@
the following modules in the stack can use the B<use_authtok> option.
This option is off by default.
+=item B<allowclasses>
+
+The list of letters that represent allowed character classes that
+are allowed in a password (digits, uppercase, lowercase, others).
+"d" is for digits, "u" is for uppercase letters, "l" is for
+lowercase letters, "o" is for other characters. (default "ludo")
+
=back
=head1 SEE ALSO
diff -ruN '--exclude=*.rej' '--exclude=*.orig' a/src/check.c b/src/check.c
--- a/src/check.c 2026-06-24 13:39:51.504779475 +0200
+++ b/src/check.c 2026-06-24 13:45:06.118168891 +0200
@@ -49,7 +49,7 @@
* the other
*/
-static int
+static int
distdifferent(const char *old, const char *new,
size_t i, size_t j)
{
@@ -202,6 +202,7 @@
int others = 0;
int size;
int i;
+ const char *allow_classes;
enum { NONE, DIGIT, UCASE, LCASE, OTHER } prevclass = NONE;
int sameclass = 0;
@@ -244,6 +245,35 @@
return PWQ_ERROR_MAX_CLASS_REPEAT;
}
}
+ pwquality_get_str_value(pwq, PWQ_SETTING_ALLOW_CLASSES, &allow_classes);
+ if (digits > 0) {
+ if (strpbrk(allow_classes, "d") == NULL) {
+ if (auxerror)
+ *auxerror = strdup("digits");
+ return PWQ_ERROR_DISALLOWED_CLASS;
+ }
+ }
+ if (uppers > 0) {
+ if (strpbrk(allow_classes, "u") == NULL) {
+ if (auxerror)
+ *auxerror = strdup("uppercase");
+ return PWQ_ERROR_DISALLOWED_CLASS;
+ }
+ }
+ if (lowers > 0) {
+ if (strpbrk(allow_classes, "l") == NULL) {
+ if (auxerror)
+ *auxerror = strdup("lowercase");
+ return PWQ_ERROR_DISALLOWED_CLASS;
+ }
+ }
+ if (others > 0) {
+ if (strpbrk(allow_classes, "o") == NULL) {
+ if (auxerror)
+ *auxerror = strdup("other");
+ return PWQ_ERROR_DISALLOWED_CLASS;
+ }
+ }
if ((pwq->dig_credit >= 0) && (digits > pwq->dig_credit))
digits = pwq->dig_credit;
diff -ruN '--exclude=*.rej' '--exclude=*.orig' a/src/error.c b/src/error.c
--- a/src/error.c 2026-06-24 13:39:51.504839606 +0200
+++ b/src/error.c 2026-06-24 13:45:29.032394205 +0200
@@ -149,6 +149,13 @@
return _("The configuration file is malformed");
case PWQ_ERROR_FATAL_FAILURE:
return _("Fatal failure");
+ case PWQ_ERROR_DISALLOWED_CLASS:
+ if (auxerror) {
+ snprintf(buf, len, _("The %s class of characters is not allowed"), (const char *)auxerror);
+ free(auxerror);
+ return buf;
+ }
+ return _("The password contains disallowed character class");
default:
return _("Unknown error");
}
@@ -188,4 +195,3 @@
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-
diff -ruN '--exclude=*.rej' '--exclude=*.orig' a/src/generate.c b/src/generate.c
--- a/src/generate.c 2026-06-24 13:39:51.504750650 +0200
+++ b/src/generate.c 2026-06-24 13:45:38.556487851 +0200
@@ -95,7 +95,7 @@
}
*offset += bits;
- return low;
+ return low;
}
/* generate a random password according to the settings */
diff -ruN '--exclude=*.rej' '--exclude=*.orig' a/src/pam_pwquality.c b/src/pam_pwquality.c
--- a/src/pam_pwquality.c 2026-06-24 13:39:51.504861183 +0200
+++ b/src/pam_pwquality.c 2026-06-24 13:46:16.791863795 +0200
@@ -88,7 +88,7 @@
} else if (!strncmp(*argv, "try_first_pass", 14)) {
/* for pam_get_authtok, ignore */;
} else if (pwquality_set_option(pwq, *argv)) {
- pam_syslog(pamh, LOG_ERR,
+ pam_syslog(pamh, LOG_ERR,
"pam_parse: unknown or broken option; %s", *argv);
}
}
@@ -213,7 +213,7 @@
if (retval != PAM_SUCCESS || newtoken == NULL) {
if (retval == PAM_AUTHTOK_ERR || newtoken == NULL)
pam_syslog(pamh, LOG_INFO, "user aborted password change");
- else
+ else
pam_syslog(pamh, LOG_ERR, "pam_get_authtok_noverify returned error: %s",
pam_strerror(pamh, retval));
pwquality_free_settings(options.pwq);
@@ -262,7 +262,7 @@
continue;
if (retval == PAM_AUTHTOK_ERR || newtoken == NULL)
pam_syslog(pamh, LOG_INFO, "user aborted password change");
- else
+ else
pam_syslog(pamh, LOG_ERR, "pam_get_authtok_verify returned error: %s",
pam_strerror(pamh, retval));
pwquality_free_settings(options.pwq);
diff -ruN '--exclude=*.rej' '--exclude=*.orig' a/src/pwqprivate.h b/src/pwqprivate.h
--- a/src/pwqprivate.h 2026-06-24 13:39:51.504670235 +0200
+++ b/src/pwqprivate.h 2026-06-24 13:43:52.963449598 +0200
@@ -33,6 +33,7 @@
int local_users_only;
char *bad_words;
char *dict_path;
+ char *allow_classes;
};
struct setting_mapping {
@@ -47,6 +48,7 @@
#define PWQ_DEFAULT_UP_CREDIT 0
#define PWQ_DEFAULT_LOW_CREDIT 0
#define PWQ_DEFAULT_OTH_CREDIT 0
+#define PWQ_DEFAULT_ALLOWCLASSES "ludo"
#ifdef HAVE_CRACK_H
#define PWQ_DEFAULT_DICT_CHECK 1
diff -ruN '--exclude=*.rej' '--exclude=*.orig' a/src/pwquality.conf b/src/pwquality.conf
--- a/src/pwquality.conf 2026-06-24 13:39:51.504930249 +0200
+++ b/src/pwquality.conf 2026-06-24 13:50:46.129512097 +0200
@@ -58,6 +58,10 @@
# The check is enabled if the value is greater than 0 and usercheck is enabled.
# usersubstr = 0
#
+# The allowed classes of characters (digits, uppercase, lowercase, others)
+# to be used in the password.
+# allowclasses = "ludo"
+#
# 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 -ruN '--exclude=*.rej' '--exclude=*.orig' a/src/pwquality.h b/src/pwquality.h
--- a/src/pwquality.h 2026-06-24 13:39:51.504650547 +0200
+++ b/src/pwquality.h 2026-06-24 13:47:07.084358309 +0200
@@ -36,6 +36,7 @@
#define PWQ_SETTING_ENFORCE_ROOT 19
#define PWQ_SETTING_LOCAL_USERS 20
#define PWQ_SETTING_USER_SUBSTR 21
+#define PWQ_SETTING_ALLOW_CLASSES 22
#define PWQ_MAX_ENTROPY_BITS 256
#define PWQ_MIN_ENTROPY_BITS 56
@@ -72,6 +73,7 @@
#define PWQ_ERROR_MAX_CLASS_REPEAT -27
#define PWQ_ERROR_BAD_WORDS -28
#define PWQ_ERROR_MAX_SEQUENCE -29
+#define PWQ_ERROR_DISALLOWED_CLASS -30
typedef struct pwquality_settings pwquality_settings_t;
@@ -135,7 +137,7 @@
* is not returned.
* Not passing the *auxerror into pwquality_strerror() can lead to memory leaks.
* The score depends on PWQ_SETTING_MIN_LENGTH. If it is set higher,
- * the score for the same passwords will be lower. */
+ * the score for the same passwords will be lower. */
int
pwquality_check(pwquality_settings_t *pwq, const char *password,
const char *oldpassword, const char *user, void **auxerror);
diff -ruN '--exclude=*.rej' '--exclude=*.orig' a/src/settings.c b/src/settings.c
--- a/src/settings.c 2026-06-24 13:39:51.504810433 +0200
+++ b/src/settings.c 2026-06-24 13:44:29.312806994 +0200
@@ -26,9 +26,15 @@
{
pwquality_settings_t *pwq;
+ char *allow_classes;
+
pwq = calloc(1, sizeof(*pwq));
- if (!pwq)
+ allow_classes = strdup(PWQ_DEFAULT_ALLOWCLASSES);
+ if (!pwq || !allow_classes) {
+ free(pwq);
+ free(allow_classes);
return NULL;
+ }
pwq->diff_ok = PWQ_DEFAULT_DIFF_OK;
pwq->min_length = PWQ_DEFAULT_MIN_LENGTH;
@@ -43,6 +49,7 @@
pwq->retry_times = PWQ_DEFAULT_RETRY_TIMES;
pwq->enforce_for_root = PWQ_DEFAULT_ENFORCE_ROOT;
pwq->local_users_only = PWQ_DEFAULT_LOCAL_USERS;
+ pwq->allow_classes = allow_classes;
return pwq;
}
@@ -54,6 +61,7 @@
if (pwq) {
free(pwq->dict_path);
free(pwq->bad_words);
+ free(pwq->allow_classes);
free(pwq);
}
}
@@ -79,7 +87,8 @@
{ "dictpath", PWQ_SETTING_DICT_PATH, PWQ_TYPE_STR},
{ "retry", PWQ_SETTING_RETRY_TIMES, PWQ_TYPE_INT},
{ "enforce_for_root", PWQ_SETTING_ENFORCE_ROOT, PWQ_TYPE_SET},
- { "local_users_only", PWQ_SETTING_LOCAL_USERS, PWQ_TYPE_SET}
+ { "local_users_only", PWQ_SETTING_LOCAL_USERS, PWQ_TYPE_SET},
+ { "allowclasses", PWQ_SETTING_ALLOW_CLASSES, PWQ_TYPE_STR}
};
/* set setting name with value */
@@ -399,6 +408,10 @@
free(pwq->dict_path);
pwq->dict_path = dup;
break;
+ case PWQ_SETTING_ALLOW_CLASSES:
+ free(pwq->allow_classes);
+ pwq->allow_classes = dup;
+ break;
default:
free(dup);
return PWQ_ERROR_NON_STR_SETTING;
@@ -490,6 +503,12 @@
*value = NULL;
#endif
break;
+ case PWQ_SETTING_ALLOW_CLASSES:
+ if (pwq->allow_classes)
+ *value = pwq->allow_classes;
+ else
+ *value = PWQ_DEFAULT_ALLOWCLASSES;
+ break;
default:
return PWQ_ERROR_NON_STR_SETTING;
}

View File

@ -1,7 +1,7 @@
Summary: A library for password generation and password quality checking
Name: libpwquality
Version: 1.4.5
Release: 12%{?dist}
Release: 13%{?dist}
URL: https://github.com/libpwquality/libpwquality/
Source0: https://github.com/libpwquality/libpwquality/releases/download/libpwquality-%{version}/libpwquality-%{version}.tar.bz2
@ -10,6 +10,7 @@ Source0: https://github.com/libpwquality/libpwquality/releases/download/libpwqua
# https://bugzilla.redhat.com/2165572
# Upstream PR: https://github.com/libpwquality/libpwquality/pull/74
Patch1: setuptools.patch
Patch2: allowed-character-classes.patch
# The package is BSD licensed with option to relicense as GPLv2+
# - this option is redundant as the BSD license allows that anyway.
@ -24,6 +25,7 @@ BuildRequires: gcc make
BuildRequires: cracklib-devel
BuildRequires: gettext
BuildRequires: pam-devel
BuildRequires: /usr/bin/pod2man
BuildRequires: python3-devel
BuildRequires: python3-setuptools
@ -105,6 +107,10 @@ mkdir %{buildroot}%{_secconfdir}/pwquality.conf.d
%{python3_sitearch}/*.egg-info
%changelog
* Tue Jun 23 2026 Ganna Starovoytova <gstarovo@redhat.com> - 1.4.5-13
- Adds allowing / disabling classes of characters
Resolves: RHEL-78718
* Tue Oct 29 2024 Troy Dawson <tdawson@redhat.com> - 1.4.5-12
- Bump release for October 2024 mass rebuild:
Resolves: RHEL-64018