diff -Nru 389-ds-base-2.4.4+dfsg1/Makefile.am 389-ds-base-2.4.5+dfsg1/Makefile.am --- 389-ds-base-2.4.4+dfsg1/Makefile.am 2023-11-15 14:06:27.000000000 +0000 +++ 389-ds-base-2.4.5+dfsg1/Makefile.am 2024-01-15 10:27:07.000000000 +0000 @@ -1738,6 +1738,7 @@ ldap/servers/plugins/syntaxes/facsimile.c \ ldap/servers/plugins/syntaxes/guide.c \ ldap/servers/plugins/syntaxes/int.c \ + ldap/servers/plugins/syntaxes/inchain.c \ ldap/servers/plugins/syntaxes/nameoptuid.c \ ldap/servers/plugins/syntaxes/numericstring.c \ ldap/servers/plugins/syntaxes/phonetic.c \ diff -Nru 389-ds-base-2.4.4+dfsg1/VERSION.sh 389-ds-base-2.4.5+dfsg1/VERSION.sh --- 389-ds-base-2.4.4+dfsg1/VERSION.sh 2023-11-15 14:06:27.000000000 +0000 +++ 389-ds-base-2.4.5+dfsg1/VERSION.sh 2024-01-15 10:27:07.000000000 +0000 @@ -10,7 +10,7 @@ # PACKAGE_VERSION is constructed from these VERSION_MAJOR=2 VERSION_MINOR=4 -VERSION_MAINT=4 +VERSION_MAINT=5 # NOTE: VERSION_PREREL is automatically set for builds made out of a git tree VERSION_PREREL= VERSION_DATE=$(date -u +%Y%m%d%H%M) diff -Nru 389-ds-base-2.4.4+dfsg1/debian/changelog 389-ds-base-2.4.5+dfsg1/debian/changelog --- 389-ds-base-2.4.4+dfsg1/debian/changelog 2024-02-10 09:04:23.000000000 +0000 +++ 389-ds-base-2.4.5+dfsg1/debian/changelog 2024-04-15 15:48:40.000000000 +0000 @@ -1,3 +1,10 @@ +389-ds-base (2.4.5+dfsg1-1) unstable; urgency=medium + + * New upstream release. + * control: Migrate to libldap-dev, and drop obsolete B/R/P. + + -- Timo Aaltonen Mon, 15 Apr 2024 18:48:40 +0300 + 389-ds-base (2.4.4+dfsg1-3) unstable; urgency=medium * rules: Fix build on 32bit. Thanks, Chris Peterson! (Closes: diff -Nru 389-ds-base-2.4.4+dfsg1/debian/control 389-ds-base-2.4.5+dfsg1/debian/control --- 389-ds-base-2.4.4+dfsg1/debian/control 2024-02-10 09:02:43.000000000 +0000 +++ 389-ds-base-2.4.5+dfsg1/debian/control 2024-04-14 17:45:47.000000000 +0000 @@ -18,7 +18,7 @@ libicu-dev, libjson-c-dev, libkrb5-dev, - libldap2-dev (>= 2.4.28), + libldap-dev, liblmdb-dev, libltdl-dev, libnspr4-dev, @@ -104,16 +104,10 @@ Multi-Arch: same Depends: 389-ds-base-libs (= ${binary:Version}), - libldap2-dev, + libldap-dev, libnspr4-dev, ${misc:Depends}, ${shlibs:Depends}, -Breaks: 389-ds-base (<< 1.3.6.7-4), - libsvrcore-dev, -Replaces: 389-ds-base (<< 1.3.6.7-4), - libsvrcore-dev, -Provides: - libsvrcore-dev, Description: 389 Directory Server suite - development files Based on the Lightweight Directory Access Protocol (LDAP), the 389 Directory Server is designed to manage large directories of users and diff -Nru 389-ds-base-2.4.4+dfsg1/dirsrvtests/tests/suites/betxns/betxn_test.py 389-ds-base-2.4.5+dfsg1/dirsrvtests/tests/suites/betxns/betxn_test.py --- 389-ds-base-2.4.4+dfsg1/dirsrvtests/tests/suites/betxns/betxn_test.py 2023-11-15 14:06:27.000000000 +0000 +++ 389-ds-base-2.4.5+dfsg1/dirsrvtests/tests/suites/betxns/betxn_test.py 2024-01-15 10:27:07.000000000 +0000 @@ -8,6 +8,7 @@ # import pytest import ldap +import os from lib389.tasks import * from lib389.utils import * from lib389.topologies import topology_st @@ -15,6 +16,7 @@ MemberOfPlugin, ManagedEntriesPlugin, ReferentialIntegrityPlugin, MEPTemplates, MEPConfigs) +from lib389.plugins import MemberOfPlugin from lib389.idm.user import UserAccounts, TEST_USER_PROPERTIES from lib389.idm.organizationalunit import OrganizationalUnits from lib389.idm.group import Groups, Group @@ -356,7 +358,179 @@ # Success log.info("Test PASSED") +def test_revert_cache(topology_st, request): + """Tests that reversion of the entry cache does not occur + during 'normal' failure (like schema violation) but only + when a plugin fails + :id: a2361285-b939-4da0-aa80-7fc54d12c981 + + :setup: Standalone instance + + :steps: + 1. Create a user account (with a homeDirectory) + 2. Remove the error log file + 3. Add a second value of homeDirectory + 4. Check that error log does not contain entry cache reversion + 5. Configure memberof to trigger a failure + 6. Do a update the will fail in memberof plugin + 7. Check that error log does contain entry cache reversion + + :expectedresults: + 1. Succeeds + 2. Fails with OBJECT_CLASS_VIOLATION + 3. Succeeds + 4. Succeeds + 5. Succeeds + 6. Fails with OBJECT_CLASS_VIOLATION + 7. Succeeds + """ + # Create an test user entry + log.info('Create a user without nsMemberOF objectclass') + try: + users = UserAccounts(topology_st.standalone, DEFAULT_SUFFIX, rdn='ou=people') + user = users.create_test_user() + user.replace('objectclass', ['top', 'account', 'person', 'inetorgperson', 'posixAccount', 'organizationalPerson', 'nsAccount']) + except ldap.LDAPError as e: + log.fatal('Failed to create test user: error ' + e.args[0]['desc']) + assert False + + # Remove the current error log file + topology_st.standalone.stop() + lpath = topology_st.standalone.ds_error_log._get_log_path() + os.unlink(lpath) + topology_st.standalone.start() + + # + # Add a second value to 'homeDirectory' + # leads to ldap.OBJECT_CLASS_VIOLATION + # And check that the entry cache was not reverted + try: + topology_st.standalone.modify_s(user.dn, [(ldap.MOD_ADD, 'homeDirectory', ensure_bytes('/home/user_new'))]) + assert False + except ldap.OBJECT_CLASS_VIOLATION: + pass + assert not topology_st.standalone.ds_error_log.match('.*WARN - flush_hash - Upon BETXN callback failure, entry cache is flushed during.*') + + # Prepare memberof so that it will fail during a next update + # If memberof plugin can not add 'memberof' to the + # member entry, it retries after adding + # 'memberOfAutoAddOC' objectclass to the member. + # If it fails again the plugin fails with 'object + # violation' + # To trigger this failure, set 'memberOfAutoAddOC' + # to a value that does *not* allow 'memberof' attribute + memberof = MemberOfPlugin(topology_st.standalone) + memberof.enable() + memberof.replace('memberOfAutoAddOC', 'account') + memberof.replace('memberofentryscope', DEFAULT_SUFFIX) + topology_st.standalone.restart() + + # Try to add the user to demo_group + # It should fail because user entry has not 'nsmemberof' objectclass + # As this is a BETXN plugin that fails it should revert the entry cache + try: + GROUP_DN = "cn=demo_group,ou=groups," + DEFAULT_SUFFIX + topology_st.standalone.modify_s(GROUP_DN, + [(ldap.MOD_REPLACE, 'member', ensure_bytes(user.dn))]) + assert False + except ldap.OBJECT_CLASS_VIOLATION: + pass + assert topology_st.standalone.ds_error_log.match('.*WARN - flush_hash - Upon BETXN callback failure, entry cache is flushed during.*') + + def fin(): + user.delete() + memberof = MemberOfPlugin(topology_st.standalone) + memberof.replace('memberOfAutoAddOC', 'nsmemberof') + + request.addfinalizer(fin) + +@pytest.mark.flaky(max_runs=2, min_passes=1) +def test_revert_cache_noloop(topology_st, request): + """Tests that when an entry is reverted, if an update + hit the reverted entry then the retry loop is aborted + and the update gets err=51 + NOTE: this test requires a dynamic so that the two updates + occurs about the same time. If the test becomes fragile it is + okay to make it flaky + + :id: 88ef0ba5-8c66-49e6-99c9-9e3f6183917f + + :setup: Standalone instance + + :steps: + 1. Create a user account (with a homeDirectory) + 2. Remove the error log file + 3. Configure memberof to trigger a failure + 4. Do in a loop 3 parallel updates (they will fail in + memberof plugin) and an updated on the reverted entry + 5. Check that error log does contain entry cache reversion + + :expectedresults: + 1. Succeeds + 2. Success + 3. Succeeds + 4. Succeeds + 5. Succeeds + """ + # Create an test user entry + log.info('Create a user without nsMemberOF objectclass') + try: + users = UserAccounts(topology_st.standalone, DEFAULT_SUFFIX, rdn='ou=people') + user = users.create_test_user() + user.replace('objectclass', ['top', 'account', 'person', 'inetorgperson', 'posixAccount', 'organizationalPerson', 'nsAccount']) + except ldap.LDAPError as e: + log.fatal('Failed to create test user: error ' + e.args[0]['desc']) + assert False + + # Remove the current error log file + topology_st.standalone.stop() + lpath = topology_st.standalone.ds_error_log._get_log_path() + os.unlink(lpath) + topology_st.standalone.start() + + # Prepare memberof so that it will fail during a next update + # If memberof plugin can not add 'memberof' to the + # member entry, it retries after adding + # 'memberOfAutoAddOC' objectclass to the member. + # If it fails again the plugin fails with 'object + # violation' + # To trigger this failure, set 'memberOfAutoAddOC' + # to a value that does *not* allow 'memberof' attribute + memberof = MemberOfPlugin(topology_st.standalone) + memberof.enable() + memberof.replace('memberOfAutoAddOC', 'account') + memberof.replace('memberofentryscope', DEFAULT_SUFFIX) + topology_st.standalone.restart() + + for i in range(50): + # Try to add the user to demo_group + # It should fail because user entry has not 'nsmemberof' objectclass + # As this is a BETXN plugin that fails it should revert the entry cache + try: + GROUP_DN = "cn=demo_group,ou=groups," + DEFAULT_SUFFIX + topology_st.standalone.modify(GROUP_DN, + [(ldap.MOD_REPLACE, 'member', ensure_bytes(user.dn))]) + topology_st.standalone.modify(GROUP_DN, + [(ldap.MOD_REPLACE, 'member', ensure_bytes(user.dn))]) + topology_st.standalone.modify(GROUP_DN, + [(ldap.MOD_REPLACE, 'member', ensure_bytes(user.dn))]) + except ldap.OBJECT_CLASS_VIOLATION: + pass + + user.replace('cn', ['new_value']) + + # Check that both a betxn failed and a reverted entry was + # detected during an update + assert topology_st.standalone.ds_error_log.match('.*WARN - flush_hash - Upon BETXN callback failure, entry cache is flushed during.*') + assert topology_st.standalone.ds_error_log.match('.*cache_is_reverted_entry - Entry reverted.*') + + def fin(): + user.delete() + memberof = MemberOfPlugin(topology_st.standalone) + memberof.replace('memberOfAutoAddOC', 'nsmemberof') + + request.addfinalizer(fin) if __name__ == '__main__': # Run isolated # -s for DEBUG mode diff -Nru 389-ds-base-2.4.4+dfsg1/dirsrvtests/tests/suites/filter/inchain_test.py 389-ds-base-2.4.5+dfsg1/dirsrvtests/tests/suites/filter/inchain_test.py --- 389-ds-base-2.4.4+dfsg1/dirsrvtests/tests/suites/filter/inchain_test.py 1970-01-01 00:00:00.000000000 +0000 +++ 389-ds-base-2.4.5+dfsg1/dirsrvtests/tests/suites/filter/inchain_test.py 2024-01-15 10:27:07.000000000 +0000 @@ -0,0 +1,817 @@ +# --- BEGIN COPYRIGHT BLOCK --- +# Copyright (C) 2023 RED Hat, Inc. +# All rights reserved. +# +# License: GPL (version 3 or any later version). +# See LICENSE for details. +# --- END COPYRIGHT BLOCK ---- + +import pytest, os, re +from lib389.tasks import * +from lib389.utils import * +from ldap import SCOPE_SUBTREE, ALREADY_EXISTS + +from lib389._constants import DEFAULT_SUFFIX, PW_DM, PLUGIN_MEMBER_OF +from lib389.topologies import topology_st as topo +from lib389.plugins import MemberOfPlugin + +from lib389.idm.user import UserAccount, UserAccounts +from lib389.idm.account import Accounts +from lib389.idm.account import Anonymous + +INCHAIN_OID = "1.2.840.113556.1.4.1941" + +pytestmark = pytest.mark.tier0 + +@pytest.fixture(scope="function") +def provision_inchain(topo): + """fixture that provision a hierachical tree + with 'manager' membership relation + + """ + # hierarchy of the entries is + # 3_1 + # |__ 2_1 + # | |__ 1_1 + # | |__ 100 + # | | |__ 101 + # | | |__ 102 + # | | |__ 103 + # | | |__ 104 + # | | + # | |__ 1_2 + # | | |__ 200 + # | | |__ 201 + # | | |__ 202 + # | | |__ 203 + # | | |__ 204 + # | + # |__ 2_2 + # |__ 1_3 + # | |__ 300 + # | |__ 301 + # | |__ 302 + # | |__ 303 + # | |__ 304 + # | + # |__ 1_4 + # |__ 400 + # |__ 401 + # |__ 402 + # |__ 403 + # |__ 404 + # + user = UserAccounts(topo.standalone, DEFAULT_SUFFIX) + try: + manager_lvl_3_1 = user.create_test_user(uid=31) + except ALREADY_EXISTS: + manager_lvl_3_1 = None + pass + + try: + manager_lvl_2_1 = user.create_test_user(uid=21) + if manager_lvl_3_1: + manager_lvl_2_1.set("manager", manager_lvl_3_1.dn) + else: + manager_lvl_2_1.set("manager", "uid=test_user_31,ou=People,%s" % DEFAULT_SUFFIX) + except ALREADY_EXISTS: + manager_lvl_2_1 = None + pass + + try: + manager_lvl_2_2 = user.create_test_user(uid=22) + if manager_lvl_3_1: + manager_lvl_2_2.set("manager", manager_lvl_3_1.dn) + else: + manager_lvl_2_2.set("manager", "uid=test_user_31,ou=People,%s" % DEFAULT_SUFFIX) + except ALREADY_EXISTS: + manager_lvl_2_2 = None + pass + + try: + manager_lvl_1_1 = user.create_test_user(uid=11) + if manager_lvl_2_1: + manager_lvl_1_1.set("manager", manager_lvl_2_1.dn) + else: + manager_lvl_1_1.set("manager", "uid=test_user_21,ou=People,%s" % DEFAULT_SUFFIX) + except ALREADY_EXISTS: + manager_lvl_1_1 = None + pass + + try: + manager_lvl_1_2 = user.create_test_user(uid=12) + if manager_lvl_2_1: + manager_lvl_1_2.set("manager", manager_lvl_2_1.dn) + else: + manager_lvl_1_2.set("manager", "uid=test_user_21,ou=People,%s" % DEFAULT_SUFFIX) + except ALREADY_EXISTS: + manager_lvl_1_2 = None + pass + + try: + manager_lvl_1_3 = user.create_test_user(uid=13) + if manager_lvl_2_2: + manager_lvl_1_3.set("manager", manager_lvl_2_2.dn) + else: + manager_lvl_1_3.set("manager", "uid=test_user_22,ou=People,%s" % DEFAULT_SUFFIX) + except ALREADY_EXISTS: + manager_lvl_1_3 = None + pass + + try: + manager_lvl_1_4 = user.create_test_user(uid=14) + if manager_lvl_2_2: + manager_lvl_1_4.set("manager", manager_lvl_2_2.dn) + else: + manager_lvl_1_4.set("manager", "uid=test_user_22,ou=People,%s" % DEFAULT_SUFFIX) + except ALREADY_EXISTS: + manager_lvl_1_4 = None + pass + + for i in range(100, 105): + try: + user1 = user.create_test_user(uid=i) + if manager_lvl_1_1: + user1.set("manager", manager_lvl_1_1.dn) + else: + user1.set("manager", "uid=test_user_11,ou=People,%s" % DEFAULT_SUFFIX) + except ALREADY_EXISTS: + pass + + for i in range(200, 205): + try: + user1 = user.create_test_user(uid=i) + if manager_lvl_1_2: + user1.set("manager", manager_lvl_1_2.dn) + else: + user1.set("manager", "uid=test_user_12,ou=People,%s" % DEFAULT_SUFFIX) + except ALREADY_EXISTS: + pass + + for i in range(300, 305): + try: + user1 = user.create_test_user(uid=i) + if manager_lvl_1_3: + user1.set("manager", manager_lvl_1_3.dn) + else: + user1.set("manager", "uid=test_user_13,ou=People,%s" % DEFAULT_SUFFIX) + except ALREADY_EXISTS: + pass + + for i in range(400, 405): + try: + user1 = user.create_test_user(uid=i) + if manager_lvl_1_4: + user1.set("manager", manager_lvl_1_4.dn) + else: + user1.set("manager", "uid=test_user_14,ou=People,%s" % DEFAULT_SUFFIX) + except ALREADY_EXISTS: + pass + +def check_subordinates(topo, uid, expected): + """Test filter can search attributes + + :id: 39640b4b-0e64-44a4-8611-191aae412370 + :setup: Standalone instance + :steps: + 1. Add test entry + 2. make search + :expectedresults: + 1. Entry should be added + 2. Operation should succeed + """ + manager = "uid=%s,ou=People,%s" % (uid, DEFAULT_SUFFIX) + topo.standalone.log.info("Subordinate of manager %s" % manager) + + subordinates = topo.standalone.search_s(DEFAULT_SUFFIX, SCOPE_SUBTREE, "(manager:%s:=%s)" % (INCHAIN_OID, manager)) + found = [] + for sub in subordinates: + p =re.compile("uid=(.*),ou.*$") + res = p.search(sub.dn) + found.append(res.group(1)) + topo.standalone.log.info("Subordinate found : %s" % res.group(1)) + + + for sub in expected: + assert sub in found + + for sub in found: + assert sub in expected + + +def test_manager_lvl_1(topo, provision_inchain): + """Test that it succeeds to retrieve the subordinate + of level 1 manager + + :id: 193040ef-861e-41bd-84c7-c07a53a74e18 + :setup: Standalone instance + :steps: + 1. fixture provision a hierachical tree + 2. Check subordinates of 1_4 entry + 3. Check subordinates of 1_3 entry + 4. Check subordinates of 1_2 entry + 5. Check subordinates of 1_1 entry + :expectedresults: + 1. provisioning done + 2. found subordinates should match expected ones + 3. found subordinates should match expected ones + 4. found subordinates should match expected ones + 5. found subordinates should match expected ones + """ + + # Check subordinates of user_14 + # |__ 1_4 + # |__ 400 + # |__ 401 + # |__ 402 + # |__ 403 + # |__ 404 + uid = "test_user_14" + topo.standalone.log.info("Subordinate of uid=%s" % uid) + expected = [] + for i in range(400, 405): + uid_expected = "test_user_%s" % i + expected.append(uid_expected) + topo.standalone.log.info("Subordinate expected: %s" % uid_expected) + + check_subordinates(topo, uid, expected) + + # Check subordinates of user_13 + # |__ 1_3 + # | |__ 300 + # | |__ 301 + # | |__ 302 + # | |__ 303 + # | |__ 304 + uid = "test_user_13" + topo.standalone.log.info("Subordinate of uid=%s" % uid) + expected = [] + for i in range(300, 305): + uid_expected = "test_user_%s" % i + expected.append(uid_expected) + topo.standalone.log.info("Subordinate expected: %s" % uid_expected) + + check_subordinates(topo, uid, expected) + + # Check subordinates of user_12 + #| |__ 1_2 + #| | |__ 200 + #| | |__ 201 + #| | |__ 202 + #| | |__ 203 + #| | |__ 204 + + uid = "test_user_12" + topo.standalone.log.info("Subordinate of uid=%s" % uid) + expected = [] + for i in range(200, 205): + uid_expected = "test_user_%s" % i + expected.append(uid_expected) + topo.standalone.log.info("Subordinate expected: %s" % uid_expected) + + check_subordinates(topo, uid, expected) + + # Check subordinates of user_11 + #| |__ 1_1 + #| | |__ 100 + #| | |__ 101 + #| | |__ 102 + #| | |__ 103 + #| | |__ 104 + uid = "test_user_11" + topo.standalone.log.info("Subordinate of uid=%s" % uid) + expected = [] + for i in range(100, 105): + uid_expected = "test_user_%s" % i + expected.append(uid_expected) + topo.standalone.log.info("Subordinate expected: %s" % uid_expected) + + check_subordinates(topo, uid, expected) + +def test_manager_lvl_2(topo, provision_inchain): + """Test that it succeeds to retrieve the subordinate + of level 2 manager + + :id: d0c98211-3b90-4764-913d-f55ea5479029 + :setup: Standalone instance + :steps: + 1. fixture provision a hierachical tree + 2. Check subordinates of 2_1 entry + 3. Check subordinates of 2_2 entry + :expectedresults: + 1. provisioning done + 2. found subordinates should match expected ones + 3. found subordinates should match expected ones + """ + + # Check subordinates of user_22 + #| + #|__ 2_2 + # |__ 1_3 + # ... + # |__ 1_4 + # ... + uid = "test_user_22" + topo.standalone.log.info("Subordinate of uid=%s" % uid) + expected = [] + + # it contains user_14 and below + # |__ 1_4 + # |__ 400 + # |__ 401 + # |__ 402 + # |__ 403 + # |__ 404 + uid_expected = "test_user_14" + topo.standalone.log.info("Subordinate expected: %s" % uid_expected) + expected.append(uid_expected) + for i in range(400, 405): + uid_expected = "test_user_%s" % i + expected.append(uid_expected) + topo.standalone.log.info("Subordinate expected: %s" % uid_expected) + + # it contains user_13 and below + #| + #|__ 2_2 + # |__ 1_3 + # | |__ 300 + # | |__ 301 + # | |__ 302 + # | |__ 303 + # | |__ 304 + # | + uid_expected = "test_user_13" + topo.standalone.log.info("Subordinate expected: %s" % uid_expected) + expected.append(uid_expected) + for i in range(300, 305): + uid_expected = "test_user_%s" % i + expected.append(uid_expected) + topo.standalone.log.info("Subordinate expected: %s" % uid_expected) + + check_subordinates(topo, uid, expected) + + # Check subordinates of user_21 + #|__ 2_1 + #| |__ 1_1 + #| | + #| ... + #| | + #| |__ 1_2 + #| ... + uid = "test_user_21" + topo.standalone.log.info("Subordinate of uid=%s" % uid) + expected = [] + + # it contains user_12 and below + #|__ 2_1 + #| ... + #| |__ 1_2 + #| | |__ 200 + #| | |__ 201 + #| | |__ 202 + #| | |__ 203 + #| | |__ 204 + + uid_expected = "test_user_12" + topo.standalone.log.info("Subordinate expected: %s" % uid_expected) + expected.append(uid_expected) + for i in range(200, 205): + uid_expected = "test_user_%s" % i + expected.append(uid_expected) + topo.standalone.log.info("Subordinate expected: %s" % uid_expected) + + # it contains user_11 and below + #|__ 2_1 + #| |__ 1_1 + #| | |__ 100 + #| | |__ 101 + #| | |__ 102 + #| | |__ 103 + #| | |__ 104 + #| | + #| ... + uid_expected = "test_user_11" + topo.standalone.log.info("Subordinate expected: %s" % uid_expected) + expected.append(uid_expected) + for i in range(100, 105): + uid_expected = "test_user_%s" % i + expected.append(uid_expected) + topo.standalone.log.info("Subordinate expected: %s" % uid_expected) + + check_subordinates(topo, uid, expected) + +def test_manager_lvl_3(topo, provision_inchain): + """Test that it succeeds to retrieve the subordinate + of level 3 manager + + :id: d3708a39-7901-4c88-b4af-272aed2aa846 + :setup: Standalone instance + :steps: + 1. fixture provision a hierachical tree + 2. Check subordinates of 3_1 entry + :expectedresults: + 1. provisioning done + 2. found subordinates should match expected ones + """ + + # Check subordinates of user_31 + # 3_1 + #|__ 2_1 + #| |__ 1_1 + #| | |__ 100 + #| | |__ 101 + #| | |__ 102 + #| | |__ 103 + #| | |__ 104 + #| | + #| |__ 1_2 + #| | |__ 200 + #| | |__ 201 + #| | |__ 202 + #| | |__ 203 + #| | |__ 204 + #| + #|__ 2_2 + # |__ 1_3 + # | |__ 300 + # | |__ 301 + # | |__ 302 + # | |__ 303 + # | |__ 304 + # | + # |__ 1_4 + # |__ 400 + # |__ 401 + # |__ 402 + # |__ 403 + # |__ 404 + uid = "test_user_31" + topo.standalone.log.info("Subordinate of uid=%s" % uid) + expected = [] + + # it contains user_22 and below + uid_expected = "test_user_22" + topo.standalone.log.info("Subordinate expected: %s" % uid_expected) + expected.append(uid_expected) + + # it contains user_14 and below + uid_expected = "test_user_14" + topo.standalone.log.info("Subordinate expected: %s" % uid_expected) + expected.append(uid_expected) + for i in range(400, 405): + uid_expected = "test_user_%s" % i + expected.append(uid_expected) + topo.standalone.log.info("Subordinate expected: %s" % uid_expected) + + # it contains user_13 and below + uid_expected = "test_user_13" + topo.standalone.log.info("Subordinate expected: %s" % uid_expected) + expected.append(uid_expected) + for i in range(300, 305): + uid_expected = "test_user_%s" % i + expected.append(uid_expected) + topo.standalone.log.info("Subordinate expected: %s" % uid_expected) + + + # it contains user_21 and below + uid_expected = "test_user_21" + topo.standalone.log.info("Subordinate expected: %s" % uid_expected) + expected.append(uid_expected) + + # it contains user_12 and below + uid_expected = "test_user_12" + topo.standalone.log.info("Subordinate expected: %s" % uid_expected) + expected.append(uid_expected) + for i in range(200, 205): + uid_expected = "test_user_%s" % i + expected.append(uid_expected) + topo.standalone.log.info("Subordinate expected: %s" % uid_expected) + + # it contains user_11 and below + uid_expected = "test_user_11" + topo.standalone.log.info("Subordinate expected: %s" % uid_expected) + expected.append(uid_expected) + for i in range(100, 105): + uid_expected = "test_user_%s" % i + expected.append(uid_expected) + topo.standalone.log.info("Subordinate expected: %s" % uid_expected) + + check_subordinates(topo, uid, expected) + +def test_recompute_del(topo, provision_inchain): + """Test that if we delete a subordinate + the subordinate list is correctly updated + + :id: 6865876d-64b5-41a2-ae6e-4453aae5caab + :setup: Standalone instance + :steps: + 1. fixture provision a hierachical tree + 2. Check subordinates of 1_1 entry + 3. Delete user_100 + 4. Check subordinates of 1_1 entry + :expectedresults: + 1. provisioning done + 2. found subordinates should match expected ones + """ + + # Check subordinates of user_11 + #| |__ 1_1 + #| | |__ 100 + #| | |__ 101 + #| | |__ 102 + #| | |__ 103 + #| | |__ 104 + + uid = "test_user_11" + topo.standalone.log.info("Subordinate of uid=%s" % uid) + expected = [] + for i in range(100, 105): + uid_expected = "test_user_%s" % i + expected.append(uid_expected) + topo.standalone.log.info("Subordinate expected: %s" % uid_expected) + + check_subordinates(topo, uid, expected) + + del_dn = "uid=test_user_100,ou=People,%s" % (DEFAULT_SUFFIX) + user = UserAccount(topo.standalone, del_dn) + topo.standalone.log.info("Delete: %s" % del_dn) + user.delete() + + # Check subordinates of user_11 + #| |__ 1_1 + #| | |__ 101 + #| | |__ 102 + #| | |__ 103 + #| | |__ 104 + topo.standalone.log.info("Subordinate of uid=%s" % uid) + expected = [] + for i in range(101, 105): + uid_expected = "test_user_%s" % i + expected.append(uid_expected) + topo.standalone.log.info("Subordinate expected: %s" % uid_expected) + + check_subordinates(topo, uid, expected) + +def test_recompute_add(topo, provision_inchain, request): + """Test that if we add a subordinate + the subordinate list is correctly updated + + :id: 60d10233-37c2-400b-ac6e-d29b68706216 + :setup: Standalone instance + :steps: + 1. fixture provision a hierachical tree + 2. Check subordinates of 1_1 entry + 3. add user_105 + 4. Check subordinates of 1_1 entry + :expectedresults: + 1. provisioning done + 2. found subordinates should match expected ones + """ + + # Check subordinates of user_11 + #| |__ 1_1 + #| | |__ 100 + #| | |__ 101 + #| | |__ 102 + #| | |__ 103 + #| | |__ 104 + uid = "test_user_11" + topo.standalone.log.info("Subordinate of uid=%s" % uid) + expected = [] + for i in range(100, 105): + uid_expected = "test_user_%s" % i + expected.append(uid_expected) + topo.standalone.log.info("Subordinate expected: %s" % uid_expected) + + check_subordinates(topo, uid, expected) + + # add a new subordinate of user_11 + users = UserAccounts(topo.standalone, DEFAULT_SUFFIX) + user_added = users.create_test_user(uid=105) + topo.standalone.log.info("Add: %s" % user_added.dn) + user_added.set("manager", "uid=%s,ou=People,%s" % (uid, DEFAULT_SUFFIX)) + + # Check subordinates of user_11 + #| |__ 1_1 + #| | |__ 100 + #| | |__ 101 + #| | |__ 102 + #| | |__ 103 + #| | |__ 104 + #| | |__ 105 + topo.standalone.log.info("Subordinate of uid=%s" % uid) + expected = [] + for i in range(100, 106): + uid_expected = "test_user_%s" % i + expected.append(uid_expected) + topo.standalone.log.info("Subordinate expected: %s" % uid_expected) + + check_subordinates(topo, uid, expected) + + def fin(): + user_added.delete() + + request.addfinalizer(fin) + + +def test_anonymous_inchain(topo, provision_inchain): + """Test that anonymous connection can not + retrieve subordinates + + :id: d6c41cc1-7c36-4a3f-bcdf-f479c12310e2 + :setup: Standalone instance + :steps: + 1. fixture provision a hierachical tree + 2. bound anonymous + 3. Check subordinates of 1_2 entry is empty although hierarchy + :expectedresults: + 1. provisioning done + 2. succeed + 3. succeeds but 0 subordinates + """ + + # create an anonymous connection + topo.standalone.log.info("Bind as anonymous user") + conn = Anonymous(topo.standalone).bind() + + # Check that there are no subordinates of test_user_12 + uid = "test_user_12" + manager = "uid=%s,ou=People,%s" % (uid, DEFAULT_SUFFIX) + topo.standalone.log.info("Subordinate of manager %s on anonymous connection" % manager) + + subordinates = conn.search_s(DEFAULT_SUFFIX, SCOPE_SUBTREE, "(manager:%s:=%s)" % (INCHAIN_OID, manager)) + assert len(subordinates) == 0 + + # Check the ACI right failure + assert topo.standalone.ds_error_log.match('.*inchain - Requestor is not allowed to use InChain Matching rule$') + +def test_authenticated_inchain(topo, provision_inchain, request): + """Test that bound connection can not + retrieve subordinates (only DM is allowed by default) + + :id: 17ebee0d-86e2-4f0d-95fe-8a4cf566a493 + :setup: Standalone instance + :steps: + 1. fixture provision a hierachical tree + 2. create a test user + 3. create a bound connection + 4. Check subordinates of 1_2 entry is empty although hierarchy + :expectedresults: + 1. provisioning done + 2. succeed + 3. succeed + 4. succeeds but 0 subordinates + """ + + # create a user + RDN = "test_bound_user" + test_user = UserAccount(topo.standalone, "uid=%s,ou=People,%s" % (RDN, DEFAULT_SUFFIX)) + test_user.create(properties={ + 'uid': RDN, + 'cn': RDN, + 'sn': RDN, + 'userPassword': "password", + 'uidNumber' : '1000', + 'gidNumber' : '2000', + 'homeDirectory' : '/home/inchain', + }) + + # create a bound connection + conn = test_user.bind("password") + + # Check that there are no subordinates of test_user_12 + uid = "test_user_12" + manager = "uid=%s,ou=People,%s" % (uid, DEFAULT_SUFFIX) + topo.standalone.log.info("Subordinate of manager %s on bound connection" % manager) + + subordinates = conn.search_s(DEFAULT_SUFFIX, SCOPE_SUBTREE, "(manager:%s:=%s)" % (INCHAIN_OID, manager)) + assert len(subordinates) == 0 + + # Check the ACI right failure + assert topo.standalone.ds_error_log.match('.*inchain - Requestor is not allowed to use InChain Matching rule$') + + def fin(): + test_user.delete() + + request.addfinalizer(fin) + + +def _create_user(topology_st, ext): + user_dn = "uid=%s,ou=People,%s" % (ext, DEFAULT_SUFFIX) + topology_st.standalone.add_s(Entry((user_dn, { + 'objectclass': 'top extensibleObject'.split(), + 'uid': ext + }))) + topology_st.standalone.log.info("Create user %s" % user_dn) + return ensure_bytes(user_dn) + +def _create_group(topology_st, ext): + group_dn = "ou=%s,ou=People,%s" % (ext, DEFAULT_SUFFIX) + topology_st.standalone.add_s(Entry((group_dn, { + 'objectclass': 'top groupOfNames extensibleObject'.split(), + 'ou': ext, + 'cn': ext + }))) + topology_st.standalone.log.info("Create group %s" % group_dn) + return ensure_bytes(group_dn) + +def test_reuse_memberof(topo, request): + """Check that slapi_memberof successfully + compute the membership either using 'memberof' attribute + or either recomputing it. + + :id: e52bd21a-3ff6-493f-9ec5-8cf4e76696a0 + :setup: Standalone instance + :steps: + 1. Enable the plugin + 2. Create a user belonging in cascade to 3 groups + 3. Check that slapi_member re-computes membership + 4. Check that slapi_member retrieve membership from memberof + :expectedresults: + 1. Success + 2. Success + 3. Success + 4. Success + """ + + # enable the plugin + topo.standalone.log.info("Enable MemberOf plugin") + topo.standalone.plugins.enable(name=PLUGIN_MEMBER_OF) + topo.standalone.restart() + + # Create a user belonging to 3 goups + # in cascade + user1 = _create_user(topo, 'user1') + + group1 = _create_group(topo, 'group1') + mods = [(ldap.MOD_ADD, 'member', user1)] + topo.standalone.modify_s(ensure_str(group1), mods) + + group2 = _create_group(topo, 'group2') + mods = [(ldap.MOD_ADD, 'member', group1)] + topo.standalone.modify_s(ensure_str(group2), mods) + + group3 = _create_group(topo, 'group3') + mods = [(ldap.MOD_ADD, 'member', group2)] + topo.standalone.modify_s(ensure_str(group3), mods) + + # Call slapi_member that does *not* reuse the memberof + # because of 'memberOfEntryScope' not being set + topo.standalone.log.info("Groups that %s is memberof" % ensure_str(user1)) + + topo.standalone.config.set('nsslapd-errorlog-level', '65536') # plugin logging + memberof = topo.standalone.search_s(DEFAULT_SUFFIX, SCOPE_SUBTREE, "(member:%s:=%s)" % (INCHAIN_OID, ensure_str(user1))) + topo.standalone.config.set('nsslapd-errorlog-level', '0') + for sub in memberof: + topo.standalone.log.info("memberof found : %s" % sub.dn) + assert sub.dn in [ensure_str(group1), ensure_str(group2), ensure_str(group3)] + assert topo.standalone.ds_error_log.match('.*sm_compare_memberof_config: fails because requested include scope is not empty.*') + assert not topo.standalone.ds_error_log.match('.*slapi_memberof - sm_compare_memberof_config: succeeds. requested options match config.*') + + # Call slapi_member that does reuse the memberof + # because of 'memberOfEntryScope' being set + memberof = MemberOfPlugin(topo.standalone) + memberof.replace('memberOfEntryScope', DEFAULT_SUFFIX) + topo.standalone.restart() + topo.standalone.config.set('nsslapd-errorlog-level', '65536') # plugin logging + memberof = topo.standalone.search_s(DEFAULT_SUFFIX, SCOPE_SUBTREE, "(member:%s:=%s)" % (INCHAIN_OID, ensure_str(user1))) + topo.standalone.config.set('nsslapd-errorlog-level', '0') + for sub in memberof: + topo.standalone.log.info("memberof found : %s" % sub.dn) + assert sub.dn in [ensure_str(group1), ensure_str(group2), ensure_str(group3)] + assert topo.standalone.ds_error_log.match('.*slapi_memberof - sm_compare_memberof_config: succeeds. requested options match config.*') + + def fin(): + topo.standalone.delete_s(ensure_str(user1)) + topo.standalone.delete_s(ensure_str(group1)) + topo.standalone.delete_s(ensure_str(group2)) + topo.standalone.delete_s(ensure_str(group3)) + + request.addfinalizer(fin) + +def test_invalid_assertion(topo): + """Check that with invalid assertion + there is no returned entries + + :id: 0a204b81-e7c0-41a0-97cc-7d9425a603c2 + :setup: Standalone instance + :steps: + 1. Search with invalid assertion '..:=foo' + 2. Search with not existing entry '..:=' + :expectedresults: + 1. Success + 2. Success + """ + topo.standalone.log.info("Search with an invalid assertion") + memberof = topo.standalone.search_s(DEFAULT_SUFFIX, SCOPE_SUBTREE, "(member:%s:=foo)" % (INCHAIN_OID)) + assert len(memberof) == 0 + + topo.standalone.log.info("Search with an none exisiting entry") + + user = "uid=not_existing_entry,ou=People,%s" % (DEFAULT_SUFFIX) + memberof = topo.standalone.search_s(DEFAULT_SUFFIX, SCOPE_SUBTREE, "(member:%s:=%s)" % (INCHAIN_OID, user)) + assert len(memberof) == 0 + +if __name__ == "__main__": + CURRENT_FILE = os.path.realpath(__file__) + pytest.main("-s -v %s" % CURRENT_FILE) diff -Nru 389-ds-base-2.4.4+dfsg1/dirsrvtests/tests/suites/healthcheck/health_tunables_test.py 389-ds-base-2.4.5+dfsg1/dirsrvtests/tests/suites/healthcheck/health_tunables_test.py --- 389-ds-base-2.4.4+dfsg1/dirsrvtests/tests/suites/healthcheck/health_tunables_test.py 1970-01-01 00:00:00.000000000 +0000 +++ 389-ds-base-2.4.5+dfsg1/dirsrvtests/tests/suites/healthcheck/health_tunables_test.py 2024-01-15 10:27:07.000000000 +0000 @@ -0,0 +1,148 @@ +# --- BEGIN COPYRIGHT BLOCK --- +# Copyright (C) 2023 Red Hat, Inc. +# All rights reserved. +# +# License: GPL (version 3 or any later version). +# See LICENSE for details. +# --- END COPYRIGHT BLOCK --- +# + +import subprocess +import pytest +import re +import os +from lib389.utils import * +from lib389.cli_base import FakeArgs +from lib389.topologies import topology_st +from lib389.cli_ctl.health import health_check_run +from lib389.paths import Paths + +CMD_OUTPUT = 'No issues found.' +JSON_OUTPUT = '[]' +RET_CODE = 'DSTHPLE0001' + +log = logging.getLogger(__name__) +p = Paths() + + +def run_healthcheck_and_flush_log(topology, instance, searched_code=None, json=False, searched_code2=None, + list_checks=False, list_errors=False, check=None, searched_list=None): + args = FakeArgs() + args.instance = instance.serverid + args.verbose = instance.verbose + args.list_errors = list_errors + args.list_checks = list_checks + args.check = check + args.dry_run = False + args.json = json + + log.info('Use healthcheck with --json == {} option'.format(json)) + health_check_run(instance, topology.logcap.log, args) + + if searched_list is not None: + for item in searched_list: + assert topology.logcap.contains(item) + log.info('Healthcheck returned searched item: %s' % item) + else: + assert topology.logcap.contains(searched_code) + log.info('Healthcheck returned searched code: %s' % searched_code) + + if searched_code2 is not None: + assert topology.logcap.contains(searched_code2) + log.info('Healthcheck returned searched code: %s' % searched_code2) + + log.info('Clear the log') + topology.logcap.flush() + + +def _set_thp_system_mode(mode): + thp_path = '/sys/kernel/mm/transparent_hugepage/enabled' + with open(thp_path, 'w') as f: + log.info(f"Setting THP mode to {mode}") + f.write(mode) + + +def _set_thp_instance_mode(inst, disable: bool): + service_config = f"[Service]\nEnvironment=THP_DISABLE={int(disable)}" + drop_in_path = f"/etc/systemd/system/dirsrv@{inst.serverid}.service.d/" + os.makedirs(drop_in_path, exist_ok=True) + with open(os.path.join(drop_in_path, "thp.conf"), 'w') as f: + f.write(service_config) + subprocess.run(['systemctl', 'daemon-reload'], check=True) + inst.restart() + + +def _get_thp_system_mode(): + thp_path = '/sys/kernel/mm/transparent_hugepage/enabled' + enabled_value_pattern = r'\[([^\]]+)\]' + with open(thp_path, 'r') as f: + text = f.read().strip() + mode = re.search(enabled_value_pattern, text)[1] + log.info(f"Current THP mode is {mode}") + return mode + + +@pytest.fixture(scope="function") +def thp_reset(request): + mode = _get_thp_system_mode() + + def fin(): + _set_thp_system_mode(mode) + + request.addfinalizer(fin) + + +@pytest.mark.skipif(get_user_is_root() is False, + reason="This test requires root permissions to change kernel tunables") +@pytest.mark.skipif(p.with_systemd is False, reason='Needs systemd to run') +@pytest.mark.skipif(ds_is_older("2.4.4"), reason="Not implemented") +@pytest.mark.parametrize("system_thp_mode,instance_thp_mode,expected_output", + [("always", False, (RET_CODE, RET_CODE)), + ("always", True, (CMD_OUTPUT, JSON_OUTPUT)), + ("never", False, (CMD_OUTPUT, JSON_OUTPUT)), + ("never", True, (CMD_OUTPUT, JSON_OUTPUT))], + ids=["System and Instance THP ON", + "System THP ON, Instance THP OFF", + "System THP OFF, Instance THP ON", + "System THP OFF, Instance THP OFF"]) +@pytest.mark.usefixtures("thp_reset") +def test_healthcheck_transparent_huge_pages(topology_st, system_thp_mode, instance_thp_mode, expected_output): + """Check if HealthCheck returns DSTHPLE0001 code + + :id: 1f195e10-6403-4c92-8ac9-724b669e8cf2 + :setup: Standalone instance + :parametrized: yes + :steps: + 1. Enable THP system wide and for the instance + 2. Use HealthCheck without --json option + 3. Use HealthCheck with --json option + 4. Enable THP system wide, disable THP for the instance + 5. Use HealthCheck without --json option + 6. Use HealthCheck with --json option + 7. Disable THP system wide, enable THP for the instance + 8. Use HealthCheck without --json option + 9. Use HealthCheck with --json option + 10. Disable THP system wide, disable THP for the instance + 11. Use HealthCheck without --json option + 12. Use HealthCheck with --json option + :expectedresults: + 1. Success + 2. HealthCheck should return code DSHTPLE0001 + 3. HealthCheck should return code DSTHPLE0001 + 4. Success + 5. HealthCheck reports no issue found + 6. HealthCheck reports no issue found + 7. Success + 8. HealthCheck reports no issue found + 9. HealthCheck reports no issue found + 10. Success + 11. HealthCheck reports no issue found + 12. HealthCheck reports no issue found + """ + standalone = topology_st.standalone + standalone.config.set("nsslapd-accesslog-logbuffering", "on") + + _set_thp_system_mode(system_thp_mode) + _set_thp_instance_mode(standalone, instance_thp_mode) + run_healthcheck_and_flush_log(topology_st, standalone, expected_output[0], json=False) + run_healthcheck_and_flush_log(topology_st, standalone, expected_output[1], json=True) diff -Nru 389-ds-base-2.4.4+dfsg1/dirsrvtests/tests/suites/healthcheck/healthcheck_test.py 389-ds-base-2.4.5+dfsg1/dirsrvtests/tests/suites/healthcheck/healthcheck_test.py --- 389-ds-base-2.4.4+dfsg1/dirsrvtests/tests/suites/healthcheck/healthcheck_test.py 2023-11-15 14:06:27.000000000 +0000 +++ 389-ds-base-2.4.5+dfsg1/dirsrvtests/tests/suites/healthcheck/healthcheck_test.py 2024-01-15 10:27:07.000000000 +0000 @@ -153,7 +153,9 @@ 'replication:conflicts', 'dseldif:nsstate', 'tls:certificate_expiration', - 'logs:notes'] + 'logs:notes', + 'tunables:thp', + ] standalone = topology_st.standalone @@ -205,6 +207,7 @@ 'DSSKEWLE0001 :: Medium time skew', 'DSSKEWLE0002 :: Major time skew', 'DSSKEWLE0003 :: Extensive time skew', + 'DSTHPLE0001 :: Transparent Huge Pages', 'DSVIRTLE0001 :: Virtual attribute indexed'] standalone = topology_st.standalone diff -Nru 389-ds-base-2.4.4+dfsg1/dirsrvtests/tests/suites/paged_results/paged_results_test.py 389-ds-base-2.4.5+dfsg1/dirsrvtests/tests/suites/paged_results/paged_results_test.py --- 389-ds-base-2.4.4+dfsg1/dirsrvtests/tests/suites/paged_results/paged_results_test.py 2023-11-15 14:06:27.000000000 +0000 +++ 389-ds-base-2.4.5+dfsg1/dirsrvtests/tests/suites/paged_results/paged_results_test.py 2024-01-15 10:27:07.000000000 +0000 @@ -7,7 +7,8 @@ # --- END COPYRIGHT BLOCK --- # import socket -from random import sample +from random import sample, randrange + import pytest from ldap.controls import SimplePagedResultsControl, GetEffectiveRightsControl from lib389.tasks import * @@ -16,6 +17,10 @@ from lib389._constants import DN_LDBM, DN_DM, DEFAULT_SUFFIX from lib389._controls import SSSRequestControl from lib389.idm.user import UserAccount, UserAccounts +from lib389.cli_base import FakeArgs +from lib389.config import LDBMConfig +from lib389.dbgen import dbgen_users + from lib389.idm.organization import Organization from lib389.idm.organizationalunit import OrganizationalUnit from lib389.backend import Backends @@ -42,11 +47,56 @@ NEW_BACKEND_2 = 'child_base' OLD_HOSTNAME = socket.gethostname() -socket.sethostname('localhost') +if os.getuid() == 0: + socket.sethostname('localhost') HOSTNAME = socket.gethostname() IP_ADDRESS = socket.gethostbyname(HOSTNAME) OLD_IP_ADDRESS = socket.gethostbyname(OLD_HOSTNAME) + +@pytest.fixture(scope="module") +def create_40k_users(topology_st, request): + inst = topology_st.standalone + + # Prepare return value + retval = FakeArgs() + retval.inst = inst + retval.bename = '40k' + retval.suffix = f'o={retval.bename}' + retval.ldif_file = f'{inst.get_ldif_dir()}/{retval.bename}.ldif' + + # Create new backend + bes = Backends(inst) + be_1 = bes.create(properties={ + 'cn': retval.bename, + 'nsslapd-suffix': retval.suffix, + }) + + # Set paged search lookthrough limit + ldbmconfig = LDBMConfig(inst) + ldbmconfig.replace('nsslapd-pagedlookthroughlimit', b'100000') + + # Create ldif and import it. + dbgen_users(inst, 40000, retval.ldif_file, retval.suffix) + # tasks = Tasks(inst) + # args = {TASK_WAIT: True} + # tasks.importLDIF(retval.suffix, None, retval.ldif_file, args) + inst.stop() + assert inst.ldif2db(retval.bename, None, None, None, retval.ldif_file, None) + inst.start() + + # And set an aci allowing anonymous read + log.info('Adding ACI to allow our test user to search') + ACI_TARGET = '(targetattr != "userPassword || aci")' + ACI_ALLOW = '(version 3.0; acl "Enable anonymous access";allow (read, search, compare)' + ACI_SUBJECT = '(userdn = "ldap:///anyone");)' + ACI_BODY = ACI_TARGET + ACI_ALLOW + ACI_SUBJECT + o_1 = Organization(inst, retval.suffix) + o_1.set('aci', ACI_BODY) + + return retval + + @pytest.fixture(scope="module") def create_user(topology_st, request): """User for binding operation""" @@ -71,8 +121,10 @@ def fin(): log.info('Deleting user simplepaged_test') - user.delete() - socket.sethostname(OLD_HOSTNAME) + if not DEBUGGING: + user.delete() + if os.getuid() == 0: + socket.sethostname(OLD_HOSTNAME) request.addfinalizer(fin) @@ -175,7 +227,7 @@ return attr_value_bck -def paged_search(conn, suffix, controls, search_flt, searchreq_attrlist): +def paged_search(conn, suffix, controls, search_flt, searchreq_attrlist, abandon_rate=0): """Search at the DEFAULT_SUFFIX with ldap.SCOPE_SUBTREE using Simple Paged Control(should the first item in the list controls. @@ -195,9 +247,16 @@ req_pr_ctrl.size, str(controls))) msgid = conn.search_ext(suffix, ldap.SCOPE_SUBTREE, search_flt, searchreq_attrlist, serverctrls=controls) + log.info('Getting page %d' % (pages,)) while True: - log.info('Getting page %d' % (pages,)) - rtype, rdata, rmsgid, rctrls = conn.result3(msgid) + try: + rtype, rdata, rmsgid, rctrls = conn.result3(msgid, timeout=0.001) + except ldap.TIMEOUT: + if pages > 0 and abandon_rate>0 and randrange(100) +#endif + +/* inchain.c - in_chain syntax routines + see https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/1e889adc-b503-4423-8985-c28d5c7d4887 + */ + +#include +#include +#include +#include "syntax.h" +#include "slapi-plugin.h" + +int inchain_filter_ava(Slapi_PBlock *pb, struct berval *bvfilter, Slapi_Value **bvals, int ftype, Slapi_Value **retVal); +int inchain_filter_sub(Slapi_PBlock *pb, char *initial, char **any, char * final, Slapi_Value **bvals); +int inchain_values2keys(Slapi_PBlock *pb, Slapi_Value **val, Slapi_Value ***ivals, int ftype); +int inchain_assertion2keys_ava(Slapi_PBlock *pb, Slapi_Value *val, Slapi_Value ***ivals, int ftype); +int inchain_assertion2keys_sub(Slapi_PBlock *pb, char *initial, char **any, char * final, Slapi_Value ***ivals); +int inchain_validate(struct berval *val); +void inchain_normalize( + Slapi_PBlock *pb, + char *s, + int trim_spaces, + char **alt); + +/* the first name is the official one from RFC 4517 */ +static char *names[] = {"inchain", "inchain", LDAP_MATCHING_RULE_IN_CHAIN_OID, 0}; + +static Slapi_PluginDesc pdesc = {"inchain-matching-rule", VENDOR, DS_PACKAGE_VERSION, + "inchain matching rule plugin"}; + +static const char *inchainMatch_names[] = {"inchainMatch", "1.2.840.113556.1.4.1941", NULL}; + +static struct mr_plugin_def mr_plugin_table[] = { + { + { + "1.2.840.113556.1.4.1941", + NULL, + "inchainMatch", + "The distinguishedNameMatch rule compares an assertion value of the DN " + "syntax to an attribute value of a syntax (e.g., the DN syntax) whose " + "corresponding ASN.1 type is DistinguishedName. " + "The rule evaluates to TRUE if and only if the attribute value and the " + "assertion value have the same number of relative distinguished names " + "and corresponding relative distinguished names (by position) are the " + "same. A relative distinguished name (RDN) of the assertion value is " + "the same as an RDN of the attribute value if and only if they have " + "the same number of attribute value assertions and each attribute " + "value assertion (AVA) of the first RDN is the same as the AVA of the " + "second RDN with the same attribute type. The order of the AVAs is " + "not significant. Also note that a particular attribute type may " + "appear in at most one AVA in an RDN. Two AVAs with the same " + "attribute type are the same if their values are equal according to " + "the equality matching rule of the attribute type. If one or more of " + "the AVA comparisons evaluate to Undefined and the remaining AVA " + "comparisons return TRUE then the distinguishedNameMatch rule " + "evaluates to Undefined.", + NULL, + 0, + NULL /* dn only for now */ + }, /* matching rule desc */ + { + "inchainMatch-mr", + VENDOR, + DS_PACKAGE_VERSION, + "inchain matching rule plugin"}, /* plugin desc */ + inchainMatch_names, /* matching rule name/oid/aliases */ + NULL, + NULL, + inchain_filter_ava, + NULL, + inchain_values2keys, + inchain_assertion2keys_ava, + NULL, + NULL, + NULL /* mr_nomalise */ + }, +}; + +static size_t mr_plugin_table_size = sizeof(mr_plugin_table) / sizeof(mr_plugin_table[0]); + +static int +matching_rule_plugin_init(Slapi_PBlock *pb) +{ + return syntax_matching_rule_plugin_init(pb, mr_plugin_table, mr_plugin_table_size); +} + +static int +register_matching_rule_plugins(void) +{ + return syntax_register_matching_rule_plugins(mr_plugin_table, mr_plugin_table_size, matching_rule_plugin_init); +} + +static int +inchain_feature_allowed(Slapi_PBlock *pb) +{ + int isroot = 0; + int ldapcode = LDAP_SUCCESS; + + slapi_pblock_get(pb, SLAPI_REQUESTOR_ISROOT, &isroot); + if (!isroot) { + char *dn; + Slapi_Entry *feature = NULL; + + /* Fetch the feature entry and see if the requestor is allowed access. */ + dn = slapi_ch_smprintf("dn: oid=%s,cn=features,cn=config", LDAP_MATCHING_RULE_IN_CHAIN_OID); + if ((feature = slapi_str2entry(dn, 0)) != NULL) { + char *dummy_attr = "1.1"; + Slapi_Backend *be = NULL; + + be = slapi_mapping_tree_find_backend_for_sdn(slapi_entry_get_sdn(feature)); + if (NULL == be) { + ldapcode = LDAP_INSUFFICIENT_ACCESS; + } else { + slapi_pblock_set(pb, SLAPI_BACKEND, be); + ldapcode = slapi_access_allowed(pb, feature, dummy_attr, NULL, SLAPI_ACL_READ); + } + } + + /* If the feature entry does not exist, deny use of the control. Only + * the root DN will be allowed to use the control in this case. */ + if ((feature == NULL) || (ldapcode != LDAP_SUCCESS)) { + ldapcode = LDAP_INSUFFICIENT_ACCESS; + } + slapi_ch_free((void **)&dn); + slapi_entry_free(feature); + } + return (ldapcode); +} + +int +inchain_init(Slapi_PBlock *pb) +{ + int rc; + + slapi_log_err(SLAPI_LOG_PLUGIN, SYNTAX_PLUGIN_SUBSYSTEM, "=> inchain_init\n"); + + rc = slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, + (void *)SLAPI_PLUGIN_VERSION_01); + rc |= slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, + (void *)&pdesc); + rc |= slapi_pblock_set(pb, SLAPI_PLUGIN_SYNTAX_FILTER_AVA, + (void *)inchain_filter_ava); + rc |= slapi_pblock_set(pb, SLAPI_PLUGIN_SYNTAX_FILTER_SUB, + (void *)inchain_filter_sub); + rc |= slapi_pblock_set(pb, SLAPI_PLUGIN_SYNTAX_VALUES2KEYS, + (void *)inchain_values2keys); + rc |= slapi_pblock_set(pb, SLAPI_PLUGIN_SYNTAX_ASSERTION2KEYS_AVA, + (void *)inchain_assertion2keys_ava); + rc |= slapi_pblock_set(pb, SLAPI_PLUGIN_SYNTAX_ASSERTION2KEYS_SUB, + (void *)inchain_assertion2keys_sub); + rc |= slapi_pblock_set(pb, SLAPI_PLUGIN_SYNTAX_NAMES, + (void *)names); + rc |= slapi_pblock_set(pb, SLAPI_PLUGIN_SYNTAX_OID, + (void *)LDAP_MATCHING_RULE_IN_CHAIN_OID); + rc |= slapi_pblock_set(pb, SLAPI_PLUGIN_SYNTAX_VALIDATE, + (void *)inchain_validate); + rc |= slapi_pblock_set(pb, SLAPI_PLUGIN_SYNTAX_NORMALIZE, + (void *)inchain_normalize); + + rc |= register_matching_rule_plugins(); + slapi_log_err(SLAPI_LOG_PLUGIN, SYNTAX_PLUGIN_SUBSYSTEM, "<= inchain_init %d\n", rc); + return (rc); +} + +int +inchain_filter_ava(Slapi_PBlock *pb, struct berval *bvfilter, Slapi_Value **bvals, int ftype, Slapi_Value **retVal) +{ + /* always true because candidate entries are valid */ + /* in theory we should check the filter but with inchain MR + * inchain_values2keys select candidates where membership attribute/value + * are not systematically present in the candidate entry (recursive call) + * this is the reason why this usual check does not apply + */ +#if 0 + int syntax = SYNTAX_CIS | SYNTAX_DN; + return (string_filter_ava(bvfilter, bvals, syntax, ftype, retVal)); +#else + return(0); +#endif +} + +int +inchain_filter_sub(Slapi_PBlock *pb, char *initial, char **any, char * final, Slapi_Value **bvals) +{ + return(1); +} + + +static PRIntn memberof_hash_compare_keys(const void *v1, const void *v2) +{ + PRIntn rc; + if (0 == strcasecmp((const char *) v1, (const char *) v2)) { + rc = 1; + } else { + rc = 0; + } + return rc; +} + +static PRIntn memberof_hash_compare_values(const void *v1, const void *v2) +{ + PRIntn rc; + if ((char *) v1 == (char *) v2) { + rc = 1; + } else { + rc = 0; + } + return rc; +} + +/* + * Hashing function using Bernstein's method + */ +static PLHashNumber memberof_hash_fn(const void *key) +{ + PLHashNumber hash = 5381; + unsigned char *x = (unsigned char *)key; + int c; + + while ((c = *x++)){ + hash = ((hash << 5) + hash) ^ c; + } + return hash; +} + +/* allocates the plugin hashtable + * This hash table is used by operation and is protected from + * concurrent operations with the memberof_lock (if not usetxn, memberof_lock + * is not implemented and the hash table will be not used. + * + * The hash table contains all the DN of the entries for which the memberof + * attribute has been computed/updated during the current operation + * + * hash table should be empty at the beginning and end of the plugin callback + */ +PLHashTable *hashtable_new(int usetxn) +{ + if (!usetxn) { + return NULL; + } + + return PL_NewHashTable(1000, + memberof_hash_fn, + memberof_hash_compare_keys, + memberof_hash_compare_values, NULL, NULL); +} +int +inchain_values2keys(Slapi_PBlock *pb, Slapi_Value **vals, Slapi_Value ***ivals, int ftype) +{ + Slapi_MemberOfResult groupvals = {0}; + Slapi_ValueSet *groupdn_vals; + Slapi_Value **result; + int nbvalues; + Slapi_Value *v; + Slapi_MemberOfConfig config = {0}; + Slapi_DN *member_sdn; + Slapi_DN *base_sdn = NULL; + size_t idx = 0; + char *mrTYPE; +#if 0 + char *filter_str; +#endif + char error_msg[1024] = {0}; + int rc; + + slapi_pblock_get(pb, SLAPI_PLUGIN_MR_TYPE, &mrTYPE); + slapi_pblock_get(pb, SLAPI_SEARCH_TARGET_SDN, &base_sdn); + + if (! slapi_attr_is_dn_syntax_type(mrTYPE)) { + slapi_log_err(SLAPI_LOG_ERR, "inchain", "Requires distinguishedName syntax. AttributeDescription %s is not distinguishedName\n"); + result = (Slapi_Value **)slapi_ch_calloc(1, sizeof(Slapi_Value *)); + *ivals = result; + return(0); + } + + /* check if the user is allowed to perform inChain matching */ + if (inchain_feature_allowed(pb) != LDAP_SUCCESS) { + slapi_log_err(SLAPI_LOG_ERR, "inchain", "Requestor is not allowed to use InChain Matching rule\n"); + result = (Slapi_Value **)slapi_ch_calloc(1, sizeof(Slapi_Value *)); + *ivals = result; + return(0); + } + + /* it is used only in case of MEMBEROF_REUSE_ONLY of MEMBEROF_REUSE_IF_POSSIBLE + * to reuse potential results from memberof plugin + * So its value is only "memberof" + */ + config.memberof_attr = "memberof"; + config.groupattrs = (char **) slapi_ch_calloc(sizeof(char*), 2); + config.groupattrs[0] = mrTYPE; + config.groupattrs[1] = NULL; + config.subtree_search = PR_FALSE; + config.allBackends = 0; + config.entryScopes = (Slapi_DN **)slapi_ch_calloc(sizeof(Slapi_DN *), 2); + /* only looking in the base search scope */ + config.entryScopes[0] = slapi_sdn_dup((const Slapi_DN *) base_sdn); + + /* no exclusion for inchain */ + config.entryScopeExcludeSubtrees = NULL; + +#if 0 + filter_str = slapi_ch_smprintf("(%s=*)", "manager"); + config.group_filter = slapi_str2filter(filter_str); + + config.group_slapiattrs = (Slapi_Attr **)slapi_ch_calloc(sizeof(Slapi_Attr *), 3); + config.group_slapiattrs[0] = slapi_attr_new(); + config.group_slapiattrs[1] = slapi_attr_new(); + slapi_attr_init(config.group_slapiattrs[0], "manager"); + slapi_attr_init(config.group_slapiattrs[1], "nsuniqueid"); +#endif + config.recurse = PR_TRUE; + config.maxgroups = 0; + config.flag = MEMBEROF_REUSE_IF_POSSIBLE; + config.error_msg = error_msg; + config.errot_msg_lenght = sizeof(error_msg); + + member_sdn = slapi_sdn_new_dn_byval((const char*) vals[0]->bv.bv_val); + rc = slapi_memberof(&config, member_sdn, &groupvals); + if (rc) { + slapi_log_err(SLAPI_LOG_ERR, "inchain", " slapi_memberof fails %d (msg=%s)\n", rc, error_msg); + } +#if 0 + slapi_filter_free(config.group_filter, 1); + slapi_attr_free(&config.group_slapiattrs[0]); + slapi_attr_free(&config.group_slapiattrs[1]); +#endif + groupdn_vals = groupvals.nsuniqueid_vals; + idx = slapi_valueset_first_value(groupdn_vals, &v); + for (; groupdn_vals && v; idx = slapi_valueset_next_value(groupdn_vals, idx, &v)) { + char value[1000]; + strncpy(value, v->bv.bv_val, v->bv.bv_len); + value[v->bv.bv_len] = '\0'; + slapi_log_err(SLAPI_LOG_FILTER, "inchain", " groupvals = %s\n", value); + + } + +#if 1 + + nbvalues = slapi_valueset_count(groupdn_vals); + result = (Slapi_Value **)slapi_ch_calloc(nbvalues + 1, sizeof(Slapi_Value *)); + for(idx = 0; idx < slapi_valueset_count(groupdn_vals); idx++) { + char value[1000]; + + result[idx] = slapi_value_dup(groupdn_vals->va[idx]); + strncpy(value, result[idx]->bv.bv_val, result[idx]->bv.bv_len); + value[result[idx]->bv.bv_len] = '\0'; + slapi_log_err(SLAPI_LOG_FILTER, "inchain", "copy key %s \n", value); + } + if (groupvals.dn_vals) { + slapi_valueset_free(groupvals.dn_vals); + groupvals.dn_vals = NULL; + } + if (groupvals.nsuniqueid_vals) { + slapi_valueset_free(groupvals.nsuniqueid_vals); + groupvals.nsuniqueid_vals = NULL; + } + *ivals = result; + return(0); +#else + return (string_values2keys(pb, vals, ivals, SYNTAX_CIS | SYNTAX_DN, + ftype)); +#endif +} + +int +inchain_assertion2keys_ava(Slapi_PBlock *pb, Slapi_Value *val, Slapi_Value ***ivals, int ftype) +{ + slapi_log_err(SLAPI_LOG_ERR, "inchain", "inchain_assertion2keys_ava \n"); + return (string_assertion2keys_ava(pb, val, ivals, + SYNTAX_CIS | SYNTAX_DN, ftype)); +} + +int +inchain_assertion2keys_sub(Slapi_PBlock *pb, char *initial, char **any, char * final, Slapi_Value ***ivals) +{ + slapi_log_err(SLAPI_LOG_ERR, "inchain", "inchain_assertion2keys_sub \n"); + return (string_assertion2keys_sub(pb, initial, any, final, ivals, + SYNTAX_CIS | SYNTAX_DN)); +} + +int +inchain_validate(struct berval *val) +{ + int rc = 0; /* Assume value is valid */ + + /* A 0 length value is valid for the DN syntax. */ + if (val == NULL) { + rc = 1; + } else if (val->bv_len > 0) { + rc = distinguishedname_validate(val->bv_val, &(val->bv_val[val->bv_len - 1])); + } + + return rc; +} + +void +inchain_normalize( + Slapi_PBlock *pb __attribute__((unused)), + char *s, + int trim_spaces, + char **alt) +{ + slapi_log_err(SLAPI_LOG_ERR, "inchain", "inchain_normalize %s \n", s); + value_normalize_ext(s, SYNTAX_CIS | SYNTAX_DN, trim_spaces, alt); + return; +} diff -Nru 389-ds-base-2.4.4+dfsg1/ldap/servers/slapd/abandon.c 389-ds-base-2.4.5+dfsg1/ldap/servers/slapd/abandon.c --- 389-ds-base-2.4.4+dfsg1/ldap/servers/slapd/abandon.c 2023-11-15 14:06:27.000000000 +0000 +++ 389-ds-base-2.4.5+dfsg1/ldap/servers/slapd/abandon.c 2024-01-15 10:27:07.000000000 +0000 @@ -38,6 +38,12 @@ Connection *pb_conn = NULL; Operation *pb_op = NULL; Operation *o; + /* Keep a copy of some data because o may vanish once conn is unlocked */ + struct { + struct timespec hr_time_end; + int nentries; + int opid; + } o_copy; slapi_pblock_get(pb, SLAPI_OPERATION, &pb_op); slapi_pblock_get(pb, SLAPI_CONNECTION, &pb_conn); @@ -90,8 +96,12 @@ pthread_mutex_lock(&(pb_conn->c_mutex)); for (o = pb_conn->c_ops; o != NULL; o = o->o_next) { - if (o->o_msgid == id && o != pb_op) + if (o->o_msgid == id && o != pb_op) { + slapi_operation_time_elapsed(o, &o_copy.hr_time_end); + o_copy.nentries = o->o_results.r.r_search.nentries; + o_copy.opid = o->o_opid; break; + } } if (o != NULL) { @@ -130,7 +140,8 @@ slapi_log_err(SLAPI_LOG_TRACE, "do_abandon", "op not found\n"); } - if (0 == pagedresults_free_one_msgid_nolock(pb_conn, id)) { + pthread_mutex_unlock(&(pb_conn->c_mutex)); + if (0 == pagedresults_free_one_msgid(pb_conn, id, pageresult_lock_get_addr(pb_conn))) { slapi_log_access(LDAP_DEBUG_STATS, "conn=%" PRIu64 " op=%d ABANDON targetop=Simple Paged Results msgid=%d\n", pb_conn->c_connid, pb_op->o_opid, id); @@ -143,15 +154,11 @@ " targetop=SUPPRESSED-BY-PLUGIN msgid=%d\n", pb_conn->c_connid, pb_op->o_opid, id); } else { - struct timespec o_hr_time_end; - slapi_operation_time_elapsed(o, &o_hr_time_end); slapi_log_access(LDAP_DEBUG_STATS, "conn=%" PRIu64 " op=%d ABANDON" " targetop=%d msgid=%d nentries=%d etime=%" PRId64 ".%010" PRId64 "\n", - pb_conn->c_connid, pb_op->o_opid, o->o_opid, id, - o->o_results.r.r_search.nentries, (int64_t)o_hr_time_end.tv_sec, (int64_t)o_hr_time_end.tv_nsec); + pb_conn->c_connid, pb_op->o_opid, o_copy.opid, id, + o_copy.nentries, (int64_t)o_copy.hr_time_end.tv_sec, (int64_t)o_copy.hr_time_end.tv_nsec); } - - pthread_mutex_unlock(&(pb_conn->c_mutex)); /* * Wake up the persistent searches, so they * can notice if they've been abandoned. diff -Nru 389-ds-base-2.4.4+dfsg1/ldap/servers/slapd/back-ldbm/cache.c 389-ds-base-2.4.5+dfsg1/ldap/servers/slapd/back-ldbm/cache.c --- 389-ds-base-2.4.4+dfsg1/ldap/servers/slapd/back-ldbm/cache.c 2023-11-15 14:06:27.000000000 +0000 +++ 389-ds-base-2.4.5+dfsg1/ldap/servers/slapd/back-ldbm/cache.c 2024-01-15 10:27:07.000000000 +0000 @@ -556,7 +556,12 @@ { Hashtable *ht = cache->c_idtable; /* start with the ID table as it's in both ENTRY and DN caches */ void *e, *laste = NULL; + char flush_etime[ETIME_BUFSIZ] = {0}; + struct timespec duration; + struct timespec flush_start; + struct timespec flush_end; + clock_gettime(CLOCK_MONOTONIC, &flush_start); cache_lock(cache); for (size_t i = 0; i < ht->size; i++) { @@ -638,6 +643,11 @@ } cache_unlock(cache); + + clock_gettime(CLOCK_MONOTONIC, &flush_end); + slapi_timespec_diff(&flush_end, &flush_start, &duration); + snprintf(flush_etime, ETIME_BUFSIZ, "%" PRId64 ".%.09" PRId64 "", (int64_t)duration.tv_sec, (int64_t)duration.tv_nsec); + slapi_log_err(SLAPI_LOG_WARNING, "flush_hash", "Upon BETXN callback failure, entry cache is flushed during %s\n", flush_etime); } void @@ -1734,6 +1744,25 @@ return 0; } +int +cache_is_reverted_entry(struct cache *cache, struct backentry *e) +{ + struct backentry *dummy_e; + + cache_lock(cache); + if (find_hash(cache->c_idtable, &e->ep_id, sizeof(ID), (void **)&dummy_e)) { + if (dummy_e->ep_state & ENTRY_STATE_INVALID) { + slapi_log_err(SLAPI_LOG_WARNING, "cache_is_reverted_entry", "Entry reverted = %d (0x%lX) [entry: 0x%lX] refcnt=%d\n", + dummy_e->ep_state, + pthread_self(), + dummy_e, dummy_e->ep_refcnt); + cache_unlock(cache); + return 1; + } + } + cache_unlock(cache); + return 0; +} /* the opposite of above */ void cache_unlock_entry(struct cache *cache __attribute__((unused)), struct backentry *e) diff -Nru 389-ds-base-2.4.4+dfsg1/ldap/servers/slapd/back-ldbm/filterindex.c 389-ds-base-2.4.5+dfsg1/ldap/servers/slapd/back-ldbm/filterindex.c --- 389-ds-base-2.4.4+dfsg1/ldap/servers/slapd/back-ldbm/filterindex.c 2023-11-15 14:06:27.000000000 +0000 +++ 389-ds-base-2.4.5+dfsg1/ldap/servers/slapd/back-ldbm/filterindex.c 2024-01-15 10:27:07.000000000 +0000 @@ -451,6 +451,7 @@ Slapi_PBlock *pb = slapi_pblock_new(); int mrOP = 0; Slapi_Operation *op = NULL; + Connection *conn = NULL; back_txn txn = {NULL}; slapi_log_err(SLAPI_LOG_TRACE, "extensible_candidates", "=> \n"); slapi_pblock_get(glob_pb, SLAPI_TXN, &txn.back_txn_txn); @@ -470,8 +471,11 @@ /* set the pb->pb_op to glob_pb->pb_op to catch the abandon req. * in case the operation is interrupted. */ slapi_pblock_get(glob_pb, SLAPI_OPERATION, &op); + slapi_pblock_get(glob_pb, SLAPI_CONNECTION, &conn); /* coverity[var_deref_model] */ slapi_pblock_set(pb, SLAPI_OPERATION, op); + slapi_pblock_set(pb, SLAPI_CONNECTION, conn); + slapi_pblock_set(pb, SLAPI_REQUESTOR_ISROOT, &op->o_isroot); slapi_pblock_get(pb, SLAPI_PLUGIN_MR_INDEX_FN, &mrINDEX); slapi_pblock_get(pb, SLAPI_PLUGIN_OBJECT, &mrOBJECT); @@ -516,16 +520,36 @@ } else if (keys == NULL || keys[0] == NULL) { /* no keys */ idl_free(&idl); - idl = idl_allids(be); + if (strcmp(mrOID, LDAP_MATCHING_RULE_IN_CHAIN_OID) == 0) { + /* we need to return no candidate else, inchain_filter_ava + * matching all candidates, the search returns invalid results + */ + idl = idl_alloc(0); + } else { + idl = idl_allids(be); + } } else { IDList *idl2 = NULL; struct berval **key; +#define KEY_STR_LGHT 35 /* stollen from nsuniqueid.c UIDSTR_SIZE 35 */ + char key_str[KEY_STR_LGHT + 1]; /* only used for debug logging */ for (key = keys; *key != NULL; ++key) { int unindexed = 0; IDList *idl3 = (mrOP == SLAPI_OP_EQUAL) ? index_read_ext_allids(pb, be, mrTYPE, mrOID, *key, &txn, err, &unindexed, allidslimit) : index_range_read_ext(pb, be, mrTYPE, mrOID, mrOP, *key, NULL, 0, &txn, err, allidslimit); + if (slapi_is_loglevel_set(SLAPI_LOG_FILTER)) { + int lenght_str = key[0]->bv_len; + + if (key[0]->bv_len > KEY_STR_LGHT) { + lenght_str = KEY_STR_LGHT; + } + + strncpy(key_str, key[0]->bv_val, lenght_str); + key_str[lenght_str] = '\0'; + slapi_log_err(SLAPI_LOG_FILTER, "extensible_candidates", "=> idl (%s) = (%d)\n", key_str, idl3->b_ids[0]); + } if (unindexed) { int pr_idx = -1; slapi_pblock_set_flag_operation_notes(pb, SLAPI_OP_NOTE_UNINDEXED); @@ -542,7 +566,12 @@ /* first iteration */ idl2 = idl3; } else { - IDList *tmp = idl_intersection(be, idl2, idl3); + IDList *tmp; + if (strcmp(mrOID, LDAP_MATCHING_RULE_IN_CHAIN_OID) == 0) { + tmp = idl_union(be, idl2, idl3); + } else { + tmp = idl_intersection(be, idl2, idl3); + } idl_free(&idl2); idl_free(&idl3); idl2 = tmp; @@ -575,8 +604,11 @@ } return_idl: op = NULL; + conn = NULL; + /* coverity[var_deref_model] */ slapi_pblock_set(pb, SLAPI_OPERATION, op); + slapi_pblock_set(pb, SLAPI_CONNECTION, conn); slapi_pblock_destroy(pb); slapi_log_err(SLAPI_LOG_TRACE, "extensible_candidates", "<= %lu\n", (u_long)IDL_NIDS(idl)); diff -Nru 389-ds-base-2.4.4+dfsg1/ldap/servers/slapd/back-ldbm/findentry.c 389-ds-base-2.4.5+dfsg1/ldap/servers/slapd/back-ldbm/findentry.c --- 389-ds-base-2.4.4+dfsg1/ldap/servers/slapd/back-ldbm/findentry.c 2023-11-15 14:06:27.000000000 +0000 +++ 389-ds-base-2.4.5+dfsg1/ldap/servers/slapd/back-ldbm/findentry.c 2024-01-15 10:27:07.000000000 +0000 @@ -98,6 +98,7 @@ size_t tries = 0; int isroot = 0; int op_type; + int reverted_entry = 0; /* get the managedsait ldap message control */ slapi_pblock_get(pb, SLAPI_MANAGEDSAIT, &managedsait); @@ -141,12 +142,20 @@ */ slapi_log_err(SLAPI_LOG_ARGS, "find_entry_internal_dn", " Retrying (%s)\n", slapi_sdn_get_dn(sdn)); + if (cache_is_reverted_entry(&inst->inst_cache, e)) { + reverted_entry = 1; + break; + } CACHE_RETURN(&inst->inst_cache, &e); tries++; } if (tries >= LDBM_CACHE_RETRY_COUNT) { slapi_log_err(SLAPI_LOG_ERR, "find_entry_internal_dn", "Retry count exceeded (%s)\n", slapi_sdn_get_dn(sdn)); } + if (reverted_entry) { + slapi_send_ldap_result(pb, LDAP_BUSY, NULL, "target entry busy because of a canceled operation", 0, NULL); + return (NULL); + } /* * there is no such entry in this server. see how far we * can match, and check if that entry contains a referral. @@ -262,6 +271,7 @@ struct backentry *e; int err; size_t tries = 0; + int reverted_entry = 0; while ((tries < LDBM_CACHE_RETRY_COUNT) && (e = uniqueid2entry(be, uniqueid, txn, &err)) != NULL) { @@ -283,6 +293,10 @@ */ slapi_log_err(SLAPI_LOG_ARGS, " find_entry_internal_uniqueid", "Retrying; uniqueid = (%s)\n", uniqueid); + if (cache_is_reverted_entry(&inst->inst_cache, e)) { + reverted_entry = 1; + break; + } CACHE_RETURN(&inst->inst_cache, &e); tries++; } @@ -292,9 +306,14 @@ uniqueid); } - /* entry not found */ - slapi_send_ldap_result(pb, (0 == err || DBI_RC_NOTFOUND == err) ? LDAP_NO_SUCH_OBJECT : LDAP_OPERATIONS_ERROR, NULL /* matched */, NULL, - 0, NULL); + if (reverted_entry) { + slapi_send_ldap_result(pb, LDAP_BUSY, NULL, "target entry busy because of a canceled operation", 0, NULL); + return (NULL); + } else { + /* entry not found */ + slapi_send_ldap_result(pb, (0 == err || DBI_RC_NOTFOUND == err) ? LDAP_NO_SUCH_OBJECT : LDAP_OPERATIONS_ERROR, NULL /* matched */, NULL, + 0, NULL); + } slapi_log_err(SLAPI_LOG_TRACE, "find_entry_internal_uniqueid", "<= not found; uniqueid = (%s)\n", uniqueid); diff -Nru 389-ds-base-2.4.4+dfsg1/ldap/servers/slapd/back-ldbm/index.c 389-ds-base-2.4.5+dfsg1/ldap/servers/slapd/back-ldbm/index.c --- 389-ds-base-2.4.4+dfsg1/ldap/servers/slapd/back-ldbm/index.c 2023-11-15 14:06:27.000000000 +0000 +++ 389-ds-base-2.4.5+dfsg1/ldap/servers/slapd/back-ldbm/index.c 2024-01-15 10:27:07.000000000 +0000 @@ -924,6 +924,10 @@ *err = 0; + if (strcmp(indextype, LDAP_MATCHING_RULE_IN_CHAIN_OID) == 0) { + type = "nsuniqueid"; + indextype = indextype_EQUALITY; + } if (unindexed != NULL) *unindexed = 0; prefix = index_index2prefix(indextype); diff -Nru 389-ds-base-2.4.4+dfsg1/ldap/servers/slapd/back-ldbm/ldbm_add.c 389-ds-base-2.4.5+dfsg1/ldap/servers/slapd/back-ldbm/ldbm_add.c --- 389-ds-base-2.4.4+dfsg1/ldap/servers/slapd/back-ldbm/ldbm_add.c 2023-11-15 14:06:27.000000000 +0000 +++ 389-ds-base-2.4.5+dfsg1/ldap/servers/slapd/back-ldbm/ldbm_add.c 2024-01-15 10:27:07.000000000 +0000 @@ -98,6 +98,7 @@ int op_id; int result_sent = 0; int32_t parent_op = 0; + int32_t betxn_callback_fails = 0; /* if a BETXN fails we need to revert entry cache */ struct timespec parent_time; if (slapi_pblock_get(pb, SLAPI_CONN_ID, &conn_id) < 0) { @@ -919,6 +920,9 @@ slapi_pblock_get(pb, SLAPI_PB_RESULT_TEXT, &ldap_result_message); slapi_log_err(SLAPI_LOG_DEBUG, "ldbm_back_add", "SLAPI_PLUGIN_BE_TXN_PRE_ADD_FN plugin failed: %d\n", ldap_result_code ? ldap_result_code : retval); + if (retval) { + betxn_callback_fails = 1; + } goto error_return; } } @@ -1229,11 +1233,15 @@ slapi_pblock_set(pb, SLAPI_PLUGIN_OPRETURN, ldap_result_code ? &ldap_result_code : &retval); } slapi_pblock_get(pb, SLAPI_PB_RESULT_TEXT, &ldap_result_message); + if (retval) { + betxn_callback_fails = 1; + } goto error_return; } retval = plugin_call_mmr_plugin_postop(pb, NULL,SLAPI_PLUGIN_BE_TXN_POST_ADD_FN); if (retval) { + betxn_callback_fails = 1; ldbm_set_error(pb, retval, &ldap_result_code, &ldap_result_message); goto error_return; } @@ -1326,9 +1334,15 @@ slapi_pblock_set(pb, SLAPI_PLUGIN_OPRETURN, &opreturn); } slapi_pblock_get(pb, SLAPI_PB_RESULT_TEXT, &ldap_result_message); + if (retval) { + betxn_callback_fails = 1; + } } /* the repl postop needs to be called for aborted operations */ retval = plugin_call_mmr_plugin_postop(pb, NULL,SLAPI_PLUGIN_BE_TXN_POST_ADD_FN); + if (retval) { + betxn_callback_fails = 1; + } if (addingentry) { if (inst && cache_is_in_cache(&inst->inst_cache, addingentry)) { CACHE_REMOVE(&inst->inst_cache, addingentry); @@ -1366,7 +1380,7 @@ } /* Revert the caches if this is the parent operation */ - if (parent_op) { + if (parent_op && betxn_callback_fails) { revert_cache(inst, &parent_time); } } diff -Nru 389-ds-base-2.4.4+dfsg1/ldap/servers/slapd/back-ldbm/ldbm_delete.c 389-ds-base-2.4.5+dfsg1/ldap/servers/slapd/back-ldbm/ldbm_delete.c --- 389-ds-base-2.4.4+dfsg1/ldap/servers/slapd/back-ldbm/ldbm_delete.c 2023-11-15 14:06:27.000000000 +0000 +++ 389-ds-base-2.4.5+dfsg1/ldap/servers/slapd/back-ldbm/ldbm_delete.c 2024-01-15 10:27:07.000000000 +0000 @@ -80,6 +80,7 @@ int result_sent = 0; Connection *pb_conn; int32_t parent_op = 0; + int32_t betxn_callback_fails = 0; /* if a BETXN fails we need to revert entry cache */ struct timespec parent_time; if (slapi_pblock_get(pb, SLAPI_CONN_ID, &conn_id) < 0) { @@ -434,6 +435,9 @@ &ldap_result_code : &retval ); } slapi_pblock_get(pb, SLAPI_PB_RESULT_TEXT, &ldap_result_message); + if (retval) { + betxn_callback_fails = 1; + } goto error_return; } } @@ -717,6 +721,9 @@ ldap_result_code ? &ldap_result_code : &retval); } slapi_pblock_get(pb, SLAPI_PB_RESULT_TEXT, &ldap_result_message); + if (retval) { + betxn_callback_fails = 1; + } goto error_return; } } @@ -746,6 +753,9 @@ } /* retval is -1 */ slapi_pblock_get(pb, SLAPI_PB_RESULT_TEXT, &ldap_result_message); + if (rc) { + betxn_callback_fails = 1; + } goto error_return; } slapi_pblock_set(pb, SLAPI_DELETE_BEPREOP_ENTRY, orig_entry); @@ -1266,6 +1276,7 @@ slapi_pblock_set(pb, SLAPI_PLUGIN_OPRETURN, &retval); } slapi_pblock_get(pb, SLAPI_PB_RESULT_TEXT, &ldap_result_message); + betxn_callback_fails = 1; goto error_return; } if (parent_found) { @@ -1281,6 +1292,7 @@ retval = plugin_call_mmr_plugin_postop(pb, NULL,SLAPI_PLUGIN_BE_TXN_POST_DELETE_FN); if (retval) { + betxn_callback_fails = 1; ldbm_set_error(pb, retval, &ldap_result_code, &ldap_result_message); goto error_return; } @@ -1416,8 +1428,12 @@ slapi_pblock_set(pb, SLAPI_PLUGIN_OPRETURN, ldap_result_code ? &ldap_result_code : &retval); } slapi_pblock_get(pb, SLAPI_PB_RESULT_TEXT, &ldap_result_message); + betxn_callback_fails = 1; } retval = plugin_call_mmr_plugin_postop(pb, NULL,SLAPI_PLUGIN_BE_TXN_POST_DELETE_FN); + if (retval) { + betxn_callback_fails = 1; + } /* Release SERIAL LOCK */ dblayer_txn_abort(be, &txn); /* abort crashes in case disk full */ @@ -1437,7 +1453,7 @@ } /* Revert the caches if this is the parent operation */ - if (parent_op) { + if (parent_op && betxn_callback_fails) { revert_cache(inst, &parent_time); } diff -Nru 389-ds-base-2.4.4+dfsg1/ldap/servers/slapd/back-ldbm/ldbm_modify.c 389-ds-base-2.4.5+dfsg1/ldap/servers/slapd/back-ldbm/ldbm_modify.c --- 389-ds-base-2.4.4+dfsg1/ldap/servers/slapd/back-ldbm/ldbm_modify.c 2023-11-15 14:06:27.000000000 +0000 +++ 389-ds-base-2.4.5+dfsg1/ldap/servers/slapd/back-ldbm/ldbm_modify.c 2024-01-15 10:27:07.000000000 +0000 @@ -522,6 +522,7 @@ int ec_locked = 0; int result_sent = 0; int32_t parent_op = 0; + int32_t betxn_callback_fails = 0; /* if a BETXN fails we need to revert entry cache */ struct timespec parent_time; Slapi_Mods *smods_add_rdn = NULL; @@ -817,6 +818,9 @@ slapi_pblock_set(pb, SLAPI_PLUGIN_OPRETURN, ldap_result_code ? &ldap_result_code : &retval); } slapi_pblock_get(pb, SLAPI_PB_RESULT_TEXT, &ldap_result_message); + if (retval) { + betxn_callback_fails = 1; + } goto error_return; } @@ -1017,10 +1021,12 @@ slapi_pblock_set(pb, SLAPI_PLUGIN_OPRETURN, ldap_result_code ? &ldap_result_code : &retval); } slapi_pblock_get(pb, SLAPI_PB_RESULT_TEXT, &ldap_result_message); + betxn_callback_fails = 1; goto error_return; } retval = plugin_call_mmr_plugin_postop(pb, NULL,SLAPI_PLUGIN_BE_TXN_POST_MODIFY_FN); if (retval) { + betxn_callback_fails = 1; ldbm_set_error(pb, retval, &ldap_result_code, &ldap_result_message); goto error_return; } @@ -1083,8 +1089,12 @@ if (!opreturn) { slapi_pblock_set(pb, SLAPI_PLUGIN_OPRETURN, ldap_result_code ? &ldap_result_code : &retval); } + betxn_callback_fails = 1; } retval = plugin_call_mmr_plugin_postop(pb, NULL,SLAPI_PLUGIN_BE_TXN_POST_MODIFY_FN); + if (retval) { + betxn_callback_fails = 1; + } /* It is safer not to abort when the transaction is not started. */ /* Release SERIAL LOCK */ @@ -1096,7 +1106,7 @@ rc = SLAPI_FAIL_GENERAL; } /* Revert the caches if this is the parent operation */ - if (parent_op) { + if (parent_op && betxn_callback_fails) { revert_cache(inst, &parent_time); } } diff -Nru 389-ds-base-2.4.4+dfsg1/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c 389-ds-base-2.4.5+dfsg1/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c --- 389-ds-base-2.4.4+dfsg1/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c 2023-11-15 14:06:27.000000000 +0000 +++ 389-ds-base-2.4.5+dfsg1/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c 2024-01-15 10:27:07.000000000 +0000 @@ -101,6 +101,7 @@ int result_sent = 0; Connection *pb_conn = NULL; int32_t parent_op = 0; + int32_t betxn_callback_fails = 0; /* if a BETXN fails we need to revert entry cache */ struct timespec parent_time; Slapi_Mods *smods_add_rdn = NULL; @@ -999,6 +1000,9 @@ slapi_pblock_set(pb, SLAPI_PLUGIN_OPRETURN, ldap_result_code ? &ldap_result_code : &retval); } slapi_pblock_get(pb, SLAPI_PB_RESULT_TEXT, &ldap_result_message); + if (retval) { + betxn_callback_fails = 1; + } goto error_return; } @@ -1250,10 +1254,12 @@ slapi_pblock_set(pb, SLAPI_PLUGIN_OPRETURN, ldap_result_code ? &ldap_result_code : &retval); } slapi_pblock_get(pb, SLAPI_PB_RESULT_TEXT, &ldap_result_message); + betxn_callback_fails = 1; goto error_return; } retval = plugin_call_mmr_plugin_postop(pb, NULL,SLAPI_PLUGIN_BE_TXN_POST_MODRDN_FN); if (retval) { + betxn_callback_fails = 1; ldbm_set_error(pb, retval, &ldap_result_code, &ldap_result_message); goto error_return; } @@ -1319,7 +1325,7 @@ error_return: /* Revert the caches if this is the parent operation */ - if (parent_op) { + if (parent_op && betxn_callback_fails) { revert_cache(inst, &parent_time); } @@ -1401,7 +1407,9 @@ } slapi_pblock_get(pb, SLAPI_PB_RESULT_TEXT, &ldap_result_message); - /* Revert the caches if this is the parent operation */ + /* As it is a BETXN plugin failure then + * revert the caches if this is the parent operation + */ if (parent_op) { revert_cache(inst, &parent_time); } diff -Nru 389-ds-base-2.4.4+dfsg1/ldap/servers/slapd/back-ldbm/proto-back-ldbm.h 389-ds-base-2.4.5+dfsg1/ldap/servers/slapd/back-ldbm/proto-back-ldbm.h --- 389-ds-base-2.4.4+dfsg1/ldap/servers/slapd/back-ldbm/proto-back-ldbm.h 2023-11-15 14:06:27.000000000 +0000 +++ 389-ds-base-2.4.5+dfsg1/ldap/servers/slapd/back-ldbm/proto-back-ldbm.h 2024-01-15 10:27:07.000000000 +0000 @@ -61,6 +61,7 @@ int cache_has_otherref(struct cache *cache, void *bep); int cache_is_in_cache(struct cache *cache, void *ptr); void revert_cache(ldbm_instance *inst, struct timespec *start_time); +int cache_is_reverted_entry(struct cache *cache, struct backentry *e); #ifdef CACHE_DEBUG void check_entry_cache(struct cache *cache, struct backentry *e); diff -Nru 389-ds-base-2.4.4+dfsg1/ldap/servers/slapd/main.c 389-ds-base-2.4.5+dfsg1/ldap/servers/slapd/main.c --- 389-ds-base-2.4.4+dfsg1/ldap/servers/slapd/main.c 2023-11-15 14:06:27.000000000 +0000 +++ 389-ds-base-2.4.5+dfsg1/ldap/servers/slapd/main.c 2024-01-15 10:27:07.000000000 +0000 @@ -67,6 +67,10 @@ #include #endif +#ifdef LINUX +#include +#endif + /* Forward Declarations */ struct main_config @@ -523,6 +527,14 @@ { int return_value = 0; struct main_config mcfg = {0}; +#ifdef LINUX +#if defined(PR_SET_THP_DISABLE) + char *thp_disable = getenv("THP_DISABLE"); + if (thp_disable != NULL && strcmp(thp_disable, "1") == 0) { + prctl(PR_SET_THP_DISABLE, 1, 0, 0, 0); + } +#endif +#endif /* Set a number of defaults */ mcfg.slapd_exemode = SLAPD_EXEMODE_UNKNOWN; diff -Nru 389-ds-base-2.4.4+dfsg1/ldap/servers/slapd/opshared.c 389-ds-base-2.4.5+dfsg1/ldap/servers/slapd/opshared.c --- 389-ds-base-2.4.4+dfsg1/ldap/servers/slapd/opshared.c 2023-11-15 14:06:27.000000000 +0000 +++ 389-ds-base-2.4.5+dfsg1/ldap/servers/slapd/opshared.c 2024-01-15 10:27:07.000000000 +0000 @@ -921,9 +921,7 @@ next_be = NULL; /* to break the loop */ if (operation->o_status & SLAPI_OP_STATUS_ABANDONED) { /* It turned out this search was abandoned. */ - pthread_mutex_lock(pagedresults_mutex); - pagedresults_free_one_msgid_nolock(pb_conn, operation->o_msgid); - pthread_mutex_unlock(pagedresults_mutex); + pagedresults_free_one_msgid(pb_conn, operation->o_msgid, pagedresults_mutex); /* paged-results-request was abandoned; making an empty cookie. */ pagedresults_set_response_control(pb, 0, estimate, -1, pr_idx); send_ldap_result(pb, 0, NULL, "Simple Paged Results Search abandoned", 0, NULL); diff -Nru 389-ds-base-2.4.4+dfsg1/ldap/servers/slapd/pagedresults.c 389-ds-base-2.4.5+dfsg1/ldap/servers/slapd/pagedresults.c --- 389-ds-base-2.4.4+dfsg1/ldap/servers/slapd/pagedresults.c 2023-11-15 14:06:27.000000000 +0000 +++ 389-ds-base-2.4.5+dfsg1/ldap/servers/slapd/pagedresults.c 2024-01-15 10:27:07.000000000 +0000 @@ -34,6 +34,10 @@ slapi_ch_free((void**)&lock_hash); } +/* Beware to the lock order with c_mutex: + * c_mutex is sometime locked while holding pageresult_lock + * ==> Do not lock pageresult_lock when holing c_mutex + */ pthread_mutex_t * pageresult_lock_get_addr(Connection *conn) { @@ -350,7 +354,7 @@ * Used for abandoning - pageresult_lock_get_addr(conn) is already locked in do_abandone. */ int -pagedresults_free_one_msgid_nolock(Connection *conn, ber_int_t msgid) +pagedresults_free_one_msgid(Connection *conn, ber_int_t msgid, pthread_mutex_t *mutex) { int rc = -1; int i; @@ -361,6 +365,7 @@ } else { slapi_log_err(SLAPI_LOG_TRACE, "pagedresults_free_one_msgid_nolock", "=> msgid=%d\n", msgid); + pthread_mutex_lock(mutex); for (i = 0; i < conn->c_pagedresults.prl_maxlen; i++) { if (conn->c_pagedresults.prl_list[i].pr_msgid == msgid) { PagedResults *prp = conn->c_pagedresults.prl_list + i; @@ -375,6 +380,7 @@ break; } } + pthread_mutex_unlock(mutex); slapi_log_err(SLAPI_LOG_TRACE, "pagedresults_free_one_msgid_nolock", "<= %d\n", rc); } diff -Nru 389-ds-base-2.4.4+dfsg1/ldap/servers/slapd/proto-slap.h 389-ds-base-2.4.5+dfsg1/ldap/servers/slapd/proto-slap.h --- 389-ds-base-2.4.4+dfsg1/ldap/servers/slapd/proto-slap.h 2023-11-15 14:06:27.000000000 +0000 +++ 389-ds-base-2.4.5+dfsg1/ldap/servers/slapd/proto-slap.h 2024-01-15 10:27:07.000000000 +0000 @@ -1620,7 +1620,7 @@ int pagedresults_reset_timedout_nolock(Connection *conn); int pagedresults_in_use_nolock(Connection *conn); int pagedresults_free_one(Connection *conn, Operation *op, int index); -int pagedresults_free_one_msgid_nolock(Connection *conn, ber_int_t msgid); +int pagedresults_free_one_msgid(Connection *conn, ber_int_t msgid, pthread_mutex_t *mutex); int op_is_pagedresults(Operation *op); int pagedresults_cleanup_all(Connection *conn, int needlock); void op_set_pagedresults(Operation *op); diff -Nru 389-ds-base-2.4.4+dfsg1/ldap/servers/slapd/slap.h 389-ds-base-2.4.5+dfsg1/ldap/servers/slapd/slap.h --- 389-ds-base-2.4.4+dfsg1/ldap/servers/slapd/slap.h 2023-11-15 14:06:27.000000000 +0000 +++ 389-ds-base-2.4.5+dfsg1/ldap/servers/slapd/slap.h 2024-01-15 10:27:07.000000000 +0000 @@ -751,6 +751,7 @@ #define INTEGERORDERINGMATCH_OID "2.5.13.15" /* integerOrderingMatch */ #define INTFIRSTCOMPMATCH_OID "2.5.13.29" /* integerFirstComponentMatch */ #define OIDFIRSTCOMPMATCH_OID "2.5.13.30" /* objectIdentifierFirstComponentMatch */ +#define LDAP_MATCHING_RULE_IN_CHAIN_OID "1.2.840.113556.1.4.1941" /* Names for some commonly used matching rules */ #define DNMATCH_NAME "distinguishedNameMatch" @@ -759,6 +760,7 @@ #define INTEGERORDERINGMATCH_NAME "integerOrderingMatch" #define INTFIRSTCOMPMATCH_NAME "integerFirstComponentMatch" #define OIDFIRSTCOMPMATCH_NAME "objectIdentifierFirstComponentMatch" +#define LDAP_MATCHING_RULE_IN_CHAIN_NAME "ancestryDNMatch" #define ATTR_STANDARD_STRING "Standard Attribute" #define ATTR_USERDEF_STRING "User Defined Attribute" diff -Nru 389-ds-base-2.4.4+dfsg1/ldap/servers/slapd/slapi-memberof.c 389-ds-base-2.4.5+dfsg1/ldap/servers/slapd/slapi-memberof.c --- 389-ds-base-2.4.4+dfsg1/ldap/servers/slapd/slapi-memberof.c 2023-11-15 14:06:27.000000000 +0000 +++ 389-ds-base-2.4.5+dfsg1/ldap/servers/slapd/slapi-memberof.c 2024-01-15 10:27:07.000000000 +0000 @@ -784,12 +784,12 @@ int32_t cnt1, cnt2; if ((memberof_config == NULL) || (memberof_config->enabled == PR_FALSE)) { - slapi_log_err(SLAPI_LOG_ERR, "slapi_memberof", "sm_compare_memberof_config: config not initialized or disabled\n"); + slapi_log_err(SLAPI_LOG_PLUGIN, "slapi_memberof", "sm_compare_memberof_config: config not initialized or disabled\n"); return PR_FALSE; } if (enabled_only) { - slapi_log_err(SLAPI_LOG_ERR, "slapi_memberof", "sm_compare_memberof_config: check the plugin is enabled that is %s\n", + slapi_log_err(SLAPI_LOG_PLUGIN, "slapi_memberof", "sm_compare_memberof_config: check the plugin is enabled that is %s\n", memberof_config->enabled ? "SUCCEEDS" : "FAILS"); if (memberof_config->enabled) { return PR_TRUE; @@ -801,7 +801,7 @@ /* Check direct flags */ if ((all_backends != memberof_config->all_backends) || (skip_nested != memberof_config->skip_nested)) { /* If those flags do not match the current set of 'memberof' values is invalid */ - slapi_log_err(SLAPI_LOG_ERR, "slapi_memberof", "sm_compare_memberof_config: fails (allbackend %d vs %d, skip_nested %d vs %d)\n", + slapi_log_err(SLAPI_LOG_PLUGIN, "slapi_memberof", "sm_compare_memberof_config: fails (allbackend %d vs %d, skip_nested %d vs %d)\n", all_backends, memberof_config->all_backends, skip_nested, memberof_config->skip_nested); return PR_FALSE; } @@ -811,7 +811,7 @@ */ if ((memberof_attr == NULL) || (memberof_config->memberof_attr == NULL) || (strcasecmp(memberof_attr, memberof_config->memberof_attr))) { /* just be conservative, we should speak about the same attribute */ - slapi_log_err(SLAPI_LOG_ERR, "slapi_memberof", + slapi_log_err(SLAPI_LOG_PLUGIN, "slapi_memberof", "sm_compare_memberof_config: fails memberof attribute differs (require '%s' vs config '%s')\n", memberof_attr ? memberof_attr : "NULL", memberof_config->memberof_attr ? memberof_config->memberof_attr : NULL); @@ -823,12 +823,12 @@ */ if (groupattrs == NULL) { /* This is a mandatory parameter */ - slapi_log_err(SLAPI_LOG_ERR, "slapi_memberof", "sm_compare_memberof_config: fails because requested group attributes is empty\n"); + slapi_log_err(SLAPI_LOG_PLUGIN, "slapi_memberof", "sm_compare_memberof_config: fails because requested group attributes is empty\n"); return PR_FALSE; } for (cnt1 = 0; groupattrs[cnt1]; cnt1++) { if (charray_inlist(memberof_config->groupattrs, groupattrs[cnt1]) == 0) { - slapi_log_err(SLAPI_LOG_ERR, "slapi_memberof", + slapi_log_err(SLAPI_LOG_PLUGIN, "slapi_memberof", "sm_compare_memberof_config: fails because requested group attribute '%s' is not configured\n", groupattrs[cnt1]); return PR_FALSE; @@ -837,26 +837,26 @@ for (cnt2 = 0; memberof_config->groupattrs && memberof_config->groupattrs[cnt2]; cnt2++); if (cnt1 != cnt2) { /* make sure groupattrs is not a subset of memberof_config->groupattrs */ - slapi_log_err(SLAPI_LOG_ERR, "slapi_memberof", "sm_compare_memberof_config: fails because number of requested group attributes differs from config\n"); + slapi_log_err(SLAPI_LOG_PLUGIN, "slapi_memberof", "sm_compare_memberof_config: fails because number of requested group attributes differs from config\n"); return PR_FALSE; } /* check Include scope that is optional */ if (include_scope == NULL) { if (memberof_config->include_scope) { - slapi_log_err(SLAPI_LOG_ERR, "slapi_memberof", "sm_compare_memberof_config: fails because requested include scope is empty that differs config\n"); + slapi_log_err(SLAPI_LOG_PLUGIN, "slapi_memberof", "sm_compare_memberof_config: fails because requested include scope is empty that differs config\n"); return PR_FALSE; } } else { if (memberof_config->include_scope == NULL) { - slapi_log_err(SLAPI_LOG_ERR, "slapi_memberof", "sm_compare_memberof_config: fails because requested include scope is not empty that differs config\n"); + slapi_log_err(SLAPI_LOG_PLUGIN, "slapi_memberof", "sm_compare_memberof_config: fails because requested include scope is not empty that differs config\n"); return PR_FALSE; } } /* here include scopes are both NULL or both not NULL */ for (cnt1 = 0; include_scope && include_scope[cnt1]; cnt1++) { if (charray_inlist(memberof_config->include_scope, (char *) slapi_sdn_get_ndn(include_scope[cnt1])) == 0) { - slapi_log_err(SLAPI_LOG_ERR, "slapi_memberof", "sm_compare_memberof_config: fails because requested include scope (%s) is not in config\n", + slapi_log_err(SLAPI_LOG_PLUGIN, "slapi_memberof", "sm_compare_memberof_config: fails because requested include scope (%s) is not in config\n", slapi_sdn_get_ndn(include_scope[cnt1])); return PR_FALSE; } @@ -864,26 +864,26 @@ for (cnt2 = 0; memberof_config->include_scope && memberof_config->include_scope[cnt2]; cnt2++); if (cnt1 != cnt2) { /* make sure include_scope is not a subset of memberof_config->include_scope */ - slapi_log_err(SLAPI_LOG_ERR, "slapi_memberof", "sm_compare_memberof_config: fails because number of requested included scopes differs from config\n"); + slapi_log_err(SLAPI_LOG_PLUGIN, "slapi_memberof", "sm_compare_memberof_config: fails because number of requested included scopes differs from config\n"); return PR_FALSE; } /* check Exclude scope that is optional */ if (exclude_scope == NULL) { if (memberof_config->exclude_scope) { - slapi_log_err(SLAPI_LOG_ERR, "slapi_memberof", "sm_compare_memberof_config: fails because requested exclude scope is empty that differs config\n"); + slapi_log_err(SLAPI_LOG_PLUGIN, "slapi_memberof", "sm_compare_memberof_config: fails because requested exclude scope is empty that differs config\n"); return PR_FALSE; } } else { if (memberof_config->exclude_scope == NULL) { - slapi_log_err(SLAPI_LOG_ERR, "slapi_memberof", "sm_compare_memberof_config: fails because requested exclude scope is not empty that differs config\n"); + slapi_log_err(SLAPI_LOG_PLUGIN, "slapi_memberof", "sm_compare_memberof_config: fails because requested exclude scope is not empty that differs config\n"); return PR_FALSE; } } /* here exclude scopes are both NULL or both not NULL */ for (cnt1 = 0; exclude_scope && exclude_scope[cnt1]; cnt1++) { if (charray_inlist(memberof_config->exclude_scope, (char *) slapi_sdn_get_ndn(exclude_scope[cnt1])) == 0) { - slapi_log_err(SLAPI_LOG_ERR, "slapi_memberof", "sm_compare_memberof_config: fails because requested exclude scope (%s) is not in config\n", + slapi_log_err(SLAPI_LOG_PLUGIN, "slapi_memberof", "sm_compare_memberof_config: fails because requested exclude scope (%s) is not in config\n", slapi_sdn_get_ndn(exclude_scope[cnt1])); return PR_FALSE; } @@ -891,10 +891,10 @@ for (cnt2 = 0; memberof_config->exclude_scope && memberof_config->exclude_scope[cnt2]; cnt2++); if (cnt1 != cnt2) { /* make sure exclude_scope is not a subset of memberof_config->exclude_scope */ - slapi_log_err(SLAPI_LOG_ERR, "slapi_memberof", "sm_compare_memberof_config: fails because number of requested exclude scopes differs from config\n"); + slapi_log_err(SLAPI_LOG_PLUGIN, "slapi_memberof", "sm_compare_memberof_config: fails because number of requested exclude scopes differs from config\n"); return PR_FALSE; } - slapi_log_err(SLAPI_LOG_ERR, "slapi_memberof", "sm_compare_memberof_config: succeeds. requested options match config\n"); + slapi_log_err(SLAPI_LOG_PLUGIN, "slapi_memberof", "sm_compare_memberof_config: succeeds. requested options match config\n"); return PR_TRUE; } Binary files /tmp/tmph38s_2tu/rLXR2vXWZU/389-ds-base-2.4.4+dfsg1/src/cockpit/389-console/cockpit_dist/index.css.gz and /tmp/tmph38s_2tu/iWrrlml7Uz/389-ds-base-2.4.5+dfsg1/src/cockpit/389-console/cockpit_dist/index.css.gz differ Binary files /tmp/tmph38s_2tu/rLXR2vXWZU/389-ds-base-2.4.4+dfsg1/src/cockpit/389-console/cockpit_dist/index.js.gz and /tmp/tmph38s_2tu/iWrrlml7Uz/389-ds-base-2.4.5+dfsg1/src/cockpit/389-console/cockpit_dist/index.js.gz differ Binary files /tmp/tmph38s_2tu/rLXR2vXWZU/389-ds-base-2.4.4+dfsg1/src/cockpit/389-console/cockpit_dist/po.de.js.gz and /tmp/tmph38s_2tu/iWrrlml7Uz/389-ds-base-2.4.5+dfsg1/src/cockpit/389-console/cockpit_dist/po.de.js.gz differ Binary files /tmp/tmph38s_2tu/rLXR2vXWZU/389-ds-base-2.4.4+dfsg1/src/cockpit/389-console/cockpit_dist/po.ja.js.gz and /tmp/tmph38s_2tu/iWrrlml7Uz/389-ds-base-2.4.5+dfsg1/src/cockpit/389-console/cockpit_dist/po.ja.js.gz differ diff -Nru 389-ds-base-2.4.4+dfsg1/src/lib389/lib389/cli_ctl/health.py 389-ds-base-2.4.5+dfsg1/src/lib389/lib389/cli_ctl/health.py --- 389-ds-base-2.4.4+dfsg1/src/lib389/lib389/cli_ctl/health.py 2023-11-15 14:06:27.000000000 +0000 +++ 389-ds-base-2.4.5+dfsg1/src/lib389/lib389/cli_ctl/health.py 2024-01-15 10:27:07.000000000 +0000 @@ -19,6 +19,7 @@ from lib389.nss_ssl import NssSsl from lib389.dseldif import FSChecks, DSEldif from lib389.dirsrv_log import DirsrvAccessLog +from lib389.tunables import Tunables from lib389 import lint from lib389 import plugins from lib389._constants import DSRC_HOME @@ -39,6 +40,7 @@ DSEldif, NssSsl, DirsrvAccessLog, + Tunables, ] diff -Nru 389-ds-base-2.4.4+dfsg1/src/lib389/lib389/lint.py 389-ds-base-2.4.5+dfsg1/src/lib389/lib389/lint.py --- 389-ds-base-2.4.4+dfsg1/src/lib389/lib389/lint.py 2023-11-15 14:06:27.000000000 +0000 +++ 389-ds-base-2.4.5+dfsg1/src/lib389/lib389/lint.py 2024-01-15 10:27:07.000000000 +0000 @@ -511,3 +511,29 @@ 'fix': """Stop using this these unknown attributes in the filter, or add the schema to the server and make sure it's properly indexed.""" } + +# Transparent Huge Pages +DSTHPLE0001 = { + 'dsle': 'DSTHPLE0001', + 'severity': 'Medium', + 'description': 'Transparent Huge Pages', + 'items': ['Possible Performance Impact'], + 'detail': """Transparent Huge Pages are enabled. This can lead to an unexpected memory +consumption, especially when using large caches.\n""", + 'fix': """Disable Transparent Huge Pages. +System-wide at boot: +Add "transparent_hugepage=never" to the list of kernel boot parameters. + +System-wide at runtime: +# echo "never" > /sys/kernel/mm/transparent_hugepage/enabled +# echo "never" > /sys/kernel/mm/transparent_hugepage/defrag + +Per instance (for the versions of 389 Directory Server that support it): +Edit dirsrv unit file: +# systemctl edit dirsrv@instance_name + +And uncomment the following lines: +[Service] +Environment=THP_DISABLE=1 +""" +} diff -Nru 389-ds-base-2.4.4+dfsg1/src/lib389/lib389/tunables.py 389-ds-base-2.4.5+dfsg1/src/lib389/lib389/tunables.py --- 389-ds-base-2.4.4+dfsg1/src/lib389/lib389/tunables.py 1970-01-01 00:00:00.000000000 +0000 +++ 389-ds-base-2.4.5+dfsg1/src/lib389/lib389/tunables.py 2024-01-15 10:27:07.000000000 +0000 @@ -0,0 +1,65 @@ +# --- BEGIN COPYRIGHT BLOCK --- +# Copyright (C) 2023 Red Hat, Inc. +# All rights reserved. +# +# License: GPL (version 3 or any later version). +# See LICENSE for details. +# --- END COPYRIGHT BLOCK --- +# + +import os +import re +import copy +from lib389._mapped_object_lint import DSLint +from lib389 import pid_from_file +from lib389.lint import DSTHPLE0001 + +class Tunables(DSLint): + """A class for working with system tunables + :param instance: An instance + :type instance: lib389.DirSrv + """ + + def __init__(self, instance): + self._instance = instance + self.pid = str(pid_from_file(instance.ds_paths.pid_file)) + + + @classmethod + def lint_uid(cls): + return 'tunables' + + + def _lint_thp(self): + """Check if THP is enabled""" + def systemwide_thp_enabled() -> bool: + thp_path = '/sys/kernel/mm/transparent_hugepage' + thp_enabled_path = os.path.join(thp_path, "enabled") + thp_status_pattern = r"(.*\[always\].*)|(.*\[madvise\].*)" + if os.path.exists(thp_enabled_path): + with open(thp_enabled_path, 'r') as f: + thp_status = f.read().strip() + match = re.match(thp_status_pattern, thp_status) + return match is not None + + + def instance_thp_enabled() -> bool: + pid_status_path = f"/proc/{self.pid}/status" + + with open(pid_status_path, 'r') as pid_status: + pid_status_content = pid_status.read() + thp_line = None + for line in pid_status_content.split('\n'): + if 'THP_enabled' in line: + thp_line = line + break + if thp_line is not None: + thp_value = int(thp_line.split()[1]) + return bool(thp_value) + + + if instance_thp_enabled() and systemwide_thp_enabled(): + report = copy.deepcopy(DSTHPLE0001) + report['check'] = 'tunables:transparent_huge_pages' + yield report + diff -Nru 389-ds-base-2.4.4+dfsg1/wrappers/systemd.template.service.custom.conf.in 389-ds-base-2.4.5+dfsg1/wrappers/systemd.template.service.custom.conf.in --- 389-ds-base-2.4.4+dfsg1/wrappers/systemd.template.service.custom.conf.in 2023-11-15 14:06:27.000000000 +0000 +++ 389-ds-base-2.4.5+dfsg1/wrappers/systemd.template.service.custom.conf.in 2024-01-15 10:27:07.000000000 +0000 @@ -46,6 +46,9 @@ # Preload jemalloc Environment=LD_PRELOAD=@libdir@/@package_name@/lib/libjemalloc.so.2 +# Disable Transparent Huge Pages +Environment=THP_DISABLE=1 + ################################################## # Heap profiling with jemalloc # ##################################################