diff --git a/SOURCES/0034-Issue-4480-Unexpected-info-returned-to-ldap-request-.patch b/SOURCES/0034-Issue-4480-Unexpected-info-returned-to-ldap-request-.patch new file mode 100644 index 0000000..3226fd5 --- /dev/null +++ b/SOURCES/0034-Issue-4480-Unexpected-info-returned-to-ldap-request-.patch @@ -0,0 +1,95 @@ +From 340b81a59cee365e7300e57c1ca5f4866373954c Mon Sep 17 00:00:00 2001 +From: tbordaz +Date: Wed, 16 Dec 2020 16:30:28 +0100 +Subject: [PATCH 1/4] Issue 4480 - Unexpected info returned to ldap request + (#4491) + +Bug description: + If the bind entry does not exist, the bind result info + reports that 'No such entry'. It should not give any + information if the target entry exists or not + +Fix description: + Does not return any additional information during a bind + +relates: https://github.com/389ds/389-ds-base/issues/4480 + +Reviewed by: William Brown, Viktor Ashirov, Mark Reynolds (thank you all) + +Platforms tested: F31 +--- + dirsrvtests/tests/suites/basic/basic_test.py | 30 ++++++++++++++++++++ + ldap/servers/slapd/back-ldbm/ldbm_config.c | 2 +- + ldap/servers/slapd/result.c | 2 +- + 3 files changed, 32 insertions(+), 2 deletions(-) + +diff --git a/dirsrvtests/tests/suites/basic/basic_test.py b/dirsrvtests/tests/suites/basic/basic_test.py +index 120207321..e9afa1e7e 100644 +--- a/dirsrvtests/tests/suites/basic/basic_test.py ++++ b/dirsrvtests/tests/suites/basic/basic_test.py +@@ -1400,6 +1400,36 @@ def test_dscreate_multiple_dashes_name(dscreate_long_instance): + assert not dscreate_long_instance.exists() + + ++def test_bind_invalid_entry(topology_st): ++ """Test the failing bind does not return information about the entry ++ ++ :id: 5cd9b083-eea6-426b-84ca-83c26fc49a6f ++ ++ :setup: Standalone instance ++ ++ :steps: ++ 1: bind as non existing entry ++ 2: check that bind info does not report 'No such entry' ++ ++ :expectedresults: ++ 1: pass ++ 2: pass ++ """ ++ ++ topology_st.standalone.restart() ++ INVALID_ENTRY="cn=foooo,%s" % DEFAULT_SUFFIX ++ try: ++ topology_st.standalone.simple_bind_s(INVALID_ENTRY, PASSWORD) ++ except ldap.LDAPError as e: ++ log.info('test_bind_invalid_entry: Failed to bind as %s (expected)' % INVALID_ENTRY) ++ log.info('exception description: ' + e.args[0]['desc']) ++ if 'info' in e.args[0]: ++ log.info('exception info: ' + e.args[0]['info']) ++ assert e.args[0]['desc'] == 'Invalid credentials' ++ assert 'info' not in e.args[0] ++ pass ++ ++ log.info('test_bind_invalid_entry: PASSED') + + if __name__ == '__main__': + # Run isolated +diff --git a/ldap/servers/slapd/back-ldbm/ldbm_config.c b/ldap/servers/slapd/back-ldbm/ldbm_config.c +index 88c186359..dee5fc088 100644 +--- a/ldap/servers/slapd/back-ldbm/ldbm_config.c ++++ b/ldap/servers/slapd/back-ldbm/ldbm_config.c +@@ -1266,7 +1266,7 @@ ldbm_config_search_entry_callback(Slapi_PBlock *pb __attribute__((unused)), + if (attrs) { + for (size_t i = 0; attrs[i]; i++) { + if (ldbm_config_moved_attr(attrs[i])) { +- slapi_pblock_set(pb, SLAPI_PB_RESULT_TEXT, "at least one required attribute has been moved to the BDB scecific configuration entry"); ++ slapi_pblock_set(pb, SLAPI_RESULT_TEXT, "at least one required attribute has been moved to the BDB scecific configuration entry"); + break; + } + } +diff --git a/ldap/servers/slapd/result.c b/ldap/servers/slapd/result.c +index 61efb6f8d..40c5dcc57 100644 +--- a/ldap/servers/slapd/result.c ++++ b/ldap/servers/slapd/result.c +@@ -355,7 +355,7 @@ send_ldap_result_ext( + if (text) { + pbtext = text; + } else { +- slapi_pblock_get(pb, SLAPI_PB_RESULT_TEXT, &pbtext); ++ slapi_pblock_get(pb, SLAPI_RESULT_TEXT, &pbtext); + } + + if (operation == NULL) { +-- +2.26.2 + diff --git a/SOURCES/0035-Issue-5442-Search-results-are-different-between-RHDS.patch b/SOURCES/0035-Issue-5442-Search-results-are-different-between-RHDS.patch new file mode 100644 index 0000000..eb06bdb --- /dev/null +++ b/SOURCES/0035-Issue-5442-Search-results-are-different-between-RHDS.patch @@ -0,0 +1,782 @@ +From 2923940ffa0db88df986dd00d74ad812ccd71188 Mon Sep 17 00:00:00 2001 +From: Mark Reynolds +Date: Wed, 20 Jan 2021 16:42:15 -0500 +Subject: [PATCH 2/4] Issue 5442 - Search results are different between RHDS10 + and RHDS11 + +Bug Description: In 1.4.x we introduced a change that was overly strict about + how a search on a non-existent subtree returned its error code. + It was changed from returning an error 32 to an error 0 with + zero entries returned. + +Fix Description: When finding the entry and processing acl's make sure to + gather the aci's that match the resource even if the resource + does not exist. This requires some extra checks when processing + the target attribute. + +relates: https://github.com/389ds/389-ds-base/issues/4542 + +Reviewed by: firstyear, elkris, and tbordaz (Thanks!) + +Apply Thierry's changes + +round 2 + +Apply more suggestions from Thierry +--- + dirsrvtests/tests/suites/acl/misc_test.py | 108 +++++++- + ldap/servers/plugins/acl/acl.c | 296 ++++++++++------------ + ldap/servers/slapd/back-ldbm/findentry.c | 6 +- + src/lib389/lib389/_mapped_object.py | 4 +- + 4 files changed, 239 insertions(+), 175 deletions(-) + +diff --git a/dirsrvtests/tests/suites/acl/misc_test.py b/dirsrvtests/tests/suites/acl/misc_test.py +index 8f122b7a7..b64961c0c 100644 +--- a/dirsrvtests/tests/suites/acl/misc_test.py ++++ b/dirsrvtests/tests/suites/acl/misc_test.py +@@ -11,7 +11,7 @@ + import os + import pytest + +-from lib389._constants import DEFAULT_SUFFIX, PW_DM ++from lib389._constants import DEFAULT_SUFFIX, PW_DM, DN_DM + from lib389.idm.user import UserAccount, UserAccounts + from lib389._mapped_object import DSLdapObject + from lib389.idm.account import Accounts, Anonymous +@@ -399,14 +399,112 @@ def test_do_bind_as_201_distinct_users(topo, clean, aci_of_user): + user = uas.create_test_user(uid=i, gid=i) + user.set('userPassword', PW_DM) + +- for i in range(len(uas.list())): +- uas.list()[i].bind(PW_DM) ++ users = uas.list() ++ for user in users: ++ user.bind(PW_DM) + + ACLPlugin(topo.standalone).replace("nsslapd-aclpb-max-selected-acls", '220') + topo.standalone.restart() + +- for i in range(len(uas.list())): +- uas.list()[i].bind(PW_DM) ++ users = uas.list() ++ for user in users: ++ user.bind(PW_DM) ++ ++ ++def test_info_disclosure(request, topo): ++ """Test that a search returns 32 when base entry does not exist ++ ++ :id: f6dec4c2-65a3-41e4-a4c0-146196863333 ++ :setup: Standalone Instance ++ :steps: ++ 1. Add aci ++ 2. Add test user ++ 3. Bind as user and search for non-existent entry ++ :expectedresults: ++ 1. Success ++ 2. Success ++ 3. Error 32 is returned ++ """ ++ ++ ACI_TARGET = "(targetattr = \"*\")(target = \"ldap:///%s\")" % (DEFAULT_SUFFIX) ++ ACI_ALLOW = "(version 3.0; acl \"Read/Search permission for all users\"; allow (read,search)" ++ ACI_SUBJECT = "(userdn=\"ldap:///all\");)" ++ ACI = ACI_TARGET + ACI_ALLOW + ACI_SUBJECT ++ ++ # Get current ACi's so we can restore them when we are done ++ suffix = Domain(topo.standalone, DEFAULT_SUFFIX) ++ preserved_acis = suffix.get_attr_vals_utf8('aci') ++ ++ def finofaci(): ++ domain = Domain(topo.standalone, DEFAULT_SUFFIX) ++ try: ++ domain.remove_all('aci') ++ domain.replace_values('aci', preserved_acis) ++ except: ++ pass ++ request.addfinalizer(finofaci) ++ ++ # Remove aci's ++ suffix.remove_all('aci') ++ ++ # Add test user ++ USER_DN = "uid=test,ou=people," + DEFAULT_SUFFIX ++ users = UserAccounts(topo.standalone, DEFAULT_SUFFIX) ++ users.create(properties={ ++ 'uid': 'test', ++ 'cn': 'test', ++ 'sn': 'test', ++ 'uidNumber': '1000', ++ 'gidNumber': '2000', ++ 'homeDirectory': '/home/test', ++ 'userPassword': PW_DM ++ }) ++ ++ # bind as user ++ conn = UserAccount(topo.standalone, USER_DN).bind(PW_DM) ++ ++ # Search fo existing base DN ++ test = Domain(conn, DEFAULT_SUFFIX) ++ try: ++ test.get_attr_vals_utf8_l('dc') ++ assert False ++ except IndexError: ++ pass ++ ++ # Search for a non existent bases ++ subtree = Domain(conn, "ou=does_not_exist," + DEFAULT_SUFFIX) ++ try: ++ subtree.get_attr_vals_utf8_l('objectclass') ++ except IndexError: ++ pass ++ subtree = Domain(conn, "ou=also does not exist,ou=does_not_exist," + DEFAULT_SUFFIX) ++ try: ++ subtree.get_attr_vals_utf8_l('objectclass') ++ except IndexError: ++ pass ++ # Try ONE level search instead of BASE ++ try: ++ Accounts(conn, "ou=does_not_exist," + DEFAULT_SUFFIX).filter("(objectclass=top)", ldap.SCOPE_ONELEVEL) ++ except IndexError: ++ pass ++ ++ # add aci ++ suffix.add('aci', ACI) ++ ++ # Search for a non existent entry which should raise an exception ++ with pytest.raises(ldap.NO_SUCH_OBJECT): ++ conn = UserAccount(topo.standalone, USER_DN).bind(PW_DM) ++ subtree = Domain(conn, "ou=does_not_exist," + DEFAULT_SUFFIX) ++ subtree.get_attr_vals_utf8_l('objectclass') ++ with pytest.raises(ldap.NO_SUCH_OBJECT): ++ conn = UserAccount(topo.standalone, USER_DN).bind(PW_DM) ++ subtree = Domain(conn, "ou=also does not exist,ou=does_not_exist," + DEFAULT_SUFFIX) ++ subtree.get_attr_vals_utf8_l('objectclass') ++ with pytest.raises(ldap.NO_SUCH_OBJECT): ++ conn = UserAccount(topo.standalone, USER_DN).bind(PW_DM) ++ DN = "ou=also does not exist,ou=does_not_exist," + DEFAULT_SUFFIX ++ Accounts(conn, DN).filter("(objectclass=top)", ldap.SCOPE_ONELEVEL, strict=True) ++ + + + if __name__ == "__main__": +diff --git a/ldap/servers/plugins/acl/acl.c b/ldap/servers/plugins/acl/acl.c +index 41a909a18..4e811f73a 100644 +--- a/ldap/servers/plugins/acl/acl.c ++++ b/ldap/servers/plugins/acl/acl.c +@@ -2111,10 +2111,11 @@ acl__resource_match_aci(Acl_PBlock *aclpb, aci_t *aci, int skip_attrEval, int *a + aci_right = aci->aci_access; + res_right = aclpb->aclpb_access; + if (!(aci_right & res_right)) { +- /* If we are looking for read/search and the acl has read/search +- ** then go further because if targets match we may keep that +- ** acl in the entry cache list. +- */ ++ /* ++ * If we are looking for read/search and the acl has read/search ++ * then go further because if targets match we may keep that ++ * acl in the entry cache list. ++ */ + if (!((res_right & (SLAPI_ACL_SEARCH | SLAPI_ACL_READ)) && + (aci_right & (SLAPI_ACL_SEARCH | SLAPI_ACL_READ)))) { + matches = ACL_FALSE; +@@ -2122,30 +2123,29 @@ acl__resource_match_aci(Acl_PBlock *aclpb, aci_t *aci, int skip_attrEval, int *a + } + } + +- +- /* first Let's see if the entry is under the subtree where the +- ** ACL resides. We can't let somebody affect a target beyond the +- ** scope of where the ACL resides +- ** Example: ACL is located in "ou=engineering, o=ace industry, c=us +- ** but if the target is "o=ace industry, c=us", then we are in trouble. +- ** +- ** If the aci is in the rootdse and the entry is not, then we do not +- ** match--ie. acis in the rootdse do NOT apply below...for the moment. +- ** +- */ ++ /* ++ * First Let's see if the entry is under the subtree where the ++ * ACL resides. We can't let somebody affect a target beyond the ++ * scope of where the ACL resides ++ * Example: ACL is located in "ou=engineering, o=ace industry, c=us ++ * but if the target is "o=ace industry, c=us", then we are in trouble. ++ * ++ * If the aci is in the rootdse and the entry is not, then we do not ++ * match--ie. acis in the rootdse do NOT apply below...for the moment. ++ */ + res_ndn = slapi_sdn_get_ndn(aclpb->aclpb_curr_entry_sdn); + aci_ndn = slapi_sdn_get_ndn(aci->aci_sdn); +- if (!slapi_sdn_issuffix(aclpb->aclpb_curr_entry_sdn, aci->aci_sdn) || (!slapi_is_rootdse(res_ndn) && slapi_is_rootdse(aci_ndn))) { +- +- /* cant' poke around */ ++ if (!slapi_sdn_issuffix(aclpb->aclpb_curr_entry_sdn, aci->aci_sdn) || ++ (!slapi_is_rootdse(res_ndn) && slapi_is_rootdse(aci_ndn))) ++ { ++ /* can't poke around */ + matches = ACL_FALSE; + goto acl__resource_match_aci_EXIT; + } + + /* +- ** We have a single ACI which we need to find if it applies to +- ** the resource or not. +- */ ++ * We have a single ACI which we need to find if it applies to the resource or not. ++ */ + if ((aci->aci_type & ACI_TARGET_DN) && (aclpb->aclpb_curr_entry_sdn)) { + char *avaType; + struct berval *avaValue; +@@ -2173,25 +2173,23 @@ acl__resource_match_aci(Acl_PBlock *aclpb, aci_t *aci, int skip_attrEval, int *a + char *avaType; + struct berval *avaValue; + char logbuf[1024]; +- +- /* We are evaluating the moddn permission. +- * The aci contains target_to and target_from +- * +- * target_to filter must be checked against the resource ndn that was stored in +- * aclpb->aclpb_curr_entry_sdn +- * +- * target_from filter must be check against the entry ndn that is in aclpb->aclpb_moddn_source_sdn +- * (sdn was stored in the pblock) +- */ ++ /* ++ * We are evaluating the moddn permission. ++ * The aci contains target_to and target_from ++ * ++ * target_to filter must be checked against the resource ndn that was stored in ++ * aclpb->aclpb_curr_entry_sdn ++ * ++ * target_from filter must be check against the entry ndn that is in aclpb->aclpb_moddn_source_sdn ++ * (sdn was stored in the pblock) ++ */ + if (aci->target_to) { + f = aci->target_to; + dn_matched = ACL_TRUE; + + /* Now check if the filter is a simple or substring filter */ + if (aci->aci_type & ACI_TARGET_MODDN_TO_PATTERN) { +- /* This is a filter with substring +- * e.g. ldap:///uid=*,cn=accounts,dc=example,dc=com +- */ ++ /* This is a filter with substring e.g. ldap:///uid=*,cn=accounts,dc=example,dc=com */ + slapi_log_err(SLAPI_LOG_ACL, plugin_name, "acl__resource_match_aci - moddn target_to substring: %s\n", + slapi_filter_to_string(f, logbuf, sizeof(logbuf))); + if ((rv = acl_match_substring(f, (char *)res_ndn, 0 /* match suffix */)) != ACL_TRUE) { +@@ -2204,9 +2202,7 @@ acl__resource_match_aci(Acl_PBlock *aclpb, aci_t *aci, int skip_attrEval, int *a + } + } + } else { +- /* This is a filter without substring +- * e.g. ldap:///cn=accounts,dc=example,dc=com +- */ ++ /* This is a filter without substring e.g. ldap:///cn=accounts,dc=example,dc=com */ + slapi_log_err(SLAPI_LOG_ACL, plugin_name, "acl__resource_match_aci - moddn target_to: %s\n", + slapi_filter_to_string(f, logbuf, sizeof(logbuf))); + slapi_filter_get_ava(f, &avaType, &avaValue); +@@ -2230,8 +2226,8 @@ acl__resource_match_aci(Acl_PBlock *aclpb, aci_t *aci, int skip_attrEval, int *a + /* Now check if the filter is a simple or substring filter */ + if (aci->aci_type & ACI_TARGET_MODDN_FROM_PATTERN) { + /* This is a filter with substring +- * e.g. ldap:///uid=*,cn=accounts,dc=example,dc=com +- */ ++ * e.g. ldap:///uid=*,cn=accounts,dc=example,dc=com ++ */ + slapi_log_err(SLAPI_LOG_ACL, plugin_name, "acl__resource_match_aci - moddn target_from substring: %s\n", + slapi_filter_to_string(f, logbuf, sizeof(logbuf))); + if ((rv = acl_match_substring(f, (char *)slapi_sdn_get_dn(aclpb->aclpb_moddn_source_sdn), 0 /* match suffix */)) != ACL_TRUE) { +@@ -2243,11 +2239,8 @@ acl__resource_match_aci(Acl_PBlock *aclpb, aci_t *aci, int skip_attrEval, int *a + goto acl__resource_match_aci_EXIT; + } + } +- + } else { +- /* This is a filter without substring +- * e.g. ldap:///cn=accounts,dc=example,dc=com +- */ ++ /* This is a filter without substring e.g. ldap:///cn=accounts,dc=example,dc=com */ + slapi_log_err(SLAPI_LOG_ACL, plugin_name, "acl__resource_match_aci - moddn target_from: %s\n", + slapi_filter_to_string(f, logbuf, sizeof(logbuf))); + if (!slapi_dn_issuffix(slapi_sdn_get_dn(aclpb->aclpb_moddn_source_sdn), avaValue->bv_val)) { +@@ -2269,10 +2262,8 @@ acl__resource_match_aci(Acl_PBlock *aclpb, aci_t *aci, int skip_attrEval, int *a + } + + if (aci->aci_type & ACI_TARGET_PATTERN) { +- + f = aci->target; + dn_matched = ACL_TRUE; +- + if ((rv = acl_match_substring(f, (char *)res_ndn, 0 /* match suffux */)) != ACL_TRUE) { + dn_matched = ACL_FALSE; + if (rv == ACL_ERR) { +@@ -2296,7 +2287,7 @@ acl__resource_match_aci(Acl_PBlock *aclpb, aci_t *aci, int skip_attrEval, int *a + + /* + * Is it a (target="ldap://cn=*,($dn),o=sun.com") kind of thing. +- */ ++ */ + if (aci->aci_type & ACI_TARGET_MACRO_DN) { + /* + * See if the ($dn) component matches the string and +@@ -2306,8 +2297,7 @@ acl__resource_match_aci(Acl_PBlock *aclpb, aci_t *aci, int skip_attrEval, int *a + * entry is the same one don't recalculate it-- + * this flag only works for search right now, could + * also optimise for mods by making it work for mods. +- */ +- ++ */ + if ((aclpb->aclpb_res_type & ACLPB_NEW_ENTRY) == 0) { + /* + * Here same entry so just look up the matched value, +@@ -2356,8 +2346,7 @@ acl__resource_match_aci(Acl_PBlock *aclpb, aci_t *aci, int skip_attrEval, int *a + * If there is already an entry for this aci in this + * aclpb then remove it--it's an old value for a + * different entry. +- */ +- ++ */ + acl_ht_add_and_freeOld(aclpb->aclpb_macro_ht, + (PLHashNumber)aci->aci_index, + matched_val); +@@ -2381,30 +2370,27 @@ acl__resource_match_aci(Acl_PBlock *aclpb, aci_t *aci, int skip_attrEval, int *a + } + + /* +- ** Here, if there's a targetfilter field, see if it matches. +- ** +- ** The commented out code below was an erroneous attempt to skip +- ** this test. It is wrong because: 1. you need to store +- ** whether the last test matched or not (you cannot just assume it did) +- ** and 2. It may not be the same aci, so the previous matched +- ** value is a function of the aci. +- ** May be interesting to build such a cache...but no evidence for +- ** for that right now. See Bug 383424. +- ** +- ** +- ** && ((aclpb->aclpb_state & ACLPB_SEARCH_BASED_ON_LIST) || +- ** (aclpb->aclpb_res_type & ACLPB_NEW_ENTRY)) +- */ ++ * Here, if there's a targetfilter field, see if it matches. ++ * ++ * The commented out code below was an erroneous attempt to skip ++ * this test. It is wrong because: 1. you need to store ++ * whether the last test matched or not (you cannot just assume it did) ++ * and 2. It may not be the same aci, so the previous matched ++ * value is a function of the aci. ++ * May be interesting to build such a cache...but no evidence for ++ * for that right now. See Bug 383424. ++ * ++ * ++ * && ((aclpb->aclpb_state & ACLPB_SEARCH_BASED_ON_LIST) || ++ * (aclpb->aclpb_res_type & ACLPB_NEW_ENTRY)) ++ */ + if (aci->aci_type & ACI_TARGET_FILTER) { + int filter_matched = ACL_TRUE; +- + /* + * Check for macros. + * For targetfilter we need to fake the lasinfo structure--it's + * created "naturally" for subjects but not targets. +- */ +- +- ++ */ + if (aci->aci_type & ACI_TARGET_FILTER_MACRO_DN) { + + lasInfo *lasinfo = NULL; +@@ -2419,11 +2405,9 @@ acl__resource_match_aci(Acl_PBlock *aclpb, aci_t *aci, int skip_attrEval, int *a + ACL_EVAL_TARGET_FILTER); + slapi_ch_free((void **)&lasinfo); + } else { +- +- + if (slapi_vattr_filter_test(NULL, aclpb->aclpb_curr_entry, + aci->targetFilter, +- 0 /*don't do acess chk*/) != 0) { ++ 0 /*don't do access check*/) != 0) { + filter_matched = ACL_FALSE; + } + } +@@ -2450,7 +2434,7 @@ acl__resource_match_aci(Acl_PBlock *aclpb, aci_t *aci, int skip_attrEval, int *a + * Check to see if we need to evaluate any targetattrfilters. + * They look as follows: + * (targetattrfilters="add=sn:(sn=rob) && gn:(gn!=byrne), +- * del=sn:(sn=rob) && gn:(gn=byrne)") ++ * del=sn:(sn=rob) && gn:(gn=byrne)") + * + * For ADD/DELETE: + * If theres's a targetattrfilter then each add/del filter +@@ -2458,29 +2442,25 @@ acl__resource_match_aci(Acl_PBlock *aclpb, aci_t *aci, int skip_attrEval, int *a + * by each value of the attribute in the entry. + * + * For MODIFY: +- * If there's a targetattrfilter then the add/del filter ++ * If there's a targetattrfilter then the add/del filter + * must be satisfied by the attribute to be added/deleted. + * (MODIFY acl is evaluated one value at a time). + * + * +- */ +- ++ */ + if (((aclpb->aclpb_access & SLAPI_ACL_ADD) && + (aci->aci_type & ACI_TARGET_ATTR_ADD_FILTERS)) || + ((aclpb->aclpb_access & SLAPI_ACL_DELETE) && +- (aci->aci_type & ACI_TARGET_ATTR_DEL_FILTERS))) { +- ++ (aci->aci_type & ACI_TARGET_ATTR_DEL_FILTERS))) ++ { + Targetattrfilter **attrFilterArray = NULL; +- + Targetattrfilter *attrFilter = NULL; +- + Slapi_Attr *attr_ptr = NULL; + Slapi_Value *sval; + const struct berval *attrVal; + int k; + int done; + +- + if ((aclpb->aclpb_access & SLAPI_ACL_ADD) && + (aci->aci_type & ACI_TARGET_ATTR_ADD_FILTERS)) { + +@@ -2497,28 +2477,20 @@ acl__resource_match_aci(Acl_PBlock *aclpb, aci_t *aci, int skip_attrEval, int *a + + while (attrFilterArray && attrFilterArray[num_attrs] && attr_matched) { + attrFilter = attrFilterArray[num_attrs]; +- + /* +- * If this filter applies to an attribute in the entry, +- * apply it to the entry. +- * Otherwise just ignore it. +- * +- */ +- +- if (slapi_entry_attr_find(aclpb->aclpb_curr_entry, +- attrFilter->attr_str, +- &attr_ptr) == 0) { +- ++ * If this filter applies to an attribute in the entry, ++ * apply it to the entry. ++ * Otherwise just ignore it. ++ * ++ */ ++ if (slapi_entry_attr_find(aclpb->aclpb_curr_entry, attrFilter->attr_str, &attr_ptr) == 0) { + /* +- * This is an applicable filter. +- * The filter is to be appplied to the entry being added +- * or deleted. +- * The filter needs to be satisfied by _each_ occurence +- * of the attribute in the entry--otherwise you +- * could satisfy the filter and then put loads of other +- * values in on the back of it. +- */ +- ++ * This is an applicable filter. ++ * The filter is to be applied to the entry being added or deleted. ++ * The filter needs to be satisfied by _each_ occurrence of the ++ * attribute in the entry--otherwise you could satisfy the filter ++ * and then put loads of other values in on the back of it. ++ */ + sval = NULL; + attrVal = NULL; + k = slapi_attr_first_value(attr_ptr, &sval); +@@ -2528,12 +2500,11 @@ acl__resource_match_aci(Acl_PBlock *aclpb, aci_t *aci, int skip_attrEval, int *a + + if (acl__make_filter_test_entry(&aclpb->aclpb_filter_test_entry, + attrFilter->attr_str, +- (struct berval *)attrVal) == LDAP_SUCCESS) { +- ++ (struct berval *)attrVal) == LDAP_SUCCESS) ++ { + attr_matched = acl__test_filter(aclpb->aclpb_filter_test_entry, + attrFilter->filter, +- 1 /* Do filter sense evaluation below */ +- ); ++ 1 /* Do filter sense evaluation below */); + done = !attr_matched; + slapi_entry_free(aclpb->aclpb_filter_test_entry); + } +@@ -2542,19 +2513,19 @@ acl__resource_match_aci(Acl_PBlock *aclpb, aci_t *aci, int skip_attrEval, int *a + } /* while */ + + /* +- * Here, we applied an applicable filter to the entry. +- * So if attr_matched is ACL_TRUE then every value +- * of the attribute in the entry satisfied the filter. +- * Otherwise, attr_matched is ACL_FALSE and not every +- * value satisfied the filter, so we will teminate the +- * scan of the filter list. +- */ ++ * Here, we applied an applicable filter to the entry. ++ * So if attr_matched is ACL_TRUE then every value ++ * of the attribute in the entry satisfied the filter. ++ * Otherwise, attr_matched is ACL_FALSE and not every ++ * value satisfied the filter, so we will terminate the ++ * scan of the filter list. ++ */ + } + + num_attrs++; + } /* while */ + +-/* ++ /* + * Here, we've applied all the applicable filters to the entry. + * Each one must have been satisfied by all the values of the attribute. + * The result of this is stored in attr_matched. +@@ -2585,7 +2556,8 @@ acl__resource_match_aci(Acl_PBlock *aclpb, aci_t *aci, int skip_attrEval, int *a + } else if (((aclpb->aclpb_access & ACLPB_SLAPI_ACL_WRITE_ADD) && + (aci->aci_type & ACI_TARGET_ATTR_ADD_FILTERS)) || + ((aclpb->aclpb_access & ACLPB_SLAPI_ACL_WRITE_DEL) && +- (aci->aci_type & ACI_TARGET_ATTR_DEL_FILTERS))) { ++ (aci->aci_type & ACI_TARGET_ATTR_DEL_FILTERS))) ++ { + /* + * Here, it's a modify add/del and we have attr filters. + * So, we need to scan the add/del filter list to find the filter +@@ -2629,11 +2601,10 @@ acl__resource_match_aci(Acl_PBlock *aclpb, aci_t *aci, int skip_attrEval, int *a + * Otherwise, ignore the targetattrfilters. + */ + if (found) { +- + if (acl__make_filter_test_entry(&aclpb->aclpb_filter_test_entry, + aclpb->aclpb_curr_attrEval->attrEval_name, +- aclpb->aclpb_curr_attrVal) == LDAP_SUCCESS) { +- ++ aclpb->aclpb_curr_attrVal) == LDAP_SUCCESS) ++ { + attr_matched = acl__test_filter(aclpb->aclpb_filter_test_entry, + attrFilter->filter, + 1 /* Do filter sense evaluation below */ +@@ -2651,20 +2622,21 @@ acl__resource_match_aci(Acl_PBlock *aclpb, aci_t *aci, int skip_attrEval, int *a + * Here this attribute appeared and was matched in a + * targetattrfilters list, so record this fact so we do + * not have to scan the targetattr list for the attribute. +- */ ++ */ + + attr_matched_in_targetattrfilters = 1; + } + } /* targetvaluefilters */ + + +- /* There are 3 cases by which acis are selected. +- ** 1) By scanning the whole list and picking based on the resource. +- ** 2) By picking a subset of the list which will be used for the whole +- ** acl evaluation. +- ** 3) A finer granularity, i.e, a selected list of acls which will be +- ** used for only that entry's evaluation. +- */ ++ /* ++ * There are 3 cases by which acis are selected. ++ * 1) By scanning the whole list and picking based on the resource. ++ * 2) By picking a subset of the list which will be used for the whole ++ * acl evaluation. ++ * 3) A finer granularity, i.e, a selected list of acls which will be ++ * used for only that entry's evaluation. ++ */ + if (!(skip_attrEval) && (aclpb->aclpb_state & ACLPB_SEARCH_BASED_ON_ENTRY_LIST) && + (res_right & SLAPI_ACL_SEARCH) && + ((aci->aci_access & SLAPI_ACL_READ) || (aci->aci_access & SLAPI_ACL_SEARCH))) { +@@ -2680,7 +2652,6 @@ acl__resource_match_aci(Acl_PBlock *aclpb, aci_t *aci, int skip_attrEval, int *a + } + } + +- + /* If we are suppose to skip attr eval, then let's skip it */ + if ((aclpb->aclpb_access & SLAPI_ACL_SEARCH) && (!skip_attrEval) && + (aclpb->aclpb_res_type & ACLPB_NEW_ENTRY)) { +@@ -2697,9 +2668,10 @@ acl__resource_match_aci(Acl_PBlock *aclpb, aci_t *aci, int skip_attrEval, int *a + goto acl__resource_match_aci_EXIT; + } + +- /* We need to check again because we don't want to select this handle +- ** if the right doesn't match for now. +- */ ++ /* ++ * We need to check again because we don't want to select this handle ++ * if the right doesn't match for now. ++ */ + if (!(aci_right & res_right)) { + matches = ACL_FALSE; + goto acl__resource_match_aci_EXIT; +@@ -2718,20 +2690,16 @@ acl__resource_match_aci(Acl_PBlock *aclpb, aci_t *aci, int skip_attrEval, int *a + * rbyrneXXX if we had a proper permission for modrdn eg SLAPI_ACL_MODRDN + * then we would not need this crappy way of telling it was a MODRDN + * request ie. SLAPI_ACL_WRITE && !(c_attrEval). +- */ +- ++ */ + c_attrEval = aclpb->aclpb_curr_attrEval; + + /* + * If we've already matched on targattrfilter then do not + * bother to look at the attrlist. +- */ +- ++ */ + if (!attr_matched_in_targetattrfilters) { +- + /* match target attr */ +- if ((c_attrEval) && +- (aci->aci_type & ACI_TARGET_ATTR)) { ++ if ((c_attrEval) && (aci->aci_type & ACI_TARGET_ATTR)) { + /* there is a target ATTR */ + Targetattr **attrArray = aci->targetAttr; + Targetattr *attr = NULL; +@@ -2773,46 +2741,43 @@ acl__resource_match_aci(Acl_PBlock *aclpb, aci_t *aci, int skip_attrEval, int *a + matches = (attr_matched ? ACL_TRUE : ACL_FALSE); + } + +- + aclpb->aclpb_state &= ~ACLPB_ATTR_STAR_MATCHED; + /* figure out how it matched, i.e star matched */ +- if (matches && star_matched && num_attrs == 1 && +- !(aclpb->aclpb_state & ACLPB_FOUND_ATTR_RULE)) ++ if (matches && star_matched && num_attrs == 1 && !(aclpb->aclpb_state & ACLPB_FOUND_ATTR_RULE)) { + aclpb->aclpb_state |= ACLPB_ATTR_STAR_MATCHED; +- else { ++ } else { + /* we are here means that there is a specific +- ** attr in the rule for this resource. +- ** We need to avoid this case +- ** Rule 1: (targetattr = "uid") +- ** Rule 2: (targetattr = "*") +- ** we cannot use STAR optimization +- */ ++ * attr in the rule for this resource. ++ * We need to avoid this case ++ * Rule 1: (targetattr = "uid") ++ * Rule 2: (targetattr = "*") ++ * we cannot use STAR optimization ++ */ + aclpb->aclpb_state |= ACLPB_FOUND_ATTR_RULE; + aclpb->aclpb_state &= ~ACLPB_ATTR_STAR_MATCHED; + } +- } else if ((c_attrEval) || +- (aci->aci_type & ACI_TARGET_ATTR)) { ++ } else if ((c_attrEval) || (aci->aci_type & ACI_TARGET_ATTR)) { + if ((aci_right & ACL_RIGHTS_TARGETATTR_NOT_NEEDED) && + (aclpb->aclpb_access & ACL_RIGHTS_TARGETATTR_NOT_NEEDED)) { + /* +- ** Targetattr rule doesn't make any sense +- ** in this case. So select this rule +- ** default: matches = ACL_TRUE; +- */ ++ * Targetattr rule doesn't make any sense ++ * in this case. So select this rule ++ * default: matches = ACL_TRUE; ++ */ + ; +- } else if (aci_right & SLAPI_ACL_WRITE && ++ } else if ((aci_right & SLAPI_ACL_WRITE) && + (aci->aci_type & ACI_TARGET_ATTR) && + !(c_attrEval) && + (aci->aci_type & ACI_HAS_ALLOW_RULE)) { + /* We need to handle modrdn operation. Modrdn doesn't +- ** change any attrs but changes the RDN and so (attr=NULL). +- ** Here we found an acl which has a targetattr but +- ** the resource doesn't need one. In that case, we should +- ** consider this acl. +- ** the opposite is true if it is a deny rule, only a deny without +- ** any targetattr should deny modrdn +- ** default: matches = ACL_TRUE; +- */ ++ * change any attrs but changes the RDN and so (attr=NULL). ++ * Here we found an acl which has a targetattr but ++ * the resource doesn't need one. In that case, we should ++ * consider this acl. ++ * the opposite is true if it is a deny rule, only a deny without ++ * any targetattr should deny modrdn ++ * default: matches = ACL_TRUE; ++ */ + ; + } else { + matches = ACL_FALSE; +@@ -2821,16 +2786,16 @@ acl__resource_match_aci(Acl_PBlock *aclpb, aci_t *aci, int skip_attrEval, int *a + } /* !attr_matched_in_targetattrfilters */ + + /* +- ** Here we are testing if we find a entry test rule (which should +- ** be rare). In that case, just remember it. An entry test rule +- ** doesn't have "(targetattr)". +- */ ++ * Here we are testing if we find a entry test rule (which should ++ * be rare). In that case, just remember it. An entry test rule ++ * doesn't have "(targetattr)". ++ */ + if ((aclpb->aclpb_state & ACLPB_EVALUATING_FIRST_ATTR) && + (!(aci->aci_type & ACI_TARGET_ATTR))) { + aclpb->aclpb_state |= ACLPB_FOUND_A_ENTRY_TEST_RULE; + } + +-/* ++ /* + * Generic exit point for this routine: + * matches is ACL_TRUE if the aci matches the target of the resource, + * ACL_FALSE othrewise. +@@ -2853,6 +2818,7 @@ acl__resource_match_aci_EXIT: + + return (matches); + } ++ + /* Macro to determine if the cached result is valid or not. */ + #define ACL_CACHED_RESULT_VALID(result) \ + (((result & ACLPB_CACHE_READ_RES_ALLOW) && \ +diff --git a/ldap/servers/slapd/back-ldbm/findentry.c b/ldap/servers/slapd/back-ldbm/findentry.c +index 6e53a0aea..bff751c88 100644 +--- a/ldap/servers/slapd/back-ldbm/findentry.c ++++ b/ldap/servers/slapd/back-ldbm/findentry.c +@@ -93,7 +93,6 @@ find_entry_internal_dn( + size_t tries = 0; + int isroot = 0; + int op_type; +- char *errbuf = NULL; + + /* get the managedsait ldap message control */ + slapi_pblock_get(pb, SLAPI_MANAGEDSAIT, &managedsait); +@@ -207,8 +206,8 @@ find_entry_internal_dn( + break; + } + if (acl_type > 0) { +- err = plugin_call_acl_plugin(pb, me->ep_entry, NULL, NULL, acl_type, +- ACLPLUGIN_ACCESS_DEFAULT, &errbuf); ++ char *dummy_attr = "1.1"; ++ err = slapi_access_allowed(pb, me->ep_entry, dummy_attr, NULL, acl_type); + } + if (((acl_type > 0) && err) || (op_type == SLAPI_OPERATION_BIND)) { + /* +@@ -237,7 +236,6 @@ find_entry_internal_dn( + CACHE_RETURN(&inst->inst_cache, &me); + } + +- slapi_ch_free_string(&errbuf); + slapi_log_err(SLAPI_LOG_TRACE, "find_entry_internal_dn", "<= Not found (%s)\n", + slapi_sdn_get_dn(sdn)); + return (NULL); +diff --git a/src/lib389/lib389/_mapped_object.py b/src/lib389/lib389/_mapped_object.py +index c60837601..ca6ea6ef8 100644 +--- a/src/lib389/lib389/_mapped_object.py ++++ b/src/lib389/lib389/_mapped_object.py +@@ -1190,7 +1190,7 @@ class DSLdapObjects(DSLogging, DSLints): + # Now actually commit the creation req + return co.ensure_state(rdn, properties, self._basedn) + +- def filter(self, search, scope=None): ++ def filter(self, search, scope=None, strict=False): + # This will yield and & filter for objectClass with as many terms as needed. + if search: + search_filter = _gen_and([self._get_objectclass_filter(), search]) +@@ -1211,5 +1211,7 @@ class DSLdapObjects(DSLogging, DSLints): + insts = [self._entry_to_instance(dn=r.dn, entry=r) for r in results] + except ldap.NO_SUCH_OBJECT: + # There are no objects to select from, se we return an empty array ++ if strict: ++ raise ldap.NO_SUCH_OBJECT + insts = [] + return insts +-- +2.26.2 + diff --git a/SOURCES/0036-Issue-4581-A-failed-re-indexing-leaves-the-database-.patch b/SOURCES/0036-Issue-4581-A-failed-re-indexing-leaves-the-database-.patch new file mode 100644 index 0000000..8632ae9 --- /dev/null +++ b/SOURCES/0036-Issue-4581-A-failed-re-indexing-leaves-the-database-.patch @@ -0,0 +1,145 @@ +From 4fb3023a55529c9d5332e3425ae8da590a8ebb69 Mon Sep 17 00:00:00 2001 +From: tbordaz +Date: Mon, 1 Feb 2021 09:28:25 +0100 +Subject: [PATCH 3/4] Issue 4581 - A failed re-indexing leaves the database in + broken state (#4582) + +Bug description: + During reindex the numsubordinates attribute is not updated in parent entries. + The consequence is that the internal counter job->numsubordinates==0. + Later when indexing the ancestorid, the server can show the progression of this + indexing with a ratio using job->numsubordinates==0. + Division with 0 -> SIGFPE + +Fix description: + if the numsubordinates is NULL, log a message without a division. + +relates: https://github.com/389ds/389-ds-base/issues/4581 + +Reviewed by: Pierre Rogier, Mark Reynolds, Simon Pichugin, Teko Mihinto (thanks !!) + +Platforms tested: F31 +--- + .../slapd/back-ldbm/db-bdb/bdb_import.c | 72 ++++++++++++++----- + 1 file changed, 54 insertions(+), 18 deletions(-) + +diff --git a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_import.c b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_import.c +index 15574e60f..9713b52f6 100644 +--- a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_import.c ++++ b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_import.c +@@ -468,18 +468,30 @@ bdb_get_nonleaf_ids(backend *be, DB_TXN *txn, IDList **idl, ImportJob *job) + } + key_count++; + if (!(key_count % PROGRESS_INTERVAL)) { +- import_log_notice(job, SLAPI_LOG_INFO, "bdb_get_nonleaf_ids", +- "Gathering ancestorid non-leaf IDs: processed %d%% (ID count %d)", +- (key_count * 100 / job->numsubordinates), key_count); ++ if (job->numsubordinates) { ++ import_log_notice(job, SLAPI_LOG_INFO, "bdb_get_nonleaf_ids", ++ "Gathering ancestorid non-leaf IDs: processed %d%% (ID count %d)", ++ (key_count * 100 / job->numsubordinates), key_count); ++ } else { ++ import_log_notice(job, SLAPI_LOG_INFO, "bdb_get_nonleaf_ids", ++ "Gathering ancestorid non-leaf IDs: processed %d ancestors...", ++ key_count); ++ } + started_progress_logging = 1; + } + } while (ret == 0 && !(job->flags & FLAG_ABORT)); + + if (started_progress_logging) { + /* finish what we started logging */ +- import_log_notice(job, SLAPI_LOG_INFO, "bdb_get_nonleaf_ids", +- "Gathering ancestorid non-leaf IDs: processed %d%% (ID count %d)", +- (key_count * 100 / job->numsubordinates), key_count); ++ if (job->numsubordinates) { ++ import_log_notice(job, SLAPI_LOG_INFO, "bdb_get_nonleaf_ids", ++ "Gathering ancestorid non-leaf IDs: processed %d%% (ID count %d)", ++ (key_count * 100 / job->numsubordinates), key_count); ++ } else { ++ import_log_notice(job, SLAPI_LOG_INFO, "bdb_get_nonleaf_ids", ++ "Gathering ancestorid non-leaf IDs: processed %d ancestors", ++ key_count); ++ } + } + import_log_notice(job, SLAPI_LOG_INFO, "bdb_get_nonleaf_ids", + "Finished gathering ancestorid non-leaf IDs."); +@@ -660,9 +672,15 @@ bdb_ancestorid_default_create_index(backend *be, ImportJob *job) + + key_count++; + if (!(key_count % PROGRESS_INTERVAL)) { +- import_log_notice(job, SLAPI_LOG_INFO, "bdb_ancestorid_default_create_index", +- "Creating ancestorid index: processed %d%% (ID count %d)", +- (key_count * 100 / job->numsubordinates), key_count); ++ if (job->numsubordinates) { ++ import_log_notice(job, SLAPI_LOG_INFO, "bdb_ancestorid_default_create_index", ++ "Creating ancestorid index: processed %d%% (ID count %d)", ++ (key_count * 100 / job->numsubordinates), key_count); ++ } else { ++ import_log_notice(job, SLAPI_LOG_INFO, "bdb_ancestorid_default_create_index", ++ "Creating ancestorid index: processed %d ancestors...", ++ key_count); ++ } + started_progress_logging = 1; + } + +@@ -743,9 +761,15 @@ out: + if (ret == 0) { + if (started_progress_logging) { + /* finish what we started logging */ +- import_log_notice(job, SLAPI_LOG_INFO, "bdb_ancestorid_default_create_index", +- "Creating ancestorid index: processed %d%% (ID count %d)", +- (key_count * 100 / job->numsubordinates), key_count); ++ if (job->numsubordinates) { ++ import_log_notice(job, SLAPI_LOG_INFO, "bdb_ancestorid_default_create_index", ++ "Creating ancestorid index: processed %d%% (ID count %d)", ++ (key_count * 100 / job->numsubordinates), key_count); ++ } else { ++ import_log_notice(job, SLAPI_LOG_INFO, "bdb_ancestorid_default_create_index", ++ "Creating ancestorid index: processed %d ancestors", ++ key_count); ++ } + } + import_log_notice(job, SLAPI_LOG_INFO, "bdb_ancestorid_default_create_index", + "Created ancestorid index (old idl)."); +@@ -869,9 +893,15 @@ bdb_ancestorid_new_idl_create_index(backend *be, ImportJob *job) + + key_count++; + if (!(key_count % PROGRESS_INTERVAL)) { +- import_log_notice(job, SLAPI_LOG_INFO, "bdb_ancestorid_new_idl_create_index", +- "Creating ancestorid index: progress %d%% (ID count %d)", +- (key_count * 100 / job->numsubordinates), key_count); ++ if (job->numsubordinates) { ++ import_log_notice(job, SLAPI_LOG_INFO, "bdb_ancestorid_new_idl_create_index", ++ "Creating ancestorid index: progress %d%% (ID count %d)", ++ (key_count * 100 / job->numsubordinates), key_count); ++ } else { ++ import_log_notice(job, SLAPI_LOG_INFO, "bdb_ancestorid_new_idl_create_index", ++ "Creating ancestorid index: progress %d ancestors...", ++ key_count); ++ } + started_progress_logging = 1; + } + +@@ -932,9 +962,15 @@ out: + if (ret == 0) { + if (started_progress_logging) { + /* finish what we started logging */ +- import_log_notice(job, SLAPI_LOG_INFO, "bdb_ancestorid_new_idl_create_index", +- "Creating ancestorid index: processed %d%% (ID count %d)", +- (key_count * 100 / job->numsubordinates), key_count); ++ if (job->numsubordinates) { ++ import_log_notice(job, SLAPI_LOG_INFO, "bdb_ancestorid_new_idl_create_index", ++ "Creating ancestorid index: processed %d%% (ID count %d)", ++ (key_count * 100 / job->numsubordinates), key_count); ++ } else { ++ import_log_notice(job, SLAPI_LOG_INFO, "bdb_ancestorid_new_idl_create_index", ++ "Creating ancestorid index: processed %d ancestors", ++ key_count); ++ } + } + import_log_notice(job, SLAPI_LOG_INFO, "bdb_ancestorid_new_idl_create_index", + "Created ancestorid index (new idl)."); +-- +2.26.2 + diff --git a/SOURCES/0037-Issue-4609-CVE-info-disclosure-when-authenticating.patch b/SOURCES/0037-Issue-4609-CVE-info-disclosure-when-authenticating.patch new file mode 100644 index 0000000..b7c56d0 --- /dev/null +++ b/SOURCES/0037-Issue-4609-CVE-info-disclosure-when-authenticating.patch @@ -0,0 +1,163 @@ +From 861f17d2cb50fc649feee004be1ce08d2e3873f8 Mon Sep 17 00:00:00 2001 +From: Mark Reynolds +Date: Tue, 9 Feb 2021 14:02:59 -0500 +Subject: [PATCH 4/4] Issue 4609 - CVE - info disclosure when authenticating + +Description: If you bind as a user that does not exist. Error 49 is returned + instead of error 32. As error 32 discloses that the entry does + not exist. When you bind as an entry that does not have userpassword + set then error 48 (inappropriate auth) is returned, but this + discloses that the entry does indeed exist. Instead we should + always return error 49, even if the password is not set in the + entry. This way we do not disclose to an attacker if the Bind + DN exists or not. + +Relates: https://github.com/389ds/389-ds-base/issues/4609 + +Reviewed by: tbordaz(Thanks!) +--- + dirsrvtests/tests/suites/basic/basic_test.py | 72 +++++++++++++++++++- + ldap/servers/slapd/back-ldbm/ldbm_bind.c | 4 +- + ldap/servers/slapd/dse.c | 7 +- + 3 files changed, 78 insertions(+), 5 deletions(-) + +diff --git a/dirsrvtests/tests/suites/basic/basic_test.py b/dirsrvtests/tests/suites/basic/basic_test.py +index e9afa1e7e..6244782fa 100644 +--- a/dirsrvtests/tests/suites/basic/basic_test.py ++++ b/dirsrvtests/tests/suites/basic/basic_test.py +@@ -13,7 +13,7 @@ + + from subprocess import check_output, PIPE, run + from lib389 import DirSrv +-from lib389.idm.user import UserAccounts ++from lib389.idm.user import UserAccount, UserAccounts + import pytest + from lib389.tasks import * + from lib389.utils import * +@@ -1062,6 +1062,76 @@ def test_search_ou(topology_st): + assert len(entries) == 0 + + ++def test_bind_invalid_entry(topology_st): ++ """Test the failing bind does not return information about the entry ++ ++ :id: 5cd9b083-eea6-426b-84ca-83c26fc49a6f ++ :customerscenario: True ++ :setup: Standalone instance ++ :steps: ++ 1: bind as non existing entry ++ 2: check that bind info does not report 'No such entry' ++ :expectedresults: ++ 1: pass ++ 2: pass ++ """ ++ ++ topology_st.standalone.restart() ++ INVALID_ENTRY="cn=foooo,%s" % DEFAULT_SUFFIX ++ try: ++ topology_st.standalone.simple_bind_s(INVALID_ENTRY, PASSWORD) ++ except ldap.LDAPError as e: ++ log.info('test_bind_invalid_entry: Failed to bind as %s (expected)' % INVALID_ENTRY) ++ log.info('exception description: ' + e.args[0]['desc']) ++ if 'info' in e.args[0]: ++ log.info('exception info: ' + e.args[0]['info']) ++ assert e.args[0]['desc'] == 'Invalid credentials' ++ assert 'info' not in e.args[0] ++ pass ++ ++ log.info('test_bind_invalid_entry: PASSED') ++ ++ # reset credentials ++ topology_st.standalone.simple_bind_s(DN_DM, PW_DM) ++ ++ ++def test_bind_entry_missing_passwd(topology_st): ++ """ ++ :id: af209149-8fb8-48cb-93ea-3e82dd7119d2 ++ :setup: Standalone Instance ++ :steps: ++ 1. Bind as database entry that does not have userpassword set ++ 2. Bind as database entry that does not exist ++ 1. Bind as cn=config entry that does not have userpassword set ++ 2. Bind as cn=config entry that does not exist ++ :expectedresults: ++ 1. Fails with error 49 ++ 2. Fails with error 49 ++ 3. Fails with error 49 ++ 4. Fails with error 49 ++ """ ++ user = UserAccount(topology_st.standalone, DEFAULT_SUFFIX) ++ with pytest.raises(ldap.INVALID_CREDENTIALS): ++ # Bind as the suffix root entry which does not have a userpassword ++ user.bind("some_password") ++ ++ user = UserAccount(topology_st.standalone, "cn=not here," + DEFAULT_SUFFIX) ++ with pytest.raises(ldap.INVALID_CREDENTIALS): ++ # Bind as the entry which does not exist ++ user.bind("some_password") ++ ++ # Test cn=config since it has its own code path ++ user = UserAccount(topology_st.standalone, "cn=config") ++ with pytest.raises(ldap.INVALID_CREDENTIALS): ++ # Bind as the config entry which does not have a userpassword ++ user.bind("some_password") ++ ++ user = UserAccount(topology_st.standalone, "cn=does not exist,cn=config") ++ with pytest.raises(ldap.INVALID_CREDENTIALS): ++ # Bind as an entry under cn=config that does not exist ++ user.bind("some_password") ++ ++ + @pytest.mark.bz1044135 + @pytest.mark.ds47319 + def test_connection_buffer_size(topology_st): +diff --git a/ldap/servers/slapd/back-ldbm/ldbm_bind.c b/ldap/servers/slapd/back-ldbm/ldbm_bind.c +index fa450ecd5..38d115a32 100644 +--- a/ldap/servers/slapd/back-ldbm/ldbm_bind.c ++++ b/ldap/servers/slapd/back-ldbm/ldbm_bind.c +@@ -76,8 +76,8 @@ ldbm_back_bind(Slapi_PBlock *pb) + case LDAP_AUTH_SIMPLE: { + Slapi_Value cv; + if (slapi_entry_attr_find(e->ep_entry, "userpassword", &attr) != 0) { +- slapi_send_ldap_result(pb, LDAP_INAPPROPRIATE_AUTH, NULL, +- NULL, 0, NULL); ++ slapi_pblock_set(pb, SLAPI_PB_RESULT_TEXT, "Entry does not have userpassword set"); ++ slapi_send_ldap_result(pb, LDAP_INVALID_CREDENTIALS, NULL, NULL, 0, NULL); + CACHE_RETURN(&inst->inst_cache, &e); + rc = SLAPI_BIND_FAIL; + goto bail; +diff --git a/ldap/servers/slapd/dse.c b/ldap/servers/slapd/dse.c +index 0e22d3cec..0d3268046 100644 +--- a/ldap/servers/slapd/dse.c ++++ b/ldap/servers/slapd/dse.c +@@ -1443,7 +1443,8 @@ dse_bind(Slapi_PBlock *pb) /* JCM There should only be one exit point from this + + ec = dse_get_entry_copy(pdse, sdn, DSE_USE_LOCK); + if (ec == NULL) { +- slapi_send_ldap_result(pb, LDAP_NO_SUCH_OBJECT, NULL, NULL, 0, NULL); ++ slapi_pblock_set(pb, SLAPI_PB_RESULT_TEXT, "Entry does not exist"); ++ slapi_send_ldap_result(pb, LDAP_INVALID_CREDENTIALS, NULL, NULL, 0, NULL); + return (SLAPI_BIND_FAIL); + } + +@@ -1451,7 +1452,8 @@ dse_bind(Slapi_PBlock *pb) /* JCM There should only be one exit point from this + case LDAP_AUTH_SIMPLE: { + Slapi_Value cv; + if (slapi_entry_attr_find(ec, "userpassword", &attr) != 0) { +- slapi_send_ldap_result(pb, LDAP_INAPPROPRIATE_AUTH, NULL, NULL, 0, NULL); ++ slapi_pblock_set(pb, SLAPI_PB_RESULT_TEXT, "Entry does not have userpassword set"); ++ slapi_send_ldap_result(pb, LDAP_INVALID_CREDENTIALS, NULL, NULL, 0, NULL); + slapi_entry_free(ec); + return SLAPI_BIND_FAIL; + } +@@ -1459,6 +1461,7 @@ dse_bind(Slapi_PBlock *pb) /* JCM There should only be one exit point from this + + slapi_value_init_berval(&cv, cred); + if (slapi_pw_find_sv(bvals, &cv) != 0) { ++ slapi_pblock_set(pb, SLAPI_PB_RESULT_TEXT, "Invalid credentials"); + slapi_send_ldap_result(pb, LDAP_INVALID_CREDENTIALS, NULL, NULL, 0, NULL); + slapi_entry_free(ec); + value_done(&cv); +-- +2.26.2 + diff --git a/SOURCES/0038-Issue-4460-BUG-lib389-should-use-system-tls-policy.patch b/SOURCES/0038-Issue-4460-BUG-lib389-should-use-system-tls-policy.patch new file mode 100644 index 0000000..9d8ea1a --- /dev/null +++ b/SOURCES/0038-Issue-4460-BUG-lib389-should-use-system-tls-policy.patch @@ -0,0 +1,97 @@ +From 82db41ae6f76464a6ee3cbfdca8019bc809b3cf3 Mon Sep 17 00:00:00 2001 +From: William Brown +Date: Thu, 26 Nov 2020 09:08:13 +1000 +Subject: [PATCH] Issue 4460 - BUG - lib389 should use system tls policy + +Bug Description: Due to some changes in dsrc for tlsreqcert +and how def open was structured in lib389, the system ldap.conf +policy was ignored. + +Fix Description: Default to using the system ldap.conf policy +if undefined in lib389 or the tls_reqcert param in dsrc. + +fixes: #4460 + +Author: William Brown + +Review by: ??? +--- + src/lib389/lib389/__init__.py | 11 +++++++---- + src/lib389/lib389/cli_base/dsrc.py | 16 +++++++++------- + 2 files changed, 16 insertions(+), 11 deletions(-) + +diff --git a/src/lib389/lib389/__init__.py b/src/lib389/lib389/__init__.py +index 63d44b60a..dc18b2bfe 100644 +--- a/src/lib389/lib389/__init__.py ++++ b/src/lib389/lib389/__init__.py +@@ -962,7 +962,7 @@ class DirSrv(SimpleLDAPObject, object): + # Now, we are still an allocated ds object so we can be re-installed + self.state = DIRSRV_STATE_ALLOCATED + +- def open(self, uri=None, saslmethod=None, sasltoken=None, certdir=None, starttls=False, connOnly=False, reqcert=ldap.OPT_X_TLS_HARD, ++ def open(self, uri=None, saslmethod=None, sasltoken=None, certdir=None, starttls=False, connOnly=False, reqcert=None, + usercert=None, userkey=None): + ''' + It opens a ldap bound connection to dirsrv so that online +@@ -1025,9 +1025,12 @@ class DirSrv(SimpleLDAPObject, object): + try: + # Note this sets LDAP.OPT not SELF. Because once self has opened + # it can NOT change opts on reused (ie restart) +- self.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, reqcert) +- self.log.debug("Using certificate policy %s", reqcert) +- self.log.debug("ldap.OPT_X_TLS_REQUIRE_CERT = %s", reqcert) ++ if reqcert is not None: ++ self.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, reqcert) ++ self.log.debug("Using lib389 certificate policy %s", reqcert) ++ else: ++ self.log.debug("Using /etc/openldap/ldap.conf certificate policy") ++ self.log.debug("ldap.OPT_X_TLS_REQUIRE_CERT = %s", self.get_option(ldap.OPT_X_TLS_REQUIRE_CERT)) + except ldap.LDAPError as e: + self.log.fatal('TLS negotiation failed: %s', e) + raise e +diff --git a/src/lib389/lib389/cli_base/dsrc.py b/src/lib389/lib389/cli_base/dsrc.py +index 9cad23437..8a4a2a55d 100644 +--- a/src/lib389/lib389/cli_base/dsrc.py ++++ b/src/lib389/lib389/cli_base/dsrc.py +@@ -45,7 +45,7 @@ def dsrc_arg_concat(args, dsrc_inst): + 'tls_cacertdir': None, + 'tls_cert': None, + 'tls_key': None, +- 'tls_reqcert': ldap.OPT_X_TLS_HARD, ++ 'tls_reqcert': None, + 'starttls': args.starttls, + 'prompt': False, + 'pwdfile': None, +@@ -134,21 +134,23 @@ def dsrc_to_ldap(path, instance_name, log): + dsrc_inst['binddn'] = config.get(instance_name, 'binddn', fallback=None) + dsrc_inst['saslmech'] = config.get(instance_name, 'saslmech', fallback=None) + if dsrc_inst['saslmech'] is not None and dsrc_inst['saslmech'] not in ['EXTERNAL', 'PLAIN']: +- raise Exception("%s [%s] saslmech must be one of EXTERNAL or PLAIN" % (path, instance_name)) ++ raise ValueError("%s [%s] saslmech must be one of EXTERNAL or PLAIN" % (path, instance_name)) + + dsrc_inst['tls_cacertdir'] = config.get(instance_name, 'tls_cacertdir', fallback=None) + dsrc_inst['tls_cert'] = config.get(instance_name, 'tls_cert', fallback=None) + dsrc_inst['tls_key'] = config.get(instance_name, 'tls_key', fallback=None) +- dsrc_inst['tls_reqcert'] = config.get(instance_name, 'tls_reqcert', fallback='hard') +- if dsrc_inst['tls_reqcert'] not in ['never', 'allow', 'hard']: +- raise Exception("dsrc tls_reqcert value invalid. %s [%s] tls_reqcert should be one of never, allow or hard" % (instance_name, +- path)) ++ dsrc_inst['tls_reqcert'] = config.get(instance_name, 'tls_reqcert', fallback=None) + if dsrc_inst['tls_reqcert'] == 'never': + dsrc_inst['tls_reqcert'] = ldap.OPT_X_TLS_NEVER + elif dsrc_inst['tls_reqcert'] == 'allow': + dsrc_inst['tls_reqcert'] = ldap.OPT_X_TLS_ALLOW +- else: ++ elif dsrc_inst['tls_reqcert'] == 'hard': + dsrc_inst['tls_reqcert'] = ldap.OPT_X_TLS_HARD ++ elif dsrc_inst['tls_reqcert'] is None: ++ # Use system value ++ pass ++ else: ++ raise ValueError("dsrc tls_reqcert value invalid. %s [%s] tls_reqcert should be one of never, allow or hard" % (instance_name, path)) + dsrc_inst['starttls'] = config.getboolean(instance_name, 'starttls', fallback=False) + dsrc_inst['pwdfile'] = None + dsrc_inst['prompt'] = False +-- +2.26.2 + diff --git a/SOURCES/0039-Issue-4460-BUG-add-machine-name-to-subject-alt-names.patch b/SOURCES/0039-Issue-4460-BUG-add-machine-name-to-subject-alt-names.patch new file mode 100644 index 0000000..a901fcc --- /dev/null +++ b/SOURCES/0039-Issue-4460-BUG-add-machine-name-to-subject-alt-names.patch @@ -0,0 +1,39 @@ +From 2d6ca042adcf0dc2bbf9b898d698bbf62514c4a5 Mon Sep 17 00:00:00 2001 +From: Firstyear +Date: Fri, 4 Dec 2020 10:14:33 +1000 +Subject: [PATCH] Issue 4460 - BUG - add machine name to subject alt names in + SSCA (#4472) + +Bug Description: During SSCA creation, the server cert did not have +the machine name, which meant that the cert would not work without +reqcert = never. + +Fix Description: Add the machine name as an alt name during SSCA +creation. It is not guaranteed this value is correct, but it +is better than nothing. + +relates: https://github.com/389ds/389-ds-base/issues/4460 + +Author: William Brown + +Review by: mreynolds389, droideck +--- + src/lib389/lib389/instance/setup.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/lib389/lib389/instance/setup.py b/src/lib389/lib389/instance/setup.py +index 45c7dfdd4..21260ee20 100644 +--- a/src/lib389/lib389/instance/setup.py ++++ b/src/lib389/lib389/instance/setup.py +@@ -870,7 +870,7 @@ class SetupDs(object): + tlsdb_inst = NssSsl(dbpath=os.path.join(etc_dirsrv_path, dir)) + tlsdb_inst.import_rsa_crt(ca) + +- csr = tlsdb.create_rsa_key_and_csr() ++ csr = tlsdb.create_rsa_key_and_csr(alt_names=[general['full_machine_name']]) + (ca, crt) = ssca.rsa_ca_sign_csr(csr) + tlsdb.import_rsa_crt(ca, crt) + if general['selinux']: +-- +2.26.2 + diff --git a/SPECS/389-ds-base.spec b/SPECS/389-ds-base.spec index f8decd7..e3f9be3 100644 --- a/SPECS/389-ds-base.spec +++ b/SPECS/389-ds-base.spec @@ -45,7 +45,7 @@ ExcludeArch: i686 Summary: 389 Directory Server (base) Name: 389-ds-base Version: 1.4.3.8 -Release: %{?relprefix}6%{?prerel}%{?dist} +Release: %{?relprefix}7%{?prerel}%{?dist} License: GPLv3+ URL: https://www.port389.org Group: System Environment/Daemons @@ -207,7 +207,12 @@ Patch30: 0030-ticket-2058-Add-keep-alive-entry-after-on-line-initi.patc Patch31: 0031-do-not-add-referrals-for-masters-with-different-data.patch Patch32: 0032-Issue-4383-Do-not-normalize-escaped-spaces-in-a-DN.patch Patch33: 0033-Issue-49300-entryUSN-is-duplicated-after-memberOf-op.patch - +Patch34: 0034-Issue-4480-Unexpected-info-returned-to-ldap-request-.patch +Patch35: 0035-Issue-5442-Search-results-are-different-between-RHDS.patch +Patch36: 0036-Issue-4581-A-failed-re-indexing-leaves-the-database-.patch +Patch37: 0037-Issue-4609-CVE-info-disclosure-when-authenticating.patch +Patch38: 0038-Issue-4460-BUG-lib389-should-use-system-tls-policy.patch +Patch39: 0039-Issue-4460-BUG-add-machine-name-to-subject-alt-names.patch %description 389 Directory Server is an LDAPv3 compliant server. The base package includes @@ -825,15 +830,21 @@ exit 0 %doc README.md %changelog +* Thu Mar 11 2021 Mark Reynolds - 1.4.3.8-7 +- Bump version to 1.4.3.8-7 +- Resolves: Bug 1908705 - CVE-2020-35518 389-ds:1.4/389-ds-base: information disclosure during the binding of a DN +- Resolves: Bug 1936461 - A failed re-indexing leaves the database in broken state. +- Resolves: Bug 1912481 - Server-Cert.crt created using dscreate has Subject:CN =localhost instead of hostname. + * Thu Dec 3 2020 Mark Reynolds - 1.4.3.8-6 - Bump version to 1.4.3.8-6 -- Resolves: Bug 1851973 - Duplicate entryUSN numbers for different LDAP entries in the same backend -- Resolves: Bug 1888863 - group rdn with leading space char and add fails error 21 invalid syntax and delete fails error 32 -- Resolves: Bug 1859228 - do not add referrals for masters with different data generation -- Resolves: Bug 1859227 - create keep alive entry after on line init -- Resolves: Bug 1896850 - NULL dereference in revert_cache() -- Resolves: Bug 1861504 - ds-replcheck crashes in offline mode -- Resolves: Bug 1898850 - Entries conflict not resolved by replication +- Resolves: Bug 1904348 - Duplicate entryUSN numbers for different LDAP entries in the same backend +- Resolves: Bug 1904349 - group rdn with leading space char and add fails error 21 invalid syntax and delete fails error 32 +- Resolves: Bug 1904350 - do not add referrals for masters with different data generation +- Resolves: Bug 1904351 - create keep alive entry after on line init +- Resolves: Bug 1904352 - NULL dereference in revert_cache() +- Resolves: Bug 1904353 - ds-replcheck crashes in offline mode +- Resolves: Bug 1904347 - Entries conflict not resolved by replication * Wed Aug 5 2020 Mark Reynolds - 1.4.3.8-5 - Bump version to 1.4.3.8-5