diff --git a/.389-ds-base.metadata b/.389-ds-base.metadata new file mode 100644 index 0000000..9ce4a90 --- /dev/null +++ b/.389-ds-base.metadata @@ -0,0 +1,3 @@ +c69c175a2f27053dffbfefac9c84ff16c7ff4cbf SOURCES/389-ds-base-1.4.3.23.tar.bz2 +9e06b5cc57fd185379d007696da153893cf73e30 SOURCES/jemalloc-5.2.1.tar.bz2 +22b1ef11852864027e184bb4bee56286b855b703 SOURCES/vendor-1.4.3.23-2.tar.gz diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3e96486 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +SOURCES/389-ds-base-1.4.3.23.tar.bz2 +SOURCES/jemalloc-5.2.1.tar.bz2 +SOURCES/vendor-1.4.3.23-2.tar.gz diff --git a/SOURCES/0001-Issue-4747-Remove-unstable-unstatus-tests-from-PRCI-.patch b/SOURCES/0001-Issue-4747-Remove-unstable-unstatus-tests-from-PRCI-.patch new file mode 100644 index 0000000..1400b43 --- /dev/null +++ b/SOURCES/0001-Issue-4747-Remove-unstable-unstatus-tests-from-PRCI-.patch @@ -0,0 +1,1370 @@ +From 5d730f7e9f1e857bc886556db0229607b8d536d2 Mon Sep 17 00:00:00 2001 +From: tbordaz +Date: Thu, 6 May 2021 18:54:20 +0200 +Subject: [PATCH 01/12] Issue 4747 - Remove unstable/unstatus tests from PRCI + (#4748) + +Bug description: + Some tests (17) in the tests suite (dirsrvtest/tests/suites) + are failing although there is no regression. + It needs (long) investigations to status if failures + are due to a bug in the tests or in DS core. + Until those investigations are completes, test suites + loose a large part of its value to detect regression. + Indeed those failing tests may hide a real regression. + +Fix description: + Flag failing tests with pytest.mark.flaky(max_runs=2, min_passes=1) + Additional action will be to create upstream 17 ticket to + status on each failing tests + +relates: https://github.com/389ds/389-ds-base/issues/4747 + +Reviewed by: Simon Pichugin, Viktor Ashirov (many thanks for your +reviews and help) + +Platforms tested: F33 +--- + .github/workflows/pytest.yml | 84 +++++ + dirsrvtests/tests/suites/acl/keywords_test.py | 16 +- + .../tests/suites/clu/dsctl_acceptance_test.py | 56 --- + .../tests/suites/clu/repl_monitor_test.py | 2 + + .../dynamic_plugins/dynamic_plugins_test.py | 8 +- + .../suites/fourwaymmr/fourwaymmr_test.py | 3 +- + .../suites/healthcheck/health_config_test.py | 1 + + .../suites/healthcheck/health_sync_test.py | 2 + + .../tests/suites/import/import_test.py | 23 +- + .../tests/suites/indexes/regression_test.py | 63 ++++ + .../paged_results/paged_results_test.py | 3 +- + .../tests/suites/password/regression_test.py | 2 + + .../tests/suites/plugins/accpol_test.py | 20 +- + .../suites/plugins/managed_entry_test.py | 351 ++++++++++++++++++ + .../tests/suites/plugins/memberof_test.py | 3 +- + .../suites/replication/cleanallruv_test.py | 8 +- + .../suites/replication/encryption_cl5_test.py | 8 +- + .../tests/suites/retrocl/basic_test.py | 292 --------------- + 18 files changed, 576 insertions(+), 369 deletions(-) + create mode 100644 .github/workflows/pytest.yml + delete mode 100644 dirsrvtests/tests/suites/clu/dsctl_acceptance_test.py + create mode 100644 dirsrvtests/tests/suites/plugins/managed_entry_test.py + delete mode 100644 dirsrvtests/tests/suites/retrocl/basic_test.py + +diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml +new file mode 100644 +index 000000000..015794d96 +--- /dev/null ++++ b/.github/workflows/pytest.yml +@@ -0,0 +1,84 @@ ++name: Test ++ ++on: [push, pull_request] ++ ++jobs: ++ build: ++ name: Build ++ runs-on: ubuntu-20.04 ++ container: ++ image: quay.io/389ds/ci-images:test ++ outputs: ++ matrix: ${{ steps.set-matrix.outputs.matrix }} ++ steps: ++ - name: Checkout ++ uses: actions/checkout@v2 ++ ++ - name: Get a list of all test suites ++ id: set-matrix ++ run: echo "::set-output name=matrix::$(python3 .github/scripts/generate_matrix.py)" ++ ++ - name: Build RPMs ++ run: cd $GITHUB_WORKSPACE && SKIP_AUDIT_CI=1 make -f rpm.mk dist-bz2 rpms ++ ++ - name: Tar build artifacts ++ run: tar -cvf dist.tar dist/ ++ ++ - name: Upload RPMs ++ uses: actions/upload-artifact@v2 ++ with: ++ name: rpms ++ path: dist.tar ++ ++ test: ++ name: Test ++ runs-on: ubuntu-20.04 ++ needs: build ++ strategy: ++ fail-fast: false ++ matrix: ${{ fromJson(needs.build.outputs.matrix) }} ++ ++ steps: ++ - name: Checkout ++ uses: actions/checkout@v2 ++ ++ - name: Install dependencies ++ run: | ++ sudo apt update -y ++ sudo apt install -y docker.io containerd runc ++ ++ sudo cp .github/daemon.json /etc/docker/daemon.json ++ ++ sudo systemctl unmask docker ++ sudo systemctl start docker ++ ++ - name: Download RPMs ++ uses: actions/download-artifact@master ++ with: ++ name: rpms ++ ++ - name: Extract RPMs ++ run: tar xvf dist.tar ++ ++ - name: Run pytest in a container ++ run: | ++ set -x ++ CID=$(sudo docker run -d -h server.example.com --privileged --rm -v /sys/fs/cgroup:/sys/fs/cgroup:rw,rslave -v ${PWD}:/workspace quay.io/389ds/ci-images:test) ++ sudo docker exec $CID sh -c "dnf install -y -v dist/rpms/*rpm" ++ sudo docker exec $CID py.test --suppress-no-test-exit-code -m "not flaky" --junit-xml=pytest.xml -v dirsrvtests/tests/suites/${{ matrix.suite }} ++ ++ - name: Make the results file readable by all ++ if: always() ++ run: ++ sudo chmod -f a+r pytest.xml ++ ++ - name: Sanitize filename ++ run: echo "PYTEST_SUITE=$(echo ${{ matrix.suite }} | sed -e 's#\/#-#g')" >> $GITHUB_ENV ++ ++ - name: Upload pytest test results ++ if: always() ++ uses: actions/upload-artifact@v2 ++ with: ++ name: pytest-${{ env.PYTEST_SUITE }} ++ path: pytest.xml ++ +diff --git a/dirsrvtests/tests/suites/acl/keywords_test.py b/dirsrvtests/tests/suites/acl/keywords_test.py +index 0174152e3..c5e989f3b 100644 +--- a/dirsrvtests/tests/suites/acl/keywords_test.py ++++ b/dirsrvtests/tests/suites/acl/keywords_test.py +@@ -216,7 +216,8 @@ def test_user_binds_without_any_password_and_cannot_access_the_data(topo, add_us + with pytest.raises(ldap.INSUFFICIENT_ACCESS): + org.replace("seeAlso", "cn=1") + +- ++#unstable or unstatus tests, skipped for now ++@pytest.mark.flaky(max_runs=2, min_passes=1) + def test_user_can_access_the_data_when_connecting_from_any_machine( + topo, add_user, aci_of_user + ): +@@ -245,6 +246,8 @@ def test_user_can_access_the_data_when_connecting_from_any_machine( + OrganizationalUnit(conn, DNS_OU_KEY).replace("seeAlso", "cn=1") + + ++#unstable or unstatus tests, skipped for now ++@pytest.mark.flaky(max_runs=2, min_passes=1) + def test_user_can_access_the_data_when_connecting_from_internal_ds_network_only( + topo, add_user, aci_of_user + ): +@@ -276,7 +279,8 @@ def test_user_can_access_the_data_when_connecting_from_internal_ds_network_only( + # Perform Operation + OrganizationalUnit(conn, DNS_OU_KEY).replace("seeAlso", "cn=1") + +- ++#unstable or unstatus tests, skipped for now ++@pytest.mark.flaky(max_runs=2, min_passes=1) + def test_user_can_access_the_data_when_connecting_from_some_network_only( + topo, add_user, aci_of_user + ): +@@ -306,7 +310,8 @@ def test_user_can_access_the_data_when_connecting_from_some_network_only( + # Perform Operation + OrganizationalUnit(conn, DNS_OU_KEY).replace("seeAlso", "cn=1") + +- ++#unstable or unstatus tests, skipped for now ++@pytest.mark.flaky(max_runs=2, min_passes=1) + def test_from_an_unauthorized_network(topo, add_user, aci_of_user): + """User cannot access the data when connecting from an unauthorized network as per the ACI. + +@@ -332,7 +337,8 @@ def test_from_an_unauthorized_network(topo, add_user, aci_of_user): + # Perform Operation + OrganizationalUnit(conn, DNS_OU_KEY).replace("seeAlso", "cn=1") + +- ++#unstable or unstatus tests, skipped for now ++@pytest.mark.flaky(max_runs=2, min_passes=1) + def test_user_cannot_access_the_data_when_connecting_from_an_unauthorized_network_2( + topo, add_user, aci_of_user): + """User cannot access the data when connecting from an unauthorized network as per the ACI. +@@ -418,6 +424,8 @@ def test_dnsalias_keyword_test_nodns_cannot(topo, add_user, aci_of_user): + with pytest.raises(ldap.INSUFFICIENT_ACCESS): + org.replace("seeAlso", "cn=1") + ++#unstable or unstatus tests, skipped for now ++@pytest.mark.flaky(max_runs=2, min_passes=1) + @pytest.mark.ds50378 + @pytest.mark.bz1710848 + @pytest.mark.parametrize("ip_addr", ['127.0.0.1', "[::1]"]) +diff --git a/dirsrvtests/tests/suites/clu/dsctl_acceptance_test.py b/dirsrvtests/tests/suites/clu/dsctl_acceptance_test.py +deleted file mode 100644 +index a0f89defd..000000000 +--- a/dirsrvtests/tests/suites/clu/dsctl_acceptance_test.py ++++ /dev/null +@@ -1,56 +0,0 @@ +-# --- BEGIN COPYRIGHT BLOCK --- +-# Copyright (C) 2021 Red Hat, Inc. +-# All rights reserved. +-# +-# License: GPL (version 3 or any later version). +-# See LICENSE for details. +-# --- END COPYRIGHT BLOCK --- +- +-import logging +-import pytest +-import os +-from lib389._constants import * +-from lib389.topologies import topology_st as topo +- +-log = logging.getLogger(__name__) +- +- +-def test_custom_path(topo): +- """Test that a custom path, backup directory, is correctly used by lib389 +- when the server is stopped. +- +- :id: 8659e209-ee83-477e-8183-1d2f555669ea +- :setup: Standalone Instance +- :steps: +- 1. Get the LDIF directory +- 2. Change the server's backup directory to the LDIF directory +- 3. Stop the server, and perform a backup +- 4. Backup was written to LDIF directory +- :expectedresults: +- 1. Success +- 2. Success +- 3. Success +- 4. Success +- """ +- +- # Get LDIF dir +- ldif_dir = topo.standalone.get_ldif_dir() +- +- # Set backup directory to LDIF directory +- topo.standalone.config.replace('nsslapd-bakdir', ldif_dir) +- +- # Stop the server and take a backup +- topo.standalone.stop() +- topo.standalone.db2bak(None) +- +- # Verify backup was written to LDIF directory +- backups = os.listdir(ldif_dir) +- assert len(backups) +- +- +-if __name__ == '__main__': +- # Run isolated +- # -s for DEBUG mode +- CURRENT_FILE = os.path.realpath(__file__) +- pytest.main(["-s", CURRENT_FILE]) +- +diff --git a/dirsrvtests/tests/suites/clu/repl_monitor_test.py b/dirsrvtests/tests/suites/clu/repl_monitor_test.py +index 9428edb26..3cf6343c8 100644 +--- a/dirsrvtests/tests/suites/clu/repl_monitor_test.py ++++ b/dirsrvtests/tests/suites/clu/repl_monitor_test.py +@@ -90,6 +90,8 @@ def get_hostnames_from_log(port1, port2): + host_m2 = match.group(2) + return (host_m1, host_m2) + ++#unstable or unstatus tests, skipped for now ++@pytest.mark.flaky(max_runs=2, min_passes=1) + @pytest.mark.ds50545 + @pytest.mark.bz1739718 + @pytest.mark.skipif(ds_is_older("1.4.0"), reason="Not implemented") +diff --git a/dirsrvtests/tests/suites/dynamic_plugins/dynamic_plugins_test.py b/dirsrvtests/tests/suites/dynamic_plugins/dynamic_plugins_test.py +index b61daed74..7558cc03d 100644 +--- a/dirsrvtests/tests/suites/dynamic_plugins/dynamic_plugins_test.py ++++ b/dirsrvtests/tests/suites/dynamic_plugins/dynamic_plugins_test.py +@@ -68,7 +68,8 @@ def check_replicas(topology_m2): + + log.info('Data is consistent across the replicas.\n') + +- ++#unstable or unstatus tests, skipped for now ++@pytest.mark.flaky(max_runs=2, min_passes=1) + def test_acceptance(topology_m2): + """Exercise each plugin and its main features, while + changing the configuration without restarting the server. +@@ -140,7 +141,8 @@ def test_acceptance(topology_m2): + ############################################################################ + check_replicas(topology_m2) + +- ++#unstable or unstatus tests, skipped for now ++@pytest.mark.flaky(max_runs=2, min_passes=1) + def test_memory_corruption(topology_m2): + """Check the plugins for memory corruption issues while + dynamic plugins option is enabled +@@ -242,6 +244,8 @@ def test_memory_corruption(topology_m2): + ############################################################################ + check_replicas(topology_m2) + ++#unstable or unstatus tests, skipped for now ++@pytest.mark.flaky(max_runs=2, min_passes=1) + @pytest.mark.tier2 + def test_stress(topology_m2): + """Test plugins while under a big load. Perform the test 5 times +diff --git a/dirsrvtests/tests/suites/fourwaymmr/fourwaymmr_test.py b/dirsrvtests/tests/suites/fourwaymmr/fourwaymmr_test.py +index 5b0754a2e..c5a746ebb 100644 +--- a/dirsrvtests/tests/suites/fourwaymmr/fourwaymmr_test.py ++++ b/dirsrvtests/tests/suites/fourwaymmr/fourwaymmr_test.py +@@ -144,7 +144,8 @@ def test_delete_a_few_entries_in_m4(topo_m4, _cleanupentris): + topo_m4.ms["supplier4"], topo_m4.ms["supplier3"], 30 + ) + +- ++#unstable or unstatus tests, skipped for now ++@pytest.mark.flaky(max_runs=2, min_passes=1) + def test_replicated_multivalued_entries(topo_m4): + """ + Replicated multivalued entries are ordered the same way on all consumers +diff --git a/dirsrvtests/tests/suites/healthcheck/health_config_test.py b/dirsrvtests/tests/suites/healthcheck/health_config_test.py +index 3d102e859..f470c05c6 100644 +--- a/dirsrvtests/tests/suites/healthcheck/health_config_test.py ++++ b/dirsrvtests/tests/suites/healthcheck/health_config_test.py +@@ -337,6 +337,7 @@ def test_healthcheck_low_disk_space(topology_st): + os.remove(file) + + ++@pytest.mark.flaky(max_runs=2, min_passes=1) + @pytest.mark.ds50791 + @pytest.mark.bz1843567 + @pytest.mark.xfail(ds_is_older("1.4.3.8"), reason="Not implemented") +diff --git a/dirsrvtests/tests/suites/healthcheck/health_sync_test.py b/dirsrvtests/tests/suites/healthcheck/health_sync_test.py +index 75bbfd35c..74df1b322 100644 +--- a/dirsrvtests/tests/suites/healthcheck/health_sync_test.py ++++ b/dirsrvtests/tests/suites/healthcheck/health_sync_test.py +@@ -70,6 +70,8 @@ def run_healthcheck_and_flush_log(topology, instance, searched_code, json, searc + @pytest.mark.ds50873 + @pytest.mark.bz1685160 + @pytest.mark.xfail(ds_is_older("1.4.1"), reason="Not implemented") ++#unstable or unstatus tests, skipped for now ++@pytest.mark.flaky(max_runs=2, min_passes=1) + def test_healthcheck_replication_out_of_sync_not_broken(topology_m3): + """Check if HealthCheck returns DSREPLLE0003 code + +diff --git a/dirsrvtests/tests/suites/import/import_test.py b/dirsrvtests/tests/suites/import/import_test.py +index defe447d5..119b097f1 100644 +--- a/dirsrvtests/tests/suites/import/import_test.py ++++ b/dirsrvtests/tests/suites/import/import_test.py +@@ -14,6 +14,7 @@ import os + import pytest + import time + import glob ++import logging + from lib389.topologies import topology_st as topo + from lib389._constants import DEFAULT_SUFFIX, TaskWarning + from lib389.dbgen import dbgen_users +@@ -28,6 +29,12 @@ from lib389.idm.account import Accounts + + pytestmark = pytest.mark.tier1 + ++DEBUGGING = os.getenv("DEBUGGING", default=False) ++if DEBUGGING: ++ logging.getLogger(__name__).setLevel(logging.DEBUG) ++else: ++ logging.getLogger(__name__).setLevel(logging.INFO) ++log = logging.getLogger(__name__) + + def _generate_ldif(topo, no_no): + """ +@@ -349,7 +356,8 @@ def _toggle_private_import_mem(request, topo): + ('nsslapd-db-private-import-mem', 'off')) + request.addfinalizer(finofaci) + +- ++#unstable or unstatus tests, skipped for now ++@pytest.mark.flaky(max_runs=2, min_passes=1) + def test_fast_slow_import(topo, _toggle_private_import_mem, _import_clean): + """With nsslapd-db-private-import-mem: on is faster import. + +@@ -381,16 +389,19 @@ def test_fast_slow_import(topo, _toggle_private_import_mem, _import_clean): + # Let's set nsslapd-db-private-import-mem:on, nsslapd-import-cache-autosize: 0 + config = LDBMConfig(topo.standalone) + # Measure offline import time duration total_time1 +- total_time1 = _import_offline(topo, 20) ++ total_time1 = _import_offline(topo, 1000) + # Now nsslapd-db-private-import-mem:off + config.replace('nsslapd-db-private-import-mem', 'off') + accounts = Accounts(topo.standalone, DEFAULT_SUFFIX) + for i in accounts.filter('(uid=*)'): + UserAccount(topo.standalone, i.dn).delete() + # Measure offline import time duration total_time2 +- total_time2 = _import_offline(topo, 20) ++ total_time2 = _import_offline(topo, 1000) + # total_time1 < total_time2 ++ log.info("total_time1 = %f" % total_time1) ++ log.info("total_time2 = %f" % total_time2) + assert total_time1 < total_time2 ++ + # Set nsslapd-db-private-import-mem:on, nsslapd-import-cache-autosize: -1 + config.replace_many( + ('nsslapd-db-private-import-mem', 'on'), +@@ -398,14 +409,16 @@ def test_fast_slow_import(topo, _toggle_private_import_mem, _import_clean): + for i in accounts.filter('(uid=*)'): + UserAccount(topo.standalone, i.dn).delete() + # Measure offline import time duration total_time1 +- total_time1 = _import_offline(topo, 20) ++ total_time1 = _import_offline(topo, 1000) + # Now nsslapd-db-private-import-mem:off + config.replace('nsslapd-db-private-import-mem', 'off') + for i in accounts.filter('(uid=*)'): + UserAccount(topo.standalone, i.dn).delete() + # Measure offline import time duration total_time2 +- total_time2 = _import_offline(topo, 20) ++ total_time2 = _import_offline(topo, 1000) + # total_time1 < total_time2 ++ log.info("toral_time1 = %f" % total_time1) ++ log.info("total_time2 = %f" % total_time2) + assert total_time1 < total_time2 + + +diff --git a/dirsrvtests/tests/suites/indexes/regression_test.py b/dirsrvtests/tests/suites/indexes/regression_test.py +index 1a71f16e9..ed0c8885f 100644 +--- a/dirsrvtests/tests/suites/indexes/regression_test.py ++++ b/dirsrvtests/tests/suites/indexes/regression_test.py +@@ -19,6 +19,68 @@ from lib389.topologies import topology_st as topo + pytestmark = pytest.mark.tier1 + + ++@pytest.fixture(scope="function") ++def add_a_group_with_users(request, topo): ++ """ ++ Add a group and users, which are members of this group. ++ """ ++ groups = Groups(topo.standalone, DEFAULT_SUFFIX, rdn=None) ++ group = groups.create(properties={'cn': 'test_group'}) ++ users_list = [] ++ users_num = 100 ++ users = UserAccounts(topo.standalone, DEFAULT_SUFFIX, rdn=None) ++ for num in range(users_num): ++ USER_NAME = f'test_{num}' ++ user = users.create(properties={ ++ 'uid': USER_NAME, ++ 'sn': USER_NAME, ++ 'cn': USER_NAME, ++ 'uidNumber': f'{num}', ++ 'gidNumber': f'{num}', ++ 'homeDirectory': f'/home/{USER_NAME}' ++ }) ++ users_list.append(user) ++ group.add_member(user.dn) ++ ++ def fin(): ++ """ ++ Removes group and users. ++ """ ++ # If the server crashed, start it again to do the cleanup ++ if not topo.standalone.status(): ++ topo.standalone.start() ++ for user in users_list: ++ user.delete() ++ group.delete() ++ ++ request.addfinalizer(fin) ++ ++ ++@pytest.fixture(scope="function") ++def set_small_idlistscanlimit(request, topo): ++ """ ++ Set nsslapd-idlistscanlimit to a smaller value to accelerate the reproducer ++ """ ++ db_cfg = DatabaseConfig(topo.standalone) ++ old_idlistscanlimit = db_cfg.get_attr_vals_utf8('nsslapd-idlistscanlimit') ++ db_cfg.set([('nsslapd-idlistscanlimit', '100')]) ++ topo.standalone.restart() ++ ++ def fin(): ++ """ ++ Set nsslapd-idlistscanlimit back to the default value ++ """ ++ # If the server crashed, start it again to do the cleanup ++ if not topo.standalone.status(): ++ topo.standalone.start() ++ db_cfg.set([('nsslapd-idlistscanlimit', old_idlistscanlimit)]) ++ topo.standalone.restart() ++ ++ request.addfinalizer(fin) ++ ++#unstable or unstatus tests, skipped for now ++@pytest.mark.flaky(max_runs=2, min_passes=1) ++@pytest.mark.skipif(ds_is_older("1.4.4.4"), reason="Not implemented") + def test_reindex_task_creates_abandoned_index_file(topo): + """ + Recreating an index for the same attribute but changing +@@ -123,3 +185,4 @@ if __name__ == "__main__": + # -s for DEBUG mode + CURRENT_FILE = os.path.realpath(__file__) + pytest.main("-s %s" % CURRENT_FILE) ++ +diff --git a/dirsrvtests/tests/suites/paged_results/paged_results_test.py b/dirsrvtests/tests/suites/paged_results/paged_results_test.py +index 9fdceb165..0b45b7d96 100644 +--- a/dirsrvtests/tests/suites/paged_results/paged_results_test.py ++++ b/dirsrvtests/tests/suites/paged_results/paged_results_test.py +@@ -506,7 +506,8 @@ def test_search_with_timelimit(topology_st, create_user): + finally: + del_users(users_list) + +- ++#unstable or unstatus tests, skipped for now ++@pytest.mark.flaky(max_runs=2, min_passes=1) + @pytest.mark.parametrize('aci_subject', + ('dns = "{}"'.format(HOSTNAME), + 'ip = "{}"'.format(IP_ADDRESS))) +diff --git a/dirsrvtests/tests/suites/password/regression_test.py b/dirsrvtests/tests/suites/password/regression_test.py +index 251834421..8f1facb6d 100644 +--- a/dirsrvtests/tests/suites/password/regression_test.py ++++ b/dirsrvtests/tests/suites/password/regression_test.py +@@ -215,6 +215,8 @@ def test_global_vs_local(topo, passw_policy, create_user, user_pasw): + # reset password + create_user.set('userPassword', PASSWORD) + ++#unstable or unstatus tests, skipped for now ++@pytest.mark.flaky(max_runs=2, min_passes=1) + @pytest.mark.ds49789 + def test_unhashed_pw_switch(topo_supplier): + """Check that nsslapd-unhashed-pw-switch works corrently +diff --git a/dirsrvtests/tests/suites/plugins/accpol_test.py b/dirsrvtests/tests/suites/plugins/accpol_test.py +index 73e2e54d1..77975c747 100644 +--- a/dirsrvtests/tests/suites/plugins/accpol_test.py ++++ b/dirsrvtests/tests/suites/plugins/accpol_test.py +@@ -520,7 +520,8 @@ def test_glinact_limit(topology_st, accpol_global): + modify_attr(topology_st, ACCP_CONF, 'accountInactivityLimit', '12') + del_users(topology_st, suffix, subtree, userid, nousrs) + +- ++#unstable or unstatus tests, skipped for now ++@pytest.mark.flaky(max_runs=2, min_passes=1) + def test_glnologin_attr(topology_st, accpol_global): + """Verify if user account is inactivated based on createTimeStamp attribute, no lastLoginTime attribute present + +@@ -610,7 +611,8 @@ def test_glnologin_attr(topology_st, accpol_global): + account_status(topology_st, suffix, subtree, userid, nousrs, 0, "Enabled") + del_users(topology_st, suffix, subtree, userid, nousrs) + +- ++#unstable or unstatus tests, skipped for now ++@pytest.mark.flaky(max_runs=2, min_passes=1) + def test_glnoalt_stattr(topology_st, accpol_global): + """Verify if user account can be inactivated based on lastLoginTime attribute, altstateattrname set to 1.1 + +@@ -656,6 +658,8 @@ def test_glnoalt_stattr(topology_st, accpol_global): + del_users(topology_st, suffix, subtree, userid, nousrs) + + ++#unstable or unstatus tests, skipped for now ++@pytest.mark.flaky(max_runs=2, min_passes=1) + def test_glattr_modtime(topology_st, accpol_global): + """Verify if user account can be inactivated based on modifyTimeStamp attribute + +@@ -705,6 +709,8 @@ def test_glattr_modtime(topology_st, accpol_global): + del_users(topology_st, suffix, subtree, userid, nousrs) + + ++#unstable or unstatus tests, skipped for now ++@pytest.mark.flaky(max_runs=2, min_passes=1) + def test_glnoalt_nologin(topology_st, accpol_global): + """Verify if account policy plugin works if we set altstateattrname set to 1.1 and alwaysrecordlogin to NO + +@@ -763,6 +769,8 @@ def test_glnoalt_nologin(topology_st, accpol_global): + del_users(topology_st, suffix, subtree, userid, nousrs) + + ++#unstable or unstatus tests, skipped for now ++@pytest.mark.flaky(max_runs=2, min_passes=1) + def test_glinact_nsact(topology_st, accpol_global): + """Verify if user account can be activated using ns-activate.pl script. + +@@ -812,6 +820,8 @@ def test_glinact_nsact(topology_st, accpol_global): + del_users(topology_st, suffix, subtree, userid, nousrs) + + ++#unstable or unstatus tests, skipped for now ++@pytest.mark.flaky(max_runs=2, min_passes=1) + def test_glinact_acclock(topology_st, accpol_global): + """Verify if user account is activated when account is unlocked by passwordlockoutduration. + +@@ -868,6 +878,8 @@ def test_glinact_acclock(topology_st, accpol_global): + del_users(topology_st, suffix, subtree, userid, nousrs) + + ++#unstable or unstatus tests, skipped for now ++@pytest.mark.flaky(max_runs=2, min_passes=1) + def test_glnact_pwexp(topology_st, accpol_global): + """Verify if user account is activated when password is reset after password is expired + +@@ -951,6 +963,8 @@ def test_glnact_pwexp(topology_st, accpol_global): + del_users(topology_st, suffix, subtree, userid, nousrs) + + ++#unstable or unstatus tests, skipped for now ++@pytest.mark.flaky(max_runs=2, min_passes=1) + def test_locact_inact(topology_st, accpol_local): + """Verify if user account is inactivated when accountInactivityLimit is exceeded. + +@@ -995,6 +1009,8 @@ def test_locact_inact(topology_st, accpol_local): + del_users(topology_st, suffix, subtree, userid, nousrs) + + ++#unstable or unstatus tests, skipped for now ++@pytest.mark.flaky(max_runs=2, min_passes=1) + def test_locinact_modrdn(topology_st, accpol_local): + """Verify if user account is inactivated when moved from ou=groups to ou=people subtree. + +diff --git a/dirsrvtests/tests/suites/plugins/managed_entry_test.py b/dirsrvtests/tests/suites/plugins/managed_entry_test.py +new file mode 100644 +index 000000000..662044ccd +--- /dev/null ++++ b/dirsrvtests/tests/suites/plugins/managed_entry_test.py +@@ -0,0 +1,351 @@ ++# --- BEGIN COPYRIGHT BLOCK --- ++# Copyright (C) 2020 Red Hat, Inc. ++# All rights reserved. ++# ++# License: GPL (version 3 or any later version). ++# See LICENSE for details. ++# --- END COPYRIGHT BLOCK --- ++# ++import pytest ++import time ++from lib389.topologies import topology_st as topo ++from lib389.idm.user import UserAccount, UserAccounts ++from lib389.idm.account import Account, Accounts ++from lib389._constants import DEFAULT_SUFFIX ++from lib389.idm.group import Groups ++from lib389.config import Config ++from lib389.idm.organizationalunit import OrganizationalUnits, OrganizationalUnit ++from lib389.plugins import MEPTemplates, MEPConfigs, ManagedEntriesPlugin, MEPTemplate ++from lib389.idm.nscontainer import nsContainers ++from lib389.idm.domain import Domain ++from lib389.tasks import Entry ++import ldap ++ ++pytestmark = pytest.mark.tier1 ++USER_PASSWORD = 'password' ++ ++ ++@pytest.fixture(scope="module") ++def _create_inital(topo): ++ """ ++ Will create entries for this module ++ """ ++ meps = MEPTemplates(topo.standalone, DEFAULT_SUFFIX) ++ mep_template1 = meps.create( ++ properties={'cn': 'UPG Template', 'mepRDNAttr': 'cn', 'mepStaticAttr': 'objectclass: posixGroup', ++ 'mepMappedAttr': 'cn: $uid|gidNumber: $gidNumber|description: User private group for $uid'.split( ++ '|')}) ++ conf_mep = MEPConfigs(topo.standalone) ++ conf_mep.create(properties={'cn': 'UPG Definition1', 'originScope': f'cn=Users,{DEFAULT_SUFFIX}', ++ 'originFilter': 'objectclass=posixaccount', ++ 'managedBase': f'cn=Groups,{DEFAULT_SUFFIX}', ++ 'managedTemplate': mep_template1.dn}) ++ container = nsContainers(topo.standalone, DEFAULT_SUFFIX) ++ for cn in ['Users', 'Groups']: ++ container.create(properties={'cn': cn}) ++ ++ ++def test_binddn_tracking(topo, _create_inital): ++ """Test Managed Entries basic functionality ++ ++ :id: ea2ddfd4-aaec-11ea-8416-8c16451d917b ++ :setup: Standalone Instance ++ :steps: ++ 1. Set nsslapd-plugin-binddn-tracking attribute under cn=config ++ 2. Add user ++ 3. Managed Entry Plugin runs against managed entries upon any update without validating ++ 4. verify creation of User Private Group with its time stamp value ++ 5. Modify the SN attribute which is not mapped with managed entry ++ 6. run ModRDN operation and check the User Private group ++ 7. Check the time stamp of UPG should be changed now ++ 8. Check the creatorsname should be user dn and internalCreatorsname should be plugin name ++ 9. Check if a managed group entry was created ++ :expected results: ++ 1. Success ++ 2. Success ++ 3. Success ++ 4. Success ++ 5. Success ++ 6. Success ++ 7. Success ++ 8. Success ++ 9. Success ++ """ ++ config = Config(topo.standalone) ++ # set nsslapd-plugin-binddn-tracking attribute under cn=config ++ config.replace('nsslapd-plugin-binddn-tracking', 'on') ++ # Add user ++ user = UserAccounts(topo.standalone, f'cn=Users,{DEFAULT_SUFFIX}', rdn=None).create_test_user() ++ assert user.get_attr_val_utf8('mepManagedEntry') == f'cn=test_user_1000,cn=Groups,{DEFAULT_SUFFIX}' ++ entry = Account(topo.standalone, f'cn=test_user_1000,cn=Groups,{DEFAULT_SUFFIX}') ++ # Managed Entry Plugin runs against managed entries upon any update without validating ++ # verify creation of User Private Group with its time stamp value ++ stamp1 = entry.get_attr_val_utf8('modifyTimestamp') ++ user.replace('sn', 'NewSN_modified') ++ stamp2 = entry.get_attr_val_utf8('modifyTimestamp') ++ # Modify the SN attribute which is not mapped with managed entry ++ # Check the time stamp of UPG should not be changed ++ assert stamp1 == stamp2 ++ time.sleep(1) ++ # run ModRDN operation and check the User Private group ++ user.rename(new_rdn='uid=UserNewRDN', newsuperior='cn=Users,dc=example,dc=com') ++ assert user.get_attr_val_utf8('mepManagedEntry') == f'cn=UserNewRDN,cn=Groups,{DEFAULT_SUFFIX}' ++ entry = Account(topo.standalone, f'cn=UserNewRDN,cn=Groups,{DEFAULT_SUFFIX}') ++ stamp3 = entry.get_attr_val_utf8('modifyTimestamp') ++ # Check the time stamp of UPG should be changed now ++ assert stamp2 != stamp3 ++ time.sleep(1) ++ user.replace('gidNumber', '1') ++ stamp4 = entry.get_attr_val_utf8('modifyTimestamp') ++ assert stamp4 != stamp3 ++ # Check the creatorsname should be user dn and internalCreatorsname should be plugin name ++ assert entry.get_attr_val_utf8('creatorsname') == 'cn=directory manager' ++ assert entry.get_attr_val_utf8('internalCreatorsname') == 'cn=Managed Entries,cn=plugins,cn=config' ++ assert entry.get_attr_val_utf8('modifiersname') == 'cn=directory manager' ++ user.delete() ++ config.replace('nsslapd-plugin-binddn-tracking', 'off') ++ ++ ++class WithObjectClass(Account): ++ def __init__(self, instance, dn=None): ++ super(WithObjectClass, self).__init__(instance, dn) ++ self._rdn_attribute = 'uid' ++ self._create_objectclasses = ['top', 'person', 'inetorgperson'] ++ ++#unstable or unstatus tests, skipped for now ++@pytest.mark.flaky(max_runs=2, min_passes=1) ++def test_mentry01(topo, _create_inital): ++ """Test Managed Entries basic functionality ++ ++ :id: 9b87493b-0493-46f9-8364-6099d0e5d806 ++ :setup: Standalone Instance ++ :steps: ++ 1. Check the plug-in status ++ 2. Add Template and definition entry ++ 3. Add our org units ++ 4. Add users with PosixAccount ObjectClass and verify creation of User Private Group ++ 5. Disable the plug-in and check the status ++ 6. Enable the plug-in and check the status the plug-in is disabled and creation of UPG should fail ++ 7. Add users with PosixAccount ObjectClass and verify creation of User Private Group ++ 8. Add users, run ModRDN operation and check the User Private group ++ 9. Add users, run LDAPMODIFY to change the gidNumber and check the User Private group ++ 10. Checking whether creation of User Private group fails for existing group entry ++ 11. Checking whether adding of posixAccount objectClass to existing user creates UPG ++ 12. Running ModRDN operation and checking the user private groups mepManagedBy attribute ++ 13. Deleting mepManagedBy attribute and running ModRDN operation to check if it creates a new UPG ++ 14. Change the RDN of template entry, DSA Unwilling to perform error expected ++ 15. Change the RDN of cn=Users to cn=TestUsers and check UPG are deleted ++ :expected results: ++ 1. Success ++ 2. Success ++ 3. Success ++ 4. Success ++ 5. Success ++ 6. Success ++ 7. Success ++ 8. Success ++ 9. Success ++ 10. Success ++ 11. Success ++ 12. Success ++ 13. Success ++ 14. Fail(Unwilling to perform ) ++ 15. Success ++ """ ++ # Check the plug-in status ++ mana = ManagedEntriesPlugin(topo.standalone) ++ assert mana.status() ++ # Add Template and definition entry ++ org1 = OrganizationalUnits(topo.standalone, DEFAULT_SUFFIX).create(properties={'ou': 'Users'}) ++ org2 = OrganizationalUnit(topo.standalone, f'ou=Groups,{DEFAULT_SUFFIX}') ++ meps = MEPTemplates(topo.standalone, DEFAULT_SUFFIX) ++ mep_template1 = meps.create(properties={ ++ 'cn': 'UPG Template1', ++ 'mepRDNAttr': 'cn', ++ 'mepStaticAttr': 'objectclass: posixGroup', ++ 'mepMappedAttr': 'cn: $uid|gidNumber: $gidNumber|description: User private group for $uid'.split('|')}) ++ conf_mep = MEPConfigs(topo.standalone) ++ mep_config = conf_mep.create(properties={ ++ 'cn': 'UPG Definition2', ++ 'originScope': org1.dn, ++ 'originFilter': 'objectclass=posixaccount', ++ 'managedBase': org2.dn, ++ 'managedTemplate': mep_template1.dn}) ++ # Add users with PosixAccount ObjectClass and verify creation of User Private Group ++ user = UserAccounts(topo.standalone, f'ou=Users,{DEFAULT_SUFFIX}', rdn=None).create_test_user() ++ assert user.get_attr_val_utf8('mepManagedEntry') == f'cn=test_user_1000,ou=Groups,{DEFAULT_SUFFIX}' ++ # Disable the plug-in and check the status ++ mana.disable() ++ user.delete() ++ topo.standalone.restart() ++ # Add users with PosixAccount ObjectClass when the plug-in is disabled and creation of UPG should fail ++ user = UserAccounts(topo.standalone, f'ou=Users,{DEFAULT_SUFFIX}', rdn=None).create_test_user() ++ assert not user.get_attr_val_utf8('mepManagedEntry') ++ # Enable the plug-in and check the status ++ mana.enable() ++ user.delete() ++ topo.standalone.restart() ++ # Add users with PosixAccount ObjectClass and verify creation of User Private Group ++ user = UserAccounts(topo.standalone, f'ou=Users,{DEFAULT_SUFFIX}', rdn=None).create_test_user() ++ assert user.get_attr_val_utf8('mepManagedEntry') == f'cn=test_user_1000,ou=Groups,{DEFAULT_SUFFIX}' ++ # Add users, run ModRDN operation and check the User Private group ++ # Add users, run LDAPMODIFY to change the gidNumber and check the User Private group ++ user.rename(new_rdn='uid=UserNewRDN', newsuperior='ou=Users,dc=example,dc=com') ++ assert user.get_attr_val_utf8('mepManagedEntry') == f'cn=UserNewRDN,ou=Groups,{DEFAULT_SUFFIX}' ++ user.replace('gidNumber', '20209') ++ entry = Account(topo.standalone, f'cn=UserNewRDN,ou=Groups,{DEFAULT_SUFFIX}') ++ assert entry.get_attr_val_utf8('gidNumber') == '20209' ++ user.replace_many(('sn', 'new_modified_sn'), ('gidNumber', '31309')) ++ assert entry.get_attr_val_utf8('gidNumber') == '31309' ++ user.delete() ++ # Checking whether creation of User Private group fails for existing group entry ++ grp = Groups(topo.standalone, f'ou=Groups,{DEFAULT_SUFFIX}', rdn=None).create(properties={'cn': 'MENTRY_14'}) ++ user = UserAccounts(topo.standalone, f'ou=Users,{DEFAULT_SUFFIX}', rdn=None).create_test_user() ++ with pytest.raises(ldap.NO_SUCH_OBJECT): ++ entry.status() ++ user.delete() ++ # Checking whether adding of posixAccount objectClass to existing user creates UPG ++ # Add Users without posixAccount objectClass ++ users = WithObjectClass(topo.standalone, f'uid=test_test, ou=Users,{DEFAULT_SUFFIX}') ++ user_properties1 = {'uid': 'test_test', 'cn': 'test', 'sn': 'test', 'mail': 'sasa@sasa.com', 'telephoneNumber': '123'} ++ user = users.create(properties=user_properties1) ++ assert not user.get_attr_val_utf8('mepManagedEntry') ++ # Add posixAccount objectClass ++ user.replace_many(('objectclass', ['top', 'person', 'inetorgperson', 'posixAccount']), ++ ('homeDirectory', '/home/ok'), ++ ('uidNumber', '61603'), ('gidNumber', '61603')) ++ assert not user.get_attr_val_utf8('mepManagedEntry') ++ user = UserAccounts(topo.standalone, f'ou=Users,{DEFAULT_SUFFIX}', rdn=None).create_test_user() ++ entry = Account(topo.standalone, 'cn=test_user_1000,ou=Groups,dc=example,dc=com') ++ # Add inetuser objectClass ++ user.replace_many( ++ ('objectclass', ['top', 'account', 'posixaccount', 'inetOrgPerson', ++ 'organizationalPerson', 'nsMemberOf', 'nsAccount', ++ 'person', 'mepOriginEntry', 'inetuser']), ++ ('memberOf', entry.dn)) ++ assert entry.status() ++ user.delete() ++ user = UserAccounts(topo.standalone, f'ou=Users,{DEFAULT_SUFFIX}', rdn=None).create_test_user() ++ entry = Account(topo.standalone, 'cn=test_user_1000,ou=Groups,dc=example,dc=com') ++ # Add groupofNames objectClass ++ user.replace_many( ++ ('objectclass', ['top', 'account', 'posixaccount', 'inetOrgPerson', ++ 'organizationalPerson', 'nsMemberOf', 'nsAccount', ++ 'person', 'mepOriginEntry', 'groupofNames']), ++ ('memberOf', user.dn)) ++ assert entry.status() ++ # Running ModRDN operation and checking the user private groups mepManagedBy attribute ++ user.replace('mepManagedEntry', f'uid=CheckModRDN,ou=Users,{DEFAULT_SUFFIX}') ++ user.rename(new_rdn='uid=UserNewRDN', newsuperior='ou=Users,dc=example,dc=com') ++ assert user.get_attr_val_utf8('mepManagedEntry') == f'uid=CheckModRDN,ou=Users,{DEFAULT_SUFFIX}' ++ # Deleting mepManagedBy attribute and running ModRDN operation to check if it creates a new UPG ++ user.remove('mepManagedEntry', f'uid=CheckModRDN,ou=Users,{DEFAULT_SUFFIX}') ++ user.rename(new_rdn='uid=UserNewRDN1', newsuperior='ou=Users,dc=example,dc=com') ++ assert user.get_attr_val_utf8('mepManagedEntry') == f'cn=UserNewRDN1,ou=Groups,{DEFAULT_SUFFIX}' ++ # Change the RDN of template entry, DSA Unwilling to perform error expected ++ mep = MEPTemplate(topo.standalone, f'cn=UPG Template,{DEFAULT_SUFFIX}') ++ with pytest.raises(ldap.UNWILLING_TO_PERFORM): ++ mep.rename(new_rdn='cn=UPG Template2', newsuperior='dc=example,dc=com') ++ # Change the RDN of cn=Users to cn=TestUsers and check UPG are deleted ++ before = user.get_attr_val_utf8('mepManagedEntry') ++ user.rename(new_rdn='uid=Anuj', newsuperior='ou=Users,dc=example,dc=com') ++ assert user.get_attr_val_utf8('mepManagedEntry') != before ++ ++ ++def test_managed_entry_removal(topo): ++ """Check that we can't remove managed entry manually ++ ++ :id: cf9c5be5-97ef-46fc-b199-8346acf4c296 ++ :setup: Standalone Instance ++ :steps: ++ 1. Enable the plugin ++ 2. Restart the instance ++ 3. Add our org units ++ 4. Set up config entry and template entry for the org units ++ 5. Add an entry that meets the MEP scope ++ 6. Check if a managed group entry was created ++ 7. Try to remove the entry while bound as Admin (non-DM) ++ 8. Remove the entry while bound as DM ++ 9. Check that the managing entry can be deleted too ++ :expectedresults: ++ 1. Success ++ 2. Success ++ 3. Success ++ 4. Success ++ 5. Success ++ 6. Success ++ 7. Should fail ++ 8. Success ++ 9. Success ++ """ ++ ++ inst = topo.standalone ++ ++ # Add ACI so we can test that non-DM user can't delete managed entry ++ domain = Domain(inst, DEFAULT_SUFFIX) ++ ACI_TARGET = f"(target = \"ldap:///{DEFAULT_SUFFIX}\")" ++ ACI_TARGETATTR = "(targetattr = *)" ++ ACI_ALLOW = "(version 3.0; acl \"Admin Access\"; allow (all) " ++ ACI_SUBJECT = "(userdn = \"ldap:///anyone\");)" ++ ACI_BODY = ACI_TARGET + ACI_TARGETATTR + ACI_ALLOW + ACI_SUBJECT ++ domain.add('aci', ACI_BODY) ++ ++ # stop the plugin, and start it ++ plugin = ManagedEntriesPlugin(inst) ++ plugin.disable() ++ plugin.enable() ++ ++ # Add our org units ++ ous = OrganizationalUnits(inst, DEFAULT_SUFFIX) ++ ou_people = ous.create(properties={'ou': 'managed_people'}) ++ ou_groups = ous.create(properties={'ou': 'managed_groups'}) ++ ++ mep_templates = MEPTemplates(inst, DEFAULT_SUFFIX) ++ mep_template1 = mep_templates.create(properties={ ++ 'cn': 'MEP template', ++ 'mepRDNAttr': 'cn', ++ 'mepStaticAttr': 'objectclass: groupOfNames|objectclass: extensibleObject'.split('|'), ++ 'mepMappedAttr': 'cn: $cn|uid: $cn|gidNumber: $uidNumber'.split('|') ++ }) ++ mep_configs = MEPConfigs(inst) ++ mep_configs.create(properties={'cn': 'config', ++ 'originScope': ou_people.dn, ++ 'originFilter': 'objectclass=posixAccount', ++ 'managedBase': ou_groups.dn, ++ 'managedTemplate': mep_template1.dn}) ++ inst.restart() ++ ++ # Add an entry that meets the MEP scope ++ test_users_m1 = UserAccounts(inst, DEFAULT_SUFFIX, rdn='ou={}'.format(ou_people.rdn)) ++ managing_entry = test_users_m1.create_test_user(1001) ++ managing_entry.reset_password(USER_PASSWORD) ++ user_bound_conn = managing_entry.bind(USER_PASSWORD) ++ ++ # Get the managed entry ++ managed_groups = Groups(inst, ou_groups.dn, rdn=None) ++ managed_entry = managed_groups.get(managing_entry.rdn) ++ ++ # Check that the managed entry was created ++ assert managed_entry.exists() ++ ++ # Try to remove the entry while bound as Admin (non-DM) ++ managed_groups_user_conn = Groups(user_bound_conn, ou_groups.dn, rdn=None) ++ managed_entry_user_conn = managed_groups_user_conn.get(managed_entry.rdn) ++ with pytest.raises(ldap.UNWILLING_TO_PERFORM): ++ managed_entry_user_conn.delete() ++ assert managed_entry_user_conn.exists() ++ ++ # Remove the entry while bound as DM ++ managed_entry.delete() ++ assert not managed_entry.exists() ++ ++ # Check that the managing entry can be deleted too ++ managing_entry.delete() ++ assert not managing_entry.exists() ++ ++ ++if __name__ == '__main__': ++ # Run isolated ++ # -s for DEBUG mode ++ CURRENT_FILE = os.path.realpath(__file__) ++ pytest.main("-s %s" % CURRENT_FILE) +diff --git a/dirsrvtests/tests/suites/plugins/memberof_test.py b/dirsrvtests/tests/suites/plugins/memberof_test.py +index bc99eef7d..d3b32c856 100644 +--- a/dirsrvtests/tests/suites/plugins/memberof_test.py ++++ b/dirsrvtests/tests/suites/plugins/memberof_test.py +@@ -2655,7 +2655,8 @@ def test_complex_group_scenario_9(topology_st): + verify_post_025(topology_st, memofegrp020_1, memofegrp020_2, memofegrp020_3, memofegrp020_4, memofegrp020_5, + memofuser1, memofuser2, memofuser3, memofuser4) + +- ++#unstable or unstatus tests, skipped for now ++@pytest.mark.flaky(max_runs=2, min_passes=1) + def test_memberof_auto_add_oc(topology_st): + """Test the auto add objectclass (OC) feature. The plugin should add a predefined + objectclass that will allow memberOf to be added to an entry. +diff --git a/dirsrvtests/tests/suites/replication/cleanallruv_test.py b/dirsrvtests/tests/suites/replication/cleanallruv_test.py +index 5610e3c19..f0cd99cfc 100644 +--- a/dirsrvtests/tests/suites/replication/cleanallruv_test.py ++++ b/dirsrvtests/tests/suites/replication/cleanallruv_test.py +@@ -223,7 +223,7 @@ def test_clean(topology_m4, m4rid): + + log.info('test_clean PASSED, restoring supplier 4...') + +- ++@pytest.mark.flaky(max_runs=2, min_passes=1) + def test_clean_restart(topology_m4, m4rid): + """Check that cleanallruv task works properly after a restart + +@@ -295,6 +295,7 @@ def test_clean_restart(topology_m4, m4rid): + log.info('test_clean_restart PASSED, restoring supplier 4...') + + ++@pytest.mark.flaky(max_runs=2, min_passes=1) + def test_clean_force(topology_m4, m4rid): + """Check that multiple tasks with a 'force' option work properly + +@@ -353,6 +354,7 @@ def test_clean_force(topology_m4, m4rid): + log.info('test_clean_force PASSED, restoring supplier 4...') + + ++@pytest.mark.flaky(max_runs=2, min_passes=1) + def test_abort(topology_m4, m4rid): + """Test the abort task basic functionality + +@@ -408,6 +410,7 @@ def test_abort(topology_m4, m4rid): + log.info('test_abort PASSED, restoring supplier 4...') + + ++@pytest.mark.flaky(max_runs=2, min_passes=1) + def test_abort_restart(topology_m4, m4rid): + """Test the abort task can handle a restart, and then resume + +@@ -486,6 +489,7 @@ def test_abort_restart(topology_m4, m4rid): + log.info('test_abort_restart PASSED, restoring supplier 4...') + + ++@pytest.mark.flaky(max_runs=2, min_passes=1) + def test_abort_certify(topology_m4, m4rid): + """Test the abort task with a replica-certify-all option + +@@ -555,6 +559,7 @@ def test_abort_certify(topology_m4, m4rid): + log.info('test_abort_certify PASSED, restoring supplier 4...') + + ++@pytest.mark.flaky(max_runs=2, min_passes=1) + def test_stress_clean(topology_m4, m4rid): + """Put each server(m1 - m4) under a stress, and perform the entire clean process + +@@ -641,6 +646,7 @@ def test_stress_clean(topology_m4, m4rid): + ldbm_config.set('nsslapd-readonly', 'off') + + ++@pytest.mark.flaky(max_runs=2, min_passes=1) + def test_multiple_tasks_with_force(topology_m4, m4rid): + """Check that multiple tasks with a 'force' option work properly + +diff --git a/dirsrvtests/tests/suites/replication/encryption_cl5_test.py b/dirsrvtests/tests/suites/replication/encryption_cl5_test.py +index 7ae7e1b13..b69863f53 100644 +--- a/dirsrvtests/tests/suites/replication/encryption_cl5_test.py ++++ b/dirsrvtests/tests/suites/replication/encryption_cl5_test.py +@@ -73,10 +73,10 @@ def _check_unhashed_userpw_encrypted(inst, change_type, user_dn, user_pw, is_enc + assert user_pw_attr in entry, 'Changelog entry does not contain clear text password' + assert count, 'Operation type and DN of the entry not matched in changelog' + +- +-@pytest.mark.parametrize("encryption", ["AES", "3DES"]) +-def test_algorithm_unhashed(topology_with_tls, encryption): +- """Check encryption algowithm AES and 3DES. ++#unstable or unstatus tests, skipped for now ++@pytest.mark.flaky(max_runs=2, min_passes=1) ++def test_algorithm_unhashed(topology_with_tls): ++ """Check encryption algorithm AES + And check unhashed#user#password attribute for encryption. + + :id: b7a37bf8-4b2e-4dbd-9891-70117d67558c +diff --git a/dirsrvtests/tests/suites/retrocl/basic_test.py b/dirsrvtests/tests/suites/retrocl/basic_test.py +deleted file mode 100644 +index 112c73cb9..000000000 +--- a/dirsrvtests/tests/suites/retrocl/basic_test.py ++++ /dev/null +@@ -1,292 +0,0 @@ +-# --- BEGIN COPYRIGHT BLOCK --- +-# Copyright (C) 2021 Red Hat, Inc. +-# All rights reserved. +-# +-# License: GPL (version 3 or any later version). +-# See LICENSE for details. +-# --- END COPYRIGHT BLOCK --- +- +-import logging +-import ldap +-import time +-import pytest +-from lib389.topologies import topology_st +-from lib389.plugins import RetroChangelogPlugin +-from lib389._constants import * +-from lib389.utils import * +-from lib389.tasks import * +-from lib389.cli_base import FakeArgs, connect_instance, disconnect_instance +-from lib389.cli_base.dsrc import dsrc_arg_concat +-from lib389.cli_conf.plugins.retrochangelog import retrochangelog_add +-from lib389.idm.user import UserAccount, UserAccounts, nsUserAccounts +- +-pytestmark = pytest.mark.tier1 +- +-USER1_DN = 'uid=user1,ou=people,'+ DEFAULT_SUFFIX +-USER2_DN = 'uid=user2,ou=people,'+ DEFAULT_SUFFIX +-USER_PW = 'password' +-ATTR_HOMEPHONE = 'homePhone' +-ATTR_CARLICENSE = 'carLicense' +- +-log = logging.getLogger(__name__) +- +-def test_retrocl_exclude_attr_add(topology_st): +- """ Test exclude attribute feature of the retrocl plugin for add operation +- +- :id: 3481650f-2070-45ef-9600-2500cfc51559 +- +- :setup: Standalone instance +- +- :steps: +- 1. Enable dynamic plugins +- 2. Confige retro changelog plugin +- 3. Add an entry +- 4. Ensure entry attrs are in the changelog +- 5. Exclude an attr +- 6. Add another entry +- 7. Ensure excluded attr is not in the changelog +- +- :expectedresults: +- 1. Success +- 2. Success +- 3. Success +- 4. Success +- 5. Success +- 6. Success +- 7. Success +- """ +- +- st = topology_st.standalone +- +- log.info('Enable dynamic plugins') +- try: +- st.config.set('nsslapd-dynamic-plugins', 'on') +- except ldap.LDAPError as e: +- ldap.error('Failed to enable dynamic plugins ' + e.args[0]['desc']) +- assert False +- +- log.info('Configure retrocl plugin') +- rcl = RetroChangelogPlugin(st) +- rcl.disable() +- rcl.enable() +- rcl.replace('nsslapd-attribute', 'nsuniqueid:targetUniqueId') +- +- log.info('Restarting instance') +- try: +- st.restart() +- except ldap.LDAPError as e: +- ldap.error('Failed to restart instance ' + e.args[0]['desc']) +- assert False +- +- users = UserAccounts(st, DEFAULT_SUFFIX) +- +- log.info('Adding user1') +- try: +- user1 = users.create(properties={ +- 'sn': '1', +- 'cn': 'user 1', +- 'uid': 'user1', +- 'uidNumber': '11', +- 'gidNumber': '111', +- 'givenname': 'user1', +- 'homePhone': '0861234567', +- 'carLicense': '131D16674', +- 'mail': 'user1@whereever.com', +- 'homeDirectory': '/home/user1', +- 'userpassword': USER_PW}) +- except ldap.ALREADY_EXISTS: +- pass +- except ldap.LDAPError as e: +- log.error("Failed to add user1") +- +- log.info('Verify homePhone and carLicense attrs are in the changelog changestring') +- try: +- cllist = st.search_s(RETROCL_SUFFIX, ldap.SCOPE_SUBTREE, '(targetDn=%s)' % USER1_DN) +- except ldap.LDAPError as e: +- log.fatal("Changelog search failed, error: " +str(e)) +- assert False +- assert len(cllist) > 0 +- if cllist[0].hasAttr('changes'): +- clstr = (cllist[0].getValue('changes')).decode() +- assert ATTR_HOMEPHONE in clstr +- assert ATTR_CARLICENSE in clstr +- +- log.info('Excluding attribute ' + ATTR_HOMEPHONE) +- args = FakeArgs() +- args.connections = [st.host + ':' + str(st.port) + ':' + DN_DM + ':' + PW_DM] +- args.instance = 'standalone1' +- args.basedn = None +- args.binddn = None +- args.starttls = False +- args.pwdfile = None +- args.bindpw = None +- args.prompt = False +- args.exclude_attrs = ATTR_HOMEPHONE +- args.func = retrochangelog_add +- dsrc_inst = dsrc_arg_concat(args, None) +- inst = connect_instance(dsrc_inst, False, args) +- result = args.func(inst, None, log, args) +- disconnect_instance(inst) +- assert result is None +- +- log.info("5s delay for retrocl plugin to restart") +- time.sleep(5) +- +- log.info('Adding user2') +- try: +- user2 = users.create(properties={ +- 'sn': '2', +- 'cn': 'user 2', +- 'uid': 'user2', +- 'uidNumber': '22', +- 'gidNumber': '222', +- 'givenname': 'user2', +- 'homePhone': '0879088363', +- 'carLicense': '04WX11038', +- 'mail': 'user2@whereever.com', +- 'homeDirectory': '/home/user2', +- 'userpassword': USER_PW}) +- except ldap.ALREADY_EXISTS: +- pass +- except ldap.LDAPError as e: +- log.error("Failed to add user2") +- +- log.info('Verify homePhone attr is not in the changelog changestring') +- try: +- cllist = st.search_s(RETROCL_SUFFIX, ldap.SCOPE_SUBTREE, '(targetDn=%s)' % USER2_DN) +- assert len(cllist) > 0 +- if cllist[0].hasAttr('changes'): +- clstr = (cllist[0].getValue('changes')).decode() +- assert ATTR_HOMEPHONE not in clstr +- assert ATTR_CARLICENSE in clstr +- except ldap.LDAPError as e: +- log.fatal("Changelog search failed, error: " +str(e)) +- assert False +- +-def test_retrocl_exclude_attr_mod(topology_st): +- """ Test exclude attribute feature of the retrocl plugin for mod operation +- +- :id: f6bef689-685b-4f86-a98d-f7e6b1fcada3 +- +- :setup: Standalone instance +- +- :steps: +- 1. Enable dynamic plugins +- 2. Confige retro changelog plugin +- 3. Add user1 entry +- 4. Ensure entry attrs are in the changelog +- 5. Exclude an attr +- 6. Modify user1 entry +- 7. Ensure excluded attr is not in the changelog +- +- :expectedresults: +- 1. Success +- 2. Success +- 3. Success +- 4. Success +- 5. Success +- 6. Success +- 7. Success +- """ +- +- st = topology_st.standalone +- +- log.info('Enable dynamic plugins') +- try: +- st.config.set('nsslapd-dynamic-plugins', 'on') +- except ldap.LDAPError as e: +- ldap.error('Failed to enable dynamic plugins ' + e.args[0]['desc']) +- assert False +- +- log.info('Configure retrocl plugin') +- rcl = RetroChangelogPlugin(st) +- rcl.disable() +- rcl.enable() +- rcl.replace('nsslapd-attribute', 'nsuniqueid:targetUniqueId') +- +- log.info('Restarting instance') +- try: +- st.restart() +- except ldap.LDAPError as e: +- ldap.error('Failed to restart instance ' + e.args[0]['desc']) +- assert False +- +- users = UserAccounts(st, DEFAULT_SUFFIX) +- +- log.info('Adding user1') +- try: +- user1 = users.create(properties={ +- 'sn': '1', +- 'cn': 'user 1', +- 'uid': 'user1', +- 'uidNumber': '11', +- 'gidNumber': '111', +- 'givenname': 'user1', +- 'homePhone': '0861234567', +- 'carLicense': '131D16674', +- 'mail': 'user1@whereever.com', +- 'homeDirectory': '/home/user1', +- 'userpassword': USER_PW}) +- except ldap.ALREADY_EXISTS: +- pass +- except ldap.LDAPError as e: +- log.error("Failed to add user1") +- +- log.info('Verify homePhone and carLicense attrs are in the changelog changestring') +- try: +- cllist = st.search_s(RETROCL_SUFFIX, ldap.SCOPE_SUBTREE, '(targetDn=%s)' % USER1_DN) +- except ldap.LDAPError as e: +- log.fatal("Changelog search failed, error: " +str(e)) +- assert False +- assert len(cllist) > 0 +- if cllist[0].hasAttr('changes'): +- clstr = (cllist[0].getValue('changes')).decode() +- assert ATTR_HOMEPHONE in clstr +- assert ATTR_CARLICENSE in clstr +- +- log.info('Excluding attribute ' + ATTR_CARLICENSE) +- args = FakeArgs() +- args.connections = [st.host + ':' + str(st.port) + ':' + DN_DM + ':' + PW_DM] +- args.instance = 'standalone1' +- args.basedn = None +- args.binddn = None +- args.starttls = False +- args.pwdfile = None +- args.bindpw = None +- args.prompt = False +- args.exclude_attrs = ATTR_CARLICENSE +- args.func = retrochangelog_add +- dsrc_inst = dsrc_arg_concat(args, None) +- inst = connect_instance(dsrc_inst, False, args) +- result = args.func(inst, None, log, args) +- disconnect_instance(inst) +- assert result is None +- +- log.info("5s delay for retrocl plugin to restart") +- time.sleep(5) +- +- log.info('Modify user1 carLicense attribute') +- try: +- st.modify_s(USER1_DN, [(ldap.MOD_REPLACE, ATTR_CARLICENSE, b"123WX321")]) +- except ldap.LDAPError as e: +- log.fatal('test_retrocl_exclude_attr_mod: Failed to update user1 attribute: error ' + e.message['desc']) +- assert False +- +- log.info('Verify carLicense attr is not in the changelog changestring') +- try: +- cllist = st.search_s(RETROCL_SUFFIX, ldap.SCOPE_SUBTREE, '(targetDn=%s)' % USER1_DN) +- assert len(cllist) > 0 +- # There will be 2 entries in the changelog for this user, we are only +- #interested in the second one, the modify operation. +- if cllist[1].hasAttr('changes'): +- clstr = (cllist[1].getValue('changes')).decode() +- assert ATTR_CARLICENSE not in clstr +- except ldap.LDAPError as e: +- log.fatal("Changelog search failed, error: " +str(e)) +- assert False +- +-if __name__ == '__main__': +- # Run isolated +- # -s for DEBUG mode +- CURRENT_FILE = os.path.realpath(__file__) +- pytest.main("-s %s" % CURRENT_FILE) +-- +2.26.3 + diff --git a/SOURCES/0002-Issue-4701-RFE-Exclude-attributes-from-retro-changel.patch b/SOURCES/0002-Issue-4701-RFE-Exclude-attributes-from-retro-changel.patch new file mode 100644 index 0000000..1b86463 --- /dev/null +++ b/SOURCES/0002-Issue-4701-RFE-Exclude-attributes-from-retro-changel.patch @@ -0,0 +1,322 @@ +From 1e1c2b23c35282481628af7e971ac683da334502 Mon Sep 17 00:00:00 2001 +From: James Chapman +Date: Tue, 27 Apr 2021 17:00:15 +0100 +Subject: [PATCH 02/12] Issue 4701 - RFE - Exclude attributes from retro + changelog (#4723) + +Description: When the retro changelog plugin is enabled it writes the + added/modified values to the "cn-changelog" suffix. In + some cases an entries attribute values can be of a + sensitive nature and should be excluded. This RFE adds + functionality that will allow an admin exclude certain + attributes from the retro changelog DB. + +Relates: https://github.com/389ds/389-ds-base/issues/4701 + +Reviewed by: mreynolds389, droideck (Thanks folks) +--- + .../tests/suites/retrocl/basic_test.py | 292 ++++++++++++++++++ + 1 file changed, 292 insertions(+) + create mode 100644 dirsrvtests/tests/suites/retrocl/basic_test.py + +diff --git a/dirsrvtests/tests/suites/retrocl/basic_test.py b/dirsrvtests/tests/suites/retrocl/basic_test.py +new file mode 100644 +index 000000000..112c73cb9 +--- /dev/null ++++ b/dirsrvtests/tests/suites/retrocl/basic_test.py +@@ -0,0 +1,292 @@ ++# --- BEGIN COPYRIGHT BLOCK --- ++# Copyright (C) 2021 Red Hat, Inc. ++# All rights reserved. ++# ++# License: GPL (version 3 or any later version). ++# See LICENSE for details. ++# --- END COPYRIGHT BLOCK --- ++ ++import logging ++import ldap ++import time ++import pytest ++from lib389.topologies import topology_st ++from lib389.plugins import RetroChangelogPlugin ++from lib389._constants import * ++from lib389.utils import * ++from lib389.tasks import * ++from lib389.cli_base import FakeArgs, connect_instance, disconnect_instance ++from lib389.cli_base.dsrc import dsrc_arg_concat ++from lib389.cli_conf.plugins.retrochangelog import retrochangelog_add ++from lib389.idm.user import UserAccount, UserAccounts, nsUserAccounts ++ ++pytestmark = pytest.mark.tier1 ++ ++USER1_DN = 'uid=user1,ou=people,'+ DEFAULT_SUFFIX ++USER2_DN = 'uid=user2,ou=people,'+ DEFAULT_SUFFIX ++USER_PW = 'password' ++ATTR_HOMEPHONE = 'homePhone' ++ATTR_CARLICENSE = 'carLicense' ++ ++log = logging.getLogger(__name__) ++ ++def test_retrocl_exclude_attr_add(topology_st): ++ """ Test exclude attribute feature of the retrocl plugin for add operation ++ ++ :id: 3481650f-2070-45ef-9600-2500cfc51559 ++ ++ :setup: Standalone instance ++ ++ :steps: ++ 1. Enable dynamic plugins ++ 2. Confige retro changelog plugin ++ 3. Add an entry ++ 4. Ensure entry attrs are in the changelog ++ 5. Exclude an attr ++ 6. Add another entry ++ 7. Ensure excluded attr is not in the changelog ++ ++ :expectedresults: ++ 1. Success ++ 2. Success ++ 3. Success ++ 4. Success ++ 5. Success ++ 6. Success ++ 7. Success ++ """ ++ ++ st = topology_st.standalone ++ ++ log.info('Enable dynamic plugins') ++ try: ++ st.config.set('nsslapd-dynamic-plugins', 'on') ++ except ldap.LDAPError as e: ++ ldap.error('Failed to enable dynamic plugins ' + e.args[0]['desc']) ++ assert False ++ ++ log.info('Configure retrocl plugin') ++ rcl = RetroChangelogPlugin(st) ++ rcl.disable() ++ rcl.enable() ++ rcl.replace('nsslapd-attribute', 'nsuniqueid:targetUniqueId') ++ ++ log.info('Restarting instance') ++ try: ++ st.restart() ++ except ldap.LDAPError as e: ++ ldap.error('Failed to restart instance ' + e.args[0]['desc']) ++ assert False ++ ++ users = UserAccounts(st, DEFAULT_SUFFIX) ++ ++ log.info('Adding user1') ++ try: ++ user1 = users.create(properties={ ++ 'sn': '1', ++ 'cn': 'user 1', ++ 'uid': 'user1', ++ 'uidNumber': '11', ++ 'gidNumber': '111', ++ 'givenname': 'user1', ++ 'homePhone': '0861234567', ++ 'carLicense': '131D16674', ++ 'mail': 'user1@whereever.com', ++ 'homeDirectory': '/home/user1', ++ 'userpassword': USER_PW}) ++ except ldap.ALREADY_EXISTS: ++ pass ++ except ldap.LDAPError as e: ++ log.error("Failed to add user1") ++ ++ log.info('Verify homePhone and carLicense attrs are in the changelog changestring') ++ try: ++ cllist = st.search_s(RETROCL_SUFFIX, ldap.SCOPE_SUBTREE, '(targetDn=%s)' % USER1_DN) ++ except ldap.LDAPError as e: ++ log.fatal("Changelog search failed, error: " +str(e)) ++ assert False ++ assert len(cllist) > 0 ++ if cllist[0].hasAttr('changes'): ++ clstr = (cllist[0].getValue('changes')).decode() ++ assert ATTR_HOMEPHONE in clstr ++ assert ATTR_CARLICENSE in clstr ++ ++ log.info('Excluding attribute ' + ATTR_HOMEPHONE) ++ args = FakeArgs() ++ args.connections = [st.host + ':' + str(st.port) + ':' + DN_DM + ':' + PW_DM] ++ args.instance = 'standalone1' ++ args.basedn = None ++ args.binddn = None ++ args.starttls = False ++ args.pwdfile = None ++ args.bindpw = None ++ args.prompt = False ++ args.exclude_attrs = ATTR_HOMEPHONE ++ args.func = retrochangelog_add ++ dsrc_inst = dsrc_arg_concat(args, None) ++ inst = connect_instance(dsrc_inst, False, args) ++ result = args.func(inst, None, log, args) ++ disconnect_instance(inst) ++ assert result is None ++ ++ log.info("5s delay for retrocl plugin to restart") ++ time.sleep(5) ++ ++ log.info('Adding user2') ++ try: ++ user2 = users.create(properties={ ++ 'sn': '2', ++ 'cn': 'user 2', ++ 'uid': 'user2', ++ 'uidNumber': '22', ++ 'gidNumber': '222', ++ 'givenname': 'user2', ++ 'homePhone': '0879088363', ++ 'carLicense': '04WX11038', ++ 'mail': 'user2@whereever.com', ++ 'homeDirectory': '/home/user2', ++ 'userpassword': USER_PW}) ++ except ldap.ALREADY_EXISTS: ++ pass ++ except ldap.LDAPError as e: ++ log.error("Failed to add user2") ++ ++ log.info('Verify homePhone attr is not in the changelog changestring') ++ try: ++ cllist = st.search_s(RETROCL_SUFFIX, ldap.SCOPE_SUBTREE, '(targetDn=%s)' % USER2_DN) ++ assert len(cllist) > 0 ++ if cllist[0].hasAttr('changes'): ++ clstr = (cllist[0].getValue('changes')).decode() ++ assert ATTR_HOMEPHONE not in clstr ++ assert ATTR_CARLICENSE in clstr ++ except ldap.LDAPError as e: ++ log.fatal("Changelog search failed, error: " +str(e)) ++ assert False ++ ++def test_retrocl_exclude_attr_mod(topology_st): ++ """ Test exclude attribute feature of the retrocl plugin for mod operation ++ ++ :id: f6bef689-685b-4f86-a98d-f7e6b1fcada3 ++ ++ :setup: Standalone instance ++ ++ :steps: ++ 1. Enable dynamic plugins ++ 2. Confige retro changelog plugin ++ 3. Add user1 entry ++ 4. Ensure entry attrs are in the changelog ++ 5. Exclude an attr ++ 6. Modify user1 entry ++ 7. Ensure excluded attr is not in the changelog ++ ++ :expectedresults: ++ 1. Success ++ 2. Success ++ 3. Success ++ 4. Success ++ 5. Success ++ 6. Success ++ 7. Success ++ """ ++ ++ st = topology_st.standalone ++ ++ log.info('Enable dynamic plugins') ++ try: ++ st.config.set('nsslapd-dynamic-plugins', 'on') ++ except ldap.LDAPError as e: ++ ldap.error('Failed to enable dynamic plugins ' + e.args[0]['desc']) ++ assert False ++ ++ log.info('Configure retrocl plugin') ++ rcl = RetroChangelogPlugin(st) ++ rcl.disable() ++ rcl.enable() ++ rcl.replace('nsslapd-attribute', 'nsuniqueid:targetUniqueId') ++ ++ log.info('Restarting instance') ++ try: ++ st.restart() ++ except ldap.LDAPError as e: ++ ldap.error('Failed to restart instance ' + e.args[0]['desc']) ++ assert False ++ ++ users = UserAccounts(st, DEFAULT_SUFFIX) ++ ++ log.info('Adding user1') ++ try: ++ user1 = users.create(properties={ ++ 'sn': '1', ++ 'cn': 'user 1', ++ 'uid': 'user1', ++ 'uidNumber': '11', ++ 'gidNumber': '111', ++ 'givenname': 'user1', ++ 'homePhone': '0861234567', ++ 'carLicense': '131D16674', ++ 'mail': 'user1@whereever.com', ++ 'homeDirectory': '/home/user1', ++ 'userpassword': USER_PW}) ++ except ldap.ALREADY_EXISTS: ++ pass ++ except ldap.LDAPError as e: ++ log.error("Failed to add user1") ++ ++ log.info('Verify homePhone and carLicense attrs are in the changelog changestring') ++ try: ++ cllist = st.search_s(RETROCL_SUFFIX, ldap.SCOPE_SUBTREE, '(targetDn=%s)' % USER1_DN) ++ except ldap.LDAPError as e: ++ log.fatal("Changelog search failed, error: " +str(e)) ++ assert False ++ assert len(cllist) > 0 ++ if cllist[0].hasAttr('changes'): ++ clstr = (cllist[0].getValue('changes')).decode() ++ assert ATTR_HOMEPHONE in clstr ++ assert ATTR_CARLICENSE in clstr ++ ++ log.info('Excluding attribute ' + ATTR_CARLICENSE) ++ args = FakeArgs() ++ args.connections = [st.host + ':' + str(st.port) + ':' + DN_DM + ':' + PW_DM] ++ args.instance = 'standalone1' ++ args.basedn = None ++ args.binddn = None ++ args.starttls = False ++ args.pwdfile = None ++ args.bindpw = None ++ args.prompt = False ++ args.exclude_attrs = ATTR_CARLICENSE ++ args.func = retrochangelog_add ++ dsrc_inst = dsrc_arg_concat(args, None) ++ inst = connect_instance(dsrc_inst, False, args) ++ result = args.func(inst, None, log, args) ++ disconnect_instance(inst) ++ assert result is None ++ ++ log.info("5s delay for retrocl plugin to restart") ++ time.sleep(5) ++ ++ log.info('Modify user1 carLicense attribute') ++ try: ++ st.modify_s(USER1_DN, [(ldap.MOD_REPLACE, ATTR_CARLICENSE, b"123WX321")]) ++ except ldap.LDAPError as e: ++ log.fatal('test_retrocl_exclude_attr_mod: Failed to update user1 attribute: error ' + e.message['desc']) ++ assert False ++ ++ log.info('Verify carLicense attr is not in the changelog changestring') ++ try: ++ cllist = st.search_s(RETROCL_SUFFIX, ldap.SCOPE_SUBTREE, '(targetDn=%s)' % USER1_DN) ++ assert len(cllist) > 0 ++ # There will be 2 entries in the changelog for this user, we are only ++ #interested in the second one, the modify operation. ++ if cllist[1].hasAttr('changes'): ++ clstr = (cllist[1].getValue('changes')).decode() ++ assert ATTR_CARLICENSE not in clstr ++ except ldap.LDAPError as e: ++ log.fatal("Changelog search failed, error: " +str(e)) ++ assert False ++ ++if __name__ == '__main__': ++ # Run isolated ++ # -s for DEBUG mode ++ CURRENT_FILE = os.path.realpath(__file__) ++ pytest.main("-s %s" % CURRENT_FILE) +-- +2.26.3 + diff --git a/SOURCES/0003-Ticket-137-Implement-EntryUUID-plugin.patch b/SOURCES/0003-Ticket-137-Implement-EntryUUID-plugin.patch new file mode 100644 index 0000000..67ccf0c --- /dev/null +++ b/SOURCES/0003-Ticket-137-Implement-EntryUUID-plugin.patch @@ -0,0 +1,5307 @@ +From eff14f0c884f3d2f541e3be6d9df86087177a76d Mon Sep 17 00:00:00 2001 +From: William Brown +Date: Mon, 16 Mar 2020 14:59:56 +1000 +Subject: [PATCH 03/12] Ticket 137 - Implement EntryUUID plugin + +Bug Description: This implements EntryUUID - A plugin that generates +uuid's on attributes, which can be used by external applications to +associate an entry uniquely. + +Fix Description: This change is quite large as it contains multiple parts: + +* Schema for entryuuid. + ldap/schema/02common.ldif + ldap/schema/03entryuuid.ldif +* Documentation of the plugin design + src/README.md +* A rust plugin api. + src/slapi_r_plugin/Cargo.toml + src/slapi_r_plugin/README.md + src/slapi_r_plugin/build.rs + src/slapi_r_plugin/src/backend.rs + src/slapi_r_plugin/src/ber.rs + src/slapi_r_plugin/src/constants.rs + src/slapi_r_plugin/src/dn.rs + src/slapi_r_plugin/src/entry.rs + src/slapi_r_plugin/src/error.rs + src/slapi_r_plugin/src/init.c + src/slapi_r_plugin/src/lib.rs + src/slapi_r_plugin/src/log.rs + src/slapi_r_plugin/src/macros.rs + src/slapi_r_plugin/src/pblock.rs + src/slapi_r_plugin/src/plugin.rs + src/slapi_r_plugin/src/search.rs + src/slapi_r_plugin/src/syntax_plugin.rs + src/slapi_r_plugin/src/task.rs + src/slapi_r_plugin/src/value.rs +* An entry uuid syntax plugin, that has functional indexing + src/plugins/entryuuid_syntax/Cargo.toml + src/plugins/entryuuid_syntax/src/lib.rs +* A entry uuid plugin that generates entryuuid's and has a fixup task. + src/plugins/entryuuid/Cargo.toml + src/plugins/entryuuid/src/lib.rs +* Supporting changes in the server core to enable and provide apis for the plugins. + ldap/servers/slapd/config.c + ldap/servers/slapd/entry.c + ldap/servers/slapd/fedse.c +* A test suite for for the entryuuid plugin + dirsrvtests/tests/data/entryuuid/localhost-userRoot-2020_03_30_13_14_47.ldif + dirsrvtests/tests/suites/entryuuid/basic_test.py +* Supporting changes in lib389 + src/lib389/lib389/_constants.py + src/lib389/lib389/backend.py + src/lib389/lib389/instance/setup.py + src/lib389/lib389/plugins.py + src/lib389/lib389/tasks.py +* Changes to support building the plugins + Makefile.am + configure.ac +* Execution of cargo fmt on the tree, causing some clean up of files. + src/Cargo.lock + src/Cargo.toml + src/librnsslapd/build.rs + src/librnsslapd/src/lib.rs + src/librslapd/Cargo.toml + src/librslapd/build.rs + src/librslapd/src/lib.rs + src/libsds/sds/lib.rs + src/libsds/sds/tqueue.rs + src/slapd/src/error.rs + src/slapd/src/fernet.rs + src/slapd/src/lib.rs + +https://pagure.io/389-ds-base/issue/137 + +Author: William Brown + +Review by: mreynolds, lkrispenz (Thanks) +--- + Makefile.am | 96 +- + ...ocalhost-userRoot-2020_03_30_13_14_47.ldif | 233 +++++ + .../tests/suites/entryuuid/basic_test.py | 226 +++++ + ldap/schema/02common.ldif | 1 + + ldap/schema/03entryuuid.ldif | 16 + + ldap/servers/slapd/config.c | 17 + + ldap/servers/slapd/entry.c | 12 + + ldap/servers/slapd/fedse.c | 28 + + src/Cargo.lock | 241 +++-- + src/Cargo.toml | 11 +- + src/README.md | 0 + src/lib389/lib389/_constants.py | 1 + + src/lib389/lib389/backend.py | 2 +- + src/lib389/lib389/instance/setup.py | 14 + + src/lib389/lib389/plugins.py | 30 + + src/lib389/lib389/tasks.py | 14 + + src/librnsslapd/build.rs | 19 +- + src/librnsslapd/src/lib.rs | 16 +- + src/librslapd/Cargo.toml | 4 - + src/librslapd/build.rs | 19 +- + src/librslapd/src/lib.rs | 11 +- + src/libsds/sds/lib.rs | 2 - + src/libsds/sds/tqueue.rs | 23 +- + src/plugins/entryuuid/Cargo.toml | 21 + + src/plugins/entryuuid/src/lib.rs | 196 ++++ + src/plugins/entryuuid_syntax/Cargo.toml | 21 + + src/plugins/entryuuid_syntax/src/lib.rs | 145 +++ + src/slapd/src/error.rs | 2 - + src/slapd/src/fernet.rs | 31 +- + src/slapd/src/lib.rs | 3 - + src/slapi_r_plugin/Cargo.toml | 19 + + src/slapi_r_plugin/README.md | 216 +++++ + src/slapi_r_plugin/build.rs | 8 + + src/slapi_r_plugin/src/backend.rs | 71 ++ + src/slapi_r_plugin/src/ber.rs | 90 ++ + src/slapi_r_plugin/src/constants.rs | 203 +++++ + src/slapi_r_plugin/src/dn.rs | 108 +++ + src/slapi_r_plugin/src/entry.rs | 92 ++ + src/slapi_r_plugin/src/error.rs | 61 ++ + src/slapi_r_plugin/src/init.c | 8 + + src/slapi_r_plugin/src/lib.rs | 36 + + src/slapi_r_plugin/src/log.rs | 87 ++ + src/slapi_r_plugin/src/macros.rs | 835 ++++++++++++++++++ + src/slapi_r_plugin/src/pblock.rs | 275 ++++++ + src/slapi_r_plugin/src/plugin.rs | 117 +++ + src/slapi_r_plugin/src/search.rs | 127 +++ + src/slapi_r_plugin/src/syntax_plugin.rs | 169 ++++ + src/slapi_r_plugin/src/task.rs | 148 ++++ + src/slapi_r_plugin/src/value.rs | 235 +++++ + 49 files changed, 4213 insertions(+), 147 deletions(-) + create mode 100644 dirsrvtests/tests/data/entryuuid/localhost-userRoot-2020_03_30_13_14_47.ldif + create mode 100644 dirsrvtests/tests/suites/entryuuid/basic_test.py + create mode 100644 ldap/schema/03entryuuid.ldif + create mode 100644 src/README.md + create mode 100644 src/plugins/entryuuid/Cargo.toml + create mode 100644 src/plugins/entryuuid/src/lib.rs + create mode 100644 src/plugins/entryuuid_syntax/Cargo.toml + create mode 100644 src/plugins/entryuuid_syntax/src/lib.rs + create mode 100644 src/slapi_r_plugin/Cargo.toml + create mode 100644 src/slapi_r_plugin/README.md + create mode 100644 src/slapi_r_plugin/build.rs + create mode 100644 src/slapi_r_plugin/src/backend.rs + create mode 100644 src/slapi_r_plugin/src/ber.rs + create mode 100644 src/slapi_r_plugin/src/constants.rs + create mode 100644 src/slapi_r_plugin/src/dn.rs + create mode 100644 src/slapi_r_plugin/src/entry.rs + create mode 100644 src/slapi_r_plugin/src/error.rs + create mode 100644 src/slapi_r_plugin/src/init.c + create mode 100644 src/slapi_r_plugin/src/lib.rs + create mode 100644 src/slapi_r_plugin/src/log.rs + create mode 100644 src/slapi_r_plugin/src/macros.rs + create mode 100644 src/slapi_r_plugin/src/pblock.rs + create mode 100644 src/slapi_r_plugin/src/plugin.rs + create mode 100644 src/slapi_r_plugin/src/search.rs + create mode 100644 src/slapi_r_plugin/src/syntax_plugin.rs + create mode 100644 src/slapi_r_plugin/src/task.rs + create mode 100644 src/slapi_r_plugin/src/value.rs + +diff --git a/Makefile.am b/Makefile.am +index 668a095da..627953850 100644 +--- a/Makefile.am ++++ b/Makefile.am +@@ -38,6 +38,7 @@ if RUST_ENABLE + RUST_ON = 1 + CARGO_FLAGS = @cargo_defs@ + RUSTC_FLAGS = @asan_rust_defs@ @msan_rust_defs@ @tsan_rust_defs@ @debug_rust_defs@ ++# -L@abs_top_builddir@/rs/@rust_target_dir@ + RUST_LDFLAGS = -ldl -lpthread -lgcc_s -lc -lm -lrt -lutil + RUST_DEFINES = -DRUST_ENABLE + if RUST_ENABLE_OFFLINE +@@ -298,7 +299,7 @@ clean-local: + -rm -rf $(abs_top_builddir)/html + -rm -rf $(abs_top_builddir)/man/man3 + if RUST_ENABLE +- CARGO_TARGET_DIR=$(abs_top_builddir)/rs cargo clean --manifest-path=$(srcdir)/src/libsds/Cargo.toml ++ CARGO_TARGET_DIR=$(abs_top_builddir)/rs cargo clean --manifest-path=$(srcdir)/src/Cargo.toml + endif + + dberrstrs.h: Makefile +@@ -416,6 +417,11 @@ serverplugin_LTLIBRARIES = libacl-plugin.la \ + $(LIBPAM_PASSTHRU_PLUGIN) $(LIBDNA_PLUGIN) \ + $(LIBBITWISE_PLUGIN) $(LIBPRESENCE_PLUGIN) $(LIBPOSIX_WINSYNC_PLUGIN) + ++if RUST_ENABLE ++serverplugin_LTLIBRARIES += libentryuuid-plugin.la libentryuuid-syntax-plugin.la ++endif ++ ++ + noinst_LIBRARIES = libavl.a + + dist_noinst_HEADERS = \ +@@ -757,6 +763,10 @@ systemschema_DATA = $(srcdir)/ldap/schema/00core.ldif \ + $(srcdir)/ldap/schema/60nss-ldap.ldif \ + $(LIBACCTPOLICY_SCHEMA) + ++if RUST_ENABLE ++systemschema_DATA += $(srcdir)/ldap/schema/03entryuuid.ldif ++endif ++ + schema_DATA = $(srcdir)/ldap/schema/99user.ldif + + libexec_SCRIPTS = +@@ -1227,7 +1237,7 @@ libsds_la_LDFLAGS = $(AM_LDFLAGS) $(SDS_LDFLAGS) + + if RUST_ENABLE + +-noinst_LTLIBRARIES = librsds.la librslapd.la librnsslapd.la ++noinst_LTLIBRARIES = librsds.la librslapd.la librnsslapd.la libentryuuid.la libentryuuid_syntax.la + + ### Why does this exist? + # +@@ -1252,6 +1262,8 @@ librsds_la_EXTRA = src/libsds/Cargo.lock + @abs_top_builddir@/rs/@rust_target_dir@/librsds.a: $(librsds_la_SOURCES) + RUST_BACKTRACE=1 RUSTC_BOOTSTRAP=1 \ + CARGO_TARGET_DIR=$(abs_top_builddir)/rs \ ++ SLAPD_DYLIB_DIR=$(abs_top_builddir)/ \ ++ SLAPD_HEADER_DIR=$(abs_top_builddir)/ \ + cargo rustc $(RUST_OFFLINE) --manifest-path=$(srcdir)/src/libsds/Cargo.toml \ + $(CARGO_FLAGS) --verbose -- $(RUSTC_FLAGS) + +@@ -1268,6 +1280,7 @@ librslapd_la_EXTRA = src/librslapd/Cargo.lock + @abs_top_builddir@/rs/@rust_target_dir@/librslapd.a: $(librslapd_la_SOURCES) + RUST_BACKTRACE=1 RUSTC_BOOTSTRAP=1 \ + CARGO_TARGET_DIR=$(abs_top_builddir)/rs \ ++ SLAPD_DYLIB_DIR=$(abs_top_builddir)/ \ + SLAPD_HEADER_DIR=$(abs_top_builddir)/ \ + cargo rustc $(RUST_OFFLINE) --manifest-path=$(srcdir)/src/librslapd/Cargo.toml \ + $(CARGO_FLAGS) --verbose -- $(RUSTC_FLAGS) +@@ -1288,6 +1301,7 @@ librnsslapd_la_EXTRA = src/librnsslapd/Cargo.lock + @abs_top_builddir@/rs/@rust_target_dir@/librnsslapd.a: $(librnsslapd_la_SOURCES) + RUST_BACKTRACE=1 RUSTC_BOOTSTRAP=1 \ + CARGO_TARGET_DIR=$(abs_top_builddir)/rs \ ++ SLAPD_DYLIB_DIR=$(abs_top_builddir)/ \ + SLAPD_HEADER_DIR=$(abs_top_builddir)/ \ + cargo rustc $(RUST_OFFLINE) --manifest-path=$(srcdir)/src/librnsslapd/Cargo.toml \ + $(CARGO_FLAGS) --verbose -- $(RUSTC_FLAGS) +@@ -1295,8 +1309,64 @@ librnsslapd_la_EXTRA = src/librnsslapd/Cargo.lock + # The header needs the lib build first. + rust-nsslapd-private.h: @abs_top_builddir@/rs/@rust_target_dir@/librnsslapd.a + ++libslapi_r_plugin_SOURCES = \ ++ src/slapi_r_plugin/src/backend.rs \ ++ src/slapi_r_plugin/src/ber.rs \ ++ src/slapi_r_plugin/src/constants.rs \ ++ src/slapi_r_plugin/src/dn.rs \ ++ src/slapi_r_plugin/src/entry.rs \ ++ src/slapi_r_plugin/src/error.rs \ ++ src/slapi_r_plugin/src/log.rs \ ++ src/slapi_r_plugin/src/macros.rs \ ++ src/slapi_r_plugin/src/pblock.rs \ ++ src/slapi_r_plugin/src/plugin.rs \ ++ src/slapi_r_plugin/src/search.rs \ ++ src/slapi_r_plugin/src/syntax_plugin.rs \ ++ src/slapi_r_plugin/src/task.rs \ ++ src/slapi_r_plugin/src/value.rs \ ++ src/slapi_r_plugin/src/lib.rs ++ ++# Build rust ns-slapd components as a library. ++ENTRYUUID_LIB = @abs_top_builddir@/rs/@rust_target_dir@/libentryuuid.a ++ ++libentryuuid_la_SOURCES = \ ++ src/plugins/entryuuid/Cargo.toml \ ++ src/plugins/entryuuid/src/lib.rs \ ++ $(libslapi_r_plugin_SOURCES) ++ ++libentryuuid_la_EXTRA = src/plugin/entryuuid/Cargo.lock ++ ++@abs_top_builddir@/rs/@rust_target_dir@/libentryuuid.a: $(libentryuuid_la_SOURCES) libslapd.la libentryuuid.la ++ RUST_BACKTRACE=1 RUSTC_BOOTSTRAP=1 \ ++ CARGO_TARGET_DIR=$(abs_top_builddir)/rs \ ++ SLAPD_DYLIB_DIR=$(abs_top_builddir)/ \ ++ SLAPD_HEADER_DIR=$(abs_top_builddir)/ \ ++ cargo rustc $(RUST_OFFLINE) --manifest-path=$(srcdir)/src/plugins/entryuuid/Cargo.toml \ ++ $(CARGO_FLAGS) --verbose -- $(RUSTC_FLAGS) ++ cp $(ENTRYUUID_LIB) @abs_top_builddir@/.libs/libentryuuid.a ++ ++ENTRYUUID_SYNTAX_LIB = @abs_top_builddir@/rs/@rust_target_dir@/libentryuuid_syntax.a ++ ++libentryuuid_syntax_la_SOURCES = \ ++ src/plugins/entryuuid_syntax/Cargo.toml \ ++ src/plugins/entryuuid_syntax/src/lib.rs \ ++ $(libslapi_r_plugin_SOURCES) ++ ++libentryuuid_syntax_la_EXTRA = src/plugin/entryuuid_syntax/Cargo.lock ++ ++@abs_top_builddir@/rs/@rust_target_dir@/libentryuuid_syntax.a: $(libentryuuid_syntax_la_SOURCES) libslapd.la libentryuuid_syntax.la ++ RUST_BACKTRACE=1 RUSTC_BOOTSTRAP=1 \ ++ CARGO_TARGET_DIR=$(abs_top_builddir)/rs \ ++ SLAPD_DYLIB_DIR=$(abs_top_builddir)/ \ ++ SLAPD_HEADER_DIR=$(abs_top_builddir)/ \ ++ cargo rustc $(RUST_OFFLINE) --manifest-path=$(srcdir)/src/plugins/entryuuid_syntax/Cargo.toml \ ++ $(CARGO_FLAGS) --verbose -- $(RUSTC_FLAGS) ++ cp $(ENTRYUUID_SYNTAX_LIB) @abs_top_builddir@/.libs/libentryuuid_syntax.a ++ + EXTRA_DIST = $(librsds_la_SOURCES) $(librsds_la_EXTRA) \ + $(librslapd_la_SOURCES) $(librslapd_la_EXTRA) \ ++ $(libentryuuid_la_SOURCES) $(libentryuuid_la_EXTRA) \ ++ $(libentryuuid_syntax_la_SOURCES) $(libentryuuid_syntax_la_EXTRA) \ + $(librnsslapd_la_SOURCES) $(librnsslapd_la_EXTRA) + + ## Run rust tests +@@ -1306,13 +1376,17 @@ else + check-local: + RUST_BACKTRACE=1 RUSTC_BOOTSTRAP=1 \ + CARGO_TARGET_DIR=$(abs_top_builddir)/rs \ ++ SLAPD_DYLIB_DIR=$(abs_top_builddir)/ \ ++ SLAPD_HEADER_DIR=$(abs_top_builddir)/ \ + cargo test $(RUST_OFFLINE) --manifest-path=$(srcdir)/src/libsds/Cargo.toml + RUST_BACKTRACE=1 RUSTC_BOOTSTRAP=1 \ + CARGO_TARGET_DIR=$(abs_top_builddir)/rs \ ++ SLAPD_DYLIB_DIR=$(abs_top_builddir)/ \ + SLAPD_HEADER_DIR=$(abs_top_builddir)/ \ + cargo test $(RUST_OFFLINE) --manifest-path=$(srcdir)/src/librslapd/Cargo.toml + RUST_BACKTRACE=1 RUSTC_BOOTSTRAP=1 \ + CARGO_TARGET_DIR=$(abs_top_builddir)/rs \ ++ SLAPD_DYLIB_DIR=$(abs_top_builddir)/ \ + SLAPD_HEADER_DIR=$(abs_top_builddir)/ \ + cargo test $(RUST_OFFLINE) --manifest-path=$(srcdir)/src/librnsslapd/Cargo.toml + endif +@@ -1735,6 +1809,24 @@ libderef_plugin_la_LIBADD = libslapd.la $(LDAPSDK_LINK) $(NSPR_LINK) + libderef_plugin_la_DEPENDENCIES = libslapd.la + libderef_plugin_la_LDFLAGS = -avoid-version + ++if RUST_ENABLE ++#------------------------ ++# libentryuuid-syntax-plugin ++#----------------------- ++libentryuuid_syntax_plugin_la_SOURCES = src/slapi_r_plugin/src/init.c ++libentryuuid_syntax_plugin_la_LIBADD = libslapd.la $(LDAPSDK_LINK) $(NSPR_LINK) -lentryuuid_syntax ++libentryuuid_syntax_plugin_la_DEPENDENCIES = libslapd.la $(ENTRYUUID_SYNTAX_LIB) ++libentryuuid_syntax_plugin_la_LDFLAGS = -avoid-version ++ ++#------------------------ ++# libentryuuid-plugin ++#----------------------- ++libentryuuid_plugin_la_SOURCES = src/slapi_r_plugin/src/init.c ++libentryuuid_plugin_la_LIBADD = libslapd.la $(LDAPSDK_LINK) $(NSPR_LINK) -lentryuuid ++libentryuuid_plugin_la_DEPENDENCIES = libslapd.la $(ENTRYUUID_LIB) ++libentryuuid_plugin_la_LDFLAGS = -avoid-version ++endif ++ + #------------------------ + # libpbe-plugin + #----------------------- +diff --git a/dirsrvtests/tests/data/entryuuid/localhost-userRoot-2020_03_30_13_14_47.ldif b/dirsrvtests/tests/data/entryuuid/localhost-userRoot-2020_03_30_13_14_47.ldif +new file mode 100644 +index 000000000..b64090af7 +--- /dev/null ++++ b/dirsrvtests/tests/data/entryuuid/localhost-userRoot-2020_03_30_13_14_47.ldif +@@ -0,0 +1,233 @@ ++version: 1 ++ ++# entry-id: 1 ++dn: dc=example,dc=com ++objectClass: top ++objectClass: domain ++dc: example ++description: dc=example,dc=com ++creatorsName: cn=Directory Manager ++modifiersName: cn=Directory Manager ++createTimestamp: 20200325015542Z ++modifyTimestamp: 20200325015542Z ++nsUniqueId: a2b33229-6e3b11ea-8de0c78c-83e27eda ++aci: (targetattr="dc || description || objectClass")(targetfilter="(objectClas ++ s=domain)")(version 3.0; acl "Enable anyone domain read"; allow (read, search ++ , compare)(userdn="ldap:///anyone");) ++aci: (targetattr="ou || objectClass")(targetfilter="(objectClass=organizationa ++ lUnit)")(version 3.0; acl "Enable anyone ou read"; allow (read, search, compa ++ re)(userdn="ldap:///anyone");) ++ ++# entry-id: 2 ++dn: cn=389_ds_system,dc=example,dc=com ++objectClass: top ++objectClass: nscontainer ++objectClass: ldapsubentry ++cn: 389_ds_system ++creatorsName: cn=Directory Manager ++modifiersName: cn=Directory Manager ++createTimestamp: 20200325015542Z ++modifyTimestamp: 20200325015542Z ++nsUniqueId: a2b3322a-6e3b11ea-8de0c78c-83e27eda ++ ++# entry-id: 3 ++dn: ou=groups,dc=example,dc=com ++objectClass: top ++objectClass: organizationalunit ++ou: groups ++aci: (targetattr="cn || member || gidNumber || nsUniqueId || description || ob ++ jectClass")(targetfilter="(objectClass=groupOfNames)")(version 3.0; acl "Enab ++ le anyone group read"; allow (read, search, compare)(userdn="ldap:///anyone") ++ ;) ++aci: (targetattr="member")(targetfilter="(objectClass=groupOfNames)")(version ++ 3.0; acl "Enable group_modify to alter members"; allow (write)(groupdn="ldap: ++ ///cn=group_modify,ou=permissions,dc=example,dc=com");) ++aci: (targetattr="cn || member || gidNumber || description || objectClass")(ta ++ rgetfilter="(objectClass=groupOfNames)")(version 3.0; acl "Enable group_admin ++ to manage groups"; allow (write, add, delete)(groupdn="ldap:///cn=group_admi ++ n,ou=permissions,dc=example,dc=com");) ++creatorsName: cn=Directory Manager ++modifiersName: cn=Directory Manager ++createTimestamp: 20200325015543Z ++modifyTimestamp: 20200325015543Z ++nsUniqueId: a2b3322b-6e3b11ea-8de0c78c-83e27eda ++ ++# entry-id: 4 ++dn: ou=people,dc=example,dc=com ++objectClass: top ++objectClass: organizationalunit ++ou: people ++aci: (targetattr="objectClass || description || nsUniqueId || uid || displayNa ++ me || loginShell || uidNumber || gidNumber || gecos || homeDirectory || cn || ++ memberOf || mail || nsSshPublicKey || nsAccountLock || userCertificate")(tar ++ getfilter="(objectClass=posixaccount)")(version 3.0; acl "Enable anyone user ++ read"; allow (read, search, compare)(userdn="ldap:///anyone");) ++aci: (targetattr="displayName || legalName || userPassword || nsSshPublicKey") ++ (version 3.0; acl "Enable self partial modify"; allow (write)(userdn="ldap:// ++ /self");) ++aci: (targetattr="legalName || telephoneNumber || mobile || sn")(targetfilter= ++ "(|(objectClass=nsPerson)(objectClass=inetOrgPerson))")(version 3.0; acl "Ena ++ ble self legalname read"; allow (read, search, compare)(userdn="ldap:///self" ++ );) ++aci: (targetattr="legalName || telephoneNumber")(targetfilter="(objectClass=ns ++ Person)")(version 3.0; acl "Enable user legalname read"; allow (read, search, ++ compare)(groupdn="ldap:///cn=user_private_read,ou=permissions,dc=example,dc= ++ com");) ++aci: (targetattr="uid || description || displayName || loginShell || uidNumber ++ || gidNumber || gecos || homeDirectory || cn || memberOf || mail || legalNam ++ e || telephoneNumber || mobile")(targetfilter="(&(objectClass=nsPerson)(objec ++ tClass=nsAccount))")(version 3.0; acl "Enable user admin create"; allow (writ ++ e, add, delete, read)(groupdn="ldap:///cn=user_admin,ou=permissions,dc=exampl ++ e,dc=com");) ++aci: (targetattr="uid || description || displayName || loginShell || uidNumber ++ || gidNumber || gecos || homeDirectory || cn || memberOf || mail || legalNam ++ e || telephoneNumber || mobile")(targetfilter="(&(objectClass=nsPerson)(objec ++ tClass=nsAccount))")(version 3.0; acl "Enable user modify to change users"; a ++ llow (write, read)(groupdn="ldap:///cn=user_modify,ou=permissions,dc=example, ++ dc=com");) ++aci: (targetattr="userPassword || nsAccountLock || userCertificate || nsSshPub ++ licKey")(targetfilter="(objectClass=nsAccount)")(version 3.0; acl "Enable use ++ r password reset"; allow (write, read)(groupdn="ldap:///cn=user_passwd_reset, ++ ou=permissions,dc=example,dc=com");) ++creatorsName: cn=Directory Manager ++modifiersName: cn=Directory Manager ++createTimestamp: 20200325015543Z ++modifyTimestamp: 20200325015543Z ++nsUniqueId: a2b3322c-6e3b11ea-8de0c78c-83e27eda ++ ++# entry-id: 5 ++dn: ou=permissions,dc=example,dc=com ++objectClass: top ++objectClass: organizationalunit ++ou: permissions ++creatorsName: cn=Directory Manager ++modifiersName: cn=Directory Manager ++createTimestamp: 20200325015543Z ++modifyTimestamp: 20200325015543Z ++nsUniqueId: a2b3322d-6e3b11ea-8de0c78c-83e27eda ++ ++# entry-id: 6 ++dn: ou=services,dc=example,dc=com ++objectClass: top ++objectClass: organizationalunit ++ou: services ++aci: (targetattr="objectClass || description || nsUniqueId || cn || memberOf | ++ | nsAccountLock ")(targetfilter="(objectClass=netscapeServer)")(version 3.0; ++ acl "Enable anyone service account read"; allow (read, search, compare)(userd ++ n="ldap:///anyone");) ++creatorsName: cn=Directory Manager ++modifiersName: cn=Directory Manager ++createTimestamp: 20200325015544Z ++modifyTimestamp: 20200325015544Z ++nsUniqueId: a2b3322e-6e3b11ea-8de0c78c-83e27eda ++ ++# entry-id: 7 ++dn: uid=demo_user,ou=people,dc=example,dc=com ++objectClass: top ++objectClass: nsPerson ++objectClass: nsAccount ++objectClass: nsOrgPerson ++objectClass: posixAccount ++uid: demo_user ++cn: Demo User ++displayName: Demo User ++legalName: Demo User Name ++uidNumber: 99998 ++gidNumber: 99998 ++homeDirectory: /var/empty ++loginShell: /bin/false ++nsAccountLock: true ++creatorsName: cn=Directory Manager ++modifiersName: cn=Directory Manager ++createTimestamp: 20200325015544Z ++modifyTimestamp: 20200325061615Z ++nsUniqueId: a2b3322f-6e3b11ea-8de0c78c-83e27eda ++entryUUID: 973e1bbf-ba9c-45d4-b01b-ff7371fd9008 ++ ++# entry-id: 8 ++dn: cn=demo_group,ou=groups,dc=example,dc=com ++objectClass: top ++objectClass: groupOfNames ++objectClass: posixGroup ++objectClass: nsMemberOf ++cn: demo_group ++gidNumber: 99999 ++creatorsName: cn=Directory Manager ++modifiersName: cn=Directory Manager ++createTimestamp: 20200325015544Z ++modifyTimestamp: 20200325015544Z ++nsUniqueId: a2b33230-6e3b11ea-8de0c78c-83e27eda ++entryUUID: f6df8fe9-6b30-46aa-aa13-f0bf755371e8 ++ ++# entry-id: 9 ++dn: cn=group_admin,ou=permissions,dc=example,dc=com ++objectClass: top ++objectClass: groupOfNames ++objectClass: nsMemberOf ++cn: group_admin ++creatorsName: cn=Directory Manager ++modifiersName: cn=Directory Manager ++createTimestamp: 20200325015545Z ++modifyTimestamp: 20200325015545Z ++nsUniqueId: a2b33231-6e3b11ea-8de0c78c-83e27eda ++ ++# entry-id: 10 ++dn: cn=group_modify,ou=permissions,dc=example,dc=com ++objectClass: top ++objectClass: groupOfNames ++objectClass: nsMemberOf ++cn: group_modify ++creatorsName: cn=Directory Manager ++modifiersName: cn=Directory Manager ++createTimestamp: 20200325015545Z ++modifyTimestamp: 20200325015545Z ++nsUniqueId: a2b33232-6e3b11ea-8de0c78c-83e27eda ++ ++# entry-id: 11 ++dn: cn=user_admin,ou=permissions,dc=example,dc=com ++objectClass: top ++objectClass: groupOfNames ++objectClass: nsMemberOf ++cn: user_admin ++creatorsName: cn=Directory Manager ++modifiersName: cn=Directory Manager ++createTimestamp: 20200325015545Z ++modifyTimestamp: 20200325015545Z ++nsUniqueId: a2b33233-6e3b11ea-8de0c78c-83e27eda ++ ++# entry-id: 12 ++dn: cn=user_modify,ou=permissions,dc=example,dc=com ++objectClass: top ++objectClass: groupOfNames ++objectClass: nsMemberOf ++cn: user_modify ++creatorsName: cn=Directory Manager ++modifiersName: cn=Directory Manager ++createTimestamp: 20200325015546Z ++modifyTimestamp: 20200325015546Z ++nsUniqueId: a2b33234-6e3b11ea-8de0c78c-83e27eda ++ ++# entry-id: 13 ++dn: cn=user_passwd_reset,ou=permissions,dc=example,dc=com ++objectClass: top ++objectClass: groupOfNames ++objectClass: nsMemberOf ++cn: user_passwd_reset ++creatorsName: cn=Directory Manager ++modifiersName: cn=Directory Manager ++createTimestamp: 20200325015546Z ++modifyTimestamp: 20200325015546Z ++nsUniqueId: a2b33235-6e3b11ea-8de0c78c-83e27eda ++ ++# entry-id: 14 ++dn: cn=user_private_read,ou=permissions,dc=example,dc=com ++objectClass: top ++objectClass: groupOfNames ++objectClass: nsMemberOf ++cn: user_private_read ++creatorsName: cn=Directory Manager ++modifiersName: cn=Directory Manager ++createTimestamp: 20200325015547Z ++modifyTimestamp: 20200325015547Z ++nsUniqueId: a2b33236-6e3b11ea-8de0c78c-83e27eda ++ +diff --git a/dirsrvtests/tests/suites/entryuuid/basic_test.py b/dirsrvtests/tests/suites/entryuuid/basic_test.py +new file mode 100644 +index 000000000..beb73701d +--- /dev/null ++++ b/dirsrvtests/tests/suites/entryuuid/basic_test.py +@@ -0,0 +1,226 @@ ++# --- BEGIN COPYRIGHT BLOCK --- ++# Copyright (C) 2020 William Brown ++# All rights reserved. ++# ++# License: GPL (version 3 or any later version). ++# See LICENSE for details. ++# --- END COPYRIGHT BLOCK --- ++ ++import ldap ++import pytest ++import time ++import shutil ++from lib389.idm.user import nsUserAccounts, UserAccounts ++from lib389.idm.account import Accounts ++from lib389.topologies import topology_st as topology ++from lib389.backend import Backends ++from lib389.paths import Paths ++from lib389.utils import ds_is_older ++from lib389._constants import * ++from lib389.plugins import EntryUUIDPlugin ++ ++default_paths = Paths() ++ ++pytestmark = pytest.mark.tier1 ++ ++DATADIR1 = os.path.join(os.path.dirname(__file__), '../../data/entryuuid/') ++IMPORT_UUID_A = "973e1bbf-ba9c-45d4-b01b-ff7371fd9008" ++UUID_BETWEEN = "eeeeeeee-0000-0000-0000-000000000000" ++IMPORT_UUID_B = "f6df8fe9-6b30-46aa-aa13-f0bf755371e8" ++UUID_MIN = "00000000-0000-0000-0000-000000000000" ++UUID_MAX = "ffffffff-ffff-ffff-ffff-ffffffffffff" ++ ++def _entryuuid_import_and_search(topology): ++ # 1 ++ ldif_dir = topology.standalone.get_ldif_dir() ++ target_ldif = os.path.join(ldif_dir, 'localhost-userRoot-2020_03_30_13_14_47.ldif') ++ import_ldif = os.path.join(DATADIR1, 'localhost-userRoot-2020_03_30_13_14_47.ldif') ++ shutil.copyfile(import_ldif, target_ldif) ++ ++ be = Backends(topology.standalone).get('userRoot') ++ task = be.import_ldif([target_ldif]) ++ task.wait() ++ assert(task.is_complete() and task.get_exit_code() == 0) ++ ++ accounts = Accounts(topology.standalone, DEFAULT_SUFFIX) ++ # 2 - positive eq test ++ r2 = accounts.filter("(entryUUID=%s)" % IMPORT_UUID_A) ++ assert(len(r2) == 1) ++ r3 = accounts.filter("(entryuuid=%s)" % IMPORT_UUID_B) ++ assert(len(r3) == 1) ++ # 3 - negative eq test ++ r4 = accounts.filter("(entryuuid=%s)" % UUID_MAX) ++ assert(len(r4) == 0) ++ # 4 - le search ++ r5 = accounts.filter("(entryuuid<=%s)" % UUID_BETWEEN) ++ assert(len(r5) == 1) ++ # 5 - ge search ++ r6 = accounts.filter("(entryuuid>=%s)" % UUID_BETWEEN) ++ assert(len(r6) == 1) ++ # 6 - le 0 search ++ r7 = accounts.filter("(entryuuid<=%s)" % UUID_MIN) ++ assert(len(r7) == 0) ++ # 7 - ge f search ++ r8 = accounts.filter("(entryuuid>=%s)" % UUID_MAX) ++ assert(len(r8) == 0) ++ # 8 - export db ++ task = be.export_ldif() ++ task.wait() ++ assert(task.is_complete() and task.get_exit_code() == 0) ++ ++ ++@pytest.mark.skipif(not default_paths.rust_enabled or ds_is_older('1.4.2.0'), reason="Entryuuid is not available in older versions") ++def test_entryuuid_indexed_import_and_search(topology): ++ """ Test that an ldif of entries containing entryUUID's can be indexed and searched ++ correctly. As https://tools.ietf.org/html/rfc4530 states, the MR's are equality and ++ ordering, so we check these are correct. ++ ++ :id: c98ee6dc-a7ee-4bd4-974d-597ea966dad9 ++ ++ :setup: Standalone instance ++ ++ :steps: ++ 1. Import the db from the ldif ++ 2. EQ search for an entryuuid (match) ++ 3. EQ search for an entryuuid that does not exist ++ 4. LE search for an entryuuid lower (1 res) ++ 5. GE search for an entryuuid greater (1 res) ++ 6. LE for the 0 uuid (0 res) ++ 7. GE for the f uuid (0 res) ++ 8. export the db to ldif ++ ++ :expectedresults: ++ 1. Success ++ 2. 1 match ++ 3. 0 match ++ 4. 1 match ++ 5. 1 match ++ 6. 0 match ++ 7. 0 match ++ 8. success ++ """ ++ # Assert that the index correctly exists. ++ be = Backends(topology.standalone).get('userRoot') ++ indexes = be.get_indexes() ++ indexes.ensure_state(properties={ ++ 'cn': 'entryUUID', ++ 'nsSystemIndex': 'false', ++ 'nsIndexType': ['eq', 'pres'], ++ }) ++ _entryuuid_import_and_search(topology) ++ ++@pytest.mark.skipif(not default_paths.rust_enabled or ds_is_older('1.4.2.0'), reason="Entryuuid is not available in older versions") ++def test_entryuuid_unindexed_import_and_search(topology): ++ """ Test that an ldif of entries containing entryUUID's can be UNindexed searched ++ correctly. As https://tools.ietf.org/html/rfc4530 states, the MR's are equality and ++ ordering, so we check these are correct. ++ ++ :id: b652b54d-f009-464b-b5bd-299a33f97243 ++ ++ :setup: Standalone instance ++ ++ :steps: ++ 1. Import the db from the ldif ++ 2. EQ search for an entryuuid (match) ++ 3. EQ search for an entryuuid that does not exist ++ 4. LE search for an entryuuid lower (1 res) ++ 5. GE search for an entryuuid greater (1 res) ++ 6. LE for the 0 uuid (0 res) ++ 7. GE for the f uuid (0 res) ++ 8. export the db to ldif ++ ++ :expectedresults: ++ 1. Success ++ 2. 1 match ++ 3. 0 match ++ 4. 1 match ++ 5. 1 match ++ 6. 0 match ++ 7. 0 match ++ 8. success ++ """ ++ # Assert that the index does NOT exist for this test. ++ be = Backends(topology.standalone).get('userRoot') ++ indexes = be.get_indexes() ++ try: ++ idx = indexes.get('entryUUID') ++ idx.delete() ++ except ldap.NO_SUCH_OBJECT: ++ # It's already not present, move along, nothing to see here. ++ pass ++ _entryuuid_import_and_search(topology) ++ ++# Test entryUUID generation ++@pytest.mark.skipif(not default_paths.rust_enabled or ds_is_older('1.4.2.0'), reason="Entryuuid is not available in older versions") ++def test_entryuuid_generation_on_add(topology): ++ """ Test that when an entry is added, the entryuuid is added. ++ ++ :id: a7439b0a-dcee-4cd6-b8ef-771476c0b4f6 ++ ++ :setup: Standalone instance ++ ++ :steps: ++ 1. Create a new entry in the db ++ 2. Check it has an entry uuid ++ ++ :expectedresults: ++ 1. Success ++ 2. An entry uuid is present ++ """ ++ # Step one - create a user! ++ account = nsUserAccounts(topology.standalone, DEFAULT_SUFFIX).create_test_user() ++ # Step two - does it have an entryuuid? ++ euuid = account.get_attr_val_utf8('entryUUID') ++ print(euuid) ++ assert(euuid is not None) ++ ++# Test fixup task ++@pytest.mark.skipif(not default_paths.rust_enabled or ds_is_older('1.4.2.0'), reason="Entryuuid is not available in older versions") ++def test_entryuuid_fixup_task(topology): ++ """Test that when an entries without UUID's can have one generated via ++ the fixup process. ++ ++ :id: ad42bba2-ffb2-4c22-a37d-cbe7bcf73d6b ++ ++ :setup: Standalone instance ++ ++ :steps: ++ 1. Disable the entryuuid plugin ++ 2. Create an entry ++ 3. Enable the entryuuid plugin ++ 4. Run the fixup ++ 5. Assert the entryuuid now exists ++ ++ :expectedresults: ++ 1. Success ++ 2. Success ++ 3. Success ++ 4. Success ++ 5. Suddenly EntryUUID! ++ """ ++ # 1. Disable the plugin ++ plug = EntryUUIDPlugin(topology.standalone) ++ plug.disable() ++ topology.standalone.restart() ++ ++ # 2. create the account ++ account = nsUserAccounts(topology.standalone, DEFAULT_SUFFIX).create_test_user(uid=2000) ++ euuid = account.get_attr_val_utf8('entryUUID') ++ assert(euuid is None) ++ ++ # 3. enable the plugin ++ plug.enable() ++ topology.standalone.restart() ++ ++ # 4. run the fix up ++ # For now set the log level to high! ++ topology.standalone.config.loglevel(vals=(ErrorLog.DEFAULT,ErrorLog.TRACE)) ++ task = plug.fixup(DEFAULT_SUFFIX) ++ task.wait() ++ assert(task.is_complete() and task.get_exit_code() == 0) ++ topology.standalone.config.loglevel(vals=(ErrorLog.DEFAULT,)) ++ ++ # 5. Assert the uuid. ++ euuid = account.get_attr_val_utf8('entryUUID') ++ assert(euuid is not None) ++ +diff --git a/ldap/schema/02common.ldif b/ldap/schema/02common.ldif +index 57e6be3b3..3b0ad0a97 100644 +--- a/ldap/schema/02common.ldif ++++ b/ldap/schema/02common.ldif +@@ -11,6 +11,7 @@ + # + # Core schema, highly recommended but not required to start the Directory Server itself. + # ++# + dn: cn=schema + # + # attributes +diff --git a/ldap/schema/03entryuuid.ldif b/ldap/schema/03entryuuid.ldif +new file mode 100644 +index 000000000..cbde981fe +--- /dev/null ++++ b/ldap/schema/03entryuuid.ldif +@@ -0,0 +1,16 @@ ++# ++# BEGIN COPYRIGHT BLOCK ++# Copyright (C) 2020 William Brown ++# All rights reserved. ++# ++# License: GPL (version 3 or any later version). ++# See LICENSE for details. ++# END COPYRIGHT BLOCK ++# ++# Core schema, highly recommended but not required to start the Directory Server itself. ++# ++dn: cn=schema ++# ++# attributes ++# ++attributeTypes: ( 1.3.6.1.1.16.4 NAME 'entryUUID' DESC 'UUID of the entry' EQUALITY UUIDMatch ORDERING UUIDOrderingMatch SYNTAX 1.3.6.1.1.16.1 SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation ) +diff --git a/ldap/servers/slapd/config.c b/ldap/servers/slapd/config.c +index 7e1618e79..bf5476272 100644 +--- a/ldap/servers/slapd/config.c ++++ b/ldap/servers/slapd/config.c +@@ -35,6 +35,10 @@ extern char *slapd_SSL3ciphers; + extern char *localuser; + char *rel2abspath(char *); + ++/* ++ * WARNING - this can only bootstrap PASSWORD and SYNTAX plugins! ++ * see fedse.c instead! ++ */ + static char *bootstrap_plugins[] = { + "dn: cn=PBKDF2_SHA256,cn=Password Storage Schemes,cn=plugins,cn=config\n" + "objectclass: top\n" +@@ -45,6 +49,19 @@ static char *bootstrap_plugins[] = { + "nsslapd-plugintype: pwdstoragescheme\n" + "nsslapd-pluginenabled: on", + ++ "dn: cn=entryuuid_syntax,cn=plugins,cn=config\n" ++ "objectclass: top\n" ++ "objectclass: nsSlapdPlugin\n" ++ "cn: entryuuid_syntax\n" ++ "nsslapd-pluginpath: libentryuuid-syntax-plugin\n" ++ "nsslapd-plugininitfunc: entryuuid_syntax_plugin_init\n" ++ "nsslapd-plugintype: syntax\n" ++ "nsslapd-pluginenabled: on\n" ++ "nsslapd-pluginId: entryuuid_syntax\n" ++ "nsslapd-pluginVersion: none\n" ++ "nsslapd-pluginVendor: 389 Project\n" ++ "nsslapd-pluginDescription: entryuuid_syntax\n", ++ + NULL + }; + +diff --git a/ldap/servers/slapd/entry.c b/ldap/servers/slapd/entry.c +index 7697e2b88..9ae9523e2 100644 +--- a/ldap/servers/slapd/entry.c ++++ b/ldap/servers/slapd/entry.c +@@ -2882,6 +2882,18 @@ slapi_entry_attr_get_bool(const Slapi_Entry *e, const char *type) + return slapi_entry_attr_get_bool_ext(e, type, PR_FALSE); + } + ++const struct slapi_value ** ++slapi_entry_attr_get_valuearray(const Slapi_Entry *e, const char *attrname) ++{ ++ Slapi_Attr *attr; ++ ++ if (slapi_entry_attr_find(e, attrname, &attr) != 0) { ++ return NULL; ++ } ++ ++ return attr->a_present_values.va; ++} ++ + /* + * Extract a single value from an entry (as a string). You do not need + * to free the returned string value. +diff --git a/ldap/servers/slapd/fedse.c b/ldap/servers/slapd/fedse.c +index 3b076eb17..0d645f909 100644 +--- a/ldap/servers/slapd/fedse.c ++++ b/ldap/servers/slapd/fedse.c +@@ -119,6 +119,34 @@ static const char *internal_entries[] = + "cn:SNMP\n" + "nsSNMPEnabled: on\n", + ++#ifdef RUST_ENABLE ++ "dn: cn=entryuuid_syntax,cn=plugins,cn=config\n" ++ "objectclass: top\n" ++ "objectclass: nsSlapdPlugin\n" ++ "cn: entryuuid_syntax\n" ++ "nsslapd-pluginpath: libentryuuid-syntax-plugin\n" ++ "nsslapd-plugininitfunc: entryuuid_syntax_plugin_init\n" ++ "nsslapd-plugintype: syntax\n" ++ "nsslapd-pluginenabled: on\n" ++ "nsslapd-pluginId: entryuuid_syntax\n" ++ "nsslapd-pluginVersion: none\n" ++ "nsslapd-pluginVendor: 389 Project\n" ++ "nsslapd-pluginDescription: entryuuid_syntax\n", ++ ++ "dn: cn=entryuuid,cn=plugins,cn=config\n" ++ "objectclass: top\n" ++ "objectclass: nsSlapdPlugin\n" ++ "cn: entryuuid\n" ++ "nsslapd-pluginpath: libentryuuid-plugin\n" ++ "nsslapd-plugininitfunc: entryuuid_plugin_init\n" ++ "nsslapd-plugintype: betxnpreoperation\n" ++ "nsslapd-pluginenabled: on\n" ++ "nsslapd-pluginId: entryuuid\n" ++ "nsslapd-pluginVersion: none\n" ++ "nsslapd-pluginVendor: 389 Project\n" ++ "nsslapd-pluginDescription: entryuuid\n", ++#endif ++ + "dn: cn=Password Storage Schemes,cn=plugins,cn=config\n" + "objectclass: top\n" + "objectclass: nsContainer\n" +diff --git a/src/Cargo.lock b/src/Cargo.lock +index ce3c7ed27..33d7b8f23 100644 +--- a/src/Cargo.lock ++++ b/src/Cargo.lock +@@ -28,12 +28,9 @@ checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + + [[package]] + name = "base64" +-version = "0.10.1" ++version = "0.13.0" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e" +-dependencies = [ +- "byteorder", +-] ++checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + + [[package]] + name = "bitflags" +@@ -43,9 +40,9 @@ checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + + [[package]] + name = "byteorder" +-version = "1.4.2" ++version = "1.4.3" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "ae44d1a3d5a19df61dd0c8beb138458ac2a53a7ac09eba97d55592540004306b" ++checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + + [[package]] + name = "cbindgen" +@@ -66,15 +63,12 @@ dependencies = [ + + [[package]] + name = "cc" +-version = "1.0.66" ++version = "1.0.67" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48" +- +-[[package]] +-name = "cfg-if" +-version = "0.1.10" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" ++checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd" ++dependencies = [ ++ "jobserver", ++] + + [[package]] + name = "cfg-if" +@@ -97,16 +91,39 @@ dependencies = [ + "vec_map", + ] + ++[[package]] ++name = "entryuuid" ++version = "0.1.0" ++dependencies = [ ++ "cc", ++ "libc", ++ "paste", ++ "slapi_r_plugin", ++ "uuid", ++] ++ ++[[package]] ++name = "entryuuid_syntax" ++version = "0.1.0" ++dependencies = [ ++ "cc", ++ "libc", ++ "paste", ++ "slapi_r_plugin", ++ "uuid", ++] ++ + [[package]] + name = "fernet" +-version = "0.1.3" ++version = "0.1.4" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "e7ac567fd75ce6bc28b68e63b5beaa3ce34f56bafd1122f64f8647c822e38a8b" ++checksum = "93804560e638370a8be6d59ce71ed803e55e230abdbf42598e666b41adda9b1f" + dependencies = [ + "base64", + "byteorder", + "getrandom", + "openssl", ++ "zeroize", + ] + + [[package]] +@@ -126,20 +143,20 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + + [[package]] + name = "getrandom" +-version = "0.1.16" ++version = "0.2.3" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" ++checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" + dependencies = [ +- "cfg-if 1.0.0", ++ "cfg-if", + "libc", + "wasi", + ] + + [[package]] + name = "hermit-abi" +-version = "0.1.17" ++version = "0.1.18" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8" ++checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" + dependencies = [ + "libc", + ] +@@ -150,6 +167,15 @@ version = "0.4.7" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" + ++[[package]] ++name = "jobserver" ++version = "0.1.22" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "972f5ae5d1cb9c6ae417789196c803205313edde988685da5e3aae0827b9e7fd" ++dependencies = [ ++ "libc", ++] ++ + [[package]] + name = "lazy_static" + version = "1.4.0" +@@ -158,9 +184,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + + [[package]] + name = "libc" +-version = "0.2.82" ++version = "0.2.94" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "89203f3fba0a3795506acaad8ebce3c80c0af93f994d5a1d7a0b1eeb23271929" ++checksum = "18794a8ad5b29321f790b55d93dfba91e125cb1a9edbd4f8e3150acc771c1a5e" + + [[package]] + name = "librnsslapd" +@@ -182,32 +208,38 @@ dependencies = [ + + [[package]] + name = "log" +-version = "0.4.11" ++version = "0.4.14" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" ++checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" + dependencies = [ +- "cfg-if 0.1.10", ++ "cfg-if", + ] + ++[[package]] ++name = "once_cell" ++version = "1.7.2" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3" ++ + [[package]] + name = "openssl" +-version = "0.10.32" ++version = "0.10.34" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "038d43985d1ddca7a9900630d8cd031b56e4794eecc2e9ea39dd17aa04399a70" ++checksum = "6d7830286ad6a3973c0f1d9b73738f69c76b739301d0229c4b96501695cbe4c8" + dependencies = [ + "bitflags", +- "cfg-if 1.0.0", ++ "cfg-if", + "foreign-types", +- "lazy_static", + "libc", ++ "once_cell", + "openssl-sys", + ] + + [[package]] + name = "openssl-sys" +-version = "0.9.60" ++version = "0.9.63" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "921fc71883267538946025deffb622905ecad223c28efbfdef9bb59a0175f3e6" ++checksum = "b6b0d6fb7d80f877617dfcb014e605e2b5ab2fb0afdf27935219bb6bd984cb98" + dependencies = [ + "autocfg", + "cc", +@@ -216,6 +248,25 @@ dependencies = [ + "vcpkg", + ] + ++[[package]] ++name = "paste" ++version = "0.1.18" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "45ca20c77d80be666aef2b45486da86238fabe33e38306bd3118fe4af33fa880" ++dependencies = [ ++ "paste-impl", ++ "proc-macro-hack", ++] ++ ++[[package]] ++name = "paste-impl" ++version = "0.1.18" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "d95a7db200b97ef370c8e6de0088252f7e0dfff7d047a28528e47456c0fc98b6" ++dependencies = [ ++ "proc-macro-hack", ++] ++ + [[package]] + name = "pkg-config" + version = "0.3.19" +@@ -228,31 +279,36 @@ version = "0.2.10" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" + ++[[package]] ++name = "proc-macro-hack" ++version = "0.5.19" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" ++ + [[package]] + name = "proc-macro2" +-version = "1.0.24" ++version = "1.0.27" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" ++checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038" + dependencies = [ + "unicode-xid", + ] + + [[package]] + name = "quote" +-version = "1.0.8" ++version = "1.0.9" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df" ++checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" + dependencies = [ + "proc-macro2", + ] + + [[package]] + name = "rand" +-version = "0.7.3" ++version = "0.8.3" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" ++checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e" + dependencies = [ +- "getrandom", + "libc", + "rand_chacha", + "rand_core", +@@ -261,9 +317,9 @@ dependencies = [ + + [[package]] + name = "rand_chacha" +-version = "0.2.2" ++version = "0.3.0" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" ++checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d" + dependencies = [ + "ppv-lite86", + "rand_core", +@@ -271,27 +327,30 @@ dependencies = [ + + [[package]] + name = "rand_core" +-version = "0.5.1" ++version = "0.6.2" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" ++checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7" + dependencies = [ + "getrandom", + ] + + [[package]] + name = "rand_hc" +-version = "0.2.0" ++version = "0.3.0" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" ++checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73" + dependencies = [ + "rand_core", + ] + + [[package]] + name = "redox_syscall" +-version = "0.1.57" ++version = "0.2.8" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" ++checksum = "742739e41cd49414de871ea5e549afb7e2a3ac77b589bcbebe8c82fab37147fc" ++dependencies = [ ++ "bitflags", ++] + + [[package]] + name = "remove_dir_all" +@@ -314,18 +373,18 @@ checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" + + [[package]] + name = "serde" +-version = "1.0.118" ++version = "1.0.126" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "06c64263859d87aa2eb554587e2d23183398d617427327cf2b3d0ed8c69e4800" ++checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03" + dependencies = [ + "serde_derive", + ] + + [[package]] + name = "serde_derive" +-version = "1.0.118" ++version = "1.0.126" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "c84d3526699cd55261af4b941e4e725444df67aa4f9e6a3564f18030d12672df" ++checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43" + dependencies = [ + "proc-macro2", + "quote", +@@ -334,9 +393,9 @@ dependencies = [ + + [[package]] + name = "serde_json" +-version = "1.0.61" ++version = "1.0.64" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "4fceb2595057b6891a4ee808f70054bd2d12f0e97f1cbb78689b59f676df325a" ++checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79" + dependencies = [ + "itoa", + "ryu", +@@ -350,6 +409,16 @@ dependencies = [ + "fernet", + ] + ++[[package]] ++name = "slapi_r_plugin" ++version = "0.1.0" ++dependencies = [ ++ "lazy_static", ++ "libc", ++ "paste", ++ "uuid", ++] ++ + [[package]] + name = "strsim" + version = "0.8.0" +@@ -358,22 +427,34 @@ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + + [[package]] + name = "syn" +-version = "1.0.58" ++version = "1.0.72" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "a1e8cdbefb79a9a5a65e0db8b47b723ee907b7c7f8496c76a1770b5c310bab82" ++dependencies = [ ++ "proc-macro2", ++ "quote", ++ "unicode-xid", ++] ++ ++[[package]] ++name = "synstructure" ++version = "0.12.4" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "cc60a3d73ea6594cd712d830cc1f0390fd71542d8c8cd24e70cc54cdfd5e05d5" ++checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701" + dependencies = [ + "proc-macro2", + "quote", ++ "syn", + "unicode-xid", + ] + + [[package]] + name = "tempfile" +-version = "3.1.0" ++version = "3.2.0" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" ++checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" + dependencies = [ +- "cfg-if 0.1.10", ++ "cfg-if", + "libc", + "rand", + "redox_syscall", +@@ -407,15 +488,24 @@ checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" + + [[package]] + name = "unicode-xid" +-version = "0.2.1" ++version = "0.2.2" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" ++checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" ++ ++[[package]] ++name = "uuid" ++version = "0.8.2" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" ++dependencies = [ ++ "getrandom", ++] + + [[package]] + name = "vcpkg" +-version = "0.2.11" ++version = "0.2.12" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "b00bca6106a5e23f3eee943593759b7fcddb00554332e856d990c893966879fb" ++checksum = "cbdbff6266a24120518560b5dc983096efb98462e51d0d68169895b237be3e5d" + + [[package]] + name = "vec_map" +@@ -425,9 +515,9 @@ checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + + [[package]] + name = "wasi" +-version = "0.9.0+wasi-snapshot-preview1" ++version = "0.10.2+wasi-snapshot-preview1" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" ++checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" + + [[package]] + name = "winapi" +@@ -450,3 +540,24 @@ name = "winapi-x86_64-pc-windows-gnu" + version = "0.4.0" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" ++ ++[[package]] ++name = "zeroize" ++version = "1.3.0" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd" ++dependencies = [ ++ "zeroize_derive", ++] ++ ++[[package]] ++name = "zeroize_derive" ++version = "1.1.0" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "a2c1e130bebaeab2f23886bf9acbaca14b092408c452543c857f66399cd6dab1" ++dependencies = [ ++ "proc-macro2", ++ "quote", ++ "syn", ++ "synstructure", ++] +diff --git a/src/Cargo.toml b/src/Cargo.toml +index f6dac010f..1ad2b21b0 100644 +--- a/src/Cargo.toml ++++ b/src/Cargo.toml +@@ -1,10 +1,13 @@ + + [workspace] + members = [ +- "librslapd", +- "librnsslapd", +- "libsds", +- "slapd", ++ "librslapd", ++ "librnsslapd", ++ "libsds", ++ "slapd", ++ "slapi_r_plugin", ++ "plugins/entryuuid", ++ "plugins/entryuuid_syntax", + ] + + [profile.release] +diff --git a/src/README.md b/src/README.md +new file mode 100644 +index 000000000..e69de29bb +diff --git a/src/lib389/lib389/_constants.py b/src/lib389/lib389/_constants.py +index 52aac0f21..c184c8d4f 100644 +--- a/src/lib389/lib389/_constants.py ++++ b/src/lib389/lib389/_constants.py +@@ -150,6 +150,7 @@ DN_IMPORT_TASK = "cn=import,%s" % DN_TASKS + DN_BACKUP_TASK = "cn=backup,%s" % DN_TASKS + DN_RESTORE_TASK = "cn=restore,%s" % DN_TASKS + DN_MBO_TASK = "cn=memberOf task,%s" % DN_TASKS ++DN_EUUID_TASK = "cn=entryuuid task,%s" % DN_TASKS + DN_TOMB_FIXUP_TASK = "cn=fixup tombstones,%s" % DN_TASKS + DN_FIXUP_LINKED_ATTIBUTES = "cn=fixup linked attributes,%s" % DN_TASKS + DN_AUTOMEMBER_REBUILD_TASK = "cn=automember rebuild membership,%s" % DN_TASKS +diff --git a/src/lib389/lib389/backend.py b/src/lib389/lib389/backend.py +index aab07c028..bcd7b383f 100644 +--- a/src/lib389/lib389/backend.py ++++ b/src/lib389/lib389/backend.py +@@ -765,7 +765,7 @@ class Backend(DSLdapObject): + enc_attr.delete() + break + +- def import_ldif(self, ldifs, chunk_size=None, encrypted=False, gen_uniq_id=False, only_core=False, ++ def import_ldif(self, ldifs, chunk_size=None, encrypted=False, gen_uniq_id=None, only_core=False, + include_suffixes=None, exclude_suffixes=None): + """Do an import of the suffix""" + +diff --git a/src/lib389/lib389/instance/setup.py b/src/lib389/lib389/instance/setup.py +index 530fb367a..ac0fe1a8c 100644 +--- a/src/lib389/lib389/instance/setup.py ++++ b/src/lib389/lib389/instance/setup.py +@@ -34,6 +34,7 @@ from lib389.instance.options import General2Base, Slapd2Base, Backend2Base + from lib389.paths import Paths + from lib389.saslmap import SaslMappings + from lib389.instance.remove import remove_ds_instance ++from lib389.index import Indexes + from lib389.utils import ( + assert_c, + is_a_dn, +@@ -928,6 +929,19 @@ class SetupDs(object): + if slapd['self_sign_cert']: + ds_instance.config.set('nsslapd-security', 'on') + ++ # Before we create any backends, create any extra default indexes that may be ++ # dynamicly provisioned, rather than from template-dse.ldif. Looking at you ++ # entryUUID (requires rust enabled). ++ # ++ # Indexes defaults to default_index_dn ++ indexes = Indexes(ds_instance) ++ if ds_instance.ds_paths.rust_enabled: ++ indexes.create(properties={ ++ 'cn': 'entryUUID', ++ 'nsSystemIndex': 'false', ++ 'nsIndexType': ['eq', 'pres'], ++ }) ++ + # Create the backends as listed + # Load example data if needed. + for backend in backends: +diff --git a/src/lib389/lib389/plugins.py b/src/lib389/lib389/plugins.py +index 16899f6d3..2d88e60bd 100644 +--- a/src/lib389/lib389/plugins.py ++++ b/src/lib389/lib389/plugins.py +@@ -2244,3 +2244,33 @@ class ContentSyncPlugin(Plugin): + + def __init__(self, instance, dn="cn=Content Synchronization,cn=plugins,cn=config"): + super(ContentSyncPlugin, self).__init__(instance, dn) ++ ++ ++class EntryUUIDPlugin(Plugin): ++ """The EntryUUID plugin configuration ++ :param instance: An instance ++ :type instance: lib389.DirSrv ++ :param dn: Entry DN ++ :type dn: str ++ """ ++ def __init__(self, instance, dn="cn=entryuuid,cn=plugins,cn=config"): ++ super(EntryUUIDPlugin, self).__init__(instance, dn) ++ ++ def fixup(self, basedn, _filter=None): ++ """Create an entryuuid fixup task ++ ++ :param basedn: Basedn to fix up ++ :type basedn: str ++ :param _filter: a filter for entries to fix up ++ :type _filter: str ++ ++ :returns: an instance of Task(DSLdapObject) ++ """ ++ ++ task = tasks.EntryUUIDFixupTask(self._instance) ++ task_properties = {'basedn': basedn} ++ if _filter is not None: ++ task_properties['filter'] = _filter ++ task.create(properties=task_properties) ++ ++ return task +diff --git a/src/lib389/lib389/tasks.py b/src/lib389/lib389/tasks.py +index b19e7918d..590c6ee79 100644 +--- a/src/lib389/lib389/tasks.py ++++ b/src/lib389/lib389/tasks.py +@@ -203,6 +203,20 @@ class USNTombstoneCleanupTask(Task): + return super(USNTombstoneCleanupTask, self)._validate(rdn, properties, basedn) + + ++class EntryUUIDFixupTask(Task): ++ """A single instance of memberOf task entry ++ ++ :param instance: An instance ++ :type instance: lib389.DirSrv ++ """ ++ ++ def __init__(self, instance, dn=None): ++ self.cn = 'entryuuid_fixup_' + Task._get_task_date() ++ dn = "cn=" + self.cn + "," + DN_EUUID_TASK ++ super(EntryUUIDFixupTask, self).__init__(instance, dn) ++ self._must_attributes.extend(['basedn']) ++ ++ + class SchemaReloadTask(Task): + """A single instance of schema reload task entry + +diff --git a/src/librnsslapd/build.rs b/src/librnsslapd/build.rs +index 9b953b246..13f6d2e03 100644 +--- a/src/librnsslapd/build.rs ++++ b/src/librnsslapd/build.rs +@@ -3,13 +3,14 @@ extern crate cbindgen; + use std::env; + + fn main() { +- let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); +- let out_dir = env::var("SLAPD_HEADER_DIR").unwrap(); +- +- cbindgen::Builder::new() +- .with_language(cbindgen::Language::C) +- .with_crate(crate_dir) +- .generate() +- .expect("Unable to generate bindings") +- .write_to_file(format!("{}/rust-nsslapd-private.h", out_dir)); ++ if let Ok(crate_dir) = env::var("CARGO_MANIFEST_DIR") { ++ if let Ok(out_dir) = env::var("SLAPD_HEADER_DIR") { ++ cbindgen::Builder::new() ++ .with_language(cbindgen::Language::C) ++ .with_crate(crate_dir) ++ .generate() ++ .expect("Unable to generate bindings") ++ .write_to_file(format!("{}/rust-nsslapd-private.h", out_dir)); ++ } ++ } + } +diff --git a/src/librnsslapd/src/lib.rs b/src/librnsslapd/src/lib.rs +index c5fd2bbaf..dffe4ce1c 100644 +--- a/src/librnsslapd/src/lib.rs ++++ b/src/librnsslapd/src/lib.rs +@@ -4,9 +4,9 @@ + // Remember this is just a c-bindgen stub, all logic should come from slapd! + + extern crate libc; +-use slapd; + use libc::c_char; +-use std::ffi::{CString, CStr}; ++use slapd; ++use std::ffi::{CStr, CString}; + + #[no_mangle] + pub extern "C" fn do_nothing_again_rust() -> usize { +@@ -29,9 +29,7 @@ pub extern "C" fn fernet_generate_token(dn: *const c_char, raw_key: *const c_cha + // We have to move string memory ownership by copying so the system + // allocator has it. + let raw = tok.into_raw(); +- let dup_tok = unsafe { +- libc::strdup(raw) +- }; ++ let dup_tok = unsafe { libc::strdup(raw) }; + unsafe { + CString::from_raw(raw); + }; +@@ -45,7 +43,12 @@ pub extern "C" fn fernet_generate_token(dn: *const c_char, raw_key: *const c_cha + } + + #[no_mangle] +-pub extern "C" fn fernet_verify_token(dn: *const c_char, token: *const c_char, raw_key: *const c_char, ttl: u64) -> bool { ++pub extern "C" fn fernet_verify_token( ++ dn: *const c_char, ++ token: *const c_char, ++ raw_key: *const c_char, ++ ttl: u64, ++) -> bool { + if dn.is_null() || raw_key.is_null() || token.is_null() { + return false; + } +@@ -67,4 +70,3 @@ pub extern "C" fn fernet_verify_token(dn: *const c_char, token: *const c_char, r + Err(_) => false, + } + } +- +diff --git a/src/librslapd/Cargo.toml b/src/librslapd/Cargo.toml +index 1dd715ed2..08309c224 100644 +--- a/src/librslapd/Cargo.toml ++++ b/src/librslapd/Cargo.toml +@@ -12,10 +12,6 @@ path = "src/lib.rs" + name = "rslapd" + crate-type = ["staticlib", "lib"] + +-# [profile.release] +-# panic = "abort" +-# lto = true +- + [dependencies] + slapd = { path = "../slapd" } + libc = "0.2" +diff --git a/src/librslapd/build.rs b/src/librslapd/build.rs +index 4d4c1ce42..84aff156b 100644 +--- a/src/librslapd/build.rs ++++ b/src/librslapd/build.rs +@@ -3,13 +3,14 @@ extern crate cbindgen; + use std::env; + + fn main() { +- let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); +- let out_dir = env::var("SLAPD_HEADER_DIR").unwrap(); +- +- cbindgen::Builder::new() +- .with_language(cbindgen::Language::C) +- .with_crate(crate_dir) +- .generate() +- .expect("Unable to generate bindings") +- .write_to_file(format!("{}/rust-slapi-private.h", out_dir)); ++ if let Ok(crate_dir) = env::var("CARGO_MANIFEST_DIR") { ++ if let Ok(out_dir) = env::var("SLAPD_HEADER_DIR") { ++ cbindgen::Builder::new() ++ .with_language(cbindgen::Language::C) ++ .with_crate(crate_dir) ++ .generate() ++ .expect("Unable to generate bindings") ++ .write_to_file(format!("{}/rust-slapi-private.h", out_dir)); ++ } ++ } + } +diff --git a/src/librslapd/src/lib.rs b/src/librslapd/src/lib.rs +index 9cce193a0..cf283a7ce 100644 +--- a/src/librslapd/src/lib.rs ++++ b/src/librslapd/src/lib.rs +@@ -8,7 +8,7 @@ extern crate libc; + use slapd; + + use libc::c_char; +-use std::ffi::{CString, CStr}; ++use std::ffi::{CStr, CString}; + + #[no_mangle] + pub extern "C" fn do_nothing_rust() -> usize { +@@ -18,9 +18,7 @@ pub extern "C" fn do_nothing_rust() -> usize { + #[no_mangle] + pub extern "C" fn rust_free_string(s: *mut c_char) { + if !s.is_null() { +- let _ = unsafe { +- CString::from_raw(s) +- }; ++ let _ = unsafe { CString::from_raw(s) }; + } + } + +@@ -35,9 +33,7 @@ pub extern "C" fn fernet_generate_new_key() -> *mut c_char { + match res_key { + Ok(key) => { + let raw = key.into_raw(); +- let dup_key = unsafe { +- libc::strdup(raw) +- }; ++ let dup_key = unsafe { libc::strdup(raw) }; + rust_free_string(raw); + dup_key + } +@@ -53,4 +49,3 @@ pub extern "C" fn fernet_validate_key(raw_key: *const c_char) -> bool { + Err(_) => false, + } + } +- +diff --git a/src/libsds/sds/lib.rs b/src/libsds/sds/lib.rs +index aa70c7a8e..9e2973222 100644 +--- a/src/libsds/sds/lib.rs ++++ b/src/libsds/sds/lib.rs +@@ -28,5 +28,3 @@ pub enum sds_result { + /// The list is exhausted, no more elements can be returned. + ListExhausted = 16, + } +- +- +diff --git a/src/libsds/sds/tqueue.rs b/src/libsds/sds/tqueue.rs +index b7042e514..ebe1f4b6c 100644 +--- a/src/libsds/sds/tqueue.rs ++++ b/src/libsds/sds/tqueue.rs +@@ -9,8 +9,8 @@ + #![warn(missing_docs)] + + use super::sds_result; +-use std::sync::Mutex; + use std::collections::LinkedList; ++use std::sync::Mutex; + + // Borrow from libc + #[doc(hidden)] +@@ -75,7 +75,10 @@ impl Drop for TQueue { + /// C compatible wrapper around the TQueue. Given a valid point, a TQueue pointer + /// is allocated on the heap and referenced in retq. free_fn_ptr may be NULL + /// but if it references a function, this will be called during drop of the TQueue. +-pub extern fn sds_tqueue_init(retq: *mut *mut TQueue, free_fn_ptr: Option) -> sds_result { ++pub extern "C" fn sds_tqueue_init( ++ retq: *mut *mut TQueue, ++ free_fn_ptr: Option, ++) -> sds_result { + // This piece of type signature magic is because in rust types that extern C, + // with option has None resolve to null. What this causes is we can wrap + // our fn ptr with Option in rust, but the C side gives us fn ptr or NULL, and +@@ -93,7 +96,7 @@ pub extern fn sds_tqueue_init(retq: *mut *mut TQueue, free_fn_ptr: Option sds_result { ++pub extern "C" fn sds_tqueue_enqueue(q: *const TQueue, elem: *const c_void) -> sds_result { + // Check for null .... + unsafe { (*q).enqueue(elem) }; + sds_result::Success +@@ -103,29 +106,27 @@ pub extern fn sds_tqueue_enqueue(q: *const TQueue, elem: *const c_void) -> sds_r + /// Dequeue from the head of the queue. The result will be placed into elem. + /// if elem is NULL no dequeue is attempted. If there are no more items + /// ListExhausted is returned. +-pub extern fn sds_tqueue_dequeue(q: *const TQueue, elem: *mut *const c_void) -> sds_result { ++pub extern "C" fn sds_tqueue_dequeue(q: *const TQueue, elem: *mut *const c_void) -> sds_result { + if elem.is_null() { + return sds_result::NullPointer; + } + match unsafe { (*q).dequeue() } { + Some(e) => { +- unsafe { *elem = e; }; ++ unsafe { ++ *elem = e; ++ }; + sds_result::Success + } +- None => { +- sds_result::ListExhausted +- } ++ None => sds_result::ListExhausted, + } + } + + #[no_mangle] + /// Free the queue and all remaining elements. After this point it is + /// not safe to access the queue. +-pub extern fn sds_tqueue_destroy(q: *mut TQueue) -> sds_result { ++pub extern "C" fn sds_tqueue_destroy(q: *mut TQueue) -> sds_result { + // This will drop the queue and free it's content + // mem::drop(q); + let _q = unsafe { Box::from_raw(q) }; + sds_result::Success + } +- +- +diff --git a/src/plugins/entryuuid/Cargo.toml b/src/plugins/entryuuid/Cargo.toml +new file mode 100644 +index 000000000..c43d7a771 +--- /dev/null ++++ b/src/plugins/entryuuid/Cargo.toml +@@ -0,0 +1,21 @@ ++[package] ++name = "entryuuid" ++version = "0.1.0" ++authors = ["William Brown "] ++edition = "2018" ++ ++# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html ++ ++[lib] ++path = "src/lib.rs" ++name = "entryuuid" ++crate-type = ["staticlib", "lib"] ++ ++[dependencies] ++libc = "0.2" ++paste = "0.1" ++slapi_r_plugin = { path="../../slapi_r_plugin" } ++uuid = { version = "0.8", features = [ "v4" ] } ++ ++[build-dependencies] ++cc = { version = "1.0", features = ["parallel"] } +diff --git a/src/plugins/entryuuid/src/lib.rs b/src/plugins/entryuuid/src/lib.rs +new file mode 100644 +index 000000000..6b5e8d1bb +--- /dev/null ++++ b/src/plugins/entryuuid/src/lib.rs +@@ -0,0 +1,196 @@ ++#[macro_use] ++extern crate slapi_r_plugin; ++use slapi_r_plugin::prelude::*; ++use std::convert::{TryFrom, TryInto}; ++use std::os::raw::c_char; ++use uuid::Uuid; ++ ++#[derive(Debug)] ++struct FixupData { ++ basedn: Sdn, ++ raw_filter: String, ++} ++ ++struct EntryUuid; ++/* ++ * /---- plugin ident ++ * | /---- Struct name. ++ * V V ++ */ ++slapi_r_plugin_hooks!(entryuuid, EntryUuid); ++ ++/* ++ * /---- plugin ident ++ * | /---- cb ident ++ * | | /---- map function ++ * V V V ++ */ ++slapi_r_search_callback_mapfn!(entryuuid, entryuuid_fixup_cb, entryuuid_fixup_mapfn); ++ ++fn assign_uuid(e: &mut EntryRef) { ++ let sdn = e.get_sdnref(); ++ ++ // We could consider making these lazy static. ++ let config_sdn = Sdn::try_from("cn=config").expect("Invalid static dn"); ++ let schema_sdn = Sdn::try_from("cn=schema").expect("Invalid static dn"); ++ ++ if sdn.is_below_suffix(&*config_sdn) || sdn.is_below_suffix(&*schema_sdn) { ++ // We don't need to assign to these suffixes. ++ log_error!( ++ ErrorLevel::Trace, ++ "assign_uuid -> not assigning to {:?} as part of system suffix", ++ sdn.to_dn_string() ++ ); ++ return; ++ } ++ ++ // Generate a new Uuid. ++ let u: Uuid = Uuid::new_v4(); ++ log_error!( ++ ErrorLevel::Trace, ++ "assign_uuid -> assigning {:?} to dn {}", ++ u, ++ sdn.to_dn_string() ++ ); ++ ++ let uuid_value = Value::from(&u); ++ ++ // Add it to the entry ++ e.add_value("entryUUID", &uuid_value); ++} ++ ++impl SlapiPlugin3 for EntryUuid { ++ // Indicate we have pre add ++ fn has_betxn_pre_add() -> bool { ++ true ++ } ++ ++ fn betxn_pre_add(pb: &mut PblockRef) -> Result<(), PluginError> { ++ log_error!(ErrorLevel::Trace, "betxn_pre_add"); ++ ++ let mut e = pb.get_op_add_entryref().map_err(|_| PluginError::Pblock)?; ++ assign_uuid(&mut e); ++ ++ Ok(()) ++ } ++ ++ fn has_task_handler() -> Option<&'static str> { ++ Some("entryuuid task") ++ } ++ ++ type TaskData = FixupData; ++ ++ fn task_validate(e: &EntryRef) -> Result { ++ // Does the entry have what we need? ++ let basedn: Sdn = match e.get_attr("basedn") { ++ Some(values) => values ++ .first() ++ .ok_or_else(|| { ++ log_error!( ++ ErrorLevel::Trace, ++ "task_validate basedn error -> empty value array?" ++ ); ++ LDAPError::Operation ++ })? ++ .as_ref() ++ .try_into() ++ .map_err(|e| { ++ log_error!(ErrorLevel::Trace, "task_validate basedn error -> {:?}", e); ++ LDAPError::Operation ++ })?, ++ None => return Err(LDAPError::ObjectClassViolation), ++ }; ++ ++ let raw_filter: String = match e.get_attr("filter") { ++ Some(values) => values ++ .first() ++ .ok_or_else(|| { ++ log_error!( ++ ErrorLevel::Trace, ++ "task_validate filter error -> empty value array?" ++ ); ++ LDAPError::Operation ++ })? ++ .as_ref() ++ .try_into() ++ .map_err(|e| { ++ log_error!(ErrorLevel::Trace, "task_validate filter error -> {:?}", e); ++ LDAPError::Operation ++ })?, ++ None => { ++ // Give a default filter. ++ "(objectClass=*)".to_string() ++ } ++ }; ++ ++ // Error if the first filter is empty? ++ ++ // Now, to make things faster, we wrap the filter in a exclude term. ++ let raw_filter = format!("(&{}(!(entryuuid=*)))", raw_filter); ++ ++ Ok(FixupData { basedn, raw_filter }) ++ } ++ ++ fn task_be_dn_hint(data: &Self::TaskData) -> Option { ++ Some(data.basedn.clone()) ++ } ++ ++ fn task_handler(_task: &Task, data: Self::TaskData) -> Result { ++ log_error!( ++ ErrorLevel::Trace, ++ "task_handler -> start thread with -> {:?}", ++ data ++ ); ++ ++ let search = Search::new_map_entry( ++ &(*data.basedn), ++ SearchScope::Subtree, ++ &data.raw_filter, ++ plugin_id(), ++ &(), ++ entryuuid_fixup_cb, ++ ) ++ .map_err(|e| { ++ log_error!( ++ ErrorLevel::Error, ++ "task_handler -> Unable to construct search -> {:?}", ++ e ++ ); ++ e ++ })?; ++ ++ match search.execute() { ++ Ok(_) => { ++ log_error!(ErrorLevel::Info, "task_handler -> fixup complete, success!"); ++ Ok(data) ++ } ++ Err(e) => { ++ // log, and return ++ log_error!( ++ ErrorLevel::Error, ++ "task_handler -> fixup complete, failed -> {:?}", ++ e ++ ); ++ Err(PluginError::GenericFailure) ++ } ++ } ++ } ++ ++ fn start(_pb: &mut PblockRef) -> Result<(), PluginError> { ++ log_error!(ErrorLevel::Trace, "plugin start"); ++ Ok(()) ++ } ++ ++ fn close(_pb: &mut PblockRef) -> Result<(), PluginError> { ++ log_error!(ErrorLevel::Trace, "plugin close"); ++ Ok(()) ++ } ++} ++ ++pub fn entryuuid_fixup_mapfn(mut e: EntryRef, _data: &()) -> Result<(), PluginError> { ++ assign_uuid(&mut e); ++ Ok(()) ++} ++ ++#[cfg(test)] ++mod tests {} +diff --git a/src/plugins/entryuuid_syntax/Cargo.toml b/src/plugins/entryuuid_syntax/Cargo.toml +new file mode 100644 +index 000000000..f7d3d64c9 +--- /dev/null ++++ b/src/plugins/entryuuid_syntax/Cargo.toml +@@ -0,0 +1,21 @@ ++[package] ++name = "entryuuid_syntax" ++version = "0.1.0" ++authors = ["William Brown "] ++edition = "2018" ++ ++# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html ++ ++[lib] ++path = "src/lib.rs" ++name = "entryuuid_syntax" ++crate-type = ["staticlib", "lib"] ++ ++[dependencies] ++libc = "0.2" ++paste = "0.1" ++slapi_r_plugin = { path="../../slapi_r_plugin" } ++uuid = { version = "0.8", features = [ "v4" ] } ++ ++[build-dependencies] ++cc = { version = "1.0", features = ["parallel"] } +diff --git a/src/plugins/entryuuid_syntax/src/lib.rs b/src/plugins/entryuuid_syntax/src/lib.rs +new file mode 100644 +index 000000000..0a4b89f16 +--- /dev/null ++++ b/src/plugins/entryuuid_syntax/src/lib.rs +@@ -0,0 +1,145 @@ ++#[macro_use] ++extern crate slapi_r_plugin; ++use slapi_r_plugin::prelude::*; ++use std::cmp::Ordering; ++use std::convert::TryInto; ++use uuid::Uuid; ++ ++struct EntryUuidSyntax; ++ ++// https://tools.ietf.org/html/rfc4530 ++ ++slapi_r_syntax_plugin_hooks!(entryuuid_syntax, EntryUuidSyntax); ++ ++impl SlapiSyntaxPlugin1 for EntryUuidSyntax { ++ fn attr_oid() -> &'static str { ++ "1.3.6.1.1.16.1" ++ } ++ ++ fn attr_compat_oids() -> Vec<&'static str> { ++ Vec::new() ++ } ++ ++ fn attr_supported_names() -> Vec<&'static str> { ++ vec!["1.3.6.1.1.16.1", "UUID"] ++ } ++ ++ fn syntax_validate(bval: &BerValRef) -> Result<(), PluginError> { ++ let r: Result = bval.try_into(); ++ r.map(|_| ()) ++ } ++ ++ fn eq_mr_oid() -> &'static str { ++ "1.3.6.1.1.16.2" ++ } ++ ++ fn eq_mr_name() -> &'static str { ++ "UUIDMatch" ++ } ++ ++ fn eq_mr_desc() -> &'static str { ++ "UUIDMatch matching rule." ++ } ++ ++ fn eq_mr_supported_names() -> Vec<&'static str> { ++ vec!["1.3.6.1.1.16.2", "uuidMatch", "UUIDMatch"] ++ } ++ ++ fn filter_ava_eq( ++ _pb: &mut PblockRef, ++ bval_filter: &BerValRef, ++ vals: &ValueArrayRef, ++ ) -> Result { ++ let u = match bval_filter.try_into() { ++ Ok(u) => u, ++ Err(_e) => return Ok(false), ++ }; ++ ++ let r = vals.iter().fold(false, |acc, va| { ++ if acc { ++ acc ++ } else { ++ // is u in va? ++ log_error!(ErrorLevel::Trace, "filter_ava_eq debug -> {:?}", va); ++ let res: Result = (&*va).try_into(); ++ match res { ++ Ok(vu) => vu == u, ++ Err(_) => acc, ++ } ++ } ++ }); ++ log_error!(ErrorLevel::Trace, "filter_ava_eq result -> {:?}", r); ++ Ok(r) ++ } ++ ++ fn eq_mr_filter_values2keys( ++ _pb: &mut PblockRef, ++ vals: &ValueArrayRef, ++ ) -> Result { ++ vals.iter() ++ .map(|va| { ++ let u: Uuid = (&*va).try_into()?; ++ Ok(Value::from(&u)) ++ }) ++ .collect() ++ } ++} ++ ++impl SlapiSubMr for EntryUuidSyntax {} ++ ++impl SlapiOrdMr for EntryUuidSyntax { ++ fn ord_mr_oid() -> Option<&'static str> { ++ Some("1.3.6.1.1.16.3") ++ } ++ ++ fn ord_mr_name() -> &'static str { ++ "UUIDOrderingMatch" ++ } ++ ++ fn ord_mr_desc() -> &'static str { ++ "UUIDMatch matching rule." ++ } ++ ++ fn ord_mr_supported_names() -> Vec<&'static str> { ++ vec!["1.3.6.1.1.16.3", "uuidOrderingMatch", "UUIDOrderingMatch"] ++ } ++ ++ fn filter_ava_ord( ++ _pb: &mut PblockRef, ++ bval_filter: &BerValRef, ++ vals: &ValueArrayRef, ++ ) -> Result, PluginError> { ++ let u: Uuid = match bval_filter.try_into() { ++ Ok(u) => u, ++ Err(_e) => return Ok(None), ++ }; ++ ++ let r = vals.iter().fold(None, |acc, va| { ++ if acc.is_some() { ++ acc ++ } else { ++ // is u in va? ++ log_error!(ErrorLevel::Trace, "filter_ava_ord debug -> {:?}", va); ++ let res: Result = (&*va).try_into(); ++ match res { ++ Ok(vu) => { ++ // 1.partial_cmp(2) => ordering::less ++ vu.partial_cmp(&u) ++ } ++ Err(_) => acc, ++ } ++ } ++ }); ++ log_error!(ErrorLevel::Trace, "filter_ava_ord result -> {:?}", r); ++ Ok(r) ++ } ++ ++ fn filter_compare(a: &BerValRef, b: &BerValRef) -> Ordering { ++ let ua: Uuid = a.try_into().expect("An invalid value a was given!"); ++ let ub: Uuid = b.try_into().expect("An invalid value b was given!"); ++ ua.cmp(&ub) ++ } ++} ++ ++#[cfg(test)] ++mod tests {} +diff --git a/src/slapd/src/error.rs b/src/slapd/src/error.rs +index 06ddb27b4..6f4d782ee 100644 +--- a/src/slapd/src/error.rs ++++ b/src/slapd/src/error.rs +@@ -1,8 +1,6 @@ +- + pub enum SlapdError { + // This occurs when a string contains an inner null byte + // that cstring can't handle. + CStringInvalidError, + FernetInvalidKey, + } +- +diff --git a/src/slapd/src/fernet.rs b/src/slapd/src/fernet.rs +index fcbd873f8..1a3251fd9 100644 +--- a/src/slapd/src/fernet.rs ++++ b/src/slapd/src/fernet.rs +@@ -1,39 +1,30 @@ + // Routines for managing fernet encryption + +-use std::ffi::{CString, CStr}; +-use fernet::Fernet; + use crate::error::SlapdError; ++use fernet::Fernet; ++use std::ffi::{CStr, CString}; + + pub fn generate_new_key() -> Result { + let k = Fernet::generate_key(); +- CString::new(k) +- .map_err(|_| { +- SlapdError::CStringInvalidError +- }) ++ CString::new(k).map_err(|_| SlapdError::CStringInvalidError) + } + + pub fn new(c_str_key: &CStr) -> Result { +- let str_key = c_str_key.to_str() ++ let str_key = c_str_key ++ .to_str() + .map_err(|_| SlapdError::CStringInvalidError)?; +- Fernet::new(str_key) +- .ok_or(SlapdError::FernetInvalidKey) ++ Fernet::new(str_key).ok_or(SlapdError::FernetInvalidKey) + } + + pub fn encrypt(fernet: &Fernet, dn: &CStr) -> Result { + let tok = fernet.encrypt(dn.to_bytes()); +- CString::new(tok) +- .map_err(|_| { +- SlapdError::CStringInvalidError +- }) ++ CString::new(tok).map_err(|_| SlapdError::CStringInvalidError) + } + + pub fn decrypt(fernet: &Fernet, tok: &CStr, ttl: u64) -> Result { +- let s = tok.to_str() +- .map_err(|_| SlapdError::CStringInvalidError)?; +- let r: Vec = fernet.decrypt_with_ttl(s, ttl) ++ let s = tok.to_str().map_err(|_| SlapdError::CStringInvalidError)?; ++ let r: Vec = fernet ++ .decrypt_with_ttl(s, ttl) + .map_err(|_| SlapdError::FernetInvalidKey)?; +- CString::new(r) +- .map_err(|_| SlapdError::CStringInvalidError) ++ CString::new(r).map_err(|_| SlapdError::CStringInvalidError) + } +- +- +diff --git a/src/slapd/src/lib.rs b/src/slapd/src/lib.rs +index 5b1f20368..79f1600c2 100644 +--- a/src/slapd/src/lib.rs ++++ b/src/slapd/src/lib.rs +@@ -1,5 +1,2 @@ +- + pub mod error; + pub mod fernet; +- +- +diff --git a/src/slapi_r_plugin/Cargo.toml b/src/slapi_r_plugin/Cargo.toml +new file mode 100644 +index 000000000..c7958671a +--- /dev/null ++++ b/src/slapi_r_plugin/Cargo.toml +@@ -0,0 +1,19 @@ ++[package] ++name = "slapi_r_plugin" ++version = "0.1.0" ++authors = ["William Brown "] ++edition = "2018" ++build = "build.rs" ++ ++# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html ++ ++[lib] ++path = "src/lib.rs" ++name = "slapi_r_plugin" ++crate-type = ["staticlib", "lib"] ++ ++[dependencies] ++libc = "0.2" ++paste = "0.1" ++lazy_static = "1.4" ++uuid = { version = "0.8", features = [ "v4" ] } +diff --git a/src/slapi_r_plugin/README.md b/src/slapi_r_plugin/README.md +new file mode 100644 +index 000000000..af9743ec9 +--- /dev/null ++++ b/src/slapi_r_plugin/README.md +@@ -0,0 +1,216 @@ ++ ++# Slapi R(ust) Plugin Bindings ++ ++If you are here, you are probably interested in the Rust bindings that allow plugins to be written ++in Rust for the 389 Directory Server project. If you are, you should use `cargo doc --workspace --no-deps` ++in `src`, as this contains the material you want for implementing safe plugins. ++ ++This readme is intended for developers of the bindings that enable those plugins to work. ++ ++As such it likely requires that you have an understanding both of C and ++the [Rust Nomicon](https://doc.rust-lang.org/nomicon/index.html) ++ ++> **WARNING** This place is not a place of honor ... no highly esteemed deed is commemorated here ++> ... nothing valued is here. What is here is dangerous and repulsive to us. This message is a ++> warning about danger. ++ ++This document will not detail the specifics of unsafe or the invariants you must adhere to for rust ++to work with C. ++ ++If you still want to see more about the plugin bindings, go on ... ++ ++## The Challenge ++ ++Rust is a memory safe language - that means you may not dereference pointers or alter or interact ++with uninitialised memory. There are whole classes of problems that this resolves, but it means ++that Rust is opiniated about how it interacts with memory. ++ ++C is an unsafe language - there are undefined behaviours all through out the specification, memory ++can be interacted with without bounds which leads to many kinds of issues ranging from crashes, ++silent data corruption, to code execution and explotation. ++ ++While it would be nice to rewrite everything from C to Rust, this is a large task - instead we need ++a way to allow Rust and C to interact. ++ ++## The Goal ++ ++To be able to define, a pure Rust, 100% safe (in rust terms) plugin for 389 Directory Server that ++can perform useful tasks. ++ ++## The 389 Directory Server Plugin API ++ ++The 389-ds plugin system works by reading an ldap entry from cn=config, that directs to a shared ++library. That shared library path is dlopened and an init symbol read and activated. At that ++point the plugin is able to call-back into 389-ds to provide registration of function handlers for ++various tasks that the plugin may wish to perform at defined points in a operations execution. ++ ++During the execution of a plugin callback, the context of the environment is passed through a ++parameter block (pblock). This pblock has a set of apis for accessing it's content, which may ++or may not be defined based on the execution state of the server. ++ ++Common plugin tasks involve the transformation of entries during write operation paths to provide ++extra attributes to the entry or generation of other entries. Values in entries are represented by ++internal structures that may or may not have sorting of content. ++ ++Already at this point it can be seen there is a lot of surface area to access. For clarity in ++our trivial example here we have required: ++ ++* Pblock ++* Entry ++* ValueSet ++* Value ++* Sdn ++* Result Codes ++ ++We need to be able to interact with all of these - and more - to make useful plugins. ++ ++## Structure of the Rust Plugin bindings. ++ ++As a result, there are a number of items we must be able to implement: ++ ++* Creation of the plugin function callback points ++* Transformation of C pointer types into Rust structures that can be interacted with. ++* Ability to have Rust interact with structures to achieve side effects in the C server ++* Mapping of errors that C can understand ++* Make all of it safe. ++ ++In order to design this, it's useful to see what a plugin from Rust should look like - by designing ++what the plugin should look like, we make the bindings that are preferable and ergonomic to rust ++rather than compromising on quality and developer experience. ++ ++Here is a minimal example of a plugin - it may not compile or be complete, it serves as an ++example. ++ ++``` ++#[macro_use] ++extern crate slapi_r_plugin; ++use slapi_r_plugin::prelude::*; ++ ++struct NewPlugin; ++ ++slapi_r_plugin_hooks!(plugin_name, NewPlugin); ++ ++impl SlapiPlugin3 for NewPlugin { ++ fn start(_pb: &mut PblockRef) -> Result<(), PluginError> { ++ log_error!(ErrorLevel::Trace, "plugin start"); ++ Ok(()) ++ } ++ ++ fn close(_pb: &mut PblockRef) -> Result<(), PluginError> { ++ log_error!(ErrorLevel::Trace, "plugin close"); ++ Ok(()) ++ } ++ ++ fn has_betxn_pre_add() -> bool { ++ true ++ } ++ ++ fn betxn_pre_add(pb: &mut PblockRef) -> Result<(), PluginError> { ++ let mut e = pb.get_op_add_entryref().map_err(|_| PluginError::Pblock)?; ++ let sdn = e.get_sdnref(); ++ ++ log_error!(ErrorLevel::Trace, "betxn_pre_add -> {:?}", sdn); ++ Ok(()) ++ } ++} ++``` ++ ++Important details - there is no unsafe, we use rust native error handling and functions, there ++is no indication of memory management, we are defined by a trait, error logging uses native ++formatting. There are probably other details too - I'll leave it as an exercise for the reader ++to play Where's Wally and find them all. ++ ++With the end goal in mind, we can begin to look at the construction of the plugin system, and ++the design choices that were made. ++ ++## The Plugin Trait ++ ++A significant choice was the use of a trait to define the possible plugin function operations ++for rust implementors. This allows the compiler to guarantee that a plugin *will* have all ++associated functions. ++ ++> Traits are synonomous with java interfaces, defining methods you "promise" to implement, unlike ++> object orientation with a class hierarchy. ++ ++Now, you may notice that not all members of the trait are implemented. This is due to a feature ++of rust known as default trait impls. This allows the trait origin (src/plugin.rs) to provide ++template versions of these functions. If you "overwrite" them, your implementation is used. Unlike ++OO, you may not inherit or call the default function. ++ ++If a default is not provided you *must* implement that function to be considered valid. Today (20200422) ++this only applies to `start` and `close`. ++ ++The default implementations all return "false" to the presence of callbacks, and if they are used, ++they will always return an error. ++ ++## Interface generation ++ ++While it is nice to have this Rust interface for plugins, C is unable to call it (Rust uses a different ++stack calling syntax to C, as well as symbol mangaling). To expose these, we must provide `extern C` ++functions, where any function that requires a static symbol must be marked as no_mangle. ++ ++Rather than ask all plugin authors to do this, we can use the rust macro system to generate these ++interfaces at compile time. This is the reason for this line: ++ ++``` ++slapi_r_plugin_hooks!(plugin_name, NewPlugin); ++``` ++ ++This macro is defined in src/macros.rs, and is "the bridge" from C to Rust. Given a plugin name ++and a struct of the trait SlapiPlugin3, this macro is able to generate all needed C compatible ++functions. Based on the calls to `has_`, the generated functions are registered to the pblock ++that is provided. ++ ++When a call back triggers, the function landing point is called. This then wraps all the pointer ++types from C into Rust structs, and then dispatches to the struct instance. ++ ++When the struct function returns, the result is unpacked and turned into C compatible result codes - ++in some cases, the result codes are sanitised due to quirks in the C ds api - `[<$mod_ident _plugin_mr_filter_ava>]` ++is an excellent example of this, where Rust returns are `true`/`false`, which would normally ++be FFI safe to convert to 1/0 respectively, but 389-ds expects the inverse in this case, where ++0 is true and all other values are false. To present a sane api to rust, the macro layer does this ++(mind bending) transformation for us. ++ ++## C Ptr Wrapper types ++ ++This is likely the major, and important detail of the plugin api. By wrapping these C ptrs with ++Rust types, we can create types that perform as rust expects, and adheres to the invariants required, ++while providing safe - and useful interfaces to users. ++ ++It's important to understand how Rust manages memory both on the stack and the heap - Please see ++[the Rust Book](https://doc.rust-lang.org/book/ch04-00-understanding-ownership.html) for more. ++ ++As a result, this means that we must express in code, assertions about the proper ownership of memory ++and who is responsible for it (unlike C, where it can be hard to determine who or what is responsible ++for freeing some value.) Failure to handle this correctly, can and will lead to crashes, leaks or ++*hand waving* magical failures that are eXtReMeLy FuN to debug. ++ ++### Reference Types ++ ++There are a number of types, such as `SdnRef`, which have a suffix of `*Ref`. These types represent ++values whos content is owned by the C server - that is, it is the responsibility of 389-ds to free ++the content of the Pointer once it has been used. A majority of values that are provided to the ++function callback points fall into this class. ++ ++### Owned Types ++ ++These types contain a pointer from the C server, but it is the responsibility of the Rust library ++to indicate when that pointer and it's content should be disposed of. This is generally handled ++by the `drop` trait, which is executed ... well, when an item is dropped. ++ ++### Dispatch from the wrapper to C ++ ++When a rust function against a wrapper is called, the type internally accesses it Ref type and ++uses the ptr to dispatch into the C server. Any required invariants are upheld, and results are ++mapped as required to match what rust callers expect. ++ ++As a result, this involves horrendous amounts of unsafe, and a detailed analysis of both the DS C ++api, what it expects, and the Rust nomicon to ensure you maintain all the invariants. ++ ++## Conclusion ++ ++Providing a bridge between C and Rust is challenging - but achievable - the result is plugins that ++are clean, safe, efficent. ++ ++ ++ +diff --git a/src/slapi_r_plugin/build.rs b/src/slapi_r_plugin/build.rs +new file mode 100644 +index 000000000..29bbd52d4 +--- /dev/null ++++ b/src/slapi_r_plugin/build.rs +@@ -0,0 +1,8 @@ ++use std::env; ++ ++fn main() { ++ if let Ok(lib_dir) = env::var("SLAPD_DYLIB_DIR") { ++ println!("cargo:rustc-link-lib=dylib=slapd"); ++ println!("cargo:rustc-link-search=native={}", lib_dir); ++ } ++} +diff --git a/src/slapi_r_plugin/src/backend.rs b/src/slapi_r_plugin/src/backend.rs +new file mode 100644 +index 000000000..f308295aa +--- /dev/null ++++ b/src/slapi_r_plugin/src/backend.rs +@@ -0,0 +1,71 @@ ++use crate::dn::SdnRef; ++use crate::pblock::Pblock; ++// use std::ops::Deref; ++ ++extern "C" { ++ fn slapi_back_transaction_begin(pb: *const libc::c_void) -> i32; ++ fn slapi_back_transaction_commit(pb: *const libc::c_void); ++ fn slapi_back_transaction_abort(pb: *const libc::c_void); ++ fn slapi_be_select_exact(sdn: *const libc::c_void) -> *const libc::c_void; ++} ++ ++pub struct BackendRef { ++ raw_be: *const libc::c_void, ++} ++ ++impl BackendRef { ++ pub fn new(dn: &SdnRef) -> Result { ++ let raw_be = unsafe { slapi_be_select_exact(dn.as_ptr()) }; ++ if raw_be.is_null() { ++ Err(()) ++ } else { ++ Ok(BackendRef { raw_be }) ++ } ++ } ++ ++ pub(crate) fn as_ptr(&self) -> *const libc::c_void { ++ self.raw_be ++ } ++ ++ pub fn begin_txn(self) -> Result { ++ let mut pb = Pblock::new(); ++ if pb.set_op_backend(&self) != 0 { ++ return Err(()); ++ } ++ let rc = unsafe { slapi_back_transaction_begin(pb.as_ptr()) }; ++ if rc != 0 { ++ Err(()) ++ } else { ++ Ok(BackendRefTxn { ++ pb, ++ be: self, ++ committed: false, ++ }) ++ } ++ } ++} ++ ++pub struct BackendRefTxn { ++ pb: Pblock, ++ be: BackendRef, ++ committed: bool, ++} ++ ++impl BackendRefTxn { ++ pub fn commit(mut self) { ++ self.committed = true; ++ unsafe { ++ slapi_back_transaction_commit(self.pb.as_ptr()); ++ } ++ } ++} ++ ++impl Drop for BackendRefTxn { ++ fn drop(&mut self) { ++ if self.committed == false { ++ unsafe { ++ slapi_back_transaction_abort(self.pb.as_ptr()); ++ } ++ } ++ } ++} +diff --git a/src/slapi_r_plugin/src/ber.rs b/src/slapi_r_plugin/src/ber.rs +new file mode 100644 +index 000000000..a501fd642 +--- /dev/null ++++ b/src/slapi_r_plugin/src/ber.rs +@@ -0,0 +1,90 @@ ++use crate::log::{log_error, ErrorLevel}; ++use libc; ++use std::ffi::CString; ++// use std::ptr; ++use std::slice; ++ ++use std::convert::TryFrom; ++use uuid::Uuid; ++ ++use crate::error::PluginError; ++ ++#[repr(C)] ++pub(crate) struct ol_berval { ++ pub len: usize, ++ pub data: *const u8, ++} ++ ++#[derive(Debug)] ++pub struct BerValRef { ++ pub(crate) raw_berval: *const ol_berval, ++} ++ ++impl BerValRef { ++ pub fn new(raw_berval: *const libc::c_void) -> Self { ++ // so we retype this ++ let raw_berval = raw_berval as *const ol_berval; ++ BerValRef { raw_berval } ++ } ++ ++ pub(crate) fn into_cstring(&self) -> Option { ++ // Cstring does not need a trailing null, so if we have one, ignore it. ++ let l: usize = unsafe { (*self.raw_berval).len }; ++ let d_slice = unsafe { slice::from_raw_parts((*self.raw_berval).data, l) }; ++ CString::new(d_slice) ++ .or_else(|e| { ++ // Try it again, but with one byte less to trim a potential trailing null that ++ // could have been allocated, and ensure it has at least 1 byte of good data ++ // remaining. ++ if l > 1 { ++ let d_slice = unsafe { slice::from_raw_parts((*self.raw_berval).data, l - 1) }; ++ CString::new(d_slice) ++ } else { ++ Err(e) ++ } ++ }) ++ .map_err(|_| { ++ log_error!( ++ ErrorLevel::Trace, ++ "invalid ber parse attempt, may contain a null byte? -> {:?}", ++ self ++ ); ++ () ++ }) ++ .ok() ++ } ++ ++ pub fn into_string(&self) -> Option { ++ // Convert a Some to a rust string. ++ self.into_cstring().and_then(|v| { ++ v.into_string() ++ .map_err(|_| { ++ log_error!( ++ ErrorLevel::Trace, ++ "failed to convert cstring to string -> {:?}", ++ self ++ ); ++ () ++ }) ++ .ok() ++ }) ++ } ++} ++ ++impl TryFrom<&BerValRef> for Uuid { ++ type Error = PluginError; ++ ++ fn try_from(value: &BerValRef) -> Result { ++ let val_string = value.into_string().ok_or(PluginError::BervalString)?; ++ ++ Uuid::parse_str(val_string.as_str()) ++ .map(|r| { ++ log_error!(ErrorLevel::Trace, "valid uuid -> {:?}", r); ++ r ++ }) ++ .map_err(|_e| { ++ log_error!(ErrorLevel::Plugin, "Invalid uuid"); ++ PluginError::InvalidSyntax ++ }) ++ } ++} +diff --git a/src/slapi_r_plugin/src/constants.rs b/src/slapi_r_plugin/src/constants.rs +new file mode 100644 +index 000000000..cf76ccbdb +--- /dev/null ++++ b/src/slapi_r_plugin/src/constants.rs +@@ -0,0 +1,203 @@ ++use crate::error::RPluginError; ++use std::convert::TryFrom; ++use std::os::raw::c_char; ++ ++pub const LDAP_SUCCESS: i32 = 0; ++pub const PLUGIN_DEFAULT_PRECEDENCE: i32 = 50; ++ ++#[repr(i32)] ++/// The set of possible function handles we can register via the pblock. These ++/// values correspond to slapi-plugin.h. ++pub enum PluginFnType { ++ /// SLAPI_PLUGIN_DESTROY_FN ++ Destroy = 11, ++ /// SLAPI_PLUGIN_CLOSE_FN ++ Close = 210, ++ /// SLAPI_PLUGIN_START_FN ++ Start = 212, ++ /// SLAPI_PLUGIN_PRE_BIND_FN ++ PreBind = 401, ++ /// SLAPI_PLUGIN_PRE_UNBIND_FN ++ PreUnbind = 402, ++ /// SLAPI_PLUGIN_PRE_SEARCH_FN ++ PreSearch = 403, ++ /// SLAPI_PLUGIN_PRE_COMPARE_FN ++ PreCompare = 404, ++ /// SLAPI_PLUGIN_PRE_MODIFY_FN ++ PreModify = 405, ++ /// SLAPI_PLUGIN_PRE_MODRDN_FN ++ PreModRDN = 406, ++ /// SLAPI_PLUGIN_PRE_ADD_FN ++ PreAdd = 407, ++ /// SLAPI_PLUGIN_PRE_DELETE_FN ++ PreDelete = 408, ++ /// SLAPI_PLUGIN_PRE_ABANDON_FN ++ PreAbandon = 409, ++ /// SLAPI_PLUGIN_PRE_ENTRY_FN ++ PreEntry = 410, ++ /// SLAPI_PLUGIN_PRE_REFERRAL_FN ++ PreReferal = 411, ++ /// SLAPI_PLUGIN_PRE_RESULT_FN ++ PreResult = 412, ++ /// SLAPI_PLUGIN_PRE_EXTOP_FN ++ PreExtop = 413, ++ /// SLAPI_PLUGIN_BE_PRE_ADD_FN ++ BeTxnPreAdd = 460, ++ /// SLAPI_PLUGIN_BE_TXN_PRE_MODIFY_FN ++ BeTxnPreModify = 461, ++ /// SLAPI_PLUGIN_BE_TXN_PRE_MODRDN_FN ++ BeTxnPreModRDN = 462, ++ /// SLAPI_PLUGIN_BE_TXN_PRE_DELETE_FN ++ BeTxnPreDelete = 463, ++ /// SLAPI_PLUGIN_BE_TXN_PRE_DELETE_TOMBSTONE_FN ++ BeTxnPreDeleteTombstone = 464, ++ /// SLAPI_PLUGIN_POST_SEARCH_FN ++ PostSearch = 503, ++ /// SLAPI_PLUGIN_BE_POST_ADD_FN ++ BeTxnPostAdd = 560, ++ /// SLAPI_PLUGIN_BE_POST_MODIFY_FN ++ BeTxnPostModify = 561, ++ /// SLAPI_PLUGIN_BE_POST_MODRDN_FN ++ BeTxnPostModRDN = 562, ++ /// SLAPI_PLUGIN_BE_POST_DELETE_FN ++ BeTxnPostDelete = 563, ++ ++ /// SLAPI_PLUGIN_MR_FILTER_CREATE_FN ++ MRFilterCreate = 600, ++ /// SLAPI_PLUGIN_MR_INDEXER_CREATE_FN ++ MRIndexerCreate = 601, ++ /// SLAPI_PLUGIN_MR_FILTER_AVA ++ MRFilterAva = 618, ++ /// SLAPI_PLUGIN_MR_FILTER_SUB ++ MRFilterSub = 619, ++ /// SLAPI_PLUGIN_MR_VALUES2KEYS ++ MRValuesToKeys = 620, ++ /// SLAPI_PLUGIN_MR_ASSERTION2KEYS_AVA ++ MRAssertionToKeysAva = 621, ++ /// SLAPI_PLUGIN_MR_ASSERTION2KEYS_SUB ++ MRAssertionToKeysSub = 622, ++ /// SLAPI_PLUGIN_MR_COMPARE ++ MRCompare = 625, ++ /// SLAPI_PLUGIN_MR_NORMALIZE ++ MRNormalize = 626, ++ ++ /// SLAPI_PLUGIN_SYNTAX_FILTER_AVA ++ SyntaxFilterAva = 700, ++ /// SLAPI_PLUGIN_SYNTAX_FILTER_SUB ++ SyntaxFilterSub = 701, ++ /// SLAPI_PLUGIN_SYNTAX_VALUES2KEYS ++ SyntaxValuesToKeys = 702, ++ /// SLAPI_PLUGIN_SYNTAX_ASSERTION2KEYS_AVA ++ SyntaxAssertion2KeysAva = 703, ++ /// SLAPI_PLUGIN_SYNTAX_ASSERTION2KEYS_SUB ++ SyntaxAssertion2KeysSub = 704, ++ /// SLAPI_PLUGIN_SYNTAX_FLAGS ++ SyntaxFlags = 707, ++ /// SLAPI_PLUGIN_SYNTAX_COMPARE ++ SyntaxCompare = 708, ++ /// SLAPI_PLUGIN_SYNTAX_VALIDATE ++ SyntaxValidate = 710, ++ /// SLAPI_PLUGIN_SYNTAX_NORMALIZE ++ SyntaxNormalize = 711, ++} ++ ++static SV01: [u8; 3] = [b'0', b'1', b'\0']; ++static SV02: [u8; 3] = [b'0', b'2', b'\0']; ++static SV03: [u8; 3] = [b'0', b'3', b'\0']; ++ ++/// Corresponding plugin versions ++pub enum PluginVersion { ++ /// SLAPI_PLUGIN_VERSION_01 ++ V01, ++ /// SLAPI_PLUGIN_VERSION_02 ++ V02, ++ /// SLAPI_PLUGIN_VERSION_03 ++ V03, ++} ++ ++impl PluginVersion { ++ pub fn to_char_ptr(&self) -> *const c_char { ++ match self { ++ PluginVersion::V01 => &SV01 as *const _ as *const c_char, ++ PluginVersion::V02 => &SV02 as *const _ as *const c_char, ++ PluginVersion::V03 => &SV03 as *const _ as *const c_char, ++ } ++ } ++} ++ ++static SMATCHINGRULE: [u8; 13] = [ ++ b'm', b'a', b't', b'c', b'h', b'i', b'n', b'g', b'r', b'u', b'l', b'e', b'\0', ++]; ++ ++pub enum PluginType { ++ MatchingRule, ++} ++ ++impl PluginType { ++ pub fn to_char_ptr(&self) -> *const c_char { ++ match self { ++ PluginType::MatchingRule => &SMATCHINGRULE as *const _ as *const c_char, ++ } ++ } ++} ++ ++#[repr(i32)] ++/// data types that we can get or retrieve from the pblock. This is only ++/// used internally. ++pub(crate) enum PblockType { ++ /// SLAPI_PLUGIN_PRIVATE ++ _PrivateData = 4, ++ /// SLAPI_PLUGIN_VERSION ++ Version = 8, ++ /// SLAPI_PLUGIN_DESCRIPTION ++ _Description = 12, ++ /// SLAPI_PLUGIN_IDENTITY ++ Identity = 13, ++ /// SLAPI_PLUGIN_INTOP_RESULT ++ OpResult = 15, ++ /// SLAPI_ADD_ENTRY ++ AddEntry = 60, ++ /// SLAPI_BACKEND ++ Backend = 130, ++ /// SLAPI_PLUGIN_MR_NAMES ++ MRNames = 624, ++ /// SLAPI_PLUGIN_SYNTAX_NAMES ++ SyntaxNames = 705, ++ /// SLAPI_PLUGIN_SYNTAX_OID ++ SyntaxOid = 706, ++} ++ ++/// See ./ldap/include/ldaprot.h ++#[derive(PartialEq)] ++pub enum FilterType { ++ And = 0xa0, ++ Or = 0xa1, ++ Not = 0xa2, ++ Equality = 0xa3, ++ Substring = 0xa4, ++ Ge = 0xa5, ++ Le = 0xa6, ++ Present = 0x87, ++ Approx = 0xa8, ++ Extended = 0xa9, ++} ++ ++impl TryFrom for FilterType { ++ type Error = RPluginError; ++ ++ fn try_from(value: i32) -> Result { ++ match value { ++ 0xa0 => Ok(FilterType::And), ++ 0xa1 => Ok(FilterType::Or), ++ 0xa2 => Ok(FilterType::Not), ++ 0xa3 => Ok(FilterType::Equality), ++ 0xa4 => Ok(FilterType::Substring), ++ 0xa5 => Ok(FilterType::Ge), ++ 0xa6 => Ok(FilterType::Le), ++ 0x87 => Ok(FilterType::Present), ++ 0xa8 => Ok(FilterType::Approx), ++ 0xa9 => Ok(FilterType::Extended), ++ _ => Err(RPluginError::FilterType), ++ } ++ } ++} +diff --git a/src/slapi_r_plugin/src/dn.rs b/src/slapi_r_plugin/src/dn.rs +new file mode 100644 +index 000000000..5f8a65743 +--- /dev/null ++++ b/src/slapi_r_plugin/src/dn.rs +@@ -0,0 +1,108 @@ ++use std::convert::TryFrom; ++use std::ffi::{CStr, CString}; ++use std::ops::Deref; ++use std::os::raw::c_char; ++ ++extern "C" { ++ fn slapi_sdn_get_dn(sdn: *const libc::c_void) -> *const c_char; ++ fn slapi_sdn_new_dn_byval(dn: *const c_char) -> *const libc::c_void; ++ fn slapi_sdn_issuffix(sdn: *const libc::c_void, suffix_sdn: *const libc::c_void) -> i32; ++ fn slapi_sdn_free(sdn: *const *const libc::c_void); ++ fn slapi_sdn_dup(sdn: *const libc::c_void) -> *const libc::c_void; ++} ++ ++#[derive(Debug)] ++pub struct SdnRef { ++ raw_sdn: *const libc::c_void, ++} ++ ++#[derive(Debug)] ++pub struct NdnRef { ++ raw_ndn: *const c_char, ++} ++ ++#[derive(Debug)] ++pub struct Sdn { ++ value: SdnRef, ++} ++ ++unsafe impl Send for Sdn {} ++ ++impl From<&CStr> for Sdn { ++ fn from(value: &CStr) -> Self { ++ Sdn { ++ value: SdnRef { ++ raw_sdn: unsafe { slapi_sdn_new_dn_byval(value.as_ptr()) }, ++ }, ++ } ++ } ++} ++ ++impl TryFrom<&str> for Sdn { ++ type Error = (); ++ ++ fn try_from(value: &str) -> Result { ++ let cstr = CString::new(value).map_err(|_| ())?; ++ Ok(Self::from(cstr.as_c_str())) ++ } ++} ++ ++impl Clone for Sdn { ++ fn clone(&self) -> Self { ++ let raw_sdn = unsafe { slapi_sdn_dup(self.value.raw_sdn) }; ++ Sdn { ++ value: SdnRef { raw_sdn }, ++ } ++ } ++} ++ ++impl Drop for Sdn { ++ fn drop(&mut self) { ++ unsafe { slapi_sdn_free(&self.value.raw_sdn as *const *const libc::c_void) } ++ } ++} ++ ++impl Deref for Sdn { ++ type Target = SdnRef; ++ ++ fn deref(&self) -> &Self::Target { ++ &self.value ++ } ++} ++ ++impl SdnRef { ++ pub fn new(raw_sdn: *const libc::c_void) -> Self { ++ SdnRef { raw_sdn } ++ } ++ ++ /// This is unsafe, as you need to ensure that the SdnRef associated lives at ++ /// least as long as the NdnRef, else this may cause a use-after-free. ++ pub unsafe fn as_ndnref(&self) -> NdnRef { ++ let raw_ndn = slapi_sdn_get_dn(self.raw_sdn); ++ NdnRef { raw_ndn } ++ } ++ ++ pub fn to_dn_string(&self) -> String { ++ let dn_raw = unsafe { slapi_sdn_get_dn(self.raw_sdn) }; ++ let dn_cstr = unsafe { CStr::from_ptr(dn_raw) }; ++ dn_cstr.to_string_lossy().to_string() ++ } ++ ++ pub(crate) fn as_ptr(&self) -> *const libc::c_void { ++ self.raw_sdn ++ } ++ ++ pub fn is_below_suffix(&self, other: &SdnRef) -> bool { ++ if unsafe { slapi_sdn_issuffix(self.raw_sdn, other.raw_sdn) } == 0 { ++ false ++ } else { ++ true ++ } ++ } ++} ++ ++impl NdnRef { ++ pub(crate) fn as_ptr(&self) -> *const c_char { ++ self.raw_ndn ++ } ++} +diff --git a/src/slapi_r_plugin/src/entry.rs b/src/slapi_r_plugin/src/entry.rs +new file mode 100644 +index 000000000..034efe692 +--- /dev/null ++++ b/src/slapi_r_plugin/src/entry.rs +@@ -0,0 +1,92 @@ ++use crate::dn::SdnRef; ++use crate::value::{slapi_value, ValueArrayRef, ValueRef}; ++use std::ffi::CString; ++use std::os::raw::c_char; ++ ++extern "C" { ++ fn slapi_entry_get_sdn(e: *const libc::c_void) -> *const libc::c_void; ++ fn slapi_entry_add_value( ++ e: *const libc::c_void, ++ a: *const c_char, ++ v: *const slapi_value, ++ ) -> i32; ++ fn slapi_entry_attr_get_valuearray( ++ e: *const libc::c_void, ++ a: *const c_char, ++ ) -> *const *const slapi_value; ++} ++ ++pub struct EntryRef { ++ raw_e: *const libc::c_void, ++} ++ ++/* ++pub struct Entry { ++ value: EntryRef, ++} ++ ++impl Drop for Entry { ++ fn drop(&mut self) { ++ () ++ } ++} ++ ++impl Deref for Entry { ++ type Target = EntryRef; ++ ++ fn deref(&self) -> &Self::Target { ++ &self.value ++ } ++} ++ ++impl Entry { ++ // Forget about this value, and get a pointer back suitable for providing to directory ++ // server to take ownership. ++ pub unsafe fn forget(self) -> *mut libc::c_void { ++ unimplemented!(); ++ } ++} ++*/ ++ ++impl EntryRef { ++ pub fn new(raw_e: *const libc::c_void) -> Self { ++ EntryRef { raw_e } ++ } ++ ++ // get the sdn ++ pub fn get_sdnref(&self) -> SdnRef { ++ let sdn_ptr = unsafe { slapi_entry_get_sdn(self.raw_e) }; ++ SdnRef::new(sdn_ptr) ++ } ++ ++ pub fn get_attr(&self, name: &str) -> Option { ++ let cname = CString::new(name).expect("invalid attr name"); ++ let va = unsafe { slapi_entry_attr_get_valuearray(self.raw_e, cname.as_ptr()) }; ++ ++ if va.is_null() { ++ None ++ } else { ++ Some(ValueArrayRef::new(va as *const libc::c_void)) ++ } ++ } ++ ++ pub fn add_value(&mut self, a: &str, v: &ValueRef) { ++ // turn the attr to a c string. ++ // TODO FIX ++ let attr_name = CString::new(a).expect("Invalid attribute name"); ++ // Get the raw ptr. ++ let raw_value_ref = unsafe { v.as_ptr() }; ++ // We ignore the return because it always returns 0. ++ let _ = unsafe { ++ // By default, this clones. ++ slapi_entry_add_value(self.raw_e, attr_name.as_ptr(), raw_value_ref) ++ }; ++ } ++ ++ /* ++ pub fn replace_value(&mut self, a: &str, v: &ValueRef) { ++ // slapi_entry_attr_replace(e, SLAPI_ATTR_ENTRYUSN, new_bvals); ++ unimplemented!(); ++ } ++ */ ++} +diff --git a/src/slapi_r_plugin/src/error.rs b/src/slapi_r_plugin/src/error.rs +new file mode 100644 +index 000000000..91c81cd26 +--- /dev/null ++++ b/src/slapi_r_plugin/src/error.rs +@@ -0,0 +1,61 @@ ++// use std::convert::TryFrom; ++ ++#[derive(Debug)] ++#[repr(i32)] ++pub enum RPluginError { ++ Unknown = 500, ++ Unimplemented = 501, ++ FilterType = 502, ++} ++ ++#[derive(Debug)] ++#[repr(i32)] ++pub enum PluginError { ++ GenericFailure = -1, ++ Unknown = 1000, ++ Unimplemented = 1001, ++ Pblock = 1002, ++ BervalString = 1003, ++ InvalidSyntax = 1004, ++ InvalidFilter = 1005, ++ TxnFailure = 1006, ++} ++ ++#[derive(Debug)] ++#[repr(i32)] ++pub enum LDAPError { ++ Success = 0, ++ Operation = 1, ++ ObjectClassViolation = 65, ++ Other = 80, ++ Unknown = 999, ++} ++ ++impl From for LDAPError { ++ fn from(value: i32) -> Self { ++ match value { ++ 0 => LDAPError::Success, ++ 1 => LDAPError::Operation, ++ 65 => LDAPError::ObjectClassViolation, ++ 80 => LDAPError::Other, ++ _ => LDAPError::Unknown, ++ } ++ } ++} ++ ++// if we make debug impl, we can use this. ++// errmsg = ldap_err2string(result); ++ ++#[derive(Debug)] ++#[repr(i32)] ++pub enum DseCallbackStatus { ++ DoNotApply = 0, ++ Ok = 1, ++ Error = -1, ++} ++ ++#[derive(Debug)] ++pub enum LoggingError { ++ Unknown, ++ CString(String), ++} +diff --git a/src/slapi_r_plugin/src/init.c b/src/slapi_r_plugin/src/init.c +new file mode 100644 +index 000000000..86d1235b8 +--- /dev/null ++++ b/src/slapi_r_plugin/src/init.c +@@ -0,0 +1,8 @@ ++ ++#include ++ ++int32_t ++do_nothing_really_well_abcdef() { ++ return 0; ++} ++ +diff --git a/src/slapi_r_plugin/src/lib.rs b/src/slapi_r_plugin/src/lib.rs +new file mode 100644 +index 000000000..d7fc22e52 +--- /dev/null ++++ b/src/slapi_r_plugin/src/lib.rs +@@ -0,0 +1,36 @@ ++// extern crate lazy_static; ++ ++#[macro_use] ++pub mod macros; ++pub mod backend; ++pub mod ber; ++mod constants; ++pub mod dn; ++pub mod entry; ++pub mod error; ++pub mod log; ++pub mod pblock; ++pub mod plugin; ++pub mod search; ++pub mod syntax_plugin; ++pub mod task; ++pub mod value; ++ ++pub mod prelude { ++ pub use crate::backend::{BackendRef, BackendRefTxn}; ++ pub use crate::ber::BerValRef; ++ pub use crate::constants::{FilterType, PluginFnType, PluginType, PluginVersion, LDAP_SUCCESS}; ++ pub use crate::dn::{Sdn, SdnRef}; ++ pub use crate::entry::EntryRef; ++ pub use crate::error::{DseCallbackStatus, LDAPError, PluginError, RPluginError}; ++ pub use crate::log::{log_error, ErrorLevel}; ++ pub use crate::pblock::{Pblock, PblockRef}; ++ pub use crate::plugin::{register_plugin_ext, PluginIdRef, SlapiPlugin3}; ++ pub use crate::search::{Search, SearchScope}; ++ pub use crate::syntax_plugin::{ ++ matchingrule_register, name_to_leaking_char, names_to_leaking_char_array, SlapiOrdMr, ++ SlapiSubMr, SlapiSyntaxPlugin1, ++ }; ++ pub use crate::task::{task_register_handler_fn, task_unregister_handler_fn, Task, TaskRef}; ++ pub use crate::value::{Value, ValueArray, ValueArrayRef, ValueRef}; ++} +diff --git a/src/slapi_r_plugin/src/log.rs b/src/slapi_r_plugin/src/log.rs +new file mode 100644 +index 000000000..f686ecd1a +--- /dev/null ++++ b/src/slapi_r_plugin/src/log.rs +@@ -0,0 +1,87 @@ ++use std::ffi::CString; ++use std::os::raw::c_char; ++ ++use crate::constants; ++use crate::error::LoggingError; ++ ++extern "C" { ++ fn slapi_log_error(level: i32, system: *const c_char, message: *const c_char) -> i32; ++} ++ ++pub fn log_error( ++ level: ErrorLevel, ++ subsystem: String, ++ message: String, ++) -> Result<(), LoggingError> { ++ let c_subsystem = CString::new(subsystem) ++ .map_err(|e| LoggingError::CString(format!("failed to convert subsystem -> {:?}", e)))?; ++ let c_message = CString::new(message) ++ .map_err(|e| LoggingError::CString(format!("failed to convert message -> {:?}", e)))?; ++ ++ match unsafe { slapi_log_error(level as i32, c_subsystem.as_ptr(), c_message.as_ptr()) } { ++ constants::LDAP_SUCCESS => Ok(()), ++ _ => Err(LoggingError::Unknown), ++ } ++} ++ ++#[repr(i32)] ++#[derive(Debug)] ++/// This is a safe rust representation of the values from slapi-plugin.h ++/// such as SLAPI_LOG_FATAL, SLAPI_LOG_TRACE, SLAPI_LOG_ ... These vaulues ++/// must matche their counter parts in slapi-plugin.h ++pub enum ErrorLevel { ++ /// Always log messages at this level. Soon to go away, see EMERG, ALERT, CRIT, ERR, WARNING, NOTICE, INFO, DEBUG ++ Fatal = 0, ++ /// Log detailed messages. ++ Trace = 1, ++ /// Log packet tracing. ++ Packets = 2, ++ /// Log argument tracing. ++ Args = 3, ++ /// Log connection tracking. ++ Conns = 4, ++ /// Log BER parsing. ++ Ber = 5, ++ /// Log filter processing. ++ Filter = 6, ++ /// Log configuration processing. ++ Config = 7, ++ /// Log access controls ++ Acl = 8, ++ /// Log .... ??? ++ Shell = 9, ++ /// Log .... ??? ++ Parse = 10, ++ /// Log .... ??? ++ House = 11, ++ /// Log detailed replication information. ++ Repl = 12, ++ /// Log cache management. ++ Cache = 13, ++ /// Log detailed plugin operations. ++ Plugin = 14, ++ /// Log .... ??? ++ Timing = 15, ++ /// Log backend infomation. ++ BackLDBM = 16, ++ /// Log ACL processing. ++ AclSummary = 17, ++ /// Log nuncstans processing. ++ NuncStansDONOTUSE = 18, ++ /// Emergency messages. Server is bursting into flame. ++ Emerg = 19, ++ /// Important alerts, server may explode soon. ++ Alert = 20, ++ /// Critical messages, but the server isn't going to explode. Admin should intervene. ++ Crit = 21, ++ /// Error has occured, but we can keep going. Could indicate misconfiguration. ++ Error = 22, ++ /// Warning about an issue that isn't very important. Good to resolve though. ++ Warning = 23, ++ /// Inform the admin of something that they should know about, IE server is running now. ++ Notice = 24, ++ /// Informational messages that are nice to know. ++ Info = 25, ++ /// Debugging information from the server. ++ Debug = 26, ++} +diff --git a/src/slapi_r_plugin/src/macros.rs b/src/slapi_r_plugin/src/macros.rs +new file mode 100644 +index 000000000..030449632 +--- /dev/null ++++ b/src/slapi_r_plugin/src/macros.rs +@@ -0,0 +1,835 @@ ++#[macro_export] ++macro_rules! log_error { ++ ($level:expr, $($arg:tt)*) => ({ ++ use std::fmt; ++ match log_error( ++ $level, ++ format!("{}:{}", file!(), line!()), ++ format!("{}\n", fmt::format(format_args!($($arg)*))) ++ ) { ++ Ok(_) => {}, ++ Err(e) => { ++ eprintln!("A logging error occured {}, {} -> {:?}", file!(), line!(), e); ++ } ++ }; ++ }) ++} ++ ++#[macro_export] ++macro_rules! slapi_r_plugin_hooks { ++ ($mod_ident:ident, $hooks_ident:ident) => ( ++ paste::item! { ++ use libc; ++ ++ static mut PLUGINID: *const libc::c_void = std::ptr::null(); ++ ++ pub(crate) fn plugin_id() -> PluginIdRef { ++ PluginIdRef { ++ raw_pid: unsafe { PLUGINID } ++ } ++ } ++ ++ #[no_mangle] ++ pub extern "C" fn [<$mod_ident _plugin_init>](raw_pb: *const libc::c_void) -> i32 { ++ let mut pb = PblockRef::new(raw_pb); ++ log_error!(ErrorLevel::Trace, "it's alive!\n"); ++ ++ match pb.set_plugin_version(PluginVersion::V03) { ++ 0 => {}, ++ e => return e, ++ }; ++ ++ // Setup the plugin id. ++ unsafe { ++ PLUGINID = pb.get_plugin_identity(); ++ } ++ ++ if $hooks_ident::has_betxn_pre_modify() { ++ match pb.register_betxn_pre_modify_fn([<$mod_ident _plugin_betxn_pre_modify>]) { ++ 0 => {}, ++ e => return e, ++ }; ++ } ++ ++ if $hooks_ident::has_betxn_pre_add() { ++ match pb.register_betxn_pre_add_fn([<$mod_ident _plugin_betxn_pre_add>]) { ++ 0 => {}, ++ e => return e, ++ }; ++ } ++ ++ // set the start fn ++ match pb.register_start_fn([<$mod_ident _plugin_start>]) { ++ 0 => {}, ++ e => return e, ++ }; ++ ++ // set the close fn ++ match pb.register_close_fn([<$mod_ident _plugin_close>]) { ++ 0 => {}, ++ e => return e, ++ }; ++ ++ 0 ++ } ++ ++ pub extern "C" fn [<$mod_ident _plugin_start>](raw_pb: *const libc::c_void) -> i32 { ++ let mut pb = PblockRef::new(raw_pb); ++ ++ if let Some(task_ident) = $hooks_ident::has_task_handler() { ++ match task_register_handler_fn(task_ident, [<$mod_ident _plugin_task_handler>], &mut pb) { ++ 0 => {}, ++ e => return e, ++ }; ++ }; ++ ++ match $hooks_ident::start(&mut pb) { ++ Ok(()) => { ++ 0 ++ } ++ Err(e) => { ++ log_error!(ErrorLevel::Error, "-> {:?}", e); ++ 1 ++ } ++ } ++ } ++ ++ pub extern "C" fn [<$mod_ident _plugin_close>](raw_pb: *const libc::c_void) -> i32 { ++ let mut pb = PblockRef::new(raw_pb); ++ ++ if let Some(task_ident) = $hooks_ident::has_task_handler() { ++ match task_unregister_handler_fn(task_ident, [<$mod_ident _plugin_task_handler>]) { ++ 0 => {}, ++ e => return e, ++ }; ++ }; ++ ++ match $hooks_ident::close(&mut pb) { ++ Ok(()) => { ++ 0 ++ } ++ Err(e) => { ++ log_error!(ErrorLevel::Error, "-> {:?}", e); ++ 1 ++ } ++ } ++ } ++ ++ pub extern "C" fn [<$mod_ident _plugin_betxn_pre_modify>](raw_pb: *const libc::c_void) -> i32 { ++ let mut pb = PblockRef::new(raw_pb); ++ match $hooks_ident::betxn_pre_modify(&mut pb) { ++ Ok(()) => { ++ 0 ++ } ++ Err(e) => { ++ log_error!(ErrorLevel::Error, "-> {:?}", e); ++ 1 ++ } ++ } ++ } ++ ++ pub extern "C" fn [<$mod_ident _plugin_betxn_pre_add>](raw_pb: *const libc::c_void) -> i32 { ++ let mut pb = PblockRef::new(raw_pb); ++ match $hooks_ident::betxn_pre_add(&mut pb) { ++ Ok(()) => { ++ 0 ++ } ++ Err(e) => { ++ log_error!(ErrorLevel::Error, "-> {:?}", e); ++ 1 ++ } ++ } ++ } ++ ++ pub extern "C" fn [<$mod_ident _plugin_task_handler>]( ++ raw_pb: *const libc::c_void, ++ raw_e_before: *const libc::c_void, ++ _raw_e_after: *const libc::c_void, ++ raw_returncode: *mut i32, ++ _raw_returntext: *mut c_char, ++ raw_arg: *const libc::c_void, ++ ) -> i32 { ++ let mut pb = PblockRef::new(raw_pb); ++ ++ let e_before = EntryRef::new(raw_e_before); ++ // let e_after = EntryRef::new(raw_e_after); ++ ++ let task_data = match $hooks_ident::task_validate( ++ &e_before ++ ) { ++ Ok(data) => data, ++ Err(retcode) => { ++ unsafe { *raw_returncode = retcode as i32 }; ++ return DseCallbackStatus::Error as i32 ++ } ++ }; ++ ++ let mut task = Task::new(&e_before, raw_arg); ++ task.register_destructor_fn([<$mod_ident _plugin_task_destructor>]); ++ ++ // Setup the task thread and then run it. Remember, because Rust is ++ // smarter about memory, the move statement here moves the task wrapper and ++ // task_data to the thread, so they drop on thread close. No need for a ++ // destructor beyond blocking on the thread to complete. ++ std::thread::spawn(move || { ++ log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_task_thread => begin")); ++ // Indicate the task is begun ++ task.begin(); ++ // Start a txn ++ let be: Option = match $hooks_ident::task_be_dn_hint(&task_data) ++ .map(|be_dn| { ++ BackendRef::new(&be_dn) ++ }) ++ .transpose() { ++ Ok(v) => v, ++ Err(_) => { ++ log_error!(ErrorLevel::Error, concat!(stringify!($mod_ident), "_plugin_task_thread => task error -> selected dn does not exist")); ++ task.error(PluginError::TxnFailure as i32); ++ return; ++ } ++ }; ++ let be_txn: Option = match be { ++ Some(b) => { ++ match b.begin_txn() { ++ Ok(txn) => Some(txn), ++ Err(_) => { ++ log_error!(ErrorLevel::Error, concat!(stringify!($mod_ident), "_plugin_task_thread => task error -> unable to begin txn")); ++ task.error(PluginError::TxnFailure as i32); ++ return; ++ } ++ } ++ } ++ None => None, ++ }; ++ ++ // Abort or commit the txn here. ++ match $hooks_ident::task_handler(&mut task, task_data) { ++ Ok(_data) => { ++ match be_txn { ++ Some(be_txn) => be_txn.commit(), ++ None => {} ++ }; ++ // These will set the status, and guarantee the drop ++ task.success(); ++ } ++ Err(e) => { ++ log_error!(ErrorLevel::Error, "{}_plugin_task_thread => task error -> {:?}", stringify!($mod_ident), e); ++ // These will set the status, and guarantee the drop ++ task.error(e as i32); ++ // On drop, be_txn implicitly aborts. ++ } ++ }; ++ ++ log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_task_thread <= complete")); ++ }); ++ ++ // Indicate that the thread started just fine. ++ unsafe { *raw_returncode = LDAP_SUCCESS }; ++ DseCallbackStatus::Ok as i32 ++ } ++ ++ pub extern "C" fn [<$mod_ident _plugin_task_destructor>]( ++ raw_task: *const libc::c_void, ++ ) { ++ // Simply block until the task refcount drops to 0. ++ let task = TaskRef::new(raw_task); ++ task.block(); ++ } ++ ++ } // end paste ++ ) ++} // end macro ++ ++#[macro_export] ++macro_rules! slapi_r_syntax_plugin_hooks { ++ ( ++ $mod_ident:ident, ++ $hooks_ident:ident ++ ) => ( ++ paste::item! { ++ use libc; ++ use std::convert::TryFrom; ++ ++ #[no_mangle] ++ pub extern "C" fn [<$mod_ident _plugin_init>](raw_pb: *const libc::c_void) -> i32 { ++ let mut pb = PblockRef::new(raw_pb); ++ log_error!(ErrorLevel::Trace, "slapi_r_syntax_plugin_hooks => begin"); ++ // Setup our plugin ++ match pb.set_plugin_version(PluginVersion::V01) { ++ 0 => {}, ++ e => return e, ++ }; ++ ++ // Setup the names/oids that this plugin provides syntaxes for. ++ ++ let name_ptr = unsafe { names_to_leaking_char_array(&$hooks_ident::attr_supported_names()) }; ++ match pb.register_syntax_names(name_ptr) { ++ 0 => {}, ++ e => return e, ++ }; ++ ++ let name_ptr = unsafe { name_to_leaking_char($hooks_ident::attr_oid()) }; ++ match pb.register_syntax_oid(name_ptr) { ++ 0 => {}, ++ e => return e, ++ }; ++ ++ match pb.register_syntax_validate_fn([<$mod_ident _plugin_syntax_validate>]) { ++ 0 => {}, ++ e => return e, ++ }; ++ ++ // Now setup the MR's ++ match register_plugin_ext( ++ PluginType::MatchingRule, ++ $hooks_ident::eq_mr_name(), ++ concat!(stringify!($mod_ident), "_plugin_eq_mr_init"), ++ [<$mod_ident _plugin_eq_mr_init>] ++ ) { ++ 0 => {}, ++ e => return e, ++ }; ++ ++ if $hooks_ident::sub_mr_oid().is_some() { ++ match register_plugin_ext( ++ PluginType::MatchingRule, ++ $hooks_ident::sub_mr_name(), ++ concat!(stringify!($mod_ident), "_plugin_ord_mr_init"), ++ [<$mod_ident _plugin_ord_mr_init>] ++ ) { ++ 0 => {}, ++ e => return e, ++ }; ++ } ++ ++ if $hooks_ident::ord_mr_oid().is_some() { ++ match register_plugin_ext( ++ PluginType::MatchingRule, ++ $hooks_ident::ord_mr_name(), ++ concat!(stringify!($mod_ident), "_plugin_ord_mr_init"), ++ [<$mod_ident _plugin_ord_mr_init>] ++ ) { ++ 0 => {}, ++ e => return e, ++ }; ++ } ++ ++ log_error!(ErrorLevel::Trace, "slapi_r_syntax_plugin_hooks <= success"); ++ ++ 0 ++ } ++ ++ pub extern "C" fn [<$mod_ident _plugin_syntax_validate>]( ++ raw_berval: *const libc::c_void, ++ ) -> i32 { ++ log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_syntax_validate => begin")); ++ ++ let bval = BerValRef::new(raw_berval); ++ ++ match $hooks_ident::syntax_validate(&bval) { ++ Ok(()) => { ++ log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_syntax_validate <= success")); ++ LDAP_SUCCESS ++ } ++ Err(e) => { ++ log_error!(ErrorLevel::Warning, ++ "{}_plugin_syntax_validate error -> {:?}", stringify!($mod_ident), e ++ ); ++ e as i32 ++ } ++ } ++ } ++ ++ // All the MR types share this. ++ pub extern "C" fn [<$mod_ident _plugin_mr_filter_ava>]( ++ raw_pb: *const libc::c_void, ++ raw_bvfilter: *const libc::c_void, ++ raw_bvals: *const libc::c_void, ++ i_ftype: i32, ++ _retval: *mut libc::c_void, ++ ) -> i32 { ++ log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_mr_filter_ava => begin")); ++ let mut pb = PblockRef::new(raw_pb); ++ let bvfilter = BerValRef::new(raw_bvfilter); ++ let bvals = ValueArrayRef::new(raw_bvals); ++ let ftype = match FilterType::try_from(i_ftype) { ++ Ok(f) => f, ++ Err(e) => { ++ log_error!(ErrorLevel::Error, "{}_plugin_ord_mr_filter_ava Error -> {:?}", ++ stringify!($mod_ident), e); ++ return e as i32 ++ } ++ }; ++ ++ let r: Result = match ftype { ++ FilterType::And | FilterType::Or | FilterType::Not => { ++ Err(PluginError::InvalidFilter) ++ } ++ FilterType::Equality => { ++ $hooks_ident::filter_ava_eq(&mut pb, &bvfilter, &bvals) ++ } ++ FilterType::Substring => { ++ Err(PluginError::Unimplemented) ++ } ++ FilterType::Ge => { ++ $hooks_ident::filter_ava_ord(&mut pb, &bvfilter, &bvals) ++ .map(|o_ord| { ++ match o_ord { ++ Some(Ordering::Greater) | Some(Ordering::Equal) => true, ++ Some(Ordering::Less) | None => false, ++ } ++ }) ++ } ++ FilterType::Le => { ++ $hooks_ident::filter_ava_ord(&mut pb, &bvfilter, &bvals) ++ .map(|o_ord| { ++ match o_ord { ++ Some(Ordering::Less) | Some(Ordering::Equal) => true, ++ Some(Ordering::Greater) | None => false, ++ } ++ }) ++ } ++ FilterType::Present => { ++ Err(PluginError::Unimplemented) ++ } ++ FilterType::Approx => { ++ Err(PluginError::Unimplemented) ++ } ++ FilterType::Extended => { ++ Err(PluginError::Unimplemented) ++ } ++ }; ++ ++ match r { ++ Ok(b) => { ++ log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_mr_filter_ava <= success")); ++ // rust bool into i32 will become 0 false, 1 true. However, ds expects 0 true and 1 false for ++ // for the filter_ava match. So we flip the bool, and send it back. ++ (!b) as i32 ++ } ++ Err(e) => { ++ log_error!(ErrorLevel::Warning, ++ "{}_plugin_mr_filter_ava error -> {:?}", ++ stringify!($mod_ident), e ++ ); ++ e as i32 ++ } ++ } ++ } ++ ++ ++ // EQ MR plugin hooks ++ #[no_mangle] ++ pub extern "C" fn [<$mod_ident _plugin_eq_mr_init>]( ++ raw_pb: *const libc::c_void, ++ ) -> i32 { ++ let mut pb = PblockRef::new(raw_pb); ++ log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_eq_mr_init => begin")); ++ match pb.set_plugin_version(PluginVersion::V01) { ++ 0 => {}, ++ e => return e, ++ }; ++ ++ let name_ptr = unsafe { names_to_leaking_char_array(&$hooks_ident::eq_mr_supported_names()) }; ++ // SLAPI_PLUGIN_MR_NAMES ++ match pb.register_mr_names(name_ptr) { ++ 0 => {}, ++ e => return e, ++ }; ++ ++ // description ++ // SLAPI_PLUGIN_MR_FILTER_CREATE_FN ++ match pb.register_mr_filter_create_fn([<$mod_ident _plugin_eq_mr_filter_create>]) { ++ 0 => {}, ++ e => return e, ++ }; ++ // SLAPI_PLUGIN_MR_INDEXER_CREATE_FN ++ match pb.register_mr_indexer_create_fn([<$mod_ident _plugin_eq_mr_indexer_create>]) { ++ 0 => {}, ++ e => return e, ++ }; ++ // SLAPI_PLUGIN_MR_FILTER_AVA ++ match pb.register_mr_filter_ava_fn([<$mod_ident _plugin_mr_filter_ava>]) { ++ 0 => {}, ++ e => return e, ++ }; ++ // SLAPI_PLUGIN_MR_FILTER_SUB ++ match pb.register_mr_filter_sub_fn([<$mod_ident _plugin_eq_mr_filter_sub>]) { ++ 0 => {}, ++ e => return e, ++ }; ++ // SLAPI_PLUGIN_MR_VALUES2KEYS ++ match pb.register_mr_values2keys_fn([<$mod_ident _plugin_eq_mr_filter_values2keys>]) { ++ 0 => {}, ++ e => return e, ++ }; ++ // SLAPI_PLUGIN_MR_ASSERTION2KEYS_AVA ++ match pb.register_mr_assertion2keys_ava_fn([<$mod_ident _plugin_eq_mr_filter_assertion2keys_ava>]) { ++ 0 => {}, ++ e => return e, ++ }; ++ // SLAPI_PLUGIN_MR_ASSERTION2KEYS_SUB ++ match pb.register_mr_assertion2keys_sub_fn([<$mod_ident _plugin_eq_mr_filter_assertion2keys_sub>]) { ++ 0 => {}, ++ e => return e, ++ }; ++ // SLAPI_PLUGIN_MR_COMPARE ++ match pb.register_mr_compare_fn([<$mod_ident _plugin_eq_mr_filter_compare>]) { ++ 0 => {}, ++ e => return e, ++ }; ++ // SLAPI_PLUGIN_MR_NORMALIZE ++ ++ // Finaly, register the MR ++ match unsafe { matchingrule_register($hooks_ident::eq_mr_oid(), $hooks_ident::eq_mr_name(), $hooks_ident::eq_mr_desc(), $hooks_ident::attr_oid(), &$hooks_ident::attr_compat_oids()) } { ++ 0 => {}, ++ e => return e, ++ }; ++ ++ log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_eq_mr_init <= success")); ++ 0 ++ } ++ ++ pub extern "C" fn [<$mod_ident _plugin_eq_mr_filter_create>]( ++ raw_pb: *const libc::c_void, ++ ) -> i32 { ++ log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_eq_mr_filter_create => begin")); ++ log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_eq_mr_filter_create <= success")); ++ 0 ++ } ++ ++ pub extern "C" fn [<$mod_ident _plugin_eq_mr_indexer_create>]( ++ raw_pb: *const libc::c_void, ++ ) -> i32 { ++ log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_eq_mr_indexer_create => begin")); ++ log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_eq_mr_indexer_create <= success")); ++ 0 ++ } ++ ++ pub extern "C" fn [<$mod_ident _plugin_eq_mr_filter_sub>]( ++ raw_pb: *const libc::c_void, ++ ) -> i32 { ++ log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_eq_mr_filter_sub => begin")); ++ log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_eq_mr_filter_sub <= success")); ++ 0 ++ } ++ ++ pub extern "C" fn [<$mod_ident _plugin_eq_mr_filter_values2keys>]( ++ raw_pb: *const libc::c_void, ++ raw_vals: *const libc::c_void, ++ raw_ivals: *mut libc::c_void, ++ i_ftype: i32, ++ ) -> i32 { ++ log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_eq_mr_filter_values2keys => begin")); ++ let mut pb = PblockRef::new(raw_pb); ++ let vals = ValueArrayRef::new(raw_vals); ++ let ftype = match FilterType::try_from(i_ftype) { ++ Ok(f) => f, ++ Err(e) => { ++ log_error!(ErrorLevel::Error, ++ "{}_plugin_eq_mr_filter_values2keys Error -> {:?}", ++ stringify!($mod_ident), ++ e); ++ return e as i32 ++ } ++ }; ++ ++ if (ftype != FilterType::Equality && ftype != FilterType::Approx) { ++ log_error!(ErrorLevel::Error, ++ "{}_plugin_eq_mr_filter_values2keys Error -> Invalid Filter type", ++ stringify!($mod_ident), ++ ); ++ return PluginError::InvalidFilter as i32 ++ } ++ ++ let va = match $hooks_ident::eq_mr_filter_values2keys(&mut pb, &vals) { ++ Ok(va) => va, ++ Err(e) => { ++ log_error!(ErrorLevel::Error, ++ "{}_plugin_eq_mr_filter_values2keys Error -> {:?}", ++ stringify!($mod_ident), ++ e); ++ return e as i32 ++ } ++ }; ++ ++ // Now, deconstruct the va, get the pointer, and put it into the ivals. ++ unsafe { ++ let ivals_ptr: *mut *const libc::c_void = raw_ivals as *mut _; ++ (*ivals_ptr) = va.take_ownership() as *const libc::c_void; ++ } ++ ++ log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_eq_mr_filter_values2keys <= success")); ++ 0 ++ } ++ ++ pub extern "C" fn [<$mod_ident _plugin_eq_mr_filter_assertion2keys_ava>]( ++ raw_pb: *const libc::c_void, ++ ) -> i32 { ++ log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_eq_mr_filter_assertion2keys_ava => begin")); ++ log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_eq_mr_filter_assertion2keys_ava <= success")); ++ 0 ++ } ++ ++ pub extern "C" fn [<$mod_ident _plugin_eq_mr_filter_assertion2keys_sub>]( ++ raw_pb: *const libc::c_void, ++ ) -> i32 { ++ log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_eq_mr_filter_assertion2keys_sub => begin")); ++ log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_eq_mr_filter_assertion2keys_sub <= success")); ++ 0 ++ } ++ ++ pub extern "C" fn [<$mod_ident _plugin_eq_mr_filter_names>]( ++ raw_pb: *const libc::c_void, ++ ) -> i32 { ++ // This is probably another char pointer. ++ 0 ++ } ++ ++ pub extern "C" fn [<$mod_ident _plugin_eq_mr_filter_compare>]( ++ raw_va: *const libc::c_void, ++ raw_vb: *const libc::c_void, ++ ) -> i32 { ++ log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_eq_mr_filter_compare => begin")); ++ log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_eq_mr_filter_compare <= success")); ++ 0 ++ } ++ ++ // SUB MR plugin hooks ++ ++ pub extern "C" fn [<$mod_ident _plugin_sub_mr_filter_create>]( ++ raw_pb: *const libc::c_void, ++ ) -> i32 { ++ log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_sub_mr_filter_create => begin")); ++ log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_sub_mr_filter_create <= success")); ++ 0 ++ } ++ ++ pub extern "C" fn [<$mod_ident _plugin_sub_mr_indexer_create>]( ++ raw_pb: *const libc::c_void, ++ ) -> i32 { ++ log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_sub_mr_indexer_create => begin")); ++ log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_sub_mr_indexer_create <= success")); ++ 0 ++ } ++ ++ pub extern "C" fn [<$mod_ident _plugin_sub_mr_filter_sub>]( ++ raw_pb: *const libc::c_void, ++ ) -> i32 { ++ log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_sub_mr_filter_sub => begin")); ++ log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_sub_mr_filter_sub <= success")); ++ 0 ++ } ++ ++ pub extern "C" fn [<$mod_ident _plugin_sub_mr_filter_values2keys>]( ++ raw_pb: *const libc::c_void, ++ ) -> i32 { ++ log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_sub_mr_filter_values2keys => begin")); ++ log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_sub_mr_filter_values2keys <= success")); ++ 0 ++ } ++ ++ pub extern "C" fn [<$mod_ident _plugin_sub_mr_filter_assertion2keys_ava>]( ++ raw_pb: *const libc::c_void, ++ ) -> i32 { ++ log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_sub_mr_filter_assertion2keys_ava => begin")); ++ log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_sub_mr_filter_assertion2keys_ava <= success")); ++ 0 ++ } ++ ++ pub extern "C" fn [<$mod_ident _plugin_sub_mr_filter_assertion2keys_sub>]( ++ raw_pb: *const libc::c_void, ++ ) -> i32 { ++ log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_sub_mr_filter_assertion2keys_sub => begin")); ++ log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_sub_mr_filter_assertion2keys_sub <= success")); ++ 0 ++ } ++ ++ pub extern "C" fn [<$mod_ident _plugin_sub_mr_filter_names>]( ++ raw_pb: *const libc::c_void, ++ ) -> i32 { ++ // Probably a char array ++ 0 ++ } ++ ++ pub extern "C" fn [<$mod_ident _plugin_sub_mr_filter_compare>]( ++ raw_pb: *const libc::c_void, ++ ) -> i32 { ++ log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_sub_mr_filter_compare => begin")); ++ log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_sub_mr_filter_compare <= success")); ++ 0 ++ } ++ ++ // ORD MR plugin hooks ++ #[no_mangle] ++ pub extern "C" fn [<$mod_ident _plugin_ord_mr_init>]( ++ raw_pb: *const libc::c_void, ++ ) -> i32 { ++ let mut pb = PblockRef::new(raw_pb); ++ log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_ord_mr_init => begin")); ++ match pb.set_plugin_version(PluginVersion::V01) { ++ 0 => {}, ++ e => return e, ++ }; ++ ++ let name_ptr = unsafe { names_to_leaking_char_array(&$hooks_ident::ord_mr_supported_names()) }; ++ // SLAPI_PLUGIN_MR_NAMES ++ match pb.register_mr_names(name_ptr) { ++ 0 => {}, ++ e => return e, ++ }; ++ ++ // description ++ // SLAPI_PLUGIN_MR_FILTER_CREATE_FN ++ match pb.register_mr_filter_create_fn([<$mod_ident _plugin_ord_mr_filter_create>]) { ++ 0 => {}, ++ e => return e, ++ }; ++ // SLAPI_PLUGIN_MR_INDEXER_CREATE_FN ++ match pb.register_mr_indexer_create_fn([<$mod_ident _plugin_ord_mr_indexer_create>]) { ++ 0 => {}, ++ e => return e, ++ }; ++ // SLAPI_PLUGIN_MR_FILTER_AVA ++ match pb.register_mr_filter_ava_fn([<$mod_ident _plugin_mr_filter_ava>]) { ++ 0 => {}, ++ e => return e, ++ }; ++ // SLAPI_PLUGIN_MR_FILTER_SUB ++ match pb.register_mr_filter_sub_fn([<$mod_ident _plugin_ord_mr_filter_sub>]) { ++ 0 => {}, ++ e => return e, ++ }; ++ // SLAPI_PLUGIN_MR_VALUES2KEYS ++ /* ++ match pb.register_mr_values2keys_fn([<$mod_ident _plugin_ord_mr_filter_values2keys>]) { ++ 0 => {}, ++ e => return e, ++ }; ++ */ ++ // SLAPI_PLUGIN_MR_ASSERTION2KEYS_AVA ++ match pb.register_mr_assertion2keys_ava_fn([<$mod_ident _plugin_ord_mr_filter_assertion2keys_ava>]) { ++ 0 => {}, ++ e => return e, ++ }; ++ // SLAPI_PLUGIN_MR_ASSERTION2KEYS_SUB ++ match pb.register_mr_assertion2keys_sub_fn([<$mod_ident _plugin_ord_mr_filter_assertion2keys_sub>]) { ++ 0 => {}, ++ e => return e, ++ }; ++ // SLAPI_PLUGIN_MR_COMPARE ++ match pb.register_mr_compare_fn([<$mod_ident _plugin_ord_mr_filter_compare>]) { ++ 0 => {}, ++ e => return e, ++ }; ++ // SLAPI_PLUGIN_MR_NORMALIZE ++ ++ // Finaly, register the MR ++ match unsafe { matchingrule_register($hooks_ident::ord_mr_oid().unwrap(), $hooks_ident::ord_mr_name(), $hooks_ident::ord_mr_desc(), $hooks_ident::attr_oid(), &$hooks_ident::attr_compat_oids()) } { ++ 0 => {}, ++ e => return e, ++ }; ++ ++ log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_ord_mr_init <= success")); ++ 0 ++ } ++ ++ pub extern "C" fn [<$mod_ident _plugin_ord_mr_filter_create>]( ++ raw_pb: *const libc::c_void, ++ ) -> i32 { ++ log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_ord_mr_filter_create => begin")); ++ log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_ord_mr_filter_create <= success")); ++ 0 ++ } ++ ++ pub extern "C" fn [<$mod_ident _plugin_ord_mr_indexer_create>]( ++ raw_pb: *const libc::c_void, ++ ) -> i32 { ++ log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_ord_mr_indexer_create => begin")); ++ log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_ord_mr_indexer_create <= success")); ++ 0 ++ } ++ ++ pub extern "C" fn [<$mod_ident _plugin_ord_mr_filter_sub>]( ++ raw_pb: *const libc::c_void, ++ ) -> i32 { ++ log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_ord_mr_filter_sub => begin")); ++ log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_ord_mr_filter_sub <= success")); ++ 0 ++ } ++ ++ pub extern "C" fn [<$mod_ident _plugin_ord_mr_filter_values2keys>]( ++ raw_pb: *const libc::c_void, ++ ) -> i32 { ++ log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_ord_mr_filter_values2keys => begin")); ++ log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_ord_mr_filter_values2keys <= success")); ++ 0 ++ } ++ ++ pub extern "C" fn [<$mod_ident _plugin_ord_mr_filter_assertion2keys_ava>]( ++ raw_pb: *const libc::c_void, ++ ) -> i32 { ++ log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_ord_mr_filter_assertion2keys_ava => begin")); ++ log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_ord_mr_filter_assertion2keys_ava <= success")); ++ 0 ++ } ++ ++ pub extern "C" fn [<$mod_ident _plugin_ord_mr_filter_assertion2keys_sub>]( ++ raw_pb: *const libc::c_void, ++ ) -> i32 { ++ log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_ord_mr_filter_assertion2keys_sub => begin")); ++ log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_ord_mr_filter_assertion2keys_sub <= success")); ++ 0 ++ } ++ ++ pub extern "C" fn [<$mod_ident _plugin_ord_mr_filter_names>]( ++ raw_pb: *const libc::c_void, ++ ) -> i32 { ++ // probably char pointers ++ 0 ++ } ++ ++ pub extern "C" fn [<$mod_ident _plugin_ord_mr_filter_compare>]( ++ raw_va: *const libc::c_void, ++ raw_vb: *const libc::c_void, ++ ) -> i32 { ++ log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_ord_mr_filter_compare => begin")); ++ let va = BerValRef::new(raw_va); ++ let vb = BerValRef::new(raw_vb); ++ let rc = match $hooks_ident::filter_compare(&va, &vb) { ++ Ordering::Less => -1, ++ Ordering::Equal => 0, ++ Ordering::Greater => 1, ++ }; ++ log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_ord_mr_filter_compare <= success")); ++ rc ++ } ++ ++ } // end paste ++ ) ++} // end macro ++ ++#[macro_export] ++macro_rules! slapi_r_search_callback_mapfn { ++ ( ++ $mod_ident:ident, ++ $cb_target_ident:ident, ++ $cb_mod_ident:ident ++ ) => { ++ paste::item! { ++ #[no_mangle] ++ pub extern "C" fn [<$cb_target_ident>]( ++ raw_e: *const libc::c_void, ++ raw_data: *const libc::c_void, ++ ) -> i32 { ++ let e = EntryRef::new(raw_e); ++ let data_ptr = raw_data as *const _; ++ let data = unsafe { &(*data_ptr) }; ++ match $cb_mod_ident(e, data) { ++ Ok(_) => LDAPError::Success as i32, ++ Err(e) => e as i32, ++ } ++ } ++ } // end paste ++ }; ++} // end macro +diff --git a/src/slapi_r_plugin/src/pblock.rs b/src/slapi_r_plugin/src/pblock.rs +new file mode 100644 +index 000000000..b69ce1680 +--- /dev/null ++++ b/src/slapi_r_plugin/src/pblock.rs +@@ -0,0 +1,275 @@ ++use libc; ++use std::ops::{Deref, DerefMut}; ++use std::os::raw::c_char; ++use std::ptr; ++ ++use crate::backend::BackendRef; ++use crate::constants::{PblockType, PluginFnType, PluginVersion}; ++use crate::entry::EntryRef; ++pub use crate::log::{log_error, ErrorLevel}; ++ ++extern "C" { ++ fn slapi_pblock_set(pb: *const libc::c_void, arg: i32, value: *const libc::c_void) -> i32; ++ fn slapi_pblock_get(pb: *const libc::c_void, arg: i32, value: *const libc::c_void) -> i32; ++ fn slapi_pblock_new() -> *const libc::c_void; ++} ++ ++pub struct Pblock { ++ value: PblockRef, ++} ++ ++impl Pblock { ++ pub fn new() -> Pblock { ++ let raw_pb = unsafe { slapi_pblock_new() }; ++ Pblock { ++ value: PblockRef { raw_pb }, ++ } ++ } ++} ++ ++impl Deref for Pblock { ++ type Target = PblockRef; ++ ++ fn deref(&self) -> &Self::Target { ++ &self.value ++ } ++} ++ ++impl DerefMut for Pblock { ++ fn deref_mut(&mut self) -> &mut Self::Target { ++ &mut self.value ++ } ++} ++ ++pub struct PblockRef { ++ raw_pb: *const libc::c_void, ++} ++ ++impl PblockRef { ++ pub fn new(raw_pb: *const libc::c_void) -> Self { ++ PblockRef { raw_pb } ++ } ++ ++ pub unsafe fn as_ptr(&self) -> *const libc::c_void { ++ self.raw_pb ++ } ++ ++ fn set_pb_char_arr_ptr(&mut self, req_type: PblockType, ptr: *const *const c_char) -> i32 { ++ let value_ptr: *const libc::c_void = ptr as *const libc::c_void; ++ unsafe { slapi_pblock_set(self.raw_pb, req_type as i32, value_ptr) } ++ } ++ ++ fn set_pb_char_ptr(&mut self, req_type: PblockType, ptr: *const c_char) -> i32 { ++ let value_ptr: *const libc::c_void = ptr as *const libc::c_void; ++ unsafe { slapi_pblock_set(self.raw_pb, req_type as i32, value_ptr) } ++ } ++ ++ fn set_pb_fn_ptr( ++ &mut self, ++ fn_type: PluginFnType, ++ ptr: extern "C" fn(*const libc::c_void) -> i32, ++ ) -> i32 { ++ let value_ptr: *const libc::c_void = ptr as *const libc::c_void; ++ unsafe { slapi_pblock_set(self.raw_pb, fn_type as i32, value_ptr) } ++ } ++ ++ fn get_value_ptr(&mut self, req_type: PblockType) -> Result<*const libc::c_void, ()> { ++ let mut value: *mut libc::c_void = ptr::null::() as *mut libc::c_void; ++ let value_ptr: *const libc::c_void = &mut value as *const _ as *const libc::c_void; ++ match unsafe { slapi_pblock_get(self.raw_pb, req_type as i32, value_ptr) } { ++ 0 => Ok(value), ++ e => { ++ log_error!(ErrorLevel::Error, "enable to get from pblock -> {:?}", e); ++ Err(()) ++ } ++ } ++ } ++ ++ fn get_value_i32(&mut self, req_type: PblockType) -> Result { ++ let mut value: i32 = 0; ++ let value_ptr: *const libc::c_void = &mut value as *const _ as *const libc::c_void; ++ match unsafe { slapi_pblock_get(self.raw_pb, req_type as i32, value_ptr) } { ++ 0 => Ok(value), ++ e => { ++ log_error!(ErrorLevel::Error, "enable to get from pblock -> {:?}", e); ++ Err(()) ++ } ++ } ++ } ++ ++ pub fn register_start_fn(&mut self, ptr: extern "C" fn(*const libc::c_void) -> i32) -> i32 { ++ self.set_pb_fn_ptr(PluginFnType::Start, ptr) ++ } ++ ++ pub fn register_close_fn(&mut self, ptr: extern "C" fn(*const libc::c_void) -> i32) -> i32 { ++ self.set_pb_fn_ptr(PluginFnType::Close, ptr) ++ } ++ ++ pub fn register_betxn_pre_add_fn( ++ &mut self, ++ ptr: extern "C" fn(*const libc::c_void) -> i32, ++ ) -> i32 { ++ self.set_pb_fn_ptr(PluginFnType::BeTxnPreAdd, ptr) ++ } ++ ++ pub fn register_betxn_pre_modify_fn( ++ &mut self, ++ ptr: extern "C" fn(*const libc::c_void) -> i32, ++ ) -> i32 { ++ self.set_pb_fn_ptr(PluginFnType::BeTxnPreModify, ptr) ++ } ++ ++ pub fn register_syntax_filter_ava_fn( ++ &mut self, ++ ptr: extern "C" fn( ++ *const core::ffi::c_void, ++ *const core::ffi::c_void, ++ *const core::ffi::c_void, ++ i32, ++ *mut core::ffi::c_void, ++ ) -> i32, ++ ) -> i32 { ++ // We can't use self.set_pb_fn_ptr here as the fn type sig is different. ++ let value_ptr: *const libc::c_void = ptr as *const libc::c_void; ++ unsafe { slapi_pblock_set(self.raw_pb, PluginFnType::SyntaxFilterAva as i32, value_ptr) } ++ } ++ ++ pub fn register_syntax_values2keys_fn( ++ &mut self, ++ ptr: extern "C" fn(*const libc::c_void) -> i32, ++ ) -> i32 { ++ self.set_pb_fn_ptr(PluginFnType::SyntaxValuesToKeys, ptr) ++ } ++ ++ pub fn register_syntax_assertion2keys_fn( ++ &mut self, ++ ptr: extern "C" fn(*const libc::c_void) -> i32, ++ ) -> i32 { ++ self.set_pb_fn_ptr(PluginFnType::SyntaxAssertion2KeysAva, ptr) ++ } ++ ++ pub fn register_syntax_flags_fn( ++ &mut self, ++ ptr: extern "C" fn(*const libc::c_void) -> i32, ++ ) -> i32 { ++ self.set_pb_fn_ptr(PluginFnType::SyntaxFlags, ptr) ++ } ++ ++ pub fn register_syntax_oid(&mut self, ptr: *const c_char) -> i32 { ++ self.set_pb_char_ptr(PblockType::SyntaxOid, ptr) ++ } ++ ++ pub fn register_syntax_compare_fn( ++ &mut self, ++ ptr: extern "C" fn(*const libc::c_void) -> i32, ++ ) -> i32 { ++ self.set_pb_fn_ptr(PluginFnType::SyntaxCompare, ptr) ++ } ++ ++ pub fn register_syntax_validate_fn( ++ &mut self, ++ ptr: extern "C" fn(*const libc::c_void) -> i32, ++ ) -> i32 { ++ self.set_pb_fn_ptr(PluginFnType::SyntaxValidate, ptr) ++ } ++ ++ pub fn register_syntax_names(&mut self, arr_ptr: *const *const c_char) -> i32 { ++ self.set_pb_char_arr_ptr(PblockType::SyntaxNames, arr_ptr) ++ } ++ ++ pub fn register_mr_filter_create_fn( ++ &mut self, ++ ptr: extern "C" fn(*const libc::c_void) -> i32, ++ ) -> i32 { ++ self.set_pb_fn_ptr(PluginFnType::MRFilterCreate, ptr) ++ } ++ ++ pub fn register_mr_indexer_create_fn( ++ &mut self, ++ ptr: extern "C" fn(*const libc::c_void) -> i32, ++ ) -> i32 { ++ self.set_pb_fn_ptr(PluginFnType::MRIndexerCreate, ptr) ++ } ++ ++ pub fn register_mr_filter_ava_fn( ++ &mut self, ++ ptr: extern "C" fn( ++ *const core::ffi::c_void, ++ *const core::ffi::c_void, ++ *const core::ffi::c_void, ++ i32, ++ *mut core::ffi::c_void, ++ ) -> i32, ++ ) -> i32 { ++ let value_ptr: *const libc::c_void = ptr as *const libc::c_void; ++ unsafe { slapi_pblock_set(self.raw_pb, PluginFnType::MRFilterAva as i32, value_ptr) } ++ } ++ ++ pub fn register_mr_filter_sub_fn( ++ &mut self, ++ ptr: extern "C" fn(*const libc::c_void) -> i32, ++ ) -> i32 { ++ self.set_pb_fn_ptr(PluginFnType::MRFilterSub, ptr) ++ } ++ ++ pub fn register_mr_values2keys_fn( ++ &mut self, ++ ptr: extern "C" fn( ++ *const core::ffi::c_void, ++ *const core::ffi::c_void, ++ *mut core::ffi::c_void, ++ i32, ++ ) -> i32, ++ ) -> i32 { ++ let value_ptr: *const libc::c_void = ptr as *const libc::c_void; ++ unsafe { slapi_pblock_set(self.raw_pb, PluginFnType::MRValuesToKeys as i32, value_ptr) } ++ } ++ ++ pub fn register_mr_assertion2keys_ava_fn( ++ &mut self, ++ ptr: extern "C" fn(*const libc::c_void) -> i32, ++ ) -> i32 { ++ self.set_pb_fn_ptr(PluginFnType::MRAssertionToKeysAva, ptr) ++ } ++ ++ pub fn register_mr_assertion2keys_sub_fn( ++ &mut self, ++ ptr: extern "C" fn(*const libc::c_void) -> i32, ++ ) -> i32 { ++ self.set_pb_fn_ptr(PluginFnType::MRAssertionToKeysSub, ptr) ++ } ++ ++ pub fn register_mr_compare_fn( ++ &mut self, ++ ptr: extern "C" fn(*const libc::c_void, *const libc::c_void) -> i32, ++ ) -> i32 { ++ let value_ptr: *const libc::c_void = ptr as *const libc::c_void; ++ unsafe { slapi_pblock_set(self.raw_pb, PluginFnType::MRCompare as i32, value_ptr) } ++ } ++ ++ pub fn register_mr_names(&mut self, arr_ptr: *const *const c_char) -> i32 { ++ self.set_pb_char_arr_ptr(PblockType::MRNames, arr_ptr) ++ } ++ ++ pub fn get_op_add_entryref(&mut self) -> Result { ++ self.get_value_ptr(PblockType::AddEntry) ++ .map(|ptr| EntryRef::new(ptr)) ++ } ++ ++ pub fn set_plugin_version(&mut self, vers: PluginVersion) -> i32 { ++ self.set_pb_char_ptr(PblockType::Version, vers.to_char_ptr()) ++ } ++ ++ pub fn set_op_backend(&mut self, be: &BackendRef) -> i32 { ++ unsafe { slapi_pblock_set(self.raw_pb, PblockType::Backend as i32, be.as_ptr()) } ++ } ++ ++ pub fn get_plugin_identity(&mut self) -> *const libc::c_void { ++ self.get_value_ptr(PblockType::Identity) ++ .unwrap_or(std::ptr::null()) ++ } ++ ++ pub fn get_op_result(&mut self) -> i32 { ++ self.get_value_i32(PblockType::OpResult).unwrap_or(-1) ++ } ++} +diff --git a/src/slapi_r_plugin/src/plugin.rs b/src/slapi_r_plugin/src/plugin.rs +new file mode 100644 +index 000000000..bf47779bc +--- /dev/null ++++ b/src/slapi_r_plugin/src/plugin.rs +@@ -0,0 +1,117 @@ ++use crate::constants::{PluginType, PLUGIN_DEFAULT_PRECEDENCE}; ++use crate::dn::Sdn; ++use crate::entry::EntryRef; ++use crate::error::LDAPError; ++use crate::error::PluginError; ++use crate::pblock::PblockRef; ++use crate::task::Task; ++use libc; ++use std::ffi::CString; ++use std::os::raw::c_char; ++use std::ptr; ++ ++extern "C" { ++ fn slapi_register_plugin_ext( ++ plugintype: *const c_char, ++ enabled: i32, ++ initsymbol: *const c_char, ++ initfunc: *const libc::c_void, ++ name: *const c_char, ++ argv: *const *const c_char, ++ group_identity: *const libc::c_void, ++ precedence: i32, ++ ) -> i32; ++} ++ ++pub struct PluginIdRef { ++ pub raw_pid: *const libc::c_void, ++} ++ ++pub fn register_plugin_ext( ++ ptype: PluginType, ++ plugname: &str, ++ initfnname: &str, ++ initfn: extern "C" fn(*const libc::c_void) -> i32, ++) -> i32 { ++ let c_plugname = match CString::new(plugname) { ++ Ok(c) => c, ++ Err(_) => return 1, ++ }; ++ let c_initfnname = match CString::new(initfnname) { ++ Ok(c) => c, ++ Err(_) => return 1, ++ }; ++ let argv = [c_plugname.as_ptr(), ptr::null()]; ++ let value_ptr: *const libc::c_void = initfn as *const libc::c_void; ++ ++ unsafe { ++ slapi_register_plugin_ext( ++ ptype.to_char_ptr(), ++ 1, ++ c_initfnname.as_ptr(), ++ value_ptr, ++ c_plugname.as_ptr(), ++ &argv as *const *const c_char, ++ ptr::null(), ++ PLUGIN_DEFAULT_PRECEDENCE, ++ ) ++ } ++} ++ ++pub trait SlapiPlugin3 { ++ // We require a newer rust for default associated types. ++ // type TaskData = (); ++ type TaskData; ++ ++ fn has_pre_modify() -> bool { ++ false ++ } ++ ++ fn has_post_modify() -> bool { ++ false ++ } ++ ++ fn has_pre_add() -> bool { ++ false ++ } ++ ++ fn has_post_add() -> bool { ++ false ++ } ++ ++ fn has_betxn_pre_modify() -> bool { ++ false ++ } ++ ++ fn has_betxn_pre_add() -> bool { ++ false ++ } ++ ++ fn has_task_handler() -> Option<&'static str> { ++ None ++ } ++ ++ fn start(_pb: &mut PblockRef) -> Result<(), PluginError>; ++ ++ fn close(_pb: &mut PblockRef) -> Result<(), PluginError>; ++ ++ fn betxn_pre_modify(_pb: &mut PblockRef) -> Result<(), PluginError> { ++ Err(PluginError::Unimplemented) ++ } ++ ++ fn betxn_pre_add(_pb: &mut PblockRef) -> Result<(), PluginError> { ++ Err(PluginError::Unimplemented) ++ } ++ ++ fn task_validate(_e: &EntryRef) -> Result { ++ Err(LDAPError::Other) ++ } ++ ++ fn task_be_dn_hint(_data: &Self::TaskData) -> Option { ++ None ++ } ++ ++ fn task_handler(_task: &Task, _data: Self::TaskData) -> Result { ++ Err(PluginError::Unimplemented) ++ } ++} +diff --git a/src/slapi_r_plugin/src/search.rs b/src/slapi_r_plugin/src/search.rs +new file mode 100644 +index 000000000..e0e2a1fd7 +--- /dev/null ++++ b/src/slapi_r_plugin/src/search.rs +@@ -0,0 +1,127 @@ ++use crate::dn::SdnRef; ++use crate::error::{LDAPError, PluginError}; ++use crate::pblock::Pblock; ++use crate::plugin::PluginIdRef; ++use std::ffi::CString; ++use std::ops::Deref; ++use std::os::raw::c_char; ++ ++extern "C" { ++ fn slapi_search_internal_set_pb_ext( ++ pb: *const libc::c_void, ++ base: *const libc::c_void, ++ scope: i32, ++ filter: *const c_char, ++ attrs: *const *const c_char, ++ attrsonly: i32, ++ controls: *const *const libc::c_void, ++ uniqueid: *const c_char, ++ plugin_ident: *const libc::c_void, ++ op_flags: i32, ++ ); ++ fn slapi_search_internal_callback_pb( ++ pb: *const libc::c_void, ++ cb_data: *const libc::c_void, ++ cb_result_ptr: *const libc::c_void, ++ cb_entry_ptr: *const libc::c_void, ++ cb_referral_ptr: *const libc::c_void, ++ ) -> i32; ++} ++ ++#[derive(Debug)] ++#[repr(i32)] ++pub enum SearchScope { ++ Base = 0, ++ Onelevel = 1, ++ Subtree = 2, ++} ++ ++enum SearchType { ++ InternalMapEntry( ++ extern "C" fn(*const core::ffi::c_void, *const core::ffi::c_void) -> i32, ++ *const libc::c_void, ++ ), ++ // InternalMapResult ++ // InternalMapReferral ++} ++ ++pub struct Search { ++ pb: Pblock, ++ // This is so that the char * to the pb lives long enough as ds won't clone it. ++ filter: Option, ++ stype: SearchType, ++} ++ ++pub struct SearchResult { ++ pb: Pblock, ++} ++ ++impl Search { ++ pub fn new_map_entry( ++ basedn: &SdnRef, ++ scope: SearchScope, ++ filter: &str, ++ plugin_id: PluginIdRef, ++ cbdata: &T, ++ mapfn: extern "C" fn(*const libc::c_void, *const libc::c_void) -> i32, ++ ) -> Result ++ where ++ T: Send, ++ { ++ // Configure a search based on the requested type. ++ let pb = Pblock::new(); ++ let raw_filter = CString::new(filter).map_err(|_| PluginError::InvalidFilter)?; ++ ++ unsafe { ++ slapi_search_internal_set_pb_ext( ++ pb.deref().as_ptr(), ++ basedn.as_ptr(), ++ scope as i32, ++ raw_filter.as_ptr(), ++ std::ptr::null(), ++ 0, ++ std::ptr::null(), ++ std::ptr::null(), ++ plugin_id.raw_pid, ++ 0, ++ ) ++ }; ++ ++ Ok(Search { ++ pb, ++ filter: Some(raw_filter), ++ stype: SearchType::InternalMapEntry(mapfn, cbdata as *const _ as *const libc::c_void), ++ }) ++ } ++ ++ // Consume self, do the search ++ pub fn execute(self) -> Result { ++ // Deconstruct self ++ let Search { ++ mut pb, ++ filter: _filter, ++ stype, ++ } = self; ++ ++ // run the search based on the type. ++ match stype { ++ SearchType::InternalMapEntry(mapfn, cbdata) => unsafe { ++ slapi_search_internal_callback_pb( ++ pb.deref().as_ptr(), ++ cbdata, ++ std::ptr::null(), ++ mapfn as *const libc::c_void, ++ std::ptr::null(), ++ ); ++ }, ++ }; ++ ++ // now check the result, and map to what we need. ++ let result = pb.get_op_result(); ++ ++ match result { ++ 0 => Ok(SearchResult { pb }), ++ _e => Err(LDAPError::from(result)), ++ } ++ } ++} +diff --git a/src/slapi_r_plugin/src/syntax_plugin.rs b/src/slapi_r_plugin/src/syntax_plugin.rs +new file mode 100644 +index 000000000..e7d5c01bd +--- /dev/null ++++ b/src/slapi_r_plugin/src/syntax_plugin.rs +@@ -0,0 +1,169 @@ ++use crate::ber::BerValRef; ++// use crate::constants::FilterType; ++use crate::error::PluginError; ++use crate::pblock::PblockRef; ++use crate::value::{ValueArray, ValueArrayRef}; ++use std::cmp::Ordering; ++use std::ffi::CString; ++use std::iter::once; ++use std::os::raw::c_char; ++use std::ptr; ++ ++// need a call to slapi_register_plugin_ext ++ ++extern "C" { ++ fn slapi_matchingrule_register(mr: *const slapi_matchingRuleEntry) -> i32; ++} ++ ++#[repr(C)] ++struct slapi_matchingRuleEntry { ++ mr_oid: *const c_char, ++ _mr_oidalias: *const c_char, ++ mr_name: *const c_char, ++ mr_desc: *const c_char, ++ mr_syntax: *const c_char, ++ _mr_obsolete: i32, // unused ++ mr_compat_syntax: *const *const c_char, ++} ++ ++pub unsafe fn name_to_leaking_char(name: &str) -> *const c_char { ++ let n = CString::new(name) ++ .expect("An invalid string has been hardcoded!") ++ .into_boxed_c_str(); ++ let n_ptr = n.as_ptr(); ++ // Now we intentionally leak the name here, and the pointer will remain valid. ++ Box::leak(n); ++ n_ptr ++} ++ ++pub unsafe fn names_to_leaking_char_array(names: &[&str]) -> *const *const c_char { ++ let n_arr: Vec = names ++ .iter() ++ .map(|s| CString::new(*s).expect("An invalid string has been hardcoded!")) ++ .collect(); ++ let n_arr = n_arr.into_boxed_slice(); ++ let n_ptr_arr: Vec<*const c_char> = n_arr ++ .iter() ++ .map(|v| v.as_ptr()) ++ .chain(once(ptr::null())) ++ .collect(); ++ let n_ptr_arr = n_ptr_arr.into_boxed_slice(); ++ ++ // Now we intentionally leak these names here, ++ let _r_n_arr = Box::leak(n_arr); ++ let r_n_ptr_arr = Box::leak(n_ptr_arr); ++ ++ let name_ptr = r_n_ptr_arr as *const _ as *const *const c_char; ++ name_ptr ++} ++ ++// oid - the oid of the matching rule ++// name - the name of the mr ++// desc - description ++// syntax - the syntax of the attribute we apply to ++// compat_syntax - extended syntaxes f other attributes we may apply to. ++pub unsafe fn matchingrule_register( ++ oid: &str, ++ name: &str, ++ desc: &str, ++ syntax: &str, ++ compat_syntax: &[&str], ++) -> i32 { ++ let oid_ptr = name_to_leaking_char(oid); ++ let name_ptr = name_to_leaking_char(name); ++ let desc_ptr = name_to_leaking_char(desc); ++ let syntax_ptr = name_to_leaking_char(syntax); ++ let compat_syntax_ptr = names_to_leaking_char_array(compat_syntax); ++ ++ let new_mr = slapi_matchingRuleEntry { ++ mr_oid: oid_ptr, ++ _mr_oidalias: ptr::null(), ++ mr_name: name_ptr, ++ mr_desc: desc_ptr, ++ mr_syntax: syntax_ptr, ++ _mr_obsolete: 0, ++ mr_compat_syntax: compat_syntax_ptr, ++ }; ++ ++ let new_mr_ptr = &new_mr as *const _; ++ slapi_matchingrule_register(new_mr_ptr) ++} ++ ++pub trait SlapiSyntaxPlugin1 { ++ fn attr_oid() -> &'static str; ++ ++ fn attr_compat_oids() -> Vec<&'static str>; ++ ++ fn attr_supported_names() -> Vec<&'static str>; ++ ++ fn syntax_validate(bval: &BerValRef) -> Result<(), PluginError>; ++ ++ fn eq_mr_oid() -> &'static str; ++ ++ fn eq_mr_name() -> &'static str; ++ ++ fn eq_mr_desc() -> &'static str; ++ ++ fn eq_mr_supported_names() -> Vec<&'static str>; ++ ++ fn filter_ava_eq( ++ _pb: &mut PblockRef, ++ _bval_filter: &BerValRef, ++ _vals: &ValueArrayRef, ++ ) -> Result { ++ Ok(false) ++ } ++ ++ fn eq_mr_filter_values2keys( ++ _pb: &mut PblockRef, ++ _vals: &ValueArrayRef, ++ ) -> Result; ++} ++ ++pub trait SlapiOrdMr: SlapiSyntaxPlugin1 { ++ fn ord_mr_oid() -> Option<&'static str> { ++ None ++ } ++ ++ fn ord_mr_name() -> &'static str { ++ panic!("Unimplemented ord_mr_name for SlapiOrdMr") ++ } ++ ++ fn ord_mr_desc() -> &'static str { ++ panic!("Unimplemented ord_mr_desc for SlapiOrdMr") ++ } ++ ++ fn ord_mr_supported_names() -> Vec<&'static str> { ++ panic!("Unimplemented ord_mr_supported_names for SlapiOrdMr") ++ } ++ ++ fn filter_ava_ord( ++ _pb: &mut PblockRef, ++ _bval_filter: &BerValRef, ++ _vals: &ValueArrayRef, ++ ) -> Result, PluginError> { ++ Ok(None) ++ } ++ ++ fn filter_compare(_a: &BerValRef, _b: &BerValRef) -> Ordering { ++ panic!("Unimplemented filter_compare") ++ } ++} ++ ++pub trait SlapiSubMr: SlapiSyntaxPlugin1 { ++ fn sub_mr_oid() -> Option<&'static str> { ++ None ++ } ++ ++ fn sub_mr_name() -> &'static str { ++ panic!("Unimplemented sub_mr_name for SlapiSubMr") ++ } ++ ++ fn sub_mr_desc() -> &'static str { ++ panic!("Unimplemented sub_mr_desc for SlapiSubMr") ++ } ++ ++ fn sub_mr_supported_names() -> Vec<&'static str> { ++ panic!("Unimplemented sub_mr_supported_names for SlapiSubMr") ++ } ++} +diff --git a/src/slapi_r_plugin/src/task.rs b/src/slapi_r_plugin/src/task.rs +new file mode 100644 +index 000000000..251ae4d82 +--- /dev/null ++++ b/src/slapi_r_plugin/src/task.rs +@@ -0,0 +1,148 @@ ++use crate::constants::LDAP_SUCCESS; ++use crate::entry::EntryRef; ++use crate::pblock::PblockRef; ++use std::ffi::CString; ++use std::os::raw::c_char; ++use std::thread; ++use std::time::Duration; ++ ++extern "C" { ++ fn slapi_plugin_new_task(ndn: *const c_char, arg: *const libc::c_void) -> *const libc::c_void; ++ fn slapi_task_dec_refcount(task: *const libc::c_void); ++ fn slapi_task_inc_refcount(task: *const libc::c_void); ++ fn slapi_task_get_refcount(task: *const libc::c_void) -> i32; ++ fn slapi_task_begin(task: *const libc::c_void, rc: i32); ++ fn slapi_task_finish(task: *const libc::c_void, rc: i32); ++ ++ fn slapi_plugin_task_register_handler( ++ ident: *const c_char, ++ cb: extern "C" fn( ++ *const libc::c_void, ++ *const libc::c_void, ++ *const libc::c_void, ++ *mut i32, ++ *mut c_char, ++ *const libc::c_void, ++ ) -> i32, ++ pb: *const libc::c_void, ++ ) -> i32; ++ fn slapi_plugin_task_unregister_handler( ++ ident: *const c_char, ++ cb: extern "C" fn( ++ *const libc::c_void, ++ *const libc::c_void, ++ *const libc::c_void, ++ *mut i32, ++ *mut c_char, ++ *const libc::c_void, ++ ) -> i32, ++ ) -> i32; ++ fn slapi_task_set_destructor_fn( ++ task: *const libc::c_void, ++ cb: extern "C" fn(*const libc::c_void), ++ ); ++} ++ ++pub struct TaskRef { ++ raw_task: *const libc::c_void, ++} ++ ++pub struct Task { ++ value: TaskRef, ++} ++ ++// Because raw pointers are not send, but we need to send the task to a thread ++// as part of the task thread spawn, we need to convince the compiler this ++// action is okay. It's probably not because C is terrible, BUT provided the ++// server and framework only touch the ref count, we are okay. ++unsafe impl Send for Task {} ++ ++pub fn task_register_handler_fn( ++ ident: &'static str, ++ cb: extern "C" fn( ++ *const libc::c_void, ++ *const libc::c_void, ++ *const libc::c_void, ++ *mut i32, ++ *mut c_char, ++ *const libc::c_void, ++ ) -> i32, ++ pb: &mut PblockRef, ++) -> i32 { ++ let cstr = CString::new(ident).expect("Invalid ident provided"); ++ unsafe { slapi_plugin_task_register_handler(cstr.as_ptr(), cb, pb.as_ptr()) } ++} ++ ++pub fn task_unregister_handler_fn( ++ ident: &'static str, ++ cb: extern "C" fn( ++ *const libc::c_void, ++ *const libc::c_void, ++ *const libc::c_void, ++ *mut i32, ++ *mut c_char, ++ *const libc::c_void, ++ ) -> i32, ++) -> i32 { ++ let cstr = CString::new(ident).expect("Invalid ident provided"); ++ unsafe { slapi_plugin_task_unregister_handler(cstr.as_ptr(), cb) } ++} ++ ++impl Task { ++ pub fn new(e: &EntryRef, arg: *const libc::c_void) -> Self { ++ let sdn = e.get_sdnref(); ++ let ndn = unsafe { sdn.as_ndnref() }; ++ let raw_task = unsafe { slapi_plugin_new_task(ndn.as_ptr(), arg) }; ++ unsafe { slapi_task_inc_refcount(raw_task) }; ++ Task { ++ value: TaskRef { raw_task }, ++ } ++ } ++ ++ pub fn begin(&self) { ++ // Indicate we begin ++ unsafe { slapi_task_begin(self.value.raw_task, 1) } ++ } ++ ++ pub fn register_destructor_fn(&mut self, cb: extern "C" fn(*const libc::c_void)) { ++ unsafe { ++ slapi_task_set_destructor_fn(self.value.raw_task, cb); ++ } ++ } ++ ++ pub fn success(self) { ++ unsafe { ++ slapi_task_finish(self.value.raw_task, LDAP_SUCCESS); ++ } ++ } ++ ++ pub fn error(self, rc: i32) { ++ unsafe { slapi_task_finish(self.value.raw_task, rc) }; ++ } ++} ++ ++impl Drop for Task { ++ fn drop(&mut self) { ++ unsafe { ++ slapi_task_dec_refcount(self.value.raw_task); ++ } ++ } ++} ++ ++impl TaskRef { ++ pub fn new(raw_task: *const libc::c_void) -> Self { ++ TaskRef { raw_task } ++ } ++ ++ pub fn block(&self) { ++ // wait for the refcount to go to 0. ++ let d = Duration::from_millis(250); ++ loop { ++ if unsafe { slapi_task_get_refcount(self.raw_task) } > 0 { ++ thread::sleep(d); ++ } else { ++ return; ++ } ++ } ++ } ++} +diff --git a/src/slapi_r_plugin/src/value.rs b/src/slapi_r_plugin/src/value.rs +new file mode 100644 +index 000000000..5a40dd279 +--- /dev/null ++++ b/src/slapi_r_plugin/src/value.rs +@@ -0,0 +1,235 @@ ++use crate::ber::{ol_berval, BerValRef}; ++use crate::dn::Sdn; ++use std::convert::{From, TryFrom}; ++use std::ffi::CString; ++use std::iter::once; ++use std::iter::FromIterator; ++use std::mem; ++use std::ops::Deref; ++use std::ptr; ++use uuid::Uuid; ++ ++extern "C" { ++ fn slapi_value_new() -> *mut slapi_value; ++ fn slapi_value_free(v: *mut *const libc::c_void); ++} ++ ++#[repr(C)] ++/// From ./ldap/servers/slapd/slap.h ++pub struct slapi_value { ++ bv: ol_berval, ++ v_csnset: *const libc::c_void, ++ v_flags: u32, ++} ++ ++pub struct ValueArrayRefIter<'a> { ++ idx: isize, ++ va_ref: &'a ValueArrayRef, ++} ++ ++impl<'a> Iterator for ValueArrayRefIter<'a> { ++ type Item = ValueRef; ++ ++ #[inline] ++ fn next(&mut self) -> Option { ++ // So long as va_ref.raw_slapi_val + offset != NULL, continue. ++ // this is so wildly unsafe, but you know, that's just daily life of C anyway ... ++ unsafe { ++ let n_ptr: *const slapi_value = *(self.va_ref.raw_slapi_val.offset(self.idx)); ++ if n_ptr.is_null() { ++ None ++ } else { ++ // Advance the iter. ++ self.idx = self.idx + 1; ++ let raw_berval: *const ol_berval = &(*n_ptr).bv as *const _; ++ Some(ValueRef { ++ raw_slapi_val: n_ptr, ++ bvr: BerValRef { raw_berval }, ++ }) ++ } ++ } ++ } ++} ++ ++pub struct ValueArrayRef { ++ raw_slapi_val: *const *const slapi_value, ++} ++ ++impl ValueArrayRef { ++ pub fn new(raw_slapi_val: *const libc::c_void) -> Self { ++ let raw_slapi_val = raw_slapi_val as *const _ as *const *const slapi_value; ++ ValueArrayRef { raw_slapi_val } ++ } ++ ++ pub fn iter(&self) -> ValueArrayRefIter { ++ ValueArrayRefIter { ++ idx: 0, ++ va_ref: &self, ++ } ++ } ++ ++ pub fn first(&self) -> Option { ++ self.iter().next() ++ } ++} ++ ++pub struct ValueArray { ++ data: Vec<*mut slapi_value>, ++ vrf: ValueArrayRef, ++} ++ ++impl Deref for ValueArray { ++ type Target = ValueArrayRef; ++ ++ fn deref(&self) -> &Self::Target { ++ &self.vrf ++ } ++} ++ ++impl ValueArray { ++ /// Take ownership of this value array, returning the pointer to the inner memory ++ /// and forgetting about it for ourself. This prevents the drop handler from freeing ++ /// the slapi_value, ie we are giving this to the 389-ds framework to manage from now. ++ pub unsafe fn take_ownership(mut self) -> *const *const slapi_value { ++ let mut vs = Vec::new(); ++ mem::swap(&mut self.data, &mut vs); ++ let bs = vs.into_boxed_slice(); ++ Box::leak(bs) as *const _ as *const *const slapi_value ++ } ++} ++ ++impl FromIterator for ValueArray { ++ fn from_iter>(iter: I) -> Self { ++ let data: Vec<*mut slapi_value> = iter ++ .into_iter() ++ .map(|v| unsafe { v.take_ownership() }) ++ .chain(once(ptr::null_mut() as *mut slapi_value)) ++ .collect(); ++ let vrf = ValueArrayRef { ++ raw_slapi_val: data.as_ptr() as *const *const slapi_value, ++ }; ++ ValueArray { data, vrf } ++ } ++} ++ ++impl Drop for ValueArray { ++ fn drop(&mut self) { ++ self.data.drain(0..).for_each(|mut v| unsafe { ++ slapi_value_free(&mut v as *mut _ as *mut *const libc::c_void); ++ }) ++ } ++} ++ ++#[derive(Debug)] ++pub struct ValueRef { ++ raw_slapi_val: *const slapi_value, ++ bvr: BerValRef, ++} ++ ++impl ValueRef { ++ pub(crate) unsafe fn as_ptr(&self) -> *const slapi_value { ++ // This is unsafe as the *const may outlive the value ref. ++ self.raw_slapi_val ++ } ++} ++ ++pub struct Value { ++ value: ValueRef, ++} ++ ++impl Value { ++ pub unsafe fn take_ownership(mut self) -> *mut slapi_value { ++ let mut n_ptr = ptr::null(); ++ mem::swap(&mut self.value.raw_slapi_val, &mut n_ptr); ++ n_ptr as *mut slapi_value ++ // Now drop will run and not care. ++ } ++} ++ ++impl Drop for Value { ++ fn drop(&mut self) { ++ if self.value.raw_slapi_val != ptr::null() { ++ // free it ++ unsafe { ++ slapi_value_free( ++ &mut self.value.raw_slapi_val as *mut _ as *mut *const libc::c_void, ++ ); ++ } ++ } ++ } ++} ++ ++impl Deref for Value { ++ type Target = ValueRef; ++ ++ fn deref(&self) -> &Self::Target { ++ &self.value ++ } ++} ++ ++impl From<&Uuid> for Value { ++ fn from(u: &Uuid) -> Self { ++ // turn the uuid to a str ++ let u_str = u.to_hyphenated().to_string(); ++ let len = u_str.len(); ++ let cstr = CString::new(u_str) ++ .expect("Invalid uuid, should never occur!") ++ .into_boxed_c_str(); ++ let s_ptr = cstr.as_ptr(); ++ Box::leak(cstr); ++ ++ let mut v = unsafe { slapi_value_new() }; ++ unsafe { ++ (*v).bv.len = len; ++ (*v).bv.data = s_ptr as *const u8; ++ } ++ ++ Value { ++ value: ValueRef::new(v as *const libc::c_void), ++ } ++ } ++} ++ ++impl ValueRef { ++ pub fn new(raw_slapi_val: *const libc::c_void) -> Self { ++ let raw_slapi_val = raw_slapi_val as *const _ as *const slapi_value; ++ let raw_berval: *const ol_berval = unsafe { &(*raw_slapi_val).bv as *const _ }; ++ ValueRef { ++ raw_slapi_val, ++ bvr: BerValRef { raw_berval }, ++ } ++ } ++} ++ ++impl TryFrom<&ValueRef> for String { ++ type Error = (); ++ ++ fn try_from(value: &ValueRef) -> Result { ++ value.bvr.into_string().ok_or(()) ++ } ++} ++ ++impl TryFrom<&ValueRef> for Sdn { ++ type Error = (); ++ ++ fn try_from(value: &ValueRef) -> Result { ++ // We need to do a middle step of moving through a cstring as ++ // bervals may not always have a trailing NULL, and sdn expects one. ++ let cdn = value.bvr.into_cstring().ok_or(())?; ++ Ok(cdn.as_c_str().into()) ++ } ++} ++ ++impl AsRef for ValueRef { ++ fn as_ref(&self) -> &ValueRef { ++ &self ++ } ++} ++ ++impl Deref for ValueRef { ++ type Target = BerValRef; ++ ++ fn deref(&self) -> &Self::Target { ++ &self.bvr ++ } ++} +-- +2.26.3 + diff --git a/SOURCES/0004-Ticket-4326-entryuuid-fixup-did-not-work-correctly-4.patch b/SOURCES/0004-Ticket-4326-entryuuid-fixup-did-not-work-correctly-4.patch new file mode 100644 index 0000000..8416726 --- /dev/null +++ b/SOURCES/0004-Ticket-4326-entryuuid-fixup-did-not-work-correctly-4.patch @@ -0,0 +1,373 @@ +From c167d6127db45d8426437c273060c8c8f7fbcb9b Mon Sep 17 00:00:00 2001 +From: Firstyear +Date: Wed, 23 Sep 2020 09:19:34 +1000 +Subject: [PATCH 04/12] Ticket 4326 - entryuuid fixup did not work correctly + (#4328) + +Bug Description: due to an oversight in how fixup tasks +worked, the entryuuid fixup task did not work correctly and +would not persist over restarts. + +Fix Description: Correctly implement entryuuid fixup. + +fixes: #4326 + +Author: William Brown + +Review by: mreynolds (thanks!) +--- + .../tests/suites/entryuuid/basic_test.py | 24 +++- + src/plugins/entryuuid/src/lib.rs | 43 ++++++- + src/slapi_r_plugin/src/constants.rs | 5 + + src/slapi_r_plugin/src/entry.rs | 8 ++ + src/slapi_r_plugin/src/lib.rs | 2 + + src/slapi_r_plugin/src/macros.rs | 2 +- + src/slapi_r_plugin/src/modify.rs | 118 ++++++++++++++++++ + src/slapi_r_plugin/src/pblock.rs | 7 ++ + src/slapi_r_plugin/src/value.rs | 4 + + 9 files changed, 206 insertions(+), 7 deletions(-) + create mode 100644 src/slapi_r_plugin/src/modify.rs + +diff --git a/dirsrvtests/tests/suites/entryuuid/basic_test.py b/dirsrvtests/tests/suites/entryuuid/basic_test.py +index beb73701d..4d8a40909 100644 +--- a/dirsrvtests/tests/suites/entryuuid/basic_test.py ++++ b/dirsrvtests/tests/suites/entryuuid/basic_test.py +@@ -12,6 +12,7 @@ import time + import shutil + from lib389.idm.user import nsUserAccounts, UserAccounts + from lib389.idm.account import Accounts ++from lib389.idm.domain import Domain + from lib389.topologies import topology_st as topology + from lib389.backend import Backends + from lib389.paths import Paths +@@ -190,6 +191,7 @@ def test_entryuuid_fixup_task(topology): + 3. Enable the entryuuid plugin + 4. Run the fixup + 5. Assert the entryuuid now exists ++ 6. Restart and check they persist + + :expectedresults: + 1. Success +@@ -197,6 +199,7 @@ def test_entryuuid_fixup_task(topology): + 3. Success + 4. Success + 5. Suddenly EntryUUID! ++ 6. Still has EntryUUID! + """ + # 1. Disable the plugin + plug = EntryUUIDPlugin(topology.standalone) +@@ -220,7 +223,22 @@ def test_entryuuid_fixup_task(topology): + assert(task.is_complete() and task.get_exit_code() == 0) + topology.standalone.config.loglevel(vals=(ErrorLog.DEFAULT,)) + +- # 5. Assert the uuid. +- euuid = account.get_attr_val_utf8('entryUUID') +- assert(euuid is not None) ++ # 5.1 Assert the uuid on the user. ++ euuid_user = account.get_attr_val_utf8('entryUUID') ++ assert(euuid_user is not None) ++ ++ # 5.2 Assert it on the domain entry. ++ domain = Domain(topology.standalone, dn=DEFAULT_SUFFIX) ++ euuid_domain = domain.get_attr_val_utf8('entryUUID') ++ assert(euuid_domain is not None) ++ ++ # Assert it persists after a restart. ++ topology.standalone.restart() ++ # 6.1 Assert the uuid on the use. ++ euuid_user_2 = account.get_attr_val_utf8('entryUUID') ++ assert(euuid_user_2 == euuid_user) ++ ++ # 6.2 Assert it on the domain entry. ++ euuid_domain_2 = domain.get_attr_val_utf8('entryUUID') ++ assert(euuid_domain_2 == euuid_domain) + +diff --git a/src/plugins/entryuuid/src/lib.rs b/src/plugins/entryuuid/src/lib.rs +index 6b5e8d1bb..92977db05 100644 +--- a/src/plugins/entryuuid/src/lib.rs ++++ b/src/plugins/entryuuid/src/lib.rs +@@ -187,9 +187,46 @@ impl SlapiPlugin3 for EntryUuid { + } + } + +-pub fn entryuuid_fixup_mapfn(mut e: EntryRef, _data: &()) -> Result<(), PluginError> { +- assign_uuid(&mut e); +- Ok(()) ++pub fn entryuuid_fixup_mapfn(e: &EntryRef, _data: &()) -> Result<(), PluginError> { ++ /* Supply a modification to the entry. */ ++ let sdn = e.get_sdnref(); ++ ++ /* Sanity check that entryuuid doesn't already exist */ ++ if e.contains_attr("entryUUID") { ++ log_error!( ++ ErrorLevel::Trace, ++ "skipping fixup for -> {}", ++ sdn.to_dn_string() ++ ); ++ return Ok(()); ++ } ++ ++ // Setup the modifications ++ let mut mods = SlapiMods::new(); ++ ++ let u: Uuid = Uuid::new_v4(); ++ let uuid_value = Value::from(&u); ++ let values: ValueArray = std::iter::once(uuid_value).collect(); ++ mods.append(ModType::Replace, "entryUUID", values); ++ ++ /* */ ++ let lmod = Modify::new(&sdn, mods, plugin_id())?; ++ ++ match lmod.execute() { ++ Ok(_) => { ++ log_error!(ErrorLevel::Trace, "fixed-up -> {}", sdn.to_dn_string()); ++ Ok(()) ++ } ++ Err(e) => { ++ log_error!( ++ ErrorLevel::Error, ++ "entryuuid_fixup_mapfn -> fixup failed -> {} {:?}", ++ sdn.to_dn_string(), ++ e ++ ); ++ Err(PluginError::GenericFailure) ++ } ++ } + } + + #[cfg(test)] +diff --git a/src/slapi_r_plugin/src/constants.rs b/src/slapi_r_plugin/src/constants.rs +index cf76ccbdb..34845c2f4 100644 +--- a/src/slapi_r_plugin/src/constants.rs ++++ b/src/slapi_r_plugin/src/constants.rs +@@ -5,6 +5,11 @@ use std::os::raw::c_char; + pub const LDAP_SUCCESS: i32 = 0; + pub const PLUGIN_DEFAULT_PRECEDENCE: i32 = 50; + ++#[repr(i32)] ++pub enum OpFlags { ++ ByassReferrals = 0x0040_0000, ++} ++ + #[repr(i32)] + /// The set of possible function handles we can register via the pblock. These + /// values correspond to slapi-plugin.h. +diff --git a/src/slapi_r_plugin/src/entry.rs b/src/slapi_r_plugin/src/entry.rs +index 034efe692..22ae45189 100644 +--- a/src/slapi_r_plugin/src/entry.rs ++++ b/src/slapi_r_plugin/src/entry.rs +@@ -70,6 +70,14 @@ impl EntryRef { + } + } + ++ pub fn contains_attr(&self, name: &str) -> bool { ++ let cname = CString::new(name).expect("invalid attr name"); ++ let va = unsafe { slapi_entry_attr_get_valuearray(self.raw_e, cname.as_ptr()) }; ++ ++ // If it's null, it's not present, so flip the logic. ++ !va.is_null() ++ } ++ + pub fn add_value(&mut self, a: &str, v: &ValueRef) { + // turn the attr to a c string. + // TODO FIX +diff --git a/src/slapi_r_plugin/src/lib.rs b/src/slapi_r_plugin/src/lib.rs +index d7fc22e52..076907bae 100644 +--- a/src/slapi_r_plugin/src/lib.rs ++++ b/src/slapi_r_plugin/src/lib.rs +@@ -9,6 +9,7 @@ pub mod dn; + pub mod entry; + pub mod error; + pub mod log; ++pub mod modify; + pub mod pblock; + pub mod plugin; + pub mod search; +@@ -24,6 +25,7 @@ pub mod prelude { + pub use crate::entry::EntryRef; + pub use crate::error::{DseCallbackStatus, LDAPError, PluginError, RPluginError}; + pub use crate::log::{log_error, ErrorLevel}; ++ pub use crate::modify::{ModType, Modify, SlapiMods}; + pub use crate::pblock::{Pblock, PblockRef}; + pub use crate::plugin::{register_plugin_ext, PluginIdRef, SlapiPlugin3}; + pub use crate::search::{Search, SearchScope}; +diff --git a/src/slapi_r_plugin/src/macros.rs b/src/slapi_r_plugin/src/macros.rs +index 030449632..bc8dfa60f 100644 +--- a/src/slapi_r_plugin/src/macros.rs ++++ b/src/slapi_r_plugin/src/macros.rs +@@ -825,7 +825,7 @@ macro_rules! slapi_r_search_callback_mapfn { + let e = EntryRef::new(raw_e); + let data_ptr = raw_data as *const _; + let data = unsafe { &(*data_ptr) }; +- match $cb_mod_ident(e, data) { ++ match $cb_mod_ident(&e, data) { + Ok(_) => LDAPError::Success as i32, + Err(e) => e as i32, + } +diff --git a/src/slapi_r_plugin/src/modify.rs b/src/slapi_r_plugin/src/modify.rs +new file mode 100644 +index 000000000..30864377a +--- /dev/null ++++ b/src/slapi_r_plugin/src/modify.rs +@@ -0,0 +1,118 @@ ++use crate::constants::OpFlags; ++use crate::dn::SdnRef; ++use crate::error::{LDAPError, PluginError}; ++use crate::pblock::Pblock; ++use crate::plugin::PluginIdRef; ++use crate::value::{slapi_value, ValueArray}; ++ ++use std::ffi::CString; ++use std::ops::{Deref, DerefMut}; ++use std::os::raw::c_char; ++ ++extern "C" { ++ fn slapi_modify_internal_set_pb_ext( ++ pb: *const libc::c_void, ++ dn: *const libc::c_void, ++ mods: *const *const libc::c_void, ++ controls: *const *const libc::c_void, ++ uniqueid: *const c_char, ++ plugin_ident: *const libc::c_void, ++ op_flags: i32, ++ ); ++ fn slapi_modify_internal_pb(pb: *const libc::c_void); ++ fn slapi_mods_free(smods: *const *const libc::c_void); ++ fn slapi_mods_get_ldapmods_byref(smods: *const libc::c_void) -> *const *const libc::c_void; ++ fn slapi_mods_new() -> *const libc::c_void; ++ fn slapi_mods_add_mod_values( ++ smods: *const libc::c_void, ++ mtype: i32, ++ attrtype: *const c_char, ++ value: *const *const slapi_value, ++ ); ++} ++ ++#[derive(Debug)] ++#[repr(i32)] ++pub enum ModType { ++ Add = 0, ++ Delete = 1, ++ Replace = 2, ++} ++ ++pub struct SlapiMods { ++ inner: *const libc::c_void, ++ vas: Vec, ++} ++ ++impl Drop for SlapiMods { ++ fn drop(&mut self) { ++ unsafe { slapi_mods_free(&self.inner as *const *const libc::c_void) } ++ } ++} ++ ++impl SlapiMods { ++ pub fn new() -> Self { ++ SlapiMods { ++ inner: unsafe { slapi_mods_new() }, ++ vas: Vec::new(), ++ } ++ } ++ ++ pub fn append(&mut self, modtype: ModType, attrtype: &str, values: ValueArray) { ++ // We can get the value array pointer here to push to the inner ++ // because the internal pointers won't change even when we push them ++ // to the list to preserve their lifetime. ++ let vas = values.as_ptr(); ++ // We take ownership of this to ensure it lives as least as long as our ++ // slapimods structure. ++ self.vas.push(values); ++ // now we can insert these to the modes. ++ let c_attrtype = CString::new(attrtype).expect("failed to allocate attrtype"); ++ unsafe { slapi_mods_add_mod_values(self.inner, modtype as i32, c_attrtype.as_ptr(), vas) }; ++ } ++} ++ ++pub struct Modify { ++ pb: Pblock, ++ mods: SlapiMods, ++} ++ ++pub struct ModifyResult { ++ pb: Pblock, ++} ++ ++impl Modify { ++ pub fn new(dn: &SdnRef, mods: SlapiMods, plugin_id: PluginIdRef) -> Result { ++ let pb = Pblock::new(); ++ let lmods = unsafe { slapi_mods_get_ldapmods_byref(mods.inner) }; ++ // OP_FLAG_ACTION_LOG_ACCESS ++ ++ unsafe { ++ slapi_modify_internal_set_pb_ext( ++ pb.deref().as_ptr(), ++ dn.as_ptr(), ++ lmods, ++ std::ptr::null(), ++ std::ptr::null(), ++ plugin_id.raw_pid, ++ OpFlags::ByassReferrals as i32, ++ ) ++ }; ++ ++ Ok(Modify { pb, mods }) ++ } ++ ++ pub fn execute(self) -> Result { ++ let Modify { ++ mut pb, ++ mods: _mods, ++ } = self; ++ unsafe { slapi_modify_internal_pb(pb.deref().as_ptr()) }; ++ let result = pb.get_op_result(); ++ ++ match result { ++ 0 => Ok(ModifyResult { pb }), ++ _e => Err(LDAPError::from(result)), ++ } ++ } ++} +diff --git a/src/slapi_r_plugin/src/pblock.rs b/src/slapi_r_plugin/src/pblock.rs +index b69ce1680..0f83914f3 100644 +--- a/src/slapi_r_plugin/src/pblock.rs ++++ b/src/slapi_r_plugin/src/pblock.rs +@@ -11,6 +11,7 @@ pub use crate::log::{log_error, ErrorLevel}; + extern "C" { + fn slapi_pblock_set(pb: *const libc::c_void, arg: i32, value: *const libc::c_void) -> i32; + fn slapi_pblock_get(pb: *const libc::c_void, arg: i32, value: *const libc::c_void) -> i32; ++ fn slapi_pblock_destroy(pb: *const libc::c_void); + fn slapi_pblock_new() -> *const libc::c_void; + } + +@@ -41,6 +42,12 @@ impl DerefMut for Pblock { + } + } + ++impl Drop for Pblock { ++ fn drop(&mut self) { ++ unsafe { slapi_pblock_destroy(self.value.raw_pb) } ++ } ++} ++ + pub struct PblockRef { + raw_pb: *const libc::c_void, + } +diff --git a/src/slapi_r_plugin/src/value.rs b/src/slapi_r_plugin/src/value.rs +index 5a40dd279..46246837a 100644 +--- a/src/slapi_r_plugin/src/value.rs ++++ b/src/slapi_r_plugin/src/value.rs +@@ -96,6 +96,10 @@ impl ValueArray { + let bs = vs.into_boxed_slice(); + Box::leak(bs) as *const _ as *const *const slapi_value + } ++ ++ pub fn as_ptr(&self) -> *const *const slapi_value { ++ self.data.as_ptr() as *const *const slapi_value ++ } + } + + impl FromIterator for ValueArray { +-- +2.26.3 + diff --git a/SOURCES/0005-Issue-4498-BUG-entryuuid-replication-may-not-work-45.patch b/SOURCES/0005-Issue-4498-BUG-entryuuid-replication-may-not-work-45.patch new file mode 100644 index 0000000..91de38c --- /dev/null +++ b/SOURCES/0005-Issue-4498-BUG-entryuuid-replication-may-not-work-45.patch @@ -0,0 +1,192 @@ +From b2e0a1d405d15383064e547fd15008bc136d3efe Mon Sep 17 00:00:00 2001 +From: Firstyear +Date: Thu, 17 Dec 2020 08:22:23 +1000 +Subject: [PATCH 05/12] Issue 4498 - BUG - entryuuid replication may not work + (#4503) + +Bug Description: EntryUUID can be duplicated in replication, +due to a missing check in assign_uuid + +Fix Description: Add a test case to determine how this occurs, +and add the correct check for existing entryUUID. + +fixes: https://github.com/389ds/389-ds-base/issues/4498 + +Author: William Brown + +Review by: @mreynolds389 +--- + .../tests/suites/entryuuid/replicated_test.py | 77 +++++++++++++++++++ + rpm.mk | 2 +- + src/plugins/entryuuid/src/lib.rs | 20 ++++- + src/slapi_r_plugin/src/constants.rs | 2 + + src/slapi_r_plugin/src/pblock.rs | 7 ++ + 5 files changed, 106 insertions(+), 2 deletions(-) + create mode 100644 dirsrvtests/tests/suites/entryuuid/replicated_test.py + +diff --git a/dirsrvtests/tests/suites/entryuuid/replicated_test.py b/dirsrvtests/tests/suites/entryuuid/replicated_test.py +new file mode 100644 +index 000000000..a2ebc8ff7 +--- /dev/null ++++ b/dirsrvtests/tests/suites/entryuuid/replicated_test.py +@@ -0,0 +1,77 @@ ++# --- BEGIN COPYRIGHT BLOCK --- ++# Copyright (C) 2020 William Brown ++# All rights reserved. ++# ++# License: GPL (version 3 or any later version). ++# See LICENSE for details. ++# --- END COPYRIGHT BLOCK --- ++ ++import ldap ++import pytest ++import logging ++from lib389.topologies import topology_m2 as topo_m2 ++from lib389.idm.user import nsUserAccounts ++from lib389.paths import Paths ++from lib389.utils import ds_is_older ++from lib389._constants import * ++from lib389.replica import ReplicationManager ++ ++default_paths = Paths() ++ ++pytestmark = pytest.mark.tier1 ++ ++@pytest.mark.skipif(not default_paths.rust_enabled or ds_is_older('1.4.2.0'), reason="Entryuuid is not available in older versions") ++ ++def test_entryuuid_with_replication(topo_m2): ++ """ Check that entryuuid works with replication ++ ++ :id: a5f15bf9-7f63-473a-840c-b9037b787024 ++ ++ :setup: two node mmr ++ ++ :steps: ++ 1. Create an entry on one server ++ 2. Wait for replication ++ 3. Assert it is on the second ++ ++ :expectedresults: ++ 1. Success ++ 1. Success ++ 1. Success ++ """ ++ ++ server_a = topo_m2.ms["supplier1"] ++ server_b = topo_m2.ms["supplier2"] ++ server_a.config.loglevel(vals=(ErrorLog.DEFAULT,ErrorLog.TRACE)) ++ server_b.config.loglevel(vals=(ErrorLog.DEFAULT,ErrorLog.TRACE)) ++ ++ repl = ReplicationManager(DEFAULT_SUFFIX) ++ ++ account_a = nsUserAccounts(server_a, DEFAULT_SUFFIX).create_test_user(uid=2000) ++ euuid_a = account_a.get_attr_vals_utf8('entryUUID') ++ print("🧩 %s" % euuid_a) ++ assert(euuid_a is not None) ++ assert(len(euuid_a) == 1) ++ ++ repl.wait_for_replication(server_a, server_b) ++ ++ account_b = nsUserAccounts(server_b, DEFAULT_SUFFIX).get("test_user_2000") ++ euuid_b = account_b.get_attr_vals_utf8('entryUUID') ++ print("🧩 %s" % euuid_b) ++ ++ server_a.config.loglevel(vals=(ErrorLog.DEFAULT,)) ++ server_b.config.loglevel(vals=(ErrorLog.DEFAULT,)) ++ ++ assert(euuid_b is not None) ++ assert(len(euuid_b) == 1) ++ assert(euuid_b == euuid_a) ++ ++ account_b.set("description", "update") ++ repl.wait_for_replication(server_b, server_a) ++ ++ euuid_c = account_a.get_attr_vals_utf8('entryUUID') ++ print("🧩 %s" % euuid_c) ++ assert(euuid_c is not None) ++ assert(len(euuid_c) == 1) ++ assert(euuid_c == euuid_a) ++ +diff --git a/rpm.mk b/rpm.mk +index 02f5bba37..d1cdff7df 100644 +--- a/rpm.mk ++++ b/rpm.mk +@@ -25,7 +25,7 @@ TSAN_ON = 0 + # Undefined Behaviour Sanitizer + UBSAN_ON = 0 + +-RUST_ON = 0 ++RUST_ON = 1 + + # PERL_ON is deprecated and turns on the LEGACY_ON, this for not breaking people's workflows. + PERL_ON = 1 +diff --git a/src/plugins/entryuuid/src/lib.rs b/src/plugins/entryuuid/src/lib.rs +index 92977db05..0197c5e83 100644 +--- a/src/plugins/entryuuid/src/lib.rs ++++ b/src/plugins/entryuuid/src/lib.rs +@@ -30,6 +30,16 @@ slapi_r_search_callback_mapfn!(entryuuid, entryuuid_fixup_cb, entryuuid_fixup_ma + fn assign_uuid(e: &mut EntryRef) { + let sdn = e.get_sdnref(); + ++ // 🚧 safety barrier 🚧 ++ if e.contains_attr("entryUUID") { ++ log_error!( ++ ErrorLevel::Trace, ++ "assign_uuid -> entryUUID exists, skipping dn {}", ++ sdn.to_dn_string() ++ ); ++ return; ++ } ++ + // We could consider making these lazy static. + let config_sdn = Sdn::try_from("cn=config").expect("Invalid static dn"); + let schema_sdn = Sdn::try_from("cn=schema").expect("Invalid static dn"); +@@ -66,7 +76,15 @@ impl SlapiPlugin3 for EntryUuid { + } + + fn betxn_pre_add(pb: &mut PblockRef) -> Result<(), PluginError> { +- log_error!(ErrorLevel::Trace, "betxn_pre_add"); ++ if pb.get_is_replicated_operation() { ++ log_error!( ++ ErrorLevel::Trace, ++ "betxn_pre_add -> replicated operation, will not change" ++ ); ++ return Ok(()); ++ } ++ ++ log_error!(ErrorLevel::Trace, "betxn_pre_add -> start"); + + let mut e = pb.get_op_add_entryref().map_err(|_| PluginError::Pblock)?; + assign_uuid(&mut e); +diff --git a/src/slapi_r_plugin/src/constants.rs b/src/slapi_r_plugin/src/constants.rs +index 34845c2f4..aa0691acc 100644 +--- a/src/slapi_r_plugin/src/constants.rs ++++ b/src/slapi_r_plugin/src/constants.rs +@@ -164,6 +164,8 @@ pub(crate) enum PblockType { + AddEntry = 60, + /// SLAPI_BACKEND + Backend = 130, ++ /// SLAPI_IS_REPLICATED_OPERATION ++ IsReplicationOperation = 142, + /// SLAPI_PLUGIN_MR_NAMES + MRNames = 624, + /// SLAPI_PLUGIN_SYNTAX_NAMES +diff --git a/src/slapi_r_plugin/src/pblock.rs b/src/slapi_r_plugin/src/pblock.rs +index 0f83914f3..718ff2ca7 100644 +--- a/src/slapi_r_plugin/src/pblock.rs ++++ b/src/slapi_r_plugin/src/pblock.rs +@@ -279,4 +279,11 @@ impl PblockRef { + pub fn get_op_result(&mut self) -> i32 { + self.get_value_i32(PblockType::OpResult).unwrap_or(-1) + } ++ ++ pub fn get_is_replicated_operation(&mut self) -> bool { ++ let i = self.get_value_i32(PblockType::IsReplicationOperation).unwrap_or(0); ++ // Because rust returns the result of the last evaluation, we can ++ // just return if not equal 0. ++ i != 0 ++ } + } +-- +2.26.3 + diff --git a/SOURCES/0006-Issue-4421-Unable-to-build-with-Rust-enabled-in-clos.patch b/SOURCES/0006-Issue-4421-Unable-to-build-with-Rust-enabled-in-clos.patch new file mode 100644 index 0000000..0affdf6 --- /dev/null +++ b/SOURCES/0006-Issue-4421-Unable-to-build-with-Rust-enabled-in-clos.patch @@ -0,0 +1,626 @@ +From 04c44e74503a842561b6c6e58001faf86d924b20 Mon Sep 17 00:00:00 2001 +From: Mark Reynolds +Date: Mon, 7 Dec 2020 11:00:45 -0500 +Subject: [PATCH 06/12] Issue 4421 - Unable to build with Rust enabled in + closed environment + +Description: Add Makefile flags and update rpm.mk that allow updating + and downloading all the cargo/rust dependencies. This is + needed for nightly tests and upstream/downstream releases. + +Fixes: https://github.com/389ds/389-ds-base/issues/4421 + +Reviewed by: firstyear(Thanks!) +--- + rpm.mk | 3 +- + rpm/389-ds-base.spec.in | 2 +- + src/Cargo.lock | 563 ---------------------------------------- + 3 files changed, 3 insertions(+), 565 deletions(-) + delete mode 100644 src/Cargo.lock + +diff --git a/rpm.mk b/rpm.mk +index d1cdff7df..ef810c63c 100644 +--- a/rpm.mk ++++ b/rpm.mk +@@ -44,6 +44,7 @@ update-cargo-dependencies: + cargo update --manifest-path=./src/Cargo.toml + + download-cargo-dependencies: ++ cargo update --manifest-path=./src/Cargo.toml + cargo vendor --manifest-path=./src/Cargo.toml + cargo fetch --manifest-path=./src/Cargo.toml + tar -czf vendor.tar.gz vendor +@@ -114,7 +115,7 @@ rpmbuildprep: + cp dist/sources/$(JEMALLOC_TARBALL) $(RPMBUILD)/SOURCES/ ; \ + fi + +-srpms: rpmroot srpmdistdir tarballs rpmbuildprep ++srpms: rpmroot srpmdistdir download-cargo-dependencies tarballs rpmbuildprep + rpmbuild --define "_topdir $(RPMBUILD)" -bs $(RPMBUILD)/SPECS/$(PACKAGE).spec + cp $(RPMBUILD)/SRPMS/$(RPM_NAME_VERSION)*.src.rpm dist/srpms/ + rm -rf $(RPMBUILD) +diff --git a/rpm/389-ds-base.spec.in b/rpm/389-ds-base.spec.in +index b9f85489b..d80de8422 100644 +--- a/rpm/389-ds-base.spec.in ++++ b/rpm/389-ds-base.spec.in +@@ -357,7 +357,7 @@ UBSAN_FLAGS="--enable-ubsan --enable-debug" + %endif + + %if %{use_rust} +-RUST_FLAGS="--enable-rust" ++RUST_FLAGS="--enable-rust --enable-rust-offline" + %endif + + %if %{use_legacy} +diff --git a/src/Cargo.lock b/src/Cargo.lock +deleted file mode 100644 +index 33d7b8f23..000000000 +--- a/src/Cargo.lock ++++ /dev/null +@@ -1,563 +0,0 @@ +-# This file is automatically @generated by Cargo. +-# It is not intended for manual editing. +-[[package]] +-name = "ansi_term" +-version = "0.11.0" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +-dependencies = [ +- "winapi", +-] +- +-[[package]] +-name = "atty" +-version = "0.2.14" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +-dependencies = [ +- "hermit-abi", +- "libc", +- "winapi", +-] +- +-[[package]] +-name = "autocfg" +-version = "1.0.1" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +- +-[[package]] +-name = "base64" +-version = "0.13.0" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +- +-[[package]] +-name = "bitflags" +-version = "1.2.1" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +- +-[[package]] +-name = "byteorder" +-version = "1.4.3" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +- +-[[package]] +-name = "cbindgen" +-version = "0.9.1" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "9daec6140ab4dcd38c3dd57e580b59a621172a526ac79f1527af760a55afeafd" +-dependencies = [ +- "clap", +- "log", +- "proc-macro2", +- "quote", +- "serde", +- "serde_json", +- "syn", +- "tempfile", +- "toml", +-] +- +-[[package]] +-name = "cc" +-version = "1.0.67" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd" +-dependencies = [ +- "jobserver", +-] +- +-[[package]] +-name = "cfg-if" +-version = "1.0.0" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +- +-[[package]] +-name = "clap" +-version = "2.33.3" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" +-dependencies = [ +- "ansi_term", +- "atty", +- "bitflags", +- "strsim", +- "textwrap", +- "unicode-width", +- "vec_map", +-] +- +-[[package]] +-name = "entryuuid" +-version = "0.1.0" +-dependencies = [ +- "cc", +- "libc", +- "paste", +- "slapi_r_plugin", +- "uuid", +-] +- +-[[package]] +-name = "entryuuid_syntax" +-version = "0.1.0" +-dependencies = [ +- "cc", +- "libc", +- "paste", +- "slapi_r_plugin", +- "uuid", +-] +- +-[[package]] +-name = "fernet" +-version = "0.1.4" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "93804560e638370a8be6d59ce71ed803e55e230abdbf42598e666b41adda9b1f" +-dependencies = [ +- "base64", +- "byteorder", +- "getrandom", +- "openssl", +- "zeroize", +-] +- +-[[package]] +-name = "foreign-types" +-version = "0.3.2" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +-dependencies = [ +- "foreign-types-shared", +-] +- +-[[package]] +-name = "foreign-types-shared" +-version = "0.1.1" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +- +-[[package]] +-name = "getrandom" +-version = "0.2.3" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" +-dependencies = [ +- "cfg-if", +- "libc", +- "wasi", +-] +- +-[[package]] +-name = "hermit-abi" +-version = "0.1.18" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" +-dependencies = [ +- "libc", +-] +- +-[[package]] +-name = "itoa" +-version = "0.4.7" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" +- +-[[package]] +-name = "jobserver" +-version = "0.1.22" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "972f5ae5d1cb9c6ae417789196c803205313edde988685da5e3aae0827b9e7fd" +-dependencies = [ +- "libc", +-] +- +-[[package]] +-name = "lazy_static" +-version = "1.4.0" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +- +-[[package]] +-name = "libc" +-version = "0.2.94" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "18794a8ad5b29321f790b55d93dfba91e125cb1a9edbd4f8e3150acc771c1a5e" +- +-[[package]] +-name = "librnsslapd" +-version = "0.1.0" +-dependencies = [ +- "cbindgen", +- "libc", +- "slapd", +-] +- +-[[package]] +-name = "librslapd" +-version = "0.1.0" +-dependencies = [ +- "cbindgen", +- "libc", +- "slapd", +-] +- +-[[package]] +-name = "log" +-version = "0.4.14" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +-dependencies = [ +- "cfg-if", +-] +- +-[[package]] +-name = "once_cell" +-version = "1.7.2" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3" +- +-[[package]] +-name = "openssl" +-version = "0.10.34" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "6d7830286ad6a3973c0f1d9b73738f69c76b739301d0229c4b96501695cbe4c8" +-dependencies = [ +- "bitflags", +- "cfg-if", +- "foreign-types", +- "libc", +- "once_cell", +- "openssl-sys", +-] +- +-[[package]] +-name = "openssl-sys" +-version = "0.9.63" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "b6b0d6fb7d80f877617dfcb014e605e2b5ab2fb0afdf27935219bb6bd984cb98" +-dependencies = [ +- "autocfg", +- "cc", +- "libc", +- "pkg-config", +- "vcpkg", +-] +- +-[[package]] +-name = "paste" +-version = "0.1.18" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "45ca20c77d80be666aef2b45486da86238fabe33e38306bd3118fe4af33fa880" +-dependencies = [ +- "paste-impl", +- "proc-macro-hack", +-] +- +-[[package]] +-name = "paste-impl" +-version = "0.1.18" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "d95a7db200b97ef370c8e6de0088252f7e0dfff7d047a28528e47456c0fc98b6" +-dependencies = [ +- "proc-macro-hack", +-] +- +-[[package]] +-name = "pkg-config" +-version = "0.3.19" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" +- +-[[package]] +-name = "ppv-lite86" +-version = "0.2.10" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" +- +-[[package]] +-name = "proc-macro-hack" +-version = "0.5.19" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" +- +-[[package]] +-name = "proc-macro2" +-version = "1.0.27" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038" +-dependencies = [ +- "unicode-xid", +-] +- +-[[package]] +-name = "quote" +-version = "1.0.9" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +-dependencies = [ +- "proc-macro2", +-] +- +-[[package]] +-name = "rand" +-version = "0.8.3" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e" +-dependencies = [ +- "libc", +- "rand_chacha", +- "rand_core", +- "rand_hc", +-] +- +-[[package]] +-name = "rand_chacha" +-version = "0.3.0" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d" +-dependencies = [ +- "ppv-lite86", +- "rand_core", +-] +- +-[[package]] +-name = "rand_core" +-version = "0.6.2" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7" +-dependencies = [ +- "getrandom", +-] +- +-[[package]] +-name = "rand_hc" +-version = "0.3.0" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73" +-dependencies = [ +- "rand_core", +-] +- +-[[package]] +-name = "redox_syscall" +-version = "0.2.8" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "742739e41cd49414de871ea5e549afb7e2a3ac77b589bcbebe8c82fab37147fc" +-dependencies = [ +- "bitflags", +-] +- +-[[package]] +-name = "remove_dir_all" +-version = "0.5.3" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +-dependencies = [ +- "winapi", +-] +- +-[[package]] +-name = "rsds" +-version = "0.1.0" +- +-[[package]] +-name = "ryu" +-version = "1.0.5" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" +- +-[[package]] +-name = "serde" +-version = "1.0.126" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03" +-dependencies = [ +- "serde_derive", +-] +- +-[[package]] +-name = "serde_derive" +-version = "1.0.126" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43" +-dependencies = [ +- "proc-macro2", +- "quote", +- "syn", +-] +- +-[[package]] +-name = "serde_json" +-version = "1.0.64" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79" +-dependencies = [ +- "itoa", +- "ryu", +- "serde", +-] +- +-[[package]] +-name = "slapd" +-version = "0.1.0" +-dependencies = [ +- "fernet", +-] +- +-[[package]] +-name = "slapi_r_plugin" +-version = "0.1.0" +-dependencies = [ +- "lazy_static", +- "libc", +- "paste", +- "uuid", +-] +- +-[[package]] +-name = "strsim" +-version = "0.8.0" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +- +-[[package]] +-name = "syn" +-version = "1.0.72" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "a1e8cdbefb79a9a5a65e0db8b47b723ee907b7c7f8496c76a1770b5c310bab82" +-dependencies = [ +- "proc-macro2", +- "quote", +- "unicode-xid", +-] +- +-[[package]] +-name = "synstructure" +-version = "0.12.4" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701" +-dependencies = [ +- "proc-macro2", +- "quote", +- "syn", +- "unicode-xid", +-] +- +-[[package]] +-name = "tempfile" +-version = "3.2.0" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" +-dependencies = [ +- "cfg-if", +- "libc", +- "rand", +- "redox_syscall", +- "remove_dir_all", +- "winapi", +-] +- +-[[package]] +-name = "textwrap" +-version = "0.11.0" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +-dependencies = [ +- "unicode-width", +-] +- +-[[package]] +-name = "toml" +-version = "0.5.8" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +-dependencies = [ +- "serde", +-] +- +-[[package]] +-name = "unicode-width" +-version = "0.1.8" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" +- +-[[package]] +-name = "unicode-xid" +-version = "0.2.2" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" +- +-[[package]] +-name = "uuid" +-version = "0.8.2" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" +-dependencies = [ +- "getrandom", +-] +- +-[[package]] +-name = "vcpkg" +-version = "0.2.12" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "cbdbff6266a24120518560b5dc983096efb98462e51d0d68169895b237be3e5d" +- +-[[package]] +-name = "vec_map" +-version = "0.8.2" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" +- +-[[package]] +-name = "wasi" +-version = "0.10.2+wasi-snapshot-preview1" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" +- +-[[package]] +-name = "winapi" +-version = "0.3.9" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +-dependencies = [ +- "winapi-i686-pc-windows-gnu", +- "winapi-x86_64-pc-windows-gnu", +-] +- +-[[package]] +-name = "winapi-i686-pc-windows-gnu" +-version = "0.4.0" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +- +-[[package]] +-name = "winapi-x86_64-pc-windows-gnu" +-version = "0.4.0" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +- +-[[package]] +-name = "zeroize" +-version = "1.3.0" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd" +-dependencies = [ +- "zeroize_derive", +-] +- +-[[package]] +-name = "zeroize_derive" +-version = "1.1.0" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "a2c1e130bebaeab2f23886bf9acbaca14b092408c452543c857f66399cd6dab1" +-dependencies = [ +- "proc-macro2", +- "quote", +- "syn", +- "synstructure", +-] +-- +2.26.3 + diff --git a/SOURCES/0007-Ticket-51175-resolve-plugin-name-leaking.patch b/SOURCES/0007-Ticket-51175-resolve-plugin-name-leaking.patch new file mode 100644 index 0000000..f5edc9d --- /dev/null +++ b/SOURCES/0007-Ticket-51175-resolve-plugin-name-leaking.patch @@ -0,0 +1,412 @@ +From 279bdb5148eb0b67ddab40c4dd9d08e9e1672f13 Mon Sep 17 00:00:00 2001 +From: William Brown +Date: Fri, 26 Jun 2020 10:27:56 +1000 +Subject: [PATCH 07/12] Ticket 51175 - resolve plugin name leaking + +Bug Description: Previously pblock.c assumed that all plugin +names were static c strings. Rust can't create static C +strings, so these were intentionally leaked. + +Fix Description: Rather than leak these, we do a dup/free +through the slapiplugin struct instead, meaning we can use +ephemeral, and properly managed strings in rust. This does not +affect any other existing code which will still handle the +static strings correctly. + +https://pagure.io/389-ds-base/issue/51175 + +Author: William Brown + +Review by: mreynolds, tbordaz (Thanks!) +--- + Makefile.am | 1 + + configure.ac | 2 +- + ldap/servers/slapd/pagedresults.c | 6 +-- + ldap/servers/slapd/pblock.c | 9 ++-- + ldap/servers/slapd/plugin.c | 7 +++ + ldap/servers/slapd/pw_verify.c | 1 + + ldap/servers/slapd/tools/pwenc.c | 2 +- + src/slapi_r_plugin/README.md | 6 +-- + src/slapi_r_plugin/src/charray.rs | 32 ++++++++++++++ + src/slapi_r_plugin/src/lib.rs | 8 ++-- + src/slapi_r_plugin/src/macros.rs | 17 +++++--- + src/slapi_r_plugin/src/syntax_plugin.rs | 57 +++++++------------------ + 12 files changed, 85 insertions(+), 63 deletions(-) + create mode 100644 src/slapi_r_plugin/src/charray.rs + +diff --git a/Makefile.am b/Makefile.am +index 627953850..36434cf17 100644 +--- a/Makefile.am ++++ b/Makefile.am +@@ -1312,6 +1312,7 @@ rust-nsslapd-private.h: @abs_top_builddir@/rs/@rust_target_dir@/librnsslapd.a + libslapi_r_plugin_SOURCES = \ + src/slapi_r_plugin/src/backend.rs \ + src/slapi_r_plugin/src/ber.rs \ ++ src/slapi_r_plugin/src/charray.rs \ + src/slapi_r_plugin/src/constants.rs \ + src/slapi_r_plugin/src/dn.rs \ + src/slapi_r_plugin/src/entry.rs \ +diff --git a/configure.ac b/configure.ac +index b3cf77d08..61bf35e4a 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -122,7 +122,7 @@ if test "$enable_debug" = yes ; then + debug_defs="-DDEBUG -DMCC_DEBUG" + debug_cflags="-g3 -O0 -rdynamic" + debug_cxxflags="-g3 -O0 -rdynamic" +- debug_rust_defs="-C debuginfo=2" ++ debug_rust_defs="-C debuginfo=2 -Z macro-backtrace" + cargo_defs="" + rust_target_dir="debug" + else +diff --git a/ldap/servers/slapd/pagedresults.c b/ldap/servers/slapd/pagedresults.c +index d8b8798b6..e3444e944 100644 +--- a/ldap/servers/slapd/pagedresults.c ++++ b/ldap/servers/slapd/pagedresults.c +@@ -738,10 +738,10 @@ pagedresults_cleanup(Connection *conn, int needlock) + int i; + PagedResults *prp = NULL; + +- slapi_log_err(SLAPI_LOG_TRACE, "pagedresults_cleanup", "=>\n"); ++ /* slapi_log_err(SLAPI_LOG_TRACE, "pagedresults_cleanup", "=>\n"); */ + + if (NULL == conn) { +- slapi_log_err(SLAPI_LOG_TRACE, "pagedresults_cleanup", "<= Connection is NULL\n"); ++ /* slapi_log_err(SLAPI_LOG_TRACE, "pagedresults_cleanup", "<= Connection is NULL\n"); */ + return 0; + } + +@@ -767,7 +767,7 @@ pagedresults_cleanup(Connection *conn, int needlock) + if (needlock) { + pthread_mutex_unlock(&(conn->c_mutex)); + } +- slapi_log_err(SLAPI_LOG_TRACE, "pagedresults_cleanup", "<= %d\n", rc); ++ /* slapi_log_err(SLAPI_LOG_TRACE, "pagedresults_cleanup", "<= %d\n", rc); */ + return rc; + } + +diff --git a/ldap/servers/slapd/pblock.c b/ldap/servers/slapd/pblock.c +index 1ad9d0399..f7d1f8885 100644 +--- a/ldap/servers/slapd/pblock.c ++++ b/ldap/servers/slapd/pblock.c +@@ -3351,13 +3351,15 @@ slapi_pblock_set(Slapi_PBlock *pblock, int arg, void *value) + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_SYNTAX) { + return (-1); + } +- pblock->pb_plugin->plg_syntax_names = (char **)value; ++ PR_ASSERT(pblock->pb_plugin->plg_syntax_names == NULL); ++ pblock->pb_plugin->plg_syntax_names = slapi_ch_array_dup((char **)value); + break; + case SLAPI_PLUGIN_SYNTAX_OID: + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_SYNTAX) { + return (-1); + } +- pblock->pb_plugin->plg_syntax_oid = (char *)value; ++ PR_ASSERT(pblock->pb_plugin->plg_syntax_oid == NULL); ++ pblock->pb_plugin->plg_syntax_oid = slapi_ch_strdup((char *)value); + break; + case SLAPI_PLUGIN_SYNTAX_FLAGS: + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_SYNTAX) { +@@ -3806,7 +3808,8 @@ slapi_pblock_set(Slapi_PBlock *pblock, int arg, void *value) + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_MATCHINGRULE) { + return (-1); + } +- pblock->pb_plugin->plg_mr_names = (char **)value; ++ PR_ASSERT(pblock->pb_plugin->plg_mr_names == NULL); ++ pblock->pb_plugin->plg_mr_names = slapi_ch_array_dup((char **)value); + break; + case SLAPI_PLUGIN_MR_COMPARE: + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_MATCHINGRULE) { +diff --git a/ldap/servers/slapd/plugin.c b/ldap/servers/slapd/plugin.c +index 282b98738..e6b48de60 100644 +--- a/ldap/servers/slapd/plugin.c ++++ b/ldap/servers/slapd/plugin.c +@@ -2694,6 +2694,13 @@ plugin_free(struct slapdplugin *plugin) + if (plugin->plg_type == SLAPI_PLUGIN_PWD_STORAGE_SCHEME || plugin->plg_type == SLAPI_PLUGIN_REVER_PWD_STORAGE_SCHEME) { + slapi_ch_free_string(&plugin->plg_pwdstorageschemename); + } ++ if (plugin->plg_type == SLAPI_PLUGIN_SYNTAX) { ++ slapi_ch_free_string(&plugin->plg_syntax_oid); ++ slapi_ch_array_free(plugin->plg_syntax_names); ++ } ++ if (plugin->plg_type == SLAPI_PLUGIN_MATCHINGRULE) { ++ slapi_ch_array_free(plugin->plg_mr_names); ++ } + release_componentid(plugin->plg_identity); + slapi_counter_destroy(&plugin->plg_op_counter); + if (!plugin->plg_group) { +diff --git a/ldap/servers/slapd/pw_verify.c b/ldap/servers/slapd/pw_verify.c +index 4f0944b73..4ff1fa2fd 100644 +--- a/ldap/servers/slapd/pw_verify.c ++++ b/ldap/servers/slapd/pw_verify.c +@@ -111,6 +111,7 @@ pw_verify_token_dn(Slapi_PBlock *pb) { + if (fernet_verify_token(dn, cred->bv_val, key, tok_ttl) != 0) { + rc = SLAPI_BIND_SUCCESS; + } ++ slapi_ch_free_string(&key); + #endif + return rc; + } +diff --git a/ldap/servers/slapd/tools/pwenc.c b/ldap/servers/slapd/tools/pwenc.c +index 1629c06cd..d89225e34 100644 +--- a/ldap/servers/slapd/tools/pwenc.c ++++ b/ldap/servers/slapd/tools/pwenc.c +@@ -34,7 +34,7 @@ + + int ldap_syslog; + int ldap_syslog_level; +-int slapd_ldap_debug = LDAP_DEBUG_ANY; ++/* int slapd_ldap_debug = LDAP_DEBUG_ANY; */ + int detached; + FILE *error_logfp; + FILE *access_logfp; +diff --git a/src/slapi_r_plugin/README.md b/src/slapi_r_plugin/README.md +index af9743ec9..1c9bcbf17 100644 +--- a/src/slapi_r_plugin/README.md ++++ b/src/slapi_r_plugin/README.md +@@ -15,7 +15,7 @@ the [Rust Nomicon](https://doc.rust-lang.org/nomicon/index.html) + > warning about danger. + + This document will not detail the specifics of unsafe or the invariants you must adhere to for rust +-to work with C. ++to work with C. Failure to uphold these invariants will lead to less than optimal consequences. + + If you still want to see more about the plugin bindings, go on ... + +@@ -135,7 +135,7 @@ associated functions. + Now, you may notice that not all members of the trait are implemented. This is due to a feature + of rust known as default trait impls. This allows the trait origin (src/plugin.rs) to provide + template versions of these functions. If you "overwrite" them, your implementation is used. Unlike +-OO, you may not inherit or call the default function. ++OO, you may not inherit or call the default function. + + If a default is not provided you *must* implement that function to be considered valid. Today (20200422) + this only applies to `start` and `close`. +@@ -183,7 +183,7 @@ It's important to understand how Rust manages memory both on the stack and the h + As a result, this means that we must express in code, assertions about the proper ownership of memory + and who is responsible for it (unlike C, where it can be hard to determine who or what is responsible + for freeing some value.) Failure to handle this correctly, can and will lead to crashes, leaks or +-*hand waving* magical failures that are eXtReMeLy FuN to debug. ++*hand waving* magical failures that are `eXtReMeLy FuN` to debug. + + ### Reference Types + +diff --git a/src/slapi_r_plugin/src/charray.rs b/src/slapi_r_plugin/src/charray.rs +new file mode 100644 +index 000000000..d2e44693c +--- /dev/null ++++ b/src/slapi_r_plugin/src/charray.rs +@@ -0,0 +1,32 @@ ++use std::ffi::CString; ++use std::iter::once; ++use std::os::raw::c_char; ++use std::ptr; ++ ++pub struct Charray { ++ pin: Vec, ++ charray: Vec<*const c_char>, ++} ++ ++impl Charray { ++ pub fn new(input: &[&str]) -> Result { ++ let pin: Result, ()> = input ++ .iter() ++ .map(|s| CString::new(*s).map_err(|_e| ())) ++ .collect(); ++ ++ let pin = pin?; ++ ++ let charray: Vec<_> = pin ++ .iter() ++ .map(|s| s.as_ptr()) ++ .chain(once(ptr::null())) ++ .collect(); ++ ++ Ok(Charray { pin, charray }) ++ } ++ ++ pub fn as_ptr(&self) -> *const *const c_char { ++ self.charray.as_ptr() ++ } ++} +diff --git a/src/slapi_r_plugin/src/lib.rs b/src/slapi_r_plugin/src/lib.rs +index 076907bae..be28cac95 100644 +--- a/src/slapi_r_plugin/src/lib.rs ++++ b/src/slapi_r_plugin/src/lib.rs +@@ -1,9 +1,11 @@ +-// extern crate lazy_static; ++#[macro_use] ++extern crate lazy_static; + + #[macro_use] + pub mod macros; + pub mod backend; + pub mod ber; ++pub mod charray; + mod constants; + pub mod dn; + pub mod entry; +@@ -20,6 +22,7 @@ pub mod value; + pub mod prelude { + pub use crate::backend::{BackendRef, BackendRefTxn}; + pub use crate::ber::BerValRef; ++ pub use crate::charray::Charray; + pub use crate::constants::{FilterType, PluginFnType, PluginType, PluginVersion, LDAP_SUCCESS}; + pub use crate::dn::{Sdn, SdnRef}; + pub use crate::entry::EntryRef; +@@ -30,8 +33,7 @@ pub mod prelude { + pub use crate::plugin::{register_plugin_ext, PluginIdRef, SlapiPlugin3}; + pub use crate::search::{Search, SearchScope}; + pub use crate::syntax_plugin::{ +- matchingrule_register, name_to_leaking_char, names_to_leaking_char_array, SlapiOrdMr, +- SlapiSubMr, SlapiSyntaxPlugin1, ++ matchingrule_register, SlapiOrdMr, SlapiSubMr, SlapiSyntaxPlugin1, + }; + pub use crate::task::{task_register_handler_fn, task_unregister_handler_fn, Task, TaskRef}; + pub use crate::value::{Value, ValueArray, ValueArrayRef, ValueRef}; +diff --git a/src/slapi_r_plugin/src/macros.rs b/src/slapi_r_plugin/src/macros.rs +index bc8dfa60f..97fc5d7ef 100644 +--- a/src/slapi_r_plugin/src/macros.rs ++++ b/src/slapi_r_plugin/src/macros.rs +@@ -249,6 +249,7 @@ macro_rules! slapi_r_syntax_plugin_hooks { + paste::item! { + use libc; + use std::convert::TryFrom; ++ use std::ffi::CString; + + #[no_mangle] + pub extern "C" fn [<$mod_ident _plugin_init>](raw_pb: *const libc::c_void) -> i32 { +@@ -261,15 +262,15 @@ macro_rules! slapi_r_syntax_plugin_hooks { + }; + + // Setup the names/oids that this plugin provides syntaxes for. +- +- let name_ptr = unsafe { names_to_leaking_char_array(&$hooks_ident::attr_supported_names()) }; +- match pb.register_syntax_names(name_ptr) { ++ // DS will clone these, so they can be ephemeral to this function. ++ let name_vec = Charray::new($hooks_ident::attr_supported_names().as_slice()).expect("invalid supported names"); ++ match pb.register_syntax_names(name_vec.as_ptr()) { + 0 => {}, + e => return e, + }; + +- let name_ptr = unsafe { name_to_leaking_char($hooks_ident::attr_oid()) }; +- match pb.register_syntax_oid(name_ptr) { ++ let attr_oid = CString::new($hooks_ident::attr_oid()).expect("invalid attr oid"); ++ match pb.register_syntax_oid(attr_oid.as_ptr()) { + 0 => {}, + e => return e, + }; +@@ -430,7 +431,8 @@ macro_rules! slapi_r_syntax_plugin_hooks { + e => return e, + }; + +- let name_ptr = unsafe { names_to_leaking_char_array(&$hooks_ident::eq_mr_supported_names()) }; ++ let name_vec = Charray::new($hooks_ident::eq_mr_supported_names().as_slice()).expect("invalid mr supported names"); ++ let name_ptr = name_vec.as_ptr(); + // SLAPI_PLUGIN_MR_NAMES + match pb.register_mr_names(name_ptr) { + 0 => {}, +@@ -672,7 +674,8 @@ macro_rules! slapi_r_syntax_plugin_hooks { + e => return e, + }; + +- let name_ptr = unsafe { names_to_leaking_char_array(&$hooks_ident::ord_mr_supported_names()) }; ++ let name_vec = Charray::new($hooks_ident::ord_mr_supported_names().as_slice()).expect("invalid ord supported names"); ++ let name_ptr = name_vec.as_ptr(); + // SLAPI_PLUGIN_MR_NAMES + match pb.register_mr_names(name_ptr) { + 0 => {}, +diff --git a/src/slapi_r_plugin/src/syntax_plugin.rs b/src/slapi_r_plugin/src/syntax_plugin.rs +index e7d5c01bd..86f84bdd8 100644 +--- a/src/slapi_r_plugin/src/syntax_plugin.rs ++++ b/src/slapi_r_plugin/src/syntax_plugin.rs +@@ -1,11 +1,11 @@ + use crate::ber::BerValRef; + // use crate::constants::FilterType; ++use crate::charray::Charray; + use crate::error::PluginError; + use crate::pblock::PblockRef; + use crate::value::{ValueArray, ValueArrayRef}; + use std::cmp::Ordering; + use std::ffi::CString; +-use std::iter::once; + use std::os::raw::c_char; + use std::ptr; + +@@ -26,37 +26,6 @@ struct slapi_matchingRuleEntry { + mr_compat_syntax: *const *const c_char, + } + +-pub unsafe fn name_to_leaking_char(name: &str) -> *const c_char { +- let n = CString::new(name) +- .expect("An invalid string has been hardcoded!") +- .into_boxed_c_str(); +- let n_ptr = n.as_ptr(); +- // Now we intentionally leak the name here, and the pointer will remain valid. +- Box::leak(n); +- n_ptr +-} +- +-pub unsafe fn names_to_leaking_char_array(names: &[&str]) -> *const *const c_char { +- let n_arr: Vec = names +- .iter() +- .map(|s| CString::new(*s).expect("An invalid string has been hardcoded!")) +- .collect(); +- let n_arr = n_arr.into_boxed_slice(); +- let n_ptr_arr: Vec<*const c_char> = n_arr +- .iter() +- .map(|v| v.as_ptr()) +- .chain(once(ptr::null())) +- .collect(); +- let n_ptr_arr = n_ptr_arr.into_boxed_slice(); +- +- // Now we intentionally leak these names here, +- let _r_n_arr = Box::leak(n_arr); +- let r_n_ptr_arr = Box::leak(n_ptr_arr); +- +- let name_ptr = r_n_ptr_arr as *const _ as *const *const c_char; +- name_ptr +-} +- + // oid - the oid of the matching rule + // name - the name of the mr + // desc - description +@@ -69,20 +38,24 @@ pub unsafe fn matchingrule_register( + syntax: &str, + compat_syntax: &[&str], + ) -> i32 { +- let oid_ptr = name_to_leaking_char(oid); +- let name_ptr = name_to_leaking_char(name); +- let desc_ptr = name_to_leaking_char(desc); +- let syntax_ptr = name_to_leaking_char(syntax); +- let compat_syntax_ptr = names_to_leaking_char_array(compat_syntax); ++ // Make everything CStrings that live long enough. ++ ++ let oid_cs = CString::new(oid).expect("invalid oid"); ++ let name_cs = CString::new(name).expect("invalid name"); ++ let desc_cs = CString::new(desc).expect("invalid desc"); ++ let syntax_cs = CString::new(syntax).expect("invalid syntax"); ++ ++ // We have to do this so the cstrings live long enough. ++ let compat_syntax_ca = Charray::new(compat_syntax).expect("invalid compat_syntax"); + + let new_mr = slapi_matchingRuleEntry { +- mr_oid: oid_ptr, ++ mr_oid: oid_cs.as_ptr(), + _mr_oidalias: ptr::null(), +- mr_name: name_ptr, +- mr_desc: desc_ptr, +- mr_syntax: syntax_ptr, ++ mr_name: name_cs.as_ptr(), ++ mr_desc: desc_cs.as_ptr(), ++ mr_syntax: syntax_cs.as_ptr(), + _mr_obsolete: 0, +- mr_compat_syntax: compat_syntax_ptr, ++ mr_compat_syntax: compat_syntax_ca.as_ptr(), + }; + + let new_mr_ptr = &new_mr as *const _; +-- +2.26.3 + diff --git a/SOURCES/0008-Issue-4773-Enable-interval-feature-of-DNA-plugin.patch b/SOURCES/0008-Issue-4773-Enable-interval-feature-of-DNA-plugin.patch new file mode 100644 index 0000000..ce8b124 --- /dev/null +++ b/SOURCES/0008-Issue-4773-Enable-interval-feature-of-DNA-plugin.patch @@ -0,0 +1,37 @@ +From 40e9a4835a6e95f021a711a7c42ce0c1bddc5ba4 Mon Sep 17 00:00:00 2001 +From: Mark Reynolds +Date: Fri, 21 May 2021 13:09:12 -0400 +Subject: [PATCH 08/12] Issue 4773 - Enable interval feature of DNA plugin + +Description: Enable the dormant interval feature in DNA plugin + +relates: https://github.com/389ds/389-ds-base/issues/4773 + +Review by: mreynolds (one line commit rule) +--- + ldap/servers/plugins/dna/dna.c | 2 -- + 1 file changed, 2 deletions(-) + +diff --git a/ldap/servers/plugins/dna/dna.c b/ldap/servers/plugins/dna/dna.c +index bf6b74a99..928a3f54a 100644 +--- a/ldap/servers/plugins/dna/dna.c ++++ b/ldap/servers/plugins/dna/dna.c +@@ -1023,7 +1023,6 @@ dna_parse_config_entry(Slapi_PBlock *pb, Slapi_Entry *e, int apply) + /* Set the default interval to 1 */ + entry->interval = 1; + +-#ifdef DNA_ENABLE_INTERVAL + value = slapi_entry_attr_get_charptr(e, DNA_INTERVAL); + if (value) { + entry->interval = strtoull(value, 0, 0); +@@ -1032,7 +1031,6 @@ dna_parse_config_entry(Slapi_PBlock *pb, Slapi_Entry *e, int apply) + + slapi_log_err(SLAPI_LOG_CONFIG, DNA_PLUGIN_SUBSYSTEM, + "dna_parse_config_entry - %s [%" PRIu64 "]\n", DNA_INTERVAL, entry->interval); +-#endif + + value = slapi_entry_attr_get_charptr(e, DNA_GENERATE); + if (value) { +-- +2.26.3 + diff --git a/SOURCES/0009-Issue-4623-RFE-Monitor-the-current-DB-locks-4762.patch b/SOURCES/0009-Issue-4623-RFE-Monitor-the-current-DB-locks-4762.patch new file mode 100644 index 0000000..b4d22df --- /dev/null +++ b/SOURCES/0009-Issue-4623-RFE-Monitor-the-current-DB-locks-4762.patch @@ -0,0 +1,926 @@ +From 8df95679519364d0993572ecbea72ab89e5250a5 Mon Sep 17 00:00:00 2001 +From: Simon Pichugin +Date: Thu, 20 May 2021 14:24:25 +0200 +Subject: [PATCH 09/12] Issue 4623 - RFE - Monitor the current DB locks (#4762) + +Description: DB lock gets exhausted because of unindexed internal searches +(under a transaction). Indexing those searches is the way to prevent exhaustion. +If db lock get exhausted during a txn, it leads to db panic and the later recovery +can possibly fail. That leads to a full reinit of the instance where the db locks +got exhausted. + +Add three attributes to global BDB config: "nsslapd-db-locks-monitoring-enabled", + "nsslapd-db-locks-monitoring-threshold" and "nsslapd-db-locks-monitoring-pause". +By default, nsslapd-db-locks-monitoring-enabled is turned on, nsslapd-db-locks-monitoring-threshold is set to 90% and nsslapd-db-locks-monitoring-threshold is 500ms. + +When current locks are close to the maximum locks value of 90% - returning +the next candidate will fail until the maximum of locks won't be +increased or current locks are released. +The monitoring thread runs with the configurable interval of 500ms. + +Add the setting to UI and CLI tools. + +Fixes: https://github.com/389ds/389-ds-base/issues/4623 + +Reviewed by: @Firstyear, @tbordaz, @jchapma, @mreynolds389 (Thank you!!) +--- + .../suites/monitor/db_locks_monitor_test.py | 251 ++++++++++++++++++ + ldap/servers/slapd/back-ldbm/back-ldbm.h | 13 +- + .../slapd/back-ldbm/db-bdb/bdb_config.c | 99 +++++++ + .../slapd/back-ldbm/db-bdb/bdb_layer.c | 85 ++++++ + ldap/servers/slapd/back-ldbm/init.c | 3 + + ldap/servers/slapd/back-ldbm/ldbm_config.c | 3 + + ldap/servers/slapd/back-ldbm/ldbm_config.h | 3 + + ldap/servers/slapd/back-ldbm/ldbm_search.c | 13 + + ldap/servers/slapd/libglobs.c | 4 +- + src/cockpit/389-console/src/css/ds.css | 4 + + src/cockpit/389-console/src/database.jsx | 7 + + src/cockpit/389-console/src/index.html | 2 +- + .../src/lib/database/databaseConfig.jsx | 88 +++++- + src/lib389/lib389/backend.py | 3 + + src/lib389/lib389/cli_conf/backend.py | 10 + + 15 files changed, 576 insertions(+), 12 deletions(-) + create mode 100644 dirsrvtests/tests/suites/monitor/db_locks_monitor_test.py + +diff --git a/dirsrvtests/tests/suites/monitor/db_locks_monitor_test.py b/dirsrvtests/tests/suites/monitor/db_locks_monitor_test.py +new file mode 100644 +index 000000000..7f9938f30 +--- /dev/null ++++ b/dirsrvtests/tests/suites/monitor/db_locks_monitor_test.py +@@ -0,0 +1,251 @@ ++# --- BEGIN COPYRIGHT BLOCK --- ++# Copyright (C) 2021 Red Hat, Inc. ++# All rights reserved. ++# ++# License: GPL (version 3 or any later version). ++# See LICENSE for details. ++# --- END COPYRIGHT BLOCK --- ++# ++import logging ++import pytest ++import datetime ++import subprocess ++from multiprocessing import Process, Queue ++from lib389 import pid_from_file ++from lib389.utils import ldap, os ++from lib389._constants import DEFAULT_SUFFIX, ReplicaRole ++from lib389.cli_base import LogCapture ++from lib389.idm.user import UserAccounts ++from lib389.idm.organizationalunit import OrganizationalUnits ++from lib389.tasks import AccessLog ++from lib389.backend import Backends ++from lib389.ldclt import Ldclt ++from lib389.dbgen import dbgen_users ++from lib389.tasks import ImportTask ++from lib389.index import Indexes ++from lib389.plugins import AttributeUniquenessPlugin ++from lib389.config import BDB_LDBMConfig ++from lib389.monitor import MonitorLDBM ++from lib389.topologies import create_topology, _remove_ssca_db ++ ++pytestmark = pytest.mark.tier2 ++db_locks_monitoring_ack = pytest.mark.skipif(not os.environ.get('DB_LOCKS_MONITORING_ACK', False), ++ reason="DB locks monitoring tests may take hours if the feature is not present or another failure exists. " ++ "Also, the feature requires a big amount of space as we set nsslapd-db-locks to 1300000.") ++ ++DEBUGGING = os.getenv('DEBUGGING', default=False) ++if DEBUGGING: ++ logging.getLogger(__name__).setLevel(logging.DEBUG) ++else: ++ logging.getLogger(__name__).setLevel(logging.INFO) ++log = logging.getLogger(__name__) ++ ++ ++def _kill_ns_slapd(inst): ++ pid = str(pid_from_file(inst.ds_paths.pid_file)) ++ cmd = ['kill', '-9', pid] ++ subprocess.Popen(cmd, stdout=subprocess.PIPE) ++ ++ ++@pytest.fixture(scope="function") ++def topology_st_fn(request): ++ """Create DS standalone instance for each test case""" ++ ++ topology = create_topology({ReplicaRole.STANDALONE: 1}) ++ ++ def fin(): ++ # Kill the hanging process at the end of test to prevent failures in the following tests ++ if DEBUGGING: ++ [_kill_ns_slapd(inst) for inst in topology] ++ else: ++ [_kill_ns_slapd(inst) for inst in topology] ++ assert _remove_ssca_db(topology) ++ [inst.stop() for inst in topology if inst.exists()] ++ [inst.delete() for inst in topology if inst.exists()] ++ request.addfinalizer(fin) ++ ++ topology.logcap = LogCapture() ++ return topology ++ ++ ++@pytest.fixture(scope="function") ++def setup_attruniq_index_be_import(topology_st_fn): ++ """Enable Attribute Uniqueness, disable indexes and ++ import 120000 entries to the default backend ++ """ ++ inst = topology_st_fn.standalone ++ ++ inst.config.loglevel([AccessLog.DEFAULT, AccessLog.INTERNAL], service='access') ++ inst.config.set('nsslapd-plugin-logging', 'on') ++ inst.restart() ++ ++ attruniq = AttributeUniquenessPlugin(inst, dn="cn=attruniq,cn=plugins,cn=config") ++ attruniq.create(properties={'cn': 'attruniq'}) ++ for cn in ['uid', 'cn', 'sn', 'uidNumber', 'gidNumber', 'homeDirectory', 'givenName', 'description']: ++ attruniq.add_unique_attribute(cn) ++ attruniq.add_unique_subtree(DEFAULT_SUFFIX) ++ attruniq.enable_all_subtrees() ++ attruniq.enable() ++ ++ indexes = Indexes(inst) ++ for cn in ['uid', 'cn', 'sn', 'uidNumber', 'gidNumber', 'homeDirectory', 'givenName', 'description']: ++ indexes.ensure_state(properties={ ++ 'cn': cn, ++ 'nsSystemIndex': 'false', ++ 'nsIndexType': 'none'}) ++ ++ bdb_config = BDB_LDBMConfig(inst) ++ bdb_config.replace("nsslapd-db-locks", "130000") ++ inst.restart() ++ ++ ldif_dir = inst.get_ldif_dir() ++ import_ldif = ldif_dir + '/perf_import.ldif' ++ ++ # Valid online import ++ import_task = ImportTask(inst) ++ dbgen_users(inst, 120000, import_ldif, DEFAULT_SUFFIX, entry_name="userNew") ++ import_task.import_suffix_from_ldif(ldiffile=import_ldif, suffix=DEFAULT_SUFFIX) ++ import_task.wait() ++ assert import_task.is_complete() ++ ++ ++def create_user_wrapper(q, users): ++ try: ++ users.create_test_user() ++ except Exception as ex: ++ q.put(ex) ++ ++ ++def spawn_worker_thread(function, users, log, timeout, info): ++ log.info(f"Starting the thread - {info}") ++ q = Queue() ++ p = Process(target=function, args=(q,users,)) ++ p.start() ++ ++ log.info(f"Waiting for {timeout} seconds for the thread to finish") ++ p.join(timeout) ++ ++ if p.is_alive(): ++ log.info("Killing the thread as it's still running") ++ p.terminate() ++ p.join() ++ raise RuntimeError(f"Function call was aborted: {info}") ++ result = q.get() ++ if isinstance(result, Exception): ++ raise result ++ else: ++ return result ++ ++ ++@db_locks_monitoring_ack ++@pytest.mark.parametrize("lock_threshold", [("70"), ("80"), ("95")]) ++def test_exhaust_db_locks_basic(topology_st_fn, setup_attruniq_index_be_import, lock_threshold): ++ """Test that when all of the locks are exhausted the instance still working ++ and database is not corrupted ++ ++ :id: 299108cc-04d8-4ddc-b58e-99157fccd643 ++ :setup: Standalone instance with Attr Uniq plugin and user indexes disabled ++ :steps: 1. Set nsslapd-db-locks to 11000 ++ 2. Check that we stop acquiring new locks when the threshold is reached ++ 3. Check that we can regulate a pause interval for DB locks monitoring thread ++ 4. Make sure the feature works for different backends on the same suffix ++ :expectedresults: ++ 1. Success ++ 2. Success ++ 3. Success ++ 4. Success ++ """ ++ ++ inst = topology_st_fn.standalone ++ ADDITIONAL_SUFFIX = 'ou=newpeople,dc=example,dc=com' ++ ++ backends = Backends(inst) ++ backends.create(properties={'nsslapd-suffix': ADDITIONAL_SUFFIX, ++ 'name': ADDITIONAL_SUFFIX[-3:]}) ++ ous = OrganizationalUnits(inst, DEFAULT_SUFFIX) ++ ous.create(properties={'ou': 'newpeople'}) ++ ++ bdb_config = BDB_LDBMConfig(inst) ++ bdb_config.replace("nsslapd-db-locks", "11000") ++ ++ # Restart server ++ inst.restart() ++ ++ for lock_enabled in ["on", "off"]: ++ for lock_pause in ["100", "500", "1000"]: ++ bdb_config.replace("nsslapd-db-locks-monitoring-enabled", lock_enabled) ++ bdb_config.replace("nsslapd-db-locks-monitoring-threshold", lock_threshold) ++ bdb_config.replace("nsslapd-db-locks-monitoring-pause", lock_pause) ++ inst.restart() ++ ++ if lock_enabled == "off": ++ raised_exception = (RuntimeError, ldap.SERVER_DOWN) ++ else: ++ raised_exception = ldap.OPERATIONS_ERROR ++ ++ users = UserAccounts(inst, DEFAULT_SUFFIX) ++ with pytest.raises(raised_exception): ++ spawn_worker_thread(create_user_wrapper, users, log, 30, ++ f"Adding user with monitoring enabled='{lock_enabled}'; " ++ f"threshold='{lock_threshold}'; pause='{lock_pause}'.") ++ # Restart because we already run out of locks and the next unindexed searches will fail eventually ++ if lock_enabled == "off": ++ _kill_ns_slapd(inst) ++ inst.restart() ++ ++ users = UserAccounts(inst, ADDITIONAL_SUFFIX, rdn=None) ++ with pytest.raises(raised_exception): ++ spawn_worker_thread(create_user_wrapper, users, log, 30, ++ f"Adding user with monitoring enabled='{lock_enabled}'; " ++ f"threshold='{lock_threshold}'; pause='{lock_pause}'.") ++ # In case feature is disabled - restart for the clean up ++ if lock_enabled == "off": ++ _kill_ns_slapd(inst) ++ inst.restart() ++ ++ ++@db_locks_monitoring_ack ++def test_exhaust_db_locks_big_pause(topology_st_fn, setup_attruniq_index_be_import): ++ """Test that DB lock pause setting increases the wait interval value for the monitoring thread ++ ++ :id: 7d5bf838-5d4e-4ad5-8c03-5716afb84ea6 ++ :setup: Standalone instance with Attr Uniq plugin and user indexes disabled ++ :steps: 1. Set nsslapd-db-locks to 20000 while using the default threshold value (95%) ++ 2. Set nsslapd-db-locks-monitoring-pause to 10000 (10 seconds) ++ 3. Make sure that the pause is successfully increased a few times in a row ++ :expectedresults: ++ 1. Success ++ 2. Success ++ 3. Success ++ """ ++ ++ inst = topology_st_fn.standalone ++ ++ bdb_config = BDB_LDBMConfig(inst) ++ bdb_config.replace("nsslapd-db-locks", "20000") ++ lock_pause = bdb_config.get_attr_val_int("nsslapd-db-locks-monitoring-pause") ++ assert lock_pause == 500 ++ lock_pause = "10000" ++ bdb_config.replace("nsslapd-db-locks-monitoring-pause", lock_pause) ++ ++ # Restart server ++ inst.restart() ++ ++ lock_enabled = bdb_config.get_attr_val_utf8_l("nsslapd-db-locks-monitoring-enabled") ++ lock_threshold = bdb_config.get_attr_val_int("nsslapd-db-locks-monitoring-threshold") ++ assert lock_enabled == "on" ++ assert lock_threshold == 90 ++ ++ users = UserAccounts(inst, DEFAULT_SUFFIX) ++ start = datetime.datetime.now() ++ with pytest.raises(ldap.OPERATIONS_ERROR): ++ spawn_worker_thread(create_user_wrapper, users, log, 30, ++ f"Adding user with monitoring enabled='{lock_enabled}'; " ++ f"threshold='{lock_threshold}'; pause='{lock_pause}'. Expect it to 'Work'") ++ end = datetime.datetime.now() ++ time_delta = end - start ++ if time_delta.seconds < 9: ++ raise RuntimeError("nsslapd-db-locks-monitoring-pause attribute doesn't function correctly. " ++ f"Finished the execution in {time_delta.seconds} seconds") ++ # In case something has failed - restart for the clean up ++ inst.restart() +diff --git a/ldap/servers/slapd/back-ldbm/back-ldbm.h b/ldap/servers/slapd/back-ldbm/back-ldbm.h +index 571b0a58b..afb831c32 100644 +--- a/ldap/servers/slapd/back-ldbm/back-ldbm.h ++++ b/ldap/servers/slapd/back-ldbm/back-ldbm.h +@@ -155,6 +155,8 @@ typedef unsigned short u_int16_t; + #define DEFAULT_DNCACHE_MAXCOUNT -1 /* no limit */ + #define DEFAULT_DBCACHE_SIZE 33554432 + #define DEFAULT_DBCACHE_SIZE_STR "33554432" ++#define DEFAULT_DBLOCK_PAUSE 500 ++#define DEFAULT_DBLOCK_PAUSE_STR "500" + #define DEFAULT_MODE 0600 + #define DEFAULT_ALLIDSTHRESHOLD 4000 + #define DEFAULT_IDL_TUNE 1 +@@ -575,12 +577,21 @@ struct ldbminfo + char *li_backend_implement; /* low layer backend implementation */ + int li_noparentcheck; /* check if parent exists on add */ + +- /* the next 3 fields are for the params that don't get changed until ++ /* db lock monitoring */ ++ /* if we decide to move the values to bdb_config, we can use slapi_back_get_info function to retrieve the values */ ++ int32_t li_dblock_monitoring; /* enables db locks monitoring thread - requires restart */ ++ uint32_t li_dblock_monitoring_pause; /* an interval for db locks monitoring thread */ ++ uint32_t li_dblock_threshold; /* when the percentage is reached, abort the search in ldbm_back_next_search_entry - requires restart*/ ++ uint32_t li_dblock_threshold_reached; ++ ++ /* the next 4 fields are for the params that don't get changed until + * the server is restarted (used by the admin console) + */ + char *li_new_directory; + uint64_t li_new_dbcachesize; + int li_new_dblock; ++ int32_t li_new_dblock_monitoring; ++ uint64_t li_new_dblock_threshold; + + int li_new_dbncache; + +diff --git a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_config.c b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_config.c +index 738b841aa..167644943 100644 +--- a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_config.c ++++ b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_config.c +@@ -190,6 +190,102 @@ bdb_config_db_lock_set(void *arg, void *value, char *errorbuf, int phase, int ap + return retval; + } + ++static void * ++bdb_config_db_lock_monitoring_get(void *arg) ++{ ++ struct ldbminfo *li = (struct ldbminfo *)arg; ++ ++ return (void *)((intptr_t)(li->li_new_dblock_monitoring)); ++} ++ ++static int ++bdb_config_db_lock_monitoring_set(void *arg, void *value, char *errorbuf __attribute__((unused)), int phase __attribute__((unused)), int apply) ++{ ++ struct ldbminfo *li = (struct ldbminfo *)arg; ++ int retval = LDAP_SUCCESS; ++ int val = (int32_t)((intptr_t)value); ++ ++ if (apply) { ++ if (CONFIG_PHASE_RUNNING == phase) { ++ li->li_new_dblock_monitoring = val; ++ slapi_log_err(SLAPI_LOG_NOTICE, "bdb_config_db_lock_monitoring_set", ++ "New nsslapd-db-lock-monitoring value will not take affect until the server is restarted\n"); ++ } else { ++ li->li_new_dblock_monitoring = val; ++ li->li_dblock_monitoring = val; ++ } ++ } ++ ++ return retval; ++} ++ ++static void * ++bdb_config_db_lock_pause_get(void *arg) ++{ ++ struct ldbminfo *li = (struct ldbminfo *)arg; ++ ++ return (void *)((uintptr_t)(slapi_atomic_load_32((int32_t *)&(li->li_dblock_monitoring_pause), __ATOMIC_RELAXED))); ++} ++ ++static int ++bdb_config_db_lock_pause_set(void *arg, void *value, char *errorbuf, int phase __attribute__((unused)), int apply) ++{ ++ struct ldbminfo *li = (struct ldbminfo *)arg; ++ int retval = LDAP_SUCCESS; ++ u_int32_t val = (u_int32_t)((uintptr_t)value); ++ ++ if (val == 0) { ++ slapi_log_err(SLAPI_LOG_NOTICE, "bdb_config_db_lock_pause_set", ++ "%s was set to '0'. The default value will be used (%s)", ++ CONFIG_DB_LOCKS_PAUSE, DEFAULT_DBLOCK_PAUSE_STR); ++ val = DEFAULT_DBLOCK_PAUSE; ++ } ++ ++ if (apply) { ++ slapi_atomic_store_32((int32_t *)&(li->li_dblock_monitoring_pause), val, __ATOMIC_RELAXED); ++ } ++ return retval; ++} ++ ++static void * ++bdb_config_db_lock_threshold_get(void *arg) ++{ ++ struct ldbminfo *li = (struct ldbminfo *)arg; ++ ++ return (void *)((uintptr_t)(li->li_new_dblock_threshold)); ++} ++ ++static int ++bdb_config_db_lock_threshold_set(void *arg, void *value, char *errorbuf, int phase __attribute__((unused)), int apply) ++{ ++ struct ldbminfo *li = (struct ldbminfo *)arg; ++ int retval = LDAP_SUCCESS; ++ u_int32_t val = (u_int32_t)((uintptr_t)value); ++ ++ if (val < 70 || val > 95) { ++ slapi_create_errormsg(errorbuf, SLAPI_DSE_RETURNTEXT_SIZE, ++ "%s: \"%d\" is invalid, threshold is indicated as a percentage and it must lie in range of 70 and 95", ++ CONFIG_DB_LOCKS_THRESHOLD, val); ++ slapi_log_err(SLAPI_LOG_ERR, "bdb_config_db_lock_threshold_set", ++ "%s: \"%d\" is invalid, threshold is indicated as a percentage and it must lie in range of 70 and 95", ++ CONFIG_DB_LOCKS_THRESHOLD, val); ++ retval = LDAP_OPERATIONS_ERROR; ++ return retval; ++ } ++ ++ if (apply) { ++ if (CONFIG_PHASE_RUNNING == phase) { ++ li->li_new_dblock_threshold = val; ++ slapi_log_err(SLAPI_LOG_NOTICE, "bdb_config_db_lock_threshold_set", ++ "New nsslapd-db-lock-monitoring-threshold value will not take affect until the server is restarted\n"); ++ } else { ++ li->li_new_dblock_threshold = val; ++ li->li_dblock_threshold = val; ++ } ++ } ++ return retval; ++} ++ + static void * + bdb_config_dbcachesize_get(void *arg) + { +@@ -1409,6 +1505,9 @@ static config_info bdb_config_param[] = { + {CONFIG_SERIAL_LOCK, CONFIG_TYPE_ONOFF, "on", &bdb_config_serial_lock_get, &bdb_config_serial_lock_set, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE}, + {CONFIG_USE_LEGACY_ERRORCODE, CONFIG_TYPE_ONOFF, "off", &bdb_config_legacy_errcode_get, &bdb_config_legacy_errcode_set, 0}, + {CONFIG_DB_DEADLOCK_POLICY, CONFIG_TYPE_INT, STRINGIFYDEFINE(DB_LOCK_YOUNGEST), &bdb_config_db_deadlock_policy_get, &bdb_config_db_deadlock_policy_set, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE}, ++ {CONFIG_DB_LOCKS_MONITORING, CONFIG_TYPE_ONOFF, "on", &bdb_config_db_lock_monitoring_get, &bdb_config_db_lock_monitoring_set, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE}, ++ {CONFIG_DB_LOCKS_THRESHOLD, CONFIG_TYPE_INT, "90", &bdb_config_db_lock_threshold_get, &bdb_config_db_lock_threshold_set, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE}, ++ {CONFIG_DB_LOCKS_PAUSE, CONFIG_TYPE_INT, DEFAULT_DBLOCK_PAUSE_STR, &bdb_config_db_lock_pause_get, &bdb_config_db_lock_pause_set, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE}, + {NULL, 0, NULL, NULL, NULL, 0}}; + + void +diff --git a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.c b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.c +index 6cccad8e6..2f25f67a2 100644 +--- a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.c ++++ b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.c +@@ -35,6 +35,8 @@ + (env)->txn_checkpoint((env), (kbyte), (min), (flags)) + #define MEMP_STAT(env, gsp, fsp, flags, malloc) \ + (env)->memp_stat((env), (gsp), (fsp), (flags)) ++#define LOCK_STAT(env, statp, flags, malloc) \ ++ (env)->lock_stat((env), (statp), (flags)) + #define MEMP_TRICKLE(env, pct, nwrotep) \ + (env)->memp_trickle((env), (pct), (nwrotep)) + #define LOG_ARCHIVE(env, listp, flags, malloc) \ +@@ -66,6 +68,7 @@ + #define NEWDIR_MODE 0755 + #define DB_REGION_PREFIX "__db." + ++static int locks_monitoring_threadmain(void *param); + static int perf_threadmain(void *param); + static int checkpoint_threadmain(void *param); + static int trickle_threadmain(void *param); +@@ -84,6 +87,7 @@ static int bdb_start_checkpoint_thread(struct ldbminfo *li); + static int bdb_start_trickle_thread(struct ldbminfo *li); + static int bdb_start_perf_thread(struct ldbminfo *li); + static int bdb_start_txn_test_thread(struct ldbminfo *li); ++static int bdb_start_locks_monitoring_thread(struct ldbminfo *li); + static int trans_batch_count = 0; + static int trans_batch_limit = 0; + static int trans_batch_txn_min_sleep = 50; /* ms */ +@@ -1299,6 +1303,10 @@ bdb_start(struct ldbminfo *li, int dbmode) + return return_value; + } + ++ if (0 != (return_value = bdb_start_locks_monitoring_thread(li))) { ++ return return_value; ++ } ++ + /* We need to free the memory to avoid a leak + * Also, we have to evaluate if the performance counter + * should be preserved or not for database restore. +@@ -2885,6 +2893,7 @@ bdb_start_perf_thread(struct ldbminfo *li) + return return_value; + } + ++ + /* Performance thread */ + static int + perf_threadmain(void *param) +@@ -2910,6 +2919,82 @@ perf_threadmain(void *param) + return 0; + } + ++ ++/* ++ * create a thread for locks_monitoring_threadmain ++ */ ++static int ++bdb_start_locks_monitoring_thread(struct ldbminfo *li) ++{ ++ int return_value = 0; ++ if (li->li_dblock_monitoring) { ++ if (NULL == PR_CreateThread(PR_USER_THREAD, ++ (VFP)(void *)locks_monitoring_threadmain, li, ++ PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, ++ PR_UNJOINABLE_THREAD, ++ SLAPD_DEFAULT_THREAD_STACKSIZE)) { ++ PRErrorCode prerr = PR_GetError(); ++ slapi_log_err(SLAPI_LOG_ERR, "bdb_start_locks_monitoring_thread", ++ "Failed to create database locks monitoring thread, " SLAPI_COMPONENT_NAME_NSPR " error %d (%s)\n", ++ prerr, slapd_pr_strerror(prerr)); ++ return_value = -1; ++ } ++ } ++ return return_value; ++} ++ ++ ++/* DB Locks Monitoring thread */ ++static int ++locks_monitoring_threadmain(void *param) ++{ ++ int ret = 0; ++ uint64_t current_locks = 0; ++ uint64_t max_locks = 0; ++ uint32_t lock_exhaustion = 0; ++ PRIntervalTime interval; ++ struct ldbminfo *li = NULL; ++ ++ PR_ASSERT(NULL != param); ++ li = (struct ldbminfo *)param; ++ ++ dblayer_private *priv = li->li_dblayer_private; ++ bdb_db_env *pEnv = (bdb_db_env *)priv->dblayer_env; ++ PR_ASSERT(NULL != priv); ++ ++ INCR_THREAD_COUNT(pEnv); ++ ++ while (!BDB_CONFIG(li)->bdb_stop_threads) { ++ if (dblayer_db_uses_locking(pEnv->bdb_DB_ENV)) { ++ DB_LOCK_STAT *lockstat = NULL; ++ ret = LOCK_STAT(pEnv->bdb_DB_ENV, &lockstat, 0, (void *)slapi_ch_malloc); ++ if (0 == ret) { ++ current_locks = lockstat->st_nlocks; ++ max_locks = lockstat->st_maxlocks; ++ if (max_locks){ ++ lock_exhaustion = (uint32_t)((double)current_locks / (double)max_locks * 100.0); ++ } else { ++ lock_exhaustion = 0; ++ } ++ if ((li->li_dblock_threshold) && ++ (lock_exhaustion >= li->li_dblock_threshold)) { ++ slapi_atomic_store_32((int32_t *)&(li->li_dblock_threshold_reached), 1, __ATOMIC_RELAXED); ++ } else { ++ slapi_atomic_store_32((int32_t *)&(li->li_dblock_threshold_reached), 0, __ATOMIC_RELAXED); ++ } ++ } ++ slapi_ch_free((void **)&lockstat); ++ } ++ interval = PR_MillisecondsToInterval(slapi_atomic_load_32((int32_t *)&(li->li_dblock_monitoring_pause), __ATOMIC_RELAXED)); ++ DS_Sleep(interval); ++ } ++ ++ DECR_THREAD_COUNT(pEnv); ++ slapi_log_err(SLAPI_LOG_TRACE, "locks_monitoring_threadmain", "Leaving locks_monitoring_threadmain\n"); ++ return 0; ++} ++ ++ + /* + * create a thread for deadlock_threadmain + */ +diff --git a/ldap/servers/slapd/back-ldbm/init.c b/ldap/servers/slapd/back-ldbm/init.c +index 893776699..4165c8fad 100644 +--- a/ldap/servers/slapd/back-ldbm/init.c ++++ b/ldap/servers/slapd/back-ldbm/init.c +@@ -70,6 +70,9 @@ ldbm_back_init(Slapi_PBlock *pb) + /* Initialize the set of instances. */ + li->li_instance_set = objset_new(&ldbm_back_instance_set_destructor); + ++ /* Init lock threshold value */ ++ li->li_dblock_threshold_reached = 0; ++ + /* ask the factory to give us space in the Connection object + * (only bulk import uses this) + */ +diff --git a/ldap/servers/slapd/back-ldbm/ldbm_config.c b/ldap/servers/slapd/back-ldbm/ldbm_config.c +index 10cef250f..60884cf33 100644 +--- a/ldap/servers/slapd/back-ldbm/ldbm_config.c ++++ b/ldap/servers/slapd/back-ldbm/ldbm_config.c +@@ -87,6 +87,9 @@ static char *ldbm_config_moved_attributes[] = + CONFIG_SERIAL_LOCK, + CONFIG_USE_LEGACY_ERRORCODE, + CONFIG_DB_DEADLOCK_POLICY, ++ CONFIG_DB_LOCKS_MONITORING, ++ CONFIG_DB_LOCKS_THRESHOLD, ++ CONFIG_DB_LOCKS_PAUSE, + ""}; + + /* Used to add an array of entries, like the one above and +diff --git a/ldap/servers/slapd/back-ldbm/ldbm_config.h b/ldap/servers/slapd/back-ldbm/ldbm_config.h +index 58e64799c..6fa8292eb 100644 +--- a/ldap/servers/slapd/back-ldbm/ldbm_config.h ++++ b/ldap/servers/slapd/back-ldbm/ldbm_config.h +@@ -104,6 +104,9 @@ struct config_info + #define CONFIG_DB_VERBOSE "nsslapd-db-verbose" + #define CONFIG_DB_DEBUG "nsslapd-db-debug" + #define CONFIG_DB_LOCK "nsslapd-db-locks" ++#define CONFIG_DB_LOCKS_MONITORING "nsslapd-db-locks-monitoring-enabled" ++#define CONFIG_DB_LOCKS_THRESHOLD "nsslapd-db-locks-monitoring-threshold" ++#define CONFIG_DB_LOCKS_PAUSE "nsslapd-db-locks-monitoring-pause" + #define CONFIG_DB_NAMED_REGIONS "nsslapd-db-named-regions" + #define CONFIG_DB_PRIVATE_MEM "nsslapd-db-private-mem" + #define CONFIG_DB_PRIVATE_IMPORT_MEM "nsslapd-db-private-import-mem" +diff --git a/ldap/servers/slapd/back-ldbm/ldbm_search.c b/ldap/servers/slapd/back-ldbm/ldbm_search.c +index 1a7b510d4..6e22debde 100644 +--- a/ldap/servers/slapd/back-ldbm/ldbm_search.c ++++ b/ldap/servers/slapd/back-ldbm/ldbm_search.c +@@ -1472,6 +1472,7 @@ ldbm_back_next_search_entry_ext(Slapi_PBlock *pb, int use_extension) + slapi_pblock_get(pb, SLAPI_CONNECTION, &conn); + slapi_pblock_get(pb, SLAPI_OPERATION, &op); + ++ + if ((reverse_list = operation_is_flag_set(op, OP_FLAG_REVERSE_CANDIDATE_ORDER))) { + /* + * Start at the end of the list and work our way forward. Since a single +@@ -1538,6 +1539,18 @@ ldbm_back_next_search_entry_ext(Slapi_PBlock *pb, int use_extension) + + /* Find the next candidate entry and return it. */ + while (1) { ++ if (li->li_dblock_monitoring && ++ slapi_atomic_load_32((int32_t *)&(li->li_dblock_threshold_reached), __ATOMIC_RELAXED)) { ++ slapi_log_err(SLAPI_LOG_CRIT, "ldbm_back_next_search_entry", ++ "DB locks threshold is reached (nsslapd-db-locks-monitoring-threshold " ++ "under cn=bdb,cn=config,cn=ldbm database,cn=plugins,cn=config). " ++ "Please, increase nsslapd-db-locks according to your needs.\n"); ++ slapi_pblock_set(pb, SLAPI_SEARCH_RESULT_ENTRY, NULL); ++ delete_search_result_set(pb, &sr); ++ rc = SLAPI_FAIL_GENERAL; ++ slapi_send_ldap_result(pb, LDAP_UNWILLING_TO_PERFORM, NULL, "DB locks threshold is reached (nsslapd-db-locks-monitoring-threshold)", 0, NULL); ++ goto bail; ++ } + + /* check for abandon */ + if (slapi_op_abandoned(pb) || (NULL == sr)) { +diff --git a/ldap/servers/slapd/libglobs.c b/ldap/servers/slapd/libglobs.c +index 388616b36..db7d01bbc 100644 +--- a/ldap/servers/slapd/libglobs.c ++++ b/ldap/servers/slapd/libglobs.c +@@ -8171,8 +8171,8 @@ config_set(const char *attr, struct berval **values, char *errorbuf, int apply) + #if 0 + debugHashTable(attr); + #endif +- slapi_create_errormsg(errorbuf, SLAPI_DSE_RETURNTEXT_SIZE, "Unknown attribute %s will be ignored", attr); +- slapi_log_err(SLAPI_LOG_ERR, "config_set", "Unknown attribute %s will be ignored", attr); ++ slapi_create_errormsg(errorbuf, SLAPI_DSE_RETURNTEXT_SIZE, "Unknown attribute %s will be ignored\n", attr); ++ slapi_log_err(SLAPI_LOG_ERR, "config_set", "Unknown attribute %s will be ignored\n", attr); + return LDAP_NO_SUCH_ATTRIBUTE; + } + +diff --git a/src/cockpit/389-console/src/css/ds.css b/src/cockpit/389-console/src/css/ds.css +index 9248116e7..3cf50b593 100644 +--- a/src/cockpit/389-console/src/css/ds.css ++++ b/src/cockpit/389-console/src/css/ds.css +@@ -639,6 +639,10 @@ option { + padding-right: 0 !important; + } + ++.ds-vertical-scroll-auto { ++ overflow-y: auto !important; ++} ++ + .alert { + max-width: 750px; + } +diff --git a/src/cockpit/389-console/src/database.jsx b/src/cockpit/389-console/src/database.jsx +index efa3ce6d5..11cae972c 100644 +--- a/src/cockpit/389-console/src/database.jsx ++++ b/src/cockpit/389-console/src/database.jsx +@@ -157,6 +157,7 @@ export class Database extends React.Component { + const attrs = config.attrs; + let db_cache_auto = false; + let import_cache_auto = false; ++ let dblocksMonitoring = false; + let dbhome = ""; + + if ('nsslapd-db-home-directory' in attrs) { +@@ -168,6 +169,9 @@ export class Database extends React.Component { + if (attrs['nsslapd-import-cache-autosize'] != "0") { + import_cache_auto = true; + } ++ if (attrs['nsslapd-db-locks-monitoring-enabled'][0] == "on") { ++ dblocksMonitoring = true; ++ } + + this.setState(() => ( + { +@@ -187,6 +191,9 @@ export class Database extends React.Component { + txnlogdir: attrs['nsslapd-db-logdirectory'], + dbhomedir: dbhome, + dblocks: attrs['nsslapd-db-locks'], ++ dblocksMonitoring: dblocksMonitoring, ++ dblocksMonitoringThreshold: attrs['nsslapd-db-locks-monitoring-threshold'], ++ dblocksMonitoringPause: attrs['nsslapd-db-locks-monitoring-pause'], + chxpoint: attrs['nsslapd-db-checkpoint-interval'], + compactinterval: attrs['nsslapd-db-compactdb-interval'], + importcacheauto: attrs['nsslapd-import-cache-autosize'], +diff --git a/src/cockpit/389-console/src/index.html b/src/cockpit/389-console/src/index.html +index 1278844fc..fd0eeb669 100644 +--- a/src/cockpit/389-console/src/index.html ++++ b/src/cockpit/389-console/src/index.html +@@ -12,7 +12,7 @@ + + + +- ++ +
+ + +diff --git a/src/cockpit/389-console/src/lib/database/databaseConfig.jsx b/src/cockpit/389-console/src/lib/database/databaseConfig.jsx +index f6e662bca..6a71c138d 100644 +--- a/src/cockpit/389-console/src/lib/database/databaseConfig.jsx ++++ b/src/cockpit/389-console/src/lib/database/databaseConfig.jsx +@@ -31,6 +31,9 @@ export class GlobalDatabaseConfig extends React.Component { + txnlogdir: this.props.data.txnlogdir, + dbhomedir: this.props.data.dbhomedir, + dblocks: this.props.data.dblocks, ++ dblocksMonitoring: this.props.data.dblocksMonitoring, ++ dblocksMonitoringThreshold: this.props.data.dblocksMonitoringThreshold, ++ dblocksMonitoringPause: this.props.data.dblocksMonitoringPause, + chxpoint: this.props.data.chxpoint, + compactinterval: this.props.data.compactinterval, + importcachesize: this.props.data.importcachesize, +@@ -47,6 +50,9 @@ export class GlobalDatabaseConfig extends React.Component { + _txnlogdir: this.props.data.txnlogdir, + _dbhomedir: this.props.data.dbhomedir, + _dblocks: this.props.data.dblocks, ++ _dblocksMonitoring: this.props.data.dblocksMonitoring, ++ _dblocksMonitoringThreshold: this.props.data.dblocksMonitoringThreshold, ++ _dblocksMonitoringPause: this.props.data.dblocksMonitoringPause, + _chxpoint: this.props.data.chxpoint, + _compactinterval: this.props.data.compactinterval, + _importcachesize: this.props.data.importcachesize, +@@ -55,6 +61,7 @@ export class GlobalDatabaseConfig extends React.Component { + _import_cache_auto: this.props.data.import_cache_auto, + }; + this.handleChange = this.handleChange.bind(this); ++ this.select_db_locks_monitoring = this.select_db_locks_monitoring.bind(this); + this.select_auto_cache = this.select_auto_cache.bind(this); + this.select_auto_import_cache = this.select_auto_import_cache.bind(this); + this.save_db_config = this.save_db_config.bind(this); +@@ -76,6 +83,12 @@ export class GlobalDatabaseConfig extends React.Component { + }, this.handleChange(e)); + } + ++ select_db_locks_monitoring (val, e) { ++ this.setState({ ++ dblocksMonitoring: !this.state.dblocksMonitoring ++ }, this.handleChange(val, e)); ++ } ++ + handleChange(e) { + // Generic + const value = e.target.type === 'checkbox' ? e.target.checked : e.target.value; +@@ -150,6 +163,21 @@ export class GlobalDatabaseConfig extends React.Component { + cmd.push("--locks=" + this.state.dblocks); + requireRestart = true; + } ++ if (this.state._dblocksMonitoring != this.state.dblocksMonitoring) { ++ if (this.state.dblocksMonitoring) { ++ cmd.push("--locks-monitoring-enabled=on"); ++ } else { ++ cmd.push("--locks-monitoring-enabled=off"); ++ } ++ requireRestart = true; ++ } ++ if (this.state._dblocksMonitoringThreshold != this.state.dblocksMonitoringThreshold) { ++ cmd.push("--locks-monitoring-threshold=" + this.state.dblocksMonitoringThreshold); ++ requireRestart = true; ++ } ++ if (this.state._dblocksMonitoringPause != this.state.dblocksMonitoringPause) { ++ cmd.push("--locks-monitoring-pause=" + this.state.dblocksMonitoringPause); ++ } + if (this.state._chxpoint != this.state.chxpoint) { + cmd.push("--checkpoint-interval=" + this.state.chxpoint); + requireRestart = true; +@@ -216,6 +244,28 @@ export class GlobalDatabaseConfig extends React.Component { + let import_cache_form; + let db_auto_checked = false; + let import_auto_checked = false; ++ let dblocksMonitor = ""; ++ ++ if (this.state.dblocksMonitoring) { ++ dblocksMonitor =
++ ++ ++ DB Locks Threshold Percentage ++ ++ ++ ++ ++ ++ ++ ++ DB Locks Pause Milliseconds ++ ++ ++ ++ ++ ++
; ++ } + + if (this.state.db_cache_auto) { + db_cache_form =
+@@ -422,14 +472,6 @@ export class GlobalDatabaseConfig extends React.Component { + + + +- +- +- Database Locks +- +- +- +- +- + + + Database Checkpoint Interval +@@ -446,6 +488,36 @@ export class GlobalDatabaseConfig extends React.Component { + + + ++ ++ ++ Database Locks ++ ++ ++ ++ ++ ++ ++ ++
DB Locks Monitoring
++
++ ++
++ ++ ++ ++ Enable Monitoring ++ ++ ++ ++ ++ ++ {dblocksMonitor} ++ ++ + +
+ +diff --git a/src/lib389/lib389/backend.py b/src/lib389/lib389/backend.py +index bcd7b383f..13bb27842 100644 +--- a/src/lib389/lib389/backend.py ++++ b/src/lib389/lib389/backend.py +@@ -1011,6 +1011,9 @@ class DatabaseConfig(DSLdapObject): + 'nsslapd-db-transaction-batch-max-wait', + 'nsslapd-db-logbuf-size', + 'nsslapd-db-locks', ++ 'nsslapd-db-locks-monitoring-enabled', ++ 'nsslapd-db-locks-monitoring-threshold', ++ 'nsslapd-db-locks-monitoring-pause', + 'nsslapd-db-private-import-mem', + 'nsslapd-import-cache-autosize', + 'nsslapd-cache-autosize', +diff --git a/src/lib389/lib389/cli_conf/backend.py b/src/lib389/lib389/cli_conf/backend.py +index 6bfbcb036..722764d10 100644 +--- a/src/lib389/lib389/cli_conf/backend.py ++++ b/src/lib389/lib389/cli_conf/backend.py +@@ -46,6 +46,9 @@ arg_to_attr = { + 'txn_batch_max': 'nsslapd-db-transaction-batch-max-wait', + 'logbufsize': 'nsslapd-db-logbuf-size', + 'locks': 'nsslapd-db-locks', ++ 'locks_monitoring_enabled': 'nsslapd-db-locks-monitoring-enabled', ++ 'locks_monitoring_threshold': 'nsslapd-db-locks-monitoring-threshold', ++ 'locks_monitoring_pause': 'nsslapd-db-locks-monitoring-pause', + 'import_cache_autosize': 'nsslapd-import-cache-autosize', + 'cache_autosize': 'nsslapd-cache-autosize', + 'cache_autosize_split': 'nsslapd-cache-autosize-split', +@@ -998,6 +1001,13 @@ def create_parser(subparsers): + 'the batch count (only works when txn-batch-val is set)') + set_db_config_parser.add_argument('--logbufsize', help='Specifies the transaction log information buffer size') + set_db_config_parser.add_argument('--locks', help='Sets the maximum number of database locks') ++ set_db_config_parser.add_argument('--locks-monitoring-enabled', help='Set to "on" or "off" to monitor DB locks. When it crosses the percentage value ' ++ 'set with "--locks-monitoring-threshold" ("on" by default)') ++ set_db_config_parser.add_argument('--locks-monitoring-threshold', help='Sets the DB lock exhaustion value in percentage (valid range is 70-95). If too many locks are ' ++ 'acquired, the server will abort the searches while the number of locks ' ++ 'are not decreased. It helps to avoid DB corruption and long recovery.') ++ set_db_config_parser.add_argument('--locks-monitoring-pause', help='Sets the DB lock monitoring value in milliseconds for the amount of time ' ++ 'that the monitoring thread spends waiting between checks.') + set_db_config_parser.add_argument('--import-cache-autosize', help='Set to "on" or "off" to automatically set the size of the import ' + 'cache to be used during the the import process of LDIF files') + set_db_config_parser.add_argument('--cache-autosize', help='Sets the percentage of free memory that is used in total for the database ' +-- +2.26.3 + diff --git a/SOURCES/0010-Issue-4764-replicated-operation-sometime-checks-ACI-.patch b/SOURCES/0010-Issue-4764-replicated-operation-sometime-checks-ACI-.patch new file mode 100644 index 0000000..489f4b3 --- /dev/null +++ b/SOURCES/0010-Issue-4764-replicated-operation-sometime-checks-ACI-.patch @@ -0,0 +1,33 @@ +From 7573c62a2e61293a4800e67919d79341fa1a1532 Mon Sep 17 00:00:00 2001 +From: progier389 +Date: Wed, 26 May 2021 16:07:43 +0200 +Subject: [PATCH 10/12] Issue 4764 - replicated operation sometime checks ACI + (#4783) + +(cherry picked from commit 0cfdea7abcacfca6686a6cf84dbf7ae1167f3022) +--- + ldap/servers/slapd/connection.c | 8 ++++++++ + 1 file changed, 8 insertions(+) + +diff --git a/ldap/servers/slapd/connection.c b/ldap/servers/slapd/connection.c +index c7a15e775..e0c1a52d2 100644 +--- a/ldap/servers/slapd/connection.c ++++ b/ldap/servers/slapd/connection.c +@@ -1771,6 +1771,14 @@ connection_threadmain() + } + } + ++ /* ++ * Fix bz 1931820 issue (the check to set OP_FLAG_REPLICATED may be done ++ * before replication session is properly set). ++ */ ++ if (replication_connection) { ++ operation_set_flag(op, OP_FLAG_REPLICATED); ++ } ++ + /* + * Call the do_ function to process this request. + */ +-- +2.26.3 + diff --git a/SOURCES/0011-Issue-4778-RFE-Allow-setting-TOD-for-db-compaction-a.patch b/SOURCES/0011-Issue-4778-RFE-Allow-setting-TOD-for-db-compaction-a.patch new file mode 100644 index 0000000..2121550 --- /dev/null +++ b/SOURCES/0011-Issue-4778-RFE-Allow-setting-TOD-for-db-compaction-a.patch @@ -0,0 +1,1453 @@ +From c79630de8012a893ed3d1c46b41bc7871a07a3e2 Mon Sep 17 00:00:00 2001 +From: Mark Reynolds +Date: Wed, 26 May 2021 13:32:13 -0400 +Subject: [PATCH 11/12] Issue 4778 - RFE - Allow setting TOD for db compaction + and add task + +Description: Since database compaction can be costly it should be allowed + to set a time to execute it during offpeak hours. Once the + compaction interval has been met, it will wait for the configured + time of day to do the compaction. The default is just before + midnight: 23:59 + + A task was also created that can run compaction on demand, + and can also just target the replication changelog. This could + be used in conjunction with a cronjob for more complex + execution patterns. + +ASAN tested and approved. + +relates: https://github.com/389ds/389-ds-base/issues/4778 + +Reviewed by: spichugi(Thanks!) +--- + .../tests/suites/config/compact_test.py | 81 ++++++ + ldap/schema/01core389.ldif | 3 +- + ldap/servers/plugins/replication/cl5.h | 1 + + ldap/servers/plugins/replication/cl5_api.c | 70 ++++- + ldap/servers/plugins/replication/cl5_api.h | 2 +- + .../servers/plugins/replication/cl5_clcache.c | 3 - + ldap/servers/plugins/replication/cl5_config.c | 102 ++++++- + ldap/servers/plugins/replication/cl5_init.c | 2 +- + .../servers/plugins/replication/repl_shared.h | 2 + + ldap/servers/plugins/retrocl/retrocl.c | 1 - + .../slapd/back-ldbm/db-bdb/bdb_config.c | 79 ++++++ + .../slapd/back-ldbm/db-bdb/bdb_layer.c | 258 ++++++++++++------ + .../slapd/back-ldbm/db-bdb/bdb_layer.h | 4 +- + ldap/servers/slapd/back-ldbm/init.c | 2 + + ldap/servers/slapd/back-ldbm/ldbm_config.h | 1 + + .../servers/slapd/back-ldbm/proto-back-ldbm.h | 1 + + ldap/servers/slapd/filtercmp.c | 5 +- + ldap/servers/slapd/pblock.c | 17 +- + ldap/servers/slapd/slap.h | 2 + + ldap/servers/slapd/slapi-private.h | 1 + + ldap/servers/slapd/task.c | 102 ++++++- + src/cockpit/389-console/src/database.jsx | 1 + + .../src/lib/database/databaseConfig.jsx | 16 +- + src/lib389/lib389/_constants.py | 1 + + src/lib389/lib389/backend.py | 1 + + src/lib389/lib389/cli_conf/backend.py | 24 +- + src/lib389/lib389/cli_conf/replication.py | 3 + + src/lib389/lib389/tasks.py | 14 +- + 28 files changed, 689 insertions(+), 110 deletions(-) + create mode 100644 dirsrvtests/tests/suites/config/compact_test.py + +diff --git a/dirsrvtests/tests/suites/config/compact_test.py b/dirsrvtests/tests/suites/config/compact_test.py +new file mode 100644 +index 000000000..1f1c097e4 +--- /dev/null ++++ b/dirsrvtests/tests/suites/config/compact_test.py +@@ -0,0 +1,81 @@ ++import logging ++import pytest ++import os ++import time ++from lib389.tasks import DBCompactTask ++from lib389.backend import DatabaseConfig ++from lib389.replica import Changelog5 ++from lib389.topologies import topology_m1 as topo ++ ++log = logging.getLogger(__name__) ++ ++ ++def test_compact_db_task(topo): ++ """Specify a test case purpose or name here ++ ++ :id: 1b3222ef-a336-4259-be21-6a52f76e1859 ++ :setup: Standalone Instance ++ :steps: ++ 1. Create task ++ 2. Check task was successful ++ 3. Check errors log to show task was run ++ 3. Create task just for replication ++ :expectedresults: ++ 1. Success ++ 2. Success ++ 3. Success ++ 4. Success ++ """ ++ inst = topo.ms["supplier1"] ++ ++ task = DBCompactTask(inst) ++ task.create() ++ task.wait() ++ assert task.get_exit_code() == 0 ++ ++ # Check errors log to make sure task actually compacted db ++ assert inst.searchErrorsLog("Compacting databases") ++ inst.deleteErrorLogs(restart=False) ++ ++ ++def test_compaction_interval_and_time(topo): ++ """Specify a test case purpose or name here ++ ++ :id: f361bee9-d7e7-4569-9255-d7b60dd9d92e ++ :setup: Supplier Instance ++ :steps: ++ 1. Configure compact interval and time for database and changelog ++ 2. Check compaction occurs as expected ++ :expectedresults: ++ 1. Success ++ 2. Success ++ """ ++ ++ inst = topo.ms["supplier1"] ++ ++ # Configure DB compaction ++ config = DatabaseConfig(inst) ++ config.set([('nsslapd-db-compactdb-interval', '2'), ('nsslapd-db-compactdb-time', '00:01')]) ++ ++ # Configure changelog compaction ++ cl5 = Changelog5(inst) ++ cl5.replace_many( ++ ('nsslapd-changelogcompactdb-interval', '2'), ++ ('nsslapd-changelogcompactdb-time', '00:01'), ++ ('nsslapd-changelogtrim-interval', '2') ++ ) ++ inst.deleteErrorLogs() ++ ++ # Check is compaction occurred ++ time.sleep(6) ++ assert inst.searchErrorsLog("Compacting databases") ++ assert inst.searchErrorsLog("compacting replication changelogs") ++ inst.deleteErrorLogs(restart=False) ++ ++ ++if __name__ == '__main__': ++ # Run isolated ++ # -s for DEBUG mode ++ CURRENT_FILE = os.path.realpath(__file__) ++ pytest.main(["-s", CURRENT_FILE]) ++ +diff --git a/ldap/schema/01core389.ldif b/ldap/schema/01core389.ldif +index 9e9a26c21..0c73e5114 100644 +--- a/ldap/schema/01core389.ldif ++++ b/ldap/schema/01core389.ldif +@@ -285,6 +285,7 @@ attributeTypes: ( 2.16.840.1.113730.3.1.2310 NAME 'nsds5ReplicaFlowControlWindow + attributeTypes: ( 2.16.840.1.113730.3.1.2311 NAME 'nsds5ReplicaFlowControlPause' DESC 'Netscape defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'Netscape Directory Server' ) + attributeTypes: ( 2.16.840.1.113730.3.1.2313 NAME 'nsslapd-changelogtrim-interval' DESC 'Netscape defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'Netscape Directory Server' ) + attributeTypes: ( 2.16.840.1.113730.3.1.2314 NAME 'nsslapd-changelogcompactdb-interval' DESC 'Netscape defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'Netscape Directory Server' ) ++attributeTypes: ( 2.16.840.1.113730.3.1.2385 NAME 'nsslapd-changelogcompactdb-time' DESC 'Netscape defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'Netscape Directory Server' ) + attributeTypes: ( 2.16.840.1.113730.3.1.2315 NAME 'nsDS5ReplicaWaitForAsyncResults' DESC 'Netscape defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'Netscape Directory Server' ) + attributeTypes: ( 2.16.840.1.113730.3.1.2316 NAME 'nsslapd-auditfaillog-maxlogsize' DESC 'Netscape defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'Netscape Directory Server' ) + attributeTypes: ( 2.16.840.1.113730.3.1.2317 NAME 'nsslapd-auditfaillog-logrotationsync-enabled' DESC 'Netscape defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'Netscape Directory Server' ) +@@ -345,5 +346,5 @@ objectClasses: ( nsEncryptionConfig-oid NAME 'nsEncryptionConfig' DESC 'Netscape + objectClasses: ( nsEncryptionModule-oid NAME 'nsEncryptionModule' DESC 'Netscape defined objectclass' SUP top MUST ( cn ) MAY ( nsSSLToken $ nsSSLPersonalityssl $ nsSSLActivation $ ServerKeyExtractFile $ ServerCertExtractFile ) X-ORIGIN 'Netscape' ) + objectClasses: ( 2.16.840.1.113730.3.2.327 NAME 'rootDNPluginConfig' DESC 'Netscape defined objectclass' SUP top MUST ( cn ) MAY ( rootdn-open-time $ rootdn-close-time $ rootdn-days-allowed $ rootdn-allow-host $ rootdn-deny-host $ rootdn-allow-ip $ rootdn-deny-ip ) X-ORIGIN 'Netscape' ) + objectClasses: ( 2.16.840.1.113730.3.2.328 NAME 'nsSchemaPolicy' DESC 'Netscape defined objectclass' SUP top MAY ( cn $ schemaUpdateObjectclassAccept $ schemaUpdateObjectclassReject $ schemaUpdateAttributeAccept $ schemaUpdateAttributeReject) X-ORIGIN 'Netscape Directory Server' ) +-objectClasses: ( 2.16.840.1.113730.3.2.332 NAME 'nsChangelogConfig' DESC 'Configuration of the changelog5 object' SUP top MUST ( cn $ nsslapd-changelogdir ) MAY ( nsslapd-changelogmaxage $ nsslapd-changelogtrim-interval $ nsslapd-changelogmaxentries $ nsslapd-changelogsuffix $ nsslapd-changelogcompactdb-interval $ nsslapd-encryptionalgorithm $ nsSymmetricKey ) X-ORIGIN '389 Directory Server' ) ++objectClasses: ( 2.16.840.1.113730.3.2.332 NAME 'nsChangelogConfig' DESC 'Configuration of the changelog5 object' SUP top MUST ( cn $ nsslapd-changelogdir ) MAY ( nsslapd-changelogmaxage $ nsslapd-changelogtrim-interval $ nsslapd-changelogmaxentries $ nsslapd-changelogsuffix $ nsslapd-changelogcompactdb-interval $ nsslapd-changelogcompactdb-time $ nsslapd-encryptionalgorithm $ nsSymmetricKey ) X-ORIGIN '389 Directory Server' ) + objectClasses: ( 2.16.840.1.113730.3.2.337 NAME 'rewriterEntry' DESC '' SUP top MUST ( nsslapd-libPath ) MAY ( cn $ nsslapd-filterrewriter $ nsslapd-returnedAttrRewriter ) X-ORIGIN '389 Directory Server' ) +diff --git a/ldap/servers/plugins/replication/cl5.h b/ldap/servers/plugins/replication/cl5.h +index 2af57e369..99ea1c6a2 100644 +--- a/ldap/servers/plugins/replication/cl5.h ++++ b/ldap/servers/plugins/replication/cl5.h +@@ -29,6 +29,7 @@ typedef struct changelog5Config + char *symmetricKey; + long compactInterval; + long trimInterval; ++ char *compactTime; + } changelog5Config; + + /* initializes changelog*/ +diff --git a/ldap/servers/plugins/replication/cl5_api.c b/ldap/servers/plugins/replication/cl5_api.c +index 403a6a666..75a2f46f5 100644 +--- a/ldap/servers/plugins/replication/cl5_api.c ++++ b/ldap/servers/plugins/replication/cl5_api.c +@@ -158,6 +158,7 @@ typedef struct cl5trim + time_t maxAge; /* maximum entry age in seconds */ + int maxEntries; /* maximum number of entries across all changelog files */ + int compactInterval; /* interval to compact changelog db */ ++ char *compactTime; /* time to compact changelog db */ + int trimInterval; /* trimming interval */ + PRLock *lock; /* controls access to trimming configuration */ + } CL5Trim; +@@ -184,6 +185,7 @@ typedef struct cl5desc + PRLock *clLock; /* Lock associated to clVar, used to notify threads on close */ + PRCondVar *clCvar; /* Condition Variable used to notify threads on close */ + void *clcrypt_handle; /* for cl encryption */ ++ char *compact_time; /* Time to execute changelog compaction */ + } CL5Desc; + + typedef void (*VFP)(void *); +@@ -1025,7 +1027,7 @@ cl5GetState() + CL5_BAD_STATE if changelog is not open + */ + int +-cl5ConfigTrimming(int maxEntries, const char *maxAge, int compactInterval, int trimInterval) ++cl5ConfigTrimming(int maxEntries, const char *maxAge, int compactInterval, char *compactTime, int trimInterval) + { + if (s_cl5Desc.dbState == CL5_STATE_NONE) { + slapi_log_err(SLAPI_LOG_ERR, repl_plugin_name_cl, +@@ -1061,6 +1063,10 @@ cl5ConfigTrimming(int maxEntries, const char *maxAge, int compactInterval, int t + s_cl5Desc.dbTrim.compactInterval = compactInterval; + } + ++ if (strcmp(compactTime, CL5_STR_IGNORE) != 0) { ++ s_cl5Desc.dbTrim.compactTime = slapi_ch_strdup(compactTime); ++ } ++ + if (trimInterval != CL5_NUM_IGNORE) { + s_cl5Desc.dbTrim.trimInterval = trimInterval; + } +@@ -3077,16 +3083,48 @@ _cl5TrimCleanup(void) + { + if (s_cl5Desc.dbTrim.lock) + PR_DestroyLock(s_cl5Desc.dbTrim.lock); ++ slapi_ch_free_string(&s_cl5Desc.dbTrim.compactTime); + + memset(&s_cl5Desc.dbTrim, 0, sizeof(s_cl5Desc.dbTrim)); + } + ++static time_t ++_cl5_get_tod_expiration(char *expire_time) ++{ ++ time_t start_time, todays_elapsed_time, now = time(NULL); ++ struct tm *tm_struct = localtime(&now); ++ char hour_str[3] = {0}; ++ char min_str[3] = {0}; ++ char *s = expire_time; ++ char *endp = NULL; ++ int32_t hour, min, expiring_time; ++ ++ /* Get today's start time */ ++ todays_elapsed_time = (tm_struct->tm_hour * 3600) + (tm_struct->tm_min * 60) + (tm_struct->tm_sec); ++ start_time = slapi_current_utc_time() - todays_elapsed_time; ++ ++ /* Get the hour and minute and calculate the expiring time. The time was ++ * already validated in bdb_config.c: HH:MM */ ++ hour_str[0] = *s++; ++ hour_str[1] = *s++; ++ s++; /* skip colon */ ++ min_str[0] = *s++; ++ min_str[1] = *s++; ++ hour = strtoll(hour_str, &endp, 10); ++ min = strtoll(min_str, &endp, 10); ++ expiring_time = (hour * 60 * 60) + (min * 60); ++ ++ return start_time + expiring_time; ++} ++ + static int + _cl5TrimMain(void *param __attribute__((unused))) + { + time_t timePrev = slapi_current_utc_time(); + time_t timeCompactPrev = slapi_current_utc_time(); + time_t timeNow; ++ PRBool compacting = PR_FALSE; ++ int32_t compactdb_time = 0; + + PR_AtomicIncrement(&s_cl5Desc.threadCount); + +@@ -3097,11 +3135,26 @@ _cl5TrimMain(void *param __attribute__((unused))) + timePrev = timeNow; + _cl5DoTrimming(); + } ++ ++ if (!compacting) { ++ /* Once we know we want to compact we need to stop refreshing the ++ * TOD expiration. Otherwise if the compact time is close to ++ * midnight we could roll over past midnight during the checkpoint ++ * sleep interval, and we'd never actually compact the databases. ++ * We also need to get this value before the sleep. ++ */ ++ compactdb_time = _cl5_get_tod_expiration(s_cl5Desc.dbTrim.compactTime); ++ } + if ((s_cl5Desc.dbTrim.compactInterval > 0) && +- (timeNow - timeCompactPrev >= s_cl5Desc.dbTrim.compactInterval)) { +- /* time to trim */ +- timeCompactPrev = timeNow; +- _cl5CompactDBs(); ++ (timeNow - timeCompactPrev >= s_cl5Desc.dbTrim.compactInterval)) ++ { ++ compacting = PR_TRUE; ++ if (slapi_current_utc_time() > compactdb_time) { ++ /* time to trim */ ++ timeCompactPrev = timeNow; ++ _cl5CompactDBs(); ++ compacting = PR_FALSE; ++ } + } + if (NULL == s_cl5Desc.clLock) { + /* most likely, emergency */ +@@ -3215,6 +3268,10 @@ _cl5CompactDBs(void) + rc, db_strerror(rc)); + goto bail; + } ++ ++ ++ slapi_log_err(SLAPI_LOG_NOTICE, repl_plugin_name_cl, ++ "_cl5CompactDBs - compacting replication changelogs...\n"); + for (fileObj = objset_first_obj(s_cl5Desc.dbFiles); + fileObj; + fileObj = objset_next_obj(s_cl5Desc.dbFiles, fileObj)) { +@@ -3235,6 +3292,9 @@ _cl5CompactDBs(void) + "_cl5CompactDBs - %s - %d pages freed\n", + dbFile->replName, c_data.compact_pages_free); + } ++ ++ slapi_log_err(SLAPI_LOG_NOTICE, repl_plugin_name_cl, ++ "_cl5CompactDBs - compacting replication changelogs finished.\n"); + bail: + if (fileObj) { + object_release(fileObj); +diff --git a/ldap/servers/plugins/replication/cl5_api.h b/ldap/servers/plugins/replication/cl5_api.h +index 302af97a0..4b0949fb3 100644 +--- a/ldap/servers/plugins/replication/cl5_api.h ++++ b/ldap/servers/plugins/replication/cl5_api.h +@@ -236,7 +236,7 @@ int cl5GetState(void); + Return: CL5_SUCCESS if successful; + CL5_BAD_STATE if changelog has not been open + */ +-int cl5ConfigTrimming(int maxEntries, const char *maxAge, int compactInterval, int trimInterval); ++int cl5ConfigTrimming(int maxEntries, const char *maxAge, int compactInterval, char *compactTime, int trimInterval); + + void cl5DestroyIterator(void *iterator); + +diff --git a/ldap/servers/plugins/replication/cl5_clcache.c b/ldap/servers/plugins/replication/cl5_clcache.c +index 90dec4d54..e5a39c9c1 100644 +--- a/ldap/servers/plugins/replication/cl5_clcache.c ++++ b/ldap/servers/plugins/replication/cl5_clcache.c +@@ -452,9 +452,6 @@ static int + clcache_cursor_set(DBC *cursor, CLC_Buffer *buf) + { + int rc; +- uint32_t ulen; +- uint32_t dlen; +- uint32_t size; + + rc = cursor->c_get(cursor, &buf->buf_key, &buf->buf_data, DB_SET); + if (rc == DB_BUFFER_SMALL) { +diff --git a/ldap/servers/plugins/replication/cl5_config.c b/ldap/servers/plugins/replication/cl5_config.c +index e0530bed2..b32686788 100644 +--- a/ldap/servers/plugins/replication/cl5_config.c ++++ b/ldap/servers/plugins/replication/cl5_config.c +@@ -131,6 +131,7 @@ changelog5_config_done(changelog5Config *config) + /* slapi_ch_free_string accepts NULL pointer */ + slapi_ch_free_string(&config->maxAge); + slapi_ch_free_string(&config->dir); ++ slapi_ch_free_string(&config->compactTime); + slapi_ch_free_string(&config->symmetricKey); + slapi_ch_free_string(&config->dbconfig.encryptionAlgorithm); + slapi_ch_free_string(&config->dbconfig.symmetricKey); +@@ -211,7 +212,7 @@ changelog5_config_add(Slapi_PBlock *pb __attribute__((unused)), + } + + /* set trimming parameters */ +- rc = cl5ConfigTrimming(config.maxEntries, config.maxAge, config.compactInterval, config.trimInterval); ++ rc = cl5ConfigTrimming(config.maxEntries, config.maxAge, config.compactInterval, config.compactTime, config.trimInterval); + if (rc != CL5_SUCCESS) { + *returncode = 1; + if (returntext) { +@@ -302,6 +303,7 @@ changelog5_config_modify(Slapi_PBlock *pb, + config.compactInterval = CL5_NUM_IGNORE; + slapi_ch_free_string(&config.maxAge); + config.maxAge = slapi_ch_strdup(CL5_STR_IGNORE); ++ config.compactTime = slapi_ch_strdup(CHANGELOGDB_COMPACT_TIME); + config.trimInterval = CL5_NUM_IGNORE; + + slapi_pblock_get(pb, SLAPI_MODIFY_MODS, &mods); +@@ -375,6 +377,55 @@ changelog5_config_modify(Slapi_PBlock *pb, + *returncode = LDAP_UNWILLING_TO_PERFORM; + goto done; + } ++ } else if (strcasecmp(config_attr, CONFIG_CHANGELOG_COMPACTTIME_ATTRIBUTE) == 0) { ++ if (config_attr_value && config_attr_value[0] != '\0') { ++ char *val = slapi_ch_strdup(config_attr_value); ++ char *endp = NULL; ++ char *hour_str = NULL; ++ char *min_str = NULL; ++ int32_t hour, min; ++ errno = 0; ++ ++ slapi_ch_free_string(&config.compactTime); ++ ++ if (strstr(val, ":")) { ++ /* Get the hour and minute */ ++ hour_str = ldap_utf8strtok_r(val, ":", &min_str); ++ /* Validate hour */ ++ hour = strtoll(hour_str, &endp, 10); ++ if (*endp != '\0' || errno == ERANGE || hour < 0 || hour > 23 || strlen(hour_str) != 2) { ++ slapi_create_errormsg(returntext, SLAPI_DSE_RETURNTEXT_SIZE, ++ "Invalid hour set (%s), must be a two digit number between 00 and 23", ++ hour_str); ++ slapi_log_err(SLAPI_LOG_ERR, "changelog5_extract_config", ++ "Invalid minute set (%s), must be a two digit number between 00 and 59. " ++ "Using default of 23:59\n", hour_str); ++ *returncode = LDAP_UNWILLING_TO_PERFORM; ++ goto done; ++ } ++ /* Validate minute */ ++ min = strtoll(min_str, &endp, 10); ++ if (*endp != '\0' || errno == ERANGE || min < 0 || min > 59 || strlen(min_str) != 2) { ++ slapi_create_errormsg(returntext, SLAPI_DSE_RETURNTEXT_SIZE, ++ "Invalid minute set (%s), must be a two digit number between 00 and 59", ++ hour_str); ++ slapi_log_err(SLAPI_LOG_ERR, "changelog5_extract_config", ++ "Invalid minute set (%s), must be a two digit number between 00 and 59. " ++ "Using default of 23:59\n", min_str); ++ *returncode = LDAP_UNWILLING_TO_PERFORM; ++ goto done; ++ } ++ } else { ++ /* Wrong format */ ++ slapi_create_errormsg(returntext, SLAPI_DSE_RETURNTEXT_SIZE, ++ "Invalid setting (%s), must have a time format of HH:MM", val); ++ slapi_log_err(SLAPI_LOG_ERR, "changelog5_extract_config", ++ "Invalid setting (%s), must have a time format of HH:MM\n", val); ++ *returncode = LDAP_UNWILLING_TO_PERFORM; ++ goto done; ++ } ++ config.compactTime = slapi_ch_strdup(config_attr_value); ++ } + } else if (strcasecmp(config_attr, CONFIG_CHANGELOG_TRIM_ATTRIBUTE) == 0) { + if (slapi_is_duration_valid(config_attr_value)) { + config.trimInterval = (long)slapi_parse_duration(config_attr_value); +@@ -419,6 +470,11 @@ changelog5_config_modify(Slapi_PBlock *pb, + if (originalConfig->maxAge) + config.maxAge = slapi_ch_strdup(originalConfig->maxAge); + } ++ if (strcmp(config.compactTime, CL5_STR_IGNORE) == 0) { ++ slapi_ch_free_string(&config.compactTime); ++ if (originalConfig->compactTime) ++ config.compactTime = slapi_ch_strdup(originalConfig->compactTime); ++ } + + /* attempt to change chagelog dir */ + if (config.dir) { +@@ -519,7 +575,7 @@ changelog5_config_modify(Slapi_PBlock *pb, + if (config.maxEntries != CL5_NUM_IGNORE || + config.trimInterval != CL5_NUM_IGNORE || + strcmp(config.maxAge, CL5_STR_IGNORE) != 0) { +- rc = cl5ConfigTrimming(config.maxEntries, config.maxAge, config.compactInterval, config.trimInterval); ++ rc = cl5ConfigTrimming(config.maxEntries, config.maxAge, config.compactInterval, config.compactTime, config.trimInterval); + if (rc != CL5_SUCCESS) { + *returncode = 1; + if (returntext) { +@@ -689,6 +745,7 @@ changelog5_extract_config(Slapi_Entry *entry, changelog5Config *config) + { + const char *arg; + char *max_age = NULL; ++ char *val = NULL; + + memset(config, 0, sizeof(*config)); + config->dir = slapi_entry_attr_get_charptr(entry, CONFIG_CHANGELOG_DIR_ATTRIBUTE); +@@ -711,6 +768,47 @@ changelog5_extract_config(Slapi_Entry *entry, changelog5Config *config) + config->compactInterval = CHANGELOGDB_COMPACT_INTERVAL; + } + ++ arg = slapi_entry_attr_get_ref(entry, CONFIG_CHANGELOG_COMPACTTIME_ATTRIBUTE); ++ if (arg) { ++ char *endp = NULL; ++ char *hour_str = NULL; ++ char *min_str = NULL; ++ int32_t hour, min; ++ errno = 0; ++ ++ val = slapi_ch_strdup((char *)arg); ++ if (strstr(val, ":")) { ++ /* Get the hour and minute */ ++ hour_str = ldap_utf8strtok_r(val, ":", &min_str); ++ /* Validate hour */ ++ hour = strtoll(hour_str, &endp, 10); ++ if (*endp != '\0' || errno == ERANGE || hour < 0 || hour > 23 || strlen(hour_str) != 2) { ++ slapi_log_err(SLAPI_LOG_ERR, "changelog5_extract_config", ++ "Invalid minute set (%s), must be a two digit number between 00 and 59. " ++ "Using default of 23:59\n", hour_str); ++ goto set_default; ++ } ++ /* Validate minute */ ++ min = strtoll(min_str, &endp, 10); ++ if (*endp != '\0' || errno == ERANGE || min < 0 || min > 59 || strlen(min_str) != 2) { ++ slapi_log_err(SLAPI_LOG_ERR, "changelog5_extract_config", ++ "Invalid minute set (%s), must be a two digit number between 00 and 59. " ++ "Using default of 23:59\n", min_str); ++ goto set_default; ++ } ++ } else { ++ /* Wrong format */ ++ slapi_log_err(SLAPI_LOG_ERR, "changelog5_extract_config", ++ "Invalid setting (%s), must have a time format of HH:MM\n", val); ++ goto set_default; ++ } ++ config->compactTime = slapi_ch_strdup(arg); ++ } else { ++ set_default: ++ config->compactTime = slapi_ch_strdup(CHANGELOGDB_COMPACT_TIME); ++ } ++ slapi_ch_free_string(&val); ++ + arg = slapi_entry_attr_get_ref(entry, CONFIG_CHANGELOG_TRIM_ATTRIBUTE); + if (arg) { + if (slapi_is_duration_valid(arg)) { +diff --git a/ldap/servers/plugins/replication/cl5_init.c b/ldap/servers/plugins/replication/cl5_init.c +index 112c4ece4..251859714 100644 +--- a/ldap/servers/plugins/replication/cl5_init.c ++++ b/ldap/servers/plugins/replication/cl5_init.c +@@ -57,7 +57,7 @@ changelog5_init() + } + + /* set trimming parameters */ +- rc = cl5ConfigTrimming(config.maxEntries, config.maxAge, config.compactInterval, config.trimInterval); ++ rc = cl5ConfigTrimming(config.maxEntries, config.maxAge, config.compactInterval, config.compactTime, config.trimInterval); + if (rc != CL5_SUCCESS) { + slapi_log_err(SLAPI_LOG_ERR, repl_plugin_name_cl, + "changelog5_init: failed to configure changelog trimming\n"); +diff --git a/ldap/servers/plugins/replication/repl_shared.h b/ldap/servers/plugins/replication/repl_shared.h +index b1ed86934..6708e12f7 100644 +--- a/ldap/servers/plugins/replication/repl_shared.h ++++ b/ldap/servers/plugins/replication/repl_shared.h +@@ -26,11 +26,13 @@ + + #define CHANGELOGDB_TRIM_INTERVAL 300 /* 5 minutes */ + #define CHANGELOGDB_COMPACT_INTERVAL 2592000 /* 30 days */ ++#define CHANGELOGDB_COMPACT_TIME "23:55" /* 30 days */ + + #define CONFIG_CHANGELOG_DIR_ATTRIBUTE "nsslapd-changelogdir" + #define CONFIG_CHANGELOG_MAXENTRIES_ATTRIBUTE "nsslapd-changelogmaxentries" + #define CONFIG_CHANGELOG_MAXAGE_ATTRIBUTE "nsslapd-changelogmaxage" + #define CONFIG_CHANGELOG_COMPACTDB_ATTRIBUTE "nsslapd-changelogcompactdb-interval" ++#define CONFIG_CHANGELOG_COMPACTTIME_ATTRIBUTE "nsslapd-changelogcompactdb-time" + #define CONFIG_CHANGELOG_TRIM_ATTRIBUTE "nsslapd-changelogtrim-interval" + /* Changelog Internal Configuration Parameters -> Changelog Cache related */ + #define CONFIG_CHANGELOG_ENCRYPTION_ALGORITHM "nsslapd-encryptionalgorithm" +diff --git a/ldap/servers/plugins/retrocl/retrocl.c b/ldap/servers/plugins/retrocl/retrocl.c +index 2a620301c..f73c81528 100644 +--- a/ldap/servers/plugins/retrocl/retrocl.c ++++ b/ldap/servers/plugins/retrocl/retrocl.c +@@ -400,7 +400,6 @@ retrocl_start(Slapi_PBlock *pb) + + for (size_t i = 0; i < num_vals; i++) { + char *value = values[i]; +- size_t length = strlen(value); + + char *pos = strchr(value, ':'); + if (pos == NULL) { +diff --git a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_config.c b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_config.c +index 167644943..4261c6ce2 100644 +--- a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_config.c ++++ b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_config.c +@@ -678,6 +678,84 @@ bdb_config_db_compactdb_interval_set(void *arg, + return retval; + } + ++static void * ++bdb_config_db_compactdb_time_get(void *arg) ++{ ++ struct ldbminfo *li = (struct ldbminfo *)arg; ++ return (void *)slapi_ch_strdup(BDB_CONFIG(li)->bdb_compactdb_time); ++} ++ ++static int ++bdb_config_db_compactdb_time_set(void *arg, ++ void *value, ++ char *errorbuf __attribute__((unused)), ++ int phase __attribute__((unused)), ++ int apply) ++{ ++ struct ldbminfo *li = (struct ldbminfo *)arg; ++ char *val = slapi_ch_strdup((char *)value); ++ char *endp = NULL; ++ char *hour_str = NULL; ++ char *min_str = NULL; ++ char *default_time = "23:59"; ++ int32_t hour, min; ++ int retval = LDAP_SUCCESS; ++ errno = 0; ++ ++ if (strstr(val, ":")) { ++ /* Get the hour and minute */ ++ hour_str = ldap_utf8strtok_r(val, ":", &min_str); ++ ++ /* Validate hour */ ++ hour = strtoll(hour_str, &endp, 10); ++ if (*endp != '\0' || errno == ERANGE || hour < 0 || hour > 23 || strlen(hour_str) != 2) { ++ slapi_create_errormsg(errorbuf, SLAPI_DSE_RETURNTEXT_SIZE, ++ "Invalid hour set (%s), must be a two digit number between 00 and 23", ++ hour_str); ++ slapi_log_err(SLAPI_LOG_ERR, "bdb_config_db_compactdb_interval_set", ++ "Invalid minute set (%s), must be a two digit number between 00 and 59. " ++ "Using default of 23:59\n", hour_str); ++ retval = LDAP_OPERATIONS_ERROR; ++ goto done; ++ } ++ ++ /* Validate minute */ ++ min = strtoll(min_str, &endp, 10); ++ if (*endp != '\0' || errno == ERANGE || min < 0 || min > 59 || strlen(min_str) != 2) { ++ slapi_create_errormsg(errorbuf, SLAPI_DSE_RETURNTEXT_SIZE, ++ "Invalid minute set (%s), must be a two digit number between 00 and 59", ++ hour_str); ++ slapi_log_err(SLAPI_LOG_ERR, "bdb_config_db_compactdb_interval_set", ++ "Invalid minute set (%s), must be a two digit number between 00 and 59. " ++ "Using default of 23:59\n", min_str); ++ retval = LDAP_OPERATIONS_ERROR; ++ goto done; ++ } ++ } else { ++ /* Wrong format */ ++ slapi_create_errormsg(errorbuf, SLAPI_DSE_RETURNTEXT_SIZE, ++ "Invalid setting (%s), must have a time format of HH:MM", val); ++ slapi_log_err(SLAPI_LOG_ERR, "bdb_config_db_compactdb_interval_set", ++ "Invalid setting (%s), must have a time format of HH:MM\n", val); ++ retval = LDAP_OPERATIONS_ERROR; ++ goto done; ++ } ++ ++done: ++ if (apply) { ++ slapi_ch_free((void **)&(BDB_CONFIG(li)->bdb_compactdb_time)); ++ if (retval) { ++ /* Something went wrong, use the default */ ++ BDB_CONFIG(li)->bdb_compactdb_time = slapi_ch_strdup(default_time); ++ } else { ++ BDB_CONFIG(li)->bdb_compactdb_time = slapi_ch_strdup((char *)value); ++ } ++ } ++ slapi_ch_free_string(&val); ++ ++ return retval; ++} ++ + static void * + bdb_config_db_page_size_get(void *arg) + { +@@ -1473,6 +1551,7 @@ static config_info bdb_config_param[] = { + {CONFIG_DB_TRANSACTION_WAIT, CONFIG_TYPE_ONOFF, "off", &bdb_config_db_transaction_wait_get, &bdb_config_db_transaction_wait_set, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE}, + {CONFIG_DB_CHECKPOINT_INTERVAL, CONFIG_TYPE_INT, "60", &bdb_config_db_checkpoint_interval_get, &bdb_config_db_checkpoint_interval_set, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE}, + {CONFIG_DB_COMPACTDB_INTERVAL, CONFIG_TYPE_INT, "2592000" /*30days*/, &bdb_config_db_compactdb_interval_get, &bdb_config_db_compactdb_interval_set, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE}, ++ {CONFIG_DB_COMPACTDB_TIME, CONFIG_TYPE_STRING, "23:59", &bdb_config_db_compactdb_time_get, &bdb_config_db_compactdb_time_set, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE}, + {CONFIG_DB_TRANSACTION_BATCH, CONFIG_TYPE_INT, "0", &bdb_get_batch_transactions, &bdb_set_batch_transactions, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE}, + {CONFIG_DB_TRANSACTION_BATCH_MIN_SLEEP, CONFIG_TYPE_INT, "50", &bdb_get_batch_txn_min_sleep, &bdb_set_batch_txn_min_sleep, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE}, + {CONFIG_DB_TRANSACTION_BATCH_MAX_SLEEP, CONFIG_TYPE_INT, "50", &bdb_get_batch_txn_max_sleep, &bdb_set_batch_txn_max_sleep, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE}, +diff --git a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.c b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.c +index 2f25f67a2..ec1976d38 100644 +--- a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.c ++++ b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.c +@@ -2126,6 +2126,7 @@ bdb_post_close(struct ldbminfo *li, int dbmode) + */ + slapi_ch_free_string(&conf->bdb_dbhome_directory); + slapi_ch_free_string(&conf->bdb_home_directory); ++ slapi_ch_free_string(&conf->bdb_compactdb_time); + } + + return return_value; +@@ -3644,6 +3645,39 @@ log_flush_threadmain(void *param) + return 0; + } + ++/* ++ * This refreshes the TOD expiration. So live changes to the configuration ++ * will take effect immediately. ++ */ ++static time_t ++bdb_get_tod_expiration(char *expire_time) ++{ ++ time_t start_time, todays_elapsed_time, now = time(NULL); ++ struct tm *tm_struct = localtime(&now); ++ char hour_str[3] = {0}; ++ char min_str[3] = {0}; ++ char *s = expire_time; ++ char *endp = NULL; ++ int32_t hour, min, expiring_time; ++ ++ /* Get today's start time */ ++ todays_elapsed_time = (tm_struct->tm_hour * 3600) + (tm_struct->tm_min * 60) + (tm_struct->tm_sec); ++ start_time = slapi_current_utc_time() - todays_elapsed_time; ++ ++ /* Get the hour and minute and calculate the expiring time. The time was ++ * already validated in bdb_config.c: HH:MM */ ++ hour_str[0] = *s++; ++ hour_str[1] = *s++; ++ s++; /* skip colon */ ++ min_str[0] = *s++; ++ min_str[1] = *s++; ++ hour = strtoll(hour_str, &endp, 10); ++ min = strtoll(min_str, &endp, 10); ++ expiring_time = (hour * 60 * 60) + (min * 60); ++ ++ return start_time + expiring_time; ++} ++ + /* + * create a thread for checkpoint_threadmain + */ +@@ -3685,7 +3719,9 @@ checkpoint_threadmain(void *param) + time_t checkpoint_interval_update = 0; + time_t compactdb_interval = 0; + time_t checkpoint_interval = 0; +- back_txn txn; ++ int32_t compactdb_time = 0; ++ PRBool compacting = PR_FALSE; ++ + + PR_ASSERT(NULL != param); + li = (struct ldbminfo *)param; +@@ -3724,22 +3760,35 @@ checkpoint_threadmain(void *param) + slapi_timespec_expire_at(checkpoint_interval, &checkpoint_expire); + + while (!BDB_CONFIG(li)->bdb_stop_threads) { +- /* sleep for a while */ +- /* why aren't we sleeping exactly the right amount of time ? */ +- /* answer---because the interval might be changed after the server +- * starts up */ ++ PR_Lock(li->li_config_mutex); ++ checkpoint_interval_update = (time_t)BDB_CONFIG(li)->bdb_checkpoint_interval; ++ compactdb_interval_update = (time_t)BDB_CONFIG(li)->bdb_compactdb_interval; ++ if (!compacting) { ++ /* Once we know we want to compact we need to stop refreshing the ++ * TOD expiration. Otherwise if the compact time is close to ++ * midnight we could roll over past midnight during the checkpoint ++ * sleep interval, and we'd never actually compact the databases. ++ * We also need to get this value before the sleep. ++ */ ++ compactdb_time = bdb_get_tod_expiration((char *)BDB_CONFIG(li)->bdb_compactdb_time); ++ } ++ PR_Unlock(li->li_config_mutex); ++ ++ if (compactdb_interval_update != compactdb_interval) { ++ /* Compact interval was changed, so reset the timer */ ++ slapi_timespec_expire_at(compactdb_interval_update, &compactdb_expire); ++ } + ++ /* Sleep for a while ... ++ * Why aren't we sleeping exactly the right amount of time ? ++ * Answer---because the interval might be changed after the server ++ * starts up */ + DS_Sleep(interval); + + if (0 == BDB_CONFIG(li)->bdb_enable_transactions) { + continue; + } + +- PR_Lock(li->li_config_mutex); +- checkpoint_interval_update = (time_t)BDB_CONFIG(li)->bdb_checkpoint_interval; +- compactdb_interval_update = (time_t)BDB_CONFIG(li)->bdb_compactdb_interval; +- PR_Unlock(li->li_config_mutex); +- + /* If the checkpoint has been updated OR we have expired */ + if (checkpoint_interval != checkpoint_interval_update || + slapi_timespec_expire_check(&checkpoint_expire) == TIMER_EXPIRED) { +@@ -3807,94 +3856,37 @@ checkpoint_threadmain(void *param) + + /* + * Remember that if compactdb_interval is 0, timer_expired can +- * never occur unless the value in compctdb_interval changes. ++ * never occur unless the value in compactdb_interval changes. + * +- * this could have been a bug infact, where compactdb_interval ++ * this could have been a bug in fact, where compactdb_interval + * was 0, if you change while running it would never take effect .... + */ +- if (compactdb_interval_update != compactdb_interval || +- slapi_timespec_expire_check(&compactdb_expire) == TIMER_EXPIRED) { +- int rc = 0; +- Object *inst_obj; +- ldbm_instance *inst; +- DB *db = NULL; +- DB_COMPACT c_data = {0}; +- +- for (inst_obj = objset_first_obj(li->li_instance_set); +- inst_obj; +- inst_obj = objset_next_obj(li->li_instance_set, inst_obj)) { +- inst = (ldbm_instance *)object_get_data(inst_obj); +- rc = dblayer_get_id2entry(inst->inst_be, &db); +- if (!db || rc) { +- continue; +- } +- slapi_log_err(SLAPI_LOG_NOTICE, "checkpoint_threadmain", "Compacting DB start: %s\n", +- inst->inst_name); +- +- /* +- * It's possible for this to heap us after free because when we access db +- * *just* as the server shut's down, we don't know it. So we should probably +- * do something like wrapping access to the db var in a rwlock, and have "read" +- * to access, and take writes to change the state. This would prevent the issue. +- */ +- DBTYPE type; +- rc = db->get_type(db, &type); +- if (rc) { +- slapi_log_err(SLAPI_LOG_ERR, "checkpoint_threadmain", +- "compactdb: failed to determine db type for %s: db error - %d %s\n", +- inst->inst_name, rc, db_strerror(rc)); +- continue; +- } ++ if (slapi_timespec_expire_check(&compactdb_expire) == TIMER_EXPIRED) { ++ compacting = PR_TRUE; ++ if (slapi_current_utc_time() < compactdb_time) { ++ /* We have passed the interval, but we need to wait for a ++ * particular TOD to pass before compacting */ ++ continue; ++ } + +- rc = dblayer_txn_begin(inst->inst_be, NULL, &txn); +- if (rc) { +- slapi_log_err(SLAPI_LOG_ERR, "checkpoint_threadmain", "compactdb: transaction begin failed: %d\n", rc); +- break; +- } +- /* +- * https://docs.oracle.com/cd/E17275_01/html/api_reference/C/BDB-C_APIReference.pdf +- * "DB_FREELIST_ONLY +- * Do no page compaction, only returning pages to the filesystem that are already free and at the end +- * of the file. This flag must be set if the database is a Hash access method database." +- * +- */ ++ /* Time to compact the DB's */ ++ dblayer_force_checkpoint(li); ++ bdb_compact(li); ++ dblayer_force_checkpoint(li); + +- uint32_t compact_flags = DB_FREE_SPACE; +- if (type == DB_HASH) { +- compact_flags |= DB_FREELIST_ONLY; +- } +- rc = db->compact(db, txn.back_txn_txn, NULL /*start*/, NULL /*stop*/, +- &c_data, compact_flags, NULL /*end*/); +- if (rc) { +- slapi_log_err(SLAPI_LOG_ERR, "checkpoint_threadmain", +- "compactdb: failed to compact %s; db error - %d %s\n", +- inst->inst_name, rc, db_strerror(rc)); +- if ((rc = dblayer_txn_abort(inst->inst_be, &txn))) { +- slapi_log_err(SLAPI_LOG_ERR, "checkpoint_threadmain", "compactdb: failed to abort txn (%s) db error - %d %s\n", +- inst->inst_name, rc, db_strerror(rc)); +- break; +- } +- } else { +- slapi_log_err(SLAPI_LOG_NOTICE, "checkpoint_threadmain", +- "compactdb: compact %s - %d pages freed\n", +- inst->inst_name, c_data.compact_pages_free); +- if ((rc = dblayer_txn_commit(inst->inst_be, &txn))) { +- slapi_log_err(SLAPI_LOG_ERR, "checkpoint_threadmain", "compactdb: failed to commit txn (%s) db error - %d %s\n", +- inst->inst_name, rc, db_strerror(rc)); +- break; +- } +- } +- } ++ /* Now reset the timer and compacting flag */ + compactdb_interval = compactdb_interval_update; + slapi_timespec_expire_at(compactdb_interval, &compactdb_expire); ++ compacting = PR_FALSE; + } + } +- slapi_log_err(SLAPI_LOG_TRACE, "checkpoint_threadmain", "Check point before leaving\n"); ++ slapi_log_err(SLAPI_LOG_HOUSE, "checkpoint_threadmain", "Check point before leaving\n"); + rval = dblayer_force_checkpoint(li); ++ + error_return: + + DECR_THREAD_COUNT(pEnv); +- slapi_log_err(SLAPI_LOG_TRACE, "checkpoint_threadmain", "Leaving checkpoint_threadmain\n"); ++ slapi_log_err(SLAPI_LOG_HOUSE, "checkpoint_threadmain", "Leaving checkpoint_threadmain\n"); + return rval; + } + +@@ -6209,3 +6201,99 @@ bdb_back_ctrl(Slapi_Backend *be, int cmd, void *info) + + return rc; + } ++ ++int32_t ++ldbm_back_compact(Slapi_Backend *be) ++{ ++ struct ldbminfo *li = NULL; ++ int32_t rc = -1; ++ ++ li = (struct ldbminfo *)be->be_database->plg_private; ++ dblayer_force_checkpoint(li); ++ rc = bdb_compact(li); ++ dblayer_force_checkpoint(li); ++ return rc; ++} ++ ++ ++int32_t ++bdb_compact(struct ldbminfo *li) ++{ ++ Object *inst_obj; ++ ldbm_instance *inst; ++ DB *db = NULL; ++ back_txn txn = {0}; ++ int rc = 0; ++ DB_COMPACT c_data = {0}; ++ ++ slapi_log_err(SLAPI_LOG_NOTICE, "bdb_compact", ++ "Compacting databases ...\n"); ++ for (inst_obj = objset_first_obj(li->li_instance_set); ++ inst_obj; ++ inst_obj = objset_next_obj(li->li_instance_set, inst_obj)) ++ { ++ inst = (ldbm_instance *)object_get_data(inst_obj); ++ rc = dblayer_get_id2entry(inst->inst_be, &db); ++ if (!db || rc) { ++ continue; ++ } ++ slapi_log_err(SLAPI_LOG_NOTICE, "bdb_compact", "Compacting DB start: %s\n", ++ inst->inst_name); ++ ++ /* ++ * It's possible for this to heap us after free because when we access db ++ * *just* as the server shut's down, we don't know it. So we should probably ++ * do something like wrapping access to the db var in a rwlock, and have "read" ++ * to access, and take writes to change the state. This would prevent the issue. ++ */ ++ DBTYPE type; ++ rc = db->get_type(db, &type); ++ if (rc) { ++ slapi_log_err(SLAPI_LOG_ERR, "bdb_compact", ++ "compactdb: failed to determine db type for %s: db error - %d %s\n", ++ inst->inst_name, rc, db_strerror(rc)); ++ continue; ++ } ++ ++ rc = dblayer_txn_begin(inst->inst_be, NULL, &txn); ++ if (rc) { ++ slapi_log_err(SLAPI_LOG_ERR, "bdb_compact", "compactdb: transaction begin failed: %d\n", rc); ++ break; ++ } ++ /* ++ * https://docs.oracle.com/cd/E17275_01/html/api_reference/C/BDB-C_APIReference.pdf ++ * "DB_FREELIST_ONLY ++ * Do no page compaction, only returning pages to the filesystem that are already free and at the end ++ * of the file. This flag must be set if the database is a Hash access method database." ++ * ++ */ ++ uint32_t compact_flags = DB_FREE_SPACE; ++ if (type == DB_HASH) { ++ compact_flags |= DB_FREELIST_ONLY; ++ } ++ rc = db->compact(db, txn.back_txn_txn, NULL /*start*/, NULL /*stop*/, ++ &c_data, compact_flags, NULL /*end*/); ++ if (rc) { ++ slapi_log_err(SLAPI_LOG_ERR, "bdb_compact", ++ "compactdb: failed to compact %s; db error - %d %s\n", ++ inst->inst_name, rc, db_strerror(rc)); ++ if ((rc = dblayer_txn_abort(inst->inst_be, &txn))) { ++ slapi_log_err(SLAPI_LOG_ERR, "bdb_compact", "compactdb: failed to abort txn (%s) db error - %d %s\n", ++ inst->inst_name, rc, db_strerror(rc)); ++ break; ++ } ++ } else { ++ slapi_log_err(SLAPI_LOG_NOTICE, "bdb_compact", ++ "compactdb: compact %s - %d pages freed\n", ++ inst->inst_name, c_data.compact_pages_free); ++ if ((rc = dblayer_txn_commit(inst->inst_be, &txn))) { ++ slapi_log_err(SLAPI_LOG_ERR, "bdb_compact", "compactdb: failed to commit txn (%s) db error - %d %s\n", ++ inst->inst_name, rc, db_strerror(rc)); ++ break; ++ } ++ } ++ } ++ slapi_log_err(SLAPI_LOG_NOTICE, "bdb_compact", "Compacting databases finished.\n"); ++ ++ return rc; ++} +diff --git a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.h b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.h +index 6bb04d21a..e3a49dbac 100644 +--- a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.h ++++ b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.h +@@ -79,7 +79,8 @@ typedef struct bdb_config + int bdb_previous_lock_config; /* Max lock count when we last shut down-- + * used to determine if we delete the mpool */ + u_int32_t bdb_deadlock_policy; /* i.e. the atype to DB_ENV->lock_detect in deadlock_threadmain */ +- int bdb_compactdb_interval; /* interval to execute compact id2entry dbs */ ++ int32_t bdb_compactdb_interval; /* interval to execute compact id2entry dbs */ ++ char *bdb_compactdb_time; /* time of day to execute compact id2entry dbs */ + } bdb_config; + + int bdb_init(struct ldbminfo *li, config_info *config_array); +@@ -96,6 +97,7 @@ int bdb_db_size(Slapi_PBlock *pb); + int bdb_upgradedb(Slapi_PBlock *pb); + int bdb_upgradednformat(Slapi_PBlock *pb); + int bdb_upgradeddformat(Slapi_PBlock *pb); ++int32_t bdb_compact(struct ldbminfo *li); + int bdb_restore(struct ldbminfo *li, char *src_dir, Slapi_Task *task); + int bdb_cleanup(struct ldbminfo *li); + int bdb_txn_begin(struct ldbminfo *li, back_txnid parent_txn, back_txn *txn, PRBool use_lock); +diff --git a/ldap/servers/slapd/back-ldbm/init.c b/ldap/servers/slapd/back-ldbm/init.c +index 4165c8fad..42c9bd00a 100644 +--- a/ldap/servers/slapd/back-ldbm/init.c ++++ b/ldap/servers/slapd/back-ldbm/init.c +@@ -180,6 +180,8 @@ ldbm_back_init(Slapi_PBlock *pb) + (void *)ldbm_back_set_info); + rc |= slapi_pblock_set(pb, SLAPI_PLUGIN_DB_CTRL_INFO_FN, + (void *)ldbm_back_ctrl_info); ++ rc |= slapi_pblock_set(pb, SLAPI_PLUGIN_DB_COMPACT_FN, ++ (void *)ldbm_back_compact); + + if (rc != 0) { + slapi_log_err(SLAPI_LOG_CRIT, "ldbm_back_init", "Failed %d\n", rc); +diff --git a/ldap/servers/slapd/back-ldbm/ldbm_config.h b/ldap/servers/slapd/back-ldbm/ldbm_config.h +index 6fa8292eb..48446193e 100644 +--- a/ldap/servers/slapd/back-ldbm/ldbm_config.h ++++ b/ldap/servers/slapd/back-ldbm/ldbm_config.h +@@ -84,6 +84,7 @@ struct config_info + #define CONFIG_DB_TRANSACTION_WAIT "nsslapd-db-transaction-wait" + #define CONFIG_DB_CHECKPOINT_INTERVAL "nsslapd-db-checkpoint-interval" + #define CONFIG_DB_COMPACTDB_INTERVAL "nsslapd-db-compactdb-interval" ++#define CONFIG_DB_COMPACTDB_TIME "nsslapd-db-compactdb-time" + #define CONFIG_DB_TRANSACTION_BATCH "nsslapd-db-transaction-batch-val" + #define CONFIG_DB_TRANSACTION_BATCH_MIN_SLEEP "nsslapd-db-transaction-batch-min-wait" + #define CONFIG_DB_TRANSACTION_BATCH_MAX_SLEEP "nsslapd-db-transaction-batch-max-wait" +diff --git a/ldap/servers/slapd/back-ldbm/proto-back-ldbm.h b/ldap/servers/slapd/back-ldbm/proto-back-ldbm.h +index 5d618a89c..30c9003bf 100644 +--- a/ldap/servers/slapd/back-ldbm/proto-back-ldbm.h ++++ b/ldap/servers/slapd/back-ldbm/proto-back-ldbm.h +@@ -478,6 +478,7 @@ void ldbm_back_search_results_release(void **search_results); + int ldbm_back_init(Slapi_PBlock *pb); + void ldbm_back_prev_search_results(Slapi_PBlock *pb); + int ldbm_back_isinitialized(void); ++int32_t ldbm_back_compact(Slapi_Backend *be); + + /* + * vlv.c +diff --git a/ldap/servers/slapd/filtercmp.c b/ldap/servers/slapd/filtercmp.c +index f7e3ed4d5..c886267bd 100644 +--- a/ldap/servers/slapd/filtercmp.c ++++ b/ldap/servers/slapd/filtercmp.c +@@ -344,7 +344,6 @@ slapi_filter_compare(struct slapi_filter *f1, struct slapi_filter *f2) + struct berval *inval1[2], *inval2[2], **outval1, **outval2; + int ret; + Slapi_Attr sattr; +- int cmplen; + + slapi_log_err(SLAPI_LOG_TRACE, "slapi_filter_compare", "=>\n"); + +@@ -379,11 +378,11 @@ slapi_filter_compare(struct slapi_filter *f1, struct slapi_filter *f2) + if (key1 && key2) { + struct berval bvkey1 = { + slapi_value_get_length(key1[0]), +- slapi_value_get_string(key1[0]) ++ (char *)slapi_value_get_string(key1[0]) + }; + struct berval bvkey2 = { + slapi_value_get_length(key2[0]), +- slapi_value_get_string(key2[0]) ++ (char *)slapi_value_get_string(key2[0]) + }; + ret = slapi_berval_cmp(&bvkey1, &bvkey2); + } +diff --git a/ldap/servers/slapd/pblock.c b/ldap/servers/slapd/pblock.c +index f7d1f8885..fcac53839 100644 +--- a/ldap/servers/slapd/pblock.c ++++ b/ldap/servers/slapd/pblock.c +@@ -925,6 +925,12 @@ slapi_pblock_get(Slapi_PBlock *pblock, int arg, void *value) + } + (*(IFP *)value) = pblock->pb_plugin->plg_db2ldif; + break; ++ case SLAPI_PLUGIN_DB_COMPACT_FN: ++ if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_DATABASE) { ++ return (-1); ++ } ++ (*(IFP *)value) = pblock->pb_plugin->plg_dbcompact; ++ break; + case SLAPI_PLUGIN_DB_DB2INDEX_FN: + if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_DATABASE) { + return (-1); +@@ -2925,7 +2931,12 @@ slapi_pblock_set(Slapi_PBlock *pblock, int arg, void *value) + } + pblock->pb_backend->be_noacl = *((int *)value); + break; +- ++ case SLAPI_PLUGIN_DB_COMPACT_FN: ++ if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_DATABASE) { ++ return (-1); ++ } ++ pblock->pb_plugin->plg_dbcompact = (IFP)value; ++ break; + + /* extendedop plugin functions */ + case SLAPI_PLUGIN_EXT_OP_FN: +@@ -4137,8 +4148,8 @@ slapi_pblock_set(Slapi_PBlock *pblock, int arg, void *value) + break; + + case SLAPI_URP_TOMBSTONE_CONFLICT_DN: +- pblock->pb_intop->pb_urp_tombstone_conflict_dn = (char *)value; +- break; ++ pblock->pb_intop->pb_urp_tombstone_conflict_dn = (char *)value; ++ break; + + case SLAPI_URP_TOMBSTONE_UNIQUEID: + _pblock_assert_pb_intop(pblock); +diff --git a/ldap/servers/slapd/slap.h b/ldap/servers/slapd/slap.h +index 3126a65f3..c48516157 100644 +--- a/ldap/servers/slapd/slap.h ++++ b/ldap/servers/slapd/slap.h +@@ -1041,6 +1041,7 @@ struct slapdplugin + IFP plg_un_db_ldif2db; /* ldif 2 database */ + IFP plg_un_db_db2ldif; /* database 2 ldif */ + IFP plg_un_db_db2index; /* database 2 index */ ++ IFP plg_un_db_dbcompact; /* compact database */ + IFP plg_un_db_archive2db; /* ldif 2 database */ + IFP plg_un_db_db2archive; /* database 2 ldif */ + IFP plg_un_db_upgradedb; /* convert old idl to new */ +@@ -1082,6 +1083,7 @@ struct slapdplugin + #define plg_result plg_un.plg_un_db.plg_un_db_result + #define plg_ldif2db plg_un.plg_un_db.plg_un_db_ldif2db + #define plg_db2ldif plg_un.plg_un_db.plg_un_db_db2ldif ++#define plg_dbcompact plg_un.plg_un_db.plg_un_db_dbcompact + #define plg_db2index plg_un.plg_un_db.plg_un_db_db2index + #define plg_archive2db plg_un.plg_un_db.plg_un_db_archive2db + #define plg_db2archive plg_un.plg_un_db.plg_un_db_db2archive +diff --git a/ldap/servers/slapd/slapi-private.h b/ldap/servers/slapd/slapi-private.h +index b956ebe63..570765e47 100644 +--- a/ldap/servers/slapd/slapi-private.h ++++ b/ldap/servers/slapd/slapi-private.h +@@ -928,6 +928,7 @@ int proxyauth_get_dn(Slapi_PBlock *pb, char **proxydnp, char **errtextp); + #define SLAPI_PLUGIN_DB_GET_INFO_FN 290 + #define SLAPI_PLUGIN_DB_SET_INFO_FN 291 + #define SLAPI_PLUGIN_DB_CTRL_INFO_FN 292 ++#define SLAPI_PLUGIN_DB_COMPACT_FN 294 + + /**** End of database plugin interface. **************************************/ + +diff --git a/ldap/servers/slapd/task.c b/ldap/servers/slapd/task.c +index 93d31b806..4c7262ab3 100644 +--- a/ldap/servers/slapd/task.c ++++ b/ldap/servers/slapd/task.c +@@ -1,6 +1,6 @@ + /** BEGIN COPYRIGHT BLOCK + * Copyright (C) 2001 Sun Microsystems, Inc. Used by permission. +- * Copyright (C) 2005 Red Hat, Inc. ++ * Copyright (C) 2021 Red Hat, Inc. + * All rights reserved. + * + * License: GPL (version 3 or any later version). +@@ -2928,6 +2928,105 @@ des2aes_task_destructor(Slapi_Task *task) + "des2aes_task_destructor <--\n"); + } + ++struct task_compact_data ++{ ++ char *suffix; ++ Slapi_Task *task; ++}; ++ ++static void ++compact_db_task_destructor(Slapi_Task *task) ++{ ++ slapi_log_err(SLAPI_LOG_PLUGIN, "compact db task", ++ "compact_db_task_destructor -->\n"); ++ if (task) { ++ struct task_compact_data *mydata = (struct task_compact_data *)slapi_task_get_data(task); ++ while (slapi_task_get_refcount(task) > 0) { ++ /* Yield to wait for the task to finish */ ++ DS_Sleep(PR_MillisecondsToInterval(100)); ++ } ++ if (mydata) { ++ slapi_ch_free((void **)&mydata); ++ } ++ } ++ slapi_log_err(SLAPI_LOG_PLUGIN, "compact db task", ++ "compact_db_task_destructor <--\n"); ++} ++ ++static void ++task_compact_thread(void *arg) ++{ ++ struct task_compact_data *task_data = arg; ++ Slapi_Task *task = task_data->task; ++ Slapi_Backend *be = NULL; ++ char *cookie = NULL; ++ int32_t rc = -1; ++ ++ slapi_task_inc_refcount(task); ++ slapi_task_begin(task, 1); ++ ++ be = slapi_get_first_backend(&cookie); ++ while (be) { ++ if (be->be_private == 0) { ++ /* Found a non-private backend, start compacting */ ++ rc = (be->be_database->plg_dbcompact)(be); ++ break; ++ } ++ be = (backend *)slapi_get_next_backend(cookie); ++ } ++ slapi_ch_free_string(&cookie); ++ ++ slapi_task_finish(task, rc); ++ slapi_task_dec_refcount(task); ++} ++ ++/* ++ * compact the BDB database ++ * ++ * dn: cn=compact_it,cn=compact db,cn=tasks,cn=config ++ * objectclass: top ++ * objectclass: extensibleObject ++ * cn: compact_it ++ */ ++static int ++task_compact_db_add(Slapi_PBlock *pb, ++ Slapi_Entry *e, ++ Slapi_Entry *eAfter __attribute__((unused)), ++ int *returncode, ++ char *returntext, ++ void *arg __attribute__((unused))) ++{ ++ Slapi_Task *task = slapi_new_task(slapi_entry_get_ndn(e)); ++ struct task_compact_data *task_data = NULL; ++ PRThread *thread = NULL; ++ ++ slapi_task_log_notice(task, "Beginning database compaction task...\n"); ++ ++ /* Register our destructor for cleaning up our private data */ ++ slapi_task_set_destructor_fn(task, compact_db_task_destructor); ++ ++ task_data = (struct task_compact_data *)slapi_ch_calloc(1, sizeof(struct task_compact_data)); ++ task_data->task = task; ++ slapi_task_set_data(task, task_data); ++ ++ /* Start the compaction as a separate thread */ ++ thread = PR_CreateThread(PR_USER_THREAD, task_compact_thread, ++ (void *)task_data, PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, ++ PR_UNJOINABLE_THREAD, SLAPD_DEFAULT_THREAD_STACKSIZE); ++ if (thread == NULL) { ++ slapi_log_err(SLAPI_LOG_ERR, "task_compact_db_add", "Unable to create db compact thread!\n"); ++ *returncode = LDAP_OPERATIONS_ERROR; ++ slapi_ch_free((void **)&task_data); ++ } ++ ++ if (*returncode != LDAP_SUCCESS) { ++ slapi_task_finish(task, *returncode); ++ return SLAPI_DSE_CALLBACK_ERROR; ++ } ++ ++ return SLAPI_DSE_CALLBACK_OK; ++} ++ + /* cleanup old tasks that may still be in the DSE from a previous session + * (this can happen if the server crashes [no matter how unlikely we like + * to think that is].) +@@ -3010,6 +3109,7 @@ task_init(void) + slapi_task_register_handler("sysconfig reload", task_sysconfig_reload_add); + slapi_task_register_handler("fixup tombstones", task_fixup_tombstones_add); + slapi_task_register_handler("des2aes", task_des2aes); ++ slapi_task_register_handler("compact db", task_compact_db_add); + } + + /* called when the server is shutting down -- abort all existing tasks */ +diff --git a/src/cockpit/389-console/src/database.jsx b/src/cockpit/389-console/src/database.jsx +index 11cae972c..b73dc8460 100644 +--- a/src/cockpit/389-console/src/database.jsx ++++ b/src/cockpit/389-console/src/database.jsx +@@ -196,6 +196,7 @@ export class Database extends React.Component { + dblocksMonitoringPause: attrs['nsslapd-db-locks-monitoring-pause'], + chxpoint: attrs['nsslapd-db-checkpoint-interval'], + compactinterval: attrs['nsslapd-db-compactdb-interval'], ++ compacttime: attrs['nsslapd-db-compactdb-time'], + importcacheauto: attrs['nsslapd-import-cache-autosize'], + importcachesize: attrs['nsslapd-import-cachesize'], + }, +diff --git a/src/cockpit/389-console/src/lib/database/databaseConfig.jsx b/src/cockpit/389-console/src/lib/database/databaseConfig.jsx +index 6a71c138d..1fa9f2cc2 100644 +--- a/src/cockpit/389-console/src/lib/database/databaseConfig.jsx ++++ b/src/cockpit/389-console/src/lib/database/databaseConfig.jsx +@@ -36,6 +36,7 @@ export class GlobalDatabaseConfig extends React.Component { + dblocksMonitoringPause: this.props.data.dblocksMonitoringPause, + chxpoint: this.props.data.chxpoint, + compactinterval: this.props.data.compactinterval, ++ compacttime: this.props.data.compacttime, + importcachesize: this.props.data.importcachesize, + importcacheauto: this.props.data.importcacheauto, + // These variables store the original value (used for saving config) +@@ -55,6 +56,7 @@ export class GlobalDatabaseConfig extends React.Component { + _dblocksMonitoringPause: this.props.data.dblocksMonitoringPause, + _chxpoint: this.props.data.chxpoint, + _compactinterval: this.props.data.compactinterval, ++ _compacttime: this.props.data.compacttime, + _importcachesize: this.props.data.importcachesize, + _importcacheauto: this.props.data.importcacheauto, + _db_cache_auto: this.props.data.db_cache_auto, +@@ -186,6 +188,10 @@ export class GlobalDatabaseConfig extends React.Component { + cmd.push("--compactdb-interval=" + this.state.compactinterval); + requireRestart = true; + } ++ if (this.state._compacttime != this.state.compacttime) { ++ cmd.push("--compactdb-time=" + this.state.compacttime); ++ requireRestart = true; ++ } + if (this.state.import_cache_auto) { + // Auto cache is selected + if (this.state._import_cache_auto != this.state.import_cache_auto) { +@@ -485,7 +491,15 @@ export class GlobalDatabaseConfig extends React.Component { + Database Compact Interval + + +- ++ ++ ++ ++ ++ ++ Database Compact Time ++ ++ ++ + + + +diff --git a/src/lib389/lib389/_constants.py b/src/lib389/lib389/_constants.py +index c184c8d4f..d6161cebb 100644 +--- a/src/lib389/lib389/_constants.py ++++ b/src/lib389/lib389/_constants.py +@@ -154,6 +154,7 @@ DN_EUUID_TASK = "cn=entryuuid task,%s" % DN_TASKS + DN_TOMB_FIXUP_TASK = "cn=fixup tombstones,%s" % DN_TASKS + DN_FIXUP_LINKED_ATTIBUTES = "cn=fixup linked attributes,%s" % DN_TASKS + DN_AUTOMEMBER_REBUILD_TASK = "cn=automember rebuild membership,%s" % DN_TASKS ++DN_COMPACTDB_TASK = "cn=compact db,%s" % DN_TASKS + + # Script Constants + LDIF2DB = 'ldif2db' +diff --git a/src/lib389/lib389/backend.py b/src/lib389/lib389/backend.py +index 13bb27842..ad78a6ffe 100644 +--- a/src/lib389/lib389/backend.py ++++ b/src/lib389/lib389/backend.py +@@ -1005,6 +1005,7 @@ class DatabaseConfig(DSLdapObject): + 'nsslapd-db-transaction-wait', + 'nsslapd-db-checkpoint-interval', + 'nsslapd-db-compactdb-interval', ++ 'nsslapd-db-compactdb-time', + 'nsslapd-db-page-size', + 'nsslapd-db-transaction-batch-val', + 'nsslapd-db-transaction-batch-min-wait', +diff --git a/src/lib389/lib389/cli_conf/backend.py b/src/lib389/lib389/cli_conf/backend.py +index 722764d10..7b2f32c23 100644 +--- a/src/lib389/lib389/cli_conf/backend.py ++++ b/src/lib389/lib389/cli_conf/backend.py +@@ -1,5 +1,5 @@ + # --- BEGIN COPYRIGHT BLOCK --- +-# Copyright (C) 2020 Red Hat, Inc. ++# Copyright (C) 2021 Red Hat, Inc. + # Copyright (C) 2019 William Brown + # All rights reserved. + # +@@ -19,6 +19,7 @@ from lib389.chaining import (ChainingLinks) + from lib389.monitor import MonitorLDBM + from lib389.replica import Replicas + from lib389.utils import ensure_str, is_a_dn, is_dn_parent ++from lib389.tasks import DBCompactTask + from lib389._constants import * + from lib389.cli_base import ( + _format_status, +@@ -41,6 +42,7 @@ arg_to_attr = { + 'txn_wait': 'nsslapd-db-transaction-wait', + 'checkpoint_interval': 'nsslapd-db-checkpoint-interval', + 'compactdb_interval': 'nsslapd-db-compactdb-interval', ++ 'compactdb_time': 'nsslapd-db-compactdb-time', + 'txn_batch_val': 'nsslapd-db-transaction-batch-val', + 'txn_batch_min': 'nsslapd-db-transaction-batch-min-wait', + 'txn_batch_max': 'nsslapd-db-transaction-batch-max-wait', +@@ -789,6 +791,18 @@ def backend_reindex_vlv(inst, basedn, log, args): + log.info("Successfully reindexed VLV indexes") + + ++def backend_compact(inst, basedn, log, args): ++ task = DBCompactTask(inst) ++ task_properties = {} ++ if args.only_changelog: ++ task_properties = {'justChangelog': 'yes'} ++ task.create(properties=task_properties) ++ task.wait() ++ if task.get_exit_code() != 0: ++ raise ValueError("Failed to create Database Compaction Task") ++ log.info("Successfully started Database Compaction Task") ++ ++ + def create_parser(subparsers): + backend_parser = subparsers.add_parser('backend', help="Manage database suffixes and backends") + subcommands = backend_parser.add_subparsers(help="action") +@@ -994,6 +1008,7 @@ def create_parser(subparsers): + set_db_config_parser.add_argument('--checkpoint-interval', help='Sets the amount of time in seconds after which the Directory Server sends a ' + 'checkpoint entry to the database transaction log') + set_db_config_parser.add_argument('--compactdb-interval', help='Sets the interval in seconds when the database is compacted') ++ set_db_config_parser.add_argument('--compactdb-time', help='Sets the Time Of Day to compact the database after the "compactdb interval" has been reached: Use this format to set the hour and minute: HH:MM') + set_db_config_parser.add_argument('--txn-batch-val', help='Specifies how many transactions will be batched before being committed') + set_db_config_parser.add_argument('--txn-batch-min', help='Controls when transactions should be flushed earliest, independently of ' + 'the batch count (only works when txn-batch-val is set)') +@@ -1121,3 +1136,10 @@ def create_parser(subparsers): + ####################################################### + get_tree_parser = subcommands.add_parser('get-tree', help='Get a representation of the suffix tree') + get_tree_parser.set_defaults(func=backend_get_tree) ++ ++ ####################################################### ++ # Run the db compaction task ++ ####################################################### ++ compact_parser = subcommands.add_parser('compact-db', help='Compact the database and the replication changelog') ++ compact_parser.set_defaults(func=backend_compact) ++ compact_parser.add_argument('--only-changelog', action='store_true', help='Only compact the Replication Change Log') +diff --git a/src/lib389/lib389/cli_conf/replication.py b/src/lib389/lib389/cli_conf/replication.py +index 04886f632..3478a0a1f 100644 +--- a/src/lib389/lib389/cli_conf/replication.py ++++ b/src/lib389/lib389/cli_conf/replication.py +@@ -37,6 +37,7 @@ arg_to_attr = { + 'max_entries': 'nsslapd-changelogmaxentries', + 'max_age': 'nsslapd-changelogmaxage', + 'compact_interval': 'nsslapd-changelogcompactdb-interval', ++ 'compact_time': 'nsslapd-changelogcompactdb-time', + 'trim_interval': 'nsslapd-changelogtrim-interval', + 'encrypt_algo': 'nsslapd-encryptionalgorithm', + 'encrypt_key': 'nssymmetrickey', +@@ -1216,6 +1217,8 @@ def create_parser(subparsers): + repl_set_cl.add_argument('--max-entries', help="The maximum number of entries to get in the replication changelog") + repl_set_cl.add_argument('--max-age', help="The maximum age of a replication changelog entry") + repl_set_cl.add_argument('--compact-interval', help="The replication changelog compaction interval") ++ repl_set_cl.add_argument('--compact-time', help='Sets the Time Of Day to compact the database after the changelog "compact interval" ' ++ 'has been reached: Use this format to set the hour and minute: HH:MM') + repl_set_cl.add_argument('--trim-interval', help="The interval to check if the replication changelog can be trimmed") + + repl_get_cl = repl_subcommands.add_parser('get-changelog', help='Display replication changelog attributes.') +diff --git a/src/lib389/lib389/tasks.py b/src/lib389/lib389/tasks.py +index 590c6ee79..b64bc6ce5 100644 +--- a/src/lib389/lib389/tasks.py ++++ b/src/lib389/lib389/tasks.py +@@ -217,6 +217,19 @@ class EntryUUIDFixupTask(Task): + self._must_attributes.extend(['basedn']) + + ++class DBCompactTask(Task): ++ """A single instance of compactdb task entry ++ ++ :param instance: An instance ++ :type instance: lib389.DirSrv ++ """ ++ ++ def __init__(self, instance, dn=None): ++ self.cn = 'compact_db_' + Task._get_task_date() ++ dn = "cn=" + self.cn + "," + DN_COMPACTDB_TASK ++ super(DBCompactTask, self).__init__(instance, dn) ++ ++ + class SchemaReloadTask(Task): + """A single instance of schema reload task entry + +@@ -227,7 +240,6 @@ class SchemaReloadTask(Task): + def __init__(self, instance, dn=None): + self.cn = 'schema_reload_' + Task._get_task_date() + dn = "cn=" + self.cn + ",cn=schema reload task," + DN_TASKS +- + super(SchemaReloadTask, self).__init__(instance, dn) + + +-- +2.26.3 + diff --git a/SOURCES/0012-Issue-4778-RFE-Add-changelog-compaction-task-in-1.4..patch b/SOURCES/0012-Issue-4778-RFE-Add-changelog-compaction-task-in-1.4..patch new file mode 100644 index 0000000..94618f6 --- /dev/null +++ b/SOURCES/0012-Issue-4778-RFE-Add-changelog-compaction-task-in-1.4..patch @@ -0,0 +1,155 @@ +From 580880a598a8f9972994684c49593a4cf8b8969b Mon Sep 17 00:00:00 2001 +From: Mark Reynolds +Date: Sat, 29 May 2021 13:19:53 -0400 +Subject: [PATCH 12/12] Issue 4778 - RFE - Add changelog compaction task in + 1.4.3 + +Description: In 1.4.3 the replication changelog is a separate database, + so it needs a separate "nsds5task" compaction task (COMPACT_CL5) + +relates: https://github.com/389ds/389-ds-base/issues/4778 + +ASAN tested and approved + +Reviewed by: mreynolds +--- + ldap/servers/plugins/replication/cl5_api.c | 21 +++++++++---------- + ldap/servers/plugins/replication/cl5_api.h | 1 + + .../replication/repl5_replica_config.c | 9 +++++++- + 3 files changed, 19 insertions(+), 12 deletions(-) + +diff --git a/ldap/servers/plugins/replication/cl5_api.c b/ldap/servers/plugins/replication/cl5_api.c +index 75a2f46f5..4c5077b48 100644 +--- a/ldap/servers/plugins/replication/cl5_api.c ++++ b/ldap/servers/plugins/replication/cl5_api.c +@@ -266,7 +266,6 @@ static int _cl5TrimInit(void); + static void _cl5TrimCleanup(void); + static int _cl5TrimMain(void *param); + static void _cl5DoTrimming(void); +-static void _cl5CompactDBs(void); + static void _cl5PurgeRID(Object *file_obj, ReplicaId cleaned_rid); + static int _cl5PurgeGetFirstEntry(Object *file_obj, CL5Entry *entry, void **iterator, DB_TXN *txnid, int rid, DBT *key); + static int _cl5PurgeGetNextEntry(CL5Entry *entry, void *iterator, DBT *key); +@@ -3152,7 +3151,7 @@ _cl5TrimMain(void *param __attribute__((unused))) + if (slapi_current_utc_time() > compactdb_time) { + /* time to trim */ + timeCompactPrev = timeNow; +- _cl5CompactDBs(); ++ cl5CompactDBs(); + compacting = PR_FALSE; + } + } +@@ -3250,8 +3249,8 @@ _cl5DoPurging(cleanruv_purge_data *purge_data) + } + + /* clear free page files to reduce changelog */ +-static void +-_cl5CompactDBs(void) ++void ++cl5CompactDBs(void) + { + int rc; + Object *fileObj = NULL; +@@ -3264,14 +3263,14 @@ _cl5CompactDBs(void) + rc = TXN_BEGIN(s_cl5Desc.dbEnv, NULL, &txnid, 0); + if (rc) { + slapi_log_err(SLAPI_LOG_ERR, repl_plugin_name_cl, +- "_cl5CompactDBs - Failed to begin transaction; db error - %d %s\n", ++ "cl5CompactDBs - Failed to begin transaction; db error - %d %s\n", + rc, db_strerror(rc)); + goto bail; + } + + + slapi_log_err(SLAPI_LOG_NOTICE, repl_plugin_name_cl, +- "_cl5CompactDBs - compacting replication changelogs...\n"); ++ "cl5CompactDBs - compacting replication changelogs...\n"); + for (fileObj = objset_first_obj(s_cl5Desc.dbFiles); + fileObj; + fileObj = objset_next_obj(s_cl5Desc.dbFiles, fileObj)) { +@@ -3284,17 +3283,17 @@ _cl5CompactDBs(void) + &c_data, DB_FREE_SPACE, NULL /*end*/); + if (rc) { + slapi_log_err(SLAPI_LOG_ERR, repl_plugin_name_cl, +- "_cl5CompactDBs - Failed to compact %s; db error - %d %s\n", ++ "cl5CompactDBs - Failed to compact %s; db error - %d %s\n", + dbFile->replName, rc, db_strerror(rc)); + goto bail; + } + slapi_log_err(SLAPI_LOG_REPL, repl_plugin_name_cl, +- "_cl5CompactDBs - %s - %d pages freed\n", ++ "cl5CompactDBs - %s - %d pages freed\n", + dbFile->replName, c_data.compact_pages_free); + } + + slapi_log_err(SLAPI_LOG_NOTICE, repl_plugin_name_cl, +- "_cl5CompactDBs - compacting replication changelogs finished.\n"); ++ "cl5CompactDBs - compacting replication changelogs finished.\n"); + bail: + if (fileObj) { + object_release(fileObj); +@@ -3303,14 +3302,14 @@ bail: + rc = TXN_ABORT(txnid); + if (rc) { + slapi_log_err(SLAPI_LOG_ERR, repl_plugin_name_cl, +- "_cl5CompactDBs - Failed to abort transaction; db error - %d %s\n", ++ "cl5CompactDBs - Failed to abort transaction; db error - %d %s\n", + rc, db_strerror(rc)); + } + } else { + rc = TXN_COMMIT(txnid); + if (rc) { + slapi_log_err(SLAPI_LOG_ERR, repl_plugin_name_cl, +- "_cl5CompactDBs - Failed to commit transaction; db error - %d %s\n", ++ "cl5CompactDBs - Failed to commit transaction; db error - %d %s\n", + rc, db_strerror(rc)); + } + } +diff --git a/ldap/servers/plugins/replication/cl5_api.h b/ldap/servers/plugins/replication/cl5_api.h +index 4b0949fb3..11db771f2 100644 +--- a/ldap/servers/plugins/replication/cl5_api.h ++++ b/ldap/servers/plugins/replication/cl5_api.h +@@ -405,5 +405,6 @@ int cl5DeleteRUV(void); + void cl5CleanRUV(ReplicaId rid); + void cl5NotifyCleanup(int rid); + void trigger_cl_purging(cleanruv_purge_data *purge_data); ++void cl5CompactDBs(void); + + #endif +diff --git a/ldap/servers/plugins/replication/repl5_replica_config.c b/ldap/servers/plugins/replication/repl5_replica_config.c +index a969ef82f..e708a1ccb 100644 +--- a/ldap/servers/plugins/replication/repl5_replica_config.c ++++ b/ldap/servers/plugins/replication/repl5_replica_config.c +@@ -29,6 +29,8 @@ + #define CLEANRUVLEN 8 + #define CLEANALLRUV "CLEANALLRUV" + #define CLEANALLRUVLEN 11 ++#define COMPACT_CL5 "COMPACT_CL5" ++#define COMPACT_CL5_LEN 11 + #define REPLICA_RDN "cn=replica" + + #define CLEANALLRUV_MAX_WAIT 7200 /* 2 hours */ +@@ -1050,7 +1052,6 @@ replica_config_change_flags(Replica *r, const char *new_flags, char *returntext + static int + replica_execute_task(Replica *r, const char *task_name, char *returntext, int apply_mods) + { +- + if (strcasecmp(task_name, CL2LDIF_TASK) == 0) { + if (apply_mods) { + return replica_execute_cl2ldif_task(r, returntext); +@@ -1084,6 +1085,12 @@ replica_execute_task(Replica *r, const char *task_name, char *returntext, int ap + return replica_execute_cleanall_ruv_task(r, (ReplicaId)temprid, empty_task, "no", PR_TRUE, returntext); + } else + return LDAP_SUCCESS; ++ } else if (strncasecmp(task_name, COMPACT_CL5, COMPACT_CL5_LEN) == 0) { ++ /* compact the replication changelogs */ ++ if (apply_mods) { ++ cl5CompactDBs(); ++ } ++ return LDAP_SUCCESS; + } else { + PR_snprintf(returntext, SLAPI_DSE_RETURNTEXT_SIZE, "Unsupported replica task - %s", task_name); + slapi_log_err(SLAPI_LOG_ERR, repl_plugin_name, +-- +2.26.3 + diff --git a/SOURCES/0013-Issue-4797-ACL-IP-ADDRESS-evaluation-may-corrupt-c_i.patch b/SOURCES/0013-Issue-4797-ACL-IP-ADDRESS-evaluation-may-corrupt-c_i.patch new file mode 100644 index 0000000..db28cfa --- /dev/null +++ b/SOURCES/0013-Issue-4797-ACL-IP-ADDRESS-evaluation-may-corrupt-c_i.patch @@ -0,0 +1,52 @@ +From bc41bbb89405b2059b80e344b2d4c59ae39aabe6 Mon Sep 17 00:00:00 2001 +From: tbordaz +Date: Thu, 10 Jun 2021 15:03:27 +0200 +Subject: [PATCH 1/3] Issue 4797 - ACL IP ADDRESS evaluation may corrupt + c_isreplication_session connection flags (#4799) + +Bug description: + The fix for ticket #3764 was broken with a missing break in a + switch. The consequence is that while setting the client IP + address in the pblock (SLAPI_CONN_CLIENTNETADDR_ACLIP), the + connection is erroneously set as replication connection. + This can lead to crash or failure of testcase + test_access_from_certain_network_only_ip. + This bug was quite hidden until the fix for #4764 is + showing it more frequently + +Fix description: + Add the missing break + +relates: https://github.com/389ds/389-ds-base/issues/4797 + +Reviewed by: Mark Reynolds + +Platforms tested: F33 +--- + ldap/servers/slapd/pblock.c | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/ldap/servers/slapd/pblock.c b/ldap/servers/slapd/pblock.c +index fcac53839..a64986aeb 100644 +--- a/ldap/servers/slapd/pblock.c ++++ b/ldap/servers/slapd/pblock.c +@@ -2595,7 +2595,7 @@ slapi_pblock_set(Slapi_PBlock *pblock, int arg, void *value) + pblock->pb_conn->c_authtype = slapi_ch_strdup((char *)value); + pthread_mutex_unlock(&(pblock->pb_conn->c_mutex)); + break; +- case SLAPI_CONN_CLIENTNETADDR_ACLIP: ++ case SLAPI_CONN_CLIENTNETADDR_ACLIP: + if (pblock->pb_conn == NULL) { + break; + } +@@ -2603,6 +2603,7 @@ slapi_pblock_set(Slapi_PBlock *pblock, int arg, void *value) + slapi_ch_free((void **)&pblock->pb_conn->cin_addr_aclip); + pblock->pb_conn->cin_addr_aclip = (PRNetAddr *)value; + pthread_mutex_unlock(&(pblock->pb_conn->c_mutex)); ++ break; + case SLAPI_CONN_IS_REPLICATION_SESSION: + if (pblock->pb_conn == NULL) { + slapi_log_err(SLAPI_LOG_ERR, +-- +2.31.1 + diff --git a/SOURCES/0014-Issue-4396-Minor-memory-leak-in-backend-4558-4572.patch b/SOURCES/0014-Issue-4396-Minor-memory-leak-in-backend-4558-4572.patch new file mode 100644 index 0000000..eb16fcb --- /dev/null +++ b/SOURCES/0014-Issue-4396-Minor-memory-leak-in-backend-4558-4572.patch @@ -0,0 +1,79 @@ +From b3170e39519530c39d59202413b20e6bd466224d Mon Sep 17 00:00:00 2001 +From: James Chapman +Date: Wed, 27 Jan 2021 09:56:38 +0000 +Subject: [PATCH 2/3] Issue 4396 - Minor memory leak in backend (#4558) (#4572) + +Bug Description: As multiple suffixes per backend were no longer used, this +functionality has been replaced with a single suffix per backend. Legacy +code remains that adds multiple suffixes to the dse internal backend, +resulting in memory allocations that are lost. + +Also a minor typo is corrected in backend.c + +Fix Description: Calls to be_addsuffix on the DSE backend are removed +as they are never used. + +Fixes: https://github.com/389ds/389-ds-base/issues/4396 + +Reviewed by: mreynolds389, Firstyear, droideck (Thank you) +--- + ldap/servers/slapd/backend.c | 2 +- + ldap/servers/slapd/fedse.c | 12 +++--------- + 2 files changed, 4 insertions(+), 10 deletions(-) + +diff --git a/ldap/servers/slapd/backend.c b/ldap/servers/slapd/backend.c +index bc52b4643..5707504a9 100644 +--- a/ldap/servers/slapd/backend.c ++++ b/ldap/servers/slapd/backend.c +@@ -42,7 +42,7 @@ be_init(Slapi_Backend *be, const char *type, const char *name, int isprivate, in + } + be->be_monitordn = slapi_create_dn_string("cn=monitor,cn=%s,cn=%s,cn=plugins,cn=config", + name, type); +- if (NULL == be->be_configdn) { ++ if (NULL == be->be_monitordn) { + slapi_log_err(SLAPI_LOG_ERR, + "be_init", "Failed create instance monitor dn for " + "plugin %s, instance %s\n", +diff --git a/ldap/servers/slapd/fedse.c b/ldap/servers/slapd/fedse.c +index 0d645f909..7b820b540 100644 +--- a/ldap/servers/slapd/fedse.c ++++ b/ldap/servers/slapd/fedse.c +@@ -2827,7 +2827,7 @@ search_snmp(Slapi_PBlock *pb __attribute__((unused)), + } + + /* +- * Called from config.c to install the internal backends ++ * Called from main.c to install the internal backends + */ + int + setup_internal_backends(char *configdir) +@@ -2846,7 +2846,6 @@ setup_internal_backends(char *configdir) + Slapi_DN counters; + Slapi_DN snmp; + Slapi_DN root; +- Slapi_Backend *be; + Slapi_DN encryption; + Slapi_DN saslmapping; + Slapi_DN plugins; +@@ -2895,16 +2894,11 @@ setup_internal_backends(char *configdir) + dse_register_callback(pfedse, SLAPI_OPERATION_ADD, DSE_FLAG_PREOP, &saslmapping, LDAP_SCOPE_SUBTREE, "(objectclass=nsSaslMapping)", sasl_map_config_add, NULL, NULL); + dse_register_callback(pfedse, SLAPI_OPERATION_ADD, DSE_FLAG_PREOP, &plugins, LDAP_SCOPE_SUBTREE, "(objectclass=nsSlapdPlugin)", check_plugin_path, NULL, NULL); + +- be = be_new_internal(pfedse, "DSE", DSE_BACKEND, &fedse_plugin); +- be_addsuffix(be, &root); +- be_addsuffix(be, &monitor); +- be_addsuffix(be, &config); ++ be_new_internal(pfedse, "DSE", DSE_BACKEND, &fedse_plugin); + + /* +- * Now that the be's are in place, we can +- * setup the mapping tree. ++ * Now that the be's are in place, we can setup the mapping tree. + */ +- + if (mapping_tree_init()) { + slapi_log_err(SLAPI_LOG_EMERG, "setup_internal_backends", "Failed to init mapping tree\n"); + exit(1); +-- +2.31.1 + diff --git a/SOURCES/0015-Issue-4700-Regression-in-winsync-replication-agreeme.patch b/SOURCES/0015-Issue-4700-Regression-in-winsync-replication-agreeme.patch new file mode 100644 index 0000000..9e5231d --- /dev/null +++ b/SOURCES/0015-Issue-4700-Regression-in-winsync-replication-agreeme.patch @@ -0,0 +1,66 @@ +From 8d06fdf44b0d337f1e321e61ee1b22972ddea917 Mon Sep 17 00:00:00 2001 +From: tbordaz +Date: Fri, 2 Apr 2021 14:05:41 +0200 +Subject: [PATCH 3/3] Issue 4700 - Regression in winsync replication agreement + (#4712) + +Bug description: + #4396 fixes a memory leak but did not set 'cn=config' as + DSE backend. + It had no signicant impact unless with sidgen IPA plugin + +Fix description: + revert the portion of the #4364 patch that set be_suffix + in be_addsuffix, free the suffix before setting it + +relates: https://github.com/389ds/389-ds-base/issues/4700 + +Reviewed by: Pierre Rogier (thanks !) + +Platforms tested: F33 +--- + ldap/servers/slapd/backend.c | 3 ++- + ldap/servers/slapd/fedse.c | 6 +++++- + 2 files changed, 7 insertions(+), 2 deletions(-) + +diff --git a/ldap/servers/slapd/backend.c b/ldap/servers/slapd/backend.c +index 5707504a9..5db706841 100644 +--- a/ldap/servers/slapd/backend.c ++++ b/ldap/servers/slapd/backend.c +@@ -173,7 +173,8 @@ void + be_addsuffix(Slapi_Backend *be, const Slapi_DN *suffix) + { + if (be->be_state != BE_STATE_DELETED) { +- be->be_suffix = slapi_sdn_dup(suffix);; ++ slapi_sdn_free(&be->be_suffix); ++ be->be_suffix = slapi_sdn_dup(suffix); + } + } + +diff --git a/ldap/servers/slapd/fedse.c b/ldap/servers/slapd/fedse.c +index 7b820b540..44159c991 100644 +--- a/ldap/servers/slapd/fedse.c ++++ b/ldap/servers/slapd/fedse.c +@@ -2846,6 +2846,7 @@ setup_internal_backends(char *configdir) + Slapi_DN counters; + Slapi_DN snmp; + Slapi_DN root; ++ Slapi_Backend *be; + Slapi_DN encryption; + Slapi_DN saslmapping; + Slapi_DN plugins; +@@ -2894,7 +2895,10 @@ setup_internal_backends(char *configdir) + dse_register_callback(pfedse, SLAPI_OPERATION_ADD, DSE_FLAG_PREOP, &saslmapping, LDAP_SCOPE_SUBTREE, "(objectclass=nsSaslMapping)", sasl_map_config_add, NULL, NULL); + dse_register_callback(pfedse, SLAPI_OPERATION_ADD, DSE_FLAG_PREOP, &plugins, LDAP_SCOPE_SUBTREE, "(objectclass=nsSlapdPlugin)", check_plugin_path, NULL, NULL); + +- be_new_internal(pfedse, "DSE", DSE_BACKEND, &fedse_plugin); ++ be = be_new_internal(pfedse, "DSE", DSE_BACKEND, &fedse_plugin); ++ be_addsuffix(be, &root); ++ be_addsuffix(be, &monitor); ++ be_addsuffix(be, &config); + + /* + * Now that the be's are in place, we can setup the mapping tree. +-- +2.31.1 + diff --git a/SOURCES/0016-Issue-4725-Fix-compiler-warnings.patch b/SOURCES/0016-Issue-4725-Fix-compiler-warnings.patch new file mode 100644 index 0000000..2371384 --- /dev/null +++ b/SOURCES/0016-Issue-4725-Fix-compiler-warnings.patch @@ -0,0 +1,88 @@ +From 7345c51c68dfd90a704ccbb0e5b1e736af80f146 Mon Sep 17 00:00:00 2001 +From: Thierry Bordaz +Date: Mon, 17 May 2021 16:10:22 +0200 +Subject: [PATCH] Issue 4725 - Fix compiler warnings + +--- + ldap/servers/slapd/proto-slap.h | 2 +- + ldap/servers/slapd/pw.c | 9 ++++----- + ldap/servers/slapd/pw_retry.c | 2 -- + 3 files changed, 5 insertions(+), 8 deletions(-) + +diff --git a/ldap/servers/slapd/proto-slap.h b/ldap/servers/slapd/proto-slap.h +index 6ff178127..2768d5a1d 100644 +--- a/ldap/servers/slapd/proto-slap.h ++++ b/ldap/servers/slapd/proto-slap.h +@@ -1012,7 +1012,7 @@ int add_shadow_ext_password_attrs(Slapi_PBlock *pb, Slapi_Entry **e); + * pw_retry.c + */ + int update_pw_retry(Slapi_PBlock *pb); +-int update_trp_pw_usecount(Slapi_PBlock *pb, Slapi_Entry *e, int32_t use_count); ++int update_tpr_pw_usecount(Slapi_PBlock *pb, Slapi_Entry *e, int32_t use_count); + void pw_apply_mods(const Slapi_DN *sdn, Slapi_Mods *mods); + void pw_set_componentID(struct slapi_componentid *cid); + struct slapi_componentid *pw_get_componentID(void); +diff --git a/ldap/servers/slapd/pw.c b/ldap/servers/slapd/pw.c +index d98422513..2a167c8f1 100644 +--- a/ldap/servers/slapd/pw.c ++++ b/ldap/servers/slapd/pw.c +@@ -2622,7 +2622,6 @@ int + slapi_check_tpr_limits(Slapi_PBlock *pb, Slapi_Entry *bind_target_entry, int send_result) { + passwdPolicy *pwpolicy = NULL; + char *dn = NULL; +- int tpr_maxuse; + char *value; + time_t cur_time; + char *cur_time_str = NULL; +@@ -2638,7 +2637,7 @@ slapi_check_tpr_limits(Slapi_PBlock *pb, Slapi_Entry *bind_target_entry, int sen + return 0; + } + +- if (slapi_entry_attr_hasvalue(bind_target_entry, "pwdTPRReset", "TRUE") == NULL) { ++ if (!slapi_entry_attr_hasvalue(bind_target_entry, "pwdTPRReset", "TRUE")) { + /* the password was not reset by an admin while a TRP pwp was set, just returned */ + return 0; + } +@@ -2646,7 +2645,7 @@ slapi_check_tpr_limits(Slapi_PBlock *pb, Slapi_Entry *bind_target_entry, int sen + /* Check entry TPR max use */ + if (pwpolicy->pw_tpr_maxuse >= 0) { + uint use_count; +- value = slapi_entry_attr_get_ref(bind_target_entry, "pwdTPRUseCount"); ++ value = (char *) slapi_entry_attr_get_ref(bind_target_entry, "pwdTPRUseCount"); + if (value) { + /* max Use is enforced */ + use_count = strtoull(value, 0, 0); +@@ -2681,7 +2680,7 @@ slapi_check_tpr_limits(Slapi_PBlock *pb, Slapi_Entry *bind_target_entry, int sen + + /* Check entry TPR expiration at a specific time */ + if (pwpolicy->pw_tpr_delay_expire_at >= 0) { +- value = slapi_entry_attr_get_ref(bind_target_entry, "pwdTPRExpireAt"); ++ value = (char *) slapi_entry_attr_get_ref(bind_target_entry, "pwdTPRExpireAt"); + if (value) { + /* max Use is enforced */ + if (difftime(parse_genTime(cur_time_str), parse_genTime(value)) >= 0) { +@@ -2709,7 +2708,7 @@ slapi_check_tpr_limits(Slapi_PBlock *pb, Slapi_Entry *bind_target_entry, int sen + + /* Check entry TPR valid after a specific time */ + if (pwpolicy->pw_tpr_delay_valid_from >= 0) { +- value = slapi_entry_attr_get_ref(bind_target_entry, "pwdTPRValidFrom"); ++ value = (char *) slapi_entry_attr_get_ref(bind_target_entry, "pwdTPRValidFrom"); + if (value) { + /* validity after a specific time is enforced */ + if (difftime(parse_genTime(value), parse_genTime(cur_time_str)) >= 0) { +diff --git a/ldap/servers/slapd/pw_retry.c b/ldap/servers/slapd/pw_retry.c +index 5d13eb636..af54aa19d 100644 +--- a/ldap/servers/slapd/pw_retry.c ++++ b/ldap/servers/slapd/pw_retry.c +@@ -163,8 +163,6 @@ set_retry_cnt_and_time(Slapi_PBlock *pb, int count, time_t cur_time) + int + set_tpr_usecount_mods(Slapi_PBlock *pb, Slapi_Mods *smods, int count) + { +- char *timestr; +- time_t unlock_time; + char retry_cnt[16] = {0}; /* 1-65535 */ + const char *dn = NULL; + Slapi_DN *sdn = NULL; +-- +2.31.1 + diff --git a/SOURCES/0017-Issue-4814-_cl5_get_tod_expiration-may-crash-at-star.patch b/SOURCES/0017-Issue-4814-_cl5_get_tod_expiration-may-crash-at-star.patch new file mode 100644 index 0000000..6785c04 --- /dev/null +++ b/SOURCES/0017-Issue-4814-_cl5_get_tod_expiration-may-crash-at-star.patch @@ -0,0 +1,202 @@ +From 59266365eda8130abf6901263efae4c87586376a Mon Sep 17 00:00:00 2001 +From: Thierry Bordaz +Date: Mon, 28 Jun 2021 16:40:15 +0200 +Subject: [PATCH] Issue 4814 - _cl5_get_tod_expiration may crash at startup + +Bug description: + This bug exist only in 1.4.3 branch + In 1.4.3, CL open as a separated database so + compaction mechanism is started along a CL + mechanism (CL trimming). + The problem is that the configuration of the CL + compaction is done after the compaction mechanism + (is started). Depending on thread scheduling it + crashes + +Fix description: + Make sure configuration of compaction thread is + taken into account (cl5ConfigSetCompaction) before + the compaction thread starts (cl5open) + +relates: https://github.com/389ds/389-ds-base/issues/4814 + +Reviewed by: Mark Reynolds, Simon Pichugin (thanks !) + +Platforms tested: 8.5 +--- + ldap/servers/plugins/replication/cl5_api.c | 24 ++++++++++++------- + ldap/servers/plugins/replication/cl5_api.h | 10 +++++++- + ldap/servers/plugins/replication/cl5_config.c | 8 +++++-- + ldap/servers/plugins/replication/cl5_init.c | 4 +++- + ldap/servers/plugins/replication/cl5_test.c | 2 +- + .../servers/plugins/replication/repl_shared.h | 2 +- + 6 files changed, 35 insertions(+), 15 deletions(-) + +diff --git a/ldap/servers/plugins/replication/cl5_api.c b/ldap/servers/plugins/replication/cl5_api.c +index 4c5077b48..954b6b9e3 100644 +--- a/ldap/servers/plugins/replication/cl5_api.c ++++ b/ldap/servers/plugins/replication/cl5_api.c +@@ -1016,6 +1016,20 @@ cl5GetState() + return s_cl5Desc.dbState; + } + ++void ++cl5ConfigSetCompaction(int compactInterval, char *compactTime) ++{ ++ ++ if (compactInterval != CL5_NUM_IGNORE) { ++ s_cl5Desc.dbTrim.compactInterval = compactInterval; ++ } ++ ++ if (strcmp(compactTime, CL5_STR_IGNORE) != 0) { ++ s_cl5Desc.dbTrim.compactTime = slapi_ch_strdup(compactTime); ++ } ++ ++} ++ + /* Name: cl5ConfigTrimming + Description: sets changelog trimming parameters; changelog must be open. + Parameters: maxEntries - maximum number of entries in the chnagelog (in all files); +@@ -1026,7 +1040,7 @@ cl5GetState() + CL5_BAD_STATE if changelog is not open + */ + int +-cl5ConfigTrimming(int maxEntries, const char *maxAge, int compactInterval, char *compactTime, int trimInterval) ++cl5ConfigTrimming(int maxEntries, const char *maxAge, int trimInterval) + { + if (s_cl5Desc.dbState == CL5_STATE_NONE) { + slapi_log_err(SLAPI_LOG_ERR, repl_plugin_name_cl, +@@ -1058,14 +1072,6 @@ cl5ConfigTrimming(int maxEntries, const char *maxAge, int compactInterval, char + s_cl5Desc.dbTrim.maxEntries = maxEntries; + } + +- if (compactInterval != CL5_NUM_IGNORE) { +- s_cl5Desc.dbTrim.compactInterval = compactInterval; +- } +- +- if (strcmp(compactTime, CL5_STR_IGNORE) != 0) { +- s_cl5Desc.dbTrim.compactTime = slapi_ch_strdup(compactTime); +- } +- + if (trimInterval != CL5_NUM_IGNORE) { + s_cl5Desc.dbTrim.trimInterval = trimInterval; + } +diff --git a/ldap/servers/plugins/replication/cl5_api.h b/ldap/servers/plugins/replication/cl5_api.h +index 11db771f2..6aa48aec4 100644 +--- a/ldap/servers/plugins/replication/cl5_api.h ++++ b/ldap/servers/plugins/replication/cl5_api.h +@@ -227,6 +227,14 @@ int cl5ImportLDIF(const char *clDir, const char *ldifFile, Replica **replicas); + + int cl5GetState(void); + ++/* Name: cl5ConfigSetCompaction ++ * Description: sets the database compaction parameters ++ * Parameters: compactInterval - Interval for compaction default is 30days ++ * compactTime - Compact time default is 23:59 ++ * Return: void ++ */ ++void cl5ConfigSetCompaction(int compactInterval, char *compactTime); ++ + /* Name: cl5ConfigTrimming + Description: sets changelog trimming parameters + Parameters: maxEntries - maximum number of entries in the log; +@@ -236,7 +244,7 @@ int cl5GetState(void); + Return: CL5_SUCCESS if successful; + CL5_BAD_STATE if changelog has not been open + */ +-int cl5ConfigTrimming(int maxEntries, const char *maxAge, int compactInterval, char *compactTime, int trimInterval); ++int cl5ConfigTrimming(int maxEntries, const char *maxAge, int trimInterval); + + void cl5DestroyIterator(void *iterator); + +diff --git a/ldap/servers/plugins/replication/cl5_config.c b/ldap/servers/plugins/replication/cl5_config.c +index b32686788..a43534c9b 100644 +--- a/ldap/servers/plugins/replication/cl5_config.c ++++ b/ldap/servers/plugins/replication/cl5_config.c +@@ -197,6 +197,8 @@ changelog5_config_add(Slapi_PBlock *pb __attribute__((unused)), + + goto done; + } ++ /* Set compaction parameters */ ++ cl5ConfigSetCompaction(config.compactInterval, config.compactTime); + + /* start the changelog */ + rc = cl5Open(config.dir, &config.dbconfig); +@@ -212,7 +214,7 @@ changelog5_config_add(Slapi_PBlock *pb __attribute__((unused)), + } + + /* set trimming parameters */ +- rc = cl5ConfigTrimming(config.maxEntries, config.maxAge, config.compactInterval, config.compactTime, config.trimInterval); ++ rc = cl5ConfigTrimming(config.maxEntries, config.maxAge, config.trimInterval); + if (rc != CL5_SUCCESS) { + *returncode = 1; + if (returntext) { +@@ -548,6 +550,8 @@ changelog5_config_modify(Slapi_PBlock *pb, + slapi_log_err(SLAPI_LOG_REPL, repl_plugin_name_cl, + "changelog5_config_modify - Deleted the changelog at %s\n", currentDir); + } ++ /* Set compaction parameters */ ++ cl5ConfigSetCompaction(config.compactInterval, config.compactTime); + + rc = cl5Open(config.dir, &config.dbconfig); + if (rc != CL5_SUCCESS) { +@@ -575,7 +579,7 @@ changelog5_config_modify(Slapi_PBlock *pb, + if (config.maxEntries != CL5_NUM_IGNORE || + config.trimInterval != CL5_NUM_IGNORE || + strcmp(config.maxAge, CL5_STR_IGNORE) != 0) { +- rc = cl5ConfigTrimming(config.maxEntries, config.maxAge, config.compactInterval, config.compactTime, config.trimInterval); ++ rc = cl5ConfigTrimming(config.maxEntries, config.maxAge, config.trimInterval); + if (rc != CL5_SUCCESS) { + *returncode = 1; + if (returntext) { +diff --git a/ldap/servers/plugins/replication/cl5_init.c b/ldap/servers/plugins/replication/cl5_init.c +index 251859714..567e0274c 100644 +--- a/ldap/servers/plugins/replication/cl5_init.c ++++ b/ldap/servers/plugins/replication/cl5_init.c +@@ -45,6 +45,8 @@ changelog5_init() + rc = 0; /* OK */ + goto done; + } ++ /* Set compaction parameters */ ++ cl5ConfigSetCompaction(config.compactInterval, config.compactTime); + + /* start changelog */ + rc = cl5Open(config.dir, &config.dbconfig); +@@ -57,7 +59,7 @@ changelog5_init() + } + + /* set trimming parameters */ +- rc = cl5ConfigTrimming(config.maxEntries, config.maxAge, config.compactInterval, config.compactTime, config.trimInterval); ++ rc = cl5ConfigTrimming(config.maxEntries, config.maxAge, config.trimInterval); + if (rc != CL5_SUCCESS) { + slapi_log_err(SLAPI_LOG_ERR, repl_plugin_name_cl, + "changelog5_init: failed to configure changelog trimming\n"); +diff --git a/ldap/servers/plugins/replication/cl5_test.c b/ldap/servers/plugins/replication/cl5_test.c +index d6656653c..efb8c543a 100644 +--- a/ldap/servers/plugins/replication/cl5_test.c ++++ b/ldap/servers/plugins/replication/cl5_test.c +@@ -281,7 +281,7 @@ testTrimming() + rc = populateChangelog(300, NULL); + + if (rc == 0) +- rc = cl5ConfigTrimming(300, "1d", CHANGELOGDB_COMPACT_INTERVAL, CHANGELOGDB_TRIM_INTERVAL); ++ rc = cl5ConfigTrimming(300, "1d", CHANGELOGDB_TRIM_INTERVAL); + + interval = PR_SecondsToInterval(300); /* 5 min is default trimming interval */ + slapi_log_err(SLAPI_LOG_ERR, repl_plugin_name_cl, +diff --git a/ldap/servers/plugins/replication/repl_shared.h b/ldap/servers/plugins/replication/repl_shared.h +index 6708e12f7..b59b2bd27 100644 +--- a/ldap/servers/plugins/replication/repl_shared.h ++++ b/ldap/servers/plugins/replication/repl_shared.h +@@ -26,7 +26,7 @@ + + #define CHANGELOGDB_TRIM_INTERVAL 300 /* 5 minutes */ + #define CHANGELOGDB_COMPACT_INTERVAL 2592000 /* 30 days */ +-#define CHANGELOGDB_COMPACT_TIME "23:55" /* 30 days */ ++#define CHANGELOGDB_COMPACT_TIME "23:59" /* around midnight */ + + #define CONFIG_CHANGELOG_DIR_ATTRIBUTE "nsslapd-changelogdir" + #define CONFIG_CHANGELOG_MAXENTRIES_ATTRIBUTE "nsslapd-changelogmaxentries" +-- +2.31.1 + diff --git a/SOURCES/0018-Issue-4789-Temporary-password-rules-are-not-enforce-.patch b/SOURCES/0018-Issue-4789-Temporary-password-rules-are-not-enforce-.patch new file mode 100644 index 0000000..5ab86af --- /dev/null +++ b/SOURCES/0018-Issue-4789-Temporary-password-rules-are-not-enforce-.patch @@ -0,0 +1,51 @@ +From e7fdfe527a5f72674fe4b577a0555cabf8ec73a5 Mon Sep 17 00:00:00 2001 +From: tbordaz +Date: Mon, 7 Jun 2021 11:23:35 +0200 +Subject: [PATCH] Issue 4789 - Temporary password rules are not enforce with + local password policy (#4790) + +Bug description: + When allocating a password policy structure (new_passwdPolicy) + it is initialized with the local policy definition or + the global one. If it exists a local policy entry, the TPR + attributes (passwordTPRMaxUse, passwordTPRDelayValidFrom and + passwordTPRDelayExpireAt) are not taken into account. + +Fix description: + Take into account TPR attributes to initialize the policy + +relates: https://github.com/389ds/389-ds-base/issues/4789 + +Reviewed by: Simon Pichugin, William Brown + +Platforms tested: F34 +--- + ldap/servers/slapd/pw.c | 12 ++++++++++++ + 1 file changed, 12 insertions(+) + +diff --git a/ldap/servers/slapd/pw.c b/ldap/servers/slapd/pw.c +index 2a167c8f1..7680df41d 100644 +--- a/ldap/servers/slapd/pw.c ++++ b/ldap/servers/slapd/pw.c +@@ -2356,6 +2356,18 @@ new_passwdPolicy(Slapi_PBlock *pb, const char *dn) + if ((sval = attr_get_present_values(attr))) { + pwdpolicy->pw_dict_path = (char *)slapi_value_get_string(*sval); + } ++ } else if (!strcasecmp(attr_name, CONFIG_PW_TPR_MAXUSE)) { ++ if ((sval = attr_get_present_values(attr))) { ++ pwdpolicy->pw_tpr_maxuse = slapi_value_get_int(*sval); ++ } ++ } else if (!strcasecmp(attr_name, CONFIG_PW_TPR_DELAY_EXPIRE_AT)) { ++ if ((sval = attr_get_present_values(attr))) { ++ pwdpolicy->pw_tpr_delay_expire_at = slapi_value_get_int(*sval); ++ } ++ } else if (!strcasecmp(attr_name, CONFIG_PW_TPR_DELAY_VALID_FROM)) { ++ if ((sval = attr_get_present_values(attr))) { ++ pwdpolicy->pw_tpr_delay_valid_from = slapi_value_get_int(*sval); ++ } + } + } /* end of for() loop */ + if (pw_entry) { +-- +2.31.1 + diff --git a/SOURCES/0019-Issue-4788-CLI-should-support-Temporary-Password-Rul.patch b/SOURCES/0019-Issue-4788-CLI-should-support-Temporary-Password-Rul.patch new file mode 100644 index 0000000..f9e4266 --- /dev/null +++ b/SOURCES/0019-Issue-4788-CLI-should-support-Temporary-Password-Rul.patch @@ -0,0 +1,350 @@ +From 6a741b3ef50babf2ac2479437a38829204ffd438 Mon Sep 17 00:00:00 2001 +From: tbordaz +Date: Thu, 17 Jun 2021 16:22:09 +0200 +Subject: [PATCH] Issue 4788 - CLI should support Temporary Password Rules + attributes (#4793) + +Bug description: + Since #4725, password policy support temporary password rules. + CLI (dsconf) does not support this RFE and only direct ldap + operation can configure global/local password policy + +Fix description: + Update dsconf to support this new RFE. + To run successfully the testcase it relies on #4788 + +relates: #4788 + +Reviewed by: Simon Pichugin (thanks !!) + +Platforms tested: F34 +--- + .../password/pwdPolicy_attribute_test.py | 172 ++++++++++++++++-- + src/lib389/lib389/cli_conf/pwpolicy.py | 5 +- + src/lib389/lib389/pwpolicy.py | 5 +- + 3 files changed, 165 insertions(+), 17 deletions(-) + +diff --git a/dirsrvtests/tests/suites/password/pwdPolicy_attribute_test.py b/dirsrvtests/tests/suites/password/pwdPolicy_attribute_test.py +index aee3a91ad..085d0a373 100644 +--- a/dirsrvtests/tests/suites/password/pwdPolicy_attribute_test.py ++++ b/dirsrvtests/tests/suites/password/pwdPolicy_attribute_test.py +@@ -34,7 +34,7 @@ log = logging.getLogger(__name__) + + + @pytest.fixture(scope="module") +-def create_user(topology_st, request): ++def test_user(topology_st, request): + """User for binding operation""" + topology_st.standalone.config.set('nsslapd-auditlog-logging-enabled', 'on') + log.info('Adding test user {}') +@@ -56,10 +56,11 @@ def create_user(topology_st, request): + topology_st.standalone.simple_bind_s(DN_DM, PASSWORD) + + request.addfinalizer(fin) ++ return user + + + @pytest.fixture(scope="module") +-def password_policy(topology_st, create_user): ++def password_policy(topology_st, test_user): + """Set up password policy for subtree and user""" + + pwp = PwPolicyManager(topology_st.standalone) +@@ -71,7 +72,7 @@ def password_policy(topology_st, create_user): + pwp.create_user_policy(TEST_USER_DN, policy_props) + + @pytest.mark.skipif(ds_is_older('1.4.3.3'), reason="Not implemented") +-def test_pwd_reset(topology_st, create_user): ++def test_pwd_reset(topology_st, test_user): + """Test new password policy attribute "pwdReset" + + :id: 03db357b-4800-411e-a36e-28a534293004 +@@ -124,7 +125,7 @@ def test_pwd_reset(topology_st, create_user): + [('on', 'off', ldap.UNWILLING_TO_PERFORM), + ('off', 'off', ldap.UNWILLING_TO_PERFORM), + ('off', 'on', False), ('on', 'on', False)]) +-def test_change_pwd(topology_st, create_user, password_policy, ++def test_change_pwd(topology_st, test_user, password_policy, + subtree_pwchange, user_pwchange, exception): + """Verify that 'passwordChange' attr works as expected + User should have a priority over a subtree. +@@ -184,7 +185,7 @@ def test_change_pwd(topology_st, create_user, password_policy, + user.reset_password(TEST_USER_PWD) + + +-def test_pwd_min_age(topology_st, create_user, password_policy): ++def test_pwd_min_age(topology_st, test_user, password_policy): + """If we set passwordMinAge to some value, for example to 10, then it + should not allow the user to change the password within 10 seconds after + his previous change. +@@ -257,7 +258,7 @@ def test_pwd_min_age(topology_st, create_user, password_policy): + topology_st.standalone.simple_bind_s(DN_DM, PASSWORD) + user.reset_password(TEST_USER_PWD) + +-def test_global_tpr_maxuse_1(topology_st, create_user, request): ++def test_global_tpr_maxuse_1(topology_st, test_user, request): + """Test global TPR policy : passwordTPRMaxUse + Test that after passwordTPRMaxUse failures to bind + additional bind with valid password are failing with CONSTRAINT_VIOLATION +@@ -374,7 +375,7 @@ def test_global_tpr_maxuse_1(topology_st, create_user, request): + + request.addfinalizer(fin) + +-def test_global_tpr_maxuse_2(topology_st, create_user, request): ++def test_global_tpr_maxuse_2(topology_st, test_user, request): + """Test global TPR policy : passwordTPRMaxUse + Test that after less than passwordTPRMaxUse failures to bind + additional bind with valid password are successfull +@@ -474,7 +475,7 @@ def test_global_tpr_maxuse_2(topology_st, create_user, request): + + request.addfinalizer(fin) + +-def test_global_tpr_maxuse_3(topology_st, create_user, request): ++def test_global_tpr_maxuse_3(topology_st, test_user, request): + """Test global TPR policy : passwordTPRMaxUse + Test that after less than passwordTPRMaxUse failures to bind + A bind with valid password is successfull but passwordMustChange +@@ -587,7 +588,7 @@ def test_global_tpr_maxuse_3(topology_st, create_user, request): + + request.addfinalizer(fin) + +-def test_global_tpr_maxuse_4(topology_st, create_user, request): ++def test_global_tpr_maxuse_4(topology_st, test_user, request): + """Test global TPR policy : passwordTPRMaxUse + Test that a TPR attribute passwordTPRMaxUse + can be updated by DM but not the by user itself +@@ -701,7 +702,148 @@ def test_global_tpr_maxuse_4(topology_st, create_user, request): + + request.addfinalizer(fin) + +-def test_global_tpr_delayValidFrom_1(topology_st, create_user, request): ++def test_local_tpr_maxuse_5(topology_st, test_user, request): ++ """Test TPR local policy overpass global one: passwordTPRMaxUse ++ Test that after passwordTPRMaxUse failures to bind ++ additional bind with valid password are failing with CONSTRAINT_VIOLATION ++ ++ :id: c3919707-d804-445a-8754-8385b1072c42 ++ :customerscenario: False ++ :setup: Standalone instance ++ :steps: ++ 1. Global password policy Enable passwordMustChange ++ 2. Global password policy Set passwordTPRMaxUse=5 ++ 3. Global password policy Set passwordMaxFailure to a higher value to not disturb the test ++ 4. Local password policy Enable passwordMustChange ++ 5. Local password policy Set passwordTPRMaxUse=10 (higher than global) ++ 6. Bind with a wrong password 10 times and check INVALID_CREDENTIALS ++ 7. Check that passwordTPRUseCount got to the limit (5) ++ 8. Bind with a wrong password (CONSTRAINT_VIOLATION) ++ and check passwordTPRUseCount overpass the limit by 1 (11) ++ 9. Bind with a valid password 10 times and check CONSTRAINT_VIOLATION ++ and check passwordTPRUseCount increases ++ 10. Reset password policy configuration and remove local password from user ++ :expected results: ++ 1. Success ++ 2. Success ++ 3. Success ++ 4. Success ++ 5. Success ++ 6. Success ++ 7. Success ++ 8. Success ++ 9. Success ++ 10. Success ++ """ ++ ++ global_tpr_maxuse = 5 ++ # Set global password policy config, passwordMaxFailure being higher than ++ # passwordTPRMaxUse so that TPR is enforced first ++ topology_st.standalone.config.replace('passwordMustChange', 'on') ++ topology_st.standalone.config.replace('passwordMaxFailure', str(global_tpr_maxuse + 20)) ++ topology_st.standalone.config.replace('passwordTPRMaxUse', str(global_tpr_maxuse)) ++ time.sleep(.5) ++ ++ local_tpr_maxuse = global_tpr_maxuse + 5 ++ # Reset user's password with a local password policy ++ # that has passwordTPRMaxUse higher than global ++ #our_user = UserAccount(topology_st.standalone, TEST_USER_DN) ++ subprocess.call(['%s/dsconf' % topology_st.standalone.get_sbin_dir(), ++ 'slapd-standalone1', ++ 'localpwp', ++ 'adduser', ++ test_user.dn]) ++ subprocess.call(['%s/dsconf' % topology_st.standalone.get_sbin_dir(), ++ 'slapd-standalone1', ++ 'localpwp', ++ 'set', ++ '--pwptprmaxuse', ++ str(local_tpr_maxuse), ++ '--pwdmustchange', ++ 'on', ++ test_user.dn]) ++ test_user.replace('userpassword', PASSWORD) ++ time.sleep(.5) ++ ++ # look up to passwordTPRMaxUse with failing ++ # bind to check that the limits of TPR are enforced ++ for i in range(local_tpr_maxuse): ++ # Bind as user with a wrong password ++ with pytest.raises(ldap.INVALID_CREDENTIALS): ++ test_user.rebind('wrong password') ++ time.sleep(.5) ++ ++ # Check that pwdReset is TRUE ++ topology_st.standalone.simple_bind_s(DN_DM, PASSWORD) ++ #assert test_user.get_attr_val_utf8('pwdReset') == 'TRUE' ++ ++ # Check that pwdTPRReset is TRUE ++ assert test_user.get_attr_val_utf8('pwdTPRReset') == 'TRUE' ++ assert test_user.get_attr_val_utf8('pwdTPRUseCount') == str(i+1) ++ log.info("%dth failing bind (INVALID_CREDENTIALS) => pwdTPRUseCount = %d" % (i+1, i+1)) ++ ++ ++ # Now the #failures reached passwordTPRMaxUse ++ # Check that pwdReset is TRUE ++ topology_st.standalone.simple_bind_s(DN_DM, PASSWORD) ++ ++ # Check that pwdTPRReset is TRUE ++ assert test_user.get_attr_val_utf8('pwdTPRReset') == 'TRUE' ++ assert test_user.get_attr_val_utf8('pwdTPRUseCount') == str(local_tpr_maxuse) ++ log.info("last failing bind (INVALID_CREDENTIALS) => pwdTPRUseCount = %d" % (local_tpr_maxuse)) ++ ++ # Bind as user with wrong password --> ldap.CONSTRAINT_VIOLATION ++ with pytest.raises(ldap.CONSTRAINT_VIOLATION): ++ test_user.rebind("wrong password") ++ time.sleep(.5) ++ ++ # Check that pwdReset is TRUE ++ topology_st.standalone.simple_bind_s(DN_DM, PASSWORD) ++ ++ # Check that pwdTPRReset is TRUE ++ assert test_user.get_attr_val_utf8('pwdTPRReset') == 'TRUE' ++ assert test_user.get_attr_val_utf8('pwdTPRUseCount') == str(local_tpr_maxuse + 1) ++ log.info("failing bind (CONSTRAINT_VIOLATION) => pwdTPRUseCount = %d" % (local_tpr_maxuse + i)) ++ ++ # Now check that all next attempts with correct password are all in LDAP_CONSTRAINT_VIOLATION ++ # and passwordTPRRetryCount remains unchanged ++ # account is now similar to locked ++ for i in range(10): ++ # Bind as user with valid password ++ with pytest.raises(ldap.CONSTRAINT_VIOLATION): ++ test_user.rebind(PASSWORD) ++ time.sleep(.5) ++ ++ # Check that pwdReset is TRUE ++ topology_st.standalone.simple_bind_s(DN_DM, PASSWORD) ++ ++ # Check that pwdTPRReset is TRUE ++ # pwdTPRUseCount keeps increasing ++ assert test_user.get_attr_val_utf8('pwdTPRReset') == 'TRUE' ++ assert test_user.get_attr_val_utf8('pwdTPRUseCount') == str(local_tpr_maxuse + i + 2) ++ log.info("Rejected bind (CONSTRAINT_VIOLATION) => pwdTPRUseCount = %d" % (local_tpr_maxuse + i + 2)) ++ ++ ++ def fin(): ++ topology_st.standalone.restart() ++ # Reset password policy config ++ topology_st.standalone.simple_bind_s(DN_DM, PASSWORD) ++ topology_st.standalone.config.replace('passwordMustChange', 'off') ++ ++ # Remove local password policy from that entry ++ subprocess.call(['%s/dsconf' % topology_st.standalone.get_sbin_dir(), ++ 'slapd-standalone1', ++ 'localpwp', ++ 'remove', ++ test_user.dn]) ++ ++ # Reset user's password ++ test_user.replace('userpassword', TEST_USER_PWD) ++ ++ ++ request.addfinalizer(fin) ++ ++def test_global_tpr_delayValidFrom_1(topology_st, test_user, request): + """Test global TPR policy : passwordTPRDelayValidFrom + Test that a TPR password is not valid before reset time + + passwordTPRDelayValidFrom +@@ -766,7 +908,7 @@ def test_global_tpr_delayValidFrom_1(topology_st, create_user, request): + + request.addfinalizer(fin) + +-def test_global_tpr_delayValidFrom_2(topology_st, create_user, request): ++def test_global_tpr_delayValidFrom_2(topology_st, test_user, request): + """Test global TPR policy : passwordTPRDelayValidFrom + Test that a TPR password is valid after reset time + + passwordTPRDelayValidFrom +@@ -838,7 +980,7 @@ def test_global_tpr_delayValidFrom_2(topology_st, create_user, request): + + request.addfinalizer(fin) + +-def test_global_tpr_delayValidFrom_3(topology_st, create_user, request): ++def test_global_tpr_delayValidFrom_3(topology_st, test_user, request): + """Test global TPR policy : passwordTPRDelayValidFrom + Test that a TPR attribute passwordTPRDelayValidFrom + can be updated by DM but not the by user itself +@@ -940,7 +1082,7 @@ def test_global_tpr_delayValidFrom_3(topology_st, create_user, request): + + request.addfinalizer(fin) + +-def test_global_tpr_delayExpireAt_1(topology_st, create_user, request): ++def test_global_tpr_delayExpireAt_1(topology_st, test_user, request): + """Test global TPR policy : passwordTPRDelayExpireAt + Test that a TPR password is not valid after reset time + + passwordTPRDelayExpireAt +@@ -1010,7 +1152,7 @@ def test_global_tpr_delayExpireAt_1(topology_st, create_user, request): + + request.addfinalizer(fin) + +-def test_global_tpr_delayExpireAt_2(topology_st, create_user, request): ++def test_global_tpr_delayExpireAt_2(topology_st, test_user, request): + """Test global TPR policy : passwordTPRDelayExpireAt + Test that a TPR password is valid before reset time + + passwordTPRDelayExpireAt +@@ -1082,7 +1224,7 @@ def test_global_tpr_delayExpireAt_2(topology_st, create_user, request): + + request.addfinalizer(fin) + +-def test_global_tpr_delayExpireAt_3(topology_st, create_user, request): ++def test_global_tpr_delayExpireAt_3(topology_st, test_user, request): + """Test global TPR policy : passwordTPRDelayExpireAt + Test that a TPR attribute passwordTPRDelayExpireAt + can be updated by DM but not the by user itself +diff --git a/src/lib389/lib389/cli_conf/pwpolicy.py b/src/lib389/lib389/cli_conf/pwpolicy.py +index 2838afcb8..26af6e7ec 100644 +--- a/src/lib389/lib389/cli_conf/pwpolicy.py ++++ b/src/lib389/lib389/cli_conf/pwpolicy.py +@@ -255,6 +255,9 @@ def create_parser(subparsers): + set_parser.add_argument('--pwpinheritglobal', help="Set to \"on\" to allow local policies to inherit the global policy") + set_parser.add_argument('--pwddictcheck', help="Set to \"on\" to enforce CrackLib dictionary checking") + set_parser.add_argument('--pwddictpath', help="Filesystem path to specific/custom CrackLib dictionary files") ++ set_parser.add_argument('--pwptprmaxuse', help="Number of times a reset password can be used for authentication") ++ set_parser.add_argument('--pwptprdelayexpireat', help="Number of seconds after which a reset password expires") ++ set_parser.add_argument('--pwptprdelayvalidfrom', help="Number of seconds to wait before using a reset password to authenticated") + # delete local password policy + del_parser = local_subcommands.add_parser('remove', help='Remove a local password policy') + del_parser.set_defaults(func=del_local_policy) +@@ -291,4 +294,4 @@ def create_parser(subparsers): + ############################################# + set_parser.add_argument('DN', nargs=1, help='Set the local policy for this entry DN') + add_subtree_parser.add_argument('DN', nargs=1, help='Add/replace the subtree policy for this entry DN') +- add_user_parser.add_argument('DN', nargs=1, help='Add/replace the local password policy for this entry DN') +\ No newline at end of file ++ add_user_parser.add_argument('DN', nargs=1, help='Add/replace the local password policy for this entry DN') +diff --git a/src/lib389/lib389/pwpolicy.py b/src/lib389/lib389/pwpolicy.py +index 8653cb195..d2427933b 100644 +--- a/src/lib389/lib389/pwpolicy.py ++++ b/src/lib389/lib389/pwpolicy.py +@@ -65,7 +65,10 @@ class PwPolicyManager(object): + 'pwddictcheck': 'passworddictcheck', + 'pwddictpath': 'passworddictpath', + 'pwdallowhash': 'nsslapd-allow-hashed-passwords', +- 'pwpinheritglobal': 'nsslapd-pwpolicy-inherit-global' ++ 'pwpinheritglobal': 'nsslapd-pwpolicy-inherit-global', ++ 'pwptprmaxuse': 'passwordTPRMaxUse', ++ 'pwptprdelayexpireat': 'passwordTPRDelayExpireAt', ++ 'pwptprdelayvalidfrom': 'passwordTPRDelayValidFrom' + } + + def is_subtree_policy(self, dn): +-- +2.31.1 + diff --git a/SOURCES/0020-Issue-4447-Crash-when-the-Referential-Integrity-log-.patch b/SOURCES/0020-Issue-4447-Crash-when-the-Referential-Integrity-log-.patch new file mode 100644 index 0000000..193d44b --- /dev/null +++ b/SOURCES/0020-Issue-4447-Crash-when-the-Referential-Integrity-log-.patch @@ -0,0 +1,179 @@ +From 7b7217538908ae58df864ef5cd82e1d3303c189f Mon Sep 17 00:00:00 2001 +From: Mark Reynolds +Date: Mon, 7 Jun 2021 12:58:42 -0400 +Subject: [PATCH] Issue 4447 - Crash when the Referential Integrity log is + manually edited + +Bug Description: If the referint log is manually edited with a string + that is not a DN the server will crash when processing + the log. + +Fix Description: Check for NULL pointers when strtoking the file line. + +relates: https://github.com/389ds/389-ds-base/issues/4447 + +Reviewed by: firstyear(Thanks!) +--- + .../tests/suites/plugins/referint_test.py | 72 +++++++++++++++---- + ldap/servers/plugins/referint/referint.c | 7 ++ + src/lib389/lib389/plugins.py | 15 ++++ + 3 files changed, 80 insertions(+), 14 deletions(-) + +diff --git a/dirsrvtests/tests/suites/plugins/referint_test.py b/dirsrvtests/tests/suites/plugins/referint_test.py +index 02b985767..fda602545 100644 +--- a/dirsrvtests/tests/suites/plugins/referint_test.py ++++ b/dirsrvtests/tests/suites/plugins/referint_test.py +@@ -1,5 +1,5 @@ + # --- BEGIN COPYRIGHT BLOCK --- +-# Copyright (C) 2016 Red Hat, Inc. ++# Copyright (C) 2021 Red Hat, Inc. + # All rights reserved. + # + # License: GPL (version 3 or any later version). +@@ -12,13 +12,11 @@ Created on Dec 12, 2019 + @author: tbordaz + ''' + import logging +-import subprocess + import pytest + from lib389 import Entry +-from lib389.utils import * +-from lib389.plugins import * +-from lib389._constants import * +-from lib389.idm.user import UserAccounts, UserAccount ++from lib389.plugins import ReferentialIntegrityPlugin ++from lib389._constants import DEFAULT_SUFFIX ++from lib389.idm.user import UserAccounts + from lib389.idm.group import Groups + from lib389.topologies import topology_st as topo + +@@ -29,21 +27,27 @@ log = logging.getLogger(__name__) + ESCAPED_RDN_BASE = "foo\\,oo" + def _user_get_dn(no): + uid = '%s%d' % (ESCAPED_RDN_BASE, no) +- dn = 'uid=%s,%s' % (uid, SUFFIX) ++ dn = 'uid=%s,%s' % (uid, DEFAULT_SUFFIX) + return (uid, dn) + + def add_escaped_user(server, no): + (uid, dn) = _user_get_dn(no) + log.fatal('Adding user (%s): ' % dn) +- server.add_s(Entry((dn, {'objectclass': ['top', 'person', 'organizationalPerson', 'inetOrgPerson'], +- 'uid': [uid], +- 'sn' : [uid], +- 'cn' : [uid]}))) ++ users = UserAccounts(server, DEFAULT_SUFFIX, None) ++ user_properties = { ++ 'objectclass': ['top', 'person', 'organizationalPerson', 'inetOrgPerson', 'posixAccount'], ++ 'uid': uid, ++ 'cn' : uid, ++ 'sn' : uid, ++ 'uidNumber' : '1000', ++ 'gidNumber' : '2000', ++ 'homeDirectory' : '/home/testuser', ++ } ++ users.create(properties=user_properties) + return dn + +-@pytest.mark.ds50020 + def test_referential_false_failure(topo): +- """On MODRDN referential integrity can erronously fail ++ """On MODRDN referential integrity can erroneously fail + + :id: f77aeb80-c4c4-471b-8c1b-4733b714778b + :setup: Standalone Instance +@@ -100,6 +104,46 @@ def test_referential_false_failure(topo): + inst.restart() + + # Here if the bug is fixed, referential is able to update the member value +- inst.rename_s(user1.dn, 'uid=new_test_user_1001', newsuperior=SUFFIX, delold=0) ++ user1.rename('uid=new_test_user_1001', newsuperior=DEFAULT_SUFFIX, deloldrdn=False) + + ++def test_invalid_referint_log(topo): ++ """If there is an invalid log line in the referint log, make sure the server ++ does not crash at startup ++ ++ :id: 34807b5a-ab17-4281-ae48-4e3513e19145 ++ :setup: Standalone Instance ++ :steps: ++ 1. Set the referint log delay ++ 2. Create invalid log ++ 3. Start the server (no crash) ++ :expectedresults: ++ 1. Success ++ 2. Success ++ 3. Success ++ """ ++ ++ inst = topo.standalone ++ ++ # Set delay - required for log parsing at server startup ++ plugin = ReferentialIntegrityPlugin(inst) ++ plugin.enable() ++ plugin.set_update_delay('2') ++ logfile = plugin.get_log_file() ++ inst.restart() ++ ++ # Create invalid log ++ inst.stop() ++ with open(logfile, 'w') as log_fh: ++ log_fh.write("CRASH\n") ++ ++ # Start the instance ++ inst.start() ++ assert inst.status() ++ ++ ++if __name__ == '__main__': ++ # Run isolated ++ # -s for DEBUG mode ++ CURRENT_FILE = os.path.realpath(__file__) ++ pytest.main("-s %s" % CURRENT_FILE) +diff --git a/ldap/servers/plugins/referint/referint.c b/ldap/servers/plugins/referint/referint.c +index fd5356d72..28240c1f6 100644 +--- a/ldap/servers/plugins/referint/referint.c ++++ b/ldap/servers/plugins/referint/referint.c +@@ -1447,6 +1447,13 @@ referint_thread_func(void *arg __attribute__((unused))) + sdn = slapi_sdn_new_normdn_byref(ptoken); + ptoken = ldap_utf8strtok_r(NULL, delimiter, &iter); + ++ if (ptoken == NULL) { ++ /* Invalid line in referint log, skip it */ ++ slapi_log_err(SLAPI_LOG_ERR, REFERINT_PLUGIN_SUBSYSTEM, ++ "Skipping invalid referint log line: (%s)\n", thisline); ++ slapi_sdn_free(&sdn); ++ continue; ++ } + if (!strcasecmp(ptoken, "NULL")) { + tmprdn = NULL; + } else { +diff --git a/src/lib389/lib389/plugins.py b/src/lib389/lib389/plugins.py +index 2d88e60bd..b07e80022 100644 +--- a/src/lib389/lib389/plugins.py ++++ b/src/lib389/lib389/plugins.py +@@ -518,6 +518,21 @@ class ReferentialIntegrityPlugin(Plugin): + + self.set('referint-update-delay', str(value)) + ++ def get_log_file(self): ++ """Get referint log file""" ++ ++ return self.get_attr_val_utf8('referint-logfile') ++ ++ def get_log_file_formatted(self): ++ """Get referint log file""" ++ ++ return self.display_attr('referint-logfile') ++ ++ def set_log_file(self, value): ++ """Set referint log file""" ++ ++ self.set('referint-logfile', value) ++ + def get_membership_attr(self, formatted=False): + """Get referint-membership-attr attribute""" + +-- +2.31.1 + diff --git a/SOURCES/0021-Issue-4791-Missing-dependency-for-RetroCL-RFE.patch b/SOURCES/0021-Issue-4791-Missing-dependency-for-RetroCL-RFE.patch new file mode 100644 index 0000000..4810288 --- /dev/null +++ b/SOURCES/0021-Issue-4791-Missing-dependency-for-RetroCL-RFE.patch @@ -0,0 +1,114 @@ +From 964a153b420b26140e0bbddfbebb4a51aaa0e4ea Mon Sep 17 00:00:00 2001 +From: James Chapman +Date: Thu, 3 Jun 2021 15:16:22 +0000 +Subject: [PATCH 1/7] Issue 4791 - Missing dependency for RetroCL RFE + +Description: The RetroCL exclude attribute RFE is dependent on functionality of the + EntryUUID bug fix, that didn't make into the latest build. This breaks the + RetroCL exclude attr feature so we need to provide a workaround. + +Fixes: https://github.com/389ds/389-ds-base/issues/4791 + +Relates: https://github.com/389ds/389-ds-base/pull/4723 + +Relates: https://github.com/389ds/389-ds-base/issues/4224 + +Reviewed by: tbordaz, droideck (Thank you) +--- + .../tests/suites/retrocl/basic_test.py | 6 ++-- + .../lib389/cli_conf/plugins/retrochangelog.py | 35 +++++++++++++++++-- + 2 files changed, 36 insertions(+), 5 deletions(-) + +diff --git a/dirsrvtests/tests/suites/retrocl/basic_test.py b/dirsrvtests/tests/suites/retrocl/basic_test.py +index 112c73cb9..f3bc50f29 100644 +--- a/dirsrvtests/tests/suites/retrocl/basic_test.py ++++ b/dirsrvtests/tests/suites/retrocl/basic_test.py +@@ -17,7 +17,7 @@ from lib389.utils import * + from lib389.tasks import * + from lib389.cli_base import FakeArgs, connect_instance, disconnect_instance + from lib389.cli_base.dsrc import dsrc_arg_concat +-from lib389.cli_conf.plugins.retrochangelog import retrochangelog_add ++from lib389.cli_conf.plugins.retrochangelog import retrochangelog_add_attr + from lib389.idm.user import UserAccount, UserAccounts, nsUserAccounts + + pytestmark = pytest.mark.tier1 +@@ -122,7 +122,7 @@ def test_retrocl_exclude_attr_add(topology_st): + args.bindpw = None + args.prompt = False + args.exclude_attrs = ATTR_HOMEPHONE +- args.func = retrochangelog_add ++ args.func = retrochangelog_add_attr + dsrc_inst = dsrc_arg_concat(args, None) + inst = connect_instance(dsrc_inst, False, args) + result = args.func(inst, None, log, args) +@@ -255,7 +255,7 @@ def test_retrocl_exclude_attr_mod(topology_st): + args.bindpw = None + args.prompt = False + args.exclude_attrs = ATTR_CARLICENSE +- args.func = retrochangelog_add ++ args.func = retrochangelog_add_attr + dsrc_inst = dsrc_arg_concat(args, None) + inst = connect_instance(dsrc_inst, False, args) + result = args.func(inst, None, log, args) +diff --git a/src/lib389/lib389/cli_conf/plugins/retrochangelog.py b/src/lib389/lib389/cli_conf/plugins/retrochangelog.py +index 9940c6532..160fbb82d 100644 +--- a/src/lib389/lib389/cli_conf/plugins/retrochangelog.py ++++ b/src/lib389/lib389/cli_conf/plugins/retrochangelog.py +@@ -6,8 +6,13 @@ + # See LICENSE for details. + # --- END COPYRIGHT BLOCK --- + ++# JC Work around for missing dependency on https://github.com/389ds/389-ds-base/pull/4344 ++import ldap ++ + from lib389.plugins import RetroChangelogPlugin +-from lib389.cli_conf import add_generic_plugin_parsers, generic_object_edit ++# JC Work around for missing dependency https://github.com/389ds/389-ds-base/pull/4344 ++# from lib389.cli_conf import add_generic_plugin_parsers, generic_object_edit, generic_object_add_attr ++from lib389.cli_conf import add_generic_plugin_parsers, generic_object_edit, _args_to_attrs + + arg_to_attr = { + 'is_replicated': 'isReplicated', +@@ -18,12 +23,38 @@ arg_to_attr = { + 'exclude_attrs': 'nsslapd-exclude-attrs' + } + +- + def retrochangelog_edit(inst, basedn, log, args): + log = log.getChild('retrochangelog_edit') + plugin = RetroChangelogPlugin(inst) + generic_object_edit(plugin, log, args, arg_to_attr) + ++# JC Work around for missing dependency https://github.com/389ds/389-ds-base/pull/4344 ++def retrochangelog_add_attr(inst, basedn, log, args): ++ log = log.getChild('retrochangelog_add_attr') ++ plugin = RetroChangelogPlugin(inst) ++ generic_object_add_attr(plugin, log, args, arg_to_attr) ++ ++# JC Work around for missing dependency https://github.com/389ds/389-ds-base/pull/4344 ++def generic_object_add_attr(dsldap_object, log, args, arg_to_attr): ++ """Add an attribute to the entry. This differs to 'edit' as edit uses replace, ++ and this allows multivalues to be added. ++ ++ dsldap_object should be a single instance of DSLdapObject with a set dn ++ """ ++ log = log.getChild('generic_object_add_attr') ++ # Gather the attributes ++ attrs = _args_to_attrs(args, arg_to_attr) ++ ++ modlist = [] ++ for attr, value in attrs.items(): ++ if not isinstance(value, list): ++ value = [value] ++ modlist.append((ldap.MOD_ADD, attr, value)) ++ if len(modlist) > 0: ++ dsldap_object.apply_mods(modlist) ++ log.info("Successfully changed the %s", dsldap_object.dn) ++ else: ++ raise ValueError("There is nothing to set in the %s plugin entry" % dsldap_object.dn) + + def _add_parser_args(parser): + parser.add_argument('--is-replicated', choices=['TRUE', 'FALSE'], type=str.upper, +-- +2.31.1 + diff --git a/SOURCES/0022-Issue-4656-remove-problematic-language-from-ds-replc.patch b/SOURCES/0022-Issue-4656-remove-problematic-language-from-ds-replc.patch new file mode 100644 index 0000000..82d6945 --- /dev/null +++ b/SOURCES/0022-Issue-4656-remove-problematic-language-from-ds-replc.patch @@ -0,0 +1,642 @@ +From d2ac7e98d53cfe6c74c99ddf3504b1072418f05a Mon Sep 17 00:00:00 2001 +From: Mark Reynolds +Date: Thu, 11 Mar 2021 10:12:46 -0500 +Subject: [PATCH] Issue 4656 - remove problematic language from ds-replcheck + +Description: remove master from ds-replcheck and replace it with supplier + +relates: https://github.com/389ds/389-ds-base/issues/4656 + +Reviewed by: mreynolds + +e with '#' will be ignored, and an empty message aborts the commit. +--- + ldap/admin/src/scripts/ds-replcheck | 202 ++++++++++++++-------------- + 1 file changed, 101 insertions(+), 101 deletions(-) + +diff --git a/ldap/admin/src/scripts/ds-replcheck b/ldap/admin/src/scripts/ds-replcheck +index 169496e8f..f411f357a 100755 +--- a/ldap/admin/src/scripts/ds-replcheck ++++ b/ldap/admin/src/scripts/ds-replcheck +@@ -1,7 +1,7 @@ + #!/usr/bin/python3 + + # --- BEGIN COPYRIGHT BLOCK --- +-# Copyright (C) 2020 Red Hat, Inc. ++# Copyright (C) 2021 Red Hat, Inc. + # All rights reserved. + # + # License: GPL (version 3 or any later version). +@@ -63,7 +63,7 @@ def remove_entry(rentries, dn): + def get_ruv_time(ruv, rid): + """Take a RUV element (nsds50ruv attribute) and extract the timestamp from maxcsn + :param ruv - A lsit of RUV elements +- :param rid - The rid of the master to extractthe maxcsn time from ++ :param rid - The rid of the supplier to extract the maxcsn time from + :return: The time in seconds of the maxcsn, or 0 if there is no maxcsn, or -1 if + the rid was not found + """ +@@ -213,22 +213,22 @@ def get_ruv_state(opts): + :param opts - all the script options + :return - A text description of the replicaton state + """ +- mtime = get_ruv_time(opts['master_ruv'], opts['rid']) ++ mtime = get_ruv_time(opts['supplier_ruv'], opts['rid']) + rtime = get_ruv_time(opts['replica_ruv'], opts['rid']) + if mtime == -1: +- repl_state = "Replication State: Replica ID ({}) not found in Master's RUV".format(opts['rid']) ++ repl_state = "Replication State: Replica ID ({}) not found in Supplier's RUV".format(opts['rid']) + elif rtime == -1: + repl_state = "Replication State: Replica ID ({}) not found in Replica's RUV (not initialized?)".format(opts['rid']) + elif mtime == 0: +- repl_state = "Replication State: Master has not seen any updates" ++ repl_state = "Replication State: Supplier has not seen any updates" + elif rtime == 0: +- repl_state = "Replication State: Replica has not seen any changes from the Master" ++ repl_state = "Replication State: Replica has not seen any changes from the Supplier" + elif mtime > rtime: +- repl_state = "Replication State: Replica is behind Master by: {} seconds".format(mtime - rtime) ++ repl_state = "Replication State: Replica is behind Supplier by: {} seconds".format(mtime - rtime) + elif mtime < rtime: +- repl_state = "Replication State: Replica is ahead of Master by: {} seconds".format(rtime - mtime) ++ repl_state = "Replication State: Replica is ahead of Supplier by: {} seconds".format(rtime - mtime) + else: +- repl_state = "Replication State: Master and Replica are in perfect synchronization" ++ repl_state = "Replication State: Supplier and Replica are in perfect synchronization" + + return repl_state + +@@ -238,11 +238,11 @@ def get_ruv_report(opts): + :param opts - all the script options + :return - A text blob to display in the report + """ +- opts['master_ruv'].sort() ++ opts['supplier_ruv'].sort() + opts['replica_ruv'].sort() + +- report = "Master RUV:\n" +- for element in opts['master_ruv']: ++ report = "Supplier RUV:\n" ++ for element in opts['supplier_ruv']: + report += " %s\n" % (element) + report += "\nReplica RUV:\n" + for element in opts['replica_ruv']: +@@ -521,7 +521,7 @@ def get_ldif_ruv(LDIF, opts): + + def cmp_entry(mentry, rentry, opts): + """Compare the two entries, and return a "diff map" +- :param mentry - A Master entry ++ :param mentry - A Supplier entry + :param rentry - A Replica entry + :param opts - A Dict of the scripts options + :return - A Dict of the differences in the entry, or None +@@ -536,7 +536,7 @@ def cmp_entry(mentry, rentry, opts): + mlist = list(mentry.data.keys()) + + # +- # Check master ++ # Check Supplier + # + for mattr in mlist: + if mattr in opts['ignore']: +@@ -555,7 +555,7 @@ def cmp_entry(mentry, rentry, opts): + if not found: + diff['missing'].append("") + found = True +- diff['missing'].append(" - Master's State Info: %s" % (val)) ++ diff['missing'].append(" - Supplier's State Info: %s" % (val)) + diff['missing'].append(" - Date: %s\n" % (time.ctime(extract_time(val)))) + else: + # No state info, just move on +@@ -566,18 +566,18 @@ def cmp_entry(mentry, rentry, opts): + if report_conflict(rentry, mattr, opts) and report_conflict(mentry, mattr, opts): + diff['diff'].append(" - Attribute '%s' is different:" % mattr) + if 'nscpentrywsi' in mentry.data: +- # Process Master ++ # Process Supplier + found = False + for val in mentry.data['nscpentrywsi']: + if val.lower().startswith(mattr + ';'): + if not found: +- diff['diff'].append(" Master:") ++ diff['diff'].append(" Supplier:") + diff['diff'].append(" - Value: %s" % (val.split(':')[1].lstrip())) + diff['diff'].append(" - State Info: %s" % (val)) + diff['diff'].append(" - Date: %s\n" % (time.ctime(extract_time(val)))) + found = True + if not found: +- diff['diff'].append(" Master: ") ++ diff['diff'].append(" Supplier: ") + for val in mentry.data[mattr]: + # This is an "origin" value which means it's never been + # updated since replication was set up. So its the +@@ -605,7 +605,7 @@ def cmp_entry(mentry, rentry, opts): + diff['diff'].append("") + else: + # no state info, report what we got +- diff['diff'].append(" Master: ") ++ diff['diff'].append(" Supplier: ") + for val in mentry.data[mattr]: + diff['diff'].append(" - %s: %s" % (mattr, val)) + diff['diff'].append(" Replica: ") +@@ -622,9 +622,9 @@ def cmp_entry(mentry, rentry, opts): + continue + + if rattr not in mlist: +- # Master is missing the attribute ++ # Supplier is missing the attribute + if report_conflict(rentry, rattr, opts): +- diff['missing'].append(" - Master missing attribute: \"%s\"" % (rattr)) ++ diff['missing'].append(" - Supplier missing attribute: \"%s\"" % (rattr)) + diff_count += 1 + if 'nscpentrywsi' in rentry.data: + found = False +@@ -663,7 +663,7 @@ def do_offline_report(opts, output_file=None): + try: + MLDIF = open(opts['mldif'], "r") + except Exception as e: +- print('Failed to open Master LDIF: ' + str(e)) ++ print('Failed to open Supplier LDIF: ' + str(e)) + return + + try: +@@ -676,10 +676,10 @@ def do_offline_report(opts, output_file=None): + # Verify LDIF Files + try: + if opts['verbose']: +- print("Validating Master ldif file ({})...".format(opts['mldif'])) ++ print("Validating Supplier ldif file ({})...".format(opts['mldif'])) + LDIFRecordList(MLDIF).parse() + except ValueError: +- print('Master LDIF file in invalid, aborting...') ++ print('Supplier LDIF file in invalid, aborting...') + MLDIF.close() + RLDIF.close() + return +@@ -696,34 +696,34 @@ def do_offline_report(opts, output_file=None): + # Get all the dn's, and entry counts + if opts['verbose']: + print ("Gathering all the DN's...") +- master_dns = get_dns(MLDIF, opts['mldif'], opts) ++ supplier_dns = get_dns(MLDIF, opts['mldif'], opts) + replica_dns = get_dns(RLDIF, opts['rldif'], opts) +- if master_dns is None or replica_dns is None: ++ if supplier_dns is None or replica_dns is None: + print("Aborting scan...") + MLDIF.close() + RLDIF.close() + sys.exit(1) +- m_count = len(master_dns) ++ m_count = len(supplier_dns) + r_count = len(replica_dns) + + # Get DB RUV + if opts['verbose']: + print ("Gathering the database RUV's...") +- opts['master_ruv'] = get_ldif_ruv(MLDIF, opts) ++ opts['supplier_ruv'] = get_ldif_ruv(MLDIF, opts) + opts['replica_ruv'] = get_ldif_ruv(RLDIF, opts) + +- """ Compare the master entries with the replica's. Take our list of dn's from +- the master ldif and get that entry( dn) from the master and replica ldif. In ++ """ Compare the Supplier entries with the replica's. Take our list of dn's from ++ the Supplier ldif and get that entry( dn) from the Supplier and replica ldif. In + this phase we keep keep track of conflict/tombstone counts, and we check for + missing entries and entry differences. We only need to do the entry diff + checking in this phase - we do not need to do it when process the replica dn's + because if the entry exists in both LDIF's then we already checked or diffs +- while processing the master dn's. ++ while processing the Supplier dn's. + """ + if opts['verbose']: +- print ("Comparing Master to Replica...") ++ print ("Comparing Supplier to Replica...") + missing = False +- for dn in master_dns: ++ for dn in supplier_dns: + mresult = ldif_search(MLDIF, dn) + if mresult['entry'] is None and mresult['conflict'] is None and not mresult['tombstone']: + # Try from the beginning +@@ -736,7 +736,7 @@ def do_offline_report(opts, output_file=None): + rresult['conflict'] is not None or rresult['tombstone']): + """ We can safely remove this DN from the replica dn list as it + does not need to be checked again. This also speeds things up +- when doing the replica vs master phase. ++ when doing the replica vs Supplier phase. + """ + replica_dns.remove(dn) + +@@ -766,7 +766,7 @@ def do_offline_report(opts, output_file=None): + missing_report += (' Entries missing on Replica:\n') + missing = True + if mresult['entry'] and 'createtimestamp' in mresult['entry'].data: +- missing_report += (' - %s (Created on Master at: %s)\n' % ++ missing_report += (' - %s (Created on Supplier at: %s)\n' % + (dn, convert_timestamp(mresult['entry'].data['createtimestamp'][0]))) + else: + missing_report += (' - %s\n' % dn) +@@ -791,7 +791,7 @@ def do_offline_report(opts, output_file=None): + remaining conflict & tombstone entries as well. + """ + if opts['verbose']: +- print ("Comparing Replica to Master...") ++ print ("Comparing Replica to Supplier...") + MLDIF.seek(0) + RLDIF.seek(0) + missing = False +@@ -811,7 +811,7 @@ def do_offline_report(opts, output_file=None): + if mresult['entry'] is None and mresult['glue'] is None: + MLDIF.seek(rresult['idx']) # Set the LDIF cursor/index to the last good line + if not missing: +- missing_report += (' Entries missing on Master:\n') ++ missing_report += (' Entries missing on Supplier:\n') + missing = True + if rresult['entry'] and 'createtimestamp' in rresult['entry'].data: + missing_report += (' - %s (Created on Replica at: %s)\n' % +@@ -837,12 +837,12 @@ def do_offline_report(opts, output_file=None): + final_report += get_ruv_report(opts) + final_report += ('Entry Counts\n') + final_report += ('=====================================================\n\n') +- final_report += ('Master: %d\n' % (m_count)) ++ final_report += ('Supplier: %d\n' % (m_count)) + final_report += ('Replica: %d\n\n' % (r_count)) + + final_report += ('\nTombstones\n') + final_report += ('=====================================================\n\n') +- final_report += ('Master: %d\n' % (mtombstones)) ++ final_report += ('Supplier: %d\n' % (mtombstones)) + final_report += ('Replica: %d\n' % (rtombstones)) + + final_report += get_conflict_report(mconflicts, rconflicts, opts['conflicts']) +@@ -859,9 +859,9 @@ def do_offline_report(opts, output_file=None): + final_report += ('\nResult\n') + final_report += ('=====================================================\n\n') + if missing_report == "" and len(diff_report) == 0: +- final_report += ('No replication differences between Master and Replica\n') ++ final_report += ('No replication differences between Supplier and Replica\n') + else: +- final_report += ('There are replication differences between Master and Replica\n') ++ final_report += ('There are replication differences between Supplier and Replica\n') + + if output_file: + output_file.write(final_report) +@@ -871,8 +871,8 @@ def do_offline_report(opts, output_file=None): + + def check_for_diffs(mentries, mglue, rentries, rglue, report, opts): + """Online mode only - Check for diffs, return the updated report +- :param mentries - Master entries +- :param mglue - Master glue entries ++ :param mentries - Supplier entries ++ :param mglue - Supplier glue entries + :param rentries - Replica entries + :param rglue - Replica glue entries + :param report - A Dict of the entire report +@@ -947,8 +947,8 @@ def validate_suffix(ldapnode, suffix, hostname): + # Check suffix is replicated + try: + replica_filter = "(&(objectclass=nsds5replica)(nsDS5ReplicaRoot=%s))" % suffix +- master_replica = ldapnode.search_s("cn=config",ldap.SCOPE_SUBTREE,replica_filter) +- if (len(master_replica) != 1): ++ supplier_replica = ldapnode.search_s("cn=config",ldap.SCOPE_SUBTREE,replica_filter) ++ if (len(supplier_replica) != 1): + print("Error: Failed to validate suffix in {}. {} is not replicated.".format(hostname, suffix)) + return False + except ldap.LDAPError as e: +@@ -969,7 +969,7 @@ def connect_to_replicas(opts): + muri = "%s://%s" % (opts['mprotocol'], opts['mhost'].replace("/", "%2f")) + else: + muri = "%s://%s:%s/" % (opts['mprotocol'], opts['mhost'], opts['mport']) +- master = SimpleLDAPObject(muri) ++ supplier = SimpleLDAPObject(muri) + + if opts['rprotocol'].lower() == 'ldapi': + ruri = "%s://%s" % (opts['rprotocol'], opts['rhost'].replace("/", "%2f")) +@@ -978,23 +978,23 @@ def connect_to_replicas(opts): + replica = SimpleLDAPObject(ruri) + + # Set timeouts +- master.set_option(ldap.OPT_NETWORK_TIMEOUT, opts['timeout']) +- master.set_option(ldap.OPT_TIMEOUT, opts['timeout']) ++ supplier.set_option(ldap.OPT_NETWORK_TIMEOUT, opts['timeout']) ++ supplier.set_option(ldap.OPT_TIMEOUT, opts['timeout']) + replica.set_option(ldap.OPT_NETWORK_TIMEOUT, opts['timeout']) + replica.set_option(ldap.OPT_TIMEOUT, opts['timeout']) + + # Setup Secure Connection + if opts['certdir'] is not None: +- # Setup Master ++ # Setup Supplier + if opts['mprotocol'] != LDAPI: +- master.set_option(ldap.OPT_X_TLS_CACERTDIR, opts['certdir']) +- master.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_HARD) ++ supplier.set_option(ldap.OPT_X_TLS_CACERTDIR, opts['certdir']) ++ supplier.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_HARD) + if opts['mprotocol'] == LDAP: + # Do StartTLS + try: +- master.start_tls_s() ++ supplier.start_tls_s() + except ldap.LDAPError as e: +- print('TLS negotiation failed on Master: {}'.format(str(e))) ++ print('TLS negotiation failed on Supplier: {}'.format(str(e))) + exit(1) + + # Setup Replica +@@ -1006,17 +1006,17 @@ def connect_to_replicas(opts): + try: + replica.start_tls_s() + except ldap.LDAPError as e: +- print('TLS negotiation failed on Master: {}'.format(str(e))) ++ print('TLS negotiation failed on Supplier: {}'.format(str(e))) + exit(1) + +- # Open connection to master ++ # Open connection to Supplier + try: +- master.simple_bind_s(opts['binddn'], opts['bindpw']) ++ supplier.simple_bind_s(opts['binddn'], opts['bindpw']) + except ldap.SERVER_DOWN as e: + print(f"Cannot connect to {muri} ({str(e)})") + sys.exit(1) + except ldap.LDAPError as e: +- print("Error: Failed to authenticate to Master: ({}). " ++ print("Error: Failed to authenticate to Supplier: ({}). " + "Please check your credentials and LDAP urls are correct.".format(str(e))) + sys.exit(1) + +@@ -1034,7 +1034,7 @@ def connect_to_replicas(opts): + # Validate suffix + if opts['verbose']: + print ("Validating suffix ...") +- if not validate_suffix(master, opts['suffix'], opts['mhost']): ++ if not validate_suffix(supplier, opts['suffix'], opts['mhost']): + sys.exit(1) + + if not validate_suffix(replica,opts['suffix'], opts['rhost']): +@@ -1042,16 +1042,16 @@ def connect_to_replicas(opts): + + # Get the RUVs + if opts['verbose']: +- print ("Gathering Master's RUV...") ++ print ("Gathering Supplier's RUV...") + try: +- master_ruv = master.search_s(opts['suffix'], ldap.SCOPE_SUBTREE, RUV_FILTER, ['nsds50ruv']) +- if len(master_ruv) > 0: +- opts['master_ruv'] = ensure_list_str(master_ruv[0][1]['nsds50ruv']) ++ supplier_ruv = supplier.search_s(opts['suffix'], ldap.SCOPE_SUBTREE, RUV_FILTER, ['nsds50ruv']) ++ if len(supplier_ruv) > 0: ++ opts['supplier_ruv'] = ensure_list_str(supplier_ruv[0][1]['nsds50ruv']) + else: +- print("Error: Master does not have an RUV entry") ++ print("Error: Supplier does not have an RUV entry") + sys.exit(1) + except ldap.LDAPError as e: +- print("Error: Failed to get Master RUV entry: {}".format(str(e))) ++ print("Error: Failed to get Supplier RUV entry: {}".format(str(e))) + sys.exit(1) + + if opts['verbose']: +@@ -1067,12 +1067,12 @@ def connect_to_replicas(opts): + print("Error: Failed to get Replica RUV entry: {}".format(str(e))) + sys.exit(1) + +- # Get the master RID ++ # Get the Supplier RID + if opts['verbose']: +- print("Getting Master's replica ID") ++ print("Getting Supplier's replica ID") + try: + search_filter = "(&(objectclass=nsds5Replica)(nsDS5ReplicaRoot={})(nsDS5ReplicaId=*))".format(opts['suffix']) +- replica_entry = master.search_s("cn=config", ldap.SCOPE_SUBTREE, search_filter) ++ replica_entry = supplier.search_s("cn=config", ldap.SCOPE_SUBTREE, search_filter) + if len(replica_entry) > 0: + opts['rid'] = ensure_int(replica_entry[0][1]['nsDS5ReplicaId'][0]) + else: +@@ -1081,7 +1081,7 @@ def connect_to_replicas(opts): + print("Error: Failed to get Replica entry: {}".format(str(e))) + sys.exit(1) + +- return (master, replica, opts) ++ return (supplier, replica, opts) + + + def print_online_report(report, opts, output_file): +@@ -1104,11 +1104,11 @@ def print_online_report(report, opts, output_file): + final_report += get_ruv_report(opts) + final_report += ('Entry Counts\n') + final_report += ('=====================================================\n\n') +- final_report += ('Master: %d\n' % (report['m_count'])) ++ final_report += ('Supplier: %d\n' % (report['m_count'])) + final_report += ('Replica: %d\n\n' % (report['r_count'])) + final_report += ('\nTombstones\n') + final_report += ('=====================================================\n\n') +- final_report += ('Master: %d\n' % (report['mtombstones'])) ++ final_report += ('Supplier: %d\n' % (report['mtombstones'])) + final_report += ('Replica: %d\n' % (report['rtombstones'])) + final_report += report['conflict'] + missing = False +@@ -1121,7 +1121,7 @@ def print_online_report(report, opts, output_file): + final_report += (' Entries missing on Replica:\n') + for entry in report['r_missing']: + if 'createtimestamp' in entry.data: +- final_report += (' - %s (Created on Master at: %s)\n' % ++ final_report += (' - %s (Created on Supplier at: %s)\n' % + (entry.dn, convert_timestamp(entry.data['createtimestamp'][0]))) + else: + final_report += (' - %s\n' % (entry.dn)) +@@ -1129,7 +1129,7 @@ def print_online_report(report, opts, output_file): + if m_missing > 0: + if r_missing > 0: + final_report += ('\n') +- final_report += (' Entries missing on Master:\n') ++ final_report += (' Entries missing on Supplier:\n') + for entry in report['m_missing']: + if 'createtimestamp' in entry.data: + final_report += (' - %s (Created on Replica at: %s)\n' % +@@ -1146,9 +1146,9 @@ def print_online_report(report, opts, output_file): + final_report += ('\nResult\n') + final_report += ('=====================================================\n\n') + if not missing and len(report['diff']) == 0: +- final_report += ('No replication differences between Master and Replica\n') ++ final_report += ('No replication differences between Supplier and Replica\n') + else: +- final_report += ('There are replication differences between Master and Replica\n') ++ final_report += ('There are replication differences between Supplier and Replica\n') + + if output_file: + output_file.write(final_report) +@@ -1170,7 +1170,7 @@ def remove_state_info(entry): + + def get_conflict_report(mentries, rentries, verbose): + """Gather the conflict entry dn's for each replica +- :param mentries - Master entries ++ :param mentries - Supplier entries + :param rentries - Replica entries + :param verbose - verbose logging + :return - A text blob to dispaly in the report +@@ -1197,7 +1197,7 @@ def get_conflict_report(mentries, rentries, verbose): + report = "\n\nConflict Entries\n" + report += "=====================================================\n\n" + if len(m_conflicts) > 0: +- report += ('Master Conflict Entries: %d\n' % (len(m_conflicts))) ++ report += ('Supplier Conflict Entries: %d\n' % (len(m_conflicts))) + if verbose: + for entry in m_conflicts: + report += ('\n - %s\n' % (entry['dn'])) +@@ -1239,8 +1239,8 @@ def do_online_report(opts, output_file=None): + rconflicts = [] + mconflicts = [] + +- # Fire off paged searches on Master and Replica +- master, replica, opts = connect_to_replicas(opts) ++ # Fire off paged searches on Supplier and Replica ++ supplier, replica, opts = connect_to_replicas(opts) + + if opts['verbose']: + print('Start searching and comparing...') +@@ -1248,12 +1248,12 @@ def do_online_report(opts, output_file=None): + controls = [paged_ctrl] + req_pr_ctrl = controls[0] + try: +- master_msgid = master.search_ext(opts['suffix'], ldap.SCOPE_SUBTREE, +- "(|(objectclass=*)(objectclass=ldapsubentry)(objectclass=nstombstone))", +- ['*', 'createtimestamp', 'nscpentrywsi', 'nsds5replconflict'], +- serverctrls=controls) ++ supplier_msgid = supplier.search_ext(opts['suffix'], ldap.SCOPE_SUBTREE, ++ "(|(objectclass=*)(objectclass=ldapsubentry)(objectclass=nstombstone))", ++ ['*', 'createtimestamp', 'nscpentrywsi', 'nsds5replconflict'], ++ serverctrls=controls) + except ldap.LDAPError as e: +- print("Error: Failed to get Master entries: %s", str(e)) ++ print("Error: Failed to get Supplier entries: %s", str(e)) + sys.exit(1) + try: + replica_msgid = replica.search_ext(opts['suffix'], ldap.SCOPE_SUBTREE, +@@ -1268,11 +1268,11 @@ def do_online_report(opts, output_file=None): + while not m_done or not r_done: + try: + if not m_done: +- m_rtype, m_rdata, m_rmsgid, m_rctrls = master.result3(master_msgid) ++ m_rtype, m_rdata, m_rmsgid, m_rctrls = supplier.result3(supplier_msgid) + elif not r_done: + m_rdata = [] + except ldap.LDAPError as e: +- print("Error: Problem getting the results from the master: %s", str(e)) ++ print("Error: Problem getting the results from the Supplier: %s", str(e)) + sys.exit(1) + try: + if not r_done: +@@ -1299,7 +1299,7 @@ def do_online_report(opts, output_file=None): + report, opts) + + if not m_done: +- # Master ++ # Supplier + m_pctrls = [ + c + for c in m_rctrls +@@ -1310,11 +1310,11 @@ def do_online_report(opts, output_file=None): + try: + # Copy cookie from response control to request control + req_pr_ctrl.cookie = m_pctrls[0].cookie +- master_msgid = master.search_ext(opts['suffix'], ldap.SCOPE_SUBTREE, ++ supplier_msgid = supplier.search_ext(opts['suffix'], ldap.SCOPE_SUBTREE, + "(|(objectclass=*)(objectclass=ldapsubentry))", + ['*', 'createtimestamp', 'nscpentrywsi', 'conflictcsn', 'nsds5replconflict'], serverctrls=controls) + except ldap.LDAPError as e: +- print("Error: Problem searching the master: %s", str(e)) ++ print("Error: Problem searching the Supplier: %s", str(e)) + sys.exit(1) + else: + m_done = True # No more pages available +@@ -1354,7 +1354,7 @@ def do_online_report(opts, output_file=None): + print_online_report(report, opts, output_file) + + # unbind +- master.unbind_s() ++ supplier.unbind_s() + replica.unbind_s() + + +@@ -1367,18 +1367,18 @@ def init_online_params(args): + + # Make sure the URLs are different + if args.murl == args.rurl: +- print("Master and Replica LDAP URLs are the same, they must be different") ++ print("Supplier and Replica LDAP URLs are the same, they must be different") + sys.exit(1) + +- # Parse Master url ++ # Parse Supplier url + if not ldapurl.isLDAPUrl(args.murl): +- print("Master LDAP URL is invalid") ++ print("Supplier LDAP URL is invalid") + sys.exit(1) + murl = ldapurl.LDAPUrl(args.murl) + if murl.urlscheme in VALID_PROTOCOLS: + opts['mprotocol'] = murl.urlscheme + else: +- print('Unsupported ldap url protocol (%s) for Master, please use "ldaps" or "ldap"' % ++ print('Unsupported ldap url protocol (%s) for Supplier, please use "ldaps" or "ldap"' % + murl.urlscheme) + sys.exit(1) + +@@ -1520,7 +1520,7 @@ def offline_report(args): + print ("LDIF file ({}) is empty".format(ldif_dir)) + sys.exit(1) + if opts['mldif'] == opts['rldif']: +- print("The Master and Replica LDIF files must be different") ++ print("The Supplier and Replica LDIF files must be different") + sys.exit(1) + + OUTPUT_FILE = None +@@ -1547,7 +1547,7 @@ def get_state(args): + """Just do the RUV comparision + """ + opts = init_online_params(args) +- master, replica, opts = connect_to_replicas(opts) ++ supplier, replica, opts = connect_to_replicas(opts) + print(get_ruv_state(opts)) + + +@@ -1569,10 +1569,10 @@ def main(): + # Get state + state_parser = subparsers.add_parser('state', help="Get the current replicaton state between two replicas") + state_parser.set_defaults(func=get_state) +- state_parser.add_argument('-m', '--master-url', help='The LDAP URL for the Master server', +- dest='murl', default=None, required=True) ++ state_parser.add_argument('-m', '--supplier-url', help='The LDAP URL for the Supplier server', ++ dest='murl', default=None, required=True) + state_parser.add_argument('-r', '--replica-url', help='The LDAP URL for the Replica server', +- dest='rurl', required=True, default=None) ++ dest='rurl', required=True, default=None) + state_parser.add_argument('-b', '--suffix', help='Replicated suffix', dest='suffix', required=True) + state_parser.add_argument('-D', '--bind-dn', help='The Bind DN', required=True, dest='binddn', default=None) + state_parser.add_argument('-w', '--bind-pw', help='The Bind password', dest='bindpw', default=None) +@@ -1586,7 +1586,7 @@ def main(): + # Online mode + online_parser = subparsers.add_parser('online', help="Compare two online replicas for differences") + online_parser.set_defaults(func=online_report) +- online_parser.add_argument('-m', '--master-url', help='The LDAP URL for the Master server (REQUIRED)', ++ online_parser.add_argument('-m', '--supplier-url', help='The LDAP URL for the Supplier server (REQUIRED)', + dest='murl', default=None, required=True) + online_parser.add_argument('-r', '--replica-url', help='The LDAP URL for the Replica server (REQUIRED)', + dest='rurl', required=True, default=None) +@@ -1612,12 +1612,12 @@ def main(): + # Offline LDIF mode + offline_parser = subparsers.add_parser('offline', help="Compare two replication LDIF files for differences (LDIF file generated by 'db2ldif -r')") + offline_parser.set_defaults(func=offline_report) +- offline_parser.add_argument('-m', '--master-ldif', help='Master LDIF file', ++ offline_parser.add_argument('-m', '--supplier-ldif', help='Supplier LDIF file', + dest='mldif', default=None, required=True) + offline_parser.add_argument('-r', '--replica-ldif', help='Replica LDIF file', + dest='rldif', default=None, required=True) + offline_parser.add_argument('--rid', dest='rid', default=None, required=True, +- help='The Replica Identifer (rid) for the "Master" server') ++ help='The Replica Identifier (rid) for the "Supplier" server') + offline_parser.add_argument('-b', '--suffix', help='Replicated suffix', dest='suffix', required=True) + offline_parser.add_argument('-c', '--conflicts', help='Display verbose conflict information', action='store_true', + dest='conflicts', default=False) +-- +2.31.1 + diff --git a/SOURCES/0023-Issue-4443-Internal-unindexed-searches-in-syncrepl-r.patch b/SOURCES/0023-Issue-4443-Internal-unindexed-searches-in-syncrepl-r.patch new file mode 100644 index 0000000..3fd6f16 --- /dev/null +++ b/SOURCES/0023-Issue-4443-Internal-unindexed-searches-in-syncrepl-r.patch @@ -0,0 +1,373 @@ +From 55a47c1bfe1ce1c27e470384c4f1d50895db25f7 Mon Sep 17 00:00:00 2001 +From: Mark Reynolds +Date: Tue, 13 Jul 2021 14:18:03 -0400 +Subject: [PATCH] Issue 4443 - Internal unindexed searches in syncrepl/retro + changelog + +Bug Description: + +When a non-system index is added to a backend it is +disabled until the database is initialized or reindexed. +So in the case of the retro changelog the changenumber index +is alway disabled by default since it is never initialized. +This leads to unexpected unindexed searches of the retro +changelog. + +Fix Description: + +If an index has "nsSystemIndex" set to "true" then enable it +immediately. + +relates: https://github.com/389ds/389-ds-base/issues/4443 + +Reviewed by: spichugi & tbordaz(Thanks!!) +--- + .../tests/suites/retrocl/basic_test.py | 53 ++++++++------- + .../suites/retrocl/retrocl_indexing_test.py | 68 +++++++++++++++++++ + ldap/servers/plugins/retrocl/retrocl_create.c | 2 +- + .../slapd/back-ldbm/ldbm_index_config.c | 25 +++++-- + src/lib389/lib389/_mapped_object.py | 13 ++++ + 5 files changed, 130 insertions(+), 31 deletions(-) + create mode 100644 dirsrvtests/tests/suites/retrocl/retrocl_indexing_test.py + +diff --git a/dirsrvtests/tests/suites/retrocl/basic_test.py b/dirsrvtests/tests/suites/retrocl/basic_test.py +index f3bc50f29..84d513829 100644 +--- a/dirsrvtests/tests/suites/retrocl/basic_test.py ++++ b/dirsrvtests/tests/suites/retrocl/basic_test.py +@@ -8,7 +8,6 @@ + + import logging + import ldap +-import time + import pytest + from lib389.topologies import topology_st + from lib389.plugins import RetroChangelogPlugin +@@ -18,7 +17,8 @@ from lib389.tasks import * + from lib389.cli_base import FakeArgs, connect_instance, disconnect_instance + from lib389.cli_base.dsrc import dsrc_arg_concat + from lib389.cli_conf.plugins.retrochangelog import retrochangelog_add_attr +-from lib389.idm.user import UserAccount, UserAccounts, nsUserAccounts ++from lib389.idm.user import UserAccount, UserAccounts ++from lib389._mapped_object import DSLdapObjects + + pytestmark = pytest.mark.tier1 + +@@ -82,7 +82,7 @@ def test_retrocl_exclude_attr_add(topology_st): + + log.info('Adding user1') + try: +- user1 = users.create(properties={ ++ users.create(properties={ + 'sn': '1', + 'cn': 'user 1', + 'uid': 'user1', +@@ -97,17 +97,18 @@ def test_retrocl_exclude_attr_add(topology_st): + except ldap.ALREADY_EXISTS: + pass + except ldap.LDAPError as e: +- log.error("Failed to add user1") ++ log.error("Failed to add user1: " + str(e)) + + log.info('Verify homePhone and carLicense attrs are in the changelog changestring') + try: +- cllist = st.search_s(RETROCL_SUFFIX, ldap.SCOPE_SUBTREE, '(targetDn=%s)' % USER1_DN) ++ retro_changelog_suffix = DSLdapObjects(st, basedn=RETROCL_SUFFIX) ++ cllist = retro_changelog_suffix.filter(f'(targetDn={USER1_DN})') + except ldap.LDAPError as e: +- log.fatal("Changelog search failed, error: " +str(e)) ++ log.fatal("Changelog search failed, error: " + str(e)) + assert False + assert len(cllist) > 0 +- if cllist[0].hasAttr('changes'): +- clstr = (cllist[0].getValue('changes')).decode() ++ if cllist[0].present('changes'): ++ clstr = str(cllist[0].get_attr_vals_utf8('changes')) + assert ATTR_HOMEPHONE in clstr + assert ATTR_CARLICENSE in clstr + +@@ -134,7 +135,7 @@ def test_retrocl_exclude_attr_add(topology_st): + + log.info('Adding user2') + try: +- user2 = users.create(properties={ ++ users.create(properties={ + 'sn': '2', + 'cn': 'user 2', + 'uid': 'user2', +@@ -149,18 +150,18 @@ def test_retrocl_exclude_attr_add(topology_st): + except ldap.ALREADY_EXISTS: + pass + except ldap.LDAPError as e: +- log.error("Failed to add user2") ++ log.error("Failed to add user2: " + str(e)) + + log.info('Verify homePhone attr is not in the changelog changestring') + try: +- cllist = st.search_s(RETROCL_SUFFIX, ldap.SCOPE_SUBTREE, '(targetDn=%s)' % USER2_DN) ++ cllist = retro_changelog_suffix.filter(f'(targetDn={USER2_DN})') + assert len(cllist) > 0 +- if cllist[0].hasAttr('changes'): +- clstr = (cllist[0].getValue('changes')).decode() ++ if cllist[0].present('changes'): ++ clstr = str(cllist[0].get_attr_vals_utf8('changes')) + assert ATTR_HOMEPHONE not in clstr + assert ATTR_CARLICENSE in clstr + except ldap.LDAPError as e: +- log.fatal("Changelog search failed, error: " +str(e)) ++ log.fatal("Changelog search failed, error: " + str(e)) + assert False + + def test_retrocl_exclude_attr_mod(topology_st): +@@ -228,19 +229,20 @@ def test_retrocl_exclude_attr_mod(topology_st): + 'homeDirectory': '/home/user1', + 'userpassword': USER_PW}) + except ldap.ALREADY_EXISTS: +- pass ++ user1 = UserAccount(st, dn=USER1_DN) + except ldap.LDAPError as e: +- log.error("Failed to add user1") ++ log.error("Failed to add user1: " + str(e)) + + log.info('Verify homePhone and carLicense attrs are in the changelog changestring') + try: +- cllist = st.search_s(RETROCL_SUFFIX, ldap.SCOPE_SUBTREE, '(targetDn=%s)' % USER1_DN) ++ retro_changelog_suffix = DSLdapObjects(st, basedn=RETROCL_SUFFIX) ++ cllist = retro_changelog_suffix.filter(f'(targetDn={USER1_DN})') + except ldap.LDAPError as e: +- log.fatal("Changelog search failed, error: " +str(e)) ++ log.fatal("Changelog search failed, error: " + str(e)) + assert False + assert len(cllist) > 0 +- if cllist[0].hasAttr('changes'): +- clstr = (cllist[0].getValue('changes')).decode() ++ if cllist[0].present('changes'): ++ clstr = str(cllist[0].get_attr_vals_utf8('changes')) + assert ATTR_HOMEPHONE in clstr + assert ATTR_CARLICENSE in clstr + +@@ -267,24 +269,25 @@ def test_retrocl_exclude_attr_mod(topology_st): + + log.info('Modify user1 carLicense attribute') + try: +- st.modify_s(USER1_DN, [(ldap.MOD_REPLACE, ATTR_CARLICENSE, b"123WX321")]) ++ user1.replace(ATTR_CARLICENSE, "123WX321") + except ldap.LDAPError as e: + log.fatal('test_retrocl_exclude_attr_mod: Failed to update user1 attribute: error ' + e.message['desc']) + assert False + + log.info('Verify carLicense attr is not in the changelog changestring') + try: +- cllist = st.search_s(RETROCL_SUFFIX, ldap.SCOPE_SUBTREE, '(targetDn=%s)' % USER1_DN) ++ cllist = retro_changelog_suffix.filter(f'(targetDn={USER1_DN})') + assert len(cllist) > 0 + # There will be 2 entries in the changelog for this user, we are only + #interested in the second one, the modify operation. +- if cllist[1].hasAttr('changes'): +- clstr = (cllist[1].getValue('changes')).decode() ++ if cllist[1].present('changes'): ++ clstr = str(cllist[1].get_attr_vals_utf8('changes')) + assert ATTR_CARLICENSE not in clstr + except ldap.LDAPError as e: +- log.fatal("Changelog search failed, error: " +str(e)) ++ log.fatal("Changelog search failed, error: " + str(e)) + assert False + ++ + if __name__ == '__main__': + # Run isolated + # -s for DEBUG mode +diff --git a/dirsrvtests/tests/suites/retrocl/retrocl_indexing_test.py b/dirsrvtests/tests/suites/retrocl/retrocl_indexing_test.py +new file mode 100644 +index 000000000..b1dfe962c +--- /dev/null ++++ b/dirsrvtests/tests/suites/retrocl/retrocl_indexing_test.py +@@ -0,0 +1,68 @@ ++import logging ++import pytest ++import os ++from lib389._constants import RETROCL_SUFFIX, DEFAULT_SUFFIX ++from lib389.topologies import topology_st as topo ++from lib389.plugins import RetroChangelogPlugin ++from lib389.idm.user import UserAccounts ++from lib389._mapped_object import DSLdapObjects ++log = logging.getLogger(__name__) ++ ++ ++def test_indexing_is_online(topo): ++ """Test that the changenmumber index is online right after enabling the plugin ++ ++ :id: 16f4c001-9e0c-4448-a2b3-08ac1e85d40f ++ :setup: Standalone Instance ++ :steps: ++ 1. Enable retro cl ++ 2. Perform some updates ++ 3. Search for "(changenumber>=-1)", and it is not partially unindexed ++ 4. Search for "(&(changenumber>=-1)(targetuniqueid=*))", and it is not partially unindexed ++ :expectedresults: ++ 1. Success ++ 2. Success ++ 3. Success ++ 4. Success ++ """ ++ ++ # Enable plugin ++ topo.standalone.config.set('nsslapd-accesslog-logbuffering', 'off') ++ plugin = RetroChangelogPlugin(topo.standalone) ++ plugin.enable() ++ topo.standalone.restart() ++ ++ # Do a bunch of updates ++ users = UserAccounts(topo.standalone, DEFAULT_SUFFIX) ++ user_entry = users.create(properties={ ++ 'sn': '1', ++ 'cn': 'user 1', ++ 'uid': 'user1', ++ 'uidNumber': '11', ++ 'gidNumber': '111', ++ 'givenname': 'user1', ++ 'homePhone': '0861234567', ++ 'carLicense': '131D16674', ++ 'mail': 'user1@whereever.com', ++ 'homeDirectory': '/home' ++ }) ++ for count in range(0, 10): ++ user_entry.replace('mail', f'test{count}@test.com') ++ ++ # Search the retro cl, and check for error messages ++ filter_simple = '(changenumber>=-1)' ++ filter_compound = '(&(changenumber>=-1)(targetuniqueid=*))' ++ retro_changelog_suffix = DSLdapObjects(topo.standalone, basedn=RETROCL_SUFFIX) ++ retro_changelog_suffix.filter(filter_simple) ++ assert not topo.standalone.searchAccessLog('Partially Unindexed Filter') ++ ++ # Search the retro cl again with compound filter ++ retro_changelog_suffix.filter(filter_compound) ++ assert not topo.standalone.searchAccessLog('Partially Unindexed Filter') ++ ++ ++if __name__ == '__main__': ++ # Run isolated ++ # -s for DEBUG mode ++ CURRENT_FILE = os.path.realpath(__file__) ++ pytest.main(["-s", CURRENT_FILE]) +diff --git a/ldap/servers/plugins/retrocl/retrocl_create.c b/ldap/servers/plugins/retrocl/retrocl_create.c +index 571e6899f..5bfde7831 100644 +--- a/ldap/servers/plugins/retrocl/retrocl_create.c ++++ b/ldap/servers/plugins/retrocl/retrocl_create.c +@@ -133,7 +133,7 @@ retrocl_create_be(const char *bedir) + val.bv_len = strlen(val.bv_val); + slapi_entry_add_values(e, "cn", vals); + +- val.bv_val = "false"; ++ val.bv_val = "true"; /* enables the index */ + val.bv_len = strlen(val.bv_val); + slapi_entry_add_values(e, "nssystemindex", vals); + +diff --git a/ldap/servers/slapd/back-ldbm/ldbm_index_config.c b/ldap/servers/slapd/back-ldbm/ldbm_index_config.c +index 9722d0ce7..38e7368e1 100644 +--- a/ldap/servers/slapd/back-ldbm/ldbm_index_config.c ++++ b/ldap/servers/slapd/back-ldbm/ldbm_index_config.c +@@ -25,7 +25,7 @@ int ldbm_instance_index_config_delete_callback(Slapi_PBlock *pb, Slapi_Entry *en + #define INDEXTYPE_NONE 1 + + static int +-ldbm_index_parse_entry(ldbm_instance *inst, Slapi_Entry *e, const char *trace_string, char **index_name, char *err_buf) ++ldbm_index_parse_entry(ldbm_instance *inst, Slapi_Entry *e, const char *trace_string, char **index_name, PRBool *is_system_index, char *err_buf) + { + Slapi_Attr *attr; + const struct berval *attrValue; +@@ -78,6 +78,15 @@ ldbm_index_parse_entry(ldbm_instance *inst, Slapi_Entry *e, const char *trace_st + } + } + ++ *is_system_index = PR_FALSE; ++ if (0 == slapi_entry_attr_find(e, "nsSystemIndex", &attr)) { ++ slapi_attr_first_value(attr, &sval); ++ attrValue = slapi_value_get_berval(sval); ++ if (strcasecmp(attrValue->bv_val, "true") == 0) { ++ *is_system_index = PR_TRUE; ++ } ++ } ++ + /* ok the entry is good to process, pass it to attr_index_config */ + if (attr_index_config(inst->inst_be, (char *)trace_string, 0, e, 0, 0, err_buf)) { + slapi_ch_free_string(index_name); +@@ -101,9 +110,10 @@ ldbm_index_init_entry_callback(Slapi_PBlock *pb __attribute__((unused)), + void *arg) + { + ldbm_instance *inst = (ldbm_instance *)arg; ++ PRBool is_system_index = PR_FALSE; + + returntext[0] = '\0'; +- *returncode = ldbm_index_parse_entry(inst, e, "from ldbm instance init", NULL, NULL); ++ *returncode = ldbm_index_parse_entry(inst, e, "from ldbm instance init", NULL, &is_system_index /* not used */, NULL); + if (*returncode == LDAP_SUCCESS) { + return SLAPI_DSE_CALLBACK_OK; + } else { +@@ -126,17 +136,21 @@ ldbm_instance_index_config_add_callback(Slapi_PBlock *pb __attribute__((unused)) + { + ldbm_instance *inst = (ldbm_instance *)arg; + char *index_name = NULL; ++ PRBool is_system_index = PR_FALSE; + + returntext[0] = '\0'; +- *returncode = ldbm_index_parse_entry(inst, e, "from DSE add", &index_name, returntext); ++ *returncode = ldbm_index_parse_entry(inst, e, "from DSE add", &index_name, &is_system_index, returntext); + if (*returncode == LDAP_SUCCESS) { + struct attrinfo *ai = NULL; + /* if the index is a "system" index, we assume it's being added by + * by the server, and it's okay for the index to go online immediately. + * if not, we set the index "offline" so it won't actually be used + * until someone runs db2index on it. ++ * If caller wants to add an index that they want to be online ++ * immediately they can also set "nsSystemIndex" to "true" in the ++ * index config entry (e.g. is_system_index). + */ +- if (!ldbm_attribute_always_indexed(index_name)) { ++ if (!is_system_index && !ldbm_attribute_always_indexed(index_name)) { + ainfo_get(inst->inst_be, index_name, &ai); + PR_ASSERT(ai != NULL); + ai->ai_indexmask |= INDEX_OFFLINE; +@@ -386,13 +400,14 @@ ldbm_instance_index_config_enable_index(ldbm_instance *inst, Slapi_Entry *e) + char *index_name = NULL; + int rc = LDAP_SUCCESS; + struct attrinfo *ai = NULL; ++ PRBool is_system_index = PR_FALSE; + + index_name = slapi_entry_attr_get_charptr(e, "cn"); + if (index_name) { + ainfo_get(inst->inst_be, index_name, &ai); + } + if (!ai) { +- rc = ldbm_index_parse_entry(inst, e, "from DSE add", &index_name, NULL); ++ rc = ldbm_index_parse_entry(inst, e, "from DSE add", &index_name, &is_system_index /* not used */, NULL); + } + if (rc == LDAP_SUCCESS) { + /* Assume the caller knows if it is OK to go online immediately */ +diff --git a/src/lib389/lib389/_mapped_object.py b/src/lib389/lib389/_mapped_object.py +index b6d778b01..fe610d175 100644 +--- a/src/lib389/lib389/_mapped_object.py ++++ b/src/lib389/lib389/_mapped_object.py +@@ -148,6 +148,19 @@ class DSLdapObject(DSLogging, DSLint): + + return True + ++ def search(self, scope="subtree", filter='objectclass=*'): ++ search_scope = ldap.SCOPE_SUBTREE ++ if scope == 'base': ++ search_scope = ldap.SCOPE_BASE ++ elif scope == 'one': ++ search_scope = ldap.SCOPE_ONE ++ elif scope == 'subtree': ++ search_scope = ldap.SCOPE_SUBTREE ++ return self._instance.search_ext_s(self._dn, search_scope, filter, ++ serverctrls=self._server_controls, ++ clientctrls=self._client_controls, ++ escapehatch='i am sure') ++ + def display(self, attrlist=['*']): + """Get an entry but represent it as a string LDIF + +-- +2.31.1 + diff --git a/SOURCES/0024-Issue-4817-BUG-locked-crypt-accounts-on-import-may-a.patch b/SOURCES/0024-Issue-4817-BUG-locked-crypt-accounts-on-import-may-a.patch new file mode 100644 index 0000000..32c0eb1 --- /dev/null +++ b/SOURCES/0024-Issue-4817-BUG-locked-crypt-accounts-on-import-may-a.patch @@ -0,0 +1,121 @@ +From 2f0218f91d35c83a2aaecb71849a54b2481390ab Mon Sep 17 00:00:00 2001 +From: Firstyear +Date: Fri, 9 Jul 2021 11:53:35 +1000 +Subject: [PATCH] Issue 4817 - BUG - locked crypt accounts on import may allow + all passwords (#4819) + +Bug Description: Due to mishanding of short dbpwd hashes, the +crypt_r algorithm was misused and was only comparing salts +in some cases, rather than checking the actual content +of the password. + +Fix Description: Stricter checks on dbpwd lengths to ensure +that content passed to crypt_r has at least 2 salt bytes and +1 hash byte, as well as stricter checks on ct_memcmp to ensure +that compared values are the same length, rather than potentially +allowing overruns/short comparisons. + +fixes: https://github.com/389ds/389-ds-base/issues/4817 + +Author: William Brown + +Review by: @mreynolds389 +--- + .../password/pwd_crypt_asterisk_test.py | 50 +++++++++++++++++++ + ldap/servers/plugins/pwdstorage/crypt_pwd.c | 20 +++++--- + 2 files changed, 64 insertions(+), 6 deletions(-) + create mode 100644 dirsrvtests/tests/suites/password/pwd_crypt_asterisk_test.py + +diff --git a/dirsrvtests/tests/suites/password/pwd_crypt_asterisk_test.py b/dirsrvtests/tests/suites/password/pwd_crypt_asterisk_test.py +new file mode 100644 +index 000000000..d76614db1 +--- /dev/null ++++ b/dirsrvtests/tests/suites/password/pwd_crypt_asterisk_test.py +@@ -0,0 +1,50 @@ ++# --- BEGIN COPYRIGHT BLOCK --- ++# Copyright (C) 2021 William Brown ++# All rights reserved. ++# ++# License: GPL (version 3 or any later version). ++# See LICENSE for details. ++# --- END COPYRIGHT BLOCK --- ++# ++import ldap ++import pytest ++from lib389.topologies import topology_st ++from lib389.idm.user import UserAccounts ++from lib389._constants import (DEFAULT_SUFFIX, PASSWORD) ++ ++pytestmark = pytest.mark.tier1 ++ ++def test_password_crypt_asterisk_is_rejected(topology_st): ++ """It was reported that {CRYPT}* was allowing all passwords to be ++ valid in the bind process. This checks that we should be rejecting ++ these as they should represent locked accounts. Similar, {CRYPT}! ++ ++ :id: 0b8f1a6a-f3eb-4443-985e-da14d0939dc3 ++ :setup: Single instance ++ :steps: 1. Set a password hash in with CRYPT and the content * ++ 2. Test a bind ++ 3. Set a password hash in with CRYPT and the content ! ++ 4. Test a bind ++ :expectedresults: ++ 1. Successfully set the values ++ 2. The bind fails ++ 3. Successfully set the values ++ 4. The bind fails ++ """ ++ topology_st.standalone.config.set('nsslapd-allow-hashed-passwords', 'on') ++ topology_st.standalone.config.set('nsslapd-enable-upgrade-hash', 'off') ++ ++ users = UserAccounts(topology_st.standalone, DEFAULT_SUFFIX) ++ user = users.create_test_user() ++ ++ user.set('userPassword', "{CRYPT}*") ++ ++ # Attempt to bind with incorrect password. ++ with pytest.raises(ldap.INVALID_CREDENTIALS): ++ badconn = user.bind('badpassword') ++ ++ user.set('userPassword', "{CRYPT}!") ++ # Attempt to bind with incorrect password. ++ with pytest.raises(ldap.INVALID_CREDENTIALS): ++ badconn = user.bind('badpassword') ++ +diff --git a/ldap/servers/plugins/pwdstorage/crypt_pwd.c b/ldap/servers/plugins/pwdstorage/crypt_pwd.c +index 9031b2199..1b37d41ed 100644 +--- a/ldap/servers/plugins/pwdstorage/crypt_pwd.c ++++ b/ldap/servers/plugins/pwdstorage/crypt_pwd.c +@@ -48,15 +48,23 @@ static unsigned char itoa64[] = /* 0 ... 63 => ascii - 64 */ + int + crypt_pw_cmp(const char *userpwd, const char *dbpwd) + { +- int rc; +- char *cp; ++ int rc = -1; ++ char *cp = NULL; ++ size_t dbpwd_len = strlen(dbpwd); + struct crypt_data data; + data.initialized = 0; + +- /* we use salt (first 2 chars) of encoded password in call to crypt_r() */ +- cp = crypt_r(userpwd, dbpwd, &data); +- if (cp) { +- rc = slapi_ct_memcmp(dbpwd, cp, strlen(dbpwd)); ++ /* ++ * there MUST be at least 2 chars of salt and some pw bytes, else this is INVALID and will ++ * allow any password to bind as we then only compare SALTS. ++ */ ++ if (dbpwd_len >= 3) { ++ /* we use salt (first 2 chars) of encoded password in call to crypt_r() */ ++ cp = crypt_r(userpwd, dbpwd, &data); ++ } ++ /* If these are not the same length, we can not proceed safely with memcmp. */ ++ if (cp && dbpwd_len == strlen(cp)) { ++ rc = slapi_ct_memcmp(dbpwd, cp, dbpwd_len); + } else { + rc = -1; + } +-- +2.31.1 + diff --git a/SOURCES/0025-Issue-4837-persistent-search-returns-entries-even-wh.patch b/SOURCES/0025-Issue-4837-persistent-search-returns-entries-even-wh.patch new file mode 100644 index 0000000..66643a1 --- /dev/null +++ b/SOURCES/0025-Issue-4837-persistent-search-returns-entries-even-wh.patch @@ -0,0 +1,39 @@ +From 31d53e7da585723e66b838dcf34b77ea7c9968c6 Mon Sep 17 00:00:00 2001 +From: tbordaz +Date: Wed, 21 Jul 2021 09:16:30 +0200 +Subject: [PATCH] Issue 4837 - persistent search returns entries even when an + error is returned by content-sync-plugin (#4838) + +Bug description: + When a ldap client sends a sync request control, the server response may contain a sync state control. + If the server fails to create the control the search should fail. + +Fix description: + In case the server fails to create the response control + logs the failure of the pre_search + +relates: https://github.com/389ds/389-ds-base/issues/4837 + +Reviewed by: Simon Pichugin + +Platforms tested: RH8.4 +--- + ldap/servers/plugins/sync/sync_refresh.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/ldap/servers/plugins/sync/sync_refresh.c b/ldap/servers/plugins/sync/sync_refresh.c +index 646ff760b..4cbb6a949 100644 +--- a/ldap/servers/plugins/sync/sync_refresh.c ++++ b/ldap/servers/plugins/sync/sync_refresh.c +@@ -213,7 +213,7 @@ sync_srch_refresh_pre_entry(Slapi_PBlock *pb) + Slapi_Entry *e; + slapi_pblock_get(pb, SLAPI_SEARCH_RESULT_ENTRY, &e); + LDAPControl **ctrl = (LDAPControl **)slapi_ch_calloc(2, sizeof(LDAPControl *)); +- sync_create_state_control(e, &ctrl[0], LDAP_SYNC_ADD, NULL); ++ rc = sync_create_state_control(e, &ctrl[0], LDAP_SYNC_ADD, NULL); + slapi_pblock_set(pb, SLAPI_SEARCH_CTRLS, ctrl); + } + return (rc); +-- +2.31.1 + diff --git a/SOURCES/0026-Hardcode-gost-crypt-passsword-storage-scheme.patch b/SOURCES/0026-Hardcode-gost-crypt-passsword-storage-scheme.patch new file mode 100644 index 0000000..aa701a0 --- /dev/null +++ b/SOURCES/0026-Hardcode-gost-crypt-passsword-storage-scheme.patch @@ -0,0 +1,49 @@ +From 616dc9964a4675dea2ab2c2efb9bd31c3903e29d Mon Sep 17 00:00:00 2001 +From: Mark Reynolds +Date: Mon, 26 Jul 2021 15:22:08 -0400 +Subject: [PATCH] Hardcode gost crypt passsword storage scheme + +--- + .../plugins/pwdstorage/gost_yescrypt.c | 22 ------------------- + 1 file changed, 22 deletions(-) + +diff --git a/ldap/servers/plugins/pwdstorage/gost_yescrypt.c b/ldap/servers/plugins/pwdstorage/gost_yescrypt.c +index 67b39395e..7b0d1653c 100644 +--- a/ldap/servers/plugins/pwdstorage/gost_yescrypt.c ++++ b/ldap/servers/plugins/pwdstorage/gost_yescrypt.c +@@ -11,7 +11,6 @@ + + #include + +-#ifdef XCRYPT_VERSION_STR + #include + int + gost_yescrypt_pw_cmp(const char *userpwd, const char *dbpwd) +@@ -64,24 +63,3 @@ gost_yescrypt_pw_enc(const char *pwd) + return enc; + } + +-#else +- +-/* +- * We do not have xcrypt, so always fail all checks. +- */ +-int +-gost_yescrypt_pw_cmp(const char *userpwd __attribute__((unused)), const char *dbpwd __attribute__((unused))) +-{ +- slapi_log_err(SLAPI_LOG_ERR, GOST_YESCRYPT_SCHEME_NAME, +- "Unable to use gost_yescrypt_pw_cmp, xcrypt is not available.\n"); +- return 1; +-} +- +-char * +-gost_yescrypt_pw_enc(const char *pwd __attribute__((unused))) +-{ +- slapi_log_err(SLAPI_LOG_ERR, GOST_YESCRYPT_SCHEME_NAME, +- "Unable to use gost_yescrypt_pw_enc, xcrypt is not available.\n"); +- return NULL; +-} +-#endif +-- +2.31.1 + diff --git a/SOURCES/0027-Issue-4734-import-of-entry-with-no-parent-warning-47.patch b/SOURCES/0027-Issue-4734-import-of-entry-with-no-parent-warning-47.patch new file mode 100644 index 0000000..138ee66 --- /dev/null +++ b/SOURCES/0027-Issue-4734-import-of-entry-with-no-parent-warning-47.patch @@ -0,0 +1,39 @@ +From a2a51130b2f95316237b85da099a8be734969e54 Mon Sep 17 00:00:00 2001 +From: James Chapman +Date: Sat, 24 Apr 2021 21:37:54 +0100 +Subject: [PATCH] Issue 4734 - import of entry with no parent warning (#4735) + +Description: Online import of ldif file that contains an entry with + no parent doesnt generate a task warning. + +Fixes: https://github.com/389ds/389-ds-base/issues/4734 + +Author: vashirov@redhat.com (Thanks) + +Reviewed by: mreynolds, jchapma +--- + ldap/servers/slapd/back-ldbm/db-bdb/bdb_import_threads.c | 6 ++++++ + 1 file changed, 6 insertions(+) + +diff --git a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_import_threads.c b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_import_threads.c +index 905a84e74..35183ed59 100644 +--- a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_import_threads.c ++++ b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_import_threads.c +@@ -2767,8 +2767,14 @@ import_foreman(void *param) + if (job->flags & FLAG_ABORT) { + goto error; + } ++ ++ /* capture skipped entry warnings for this task */ ++ if((job) && (job->skipped)) { ++ slapi_task_set_warning(job->task, WARN_SKIPPED_IMPORT_ENTRY); ++ } + } + ++ + slapi_pblock_destroy(pb); + info->state = FINISHED; + return; +-- +2.31.1 + diff --git a/SOURCES/0028-Issue-4872-BUG-entryuuid-enabled-by-default-causes-r.patch b/SOURCES/0028-Issue-4872-BUG-entryuuid-enabled-by-default-causes-r.patch new file mode 100644 index 0000000..a9d5958 --- /dev/null +++ b/SOURCES/0028-Issue-4872-BUG-entryuuid-enabled-by-default-causes-r.patch @@ -0,0 +1,37 @@ +From f9bc249b2baa11a8ac0eb54e4077eb706d137e38 Mon Sep 17 00:00:00 2001 +From: Firstyear +Date: Thu, 19 Aug 2021 11:06:06 +1000 +Subject: [PATCH] Issue 4872 - BUG - entryuuid enabled by default causes + replication issues (#4876) + +Bug Description: Due to older servers missing the syntax +plugin this breaks schema replication and causes cascading +errors. + +Fix Description: This changes the syntax to be a case +insensitive string, while leaving the plugins in place +for other usage. + +fixes: https://github.com/389ds/389-ds-base/issues/4872 + +Author: William Brown + +Review by: @mreynolds389 @progier389 +--- + ldap/schema/03entryuuid.ldif | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/ldap/schema/03entryuuid.ldif b/ldap/schema/03entryuuid.ldif +index cbde981fe..f7a7f40d5 100644 +--- a/ldap/schema/03entryuuid.ldif ++++ b/ldap/schema/03entryuuid.ldif +@@ -13,4 +13,5 @@ dn: cn=schema + # + # attributes + # +-attributeTypes: ( 1.3.6.1.1.16.4 NAME 'entryUUID' DESC 'UUID of the entry' EQUALITY UUIDMatch ORDERING UUIDOrderingMatch SYNTAX 1.3.6.1.1.16.1 SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation ) ++# attributeTypes: ( 1.3.6.1.1.16.4 NAME 'entryUUID' DESC 'UUID of the entry' EQUALITY UUIDMatch ORDERING UUIDOrderingMatch SYNTAX 1.3.6.1.1.16.1 SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation ) ++attributeTypes: ( 1.3.6.1.1.16.4 NAME 'entryUUID' DESC 'UUID of the entry' EQUALITY caseIgnoreMatch ORDERING caseIgnoreOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation ) +-- +2.31.1 + diff --git a/SOURCES/0029-Remove-GOST-YESCRYPT-password-sotrage-scheme.patch b/SOURCES/0029-Remove-GOST-YESCRYPT-password-sotrage-scheme.patch new file mode 100644 index 0000000..7b74019 --- /dev/null +++ b/SOURCES/0029-Remove-GOST-YESCRYPT-password-sotrage-scheme.patch @@ -0,0 +1,125 @@ +From 120511d35095a48d60abbb7cb2367d0c30fbc757 Mon Sep 17 00:00:00 2001 +From: Mark Reynolds +Date: Wed, 25 Aug 2021 13:20:56 -0400 +Subject: [PATCH] Remove GOST-YESCRYPT password sotrage scheme + +--- + .../tests/suites/password/pwd_algo_test.py | 1 - + ldap/ldif/template-dse-minimal.ldif.in | 9 --------- + ldap/ldif/template-dse.ldif.in | 9 --------- + ldap/servers/plugins/pwdstorage/pwd_init.c | 18 ------------------ + ldap/servers/slapd/fedse.c | 13 ------------- + 5 files changed, 50 deletions(-) + +diff --git a/dirsrvtests/tests/suites/password/pwd_algo_test.py b/dirsrvtests/tests/suites/password/pwd_algo_test.py +index 66bda420e..88f8e40b7 100644 +--- a/dirsrvtests/tests/suites/password/pwd_algo_test.py ++++ b/dirsrvtests/tests/suites/password/pwd_algo_test.py +@@ -124,7 +124,6 @@ def _test_algo_for_pbkdf2(inst, algo_name): + ('CLEAR', 'CRYPT', 'CRYPT-MD5', 'CRYPT-SHA256', 'CRYPT-SHA512', + 'MD5', 'SHA', 'SHA256', 'SHA384', 'SHA512', 'SMD5', 'SSHA', + 'SSHA256', 'SSHA384', 'SSHA512', 'PBKDF2_SHA256', 'DEFAULT', +- 'GOST_YESCRYPT', + )) + def test_pwd_algo_test(topology_st, algo): + """Assert that all of our password algorithms correctly PASS and FAIL varying +diff --git a/ldap/ldif/template-dse-minimal.ldif.in b/ldap/ldif/template-dse-minimal.ldif.in +index 2eccae9b2..1a05f4a67 100644 +--- a/ldap/ldif/template-dse-minimal.ldif.in ++++ b/ldap/ldif/template-dse-minimal.ldif.in +@@ -194,15 +194,6 @@ nsslapd-pluginarg1: nsds5ReplicaCredentials + nsslapd-pluginid: aes-storage-scheme + nsslapd-pluginprecedence: 1 + +-dn: cn=GOST_YESCRYPT,cn=Password Storage Schemes,cn=plugins,cn=config +-objectclass: top +-objectclass: nsSlapdPlugin +-cn: GOST_YESCRYPT +-nsslapd-pluginpath: libpwdstorage-plugin +-nsslapd-plugininitfunc: gost_yescrypt_pwd_storage_scheme_init +-nsslapd-plugintype: pwdstoragescheme +-nsslapd-pluginenabled: on +- + dn: cn=Syntax Validation Task,cn=plugins,cn=config + objectclass: top + objectclass: nsSlapdPlugin +diff --git a/ldap/ldif/template-dse.ldif.in b/ldap/ldif/template-dse.ldif.in +index 7e7480cba..f30531bec 100644 +--- a/ldap/ldif/template-dse.ldif.in ++++ b/ldap/ldif/template-dse.ldif.in +@@ -242,15 +242,6 @@ nsslapd-pluginarg2: nsds5ReplicaBootstrapCredentials + nsslapd-pluginid: aes-storage-scheme + nsslapd-pluginprecedence: 1 + +-dn: cn=GOST_YESCRYPT,cn=Password Storage Schemes,cn=plugins,cn=config +-objectclass: top +-objectclass: nsSlapdPlugin +-cn: GOST_YESCRYPT +-nsslapd-pluginpath: libpwdstorage-plugin +-nsslapd-plugininitfunc: gost_yescrypt_pwd_storage_scheme_init +-nsslapd-plugintype: pwdstoragescheme +-nsslapd-pluginenabled: on +- + dn: cn=Syntax Validation Task,cn=plugins,cn=config + objectclass: top + objectclass: nsSlapdPlugin +diff --git a/ldap/servers/plugins/pwdstorage/pwd_init.c b/ldap/servers/plugins/pwdstorage/pwd_init.c +index 606e63404..59cfc4684 100644 +--- a/ldap/servers/plugins/pwdstorage/pwd_init.c ++++ b/ldap/servers/plugins/pwdstorage/pwd_init.c +@@ -52,8 +52,6 @@ static Slapi_PluginDesc smd5_pdesc = {"smd5-password-storage-scheme", VENDOR, DS + + static Slapi_PluginDesc pbkdf2_sha256_pdesc = {"pbkdf2-sha256-password-storage-scheme", VENDOR, DS_PACKAGE_VERSION, "Salted PBKDF2 SHA256 hash algorithm (PBKDF2_SHA256)"}; + +-static Slapi_PluginDesc gost_yescrypt_pdesc = {"gost-yescrypt-password-storage-scheme", VENDOR, DS_PACKAGE_VERSION, "Yescrypt KDF algorithm (Streebog256)"}; +- + static char *plugin_name = "NSPwdStoragePlugin"; + + int +@@ -431,19 +429,3 @@ pbkdf2_sha256_pwd_storage_scheme_init(Slapi_PBlock *pb) + return rc; + } + +-int +-gost_yescrypt_pwd_storage_scheme_init(Slapi_PBlock *pb) +-{ +- int rc; +- +- slapi_log_err(SLAPI_LOG_PLUGIN, plugin_name, "=> gost_yescrypt_pwd_storage_scheme_init\n"); +- +- rc = slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, (void *)SLAPI_PLUGIN_VERSION_01); +- rc |= slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&gost_yescrypt_pdesc); +- rc |= slapi_pblock_set(pb, SLAPI_PLUGIN_PWD_STORAGE_SCHEME_ENC_FN, (void *)gost_yescrypt_pw_enc); +- rc |= slapi_pblock_set(pb, SLAPI_PLUGIN_PWD_STORAGE_SCHEME_CMP_FN, (void *)gost_yescrypt_pw_cmp); +- rc |= slapi_pblock_set(pb, SLAPI_PLUGIN_PWD_STORAGE_SCHEME_NAME, GOST_YESCRYPT_SCHEME_NAME); +- +- slapi_log_err(SLAPI_LOG_PLUGIN, plugin_name, "<= gost_yescrypt_pwd_storage_scheme_init %d\n", rc); +- return rc; +-} +diff --git a/ldap/servers/slapd/fedse.c b/ldap/servers/slapd/fedse.c +index 44159c991..24b7ed11c 100644 +--- a/ldap/servers/slapd/fedse.c ++++ b/ldap/servers/slapd/fedse.c +@@ -203,19 +203,6 @@ static const char *internal_entries[] = + "nsslapd-pluginVersion: none\n" + "nsslapd-pluginVendor: 389 Project\n" + "nsslapd-pluginDescription: CRYPT-SHA512\n", +- +- "dn: cn=GOST_YESCRYPT,cn=Password Storage Schemes,cn=plugins,cn=config\n" +- "objectclass: top\n" +- "objectclass: nsSlapdPlugin\n" +- "cn: GOST_YESCRYPT\n" +- "nsslapd-pluginpath: libpwdstorage-plugin\n" +- "nsslapd-plugininitfunc: gost_yescrypt_pwd_storage_scheme_init\n" +- "nsslapd-plugintype: pwdstoragescheme\n" +- "nsslapd-pluginenabled: on\n" +- "nsslapd-pluginId: GOST_YESCRYPT\n" +- "nsslapd-pluginVersion: none\n" +- "nsslapd-pluginVendor: 389 Project\n" +- "nsslapd-pluginDescription: GOST_YESCRYPT\n", + }; + + static int NUM_INTERNAL_ENTRIES = sizeof(internal_entries) / sizeof(internal_entries[0]); +-- +2.31.1 + diff --git a/SOURCES/0030-Issue-4884-server-crashes-when-dnaInterval-attribute.patch b/SOURCES/0030-Issue-4884-server-crashes-when-dnaInterval-attribute.patch new file mode 100644 index 0000000..332394c --- /dev/null +++ b/SOURCES/0030-Issue-4884-server-crashes-when-dnaInterval-attribute.patch @@ -0,0 +1,44 @@ +From df0ccce06259b9ef06d522e61da4e3ffcbbf5016 Mon Sep 17 00:00:00 2001 +From: Mark Reynolds +Date: Wed, 25 Aug 2021 16:54:57 -0400 +Subject: [PATCH] Issue 4884 - server crashes when dnaInterval attribute is set + to zero + +Bug Description: + +A division by zero crash occurs if the dnaInterval is set to zero + +Fix Description: + +Validate the config value of dnaInterval and adjust it to the +default/safe value of "1" if needed. + +relates: https://github.com/389ds/389-ds-base/issues/4884 + +Reviewed by: tbordaz(Thanks!) +--- + ldap/servers/plugins/dna/dna.c | 7 +++++++ + 1 file changed, 7 insertions(+) + +diff --git a/ldap/servers/plugins/dna/dna.c b/ldap/servers/plugins/dna/dna.c +index 928a3f54a..c983ebdd0 100644 +--- a/ldap/servers/plugins/dna/dna.c ++++ b/ldap/servers/plugins/dna/dna.c +@@ -1025,7 +1025,14 @@ dna_parse_config_entry(Slapi_PBlock *pb, Slapi_Entry *e, int apply) + + value = slapi_entry_attr_get_charptr(e, DNA_INTERVAL); + if (value) { ++ errno = 0; + entry->interval = strtoull(value, 0, 0); ++ if (entry->interval == 0 || errno == ERANGE) { ++ slapi_log_err(SLAPI_LOG_WARNING, DNA_PLUGIN_SUBSYSTEM, ++ "dna_parse_config_entry - Invalid value for dnaInterval (%s), " ++ "Using default value of 1\n", value); ++ entry->interval = 1; ++ } + slapi_ch_free_string(&value); + } + +-- +2.31.1 + diff --git a/SOURCES/389-ds-base-devel.README b/SOURCES/389-ds-base-devel.README new file mode 100644 index 0000000..190c874 --- /dev/null +++ b/SOURCES/389-ds-base-devel.README @@ -0,0 +1,4 @@ +For detailed information on developing plugins for +389 Directory Server visit. + +http://port389/wiki/Plugins diff --git a/SOURCES/389-ds-base-git.sh b/SOURCES/389-ds-base-git.sh new file mode 100644 index 0000000..0043901 --- /dev/null +++ b/SOURCES/389-ds-base-git.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +DATE=`date +%Y%m%d` +# use a real tag name here +VERSION=1.3.5.14 +PKGNAME=389-ds-base +TAG=${TAG:-$PKGNAME-$VERSION} +URL="https://git.fedorahosted.org/git/?p=389/ds.git;a=snapshot;h=$TAG;sf=tgz" +SRCNAME=$PKGNAME-$VERSION + +wget -O $SRCNAME.tar.gz "$URL" + +echo convert tgz format to tar.bz2 format + +gunzip $PKGNAME-$VERSION.tar.gz +bzip2 $PKGNAME-$VERSION.tar diff --git a/SOURCES/Cargo.lock b/SOURCES/Cargo.lock new file mode 100644 index 0000000..1127ca0 --- /dev/null +++ b/SOURCES/Cargo.lock @@ -0,0 +1,565 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ansi_term" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +dependencies = [ + "winapi", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "cbindgen" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9daec6140ab4dcd38c3dd57e580b59a621172a526ac79f1527af760a55afeafd" +dependencies = [ + "clap", + "log", + "proc-macro2", + "quote", + "serde", + "serde_json", + "syn", + "tempfile", + "toml", +] + +[[package]] +name = "cc" +version = "1.0.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a72c244c1ff497a746a7e1fb3d14bd08420ecda70c8f25c7112f2781652d787" +dependencies = [ + "jobserver", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "2.33.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" +dependencies = [ + "ansi_term", + "atty", + "bitflags", + "strsim", + "textwrap", + "unicode-width", + "vec_map", +] + +[[package]] +name = "entryuuid" +version = "0.1.0" +dependencies = [ + "cc", + "libc", + "paste", + "slapi_r_plugin", + "uuid", +] + +[[package]] +name = "entryuuid_syntax" +version = "0.1.0" +dependencies = [ + "cc", + "libc", + "paste", + "slapi_r_plugin", + "uuid", +] + +[[package]] +name = "fernet" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93804560e638370a8be6d59ce71ed803e55e230abdbf42598e666b41adda9b1f" +dependencies = [ + "base64", + "byteorder", + "getrandom", + "openssl", + "zeroize", +] + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "getrandom" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "hermit-abi" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" +dependencies = [ + "libc", +] + +[[package]] +name = "itoa" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" + +[[package]] +name = "jobserver" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "972f5ae5d1cb9c6ae417789196c803205313edde988685da5e3aae0827b9e7fd" +dependencies = [ + "libc", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "789da6d93f1b866ffe175afc5322a4d76c038605a1c3319bb57b06967ca98a36" + +[[package]] +name = "librnsslapd" +version = "0.1.0" +dependencies = [ + "cbindgen", + "libc", + "slapd", +] + +[[package]] +name = "librslapd" +version = "0.1.0" +dependencies = [ + "cbindgen", + "libc", + "slapd", +] + +[[package]] +name = "log" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "once_cell" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3" + +[[package]] +name = "openssl" +version = "0.10.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d7830286ad6a3973c0f1d9b73738f69c76b739301d0229c4b96501695cbe4c8" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-sys", +] + +[[package]] +name = "openssl-sys" +version = "0.9.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6b0d6fb7d80f877617dfcb014e605e2b5ab2fb0afdf27935219bb6bd984cb98" +dependencies = [ + "autocfg", + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "paste" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45ca20c77d80be666aef2b45486da86238fabe33e38306bd3118fe4af33fa880" +dependencies = [ + "paste-impl", + "proc-macro-hack", +] + +[[package]] +name = "paste-impl" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d95a7db200b97ef370c8e6de0088252f7e0dfff7d047a28528e47456c0fc98b6" +dependencies = [ + "proc-macro-hack", +] + +[[package]] +name = "pkg-config" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" + +[[package]] +name = "ppv-lite86" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" + +[[package]] +name = "proc-macro-hack" +version = "0.5.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" + +[[package]] +name = "proc-macro2" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_hc" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73" +dependencies = [ + "rand_core", +] + +[[package]] +name = "redox_syscall" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "742739e41cd49414de871ea5e549afb7e2a3ac77b589bcbebe8c82fab37147fc" +dependencies = [ + "bitflags", +] + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + +[[package]] +name = "rsds" +version = "0.1.0" + +[[package]] +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" + +[[package]] +name = "serde" +version = "1.0.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "slapd" +version = "0.1.0" +dependencies = [ + "fernet", +] + +[[package]] +name = "slapi_r_plugin" +version = "0.1.0" +dependencies = [ + "lazy_static", + "libc", + "paste", + "uuid", +] + +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[package]] +name = "syn" +version = "1.0.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1e8cdbefb79a9a5a65e0db8b47b723ee907b7c7f8496c76a1770b5c310bab82" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "synstructure" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "unicode-xid", +] + +[[package]] +name = "tempfile" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" +dependencies = [ + "cfg-if", + "libc", + "rand", + "redox_syscall", + "remove_dir_all", + "winapi", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "toml" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +dependencies = [ + "serde", +] + +[[package]] +name = "unicode-width" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "uuid" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "vcpkg" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "025ce40a007e1907e58d5bc1a594def78e5573bb0b1160bc389634e8f12e4faa" + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "zeroize" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2c1e130bebaeab2f23886bf9acbaca14b092408c452543c857f66399cd6dab1" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] diff --git a/SPECS/389-ds-base.spec b/SPECS/389-ds-base.spec new file mode 100644 index 0000000..bd2daeb --- /dev/null +++ b/SPECS/389-ds-base.spec @@ -0,0 +1,948 @@ + +%global pkgname dirsrv +%global srcname 389-ds-base + +# Exclude i686 bit arches +ExcludeArch: i686 + +# for a pre-release, define the prerel field e.g. .a1 .rc2 - comment out for official release +# also remove the space between % and global - this space is needed because +# fedpkg verrel stupidly ignores comment lines +#% global prerel .rc3 +# also need the relprefix field for a pre-release e.g. .0 - also comment out for official release +#% global relprefix 0. + +# If perl-Socket-2.000 or newer is available, set 0 to use_Socket6. +%global use_Socket6 0 + +%global use_asan 0 +%global use_rust 1 +%global use_legacy 1 +%global bundle_jemalloc 1 +%if %{use_asan} +%global bundle_jemalloc 0 +%endif + +%if %{bundle_jemalloc} +%global jemalloc_name jemalloc +%global jemalloc_ver 5.2.1 +%global __provides_exclude ^libjemalloc\\.so.*$ +%endif + +# Use Clang instead of GCC +%global use_clang 0 + +# fedora 15 and later uses tmpfiles.d +# otherwise, comment this out +%{!?with_tmpfiles_d: %global with_tmpfiles_d %{_sysconfdir}/tmpfiles.d} + +# systemd support +%global groupname %{pkgname}.target + +# set PIE flag +%global _hardened_build 1 + +# Filter argparse-manpage from autogenerated package Requires +%global __requires_exclude ^python.*argparse-manpage + +Summary: 389 Directory Server (base) +Name: 389-ds-base +Version: 1.4.3.23 +Release: %{?relprefix}10%{?prerel}%{?dist} +License: GPLv3+ +URL: https://www.port389.org +Group: System Environment/Daemons +Conflicts: selinux-policy-base < 3.9.8 +Conflicts: freeipa-server < 4.0.3 +Obsoletes: %{name} <= 1.4.0.9 +Provides: ldif2ldbm >= 0 + +##### Bundled cargo crates list - START ##### +Provides: bundled(crate(ansi_term)) = 0.11.0 +Provides: bundled(crate(atty)) = 0.2.14 +Provides: bundled(crate(autocfg)) = 1.0.1 +Provides: bundled(crate(base64)) = 0.10.1 +Provides: bundled(crate(bitflags)) = 1.2.1 +Provides: bundled(crate(byteorder)) = 1.4.2 +Provides: bundled(crate(cbindgen)) = 0.9.1 +Provides: bundled(crate(cc)) = 1.0.66 +Provides: bundled(crate(cfg-if)) = 0.1.10 +Provides: bundled(crate(cfg-if)) = 1.0.0 +Provides: bundled(crate(clap)) = 2.33.3 +Provides: bundled(crate(fernet)) = 0.1.3 +Provides: bundled(crate(foreign-types)) = 0.3.2 +Provides: bundled(crate(foreign-types-shared)) = 0.1.1 +Provides: bundled(crate(getrandom)) = 0.1.16 +Provides: bundled(crate(hermit-abi)) = 0.1.17 +Provides: bundled(crate(itoa)) = 0.4.7 +Provides: bundled(crate(lazy_static)) = 1.4.0 +Provides: bundled(crate(libc)) = 0.2.82 +Provides: bundled(crate(librnsslapd)) = 0.1.0 +Provides: bundled(crate(librslapd)) = 0.1.0 +Provides: bundled(crate(log)) = 0.4.11 +Provides: bundled(crate(openssl)) = 0.10.32 +Provides: bundled(crate(openssl-sys)) = 0.9.60 +Provides: bundled(crate(pkg-config)) = 0.3.19 +Provides: bundled(crate(ppv-lite86)) = 0.2.10 +Provides: bundled(crate(proc-macro2)) = 1.0.24 +Provides: bundled(crate(quote)) = 1.0.8 +Provides: bundled(crate(rand)) = 0.7.3 +Provides: bundled(crate(rand_chacha)) = 0.2.2 +Provides: bundled(crate(rand_core)) = 0.5.1 +Provides: bundled(crate(rand_hc)) = 0.2.0 +Provides: bundled(crate(redox_syscall)) = 0.1.57 +Provides: bundled(crate(remove_dir_all)) = 0.5.3 +Provides: bundled(crate(rsds)) = 0.1.0 +Provides: bundled(crate(ryu)) = 1.0.5 +Provides: bundled(crate(serde)) = 1.0.118 +Provides: bundled(crate(serde_derive)) = 1.0.118 +Provides: bundled(crate(serde_json)) = 1.0.61 +Provides: bundled(crate(slapd)) = 0.1.0 +Provides: bundled(crate(strsim)) = 0.8.0 +Provides: bundled(crate(syn)) = 1.0.58 +Provides: bundled(crate(tempfile)) = 3.1.0 +Provides: bundled(crate(textwrap)) = 0.11.0 +Provides: bundled(crate(toml)) = 0.5.8 +Provides: bundled(crate(unicode-width)) = 0.1.8 +Provides: bundled(crate(unicode-xid)) = 0.2.1 +Provides: bundled(crate(vcpkg)) = 0.2.11 +Provides: bundled(crate(vec_map)) = 0.8.2 +Provides: bundled(crate(wasi)) = 0.9.0+wasi_snapshot_preview1 +Provides: bundled(crate(winapi)) = 0.3.9 +Provides: bundled(crate(winapi-i686-pc-windows-gnu)) = 0.4.0 +Provides: bundled(crate(winapi-x86_64-pc-windows-gnu)) = 0.4.0 +##### Bundled cargo crates list - END ##### + +BuildRequires: nspr-devel +BuildRequires: nss-devel >= 3.34 +BuildRequires: perl-generators +BuildRequires: openldap-devel +BuildRequires: libdb-devel +BuildRequires: cyrus-sasl-devel +BuildRequires: icu +BuildRequires: libicu-devel +BuildRequires: pcre-devel +BuildRequires: cracklib-devel +%if %{use_clang} +BuildRequires: libatomic +BuildRequires: clang +%else +BuildRequires: gcc +BuildRequires: gcc-c++ +%endif +# The following are needed to build the snmp ldap-agent +BuildRequires: net-snmp-devel +BuildRequires: lm_sensors-devel +BuildRequires: bzip2-devel +BuildRequires: zlib-devel +BuildRequires: openssl-devel +# the following is for the pam passthru auth plug-in +BuildRequires: pam-devel +BuildRequires: systemd-units +BuildRequires: systemd-devel +%if %{use_asan} +BuildRequires: libasan +%endif +# If rust is enabled +%if %{use_rust} +BuildRequires: cargo +BuildRequires: rust +%endif +BuildRequires: pkgconfig +BuildRequires: pkgconfig(systemd) +BuildRequires: pkgconfig(krb5) + +# Needed to support regeneration of the autotool artifacts. +BuildRequires: autoconf +BuildRequires: automake +BuildRequires: libtool +# For our documentation +BuildRequires: doxygen +# For tests! +BuildRequires: libcmocka-devel +BuildRequires: libevent-devel +# For lib389 and related components +BuildRequires: python%{python3_pkgversion} +BuildRequires: python%{python3_pkgversion}-devel +BuildRequires: python%{python3_pkgversion}-setuptools +BuildRequires: python%{python3_pkgversion}-ldap +BuildRequires: python%{python3_pkgversion}-six +BuildRequires: python%{python3_pkgversion}-pyasn1 +BuildRequires: python%{python3_pkgversion}-pyasn1-modules +BuildRequires: python%{python3_pkgversion}-dateutil +BuildRequires: python%{python3_pkgversion}-argcomplete +BuildRequires: python%{python3_pkgversion}-argparse-manpage +BuildRequires: python%{python3_pkgversion}-policycoreutils +BuildRequires: python%{python3_pkgversion}-libselinux + +# For cockpit +BuildRequires: rsync + +Requires: %{name}-libs = %{version}-%{release} +Requires: python%{python3_pkgversion}-lib389 = %{version}-%{release} + +# this is needed for using semanage from our setup scripts +Requires: policycoreutils-python-utils +Requires: /usr/sbin/semanage +Requires: libsemanage-python%{python3_pkgversion} + +Requires: selinux-policy >= 3.14.1-29 + +# the following are needed for some of our scripts +Requires: openldap-clients +Requires: openssl-perl +Requires: python%{python3_pkgversion}-ldap + +# this is needed to setup SSL if you are not using the +# administration server package +Requires: nss-tools +Requires: nss >= 3.34 + +# these are not found by the auto-dependency method +# they are required to support the mandatory LDAP SASL mechs +Requires: cyrus-sasl-gssapi +Requires: cyrus-sasl-md5 +Requires: cyrus-sasl-plain + +# this is needed for verify-db.pl +Requires: libdb-utils + +# Needed for password dictionary checks +Requires: cracklib-dicts + +# This picks up libperl.so as a Requires, so we add this versioned one +Requires: perl(:MODULE_COMPAT_%(eval "`%{__perl} -V:version`"; echo $version)) +Requires: perl-Errno >= 1.23-360 + +# Needed by logconv.pl +Requires: perl-DB_File +Requires: perl-Archive-Tar + +# Needed for password dictionary checks +Requires: cracklib-dicts + +# Picks up our systemd deps. +%{?systemd_requires} + +Obsoletes: %{name} <= 1.3.5.4 + +Source0: https://releases.pagure.org/389-ds-base/%{name}-%{version}.tar.bz2 +# 389-ds-git.sh should be used to generate the source tarball from git +Source1: %{name}-git.sh +Source2: %{name}-devel.README +%if %{bundle_jemalloc} +Source3: https://github.com/jemalloc/%{jemalloc_name}/releases/download/%{jemalloc_ver}/%{jemalloc_name}-%{jemalloc_ver}.tar.bz2 +%endif +%if %{use_rust} +Source4: vendor-%{version}-2.tar.gz +Source5: Cargo.lock +%endif +Patch01: 0001-Issue-4747-Remove-unstable-unstatus-tests-from-PRCI-.patch +Patch02: 0002-Issue-4701-RFE-Exclude-attributes-from-retro-changel.patch +Patch03: 0003-Ticket-137-Implement-EntryUUID-plugin.patch +Patch04: 0004-Ticket-4326-entryuuid-fixup-did-not-work-correctly-4.patch +Patch05: 0005-Issue-4498-BUG-entryuuid-replication-may-not-work-45.patch +Patch06: 0006-Issue-4421-Unable-to-build-with-Rust-enabled-in-clos.patch +Patch07: 0007-Ticket-51175-resolve-plugin-name-leaking.patch +Patch08: 0008-Issue-4773-Enable-interval-feature-of-DNA-plugin.patch +Patch09: 0009-Issue-4623-RFE-Monitor-the-current-DB-locks-4762.patch +Patch10: 0010-Issue-4764-replicated-operation-sometime-checks-ACI-.patch +Patch11: 0011-Issue-4778-RFE-Allow-setting-TOD-for-db-compaction-a.patch +Patch12: 0012-Issue-4778-RFE-Add-changelog-compaction-task-in-1.4..patch +Patch13: 0013-Issue-4797-ACL-IP-ADDRESS-evaluation-may-corrupt-c_i.patch +Patch14: 0014-Issue-4396-Minor-memory-leak-in-backend-4558-4572.patch +Patch15: 0015-Issue-4700-Regression-in-winsync-replication-agreeme.patch +Patch16: 0016-Issue-4725-Fix-compiler-warnings.patch +Patch17: 0017-Issue-4814-_cl5_get_tod_expiration-may-crash-at-star.patch +Patch18: 0018-Issue-4789-Temporary-password-rules-are-not-enforce-.patch +Patch19: 0019-Issue-4788-CLI-should-support-Temporary-Password-Rul.patch +Patch20: 0020-Issue-4447-Crash-when-the-Referential-Integrity-log-.patch +Patch21: 0021-Issue-4791-Missing-dependency-for-RetroCL-RFE.patch +Patch22: 0022-Issue-4656-remove-problematic-language-from-ds-replc.patch +Patch23: 0023-Issue-4443-Internal-unindexed-searches-in-syncrepl-r.patch +Patch24: 0024-Issue-4817-BUG-locked-crypt-accounts-on-import-may-a.patch +Patch25: 0025-Issue-4837-persistent-search-returns-entries-even-wh.patch +Patch26: 0026-Hardcode-gost-crypt-passsword-storage-scheme.patch +Patch27: 0027-Issue-4734-import-of-entry-with-no-parent-warning-47.patch +Patch28: 0028-Issue-4872-BUG-entryuuid-enabled-by-default-causes-r.patch +Patch29: 0029-Remove-GOST-YESCRYPT-password-sotrage-scheme.patch +Patch30: 0030-Issue-4884-server-crashes-when-dnaInterval-attribute.patch + + +%description +389 Directory Server is an LDAPv3 compliant server. The base package includes +the LDAP server and command line utilities for server administration. +%if %{use_asan} +WARNING! This build is linked to Address Sanitisation libraries. This probably +isn't what you want. Please contact support immediately. +Please see http://seclists.org/oss-sec/2016/q1/363 for more information. +%endif + +%package libs +Summary: Core libraries for 389 Directory Server +Group: System Environment/Daemons +BuildRequires: nspr-devel +BuildRequires: nss-devel >= 3.34 +BuildRequires: openldap-devel +BuildRequires: libdb-devel +BuildRequires: cyrus-sasl-devel +BuildRequires: libicu-devel +BuildRequires: pcre-devel +BuildRequires: libtalloc-devel +BuildRequires: libevent-devel +BuildRequires: libtevent-devel +Requires: krb5-libs +Requires: libevent +BuildRequires: systemd-devel +Provides: svrcore = 4.1.4 +Conflicts: svrcore +Obsoletes: svrcore <= 4.1.3 + +%description libs +Core libraries for the 389 Directory Server base package. These libraries +are used by the main package and the -devel package. This allows the -devel +package to be installed with just the -libs package and without the main package. + +%if %{use_legacy} +%package legacy-tools +Summary: Legacy utilities for 389 Directory Server +Group: System Environment/Daemons +Obsoletes: %{name} <= 1.4.0.9 +Requires: %{name}-libs = %{version}-%{release} +# for setup-ds.pl to support ipv6 +%if %{use_Socket6} +Requires: perl-Socket6 +%else +Requires: perl-Socket +%endif +Requires: perl-NetAddr-IP +# use_openldap assumes perl-Mozilla-LDAP is built with openldap support +Requires: perl-Mozilla-LDAP +# for setup-ds.pl +Requires: bind-utils +%global __provides_exclude_from %{_libdir}/%{pkgname}/perl +%global __requires_exclude perl\\((DSCreate|DSMigration|DSUpdate|DSUtil|Dialog|DialogManager|FileConn|Inf|Migration|Resource|Setup|SetupLog) +%{?perl_default_filter} + +%description legacy-tools +Legacy (and deprecated) utilities for 389 Directory Server. This includes +the old account management and task scripts. These are deprecated in favour of +the dscreate, dsctl, dsconf and dsidm tools. +%endif + +%package devel +Summary: Development libraries for 389 Directory Server +Group: Development/Libraries +Requires: %{name}-libs = %{version}-%{release} +Requires: pkgconfig +Requires: nspr-devel +Requires: nss-devel >= 3.34 +Requires: openldap-devel +Requires: libtalloc +Requires: libevent +Requires: libtevent +Requires: systemd-libs +Provides: svrcore-devel = 4.1.4 +Conflicts: svrcore-devel +Obsoletes: svrcore-devel <= 4.1.3 + +%description devel +Development Libraries and headers for the 389 Directory Server base package. + +%package snmp +Summary: SNMP Agent for 389 Directory Server +Group: System Environment/Daemons +Requires: %{name} = %{version}-%{release} + +Obsoletes: %{name} <= 1.4.0.0 + +%description snmp +SNMP Agent for the 389 Directory Server base package. + +%package -n python%{python3_pkgversion}-lib389 +Summary: A library for accessing, testing, and configuring the 389 Directory Server +BuildArch: noarch +Group: Development/Libraries +Requires: openssl +Requires: iproute +Requires: platform-python +Recommends: bash-completion +Requires: python%{python3_pkgversion}-ldap +Requires: python%{python3_pkgversion}-six +Requires: python%{python3_pkgversion}-pyasn1 +Requires: python%{python3_pkgversion}-pyasn1-modules +Requires: python%{python3_pkgversion}-dateutil +Requires: python%{python3_pkgversion}-argcomplete +Requires: python%{python3_pkgversion}-libselinux +Requires: python%{python3_pkgversion}-setuptools +Requires: python%{python3_pkgversion}-distro +%{?python_provide:%python_provide python%{python3_pkgversion}-lib389} + +%description -n python%{python3_pkgversion}-lib389 +This module contains tools and libraries for accessing, testing, + and configuring the 389 Directory Server. + +%package -n cockpit-389-ds +Summary: Cockpit UI Plugin for configuring and administering the 389 Directory Server +BuildArch: noarch +Requires: cockpit +Requires: platform-python +Requires: python%{python3_pkgversion}-lib389 + +%description -n cockpit-389-ds +A cockpit UI Plugin for configuring and administering the 389 Directory Server + +%prep +%autosetup -p1 -v -n %{name}-%{version}%{?prerel} +%if %{use_rust} +tar xvzf %{SOURCE4} +cp %{SOURCE5} src/ +%endif +%if %{bundle_jemalloc} +%setup -q -n %{name}-%{version}%{?prerel} -T -D -b 3 +%endif +cp %{SOURCE2} README.devel + +%build + +OPENLDAP_FLAG="--with-openldap" +%{?with_tmpfiles_d: TMPFILES_FLAG="--with-tmpfiles-d=%{with_tmpfiles_d}"} +# hack hack hack https://bugzilla.redhat.com/show_bug.cgi?id=833529 +NSSARGS="--with-nss-lib=%{_libdir} --with-nss-inc=%{_includedir}/nss3" + +%if %{use_asan} +ASAN_FLAGS="--enable-asan --enable-debug" +%endif + +%if %{use_rust} +RUST_FLAGS="--enable-rust --enable-rust-offline" +%endif + +%if %{use_legacy} +LEGACY_FLAGS="--enable-legacy --enable-perl" +%else +LEGACY_FLAGS="--disable-legacy --disable-perl" +%endif + +%if %{use_clang} +export CC=clang +export CXX=clang++ +CLANG_FLAGS="--enable-clang" +%endif + +%if %{bundle_jemalloc} +# Override page size, bz #1545539 +# 4K +%ifarch %ix86 %arm x86_64 s390x +%define lg_page --with-lg-page=12 +%endif + +# 64K +%ifarch ppc64 ppc64le aarch64 +%define lg_page --with-lg-page=16 +%endif + +# Override huge page size on aarch64 +# 2M instead of 512M +%ifarch aarch64 +%define lg_hugepage --with-lg-hugepage=21 +%endif + +# Build jemalloc +pushd ../%{jemalloc_name}-%{jemalloc_ver} +%configure \ + --libdir=%{_libdir}/%{pkgname}/lib \ + --bindir=%{_libdir}/%{pkgname}/bin \ + --enable-prof +make %{?_smp_mflags} +popd +%endif + + +# Enforce strict linking +%define _strict_symbol_defs_build 1 + +# Rebuild the autotool artifacts now. +autoreconf -fiv + +%configure --enable-autobind --with-selinux $OPENLDAP_FLAG $TMPFILES_FLAG \ + --with-systemd \ + --with-systemdsystemunitdir=%{_unitdir} \ + --with-systemdsystemconfdir=%{_sysconfdir}/systemd/system \ + --with-systemdgroupname=%{groupname} \ + --libexecdir=%{_libexecdir}/%{pkgname} \ + $NSSARGS $ASAN_FLAGS $RUST_FLAGS $LEGACY_FLAGS $CLANG_FLAGS \ + --enable-cmocka + +# lib389 +pushd ./src/lib389 +%py3_build +popd +# argparse-manpage dynamic man pages have hardcoded man v1 in header, +# need to change it to v8 +sed -i "1s/\"1\"/\"8\"/" %{_builddir}/%{name}-%{version}%{?prerel}/src/lib389/man/dsconf.8 +sed -i "1s/\"1\"/\"8\"/" %{_builddir}/%{name}-%{version}%{?prerel}/src/lib389/man/dsctl.8 +sed -i "1s/\"1\"/\"8\"/" %{_builddir}/%{name}-%{version}%{?prerel}/src/lib389/man/dsidm.8 +sed -i "1s/\"1\"/\"8\"/" %{_builddir}/%{name}-%{version}%{?prerel}/src/lib389/man/dscreate.8 + +# Generate symbolic info for debuggers +export XCFLAGS=$RPM_OPT_FLAGS + +#make %{?_smp_mflags} +make + +%install + +mkdir -p %{buildroot}%{_datadir}/gdb/auto-load%{_sbindir} +mkdir -p %{buildroot}%{_datadir}/cockpit +make DESTDIR="$RPM_BUILD_ROOT" install + +# Cockpit file list +find %{buildroot}%{_datadir}/cockpit/389-console -type d | sed -e "s@%{buildroot}@@" | sed -e 's/^/\%dir /' > cockpit.list +find %{buildroot}%{_datadir}/cockpit/389-console -type f | sed -e "s@%{buildroot}@@" >> cockpit.list + +# Copy in our docs from doxygen. +cp -r %{_builddir}/%{name}-%{version}%{?prerel}/man/man3 $RPM_BUILD_ROOT/%{_mandir}/man3 + +# lib389 +pushd src/lib389 +%py3_install +popd + +mkdir -p $RPM_BUILD_ROOT/var/log/%{pkgname} +mkdir -p $RPM_BUILD_ROOT/var/lib/%{pkgname} +mkdir -p $RPM_BUILD_ROOT/var/3lock/%{pkgname} + +# for systemd +mkdir -p $RPM_BUILD_ROOT%{_sysconfdir}/systemd/system/%{groupname}.wants + +#remove libtool archives and static libs +find %{buildroot} -type f -name "*.la" -delete +find %{buildroot} -type f -name "*.a" -delete + +%if %{use_legacy} +# make sure perl scripts have a proper shebang +sed -i -e 's|#{{PERL-EXEC}}|#!/usr/bin/perl|' $RPM_BUILD_ROOT%{_datadir}/%{pkgname}/script-templates/template-*.pl +%endif + +%if %{bundle_jemalloc} +pushd ../%{jemalloc_name}-%{jemalloc_ver} +make DESTDIR="$RPM_BUILD_ROOT" install_lib install_bin +cp -pa COPYING ../%{name}-%{version}%{?prerel}/COPYING.jemalloc +cp -pa README ../%{name}-%{version}%{?prerel}/README.jemalloc +popd +%endif + +%check +# This checks the code, if it fails it prints why, then re-raises the fail to shortcircuit the rpm build. +if ! make DESTDIR="$RPM_BUILD_ROOT" check; then cat ./test-suite.log && false; fi + +%clean +rm -rf $RPM_BUILD_ROOT + +%post +if [ -n "$DEBUGPOSTTRANS" ] ; then + output=$DEBUGPOSTTRANS + output2=${DEBUGPOSTTRANS}.upgrade +else + output=/dev/null + output2=/dev/null +fi + +# reload to pick up any changes to systemd files +/bin/systemctl daemon-reload >$output 2>&1 || : + +# https://fedoraproject.org/wiki/Packaging:UsersAndGroups#Soft_static_allocation +# Soft static allocation for UID and GID +USERNAME="dirsrv" +ALLOCATED_UID=389 +GROUPNAME="dirsrv" +ALLOCATED_GID=389 +HOMEDIR="/usr/share/dirsrv" + +getent group $GROUPNAME >/dev/null || /usr/sbin/groupadd -f -g $ALLOCATED_GID -r $GROUPNAME +if ! getent passwd $USERNAME >/dev/null ; then + if ! getent passwd $ALLOCATED_UID >/dev/null ; then + /usr/sbin/useradd -r -u $ALLOCATED_UID -g $GROUPNAME -d $HOMEDIR -s /sbin/nologin -c "user for 389-ds-base" $USERNAME + else + /usr/sbin/useradd -r -g $GROUPNAME -d $HOMEDIR -s /sbin/nologin -c "user for 389-ds-base" $USERNAME + fi +fi + +# Reload our sysctl before we restart (if we can) +sysctl --system &> $output; true + +%preun +if [ $1 -eq 0 ]; then # Final removal + # remove instance specific service files/links + rm -rf %{_sysconfdir}/systemd/system/%{groupname}.wants/* > /dev/null 2>&1 || : +fi + +%postun +if [ $1 = 0 ]; then # Final removal + rm -rf /var/run/%{pkgname} +fi + +%post snmp +%systemd_post %{pkgname}-snmp.service + +%preun snmp +%systemd_preun %{pkgname}-snmp.service %{groupname} + +%postun snmp +%systemd_postun_with_restart %{pkgname}-snmp.service + +%if %{use_legacy} +%post legacy-tools + +# START UPGRADE SCRIPT + +if [ -n "$DEBUGPOSTTRANS" ] ; then + output=$DEBUGPOSTTRANS + output2=${DEBUGPOSTTRANS}.upgrade +else + output=/dev/null + output2=/dev/null +fi + +# find all instances +instances="" # instances that require a restart after upgrade +ninst=0 # number of instances found in total + +echo looking for instances in %{_sysconfdir}/%{pkgname} > $output 2>&1 || : +instbase="%{_sysconfdir}/%{pkgname}" +for dir in $instbase/slapd-* ; do + echo dir = $dir >> $output 2>&1 || : + if [ ! -d "$dir" ] ; then continue ; fi + case "$dir" in *.removed) continue ;; esac + basename=`basename $dir` + inst="%{pkgname}@`echo $basename | sed -e 's/slapd-//g'`" + echo found instance $inst - getting status >> $output 2>&1 || : + if /bin/systemctl -q is-active $inst ; then + echo instance $inst is running >> $output 2>&1 || : + instances="$instances $inst" + else + echo instance $inst is not running >> $output 2>&1 || : + fi + ninst=`expr $ninst + 1` +done +if [ $ninst -eq 0 ] ; then + echo no instances to upgrade >> $output 2>&1 || : + exit 0 # have no instances to upgrade - just skip the rest +fi +# shutdown all instances +echo shutting down all instances . . . >> $output 2>&1 || : +for inst in $instances ; do + echo stopping instance $inst >> $output 2>&1 || : + /bin/systemctl stop $inst >> $output 2>&1 || : +done +echo remove pid files . . . >> $output 2>&1 || : +/bin/rm -f /var/run/%{pkgname}*.pid /var/run/%{pkgname}*.startpid +# do the upgrade +echo upgrading instances . . . >> $output 2>&1 || : +DEBUGPOSTSETUPOPT=`/usr/bin/echo $DEBUGPOSTSETUP | /usr/bin/sed -e "s/[^d]//g"` +if [ -n "$DEBUGPOSTSETUPOPT" ] ; then + %{_sbindir}/setup-ds.pl -$DEBUGPOSTSETUPOPT -u -s General.UpdateMode=offline >> $output 2>&1 || : +else + %{_sbindir}/setup-ds.pl -u -s General.UpdateMode=offline >> $output 2>&1 || : +fi + +# restart instances that require it +for inst in $instances ; do + echo restarting instance $inst >> $output 2>&1 || : + /bin/systemctl start $inst >> $output 2>&1 || : +done +#END UPGRADE +%endif + +exit 0 + + +%files +%if %{bundle_jemalloc} +%doc LICENSE LICENSE.GPLv3+ LICENSE.openssl README.jemalloc +%license COPYING.jemalloc +%else +%doc LICENSE LICENSE.GPLv3+ LICENSE.openssl +%endif +%dir %{_sysconfdir}/%{pkgname} +%dir %{_sysconfdir}/%{pkgname}/schema +%config(noreplace)%{_sysconfdir}/%{pkgname}/schema/*.ldif +%dir %{_sysconfdir}/%{pkgname}/config +%dir %{_sysconfdir}/systemd/system/%{groupname}.wants +%config(noreplace)%{_sysconfdir}/%{pkgname}/config/slapd-collations.conf +%config(noreplace)%{_sysconfdir}/%{pkgname}/config/certmap.conf +%{_datadir}/%{pkgname} +%{_datadir}/gdb/auto-load/* +%{_unitdir} +%{_bindir}/dbscan +%{_mandir}/man1/dbscan.1.gz +%{_bindir}/ds-replcheck +%{_mandir}/man1/ds-replcheck.1.gz +%{_bindir}/ds-logpipe.py +%{_mandir}/man1/ds-logpipe.py.1.gz +%{_bindir}/ldclt +%{_mandir}/man1/ldclt.1.gz +%{_sbindir}/ldif2ldap +%{_mandir}/man8/ldif2ldap.8.gz +%{_bindir}/logconv.pl +%{_mandir}/man1/logconv.pl.1.gz +%{_bindir}/pwdhash +%{_mandir}/man1/pwdhash.1.gz +%{_bindir}/readnsstate +%{_mandir}/man1/readnsstate.1.gz +# Remove for now: %caps(CAP_NET_BIND_SERVICE=pe) {_sbindir}/ns-slapd +%{_sbindir}/ns-slapd +%{_mandir}/man8/ns-slapd.8.gz +%{_libexecdir}/%{pkgname}/ds_systemd_ask_password_acl +%{_mandir}/man5/99user.ldif.5.gz +%{_mandir}/man5/certmap.conf.5.gz +%{_mandir}/man5/slapd-collations.conf.5.gz +%{_mandir}/man5/dirsrv.5.gz +%{_mandir}/man5/dirsrv.systemd.5.gz +%{_libdir}/%{pkgname}/python +%dir %{_libdir}/%{pkgname}/plugins +%{_libdir}/%{pkgname}/plugins/*.so +# This has to be hardcoded to /lib - $libdir changes between lib/lib64, but +# sysctl.d is always in /lib. +%{_prefix}/lib/sysctl.d/* +%dir %{_localstatedir}/lib/%{pkgname} +%dir %{_localstatedir}/log/%{pkgname} +%ghost %dir %{_localstatedir}/lock/%{pkgname} +%exclude %{_sbindir}/ldap-agent* +%exclude %{_mandir}/man1/ldap-agent.1.gz +%exclude %{_unitdir}/%{pkgname}-snmp.service +%if %{bundle_jemalloc} +%{_libdir}/%{pkgname}/lib/ +%{_libdir}/%{pkgname}/bin/ +%exclude %{_libdir}/%{pkgname}/bin/jemalloc-config +%exclude %{_libdir}/%{pkgname}/bin/jemalloc.sh +%exclude %{_libdir}/%{pkgname}/lib/libjemalloc.a +%exclude %{_libdir}/%{pkgname}/lib/libjemalloc.so +%exclude %{_libdir}/%{pkgname}/lib/libjemalloc_pic.a +%exclude %{_libdir}/%{pkgname}/lib/pkgconfig +%endif + +%files devel +%doc LICENSE LICENSE.GPLv3+ LICENSE.openssl README.devel +%{_mandir}/man3/* +%{_includedir}/svrcore.h +%{_includedir}/%{pkgname} +%{_libdir}/libsvrcore.so +%{_libdir}/%{pkgname}/libslapd.so +%{_libdir}/%{pkgname}/libns-dshttpd.so +%{_libdir}/%{pkgname}/libsds.so +%{_libdir}/%{pkgname}/libldaputil.so +%{_libdir}/pkgconfig/svrcore.pc +%{_libdir}/pkgconfig/dirsrv.pc +%{_libdir}/pkgconfig/libsds.pc + +%files libs +%doc LICENSE LICENSE.GPLv3+ LICENSE.openssl README.devel +%dir %{_libdir}/%{pkgname} +%{_libdir}/libsvrcore.so.* +%{_libdir}/%{pkgname}/libslapd.so.* +%{_libdir}/%{pkgname}/libns-dshttpd-*.so +%{_libdir}/%{pkgname}/libsds.so.* +%{_libdir}/%{pkgname}/libldaputil.so.* +%{_libdir}/%{pkgname}/librewriters.so* +%if %{bundle_jemalloc} +%{_libdir}/%{pkgname}/lib/libjemalloc.so.2 +%endif + +%if %{use_legacy} +%files legacy-tools +%doc LICENSE LICENSE.GPLv3+ LICENSE.openssl README.devel +%{_bindir}/infadd +%{_mandir}/man1/infadd.1.gz +%{_bindir}/ldif +%{_mandir}/man1/ldif.1.gz +%{_bindir}/migratecred +%{_mandir}/man1/migratecred.1.gz +%{_bindir}/mmldif +%{_mandir}/man1/mmldif.1.gz +%{_bindir}/rsearch +%{_mandir}/man1/rsearch.1.gz +%{_libexecdir}/%{pkgname}/ds_selinux_enabled +%{_libexecdir}/%{pkgname}/ds_selinux_port_query +%config(noreplace)%{_sysconfdir}/%{pkgname}/config/template-initconfig +%{_mandir}/man5/template-initconfig.5.gz +%{_datadir}/%{pkgname}/properties/*.res +%{_datadir}/%{pkgname}/script-templates +%{_datadir}/%{pkgname}/updates +%{_sbindir}/ldif2ldap +%{_mandir}/man8/ldif2ldap.8.gz +%{_sbindir}/bak2db +%{_mandir}/man8/bak2db.8.gz +%{_sbindir}/db2bak +%{_mandir}/man8/db2bak.8.gz +%{_sbindir}/db2index +%{_mandir}/man8/db2index.8.gz +%{_sbindir}/db2ldif +%{_mandir}/man8/db2ldif.8.gz +%{_sbindir}/dbverify +%{_mandir}/man8/dbverify.8.gz +%{_sbindir}/ldif2db +%{_mandir}/man8/ldif2db.8.gz +%{_sbindir}/restart-dirsrv +%{_mandir}/man8/restart-dirsrv.8.gz +%{_sbindir}/start-dirsrv +%{_mandir}/man8/start-dirsrv.8.gz +%{_sbindir}/status-dirsrv +%{_mandir}/man8/status-dirsrv.8.gz +%{_sbindir}/stop-dirsrv +%{_mandir}/man8/stop-dirsrv.8.gz +%{_sbindir}/upgradedb +%{_mandir}/man8/upgradedb.8.gz +%{_sbindir}/vlvindex +%{_mandir}/man8/vlvindex.8.gz +%{_sbindir}/monitor +%{_mandir}/man8/monitor.8.gz +%{_sbindir}/dbmon.sh +%{_mandir}/man8/dbmon.sh.8.gz +%{_sbindir}/dn2rdn +%{_mandir}/man8/dn2rdn.8.gz +%{_sbindir}/restoreconfig +%{_mandir}/man8/restoreconfig.8.gz +%{_sbindir}/saveconfig +%{_mandir}/man8/saveconfig.8.gz +%{_sbindir}/suffix2instance +%{_mandir}/man8/suffix2instance.8.gz +%{_sbindir}/upgradednformat +%{_mandir}/man8/upgradednformat.8.gz +%{_mandir}/man1/dbgen.pl.1.gz +%{_bindir}/repl-monitor +%{_mandir}/man1/repl-monitor.1.gz +%{_bindir}/repl-monitor.pl +%{_mandir}/man1/repl-monitor.pl.1.gz +%{_bindir}/cl-dump +%{_mandir}/man1/cl-dump.1.gz +%{_bindir}/cl-dump.pl +%{_mandir}/man1/cl-dump.pl.1.gz +%{_bindir}/dbgen.pl +%{_mandir}/man8/bak2db.pl.8.gz +%{_sbindir}/bak2db.pl +%{_sbindir}/cleanallruv.pl +%{_mandir}/man8/cleanallruv.pl.8.gz +%{_sbindir}/db2bak.pl +%{_mandir}/man8/db2bak.pl.8.gz +%{_sbindir}/db2index.pl +%{_mandir}/man8/db2index.pl.8.gz +%{_sbindir}/db2ldif.pl +%{_mandir}/man8/db2ldif.pl.8.gz +%{_sbindir}/fixup-linkedattrs.pl +%{_mandir}/man8/fixup-linkedattrs.pl.8.gz +%{_sbindir}/fixup-memberof.pl +%{_mandir}/man8/fixup-memberof.pl.8.gz +%{_sbindir}/ldif2db.pl +%{_mandir}/man8/ldif2db.pl.8.gz +%{_sbindir}/migrate-ds.pl +%{_mandir}/man8/migrate-ds.pl.8.gz +%{_sbindir}/ns-accountstatus.pl +%{_mandir}/man8/ns-accountstatus.pl.8.gz +%{_sbindir}/ns-activate.pl +%{_mandir}/man8/ns-activate.pl.8.gz +%{_sbindir}/ns-inactivate.pl +%{_mandir}/man8/ns-inactivate.pl.8.gz +%{_sbindir}/ns-newpwpolicy.pl +%{_mandir}/man8/ns-newpwpolicy.pl.8.gz +%{_sbindir}/remove-ds.pl +%{_mandir}/man8/remove-ds.pl.8.gz +%{_sbindir}/schema-reload.pl +%{_mandir}/man8/schema-reload.pl.8.gz +%{_sbindir}/setup-ds.pl +%{_mandir}/man8/setup-ds.pl.8.gz +%{_sbindir}/syntax-validate.pl +%{_mandir}/man8/syntax-validate.pl.8.gz +%{_sbindir}/usn-tombstone-cleanup.pl +%{_mandir}/man8/usn-tombstone-cleanup.pl.8.gz +%{_sbindir}/verify-db.pl +%{_mandir}/man8/verify-db.pl.8.gz +%{_libdir}/%{pkgname}/perl +%endif + +%files snmp +%doc LICENSE LICENSE.GPLv3+ LICENSE.openssl README.devel +%config(noreplace)%{_sysconfdir}/%{pkgname}/config/ldap-agent.conf +%{_sbindir}/ldap-agent* +%{_mandir}/man1/ldap-agent.1.gz +%{_unitdir}/%{pkgname}-snmp.service + +%files -n python%{python3_pkgversion}-lib389 +%doc LICENSE LICENSE.GPLv3+ +%{python3_sitelib}/lib389* +%{_sbindir}/dsconf +%{_mandir}/man8/dsconf.8.gz +%{_sbindir}/dscreate +%{_mandir}/man8/dscreate.8.gz +%{_sbindir}/dsctl +%{_mandir}/man8/dsctl.8.gz +%{_sbindir}/dsidm +%{_mandir}/man8/dsidm.8.gz +%{_libexecdir}/%{pkgname}/dscontainer + +%files -n cockpit-389-ds -f cockpit.list +%{_datarootdir}/metainfo/389-console/org.port389.cockpit_console.metainfo.xml +%doc README.md + +%changelog +* Thu Aug 26 2021 Mark Reynolds - 1.4.3.23-10 +- Bump version to 1.4.3.23-10 +- Resolves: Bug 1997138 - LDAP server crashes when dnaInterval attribute is set to 0 + +* Wed Aug 25 2021 Mark Reynolds - 1.4.3.23-9 +- Bump version to 1.4.3.23-9 +- Resolves: Bug 1947044 - remove unsupported GOST password storage scheme + +* Thu Aug 19 2021 Mark Reynolds - 1.4.3.23-8 +- Bump version to 1.4.3.23-8 +- Resolves: Bug 1947044 - add missing patch for import result code +- Resolves: Bug 1944494 - support for RFC 4530 entryUUID attribute + +* Mon Jul 26 2021 Mark Reynolds - 1.4.3.23-7 +- Bump version to 1.4.3.23-7 +- Resolves: Bug 1983921 - persistent search returns entries even when an error is returned by content-sync-plugin + +* Fri Jul 16 2021 Mark Reynolds - 1.4.3.23-6 +- Bump version to 1.4.3.23-6 +- Resolves: Bug 1982787 - CRYPT password hash with asterisk allows any bind attempt to succeed + +* Thu Jul 15 2021 Mark Reynolds - 1.4.3.23-5 +- Bump version to 1.4.3.23-5 +- Resolves: Bug 1951020 - Internal unindexed searches in syncrepl +- Resolves: Bug 1978279 - ds-replcheck state output message has 'Master' instead of 'Supplier' + +* Tue Jun 29 2021 Mark Reynolds - 1.4.3.23-4 +- Bump version to 1.4.3.23-4 +- Resolves: Bug 1976906 - Instance crash at restart after changelog configuration +- Resolves: Bug 1480323 - ns-slapd crash at startup - Segmentation fault in strcmpi_fast() when the Referential Integrity log is manually edited +- Resolves: Bug 1967596 - Temporary password - add CLI and fix compiler errors + +* Thu Jun 17 2021 Mark Reynolds - 1.4.3.23-3 +- Bump version to 1.4.3.23-3 +- Resolves: Bug 1944494 - support for RFC 4530 entryUUID attribute +- Resolves: Bug 1967839 - ACIs are being evaluated against the Replication Manager account in a replication context +- Resolves: Bug 1970259 - A connection can be erroneously flagged as replication conn during evaluation of an aci with ip bind rule +- Resolves: Bug 1972590 - Large updates can reset the CLcache to the beginning of the changelog +- Resolves: Bug 1903221 - Memory leak in 389ds backend (Minor) + +* Sun May 30 2021 Mark Reynolds - 1.4.3.23-2 +- Bump version to 1.4.3.23-2 +- Resolves: Bug 1812286 - RFE - Monitor the current DB locks ( nsslapd-db-current-locks ) +- Resolves: Bug 1748441 - RFE - Schedule execution of "compactdb" at specific date/time +- Resolves: Bug 1938239 - RFE - Extend DNA plugin to support intervals sizes for subuids + +* Fri May 14 2021 Mark Reynolds - 1.4.3.23-1 +- Bump version to 1.4.3.23-1 +- Resolves: Bug 1947044 - Rebase 389 DS with 389-ds-base-1.4.3.23 for RHEL 8.5 +- Resolves: Bug 1850664 - RFE - Add an option for the Retro Changelog to ignore some attributes +- Resolves: Bug 1903221 - Memory leak in 389ds backend (Minor) +- Resolves: Bug 1898541 - Changelog cache can upload updates from a wrong starting point (CSN) +- Resolves: Bug 1889562 - client psearch with multiple threads hangs if nsslapd-maxthreadsperconn is under sized +- Resolves: Bug 1924848 - Negative wtime on ldapcompare +- Resolves: Bug 1895460 - RFE - Log an additional message if the server certificate nickname doesn't match nsSSLPersonalitySSL value +- Resolves: Bug 1897614 - Performance search rate: change entry cache monitor to recursive pthread mutex +- Resolves: Bug 1939607 - hang because of incorrect accounting of readers in vattr rwlock +- Resolves: Bug 1626633 - [RFE] DS - Update the password policy to support a Temporary Password with expiration +- Resolves: Bug 1952804 - CVE-2021-3514 389-ds:1.4/389-ds-base: sync_repl NULL pointer dereference in sync_create_state_control() +