From fd8ca9aaa55e3745203eacb305afd528942b552d Mon Sep 17 00:00:00 2001 From: Mark Reynolds Date: Jul 24 2018 14:51:25 +0000 Subject: Ticket 49794 - Add pam_pwquality features to password syntax checking Description: Added the following pam_pwquality fartures to DS. We can not add all the features because some of them require that you have the the previous or current password in clear text. New features: - Check password is not in dictionary - Password is not a palindrome - Maximum allowed monotonic sequence characters - Maximum allowed monotonic sequence characters that can be repeated. - Maximum number of consectuve characters from the same class of characters (digits, alphas, specials, etc) - List of words that are not allowed to appear in the new password - List of attributes to check in the user's entry to see if those values are in the new password. https://pagure.io/389-ds-base/issue/49794 Reviewed by: vashirov & spichugi(Thanks!!) --- diff --git a/Makefile.am b/Makefile.am index 16e3f58..4cc0fe7 100644 --- a/Makefile.am +++ b/Makefile.am @@ -178,6 +178,7 @@ NETSNMP_LINK = @netsnmp_lib@ @netsnmp_link@ PAM_LINK = -lpam KERBEROS_LINK = $(kerberos_lib) EVENT_LINK = @event_lib@ +PW_CRACK_LINK = -lcrack LIBSOCKET=@LIBSOCKET@ LIBNSL=@LIBNSL@ @@ -192,7 +193,7 @@ if HPUX AM_LDFLAGS = -lpthread else #AM_LDFLAGS = -Wl,-z,defs -AM_LDFLAGS = $(RUST_LDFLAGS) $(ASAN_CFLAGS) $(MSAN_CFLAGS) $(TSAN_CFLAGS) $(UBSAN_CFLAGS) $(PROFILING_LINKS) $(CLANG_LDFLAGS) +AM_LDFLAGS = $(PW_CRACK_LINK) $(RUST_LDFLAGS) $(ASAN_CFLAGS) $(MSAN_CFLAGS) $(TSAN_CFLAGS) $(UBSAN_CFLAGS) $(PROFILING_LINKS) $(CLANG_LDFLAGS) endif #end hpux # https://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html#Updating-version-info diff --git a/dirsrvtests/tests/suites/password/pwdPolicy_syntax_test.py b/dirsrvtests/tests/suites/password/pwdPolicy_syntax_test.py index 1104ced..1a2a026 100644 --- a/dirsrvtests/tests/suites/password/pwdPolicy_syntax_test.py +++ b/dirsrvtests/tests/suites/password/pwdPolicy_syntax_test.py @@ -41,6 +41,7 @@ def test_user(topology_st): USER_DN, { 'objectClass': 'top account simplesecurityobject'.split(), 'uid': 'user', + 'description': 'd_e_s_c', 'userpassword': PASSWORD }))) @@ -169,6 +170,19 @@ def test_basic(topology_st, test_user, password_policy): 26. Set userPassword to 'password' in cn=config 27. Set userPassword to 'PASSWORD' in cn=config 28. Set passwordMinUppers to 0 in cn=config + 29. Test passwordDictCheck + 30. Test passwordPalindrome + 31. Test passwordMaxSequence for forward number sequence + 32. Test passwordMaxSequence for backward number sequence + 33. Test passwordMaxSequence for forward alpha sequence + 34. Test passwordMaxSequence for backward alpha sequence + 35. Test passwordMaxClassChars for digits + 36. Test passwordMaxClassChars for specials + 37. Test passwordMaxClassChars for lowers + 38. Test passwordMaxClassChars for uppers + 39. Test passwordBadWords using 'redhat' and 'fedora' + 40. Test passwordUserAttrs using description attribute + :expectedresults: 1. passwordMinLength should be successfully set 2. Password should be rejected because length too short @@ -203,6 +217,18 @@ def test_basic(topology_st, test_user, password_policy): it does not contain minimum number of lowercase characters 27. Password should be accepted 28. passwordMinUppers should be successfully set + 29. The passwordDictCheck test succeeds + 30. The passwordPalindrome test succeeds + 31. Test passwordMaxSequence for forward number sequence succeeds + 32. Test passwordMaxSequence for backward number sequence succeeds + 33. Test passwordMaxSequence for forward alpha sequence succeeds + 34. Test passwordMaxSequence for backward alpha sequence succeeds + 35. Test passwordMaxClassChars for digits succeeds + 36. Test passwordMaxClassChars for specials succeeds + 37. Test passwordMaxClassChars for lowers succeeds + 38. Test passwordMaxClassChars for uppers succeeds + 39. The passwordBadWords test succeeds + 40. The passwordUserAttrs test succeeds """ # @@ -235,6 +261,49 @@ def test_basic(topology_st, test_user, password_policy): 'does not contain minimum number of lowercase characters') # Min 8-bits - "ldap" package only accepts ascii strings at the moment + if ds_is_newer('1.4.0.13'): + # Dictionary check + tryPassword(topology_st.standalone, 'passwordDictCheck', 'on', 'on', 'PASSWORD', + '13_#Kad472h', 'Password found in dictionary') + + # Palindromes + tryPassword(topology_st.standalone, 'passwordPalindrome', 'on', 'on', 'Za12_#_21aZ', + '13_#Kad472h', 'Password is palindrome') + + # Sequences + tryPassword(topology_st.standalone, 'passwordMaxSequence', 3, 0, 'Za1_1234', + '13_#Kad472h', 'Max montonic sequence is not allowed') + tryPassword(topology_st.standalone, 'passwordMaxSequence', 3, 0, 'Za1_4321', + '13_#Kad472h', 'Max montonic sequence is not allowed') + tryPassword(topology_st.standalone, 'passwordMaxSequence', 3, 0, 'Za1_abcd', + '13_#Kad472h', 'Max montonic sequence is not allowed') + tryPassword(topology_st.standalone, 'passwordMaxSequence', 3, 0, 'Za1_dcba', + '13_#Kad472h', 'Max montonic sequence is not allowed') + + # Sequence Sets + tryPassword(topology_st.standalone, 'passwordMaxSeqSets', 2, 0, 'Za1_123--123', + '13_#Kad472h', 'Max montonic sequence is not allowed') + + # Max characters in a character class + tryPassword(topology_st.standalone, 'passwordMaxClassChars', 3, 0, 'Za1_9376', + '13_#Kad472h', 'Too may consecutive characters from the same class') + tryPassword(topology_st.standalone, 'passwordMaxClassChars', 3, 0, 'Za1_#$&!', + '13_#Kad472h', 'Too may consecutive characters from the same class') + tryPassword(topology_st.standalone, 'passwordMaxClassChars', 3, 0, 'Za1_ahtf', + '13_#Kad472h', 'Too may consecutive characters from the same class') + tryPassword(topology_st.standalone, 'passwordMaxClassChars', 3, 0, 'Za1_HTSE', + '13_#Kad472h', 'Too may consecutive characters from the same class') + + # Bad words + tryPassword(topology_st.standalone, 'passwordBadWords', 'redhat fedora', 'none', 'Za1_redhat', + '13_#Kad472h', 'Too may consecutive characters from the same class') + tryPassword(topology_st.standalone, 'passwordBadWords', 'redhat fedora', 'none', 'Za1_fedora', + '13_#Kad472h', 'Too may consecutive characters from the same class') + + # User Attributes + tryPassword(topology_st.standalone, 'passwordUserAttributes', 'description', 0, 'Za1_d_e_s_c', + '13_#Kad472h', 'Password found in user entry') + log.info('pwdPolicy tests PASSED') diff --git a/ldap/schema/02common.ldif b/ldap/schema/02common.ldif index 0f8a5f5..8efaf19 100644 --- a/ldap/schema/02common.ldif +++ b/ldap/schema/02common.ldif @@ -68,6 +68,14 @@ attributeTypes: ( 2.16.840.1.113730.3.1.2083 NAME ( 'passwordMinTokenLength' 'pw attributeTypes: ( 2.16.840.1.113730.3.1.2153 NAME ( 'passwordAdminDN' 'pwdAdminDN' ) DESC 'Netscape defined password policy attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE X-ORIGIN 'Netscape Directory Server' ) attributeTypes: ( 2.16.840.1.113730.3.1.2140 NAME ( 'passwordTrackUpdateTime' ) DESC 'Netscape defined password policy attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'Netscape Directory Server' ) attributeTypes: ( 2.16.840.1.113730.3.1.2329 NAME ( 'passwordSendExpiringTime' 'pwdSendExpiringTime' ) DESC 'Netscape defined password policy attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'Netscape Directory Server' ) +attributeTypes: ( 2.16.840.1.113730.3.1.2345 NAME ( 'passwordPalindrome' 'pwdPalindrome' ) DESC '389 Directory Server password policy attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN '389 Directory Server' ) +attributeTypes: ( 2.16.840.1.113730.3.1.2346 NAME ( 'passwordMaxSequence' 'pwdMaxSequence' ) DESC '389 Directory Server password policy attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN '389 Directory Server' ) +attributeTypes: ( 2.16.840.1.113730.3.1.2347 NAME ( 'passwordMaxSeqSets' 'pwdMaxSeqSets' ) DESC '389 Directory Server password policy attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN '389 Directory Server' ) +attributeTypes: ( 2.16.840.1.113730.3.1.2348 NAME ( 'passwordMaxClassChars' 'pwdMaxClassChars' ) DESC '389 Directory Server password policy attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN '389 Directory Server' ) +attributeTypes: ( 2.16.840.1.113730.3.1.2349 NAME ( 'passwordDictCheck' 'pwdDictCheck' ) DESC '389 Directory Server password policy attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN '389 Directory Server' ) +attributeTypes: ( 2.16.840.1.113730.3.1.2350 NAME ( 'passwordDictPath' 'pwdDictPath' ) DESC '389 Directory Server password policy attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN '389 Directory Server' ) +attributeTypes: ( 2.16.840.1.113730.3.1.2351 NAME ( 'passwordUserAttributes' 'pwdUserAttributes' ) DESC '389 Directory Server password policy attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN '389 Directory Server' ) +attributeTypes: ( 2.16.840.1.113730.3.1.2352 NAME ( 'passwordBadWords' 'pwdBadWords' ) DESC '389 Directory Server password policy attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN '389 Directory Server' ) attributeTypes: ( 2.16.840.1.113730.3.1.198 NAME 'memberURL' DESC 'Netscape defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 X-ORIGIN 'Netscape Directory Server' ) attributeTypes: ( 2.16.840.1.113730.3.1.199 NAME 'memberCertificateDescription' DESC 'Netscape defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 X-ORIGIN 'Netscape Directory Server' ) attributeTypes: ( 2.16.840.1.113730.3.1.207 NAME 'vlvBase' DESC 'Netscape defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 X-ORIGIN 'Netscape Directory Server' ) diff --git a/ldap/servers/slapd/libglobs.c b/ldap/servers/slapd/libglobs.c index 2c1e905..9ecbb36 100644 --- a/ldap/servers/slapd/libglobs.c +++ b/ldap/servers/slapd/libglobs.c @@ -194,6 +194,8 @@ slapi_onoff_t init_pw_track_update_time; slapi_onoff_t init_pw_change; slapi_onoff_t init_pw_exp; slapi_onoff_t init_pw_send_expiring; +slapi_onoff_t init_pw_palindrome; +slapi_onoff_t init_pw_dict_check; slapi_onoff_t init_allow_hashed_pw; slapi_onoff_t init_pw_syntax; slapi_onoff_t init_schemacheck; @@ -499,6 +501,47 @@ static struct config_get_and_set NULL, 0, (void **)&global_slapdFrontendConfig.pw_policy.pw_mintokenlength, CONFIG_INT, NULL, SLAPD_DEFAULT_PW_MINTOKENLENGTH_STR}, + + /* Password palindrome */ + {CONFIG_PW_PALINDROME_ATTRIBUTE, config_set_pw_palindrome, + NULL, 0, + (void **)&global_slapdFrontendConfig.pw_policy.pw_palindrome, + CONFIG_ON_OFF, NULL, &init_pw_palindrome}, + /* password dictionary check */ + {CONFIG_PW_CHECK_DICT_ATTRIBUTE, config_set_pw_dict_check, + NULL, 0, + (void **)&global_slapdFrontendConfig.pw_policy.pw_check_dict, + CONFIG_ON_OFF, NULL, &init_pw_dict_check}, + /* password dictionary path */ + {CONFIG_PW_DICT_PATH_ATTRIBUTE, config_set_pw_dict_path, + NULL, 0, + (void **)&global_slapdFrontendConfig.pw_policy.pw_dict_path, + CONFIG_STRING, NULL, ""}, + /* password user attr check list */ + {CONFIG_PW_USERATTRS_ATTRIBUTE, config_set_pw_user_attrs, + NULL, 0, + (void **)&global_slapdFrontendConfig.pw_policy.pw_cmp_attrs, + CONFIG_CHARRAY, NULL, NULL}, + /* password bad work list */ + {CONFIG_PW_BAD_WORDS_ATTRIBUTE, config_set_pw_bad_words, + NULL, 0, + (void **)&global_slapdFrontendConfig.pw_policy.pw_bad_words, + CONFIG_CHARRAY, NULL, NULL}, + /* password max sequence */ + {CONFIG_PW_MAX_SEQ_ATTRIBUTE, config_set_pw_max_seq, + NULL, 0, + (void **)&global_slapdFrontendConfig.pw_policy.pw_max_seq, + CONFIG_INT, NULL, SLAPD_DEFAULT_PW_MAX_SEQ_ATTRIBUTE_STR}, + /* Max sequence sets */ + {CONFIG_PW_MAX_SEQ_SETS_ATTRIBUTE, config_set_pw_max_seq_sets, + NULL, 0, + (void **)&global_slapdFrontendConfig.pw_policy.pw_seq_char_sets, + CONFIG_INT, NULL, SLAPD_DEFAULT_PW_MAX_SEQ_SETS_ATTRIBUTE_STR}, + /* password max repeated characters per class */ + {CONFIG_PW_MAX_CLASS_CHARS_ATTRIBUTE, config_set_pw_max_class_repeats, + NULL, 0, + (void **)&global_slapdFrontendConfig.pw_policy.pw_max_class_repeats, + CONFIG_INT, NULL, SLAPD_DEFAULT_PW_MAX_CLASS_CHARS_ATTRIBUTE_STR}, {CONFIG_ERRORLOG_ATTRIBUTE, config_set_errorlog, NULL, 0, (void **)&global_slapdFrontendConfig.errorlog, @@ -1467,6 +1510,8 @@ pwpolicy_fe_init_onoff(passwdPolicy *pw_policy) init_pw_unlock = pw_policy->pw_unlock; init_pw_is_legacy = pw_policy->pw_is_legacy; init_pw_track_update_time = pw_policy->pw_track_update_time; + init_pw_palindrome = pw_policy->pw_palindrome; + init_pw_dict_check = pw_policy->pw_check_dict; } void @@ -2751,6 +2796,220 @@ config_set_pw_syntax(const char *attrname, char *value, char *errorbuf, int appl return retVal; } +int32_t +config_set_pw_palindrome(const char *attrname, char *value, char *errorbuf, int apply) +{ + int32_t retVal = LDAP_SUCCESS; + slapdFrontendConfig_t *slapdFrontendConfig = getFrontendConfig(); + + retVal = config_set_onoff(attrname, + value, + &(slapdFrontendConfig->pw_policy.pw_palindrome), + errorbuf, + apply); + + return retVal; +} + +int32_t +config_set_pw_dict_check(const char *attrname, char *value, char *errorbuf, int apply) +{ + int32_t retVal = LDAP_SUCCESS; + slapdFrontendConfig_t *slapdFrontendConfig = getFrontendConfig(); + + retVal = config_set_onoff(attrname, + value, + &(slapdFrontendConfig->pw_policy.pw_check_dict), + errorbuf, + apply); + + return retVal; +} + +int32_t +config_set_pw_dict_path(const char *attrname, char *value, char *errorbuf, int apply) +{ + int retVal = LDAP_SUCCESS; + slapdFrontendConfig_t *slapdFrontendConfig = getFrontendConfig(); + + if (config_value_is_null(attrname, value, errorbuf, 0)) { + value = NULL; + } else { + /* We have a value, do some basic checks */ + if (value[0] != '/') { + /* Not a path - error */ + slapi_create_errormsg(errorbuf, SLAPI_DSE_RETURNTEXT_SIZE, + "password dictionary path \"%s\" is invalid.", value); + retVal = LDAP_OPERATIONS_ERROR; + return retVal; + } + } + + if (apply) { + CFG_LOCK_WRITE(slapdFrontendConfig); + slapi_ch_free_string(&slapdFrontendConfig->pw_policy.pw_dict_path); + slapdFrontendConfig->pw_policy.pw_dict_path = slapi_ch_strdup(value); + CFG_UNLOCK_WRITE(slapdFrontendConfig); + } + return retVal; +} + +int32_t +config_set_pw_user_attrs(const char *attrname, char *value, char *errorbuf, int apply) +{ + int retVal = LDAP_SUCCESS; + char **attrs = NULL; + slapdFrontendConfig_t *slapdFrontendConfig = getFrontendConfig(); + + if (config_value_is_null(attrname, value, errorbuf, 0)) { + value = NULL; + } + if (apply) { + if (value) { + /* Take list of attributes and break it up into a char array */ + char *attr = NULL; + char *token = NULL; + char *next = NULL; + + token = slapi_ch_strdup(value); + for (attr = ldap_utf8strtok_r(token, " ", &next); attr != NULL; + attr = ldap_utf8strtok_r(NULL, " ", &next)) + { + slapi_ch_array_add(&attrs, slapi_ch_strdup(attr)); + } + slapi_ch_free_string(&token); + } + + CFG_LOCK_WRITE(slapdFrontendConfig); + slapi_ch_array_free(slapdFrontendConfig->pw_policy.pw_cmp_attrs); + slapdFrontendConfig->pw_policy.pw_cmp_attrs = attrs; + CFG_UNLOCK_WRITE(slapdFrontendConfig); + } + return retVal; +} + +int32_t +config_set_pw_bad_words(const char *attrname, char *value, char *errorbuf, int apply) +{ + int retVal = LDAP_SUCCESS; + char **words = NULL; + slapdFrontendConfig_t *slapdFrontendConfig = getFrontendConfig(); + + if (config_value_is_null(attrname, value, errorbuf, 0)) { + value = NULL; + } + if (apply) { + if (value) { + /* Take list of attributes and break it up into a char array */ + char *word = NULL; + char *token = NULL; + char *next = NULL; + + token = slapi_ch_strdup(value); + for (word = ldap_utf8strtok_r(token, " ", &next); word != NULL; + word = ldap_utf8strtok_r(NULL, " ", &next)) + { + slapi_ch_array_add(&words, slapi_ch_strdup(word)); + } + slapi_ch_free_string(&token); + } + + CFG_LOCK_WRITE(slapdFrontendConfig); + slapi_ch_array_free(slapdFrontendConfig->pw_policy.pw_bad_words); + slapdFrontendConfig->pw_policy.pw_bad_words = words; + CFG_UNLOCK_WRITE(slapdFrontendConfig); + } + return retVal; +} + +int32_t +config_set_pw_max_seq(const char *attrname, char *value, char *errorbuf, int apply) +{ + int retVal = LDAP_SUCCESS; + int32_t max = 0; + char *endp = NULL; + slapdFrontendConfig_t *slapdFrontendConfig = getFrontendConfig(); + + if (config_value_is_null(attrname, value, errorbuf, 0)) { + max = 0; + } else { + errno = 0; + max = (int32_t)strtol(value, &endp, 10); + + if (*endp != '\0' || errno == ERANGE || max < 0 || max > 10) { + slapi_create_errormsg(errorbuf, SLAPI_DSE_RETURNTEXT_SIZE, + "password maximum sequence \"%s\" is invalid. The range is from 0 to 10.", value); + retVal = LDAP_OPERATIONS_ERROR; + return retVal; + } + } + if (apply) { + CFG_LOCK_WRITE(slapdFrontendConfig); + slapdFrontendConfig->pw_policy.pw_max_seq = max; + CFG_UNLOCK_WRITE(slapdFrontendConfig); + } + return retVal; +} + + +int32_t +config_set_pw_max_seq_sets(const char *attrname, char *value, char *errorbuf, int apply) +{ + int retVal = LDAP_SUCCESS; + int32_t max = 0; + char *endp = NULL; + slapdFrontendConfig_t *slapdFrontendConfig = getFrontendConfig(); + + if (config_value_is_null(attrname, value, errorbuf, 0)) { + max = 0; + } else { + errno = 0; + max = (int32_t)strtol(value, &endp, 10); + + if (*endp != '\0' || errno == ERANGE || max < 0 || max > 10) { + slapi_create_errormsg(errorbuf, SLAPI_DSE_RETURNTEXT_SIZE, + "password maximum sequence sets \"%s\" is invalid. The range is from 0 to 10.", value); + retVal = LDAP_OPERATIONS_ERROR; + return retVal; + } + } + if (apply) { + CFG_LOCK_WRITE(slapdFrontendConfig); + slapdFrontendConfig->pw_policy.pw_seq_char_sets = max; + CFG_UNLOCK_WRITE(slapdFrontendConfig); + } + return retVal; +} + +int32_t +config_set_pw_max_class_repeats(const char *attrname, char *value, char *errorbuf, int apply) +{ + int retVal = LDAP_SUCCESS; + int32_t max = 0; + char *endp = NULL; + slapdFrontendConfig_t *slapdFrontendConfig = getFrontendConfig(); + + if (config_value_is_null(attrname, value, errorbuf, 0)) { + max = 0; + } else { + errno = 0; + max = (int32_t)strtol(value, &endp, 10); + + if (*endp != '\0' || errno == ERANGE || max < 0 || max > 1024) { + slapi_create_errormsg(errorbuf, SLAPI_DSE_RETURNTEXT_SIZE, + "password maximum repated characters per characters class \"%s\" is invalid. " + "The range is from 0 to 1024.", value); + retVal = LDAP_OPERATIONS_ERROR; + return retVal; + } + } + if (apply) { + CFG_LOCK_WRITE(slapdFrontendConfig); + slapdFrontendConfig->pw_policy.pw_max_class_repeats = max; + CFG_UNLOCK_WRITE(slapdFrontendConfig); + } + return retVal; +} int config_set_pw_minlength(const char *attrname, char *value, char *errorbuf, int apply) diff --git a/ldap/servers/slapd/proto-slap.h b/ldap/servers/slapd/proto-slap.h index 7a429b2..ca946fb 100644 --- a/ldap/servers/slapd/proto-slap.h +++ b/ldap/servers/slapd/proto-slap.h @@ -299,6 +299,16 @@ int config_set_pwpolicy_local(const char *attrname, char *value, char *errorbuf, int config_set_allow_hashed_pw(const char *attrname, char *value, char *errorbuf, int apply); int config_set_pwpolicy_inherit_global(const char *attrname, char *value, char *errorbuf, int apply); int config_set_pw_syntax(const char *attrname, char *value, char *errorbuf, int apply); + +int32_t config_set_pw_palindrome(const char *attrname, char *value, char *errorbuf, int apply); +int32_t config_set_pw_dict_check(const char *attrname, char *value, char *errorbuf, int apply); +int32_t config_set_pw_dict_path(const char *attrname, char *value, char *errorbuf, int apply); +int32_t config_set_pw_user_attrs(const char *attrname, char *value, char *errorbuf, int apply); +int32_t config_set_pw_bad_words(const char *attrname, char *value, char *errorbuf, int apply); +int32_t config_set_pw_max_seq_sets(const char *attrname, char *value, char *errorbuf, int apply); +int32_t config_set_pw_max_seq(const char *attrname, char *value, char *errorbuf, int apply); +int32_t config_set_pw_max_class_repeats(const char *attrname, char *value, char *errorbuf, int apply); + int config_set_pw_minlength(const char *attrname, char *value, char *errorbuf, int apply); int config_set_pw_mindigits(const char *attrname, char *value, char *errorbuf, int apply); int config_set_pw_minalphas(const char *attrname, char *value, char *errorbuf, int apply); diff --git a/ldap/servers/slapd/pw.c b/ldap/servers/slapd/pw.c index 9b5e03b..38d9e06 100644 --- a/ldap/servers/slapd/pw.c +++ b/ldap/servers/slapd/pw.c @@ -1,6 +1,6 @@ /** BEGIN COPYRIGHT BLOCK * Copyright (C) 2001 Sun Microsystems, Inc. Used by permission. - * Copyright (C) 2009 Red Hat, Inc. + * Copyright (C) 2018 Red Hat, Inc. * Copyright (C) 2009 Hewlett-Packard Development Company, L.P. * All rights reserved. * @@ -12,6 +12,63 @@ * See LICENSE for details. * END COPYRIGHT BLOCK **/ +/* Copyright notice below for using portions of pam_pwquality source code + * (pam_cracklib.c) */ +/* + * Copyright (c) Cristian Gafton , 1996. + * All rights reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * ALTERNATIVELY, this product may be distributed under the terms of + * the GNU Public License, in which case the provisions of the GPL are + * required INSTEAD OF the above restrictions. (This clause is + * necessary due to a potential bad interaction between the GPL and + * the restrictions contained in a BSD-style copyright.) + * + * THIS SOFTWARE IS PROVIDED `AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The following copyright was appended for the long password support + * added with the libpam 0.58 release: + * + * Modificaton Copyright (c) Philip W. Dalrymple III + * 1997. All rights reserved + * + * THE MODIFICATION THAT PROVIDES SUPPORT FOR LONG PASSWORD TYPE CHECKING TO + * THIS SOFTWARE IS PROVIDED `AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + #ifdef HAVE_CONFIG_H #include #endif @@ -25,12 +82,12 @@ #include #include #include -#if defined(USE_MOZLDAP) -#define LDAP_MOD_OP (0x0007) -#endif /* USE_MOZLDAP */ - +#include #include "slap.h" +#ifndef CRACKLIB_DICTS +#define CRACKLIB_DICTS NULL +#endif #define DENY_PW_CHANGE_ACI "(targetattr = \"userPassword\") ( version 3.0; acl \"disallow_pw_change_aci\"; deny (write ) userdn = \"ldap:///self\";)" #define GENERALIZED_TIME_LENGTH 15 @@ -769,6 +826,156 @@ check_pw_minage(Slapi_PBlock *pb, const Slapi_DN *sdn, struct berval **vals __at return (0); } + +/* pam_pwquality functions */ +static int +palindrome(const char *new) +{ + int i, j; + + i = strlen(new); + for (j = 0;j < i;j++) + if (new[i - j - 1] != new[j]) + return 0; + + return 1; +} + +static int +pw_sequence_sets(const char *new, int32_t max_seq, int check_sets) +{ + char c; + int i; + int sequp = 1; + int seqdown = 1; + + if (new[0] == '\0') + return 0; + + for (i = 1; new[i]; i++) { + c = new[i-1]; + if (new[i] == c+1) { + ++sequp; + if (sequp > max_seq) { + if (check_sets) { + /* remember this seq, can call pw_sequence on remaining password */ + char *remaining = slapi_ch_smprintf("%s", new + i); + char token[11]; + + memcpy(token, new + (i - max_seq), max_seq); + if (strstr(remaining, token)) { + /* we have a duplicate set */ + slapi_ch_free_string(&remaining); + return 1; + } + slapi_ch_free_string(&remaining); + } else { + return 1; + } + } + seqdown = 1; + } else if (new[i] == c-1) { + ++seqdown; + if (seqdown > max_seq) { + if (check_sets) { + /* remember this seq, so we can check if it occurs again */ + char *remaining = slapi_ch_smprintf("%s", new + i); + char token[11] = {0}; + + memcpy(token, new + (i - max_seq), max_seq); + if (strstr(remaining, token)) { + /* we have a duplicate set */ + slapi_ch_free_string(&remaining); + return 1; + } + slapi_ch_free_string(&remaining); + + } else { + return 1; + } + } + sequp = 1; + } else { + sequp = 1; + seqdown = 1; + } + } + return 0; +} + +static int +pw_sequence(const char *new, int32_t max_seq) +{ + return pw_sequence_sets(new, max_seq, 0); +} + +static int +pw_max_class_repeats(const char *new, int32_t max_repeats) +{ + int digits = 0; + int uppers = 0; + int lowers = 0; + int others = 0; + int i; + enum { NONE, DIGIT, UCASE, LCASE, OTHER } prevclass = NONE; + int sameclass = 0; + + for (i = 0; new[i]; i++) { + if (isdigit(new[i])) { + digits++; + if (prevclass != DIGIT) { + prevclass = DIGIT; + sameclass = 1; + } else { + sameclass++; + } + } else if (isupper (new[i])) { + uppers++; + if (prevclass != UCASE) { + prevclass = UCASE; + sameclass = 1; + } else { + sameclass++; + } + } else if (islower (new[i])) { + lowers++; + if (prevclass != LCASE) { + prevclass = LCASE; + sameclass = 1; + } else { + sameclass++; + } + } else { + others++; + if (prevclass != OTHER) { + prevclass = OTHER; + sameclass = 1; + } else { + sameclass++; + } + } + if (sameclass > max_repeats) { + return 1; + } + } + return 0; +} + +static void +report_pw_violation(Slapi_PBlock *pb, int pwresponse_req, char *fmt, ...) +{ + char errormsg[SLAPI_DSE_RETURNTEXT_SIZE] = {0}; + va_list msg; + + va_start(msg, fmt); + PR_vsnprintf(errormsg, SLAPI_DSE_RETURNTEXT_SIZE - 1, fmt, msg); + if (pwresponse_req == 1) { + slapi_pwpolicy_make_response_control(pb, -1, -1, LDAP_PWPOLICY_INVALIDPWDSYNTAX); + } + pw_send_ldap_result(pb, LDAP_CONSTRAINT_VIOLATION, NULL, errormsg, 0, NULL); + va_end(msg); +} + /* check_pw_syntax is called before add or modify operation on userpassword attribute*/ int @@ -835,12 +1042,9 @@ check_pw_syntax_ext(Slapi_PBlock *pb, const Slapi_DN *sdn, Slapi_Value **vals, c if (slapi_is_encoded((char *)slapi_value_get_string(vals[i]))) { if (!is_replication && !config_get_allow_hashed_pw() && ((internal_op && pb_conn && !slapi_dn_isroot(pb_conn->c_dn)) || - (!internal_op && !pw_is_pwp_admin(pb, pwpolicy)))) { - PR_snprintf(errormsg, sizeof(errormsg) - 1, "invalid password syntax - passwords with storage scheme are not allowed"); - if (pwresponse_req == 1) { - slapi_pwpolicy_make_response_control(pb, -1, -1, LDAP_PWPOLICY_INVALIDPWDSYNTAX); - } - pw_send_ldap_result(pb, LDAP_CONSTRAINT_VIOLATION, NULL, errormsg, 0, NULL); + (!internal_op && !pw_is_pwp_admin(pb, pwpolicy)))) + { + report_pw_violation(pb, pwresponse_req, "invalid password syntax - passwords with storage scheme are not allowed"); return (1); } else { /* We want to skip syntax checking since this is a pre-hashed password */ @@ -862,21 +1066,71 @@ check_pw_syntax_ext(Slapi_PBlock *pb, const Slapi_DN *sdn, Slapi_Value **vals, c int max_repeated = 0; int num_categories = 0; - /* check for the minimum password length */ - if (pwpolicy->pw_minlength > - (int)ldap_utf8characters((char *)slapi_value_get_string(vals[i]))) { - PR_snprintf(errormsg, sizeof(errormsg) - 1, "invalid password syntax - password must be at least %d characters long", - pwpolicy->pw_minlength); - if (pwresponse_req == 1) { - slapi_pwpolicy_make_response_control(pb, -1, -1, - LDAP_PWPOLICY_PWDTOOSHORT); + pwd = (char *)slapi_value_get_string(vals[i]); + + /* Check dictionary */ + if (pwpolicy->pw_check_dict) { + const char *crack_msg; + if ((crack_msg = FascistCheck(pwd, pwpolicy->pw_dict_path))) { + report_pw_violation(pb, pwresponse_req, "Password failed dictionary check: %s", crack_msg); + return (1); } - pw_send_ldap_result(pb, LDAP_CONSTRAINT_VIOLATION, NULL, errormsg, 0, NULL); + } + + /* check palindrome */ + if (pwpolicy->pw_palindrome) { + if (palindrome(pwd)) { + report_pw_violation(pb, pwresponse_req, "Password is a palindrome"); + return (1); + } + } + + /* Check for bad words */ + if (pwpolicy->pw_bad_words) { + for (size_t b = 0; pwpolicy->pw_bad_words && pwpolicy->pw_bad_words[b]; b++) { + if (strcasestr(pwd, pwpolicy->pw_bad_words[b])) { + report_pw_violation(pb, pwresponse_req, "Password contains a restricted word"); + return (1); + } + } + } + + /* Check for sequences */ + if (pwpolicy->pw_max_seq) { + if (pw_sequence(pwd, pwpolicy->pw_max_seq)) { + report_pw_violation(pb, pwresponse_req, "Password contains a monotonic sequence"); + PR_snprintf(errormsg, sizeof(errormsg) - 1, "Password contains a monotonic sequence"); + + return (1); + } + } + + /* Check for sets of sequences */ + if (pwpolicy->pw_seq_char_sets) { + if (pw_sequence_sets(pwd, pwpolicy->pw_seq_char_sets, 1)){ + report_pw_violation(pb, pwresponse_req, "Password contains repeated identical sequences"); + return (1); + } + } + + /* Check for max repeated characters from the same class */ + if (pwpolicy->pw_max_class_repeats) { + if (pw_max_class_repeats(pwd, pwpolicy->pw_max_class_repeats)){ + report_pw_violation(pb, pwresponse_req, + "Password contains too many repeated characters from the same character class"); + return (1); + } + } + + /* check for the minimum password length */ + if (pwpolicy->pw_minlength > (int)ldap_utf8characters((char *)pwd)) { + report_pw_violation(pb, pwresponse_req, + "invalid password syntax - password must be at least %d characters long", + pwpolicy->pw_minlength); return (1); } /* check character types */ - pwd = (char *)slapi_value_get_string(vals[i]); p = pwd; while (p && *p) { if (ldap_utf8isdigit(p)) { @@ -1066,6 +1320,17 @@ check_pw_syntax_ext(Slapi_PBlock *pb, const Slapi_DN *sdn, Slapi_Value **vals, c return 1; } + /* Check user attributes */ + if (pwpolicy->pw_cmp_attrs) { + for (size_t a = 0; pwpolicy->pw_cmp_attrs && pwpolicy->pw_cmp_attrs[a]; a++) { + if (check_trivial_words(pb, e, vals, pwpolicy->pw_cmp_attrs[a], pwpolicy->pw_mintokenlength, smods) == 1 ){ + if (mod_op) { + slapi_entry_free(e); + } + return 1; + } + } + } } if (mod_op) { @@ -1947,6 +2212,64 @@ new_passwdPolicy(Slapi_PBlock *pb, const char *dn) pwdpolicy->pw_admin = slapi_sdn_new_dn_byval(slapi_value_get_string(*sval)); pw_get_admin_users(pwdpolicy); } + } else if (!strcasecmp(attr_name, "passwordPalindrome")) { + if ((sval = attr_get_present_values(attr))) { + pwdpolicy->pw_palindrome = + pw_boolean_str2value(slapi_value_get_string(*sval)); + } + } else if (!strcasecmp(attr_name, "passwordDictCheck")) { + if ((sval = attr_get_present_values(attr))) { + pwdpolicy->pw_check_dict = + pw_boolean_str2value(slapi_value_get_string(*sval)); + } + } else if (!strcasecmp(attr_name, "passwordUserAttributes")) { + if ((sval = attr_get_present_values(attr))) { + char **attrs = NULL; + char *attr = NULL; + char *token = NULL; + char *next = NULL; + + token = slapi_ch_strdup(slapi_value_get_string(*sval)); + for (attr = ldap_utf8strtok_r(token, " ", &next); attr != NULL; + attr = ldap_utf8strtok_r(NULL, " ", &next)) + { + slapi_ch_array_add(&attrs, slapi_ch_strdup(attr)); + } + slapi_ch_free_string(&token); + pwdpolicy->pw_cmp_attrs = attrs; + } + } else if (!strcasecmp(attr_name, "passwordBadWords")) { + if ((sval = attr_get_present_values(attr))) { + char **words = NULL; + char *word = NULL; + char *token = NULL; + char *next = NULL; + + token = slapi_ch_strdup(slapi_value_get_string(*sval)); + for (word = ldap_utf8strtok_r(token, " ", &next); word != NULL; + word = ldap_utf8strtok_r(NULL, " ", &next)) + { + slapi_ch_array_add(&words, slapi_ch_strdup(word)); + } + slapi_ch_free_string(&token); + pwdpolicy->pw_bad_words = words; + } + } else if (!strcasecmp(attr_name, "passwordMaxSequence")) { + if ((sval = attr_get_present_values(attr))) { + pwdpolicy->pw_max_seq = slapi_value_get_int(*sval); + } + } else if (!strcasecmp(attr_name, "passwordMaxSeqSets")) { + if ((sval = attr_get_present_values(attr))) { + pwdpolicy->pw_seq_char_sets = slapi_value_get_int(*sval); + } + } else if (!strcasecmp(attr_name, "passwordMaxClassChars")) { + if ((sval = attr_get_present_values(attr))) { + pwdpolicy->pw_max_class_repeats = slapi_value_get_int(*sval); + } + } else if (!strcasecmp(attr_name, "passwordDictPath")) { + if ((sval = attr_get_present_values(attr))) { + pwdpolicy->pw_dict_path = (char *)slapi_value_get_string(*sval); + } } } /* end of for() loop */ if (pw_entry) { @@ -2913,3 +3236,4 @@ add_shadow_ext_password_attrs(Slapi_PBlock *pb, Slapi_Entry **e) slapi_log_err(SLAPI_LOG_TRACE, "add_shadow_ext_password_attrs", "<=\n"); return rc; } + diff --git a/ldap/servers/slapd/slap.h b/ldap/servers/slapd/slap.h index 3ddab45..163cb19 100644 --- a/ldap/servers/slapd/slap.h +++ b/ldap/servers/slapd/slap.h @@ -397,6 +397,14 @@ typedef void (*VFPV)(); /* takes undefined arguments */ #define SLAPD_DEFAULT_PW_LOCKDURATION 3600 #define SLAPD_DEFAULT_PW_LOCKDURATION_STR "3600" +#define SLAPD_DEFAULT_PW_MAX_SEQ_ATTRIBUTE 0 +#define SLAPD_DEFAULT_PW_MAX_SEQ_ATTRIBUTE_STR "0" +#define SLAPD_DEFAULT_PW_MAX_SEQ_SETS_ATTRIBUTE 0 +#define SLAPD_DEFAULT_PW_MAX_SEQ_SETS_ATTRIBUTE_STR "0" +#define SLAPD_DEFAULT_PW_MAX_CLASS_CHARS_ATTRIBUTE 0 +#define SLAPD_DEFAULT_PW_MAX_CLASS_CHARS_ATTRIBUTE_STR "0" + + /* Default password values. */ /* ================ END CONFIGURATION DEFAULTS ============================ */ @@ -1752,16 +1760,33 @@ typedef struct passwordpolicyarray slapi_onoff_t pw_change; /* 1 - indicates that users are allowed to change the pwd */ slapi_onoff_t pw_must_change; /* 1 - indicates that users must change pwd upon reset */ slapi_onoff_t pw_syntax; - int pw_minlength; - int pw_mindigits; - int pw_minalphas; - int pw_minuppers; - int pw_minlowers; - int pw_minspecials; - int pw_min8bit; - int pw_maxrepeats; - int pw_mincategories; - int pw_mintokenlength; + int32_t pw_minlength; + int32_t pw_mindigits; + int32_t pw_minalphas; + int32_t pw_minuppers; + int32_t pw_minlowers; + int32_t pw_minspecials; + int32_t pw_min8bit; + int32_t pw_maxrepeats; + int32_t pw_mincategories; + int32_t pw_mintokenlength; + + slapi_onoff_t pw_palindrome; /* Reject passwords that are palindromes */ + int32_t pw_max_seq; /* max number of monotonic sequences: 2345, lmnop */ + int32_t pw_seq_char_sets; /* max number of identical monotonic sequences that are + * allowed multiple times. For example: "az12of12", if + * value is set to 2, meaning sequence of two + * characters, then this password would be blocked. If + * it's set to 3, then the password is allowed. */ + int32_t pw_max_class_repeats; /* The maximum number of consecutive characters from + the same character class. */ + slapi_onoff_t pw_check_dict; + char *pw_dict_path; /* custom dictionary */ + char **pw_cmp_attrs; /* Space-separated list of attributes to see if the + attribute values (and reversed values) in the entry + are contained in the new password. */ + char **pw_bad_words; /* Space-separated list of words to reject */ + slapi_onoff_t pw_exp; slapi_onoff_t pw_send_expiring; time_t pw_maxage; @@ -2109,6 +2134,14 @@ typedef struct _slapdEntryPoints #define CONFIG_PW_MAXREPEATS_ATTRIBUTE "passwordMaxRepeats" #define CONFIG_PW_MINCATEGORIES_ATTRIBUTE "passwordMinCategories" #define CONFIG_PW_MINTOKENLENGTH_ATTRIBUTE "passwordMinTokenLength" +#define CONFIG_PW_PALINDROME_ATTRIBUTE "passwordPalindrome" +#define CONFIG_PW_MAX_SEQ_ATTRIBUTE "passwordMaxSequence" +#define CONFIG_PW_MAX_SEQ_SETS_ATTRIBUTE "passwordMaxSeqSets" +#define CONFIG_PW_MAX_CLASS_CHARS_ATTRIBUTE "passwordMaxClassChars" +#define CONFIG_PW_CHECK_DICT_ATTRIBUTE "passwordDictCheck" +#define CONFIG_PW_DICT_PATH_ATTRIBUTE "passwordDictPath" +#define CONFIG_PW_USERATTRS_ATTRIBUTE "passwordUserAttributes" +#define CONFIG_PW_BAD_WORDS_ATTRIBUTE "passwordBadWords" #define CONFIG_PW_EXP_ATTRIBUTE "passwordExp" #define CONFIG_PW_MAXAGE_ATTRIBUTE "passwordMaxAge" #define CONFIG_PW_MINAGE_ATTRIBUTE "passwordMinAge" diff --git a/rpm/389-ds-base.spec.in b/rpm/389-ds-base.spec.in index 99fdabd..2214da2 100644 --- a/rpm/389-ds-base.spec.in +++ b/rpm/389-ds-base.spec.in @@ -73,6 +73,7 @@ BuildRequires: cyrus-sasl-devel BuildRequires: icu BuildRequires: libicu-devel BuildRequires: pcre-devel +BuildRequires: cracklib-devel %if %{use_clang} BuildRequires: libatomic BuildRequires: clang