diff --git a/389-ds-base.spec b/389-ds-base.spec index 1a3cca6ef51ec99eaa35fe5505c341d804e8b9b5..008a70da216acad72a6babd57c39db85ae6ae23a 100644 --- a/389-ds-base.spec +++ b/389-ds-base.spec @@ -6,7 +6,7 @@ ExcludeArch: i686 Name: 389-ds-base Summary: Base 389 Directory Server Version: 1.4.3.36 -Release: 5 +Release: 6 License: GPLv3+ URL: https://www.port389.org Source0: https://github.com/389ds/389-ds-base/archive/refs/tags/389-ds-base-%{version}.tar.gz @@ -20,6 +20,7 @@ Patch2: CVE-2024-1062-1.patch Patch3: CVE-2024-1062-2.patch Patch4: CVE-2024-2199.patch Patch5: CVE-2024-3657.patch +Patch6: CVE-2024-6237.patch BuildRequires: nspr-devel nss-devel >= 3.34 perl-generators openldap-devel libdb-devel cyrus-sasl-devel icu BuildRequires: libicu-devel pcre-devel cracklib-devel gcc-c++ net-snmp-devel lm_sensors-devel bzip2-devel @@ -360,6 +361,9 @@ exit 0 %{_mandir}/*/* %changelog +* Thu Jul 11 2024 zhangxianting - 1.4.3.36-6 +- Fix CVE-2024-6237 + * Wed Jun 05 2024 wangkai <13474090681@163.com> - 1.4.3.36-5 - Fix CVE-2024-2199 and CVE-2024-3657 diff --git a/CVE-2024-6237.patch b/CVE-2024-6237.patch new file mode 100644 index 0000000000000000000000000000000000000000..854c0cdb980b08ecb5898e03ed3530a47106e378 --- /dev/null +++ b/CVE-2024-6237.patch @@ -0,0 +1,1469 @@ +From e8dd583685e6143f2027f97569de4cc45ba46e14 Mon Sep 17 00:00:00 2001 +From: tbordaz +Date: Wed, 10 Jan 2024 16:53:08 +0100 +Subject: [PATCH] Issue 5989 - RFE support of inChain Matching Rule (#5990) +https://github.com/389ds/389-ds-base/commit/e8dd583685e6143f2027f97569de4cc45ba46e14 + +Bug description: + Computation of membership (like 'memberof') is a common issue. + The issue is more expensive to solve when there are nested membership. + For example "gives me all the groups this entry belongs to" or "gives me + all subordinates having this manager". + Either the LDAP client computes the values or dedicated plugin (like 'memberof') + maintains direct membership attribute for the LDAP client. + InChain Matching Rule allow a LDAP client to request the server to compute this membership. + +Fix description: + The implementation is designed https://www.port389.org/docs/389ds/design/matching-rule-in-chain.html + + A specific fix in aclanom.c because inChain MR adds a acl DENY + on 'cn=config'. There was a bug that cleared anonymous aci + if the it existed a DENY acl anywhere (except a specific + list of entries like 'cn=monitor'). It triggered a failure + on chaining backend suite + +relates: #5989 + +Reviewed by: William Brown, Mark Reynolds, Pierre Rogier, Simon Pichugin (Thanks !) +--- + Makefile.am | 1 + + .../tests/suites/filter/inchain_test.py | 817 ++++++++++++++++++ + ldap/servers/plugins/acl/aclanom.c | 4 +- + ldap/servers/plugins/syntaxes/inchain.c | 450 ++++++++++ + ldap/servers/slapd/back-ldbm/filterindex.c | 36 +- + ldap/servers/slapd/back-ldbm/index.c | 4 + + ldap/servers/slapd/slap.h | 2 + + 7 files changed, 1311 insertions(+), 3 deletions(-) + create mode 100644 dirsrvtests/tests/suites/filter/inchain_test.py + create mode 100644 ldap/servers/plugins/syntaxes/inchain.c + +diff --git a/Makefile.am b/Makefile.am +index 5a08de2..98d724b 100644 +--- a/Makefile.am ++++ b/Makefile.am +@@ -2170,6 +2170,7 @@ libsyntax_plugin_la_SOURCES = ldap/servers/plugins/syntaxes/bin.c \ + 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 --git a/dirsrvtests/tests/suites/filter/inchain_test.py b/dirsrvtests/tests/suites/filter/inchain_test.py +new file mode 100644 +index 0000000..c650b93 +--- /dev/null ++++ b/dirsrvtests/tests/suites/filter/inchain_test.py +@@ -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 --git a/ldap/servers/plugins/acl/aclanom.c b/ldap/servers/plugins/acl/aclanom.c +index 2521978..47fc5d9 100644 +--- a/ldap/servers/plugins/acl/aclanom.c ++++ b/ldap/servers/plugins/acl/aclanom.c +@@ -176,7 +176,9 @@ aclanom_gen_anomProfile(acl_lock_flag_t lock_flag) + /* see if this is a monitor acl */ + if ((strcasecmp(dn, "cn=monitor") == 0) || + /* cn=monitor,cn=ldbm: No such object */ +- (strcasecmp(dn, "cn=monitor,cn=ldbm") == 0)) { ++ (strcasecmp(dn, "cn=monitor,cn=ldbm") == 0) || ++ /* a 'deny' aci on supported feature should not delete anonymous profile everywhere */ ++ (strcasestr(dn, "cn=features,cn=config"))) { + aci = acllist_get_next_aci(NULL, aci, &cookie); + continue; + } else { +diff --git a/ldap/servers/plugins/syntaxes/inchain.c b/ldap/servers/plugins/syntaxes/inchain.c +new file mode 100644 +index 0000000..53c8b4d +--- /dev/null ++++ b/ldap/servers/plugins/syntaxes/inchain.c +@@ -0,0 +1,450 @@ ++/** 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 **/ ++ ++#ifdef HAVE_CONFIG_H ++#include ++#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); ++} ++ ++typedef enum { ++ MEMBEROF_REUSE_ONLY, ++ MEMBEROF_REUSE_IF_POSSIBLE, ++ MEMBEROF_RECOMPUTE ++} memberof_flag_t; ++ ++typedef struct _slapi_memberofresult { ++ Slapi_ValueSet *nsuniqueid_vals; ++ Slapi_ValueSet *dn_vals; ++ PRBool maxgroups_reached; /* flag is true if the number of groups hit the max limit */ ++} Slapi_MemberOfResult; ++ ++typedef struct _slapi_memberofconfig ++{ ++ char **groupattrs; ++ PRBool subtree_search; ++ int allBackends; ++ Slapi_DN **entryScopes; ++ Slapi_DN **entryScopeExcludeSubtrees; ++ PRBool recurse; ++ int maxgroups; ++ memberof_flag_t flag; ++ char *error_msg; ++ int errot_msg_lenght; ++ int entryScopeCount; /* private to slapi_memberof */ ++ int entryExcludeScopeCount; /* private to slapi_memberof */ ++ PRBool maxgroups_reached; /* private to slapi_memberof */ ++ const char *memberof_attr; /* private to slapi_memberof */ ++ Slapi_Attr *dn_syntax_attr; /* private to slapi_memberof */ ++ PLHashTable *ancestors_cache; /* private to slapi_memberof */ ++ int current_maxgroup; /* private to slapi_memberof */ ++} Slapi_MemberOfConfig; ++ ++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 --git a/ldap/servers/slapd/back-ldbm/filterindex.c b/ldap/servers/slapd/back-ldbm/filterindex.c +index 8a79848..8b1cbd3 100644 +--- a/ldap/servers/slapd/back-ldbm/filterindex.c ++++ b/ldap/servers/slapd/back-ldbm/filterindex.c +@@ -446,6 +446,7 @@ extensible_candidates( + 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); +@@ -465,7 +466,10 @@ extensible_candidates( + /* 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); + 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); +@@ -508,16 +512,36 @@ extensible_candidates( + } 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); +@@ -534,7 +558,12 @@ extensible_candidates( + /* 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; +@@ -567,7 +596,10 @@ extensible_candidates( + } + return_idl: + op = NULL; ++ conn = NULL; ++ + 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 --git a/ldap/servers/slapd/back-ldbm/index.c b/ldap/servers/slapd/back-ldbm/index.c +index 30fa09e..a93c564 100644 +--- a/ldap/servers/slapd/back-ldbm/index.c ++++ b/ldap/servers/slapd/back-ldbm/index.c +@@ -927,6 +927,10 @@ index_read_ext_allids( + + *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 --git a/ldap/servers/slapd/slap.h b/ldap/servers/slapd/slap.h +index 4a1c1c7..878d628 100644 +--- a/ldap/servers/slapd/slap.h ++++ b/ldap/servers/slapd/slap.h +@@ -726,6 +726,7 @@ typedef int (*SyntaxEnumFunc)(char **names, Slapi_PluginDesc *plugindesc, void * + #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" +@@ -734,6 +735,7 @@ typedef int (*SyntaxEnumFunc)(char **names, Slapi_PluginDesc *plugindesc, void * + #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" +-- +2.33.0 +