Compare commits
No commits in common. "c8-stream-1.4" and "c9-beta" have entirely different histories.
c8-stream-
...
c9-beta
@ -1,3 +1,3 @@
|
||||
bd9aab32d9cbf9231058d585479813f3420dc872 SOURCES/389-ds-base-1.4.3.39.tar.bz2
|
||||
14e38ca3a1851bec5023c191a756c3c58b15f5e0 SOURCES/389-ds-base-2.8.0.tar.bz2
|
||||
1c8f2d0dfbf39fa8cd86363bf3314351ab21f8d4 SOURCES/jemalloc-5.3.0.tar.bz2
|
||||
8d3275209f2f8e1a69053340930ad1fb037d61fb SOURCES/vendor-1.4.3.39-3.tar.gz
|
||||
04340b73d0c8dcc03e2fd08948334f51ffbd49d7 SOURCES/vendor-2.8.0-5.tar.gz
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,3 +1,3 @@
|
||||
SOURCES/389-ds-base-1.4.3.39.tar.bz2
|
||||
SOURCES/389-ds-base-2.8.0.tar.bz2
|
||||
SOURCES/jemalloc-5.3.0.tar.bz2
|
||||
SOURCES/vendor-1.4.3.39-3.tar.gz
|
||||
SOURCES/vendor-2.8.0-5.tar.gz
|
||||
|
||||
@ -0,0 +1,80 @@
|
||||
From 94a52cd03a6fcccf78a4d7e5c4adc65fb777fcfb Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Mon, 10 Nov 2025 13:20:28 +0100
|
||||
Subject: [PATCH] Issue 7049 - RetroCL plugin generates invalid LDIF
|
||||
|
||||
Bug Description:
|
||||
When a replicated modification marked with LDAP_MOD_IGNORE is logged,
|
||||
`changes` attribute contains invalid LDIF:
|
||||
|
||||
```
|
||||
replace: modifiersName
|
||||
modifiersName: cn=MemberOf Plugin,cn=plugins,cn=config
|
||||
-
|
||||
modifyTimestamp: 20250903092211Z
|
||||
-
|
||||
```
|
||||
Line `replace: modifyTimestamp` is missing.
|
||||
|
||||
A similar issue is present in audit log:
|
||||
```
|
||||
time: 20251031064114
|
||||
dn: ou=tuser,dc=example,dc=com
|
||||
result: 0
|
||||
changetype: modify
|
||||
add: objectClass
|
||||
objectClass: nsMemberOf
|
||||
-
|
||||
replace: modifiersName
|
||||
modifiersName: cn=MemberOf Plugin,cn=plugins,cn=config
|
||||
-
|
||||
-
|
||||
```
|
||||
Dash separator is logged, while the operation is not.
|
||||
This issue is not present wheh JSON format is used.
|
||||
|
||||
Fix Description:
|
||||
* retrocl_po.c: add a default case to skip the entire modification if it
|
||||
has LDAP_MOD_IGNORE flag.
|
||||
* auditlog.c: write the dash separator only if operation type is not
|
||||
LDAP_MOD_IGNORE
|
||||
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/7049
|
||||
|
||||
Reviewed by: @progier389 (Thanks!)
|
||||
---
|
||||
ldap/servers/plugins/retrocl/retrocl_po.c | 3 +++
|
||||
ldap/servers/slapd/auditlog.c | 2 +-
|
||||
2 files changed, 4 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/ldap/servers/plugins/retrocl/retrocl_po.c b/ldap/servers/plugins/retrocl/retrocl_po.c
|
||||
index e447ccdfb..277711dd3 100644
|
||||
--- a/ldap/servers/plugins/retrocl/retrocl_po.c
|
||||
+++ b/ldap/servers/plugins/retrocl/retrocl_po.c
|
||||
@@ -99,6 +99,9 @@ make_changes_string(LDAPMod **ldm, const char **includeattrs)
|
||||
addlenstr(l, ldm[i]->mod_type);
|
||||
addlenstr(l, "\n");
|
||||
break;
|
||||
+ default:
|
||||
+ /* LDAP_MOD_IGNORE or unknown - skip this mod entirely */
|
||||
+ continue;
|
||||
}
|
||||
for (j = 0; ldm[i]->mod_bvalues != NULL &&
|
||||
ldm[i]->mod_bvalues[j] != NULL;
|
||||
diff --git a/ldap/servers/slapd/auditlog.c b/ldap/servers/slapd/auditlog.c
|
||||
index 32fcea38a..e2db40722 100644
|
||||
--- a/ldap/servers/slapd/auditlog.c
|
||||
+++ b/ldap/servers/slapd/auditlog.c
|
||||
@@ -853,8 +853,8 @@ write_audit_file(
|
||||
slapi_ch_free((void **)&buf);
|
||||
}
|
||||
}
|
||||
+ addlenstr(l, "-\n");
|
||||
}
|
||||
- addlenstr(l, "-\n");
|
||||
}
|
||||
break;
|
||||
|
||||
--
|
||||
2.52.0
|
||||
|
||||
@ -1,119 +0,0 @@
|
||||
From dddb14210b402f317e566b6387c76a8e659bf7fa Mon Sep 17 00:00:00 2001
|
||||
From: progier389 <progier@redhat.com>
|
||||
Date: Tue, 14 Feb 2023 13:34:10 +0100
|
||||
Subject: [PATCH 1/2] issue 5647 - covscan: memory leak in audit log when
|
||||
adding entries (#5650)
|
||||
|
||||
covscan reported an issue about "vals" variable in auditlog.c:231 and indeed a charray_free is missing.
|
||||
Issue: 5647
|
||||
Reviewed by: @mreynolds389, @droideck
|
||||
---
|
||||
ldap/servers/slapd/auditlog.c | 71 +++++++++++++++++++----------------
|
||||
1 file changed, 38 insertions(+), 33 deletions(-)
|
||||
|
||||
diff --git a/ldap/servers/slapd/auditlog.c b/ldap/servers/slapd/auditlog.c
|
||||
index 68cbc674d..3128e0497 100644
|
||||
--- a/ldap/servers/slapd/auditlog.c
|
||||
+++ b/ldap/servers/slapd/auditlog.c
|
||||
@@ -177,6 +177,40 @@ write_auditfail_log_entry(Slapi_PBlock *pb)
|
||||
slapi_ch_free_string(&audit_config);
|
||||
}
|
||||
|
||||
+/*
|
||||
+ * Write the attribute values to the audit log as "comments"
|
||||
+ *
|
||||
+ * Slapi_Attr *entry - the attribute begin logged.
|
||||
+ * char *attrname - the attribute name.
|
||||
+ * lenstr *l - the audit log buffer
|
||||
+ *
|
||||
+ * Resulting output in the log:
|
||||
+ *
|
||||
+ * #ATTR: VALUE
|
||||
+ * #ATTR: VALUE
|
||||
+ */
|
||||
+static void
|
||||
+log_entry_attr(Slapi_Attr *entry_attr, char *attrname, lenstr *l)
|
||||
+{
|
||||
+ Slapi_Value **vals = attr_get_present_values(entry_attr);
|
||||
+ for(size_t i = 0; vals && vals[i]; i++) {
|
||||
+ char log_val[256] = "";
|
||||
+ const struct berval *bv = slapi_value_get_berval(vals[i]);
|
||||
+ if (bv->bv_len >= 256) {
|
||||
+ strncpy(log_val, bv->bv_val, 252);
|
||||
+ strcpy(log_val+252, "...");
|
||||
+ } else {
|
||||
+ strncpy(log_val, bv->bv_val, bv->bv_len);
|
||||
+ log_val[bv->bv_len] = 0;
|
||||
+ }
|
||||
+ addlenstr(l, "#");
|
||||
+ addlenstr(l, attrname);
|
||||
+ addlenstr(l, ": ");
|
||||
+ addlenstr(l, log_val);
|
||||
+ addlenstr(l, "\n");
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
/*
|
||||
* Write "requested" attributes from the entry to the audit log as "comments"
|
||||
*
|
||||
@@ -212,21 +246,9 @@ add_entry_attrs(Slapi_Entry *entry, lenstr *l)
|
||||
for (req_attr = ldap_utf8strtok_r(display_attrs, ", ", &last); req_attr;
|
||||
req_attr = ldap_utf8strtok_r(NULL, ", ", &last))
|
||||
{
|
||||
- char **vals = slapi_entry_attr_get_charray(entry, req_attr);
|
||||
- for(size_t i = 0; vals && vals[i]; i++) {
|
||||
- char log_val[256] = {0};
|
||||
-
|
||||
- if (strlen(vals[i]) > 256) {
|
||||
- strncpy(log_val, vals[i], 252);
|
||||
- strcat(log_val, "...");
|
||||
- } else {
|
||||
- strcpy(log_val, vals[i]);
|
||||
- }
|
||||
- addlenstr(l, "#");
|
||||
- addlenstr(l, req_attr);
|
||||
- addlenstr(l, ": ");
|
||||
- addlenstr(l, log_val);
|
||||
- addlenstr(l, "\n");
|
||||
+ slapi_entry_attr_find(entry, req_attr, &entry_attr);
|
||||
+ if (entry_attr) {
|
||||
+ log_entry_attr(entry_attr, req_attr, l);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -234,7 +256,6 @@ add_entry_attrs(Slapi_Entry *entry, lenstr *l)
|
||||
for (; entry_attr; entry_attr = entry_attr->a_next) {
|
||||
Slapi_Value **vals = attr_get_present_values(entry_attr);
|
||||
char *attr = NULL;
|
||||
- const char *val = NULL;
|
||||
|
||||
slapi_attr_get_type(entry_attr, &attr);
|
||||
if (strcmp(attr, PSEUDO_ATTR_UNHASHEDUSERPASSWORD) == 0) {
|
||||
@@ -251,23 +272,7 @@ add_entry_attrs(Slapi_Entry *entry, lenstr *l)
|
||||
addlenstr(l, ": ****************************\n");
|
||||
continue;
|
||||
}
|
||||
-
|
||||
- for(size_t i = 0; vals && vals[i]; i++) {
|
||||
- char log_val[256] = {0};
|
||||
-
|
||||
- val = slapi_value_get_string(vals[i]);
|
||||
- if (strlen(val) > 256) {
|
||||
- strncpy(log_val, val, 252);
|
||||
- strcat(log_val, "...");
|
||||
- } else {
|
||||
- strcpy(log_val, val);
|
||||
- }
|
||||
- addlenstr(l, "#");
|
||||
- addlenstr(l, attr);
|
||||
- addlenstr(l, ": ");
|
||||
- addlenstr(l, log_val);
|
||||
- addlenstr(l, "\n");
|
||||
- }
|
||||
+ log_entry_attr(entry_attr, attr, l);
|
||||
}
|
||||
}
|
||||
slapi_ch_free_string(&display_attrs);
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@ -1,27 +0,0 @@
|
||||
From be7c2b82958e91ce08775bf6b5da3c311d3b00e5 Mon Sep 17 00:00:00 2001
|
||||
From: progier389 <progier@redhat.com>
|
||||
Date: Mon, 20 Feb 2023 16:14:05 +0100
|
||||
Subject: [PATCH 2/2] Issue 5647 - Fix unused variable warning from previous
|
||||
commit (#5670)
|
||||
|
||||
* issue 5647 - memory leak in audit log when adding entries
|
||||
* Issue 5647 - Fix unused variable warning from previous commit
|
||||
---
|
||||
ldap/servers/slapd/auditlog.c | 1 -
|
||||
1 file changed, 1 deletion(-)
|
||||
|
||||
diff --git a/ldap/servers/slapd/auditlog.c b/ldap/servers/slapd/auditlog.c
|
||||
index 3128e0497..0597ecc6f 100644
|
||||
--- a/ldap/servers/slapd/auditlog.c
|
||||
+++ b/ldap/servers/slapd/auditlog.c
|
||||
@@ -254,7 +254,6 @@ add_entry_attrs(Slapi_Entry *entry, lenstr *l)
|
||||
} else {
|
||||
/* Return all attributes */
|
||||
for (; entry_attr; entry_attr = entry_attr->a_next) {
|
||||
- Slapi_Value **vals = attr_get_present_values(entry_attr);
|
||||
char *attr = NULL;
|
||||
|
||||
slapi_attr_get_type(entry_attr, &attr);
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
From 2da3ad24ce028c6b68abef0d115dae4215de558d Mon Sep 17 00:00:00 2001
|
||||
From 67118939bd2b51e53f7284c2058a43744d1dba76 Mon Sep 17 00:00:00 2001
|
||||
From: tbordaz <tbordaz@redhat.com>
|
||||
Date: Wed, 7 Jan 2026 11:21:12 +0100
|
||||
Subject: [PATCH] Issue 7096 - During replication online total init the
|
||||
@ -21,15 +21,15 @@ Reviewed by: Mark Reynolds, Pierre Rogier (Thanks !!)
|
||||
---
|
||||
ldap/servers/slapd/back-ldbm/back-ldbm.h | 12 ++
|
||||
ldap/servers/slapd/back-ldbm/idl_common.c | 163 ++++++++++++++++++
|
||||
ldap/servers/slapd/back-ldbm/idl_new.c | 29 ++--
|
||||
ldap/servers/slapd/back-ldbm/idl_new.c | 30 ++--
|
||||
.../servers/slapd/back-ldbm/proto-back-ldbm.h | 3 +
|
||||
4 files changed, 189 insertions(+), 18 deletions(-)
|
||||
4 files changed, 189 insertions(+), 19 deletions(-)
|
||||
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/back-ldbm.h b/ldap/servers/slapd/back-ldbm/back-ldbm.h
|
||||
index d17ec644b..462d1763c 100644
|
||||
index 36aa432e0..5e4988782 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/back-ldbm.h
|
||||
+++ b/ldap/servers/slapd/back-ldbm/back-ldbm.h
|
||||
@@ -285,6 +285,18 @@ typedef struct _idlist_set
|
||||
@@ -281,6 +281,18 @@ typedef struct _idlist_set
|
||||
#define INDIRECT_BLOCK(idl) ((idl)->b_nids == INDBLOCK)
|
||||
#define IDL_NIDS(idl) (idl ? (idl)->b_nids : (NIDS)0)
|
||||
|
||||
@ -49,7 +49,7 @@ index d17ec644b..462d1763c 100644
|
||||
|
||||
/* small hashtable implementation used in the entry cache -- the table
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/idl_common.c b/ldap/servers/slapd/back-ldbm/idl_common.c
|
||||
index e609d349e..3827e59ea 100644
|
||||
index fcb0ece4b..fdc9b4e67 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/idl_common.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/idl_common.c
|
||||
@@ -172,6 +172,169 @@ idl_min(IDList *a, IDList *b)
|
||||
@ -223,18 +223,26 @@ index e609d349e..3827e59ea 100644
|
||||
idl_id_is_in_idlist(IDList *idl, ID id)
|
||||
{
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/idl_new.c b/ldap/servers/slapd/back-ldbm/idl_new.c
|
||||
index 916e0781e..212f31b7b 100644
|
||||
index 5fbcaff2e..2d978353f 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/idl_new.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/idl_new.c
|
||||
@@ -378,6 +378,7 @@ idl_new_range_fetch(
|
||||
idl_range_id_pair *leftover = NULL;
|
||||
@@ -417,7 +417,6 @@ idl_new_range_fetch(
|
||||
{
|
||||
int ret = 0;
|
||||
int ret2 = 0;
|
||||
- int idl_rc = 0;
|
||||
dbi_cursor_t cursor = {0};
|
||||
IDList *idl = NULL;
|
||||
dbi_val_t cur_key = {0};
|
||||
@@ -436,6 +435,7 @@ idl_new_range_fetch(
|
||||
size_t leftoverlen = 32;
|
||||
size_t leftovercnt = 0;
|
||||
char *index_id = get_index_name(be, db, ai);
|
||||
+ IdRange_t *idrange_list = NULL;
|
||||
|
||||
|
||||
if (NULL == flag_err) {
|
||||
return NULL;
|
||||
@@ -528,10 +529,12 @@ idl_new_range_fetch(
|
||||
@@ -578,10 +578,12 @@ idl_new_range_fetch(
|
||||
* found entry is the one from the suffix
|
||||
*/
|
||||
suffix = key;
|
||||
@ -250,7 +258,7 @@ index 916e0781e..212f31b7b 100644
|
||||
} else {
|
||||
/* Otherwise, keep the {key,id} in leftover array */
|
||||
if (!leftover) {
|
||||
@@ -546,13 +549,7 @@ idl_new_range_fetch(
|
||||
@@ -596,13 +598,7 @@ idl_new_range_fetch(
|
||||
leftovercnt++;
|
||||
}
|
||||
} else {
|
||||
@ -265,7 +273,7 @@ index 916e0781e..212f31b7b 100644
|
||||
}
|
||||
|
||||
count++;
|
||||
@@ -655,21 +652,17 @@ error:
|
||||
@@ -695,21 +691,17 @@ error:
|
||||
|
||||
while(remaining > 0) {
|
||||
for (size_t i = 0; i < leftovercnt; i++) {
|
||||
@ -289,13 +297,13 @@ index 916e0781e..212f31b7b 100644
|
||||
slapi_ch_free((void **)&leftover);
|
||||
+ idrange_free(&idrange_list);
|
||||
}
|
||||
return idl;
|
||||
}
|
||||
slapi_log_err(SLAPI_LOG_FILTER, "idl_new_range_fetch",
|
||||
"Found %d candidates; error code is: %d\n",
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/proto-back-ldbm.h b/ldap/servers/slapd/back-ldbm/proto-back-ldbm.h
|
||||
index 157788fa4..1d93b6402 100644
|
||||
index 9a7ffee37..f16c37d73 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/proto-back-ldbm.h
|
||||
+++ b/ldap/servers/slapd/back-ldbm/proto-back-ldbm.h
|
||||
@@ -229,6 +229,9 @@ ID idl_firstid(IDList *idl);
|
||||
@@ -217,6 +217,9 @@ ID idl_firstid(IDList *idl);
|
||||
ID idl_nextid(IDList *idl, ID id);
|
||||
int idl_init_private(backend *be, struct attrinfo *a);
|
||||
int idl_release_private(struct attrinfo *a);
|
||||
@ -1,147 +0,0 @@
|
||||
From 692c4cec6cc5c0086cf58f83bcfa690c766c9887 Mon Sep 17 00:00:00 2001
|
||||
From: Thierry Bordaz <tbordaz@redhat.com>
|
||||
Date: Fri, 2 Feb 2024 14:14:28 +0100
|
||||
Subject: [PATCH] Issue 5407 - sync_repl crashes if enabled while dynamic
|
||||
plugin is enabled (#5411)
|
||||
|
||||
Bug description:
|
||||
When dynamic plugin is enabled, if a MOD enables sync_repl plugin
|
||||
then sync_repl init function registers the postop callback
|
||||
that will be called for the MOD itself while the preop
|
||||
has not been called.
|
||||
postop expects preop to be called and so primary operation
|
||||
to be set. When it is not set it crashes
|
||||
|
||||
Fix description:
|
||||
If the primary operation is not set, just return
|
||||
|
||||
relates: #5407
|
||||
---
|
||||
.../suites/syncrepl_plugin/basic_test.py | 68 +++++++++++++++++++
|
||||
ldap/servers/plugins/sync/sync_persist.c | 23 ++++++-
|
||||
2 files changed, 90 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/syncrepl_plugin/basic_test.py b/dirsrvtests/tests/suites/syncrepl_plugin/basic_test.py
|
||||
index eb3770b78..cdf35eeaa 100644
|
||||
--- a/dirsrvtests/tests/suites/syncrepl_plugin/basic_test.py
|
||||
+++ b/dirsrvtests/tests/suites/syncrepl_plugin/basic_test.py
|
||||
@@ -592,6 +592,74 @@ def test_sync_repl_cenotaph(topo_m2, request):
|
||||
|
||||
request.addfinalizer(fin)
|
||||
|
||||
+def test_sync_repl_dynamic_plugin(topology, request):
|
||||
+ """Test sync_repl with dynamic plugin
|
||||
+
|
||||
+ :id: d4f84913-c18a-459f-8525-110f610ca9e6
|
||||
+ :setup: install a standalone instance
|
||||
+ :steps:
|
||||
+ 1. reset instance to standard (no retroCL, no sync_repl, no dynamic plugin)
|
||||
+ 2. Enable dynamic plugin
|
||||
+ 3. Enable retroCL/content_sync
|
||||
+ 4. Establish a sync_repl req
|
||||
+ :expectedresults:
|
||||
+ 1. Should succeeds
|
||||
+ 2. Should succeeds
|
||||
+ 3. Should succeeds
|
||||
+ 4. Should succeeds
|
||||
+ """
|
||||
+
|
||||
+ # Reset the instance in a default config
|
||||
+ # Disable content sync plugin
|
||||
+ topology.standalone.plugins.disable(name=PLUGIN_REPL_SYNC)
|
||||
+
|
||||
+ # Disable retro changelog
|
||||
+ topology.standalone.plugins.disable(name=PLUGIN_RETRO_CHANGELOG)
|
||||
+
|
||||
+ # Disable dynamic plugins
|
||||
+ topology.standalone.modify_s(DN_CONFIG, [(ldap.MOD_REPLACE, 'nsslapd-dynamic-plugins', b'off')])
|
||||
+ topology.standalone.restart()
|
||||
+
|
||||
+ # Now start the test
|
||||
+ # Enable dynamic plugins
|
||||
+ try:
|
||||
+ topology.standalone.modify_s(DN_CONFIG, [(ldap.MOD_REPLACE, 'nsslapd-dynamic-plugins', b'on')])
|
||||
+ except ldap.LDAPError as e:
|
||||
+ log.error('Failed to enable dynamic plugin! {}'.format(e.args[0]['desc']))
|
||||
+ assert False
|
||||
+
|
||||
+ # Enable retro changelog
|
||||
+ topology.standalone.plugins.enable(name=PLUGIN_RETRO_CHANGELOG)
|
||||
+
|
||||
+ # Enbale content sync plugin
|
||||
+ topology.standalone.plugins.enable(name=PLUGIN_REPL_SYNC)
|
||||
+
|
||||
+ # create a sync repl client and wait 5 seconds to be sure it is running
|
||||
+ sync_repl = Sync_persist(topology.standalone)
|
||||
+ sync_repl.start()
|
||||
+ time.sleep(5)
|
||||
+
|
||||
+ # create users
|
||||
+ users = UserAccounts(topology.standalone, DEFAULT_SUFFIX)
|
||||
+ users_set = []
|
||||
+ for i in range(10001, 10004):
|
||||
+ users_set.append(users.create_test_user(uid=i))
|
||||
+
|
||||
+ time.sleep(10)
|
||||
+ # delete users, that automember/memberof will generate nested updates
|
||||
+ for user in users_set:
|
||||
+ user.delete()
|
||||
+ # stop the server to get the sync_repl result set (exit from while loop).
|
||||
+ # Only way I found to acheive that.
|
||||
+ # and wait a bit to let sync_repl thread time to set its result before fetching it.
|
||||
+ topology.standalone.stop()
|
||||
+ sync_repl.get_result()
|
||||
+ sync_repl.join()
|
||||
+ log.info('test_sync_repl_dynamic_plugin: PASS\n')
|
||||
+
|
||||
+ # Success
|
||||
+ log.info('Test complete')
|
||||
+
|
||||
def test_sync_repl_invalid_cookie(topology, request):
|
||||
"""Test sync_repl with invalid cookie
|
||||
|
||||
diff --git a/ldap/servers/plugins/sync/sync_persist.c b/ldap/servers/plugins/sync/sync_persist.c
|
||||
index d2210b64c..283607361 100644
|
||||
--- a/ldap/servers/plugins/sync/sync_persist.c
|
||||
+++ b/ldap/servers/plugins/sync/sync_persist.c
|
||||
@@ -156,6 +156,17 @@ ignore_op_pl(Slapi_PBlock *pb)
|
||||
* This is the same for ident
|
||||
*/
|
||||
prim_op = get_thread_primary_op();
|
||||
+ if (prim_op == NULL) {
|
||||
+ /* This can happen if the PRE_OP (sync_update_persist_betxn_pre_op) was not called.
|
||||
+ * The only known case it happens is with dynamic plugin enabled and an
|
||||
+ * update that enable the sync_repl plugin. In such case sync_repl registers
|
||||
+ * the postop (sync_update_persist_op) that is called while the preop was not called
|
||||
+ */
|
||||
+ slapi_log_err(SLAPI_LOG_PLUGIN, SYNC_PLUGIN_SUBSYSTEM,
|
||||
+ "ignore_op_pl - Operation without primary op set (0x%lx)\n",
|
||||
+ (ulong) op);
|
||||
+ return;
|
||||
+ }
|
||||
ident = sync_persist_get_operation_extension(pb);
|
||||
|
||||
if (ident) {
|
||||
@@ -232,8 +243,18 @@ sync_update_persist_op(Slapi_PBlock *pb, Slapi_Entry *e, Slapi_Entry *eprev, ber
|
||||
|
||||
|
||||
prim_op = get_thread_primary_op();
|
||||
+ if (prim_op == NULL) {
|
||||
+ /* This can happen if the PRE_OP (sync_update_persist_betxn_pre_op) was not called.
|
||||
+ * The only known case it happens is with dynamic plugin enabled and an
|
||||
+ * update that enable the sync_repl plugin. In such case sync_repl registers
|
||||
+ * the postop (sync_update_persist_op) that is called while the preop was not called
|
||||
+ */
|
||||
+ slapi_log_err(SLAPI_LOG_PLUGIN, SYNC_PLUGIN_SUBSYSTEM,
|
||||
+ "sync_update_persist_op - Operation without primary op set (0x%lx)\n",
|
||||
+ (ulong) pb_op);
|
||||
+ return;
|
||||
+ }
|
||||
ident = sync_persist_get_operation_extension(pb);
|
||||
- PR_ASSERT(prim_op);
|
||||
|
||||
if ((ident == NULL) && operation_is_flag_set(pb_op, OP_FLAG_NOOP)) {
|
||||
/* This happens for URP (add cenotaph, fixup rename, tombstone resurrect)
|
||||
--
|
||||
2.43.0
|
||||
|
||||
765
SOURCES/0003-Issue-Revise-paged-result-search-locking.patch
Normal file
765
SOURCES/0003-Issue-Revise-paged-result-search-locking.patch
Normal file
@ -0,0 +1,765 @@
|
||||
From a0af5ab79ddad7944687e31f16bb2a667c800958 Mon Sep 17 00:00:00 2001
|
||||
From: Mark Reynolds <mreynolds@redhat.com>
|
||||
Date: Wed, 7 Jan 2026 16:55:27 -0500
|
||||
Subject: [PATCH] Issue - Revise paged result search locking
|
||||
|
||||
Description:
|
||||
|
||||
Move to a single lock approach verses having two locks. This will impact
|
||||
concurrency when multiple async paged result searches are done on the same
|
||||
connection, but it simplifies the code and avoids race conditions and
|
||||
deadlocks.
|
||||
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/7118
|
||||
|
||||
Reviewed by: progier & tbordaz (Thanks!!)
|
||||
---
|
||||
ldap/servers/slapd/abandon.c | 2 +-
|
||||
ldap/servers/slapd/opshared.c | 60 ++++----
|
||||
ldap/servers/slapd/pagedresults.c | 228 +++++++++++++++++++-----------
|
||||
ldap/servers/slapd/proto-slap.h | 26 ++--
|
||||
ldap/servers/slapd/slap.h | 5 +-
|
||||
5 files changed, 187 insertions(+), 134 deletions(-)
|
||||
|
||||
diff --git a/ldap/servers/slapd/abandon.c b/ldap/servers/slapd/abandon.c
|
||||
index 2dd1ee320..d7c1f8868 100644
|
||||
--- a/ldap/servers/slapd/abandon.c
|
||||
+++ b/ldap/servers/slapd/abandon.c
|
||||
@@ -141,7 +141,7 @@ do_abandon(Slapi_PBlock *pb)
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&(pb_conn->c_mutex));
|
||||
- if (0 == pagedresults_free_one_msgid(pb_conn, id, pageresult_lock_get_addr(pb_conn))) {
|
||||
+ if (0 == pagedresults_free_one_msgid(pb_conn, id, PR_NOT_LOCKED)) {
|
||||
slapi_log_access(LDAP_DEBUG_STATS, "conn=%" PRIu64
|
||||
" op=%d ABANDON targetop=Simple Paged Results msgid=%d\n",
|
||||
pb_conn->c_connid, pb_op->o_opid, id);
|
||||
diff --git a/ldap/servers/slapd/opshared.c b/ldap/servers/slapd/opshared.c
|
||||
index 03ed60981..bb474ef3d 100644
|
||||
--- a/ldap/servers/slapd/opshared.c
|
||||
+++ b/ldap/servers/slapd/opshared.c
|
||||
@@ -545,8 +545,8 @@ op_shared_search(Slapi_PBlock *pb, int send_result)
|
||||
be = be_list[index];
|
||||
}
|
||||
}
|
||||
- pr_search_result = pagedresults_get_search_result(pb_conn, operation, 0 /*not locked*/, pr_idx);
|
||||
- estimate = pagedresults_get_search_result_set_size_estimate(pb_conn, operation, pr_idx);
|
||||
+ pr_search_result = pagedresults_get_search_result(pb_conn, operation, PR_NOT_LOCKED, pr_idx);
|
||||
+ estimate = pagedresults_get_search_result_set_size_estimate(pb_conn, operation, PR_NOT_LOCKED, pr_idx);
|
||||
/* Set operation note flags as required. */
|
||||
if (pagedresults_get_unindexed(pb_conn, operation, pr_idx)) {
|
||||
slapi_pblock_set_flag_operation_notes(pb, SLAPI_OP_NOTE_UNINDEXED);
|
||||
@@ -592,14 +592,7 @@ op_shared_search(Slapi_PBlock *pb, int send_result)
|
||||
int32_t tlimit;
|
||||
slapi_pblock_get(pb, SLAPI_SEARCH_TIMELIMIT, &tlimit);
|
||||
pagedresults_set_timelimit(pb_conn, operation, (time_t)tlimit, pr_idx);
|
||||
- /* When using this mutex in conjunction with the main paged
|
||||
- * result lock, you must do so in this order:
|
||||
- *
|
||||
- * --> pagedresults_lock()
|
||||
- * --> pagedresults_mutex
|
||||
- * <-- pagedresults_mutex
|
||||
- * <-- pagedresults_unlock()
|
||||
- */
|
||||
+ /* IMPORTANT: Never acquire pagedresults_mutex when holding c_mutex. */
|
||||
pagedresults_mutex = pageresult_lock_get_addr(pb_conn);
|
||||
}
|
||||
|
||||
@@ -716,17 +709,15 @@ op_shared_search(Slapi_PBlock *pb, int send_result)
|
||||
if (op_is_pagedresults(operation) && pr_search_result) {
|
||||
void *sr = NULL;
|
||||
/* PAGED RESULTS and already have the search results from the prev op */
|
||||
- pagedresults_lock(pb_conn, pr_idx);
|
||||
/*
|
||||
* In async paged result case, the search result might be released
|
||||
* by other theads. We need to double check it in the locked region.
|
||||
*/
|
||||
pthread_mutex_lock(pagedresults_mutex);
|
||||
- pr_search_result = pagedresults_get_search_result(pb_conn, operation, 1 /*locked*/, pr_idx);
|
||||
+ pr_search_result = pagedresults_get_search_result(pb_conn, operation, PR_LOCKED, pr_idx);
|
||||
if (pr_search_result) {
|
||||
- if (pagedresults_is_abandoned_or_notavailable(pb_conn, 1 /*locked*/, pr_idx)) {
|
||||
+ if (pagedresults_is_abandoned_or_notavailable(pb_conn, PR_LOCKED, pr_idx)) {
|
||||
pthread_mutex_unlock(pagedresults_mutex);
|
||||
- pagedresults_unlock(pb_conn, pr_idx);
|
||||
/* Previous operation was abandoned and the simplepaged object is not in use. */
|
||||
send_ldap_result(pb, 0, NULL, "Simple Paged Results Search abandoned", 0, NULL);
|
||||
rc = LDAP_SUCCESS;
|
||||
@@ -737,14 +728,13 @@ op_shared_search(Slapi_PBlock *pb, int send_result)
|
||||
|
||||
/* search result could be reset in the backend/dse */
|
||||
slapi_pblock_get(pb, SLAPI_SEARCH_RESULT_SET, &sr);
|
||||
- pagedresults_set_search_result(pb_conn, operation, sr, 1 /*locked*/, pr_idx);
|
||||
+ pagedresults_set_search_result(pb_conn, operation, sr, PR_LOCKED, pr_idx);
|
||||
}
|
||||
} else {
|
||||
pr_stat = PAGEDRESULTS_SEARCH_END;
|
||||
rc = LDAP_SUCCESS;
|
||||
}
|
||||
pthread_mutex_unlock(pagedresults_mutex);
|
||||
- pagedresults_unlock(pb_conn, pr_idx);
|
||||
|
||||
if ((PAGEDRESULTS_SEARCH_END == pr_stat) || (0 == pnentries)) {
|
||||
/* no more entries to send in the backend */
|
||||
@@ -762,22 +752,22 @@ op_shared_search(Slapi_PBlock *pb, int send_result)
|
||||
}
|
||||
pagedresults_set_response_control(pb, 0, estimate,
|
||||
curr_search_count, pr_idx);
|
||||
- if (pagedresults_get_with_sort(pb_conn, operation, pr_idx)) {
|
||||
+ if (pagedresults_get_with_sort(pb_conn, operation, PR_NOT_LOCKED, pr_idx)) {
|
||||
sort_make_sort_response_control(pb, CONN_GET_SORT_RESULT_CODE, NULL);
|
||||
}
|
||||
pagedresults_set_search_result_set_size_estimate(pb_conn,
|
||||
operation,
|
||||
- estimate, pr_idx);
|
||||
+ estimate, PR_NOT_LOCKED, pr_idx);
|
||||
if (PAGEDRESULTS_SEARCH_END == pr_stat) {
|
||||
- pagedresults_lock(pb_conn, pr_idx);
|
||||
+ pthread_mutex_lock(pagedresults_mutex);
|
||||
slapi_pblock_set(pb, SLAPI_SEARCH_RESULT_SET, NULL);
|
||||
- if (!pagedresults_is_abandoned_or_notavailable(pb_conn, 0 /*not locked*/, pr_idx)) {
|
||||
- pagedresults_free_one(pb_conn, operation, pr_idx);
|
||||
+ if (!pagedresults_is_abandoned_or_notavailable(pb_conn, PR_LOCKED, pr_idx)) {
|
||||
+ pagedresults_free_one(pb_conn, operation, PR_LOCKED, pr_idx);
|
||||
}
|
||||
- pagedresults_unlock(pb_conn, pr_idx);
|
||||
+ pthread_mutex_unlock(pagedresults_mutex);
|
||||
if (next_be) {
|
||||
/* no more entries, but at least another backend */
|
||||
- if (pagedresults_set_current_be(pb_conn, next_be, pr_idx, 0) < 0) {
|
||||
+ if (pagedresults_set_current_be(pb_conn, next_be, pr_idx, PR_NOT_LOCKED) < 0) {
|
||||
goto free_and_return;
|
||||
}
|
||||
}
|
||||
@@ -884,7 +874,7 @@ op_shared_search(Slapi_PBlock *pb, int send_result)
|
||||
}
|
||||
}
|
||||
pagedresults_set_search_result(pb_conn, operation, NULL, 1, pr_idx);
|
||||
- rc = pagedresults_set_current_be(pb_conn, NULL, pr_idx, 1);
|
||||
+ rc = pagedresults_set_current_be(pb_conn, NULL, pr_idx, PR_LOCKED);
|
||||
pthread_mutex_unlock(pagedresults_mutex);
|
||||
}
|
||||
|
||||
@@ -922,7 +912,7 @@ op_shared_search(Slapi_PBlock *pb, int send_result)
|
||||
pthread_mutex_lock(pagedresults_mutex);
|
||||
pagedresults_set_search_result(pb_conn, operation, NULL, 1, pr_idx);
|
||||
be->be_search_results_release(&sr);
|
||||
- rc = pagedresults_set_current_be(pb_conn, next_be, pr_idx, 1);
|
||||
+ rc = pagedresults_set_current_be(pb_conn, next_be, pr_idx, PR_LOCKED);
|
||||
pthread_mutex_unlock(pagedresults_mutex);
|
||||
pr_stat = PAGEDRESULTS_SEARCH_END; /* make sure stat is SEARCH_END */
|
||||
if (NULL == next_be) {
|
||||
@@ -935,23 +925,23 @@ op_shared_search(Slapi_PBlock *pb, int send_result)
|
||||
} else {
|
||||
curr_search_count = pnentries;
|
||||
slapi_pblock_get(pb, SLAPI_SEARCH_RESULT_SET_SIZE_ESTIMATE, &estimate);
|
||||
- pagedresults_lock(pb_conn, pr_idx);
|
||||
- if ((pagedresults_set_current_be(pb_conn, be, pr_idx, 0) < 0) ||
|
||||
- (pagedresults_set_search_result(pb_conn, operation, sr, 0, pr_idx) < 0) ||
|
||||
- (pagedresults_set_search_result_count(pb_conn, operation, curr_search_count, pr_idx) < 0) ||
|
||||
- (pagedresults_set_search_result_set_size_estimate(pb_conn, operation, estimate, pr_idx) < 0) ||
|
||||
- (pagedresults_set_with_sort(pb_conn, operation, with_sort, pr_idx) < 0)) {
|
||||
- pagedresults_unlock(pb_conn, pr_idx);
|
||||
+ pthread_mutex_lock(pagedresults_mutex);
|
||||
+ if ((pagedresults_set_current_be(pb_conn, be, pr_idx, PR_LOCKED) < 0) ||
|
||||
+ (pagedresults_set_search_result(pb_conn, operation, sr, PR_LOCKED, pr_idx) < 0) ||
|
||||
+ (pagedresults_set_search_result_count(pb_conn, operation, curr_search_count, PR_LOCKED, pr_idx) < 0) ||
|
||||
+ (pagedresults_set_search_result_set_size_estimate(pb_conn, operation, estimate, PR_LOCKED, pr_idx) < 0) ||
|
||||
+ (pagedresults_set_with_sort(pb_conn, operation, with_sort, PR_LOCKED, pr_idx) < 0)) {
|
||||
+ pthread_mutex_unlock(pagedresults_mutex);
|
||||
cache_return_target_entry(pb, be, operation);
|
||||
goto free_and_return;
|
||||
}
|
||||
- pagedresults_unlock(pb_conn, pr_idx);
|
||||
+ pthread_mutex_unlock(pagedresults_mutex);
|
||||
}
|
||||
slapi_pblock_set(pb, SLAPI_SEARCH_RESULT_SET, NULL);
|
||||
next_be = NULL; /* to break the loop */
|
||||
if (operation->o_status & SLAPI_OP_STATUS_ABANDONED) {
|
||||
/* It turned out this search was abandoned. */
|
||||
- pagedresults_free_one_msgid(pb_conn, operation->o_msgid, pagedresults_mutex);
|
||||
+ pagedresults_free_one_msgid(pb_conn, operation->o_msgid, PR_NOT_LOCKED);
|
||||
/* paged-results-request was abandoned; making an empty cookie. */
|
||||
pagedresults_set_response_control(pb, 0, estimate, -1, pr_idx);
|
||||
send_ldap_result(pb, 0, NULL, "Simple Paged Results Search abandoned", 0, NULL);
|
||||
@@ -961,7 +951,7 @@ op_shared_search(Slapi_PBlock *pb, int send_result)
|
||||
}
|
||||
pagedresults_set_response_control(pb, 0, estimate, curr_search_count, pr_idx);
|
||||
if (curr_search_count == -1) {
|
||||
- pagedresults_free_one(pb_conn, operation, pr_idx);
|
||||
+ pagedresults_free_one(pb_conn, operation, PR_NOT_LOCKED, pr_idx);
|
||||
}
|
||||
}
|
||||
|
||||
diff --git a/ldap/servers/slapd/pagedresults.c b/ldap/servers/slapd/pagedresults.c
|
||||
index 941ab97e3..0d6c4a1aa 100644
|
||||
--- a/ldap/servers/slapd/pagedresults.c
|
||||
+++ b/ldap/servers/slapd/pagedresults.c
|
||||
@@ -34,9 +34,9 @@ pageresult_lock_cleanup()
|
||||
slapi_ch_free((void**)&lock_hash);
|
||||
}
|
||||
|
||||
-/* Beware to the lock order with c_mutex:
|
||||
- * c_mutex is sometime locked while holding pageresult_lock
|
||||
- * ==> Do not lock pageresult_lock when holing c_mutex
|
||||
+/* Lock ordering constraint with c_mutex:
|
||||
+ * c_mutex is sometimes locked while holding pageresult_lock.
|
||||
+ * Therefore: DO NOT acquire pageresult_lock when holding c_mutex.
|
||||
*/
|
||||
pthread_mutex_t *
|
||||
pageresult_lock_get_addr(Connection *conn)
|
||||
@@ -44,7 +44,11 @@ pageresult_lock_get_addr(Connection *conn)
|
||||
return &lock_hash[(((size_t)conn)/sizeof (Connection))%LOCK_HASH_SIZE];
|
||||
}
|
||||
|
||||
-/* helper function to clean up one prp slot */
|
||||
+/* helper function to clean up one prp slot
|
||||
+ *
|
||||
+ * NOTE: This function must be called while holding the pageresult_lock
|
||||
+ * (via pageresult_lock_get_addr(conn)) to ensure thread-safe cleanup.
|
||||
+ */
|
||||
static void
|
||||
_pr_cleanup_one_slot(PagedResults *prp)
|
||||
{
|
||||
@@ -56,7 +60,7 @@ _pr_cleanup_one_slot(PagedResults *prp)
|
||||
prp->pr_current_be->be_search_results_release(&(prp->pr_search_result_set));
|
||||
}
|
||||
|
||||
- /* clean up the slot except the mutex */
|
||||
+ /* clean up the slot */
|
||||
prp->pr_current_be = NULL;
|
||||
prp->pr_search_result_set = NULL;
|
||||
prp->pr_search_result_count = 0;
|
||||
@@ -136,6 +140,8 @@ pagedresults_parse_control_value(Slapi_PBlock *pb,
|
||||
return LDAP_UNWILLING_TO_PERFORM;
|
||||
}
|
||||
|
||||
+ /* Acquire hash-based lock for paged results list access
|
||||
+ * IMPORTANT: Never acquire this lock when holding c_mutex */
|
||||
pthread_mutex_lock(pageresult_lock_get_addr(conn));
|
||||
/* the ber encoding is no longer needed */
|
||||
ber_free(ber, 1);
|
||||
@@ -184,10 +190,6 @@ pagedresults_parse_control_value(Slapi_PBlock *pb,
|
||||
goto bail;
|
||||
}
|
||||
|
||||
- if ((*index > -1) && (*index < conn->c_pagedresults.prl_maxlen) &&
|
||||
- !conn->c_pagedresults.prl_list[*index].pr_mutex) {
|
||||
- conn->c_pagedresults.prl_list[*index].pr_mutex = PR_NewLock();
|
||||
- }
|
||||
conn->c_pagedresults.prl_count++;
|
||||
} else {
|
||||
/* Repeated paged results request.
|
||||
@@ -327,8 +329,14 @@ bailout:
|
||||
"<= idx=%d\n", index);
|
||||
}
|
||||
|
||||
+/*
|
||||
+ * Free one paged result entry by index.
|
||||
+ *
|
||||
+ * Locking: If locked=0, acquires pageresult_lock. If locked=1, assumes
|
||||
+ * caller already holds pageresult_lock. Never call when holding c_mutex.
|
||||
+ */
|
||||
int
|
||||
-pagedresults_free_one(Connection *conn, Operation *op, int index)
|
||||
+pagedresults_free_one(Connection *conn, Operation *op, bool locked, int index)
|
||||
{
|
||||
int rc = -1;
|
||||
|
||||
@@ -338,7 +346,9 @@ pagedresults_free_one(Connection *conn, Operation *op, int index)
|
||||
slapi_log_err(SLAPI_LOG_TRACE, "pagedresults_free_one",
|
||||
"=> idx=%d\n", index);
|
||||
if (conn && (index > -1)) {
|
||||
- pthread_mutex_lock(pageresult_lock_get_addr(conn));
|
||||
+ if (!locked) {
|
||||
+ pthread_mutex_lock(pageresult_lock_get_addr(conn));
|
||||
+ }
|
||||
if (conn->c_pagedresults.prl_count <= 0) {
|
||||
slapi_log_err(SLAPI_LOG_TRACE, "pagedresults_free_one",
|
||||
"conn=%" PRIu64 " paged requests list count is %d\n",
|
||||
@@ -349,7 +359,9 @@ pagedresults_free_one(Connection *conn, Operation *op, int index)
|
||||
conn->c_pagedresults.prl_count--;
|
||||
rc = 0;
|
||||
}
|
||||
- pthread_mutex_unlock(pageresult_lock_get_addr(conn));
|
||||
+ if (!locked) {
|
||||
+ pthread_mutex_unlock(pageresult_lock_get_addr(conn));
|
||||
+ }
|
||||
}
|
||||
|
||||
slapi_log_err(SLAPI_LOG_TRACE, "pagedresults_free_one", "<= %d\n", rc);
|
||||
@@ -357,21 +369,28 @@ pagedresults_free_one(Connection *conn, Operation *op, int index)
|
||||
}
|
||||
|
||||
/*
|
||||
- * Used for abandoning - pageresult_lock_get_addr(conn) is already locked in do_abandone.
|
||||
+ * Free one paged result entry by message ID.
|
||||
+ *
|
||||
+ * Locking: If locked=0, acquires pageresult_lock. If locked=1, assumes
|
||||
+ * caller already holds pageresult_lock. Never call when holding c_mutex.
|
||||
*/
|
||||
int
|
||||
-pagedresults_free_one_msgid(Connection *conn, ber_int_t msgid, pthread_mutex_t *mutex)
|
||||
+pagedresults_free_one_msgid(Connection *conn, ber_int_t msgid, bool locked)
|
||||
{
|
||||
int rc = -1;
|
||||
int i;
|
||||
+ pthread_mutex_t *lock = NULL;
|
||||
|
||||
if (conn && (msgid > -1)) {
|
||||
if (conn->c_pagedresults.prl_maxlen <= 0) {
|
||||
; /* Not a paged result. */
|
||||
} else {
|
||||
slapi_log_err(SLAPI_LOG_TRACE,
|
||||
- "pagedresults_free_one_msgid_nolock", "=> msgid=%d\n", msgid);
|
||||
- pthread_mutex_lock(mutex);
|
||||
+ "pagedresults_free_one_msgid", "=> msgid=%d\n", msgid);
|
||||
+ lock = pageresult_lock_get_addr(conn);
|
||||
+ if (!locked) {
|
||||
+ pthread_mutex_lock(lock);
|
||||
+ }
|
||||
for (i = 0; i < conn->c_pagedresults.prl_maxlen; i++) {
|
||||
if (conn->c_pagedresults.prl_list[i].pr_msgid == msgid) {
|
||||
PagedResults *prp = conn->c_pagedresults.prl_list + i;
|
||||
@@ -390,9 +409,11 @@ pagedresults_free_one_msgid(Connection *conn, ber_int_t msgid, pthread_mutex_t *
|
||||
break;
|
||||
}
|
||||
}
|
||||
- pthread_mutex_unlock(mutex);
|
||||
+ if (!locked) {
|
||||
+ pthread_mutex_unlock(lock);
|
||||
+ }
|
||||
slapi_log_err(SLAPI_LOG_TRACE,
|
||||
- "pagedresults_free_one_msgid_nolock", "<= %d\n", rc);
|
||||
+ "pagedresults_free_one_msgid", "<= %d\n", rc);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -418,29 +439,43 @@ pagedresults_get_current_be(Connection *conn, int index)
|
||||
return be;
|
||||
}
|
||||
|
||||
+/*
|
||||
+ * Set current backend for a paged result entry.
|
||||
+ *
|
||||
+ * Locking: If locked=false, acquires pageresult_lock. If locked=true, assumes
|
||||
+ * caller already holds pageresult_lock. Never call when holding c_mutex.
|
||||
+ */
|
||||
int
|
||||
-pagedresults_set_current_be(Connection *conn, Slapi_Backend *be, int index, int nolock)
|
||||
+pagedresults_set_current_be(Connection *conn, Slapi_Backend *be, int index, bool locked)
|
||||
{
|
||||
int rc = -1;
|
||||
slapi_log_err(SLAPI_LOG_TRACE,
|
||||
"pagedresults_set_current_be", "=> idx=%d\n", index);
|
||||
if (conn && (index > -1)) {
|
||||
- if (!nolock)
|
||||
+ if (!locked) {
|
||||
pthread_mutex_lock(pageresult_lock_get_addr(conn));
|
||||
+ }
|
||||
if (index < conn->c_pagedresults.prl_maxlen) {
|
||||
conn->c_pagedresults.prl_list[index].pr_current_be = be;
|
||||
}
|
||||
rc = 0;
|
||||
- if (!nolock)
|
||||
+ if (!locked) {
|
||||
pthread_mutex_unlock(pageresult_lock_get_addr(conn));
|
||||
+ }
|
||||
}
|
||||
slapi_log_err(SLAPI_LOG_TRACE,
|
||||
"pagedresults_set_current_be", "<= %d\n", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
+/*
|
||||
+ * Get search result set for a paged result entry.
|
||||
+ *
|
||||
+ * Locking: If locked=0, acquires pageresult_lock. If locked=1, assumes
|
||||
+ * caller already holds pageresult_lock. Never call when holding c_mutex.
|
||||
+ */
|
||||
void *
|
||||
-pagedresults_get_search_result(Connection *conn, Operation *op, int locked, int index)
|
||||
+pagedresults_get_search_result(Connection *conn, Operation *op, bool locked, int index)
|
||||
{
|
||||
void *sr = NULL;
|
||||
if (!op_is_pagedresults(op)) {
|
||||
@@ -465,8 +500,14 @@ pagedresults_get_search_result(Connection *conn, Operation *op, int locked, int
|
||||
return sr;
|
||||
}
|
||||
|
||||
+/*
|
||||
+ * Set search result set for a paged result entry.
|
||||
+ *
|
||||
+ * Locking: If locked=0, acquires pageresult_lock. If locked=1, assumes
|
||||
+ * caller already holds pageresult_lock. Never call when holding c_mutex.
|
||||
+ */
|
||||
int
|
||||
-pagedresults_set_search_result(Connection *conn, Operation *op, void *sr, int locked, int index)
|
||||
+pagedresults_set_search_result(Connection *conn, Operation *op, void *sr, bool locked, int index)
|
||||
{
|
||||
int rc = -1;
|
||||
if (!op_is_pagedresults(op)) {
|
||||
@@ -494,8 +535,14 @@ pagedresults_set_search_result(Connection *conn, Operation *op, void *sr, int lo
|
||||
return rc;
|
||||
}
|
||||
|
||||
+/*
|
||||
+ * Get search result count for a paged result entry.
|
||||
+ *
|
||||
+ * Locking: If locked=0, acquires pageresult_lock. If locked=1, assumes
|
||||
+ * caller already holds pageresult_lock. Never call when holding c_mutex.
|
||||
+ */
|
||||
int
|
||||
-pagedresults_get_search_result_count(Connection *conn, Operation *op, int index)
|
||||
+pagedresults_get_search_result_count(Connection *conn, Operation *op, bool locked, int index)
|
||||
{
|
||||
int count = 0;
|
||||
if (!op_is_pagedresults(op)) {
|
||||
@@ -504,19 +551,29 @@ pagedresults_get_search_result_count(Connection *conn, Operation *op, int index)
|
||||
slapi_log_err(SLAPI_LOG_TRACE,
|
||||
"pagedresults_get_search_result_count", "=> idx=%d\n", index);
|
||||
if (conn && (index > -1)) {
|
||||
- pthread_mutex_lock(pageresult_lock_get_addr(conn));
|
||||
+ if (!locked) {
|
||||
+ pthread_mutex_lock(pageresult_lock_get_addr(conn));
|
||||
+ }
|
||||
if (index < conn->c_pagedresults.prl_maxlen) {
|
||||
count = conn->c_pagedresults.prl_list[index].pr_search_result_count;
|
||||
}
|
||||
- pthread_mutex_unlock(pageresult_lock_get_addr(conn));
|
||||
+ if (!locked) {
|
||||
+ pthread_mutex_unlock(pageresult_lock_get_addr(conn));
|
||||
+ }
|
||||
}
|
||||
slapi_log_err(SLAPI_LOG_TRACE,
|
||||
"pagedresults_get_search_result_count", "<= %d\n", count);
|
||||
return count;
|
||||
}
|
||||
|
||||
+/*
|
||||
+ * Set search result count for a paged result entry.
|
||||
+ *
|
||||
+ * Locking: If locked=0, acquires pageresult_lock. If locked=1, assumes
|
||||
+ * caller already holds pageresult_lock. Never call when holding c_mutex.
|
||||
+ */
|
||||
int
|
||||
-pagedresults_set_search_result_count(Connection *conn, Operation *op, int count, int index)
|
||||
+pagedresults_set_search_result_count(Connection *conn, Operation *op, int count, bool locked, int index)
|
||||
{
|
||||
int rc = -1;
|
||||
if (!op_is_pagedresults(op)) {
|
||||
@@ -525,11 +582,15 @@ pagedresults_set_search_result_count(Connection *conn, Operation *op, int count,
|
||||
slapi_log_err(SLAPI_LOG_TRACE,
|
||||
"pagedresults_set_search_result_count", "=> idx=%d\n", index);
|
||||
if (conn && (index > -1)) {
|
||||
- pthread_mutex_lock(pageresult_lock_get_addr(conn));
|
||||
+ if (!locked) {
|
||||
+ pthread_mutex_lock(pageresult_lock_get_addr(conn));
|
||||
+ }
|
||||
if (index < conn->c_pagedresults.prl_maxlen) {
|
||||
conn->c_pagedresults.prl_list[index].pr_search_result_count = count;
|
||||
}
|
||||
- pthread_mutex_unlock(pageresult_lock_get_addr(conn));
|
||||
+ if (!locked) {
|
||||
+ pthread_mutex_unlock(pageresult_lock_get_addr(conn));
|
||||
+ }
|
||||
rc = 0;
|
||||
}
|
||||
slapi_log_err(SLAPI_LOG_TRACE,
|
||||
@@ -537,9 +598,16 @@ pagedresults_set_search_result_count(Connection *conn, Operation *op, int count,
|
||||
return rc;
|
||||
}
|
||||
|
||||
+/*
|
||||
+ * Get search result set size estimate for a paged result entry.
|
||||
+ *
|
||||
+ * Locking: If locked=0, acquires pageresult_lock. If locked=1, assumes
|
||||
+ * caller already holds pageresult_lock. Never call when holding c_mutex.
|
||||
+ */
|
||||
int
|
||||
pagedresults_get_search_result_set_size_estimate(Connection *conn,
|
||||
Operation *op,
|
||||
+ bool locked,
|
||||
int index)
|
||||
{
|
||||
int count = 0;
|
||||
@@ -550,11 +618,15 @@ pagedresults_get_search_result_set_size_estimate(Connection *conn,
|
||||
"pagedresults_get_search_result_set_size_estimate",
|
||||
"=> idx=%d\n", index);
|
||||
if (conn && (index > -1)) {
|
||||
- pthread_mutex_lock(pageresult_lock_get_addr(conn));
|
||||
+ if (!locked) {
|
||||
+ pthread_mutex_lock(pageresult_lock_get_addr(conn));
|
||||
+ }
|
||||
if (index < conn->c_pagedresults.prl_maxlen) {
|
||||
count = conn->c_pagedresults.prl_list[index].pr_search_result_set_size_estimate;
|
||||
}
|
||||
- pthread_mutex_unlock(pageresult_lock_get_addr(conn));
|
||||
+ if (!locked) {
|
||||
+ pthread_mutex_unlock(pageresult_lock_get_addr(conn));
|
||||
+ }
|
||||
}
|
||||
slapi_log_err(SLAPI_LOG_TRACE,
|
||||
"pagedresults_get_search_result_set_size_estimate", "<= %d\n",
|
||||
@@ -562,10 +634,17 @@ pagedresults_get_search_result_set_size_estimate(Connection *conn,
|
||||
return count;
|
||||
}
|
||||
|
||||
+/*
|
||||
+ * Set search result set size estimate for a paged result entry.
|
||||
+ *
|
||||
+ * Locking: If locked=0, acquires pageresult_lock. If locked=1, assumes
|
||||
+ * caller already holds pageresult_lock. Never call when holding c_mutex.
|
||||
+ */
|
||||
int
|
||||
pagedresults_set_search_result_set_size_estimate(Connection *conn,
|
||||
Operation *op,
|
||||
int count,
|
||||
+ bool locked,
|
||||
int index)
|
||||
{
|
||||
int rc = -1;
|
||||
@@ -576,11 +655,15 @@ pagedresults_set_search_result_set_size_estimate(Connection *conn,
|
||||
"pagedresults_set_search_result_set_size_estimate",
|
||||
"=> idx=%d\n", index);
|
||||
if (conn && (index > -1)) {
|
||||
- pthread_mutex_lock(pageresult_lock_get_addr(conn));
|
||||
+ if (!locked) {
|
||||
+ pthread_mutex_lock(pageresult_lock_get_addr(conn));
|
||||
+ }
|
||||
if (index < conn->c_pagedresults.prl_maxlen) {
|
||||
conn->c_pagedresults.prl_list[index].pr_search_result_set_size_estimate = count;
|
||||
}
|
||||
- pthread_mutex_unlock(pageresult_lock_get_addr(conn));
|
||||
+ if (!locked) {
|
||||
+ pthread_mutex_unlock(pageresult_lock_get_addr(conn));
|
||||
+ }
|
||||
rc = 0;
|
||||
}
|
||||
slapi_log_err(SLAPI_LOG_TRACE,
|
||||
@@ -589,8 +672,14 @@ pagedresults_set_search_result_set_size_estimate(Connection *conn,
|
||||
return rc;
|
||||
}
|
||||
|
||||
+/*
|
||||
+ * Get with_sort flag for a paged result entry.
|
||||
+ *
|
||||
+ * Locking: If locked=0, acquires pageresult_lock. If locked=1, assumes
|
||||
+ * caller already holds pageresult_lock. Never call when holding c_mutex.
|
||||
+ */
|
||||
int
|
||||
-pagedresults_get_with_sort(Connection *conn, Operation *op, int index)
|
||||
+pagedresults_get_with_sort(Connection *conn, Operation *op, bool locked, int index)
|
||||
{
|
||||
int flags = 0;
|
||||
if (!op_is_pagedresults(op)) {
|
||||
@@ -599,19 +688,29 @@ pagedresults_get_with_sort(Connection *conn, Operation *op, int index)
|
||||
slapi_log_err(SLAPI_LOG_TRACE,
|
||||
"pagedresults_get_with_sort", "=> idx=%d\n", index);
|
||||
if (conn && (index > -1)) {
|
||||
- pthread_mutex_lock(pageresult_lock_get_addr(conn));
|
||||
+ if (!locked) {
|
||||
+ pthread_mutex_lock(pageresult_lock_get_addr(conn));
|
||||
+ }
|
||||
if (index < conn->c_pagedresults.prl_maxlen) {
|
||||
flags = conn->c_pagedresults.prl_list[index].pr_flags & CONN_FLAG_PAGEDRESULTS_WITH_SORT;
|
||||
}
|
||||
- pthread_mutex_unlock(pageresult_lock_get_addr(conn));
|
||||
+ if (!locked) {
|
||||
+ pthread_mutex_unlock(pageresult_lock_get_addr(conn));
|
||||
+ }
|
||||
}
|
||||
slapi_log_err(SLAPI_LOG_TRACE,
|
||||
"pagedresults_get_with_sort", "<= %d\n", flags);
|
||||
return flags;
|
||||
}
|
||||
|
||||
+/*
|
||||
+ * Set with_sort flag for a paged result entry.
|
||||
+ *
|
||||
+ * Locking: If locked=0, acquires pageresult_lock. If locked=1, assumes
|
||||
+ * caller already holds pageresult_lock. Never call when holding c_mutex.
|
||||
+ */
|
||||
int
|
||||
-pagedresults_set_with_sort(Connection *conn, Operation *op, int flags, int index)
|
||||
+pagedresults_set_with_sort(Connection *conn, Operation *op, int flags, bool locked, int index)
|
||||
{
|
||||
int rc = -1;
|
||||
if (!op_is_pagedresults(op)) {
|
||||
@@ -620,14 +719,18 @@ pagedresults_set_with_sort(Connection *conn, Operation *op, int flags, int index
|
||||
slapi_log_err(SLAPI_LOG_TRACE,
|
||||
"pagedresults_set_with_sort", "=> idx=%d\n", index);
|
||||
if (conn && (index > -1)) {
|
||||
- pthread_mutex_lock(pageresult_lock_get_addr(conn));
|
||||
+ if (!locked) {
|
||||
+ pthread_mutex_lock(pageresult_lock_get_addr(conn));
|
||||
+ }
|
||||
if (index < conn->c_pagedresults.prl_maxlen) {
|
||||
if (flags & OP_FLAG_SERVER_SIDE_SORTING) {
|
||||
conn->c_pagedresults.prl_list[index].pr_flags |=
|
||||
CONN_FLAG_PAGEDRESULTS_WITH_SORT;
|
||||
}
|
||||
}
|
||||
- pthread_mutex_unlock(pageresult_lock_get_addr(conn));
|
||||
+ if (!locked) {
|
||||
+ pthread_mutex_unlock(pageresult_lock_get_addr(conn));
|
||||
+ }
|
||||
rc = 0;
|
||||
}
|
||||
slapi_log_err(SLAPI_LOG_TRACE, "pagedresults_set_with_sort", "<= %d\n", rc);
|
||||
@@ -802,10 +905,6 @@ pagedresults_cleanup(Connection *conn, int needlock)
|
||||
rc = 1;
|
||||
}
|
||||
prp->pr_current_be = NULL;
|
||||
- if (prp->pr_mutex) {
|
||||
- PR_DestroyLock(prp->pr_mutex);
|
||||
- prp->pr_mutex = NULL;
|
||||
- }
|
||||
memset(prp, '\0', sizeof(PagedResults));
|
||||
}
|
||||
conn->c_pagedresults.prl_count = 0;
|
||||
@@ -840,10 +939,6 @@ pagedresults_cleanup_all(Connection *conn, int needlock)
|
||||
i < conn->c_pagedresults.prl_maxlen;
|
||||
i++) {
|
||||
prp = conn->c_pagedresults.prl_list + i;
|
||||
- if (prp->pr_mutex) {
|
||||
- PR_DestroyLock(prp->pr_mutex);
|
||||
- prp->pr_mutex = NULL;
|
||||
- }
|
||||
if (prp->pr_current_be && prp->pr_search_result_set &&
|
||||
prp->pr_current_be->be_search_results_release) {
|
||||
prp->pr_current_be->be_search_results_release(&(prp->pr_search_result_set));
|
||||
@@ -1010,43 +1105,8 @@ op_set_pagedresults(Operation *op)
|
||||
op->o_flags |= OP_FLAG_PAGED_RESULTS;
|
||||
}
|
||||
|
||||
-/*
|
||||
- * pagedresults_lock/unlock -- introduced to protect search results for the
|
||||
- * asynchronous searches. Do not call these functions while the PR conn lock
|
||||
- * is held (e.g. pageresult_lock_get_addr(conn))
|
||||
- */
|
||||
-void
|
||||
-pagedresults_lock(Connection *conn, int index)
|
||||
-{
|
||||
- PagedResults *prp;
|
||||
- if (!conn || (index < 0) || (index >= conn->c_pagedresults.prl_maxlen)) {
|
||||
- return;
|
||||
- }
|
||||
- pthread_mutex_lock(pageresult_lock_get_addr(conn));
|
||||
- prp = conn->c_pagedresults.prl_list + index;
|
||||
- if (prp->pr_mutex) {
|
||||
- PR_Lock(prp->pr_mutex);
|
||||
- }
|
||||
- pthread_mutex_unlock(pageresult_lock_get_addr(conn));
|
||||
-}
|
||||
-
|
||||
-void
|
||||
-pagedresults_unlock(Connection *conn, int index)
|
||||
-{
|
||||
- PagedResults *prp;
|
||||
- if (!conn || (index < 0) || (index >= conn->c_pagedresults.prl_maxlen)) {
|
||||
- return;
|
||||
- }
|
||||
- pthread_mutex_lock(pageresult_lock_get_addr(conn));
|
||||
- prp = conn->c_pagedresults.prl_list + index;
|
||||
- if (prp->pr_mutex) {
|
||||
- PR_Unlock(prp->pr_mutex);
|
||||
- }
|
||||
- pthread_mutex_unlock(pageresult_lock_get_addr(conn));
|
||||
-}
|
||||
-
|
||||
int
|
||||
-pagedresults_is_abandoned_or_notavailable(Connection *conn, int locked, int index)
|
||||
+pagedresults_is_abandoned_or_notavailable(Connection *conn, bool locked, int index)
|
||||
{
|
||||
PagedResults *prp;
|
||||
int32_t result;
|
||||
@@ -1066,7 +1126,7 @@ pagedresults_is_abandoned_or_notavailable(Connection *conn, int locked, int inde
|
||||
}
|
||||
|
||||
int
|
||||
-pagedresults_set_search_result_pb(Slapi_PBlock *pb, void *sr, int locked)
|
||||
+pagedresults_set_search_result_pb(Slapi_PBlock *pb, void *sr, bool locked)
|
||||
{
|
||||
int rc = -1;
|
||||
Connection *conn = NULL;
|
||||
diff --git a/ldap/servers/slapd/proto-slap.h b/ldap/servers/slapd/proto-slap.h
|
||||
index 37c643ea9..742e3ee14 100644
|
||||
--- a/ldap/servers/slapd/proto-slap.h
|
||||
+++ b/ldap/servers/slapd/proto-slap.h
|
||||
@@ -1597,20 +1597,22 @@ pthread_mutex_t *pageresult_lock_get_addr(Connection *conn);
|
||||
int pagedresults_parse_control_value(Slapi_PBlock *pb, struct berval *psbvp, ber_int_t *pagesize, int *index, Slapi_Backend *be);
|
||||
void pagedresults_set_response_control(Slapi_PBlock *pb, int iscritical, ber_int_t estimate, int curr_search_count, int index);
|
||||
Slapi_Backend *pagedresults_get_current_be(Connection *conn, int index);
|
||||
-int pagedresults_set_current_be(Connection *conn, Slapi_Backend *be, int index, int nolock);
|
||||
-void *pagedresults_get_search_result(Connection *conn, Operation *op, int locked, int index);
|
||||
-int pagedresults_set_search_result(Connection *conn, Operation *op, void *sr, int locked, int index);
|
||||
-int pagedresults_get_search_result_count(Connection *conn, Operation *op, int index);
|
||||
-int pagedresults_set_search_result_count(Connection *conn, Operation *op, int cnt, int index);
|
||||
+int pagedresults_set_current_be(Connection *conn, Slapi_Backend *be, int index, bool locked);
|
||||
+void *pagedresults_get_search_result(Connection *conn, Operation *op, bool locked, int index);
|
||||
+int pagedresults_set_search_result(Connection *conn, Operation *op, void *sr, bool locked, int index);
|
||||
+int pagedresults_get_search_result_count(Connection *conn, Operation *op, bool locked, int index);
|
||||
+int pagedresults_set_search_result_count(Connection *conn, Operation *op, int cnt, bool locked, int index);
|
||||
int pagedresults_get_search_result_set_size_estimate(Connection *conn,
|
||||
Operation *op,
|
||||
+ bool locked,
|
||||
int index);
|
||||
int pagedresults_set_search_result_set_size_estimate(Connection *conn,
|
||||
Operation *op,
|
||||
int cnt,
|
||||
+ bool locked,
|
||||
int index);
|
||||
-int pagedresults_get_with_sort(Connection *conn, Operation *op, int index);
|
||||
-int pagedresults_set_with_sort(Connection *conn, Operation *op, int flags, int index);
|
||||
+int pagedresults_get_with_sort(Connection *conn, Operation *op, bool locked, int index);
|
||||
+int pagedresults_set_with_sort(Connection *conn, Operation *op, int flags, bool locked, int index);
|
||||
int pagedresults_get_unindexed(Connection *conn, Operation *op, int index);
|
||||
int pagedresults_set_unindexed(Connection *conn, Operation *op, int index);
|
||||
int pagedresults_get_sort_result_code(Connection *conn, Operation *op, int index);
|
||||
@@ -1622,15 +1624,13 @@ int pagedresults_cleanup(Connection *conn, int needlock);
|
||||
int pagedresults_is_timedout_nolock(Connection *conn);
|
||||
int pagedresults_reset_timedout_nolock(Connection *conn);
|
||||
int pagedresults_in_use_nolock(Connection *conn);
|
||||
-int pagedresults_free_one(Connection *conn, Operation *op, int index);
|
||||
-int pagedresults_free_one_msgid(Connection *conn, ber_int_t msgid, pthread_mutex_t *mutex);
|
||||
+int pagedresults_free_one(Connection *conn, Operation *op, bool locked, int index);
|
||||
+int pagedresults_free_one_msgid(Connection *conn, ber_int_t msgid, bool locked);
|
||||
int op_is_pagedresults(Operation *op);
|
||||
int pagedresults_cleanup_all(Connection *conn, int needlock);
|
||||
void op_set_pagedresults(Operation *op);
|
||||
-void pagedresults_lock(Connection *conn, int index);
|
||||
-void pagedresults_unlock(Connection *conn, int index);
|
||||
-int pagedresults_is_abandoned_or_notavailable(Connection *conn, int locked, int index);
|
||||
-int pagedresults_set_search_result_pb(Slapi_PBlock *pb, void *sr, int locked);
|
||||
+int pagedresults_is_abandoned_or_notavailable(Connection *conn, bool locked, int index);
|
||||
+int pagedresults_set_search_result_pb(Slapi_PBlock *pb, void *sr, bool locked);
|
||||
|
||||
/*
|
||||
* sort.c
|
||||
diff --git a/ldap/servers/slapd/slap.h b/ldap/servers/slapd/slap.h
|
||||
index 9aaa4fb80..f2395cfa2 100644
|
||||
--- a/ldap/servers/slapd/slap.h
|
||||
+++ b/ldap/servers/slapd/slap.h
|
||||
@@ -80,6 +80,10 @@ static char ptokPBE[34] = "Internal (Software) Token ";
|
||||
#include <stdbool.h>
|
||||
#include <time.h> /* For timespec definitions */
|
||||
|
||||
+/* Macros for paged results lock parameter */
|
||||
+#define PR_LOCKED true
|
||||
+#define PR_NOT_LOCKED false
|
||||
+
|
||||
/* Provides our int types and platform specific requirements. */
|
||||
#include <slapi_pal.h>
|
||||
|
||||
@@ -1656,7 +1660,6 @@ typedef struct _paged_results
|
||||
struct timespec pr_timelimit_hr; /* expiry time of this request rel to clock monotonic */
|
||||
int pr_flags;
|
||||
ber_int_t pr_msgid; /* msgid of the request; to abandon */
|
||||
- PRLock *pr_mutex; /* protect each conn structure */
|
||||
} PagedResults;
|
||||
|
||||
/* array of simple paged structure stashed in connection */
|
||||
--
|
||||
2.52.0
|
||||
|
||||
@ -1,840 +0,0 @@
|
||||
From 8dc61a176323f0d41df730abd715ccff3034c2be Mon Sep 17 00:00:00 2001
|
||||
From: Mark Reynolds <mreynolds@redhat.com>
|
||||
Date: Sun, 27 Nov 2022 09:37:19 -0500
|
||||
Subject: [PATCH] Issue 5547 - automember plugin improvements
|
||||
|
||||
Description:
|
||||
|
||||
Rebuild task has the following improvements:
|
||||
|
||||
- Only one task allowed at a time
|
||||
- Do not cleanup previous members by default. Add new CLI option to intentionally
|
||||
cleanup memberships before rebuilding from scratch.
|
||||
- Add better task logging to show fixup progress
|
||||
|
||||
To prevent automember from being called in a nested be_txn loop thread storage is
|
||||
used to check and skip these loops.
|
||||
|
||||
relates: https://github.com/389ds/389-ds-base/issues/5547
|
||||
|
||||
Reviewed by: spichugi(Thanks!)
|
||||
---
|
||||
.../automember_plugin/automember_mod_test.py | 43 +++-
|
||||
ldap/servers/plugins/automember/automember.c | 232 ++++++++++++++----
|
||||
ldap/servers/slapd/back-ldbm/ldbm_add.c | 11 +-
|
||||
ldap/servers/slapd/back-ldbm/ldbm_delete.c | 10 +-
|
||||
ldap/servers/slapd/back-ldbm/ldbm_modify.c | 11 +-
|
||||
.../lib389/cli_conf/plugins/automember.py | 10 +-
|
||||
src/lib389/lib389/plugins.py | 7 +-
|
||||
src/lib389/lib389/tasks.py | 9 +-
|
||||
8 files changed, 250 insertions(+), 83 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/automember_plugin/automember_mod_test.py b/dirsrvtests/tests/suites/automember_plugin/automember_mod_test.py
|
||||
index 8d25384bf..7a0ed3275 100644
|
||||
--- a/dirsrvtests/tests/suites/automember_plugin/automember_mod_test.py
|
||||
+++ b/dirsrvtests/tests/suites/automember_plugin/automember_mod_test.py
|
||||
@@ -5,12 +5,13 @@
|
||||
# License: GPL (version 3 or any later version).
|
||||
# See LICENSE for details.
|
||||
# --- END COPYRIGHT BLOCK ---
|
||||
-#
|
||||
+import ldap
|
||||
import logging
|
||||
import pytest
|
||||
import os
|
||||
+import time
|
||||
from lib389.utils import ds_is_older
|
||||
-from lib389._constants import *
|
||||
+from lib389._constants import DEFAULT_SUFFIX
|
||||
from lib389.plugins import AutoMembershipPlugin, AutoMembershipDefinitions
|
||||
from lib389.idm.user import UserAccounts
|
||||
from lib389.idm.group import Groups
|
||||
@@ -41,6 +42,11 @@ def automember_fixture(topo, request):
|
||||
user_accts = UserAccounts(topo.standalone, DEFAULT_SUFFIX)
|
||||
user = user_accts.create_test_user()
|
||||
|
||||
+ # Create extra users
|
||||
+ users = UserAccounts(topo.standalone, DEFAULT_SUFFIX)
|
||||
+ for i in range(0, 100):
|
||||
+ users.create_test_user(uid=i)
|
||||
+
|
||||
# Create automember definitions and regex rules
|
||||
automember_prop = {
|
||||
'cn': 'testgroup_definition',
|
||||
@@ -59,7 +65,7 @@ def automember_fixture(topo, request):
|
||||
automemberplugin.enable()
|
||||
topo.standalone.restart()
|
||||
|
||||
- return (user, groups)
|
||||
+ return user, groups
|
||||
|
||||
|
||||
def test_mods(automember_fixture, topo):
|
||||
@@ -72,19 +78,21 @@ def test_mods(automember_fixture, topo):
|
||||
2. Update user that should add it to group[1]
|
||||
3. Update user that should add it to group[2]
|
||||
4. Update user that should add it to group[0]
|
||||
- 5. Test rebuild task correctly moves user to group[1]
|
||||
+ 5. Test rebuild task adds user to group[1]
|
||||
+ 6. Test rebuild task cleanups groups and only adds it to group[1]
|
||||
:expectedresults:
|
||||
1. Success
|
||||
2. Success
|
||||
3. Success
|
||||
4. Success
|
||||
5. Success
|
||||
+ 6. Success
|
||||
"""
|
||||
(user, groups) = automember_fixture
|
||||
|
||||
# Update user which should go into group[0]
|
||||
user.replace('cn', 'whatever')
|
||||
- groups[0].is_member(user.dn)
|
||||
+ assert groups[0].is_member(user.dn)
|
||||
if groups[1].is_member(user.dn):
|
||||
assert False
|
||||
if groups[2].is_member(user.dn):
|
||||
@@ -92,7 +100,7 @@ def test_mods(automember_fixture, topo):
|
||||
|
||||
# Update user0 which should go into group[1]
|
||||
user.replace('cn', 'mark')
|
||||
- groups[1].is_member(user.dn)
|
||||
+ assert groups[1].is_member(user.dn)
|
||||
if groups[0].is_member(user.dn):
|
||||
assert False
|
||||
if groups[2].is_member(user.dn):
|
||||
@@ -100,7 +108,7 @@ def test_mods(automember_fixture, topo):
|
||||
|
||||
# Update user which should go into group[2]
|
||||
user.replace('cn', 'simon')
|
||||
- groups[2].is_member(user.dn)
|
||||
+ assert groups[2].is_member(user.dn)
|
||||
if groups[0].is_member(user.dn):
|
||||
assert False
|
||||
if groups[1].is_member(user.dn):
|
||||
@@ -108,7 +116,7 @@ def test_mods(automember_fixture, topo):
|
||||
|
||||
# Update user which should go back into group[0] (full circle)
|
||||
user.replace('cn', 'whatever')
|
||||
- groups[0].is_member(user.dn)
|
||||
+ assert groups[0].is_member(user.dn)
|
||||
if groups[1].is_member(user.dn):
|
||||
assert False
|
||||
if groups[2].is_member(user.dn):
|
||||
@@ -128,12 +136,24 @@ def test_mods(automember_fixture, topo):
|
||||
automemberplugin.enable()
|
||||
topo.standalone.restart()
|
||||
|
||||
- # Run rebuild task
|
||||
+ # Run rebuild task (no cleanup)
|
||||
task = automemberplugin.fixup(DEFAULT_SUFFIX, "objectclass=posixaccount")
|
||||
+ with pytest.raises(ldap.UNWILLING_TO_PERFORM):
|
||||
+ # test only one fixup task is allowed at a time
|
||||
+ automemberplugin.fixup(DEFAULT_SUFFIX, "objectclass=top")
|
||||
task.wait()
|
||||
|
||||
- # Test membership
|
||||
- groups[1].is_member(user.dn)
|
||||
+ # Test membership (user should still be in groups[0])
|
||||
+ assert groups[1].is_member(user.dn)
|
||||
+ if not groups[0].is_member(user.dn):
|
||||
+ assert False
|
||||
+
|
||||
+ # Run rebuild task with cleanup
|
||||
+ task = automemberplugin.fixup(DEFAULT_SUFFIX, "objectclass=posixaccount", cleanup=True)
|
||||
+ task.wait()
|
||||
+
|
||||
+ # Test membership (user should only be in groups[1])
|
||||
+ assert groups[1].is_member(user.dn)
|
||||
if groups[0].is_member(user.dn):
|
||||
assert False
|
||||
if groups[2].is_member(user.dn):
|
||||
@@ -148,4 +168,3 @@ if __name__ == '__main__':
|
||||
# -s for DEBUG mode
|
||||
CURRENT_FILE = os.path.realpath(__file__)
|
||||
pytest.main(["-s", CURRENT_FILE])
|
||||
-
|
||||
diff --git a/ldap/servers/plugins/automember/automember.c b/ldap/servers/plugins/automember/automember.c
|
||||
index 3494d0343..419adb052 100644
|
||||
--- a/ldap/servers/plugins/automember/automember.c
|
||||
+++ b/ldap/servers/plugins/automember/automember.c
|
||||
@@ -1,5 +1,5 @@
|
||||
/** BEGIN COPYRIGHT BLOCK
|
||||
- * Copyright (C) 2011 Red Hat, Inc.
|
||||
+ * Copyright (C) 2022 Red Hat, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* License: GPL (version 3 or any later version).
|
||||
@@ -14,7 +14,7 @@
|
||||
* Auto Membership Plug-in
|
||||
*/
|
||||
#include "automember.h"
|
||||
-
|
||||
+#include <pthread.h>
|
||||
|
||||
/*
|
||||
* Plug-in globals
|
||||
@@ -22,7 +22,9 @@
|
||||
static PRCList *g_automember_config = NULL;
|
||||
static Slapi_RWLock *g_automember_config_lock = NULL;
|
||||
static uint64_t abort_rebuild_task = 0;
|
||||
-
|
||||
+static pthread_key_t td_automem_block_nested;
|
||||
+static PRBool fixup_running = PR_FALSE;
|
||||
+static PRLock *fixup_lock = NULL;
|
||||
static void *_PluginID = NULL;
|
||||
static Slapi_DN *_PluginDN = NULL;
|
||||
static Slapi_DN *_ConfigAreaDN = NULL;
|
||||
@@ -93,9 +95,43 @@ static void automember_task_export_destructor(Slapi_Task *task);
|
||||
static void automember_task_map_destructor(Slapi_Task *task);
|
||||
|
||||
#define DEFAULT_FILE_MODE PR_IRUSR | PR_IWUSR
|
||||
+#define FIXUP_PROGRESS_LIMIT 1000
|
||||
static uint64_t plugin_do_modify = 0;
|
||||
static uint64_t plugin_is_betxn = 0;
|
||||
|
||||
+/* automember_plugin fixup task and add operations should block other be_txn
|
||||
+ * plugins from calling automember_post_op_mod() */
|
||||
+static int32_t
|
||||
+slapi_td_block_nested_post_op(void)
|
||||
+{
|
||||
+ int32_t val = 12345;
|
||||
+
|
||||
+ if (pthread_setspecific(td_automem_block_nested, (void *)&val) != 0) {
|
||||
+ return PR_FAILURE;
|
||||
+ }
|
||||
+ return PR_SUCCESS;
|
||||
+}
|
||||
+
|
||||
+static int32_t
|
||||
+slapi_td_unblock_nested_post_op(void)
|
||||
+{
|
||||
+ if (pthread_setspecific(td_automem_block_nested, NULL) != 0) {
|
||||
+ return PR_FAILURE;
|
||||
+ }
|
||||
+ return PR_SUCCESS;
|
||||
+}
|
||||
+
|
||||
+static int32_t
|
||||
+slapi_td_is_post_op_nested(void)
|
||||
+{
|
||||
+ int32_t *value = pthread_getspecific(td_automem_block_nested);
|
||||
+
|
||||
+ if (value == NULL) {
|
||||
+ return 0;
|
||||
+ }
|
||||
+ return 1;
|
||||
+}
|
||||
+
|
||||
/*
|
||||
* Config cache locking functions
|
||||
*/
|
||||
@@ -317,6 +353,14 @@ automember_start(Slapi_PBlock *pb)
|
||||
return -1;
|
||||
}
|
||||
|
||||
+ if (fixup_lock == NULL) {
|
||||
+ if ((fixup_lock = PR_NewLock()) == NULL) {
|
||||
+ slapi_log_err(SLAPI_LOG_ERR, AUTOMEMBER_PLUGIN_SUBSYSTEM,
|
||||
+ "automember_start - Failed to create fixup lock.\n");
|
||||
+ return -1;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
/*
|
||||
* Get the plug-in target dn from the system
|
||||
* and store it for future use. */
|
||||
@@ -360,6 +404,11 @@ automember_start(Slapi_PBlock *pb)
|
||||
}
|
||||
}
|
||||
|
||||
+ if (pthread_key_create(&td_automem_block_nested, NULL) != 0) {
|
||||
+ slapi_log_err(SLAPI_LOG_ERR, AUTOMEMBER_PLUGIN_SUBSYSTEM,
|
||||
+ "automember_start - pthread_key_create failed\n");
|
||||
+ }
|
||||
+
|
||||
slapi_log_err(SLAPI_LOG_PLUGIN, AUTOMEMBER_PLUGIN_SUBSYSTEM,
|
||||
"automember_start - ready for service\n");
|
||||
slapi_log_err(SLAPI_LOG_TRACE, AUTOMEMBER_PLUGIN_SUBSYSTEM,
|
||||
@@ -394,6 +443,8 @@ automember_close(Slapi_PBlock *pb __attribute__((unused)))
|
||||
slapi_sdn_free(&_ConfigAreaDN);
|
||||
slapi_destroy_rwlock(g_automember_config_lock);
|
||||
g_automember_config_lock = NULL;
|
||||
+ PR_DestroyLock(fixup_lock);
|
||||
+ fixup_lock = NULL;
|
||||
|
||||
slapi_log_err(SLAPI_LOG_TRACE, AUTOMEMBER_PLUGIN_SUBSYSTEM,
|
||||
"<-- automember_close\n");
|
||||
@@ -1619,7 +1670,6 @@ out:
|
||||
return rc;
|
||||
}
|
||||
|
||||
-
|
||||
/*
|
||||
* automember_update_member_value()
|
||||
*
|
||||
@@ -1634,7 +1684,7 @@ automember_update_member_value(Slapi_Entry *member_e, const char *group_dn, char
|
||||
LDAPMod *mods[2];
|
||||
char *vals[2];
|
||||
char *member_value = NULL;
|
||||
- int rc = 0;
|
||||
+ int rc = LDAP_SUCCESS;
|
||||
Slapi_DN *group_sdn;
|
||||
|
||||
/* First thing check that the group still exists */
|
||||
@@ -1653,7 +1703,7 @@ automember_update_member_value(Slapi_Entry *member_e, const char *group_dn, char
|
||||
"automember_update_member_value - group (default or target) can not be retrieved (%s) err=%d\n",
|
||||
group_dn, rc);
|
||||
}
|
||||
- return rc;
|
||||
+ goto out;
|
||||
}
|
||||
|
||||
/* If grouping_value is dn, we need to fetch the dn instead. */
|
||||
@@ -1879,6 +1929,13 @@ automember_mod_post_op(Slapi_PBlock *pb)
|
||||
PRCList *list = NULL;
|
||||
int rc = SLAPI_PLUGIN_SUCCESS;
|
||||
|
||||
+ if (slapi_td_is_post_op_nested()) {
|
||||
+ /* don't process op twice in the same thread */
|
||||
+ return rc;
|
||||
+ } else {
|
||||
+ slapi_td_block_nested_post_op();
|
||||
+ }
|
||||
+
|
||||
slapi_log_err(SLAPI_LOG_TRACE, AUTOMEMBER_PLUGIN_SUBSYSTEM,
|
||||
"--> automember_mod_post_op\n");
|
||||
|
||||
@@ -2005,6 +2062,7 @@ automember_mod_post_op(Slapi_PBlock *pb)
|
||||
}
|
||||
}
|
||||
}
|
||||
+ slapi_td_unblock_nested_post_op();
|
||||
|
||||
slapi_log_err(SLAPI_LOG_TRACE, AUTOMEMBER_PLUGIN_SUBSYSTEM,
|
||||
"<-- automember_mod_post_op (%d)\n", rc);
|
||||
@@ -2024,6 +2082,13 @@ automember_add_post_op(Slapi_PBlock *pb)
|
||||
slapi_log_err(SLAPI_LOG_TRACE, AUTOMEMBER_PLUGIN_SUBSYSTEM,
|
||||
"--> automember_add_post_op\n");
|
||||
|
||||
+ if (slapi_td_is_post_op_nested()) {
|
||||
+ /* don't process op twice in the same thread */
|
||||
+ return rc;
|
||||
+ } else {
|
||||
+ slapi_td_block_nested_post_op();
|
||||
+ }
|
||||
+
|
||||
/* Reload config if a config entry was added. */
|
||||
if ((sdn = automember_get_sdn(pb))) {
|
||||
if (automember_dn_is_config(sdn)) {
|
||||
@@ -2039,7 +2104,7 @@ automember_add_post_op(Slapi_PBlock *pb)
|
||||
|
||||
/* If replication, just bail. */
|
||||
if (automember_isrepl(pb)) {
|
||||
- return SLAPI_PLUGIN_SUCCESS;
|
||||
+ goto bail;
|
||||
}
|
||||
|
||||
/* Get the newly added entry. */
|
||||
@@ -2052,7 +2117,7 @@ automember_add_post_op(Slapi_PBlock *pb)
|
||||
tombstone);
|
||||
slapi_value_free(&tombstone);
|
||||
if (is_tombstone) {
|
||||
- return SLAPI_PLUGIN_SUCCESS;
|
||||
+ goto bail;
|
||||
}
|
||||
|
||||
/* Check if a config entry applies
|
||||
@@ -2063,21 +2128,19 @@ automember_add_post_op(Slapi_PBlock *pb)
|
||||
list = PR_LIST_HEAD(g_automember_config);
|
||||
while (list != g_automember_config) {
|
||||
config = (struct configEntry *)list;
|
||||
-
|
||||
/* Does the entry meet scope and filter requirements? */
|
||||
if (slapi_dn_issuffix(slapi_sdn_get_dn(sdn), config->scope) &&
|
||||
- (slapi_filter_test_simple(e, config->filter) == 0)) {
|
||||
+ (slapi_filter_test_simple(e, config->filter) == 0))
|
||||
+ {
|
||||
/* Find out what membership changes are needed and make them. */
|
||||
if (automember_update_membership(config, e, NULL) == SLAPI_PLUGIN_FAILURE) {
|
||||
rc = SLAPI_PLUGIN_FAILURE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
-
|
||||
list = PR_NEXT_LINK(list);
|
||||
}
|
||||
}
|
||||
-
|
||||
automember_config_unlock();
|
||||
} else {
|
||||
slapi_log_err(SLAPI_LOG_PLUGIN, AUTOMEMBER_PLUGIN_SUBSYSTEM,
|
||||
@@ -2098,6 +2161,7 @@ bail:
|
||||
slapi_pblock_set(pb, SLAPI_RESULT_CODE, &result);
|
||||
slapi_pblock_set(pb, SLAPI_PB_RESULT_TEXT, &errtxt);
|
||||
}
|
||||
+ slapi_td_unblock_nested_post_op();
|
||||
|
||||
return rc;
|
||||
}
|
||||
@@ -2138,6 +2202,7 @@ typedef struct _task_data
|
||||
Slapi_DN *base_dn;
|
||||
char *bind_dn;
|
||||
int scope;
|
||||
+ PRBool cleanup;
|
||||
} task_data;
|
||||
|
||||
static void
|
||||
@@ -2270,6 +2335,7 @@ automember_task_abort_thread(void *arg)
|
||||
* basedn: dc=example,dc=com
|
||||
* filter: (uid=*)
|
||||
* scope: sub
|
||||
+ * cleanup: yes/on (default is off)
|
||||
*
|
||||
* basedn and filter are required. If scope is omitted, the default is sub
|
||||
*/
|
||||
@@ -2284,9 +2350,22 @@ automember_task_add(Slapi_PBlock *pb, Slapi_Entry *e, Slapi_Entry *eAfter __attr
|
||||
const char *base_dn;
|
||||
const char *filter;
|
||||
const char *scope;
|
||||
+ const char *cleanup_str;
|
||||
+ PRBool cleanup = PR_FALSE;
|
||||
|
||||
*returncode = LDAP_SUCCESS;
|
||||
|
||||
+ PR_Lock(fixup_lock);
|
||||
+ if (fixup_running) {
|
||||
+ PR_Unlock(fixup_lock);
|
||||
+ *returncode = LDAP_UNWILLING_TO_PERFORM;
|
||||
+ slapi_log_err(SLAPI_LOG_ERR, AUTOMEMBER_PLUGIN_SUBSYSTEM,
|
||||
+ "automember_task_add - there is already a fixup task running\n");
|
||||
+ rv = SLAPI_DSE_CALLBACK_ERROR;
|
||||
+ goto out;
|
||||
+ }
|
||||
+ PR_Unlock(fixup_lock);
|
||||
+
|
||||
/*
|
||||
* Grab the task params
|
||||
*/
|
||||
@@ -2300,6 +2379,12 @@ automember_task_add(Slapi_PBlock *pb, Slapi_Entry *e, Slapi_Entry *eAfter __attr
|
||||
rv = SLAPI_DSE_CALLBACK_ERROR;
|
||||
goto out;
|
||||
}
|
||||
+ if ((cleanup_str = slapi_entry_attr_get_ref(e, "cleanup"))) {
|
||||
+ if (strcasecmp(cleanup_str, "yes") == 0 || strcasecmp(cleanup_str, "on")) {
|
||||
+ cleanup = PR_TRUE;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
scope = slapi_fetch_attr(e, "scope", "sub");
|
||||
/*
|
||||
* setup our task data
|
||||
@@ -2315,6 +2400,7 @@ automember_task_add(Slapi_PBlock *pb, Slapi_Entry *e, Slapi_Entry *eAfter __attr
|
||||
mytaskdata->bind_dn = slapi_ch_strdup(bind_dn);
|
||||
mytaskdata->base_dn = slapi_sdn_new_dn_byval(base_dn);
|
||||
mytaskdata->filter_str = slapi_ch_strdup(filter);
|
||||
+ mytaskdata->cleanup = cleanup;
|
||||
|
||||
if (scope) {
|
||||
if (strcasecmp(scope, "sub") == 0) {
|
||||
@@ -2334,6 +2420,9 @@ automember_task_add(Slapi_PBlock *pb, Slapi_Entry *e, Slapi_Entry *eAfter __attr
|
||||
task = slapi_plugin_new_task(slapi_entry_get_ndn(e), arg);
|
||||
slapi_task_set_destructor_fn(task, automember_task_destructor);
|
||||
slapi_task_set_data(task, mytaskdata);
|
||||
+ PR_Lock(fixup_lock);
|
||||
+ fixup_running = PR_TRUE;
|
||||
+ PR_Unlock(fixup_lock);
|
||||
/*
|
||||
* Start the task as a separate thread
|
||||
*/
|
||||
@@ -2345,6 +2434,9 @@ automember_task_add(Slapi_PBlock *pb, Slapi_Entry *e, Slapi_Entry *eAfter __attr
|
||||
"automember_task_add - Unable to create task thread!\n");
|
||||
*returncode = LDAP_OPERATIONS_ERROR;
|
||||
slapi_task_finish(task, *returncode);
|
||||
+ PR_Lock(fixup_lock);
|
||||
+ fixup_running = PR_FALSE;
|
||||
+ PR_Unlock(fixup_lock);
|
||||
rv = SLAPI_DSE_CALLBACK_ERROR;
|
||||
} else {
|
||||
rv = SLAPI_DSE_CALLBACK_OK;
|
||||
@@ -2372,6 +2464,9 @@ automember_rebuild_task_thread(void *arg)
|
||||
PRCList *list = NULL;
|
||||
PRCList *include_list = NULL;
|
||||
int result = 0;
|
||||
+ int64_t fixup_progress_count = 0;
|
||||
+ int64_t fixup_progress_elapsed = 0;
|
||||
+ int64_t fixup_start_time = 0;
|
||||
size_t i = 0;
|
||||
|
||||
/* Reset abort flag */
|
||||
@@ -2380,6 +2475,7 @@ automember_rebuild_task_thread(void *arg)
|
||||
if (!task) {
|
||||
return; /* no task */
|
||||
}
|
||||
+
|
||||
slapi_task_inc_refcount(task);
|
||||
slapi_log_err(SLAPI_LOG_PLUGIN, AUTOMEMBER_PLUGIN_SUBSYSTEM,
|
||||
"automember_rebuild_task_thread - Refcount incremented.\n");
|
||||
@@ -2393,9 +2489,11 @@ automember_rebuild_task_thread(void *arg)
|
||||
slapi_task_log_status(task, "Automember rebuild task starting (base dn: (%s) filter (%s)...",
|
||||
slapi_sdn_get_dn(td->base_dn), td->filter_str);
|
||||
/*
|
||||
- * Set the bind dn in the local thread data
|
||||
+ * Set the bind dn in the local thread data, and block post op mods
|
||||
*/
|
||||
slapi_td_set_dn(slapi_ch_strdup(td->bind_dn));
|
||||
+ slapi_td_block_nested_post_op();
|
||||
+ fixup_start_time = slapi_current_rel_time_t();
|
||||
/*
|
||||
* Take the config lock now and search the database
|
||||
*/
|
||||
@@ -2426,6 +2524,21 @@ automember_rebuild_task_thread(void *arg)
|
||||
* Loop over the entries
|
||||
*/
|
||||
for (i = 0; entries && (entries[i] != NULL); i++) {
|
||||
+ fixup_progress_count++;
|
||||
+ if (fixup_progress_count % FIXUP_PROGRESS_LIMIT == 0 ) {
|
||||
+ slapi_task_log_notice(task,
|
||||
+ "Processed %ld entries in %ld seconds (+%ld seconds)",
|
||||
+ fixup_progress_count,
|
||||
+ slapi_current_rel_time_t() - fixup_start_time,
|
||||
+ slapi_current_rel_time_t() - fixup_progress_elapsed);
|
||||
+ slapi_task_log_status(task,
|
||||
+ "Processed %ld entries in %ld seconds (+%ld seconds)",
|
||||
+ fixup_progress_count,
|
||||
+ slapi_current_rel_time_t() - fixup_start_time,
|
||||
+ slapi_current_rel_time_t() - fixup_progress_elapsed);
|
||||
+ slapi_task_inc_progress(task);
|
||||
+ fixup_progress_elapsed = slapi_current_rel_time_t();
|
||||
+ }
|
||||
if (slapi_atomic_load_64(&abort_rebuild_task, __ATOMIC_ACQUIRE) == 1) {
|
||||
/* The task was aborted */
|
||||
slapi_task_log_notice(task, "Automember rebuild task was intentionally aborted");
|
||||
@@ -2443,48 +2556,66 @@ automember_rebuild_task_thread(void *arg)
|
||||
if (slapi_dn_issuffix(slapi_entry_get_dn(entries[i]), config->scope) &&
|
||||
(slapi_filter_test_simple(entries[i], config->filter) == 0))
|
||||
{
|
||||
- /* First clear out all the defaults groups */
|
||||
- for (size_t ii = 0; config->default_groups && config->default_groups[ii]; ii++) {
|
||||
- if ((result = automember_update_member_value(entries[i], config->default_groups[ii],
|
||||
- config->grouping_attr, config->grouping_value, NULL, DEL_MEMBER)))
|
||||
- {
|
||||
- slapi_task_log_notice(task, "Automember rebuild membership task unable to delete "
|
||||
- "member from default group (%s) error (%d)",
|
||||
- config->default_groups[ii], result);
|
||||
- slapi_task_log_status(task, "Automember rebuild membership task unable to delete "
|
||||
- "member from default group (%s) error (%d)",
|
||||
- config->default_groups[ii], result);
|
||||
- slapi_log_err(SLAPI_LOG_ERR, AUTOMEMBER_PLUGIN_SUBSYSTEM,
|
||||
- "automember_rebuild_task_thread - Unable to unable to delete from (%s) error (%d)\n",
|
||||
- config->default_groups[ii], result);
|
||||
- goto out;
|
||||
- }
|
||||
- }
|
||||
-
|
||||
- /* Then clear out the non-default group */
|
||||
- if (config->inclusive_rules && !PR_CLIST_IS_EMPTY((PRCList *)config->inclusive_rules)) {
|
||||
- include_list = PR_LIST_HEAD((PRCList *)config->inclusive_rules);
|
||||
- while (include_list != (PRCList *)config->inclusive_rules) {
|
||||
- struct automemberRegexRule *curr_rule = (struct automemberRegexRule *)include_list;
|
||||
- if ((result = automember_update_member_value(entries[i], slapi_sdn_get_dn(curr_rule->target_group_dn),
|
||||
- config->grouping_attr, config->grouping_value, NULL, DEL_MEMBER)))
|
||||
+ if (td->cleanup) {
|
||||
+
|
||||
+ slapi_log_err(SLAPI_LOG_PLUGIN, AUTOMEMBER_PLUGIN_SUBSYSTEM,
|
||||
+ "automember_rebuild_task_thread - Cleaning up groups (config %s)\n",
|
||||
+ config->dn);
|
||||
+ /* First clear out all the defaults groups */
|
||||
+ for (size_t ii = 0; config->default_groups && config->default_groups[ii]; ii++) {
|
||||
+ if ((result = automember_update_member_value(entries[i],
|
||||
+ config->default_groups[ii],
|
||||
+ config->grouping_attr,
|
||||
+ config->grouping_value,
|
||||
+ NULL, DEL_MEMBER)))
|
||||
{
|
||||
slapi_task_log_notice(task, "Automember rebuild membership task unable to delete "
|
||||
- "member from group (%s) error (%d)",
|
||||
- slapi_sdn_get_dn(curr_rule->target_group_dn), result);
|
||||
+ "member from default group (%s) error (%d)",
|
||||
+ config->default_groups[ii], result);
|
||||
slapi_task_log_status(task, "Automember rebuild membership task unable to delete "
|
||||
- "member from group (%s) error (%d)",
|
||||
- slapi_sdn_get_dn(curr_rule->target_group_dn), result);
|
||||
+ "member from default group (%s) error (%d)",
|
||||
+ config->default_groups[ii], result);
|
||||
slapi_log_err(SLAPI_LOG_ERR, AUTOMEMBER_PLUGIN_SUBSYSTEM,
|
||||
"automember_rebuild_task_thread - Unable to unable to delete from (%s) error (%d)\n",
|
||||
- slapi_sdn_get_dn(curr_rule->target_group_dn), result);
|
||||
+ config->default_groups[ii], result);
|
||||
goto out;
|
||||
}
|
||||
- include_list = PR_NEXT_LINK(include_list);
|
||||
}
|
||||
+
|
||||
+ /* Then clear out the non-default group */
|
||||
+ if (config->inclusive_rules && !PR_CLIST_IS_EMPTY((PRCList *)config->inclusive_rules)) {
|
||||
+ include_list = PR_LIST_HEAD((PRCList *)config->inclusive_rules);
|
||||
+ while (include_list != (PRCList *)config->inclusive_rules) {
|
||||
+ struct automemberRegexRule *curr_rule = (struct automemberRegexRule *)include_list;
|
||||
+ if ((result = automember_update_member_value(entries[i],
|
||||
+ slapi_sdn_get_dn(curr_rule->target_group_dn),
|
||||
+ config->grouping_attr,
|
||||
+ config->grouping_value,
|
||||
+ NULL, DEL_MEMBER)))
|
||||
+ {
|
||||
+ slapi_task_log_notice(task, "Automember rebuild membership task unable to delete "
|
||||
+ "member from group (%s) error (%d)",
|
||||
+ slapi_sdn_get_dn(curr_rule->target_group_dn), result);
|
||||
+ slapi_task_log_status(task, "Automember rebuild membership task unable to delete "
|
||||
+ "member from group (%s) error (%d)",
|
||||
+ slapi_sdn_get_dn(curr_rule->target_group_dn), result);
|
||||
+ slapi_log_err(SLAPI_LOG_ERR, AUTOMEMBER_PLUGIN_SUBSYSTEM,
|
||||
+ "automember_rebuild_task_thread - Unable to unable to delete from (%s) error (%d)\n",
|
||||
+ slapi_sdn_get_dn(curr_rule->target_group_dn), result);
|
||||
+ goto out;
|
||||
+ }
|
||||
+ include_list = PR_NEXT_LINK(include_list);
|
||||
+ }
|
||||
+ }
|
||||
+ slapi_log_err(SLAPI_LOG_PLUGIN, AUTOMEMBER_PLUGIN_SUBSYSTEM,
|
||||
+ "automember_rebuild_task_thread - Finished cleaning up groups (config %s)\n",
|
||||
+ config->dn);
|
||||
}
|
||||
|
||||
/* Update the memberships for this entries */
|
||||
+ slapi_log_err(SLAPI_LOG_PLUGIN, AUTOMEMBER_PLUGIN_SUBSYSTEM,
|
||||
+ "automember_rebuild_task_thread - Updating membership (config %s)\n",
|
||||
+ config->dn);
|
||||
if (slapi_is_shutting_down() ||
|
||||
automember_update_membership(config, entries[i], NULL) == SLAPI_PLUGIN_FAILURE)
|
||||
{
|
||||
@@ -2508,15 +2639,22 @@ out:
|
||||
slapi_task_log_notice(task, "Automember rebuild task aborted. Error (%d)", result);
|
||||
slapi_task_log_status(task, "Automember rebuild task aborted. Error (%d)", result);
|
||||
} else {
|
||||
- slapi_task_log_notice(task, "Automember rebuild task finished. Processed (%d) entries.", (int32_t)i);
|
||||
- slapi_task_log_status(task, "Automember rebuild task finished. Processed (%d) entries.", (int32_t)i);
|
||||
+ slapi_task_log_notice(task, "Automember rebuild task finished. Processed (%ld) entries in %ld seconds",
|
||||
+ (int64_t)i, slapi_current_rel_time_t() - fixup_start_time);
|
||||
+ slapi_task_log_status(task, "Automember rebuild task finished. Processed (%ld) entries in %ld seconds",
|
||||
+ (int64_t)i, slapi_current_rel_time_t() - fixup_start_time);
|
||||
}
|
||||
slapi_task_inc_progress(task);
|
||||
slapi_task_finish(task, result);
|
||||
slapi_task_dec_refcount(task);
|
||||
slapi_atomic_store_64(&abort_rebuild_task, 0, __ATOMIC_RELEASE);
|
||||
+ slapi_td_unblock_nested_post_op();
|
||||
+ PR_Lock(fixup_lock);
|
||||
+ fixup_running = PR_FALSE;
|
||||
+ PR_Unlock(fixup_lock);
|
||||
+
|
||||
slapi_log_err(SLAPI_LOG_PLUGIN, AUTOMEMBER_PLUGIN_SUBSYSTEM,
|
||||
- "automember_rebuild_task_thread - Refcount decremented.\n");
|
||||
+ "automember_rebuild_task_thread - task finished, refcount decremented.\n");
|
||||
}
|
||||
|
||||
/*
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_add.c b/ldap/servers/slapd/back-ldbm/ldbm_add.c
|
||||
index ba2d73a84..ce4c314a1 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/ldbm_add.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/ldbm_add.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) 2022 Red Hat, Inc.
|
||||
* Copyright (C) 2009 Hewlett-Packard Development Company, L.P.
|
||||
* All rights reserved.
|
||||
*
|
||||
@@ -1264,10 +1264,6 @@ ldbm_back_add(Slapi_PBlock *pb)
|
||||
goto common_return;
|
||||
|
||||
error_return:
|
||||
- /* Revert the caches if this is the parent operation */
|
||||
- if (parent_op && betxn_callback_fails) {
|
||||
- revert_cache(inst, &parent_time);
|
||||
- }
|
||||
if (addingentry_id_assigned) {
|
||||
next_id_return(be, addingentry->ep_id);
|
||||
}
|
||||
@@ -1376,6 +1372,11 @@ diskfull_return:
|
||||
if (!not_an_error) {
|
||||
rc = SLAPI_FAIL_GENERAL;
|
||||
}
|
||||
+
|
||||
+ /* Revert the caches if this is the parent operation */
|
||||
+ if (parent_op && betxn_callback_fails) {
|
||||
+ revert_cache(inst, &parent_time);
|
||||
+ }
|
||||
}
|
||||
|
||||
common_return:
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_delete.c b/ldap/servers/slapd/back-ldbm/ldbm_delete.c
|
||||
index de23190c3..27f0ac58a 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/ldbm_delete.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/ldbm_delete.c
|
||||
@@ -1407,11 +1407,6 @@ commit_return:
|
||||
goto common_return;
|
||||
|
||||
error_return:
|
||||
- /* Revert the caches if this is the parent operation */
|
||||
- if (parent_op && betxn_callback_fails) {
|
||||
- revert_cache(inst, &parent_time);
|
||||
- }
|
||||
-
|
||||
if (tombstone) {
|
||||
if (cache_is_in_cache(&inst->inst_cache, tombstone)) {
|
||||
tomb_ep_id = tombstone->ep_id; /* Otherwise, tombstone might have been freed. */
|
||||
@@ -1496,6 +1491,11 @@ error_return:
|
||||
conn_id, op_id, parent_modify_c.old_entry, parent_modify_c.new_entry, myrc);
|
||||
}
|
||||
|
||||
+ /* Revert the caches if this is the parent operation */
|
||||
+ if (parent_op && betxn_callback_fails) {
|
||||
+ revert_cache(inst, &parent_time);
|
||||
+ }
|
||||
+
|
||||
common_return:
|
||||
if (orig_entry) {
|
||||
/* NOTE: #define SLAPI_DELETE_BEPREOP_ENTRY SLAPI_ENTRY_PRE_OP */
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_modify.c b/ldap/servers/slapd/back-ldbm/ldbm_modify.c
|
||||
index 537369055..64b293001 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/ldbm_modify.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/ldbm_modify.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) 2022 Red Hat, Inc.
|
||||
* Copyright (C) 2009 Hewlett-Packard Development Company, L.P.
|
||||
* All rights reserved.
|
||||
*
|
||||
@@ -1043,11 +1043,6 @@ ldbm_back_modify(Slapi_PBlock *pb)
|
||||
goto common_return;
|
||||
|
||||
error_return:
|
||||
- /* Revert the caches if this is the parent operation */
|
||||
- if (parent_op && betxn_callback_fails) {
|
||||
- revert_cache(inst, &parent_time);
|
||||
- }
|
||||
-
|
||||
if (postentry != NULL) {
|
||||
slapi_entry_free(postentry);
|
||||
postentry = NULL;
|
||||
@@ -1103,6 +1098,10 @@ error_return:
|
||||
if (!not_an_error) {
|
||||
rc = SLAPI_FAIL_GENERAL;
|
||||
}
|
||||
+ /* Revert the caches if this is the parent operation */
|
||||
+ if (parent_op && betxn_callback_fails) {
|
||||
+ revert_cache(inst, &parent_time);
|
||||
+ }
|
||||
}
|
||||
|
||||
/* if ec is in cache, remove it, then add back e if we still have it */
|
||||
diff --git a/src/lib389/lib389/cli_conf/plugins/automember.py b/src/lib389/lib389/cli_conf/plugins/automember.py
|
||||
index 15b00c633..568586ad8 100644
|
||||
--- a/src/lib389/lib389/cli_conf/plugins/automember.py
|
||||
+++ b/src/lib389/lib389/cli_conf/plugins/automember.py
|
||||
@@ -155,7 +155,7 @@ def fixup(inst, basedn, log, args):
|
||||
log.info('Attempting to add task entry... This will fail if Automembership plug-in is not enabled.')
|
||||
if not plugin.status():
|
||||
log.error("'%s' is disabled. Rebuild membership task can't be executed" % plugin.rdn)
|
||||
- fixup_task = plugin.fixup(args.DN, args.filter)
|
||||
+ fixup_task = plugin.fixup(args.DN, args.filter, args.cleanup)
|
||||
if args.wait:
|
||||
log.info(f'Waiting for fixup task "{fixup_task.dn}" to complete. You can safely exit by pressing Control C ...')
|
||||
fixup_task.wait(timeout=args.timeout)
|
||||
@@ -225,8 +225,8 @@ def create_parser(subparsers):
|
||||
subcommands = automember.add_subparsers(help='action')
|
||||
add_generic_plugin_parsers(subcommands, AutoMembershipPlugin)
|
||||
|
||||
- list = subcommands.add_parser('list', help='List Automembership definitions or regex rules.')
|
||||
- subcommands_list = list.add_subparsers(help='action')
|
||||
+ automember_list = subcommands.add_parser('list', help='List Automembership definitions or regex rules.')
|
||||
+ subcommands_list = automember_list.add_subparsers(help='action')
|
||||
list_definitions = subcommands_list.add_parser('definitions', help='Lists Automembership definitions.')
|
||||
list_definitions.set_defaults(func=definition_list)
|
||||
list_regexes = subcommands_list.add_parser('regexes', help='List Automembership regex rules.')
|
||||
@@ -269,6 +269,8 @@ def create_parser(subparsers):
|
||||
fixup_task.add_argument('-f', '--filter', required=True, help='Sets the LDAP filter for entries to fix up')
|
||||
fixup_task.add_argument('-s', '--scope', required=True, choices=['sub', 'base', 'one'], type=str.lower,
|
||||
help='Sets the LDAP search scope for entries to fix up')
|
||||
+ fixup_task.add_argument('--cleanup', action='store_true',
|
||||
+ help="Clean up previous group memberships before rebuilding")
|
||||
fixup_task.add_argument('--wait', action='store_true',
|
||||
help="Wait for the task to finish, this could take a long time")
|
||||
fixup_task.add_argument('--timeout', default=0, type=int,
|
||||
@@ -279,7 +281,7 @@ def create_parser(subparsers):
|
||||
fixup_status.add_argument('--dn', help="The task entry's DN")
|
||||
fixup_status.add_argument('--show-log', action='store_true', help="Display the task log")
|
||||
fixup_status.add_argument('--watch', action='store_true',
|
||||
- help="Watch the task's status and wait for it to finish")
|
||||
+ help="Watch the task's status and wait for it to finish")
|
||||
|
||||
abort_fixup = subcommands.add_parser('abort-fixup', help='Abort the rebuild membership task.')
|
||||
abort_fixup.set_defaults(func=abort)
|
||||
diff --git a/src/lib389/lib389/plugins.py b/src/lib389/lib389/plugins.py
|
||||
index 52691a44c..a1ad0a45b 100644
|
||||
--- a/src/lib389/lib389/plugins.py
|
||||
+++ b/src/lib389/lib389/plugins.py
|
||||
@@ -1141,13 +1141,15 @@ class AutoMembershipPlugin(Plugin):
|
||||
def __init__(self, instance, dn="cn=Auto Membership Plugin,cn=plugins,cn=config"):
|
||||
super(AutoMembershipPlugin, self).__init__(instance, dn)
|
||||
|
||||
- def fixup(self, basedn, _filter=None):
|
||||
+ def fixup(self, basedn, _filter=None, cleanup=False):
|
||||
"""Create an automember rebuild membership task
|
||||
|
||||
:param basedn: Basedn to fix up
|
||||
:type basedn: str
|
||||
:param _filter: a filter for entries to fix up
|
||||
:type _filter: str
|
||||
+ :param cleanup: cleanup old group memberships
|
||||
+ :type cleanup: boolean
|
||||
|
||||
:returns: an instance of Task(DSLdapObject)
|
||||
"""
|
||||
@@ -1156,6 +1158,9 @@ class AutoMembershipPlugin(Plugin):
|
||||
task_properties = {'basedn': basedn}
|
||||
if _filter is not None:
|
||||
task_properties['filter'] = _filter
|
||||
+ if cleanup:
|
||||
+ task_properties['cleanup'] = "yes"
|
||||
+
|
||||
task.create(properties=task_properties)
|
||||
|
||||
return task
|
||||
diff --git a/src/lib389/lib389/tasks.py b/src/lib389/lib389/tasks.py
|
||||
index 1a16bbb83..193805780 100644
|
||||
--- a/src/lib389/lib389/tasks.py
|
||||
+++ b/src/lib389/lib389/tasks.py
|
||||
@@ -1006,12 +1006,13 @@ class Tasks(object):
|
||||
return exitCode
|
||||
|
||||
def automemberRebuild(self, suffix=DEFAULT_SUFFIX, scope='sub',
|
||||
- filterstr='objectclass=top', args=None):
|
||||
+ filterstr='objectclass=top', cleanup=False, args=None):
|
||||
'''
|
||||
- @param suffix - The suffix the task should examine - defualt is
|
||||
+ @param suffix - The suffix the task should examine - default is
|
||||
"dc=example,dc=com"
|
||||
@param scope - The scope of the search to find entries
|
||||
- @param fitlerstr - THe search filter to find entries
|
||||
+ @param fitlerstr - The search filter to find entries
|
||||
+ @param cleanup - reset/clear the old group mmeberships prior to rebuilding
|
||||
@param args - is a dictionary that contains modifier of the task
|
||||
wait: True/[False] - If True, waits for the completion of
|
||||
the task before to return
|
||||
@@ -1027,6 +1028,8 @@ class Tasks(object):
|
||||
entry.setValues('basedn', suffix)
|
||||
entry.setValues('filter', filterstr)
|
||||
entry.setValues('scope', scope)
|
||||
+ if cleanup:
|
||||
+ entry.setValues('cleanup', 'yes')
|
||||
|
||||
# start the task and possibly wait for task completion
|
||||
try:
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@ -0,0 +1,213 @@
|
||||
From 5b6211de252f801ffc088703acc242e47f6273ab Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Fri, 9 Jan 2026 11:39:50 +0100
|
||||
Subject: [PATCH] Issue 7172 - Index ordering mismatch after upgrade (#7173)
|
||||
|
||||
Bug Description:
|
||||
Commit daf731f55071d45eaf403a52b63d35f4e699ff28 introduced a regression.
|
||||
After upgrading to a version that adds `integerOrderingMatch` matching
|
||||
rule to `parentid` and `ancestorid` indexes, searches may return empty
|
||||
or incorrect results.
|
||||
|
||||
This happens because the existing index data was created with
|
||||
lexicographic ordering, but the new compare function expects integer
|
||||
ordering. Index lookups fail because the compare function doesn't match
|
||||
the data ordering.
|
||||
The root cause is that `ldbm_instance_create_default_indexes()` calls
|
||||
`attr_index_config()` unconditionally for `parentid` and `ancestorid`
|
||||
indexes, which triggers `ainfo_dup()` to overwrite `ai_key_cmp_fn` on
|
||||
existing indexes. This breaks indexes that were created without the
|
||||
`integerOrderingMatch` matching rule.
|
||||
|
||||
Fix Description:
|
||||
* Call `attr_index_config()` for `parentid` and `ancestorid` indexes
|
||||
only if index config doesn't exist.
|
||||
|
||||
* Add `upgrade_check_id_index_matching_rule()` that logs an error on
|
||||
server startup if `parentid` or `ancestorid` indexes are missing the
|
||||
integerOrderingMatch matching rule, advising administrators to reindex.
|
||||
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/7172
|
||||
|
||||
Reviewed by: @tbordaz, @progier389, @droideck (Thanks!)
|
||||
---
|
||||
ldap/servers/slapd/back-ldbm/instance.c | 25 ++++--
|
||||
ldap/servers/slapd/upgrade.c | 105 ++++++++++++++++++++++++
|
||||
2 files changed, 122 insertions(+), 8 deletions(-)
|
||||
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/instance.c b/ldap/servers/slapd/back-ldbm/instance.c
|
||||
index 24c00200b..30db99c81 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/instance.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/instance.c
|
||||
@@ -191,6 +191,7 @@ ldbm_instance_create_default_indexes(backend *be)
|
||||
char *ancestorid_indexes_limit = NULL;
|
||||
char *parentid_indexes_limit = NULL;
|
||||
struct attrinfo *ai = NULL;
|
||||
+ struct attrinfo *index_already_configured = NULL;
|
||||
struct index_idlistsizeinfo *iter;
|
||||
int cookie;
|
||||
int limit;
|
||||
@@ -255,10 +256,14 @@ ldbm_instance_create_default_indexes(backend *be)
|
||||
slapi_entry_free(e);
|
||||
}
|
||||
|
||||
- e = ldbm_instance_init_config_entry(LDBM_PARENTID_STR, "eq", 0, 0, 0, "integerOrderingMatch", parentid_indexes_limit);
|
||||
- ldbm_instance_config_add_index_entry(inst, e, flags);
|
||||
- attr_index_config(be, "ldbm index init", 0, e, 1, 0, NULL);
|
||||
- slapi_entry_free(e);
|
||||
+ ainfo_get(be, (char *)LDBM_PARENTID_STR, &ai);
|
||||
+ index_already_configured = ai;
|
||||
+ if (!index_already_configured) {
|
||||
+ e = ldbm_instance_init_config_entry(LDBM_PARENTID_STR, "eq", 0, 0, 0, "integerOrderingMatch", parentid_indexes_limit);
|
||||
+ ldbm_instance_config_add_index_entry(inst, e, flags);
|
||||
+ attr_index_config(be, "ldbm index init", 0, e, 1, 0, NULL);
|
||||
+ slapi_entry_free(e);
|
||||
+ }
|
||||
|
||||
e = ldbm_instance_init_config_entry("objectclass", "eq", 0, 0, 0, 0, 0);
|
||||
ldbm_instance_config_add_index_entry(inst, e, flags);
|
||||
@@ -296,10 +301,14 @@ ldbm_instance_create_default_indexes(backend *be)
|
||||
* ancestorid is special, there is actually no such attr type
|
||||
* but we still want to use the attr index file APIs.
|
||||
*/
|
||||
- e = ldbm_instance_init_config_entry(LDBM_ANCESTORID_STR, "eq", 0, 0, 0, "integerOrderingMatch", ancestorid_indexes_limit);
|
||||
- ldbm_instance_config_add_index_entry(inst, e, flags);
|
||||
- attr_index_config(be, "ldbm index init", 0, e, 1, 0, NULL);
|
||||
- slapi_entry_free(e);
|
||||
+ ainfo_get(be, (char *)LDBM_ANCESTORID_STR, &ai);
|
||||
+ index_already_configured = ai;
|
||||
+ if (!index_already_configured) {
|
||||
+ e = ldbm_instance_init_config_entry(LDBM_ANCESTORID_STR, "eq", 0, 0, 0, "integerOrderingMatch", ancestorid_indexes_limit);
|
||||
+ ldbm_instance_config_add_index_entry(inst, e, flags);
|
||||
+ attr_index_config(be, "ldbm index init", 0, e, 1, 0, NULL);
|
||||
+ slapi_entry_free(e);
|
||||
+ }
|
||||
}
|
||||
|
||||
slapi_ch_free_string(&ancestorid_indexes_limit);
|
||||
diff --git a/ldap/servers/slapd/upgrade.c b/ldap/servers/slapd/upgrade.c
|
||||
index 0d1e2abf4..43906c1af 100644
|
||||
--- a/ldap/servers/slapd/upgrade.c
|
||||
+++ b/ldap/servers/slapd/upgrade.c
|
||||
@@ -279,6 +279,107 @@ upgrade_205_fixup_repl_dep(void)
|
||||
return UPGRADE_SUCCESS;
|
||||
}
|
||||
|
||||
+/*
|
||||
+ * Check if parentid/ancestorid indexes are missing the integerOrderingMatch
|
||||
+ * matching rule.
|
||||
+ *
|
||||
+ * This function logs a warning if we detect this condition, advising
|
||||
+ * the administrator to reindex the affected attributes.
|
||||
+ */
|
||||
+static upgrade_status
|
||||
+upgrade_check_id_index_matching_rule(void)
|
||||
+{
|
||||
+ struct slapi_pblock *pb = slapi_pblock_new();
|
||||
+ Slapi_Entry **backends = NULL;
|
||||
+ const char *be_base_dn = "cn=ldbm database,cn=plugins,cn=config";
|
||||
+ const char *be_filter = "(objectclass=nsBackendInstance)";
|
||||
+ const char *attrs_to_check[] = {"parentid", "ancestorid", NULL};
|
||||
+ upgrade_status uresult = UPGRADE_SUCCESS;
|
||||
+
|
||||
+ /* Search for all backend instances */
|
||||
+ slapi_search_internal_set_pb(
|
||||
+ pb, be_base_dn,
|
||||
+ LDAP_SCOPE_ONELEVEL,
|
||||
+ be_filter, NULL, 0, NULL, NULL,
|
||||
+ plugin_get_default_component_id(), 0);
|
||||
+ slapi_search_internal_pb(pb);
|
||||
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &backends);
|
||||
+
|
||||
+ if (backends) {
|
||||
+ for (size_t be_idx = 0; backends[be_idx] != NULL; be_idx++) {
|
||||
+ const char *be_name = slapi_entry_attr_get_ref(backends[be_idx], "cn");
|
||||
+ if (!be_name) {
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ /* Check each attribute that should have integerOrderingMatch */
|
||||
+ for (size_t attr_idx = 0; attrs_to_check[attr_idx] != NULL; attr_idx++) {
|
||||
+ const char *attr_name = attrs_to_check[attr_idx];
|
||||
+ struct slapi_pblock *idx_pb = slapi_pblock_new();
|
||||
+ Slapi_Entry **idx_entries = NULL;
|
||||
+ char *idx_dn = slapi_create_dn_string("cn=%s,cn=index,cn=%s,%s",
|
||||
+ attr_name, be_name, be_base_dn);
|
||||
+ char *idx_filter = "(objectclass=nsIndex)";
|
||||
+ PRBool has_matching_rule = PR_FALSE;
|
||||
+
|
||||
+ if (!idx_dn) {
|
||||
+ slapi_pblock_destroy(idx_pb);
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ slapi_search_internal_set_pb(
|
||||
+ idx_pb, idx_dn,
|
||||
+ LDAP_SCOPE_BASE,
|
||||
+ idx_filter, NULL, 0, NULL, NULL,
|
||||
+ plugin_get_default_component_id(), 0);
|
||||
+ slapi_search_internal_pb(idx_pb);
|
||||
+ slapi_pblock_get(idx_pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &idx_entries);
|
||||
+
|
||||
+ if (idx_entries && idx_entries[0]) {
|
||||
+ /* Index exists, check if it has integerOrderingMatch */
|
||||
+ Slapi_Attr *mr_attr = NULL;
|
||||
+ if (slapi_entry_attr_find(idx_entries[0], "nsMatchingRule", &mr_attr) == 0) {
|
||||
+ Slapi_Value *sval = NULL;
|
||||
+ int idx;
|
||||
+ for (idx = slapi_attr_first_value(mr_attr, &sval);
|
||||
+ idx != -1;
|
||||
+ idx = slapi_attr_next_value(mr_attr, idx, &sval)) {
|
||||
+ const struct berval *bval = slapi_value_get_berval(sval);
|
||||
+ if (bval && bval->bv_val &&
|
||||
+ strcasecmp(bval->bv_val, "integerOrderingMatch") == 0) {
|
||||
+ has_matching_rule = PR_TRUE;
|
||||
+ break;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ if (!has_matching_rule) {
|
||||
+ /* Index exists but doesn't have integerOrderingMatch, log a warning */
|
||||
+ slapi_log_err(SLAPI_LOG_ERR, "upgrade_check_id_index_matching_rule",
|
||||
+ "Index '%s' in backend '%s' is missing 'nsMatchingRule: integerOrderingMatch'. "
|
||||
+ "Incorrectly configured system indexes can lead to poor search performance, replication issues, and other operational problems. "
|
||||
+ "To fix this, add the matching rule and reindex: "
|
||||
+ "dsconf <instance> backend index set --add-mr integerOrderingMatch --attr %s %s && "
|
||||
+ "dsconf <instance> backend index reindex --attr %s %s. "
|
||||
+ "WARNING: Reindexing can be resource-intensive and may impact server performance on a live system. "
|
||||
+ "Consider scheduling reindexing during maintenance windows or periods of low activity.\n",
|
||||
+ attr_name, be_name, attr_name, be_name, attr_name, be_name);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ slapi_ch_free_string(&idx_dn);
|
||||
+ slapi_free_search_results_internal(idx_pb);
|
||||
+ slapi_pblock_destroy(idx_pb);
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ slapi_free_search_results_internal(pb);
|
||||
+ slapi_pblock_destroy(pb);
|
||||
+
|
||||
+ return uresult;
|
||||
+}
|
||||
+
|
||||
upgrade_status
|
||||
upgrade_server(void)
|
||||
{
|
||||
@@ -306,6 +407,10 @@ upgrade_server(void)
|
||||
return UPGRADE_FAILURE;
|
||||
}
|
||||
|
||||
+ if (upgrade_check_id_index_matching_rule() != UPGRADE_SUCCESS) {
|
||||
+ return UPGRADE_FAILURE;
|
||||
+ }
|
||||
+
|
||||
return UPGRADE_SUCCESS;
|
||||
}
|
||||
|
||||
--
|
||||
2.52.0
|
||||
|
||||
@ -1,83 +0,0 @@
|
||||
From 9319d5b022918f14cacb00e3faef85a6ab730a26 Mon Sep 17 00:00:00 2001
|
||||
From: Simon Pichugin <spichugi@redhat.com>
|
||||
Date: Tue, 27 Feb 2024 16:30:47 -0800
|
||||
Subject: [PATCH] Issue 3527 - Support HAProxy and Instance on the same machine
|
||||
configuration (#6107)
|
||||
|
||||
Description: Improve how we handle HAProxy connections to work better when
|
||||
the DS and HAProxy are on the same machine.
|
||||
Ensure the client and header destination IPs are checked against the trusted IP list.
|
||||
|
||||
Additionally, this change will also allow configuration having
|
||||
HAProxy is listening on a different subnet than the one used to forward the request.
|
||||
|
||||
Related: https://github.com/389ds/389-ds-base/issues/3527
|
||||
|
||||
Reviewed by: @progier389, @jchapma (Thanks!)
|
||||
---
|
||||
ldap/servers/slapd/connection.c | 35 +++++++++++++++++++++++++--------
|
||||
1 file changed, 27 insertions(+), 8 deletions(-)
|
||||
|
||||
diff --git a/ldap/servers/slapd/connection.c b/ldap/servers/slapd/connection.c
|
||||
index d28a39bf7..10a8cc577 100644
|
||||
--- a/ldap/servers/slapd/connection.c
|
||||
+++ b/ldap/servers/slapd/connection.c
|
||||
@@ -1187,6 +1187,8 @@ connection_read_operation(Connection *conn, Operation *op, ber_tag_t *tag, int *
|
||||
char str_ip[INET6_ADDRSTRLEN + 1] = {0};
|
||||
char str_haproxy_ip[INET6_ADDRSTRLEN + 1] = {0};
|
||||
char str_haproxy_destip[INET6_ADDRSTRLEN + 1] = {0};
|
||||
+ int trusted_matches_ip_found = 0;
|
||||
+ int trusted_matches_destip_found = 0;
|
||||
struct berval **bvals = NULL;
|
||||
int proxy_connection = 0;
|
||||
|
||||
@@ -1245,21 +1247,38 @@ connection_read_operation(Connection *conn, Operation *op, ber_tag_t *tag, int *
|
||||
normalize_IPv4(conn->cin_addr, buf_ip, sizeof(buf_ip), str_ip, sizeof(str_ip));
|
||||
normalize_IPv4(&pr_netaddr_dest, buf_haproxy_destip, sizeof(buf_haproxy_destip),
|
||||
str_haproxy_destip, sizeof(str_haproxy_destip));
|
||||
+ size_t ip_len = strlen(buf_ip);
|
||||
+ size_t destip_len = strlen(buf_haproxy_destip);
|
||||
|
||||
/* Now, reset RC and set it to 0 only if a match is found */
|
||||
haproxy_rc = -1;
|
||||
|
||||
- /* Allow only:
|
||||
- * Trusted IP == Original Client IP == HAProxy Header Destination IP */
|
||||
+ /*
|
||||
+ * We need to allow a configuration where DS instance and HAProxy are on the same machine.
|
||||
+ * In this case, we need to check if
|
||||
+ * the HAProxy client IP (which will be a loopback address) matches one of the the trusted IP addresses,
|
||||
+ * while still checking that
|
||||
+ * the HAProxy header destination IP address matches one of the trusted IP addresses.
|
||||
+ * Additionally, this change will also allow configuration having
|
||||
+ * HAProxy listening on a different subnet than one used to forward the request.
|
||||
+ */
|
||||
for (size_t i = 0; bvals[i] != NULL; ++i) {
|
||||
- if ((strlen(bvals[i]->bv_val) == strlen(buf_ip)) &&
|
||||
- (strlen(bvals[i]->bv_val) == strlen(buf_haproxy_destip)) &&
|
||||
- (strncasecmp(bvals[i]->bv_val, buf_ip, strlen(buf_ip)) == 0) &&
|
||||
- (strncasecmp(bvals[i]->bv_val, buf_haproxy_destip, strlen(buf_haproxy_destip)) == 0)) {
|
||||
- haproxy_rc = 0;
|
||||
- break;
|
||||
+ size_t bval_len = strlen(bvals[i]->bv_val);
|
||||
+
|
||||
+ /* Check if the Client IP (HAProxy's machine IP) address matches the trusted IP address */
|
||||
+ if (!trusted_matches_ip_found) {
|
||||
+ trusted_matches_ip_found = (bval_len == ip_len) && (strncasecmp(bvals[i]->bv_val, buf_ip, ip_len) == 0);
|
||||
+ }
|
||||
+ /* Check if the HAProxy header destination IP address matches the trusted IP address */
|
||||
+ if (!trusted_matches_destip_found) {
|
||||
+ trusted_matches_destip_found = (bval_len == destip_len) && (strncasecmp(bvals[i]->bv_val, buf_haproxy_destip, destip_len) == 0);
|
||||
}
|
||||
}
|
||||
+
|
||||
+ if (trusted_matches_ip_found && trusted_matches_destip_found) {
|
||||
+ haproxy_rc = 0;
|
||||
+ }
|
||||
+
|
||||
if (haproxy_rc == -1) {
|
||||
slapi_log_err(SLAPI_LOG_CONNS, "connection_read_operation", "HAProxy header received from unknown source.\n");
|
||||
disconnect_server_nomutex(conn, conn->c_connid, -1, SLAPD_DISCONNECT_PROXY_UNKNOWN, EPROTO);
|
||||
--
|
||||
2.45.0
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
From 2d9618da04161d7aecf2a19f5c06cd0b5749921c Mon Sep 17 00:00:00 2001
|
||||
From cae81cde000df9a28733c673ee7c9b8292d69ef3 Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Mon, 12 Jan 2026 10:58:02 +0100
|
||||
Subject: [PATCH 2/2] Issue 7172 - (2nd) Index ordering mismatch after upgrade
|
||||
Subject: [PATCH] Issue 7172 - (2nd) Index ordering mismatch after upgrade
|
||||
(#7180)
|
||||
|
||||
Commit 742c12e0247ab64e87da000a4de2f3e5c99044ab introduced a regression
|
||||
@ -30,7 +30,7 @@ Reviewed by: @tbordaz (Thanks!)
|
||||
1 file changed, 5 insertions(+), 3 deletions(-)
|
||||
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/instance.c b/ldap/servers/slapd/back-ldbm/instance.c
|
||||
index 04340a992..29643f7e4 100644
|
||||
index 30db99c81..23bd70243 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/instance.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/instance.c
|
||||
@@ -191,7 +191,7 @@ ldbm_instance_create_default_indexes(backend *be)
|
||||
@ -52,7 +52,7 @@ index 04340a992..29643f7e4 100644
|
||||
if (!index_already_configured) {
|
||||
e = ldbm_instance_init_config_entry(LDBM_PARENTID_STR, "eq", 0, 0, 0, "integerOrderingMatch", parentid_indexes_limit);
|
||||
ldbm_instance_config_add_index_entry(inst, e, flags);
|
||||
@@ -308,7 +309,8 @@ ldbm_instance_create_default_indexes(backend *be)
|
||||
@@ -302,7 +303,8 @@ ldbm_instance_create_default_indexes(backend *be)
|
||||
* but we still want to use the attr index file APIs.
|
||||
*/
|
||||
ainfo_get(be, (char *)LDBM_ANCESTORID_STR, &ai);
|
||||
@ -1,108 +0,0 @@
|
||||
From 016a2b6bd3e27cbff36609824a75b020dfd24823 Mon Sep 17 00:00:00 2001
|
||||
From: James Chapman <jachapma@redhat.com>
|
||||
Date: Wed, 1 May 2024 15:01:33 +0100
|
||||
Subject: [PATCH] CVE-2024-2199
|
||||
|
||||
---
|
||||
.../tests/suites/password/password_test.py | 56 +++++++++++++++++++
|
||||
ldap/servers/slapd/modify.c | 8 ++-
|
||||
2 files changed, 62 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/password/password_test.py b/dirsrvtests/tests/suites/password/password_test.py
|
||||
index 38079476a..b3ff08904 100644
|
||||
--- a/dirsrvtests/tests/suites/password/password_test.py
|
||||
+++ b/dirsrvtests/tests/suites/password/password_test.py
|
||||
@@ -65,6 +65,62 @@ def test_password_delete_specific_password(topology_st):
|
||||
log.info('test_password_delete_specific_password: PASSED')
|
||||
|
||||
|
||||
+def test_password_modify_non_utf8(topology_st):
|
||||
+ """Attempt a modify of the userPassword attribute with
|
||||
+ an invalid non utf8 value
|
||||
+
|
||||
+ :id: a31af9d5-d665-42b9-8d6e-fea3d0837d36
|
||||
+ :setup: Standalone instance
|
||||
+ :steps:
|
||||
+ 1. Add a user if it doesnt exist and set its password
|
||||
+ 2. Verify password with a bind
|
||||
+ 3. Modify userPassword attr with invalid value
|
||||
+ 4. Attempt a bind with invalid password value
|
||||
+ 5. Verify original password with a bind
|
||||
+ :expectedresults:
|
||||
+ 1. The user with userPassword should be added successfully
|
||||
+ 2. Operation should be successful
|
||||
+ 3. Server returns ldap.UNWILLING_TO_PERFORM
|
||||
+ 4. Server returns ldap.INVALID_CREDENTIALS
|
||||
+ 5. Operation should be successful
|
||||
+ """
|
||||
+
|
||||
+ log.info('Running test_password_modify_non_utf8...')
|
||||
+
|
||||
+ # Create user and set password
|
||||
+ standalone = topology_st.standalone
|
||||
+ users = UserAccounts(standalone, DEFAULT_SUFFIX)
|
||||
+ if not users.exists(TEST_USER_PROPERTIES['uid'][0]):
|
||||
+ user = users.create(properties=TEST_USER_PROPERTIES)
|
||||
+ else:
|
||||
+ user = users.get(TEST_USER_PROPERTIES['uid'][0])
|
||||
+ user.set('userpassword', PASSWORD)
|
||||
+
|
||||
+ # Verify password
|
||||
+ try:
|
||||
+ user.bind(PASSWORD)
|
||||
+ except ldap.LDAPError as e:
|
||||
+ log.fatal('Failed to bind as {}, error: '.format(user.dn) + e.args[0]['desc'])
|
||||
+ assert False
|
||||
+
|
||||
+ # Modify userPassword with an invalid value
|
||||
+ password = b'tes\x82t-password' # A non UTF-8 encoded password
|
||||
+ with pytest.raises(ldap.UNWILLING_TO_PERFORM):
|
||||
+ user.replace('userpassword', password)
|
||||
+
|
||||
+ # Verify a bind fails with invalid pasword
|
||||
+ with pytest.raises(ldap.INVALID_CREDENTIALS):
|
||||
+ user.bind(password)
|
||||
+
|
||||
+ # Verify we can still bind with original password
|
||||
+ try:
|
||||
+ user.bind(PASSWORD)
|
||||
+ except ldap.LDAPError as e:
|
||||
+ log.fatal('Failed to bind as {}, error: '.format(user.dn) + e.args[0]['desc'])
|
||||
+ assert False
|
||||
+
|
||||
+ log.info('test_password_modify_non_utf8: PASSED')
|
||||
+
|
||||
if __name__ == '__main__':
|
||||
# Run isolated
|
||||
# -s for DEBUG mode
|
||||
diff --git a/ldap/servers/slapd/modify.c b/ldap/servers/slapd/modify.c
|
||||
index 5ca78539c..669bb104c 100644
|
||||
--- a/ldap/servers/slapd/modify.c
|
||||
+++ b/ldap/servers/slapd/modify.c
|
||||
@@ -765,8 +765,10 @@ op_shared_modify(Slapi_PBlock *pb, int pw_change, char *old_pw)
|
||||
* flagged - leave mod attributes alone */
|
||||
if (!repl_op && !skip_modified_attrs && lastmod) {
|
||||
modify_update_last_modified_attr(pb, &smods);
|
||||
+ slapi_pblock_set(pb, SLAPI_MODIFY_MODS, slapi_mods_get_ldapmods_byref(&smods));
|
||||
}
|
||||
|
||||
+
|
||||
if (0 == slapi_mods_get_num_mods(&smods)) {
|
||||
/* nothing to do - no mods - this is not an error - just
|
||||
send back LDAP_SUCCESS */
|
||||
@@ -933,8 +935,10 @@ op_shared_modify(Slapi_PBlock *pb, int pw_change, char *old_pw)
|
||||
|
||||
/* encode password */
|
||||
if (pw_encodevals_ext(pb, sdn, va)) {
|
||||
- slapi_log_err(SLAPI_LOG_CRIT, "op_shared_modify", "Unable to hash userPassword attribute for %s.\n", slapi_entry_get_dn_const(e));
|
||||
- send_ldap_result(pb, LDAP_UNWILLING_TO_PERFORM, NULL, "Unable to store attribute \"userPassword\" correctly\n", 0, NULL);
|
||||
+ slapi_log_err(SLAPI_LOG_CRIT, "op_shared_modify", "Unable to hash userPassword attribute for %s, "
|
||||
+ "check value is utf8 string.\n", slapi_entry_get_dn_const(e));
|
||||
+ send_ldap_result(pb, LDAP_UNWILLING_TO_PERFORM, NULL, "Unable to hash \"userPassword\" attribute, "
|
||||
+ "check value is utf8 string.\n", 0, NULL);
|
||||
valuearray_free(&va);
|
||||
goto free_and_return;
|
||||
}
|
||||
--
|
||||
2.45.0
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
From 05ce84ae76e0e71381bcc7b8491a74e7edcd888f Mon Sep 17 00:00:00 2001
|
||||
From c5aab4f8ba822572daa9ef69a0109577bf2147e1 Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Tue, 20 Jan 2026 09:52:47 +0100
|
||||
Subject: [PATCH 2/2] Issue 7189 - DSBLE0007 generates incorrect remediation
|
||||
Subject: [PATCH] Issue 7189 - DSBLE0007 generates incorrect remediation
|
||||
commands for scan limits
|
||||
|
||||
Bug Description:
|
||||
@ -32,10 +32,10 @@ Reviewed by: @progier389, @droideck (Thanks!)
|
||||
2 files changed, 147 insertions(+), 18 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
|
||||
index 1700ba207..f25de4214 100644
|
||||
index 6293340ca..5eadf6283 100644
|
||||
--- a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
|
||||
+++ b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
|
||||
@@ -406,6 +406,132 @@ def test_retrocl_plugin_missing_matching_rule(topology_st, retrocl_plugin_enable
|
||||
@@ -405,6 +405,132 @@ def test_retrocl_plugin_missing_matching_rule(topology_st, retrocl_plugin_enable
|
||||
run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
|
||||
|
||||
|
||||
@ -169,10 +169,10 @@ index 1700ba207..f25de4214 100644
|
||||
"""Check if healthcheck returns DSBLE0007 code when multiple system indexes are missing
|
||||
|
||||
diff --git a/src/lib389/lib389/backend.py b/src/lib389/lib389/backend.py
|
||||
index 14b64d1d3..3cea0df36 100644
|
||||
index 024de2adb..d3c9ccf35 100644
|
||||
--- a/src/lib389/lib389/backend.py
|
||||
+++ b/src/lib389/lib389/backend.py
|
||||
@@ -602,7 +602,7 @@ class Backend(DSLdapObject):
|
||||
@@ -678,7 +678,7 @@ class Backend(DSLdapObject):
|
||||
if expected_config.get('matching_rule'):
|
||||
cmd += f" --matching-rule {expected_config['matching_rule']}"
|
||||
if expected_config.get('scanlimit'):
|
||||
@ -181,7 +181,7 @@ index 14b64d1d3..3cea0df36 100644
|
||||
remediation_commands.append(cmd)
|
||||
reindex_attrs.add(attr_name) # New index needs reindexing
|
||||
else:
|
||||
@@ -624,28 +624,31 @@ class Backend(DSLdapObject):
|
||||
@@ -700,28 +700,31 @@ class Backend(DSLdapObject):
|
||||
remediation_commands.append(cmd)
|
||||
reindex_attrs.add(attr_name)
|
||||
|
||||
@ -1,213 +0,0 @@
|
||||
From d5bbe52fbe84a7d3b5938bf82d5c4af15061a8e2 Mon Sep 17 00:00:00 2001
|
||||
From: Pierre Rogier <progier@redhat.com>
|
||||
Date: Wed, 17 Apr 2024 18:18:04 +0200
|
||||
Subject: [PATCH] CVE-2024-3657
|
||||
|
||||
---
|
||||
.../tests/suites/filter/large_filter_test.py | 34 +++++-
|
||||
ldap/servers/slapd/back-ldbm/index.c | 111 ++++++++++--------
|
||||
2 files changed, 92 insertions(+), 53 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/filter/large_filter_test.py b/dirsrvtests/tests/suites/filter/large_filter_test.py
|
||||
index ecc7bf979..40526bb16 100644
|
||||
--- a/dirsrvtests/tests/suites/filter/large_filter_test.py
|
||||
+++ b/dirsrvtests/tests/suites/filter/large_filter_test.py
|
||||
@@ -13,19 +13,29 @@ verify and testing Filter from a search
|
||||
|
||||
import os
|
||||
import pytest
|
||||
+import ldap
|
||||
|
||||
-from lib389._constants import PW_DM
|
||||
+from lib389._constants import PW_DM, DEFAULT_SUFFIX, ErrorLog
|
||||
from lib389.topologies import topology_st as topo
|
||||
from lib389.idm.user import UserAccounts, UserAccount
|
||||
from lib389.idm.account import Accounts
|
||||
from lib389.backend import Backends
|
||||
from lib389.idm.domain import Domain
|
||||
+from lib389.utils import get_ldapurl_from_serverid
|
||||
|
||||
SUFFIX = 'dc=anuj,dc=com'
|
||||
|
||||
pytestmark = pytest.mark.tier1
|
||||
|
||||
|
||||
+def open_new_ldapi_conn(dsinstance):
|
||||
+ ldapurl, certdir = get_ldapurl_from_serverid(dsinstance)
|
||||
+ assert 'ldapi://' in ldapurl
|
||||
+ conn = ldap.initialize(ldapurl)
|
||||
+ conn.sasl_interactive_bind_s("", ldap.sasl.external())
|
||||
+ return conn
|
||||
+
|
||||
+
|
||||
@pytest.fixture(scope="module")
|
||||
def _create_entries(request, topo):
|
||||
"""
|
||||
@@ -160,6 +170,28 @@ def test_large_filter(topo, _create_entries, real_value):
|
||||
assert len(Accounts(conn, SUFFIX).filter(real_value)) == 3
|
||||
|
||||
|
||||
+def test_long_filter_value(topo):
|
||||
+ """Exercise large eq filter with dn syntax attributes
|
||||
+
|
||||
+ :id: b069ef72-fcc3-11ee-981c-482ae39447e5
|
||||
+ :setup: Standalone
|
||||
+ :steps:
|
||||
+ 1. Try to pass filter rules as per the condition.
|
||||
+ :expectedresults:
|
||||
+ 1. Pass
|
||||
+ """
|
||||
+ inst = topo.standalone
|
||||
+ conn = open_new_ldapi_conn(inst.serverid)
|
||||
+ inst.config.loglevel(vals=(ErrorLog.DEFAULT,ErrorLog.TRACE,ErrorLog.SEARCH_FILTER))
|
||||
+ filter_value = "a\x1Edmin" * 1025
|
||||
+ conn.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, f'(cn={filter_value})')
|
||||
+ filter_value = "aAdmin" * 1025
|
||||
+ conn.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, f'(cn={filter_value})')
|
||||
+ filter_value = "*"
|
||||
+ conn.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, f'(cn={filter_value})')
|
||||
+ inst.config.loglevel(vals=(ErrorLog.DEFAULT,))
|
||||
+
|
||||
+
|
||||
if __name__ == '__main__':
|
||||
CURRENT_FILE = os.path.realpath(__file__)
|
||||
pytest.main("-s -v %s" % CURRENT_FILE)
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/index.c b/ldap/servers/slapd/back-ldbm/index.c
|
||||
index 410db23d1..30fa09ebb 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/index.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/index.c
|
||||
@@ -71,6 +71,32 @@ typedef struct _index_buffer_handle index_buffer_handle;
|
||||
#define INDEX_BUFFER_FLAG_SERIALIZE 1
|
||||
#define INDEX_BUFFER_FLAG_STATS 2
|
||||
|
||||
+/*
|
||||
+ * space needed to encode a byte:
|
||||
+ * 0x00-0x31 and 0x7f-0xff requires 3 bytes: \xx
|
||||
+ * 0x22 and 0x5C requires 2 bytes: \" and \\
|
||||
+ * other requires 1 byte: c
|
||||
+ */
|
||||
+static char encode_size[] = {
|
||||
+ /* 0x00 */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
+ /* 0x10 */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
+ /* 0x20 */ 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
+ /* 0x30 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
+ /* 0x40 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
+ /* 0x50 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1,
|
||||
+ /* 0x60 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
+ /* 0x70 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3,
|
||||
+ /* 0x80 */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
+ /* 0x90 */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
+ /* 0xA0 */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
+ /* 0xB0 */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
+ /* 0xC0 */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
+ /* 0xD0 */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
+ /* 0xE0 */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
+ /* 0xF0 */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
+};
|
||||
+
|
||||
+
|
||||
/* Index buffering functions */
|
||||
|
||||
static int
|
||||
@@ -799,65 +825,46 @@ index_add_mods(
|
||||
|
||||
/*
|
||||
* Convert a 'struct berval' into a displayable ASCII string
|
||||
+ * returns the printable string
|
||||
*/
|
||||
-
|
||||
-#define SPECIAL(c) (c < 32 || c > 126 || c == '\\' || c == '"')
|
||||
-
|
||||
const char *
|
||||
encode(const struct berval *data, char buf[BUFSIZ])
|
||||
{
|
||||
- char *s;
|
||||
- char *last;
|
||||
- if (data == NULL || data->bv_len == 0)
|
||||
- return "";
|
||||
- last = data->bv_val + data->bv_len - 1;
|
||||
- for (s = data->bv_val; s < last; ++s) {
|
||||
- if (SPECIAL(*s)) {
|
||||
- char *first = data->bv_val;
|
||||
- char *bufNext = buf;
|
||||
- size_t bufSpace = BUFSIZ - 4;
|
||||
- while (1) {
|
||||
- /* printf ("%lu bytes ASCII\n", (unsigned long)(s - first)); */
|
||||
- if (bufSpace < (size_t)(s - first))
|
||||
- s = first + bufSpace - 1;
|
||||
- if (s != first) {
|
||||
- memcpy(bufNext, first, s - first);
|
||||
- bufNext += (s - first);
|
||||
- bufSpace -= (s - first);
|
||||
- }
|
||||
- do {
|
||||
- if (bufSpace) {
|
||||
- *bufNext++ = '\\';
|
||||
- --bufSpace;
|
||||
- }
|
||||
- if (bufSpace < 2) {
|
||||
- memcpy(bufNext, "..", 2);
|
||||
- bufNext += 2;
|
||||
- goto bail;
|
||||
- }
|
||||
- if (*s == '\\' || *s == '"') {
|
||||
- *bufNext++ = *s;
|
||||
- --bufSpace;
|
||||
- } else {
|
||||
- sprintf(bufNext, "%02x", (unsigned)*(unsigned char *)s);
|
||||
- bufNext += 2;
|
||||
- bufSpace -= 2;
|
||||
- }
|
||||
- } while (++s <= last && SPECIAL(*s));
|
||||
- if (s > last)
|
||||
- break;
|
||||
- first = s;
|
||||
- while (!SPECIAL(*s) && s <= last)
|
||||
- ++s;
|
||||
- }
|
||||
- bail:
|
||||
- *bufNext = '\0';
|
||||
- /* printf ("%lu chars in buffer\n", (unsigned long)(bufNext - buf)); */
|
||||
+ if (!data || !data->bv_val) {
|
||||
+ strcpy(buf, "<NULL>");
|
||||
+ return buf;
|
||||
+ }
|
||||
+ char *endbuff = &buf[BUFSIZ-4]; /* Reserve space to append "...\0" */
|
||||
+ char *ptout = buf;
|
||||
+ unsigned char *ptin = (unsigned char*) data->bv_val;
|
||||
+ unsigned char *endptin = ptin+data->bv_len;
|
||||
+
|
||||
+ while (ptin < endptin) {
|
||||
+ if (ptout >= endbuff) {
|
||||
+ /*
|
||||
+ * BUFSIZ(8K) > SLAPI_LOG_BUFSIZ(2K) so the error log message will be
|
||||
+ * truncated anyway. So there is no real interrest to test if the original
|
||||
+ * data contains no special characters and return it as is.
|
||||
+ */
|
||||
+ strcpy(endbuff, "...");
|
||||
return buf;
|
||||
}
|
||||
+ switch (encode_size[*ptin]) {
|
||||
+ case 1:
|
||||
+ *ptout++ = *ptin++;
|
||||
+ break;
|
||||
+ case 2:
|
||||
+ *ptout++ = '\\';
|
||||
+ *ptout++ = *ptin++;
|
||||
+ break;
|
||||
+ case 3:
|
||||
+ sprintf(ptout, "\\%02x", *ptin++);
|
||||
+ ptout += 3;
|
||||
+ break;
|
||||
+ }
|
||||
}
|
||||
- /* printf ("%lu bytes, all ASCII\n", (unsigned long)(s - data->bv_val)); */
|
||||
- return data->bv_val;
|
||||
+ *ptout = 0;
|
||||
+ return buf;
|
||||
}
|
||||
|
||||
static const char *
|
||||
--
|
||||
2.45.0
|
||||
|
||||
@ -0,0 +1,48 @@
|
||||
From 327ebf93decaa19bd3919afaeba0dc122eef1990 Mon Sep 17 00:00:00 2001
|
||||
From: Mark Reynolds <mreynolds@redhat.com>
|
||||
Date: Mon, 12 Jan 2026 13:53:05 -0500
|
||||
Subject: [PATCH] Issue 7184 - argparse.HelpFormatter _format_actions_usage()
|
||||
is deprecated
|
||||
|
||||
Description:
|
||||
|
||||
_format_actions_usage() was removed in python 3.15. Instead we can use
|
||||
_get_actions_usage_parts() but it also behaves differently between
|
||||
python 3.14 and 3.15 so we need special handling.
|
||||
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/7184
|
||||
|
||||
Reviewed by: spichugi(Thanks!)
|
||||
---
|
||||
src/lib389/lib389/cli_base/__init__.py | 15 ++++++++++++++-
|
||||
1 file changed, 14 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/src/lib389/lib389/cli_base/__init__.py b/src/lib389/lib389/cli_base/__init__.py
|
||||
index 06b8f9964..f1055aadc 100644
|
||||
--- a/src/lib389/lib389/cli_base/__init__.py
|
||||
+++ b/src/lib389/lib389/cli_base/__init__.py
|
||||
@@ -413,7 +413,20 @@ class CustomHelpFormatter(argparse.HelpFormatter):
|
||||
|
||||
def _format_usage(self, usage, actions, groups, prefix):
|
||||
usage = super(CustomHelpFormatter, self)._format_usage(usage, actions, groups, prefix)
|
||||
- formatted_options = self._format_actions_usage(parent_arguments, [])
|
||||
+
|
||||
+ if sys.version_info < (3, 13):
|
||||
+ # Use _format_actions_usage() for Python 3.12 and earlier
|
||||
+ formatted_options = self._format_actions_usage(parent_arguments, [])
|
||||
+ else:
|
||||
+ # Use _get_actions_usage_parts() for Python 3.13 and later
|
||||
+ action_parts = self._get_actions_usage_parts(parent_arguments, [])
|
||||
+ if sys.version_info >= (3, 15):
|
||||
+ # Python 3.15 returns a tuple (list of actions, count of actions)
|
||||
+ formatted_options = ' '.join(action_parts[0])
|
||||
+ else:
|
||||
+ # Python 3.13 and 3.14 return a list of actions
|
||||
+ formatted_options = ' '.join(action_parts)
|
||||
+
|
||||
# If formatted_options already in usage - remove them
|
||||
if formatted_options in usage:
|
||||
usage = usage.replace(f' {formatted_options}', '')
|
||||
--
|
||||
2.52.0
|
||||
|
||||
@ -1,143 +0,0 @@
|
||||
From 6e5f03d5872129963106024f53765234a282406c Mon Sep 17 00:00:00 2001
|
||||
From: James Chapman <jachapma@redhat.com>
|
||||
Date: Fri, 16 Feb 2024 11:13:16 +0000
|
||||
Subject: [PATCH] Issue 6096 - Improve connection timeout error logging (#6097)
|
||||
|
||||
Bug description: When a paged result search is run with a time limit,
|
||||
if the time limit is exceed the server closes the connection with
|
||||
closed IO timeout (nsslapd-ioblocktimeout) - T2. This error message
|
||||
is incorrect as the reason the connection has been closed was because
|
||||
the specified time limit on a paged result search has been exceeded.
|
||||
|
||||
Fix description: Correct error message
|
||||
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/6096
|
||||
|
||||
Reviewed by: @tbordaz (Thank you)
|
||||
---
|
||||
ldap/admin/src/logconv.pl | 24 ++++++++++++++++++-
|
||||
ldap/servers/slapd/daemon.c | 4 ++--
|
||||
ldap/servers/slapd/disconnect_error_strings.h | 1 +
|
||||
ldap/servers/slapd/disconnect_errors.h | 2 +-
|
||||
4 files changed, 27 insertions(+), 4 deletions(-)
|
||||
|
||||
diff --git a/ldap/admin/src/logconv.pl b/ldap/admin/src/logconv.pl
|
||||
index 7698c383a..2a933c4a3 100755
|
||||
--- a/ldap/admin/src/logconv.pl
|
||||
+++ b/ldap/admin/src/logconv.pl
|
||||
@@ -267,7 +267,7 @@ my $optimeAvg = 0;
|
||||
my %cipher = ();
|
||||
my @removefiles = ();
|
||||
|
||||
-my @conncodes = qw(A1 B1 B4 T1 T2 B2 B3 R1 P1 P2 U1);
|
||||
+my @conncodes = qw(A1 B1 B4 T1 T2 T3 B2 B3 R1 P1 P2 U1);
|
||||
my %conn = ();
|
||||
map {$conn{$_} = $_} @conncodes;
|
||||
|
||||
@@ -355,6 +355,7 @@ $connmsg{"B1"} = "Bad Ber Tag Encountered";
|
||||
$connmsg{"B4"} = "Server failed to flush data (response) back to Client";
|
||||
$connmsg{"T1"} = "Idle Timeout Exceeded";
|
||||
$connmsg{"T2"} = "IO Block Timeout Exceeded or NTSSL Timeout";
|
||||
+$connmsg{"T3"} = "Paged Search Time Limit Exceeded";
|
||||
$connmsg{"B2"} = "Ber Too Big";
|
||||
$connmsg{"B3"} = "Ber Peek";
|
||||
$connmsg{"R1"} = "Revents";
|
||||
@@ -1723,6 +1724,10 @@ if ($usage =~ /j/i || $verb eq "yes"){
|
||||
print "\n $recCount. You have some coonections that are being closed by the ioblocktimeout setting. You may want to increase the ioblocktimeout.\n";
|
||||
$recCount++;
|
||||
}
|
||||
+ if (defined($conncount->{"T3"}) and $conncount->{"T3"} > 0){
|
||||
+ print "\n $recCount. You have some connections that are being closed because a paged result search limit has been exceeded. You may want to increase the search time limit.\n";
|
||||
+ $recCount++;
|
||||
+ }
|
||||
# compare binds to unbinds, if the difference is more than 30% of the binds, then report a issue
|
||||
if (($bindCount - $unbindCount) > ($bindCount*.3)){
|
||||
print "\n $recCount. You have a significant difference between binds and unbinds. You may want to investigate this difference.\n";
|
||||
@@ -2366,6 +2371,7 @@ sub parseLineNormal
|
||||
$brokenPipeCount++;
|
||||
if (m/- T1/){ $hashes->{rc}->{"T1"}++; }
|
||||
elsif (m/- T2/){ $hashes->{rc}->{"T2"}++; }
|
||||
+ elsif (m/- T3/){ $hashes->{rc}->{"T3"}++; }
|
||||
elsif (m/- A1/){ $hashes->{rc}->{"A1"}++; }
|
||||
elsif (m/- B1/){ $hashes->{rc}->{"B1"}++; }
|
||||
elsif (m/- B4/){ $hashes->{rc}->{"B4"}++; }
|
||||
@@ -2381,6 +2387,7 @@ sub parseLineNormal
|
||||
$connResetByPeerCount++;
|
||||
if (m/- T1/){ $hashes->{src}->{"T1"}++; }
|
||||
elsif (m/- T2/){ $hashes->{src}->{"T2"}++; }
|
||||
+ elsif (m/- T3/){ $hashes->{src}->{"T3"}++; }
|
||||
elsif (m/- A1/){ $hashes->{src}->{"A1"}++; }
|
||||
elsif (m/- B1/){ $hashes->{src}->{"B1"}++; }
|
||||
elsif (m/- B4/){ $hashes->{src}->{"B4"}++; }
|
||||
@@ -2396,6 +2403,7 @@ sub parseLineNormal
|
||||
$resourceUnavailCount++;
|
||||
if (m/- T1/){ $hashes->{rsrc}->{"T1"}++; }
|
||||
elsif (m/- T2/){ $hashes->{rsrc}->{"T2"}++; }
|
||||
+ elsif (m/- T3/){ $hashes->{rsrc}->{"T3"}++; }
|
||||
elsif (m/- A1/){ $hashes->{rsrc}->{"A1"}++; }
|
||||
elsif (m/- B1/){ $hashes->{rsrc}->{"B1"}++; }
|
||||
elsif (m/- B4/){ $hashes->{rsrc}->{"B4"}++; }
|
||||
@@ -2494,6 +2502,20 @@ sub parseLineNormal
|
||||
}
|
||||
}
|
||||
}
|
||||
+ if (m/- T3/){
|
||||
+ if ($_ =~ /conn= *([0-9A-Z]+)/i) {
|
||||
+ $exc = "no";
|
||||
+ $ip = getIPfromConn($1, $serverRestartCount);
|
||||
+ for (my $xxx = 0; $xxx < $#excludeIP; $xxx++){
|
||||
+ if ($ip eq $excludeIP[$xxx]){$exc = "yes";}
|
||||
+ }
|
||||
+ if ($exc ne "yes"){
|
||||
+ $hashes->{T3}->{$ip}++;
|
||||
+ $hashes->{conncount}->{"T3"}++;
|
||||
+ $connCodeCount++;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
if (m/- B2/){
|
||||
if ($_ =~ /conn= *([0-9A-Z]+)/i) {
|
||||
$exc = "no";
|
||||
diff --git a/ldap/servers/slapd/daemon.c b/ldap/servers/slapd/daemon.c
|
||||
index 5a48aa66f..bb80dae36 100644
|
||||
--- a/ldap/servers/slapd/daemon.c
|
||||
+++ b/ldap/servers/slapd/daemon.c
|
||||
@@ -1599,9 +1599,9 @@ setup_pr_read_pds(Connection_Table *ct)
|
||||
int add_fd = 1;
|
||||
/* check timeout for PAGED RESULTS */
|
||||
if (pagedresults_is_timedout_nolock(c)) {
|
||||
- /* Exceeded the timelimit; disconnect the client */
|
||||
+ /* Exceeded the paged search timelimit; disconnect the client */
|
||||
disconnect_server_nomutex(c, c->c_connid, -1,
|
||||
- SLAPD_DISCONNECT_IO_TIMEOUT,
|
||||
+ SLAPD_DISCONNECT_PAGED_SEARCH_LIMIT,
|
||||
0);
|
||||
connection_table_move_connection_out_of_active_list(ct,
|
||||
c);
|
||||
diff --git a/ldap/servers/slapd/disconnect_error_strings.h b/ldap/servers/slapd/disconnect_error_strings.h
|
||||
index f7a31d728..c2d9e283b 100644
|
||||
--- a/ldap/servers/slapd/disconnect_error_strings.h
|
||||
+++ b/ldap/servers/slapd/disconnect_error_strings.h
|
||||
@@ -27,6 +27,7 @@ ER2(SLAPD_DISCONNECT_BER_FLUSH, "B4")
|
||||
ER2(SLAPD_DISCONNECT_IDLE_TIMEOUT, "T1")
|
||||
ER2(SLAPD_DISCONNECT_REVENTS, "R1")
|
||||
ER2(SLAPD_DISCONNECT_IO_TIMEOUT, "T2")
|
||||
+ER2(SLAPD_DISCONNECT_PAGED_SEARCH_LIMIT, "T3")
|
||||
ER2(SLAPD_DISCONNECT_PLUGIN, "P1")
|
||||
ER2(SLAPD_DISCONNECT_UNBIND, "U1")
|
||||
ER2(SLAPD_DISCONNECT_POLL, "P2")
|
||||
diff --git a/ldap/servers/slapd/disconnect_errors.h b/ldap/servers/slapd/disconnect_errors.h
|
||||
index a0484f1c2..e118f674c 100644
|
||||
--- a/ldap/servers/slapd/disconnect_errors.h
|
||||
+++ b/ldap/servers/slapd/disconnect_errors.h
|
||||
@@ -35,6 +35,6 @@
|
||||
#define SLAPD_DISCONNECT_SASL_FAIL SLAPD_DISCONNECT_ERROR_BASE + 12
|
||||
#define SLAPD_DISCONNECT_PROXY_INVALID_HEADER SLAPD_DISCONNECT_ERROR_BASE + 13
|
||||
#define SLAPD_DISCONNECT_PROXY_UNKNOWN SLAPD_DISCONNECT_ERROR_BASE + 14
|
||||
-
|
||||
+#define SLAPD_DISCONNECT_PAGED_SEARCH_LIMIT SLAPD_DISCONNECT_ERROR_BASE + 15
|
||||
|
||||
#endif /* __DISCONNECT_ERRORS_H_ */
|
||||
--
|
||||
2.45.0
|
||||
|
||||
@ -0,0 +1,53 @@
|
||||
From dbabbe6a3c42b979d14960b355925c9748371cad Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Fri, 30 Jan 2026 12:00:13 +0100
|
||||
Subject: [PATCH] Issue 7027 - (2nd) 389-ds-base OpenScanHub Leaks Detected
|
||||
(#7211)
|
||||
|
||||
Fix Description:
|
||||
Update coverity annotations.
|
||||
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/7027
|
||||
|
||||
Reviewed by: @aadhikar (Thanks!)
|
||||
---
|
||||
ldap/servers/slapd/log.c | 6 +++---
|
||||
1 file changed, 3 insertions(+), 3 deletions(-)
|
||||
|
||||
diff --git a/ldap/servers/slapd/log.c b/ldap/servers/slapd/log.c
|
||||
index 668a02454..6f57a3d9c 100644
|
||||
--- a/ldap/servers/slapd/log.c
|
||||
+++ b/ldap/servers/slapd/log.c
|
||||
@@ -203,8 +203,8 @@ compress_log_file(char *log_name, int32_t mode)
|
||||
|
||||
if ((source = fopen(log_name, "r")) == NULL) {
|
||||
/* Failed to open log file */
|
||||
- /* coverity[leaked_storage] gzclose does close FD */
|
||||
gzclose(outfile);
|
||||
+ /* coverity[leaked_handle] gzclose does close FD */
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -214,17 +214,17 @@ compress_log_file(char *log_name, int32_t mode)
|
||||
if (bytes_written == 0)
|
||||
{
|
||||
fclose(source);
|
||||
- /* coverity[leaked_storage] gzclose does close FD */
|
||||
gzclose(outfile);
|
||||
+ /* coverity[leaked_handle] gzclose does close FD */
|
||||
return -1;
|
||||
}
|
||||
bytes_read = fread(buf, 1, LOG_CHUNK, source);
|
||||
}
|
||||
- /* coverity[leaked_storage] gzclose does close FD */
|
||||
gzclose(outfile);
|
||||
fclose(source);
|
||||
PR_Delete(log_name); /* remove the old uncompressed log */
|
||||
|
||||
+ /* coverity[leaked_handle] gzclose does close FD */
|
||||
return 0;
|
||||
}
|
||||
|
||||
--
|
||||
2.52.0
|
||||
|
||||
@ -1,44 +0,0 @@
|
||||
From a112394af3a20787755029804684d57a9c3ffa9a Mon Sep 17 00:00:00 2001
|
||||
From: James Chapman <jachapma@redhat.com>
|
||||
Date: Wed, 21 Feb 2024 12:43:03 +0000
|
||||
Subject: [PATCH] Issue 6103 - New connection timeout error breaks errormap
|
||||
(#6104)
|
||||
|
||||
Bug description: A recent addition to the connection disconnect error
|
||||
messaging, conflicts with how errormap.c maps error codes/strings.
|
||||
|
||||
Fix description: errormap expects error codes/strings to be in ascending
|
||||
order. Moved the new error code to the bottom of the list.
|
||||
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/6103
|
||||
|
||||
Reviewed by: @droideck. @progier389 (Thank you)
|
||||
---
|
||||
ldap/servers/slapd/disconnect_error_strings.h | 5 +++--
|
||||
1 file changed, 3 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/ldap/servers/slapd/disconnect_error_strings.h b/ldap/servers/slapd/disconnect_error_strings.h
|
||||
index c2d9e283b..f603a08ce 100644
|
||||
--- a/ldap/servers/slapd/disconnect_error_strings.h
|
||||
+++ b/ldap/servers/slapd/disconnect_error_strings.h
|
||||
@@ -14,7 +14,8 @@
|
||||
/* disconnect_error_strings.h
|
||||
*
|
||||
* Strings describing the errors used in logging the reason a connection
|
||||
- * was closed.
|
||||
+ * was closed. Ensure definitions are in the same order as the error codes
|
||||
+ * defined in disconnect_errors.h
|
||||
*/
|
||||
#ifndef __DISCONNECT_ERROR_STRINGS_H_
|
||||
#define __DISCONNECT_ERROR_STRINGS_H_
|
||||
@@ -35,6 +36,6 @@ ER2(SLAPD_DISCONNECT_NTSSL_TIMEOUT, "T2")
|
||||
ER2(SLAPD_DISCONNECT_SASL_FAIL, "S1")
|
||||
ER2(SLAPD_DISCONNECT_PROXY_INVALID_HEADER, "P3")
|
||||
ER2(SLAPD_DISCONNECT_PROXY_UNKNOWN, "P4")
|
||||
-
|
||||
+ER2(SLAPD_DISCONNECT_PAGED_SEARCH_LIMIT, "T3")
|
||||
|
||||
#endif /* __DISCONNECT_ERROR_STRINGS_H_ */
|
||||
--
|
||||
2.45.0
|
||||
|
||||
@ -0,0 +1,197 @@
|
||||
From 39d47ca91e866d30b35dca41d477ad4ee07f8a14 Mon Sep 17 00:00:00 2001
|
||||
From: progier389 <progier@redhat.com>
|
||||
Date: Mon, 2 Feb 2026 15:39:18 +0100
|
||||
Subject: [PATCH] Issue 7213 - MDB_BAD_VALSIZE error while handling VLV (#7214)
|
||||
|
||||
* Issue 7213 - MDB_BAD_VALSIZE error while handling VLV
|
||||
Avoid failing lmdb operation when handling VLV index by truncating the key so that key+data is small enough.
|
||||
|
||||
Issue: #7213
|
||||
|
||||
Reviewed by: @mreynolds389 , @vashirov (Thanks!)
|
||||
|
||||
Assisted by: Claude A/I
|
||||
|
||||
(cherry picked from commit 5ebce22d4214bec5ed94ad84c4448164be99389a)
|
||||
---
|
||||
.../tests/suites/vlv/regression_test.py | 110 ++++++++++++++++++
|
||||
.../slapd/back-ldbm/db-mdb/mdb_layer.c | 5 +
|
||||
ldap/servers/slapd/back-ldbm/vlv.c | 7 +-
|
||||
3 files changed, 121 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/vlv/regression_test.py b/dirsrvtests/tests/suites/vlv/regression_test.py
|
||||
index c8db94b19..1cc03c303 100644
|
||||
--- a/dirsrvtests/tests/suites/vlv/regression_test.py
|
||||
+++ b/dirsrvtests/tests/suites/vlv/regression_test.py
|
||||
@@ -1178,6 +1178,116 @@ def test_vlv_with_mr(vlv_setup_with_uid_mr):
|
||||
|
||||
|
||||
|
||||
+def test_vlv_long_attribute_value(topology_st, request):
|
||||
+ """
|
||||
+ Test VLV with an entry containing a very long attribute value (2K).
|
||||
+
|
||||
+ :id: 99126fa4-003e-11f1-b7d6-c85309d5c3e3
|
||||
+ :setup: Standalone instance.
|
||||
+ :steps:
|
||||
+ 1. Cleanup leftover from previous tests
|
||||
+ 2. Create VLV search and index on cn attribute
|
||||
+ 3. Reindex VLV
|
||||
+ 4. Add an entry with a cn attribute having 2K character value
|
||||
+ 5. Verify the entry was added successfully
|
||||
+ 6. Perform a VLV search to ensure it still works
|
||||
+ 7. Add another entry with a cn attribute having 2K character value
|
||||
+ 8. Verify the entry was added successfully
|
||||
+ 9. Perform a VLV search to ensure it still works
|
||||
+ :expectedresults:
|
||||
+ 1. Should Success.
|
||||
+ 2. Should Success.
|
||||
+ 3. Should Success.
|
||||
+ 4. Should Success.
|
||||
+ 5. Should Success.
|
||||
+ 6. Should Success.
|
||||
+ 7. Should Success.
|
||||
+ 8. Should Success.
|
||||
+ 9. Should Success.
|
||||
+ """
|
||||
+ inst = topology_st.standalone
|
||||
+ reindex_task = Tasks(inst)
|
||||
+
|
||||
+ users_to_delete = []
|
||||
+
|
||||
+ def fin():
|
||||
+ cleanup(inst)
|
||||
+ # Clean the added users
|
||||
+ for user in users_to_delete:
|
||||
+ user.delete()
|
||||
+
|
||||
+ if not DEBUGGING:
|
||||
+ request.addfinalizer(fin)
|
||||
+
|
||||
+ # Clean previous tests leftover
|
||||
+ fin()
|
||||
+
|
||||
+ # Create VLV search and index
|
||||
+ vlv_search, vlv_index = create_vlv_search_and_index(inst)
|
||||
+ assert reindex_task.reindex(
|
||||
+ suffix=DEFAULT_SUFFIX,
|
||||
+ attrname=vlv_index.rdn,
|
||||
+ args={TASK_WAIT: True},
|
||||
+ vlv=True
|
||||
+ ) == 0
|
||||
+
|
||||
+ # Add a few regular users first
|
||||
+ add_users(inst, 10)
|
||||
+
|
||||
+ # Create a very long cn value (2K characters)
|
||||
+ long_cn_value = 'a' * 2048 + '1'
|
||||
+
|
||||
+ # Add an entry with the long cn attribute
|
||||
+ users = UserAccounts(inst, DEFAULT_SUFFIX)
|
||||
+ user_properties = {
|
||||
+ 'uid': 'longcnuser1',
|
||||
+ 'cn': long_cn_value,
|
||||
+ 'sn': 'user1',
|
||||
+ 'uidNumber': '99999',
|
||||
+ 'gidNumber': '99999',
|
||||
+ 'homeDirectory': '/home/longcnuser1'
|
||||
+ }
|
||||
+ user = users.create(properties=user_properties)
|
||||
+ users_to_delete.append(user);
|
||||
+
|
||||
+ # Verify the entry was created and has the long cn value
|
||||
+ entry = user.get_attr_vals_utf8('cn')
|
||||
+ assert entry[0] == long_cn_value
|
||||
+ log.info(f'Successfully created user with cn length: {len(entry[0])}')
|
||||
+
|
||||
+ # Perform VLV search to ensure VLV still works with long attribute values
|
||||
+ conn = open_new_ldapi_conn(inst.serverid)
|
||||
+ count = len(conn.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, "(uid=*)"))
|
||||
+ assert count > 0
|
||||
+ log.info(f'VLV search successful with {count} entries including entry with 2K cn value')
|
||||
+
|
||||
+ # Add another entry with the long cn attribute
|
||||
+ long_cn_value = 'a' * 2048 + '2'
|
||||
+
|
||||
+ user_properties = {
|
||||
+ 'uid': 'longcnuser2',
|
||||
+ 'cn': long_cn_value,
|
||||
+ 'sn': 'user2',
|
||||
+ 'uidNumber': '99998',
|
||||
+ 'gidNumber': '99998',
|
||||
+ 'homeDirectory': '/home/longcnuser2'
|
||||
+ }
|
||||
+ user = users.create(properties=user_properties)
|
||||
+ users_to_delete.append(user);
|
||||
+
|
||||
+ # Verify the entry was created and has the long cn value
|
||||
+ entry = user.get_attr_vals_utf8('cn')
|
||||
+ assert entry[0] == long_cn_value
|
||||
+ log.info(f'Successfully created user with cn length: {len(entry[0])}')
|
||||
+
|
||||
+ # Perform VLV search to ensure VLV still works with long attribute values
|
||||
+ conn = open_new_ldapi_conn(inst.serverid)
|
||||
+ count = len(conn.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, "(uid=*)"))
|
||||
+ assert count > 1
|
||||
+ log.info(f'VLV search successful with {count} entries including entry with 2K cn value')
|
||||
+
|
||||
+
|
||||
+
|
||||
if __name__ == "__main__":
|
||||
# Run isolated
|
||||
# -s for DEBUG mode
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_layer.c b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_layer.c
|
||||
index c6e9f8b01..ffbd6609f 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_layer.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_layer.c
|
||||
@@ -2138,10 +2138,15 @@ void *dbmdb_recno_cache_build(void *arg)
|
||||
recno = 1;
|
||||
}
|
||||
while (rc == 0) {
|
||||
+ struct ldbminfo *li = (struct ldbminfo *)rcctx->cursor->be->be_database->plg_private;
|
||||
slapi_log_err(SLAPI_LOG_DEBUG, "dbmdb_recno_cache_build", "recno=%d\n", recno);
|
||||
if (recno % RECNO_CACHE_INTERVAL == 1) {
|
||||
/* Prepare the cache data */
|
||||
len = sizeof(*rce) + data.mv_size + key.mv_size;
|
||||
+ if (len > li->li_max_key_len) {
|
||||
+ key.mv_size = li->li_max_key_len - data.mv_size - sizeof(*rce);
|
||||
+ len = li->li_max_key_len;
|
||||
+ }
|
||||
rce = (dbmdb_recno_cache_elmt_t*)slapi_ch_malloc(len);
|
||||
rce->len = len;
|
||||
rce->recno = recno;
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/vlv.c b/ldap/servers/slapd/back-ldbm/vlv.c
|
||||
index 18431799c..a1cd226f4 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/vlv.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/vlv.c
|
||||
@@ -866,6 +866,7 @@ do_vlv_update_index(back_txn *txn, struct ldbminfo *li, Slapi_PBlock *pb, struct
|
||||
struct vlv_key *key = NULL;
|
||||
dbi_val_t data = {0};
|
||||
dblayer_private *priv = NULL;
|
||||
+ size_t key_size_limit = li->li_max_key_len - sizeof(entry->ep_id);
|
||||
|
||||
slapi_pblock_get(pb, SLAPI_BACKEND, &be);
|
||||
priv = (dblayer_private *)li->li_dblayer_private;
|
||||
@@ -886,6 +887,10 @@ do_vlv_update_index(back_txn *txn, struct ldbminfo *li, Slapi_PBlock *pb, struct
|
||||
return rc;
|
||||
}
|
||||
|
||||
+ /* Truncate the key if it is too long */
|
||||
+ if (key->key.size > key_size_limit) {
|
||||
+ key->key.size = key_size_limit;
|
||||
+ }
|
||||
if (NULL != txn) {
|
||||
db_txn = txn->back_txn_txn;
|
||||
} else {
|
||||
@@ -930,7 +935,7 @@ do_vlv_update_index(back_txn *txn, struct ldbminfo *li, Slapi_PBlock *pb, struct
|
||||
if (txn && txn->back_special_handling_fn) {
|
||||
rc = txn->back_special_handling_fn(be, BTXNACT_VLV_DEL, db, &key->key, &data, txn);
|
||||
} else {
|
||||
- rc = dblayer_db_op(be, db, db_txn, DBI_OP_DEL, &key->key, NULL);
|
||||
+ rc = dblayer_db_op(be, db, db_txn, DBI_OP_DEL, &key->key, &data);
|
||||
}
|
||||
if (rc == 0) {
|
||||
if (txn && txn->back_special_handling_fn) {
|
||||
--
|
||||
2.52.0
|
||||
|
||||
@ -1,30 +0,0 @@
|
||||
From edd9abc8901604dde1d739d87ca2906734d53dd3 Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Thu, 13 Jun 2024 13:35:09 +0200
|
||||
Subject: [PATCH] Issue 6103 - New connection timeout error breaks errormap
|
||||
|
||||
Description:
|
||||
Remove duplicate SLAPD_DISCONNECT_PAGED_SEARCH_LIMIT error code.
|
||||
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/6103
|
||||
|
||||
Reviewed by: @tbordaz (Thanks!)
|
||||
---
|
||||
ldap/servers/slapd/disconnect_error_strings.h | 1 -
|
||||
1 file changed, 1 deletion(-)
|
||||
|
||||
diff --git a/ldap/servers/slapd/disconnect_error_strings.h b/ldap/servers/slapd/disconnect_error_strings.h
|
||||
index f603a08ce..d49cc79a2 100644
|
||||
--- a/ldap/servers/slapd/disconnect_error_strings.h
|
||||
+++ b/ldap/servers/slapd/disconnect_error_strings.h
|
||||
@@ -28,7 +28,6 @@ ER2(SLAPD_DISCONNECT_BER_FLUSH, "B4")
|
||||
ER2(SLAPD_DISCONNECT_IDLE_TIMEOUT, "T1")
|
||||
ER2(SLAPD_DISCONNECT_REVENTS, "R1")
|
||||
ER2(SLAPD_DISCONNECT_IO_TIMEOUT, "T2")
|
||||
-ER2(SLAPD_DISCONNECT_PAGED_SEARCH_LIMIT, "T3")
|
||||
ER2(SLAPD_DISCONNECT_PLUGIN, "P1")
|
||||
ER2(SLAPD_DISCONNECT_UNBIND, "U1")
|
||||
ER2(SLAPD_DISCONNECT_POLL, "P2")
|
||||
--
|
||||
2.45.0
|
||||
|
||||
43
SOURCES/0010-Issue-6542-RPM-build-errors-on-Fedora-42.patch
Normal file
43
SOURCES/0010-Issue-6542-RPM-build-errors-on-Fedora-42.patch
Normal file
@ -0,0 +1,43 @@
|
||||
From 70d323d4d9ca4d1d682ab4a273178946922c6929 Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Fri, 24 Jan 2025 11:10:45 +0100
|
||||
Subject: [PATCH] Issue 6542 - RPM build errors on Fedora 42
|
||||
|
||||
Bug Description:
|
||||
Fedora 42 has unified `/bin` and `/sbin`: https://fedoraproject.org/wiki/Changes/Unify_bin_and_sbin
|
||||
https://docs.fedoraproject.org/en-US/packaging-guidelines/#_merged_file_system_layout
|
||||
|
||||
This change causes RPM build to fail with:
|
||||
```
|
||||
RPM build errors:
|
||||
File not found: /builddir/build/BUILD/389-ds-base-3.1.2-build/BUILDROOT/usr/bin/openldap_to_ds
|
||||
```
|
||||
|
||||
Fix Description:
|
||||
Patch `setup.py.in` based on Fedora or RHEL version that support unified
|
||||
`/bin` and `/sbin`.
|
||||
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/6542
|
||||
|
||||
Reviewed by: @mreynolds389, @droideck (Thanks!)
|
||||
---
|
||||
rpm/389-ds-base.spec.in | 3 +++
|
||||
1 file changed, 3 insertions(+)
|
||||
|
||||
diff --git a/rpm/389-ds-base.spec.in b/rpm/389-ds-base.spec.in
|
||||
index 258d94698..44a158ce5 100644
|
||||
--- a/rpm/389-ds-base.spec.in
|
||||
+++ b/rpm/389-ds-base.spec.in
|
||||
@@ -522,6 +522,9 @@ autoreconf -fiv
|
||||
|
||||
%if 0%{?rhel} > 7 || 0%{?fedora}
|
||||
# lib389
|
||||
+%if 0%{?fedora} >= 42 || 0%{?rhel} >= 11
|
||||
+ sed -i "/prefix/s@sbin@bin@g" src/lib389/setup.py.in
|
||||
+%endif
|
||||
make src/lib389/setup.py
|
||||
pushd ./src/lib389
|
||||
%py3_build
|
||||
--
|
||||
2.52.0
|
||||
|
||||
@ -1,220 +0,0 @@
|
||||
From 8cf981c00ae18d3efaeb10819282cd991621e9a2 Mon Sep 17 00:00:00 2001
|
||||
From: tbordaz <tbordaz@redhat.com>
|
||||
Date: Wed, 22 May 2024 11:29:05 +0200
|
||||
Subject: [PATCH] Issue 6172 - RFE: improve the performance of evaluation of
|
||||
filter component when tested against a large valueset (like group members)
|
||||
(#6173)
|
||||
|
||||
Bug description:
|
||||
Before returning an entry (to a SRCH) the server checks that the entry matches the SRCH filter.
|
||||
If a filter component (equality) is testing the value (ava) against a
|
||||
large valueset (like uniquemember values), it takes a long time because
|
||||
of the large number of values and required normalization of the values.
|
||||
This can be improved taking benefit of sorted valueset. Those sorted
|
||||
valueset were created to improve updates of large valueset (groups) but
|
||||
at that time not implemented in SRCH path.
|
||||
|
||||
Fix description:
|
||||
In case of LDAP_FILTER_EQUALITY component, the server can get
|
||||
benefit of the sorted valuearray.
|
||||
To limit the risk of regression, we use the sorted valuearray
|
||||
only for the DN syntax attribute. Indeed the sorted valuearray was
|
||||
designed for those type of attribute.
|
||||
With those two limitations, there is no need of a toggle and
|
||||
the call to plugin_call_syntax_filter_ava can be replaced by
|
||||
a call to slapi_valueset_find.
|
||||
In both cases, sorted valueset and plugin_call_syntax_filter_ava, ava and
|
||||
values are normalized.
|
||||
In sorted valueset, the values have been normalized to insert the index
|
||||
in the sorted array and then comparison is done on normalized values.
|
||||
In plugin_call_syntax_filter_ava, all values in valuearray (of valueset) are normalized
|
||||
before comparison.
|
||||
|
||||
relates: #6172
|
||||
|
||||
Reviewed by: Pierre Rogier, Simon Pichugin (Big Thanks !!!)
|
||||
---
|
||||
.../tests/suites/filter/filter_test.py | 125 ++++++++++++++++++
|
||||
ldap/servers/slapd/filterentry.c | 22 ++-
|
||||
2 files changed, 146 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/filter/filter_test.py b/dirsrvtests/tests/suites/filter/filter_test.py
|
||||
index d6bfa5a3b..4baaf04a7 100644
|
||||
--- a/dirsrvtests/tests/suites/filter/filter_test.py
|
||||
+++ b/dirsrvtests/tests/suites/filter/filter_test.py
|
||||
@@ -9,7 +9,11 @@
|
||||
import logging
|
||||
|
||||
import pytest
|
||||
+import time
|
||||
+from lib389.dirsrv_log import DirsrvAccessLog
|
||||
from lib389.tasks import *
|
||||
+from lib389.backend import Backends, Backend
|
||||
+from lib389.dbgen import dbgen_users, dbgen_groups
|
||||
from lib389.topologies import topology_st
|
||||
from lib389._constants import PASSWORD, DEFAULT_SUFFIX, DN_DM, SUFFIX
|
||||
from lib389.utils import *
|
||||
@@ -304,6 +308,127 @@ def test_extended_search(topology_st):
|
||||
ents = topology_st.standalone.search_s(SUFFIX, ldap.SCOPE_SUBTREE, myfilter)
|
||||
assert len(ents) == 1
|
||||
|
||||
+def test_match_large_valueset(topology_st):
|
||||
+ """Test that when returning a big number of entries
|
||||
+ and that we need to match the filter from a large valueset
|
||||
+ we get benefit to use the sorted valueset
|
||||
+
|
||||
+ :id: 7db5aa88-50e0-4c31-85dd-1d2072cb674c
|
||||
+
|
||||
+ :setup: Standalone instance
|
||||
+
|
||||
+ :steps:
|
||||
+ 1. Create a users and groups backends and tune them
|
||||
+ 2. Generate a test ldif (2k users and 1K groups with all users)
|
||||
+ 3. Import test ldif file using Offline import (ldif2db).
|
||||
+ 4. Prim the 'groups' entrycache with a "fast" search
|
||||
+ 5. Search the 'groups' with a difficult matching value
|
||||
+ 6. check that etime from step 5 is less than a second
|
||||
+
|
||||
+ :expectedresults:
|
||||
+ 1. Create a users and groups backends should PASS
|
||||
+ 2. Generate LDIF should PASS.
|
||||
+ 3. Offline import should PASS.
|
||||
+ 4. Priming should PASS.
|
||||
+ 5. Performance search should PASS.
|
||||
+ 6. Etime of performance search should PASS.
|
||||
+ """
|
||||
+
|
||||
+ log.info('Running test_match_large_valueset...')
|
||||
+ #
|
||||
+ # Test online/offline LDIF imports
|
||||
+ #
|
||||
+ inst = topology_st.standalone
|
||||
+ inst.start()
|
||||
+ backends = Backends(inst)
|
||||
+ users_suffix = "ou=users,%s" % DEFAULT_SUFFIX
|
||||
+ users_backend = 'users'
|
||||
+ users_ldif = 'users_import.ldif'
|
||||
+ groups_suffix = "ou=groups,%s" % DEFAULT_SUFFIX
|
||||
+ groups_backend = 'groups'
|
||||
+ groups_ldif = 'groups_import.ldif'
|
||||
+ groups_entrycache = '200000000'
|
||||
+ users_number = 2000
|
||||
+ groups_number = 1000
|
||||
+
|
||||
+
|
||||
+ # For priming the cache we just want to be fast
|
||||
+ # taking the first value in the valueset is good
|
||||
+ # whether the valueset is sorted or not
|
||||
+ priming_user_rdn = "user0001"
|
||||
+
|
||||
+ # For performance testing, this is important to use
|
||||
+ # user1000 rather then user0001
|
||||
+ # Because user0001 is the first value in the valueset
|
||||
+ # whether we use the sorted valuearray or non sorted
|
||||
+ # valuearray the performance will be similar.
|
||||
+ # With middle value user1000, the performance boost of
|
||||
+ # the sorted valuearray will make the difference.
|
||||
+ perf_user_rdn = "user1000"
|
||||
+
|
||||
+ # Step 1. Prepare the backends and tune the groups entrycache
|
||||
+ try:
|
||||
+ be_users = backends.create(properties={'parent': DEFAULT_SUFFIX, 'nsslapd-suffix': users_suffix, 'name': users_backend})
|
||||
+ be_groups = backends.create(properties={'parent': DEFAULT_SUFFIX, 'nsslapd-suffix': groups_suffix, 'name': groups_backend})
|
||||
+
|
||||
+ # set the entry cache to 200Mb as the 1K groups of 2K users require at least 170Mb
|
||||
+ be_groups.replace('nsslapd-cachememsize', groups_entrycache)
|
||||
+ except:
|
||||
+ raise
|
||||
+
|
||||
+ # Step 2. Generate a test ldif (10k users entries)
|
||||
+ log.info("Generating users LDIF...")
|
||||
+ ldif_dir = inst.get_ldif_dir()
|
||||
+ users_import_ldif = "%s/%s" % (ldif_dir, users_ldif)
|
||||
+ groups_import_ldif = "%s/%s" % (ldif_dir, groups_ldif)
|
||||
+ dbgen_users(inst, users_number, users_import_ldif, suffix=users_suffix, generic=True, parent=users_suffix)
|
||||
+
|
||||
+ # Generate a test ldif (800 groups with 10k members) that fit in 700Mb entry cache
|
||||
+ props = {
|
||||
+ "name": "group",
|
||||
+ "suffix": groups_suffix,
|
||||
+ "parent": groups_suffix,
|
||||
+ "number": groups_number,
|
||||
+ "numMembers": users_number,
|
||||
+ "createMembers": False,
|
||||
+ "memberParent": users_suffix,
|
||||
+ "membershipAttr": "uniquemember",
|
||||
+ }
|
||||
+ dbgen_groups(inst, groups_import_ldif, props)
|
||||
+
|
||||
+ # Step 3. Do the both offline imports
|
||||
+ inst.stop()
|
||||
+ if not inst.ldif2db(users_backend, None, None, None, users_import_ldif):
|
||||
+ log.fatal('test_basic_import_export: Offline users import failed')
|
||||
+ assert False
|
||||
+ if not inst.ldif2db(groups_backend, None, None, None, groups_import_ldif):
|
||||
+ log.fatal('test_basic_import_export: Offline groups import failed')
|
||||
+ assert False
|
||||
+ inst.start()
|
||||
+
|
||||
+ # Step 4. first prime the cache
|
||||
+ # Just request the 'DN'. We are interested by the time of matching not by the time of transfert
|
||||
+ entries = topology_st.standalone.search_s(groups_suffix, ldap.SCOPE_SUBTREE, "(&(objectclass=groupOfUniqueNames)(uniquemember=uid=%s,%s))" % (priming_user_rdn, users_suffix), ['dn'])
|
||||
+ assert len(entries) == groups_number
|
||||
+
|
||||
+ # Step 5. Now do the real performance checking it should take less than a second
|
||||
+ # Just request the 'DN'. We are interested by the time of matching not by the time of transfert
|
||||
+ search_start = time.time()
|
||||
+ entries = topology_st.standalone.search_s(groups_suffix, ldap.SCOPE_SUBTREE, "(&(objectclass=groupOfUniqueNames)(uniquemember=uid=%s,%s))" % (perf_user_rdn, users_suffix), ['dn'])
|
||||
+ duration = time.time() - search_start
|
||||
+ log.info("Duration of the search was %f", duration)
|
||||
+
|
||||
+ # Step 6. Gather the etime from the access log
|
||||
+ inst.stop()
|
||||
+ access_log = DirsrvAccessLog(inst)
|
||||
+ search_result = access_log.match(".*RESULT err=0 tag=101 nentries=%s.*" % groups_number)
|
||||
+ log.info("Found patterns are %s", search_result[0])
|
||||
+ log.info("Found patterns are %s", search_result[1])
|
||||
+ etime = float(search_result[1].split('etime=')[1])
|
||||
+ log.info("Duration of the search from access log was %f", etime)
|
||||
+ assert len(entries) == groups_number
|
||||
+ assert (etime < 1)
|
||||
+
|
||||
if __name__ == '__main__':
|
||||
# Run isolated
|
||||
# -s for DEBUG mode
|
||||
diff --git a/ldap/servers/slapd/filterentry.c b/ldap/servers/slapd/filterentry.c
|
||||
index fd8fdda9f..cae5c7edc 100644
|
||||
--- a/ldap/servers/slapd/filterentry.c
|
||||
+++ b/ldap/servers/slapd/filterentry.c
|
||||
@@ -296,7 +296,27 @@ test_ava_filter(
|
||||
rc = -1;
|
||||
for (; a != NULL; a = a->a_next) {
|
||||
if (slapi_attr_type_cmp(ava->ava_type, a->a_type, SLAPI_TYPE_CMP_SUBTYPE) == 0) {
|
||||
- rc = plugin_call_syntax_filter_ava(a, ftype, ava);
|
||||
+ if ((ftype == LDAP_FILTER_EQUALITY) &&
|
||||
+ (slapi_attr_is_dn_syntax_type(a->a_type))) {
|
||||
+ /* This path is for a performance improvement */
|
||||
+
|
||||
+ /* In case of equality filter we can get benefit of the
|
||||
+ * sorted valuearray (from valueset).
|
||||
+ * This improvement is limited to DN syntax attributes for
|
||||
+ * which the sorted valueset was designed.
|
||||
+ */
|
||||
+ Slapi_Value *sval = NULL;
|
||||
+ sval = slapi_value_new_berval(&ava->ava_value);
|
||||
+ if (slapi_valueset_find((const Slapi_Attr *)a, &a->a_present_values, sval)) {
|
||||
+ rc = 0;
|
||||
+ }
|
||||
+ slapi_value_free(&sval);
|
||||
+ } else {
|
||||
+ /* When sorted valuearray optimization cannot be used
|
||||
+ * lets filter the value according to its syntax
|
||||
+ */
|
||||
+ rc = plugin_call_syntax_filter_ava(a, ftype, ava);
|
||||
+ }
|
||||
if (rc == 0) {
|
||||
break;
|
||||
}
|
||||
--
|
||||
2.46.0
|
||||
|
||||
3318
SOURCES/0011-Issue-6476-Fix-build-failure-with-GCC-15.patch
Normal file
3318
SOURCES/0011-Issue-6476-Fix-build-failure-with-GCC-15.patch
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,4 @@
|
||||
From 850cb2ae142b1dc31081387e8e997699668e70f4 Mon Sep 17 00:00:00 2001
|
||||
From 7a45b50bf3d9cfc8f7e805379fcb39d5669d6c8b Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Thu, 5 Feb 2026 12:17:06 +0100
|
||||
Subject: [PATCH] Issue 7223 - Revert index scan limits for system indexes
|
||||
@ -9,28 +9,28 @@ c6f458b42 Issue 7189 - DSBLE0007 generates incorrect remediation commands for sc
|
||||
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/7223
|
||||
|
||||
Reviewed by: @progier389, @tbordaz (Thanks!)
|
||||
Reviewed by: @progier389, @tbordaz, @droideck (Thanks!)
|
||||
---
|
||||
.../tests/suites/config/config_test.py | 27 +---
|
||||
.../healthcheck/health_system_indexes_test.py | 135 +-----------------
|
||||
.../paged_results/paged_results_test.py | 25 +---
|
||||
ldap/servers/slapd/back-ldbm/back-ldbm.h | 1 -
|
||||
ldap/servers/slapd/back-ldbm/index.c | 2 -
|
||||
ldap/servers/slapd/back-ldbm/instance.c | 108 ++------------
|
||||
ldap/servers/slapd/back-ldbm/instance.c | 106 +++-----------
|
||||
ldap/servers/slapd/back-ldbm/ldbm_config.c | 30 ----
|
||||
ldap/servers/slapd/back-ldbm/ldbm_config.h | 1 -
|
||||
.../slapd/back-ldbm/ldbm_index_config.c | 8 --
|
||||
src/lib389/lib389/backend.py | 50 ++-----
|
||||
src/lib389/lib389/cli_conf/backend.py | 20 ---
|
||||
11 files changed, 39 insertions(+), 368 deletions(-)
|
||||
11 files changed, 41 insertions(+), 364 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/config/config_test.py b/dirsrvtests/tests/suites/config/config_test.py
|
||||
index 430176602..bbe13d248 100644
|
||||
index b9ba684d9..6f11e7fc8 100644
|
||||
--- a/dirsrvtests/tests/suites/config/config_test.py
|
||||
+++ b/dirsrvtests/tests/suites/config/config_test.py
|
||||
@@ -514,19 +514,17 @@ def test_ndn_cache_enabled(topo):
|
||||
topo.standalone.config.set('nsslapd-ndn-cache-max-size', 'invalid_value')
|
||||
@@ -715,19 +715,17 @@ def test_ndn_cache_size_enforcement(topo, request):
|
||||
|
||||
request.addfinalizer(fin)
|
||||
|
||||
-def test_require_index(topo, request):
|
||||
+def test_require_index(topo):
|
||||
@ -50,7 +50,7 @@ index 430176602..bbe13d248 100644
|
||||
"""
|
||||
|
||||
# Set the config
|
||||
@@ -537,10 +535,6 @@ def test_require_index(topo, request):
|
||||
@@ -738,10 +736,6 @@ def test_require_index(topo, request):
|
||||
|
||||
db_cfg = DatabaseConfig(topo.standalone)
|
||||
db_cfg.set([('nsslapd-idlistscanlimit', '100')])
|
||||
@ -61,7 +61,7 @@ index 430176602..bbe13d248 100644
|
||||
|
||||
users = UserAccounts(topo.standalone, DEFAULT_SUFFIX)
|
||||
for i in range(101):
|
||||
@@ -551,15 +545,10 @@ def test_require_index(topo, request):
|
||||
@@ -752,15 +746,10 @@ def test_require_index(topo, request):
|
||||
with pytest.raises(ldap.UNWILLING_TO_PERFORM):
|
||||
raw_objects.filter("(description=test*)")
|
||||
|
||||
@ -78,7 +78,7 @@ index 430176602..bbe13d248 100644
|
||||
"""Ensure internal operations require indexed attributes
|
||||
|
||||
:id: 22b94f30-59e3-4f27-89a1-c4f4be036f7f
|
||||
@@ -591,10 +580,6 @@ def test_require_internal_index(topo, request):
|
||||
@@ -791,10 +780,6 @@ def test_require_internal_index(topo, request):
|
||||
# Create a bunch of users
|
||||
db_cfg = DatabaseConfig(topo.standalone)
|
||||
db_cfg.set([('nsslapd-idlistscanlimit', '100')])
|
||||
@ -89,7 +89,7 @@ index 430176602..bbe13d248 100644
|
||||
users = UserAccounts(topo.standalone, DEFAULT_SUFFIX)
|
||||
for i in range(102, 202):
|
||||
users.create_test_user(uid=i)
|
||||
@@ -619,12 +604,6 @@ def test_require_internal_index(topo, request):
|
||||
@@ -819,12 +804,6 @@ def test_require_internal_index(topo, request):
|
||||
with pytest.raises(ldap.UNWILLING_TO_PERFORM):
|
||||
user.delete()
|
||||
|
||||
@ -100,13 +100,13 @@ index 430176602..bbe13d248 100644
|
||||
-
|
||||
-
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Run isolated
|
||||
def get_pstack(pid):
|
||||
"""Get a pstack of the pid."""
|
||||
diff --git a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
|
||||
index f25de4214..842f7e8dd 100644
|
||||
index 5eadf6283..61972d60c 100644
|
||||
--- a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
|
||||
+++ b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
|
||||
@@ -172,8 +172,7 @@ def test_missing_parentid(topology_st, log_buffering_enabled):
|
||||
@@ -171,8 +171,7 @@ def test_missing_parentid(topology_st, log_buffering_enabled):
|
||||
|
||||
log.info("Re-add the parentId index")
|
||||
backend = Backends(standalone).get("userRoot")
|
||||
@ -116,7 +116,7 @@ index f25de4214..842f7e8dd 100644
|
||||
|
||||
run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT)
|
||||
run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
|
||||
@@ -261,8 +260,7 @@ def test_usn_plugin_missing_entryusn(topology_st, usn_plugin_enabled, log_buffer
|
||||
@@ -260,8 +259,7 @@ def test_usn_plugin_missing_entryusn(topology_st, usn_plugin_enabled, log_buffer
|
||||
|
||||
log.info("Re-add the entryusn index")
|
||||
backend = Backends(standalone).get("userRoot")
|
||||
@ -126,7 +126,7 @@ index f25de4214..842f7e8dd 100644
|
||||
|
||||
run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT)
|
||||
run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
|
||||
@@ -406,132 +404,6 @@ def test_retrocl_plugin_missing_matching_rule(topology_st, retrocl_plugin_enable
|
||||
@@ -405,132 +403,6 @@ def test_retrocl_plugin_missing_matching_rule(topology_st, retrocl_plugin_enable
|
||||
run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
|
||||
|
||||
|
||||
@ -259,7 +259,7 @@ index f25de4214..842f7e8dd 100644
|
||||
def test_multiple_missing_indexes(topology_st, log_buffering_enabled):
|
||||
"""Check if healthcheck returns DSBLE0007 code when multiple system indexes are missing
|
||||
|
||||
@@ -572,8 +444,7 @@ def test_multiple_missing_indexes(topology_st, log_buffering_enabled):
|
||||
@@ -571,8 +443,7 @@ def test_multiple_missing_indexes(topology_st, log_buffering_enabled):
|
||||
|
||||
log.info("Re-add the missing system indexes")
|
||||
backend = Backends(standalone).get("userRoot")
|
||||
@ -270,10 +270,10 @@ index f25de4214..842f7e8dd 100644
|
||||
|
||||
run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT)
|
||||
diff --git a/dirsrvtests/tests/suites/paged_results/paged_results_test.py b/dirsrvtests/tests/suites/paged_results/paged_results_test.py
|
||||
index 8835be8fa..1ed11c891 100644
|
||||
index 61d6702da..1bb94b53a 100644
|
||||
--- a/dirsrvtests/tests/suites/paged_results/paged_results_test.py
|
||||
+++ b/dirsrvtests/tests/suites/paged_results/paged_results_test.py
|
||||
@@ -317,19 +317,19 @@ def test_search_success(topology_st, create_user, page_size, users_num):
|
||||
@@ -306,19 +306,19 @@ def test_search_success(topology_st, create_user, page_size, users_num):
|
||||
del_users(users_list)
|
||||
|
||||
|
||||
@ -300,7 +300,7 @@ index 8835be8fa..1ed11c891 100644
|
||||
"""Verify that search with a simple paged results control
|
||||
throws expected exceptoins when corresponding limits are
|
||||
exceeded.
|
||||
@@ -351,15 +351,6 @@ def test_search_limits_fail(topology_st, create_user, page_size, users_num,
|
||||
@@ -341,15 +341,6 @@ def test_search_limits_fail(topology_st, create_user, page_size, users_num,
|
||||
|
||||
users_list = add_users(topology_st, users_num, DEFAULT_SUFFIX)
|
||||
attr_value_bck = change_conf_attr(topology_st, suffix, attr_name, attr_value)
|
||||
@ -316,7 +316,7 @@ index 8835be8fa..1ed11c891 100644
|
||||
conf_param_dict = {attr_name: attr_value}
|
||||
search_flt = r'(uid=test*)'
|
||||
searchreq_attrlist = ['dn', 'sn']
|
||||
@@ -412,8 +403,6 @@ def test_search_limits_fail(topology_st, create_user, page_size, users_num,
|
||||
@@ -402,8 +393,6 @@ def test_search_limits_fail(topology_st, create_user, page_size, users_num,
|
||||
else:
|
||||
break
|
||||
finally:
|
||||
@ -326,10 +326,10 @@ index 8835be8fa..1ed11c891 100644
|
||||
change_conf_attr(topology_st, suffix, attr_name, attr_value_bck)
|
||||
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/back-ldbm.h b/ldap/servers/slapd/back-ldbm/back-ldbm.h
|
||||
index cde30cedd..d17ec644b 100644
|
||||
index 5e4988782..a4993208c 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/back-ldbm.h
|
||||
+++ b/ldap/servers/slapd/back-ldbm/back-ldbm.h
|
||||
@@ -554,7 +554,6 @@ struct ldbminfo
|
||||
@@ -561,7 +561,6 @@ struct ldbminfo
|
||||
int li_mode;
|
||||
int li_lookthroughlimit;
|
||||
int li_allidsthreshold;
|
||||
@ -338,10 +338,10 @@ index cde30cedd..d17ec644b 100644
|
||||
int li_reslimit_lookthrough_handle;
|
||||
uint64_t li_dbcachesize;
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/index.c b/ldap/servers/slapd/back-ldbm/index.c
|
||||
index 63f0196c1..30fa09ebb 100644
|
||||
index e06a8ee5f..90129b682 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/index.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/index.c
|
||||
@@ -999,8 +999,6 @@ index_read_ext_allids(
|
||||
@@ -1007,8 +1007,6 @@ index_read_ext_allids(
|
||||
}
|
||||
if (pb) {
|
||||
slapi_pblock_get(pb, SLAPI_SEARCH_IS_AND, &is_and);
|
||||
@ -351,7 +351,7 @@ index 63f0196c1..30fa09ebb 100644
|
||||
ai_flags = is_and ? INDEX_ALLIDS_FLAG_AND : 0;
|
||||
/* the caller can pass in a value of 0 - just ignore those - but if the index
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/instance.c b/ldap/servers/slapd/back-ldbm/instance.c
|
||||
index 29643f7e4..6098e04fc 100644
|
||||
index 23bd70243..f9a546661 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/instance.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/instance.c
|
||||
@@ -16,7 +16,7 @@
|
||||
@ -445,7 +445,7 @@ index 29643f7e4..6098e04fc 100644
|
||||
|
||||
/*
|
||||
* Always index (entrydn or entryrdn), parentid, objectclass,
|
||||
@@ -247,59 +190,47 @@ ldbm_instance_create_default_indexes(backend *be)
|
||||
@@ -247,53 +190,47 @@ ldbm_instance_create_default_indexes(backend *be)
|
||||
* ACL routines.
|
||||
*/
|
||||
if (entryrdn_get_switch()) { /* subtree-rename: on */
|
||||
@ -469,28 +469,24 @@ index 29643f7e4..6098e04fc 100644
|
||||
- attr_index_config(be, "ldbm index init", 0, e, 1, 0, NULL);
|
||||
- slapi_entry_free(e);
|
||||
- }
|
||||
-
|
||||
- e = ldbm_instance_init_config_entry("objectclass", "eq", 0, 0, 0, 0, 0);
|
||||
+ e = ldbm_instance_init_config_entry(LDBM_PARENTID_STR, "eq", 0, 0, 0, "integerOrderingMatch");
|
||||
ldbm_instance_config_add_index_entry(inst, e, flags);
|
||||
slapi_entry_free(e);
|
||||
+ ldbm_instance_config_add_index_entry(inst, e, flags);
|
||||
+ slapi_entry_free(e);
|
||||
|
||||
- e = ldbm_instance_init_config_entry("aci", "pres", 0, 0, 0, 0, 0);
|
||||
- e = ldbm_instance_init_config_entry("objectclass", "eq", 0, 0, 0, 0, 0);
|
||||
+ e = ldbm_instance_init_config_entry("objectclass", "eq", 0, 0, 0, 0);
|
||||
ldbm_instance_config_add_index_entry(inst, e, flags);
|
||||
slapi_entry_free(e);
|
||||
|
||||
- e = ldbm_instance_init_config_entry(LDBM_NUMSUBORDINATES_STR, "pres", 0, 0, 0, 0, 0);
|
||||
- e = ldbm_instance_init_config_entry("aci", "pres", 0, 0, 0, 0, 0);
|
||||
+ e = ldbm_instance_init_config_entry("aci", "pres", 0, 0, 0, 0);
|
||||
ldbm_instance_config_add_index_entry(inst, e, flags);
|
||||
slapi_entry_free(e);
|
||||
|
||||
-#if 0 /* don't need copiedfrom */
|
||||
- e = ldbm_instance_init_config_entry("copiedfrom","pres",0 ,0);
|
||||
- e = ldbm_instance_init_config_entry(LDBM_NUMSUBORDINATES_STR, "pres", 0, 0, 0, 0, 0);
|
||||
+ e = ldbm_instance_init_config_entry(LDBM_NUMSUBORDINATES_STR, "pres", 0, 0, 0, 0);
|
||||
ldbm_instance_config_add_index_entry(inst, e, flags);
|
||||
slapi_entry_free(e);
|
||||
-#endif
|
||||
|
||||
- e = ldbm_instance_init_config_entry(SLAPI_ATTR_UNIQUEID, "eq", 0, 0, 0, 0, 0);
|
||||
+ e = ldbm_instance_init_config_entry(SLAPI_ATTR_UNIQUEID, "eq", 0, 0, 0, 0);
|
||||
@ -515,7 +511,7 @@ index 29643f7e4..6098e04fc 100644
|
||||
attr_index_config(be, "ldbm index init", 0, e, 1, 0, NULL);
|
||||
slapi_entry_free(e);
|
||||
|
||||
@@ -308,20 +239,11 @@ ldbm_instance_create_default_indexes(backend *be)
|
||||
@@ -302,20 +239,11 @@ ldbm_instance_create_default_indexes(backend *be)
|
||||
* ancestorid is special, there is actually no such attr type
|
||||
* but we still want to use the attr index file APIs.
|
||||
*/
|
||||
@ -540,10 +536,10 @@ index 29643f7e4..6098e04fc 100644
|
||||
}
|
||||
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_config.c b/ldap/servers/slapd/back-ldbm/ldbm_config.c
|
||||
index f8d8f7474..b7bceabf2 100644
|
||||
index d8ffc4479..5cce0dbd3 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/ldbm_config.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/ldbm_config.c
|
||||
@@ -366,35 +366,6 @@ ldbm_config_allidsthreshold_set(void *arg, void *value, char *errorbuf __attribu
|
||||
@@ -386,35 +386,6 @@ ldbm_config_allidsthreshold_set(void *arg, void *value, char *errorbuf __attribu
|
||||
return retval;
|
||||
}
|
||||
|
||||
@ -579,7 +575,7 @@ index f8d8f7474..b7bceabf2 100644
|
||||
static void *
|
||||
ldbm_config_pagedallidsthreshold_get(void *arg)
|
||||
{
|
||||
@@ -974,7 +945,6 @@ static config_info ldbm_config[] = {
|
||||
@@ -1133,7 +1104,6 @@ static config_info ldbm_config[] = {
|
||||
{CONFIG_LOOKTHROUGHLIMIT, CONFIG_TYPE_INT, "5000", &ldbm_config_lookthroughlimit_get, &ldbm_config_lookthroughlimit_set, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE},
|
||||
{CONFIG_MODE, CONFIG_TYPE_INT_OCTAL, "0600", &ldbm_config_mode_get, &ldbm_config_mode_set, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE},
|
||||
{CONFIG_IDLISTSCANLIMIT, CONFIG_TYPE_INT, "2147483646", &ldbm_config_allidsthreshold_get, &ldbm_config_allidsthreshold_set, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE},
|
||||
@ -588,7 +584,7 @@ index f8d8f7474..b7bceabf2 100644
|
||||
{CONFIG_MAXPASSBEFOREMERGE, CONFIG_TYPE_INT, "100", &ldbm_config_maxpassbeforemerge_get, &ldbm_config_maxpassbeforemerge_set, 0},
|
||||
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_config.h b/ldap/servers/slapd/back-ldbm/ldbm_config.h
|
||||
index 004e5ea7e..48446193e 100644
|
||||
index 30e3477ed..e1f05d7a2 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/ldbm_config.h
|
||||
+++ b/ldap/servers/slapd/back-ldbm/ldbm_config.h
|
||||
@@ -60,7 +60,6 @@ struct config_info
|
||||
@ -619,10 +615,10 @@ index bae2a64b9..38e7368e1 100644
|
||||
if (eBuf) {
|
||||
PR_smprintf_free(eBuf);
|
||||
diff --git a/src/lib389/lib389/backend.py b/src/lib389/lib389/backend.py
|
||||
index 3cea0df36..4babf6850 100644
|
||||
index d3c9ccf35..1d9be4683 100644
|
||||
--- a/src/lib389/lib389/backend.py
|
||||
+++ b/src/lib389/lib389/backend.py
|
||||
@@ -539,10 +539,11 @@ class Backend(DSLdapObject):
|
||||
@@ -615,10 +615,11 @@ class Backend(DSLdapObject):
|
||||
indexes = self.get_indexes()
|
||||
|
||||
# Default system indexes taken from ldap/servers/slapd/back-ldbm/instance.c
|
||||
@ -637,7 +633,7 @@ index 3cea0df36..4babf6850 100644
|
||||
'objectClass': {'types': ['eq'], 'matching_rule': None},
|
||||
'aci': {'types': ['pres'], 'matching_rule': None},
|
||||
'nscpEntryDN': {'types': ['eq'], 'matching_rule': None},
|
||||
@@ -599,17 +600,14 @@ class Backend(DSLdapObject):
|
||||
@@ -675,17 +676,14 @@ class Backend(DSLdapObject):
|
||||
# Generate remediation command
|
||||
index_types = ' '.join([f"--index-type {t}" for t in expected_config['types']])
|
||||
cmd = f"dsconf YOUR_INSTANCE backend index add {bename} --attr {attr_name} {index_types}"
|
||||
@ -656,7 +652,7 @@ index 3cea0df36..4babf6850 100644
|
||||
|
||||
# Normalize to lowercase for comparison
|
||||
actual_types = [t.lower() for t in actual_types]
|
||||
@@ -624,31 +622,16 @@ class Backend(DSLdapObject):
|
||||
@@ -700,31 +698,16 @@ class Backend(DSLdapObject):
|
||||
remediation_commands.append(cmd)
|
||||
reindex_attrs.add(attr_name)
|
||||
|
||||
@ -693,7 +689,7 @@ index 3cea0df36..4babf6850 100644
|
||||
|
||||
except Exception as e:
|
||||
self._log.debug(f"_lint_system_indexes - Error checking index {attr_name}: {e}")
|
||||
@@ -879,13 +862,12 @@ class Backend(DSLdapObject):
|
||||
@@ -963,13 +946,12 @@ class Backend(DSLdapObject):
|
||||
return
|
||||
raise ValueError("Can not delete index because it does not exist")
|
||||
|
||||
@ -708,7 +704,7 @@ index 3cea0df36..4babf6850 100644
|
||||
:param reindex - If set to True then index the attribute after creating it.
|
||||
"""
|
||||
|
||||
@@ -915,15 +897,6 @@ class Backend(DSLdapObject):
|
||||
@@ -999,15 +981,6 @@ class Backend(DSLdapObject):
|
||||
# Only add if there are actually rules present in the list.
|
||||
if len(mrs) > 0:
|
||||
props['nsMatchingRule'] = mrs
|
||||
@ -724,7 +720,7 @@ index 3cea0df36..4babf6850 100644
|
||||
new_index.create(properties=props, basedn="cn=index," + self._dn)
|
||||
|
||||
if reindex:
|
||||
@@ -1230,7 +1203,6 @@ class DatabaseConfig(DSLdapObject):
|
||||
@@ -1314,7 +1287,6 @@ class DatabaseConfig(DSLdapObject):
|
||||
'nsslapd-lookthroughlimit',
|
||||
'nsslapd-mode',
|
||||
'nsslapd-idlistscanlimit',
|
||||
@ -733,7 +729,7 @@ index 3cea0df36..4babf6850 100644
|
||||
'nsslapd-import-cachesize',
|
||||
'nsslapd-idl-switch',
|
||||
diff --git a/src/lib389/lib389/cli_conf/backend.py b/src/lib389/lib389/cli_conf/backend.py
|
||||
index d57cb9433..4dc67d563 100644
|
||||
index dcf57d006..80008d22d 100644
|
||||
--- a/src/lib389/lib389/cli_conf/backend.py
|
||||
+++ b/src/lib389/lib389/cli_conf/backend.py
|
||||
@@ -39,7 +39,6 @@ arg_to_attr = {
|
||||
@ -744,7 +740,7 @@ index d57cb9433..4dc67d563 100644
|
||||
'directory': 'nsslapd-directory',
|
||||
'dbcachesize': 'nsslapd-dbcachesize',
|
||||
'logdirectory': 'nsslapd-db-logdirectory',
|
||||
@@ -588,21 +587,6 @@ def backend_set_index(inst, basedn, log, args):
|
||||
@@ -626,21 +625,6 @@ def backend_set_index(inst, basedn, log, args):
|
||||
except ldap.NO_SUCH_ATTRIBUTE:
|
||||
raise ValueError('Can not delete matching rule type because it does not exist')
|
||||
|
||||
@ -766,7 +762,7 @@ index d57cb9433..4dc67d563 100644
|
||||
if args.reindex:
|
||||
be.reindex(attrs=[args.attr])
|
||||
log.info("Index successfully updated")
|
||||
@@ -924,9 +908,6 @@ def create_parser(subparsers):
|
||||
@@ -962,9 +946,6 @@ def create_parser(subparsers):
|
||||
edit_index_parser.add_argument('--del-type', action='append', help='Removes an index type from the index: (eq, sub, pres, or approx)')
|
||||
edit_index_parser.add_argument('--add-mr', action='append', help='Adds a matching-rule to the index')
|
||||
edit_index_parser.add_argument('--del-mr', action='append', help='Removes a matching-rule from the index')
|
||||
@ -776,7 +772,7 @@ index d57cb9433..4dc67d563 100644
|
||||
edit_index_parser.add_argument('--reindex', action='store_true', help='Re-indexes the database after editing the index')
|
||||
edit_index_parser.add_argument('be_name', help='The backend name or suffix')
|
||||
|
||||
@@ -1053,7 +1034,6 @@ def create_parser(subparsers):
|
||||
@@ -1091,7 +1072,6 @@ def create_parser(subparsers):
|
||||
'will check when examining candidate entries in response to a search request')
|
||||
set_db_config_parser.add_argument('--mode', help='Specifies the permissions used for newly created index files')
|
||||
set_db_config_parser.add_argument('--idlistscanlimit', help='Specifies the number of entry IDs that are searched during a search operation')
|
||||
@ -1,163 +0,0 @@
|
||||
From 57051154bafaf50b83fc27dadbd89a49fd1c8c36 Mon Sep 17 00:00:00 2001
|
||||
From: Pierre Rogier <progier@redhat.com>
|
||||
Date: Fri, 14 Jun 2024 13:27:10 +0200
|
||||
Subject: [PATCH] Security fix for CVE-2024-5953
|
||||
|
||||
Description:
|
||||
A denial of service vulnerability was found in the 389 Directory Server.
|
||||
This issue may allow an authenticated user to cause a server denial
|
||||
of service while attempting to log in with a user with a malformed hash
|
||||
in their password.
|
||||
|
||||
Fix Description:
|
||||
To prevent buffer overflow when a bind request is processed, the bind fails
|
||||
if the hash size is not coherent without even attempting to process further
|
||||
the hashed password.
|
||||
|
||||
References:
|
||||
- https://nvd.nist.gov/vuln/detail/CVE-2024-5953
|
||||
- https://access.redhat.com/security/cve/CVE-2024-5953
|
||||
- https://bugzilla.redhat.com/show_bug.cgi?id=2292104
|
||||
---
|
||||
.../tests/suites/password/regression_test.py | 54 ++++++++++++++++++-
|
||||
ldap/servers/plugins/pwdstorage/md5_pwd.c | 9 +++-
|
||||
ldap/servers/plugins/pwdstorage/pbkdf2_pwd.c | 6 +++
|
||||
3 files changed, 66 insertions(+), 3 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/password/regression_test.py b/dirsrvtests/tests/suites/password/regression_test.py
|
||||
index 8f1facb6d..1fa581643 100644
|
||||
--- a/dirsrvtests/tests/suites/password/regression_test.py
|
||||
+++ b/dirsrvtests/tests/suites/password/regression_test.py
|
||||
@@ -7,12 +7,14 @@
|
||||
#
|
||||
import pytest
|
||||
import time
|
||||
+import glob
|
||||
+import base64
|
||||
from lib389._constants import PASSWORD, DN_DM, DEFAULT_SUFFIX
|
||||
from lib389._constants import SUFFIX, PASSWORD, DN_DM, DN_CONFIG, PLUGIN_RETRO_CHANGELOG, DEFAULT_SUFFIX, DEFAULT_CHANGELOG_DB
|
||||
from lib389 import Entry
|
||||
from lib389.topologies import topology_m1 as topo_supplier
|
||||
-from lib389.idm.user import UserAccounts
|
||||
-from lib389.utils import ldap, os, logging, ensure_bytes, ds_is_newer
|
||||
+from lib389.idm.user import UserAccounts, UserAccount
|
||||
+from lib389.utils import ldap, os, logging, ensure_bytes, ds_is_newer, ds_supports_new_changelog
|
||||
from lib389.topologies import topology_st as topo
|
||||
from lib389.idm.organizationalunit import OrganizationalUnits
|
||||
|
||||
@@ -39,6 +41,13 @@ TEST_PASSWORDS += ['CNpwtest1ZZZZ', 'ZZZZZCNpwtest1',
|
||||
TEST_PASSWORDS2 = (
|
||||
'CN12pwtest31', 'SN3pwtest231', 'UID1pwtest123', 'MAIL2pwtest12@redhat.com', '2GN1pwtest123', 'People123')
|
||||
|
||||
+SUPPORTED_SCHEMES = (
|
||||
+ "{SHA}", "{SSHA}", "{SHA256}", "{SSHA256}",
|
||||
+ "{SHA384}", "{SSHA384}", "{SHA512}", "{SSHA512}",
|
||||
+ "{crypt}", "{NS-MTA-MD5}", "{clear}", "{MD5}",
|
||||
+ "{SMD5}", "{PBKDF2_SHA256}", "{PBKDF2_SHA512}",
|
||||
+ "{GOST_YESCRYPT}", "{PBKDF2-SHA256}", "{PBKDF2-SHA512}" )
|
||||
+
|
||||
def _check_unhashed_userpw(inst, user_dn, is_present=False):
|
||||
"""Check if unhashed#user#password attribute is present or not in the changelog"""
|
||||
unhashed_pwd_attribute = 'unhashed#user#password'
|
||||
@@ -319,6 +328,47 @@ def test_unhashed_pw_switch(topo_supplier):
|
||||
# Add debugging steps(if any)...
|
||||
pass
|
||||
|
||||
+@pytest.mark.parametrize("scheme", SUPPORTED_SCHEMES )
|
||||
+def test_long_hashed_password(topo, create_user, scheme):
|
||||
+ """Check that hashed password with very long value does not cause trouble
|
||||
+
|
||||
+ :id: 252a1f76-114b-11ef-8a7a-482ae39447e5
|
||||
+ :setup: standalone Instance
|
||||
+ :parametrized: yes
|
||||
+ :steps:
|
||||
+ 1. Add a test user user
|
||||
+ 2. Set a long password with requested scheme
|
||||
+ 3. Bind on that user using a wrong password
|
||||
+ 4. Check that instance is still alive
|
||||
+ 5. Remove the added user
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Should get ldap.INVALID_CREDENTIALS exception
|
||||
+ 4. Success
|
||||
+ 5. Success
|
||||
+ """
|
||||
+ inst = topo.standalone
|
||||
+ inst.simple_bind_s(DN_DM, PASSWORD)
|
||||
+ users = UserAccounts(inst, DEFAULT_SUFFIX)
|
||||
+ # Make sure that server is started as this test may crash it
|
||||
+ inst.start()
|
||||
+ # Adding Test user (It may already exists if previous test failed)
|
||||
+ user2 = UserAccount(inst, dn='uid=test_user_1002,ou=People,dc=example,dc=com')
|
||||
+ if not user2.exists():
|
||||
+ user2 = users.create_test_user(uid=1002, gid=2002)
|
||||
+ # Setting hashed password
|
||||
+ passwd = 'A'*4000
|
||||
+ hashed_passwd = scheme.encode('utf-8') + base64.b64encode(passwd.encode('utf-8'))
|
||||
+ user2.replace('userpassword', hashed_passwd)
|
||||
+ # Bind on that user using a wrong password
|
||||
+ with pytest.raises(ldap.INVALID_CREDENTIALS):
|
||||
+ conn = user2.bind(PASSWORD)
|
||||
+ # Check that instance is still alive
|
||||
+ assert inst.status()
|
||||
+ # Remove the added user
|
||||
+ user2.delete()
|
||||
+
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Run isolated
|
||||
diff --git a/ldap/servers/plugins/pwdstorage/md5_pwd.c b/ldap/servers/plugins/pwdstorage/md5_pwd.c
|
||||
index 1e2cf58e7..b9a48d5ca 100644
|
||||
--- a/ldap/servers/plugins/pwdstorage/md5_pwd.c
|
||||
+++ b/ldap/servers/plugins/pwdstorage/md5_pwd.c
|
||||
@@ -37,6 +37,7 @@ md5_pw_cmp(const char *userpwd, const char *dbpwd)
|
||||
unsigned char hash_out[MD5_HASH_LEN];
|
||||
unsigned char b2a_out[MD5_HASH_LEN * 2]; /* conservative */
|
||||
SECItem binary_item;
|
||||
+ size_t dbpwd_len = strlen(dbpwd);
|
||||
|
||||
ctx = PK11_CreateDigestContext(SEC_OID_MD5);
|
||||
if (ctx == NULL) {
|
||||
@@ -45,6 +46,12 @@ md5_pw_cmp(const char *userpwd, const char *dbpwd)
|
||||
goto loser;
|
||||
}
|
||||
|
||||
+ if (dbpwd_len >= sizeof b2a_out) {
|
||||
+ slapi_log_err(SLAPI_LOG_PLUGIN, MD5_SUBSYSTEM_NAME,
|
||||
+ "The hashed password stored in the user entry is longer than any valid md5 hash");
|
||||
+ goto loser;
|
||||
+ }
|
||||
+
|
||||
/* create the hash */
|
||||
PK11_DigestBegin(ctx);
|
||||
PK11_DigestOp(ctx, (const unsigned char *)userpwd, strlen(userpwd));
|
||||
@@ -57,7 +64,7 @@ md5_pw_cmp(const char *userpwd, const char *dbpwd)
|
||||
bver = NSSBase64_EncodeItem(NULL, (char *)b2a_out, sizeof b2a_out, &binary_item);
|
||||
/* bver points to b2a_out upon success */
|
||||
if (bver) {
|
||||
- rc = slapi_ct_memcmp(bver, dbpwd, strlen(dbpwd));
|
||||
+ rc = slapi_ct_memcmp(bver, dbpwd, dbpwd_len);
|
||||
} else {
|
||||
slapi_log_err(SLAPI_LOG_PLUGIN, MD5_SUBSYSTEM_NAME,
|
||||
"Could not base64 encode hashed value for password compare");
|
||||
diff --git a/ldap/servers/plugins/pwdstorage/pbkdf2_pwd.c b/ldap/servers/plugins/pwdstorage/pbkdf2_pwd.c
|
||||
index dcac4fcdd..82b8c9501 100644
|
||||
--- a/ldap/servers/plugins/pwdstorage/pbkdf2_pwd.c
|
||||
+++ b/ldap/servers/plugins/pwdstorage/pbkdf2_pwd.c
|
||||
@@ -255,6 +255,12 @@ pbkdf2_sha256_pw_cmp(const char *userpwd, const char *dbpwd)
|
||||
passItem.data = (unsigned char *)userpwd;
|
||||
passItem.len = strlen(userpwd);
|
||||
|
||||
+ if (pwdstorage_base64_decode_len(dbpwd, dbpwd_len) > sizeof dbhash) {
|
||||
+ /* Hashed value is too long and cannot match any value generated by pbkdf2_sha256_hash */
|
||||
+ slapi_log_err(SLAPI_LOG_ERR, (char *)schemeName, "Unable to base64 decode dbpwd value. (hashed value is too long)\n");
|
||||
+ return result;
|
||||
+ }
|
||||
+
|
||||
/* Decode the DBpwd to bytes from b64 */
|
||||
if (PL_Base64Decode(dbpwd, dbpwd_len, dbhash) == NULL) {
|
||||
slapi_log_err(SLAPI_LOG_ERR, (char *)schemeName, "Unable to base64 decode dbpwd value\n");
|
||||
--
|
||||
2.46.0
|
||||
|
||||
@ -1,178 +0,0 @@
|
||||
From e8a5b1deef1b455aafecb71efc029d2407b1b06f Mon Sep 17 00:00:00 2001
|
||||
From: Simon Pichugin <spichugi@redhat.com>
|
||||
Date: Tue, 16 Jul 2024 08:32:21 -0700
|
||||
Subject: [PATCH] Issue 4778 - Add COMPACT_CL5 task to dsconf replication
|
||||
(#6260)
|
||||
|
||||
Description: In 1.4.3, the changelog is not part of a backend.
|
||||
It can be compacted with nsds5task: CAMPACT_CL5 as part of the replication entry.
|
||||
Add the task as a compact-changelog command under the dsconf replication tool.
|
||||
Add tests for the feature and fix old tests.
|
||||
|
||||
Related: https://github.com/389ds/389-ds-base/issues/4778
|
||||
|
||||
Reviewed by: @progier389 (Thanks!)
|
||||
---
|
||||
.../tests/suites/config/compact_test.py | 36 ++++++++++++++---
|
||||
src/lib389/lib389/cli_conf/replication.py | 10 +++++
|
||||
src/lib389/lib389/replica.py | 40 +++++++++++++++++++
|
||||
3 files changed, 81 insertions(+), 5 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/config/compact_test.py b/dirsrvtests/tests/suites/config/compact_test.py
|
||||
index 317258d0e..31d98d10c 100644
|
||||
--- a/dirsrvtests/tests/suites/config/compact_test.py
|
||||
+++ b/dirsrvtests/tests/suites/config/compact_test.py
|
||||
@@ -13,14 +13,14 @@ import time
|
||||
import datetime
|
||||
from lib389.tasks import DBCompactTask
|
||||
from lib389.backend import DatabaseConfig
|
||||
-from lib389.replica import Changelog5
|
||||
+from lib389.replica import Changelog5, Replicas
|
||||
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
|
||||
+ """Test compaction of database
|
||||
|
||||
:id: 1b3222ef-a336-4259-be21-6a52f76e1859
|
||||
:setup: Standalone Instance
|
||||
@@ -48,7 +48,7 @@ def test_compact_db_task(topo):
|
||||
|
||||
|
||||
def test_compaction_interval_and_time(topo):
|
||||
- """Specify a test case purpose or name here
|
||||
+ """Test compaction interval and time for database and changelog
|
||||
|
||||
:id: f361bee9-d7e7-4569-9255-d7b60dd9d92e
|
||||
:setup: Supplier Instance
|
||||
@@ -95,10 +95,36 @@ def test_compaction_interval_and_time(topo):
|
||||
|
||||
# Check compaction occurred as expected
|
||||
time.sleep(45)
|
||||
- assert not inst.searchErrorsLog("Compacting databases")
|
||||
+ assert not inst.searchErrorsLog("compacting replication changelogs")
|
||||
|
||||
time.sleep(90)
|
||||
- assert inst.searchErrorsLog("Compacting databases")
|
||||
+ assert inst.searchErrorsLog("compacting replication changelogs")
|
||||
+ inst.deleteErrorLogs(restart=False)
|
||||
+
|
||||
+
|
||||
+def test_compact_cl5_task(topo):
|
||||
+ """Test compaction of changelog5 database
|
||||
+
|
||||
+ :id: aadfa9f7-73c0-463a-912c-0a29aa1f8167
|
||||
+ :setup: Standalone Instance
|
||||
+ :steps:
|
||||
+ 1. Run compaction task
|
||||
+ 2. Check errors log to show task was run
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ """
|
||||
+ inst = topo.ms["supplier1"]
|
||||
+
|
||||
+ replicas = Replicas(inst)
|
||||
+ replicas.compact_changelog(log=log)
|
||||
+
|
||||
+ # Check compaction occurred as expected. But instead of time.sleep(5) check 1 sec in loop
|
||||
+ for _ in range(5):
|
||||
+ time.sleep(1)
|
||||
+ if inst.searchErrorsLog("compacting replication changelogs"):
|
||||
+ break
|
||||
+ assert inst.searchErrorsLog("compacting replication changelogs")
|
||||
inst.deleteErrorLogs(restart=False)
|
||||
|
||||
|
||||
diff --git a/src/lib389/lib389/cli_conf/replication.py b/src/lib389/lib389/cli_conf/replication.py
|
||||
index 352c0ee5b..ccc394255 100644
|
||||
--- a/src/lib389/lib389/cli_conf/replication.py
|
||||
+++ b/src/lib389/lib389/cli_conf/replication.py
|
||||
@@ -1199,6 +1199,11 @@ def restore_cl_dir(inst, basedn, log, args):
|
||||
replicas.restore_changelog(replica_roots=args.REPLICA_ROOTS, log=log)
|
||||
|
||||
|
||||
+def compact_cl5(inst, basedn, log, args):
|
||||
+ replicas = Replicas(inst)
|
||||
+ replicas.compact_changelog(replica_roots=args.REPLICA_ROOTS, log=log)
|
||||
+
|
||||
+
|
||||
def create_parser(subparsers):
|
||||
|
||||
############################################
|
||||
@@ -1326,6 +1331,11 @@ def create_parser(subparsers):
|
||||
help="Specify one replica root whose changelog you want to restore. "
|
||||
"The replica root will be consumed from the LDIF file name if the option is omitted.")
|
||||
|
||||
+ compact_cl = repl_subcommands.add_parser('compact-changelog', help='Compact the changelog database')
|
||||
+ compact_cl.set_defaults(func=compact_cl5)
|
||||
+ compact_cl.add_argument('REPLICA_ROOTS', nargs="+",
|
||||
+ help="Specify replica roots whose changelog you want to compact.")
|
||||
+
|
||||
restore_changelogdir = restore_subcommands.add_parser('from-changelogdir', help='Restore LDIF files from changelogdir.')
|
||||
restore_changelogdir.set_defaults(func=restore_cl_dir)
|
||||
restore_changelogdir.add_argument('REPLICA_ROOTS', nargs="+",
|
||||
diff --git a/src/lib389/lib389/replica.py b/src/lib389/lib389/replica.py
|
||||
index 94e1fdad5..1f321972d 100644
|
||||
--- a/src/lib389/lib389/replica.py
|
||||
+++ b/src/lib389/lib389/replica.py
|
||||
@@ -1648,6 +1648,11 @@ class Replica(DSLdapObject):
|
||||
"""
|
||||
self.replace('nsds5task', 'ldif2cl')
|
||||
|
||||
+ def begin_task_compact_cl5(self):
|
||||
+ """Begin COMPACT_CL5 task
|
||||
+ """
|
||||
+ self.replace('nsds5task', 'COMPACT_CL5')
|
||||
+
|
||||
def get_suffix(self):
|
||||
"""Return the suffix
|
||||
"""
|
||||
@@ -1829,6 +1834,41 @@ class Replicas(DSLdapObjects):
|
||||
log.error(f"Changelog LDIF for '{repl_root}' was not found")
|
||||
continue
|
||||
|
||||
+ def compact_changelog(self, replica_roots=[], log=None):
|
||||
+ """Compact Directory Server replication changelog
|
||||
+
|
||||
+ :param replica_roots: Replica suffixes that need to be processed (and optional LDIF file path)
|
||||
+ :type replica_roots: list of str
|
||||
+ :param log: The logger object
|
||||
+ :type log: logger
|
||||
+ """
|
||||
+
|
||||
+ if log is None:
|
||||
+ log = self._log
|
||||
+
|
||||
+ # Check if the changelog entry exists
|
||||
+ try:
|
||||
+ cl = Changelog5(self._instance)
|
||||
+ cl.get_attr_val_utf8_l("nsslapd-changelogdir")
|
||||
+ except ldap.NO_SUCH_OBJECT:
|
||||
+ raise ValueError("Changelog entry was not found. Probably, the replication is not enabled on this instance")
|
||||
+
|
||||
+ # Get all the replicas on the server if --replica-roots option is not specified
|
||||
+ repl_roots = []
|
||||
+ if not replica_roots:
|
||||
+ for replica in self.list():
|
||||
+ repl_roots.append(replica.get_attr_val_utf8("nsDS5ReplicaRoot"))
|
||||
+ else:
|
||||
+ for repl_root in replica_roots:
|
||||
+ repl_roots.append(repl_root)
|
||||
+
|
||||
+ # Dump the changelog for the replica
|
||||
+
|
||||
+ # Dump the changelog for the replica
|
||||
+ for repl_root in repl_roots:
|
||||
+ replica = self.get(repl_root)
|
||||
+ replica.begin_task_compact_cl5()
|
||||
+
|
||||
|
||||
class BootstrapReplicationManager(DSLdapObject):
|
||||
"""A Replication Manager credential for bootstrapping the repl process.
|
||||
--
|
||||
2.47.0
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
From c6e2911dca08aac40e98999b48019eac72cc74a6 Mon Sep 17 00:00:00 2001
|
||||
From a58212943da2604091e7d5a20829ab54970d41c9 Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Thu, 5 Feb 2026 12:17:06 +0100
|
||||
Subject: [PATCH] Issue 7223 - Add upgrade function to remove
|
||||
@ -15,17 +15,17 @@ automatically on server startup and removes the attribute if found.
|
||||
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/7223
|
||||
|
||||
Reviewed by: @progier389, @tbordaz (Thanks!)
|
||||
Reviewed by: @progier389, @tbordaz, @droideck (Thanks!)
|
||||
---
|
||||
.../healthcheck/health_system_indexes_test.py | 52 +++++
|
||||
ldap/servers/slapd/upgrade.c | 210 ++++++++++++++++++
|
||||
2 files changed, 262 insertions(+)
|
||||
.../healthcheck/health_system_indexes_test.py | 52 +++++++++
|
||||
ldap/servers/slapd/upgrade.c | 105 ++++++++++++++++++
|
||||
2 files changed, 157 insertions(+)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
|
||||
index 842f7e8dd..72a04fdab 100644
|
||||
index 61972d60c..b0d7a99ec 100644
|
||||
--- a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
|
||||
+++ b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
|
||||
@@ -451,6 +451,58 @@ def test_multiple_missing_indexes(topology_st, log_buffering_enabled):
|
||||
@@ -450,6 +450,58 @@ def test_multiple_missing_indexes(topology_st, log_buffering_enabled):
|
||||
run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
|
||||
|
||||
|
||||
@ -85,12 +85,12 @@ index 842f7e8dd..72a04fdab 100644
|
||||
# Run isolated
|
||||
# -s for DEBUG mode
|
||||
diff --git a/ldap/servers/slapd/upgrade.c b/ldap/servers/slapd/upgrade.c
|
||||
index 2f124afaf..074c15e3c 100644
|
||||
index 43906c1af..adfec63de 100644
|
||||
--- a/ldap/servers/slapd/upgrade.c
|
||||
+++ b/ldap/servers/slapd/upgrade.c
|
||||
@@ -22,8 +22,218 @@
|
||||
* or altered.
|
||||
*/
|
||||
@@ -279,6 +279,107 @@ upgrade_205_fixup_repl_dep(void)
|
||||
return UPGRADE_SUCCESS;
|
||||
}
|
||||
|
||||
+/*
|
||||
+ * Remove nsIndexIDListScanLimit from parentid index configuration.
|
||||
@ -193,120 +193,20 @@ index 2f124afaf..074c15e3c 100644
|
||||
+ return uresult;
|
||||
+}
|
||||
+
|
||||
+/*
|
||||
+ * Check if parentid indexes are missing the integerOrderingMatch
|
||||
+ * matching rule.
|
||||
+ *
|
||||
+ * This function logs a warning if we detect this condition, advising
|
||||
+ * the administrator to reindex the affected attributes.
|
||||
+ */
|
||||
+static upgrade_status
|
||||
+upgrade_check_id_index_matching_rule(void)
|
||||
+{
|
||||
+ struct slapi_pblock *pb = slapi_pblock_new();
|
||||
+ Slapi_Entry **backends = NULL;
|
||||
+ const char *be_base_dn = "cn=ldbm database,cn=plugins,cn=config";
|
||||
+ const char *be_filter = "(objectclass=nsBackendInstance)";
|
||||
+ const char *attrs_to_check[] = {"parentid", "ancestorid", NULL};
|
||||
+ upgrade_status uresult = UPGRADE_SUCCESS;
|
||||
+
|
||||
+ /* Search for all backend instances */
|
||||
+ slapi_search_internal_set_pb(
|
||||
+ pb, be_base_dn,
|
||||
+ LDAP_SCOPE_ONELEVEL,
|
||||
+ be_filter, NULL, 0, NULL, NULL,
|
||||
+ plugin_get_default_component_id(), 0);
|
||||
+ slapi_search_internal_pb(pb);
|
||||
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &backends);
|
||||
+
|
||||
+ if (backends) {
|
||||
+ for (size_t be_idx = 0; backends[be_idx] != NULL; be_idx++) {
|
||||
+ const char *be_name = slapi_entry_attr_get_ref(backends[be_idx], "cn");
|
||||
+ if (!be_name) {
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ /* Check each attribute that should have integerOrderingMatch */
|
||||
+ for (size_t attr_idx = 0; attrs_to_check[attr_idx] != NULL; attr_idx++) {
|
||||
+ const char *attr_name = attrs_to_check[attr_idx];
|
||||
+ struct slapi_pblock *idx_pb = slapi_pblock_new();
|
||||
+ Slapi_Entry **idx_entries = NULL;
|
||||
+ char *idx_dn = slapi_create_dn_string("cn=%s,cn=index,cn=%s,%s",
|
||||
+ attr_name, be_name, be_base_dn);
|
||||
+ char *idx_filter = "(objectclass=nsIndex)";
|
||||
+ PRBool has_matching_rule = PR_FALSE;
|
||||
+
|
||||
+ if (!idx_dn) {
|
||||
+ slapi_pblock_destroy(idx_pb);
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ slapi_search_internal_set_pb(
|
||||
+ idx_pb, idx_dn,
|
||||
+ LDAP_SCOPE_BASE,
|
||||
+ idx_filter, NULL, 0, NULL, NULL,
|
||||
+ plugin_get_default_component_id(), 0);
|
||||
+ slapi_search_internal_pb(idx_pb);
|
||||
+ slapi_pblock_get(idx_pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &idx_entries);
|
||||
+
|
||||
+ if (idx_entries && idx_entries[0]) {
|
||||
+ /* Index exists, check if it has integerOrderingMatch */
|
||||
+ Slapi_Attr *mr_attr = NULL;
|
||||
+ if (slapi_entry_attr_find(idx_entries[0], "nsMatchingRule", &mr_attr) == 0) {
|
||||
+ Slapi_Value *sval = NULL;
|
||||
+ int idx;
|
||||
+ for (idx = slapi_attr_first_value(mr_attr, &sval);
|
||||
+ idx != -1;
|
||||
+ idx = slapi_attr_next_value(mr_attr, idx, &sval)) {
|
||||
+ const struct berval *bval = slapi_value_get_berval(sval);
|
||||
+ if (bval && bval->bv_val &&
|
||||
+ strcasecmp(bval->bv_val, "integerOrderingMatch") == 0) {
|
||||
+ has_matching_rule = PR_TRUE;
|
||||
+ break;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ if (!has_matching_rule) {
|
||||
+ /* Index exists but doesn't have integerOrderingMatch, log a warning */
|
||||
+ slapi_log_err(SLAPI_LOG_ERR, "upgrade_check_id_index_matching_rule",
|
||||
+ "Index '%s' in backend '%s' is missing 'nsMatchingRule: integerOrderingMatch'. "
|
||||
+ "Incorrectly configured system indexes can lead to poor search performance, replication issues, and other operational problems. "
|
||||
+ "To fix this, add the matching rule and reindex: "
|
||||
+ "dsconf <instance> backend index set --add-mr integerOrderingMatch --attr %s %s && "
|
||||
+ "dsconf <instance> backend index reindex --attr %s %s. "
|
||||
+ "WARNING: Reindexing can be resource-intensive and may impact server performance on a live system. "
|
||||
+ "Consider scheduling reindexing during maintenance windows or periods of low activity.\n",
|
||||
+ attr_name, be_name, attr_name, be_name, attr_name, be_name);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ slapi_ch_free_string(&idx_dn);
|
||||
+ slapi_free_search_results_internal(idx_pb);
|
||||
+ slapi_pblock_destroy(idx_pb);
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ slapi_free_search_results_internal(pb);
|
||||
+ slapi_pblock_destroy(pb);
|
||||
+
|
||||
+ return uresult;
|
||||
+}
|
||||
+
|
||||
upgrade_status
|
||||
upgrade_server(void)
|
||||
{
|
||||
/*
|
||||
* Check if parentid/ancestorid indexes are missing the integerOrderingMatch
|
||||
* matching rule.
|
||||
@@ -407,6 +508,10 @@ upgrade_server(void)
|
||||
return UPGRADE_FAILURE;
|
||||
}
|
||||
|
||||
+ if (upgrade_remove_index_scanlimit() != UPGRADE_SUCCESS) {
|
||||
+ return UPGRADE_FAILURE;
|
||||
+ }
|
||||
+
|
||||
+ if (upgrade_check_id_index_matching_rule() != UPGRADE_SUCCESS) {
|
||||
+ return UPGRADE_FAILURE;
|
||||
+ }
|
||||
+
|
||||
return UPGRADE_SUCCESS;
|
||||
}
|
||||
if (upgrade_check_id_index_matching_rule() != UPGRADE_SUCCESS) {
|
||||
return UPGRADE_FAILURE;
|
||||
}
|
||||
--
|
||||
2.52.0
|
||||
|
||||
@ -1,55 +0,0 @@
|
||||
From d1cd9a5675e2953b7c8034ebb87a434cdd3ce0c3 Mon Sep 17 00:00:00 2001
|
||||
From: tbordaz <tbordaz@redhat.com>
|
||||
Date: Mon, 2 Dec 2024 17:18:32 +0100
|
||||
Subject: [PATCH] Issue 6417 - If an entry RDN is identical to the suffix, then
|
||||
Entryrdn gets broken during a reindex (#6418)
|
||||
|
||||
Bug description:
|
||||
During a reindex, the entryrdn index is built at the end from
|
||||
each entry in the suffix.
|
||||
If one entry has a RDN that is identical to the suffix DN,
|
||||
then entryrdn_lookup_dn may erroneously return the suffix DN
|
||||
as the DN of the entry.
|
||||
|
||||
Fix description:
|
||||
When the lookup entry has no parent (because index is under
|
||||
work) the loop lookup the entry using the RDN.
|
||||
If this RDN matches the suffix DN, then it exits from the loop
|
||||
with the suffix DN.
|
||||
Before exiting it checks that the original lookup entryID
|
||||
is equal to suffix entryID. If it does not match
|
||||
the function fails and then the DN from the entry will be
|
||||
built from id2enty
|
||||
|
||||
fixes: #6417
|
||||
|
||||
Reviewed by: Pierre Rogier, Simon Pichugin (Thanks !!!)
|
||||
---
|
||||
ldap/servers/slapd/back-ldbm/ldbm_entryrdn.c | 11 ++++++++++-
|
||||
1 file changed, 10 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_entryrdn.c b/ldap/servers/slapd/back-ldbm/ldbm_entryrdn.c
|
||||
index 5797dd779..83b041192 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/ldbm_entryrdn.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/ldbm_entryrdn.c
|
||||
@@ -1224,7 +1224,16 @@ entryrdn_lookup_dn(backend *be,
|
||||
}
|
||||
goto bail;
|
||||
}
|
||||
- maybesuffix = 1;
|
||||
+ if (workid == 1) {
|
||||
+ /* The loop (workid) iterates from the starting 'id'
|
||||
+ * up to the suffix ID (i.e. '1').
|
||||
+ * A corner case (#6417) is if an entry, on the path
|
||||
+ * 'id' -> suffix, has the same RDN than the suffix.
|
||||
+ * In order to erroneously believe the loop hits the suffix
|
||||
+ * we need to check that 'workid' is '1' (suffix)
|
||||
+ */
|
||||
+ maybesuffix = 1;
|
||||
+ }
|
||||
} else {
|
||||
_entryrdn_cursor_print_error("entryrdn_lookup_dn",
|
||||
key.data, data.size, data.ulen, rc);
|
||||
--
|
||||
2.48.0
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
From 1669e65b36d543fb69ef5503e5072ea37e9a0e19 Mon Sep 17 00:00:00 2001
|
||||
From 9a9446b9bafe25eacf97039e66d6ef19d366315c Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Mon, 9 Feb 2026 14:12:18 +0100
|
||||
Date: Thu, 5 Feb 2026 12:17:06 +0100
|
||||
Subject: [PATCH] Issue 7223 - Add upgrade function to remove ancestorid index
|
||||
config entry
|
||||
|
||||
@ -13,7 +13,7 @@ Also remove ancestorid index configuration from template-dse.ldif.
|
||||
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/7223
|
||||
|
||||
Reviewed by: @progier389, @tbordaz (Thanks!)
|
||||
Reviewed by: @progier389, @tbordaz, @droideck (Thanks!)
|
||||
---
|
||||
.../healthcheck/health_system_indexes_test.py | 85 +++++++++++
|
||||
ldap/ldif/template-dse.ldif.in | 8 --
|
||||
@ -21,10 +21,10 @@ Reviewed by: @progier389, @tbordaz (Thanks!)
|
||||
3 files changed, 214 insertions(+), 12 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
|
||||
index 72a04fdab..db5d13bf8 100644
|
||||
index b0d7a99ec..4b0c58835 100644
|
||||
--- a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
|
||||
+++ b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
|
||||
@@ -502,6 +502,91 @@ def test_upgrade_removes_parentid_scanlimit(topology_st):
|
||||
@@ -501,6 +501,91 @@ def test_upgrade_removes_parentid_scanlimit(topology_st):
|
||||
|
||||
log.info("Upgrade successfully removed nsIndexIDListScanLimit from parentid index")
|
||||
|
||||
@ -117,10 +117,10 @@ index 72a04fdab..db5d13bf8 100644
|
||||
if __name__ == "__main__":
|
||||
# Run isolated
|
||||
diff --git a/ldap/ldif/template-dse.ldif.in b/ldap/ldif/template-dse.ldif.in
|
||||
index c2754adf8..2ddaf5fb3 100644
|
||||
index 6f97d492b..70e7a85ac 100644
|
||||
--- a/ldap/ldif/template-dse.ldif.in
|
||||
+++ b/ldap/ldif/template-dse.ldif.in
|
||||
@@ -973,14 +973,6 @@ cn: aci
|
||||
@@ -990,14 +990,6 @@ cn: aci
|
||||
nssystemindex: true
|
||||
nsindextype: pres
|
||||
|
||||
@ -136,10 +136,10 @@ index c2754adf8..2ddaf5fb3 100644
|
||||
objectclass: top
|
||||
objectclass: nsIndex
|
||||
diff --git a/ldap/servers/slapd/upgrade.c b/ldap/servers/slapd/upgrade.c
|
||||
index 074c15e3c..aa51e72d2 100644
|
||||
index adfec63de..d9156cae9 100644
|
||||
--- a/ldap/servers/slapd/upgrade.c
|
||||
+++ b/ldap/servers/slapd/upgrade.c
|
||||
@@ -123,6 +123,126 @@ upgrade_remove_index_scanlimit(void)
|
||||
@@ -380,6 +380,126 @@ upgrade_remove_index_scanlimit(void)
|
||||
return uresult;
|
||||
}
|
||||
|
||||
@ -264,9 +264,9 @@ index 074c15e3c..aa51e72d2 100644
|
||||
+}
|
||||
+
|
||||
/*
|
||||
* Check if parentid indexes are missing the integerOrderingMatch
|
||||
* Check if parentid/ancestorid indexes are missing the integerOrderingMatch
|
||||
* matching rule.
|
||||
@@ -137,7 +257,7 @@ upgrade_check_id_index_matching_rule(void)
|
||||
@@ -394,7 +514,7 @@ upgrade_check_id_index_matching_rule(void)
|
||||
Slapi_Entry **backends = NULL;
|
||||
const char *be_base_dn = "cn=ldbm database,cn=plugins,cn=config";
|
||||
const char *be_filter = "(objectclass=nsBackendInstance)";
|
||||
@ -275,7 +275,7 @@ index 074c15e3c..aa51e72d2 100644
|
||||
upgrade_status uresult = UPGRADE_SUCCESS;
|
||||
|
||||
/* Search for all backend instances */
|
||||
@@ -151,8 +271,9 @@ upgrade_check_id_index_matching_rule(void)
|
||||
@@ -408,8 +528,9 @@ upgrade_check_id_index_matching_rule(void)
|
||||
|
||||
if (backends) {
|
||||
for (size_t be_idx = 0; backends[be_idx] != NULL; be_idx++) {
|
||||
@ -286,7 +286,7 @@ index 074c15e3c..aa51e72d2 100644
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -161,8 +282,8 @@ upgrade_check_id_index_matching_rule(void)
|
||||
@@ -418,8 +539,8 @@ upgrade_check_id_index_matching_rule(void)
|
||||
const char *attr_name = attrs_to_check[attr_idx];
|
||||
struct slapi_pblock *idx_pb = slapi_pblock_new();
|
||||
Slapi_Entry **idx_entries = NULL;
|
||||
@ -297,7 +297,7 @@ index 074c15e3c..aa51e72d2 100644
|
||||
char *idx_filter = "(objectclass=nsIndex)";
|
||||
PRBool has_matching_rule = PR_FALSE;
|
||||
|
||||
@@ -231,6 +352,10 @@ upgrade_server(void)
|
||||
@@ -512,6 +633,10 @@ upgrade_server(void)
|
||||
return UPGRADE_FAILURE;
|
||||
}
|
||||
|
||||
@ -1,267 +0,0 @@
|
||||
From 9b2fc77a36156ea987dcea6e2043f8e4c4a6b259 Mon Sep 17 00:00:00 2001
|
||||
From: progier389 <progier@redhat.com>
|
||||
Date: Tue, 18 Jun 2024 14:21:07 +0200
|
||||
Subject: [PATCH] Issue 6224 - d2entry - Could not open id2entry err 0 - at
|
||||
startup when having sub-suffixes (#6225)
|
||||
|
||||
Problem:: d2entry - Could not open id2entry err 0 is logged at startup when having sub-suffixes
|
||||
Reason: The slapi_exist_referral internal search access a backend that is not yet started.
|
||||
Solution: Limit the internal search to a single backend
|
||||
|
||||
Issue: #6224
|
||||
|
||||
Reviewed by: @droideck Thanks!
|
||||
|
||||
(cherry picked from commit 796f703021e961fdd8cbc53b4ad4e20258af0e96)
|
||||
---
|
||||
.../tests/suites/ds_logs/ds_logs_test.py | 1 +
|
||||
.../suites/mapping_tree/regression_test.py | 161 +++++++++++++++++-
|
||||
ldap/servers/slapd/backend.c | 7 +-
|
||||
3 files changed, 159 insertions(+), 10 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/ds_logs/ds_logs_test.py b/dirsrvtests/tests/suites/ds_logs/ds_logs_test.py
|
||||
index 812936c62..84a9c6ec8 100644
|
||||
--- a/dirsrvtests/tests/suites/ds_logs/ds_logs_test.py
|
||||
+++ b/dirsrvtests/tests/suites/ds_logs/ds_logs_test.py
|
||||
@@ -1222,6 +1222,7 @@ def test_referral_check(topology_st, request):
|
||||
|
||||
request.addfinalizer(fin)
|
||||
|
||||
+<<<<<<< HEAD
|
||||
def test_referral_subsuffix(topology_st, request):
|
||||
"""Test the results of an inverted parent suffix definition in the configuration.
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/mapping_tree/regression_test.py b/dirsrvtests/tests/suites/mapping_tree/regression_test.py
|
||||
index 99d4a1d5f..689ff9f59 100644
|
||||
--- a/dirsrvtests/tests/suites/mapping_tree/regression_test.py
|
||||
+++ b/dirsrvtests/tests/suites/mapping_tree/regression_test.py
|
||||
@@ -11,10 +11,14 @@ import ldap
|
||||
import logging
|
||||
import os
|
||||
import pytest
|
||||
+import time
|
||||
from lib389.backend import Backends, Backend
|
||||
+from lib389._constants import HOST_STANDALONE, PORT_STANDALONE, DN_DM, PW_DM
|
||||
from lib389.dbgen import dbgen_users
|
||||
from lib389.mappingTree import MappingTrees
|
||||
from lib389.topologies import topology_st
|
||||
+from lib389.referral import Referrals, Referral
|
||||
+
|
||||
|
||||
try:
|
||||
from lib389.backend import BackendSuffixView
|
||||
@@ -31,14 +35,26 @@ else:
|
||||
logging.getLogger(__name__).setLevel(logging.INFO)
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
+PARENT_SUFFIX = "dc=parent"
|
||||
+CHILD1_SUFFIX = f"dc=child1,{PARENT_SUFFIX}"
|
||||
+CHILD2_SUFFIX = f"dc=child2,{PARENT_SUFFIX}"
|
||||
+
|
||||
+PARENT_REFERRAL_DN = f"cn=ref,ou=People,{PARENT_SUFFIX}"
|
||||
+CHILD1_REFERRAL_DN = f"cn=ref,ou=people,{CHILD1_SUFFIX}"
|
||||
+CHILD2_REFERRAL_DN = f"cn=ref,ou=people,{CHILD2_SUFFIX}"
|
||||
+
|
||||
+REFERRAL_CHECK_PEDIOD = 7
|
||||
+
|
||||
+
|
||||
+
|
||||
BESTRUCT = [
|
||||
- { "bename" : "parent", "suffix": "dc=parent" },
|
||||
- { "bename" : "child1", "suffix": "dc=child1,dc=parent" },
|
||||
- { "bename" : "child2", "suffix": "dc=child2,dc=parent" },
|
||||
+ { "bename" : "parent", "suffix": PARENT_SUFFIX },
|
||||
+ { "bename" : "child1", "suffix": CHILD1_SUFFIX },
|
||||
+ { "bename" : "child2", "suffix": CHILD2_SUFFIX },
|
||||
]
|
||||
|
||||
|
||||
-@pytest.fixture(scope="function")
|
||||
+@pytest.fixture(scope="module")
|
||||
def topo(topology_st, request):
|
||||
bes = []
|
||||
|
||||
@@ -50,6 +66,9 @@ def topo(topology_st, request):
|
||||
request.addfinalizer(fin)
|
||||
|
||||
inst = topology_st.standalone
|
||||
+ # Reduce nsslapd-referral-check-period to accelerate test
|
||||
+ topology_st.standalone.config.set("nsslapd-referral-check-period", str(REFERRAL_CHECK_PEDIOD))
|
||||
+
|
||||
ldif_files = {}
|
||||
for d in BESTRUCT:
|
||||
bename = d['bename']
|
||||
@@ -76,14 +95,13 @@ def topo(topology_st, request):
|
||||
inst.start()
|
||||
return topology_st
|
||||
|
||||
-# Parameters for test_change_repl_passwd
|
||||
-EXPECTED_ENTRIES = (("dc=parent", 39), ("dc=child1,dc=parent", 13), ("dc=child2,dc=parent", 13))
|
||||
+# Parameters for test_sub_suffixes
|
||||
@pytest.mark.parametrize(
|
||||
"orphan_param",
|
||||
[
|
||||
- pytest.param( ( True, { "dc=parent": 2, "dc=child1,dc=parent":1, "dc=child2,dc=parent":1}), id="orphan-is-true" ),
|
||||
- pytest.param( ( False, { "dc=parent": 3, "dc=child1,dc=parent":1, "dc=child2,dc=parent":1}), id="orphan-is-false" ),
|
||||
- pytest.param( ( None, { "dc=parent": 3, "dc=child1,dc=parent":1, "dc=child2,dc=parent":1}), id="no-orphan" ),
|
||||
+ pytest.param( ( True, { PARENT_SUFFIX: 2, CHILD1_SUFFIX:1, CHILD2_SUFFIX:1}), id="orphan-is-true" ),
|
||||
+ pytest.param( ( False, { PARENT_SUFFIX: 3, CHILD1_SUFFIX:1, CHILD2_SUFFIX:1}), id="orphan-is-false" ),
|
||||
+ pytest.param( ( None, { PARENT_SUFFIX: 3, CHILD1_SUFFIX:1, CHILD2_SUFFIX:1}), id="no-orphan" ),
|
||||
],
|
||||
)
|
||||
|
||||
@@ -128,3 +146,128 @@ def test_sub_suffixes(topo, orphan_param):
|
||||
log.info('Test PASSED')
|
||||
|
||||
|
||||
+def test_one_level_search_on_sub_suffixes(topo):
|
||||
+ """ Perform one level scoped search accross suffix and sub-suffix
|
||||
+
|
||||
+ :id: 92f3139e-280e-11ef-a989-482ae39447e5
|
||||
+ :feature: mapping-tree
|
||||
+ :setup: Standalone instance with 3 additional backends:
|
||||
+ dc=parent, dc=child1,dc=parent, dc=childr21,dc=parent
|
||||
+ :steps:
|
||||
+ 1. Perform a ONE LEVEL search on dc=parent
|
||||
+ 2. Check that all expected entries have been returned
|
||||
+ 3. Check that only the expected entries have been returned
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. each expected dn should be in the result set
|
||||
+ 3. Number of returned entries should be the same as the number of expected entries
|
||||
+ """
|
||||
+ expected_dns = ( 'dc=child1,dc=parent',
|
||||
+ 'dc=child2,dc=parent',
|
||||
+ 'ou=accounting,dc=parent',
|
||||
+ 'ou=product development,dc=parent',
|
||||
+ 'ou=product testing,dc=parent',
|
||||
+ 'ou=human resources,dc=parent',
|
||||
+ 'ou=payroll,dc=parent',
|
||||
+ 'ou=people,dc=parent',
|
||||
+ 'ou=groups,dc=parent', )
|
||||
+ entries = topo.standalone.search_s("dc=parent", ldap.SCOPE_ONELEVEL, "(objectClass=*)",
|
||||
+ attrlist=("dc","ou"), escapehatch='i am sure')
|
||||
+ log.info(f'one level search on dc=parent returned the following entries: {entries}')
|
||||
+ dns = [ entry.dn for entry in entries ]
|
||||
+ for dn in expected_dns:
|
||||
+ assert dn in dns
|
||||
+ assert len(entries) == len(expected_dns)
|
||||
+
|
||||
+
|
||||
+def test_sub_suffixes_errlog(topo):
|
||||
+ """ check the entries found on suffix/sub-suffix
|
||||
+ used int
|
||||
+
|
||||
+ :id: 1db9d52e-28de-11ef-b286-482ae39447e5
|
||||
+ :feature: mapping-tree
|
||||
+ :setup: Standalone instance with 3 additional backends:
|
||||
+ dc=parent, dc=child1,dc=parent, dc=childr21,dc=parent
|
||||
+ :steps:
|
||||
+ 1. Check that id2entry error message is not in the error log.
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ """
|
||||
+ inst = topo.standalone
|
||||
+ assert not inst.searchErrorsLog('id2entry - Could not open id2entry err 0')
|
||||
+
|
||||
+
|
||||
+# Parameters for test_referral_subsuffix:
|
||||
+# a tuple pair containing:
|
||||
+# - list of referral dn that must be created
|
||||
+# - dict of searches basedn: expected_number_of_referrals
|
||||
+@pytest.mark.parametrize(
|
||||
+ "parameters",
|
||||
+ [
|
||||
+ pytest.param( ((PARENT_REFERRAL_DN, CHILD1_REFERRAL_DN), {PARENT_SUFFIX: 2, CHILD1_SUFFIX:1, CHILD2_SUFFIX:0}), id="Both"),
|
||||
+ pytest.param( ((PARENT_REFERRAL_DN,), {PARENT_SUFFIX: 1, CHILD1_SUFFIX:0, CHILD2_SUFFIX:0}) , id="Parent"),
|
||||
+ pytest.param( ((CHILD1_REFERRAL_DN,), {PARENT_SUFFIX: 1, CHILD1_SUFFIX:1, CHILD2_SUFFIX:0}) , id="Child"),
|
||||
+ pytest.param( ((), {PARENT_SUFFIX: 0, CHILD1_SUFFIX:0, CHILD2_SUFFIX:0}), id="None"),
|
||||
+ ])
|
||||
+
|
||||
+def test_referral_subsuffix(topo, request, parameters):
|
||||
+ """Test the results of an inverted parent suffix definition in the configuration.
|
||||
+
|
||||
+ For more details see:
|
||||
+ https://www.port389.org/docs/389ds/design/mapping_tree_assembly.html
|
||||
+
|
||||
+ :id: 4e111a22-2a5d-11ef-a890-482ae39447e5
|
||||
+ :feature: referrals
|
||||
+ :setup: Standalone instance with 3 additional backends:
|
||||
+ dc=parent, dc=child1,dc=parent, dc=childr21,dc=parent
|
||||
+
|
||||
+ :setup: Standalone instance
|
||||
+ :parametrized: yes
|
||||
+ :steps:
|
||||
+ refs,searches = referrals
|
||||
+
|
||||
+ 1. Create the referrals according to the current parameter
|
||||
+ 2. Wait enough time so they get detected
|
||||
+ 3. For each search base dn, in the current parameter, perform the two following steps
|
||||
+ 4. In 3. loop: Perform a search with provided base dn
|
||||
+ 5. In 3. loop: Check that the number of returned referrals is the expected one.
|
||||
+
|
||||
+ :expectedresults:
|
||||
+ all steps succeeds
|
||||
+ """
|
||||
+ inst = topo.standalone
|
||||
+
|
||||
+ def fin():
|
||||
+ log.info('Deleting all referrals')
|
||||
+ for ref in Referrals(inst, PARENT_SUFFIX).list():
|
||||
+ ref.delete()
|
||||
+
|
||||
+ # Set cleanup callback
|
||||
+ if DEBUGGING:
|
||||
+ request.addfinalizer(fin)
|
||||
+
|
||||
+ # Remove all referrals
|
||||
+ fin()
|
||||
+ # Add requested referrals
|
||||
+ for dn in parameters[0]:
|
||||
+ refs = Referral(inst, dn=dn)
|
||||
+ refs.create(basedn=dn, properties={ 'cn': 'ref', 'ref': f'ldap://remote/{dn}'})
|
||||
+ # Wait that the internal search detects the referrals
|
||||
+ time.sleep(REFERRAL_CHECK_PEDIOD + 1)
|
||||
+ # Open a test connection
|
||||
+ ldc = ldap.initialize(f"ldap://{HOST_STANDALONE}:{PORT_STANDALONE}")
|
||||
+ ldc.set_option(ldap.OPT_REFERRALS,0)
|
||||
+ ldc.simple_bind_s(DN_DM,PW_DM)
|
||||
+
|
||||
+ # For each search base dn:
|
||||
+ for basedn,nbref in parameters[1].items():
|
||||
+ log.info(f"Referrals are: {parameters[0]}")
|
||||
+ # Perform a search with provided base dn
|
||||
+ result = ldc.search_s(basedn, ldap.SCOPE_SUBTREE, filterstr="(ou=People)")
|
||||
+ found_dns = [ dn for dn,entry in result if dn is not None ]
|
||||
+ found_refs = [ entry for dn,entry in result if dn is None ]
|
||||
+ log.info(f"Search on {basedn} returned {found_dns} and {found_refs}")
|
||||
+ # Check that the number of returned referrals is the expected one.
|
||||
+ log.info(f"Search returned {len(found_refs)} referrals. {nbref} are expected.")
|
||||
+ assert len(found_refs) == nbref
|
||||
+ ldc.unbind()
|
||||
diff --git a/ldap/servers/slapd/backend.c b/ldap/servers/slapd/backend.c
|
||||
index 498f683b1..f86b0b9b6 100644
|
||||
--- a/ldap/servers/slapd/backend.c
|
||||
+++ b/ldap/servers/slapd/backend.c
|
||||
@@ -230,12 +230,17 @@ slapi_exist_referral(Slapi_Backend *be)
|
||||
|
||||
/* search for ("smart") referral entries */
|
||||
search_pb = slapi_pblock_new();
|
||||
- server_ctrls = (LDAPControl **) slapi_ch_calloc(2, sizeof (LDAPControl *));
|
||||
+ server_ctrls = (LDAPControl **) slapi_ch_calloc(3, sizeof (LDAPControl *));
|
||||
server_ctrls[0] = (LDAPControl *) slapi_ch_malloc(sizeof (LDAPControl));
|
||||
server_ctrls[0]->ldctl_oid = slapi_ch_strdup(LDAP_CONTROL_MANAGEDSAIT);
|
||||
server_ctrls[0]->ldctl_value.bv_val = NULL;
|
||||
server_ctrls[0]->ldctl_value.bv_len = 0;
|
||||
server_ctrls[0]->ldctl_iscritical = '\0';
|
||||
+ server_ctrls[1] = (LDAPControl *) slapi_ch_malloc(sizeof (LDAPControl));
|
||||
+ server_ctrls[1]->ldctl_oid = slapi_ch_strdup(MTN_CONTROL_USE_ONE_BACKEND_EXT_OID);
|
||||
+ server_ctrls[1]->ldctl_value.bv_val = NULL;
|
||||
+ server_ctrls[1]->ldctl_value.bv_len = 0;
|
||||
+ server_ctrls[1]->ldctl_iscritical = '\0';
|
||||
slapi_search_internal_set_pb(search_pb, suffix, LDAP_SCOPE_SUBTREE,
|
||||
filter, NULL, 0, server_ctrls, NULL,
|
||||
(void *) plugin_get_default_component_id(), 0);
|
||||
--
|
||||
2.48.0
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
From f6dd2d6dc94290c85d221951b34792443bb08d80 Mon Sep 17 00:00:00 2001
|
||||
From b5bee921b7f4cfbf7a2edbe55d4291f487f4a140 Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Thu, 5 Feb 2026 12:17:06 +0100
|
||||
Subject: [PATCH] Issue 7223 - Detect and log index ordering mismatch during
|
||||
@ -10,16 +10,16 @@ index data and logs a message in case of a mismatch with DSE config entry.
|
||||
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/7223
|
||||
|
||||
Reviewed by: @progier389, @tbordaz (Thanks!)
|
||||
Reviewed by: @progier389, @tbordaz, @droideck (Thanks!)
|
||||
---
|
||||
ldap/servers/slapd/back-ldbm/instance.c | 269 ++++++++++++++++++++++++
|
||||
1 file changed, 269 insertions(+)
|
||||
ldap/servers/slapd/back-ldbm/instance.c | 262 ++++++++++++++++++++++++
|
||||
1 file changed, 262 insertions(+)
|
||||
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/instance.c b/ldap/servers/slapd/back-ldbm/instance.c
|
||||
index 6098e04fc..388dd6efb 100644
|
||||
index f9a546661..3fcdb5554 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/instance.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/instance.c
|
||||
@@ -248,6 +248,273 @@ ldbm_instance_create_default_indexes(backend *be)
|
||||
@@ -248,6 +248,266 @@ ldbm_instance_create_default_indexes(backend *be)
|
||||
}
|
||||
|
||||
|
||||
@ -107,10 +107,10 @@ index 6098e04fc..388dd6efb 100644
|
||||
+{
|
||||
+ ldbm_instance *inst = (ldbm_instance *)be->be_instance_info;
|
||||
+ struct attrinfo *ai = NULL;
|
||||
+ DB *db = NULL;
|
||||
+ DBC *dbc = NULL;
|
||||
+ DBT key;
|
||||
+ DBT data;
|
||||
+ dbi_db_t *db = NULL;
|
||||
+ dbi_cursor_t dbc = {0};
|
||||
+ dbi_val_t key = {0};
|
||||
+ dbi_val_t data = {0};
|
||||
+ int ret = 0;
|
||||
+ PRBool config_has_int_order = PR_FALSE;
|
||||
+ PRBool disk_has_int_order = PR_TRUE; /* Assume integer order until proven otherwise */
|
||||
@ -147,7 +147,7 @@ index 6098e04fc..388dd6efb 100644
|
||||
+ }
|
||||
+
|
||||
+ /* Create a cursor to read keys */
|
||||
+ ret = db->cursor(db, NULL, &dbc, 0);
|
||||
+ ret = dblayer_new_cursor(be, db, NULL, &dbc);
|
||||
+ if (ret != 0) {
|
||||
+ slapi_log_err(SLAPI_LOG_ERR, "ldbm_instance_check_index_config",
|
||||
+ "Backend '%s': could not create cursor on %s index (ret=%d)\n",
|
||||
@ -156,10 +156,8 @@ index 6098e04fc..388dd6efb 100644
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ memset(&key, 0, sizeof(key));
|
||||
+ memset(&data, 0, sizeof(data));
|
||||
+ key.flags = DB_DBT_MALLOC;
|
||||
+ data.flags = DB_DBT_MALLOC;
|
||||
+ dblayer_value_init(be, &key);
|
||||
+ dblayer_value_init(be, &data);
|
||||
+
|
||||
+ /*
|
||||
+ * Read up to 100 unique keys and check their ordering.
|
||||
@ -173,12 +171,7 @@ index 6098e04fc..388dd6efb 100644
|
||||
+ while (key_count < 100) {
|
||||
+ ID current_id;
|
||||
+
|
||||
+ slapi_ch_free(&(key.data));
|
||||
+ slapi_ch_free(&(data.data));
|
||||
+ key.size = 0;
|
||||
+ data.size = 0;
|
||||
+
|
||||
+ ret = dbc->c_get(dbc, &key, &data, first_key ? DB_FIRST : DB_NEXT_NODUP);
|
||||
+ ret = dblayer_cursor_op(&dbc, first_key ? DBI_OP_MOVE_TO_FIRST : DBI_OP_NEXT_KEY, &key, &data);
|
||||
+ first_key = PR_FALSE; /* Always advance cursor on next iteration */
|
||||
+ if (ret != 0) {
|
||||
+ break; /* No more keys or error */
|
||||
@ -229,9 +222,9 @@ index 6098e04fc..388dd6efb 100644
|
||||
+ }
|
||||
+
|
||||
+ /* Close the cursor and free values */
|
||||
+ slapi_ch_free(&(key.data));
|
||||
+ slapi_ch_free(&(data.data));
|
||||
+ dbc->c_close(dbc);
|
||||
+ dblayer_cursor_op(&dbc, DBI_OP_CLOSE, NULL, NULL);
|
||||
+ dblayer_value_free(be, &key);
|
||||
+ dblayer_value_free(be, &data);
|
||||
+
|
||||
+ /* Release the index file */
|
||||
+ dblayer_release_index_file(be, ai, db);
|
||||
@ -293,7 +286,7 @@ index 6098e04fc..388dd6efb 100644
|
||||
/* Starts a backend instance */
|
||||
int
|
||||
ldbm_instance_start(backend *be)
|
||||
@@ -316,6 +583,8 @@ ldbm_instance_startall(struct ldbminfo *li)
|
||||
@@ -319,6 +579,8 @@ ldbm_instance_startall(struct ldbminfo *li)
|
||||
ldbm_instance_register_modify_callback(inst);
|
||||
vlv_init(inst);
|
||||
slapi_mtn_be_started(inst->inst_be);
|
||||
@ -1,32 +0,0 @@
|
||||
From ab06b3cebbe0287ef557c0307ca2ee86fe8cb761 Mon Sep 17 00:00:00 2001
|
||||
From: progier389 <progier@redhat.com>
|
||||
Date: Thu, 21 Nov 2024 16:26:02 +0100
|
||||
Subject: [PATCH] Issue 6224 - Fix merge issue in 389-ds-base-2.1 for
|
||||
ds_log_test.py (#6414)
|
||||
|
||||
Fix a merge issue during cherry-pick over 389-ds-base-2.1 and 389-ds-base-1.4.3 branches
|
||||
|
||||
Issue: #6224
|
||||
|
||||
Reviewed by: @mreynolds389
|
||||
|
||||
(cherry picked from commit 2b541c64b8317209e4dafa4f82918d714039907c)
|
||||
---
|
||||
dirsrvtests/tests/suites/ds_logs/ds_logs_test.py | 1 -
|
||||
1 file changed, 1 deletion(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/ds_logs/ds_logs_test.py b/dirsrvtests/tests/suites/ds_logs/ds_logs_test.py
|
||||
index 84a9c6ec8..812936c62 100644
|
||||
--- a/dirsrvtests/tests/suites/ds_logs/ds_logs_test.py
|
||||
+++ b/dirsrvtests/tests/suites/ds_logs/ds_logs_test.py
|
||||
@@ -1222,7 +1222,6 @@ def test_referral_check(topology_st, request):
|
||||
|
||||
request.addfinalizer(fin)
|
||||
|
||||
-<<<<<<< HEAD
|
||||
def test_referral_subsuffix(topology_st, request):
|
||||
"""Test the results of an inverted parent suffix definition in the configuration.
|
||||
|
||||
--
|
||||
2.48.0
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
From 99470a4befbea7eda777b9096337e29113e1ddda Mon Sep 17 00:00:00 2001
|
||||
From 04eca1fe36480561bc2f59440d971e000133d213 Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Thu, 5 Feb 2026 12:17:06 +0100
|
||||
Subject: [PATCH] Issue 7223 - Add dsctl index-check command for offline index
|
||||
@ -15,19 +15,19 @@ upgrade.
|
||||
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/7223
|
||||
|
||||
Reviewed by: @progier389, @tbordaz (Thanks!)
|
||||
Reviewed by: @progier389, @tbordaz, @droideck (Thanks!)
|
||||
---
|
||||
.../healthcheck/health_system_indexes_test.py | 593 ++++++++++++++++++
|
||||
rpm/389-ds-base.spec.in | 40 +-
|
||||
rpm/389-ds-base.spec.in | 51 +-
|
||||
src/lib389/lib389/cli_ctl/dbtasks.py | 402 ++++++++++++
|
||||
src/lib389/lib389/dseldif.py | 56 +-
|
||||
4 files changed, 1085 insertions(+), 6 deletions(-)
|
||||
src/lib389/lib389/dseldif.py | 51 +-
|
||||
4 files changed, 1068 insertions(+), 29 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
|
||||
index db5d13bf8..ce86239e5 100644
|
||||
index 4b0c58835..571465562 100644
|
||||
--- a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
|
||||
+++ b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
|
||||
@@ -588,6 +588,599 @@ def test_upgrade_removes_ancestorid_index_config(topology_st):
|
||||
@@ -587,6 +587,599 @@ def test_upgrade_removes_ancestorid_index_config(topology_st):
|
||||
log.info(f"Idempotency verified - ancestorid still absent after second restart (got exception: {e})")
|
||||
|
||||
|
||||
@ -628,47 +628,70 @@ index db5d13bf8..ce86239e5 100644
|
||||
# Run isolated
|
||||
# -s for DEBUG mode
|
||||
diff --git a/rpm/389-ds-base.spec.in b/rpm/389-ds-base.spec.in
|
||||
index b8c14cd14..16b8d14c5 100644
|
||||
index 44a158ce5..0175dfa7c 100644
|
||||
--- a/rpm/389-ds-base.spec.in
|
||||
+++ b/rpm/389-ds-base.spec.in
|
||||
@@ -507,7 +507,45 @@ if ! getent passwd $USERNAME >/dev/null ; then
|
||||
@@ -645,42 +645,45 @@ if ! getent passwd $USERNAME >/dev/null ; then
|
||||
fi
|
||||
|
||||
# Reload our sysctl before we restart (if we can)
|
||||
-sysctl --system &> $output; true
|
||||
+sysctl --system &> "$output"; true
|
||||
+
|
||||
|
||||
-# Gather the running instances so we can restart them
|
||||
+# Gather running instances, stop them, run index-check, then restart
|
||||
+instbase="%{_sysconfdir}/%{pkgname}"
|
||||
instbase="%{_sysconfdir}/%{pkgname}"
|
||||
+instances=""
|
||||
+ninst=0
|
||||
ninst=0
|
||||
-for dir in $instbase/slapd-* ; do
|
||||
- echo dir = $dir >> $output 2>&1 || :
|
||||
+
|
||||
+for dir in "$instbase"/slapd-* ; do
|
||||
+ echo "dir = $dir" >> "$output" 2>&1 || :
|
||||
+ if [ ! -d "$dir" ] ; then continue ; fi
|
||||
+ case "$dir" in *.removed) continue ;; esac
|
||||
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 || :
|
||||
+ basename=$(basename "$dir")
|
||||
+ inst="%{pkgname}@${basename#slapd-}"
|
||||
+ inst_name="${basename#slapd-}"
|
||||
+ echo "found instance $inst - getting status" >> "$output" 2>&1 || :
|
||||
+ if /bin/systemctl -q is-active "$inst" ; then
|
||||
+ echo "instance $inst is running - stopping for upgrade" >> "$output" 2>&1 || :
|
||||
+ instances="$instances $inst"
|
||||
instances="$instances $inst"
|
||||
+ /bin/systemctl stop "$inst" >> "$output" 2>&1 || :
|
||||
+ else
|
||||
else
|
||||
- echo instance $inst is not running >> $output 2>&1 || :
|
||||
+ echo "instance $inst is not running" >> "$output" 2>&1 || :
|
||||
+ fi
|
||||
fi
|
||||
- ninst=`expr $ninst + 1`
|
||||
+ # Run index-check on all instances (running or not)
|
||||
+ # This fixes index ordering mismatches from older versions
|
||||
+ dsctl "$inst_name" index-check --fix >> "$output2" 2>&1 || :
|
||||
+ ninst=$((ninst + 1))
|
||||
+done
|
||||
done
|
||||
+
|
||||
+if [ $ninst -eq 0 ] ; then
|
||||
if [ $ninst -eq 0 ] ; then
|
||||
- echo no instances to upgrade >> $output 2>&1 || :
|
||||
- exit 0 # have no instances to upgrade - just skip the rest
|
||||
-else
|
||||
- # restart running 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
|
||||
- for inst in $instances ; do
|
||||
- echo starting instance $inst >> $output 2>&1 || :
|
||||
- /bin/systemctl start $inst >> $output 2>&1 || :
|
||||
- done
|
||||
+ echo "no instances to upgrade" >> "$output" 2>&1 || :
|
||||
+ exit 0
|
||||
+fi
|
||||
+
|
||||
fi
|
||||
|
||||
+# Restart previously running instances
|
||||
+for inst in $instances ; do
|
||||
+ echo "starting instance $inst" >> "$output" 2>&1 || :
|
||||
@ -1109,19 +1132,14 @@ index 856639672..16da966d1 100644
|
||||
+ help="Fix mismatches by reindexing affected indexes")
|
||||
+ index_check_parser.set_defaults(func=dbtasks_index_check)
|
||||
diff --git a/src/lib389/lib389/dseldif.py b/src/lib389/lib389/dseldif.py
|
||||
index 3104a7b6f..49efc5dcb 100644
|
||||
index d12c6424c..7834d9468 100644
|
||||
--- a/src/lib389/lib389/dseldif.py
|
||||
+++ b/src/lib389/lib389/dseldif.py
|
||||
@@ -118,11 +118,19 @@ class DSEldif(DSLint):
|
||||
with open(self.path, "w") as file_dse:
|
||||
file_dse.write("".join(self._contents))
|
||||
@@ -125,11 +125,14 @@ class DSEldif(DSLint):
|
||||
self._contents[i] = self._contents[i].replace(strfrom, strto)
|
||||
self._update()
|
||||
|
||||
- def _find_attr(self, entry_dn, attr):
|
||||
+ def globalSubstitute(self, strfrom, strto):
|
||||
+ for i in range(0, len(self._contents)-1):
|
||||
+ self._contents[i] = self._contents[i].replace(strfrom, strto)
|
||||
+ self._update()
|
||||
+
|
||||
+ def _find_attr(self, entry_dn, attr, lower=False):
|
||||
"""Find all attribute values and indexes under a given entry
|
||||
|
||||
@ -1133,7 +1151,7 @@ index 3104a7b6f..49efc5dcb 100644
|
||||
"""
|
||||
|
||||
entry_dn_i = self._contents.index("dn: {}\n".format(entry_dn.lower()))
|
||||
@@ -139,7 +147,11 @@ class DSEldif(DSLint):
|
||||
@@ -146,7 +149,11 @@ class DSEldif(DSLint):
|
||||
|
||||
# Find the attribute
|
||||
for line in entry_slice:
|
||||
@ -1146,7 +1164,7 @@ index 3104a7b6f..49efc5dcb 100644
|
||||
attr_value = line.split(" ", 1)[1][:-1]
|
||||
attr_data.update({entry_slice.index(line): attr_value})
|
||||
|
||||
@@ -148,7 +160,7 @@ class DSEldif(DSLint):
|
||||
@@ -155,7 +162,7 @@ class DSEldif(DSLint):
|
||||
|
||||
return entry_dn_i, attr_data
|
||||
|
||||
@ -1155,7 +1173,7 @@ index 3104a7b6f..49efc5dcb 100644
|
||||
"""Return attribute values under a given entry
|
||||
|
||||
:param entry_dn: a DN of entry we want to get attribute from
|
||||
@@ -156,11 +168,13 @@ class DSEldif(DSLint):
|
||||
@@ -163,11 +170,13 @@ class DSEldif(DSLint):
|
||||
:param attr: an attribute name
|
||||
:type attr: str
|
||||
:param single: Return a single value instead of a list
|
||||
@ -1171,7 +1189,7 @@ index 3104a7b6f..49efc5dcb 100644
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
@@ -183,6 +197,38 @@ class DSEldif(DSLint):
|
||||
@@ -190,6 +199,38 @@ class DSEldif(DSLint):
|
||||
|
||||
return indexes
|
||||
|
||||
@ -1,214 +0,0 @@
|
||||
From 3fe2cf7cdedcdf5cafb59867e52a1fbe4a643571 Mon Sep 17 00:00:00 2001
|
||||
From: Masahiro Matsuya <mmatsuya@redhat.com>
|
||||
Date: Fri, 20 Dec 2024 22:37:15 +0900
|
||||
Subject: [PATCH] Issue 6224 - Remove test_referral_subsuffix from
|
||||
ds_logs_test.py (#6456)
|
||||
|
||||
Bug Description:
|
||||
|
||||
test_referral_subsuffix test was removed from main branch and some other
|
||||
ones for higher versions. But, it was not removed from 389-ds-base-1.4.3
|
||||
and 389-ds-base-2.1. The test doesn't work anymore with the fix for
|
||||
Issue 6224, because the added new control limited one backend for internal
|
||||
search. The test should be removed.
|
||||
|
||||
Fix Description:
|
||||
|
||||
remove the test from ds_logs_test.py
|
||||
|
||||
relates: https://github.com/389ds/389-ds-base/issues/6224
|
||||
---
|
||||
.../tests/suites/ds_logs/ds_logs_test.py | 177 ------------------
|
||||
1 file changed, 177 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/ds_logs/ds_logs_test.py b/dirsrvtests/tests/suites/ds_logs/ds_logs_test.py
|
||||
index 812936c62..84d721756 100644
|
||||
--- a/dirsrvtests/tests/suites/ds_logs/ds_logs_test.py
|
||||
+++ b/dirsrvtests/tests/suites/ds_logs/ds_logs_test.py
|
||||
@@ -1222,183 +1222,6 @@ def test_referral_check(topology_st, request):
|
||||
|
||||
request.addfinalizer(fin)
|
||||
|
||||
-def test_referral_subsuffix(topology_st, request):
|
||||
- """Test the results of an inverted parent suffix definition in the configuration.
|
||||
-
|
||||
- For more details see:
|
||||
- https://www.port389.org/docs/389ds/design/mapping_tree_assembly.html
|
||||
-
|
||||
- :id: 4faf210a-4fde-4e4f-8834-865bdc8f4d37
|
||||
- :setup: Standalone instance
|
||||
- :steps:
|
||||
- 1. First create two Backends, without mapping trees.
|
||||
- 2. create the mapping trees for these backends
|
||||
- 3. reduce nsslapd-referral-check-period to accelerate test
|
||||
- 4. Remove error log file
|
||||
- 5. Create a referral entry on parent suffix
|
||||
- 6. Check that the server detected the referral
|
||||
- 7. Delete the referral entry
|
||||
- 8. Check that the server detected the deletion of the referral
|
||||
- 9. Remove error log file
|
||||
- 10. Create a referral entry on child suffix
|
||||
- 11. Check that the server detected the referral on both parent and child suffixes
|
||||
- 12. Delete the referral entry
|
||||
- 13. Check that the server detected the deletion of the referral on both parent and child suffixes
|
||||
- 14. Remove error log file
|
||||
- 15. Create a referral entry on parent suffix
|
||||
- 16. Check that the server detected the referral on both parent and child suffixes
|
||||
- 17. Delete the child referral entry
|
||||
- 18. Check that the server detected the deletion of the referral on child suffix but not on parent suffix
|
||||
- 19. Delete the parent referral entry
|
||||
- 20. Check that the server detected the deletion of the referral parent suffix
|
||||
-
|
||||
- :expectedresults:
|
||||
- all steps succeeds
|
||||
- """
|
||||
- inst = topology_st.standalone
|
||||
- # Step 1 First create two Backends, without mapping trees.
|
||||
- PARENT_SUFFIX='dc=parent,dc=com'
|
||||
- CHILD_SUFFIX='dc=child,%s' % PARENT_SUFFIX
|
||||
- be1 = create_backend(inst, 'Parent', PARENT_SUFFIX)
|
||||
- be2 = create_backend(inst, 'Child', CHILD_SUFFIX)
|
||||
- # Step 2 create the mapping trees for these backends
|
||||
- mts = MappingTrees(inst)
|
||||
- mt1 = mts.create(properties={
|
||||
- 'cn': PARENT_SUFFIX,
|
||||
- 'nsslapd-state': 'backend',
|
||||
- 'nsslapd-backend': 'Parent',
|
||||
- })
|
||||
- mt2 = mts.create(properties={
|
||||
- 'cn': CHILD_SUFFIX,
|
||||
- 'nsslapd-state': 'backend',
|
||||
- 'nsslapd-backend': 'Child',
|
||||
- 'nsslapd-parent-suffix': PARENT_SUFFIX,
|
||||
- })
|
||||
-
|
||||
- dc_ex = Domain(inst, dn=PARENT_SUFFIX)
|
||||
- assert dc_ex.exists()
|
||||
-
|
||||
- dc_st = Domain(inst, dn=CHILD_SUFFIX)
|
||||
- assert dc_st.exists()
|
||||
-
|
||||
- # Step 3 reduce nsslapd-referral-check-period to accelerate test
|
||||
- # requires a restart done on step 4
|
||||
- REFERRAL_CHECK=7
|
||||
- topology_st.standalone.config.set("nsslapd-referral-check-period", str(REFERRAL_CHECK))
|
||||
-
|
||||
- # Check that if we create a referral at parent level
|
||||
- # - referral is detected at parent backend
|
||||
- # - referral is not detected at child backend
|
||||
-
|
||||
- # Step 3 Remove error log file
|
||||
- topology_st.standalone.stop()
|
||||
- lpath = topology_st.standalone.ds_error_log._get_log_path()
|
||||
- os.unlink(lpath)
|
||||
- topology_st.standalone.start()
|
||||
-
|
||||
- # Step 4 Create a referral entry on parent suffix
|
||||
- rs_parent = Referrals(topology_st.standalone, PARENT_SUFFIX)
|
||||
-
|
||||
- referral_entry_parent = rs_parent.create(properties={
|
||||
- 'cn': 'testref',
|
||||
- 'ref': 'ldap://localhost:38901/ou=People,dc=example,dc=com'
|
||||
- })
|
||||
-
|
||||
- # Step 5 Check that the server detected the referral
|
||||
- time.sleep(REFERRAL_CHECK + 1)
|
||||
- assert topology_st.standalone.ds_error_log.match('.*slapd_daemon - New referral entries are detected under %s.*' % PARENT_SUFFIX)
|
||||
- assert not topology_st.standalone.ds_error_log.match('.*slapd_daemon - New referral entries are detected under %s.*' % CHILD_SUFFIX)
|
||||
- assert not topology_st.standalone.ds_error_log.match('.*slapd_daemon - No more referral entry under %s' % PARENT_SUFFIX)
|
||||
-
|
||||
- # Step 6 Delete the referral entry
|
||||
- referral_entry_parent.delete()
|
||||
-
|
||||
- # Step 7 Check that the server detected the deletion of the referral
|
||||
- time.sleep(REFERRAL_CHECK + 1)
|
||||
- assert topology_st.standalone.ds_error_log.match('.*slapd_daemon - No more referral entry under %s' % PARENT_SUFFIX)
|
||||
-
|
||||
- # Check that if we create a referral at child level
|
||||
- # - referral is detected at parent backend
|
||||
- # - referral is detected at child backend
|
||||
-
|
||||
- # Step 8 Remove error log file
|
||||
- topology_st.standalone.stop()
|
||||
- lpath = topology_st.standalone.ds_error_log._get_log_path()
|
||||
- os.unlink(lpath)
|
||||
- topology_st.standalone.start()
|
||||
-
|
||||
- # Step 9 Create a referral entry on child suffix
|
||||
- rs_child = Referrals(topology_st.standalone, CHILD_SUFFIX)
|
||||
- referral_entry_child = rs_child.create(properties={
|
||||
- 'cn': 'testref',
|
||||
- 'ref': 'ldap://localhost:38901/ou=People,dc=example,dc=com'
|
||||
- })
|
||||
-
|
||||
- # Step 10 Check that the server detected the referral on both parent and child suffixes
|
||||
- time.sleep(REFERRAL_CHECK + 1)
|
||||
- assert topology_st.standalone.ds_error_log.match('.*slapd_daemon - New referral entries are detected under %s.*' % PARENT_SUFFIX)
|
||||
- assert topology_st.standalone.ds_error_log.match('.*slapd_daemon - New referral entries are detected under %s.*' % CHILD_SUFFIX)
|
||||
- assert not topology_st.standalone.ds_error_log.match('.*slapd_daemon - No more referral entry under %s' % CHILD_SUFFIX)
|
||||
-
|
||||
- # Step 11 Delete the referral entry
|
||||
- referral_entry_child.delete()
|
||||
-
|
||||
- # Step 12 Check that the server detected the deletion of the referral on both parent and child suffixes
|
||||
- time.sleep(REFERRAL_CHECK + 1)
|
||||
- assert topology_st.standalone.ds_error_log.match('.*slapd_daemon - No more referral entry under %s' % PARENT_SUFFIX)
|
||||
- assert topology_st.standalone.ds_error_log.match('.*slapd_daemon - No more referral entry under %s' % CHILD_SUFFIX)
|
||||
-
|
||||
- # Check that if we create a referral at child level and parent level
|
||||
- # - referral is detected at parent backend
|
||||
- # - referral is detected at child backend
|
||||
-
|
||||
- # Step 13 Remove error log file
|
||||
- topology_st.standalone.stop()
|
||||
- lpath = topology_st.standalone.ds_error_log._get_log_path()
|
||||
- os.unlink(lpath)
|
||||
- topology_st.standalone.start()
|
||||
-
|
||||
- # Step 14 Create a referral entry on parent suffix
|
||||
- # Create a referral entry on child suffix
|
||||
- referral_entry_parent = rs_parent.create(properties={
|
||||
- 'cn': 'testref',
|
||||
- 'ref': 'ldap://localhost:38901/ou=People,dc=example,dc=com'
|
||||
- })
|
||||
- referral_entry_child = rs_child.create(properties={
|
||||
- 'cn': 'testref',
|
||||
- 'ref': 'ldap://localhost:38901/ou=People,dc=example,dc=com'
|
||||
- })
|
||||
-
|
||||
- # Step 15 Check that the server detected the referral on both parent and child suffixes
|
||||
- time.sleep(REFERRAL_CHECK + 1)
|
||||
- assert topology_st.standalone.ds_error_log.match('.*slapd_daemon - New referral entries are detected under %s.*' % PARENT_SUFFIX)
|
||||
- assert topology_st.standalone.ds_error_log.match('.*slapd_daemon - New referral entries are detected under %s.*' % CHILD_SUFFIX)
|
||||
- assert not topology_st.standalone.ds_error_log.match('.*slapd_daemon - No more referral entry under %s' % CHILD_SUFFIX)
|
||||
-
|
||||
- # Step 16 Delete the child referral entry
|
||||
- referral_entry_child.delete()
|
||||
-
|
||||
- # Step 17 Check that the server detected the deletion of the referral on child suffix but not on parent suffix
|
||||
- time.sleep(REFERRAL_CHECK + 1)
|
||||
- assert topology_st.standalone.ds_error_log.match('.*slapd_daemon - No more referral entry under %s' % CHILD_SUFFIX)
|
||||
- assert not topology_st.standalone.ds_error_log.match('.*slapd_daemon - No more referral entry under %s' % PARENT_SUFFIX)
|
||||
-
|
||||
- # Step 18 Delete the parent referral entry
|
||||
- referral_entry_parent.delete()
|
||||
-
|
||||
- # Step 19 Check that the server detected the deletion of the referral parent suffix
|
||||
- time.sleep(REFERRAL_CHECK + 1)
|
||||
- assert topology_st.standalone.ds_error_log.match('.*slapd_daemon - No more referral entry under %s' % PARENT_SUFFIX)
|
||||
-
|
||||
- def fin():
|
||||
- log.info('Deleting referral')
|
||||
- try:
|
||||
- referral_entry_parent.delete()
|
||||
- referral.entry_child.delete()
|
||||
- except:
|
||||
- pass
|
||||
-
|
||||
- request.addfinalizer(fin)
|
||||
|
||||
def test_missing_backend_suffix(topology_st, request):
|
||||
"""Test that the server does not crash if a backend has no suffix
|
||||
--
|
||||
2.48.0
|
||||
|
||||
@ -0,0 +1,135 @@
|
||||
From c1f0756b453b7ea219add236f60a72b3fc660af0 Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Tue, 27 Jan 2026 14:26:29 +0100
|
||||
Subject: [PATCH] Issue 7096 - (2nd) During replication online total init the
|
||||
function idl_id_is_in_idlist is not scaling with large database (#7205)
|
||||
|
||||
Bug Description:
|
||||
The fix for #7096 optimized the BDB backend's `idl_new_range_fetch()`
|
||||
function to use ID ranges instead of checking the full ID list during
|
||||
online total initialization. However, the LMDB backend's
|
||||
`idl_lmdb_range_fetch()` function and its callback
|
||||
`idl_range_add_id_cb()` were not updated and still use the non-scaling
|
||||
`idl_id_is_in_idlist()` function.
|
||||
|
||||
Fix Description:
|
||||
Apply the same optimization to the LMDB backend.
|
||||
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/7096
|
||||
|
||||
Reviewed by: @tbordaz, @droideck (Thanks!)
|
||||
---
|
||||
ldap/servers/slapd/back-ldbm/idl_new.c | 39 ++++++++++----------------
|
||||
1 file changed, 15 insertions(+), 24 deletions(-)
|
||||
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/idl_new.c b/ldap/servers/slapd/back-ldbm/idl_new.c
|
||||
index 2d978353f..613d53815 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/idl_new.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/idl_new.c
|
||||
@@ -66,6 +66,7 @@ typedef struct {
|
||||
size_t leftoverlen;
|
||||
size_t leftovercnt;
|
||||
IDList *idl;
|
||||
+ IdRange_t *idrange_list;
|
||||
int flag_err;
|
||||
ID lastid;
|
||||
ID suffix;
|
||||
@@ -700,9 +701,9 @@ error:
|
||||
}
|
||||
}
|
||||
}
|
||||
- slapi_ch_free((void **)&leftover);
|
||||
- idrange_free(&idrange_list);
|
||||
}
|
||||
+ slapi_ch_free((void **)&leftover);
|
||||
+ idrange_free(&idrange_list);
|
||||
slapi_log_err(SLAPI_LOG_FILTER, "idl_new_range_fetch",
|
||||
"Found %d candidates; error code is: %d\n",
|
||||
idl ? idl->b_nids : 0, *flag_err);
|
||||
@@ -716,7 +717,6 @@ static int
|
||||
idl_range_add_id_cb(dbi_val_t *key, dbi_val_t *data, void *ctx)
|
||||
{
|
||||
idl_range_ctx_t *rctx = ctx;
|
||||
- int idl_rc = 0;
|
||||
ID id = 0;
|
||||
|
||||
if (key->data == NULL) {
|
||||
@@ -779,10 +779,12 @@ idl_range_add_id_cb(dbi_val_t *key, dbi_val_t *data, void *ctx)
|
||||
* found entry is the one from the suffix
|
||||
*/
|
||||
rctx->suffix = keyval;
|
||||
- idl_rc = idl_append_extend(&rctx->idl, id);
|
||||
- } else if ((keyval == rctx->suffix) || idl_id_is_in_idlist(rctx->idl, keyval)) {
|
||||
+ idl_append_extend(&rctx->idl, id);
|
||||
+ idrange_add_id(&rctx->idrange_list, id);
|
||||
+ } else if ((keyval == rctx->suffix) || idl_id_is_in_idlist_ranges(rctx->idl, rctx->idrange_list, keyval)) {
|
||||
/* the parent is the suffix or already in idl. */
|
||||
- idl_rc = idl_append_extend(&rctx->idl, id);
|
||||
+ idl_append_extend(&rctx->idl, id);
|
||||
+ idrange_add_id(&rctx->idrange_list, id);
|
||||
} else {
|
||||
/* Otherwise, keep the {keyval,id} in leftover array */
|
||||
if (!rctx->leftover) {
|
||||
@@ -797,14 +799,7 @@ idl_range_add_id_cb(dbi_val_t *key, dbi_val_t *data, void *ctx)
|
||||
rctx->leftovercnt++;
|
||||
}
|
||||
} else {
|
||||
- idl_rc = idl_append_extend(&rctx->idl, id);
|
||||
- }
|
||||
- if (idl_rc) {
|
||||
- slapi_log_err(SLAPI_LOG_ERR, "idl_lmdb_range_fetch",
|
||||
- "Unable to extend id list (err=%d)\n", idl_rc);
|
||||
- idl_free(&rctx->idl);
|
||||
- rctx->flag_err = LDAP_UNWILLING_TO_PERFORM;
|
||||
- return DBI_RC_NOTFOUND;
|
||||
+ idl_append_extend(&rctx->idl, id);
|
||||
}
|
||||
#if defined(DB_ALLIDS_ON_READ)
|
||||
/* enforce the allids read limit */
|
||||
@@ -841,7 +836,6 @@ idl_lmdb_range_fetch(
|
||||
{
|
||||
int ret = 0;
|
||||
int ret2 = 0;
|
||||
- int idl_rc = 0;
|
||||
dbi_cursor_t cursor = {0};
|
||||
back_txn s_txn;
|
||||
struct ldbminfo *li = (struct ldbminfo *)be->be_database->plg_private;
|
||||
@@ -891,6 +885,7 @@ idl_lmdb_range_fetch(
|
||||
idl_range_ctx.lastid = 0;
|
||||
idl_range_ctx.count = 0;
|
||||
idl_range_ctx.index_id = index_id;
|
||||
+ idl_range_ctx.idrange_list = NULL;
|
||||
if (operator & SLAPI_OP_RANGE_NO_IDL_SORT) {
|
||||
struct _back_info_index_key bck_info;
|
||||
/* We are doing a bulk import
|
||||
@@ -966,22 +961,18 @@ error:
|
||||
while(remaining > 0) {
|
||||
for (size_t i = 0; i < idl_range_ctx.leftovercnt; i++) {
|
||||
if (idl_range_ctx.leftover[i].key > 0 &&
|
||||
- idl_id_is_in_idlist(idl_range_ctx.idl, idl_range_ctx.leftover[i].key) != 0) {
|
||||
+ idl_id_is_in_idlist_ranges(idl_range_ctx.idl, idl_range_ctx.idrange_list, idl_range_ctx.leftover[i].key) != 0) {
|
||||
/* if the leftover key has its parent in the idl */
|
||||
- idl_rc = idl_append_extend(&idl_range_ctx.idl, idl_range_ctx.leftover[i].id);
|
||||
- if (idl_rc) {
|
||||
- slapi_log_err(SLAPI_LOG_ERR, "idl_lmdb_range_fetch",
|
||||
- "Unable to extend id list (err=%d)\n", idl_rc);
|
||||
- idl_free(&idl_range_ctx.idl);
|
||||
- break;
|
||||
- }
|
||||
+ idl_append_extend(&idl_range_ctx.idl, idl_range_ctx.leftover[i].id);
|
||||
+ idrange_add_id(&idl_range_ctx.idrange_list, idl_range_ctx.leftover[i].id);
|
||||
idl_range_ctx.leftover[i].key = 0;
|
||||
remaining--;
|
||||
}
|
||||
}
|
||||
}
|
||||
- slapi_ch_free((void **)&idl_range_ctx.leftover);
|
||||
}
|
||||
+ slapi_ch_free((void **)&idl_range_ctx.leftover);
|
||||
+ idrange_free(&idl_range_ctx.idrange_list);
|
||||
*flag_err = idl_range_ctx.flag_err;
|
||||
slapi_log_err(SLAPI_LOG_FILTER, "idl_lmdb_range_fetch",
|
||||
"Found %d candidates; error code is: %d\n",
|
||||
--
|
||||
2.52.0
|
||||
|
||||
@ -1,90 +0,0 @@
|
||||
From 4121ffe7a44fbacf513758661e71e483eb11ee3c Mon Sep 17 00:00:00 2001
|
||||
From: tbordaz <tbordaz@redhat.com>
|
||||
Date: Mon, 6 Jan 2025 14:00:39 +0100
|
||||
Subject: [PATCH] Issue 6417 - (2nd) If an entry RDN is identical to the
|
||||
suffix, then Entryrdn gets broken during a reindex (#6460)
|
||||
|
||||
Bug description:
|
||||
The primary fix has a flaw as it assumes that the
|
||||
suffix ID is '1'.
|
||||
If the RUV entry is the first entry of the database
|
||||
the server loops indefinitely
|
||||
|
||||
Fix description:
|
||||
Read the suffix ID from the entryrdn index
|
||||
|
||||
fixes: #6417
|
||||
|
||||
Reviewed by: Pierre Rogier (also reviewed the first fix)
|
||||
---
|
||||
.../suites/replication/regression_m2_test.py | 9 +++++++++
|
||||
ldap/servers/slapd/back-ldbm/ldbm_entryrdn.c | 19 ++++++++++++++++++-
|
||||
2 files changed, 27 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/replication/regression_m2_test.py b/dirsrvtests/tests/suites/replication/regression_m2_test.py
|
||||
index abac46ada..72d4b9f89 100644
|
||||
--- a/dirsrvtests/tests/suites/replication/regression_m2_test.py
|
||||
+++ b/dirsrvtests/tests/suites/replication/regression_m2_test.py
|
||||
@@ -1010,6 +1010,15 @@ def test_online_reinit_may_hang(topo_with_sigkill):
|
||||
"""
|
||||
M1 = topo_with_sigkill.ms["supplier1"]
|
||||
M2 = topo_with_sigkill.ms["supplier2"]
|
||||
+
|
||||
+ # The RFE 5367 (when enabled) retrieves the DN
|
||||
+ # from the dncache. This hides an issue
|
||||
+ # with primary fix for 6417.
|
||||
+ # We need to disable the RFE to verify that the primary
|
||||
+ # fix is properly fixed.
|
||||
+ if ds_is_newer('2.3.1'):
|
||||
+ M1.config.replace('nsslapd-return-original-entrydn', 'off')
|
||||
+
|
||||
M1.stop()
|
||||
ldif_file = '%s/supplier1.ldif' % M1.get_ldif_dir()
|
||||
M1.db2ldif(bename=DEFAULT_BENAME, suffixes=[DEFAULT_SUFFIX],
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_entryrdn.c b/ldap/servers/slapd/back-ldbm/ldbm_entryrdn.c
|
||||
index 83b041192..1bbb6252a 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/ldbm_entryrdn.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/ldbm_entryrdn.c
|
||||
@@ -1115,6 +1115,7 @@ entryrdn_lookup_dn(backend *be,
|
||||
rdn_elem *elem = NULL;
|
||||
int maybesuffix = 0;
|
||||
int db_retry = 0;
|
||||
+ ID suffix_id = 1;
|
||||
|
||||
slapi_log_err(SLAPI_LOG_TRACE, "entryrdn_lookup_dn",
|
||||
"--> entryrdn_lookup_dn\n");
|
||||
@@ -1175,6 +1176,22 @@ entryrdn_lookup_dn(backend *be,
|
||||
/* Setting the bulk fetch buffer */
|
||||
data.flags = DB_DBT_MALLOC;
|
||||
|
||||
+ /* Just in case the suffix ID is not '1' retrieve it from the database */
|
||||
+ keybuf = slapi_ch_strdup(slapi_sdn_get_ndn(be->be_suffix));
|
||||
+ dblayer_value_set(be, &key, keybuf, strlen(keybuf) + 1);
|
||||
+ rc = dblayer_cursor_op(&ctx.cursor, DBI_OP_MOVE_TO_KEY, &key, &data);
|
||||
+ if (rc) {
|
||||
+ slapi_log_err(SLAPI_LOG_WARNING, "entryrdn_lookup_dn",
|
||||
+ "Fails to retrieve the ID of suffix %s - keep the default value '%d'\n",
|
||||
+ slapi_sdn_get_ndn(be->be_suffix),
|
||||
+ suffix_id);
|
||||
+ } else {
|
||||
+ elem = (rdn_elem *)data.data;
|
||||
+ suffix_id = id_stored_to_internal(elem->rdn_elem_id);
|
||||
+ }
|
||||
+ dblayer_value_free(be, &data);
|
||||
+ dblayer_value_free(be, &key);
|
||||
+
|
||||
do {
|
||||
/* Setting up a key for the node to get its parent */
|
||||
slapi_ch_free_string(&keybuf);
|
||||
@@ -1224,7 +1241,7 @@ entryrdn_lookup_dn(backend *be,
|
||||
}
|
||||
goto bail;
|
||||
}
|
||||
- if (workid == 1) {
|
||||
+ if (workid == suffix_id) {
|
||||
/* The loop (workid) iterates from the starting 'id'
|
||||
* up to the suffix ID (i.e. '1').
|
||||
* A corner case (#6417) is if an entry, on the path
|
||||
--
|
||||
2.48.0
|
||||
|
||||
@ -0,0 +1,877 @@
|
||||
From 35237d57daf6fdf20a42c3cef27130ba70f0cdc2 Mon Sep 17 00:00:00 2001
|
||||
From: Akshay Adhikari <aadhikar@redhat.com>
|
||||
Date: Tue, 18 Nov 2025 21:57:10 +0530
|
||||
Subject: [PATCH] Issue 7076, 6992, 6784, 6214 - Fix CI test failures (#7077)
|
||||
|
||||
- Fixed import test bugs in regression_test.py (cleanup handler, LDIF permissions) -
|
||||
https://github.com/389ds/389-ds-base/issues/6992
|
||||
- Fixed ModRDN cache corruption on failed operations (parent update check, cache cleanup)
|
||||
- Fixed attribute uniqueness test fixture cleanup in attruniq_test.py
|
||||
- mproved test stability by fixing race conditions in replication, healthcheck,
|
||||
web UI, memberOf, and basic tests.
|
||||
- Fixed entrycache_eviction_test.py to track incremental log counts instead of cumulative -
|
||||
https://github.com/389ds/389-ds-base/issues/6784
|
||||
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/7076
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/6992
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/6784
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/6214
|
||||
|
||||
Reviewed by: @vashirov, @progier389 (Thanks!)
|
||||
---
|
||||
dirsrvtests/tests/suites/basic/basic_test.py | 4 +-
|
||||
.../healthcheck/health_system_indexes_test.py | 3 +
|
||||
.../tests/suites/import/regression_test.py | 388 +++++++++++++++++-
|
||||
.../suites/memberof_plugin/fixup_test.py | 8 +-
|
||||
.../tests/suites/plugins/attruniq_test.py | 84 ++--
|
||||
.../suites/replication/regression_m2_test.py | 4 +
|
||||
.../replication/repl_log_monitoring_test.py | 8 +-
|
||||
.../suites/webui/database/database_test.py | 1 +
|
||||
ldap/servers/slapd/back-ldbm/ldbm_modrdn.c | 44 +-
|
||||
9 files changed, 481 insertions(+), 63 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/basic/basic_test.py b/dirsrvtests/tests/suites/basic/basic_test.py
|
||||
index be825efe9..e9b611439 100644
|
||||
--- a/dirsrvtests/tests/suites/basic/basic_test.py
|
||||
+++ b/dirsrvtests/tests/suites/basic/basic_test.py
|
||||
@@ -593,7 +593,7 @@ def test_basic_import_export(topology_st, import_example_ldif):
|
||||
#
|
||||
# Test online/offline LDIF imports
|
||||
#
|
||||
- topology_st.standalone.start()
|
||||
+ topology_st.standalone.restart()
|
||||
# topology_st.standalone.config.set('nsslapd-errorlog-level', '1')
|
||||
|
||||
# Generate a test ldif (50k entries)
|
||||
@@ -691,6 +691,8 @@ def test_basic_backup(topology_st, import_example_ldif):
|
||||
|
||||
log.info('Running test_basic_backup...')
|
||||
|
||||
+ topology_st.standalone.restart()
|
||||
+
|
||||
backup_dir = topology_st.standalone.get_bak_dir() + '/backup_test_online'
|
||||
log.info(f'Backup directory is {backup_dir}')
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
|
||||
index 571465562..4cd4d0c70 100644
|
||||
--- a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
|
||||
+++ b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
|
||||
@@ -172,6 +172,7 @@ def test_missing_parentid(topology_st, log_buffering_enabled):
|
||||
log.info("Re-add the parentId index")
|
||||
backend = Backends(standalone).get("userRoot")
|
||||
backend.add_index("parentid", ["eq"], matching_rules=["integerOrderingMatch"])
|
||||
+ standalone.restart()
|
||||
|
||||
run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT)
|
||||
run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
|
||||
@@ -215,6 +216,7 @@ def test_missing_matching_rule(topology_st, log_buffering_enabled):
|
||||
log.info("Re-add the integerOrderingMatch matching rule")
|
||||
parentid_index = Index(standalone, PARENTID_DN)
|
||||
parentid_index.add("nsMatchingRule", "integerOrderingMatch")
|
||||
+ standalone.restart()
|
||||
|
||||
run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT)
|
||||
run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
|
||||
@@ -445,6 +447,7 @@ def test_multiple_missing_indexes(topology_st, log_buffering_enabled):
|
||||
backend = Backends(standalone).get("userRoot")
|
||||
backend.add_index("parentid", ["eq"], matching_rules=["integerOrderingMatch"])
|
||||
backend.add_index("nsuniqueid", ["eq"])
|
||||
+ standalone.restart()
|
||||
|
||||
run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT)
|
||||
run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
|
||||
diff --git a/dirsrvtests/tests/suites/import/regression_test.py b/dirsrvtests/tests/suites/import/regression_test.py
|
||||
index 18611de35..bdbb516e8 100644
|
||||
--- a/dirsrvtests/tests/suites/import/regression_test.py
|
||||
+++ b/dirsrvtests/tests/suites/import/regression_test.py
|
||||
@@ -5,6 +5,7 @@
|
||||
# See LICENSE for details.
|
||||
# --- END COPYRIGHT BLOCK ---
|
||||
#
|
||||
+from abc import ABC, abstractmethod
|
||||
from decimal import *
|
||||
import ldap
|
||||
import logging
|
||||
@@ -16,9 +17,9 @@ from lib389.backend import Backends
|
||||
from lib389.properties import TASK_WAIT
|
||||
from lib389.topologies import topology_st as topo
|
||||
from lib389.dbgen import dbgen_users
|
||||
-from lib389._constants import DEFAULT_SUFFIX
|
||||
+from lib389._constants import DEFAULT_SUFFIX, DEFAULT_BENAME
|
||||
from lib389.tasks import *
|
||||
-from lib389.idm.user import UserAccounts
|
||||
+from lib389.idm.user import UserAccounts, UserAccount
|
||||
from lib389.idm.directorymanager import DirectoryManager
|
||||
from lib389.dbgen import *
|
||||
from lib389.utils import *
|
||||
@@ -92,7 +93,203 @@ class AddDelUsers(threading.Thread):
|
||||
return self._ran
|
||||
|
||||
|
||||
-def test_replay_import_operation(topo):
|
||||
+def get_backend_by_name(inst, bename):
|
||||
+ bes = Backends(inst)
|
||||
+ bename = bename.lower()
|
||||
+ be = [ be for be in bes if be.get_attr_val_utf8_l('cn') == bename ]
|
||||
+ return be[0] if len(be) == 1 else None
|
||||
+
|
||||
+
|
||||
+class LogHandler():
|
||||
+ def __init__(self, logfd, patterns):
|
||||
+ self.logfd = logfd
|
||||
+ self.patterns = [ p.lower() for p in patterns ]
|
||||
+ self.pos = logfd.tell()
|
||||
+ self.last_result = None
|
||||
+
|
||||
+ def zero(self):
|
||||
+ return [0 for _ in range(len(self.patterns))]
|
||||
+
|
||||
+ def countCaptures(self):
|
||||
+ res = self.zero()
|
||||
+ self.logfd.seek(self.pos)
|
||||
+ for line in iter(self.logfd.readline, ''):
|
||||
+ # Ignore autotune messages that may confuse the counts
|
||||
+ if 'bdb_start_autotune' in line:
|
||||
+ continue
|
||||
+ # Ignore LMDB size warnings that may confuse the counts
|
||||
+ if 'dbmdb_ctx_t_db_max_size_set' in line:
|
||||
+ continue
|
||||
+ log.info(f'ERROR LOG line is {line.strip()}')
|
||||
+ for idx,pattern in enumerate(self.patterns):
|
||||
+ if pattern in line.lower():
|
||||
+ res[idx] += 1
|
||||
+ self.pos = self.logfd.tell()
|
||||
+ self.last_result = res
|
||||
+ log.info(f'ERROR LOG counts are: {res}')
|
||||
+ return res
|
||||
+
|
||||
+ def seek2end(self):
|
||||
+ self.pos = os.fstat(self.logfd.fileno()).st_size
|
||||
+
|
||||
+ def check(self, idx, val):
|
||||
+ count = self.last_result[idx]
|
||||
+ assert count == val , f"Should have {val} '{self.patterns[idx]}' messages but got: {count} - idx = {idx}"
|
||||
+
|
||||
+
|
||||
+class IEHandler(ABC):
|
||||
+ def __init__(self, inst, errlog, ldifname, bename=DEFAULT_BENAME, suffix=None):
|
||||
+ self.inst = inst
|
||||
+ self.errlog = errlog
|
||||
+ self.ldifname = ldifname
|
||||
+ self.bename = bename
|
||||
+ self.suffix = suffix
|
||||
+ self.ldif = ldifname if ldifname.startswith('/') else f'{inst.get_ldif_dir()}/{ldifname}.ldif'
|
||||
+
|
||||
+ @abstractmethod
|
||||
+ def get_name(self):
|
||||
+ pass
|
||||
+
|
||||
+ @abstractmethod
|
||||
+ def _run_task_b(self):
|
||||
+ pass
|
||||
+
|
||||
+ @abstractmethod
|
||||
+ def _run_task_s(self):
|
||||
+ pass
|
||||
+
|
||||
+ @abstractmethod
|
||||
+ def _run_offline(self):
|
||||
+ pass
|
||||
+
|
||||
+ @abstractmethod
|
||||
+ def _set_log_pattern(self, success):
|
||||
+ pass
|
||||
+
|
||||
+ def run(self, extra_checks, success=True):
|
||||
+ if self.errlog:
|
||||
+ self._set_log_pattern(success)
|
||||
+ self.errlog.seek2end()
|
||||
+
|
||||
+ if self.inst.status():
|
||||
+ if self.bename:
|
||||
+ log.info(f"Performing online {self.get_name()} of backend {self.bename} into LDIF file {self.ldif}")
|
||||
+ r = self._run_task_b()
|
||||
+ else:
|
||||
+ log.info(f"Performing online {self.get_name()} of suffix {self.suffix} into LDIF file {self.ldif}")
|
||||
+ r = self._run_task_s()
|
||||
+ r.wait()
|
||||
+ time.sleep(1)
|
||||
+ else:
|
||||
+ if self.bename:
|
||||
+ log.info(f"Performing offline {self.get_name()} of backend {self.bename} into LDIF file {self.ldif}")
|
||||
+ else:
|
||||
+ log.info(f"Performing offline {self.get_name()} of suffix {self.suffix} into LDIF file {self.ldif}")
|
||||
+ self._run_offline()
|
||||
+ if self.errlog:
|
||||
+ expected_counts = ['*' for _ in range(len(self.errlog.patterns))]
|
||||
+ for (idx, val) in extra_checks:
|
||||
+ expected_counts[idx] = val
|
||||
+ res = self.errlog.countCaptures()
|
||||
+ log.info(f'Expected errorlog counts are: {expected_counts}')
|
||||
+ if success is True or success is False:
|
||||
+ log.info(f'Number of {self.errlog.patterns[0]} in errorlog is: {res[0]}')
|
||||
+ assert res[0] >= 1
|
||||
+ for (idx, val) in extra_checks:
|
||||
+ self.errlog.check(idx, val)
|
||||
+
|
||||
+ def check_db(self):
|
||||
+ assert self.inst.dbscan(bename=self.bename, index='id2entry')
|
||||
+
|
||||
+
|
||||
+class Importer(IEHandler):
|
||||
+ def get_name(self):
|
||||
+ return "import"
|
||||
+
|
||||
+ def _set_log_pattern(self, success):
|
||||
+ if success is True:
|
||||
+ self.errlog.patterns[0] = 'import complete'
|
||||
+ elif success is False:
|
||||
+ self.errlog.patterns[0] = 'import failed'
|
||||
+
|
||||
+ def _run_task_b(self):
|
||||
+ bes = Backends(self.inst)
|
||||
+ r = bes.import_ldif(self.bename, [self.ldif,], include_suffixes=self.suffix)
|
||||
+ return r
|
||||
+
|
||||
+ def _run_task_s(self):
|
||||
+ r = ImportTask(self.inst)
|
||||
+ r.import_suffix_from_ldif(self.ldif, self.suffix)
|
||||
+ return r
|
||||
+
|
||||
+ def _run_offline(self):
|
||||
+ log.info(f'self.inst.ldif2db({self.bename}, {self.suffix}, ...)')
|
||||
+ if self.suffix is None:
|
||||
+ self.inst.ldif2db(self.bename, self.suffix, None, False, self.ldif)
|
||||
+ else:
|
||||
+ self.inst.ldif2db(self.bename, [self.suffix, ], None, False, self.ldif)
|
||||
+
|
||||
+
|
||||
+class Exporter(IEHandler):
|
||||
+ def get_name(self):
|
||||
+ return "export"
|
||||
+
|
||||
+ def _set_log_pattern(self, success):
|
||||
+ if success is True:
|
||||
+ self.errlog.patterns[0] = 'export finished'
|
||||
+ elif success is False:
|
||||
+ self.errlog.patterns[0] = 'export failed'
|
||||
+
|
||||
+ def _run_task_b(self):
|
||||
+ bes = Backends(self.inst)
|
||||
+ r = bes.export_ldif(self.bename, self.ldif, include_suffixes=self.suffix)
|
||||
+ return r
|
||||
+
|
||||
+ def _run_task_s(self):
|
||||
+ r = ExportTask(self.inst)
|
||||
+ r.export_suffix_to_ldif(self.ldif, self.suffix)
|
||||
+ return r
|
||||
+
|
||||
+ def _run_offline(self):
|
||||
+ self.inst.db2ldif(self.bename, self.suffix, None, False, False, self.ldif)
|
||||
+
|
||||
+
|
||||
+def preserve_func(topo, request, restart):
|
||||
+ # Ensure that topology get preserved helper
|
||||
+ inst = topo.standalone
|
||||
+
|
||||
+ def fin():
|
||||
+ if restart:
|
||||
+ inst.restart()
|
||||
+ Importer(inst, None, "save").run(())
|
||||
+
|
||||
+ r = Exporter(inst, None, "save")
|
||||
+ if not os.path.isfile(r.ldif):
|
||||
+ r.run(())
|
||||
+ request.addfinalizer(fin)
|
||||
+
|
||||
+
|
||||
+@pytest.fixture(scope="function")
|
||||
+def preserve(topo, request):
|
||||
+ # Ensure that topology get preserved (no restart)
|
||||
+ preserve_func(topo, request, False)
|
||||
+
|
||||
+
|
||||
+@pytest.fixture(scope="function")
|
||||
+def preserve_r(topo, request):
|
||||
+ # Ensure that topology get preserved (with restart)
|
||||
+ preserve_func(topo, request, True)
|
||||
+
|
||||
+
|
||||
+@pytest.fixture(scope="function")
|
||||
+def verify(topo):
|
||||
+ # Check that backend is not broken
|
||||
+ inst = topo.standalone
|
||||
+ dn=f'uid=demo_user,ou=people,{DEFAULT_SUFFIX}'
|
||||
+ assert UserAccount(inst,dn).exists()
|
||||
+
|
||||
+
|
||||
+def test_replay_import_operation(topo, preserve_r, verify):
|
||||
""" Check after certain failed import operation, is it
|
||||
possible to replay an import operation
|
||||
|
||||
@@ -487,7 +684,190 @@ def test_ldif2db_after_backend_create(topo):
|
||||
import_time_2 = create_backend_and_import(instance, ldif_file_2, 'o=test_2', 'test_2')
|
||||
|
||||
log.info('Import times should be approximately the same')
|
||||
- assert abs(import_time_1 - import_time_2) < 5
|
||||
+ assert abs(import_time_1 - import_time_2) < 15
|
||||
+
|
||||
+
|
||||
+def test_ldif_missing_suffix_entry(topo, request, verify):
|
||||
+ """Test that ldif2db/import aborts if suffix entry is not in the ldif
|
||||
+
|
||||
+ :id: 731bd0d6-8cc8-11f0-8ef2-c85309d5c3e3
|
||||
+ :setup: Standalone Instance
|
||||
+ :steps:
|
||||
+ 1. Prepare final cleanup
|
||||
+ 2. Add a few users
|
||||
+ 3. Export ou=people subtree
|
||||
+ 4. Online import using backend name ou=people subtree
|
||||
+ 5. Online import using suffix name ou=people subtree
|
||||
+ 6. Stop the instance
|
||||
+ 7. Offline import using backend name ou=people subtree
|
||||
+ 8. Offline import using suffix name ou=people subtree
|
||||
+ 9. Generate ldif with a far away suffix
|
||||
+ 10. Offline import using backend name and "far" ldif
|
||||
+ 11. Offline import using suffix name and "far" ldif
|
||||
+ 12. Start the instance
|
||||
+ 13. Online import using backend name and "far" ldif
|
||||
+ 14. Online import using suffix name and "far" ldif
|
||||
+ :expectedresults:
|
||||
+ 1. Operation successful
|
||||
+ 2. Operation successful
|
||||
+ 3. Operation successful
|
||||
+ 4. Import should success, skip all entries, db should exists
|
||||
+ 5. Import should success, skip all entries, db should exists
|
||||
+ 6. Operation successful
|
||||
+ 7. Import should success, skip all entries, db should exists
|
||||
+ 8. Import should success, skip all entries, db should exists
|
||||
+ 9. Operation successful
|
||||
+ 10. Import should success, skip all entries, db should exists
|
||||
+ 11. Import should success, 10 entries skipped, db should exists
|
||||
+ 12. Operation successful
|
||||
+ 13. Import should success, skip all entries, db should exists
|
||||
+ 14. Import should success, 10 entries skipped, db should exists
|
||||
+ """
|
||||
+
|
||||
+ inst = topo.standalone
|
||||
+ inst.config.set('nsslapd-errorlog-level', '266354688')
|
||||
+ no_suffix_on = (
|
||||
+ (1, 0), # no errors are expected.
|
||||
+ (2, 1), # 1 warning is expected.
|
||||
+ (3, 0), # no 'no parent' warning is expected.
|
||||
+ (4, 1), # 1 'all entries were skipped' warning
|
||||
+ (5, 0), # no 'returning task warning' info message
|
||||
+ )
|
||||
+ no_suffix_off = (
|
||||
+ (1, 0), # no errors are expected.
|
||||
+ (2, 1), # 1 warning is expected.
|
||||
+ (3, 0), # no 'no parent' warning is expected.
|
||||
+ (4, 1), # 1 'all entries were skipped' warning
|
||||
+ (5, 1), # 1 'returning task warning' info message
|
||||
+ )
|
||||
+
|
||||
+ far_suffix_on = (
|
||||
+ (1, 0), # no errors are expected.
|
||||
+ (2, 1), # 1 warning (consolidated, pre-check aborts after 4 entries)
|
||||
+ (3, 0), # 0 'no parent' warnings (pre-check aborts before processing)
|
||||
+ (4, 1), # 1 'all entries were skipped' warning (from pre-check)
|
||||
+ (5, 0), # 0 'returning task warning' info message (online import)
|
||||
+ )
|
||||
+ # Backend-specific behavior for orphan detection when suffix parameter is provided
|
||||
+ nbw = 0 if get_default_db_lib() == "bdb" else 10
|
||||
+ far_suffix_with_suffix_on = (
|
||||
+ (1, 0), # no errors are expected.
|
||||
+ (2, nbw), # 0 (BDB early filtering) or 10 (LMDB orphan detection) warnings
|
||||
+ (3, nbw), # 0 (BDB early filtering) or 10 (LMDB orphan detection) 'no parent' warnings
|
||||
+ (4, 0), # 0 'all entries were skipped' warning (no pre-check abort)
|
||||
+ (5, 0), # 0 'returning task warning' info message (online import)
|
||||
+ )
|
||||
+ far_suffix_off = (
|
||||
+ (1, 0), # no errors are expected.
|
||||
+ (2, 1), # 1 warning (consolidated, pre-check detects missing suffix)
|
||||
+ (3, 0), # 0 'no parent' warnings (pre-check aborts before processing)
|
||||
+ (4, 1), # 1 'all entries were skipped' warning (from pre-check)
|
||||
+ (5, 1), # 1 'returning task warning' info message (offline import)
|
||||
+ )
|
||||
+ far_suffix_with_suffix_off = (
|
||||
+ (1, 0), # no errors are expected.
|
||||
+ (2, nbw), # 0 (BDB early filtering) or 10 (LMDB orphan detection) warnings
|
||||
+ (3, nbw), # 0 (BDB early filtering) or 10 (LMDB orphan detection) 'no parent' warnings
|
||||
+ (4, 0), # 0 'all entries were skipped' warning (no pre-check abort)
|
||||
+ (5, 0), # 0 'returning task warning' (rc=0, successful import of suffix)
|
||||
+ )
|
||||
+
|
||||
+ with open(inst.ds_paths.error_log, 'at+') as fd:
|
||||
+ patterns = (
|
||||
+ "Reserved for IEHandler",
|
||||
+ " ERR ",
|
||||
+ " WARN ",
|
||||
+ "has no parent",
|
||||
+ "all entries were skipped",
|
||||
+ "returning task warning",
|
||||
+ )
|
||||
+ errlog = LogHandler(fd, patterns)
|
||||
+ no_errors = ((1, 0), (2, 0)) # no errors nor warnings are expected.
|
||||
+
|
||||
+
|
||||
+ # 1. Prepare final cleanup
|
||||
+ Exporter(inst, errlog, "full").run(no_errors)
|
||||
+
|
||||
+ def fin():
|
||||
+ inst.start()
|
||||
+ with open(inst.ds_paths.error_log, 'at+') as cleanup_fd:
|
||||
+ cleanup_errlog = LogHandler(cleanup_fd, patterns)
|
||||
+ Importer(inst, cleanup_errlog, "full").run(no_errors)
|
||||
+
|
||||
+ if not DEBUGGING:
|
||||
+ request.addfinalizer(fin)
|
||||
+
|
||||
+ # 2. Add a few users
|
||||
+ user = UserAccounts(inst, DEFAULT_SUFFIX)
|
||||
+ users = [ user.create_test_user(uid=i) for i in range(10) ]
|
||||
+
|
||||
+ # 3. Export ou=people subtree
|
||||
+ e = Exporter(inst, errlog, "people", suffix=f'ou=people,{DEFAULT_SUFFIX}')
|
||||
+ e.run(no_errors) # no errors nor warnings are expected.
|
||||
+
|
||||
+ # 4. Online import using backend name ou=people subtree
|
||||
+ e = Importer(inst, errlog, "people")
|
||||
+ e.run(no_suffix_on)
|
||||
+ e.check_db()
|
||||
+
|
||||
+ # 5. Online import using suffix name ou=people subtree
|
||||
+ e = Importer(inst, errlog, "people", suffix=DEFAULT_SUFFIX)
|
||||
+ e.run(no_suffix_on)
|
||||
+ e.check_db()
|
||||
+
|
||||
+ # 6. Stop the instance
|
||||
+ inst.stop()
|
||||
+
|
||||
+ # 7. Offline import using backend name ou=people subtree
|
||||
+ e = Importer(inst, errlog, "people")
|
||||
+ e.run(no_suffix_off)
|
||||
+ e.check_db()
|
||||
+
|
||||
+ # 8. Offline import using suffix name ou=people subtree
|
||||
+ e = Importer(inst, errlog, "people", suffix=DEFAULT_SUFFIX)
|
||||
+ e.run(no_suffix_off)
|
||||
+ e.check_db()
|
||||
+
|
||||
+ # 9. Generate ldif with a far away suffix
|
||||
+ e = Importer(inst, errlog, "full")
|
||||
+ people_ldif = e.ldif
|
||||
+ e = Importer(inst, errlog, "far")
|
||||
+ with open(e.ldif, "wt") as fout:
|
||||
+ with open(people_ldif, "rt") as fin:
|
||||
+ # Copy version
|
||||
+ line = fin.readline()
|
||||
+ fout.write(line)
|
||||
+ line = fin.readline()
|
||||
+ fout.write(line)
|
||||
+ # Generate fake entries
|
||||
+ for idx in range(10):
|
||||
+ fout.write(f"dn: uid=id{idx},dc=foo\nobjectclasses: extensibleObject\n\n")
|
||||
+ for line in iter(fin.readline, ''):
|
||||
+ fout.write(line)
|
||||
+
|
||||
+ os.chmod(e.ldif, 0o644)
|
||||
+
|
||||
+ # 10. Offline import using backend name ou=people subtree
|
||||
+ e.run(far_suffix_off)
|
||||
+ e.check_db()
|
||||
+
|
||||
+ # 11. Offline import using suffix name ou=people subtree
|
||||
+ e = Importer(inst, errlog, "far", suffix=DEFAULT_SUFFIX)
|
||||
+ e.run(far_suffix_with_suffix_off)
|
||||
+ e.check_db()
|
||||
+
|
||||
+ # 12. Start the instance
|
||||
+ inst.start()
|
||||
+
|
||||
+ # 13. Online import using backend name ou=people subtree
|
||||
+ e = Importer(inst, errlog, "far")
|
||||
+ e.run(far_suffix_on)
|
||||
+ e.check_db()
|
||||
+
|
||||
+ # 14. Online import using suffix name ou=people subtree
|
||||
+ e = Importer(inst, errlog, "far", suffix=DEFAULT_SUFFIX)
|
||||
+ e.run(far_suffix_with_suffix_on)
|
||||
+ e.check_db()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
diff --git a/dirsrvtests/tests/suites/memberof_plugin/fixup_test.py b/dirsrvtests/tests/suites/memberof_plugin/fixup_test.py
|
||||
index 5aac40d2b..44804bd1c 100644
|
||||
--- a/dirsrvtests/tests/suites/memberof_plugin/fixup_test.py
|
||||
+++ b/dirsrvtests/tests/suites/memberof_plugin/fixup_test.py
|
||||
@@ -44,7 +44,10 @@ def test_fixup_task_limit(topo):
|
||||
group = groups.create(properties={'cn': 'test'})
|
||||
|
||||
users = UserAccounts(topo.standalone, DEFAULT_SUFFIX)
|
||||
- for idx in range(400):
|
||||
+ # Turn on access log buffering to speed up user creation
|
||||
+ buffering = topo.standalone.config.get_attr_val_utf8('nsslapd-accesslog-logbuffering')
|
||||
+ topo.standalone.config.set('nsslapd-accesslog-logbuffering', 'on')
|
||||
+ for idx in range(6000):
|
||||
user = users.create(properties={
|
||||
'uid': 'testuser%s' % idx,
|
||||
'cn' : 'testuser%s' % idx,
|
||||
@@ -55,6 +58,9 @@ def test_fixup_task_limit(topo):
|
||||
})
|
||||
group.add('member', user.dn)
|
||||
|
||||
+ # Restore access log buffering
|
||||
+ topo.standalone.config.set('nsslapd-accesslog-logbuffering', buffering)
|
||||
+
|
||||
# Configure memberOf plugin
|
||||
memberof = MemberOfPlugin(topo.standalone)
|
||||
memberof.enable()
|
||||
diff --git a/dirsrvtests/tests/suites/plugins/attruniq_test.py b/dirsrvtests/tests/suites/plugins/attruniq_test.py
|
||||
index 6eaee08a4..a2be413c8 100644
|
||||
--- a/dirsrvtests/tests/suites/plugins/attruniq_test.py
|
||||
+++ b/dirsrvtests/tests/suites/plugins/attruniq_test.py
|
||||
@@ -84,14 +84,23 @@ def containers(topology_st, request):
|
||||
def attruniq(topology_st, request):
|
||||
log.info('Setup attribute uniqueness plugin')
|
||||
attruniq = AttributeUniquenessPlugin(topology_st.standalone, dn="cn=attruniq,cn=plugins,cn=config")
|
||||
- attruniq.create(properties={'cn': 'attruniq'})
|
||||
- attruniq.add_unique_attribute('cn')
|
||||
+
|
||||
+ if attruniq.exists():
|
||||
+ attruniq.delete()
|
||||
+ topology_st.standalone.restart()
|
||||
+
|
||||
+ attruniq.create(properties={
|
||||
+ 'cn': 'attruniq',
|
||||
+ 'uniqueness-attribute-name': 'cn',
|
||||
+ 'uniqueness-subtrees': 'cn=config',
|
||||
+ 'nsslapd-pluginEnabled': 'on'
|
||||
+ })
|
||||
topology_st.standalone.restart()
|
||||
|
||||
def fin():
|
||||
if attruniq.exists():
|
||||
- attruniq.disable()
|
||||
attruniq.delete()
|
||||
+ topology_st.standalone.restart()
|
||||
|
||||
request.addfinalizer(fin)
|
||||
|
||||
@@ -250,8 +259,8 @@ def test_modrdn_attr_uniqueness(topology_st, attruniq):
|
||||
group1 = groups.create(properties={'cn': 'group1'})
|
||||
group2 = groups.create(properties={'cn': 'group2'})
|
||||
|
||||
- attruniq.add_unique_attribute('mail')
|
||||
- attruniq.add_unique_subtree(group2.dn)
|
||||
+ attruniq.replace('uniqueness-attribute-name', 'mail')
|
||||
+ attruniq.replace('uniqueness-subtrees', group2.dn)
|
||||
attruniq.enable_all_subtrees()
|
||||
log.debug(f'Enable PLUGIN_ATTR_UNIQUENESS plugin as "ON"')
|
||||
attruniq.enable()
|
||||
@@ -274,9 +283,6 @@ def test_modrdn_attr_uniqueness(topology_st, attruniq):
|
||||
assert 'attribute value already exist' in str(excinfo.value)
|
||||
log.debug(excinfo.value)
|
||||
|
||||
- log.debug('Move user2 to group1')
|
||||
- user2.rename(f'uid={user2.rdn}', group1.dn)
|
||||
-
|
||||
user1.delete()
|
||||
user2.delete()
|
||||
|
||||
@@ -302,17 +308,11 @@ def test_multiple_attr_uniqueness(topology_st, attruniq):
|
||||
6. Should raise CONSTRAINT_VIOLATION
|
||||
"""
|
||||
|
||||
- try:
|
||||
- attruniq.add_unique_attribute('mail')
|
||||
- attruniq.add_unique_attribute('mailAlternateAddress')
|
||||
- attruniq.add_unique_subtree(DEFAULT_SUFFIX)
|
||||
- attruniq.enable_all_subtrees()
|
||||
- log.debug(f'Enable PLUGIN_ATTR_UNIQUENESS plugin as "ON"')
|
||||
- attruniq.enable()
|
||||
- except ldap.LDAPError as e:
|
||||
- log.fatal('test_multiple_attribute_uniqueness: Failed to configure plugin for "mail": error {}'.format(e.args[0]['desc']))
|
||||
- assert False
|
||||
-
|
||||
+ attruniq.replace('uniqueness-attribute-name', ['mail', 'mailAlternateAddress'])
|
||||
+ attruniq.replace('uniqueness-subtrees', DEFAULT_SUFFIX)
|
||||
+ attruniq.enable_all_subtrees()
|
||||
+ log.debug(f'Enable PLUGIN_ATTR_UNIQUENESS plugin as "ON"')
|
||||
+ attruniq.enable()
|
||||
topology_st.standalone.restart()
|
||||
|
||||
users = UserAccounts(topology_st.standalone, DEFAULT_SUFFIX)
|
||||
@@ -383,8 +383,9 @@ def test_exclude_subtrees(topology_st, attruniq):
|
||||
16. Success
|
||||
17. Success
|
||||
"""
|
||||
- attruniq.add_unique_attribute('telephonenumber')
|
||||
- attruniq.add_unique_subtree(DEFAULT_SUFFIX)
|
||||
+ # Replace dummy config with actual test config
|
||||
+ attruniq.replace('uniqueness-attribute-name', 'telephonenumber')
|
||||
+ attruniq.replace('uniqueness-subtrees', DEFAULT_SUFFIX)
|
||||
attruniq.enable_all_subtrees()
|
||||
attruniq.enable()
|
||||
topology_st.standalone.restart()
|
||||
@@ -517,10 +518,18 @@ def test_matchingrule_attr(topology_st):
|
||||
"""
|
||||
|
||||
inst = topology_st.standalone
|
||||
-
|
||||
attruniq = AttributeUniquenessPlugin(inst,
|
||||
dn="cn=attribute uniqueness,cn=plugins,cn=config")
|
||||
- attruniq.add_unique_attribute('cn:CaseExactMatch:')
|
||||
+
|
||||
+ if attruniq.exists():
|
||||
+ attruniq.delete()
|
||||
+ inst.restart()
|
||||
+
|
||||
+ attruniq.create(properties={
|
||||
+ 'cn': 'attribute uniqueness',
|
||||
+ 'uniqueness-attribute-name': 'cn:CaseExactMatch:',
|
||||
+ 'uniqueness-subtrees': DEFAULT_SUFFIX
|
||||
+ })
|
||||
attruniq.enable_all_subtrees()
|
||||
attruniq.enable()
|
||||
inst.restart()
|
||||
@@ -595,7 +604,7 @@ def test_one_container_add(topology_st, attruniq, containers, active_user_1):
|
||||
active_2.delete()
|
||||
|
||||
log.info('Setup attribute uniqueness plugin for "cn" attribute')
|
||||
- attruniq.add_unique_subtree(ACTIVE_DN)
|
||||
+ attruniq.replace('uniqueness-subtrees', ACTIVE_DN)
|
||||
attruniq.enable()
|
||||
topology_st.standalone.restart()
|
||||
|
||||
@@ -628,7 +637,7 @@ def test_one_container_mod(topology_st, attruniq, containers,
|
||||
"""
|
||||
|
||||
log.info('Setup attribute uniqueness plugin for "cn" attribute')
|
||||
- attruniq.add_unique_subtree(ACTIVE_DN)
|
||||
+ attruniq.replace('uniqueness-subtrees', ACTIVE_DN)
|
||||
attruniq.enable()
|
||||
topology_st.standalone.restart()
|
||||
|
||||
@@ -656,7 +665,7 @@ def test_one_container_modrdn(topology_st, attruniq, containers,
|
||||
"""
|
||||
|
||||
log.info('Setup attribute uniqueness plugin for "cn" attribute')
|
||||
- attruniq.add_unique_subtree(ACTIVE_DN)
|
||||
+ attruniq.replace('uniqueness-subtrees', ACTIVE_DN)
|
||||
attruniq.enable()
|
||||
topology_st.standalone.restart()
|
||||
|
||||
@@ -690,8 +699,7 @@ def test_multiple_containers_add(topology_st, attruniq, containers,
|
||||
"""
|
||||
|
||||
log.info('Setup attribute uniqueness plugin for "cn" attribute')
|
||||
- attruniq.add_unique_subtree(ACTIVE_DN)
|
||||
- attruniq.add_unique_subtree(STAGE_DN)
|
||||
+ attruniq.replace('uniqueness-subtrees', [ACTIVE_DN, STAGE_DN])
|
||||
attruniq.enable()
|
||||
topology_st.standalone.restart()
|
||||
|
||||
@@ -789,8 +797,7 @@ def test_multiple_containers_mod(topology_st, attruniq, containers,
|
||||
"""
|
||||
|
||||
log.info('Setup attribute uniqueness plugin for "cn" attribute')
|
||||
- attruniq.add_unique_subtree(ACTIVE_DN)
|
||||
- attruniq.add_unique_subtree(STAGE_DN)
|
||||
+ attruniq.replace('uniqueness-subtrees', [ACTIVE_DN, STAGE_DN])
|
||||
attruniq.enable()
|
||||
topology_st.standalone.restart()
|
||||
|
||||
@@ -874,8 +881,8 @@ def test_multiple_containers_modrdn(topology_st, attruniq, containers,
|
||||
"""
|
||||
|
||||
log.info('Setup attribute uniqueness plugin for "cn" attribute')
|
||||
- attruniq.add_unique_subtree(ACTIVE_DN)
|
||||
- attruniq.add_unique_subtree(STAGE_DN)
|
||||
+ # Replace dummy subtree with actual test subtrees
|
||||
+ attruniq.replace('uniqueness-subtrees', [ACTIVE_DN, STAGE_DN])
|
||||
attruniq.enable()
|
||||
topology_st.standalone.restart()
|
||||
|
||||
@@ -993,7 +1000,10 @@ def test_invalid_config_missing_attr_name(topology_st):
|
||||
_config_file(topology_st, action='save')
|
||||
|
||||
attruniq = AttributeUniquenessPlugin(topology_st.standalone, dn="cn=attruniq,cn=plugins,cn=config")
|
||||
- attruniq.create(properties={'cn': 'attruniq'})
|
||||
+ attruniq.create(properties={
|
||||
+ 'cn': 'attruniq',
|
||||
+ 'uniqueness-subtrees': DEFAULT_SUFFIX
|
||||
+ })
|
||||
attruniq.enable()
|
||||
|
||||
topology_st.standalone.errorlog_file = open(topology_st.standalone.errlog, "r")
|
||||
@@ -1040,9 +1050,11 @@ def test_invalid_config_invalid_subtree(topology_st):
|
||||
_config_file(topology_st, action='save')
|
||||
|
||||
attruniq = AttributeUniquenessPlugin(topology_st.standalone, dn="cn=attruniq,cn=plugins,cn=config")
|
||||
- attruniq.create(properties={'cn': 'attruniq'})
|
||||
- attruniq.add_unique_attribute('cn')
|
||||
- attruniq.add_unique_subtree('invalid_subtree')
|
||||
+ attruniq.create(properties={
|
||||
+ 'cn': 'attruniq',
|
||||
+ 'uniqueness-attribute-name': 'cn',
|
||||
+ 'uniqueness-subtrees': 'invalid_subtree'
|
||||
+ })
|
||||
attruniq.enable()
|
||||
|
||||
topology_st.standalone.errorlog_file = open(topology_st.standalone.errlog, "r")
|
||||
diff --git a/dirsrvtests/tests/suites/replication/regression_m2_test.py b/dirsrvtests/tests/suites/replication/regression_m2_test.py
|
||||
index ba1ffcc9c..db5140b0b 100644
|
||||
--- a/dirsrvtests/tests/suites/replication/regression_m2_test.py
|
||||
+++ b/dirsrvtests/tests/suites/replication/regression_m2_test.py
|
||||
@@ -1221,6 +1221,10 @@ def test_rid_starting_with_0(topo_m2, request):
|
||||
for replica,rid in zip(replicas, ['010', '020']):
|
||||
replica.replace('nsDS5ReplicaId', rid)
|
||||
|
||||
+ # Restart required - replica IDs are loaded at startup and cached in memory
|
||||
+ S1.restart()
|
||||
+ S2.restart()
|
||||
+
|
||||
# Restore replica id in finalizer
|
||||
def fin():
|
||||
for replica,rid in zip(replicas, ['1', '2']):
|
||||
diff --git a/dirsrvtests/tests/suites/replication/repl_log_monitoring_test.py b/dirsrvtests/tests/suites/replication/repl_log_monitoring_test.py
|
||||
index 665fcb96f..b2b2d25a2 100644
|
||||
--- a/dirsrvtests/tests/suites/replication/repl_log_monitoring_test.py
|
||||
+++ b/dirsrvtests/tests/suites/replication/repl_log_monitoring_test.py
|
||||
@@ -369,11 +369,13 @@ def test_replication_log_monitoring_multi_suffix(topo_m4):
|
||||
repl.ensure_agreement(s1, s2)
|
||||
repl.ensure_agreement(s2, s1)
|
||||
|
||||
- # Allow initial topology to settle before capturing metrics
|
||||
+ # Wait for all the setup replication to settle, then clear the logs
|
||||
for suffix in all_suffixes:
|
||||
repl = ReplicationManager(suffix)
|
||||
- repl.test_replication_topology(topo_m4)
|
||||
-
|
||||
+ for s1 in suppliers:
|
||||
+ for s2 in suppliers:
|
||||
+ if s1 != s2:
|
||||
+ repl.wait_for_replication(s1, s2)
|
||||
for supplier in suppliers:
|
||||
supplier.deleteAccessLogs(restart=True)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/webui/database/database_test.py b/dirsrvtests/tests/suites/webui/database/database_test.py
|
||||
index ef105d262..05ddb6b00 100644
|
||||
--- a/dirsrvtests/tests/suites/webui/database/database_test.py
|
||||
+++ b/dirsrvtests/tests/suites/webui/database/database_test.py
|
||||
@@ -138,6 +138,7 @@ def test_chaining_configuration_availability(topology_st, page, browser_name):
|
||||
log.info('Click on Chaining Configuration and check if element is loaded.')
|
||||
frame.get_by_role('tab', name='Database', exact=True).click()
|
||||
frame.locator('#chaining-config').click()
|
||||
+ frame.locator('#chaining-page').wait_for()
|
||||
frame.locator('#defSizeLimit').wait_for()
|
||||
assert frame.locator('#defSizeLimit').is_visible()
|
||||
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c b/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c
|
||||
index e3b7e5783..b1a29ff7f 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c
|
||||
@@ -1080,7 +1080,9 @@ ldbm_back_modrdn(Slapi_PBlock *pb)
|
||||
}
|
||||
}
|
||||
}
|
||||
- if (slapi_sdn_get_dn(dn_newsuperiordn) != NULL) {
|
||||
+ /* Only update parent if we're actually moving to a NEW parent (not the same parent) */
|
||||
+ if (slapi_sdn_get_dn(dn_newsuperiordn) != NULL &&
|
||||
+ slapi_sdn_compare(dn_newsuperiordn, &dn_parentdn) != 0) {
|
||||
/* Push out the db modifications from the parent entry */
|
||||
retval = modify_update_all(be, pb, &parent_modify_context, &txn);
|
||||
if (DBI_RC_RETRY == retval) {
|
||||
@@ -1335,11 +1337,6 @@ ldbm_back_modrdn(Slapi_PBlock *pb)
|
||||
goto common_return;
|
||||
|
||||
error_return:
|
||||
- /* Revert the caches if this is the parent operation */
|
||||
- if (parent_op && betxn_callback_fails) {
|
||||
- revert_cache(inst, &parent_time);
|
||||
- }
|
||||
-
|
||||
/* result already sent above - just free stuff */
|
||||
if (postentry) {
|
||||
slapi_entry_free(postentry);
|
||||
@@ -1417,13 +1414,6 @@ error_return:
|
||||
slapi_pblock_set(pb, SLAPI_PLUGIN_OPRETURN, ldap_result_code ? &ldap_result_code : &retval);
|
||||
}
|
||||
slapi_pblock_get(pb, SLAPI_PB_RESULT_TEXT, &ldap_result_message);
|
||||
-
|
||||
- /* As it is a BETXN plugin failure then
|
||||
- * revert the caches if this is the parent operation
|
||||
- */
|
||||
- if (parent_op) {
|
||||
- revert_cache(inst, &parent_time);
|
||||
- }
|
||||
}
|
||||
retval = plugin_call_mmr_plugin_postop(pb, NULL,SLAPI_PLUGIN_BE_TXN_POST_MODRDN_FN);
|
||||
|
||||
@@ -1437,6 +1427,15 @@ error_return:
|
||||
}
|
||||
}
|
||||
|
||||
+ /* Revert the caches if this is the parent operation and cache modifications were made.
|
||||
+ * Cache modifications (via modify_switch_entries) only happen after BETXN PRE plugins succeed,
|
||||
+ * so we should only revert if we got past that point (i.e., BETXN POST plugin failures).
|
||||
+ * For BETXN PRE failures, no cache modifications were made to parent/newparent entries.
|
||||
+ */
|
||||
+ if (parent_op && betxn_callback_fails && postentry) {
|
||||
+ revert_cache(inst, &parent_time);
|
||||
+ }
|
||||
+
|
||||
common_return:
|
||||
|
||||
/* result code could be used in the bepost plugin functions. */
|
||||
@@ -1482,12 +1481,22 @@ common_return:
|
||||
"operation failed, the target entry is cleared from dncache (%s)\n", slapi_entry_get_dn(ec->ep_entry));
|
||||
CACHE_REMOVE(&inst->inst_dncache, bdn);
|
||||
CACHE_RETURN(&inst->inst_dncache, &bdn);
|
||||
+
|
||||
+ /* Also remove ec from entry cache and free it since the operation failed */
|
||||
+ if (inst && cache_is_in_cache(&inst->inst_cache, ec)) {
|
||||
+ CACHE_REMOVE(&inst->inst_cache, ec);
|
||||
+ CACHE_RETURN(&inst->inst_cache, &ec);
|
||||
+ } else {
|
||||
+ /* ec was not in cache, just free it */
|
||||
+ backentry_free(&ec);
|
||||
+ }
|
||||
+ ec = NULL;
|
||||
}
|
||||
|
||||
if (ec && inst) {
|
||||
CACHE_RETURN(&inst->inst_cache, &ec);
|
||||
+ ec = NULL;
|
||||
}
|
||||
- ec = NULL;
|
||||
}
|
||||
|
||||
if (inst) {
|
||||
@@ -1817,6 +1826,8 @@ moddn_get_newdn(Slapi_PBlock *pb, Slapi_DN *dn_olddn, Slapi_DN *dn_newrdn, Slapi
|
||||
|
||||
/*
|
||||
* Return the entries to the cache.
|
||||
+ * For the original entry 'e', we should NOT remove it from cache on failure,
|
||||
+ * as it's still a valid entry in the directory.
|
||||
*/
|
||||
static void
|
||||
moddn_unlock_and_return_entry(
|
||||
@@ -1825,12 +1836,9 @@ moddn_unlock_and_return_entry(
|
||||
{
|
||||
ldbm_instance *inst = (ldbm_instance *)be->be_instance_info;
|
||||
|
||||
- /* Something bad happened so we should give back all the entries */
|
||||
+ /* Unlock and return the entry to the cache */
|
||||
if (*targetentry != NULL) {
|
||||
cache_unlock_entry(&inst->inst_cache, *targetentry);
|
||||
- if (cache_is_in_cache(&inst->inst_cache, *targetentry)) {
|
||||
- CACHE_REMOVE(&inst->inst_cache, *targetentry);
|
||||
- }
|
||||
CACHE_RETURN(&inst->inst_cache, targetentry);
|
||||
*targetentry = NULL;
|
||||
}
|
||||
--
|
||||
2.52.0
|
||||
|
||||
@ -1,40 +0,0 @@
|
||||
From 1ffcc9aa9a397180fe35283ee61b164471d073fb Mon Sep 17 00:00:00 2001
|
||||
From: Thierry Bordaz <tbordaz@redhat.com>
|
||||
Date: Tue, 7 Jan 2025 10:01:51 +0100
|
||||
Subject: [PATCH] Issue 6417 - (2nd) fix typo
|
||||
|
||||
---
|
||||
ldap/servers/slapd/back-ldbm/ldbm_entryrdn.c | 10 ++++++----
|
||||
1 file changed, 6 insertions(+), 4 deletions(-)
|
||||
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_entryrdn.c b/ldap/servers/slapd/back-ldbm/ldbm_entryrdn.c
|
||||
index 1bbb6252a..e2b8273a2 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/ldbm_entryrdn.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/ldbm_entryrdn.c
|
||||
@@ -1178,8 +1178,10 @@ entryrdn_lookup_dn(backend *be,
|
||||
|
||||
/* Just in case the suffix ID is not '1' retrieve it from the database */
|
||||
keybuf = slapi_ch_strdup(slapi_sdn_get_ndn(be->be_suffix));
|
||||
- dblayer_value_set(be, &key, keybuf, strlen(keybuf) + 1);
|
||||
- rc = dblayer_cursor_op(&ctx.cursor, DBI_OP_MOVE_TO_KEY, &key, &data);
|
||||
+ key.data = keybuf;
|
||||
+ key.size = key.ulen = strlen(keybuf) + 1;
|
||||
+ key.flags = DB_DBT_USERMEM;
|
||||
+ rc = cursor->c_get(cursor, &key, &data, DB_SET);
|
||||
if (rc) {
|
||||
slapi_log_err(SLAPI_LOG_WARNING, "entryrdn_lookup_dn",
|
||||
"Fails to retrieve the ID of suffix %s - keep the default value '%d'\n",
|
||||
@@ -1189,8 +1191,8 @@ entryrdn_lookup_dn(backend *be,
|
||||
elem = (rdn_elem *)data.data;
|
||||
suffix_id = id_stored_to_internal(elem->rdn_elem_id);
|
||||
}
|
||||
- dblayer_value_free(be, &data);
|
||||
- dblayer_value_free(be, &key);
|
||||
+ slapi_ch_free(&data.data);
|
||||
+ slapi_ch_free_string(&keybuf);
|
||||
|
||||
do {
|
||||
/* Setting up a key for the node to get its parent */
|
||||
--
|
||||
2.48.0
|
||||
|
||||
@ -0,0 +1,57 @@
|
||||
From 04a0f5560ea741972b9c264af23b377893ab7133 Mon Sep 17 00:00:00 2001
|
||||
From: Akshay Adhikari <aadhikar@redhat.com>
|
||||
Date: Thu, 5 Feb 2026 15:41:09 +0530
|
||||
Subject: [PATCH] Issue 7076 - Fix revert_cache() never called in modrdn
|
||||
(#7220)
|
||||
|
||||
Description: The postentry check in PR #7077 was broken - postentry is always NULL
|
||||
at that point, fixed by removing the check.
|
||||
|
||||
Relates: #7076
|
||||
|
||||
Reviewed by: @vashirov, @mreynolds389, @droideck (Thanks!)
|
||||
---
|
||||
ldap/servers/slapd/back-ldbm/ldbm_modrdn.c | 12 +++++++-----
|
||||
1 file changed, 7 insertions(+), 5 deletions(-)
|
||||
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c b/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c
|
||||
index b1a29ff7f..36377fc01 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c
|
||||
@@ -103,6 +103,7 @@ ldbm_back_modrdn(Slapi_PBlock *pb)
|
||||
Connection *pb_conn = NULL;
|
||||
int32_t parent_op = 0;
|
||||
int32_t betxn_callback_fails = 0; /* if a BETXN fails we need to revert entry cache */
|
||||
+ int32_t cache_mod_phase = 0; /* set when we reach the cache modification phase */
|
||||
struct timespec parent_time;
|
||||
Slapi_Mods *smods_add_rdn = NULL;
|
||||
|
||||
@@ -1229,6 +1230,8 @@ ldbm_back_modrdn(Slapi_PBlock *pb)
|
||||
goto error_return;
|
||||
}
|
||||
|
||||
+ /* We're now past the BETXN PRE phase and entering the cache modification phase */
|
||||
+ cache_mod_phase = 1;
|
||||
postentry = slapi_entry_dup(ec->ep_entry);
|
||||
|
||||
if (parententry != NULL) {
|
||||
@@ -1427,12 +1430,11 @@ error_return:
|
||||
}
|
||||
}
|
||||
|
||||
- /* Revert the caches if this is the parent operation and cache modifications were made.
|
||||
- * Cache modifications (via modify_switch_entries) only happen after BETXN PRE plugins succeed,
|
||||
- * so we should only revert if we got past that point (i.e., BETXN POST plugin failures).
|
||||
- * For BETXN PRE failures, no cache modifications were made to parent/newparent entries.
|
||||
+ /* Revert the caches if this is the parent operation AND we reached the
|
||||
+ * cache modification phase. If BETXN PRE fails, cache_mod_phase is 0
|
||||
+ * and we don't need to revert since no cache modifications were made.
|
||||
*/
|
||||
- if (parent_op && betxn_callback_fails && postentry) {
|
||||
+ if (parent_op && betxn_callback_fails && cache_mod_phase) {
|
||||
revert_cache(inst, &parent_time);
|
||||
}
|
||||
|
||||
--
|
||||
2.52.0
|
||||
|
||||
@ -1,75 +0,0 @@
|
||||
From 9e1284122a929fe14633a2aa6e2de4d72891f98f Mon Sep 17 00:00:00 2001
|
||||
From: Thierry Bordaz <tbordaz@redhat.com>
|
||||
Date: Mon, 13 Jan 2025 17:41:18 +0100
|
||||
Subject: [PATCH] Issue 6417 - (3rd) If an entry RDN is identical to the
|
||||
suffix, then Entryrdn gets broken during a reindex (#6480)
|
||||
|
||||
Bug description:
|
||||
The previous fix had a flaw.
|
||||
In case entryrdn_lookup_dn is called with an undefined suffix
|
||||
the lookup of the suffix trigger a crash.
|
||||
For example it can occur during internal search of an
|
||||
unexisting map (view plugin).
|
||||
The issue exists in all releases but is hidden since 2.3.
|
||||
|
||||
Fix description:
|
||||
testing the suffix is defined
|
||||
|
||||
fixes: #6417
|
||||
|
||||
Reviewed by: Pierre Rogier (THnaks !)
|
||||
---
|
||||
ldap/servers/slapd/back-ldbm/ldbm_entryrdn.c | 36 +++++++++++---------
|
||||
1 file changed, 20 insertions(+), 16 deletions(-)
|
||||
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_entryrdn.c b/ldap/servers/slapd/back-ldbm/ldbm_entryrdn.c
|
||||
index e2b8273a2..01c77156f 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/ldbm_entryrdn.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/ldbm_entryrdn.c
|
||||
@@ -1176,23 +1176,27 @@ entryrdn_lookup_dn(backend *be,
|
||||
/* Setting the bulk fetch buffer */
|
||||
data.flags = DB_DBT_MALLOC;
|
||||
|
||||
- /* Just in case the suffix ID is not '1' retrieve it from the database */
|
||||
- keybuf = slapi_ch_strdup(slapi_sdn_get_ndn(be->be_suffix));
|
||||
- key.data = keybuf;
|
||||
- key.size = key.ulen = strlen(keybuf) + 1;
|
||||
- key.flags = DB_DBT_USERMEM;
|
||||
- rc = cursor->c_get(cursor, &key, &data, DB_SET);
|
||||
- if (rc) {
|
||||
- slapi_log_err(SLAPI_LOG_WARNING, "entryrdn_lookup_dn",
|
||||
- "Fails to retrieve the ID of suffix %s - keep the default value '%d'\n",
|
||||
- slapi_sdn_get_ndn(be->be_suffix),
|
||||
- suffix_id);
|
||||
- } else {
|
||||
- elem = (rdn_elem *)data.data;
|
||||
- suffix_id = id_stored_to_internal(elem->rdn_elem_id);
|
||||
+ /* Just in case the suffix ID is not '1' retrieve it from the database
|
||||
+ * if the suffix is not defined suffix_id remains '1'
|
||||
+ */
|
||||
+ if (be->be_suffix) {
|
||||
+ keybuf = slapi_ch_strdup(slapi_sdn_get_ndn(be->be_suffix));
|
||||
+ key.data = keybuf;
|
||||
+ key.size = key.ulen = strlen(keybuf) + 1;
|
||||
+ key.flags = DB_DBT_USERMEM;
|
||||
+ rc = cursor->c_get(cursor, &key, &data, DB_SET);
|
||||
+ if (rc) {
|
||||
+ slapi_log_err(SLAPI_LOG_WARNING, "entryrdn_lookup_dn",
|
||||
+ "Fails to retrieve the ID of suffix %s - keep the default value '%d'\n",
|
||||
+ slapi_sdn_get_ndn(be->be_suffix),
|
||||
+ suffix_id);
|
||||
+ } else {
|
||||
+ elem = (rdn_elem *) data.data;
|
||||
+ suffix_id = id_stored_to_internal(elem->rdn_elem_id);
|
||||
+ }
|
||||
+ slapi_ch_free(&data.data);
|
||||
+ slapi_ch_free_string(&keybuf);
|
||||
}
|
||||
- slapi_ch_free(&data.data);
|
||||
- slapi_ch_free_string(&keybuf);
|
||||
|
||||
do {
|
||||
/* Setting up a key for the node to get its parent */
|
||||
--
|
||||
2.48.0
|
||||
|
||||
@ -0,0 +1,24 @@
|
||||
From fb8acf6141b069e250be3821c1e70ee7ed448720 Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Wed, 11 Feb 2026 19:53:44 +0100
|
||||
Subject: [PATCH] Issue 6947 - Fix health_system_indexes_test.py
|
||||
|
||||
---
|
||||
.../tests/suites/healthcheck/health_system_indexes_test.py | 1 +
|
||||
1 file changed, 1 insertion(+)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
|
||||
index 4cd4d0c70..3b3651b38 100644
|
||||
--- a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
|
||||
+++ b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
|
||||
@@ -99,6 +99,7 @@ def run_healthcheck_and_flush_log(topology, instance, searched_code, json, searc
|
||||
"memberof",
|
||||
]
|
||||
args.dry_run = False
|
||||
+ args.exclude_check = []
|
||||
|
||||
# If we are using BDB as a backend, we will get error DSBLE0006 on new versions
|
||||
if (
|
||||
--
|
||||
2.52.0
|
||||
|
||||
@ -1,297 +0,0 @@
|
||||
From d2f9dd82e3610ee9b73feea981c680c03bb21394 Mon Sep 17 00:00:00 2001
|
||||
From: Mark Reynolds <mreynolds@redhat.com>
|
||||
Date: Thu, 16 Jan 2025 08:42:53 -0500
|
||||
Subject: [PATCH] Issue 6509 - Race condition with Paged Result searches
|
||||
|
||||
Description:
|
||||
|
||||
There is a race condition with Paged Result searches when a new operation comes
|
||||
in while a paged search is finishing. This triggers an invalid time out error
|
||||
and closes the connection with a T3 code.
|
||||
|
||||
The problem is that we do not use the "PagedResult lock" when checking the
|
||||
connection's paged result data for a timeout event. This causes the paged
|
||||
result timeout value to change unexpectedly and trigger a false timeout when a
|
||||
new operation arrives.
|
||||
|
||||
Now we check the timeout without hte conn lock, if its expired it could
|
||||
be a race condition and false positive. Try the lock again and test the
|
||||
timeout. This also prevents blocking non-paged result searches from
|
||||
getting held up by the lock when it's not necessary.
|
||||
|
||||
This also fixes some memory leaks that occur when an error happens.
|
||||
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/6509
|
||||
|
||||
Reviewed by: tbordaz & proger (Thanks!!)
|
||||
---
|
||||
ldap/servers/slapd/daemon.c | 61 ++++++++++++++++++-------------
|
||||
ldap/servers/slapd/opshared.c | 58 ++++++++++++++---------------
|
||||
ldap/servers/slapd/pagedresults.c | 9 +++++
|
||||
ldap/servers/slapd/slap.h | 2 +-
|
||||
4 files changed, 75 insertions(+), 55 deletions(-)
|
||||
|
||||
diff --git a/ldap/servers/slapd/daemon.c b/ldap/servers/slapd/daemon.c
|
||||
index bb80dae36..13dfe250d 100644
|
||||
--- a/ldap/servers/slapd/daemon.c
|
||||
+++ b/ldap/servers/slapd/daemon.c
|
||||
@@ -1578,7 +1578,29 @@ setup_pr_read_pds(Connection_Table *ct)
|
||||
if (c->c_state == CONN_STATE_FREE) {
|
||||
connection_table_move_connection_out_of_active_list(ct, c);
|
||||
} else {
|
||||
- /* we try to acquire the connection mutex, if it is already
|
||||
+ /* Check for a timeout for PAGED RESULTS */
|
||||
+ if (pagedresults_is_timedout_nolock(c)) {
|
||||
+ /*
|
||||
+ * There could be a race condition so lets try again with the
|
||||
+ * right lock
|
||||
+ */
|
||||
+ pthread_mutex_t *pr_mutex = pageresult_lock_get_addr(c);
|
||||
+ if (pthread_mutex_trylock(pr_mutex) == EBUSY) {
|
||||
+ c = next;
|
||||
+ continue;
|
||||
+ }
|
||||
+ if (pagedresults_is_timedout_nolock(c)) {
|
||||
+ pthread_mutex_unlock(pr_mutex);
|
||||
+ disconnect_server(c, c->c_connid, -1,
|
||||
+ SLAPD_DISCONNECT_PAGED_SEARCH_LIMIT,
|
||||
+ 0);
|
||||
+ } else {
|
||||
+ pthread_mutex_unlock(pr_mutex);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ /*
|
||||
+ * we try to acquire the connection mutex, if it is already
|
||||
* acquired by another thread, don't wait
|
||||
*/
|
||||
if (pthread_mutex_trylock(&(c->c_mutex)) == EBUSY) {
|
||||
@@ -1586,35 +1608,24 @@ setup_pr_read_pds(Connection_Table *ct)
|
||||
continue;
|
||||
}
|
||||
if (c->c_flags & CONN_FLAG_CLOSING) {
|
||||
- /* A worker thread has marked that this connection
|
||||
- * should be closed by calling disconnect_server.
|
||||
- * move this connection out of the active list
|
||||
- * the last thread to use the connection will close it
|
||||
+ /*
|
||||
+ * A worker thread, or paged result timeout, has marked that
|
||||
+ * this connection should be closed by calling
|
||||
+ * disconnect_server(). Move this connection out of the active
|
||||
+ * list then the last thread to use the connection will close
|
||||
+ * it.
|
||||
*/
|
||||
connection_table_move_connection_out_of_active_list(ct, c);
|
||||
} else if (c->c_sd == SLAPD_INVALID_SOCKET) {
|
||||
connection_table_move_connection_out_of_active_list(ct, c);
|
||||
} else if (c->c_prfd != NULL) {
|
||||
if ((!c->c_gettingber) && (c->c_threadnumber < c->c_max_threads_per_conn)) {
|
||||
- int add_fd = 1;
|
||||
- /* check timeout for PAGED RESULTS */
|
||||
- if (pagedresults_is_timedout_nolock(c)) {
|
||||
- /* Exceeded the paged search timelimit; disconnect the client */
|
||||
- disconnect_server_nomutex(c, c->c_connid, -1,
|
||||
- SLAPD_DISCONNECT_PAGED_SEARCH_LIMIT,
|
||||
- 0);
|
||||
- connection_table_move_connection_out_of_active_list(ct,
|
||||
- c);
|
||||
- add_fd = 0; /* do not poll on this fd */
|
||||
- }
|
||||
- if (add_fd) {
|
||||
- ct->fd[count].fd = c->c_prfd;
|
||||
- ct->fd[count].in_flags = SLAPD_POLL_FLAGS;
|
||||
- /* slot i of the connection table is mapped to slot
|
||||
- * count of the fds array */
|
||||
- c->c_fdi = count;
|
||||
- count++;
|
||||
- }
|
||||
+ ct->fd[listnum][count].fd = c->c_prfd;
|
||||
+ ct->fd[listnum][count].in_flags = SLAPD_POLL_FLAGS;
|
||||
+ /* slot i of the connection table is mapped to slot
|
||||
+ * count of the fds array */
|
||||
+ c->c_fdi = count;
|
||||
+ count++;
|
||||
} else {
|
||||
if (c->c_threadnumber >= c->c_max_threads_per_conn) {
|
||||
c->c_maxthreadsblocked++;
|
||||
@@ -1675,7 +1686,7 @@ handle_pr_read_ready(Connection_Table *ct, PRIntn num_poll __attribute__((unused
|
||||
continue;
|
||||
}
|
||||
|
||||
- /* Try to get connection mutex, if not available just skip the connection and
|
||||
+ /* Try to get connection mutex, if not available just skip the connection and
|
||||
* process other connections events. May generates cpu load for listening thread
|
||||
* if connection mutex is held for a long time
|
||||
*/
|
||||
diff --git a/ldap/servers/slapd/opshared.c b/ldap/servers/slapd/opshared.c
|
||||
index 7ab4117cd..a29eed052 100644
|
||||
--- a/ldap/servers/slapd/opshared.c
|
||||
+++ b/ldap/servers/slapd/opshared.c
|
||||
@@ -250,7 +250,7 @@ op_shared_search(Slapi_PBlock *pb, int send_result)
|
||||
char *errtext = NULL;
|
||||
int nentries, pnentries;
|
||||
int flag_search_base_found = 0;
|
||||
- int flag_no_such_object = 0;
|
||||
+ bool flag_no_such_object = false;
|
||||
int flag_referral = 0;
|
||||
int flag_psearch = 0;
|
||||
int err_code = LDAP_SUCCESS;
|
||||
@@ -315,7 +315,7 @@ op_shared_search(Slapi_PBlock *pb, int send_result)
|
||||
rc = -1;
|
||||
goto free_and_return_nolock;
|
||||
}
|
||||
-
|
||||
+
|
||||
/* Set the time we actually started the operation */
|
||||
slapi_operation_set_time_started(operation);
|
||||
|
||||
@@ -798,11 +798,11 @@ op_shared_search(Slapi_PBlock *pb, int send_result)
|
||||
}
|
||||
|
||||
/* subtree searches :
|
||||
- * if the search was started above the backend suffix
|
||||
- * - temporarily set the SLAPI_SEARCH_TARGET_SDN to the
|
||||
- * base of the node so that we don't get a NO SUCH OBJECT error
|
||||
- * - do not change the scope
|
||||
- */
|
||||
+ * if the search was started above the backend suffix
|
||||
+ * - temporarily set the SLAPI_SEARCH_TARGET_SDN to the
|
||||
+ * base of the node so that we don't get a NO SUCH OBJECT error
|
||||
+ * - do not change the scope
|
||||
+ */
|
||||
if (scope == LDAP_SCOPE_SUBTREE) {
|
||||
if (slapi_sdn_issuffix(be_suffix, basesdn)) {
|
||||
if (free_sdn) {
|
||||
@@ -825,53 +825,53 @@ op_shared_search(Slapi_PBlock *pb, int send_result)
|
||||
switch (rc) {
|
||||
case 1:
|
||||
/* if the backend returned LDAP_NO_SUCH_OBJECT for a SEARCH request,
|
||||
- * it will not have sent back a result - otherwise, it will have
|
||||
- * sent a result */
|
||||
+ * it will not have sent back a result - otherwise, it will have
|
||||
+ * sent a result */
|
||||
rc = SLAPI_FAIL_GENERAL;
|
||||
slapi_pblock_get(pb, SLAPI_RESULT_CODE, &err);
|
||||
if (err == LDAP_NO_SUCH_OBJECT) {
|
||||
/* may be the object exist somewhere else
|
||||
- * wait the end of the loop to send back this error
|
||||
- */
|
||||
- flag_no_such_object = 1;
|
||||
+ * wait the end of the loop to send back this error
|
||||
+ */
|
||||
+ flag_no_such_object = true;
|
||||
} else {
|
||||
/* err something other than LDAP_NO_SUCH_OBJECT, so the backend will
|
||||
- * have sent the result -
|
||||
- * Set a flag here so we don't return another result. */
|
||||
+ * have sent the result -
|
||||
+ * Set a flag here so we don't return another result. */
|
||||
sent_result = 1;
|
||||
}
|
||||
- /* fall through */
|
||||
+ /* fall through */
|
||||
|
||||
case -1: /* an error occurred */
|
||||
+ slapi_pblock_get(pb, SLAPI_RESULT_CODE, &err);
|
||||
/* PAGED RESULTS */
|
||||
if (op_is_pagedresults(operation)) {
|
||||
/* cleanup the slot */
|
||||
pthread_mutex_lock(pagedresults_mutex);
|
||||
+ if (err != LDAP_NO_SUCH_OBJECT && !flag_no_such_object) {
|
||||
+ /* Free the results if not "no_such_object" */
|
||||
+ void *sr = NULL;
|
||||
+ slapi_pblock_get(pb, SLAPI_SEARCH_RESULT_SET, &sr);
|
||||
+ be->be_search_results_release(&sr);
|
||||
+ }
|
||||
pagedresults_set_search_result(pb_conn, operation, NULL, 1, pr_idx);
|
||||
rc = pagedresults_set_current_be(pb_conn, NULL, pr_idx, 1);
|
||||
pthread_mutex_unlock(pagedresults_mutex);
|
||||
}
|
||||
- if (1 == flag_no_such_object) {
|
||||
- break;
|
||||
- }
|
||||
- slapi_pblock_get(pb, SLAPI_RESULT_CODE, &err);
|
||||
- if (err == LDAP_NO_SUCH_OBJECT) {
|
||||
- /* may be the object exist somewhere else
|
||||
- * wait the end of the loop to send back this error
|
||||
- */
|
||||
- flag_no_such_object = 1;
|
||||
+
|
||||
+ if (err == LDAP_NO_SUCH_OBJECT || flag_no_such_object) {
|
||||
+ /* Maybe the object exists somewhere else, wait to the end
|
||||
+ * of the loop to send back this error */
|
||||
+ flag_no_such_object = true;
|
||||
break;
|
||||
} else {
|
||||
- /* for error other than LDAP_NO_SUCH_OBJECT
|
||||
- * the error has already been sent
|
||||
- * stop the search here
|
||||
- */
|
||||
+ /* For error other than LDAP_NO_SUCH_OBJECT the error has
|
||||
+ * already been sent stop the search here */
|
||||
cache_return_target_entry(pb, be, operation);
|
||||
goto free_and_return;
|
||||
}
|
||||
|
||||
/* when rc == SLAPI_FAIL_DISKFULL this case is executed */
|
||||
-
|
||||
case SLAPI_FAIL_DISKFULL:
|
||||
operation_out_of_disk_space();
|
||||
cache_return_target_entry(pb, be, operation);
|
||||
diff --git a/ldap/servers/slapd/pagedresults.c b/ldap/servers/slapd/pagedresults.c
|
||||
index db87e486e..4aa1fa3e5 100644
|
||||
--- a/ldap/servers/slapd/pagedresults.c
|
||||
+++ b/ldap/servers/slapd/pagedresults.c
|
||||
@@ -121,12 +121,15 @@ pagedresults_parse_control_value(Slapi_PBlock *pb,
|
||||
if (ber_scanf(ber, "{io}", pagesize, &cookie) == LBER_ERROR) {
|
||||
slapi_log_err(SLAPI_LOG_ERR, "pagedresults_parse_control_value",
|
||||
"<= corrupted control value\n");
|
||||
+ ber_free(ber, 1);
|
||||
return LDAP_PROTOCOL_ERROR;
|
||||
}
|
||||
if (!maxreqs) {
|
||||
slapi_log_err(SLAPI_LOG_ERR, "pagedresults_parse_control_value",
|
||||
"Simple paged results requests per conn exceeded the limit: %d\n",
|
||||
maxreqs);
|
||||
+ ber_free(ber, 1);
|
||||
+ slapi_ch_free_string(&cookie.bv_val);
|
||||
return LDAP_UNWILLING_TO_PERFORM;
|
||||
}
|
||||
|
||||
@@ -376,6 +379,10 @@ pagedresults_free_one_msgid(Connection *conn, ber_int_t msgid, pthread_mutex_t *
|
||||
}
|
||||
prp->pr_flags |= CONN_FLAG_PAGEDRESULTS_ABANDONED;
|
||||
prp->pr_flags &= ~CONN_FLAG_PAGEDRESULTS_PROCESSING;
|
||||
+ if (conn->c_pagedresults.prl_count > 0) {
|
||||
+ _pr_cleanup_one_slot(prp);
|
||||
+ conn->c_pagedresults.prl_count--;
|
||||
+ }
|
||||
rc = 0;
|
||||
break;
|
||||
}
|
||||
@@ -940,7 +947,9 @@ pagedresults_is_timedout_nolock(Connection *conn)
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
+
|
||||
slapi_log_err(SLAPI_LOG_TRACE, "<-- pagedresults_is_timedout", "<= false 2\n");
|
||||
+
|
||||
return 0;
|
||||
}
|
||||
|
||||
diff --git a/ldap/servers/slapd/slap.h b/ldap/servers/slapd/slap.h
|
||||
index 072f6f962..469874fd1 100644
|
||||
--- a/ldap/servers/slapd/slap.h
|
||||
+++ b/ldap/servers/slapd/slap.h
|
||||
@@ -74,7 +74,7 @@ static char ptokPBE[34] = "Internal (Software) Token ";
|
||||
#include <sys/stat.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
-
|
||||
+#include <stdbool.h>
|
||||
#include <time.h> /* For timespec definitions */
|
||||
|
||||
/* Provides our int types and platform specific requirements. */
|
||||
--
|
||||
2.48.0
|
||||
|
||||
@ -0,0 +1,71 @@
|
||||
From 8761b28fd2f2cf3d2e2119bdf1b348bc7d2d1834 Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Mon, 9 Feb 2026 13:18:09 +0100
|
||||
Subject: [PATCH] Issue 7121 - (2nd) LeakSanitizer: various leaks during
|
||||
replication (#7212)
|
||||
|
||||
Bug Description:
|
||||
With the previous fix 75e0e487545893a7b0d83f94f9264c10f8bb0353 applied,
|
||||
server can crash in ber_bvcpy.
|
||||
|
||||
```
|
||||
Program terminated with signal SIGSEGV, Segmentation fault.
|
||||
#0 ber_bvcpy (bvs=0x7f1d00000000, bvd=0x7f1da2cd73c0) at ldap/servers/slapd/value.c:47
|
||||
47 len = bvs->bv_len;
|
||||
[Current thread is 1 (Thread 0x7f1db47fe640 (LWP 36576))]
|
||||
(gdb) bt
|
||||
#0 ber_bvcpy (bvs=0x7f1d00000000, bvd=0x7f1da2cd73c0) at ldap/servers/slapd/value.c:47
|
||||
#1 ber_bvcpy (bvs=0x7f1d00000000, bvd=0x7f1da2cd73c0) at ldap/servers/slapd/value.c:40
|
||||
#2 slapi_value_set_berval (bval=0x7f1d00000000, value=0x7f1da2cd73c0) at ldap/servers/slapd/value.c:322
|
||||
#3 slapi_value_set_berval (value=value@entry=0x7f1da2cd73c0, bval=bval@entry=0x7f1d00000000) at ldap/servers/slapd/value.c:317
|
||||
#4 0x00007f1e48b7d787 in value_init (v=v@entry=0x7f1da2cd73c0, bval=bval@entry=0x7f1d00000000, t=t@entry=0 '\000', csn=csn@entry=0x0)
|
||||
at ldap/servers/slapd/value.c:179
|
||||
#5 0x00007f1e48b7d884 in value_new (bval=bval@entry=0x7f1d00000000, t=t@entry=0 '\000', csn=csn@entry=0x0) at ldap/servers/slapd/value.c:158
|
||||
#6 0x00007f1e48b7ddb7 in slapi_value_dup (v=0x7f1d00000000) at ldap/servers/slapd/value.c:147
|
||||
#7 0x00007f1e48b7e262 in valueset_set_valueset (vs2=0x7f1d502b5218, vs1=0x7f1da2c5b358) at ldap/servers/slapd/valueset.c:1244
|
||||
#8 valueset_set_valueset (vs1=0x7f1da2c5b358, vs2=0x7f1d502b5218) at ldap/servers/slapd/valueset.c:1220
|
||||
#9 0x00007f1e48add4af in slapi_attr_dup (attr=0x7f1d502b51e0) at ldap/servers/slapd/attr.c:396
|
||||
#10 0x00007f1e48af0f60 in slapi_entry_dup (e=0x7f1da2c19000) at ldap/servers/slapd/entry.c:2036
|
||||
#11 0x00007f1e442c734e in ldbm_back_modify (pb=0x7f1da2c00000) at ldap/servers/slapd/back-ldbm/ldbm_modify.c:741
|
||||
#12 0x00007f1e48b30076 in op_shared_modify (pb=pb@entry=0x7f1da2c00000, pw_change=pw_change@entry=0, old_pw=0x0)
|
||||
at ldap/servers/slapd/modify.c:1079
|
||||
#13 0x00007f1e48b30ced in do_modify (pb=pb@entry=0x7f1da2c00000) at ldap/servers/slapd/modify.c:377
|
||||
#14 0x000055e990e2fd1c in connection_dispatch_operation (pb=0x7f1da2c00000, op=<optimized out>, conn=<optimized out>)
|
||||
at ldap/servers/slapd/connection.c:672
|
||||
#15 connection_threadmain (arg=<optimized out>) at ldap/servers/slapd/connection.c:1955
|
||||
#16 0x00007f1e48839bd4 in _pt_root (arg=0x7f1e439d9500) at pthreads/../../../../nspr/pr/src/pthreads/ptthread.c:191
|
||||
#17 0x00007f1e4868a19a in start_thread (arg=<optimized out>) at pthread_create.c:443
|
||||
#18 0x00007f1e4870f100 in clone3 () at ../sysdeps/unix/sysv/linux/x86_64/clone3.S:81
|
||||
```
|
||||
|
||||
The fix changed from always setting `v_csnset = NULL` to only freeing it
|
||||
inside the if-block.
|
||||
|
||||
Fix Description:
|
||||
Keep `csnset_free()` outside the if-block to handle all values, not just
|
||||
those matching the condtion.
|
||||
|
||||
Related: https://github.com/389ds/389-ds-base/issues/7121
|
||||
|
||||
Reviewed by: @progier389, @droideck (Thanks!)
|
||||
---
|
||||
ldap/servers/slapd/entrywsi.c | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/ldap/servers/slapd/entrywsi.c b/ldap/servers/slapd/entrywsi.c
|
||||
index e1bdc1bab..0d044092d 100644
|
||||
--- a/ldap/servers/slapd/entrywsi.c
|
||||
+++ b/ldap/servers/slapd/entrywsi.c
|
||||
@@ -1185,8 +1185,8 @@ resolve_attribute_state_deleted_to_present(Slapi_Entry *e, Slapi_Attr *a, Slapi_
|
||||
if ((csn_compare(vucsn, deletedcsn) >= 0) ||
|
||||
value_distinguished_at_csn(e, a, valuestoupdate[i], deletedcsn)) {
|
||||
entry_deleted_value_to_present_value(a, valuestoupdate[i]);
|
||||
- csnset_free(&valuestoupdate[i]->v_csnset);
|
||||
}
|
||||
+ csnset_free(&valuestoupdate[i]->v_csnset);
|
||||
}
|
||||
}
|
||||
}
|
||||
--
|
||||
2.52.0
|
||||
|
||||
@ -1,29 +0,0 @@
|
||||
From 27cd055197bc3cae458a1f86621aa5410c66dd2c Mon Sep 17 00:00:00 2001
|
||||
From: Mark Reynolds <mreynolds@redhat.com>
|
||||
Date: Mon, 20 Jan 2025 15:51:24 -0500
|
||||
Subject: [PATCH] Issue 6509 - Fix cherry pick issue (race condition in Paged
|
||||
results)
|
||||
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/6509
|
||||
---
|
||||
ldap/servers/slapd/daemon.c | 4 ++--
|
||||
1 file changed, 2 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/ldap/servers/slapd/daemon.c b/ldap/servers/slapd/daemon.c
|
||||
index 13dfe250d..57e07e5f5 100644
|
||||
--- a/ldap/servers/slapd/daemon.c
|
||||
+++ b/ldap/servers/slapd/daemon.c
|
||||
@@ -1620,8 +1620,8 @@ setup_pr_read_pds(Connection_Table *ct)
|
||||
connection_table_move_connection_out_of_active_list(ct, c);
|
||||
} else if (c->c_prfd != NULL) {
|
||||
if ((!c->c_gettingber) && (c->c_threadnumber < c->c_max_threads_per_conn)) {
|
||||
- ct->fd[listnum][count].fd = c->c_prfd;
|
||||
- ct->fd[listnum][count].in_flags = SLAPD_POLL_FLAGS;
|
||||
+ ct->fd[count].fd = c->c_prfd;
|
||||
+ ct->fd[count].in_flags = SLAPD_POLL_FLAGS;
|
||||
/* slot i of the connection table is mapped to slot
|
||||
* count of the fds array */
|
||||
c->c_fdi = count;
|
||||
--
|
||||
2.48.0
|
||||
|
||||
@ -0,0 +1,551 @@
|
||||
From 86b15d688949197a9efe3d5fc7a7eadcdd46e115 Mon Sep 17 00:00:00 2001
|
||||
From: Simon Pichugin <spichugi@redhat.com>
|
||||
Date: Tue, 16 Dec 2025 15:48:35 -0800
|
||||
Subject: [PATCH] Issue 7150 - Compressed access log rotations skipped,
|
||||
accesslog-list out of sync (#7151)
|
||||
|
||||
Description: Accept `.gz`-suffixed rotated log filenames when
|
||||
rebuilding rotation info and checking previous logs, preventing
|
||||
compressed rotations from being dropped from the internal list.
|
||||
|
||||
Add regression tests to stress log rotation with compression,
|
||||
verify `nsslapd-accesslog-list` stays in sync, and guard against
|
||||
crashes when flushing buffered logs during rotation.
|
||||
Minor doc fix in test.
|
||||
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/7150
|
||||
|
||||
Reviewed by: @progier389 (Thanks!)
|
||||
---
|
||||
.../suites/logging/log_flush_rotation_test.py | 341 +++++++++++++++++-
|
||||
ldap/servers/slapd/log.c | 99 +++--
|
||||
2 files changed, 402 insertions(+), 38 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/logging/log_flush_rotation_test.py b/dirsrvtests/tests/suites/logging/log_flush_rotation_test.py
|
||||
index b33a622e1..864ba9c5d 100644
|
||||
--- a/dirsrvtests/tests/suites/logging/log_flush_rotation_test.py
|
||||
+++ b/dirsrvtests/tests/suites/logging/log_flush_rotation_test.py
|
||||
@@ -6,6 +6,7 @@
|
||||
# See LICENSE for details.
|
||||
# --- END COPYRIGHT BLOCK ---
|
||||
#
|
||||
+import glob
|
||||
import os
|
||||
import logging
|
||||
import time
|
||||
@@ -13,14 +14,351 @@ import pytest
|
||||
from lib389._constants import DEFAULT_SUFFIX, PW_DM
|
||||
from lib389.tasks import ImportTask
|
||||
from lib389.idm.user import UserAccounts
|
||||
+from lib389.idm.domain import Domain
|
||||
+from lib389.idm.directorymanager import DirectoryManager
|
||||
from lib389.topologies import topology_st as topo
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
+def remove_rotated_access_logs(inst):
|
||||
+ """
|
||||
+ Remove all rotated access log files to start fresh for each test.
|
||||
+ This prevents log files from previous tests affecting current test results.
|
||||
+ """
|
||||
+ log_dir = inst.get_log_dir()
|
||||
+ patterns = [
|
||||
+ f'{log_dir}/access.2*', # Uncompressed rotated logs
|
||||
+ f'{log_dir}/access.*.gz', # Compressed rotated logs
|
||||
+ ]
|
||||
+ for pattern in patterns:
|
||||
+ for log_file in glob.glob(pattern):
|
||||
+ try:
|
||||
+ os.remove(log_file)
|
||||
+ log.info(f"Removed old log file: {log_file}")
|
||||
+ except OSError as e:
|
||||
+ log.warning(f"Could not remove {log_file}: {e}")
|
||||
+
|
||||
+
|
||||
+def reset_access_log_config(inst):
|
||||
+ """
|
||||
+ Reset access log configuration to default values.
|
||||
+ """
|
||||
+ inst.config.set('nsslapd-accesslog-compress', 'off')
|
||||
+ inst.config.set('nsslapd-accesslog-maxlogsize', '100')
|
||||
+ inst.config.set('nsslapd-accesslog-maxlogsperdir', '10')
|
||||
+ inst.config.set('nsslapd-accesslog-logrotationsync-enabled', 'off')
|
||||
+ inst.config.set('nsslapd-accesslog-logbuffering', 'on')
|
||||
+ inst.config.set('nsslapd-accesslog-logexpirationtime', '-1')
|
||||
+ inst.config.set('nsslapd-accesslog-logminfreediskspace', '5')
|
||||
+
|
||||
+
|
||||
+def generate_heavy_load(inst, suffix, iterations=50):
|
||||
+ """
|
||||
+ Generate heavy LDAP load to fill access log quickly.
|
||||
+ Performs multiple operations: searches, modifies, binds to populate logs.
|
||||
+ """
|
||||
+ for i in range(iterations):
|
||||
+ suffix.replace('description', f'iteration_{i}')
|
||||
+ suffix.get_attr_val('description')
|
||||
+
|
||||
+
|
||||
+def count_access_logs(log_dir, compressed_only=False):
|
||||
+ """
|
||||
+ Count access log files in the log directory.
|
||||
+ Returns count of rotated access logs (not including the active 'access' file).
|
||||
+ """
|
||||
+ if compressed_only:
|
||||
+ pattern = f'{log_dir}/access.*.gz'
|
||||
+ else:
|
||||
+ pattern = f'{log_dir}/access.2*'
|
||||
+ log_files = glob.glob(pattern)
|
||||
+ return len(log_files)
|
||||
+
|
||||
+
|
||||
+def test_log_pileup_with_compression(topo):
|
||||
+ """Test that log rotation properly deletes old logs when compression is enabled.
|
||||
+
|
||||
+ :id: fa1bfce8-b6d3-4520-a0a8-bead14fa5838
|
||||
+ :setup: Standalone Instance
|
||||
+ :steps:
|
||||
+ 1. Clean up existing rotated logs and reset configuration
|
||||
+ 2. Enable access log compression
|
||||
+ 3. Set strict log limits (small maxlogsperdir)
|
||||
+ 4. Disable log expiration to test count-based deletion
|
||||
+ 5. Generate heavy load to create many log rotations
|
||||
+ 6. Verify log count does not exceed maxlogsperdir limit
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Success
|
||||
+ 4. Success
|
||||
+ 5. Success
|
||||
+ 6. Log count should be at or below maxlogsperdir + small buffer
|
||||
+ """
|
||||
+
|
||||
+ inst = topo.standalone
|
||||
+ suffix = Domain(inst, DEFAULT_SUFFIX)
|
||||
+ log_dir = inst.get_log_dir()
|
||||
+
|
||||
+ # Clean up before test
|
||||
+ remove_rotated_access_logs(inst)
|
||||
+ reset_access_log_config(inst)
|
||||
+ inst.restart()
|
||||
+
|
||||
+ max_logs = 5
|
||||
+ inst.config.set('nsslapd-accesslog-compress', 'on')
|
||||
+ inst.config.set('nsslapd-accesslog-maxlogsperdir', str(max_logs))
|
||||
+ inst.config.set('nsslapd-accesslog-maxlogsize', '1') # 1MB to trigger rotation
|
||||
+ inst.config.set('nsslapd-accesslog-logrotationsync-enabled', 'off')
|
||||
+ inst.config.set('nsslapd-accesslog-logbuffering', 'off')
|
||||
+
|
||||
+ inst.config.set('nsslapd-accesslog-logexpirationtime', '-1')
|
||||
+
|
||||
+ inst.config.set('nsslapd-accesslog-logminfreediskspace', '5')
|
||||
+
|
||||
+ inst.restart()
|
||||
+ time.sleep(2)
|
||||
+
|
||||
+ target_logs = max_logs * 3
|
||||
+ for i in range(target_logs):
|
||||
+ log.info(f"Generating load for log rotation {i+1}/{target_logs}")
|
||||
+ generate_heavy_load(inst, suffix, iterations=150)
|
||||
+ time.sleep(1) # Wait for rotation
|
||||
+
|
||||
+ time.sleep(3)
|
||||
+
|
||||
+ logs_on_disk = count_access_logs(log_dir)
|
||||
+ log.info(f"Configured maxlogsperdir: {max_logs}")
|
||||
+ log.info(f"Actual rotated logs on disk: {logs_on_disk}")
|
||||
+
|
||||
+ all_access_logs = glob.glob(f'{log_dir}/access*')
|
||||
+ log.info(f"All access log files: {all_access_logs}")
|
||||
+
|
||||
+ max_allowed = max_logs + 2
|
||||
+ assert logs_on_disk <= max_allowed, (
|
||||
+ f"Log rotation failed to delete old files! "
|
||||
+ f"Expected at most {max_allowed} rotated logs (maxlogsperdir={max_logs} + 2 buffer), "
|
||||
+ f"but found {logs_on_disk}. The server has lost track of the file list."
|
||||
+ )
|
||||
+
|
||||
+
|
||||
+@pytest.mark.parametrize("compress_enabled", ["on", "off"])
|
||||
+def test_accesslog_list_mismatch(topo, compress_enabled):
|
||||
+ """Test that nsslapd-accesslog-list stays synchronized with actual log files.
|
||||
+
|
||||
+ :id: 0a8a46a6-cae7-43bd-8b64-5e3481480cd3
|
||||
+ :parametrized: yes
|
||||
+ :setup: Standalone Instance
|
||||
+ :steps:
|
||||
+ 1. Clean up existing rotated logs and reset configuration
|
||||
+ 2. Configure log rotation with compression enabled/disabled
|
||||
+ 3. Generate activity to trigger multiple rotations
|
||||
+ 4. Get the nsslapd-accesslog-list attribute
|
||||
+ 5. Compare with actual files on disk
|
||||
+ 6. Verify they match (accounting for .gz extension when enabled)
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Success
|
||||
+ 4. Success
|
||||
+ 5. Success
|
||||
+ 6. The list attribute should match actual files on disk
|
||||
+ """
|
||||
+
|
||||
+ inst = topo.standalone
|
||||
+ suffix = Domain(inst, DEFAULT_SUFFIX)
|
||||
+ log_dir = inst.get_log_dir()
|
||||
+ compression_on = compress_enabled == "on"
|
||||
+
|
||||
+ # Clean up before test
|
||||
+ remove_rotated_access_logs(inst)
|
||||
+ reset_access_log_config(inst)
|
||||
+ inst.restart()
|
||||
+
|
||||
+ inst.config.set('nsslapd-accesslog-compress', compress_enabled)
|
||||
+ inst.config.set('nsslapd-accesslog-maxlogsize', '1')
|
||||
+ inst.config.set('nsslapd-accesslog-maxlogsperdir', '10')
|
||||
+ inst.config.set('nsslapd-accesslog-logrotationsync-enabled', 'off')
|
||||
+ inst.config.set('nsslapd-accesslog-logbuffering', 'off')
|
||||
+ inst.config.set('nsslapd-accesslog-logexpirationtime', '-1')
|
||||
+
|
||||
+ inst.restart()
|
||||
+ time.sleep(2)
|
||||
+
|
||||
+ for i in range(15):
|
||||
+ suffix_note = "(no compression)" if not compression_on else ""
|
||||
+ log.info(f"Generating load for rotation {i+1}/15 {suffix_note}")
|
||||
+ generate_heavy_load(inst, suffix, iterations=150)
|
||||
+ time.sleep(1)
|
||||
+
|
||||
+ time.sleep(3)
|
||||
+
|
||||
+ accesslog_list = inst.config.get_attr_vals_utf8('nsslapd-accesslog-list')
|
||||
+ log.info(f"nsslapd-accesslog-list entries (compress={compress_enabled}): {len(accesslog_list)}")
|
||||
+ log.info(f"nsslapd-accesslog-list (compress={compress_enabled}): {accesslog_list}")
|
||||
+
|
||||
+ disk_files = glob.glob(f'{log_dir}/access.2*')
|
||||
+ log.info(f"Actual files on disk (compress={compress_enabled}): {len(disk_files)}")
|
||||
+ log.info(f"Disk files (compress={compress_enabled}): {disk_files}")
|
||||
+
|
||||
+ disk_files_for_compare = set()
|
||||
+ for fpath in disk_files:
|
||||
+ if compression_on and fpath.endswith('.gz'):
|
||||
+ disk_files_for_compare.add(fpath[:-3])
|
||||
+ else:
|
||||
+ disk_files_for_compare.add(fpath)
|
||||
+
|
||||
+ list_files_set = set(accesslog_list)
|
||||
+ missing_from_disk = list_files_set - disk_files_for_compare
|
||||
+ extra_on_disk = disk_files_for_compare - list_files_set
|
||||
+
|
||||
+ if missing_from_disk:
|
||||
+ log.error(
|
||||
+ f"[compress={compress_enabled}] Files in list but NOT on disk: {missing_from_disk}"
|
||||
+ )
|
||||
+ if extra_on_disk:
|
||||
+ log.warning(
|
||||
+ f"[compress={compress_enabled}] Files on disk but NOT in list: {extra_on_disk}"
|
||||
+ )
|
||||
+
|
||||
+ assert not missing_from_disk, (
|
||||
+ f"nsslapd-accesslog-list mismatch (compress={compress_enabled})! "
|
||||
+ f"Files listed but missing from disk: {missing_from_disk}. "
|
||||
+ f"This indicates the server's internal list is out of sync with actual files."
|
||||
+ )
|
||||
+
|
||||
+ if len(extra_on_disk) > 2:
|
||||
+ log.warning(
|
||||
+ f"Potential log tracking issue (compress={compress_enabled}): "
|
||||
+ f"{len(extra_on_disk)} files on disk are not tracked in the accesslog-list: "
|
||||
+ f"{extra_on_disk}"
|
||||
+ )
|
||||
+
|
||||
+
|
||||
+def test_accesslog_list_mixed_compression(topo):
|
||||
+ """Test that nsslapd-accesslog-list correctly tracks both compressed and uncompressed logs.
|
||||
+
|
||||
+ :id: 11b088cd-23be-407d-ad16-4ce2e12da09e
|
||||
+ :setup: Standalone Instance
|
||||
+ :steps:
|
||||
+ 1. Clean up existing rotated logs and reset configuration
|
||||
+ 2. Create rotated logs with compression OFF
|
||||
+ 3. Enable compression and create more rotated logs
|
||||
+ 4. Get the nsslapd-accesslog-list attribute
|
||||
+ 5. Compare with actual files on disk
|
||||
+ 6. Verify all files are correctly tracked (uncompressed and compressed)
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success - uncompressed rotated logs created
|
||||
+ 3. Success - compressed rotated logs created
|
||||
+ 4. Success
|
||||
+ 5. Success
|
||||
+ 6. The list should contain base filenames (without .gz) that
|
||||
+ correspond to files on disk (either as-is or with .gz suffix)
|
||||
+ """
|
||||
+
|
||||
+ inst = topo.standalone
|
||||
+ suffix = Domain(inst, DEFAULT_SUFFIX)
|
||||
+ log_dir = inst.get_log_dir()
|
||||
+
|
||||
+ # Clean up before test
|
||||
+ remove_rotated_access_logs(inst)
|
||||
+ reset_access_log_config(inst)
|
||||
+ inst.restart()
|
||||
+
|
||||
+ inst.config.set('nsslapd-accesslog-compress', 'off')
|
||||
+ inst.config.set('nsslapd-accesslog-maxlogsize', '1')
|
||||
+ inst.config.set('nsslapd-accesslog-maxlogsperdir', '20')
|
||||
+ inst.config.set('nsslapd-accesslog-logrotationsync-enabled', 'off')
|
||||
+ inst.config.set('nsslapd-accesslog-logbuffering', 'off')
|
||||
+ inst.config.set('nsslapd-accesslog-logexpirationtime', '-1')
|
||||
+
|
||||
+ inst.restart()
|
||||
+ time.sleep(2)
|
||||
+
|
||||
+ for i in range(15):
|
||||
+ log.info(f"Generating load for uncompressed rotation {i+1}/15")
|
||||
+ generate_heavy_load(inst, suffix, iterations=150)
|
||||
+ time.sleep(1)
|
||||
+
|
||||
+ time.sleep(2)
|
||||
+
|
||||
+ # Check what we have so far
|
||||
+ uncompressed_files = glob.glob(f'{log_dir}/access.2*')
|
||||
+ log.info(f"Files on disk after uncompressed phase: {uncompressed_files}")
|
||||
+
|
||||
+ inst.config.set('nsslapd-accesslog-compress', 'on')
|
||||
+ inst.restart()
|
||||
+ time.sleep(2)
|
||||
+
|
||||
+ for i in range(15):
|
||||
+ log.info(f"Generating load for compressed rotation {i+1}/15")
|
||||
+ generate_heavy_load(inst, suffix, iterations=150)
|
||||
+ time.sleep(1)
|
||||
+
|
||||
+ time.sleep(3)
|
||||
+
|
||||
+ accesslog_list = inst.config.get_attr_vals_utf8('nsslapd-accesslog-list')
|
||||
+
|
||||
+ disk_files = glob.glob(f'{log_dir}/access.2*')
|
||||
+
|
||||
+ log.info(f"nsslapd-accesslog-list entries: {len(accesslog_list)}")
|
||||
+ log.info(f"nsslapd-accesslog-list: {sorted(accesslog_list)}")
|
||||
+ log.info(f"Actual files on disk: {len(disk_files)}")
|
||||
+ log.info(f"Disk files: {sorted(disk_files)}")
|
||||
+
|
||||
+ compressed_on_disk = [f for f in disk_files if f.endswith('.gz')]
|
||||
+ uncompressed_on_disk = [f for f in disk_files if not f.endswith('.gz')]
|
||||
+ log.info(f"Compressed files on disk: {compressed_on_disk}")
|
||||
+ log.info(f"Uncompressed files on disk: {uncompressed_on_disk}")
|
||||
+
|
||||
+ list_files_set = set(accesslog_list)
|
||||
+
|
||||
+ disk_files_base = set()
|
||||
+ for fpath in disk_files:
|
||||
+ if fpath.endswith('.gz'):
|
||||
+ disk_files_base.add(fpath[:-3]) # Strip .gz
|
||||
+ else:
|
||||
+ disk_files_base.add(fpath)
|
||||
+
|
||||
+ missing_from_disk = list_files_set - disk_files_base
|
||||
+
|
||||
+ extra_on_disk = disk_files_base - list_files_set
|
||||
+
|
||||
+ if missing_from_disk:
|
||||
+ log.error(f"Files in list but NOT on disk: {missing_from_disk}")
|
||||
+ if extra_on_disk:
|
||||
+ log.warning(f"Files on disk but NOT in list: {extra_on_disk}")
|
||||
+
|
||||
+ assert not missing_from_disk, (
|
||||
+ f"nsslapd-accesslog-list contains stale entries! "
|
||||
+ f"Files in list but not on disk (as base or .gz): {missing_from_disk}"
|
||||
+ )
|
||||
+
|
||||
+ for list_file in accesslog_list:
|
||||
+ exists_uncompressed = os.path.exists(list_file)
|
||||
+ exists_compressed = os.path.exists(list_file + '.gz')
|
||||
+ assert exists_uncompressed or exists_compressed, (
|
||||
+ f"File in accesslog-list does not exist on disk: {list_file} "
|
||||
+ f"(checked both {list_file} and {list_file}.gz)"
|
||||
+ )
|
||||
+ if exists_compressed and not exists_uncompressed:
|
||||
+ log.info(f" {list_file} -> exists as .gz (compressed)")
|
||||
+ elif exists_uncompressed:
|
||||
+ log.info(f" {list_file} -> exists (uncompressed)")
|
||||
+
|
||||
+ if len(extra_on_disk) > 1:
|
||||
+ log.warning(
|
||||
+ f"Some files on disk are not tracked in accesslog-list: {extra_on_disk}"
|
||||
+ )
|
||||
+
|
||||
+ log.info("Mixed compression test completed successfully")
|
||||
+
|
||||
+
|
||||
def test_log_flush_and_rotation_crash(topo):
|
||||
- """Make sure server does not crash whening flushing a buffer and rotating
|
||||
+ """Make sure server does not crash when flushing a buffer and rotating
|
||||
the log at the same time
|
||||
|
||||
:id: d4b0af2f-48b2-45f5-ae8b-f06f692c3133
|
||||
@@ -36,6 +374,7 @@ def test_log_flush_and_rotation_crash(topo):
|
||||
3. Success
|
||||
4. Success
|
||||
"""
|
||||
+ # NOTE: This test is placed last as it may affect the suffix state.
|
||||
|
||||
inst = topo.standalone
|
||||
|
||||
diff --git a/ldap/servers/slapd/log.c b/ldap/servers/slapd/log.c
|
||||
index 6f57a3d9c..ca8d481e5 100644
|
||||
--- a/ldap/servers/slapd/log.c
|
||||
+++ b/ldap/servers/slapd/log.c
|
||||
@@ -135,6 +135,7 @@ static void vslapd_log_emergency_error(LOGFD fp, const char *msg, int locked);
|
||||
static int get_syslog_loglevel(int loglevel);
|
||||
static void log_external_libs_debug_openldap_print(char *buffer);
|
||||
static int log__fix_rotationinfof(char *pathname);
|
||||
+static int log__validate_rotated_logname(const char *timestamp_str, PRBool *is_compressed);
|
||||
|
||||
static int
|
||||
get_syslog_loglevel(int loglevel)
|
||||
@@ -410,7 +411,7 @@ g_log_init()
|
||||
loginfo.log_security_fdes = NULL;
|
||||
loginfo.log_security_file = NULL;
|
||||
loginfo.log_securityinfo_file = NULL;
|
||||
- loginfo.log_numof_access_logs = 1;
|
||||
+ loginfo.log_numof_security_logs = 1;
|
||||
loginfo.log_security_logchain = NULL;
|
||||
loginfo.log_security_buffer = log_create_buffer(LOG_BUFFER_MAXSIZE);
|
||||
loginfo.log_security_compress = cfg->securitylog_compress;
|
||||
@@ -3311,7 +3312,7 @@ log__open_accesslogfile(int logfile_state, int locked)
|
||||
}
|
||||
} else if (loginfo.log_access_compress) {
|
||||
if (compress_log_file(newfile, loginfo.log_access_mode) != 0) {
|
||||
- slapi_log_err(SLAPI_LOG_ERR, "log__open_auditfaillogfile",
|
||||
+ slapi_log_err(SLAPI_LOG_ERR, "log__open_accesslogfile",
|
||||
"failed to compress rotated access log (%s)\n",
|
||||
newfile);
|
||||
} else {
|
||||
@@ -4710,6 +4711,50 @@ log__delete_rotated_logs()
|
||||
loginfo.log_error_logchain = NULL;
|
||||
}
|
||||
|
||||
+/*
|
||||
+ * log__validate_rotated_logname
|
||||
+ *
|
||||
+ * Validates that a log filename timestamp suffix matches the expected format:
|
||||
+ * YYYYMMDD-HHMMSS (15 chars) or YYYYMMDD-HHMMSS.gz (18 chars) for compressed files.
|
||||
+ * Uses regex pattern: ^[0-9]{8}-[0-9]{6}(\.gz)?$
|
||||
+ *
|
||||
+ * \param timestamp_str The timestamp portion of the log filename (after the first '.')
|
||||
+ * \param is_compressed Output parameter set to PR_TRUE if the file has .gz suffix
|
||||
+ * \return 1 if valid, 0 if invalid
|
||||
+ */
|
||||
+static int
|
||||
+log__validate_rotated_logname(const char *timestamp_str, PRBool *is_compressed)
|
||||
+{
|
||||
+ Slapi_Regex *re = NULL;
|
||||
+ char *re_error = NULL;
|
||||
+ int rc = 0;
|
||||
+
|
||||
+ /* Match YYYYMMDD-HHMMSS with optional .gz suffix */
|
||||
+ static const char *pattern = "^[0-9]{8}-[0-9]{6}(\\.gz)?$";
|
||||
+
|
||||
+ *is_compressed = PR_FALSE;
|
||||
+
|
||||
+ re = slapi_re_comp(pattern, &re_error);
|
||||
+ if (re == NULL) {
|
||||
+ slapi_log_err(SLAPI_LOG_ERR, "log__validate_rotated_logname",
|
||||
+ "Failed to compile regex: %s\n", re_error ? re_error : "unknown error");
|
||||
+ slapi_ch_free_string(&re_error);
|
||||
+ return 0;
|
||||
+ }
|
||||
+
|
||||
+ rc = slapi_re_exec_nt(re, timestamp_str);
|
||||
+ if (rc == 1) {
|
||||
+ /* Check if compressed by looking for .gz suffix */
|
||||
+ size_t len = strlen(timestamp_str);
|
||||
+ if (len >= 3 && strcmp(timestamp_str + len - 3, ".gz") == 0) {
|
||||
+ *is_compressed = PR_TRUE;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ slapi_re_free(re);
|
||||
+ return rc == 1 ? 1 : 0;
|
||||
+}
|
||||
+
|
||||
#define ERRORSLOG 1
|
||||
#define ACCESSLOG 2
|
||||
#define AUDITLOG 3
|
||||
@@ -4792,31 +4837,19 @@ log__fix_rotationinfof(char *pathname)
|
||||
}
|
||||
} else if (0 == strncmp(log_type, dirent->name, strlen(log_type)) &&
|
||||
(p = strchr(dirent->name, '.')) != NULL &&
|
||||
- NULL != strchr(p, '-')) /* e.g., errors.20051123-165135 */
|
||||
+ NULL != strchr(p, '-')) /* e.g., errors.20051123-165135 or errors.20051123-165135.gz */
|
||||
{
|
||||
struct logfileinfo *logp;
|
||||
- char *q;
|
||||
- int ignoreit = 0;
|
||||
-
|
||||
- for (q = ++p; q && *q; q++) {
|
||||
- if (*q != '-' &&
|
||||
- *q != '.' && /* .gz */
|
||||
- *q != 'g' &&
|
||||
- *q != 'z' &&
|
||||
- !isdigit(*q))
|
||||
- {
|
||||
- ignoreit = 1;
|
||||
- }
|
||||
- }
|
||||
- if (ignoreit || (q - p != 15)) {
|
||||
+ PRBool is_compressed = PR_FALSE;
|
||||
+
|
||||
+ /* Skip the '.' to get the timestamp portion */
|
||||
+ p++;
|
||||
+ if (!log__validate_rotated_logname(p, &is_compressed)) {
|
||||
continue;
|
||||
}
|
||||
logp = (struct logfileinfo *)slapi_ch_malloc(sizeof(struct logfileinfo));
|
||||
logp->l_ctime = log_reverse_convert_time(p);
|
||||
- logp->l_compressed = PR_FALSE;
|
||||
- if (strcmp(p + strlen(p) - 3, ".gz") == 0) {
|
||||
- logp->l_compressed = PR_TRUE;
|
||||
- }
|
||||
+ logp->l_compressed = is_compressed;
|
||||
PR_snprintf(rotated_log, rotated_log_len, "%s/%s",
|
||||
logsdir, dirent->name);
|
||||
|
||||
@@ -4982,23 +5015,15 @@ log__check_prevlogs(FILE *fp, char *pathname)
|
||||
for (dirent = PR_ReadDir(dirptr, dirflags); dirent;
|
||||
dirent = PR_ReadDir(dirptr, dirflags)) {
|
||||
if (0 == strncmp(log_type, dirent->name, strlen(log_type)) &&
|
||||
- (p = strrchr(dirent->name, '.')) != NULL &&
|
||||
- NULL != strchr(p, '-')) { /* e.g., errors.20051123-165135 */
|
||||
- char *q;
|
||||
- int ignoreit = 0;
|
||||
-
|
||||
- for (q = ++p; q && *q; q++) {
|
||||
- if (*q != '-' &&
|
||||
- *q != '.' && /* .gz */
|
||||
- *q != 'g' &&
|
||||
- *q != 'z' &&
|
||||
- !isdigit(*q))
|
||||
- {
|
||||
- ignoreit = 1;
|
||||
- }
|
||||
- }
|
||||
- if (ignoreit || (q - p != 15))
|
||||
+ (p = strchr(dirent->name, '.')) != NULL &&
|
||||
+ NULL != strchr(p, '-')) { /* e.g., errors.20051123-165135 or errors.20051123-165135.gz */
|
||||
+ PRBool is_compressed = PR_FALSE;
|
||||
+
|
||||
+ /* Skip the '.' to get the timestamp portion */
|
||||
+ p++;
|
||||
+ if (!log__validate_rotated_logname(p, &is_compressed)) {
|
||||
continue;
|
||||
+ }
|
||||
|
||||
fseek(fp, 0, SEEK_SET);
|
||||
buf[BUFSIZ - 1] = '\0';
|
||||
--
|
||||
2.52.0
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,93 @@
|
||||
From 524cae18721234e1bc8d958c89684008d62e6b30 Mon Sep 17 00:00:00 2001
|
||||
From: James Chapman <jachapma@redhat.com>
|
||||
Date: Thu, 5 Feb 2026 15:33:08 +0000
|
||||
Subject: [PATCH] Issue 7224 - CI Test - Simplify
|
||||
test_reserve_descriptor_validation (#7225)
|
||||
|
||||
Description:
|
||||
Previously, the test_reserve_descriptor_validation CItest calculated
|
||||
the expected number of file descriptors based on backends, indexes,
|
||||
SSL/FIPS mode, and compared it to the value returned by the server.
|
||||
This approach is fragile, especially in FIPS mode.
|
||||
|
||||
Fix:
|
||||
The test has been updated to simply verify that the server corrects
|
||||
the configured nsslapd-reservedescriptors value if it is set too low,
|
||||
instead of calculating the expected total.
|
||||
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/7224
|
||||
|
||||
Reviewed by: @bsimonova (Thank you)
|
||||
---
|
||||
.../suites/resource_limits/fdlimits_test.py | 36 +++++++------------
|
||||
1 file changed, 13 insertions(+), 23 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/resource_limits/fdlimits_test.py b/dirsrvtests/tests/suites/resource_limits/fdlimits_test.py
|
||||
index edcae28a5..9d8fdec52 100644
|
||||
--- a/dirsrvtests/tests/suites/resource_limits/fdlimits_test.py
|
||||
+++ b/dirsrvtests/tests/suites/resource_limits/fdlimits_test.py
|
||||
@@ -27,7 +27,7 @@ RESRV_FD_ATTR = "nsslapd-reservedescriptors"
|
||||
GLOBAL_LIMIT = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
|
||||
SYSTEMD_LIMIT = ensure_str(check_output("systemctl show -p LimitNOFILE dirsrv@standalone1".split(" ")).strip()).split('=')[1]
|
||||
CUSTOM_VAL = str(int(SYSTEMD_LIMIT) - 10)
|
||||
-RESRV_DESC_VAL = str(10)
|
||||
+RESRV_DESC_VAL_LOW = 10
|
||||
TOO_HIGH_VAL = str(GLOBAL_LIMIT * 2)
|
||||
TOO_HIGH_VAL2 = str(int(SYSTEMD_LIMIT) * 2)
|
||||
TOO_LOW_VAL = "0"
|
||||
@@ -86,40 +86,30 @@ def test_reserve_descriptor_validation(topology_st):
|
||||
:id: 9bacdbcc-7754-4955-8a56-1d8c82bce274
|
||||
:setup: Standalone Instance
|
||||
:steps:
|
||||
- 1. Set attr nsslapd-reservedescriptors to a low value of RESRV_DESC_VAL (10)
|
||||
+ 1. Set attr nsslapd-reservedescriptors to a low value (10)
|
||||
2. Verify low value has been set
|
||||
3. Restart instance (On restart the reservedescriptor attr will be validated)
|
||||
- 4. Check updated value for nsslapd-reservedescriptors attr
|
||||
+ 4. Verify corrected value for nsslapd-reservedescriptors > low value
|
||||
:expectedresults:
|
||||
1. Success
|
||||
- 2. A value of RESRV_DESC_VAL (10) is returned
|
||||
+ 2. A value of RESRV_DESC_VAL_LOW (10) is returned
|
||||
3. Success
|
||||
- 4. A value of STANDALONE_INST_RESRV_DESCS (55) is returned
|
||||
+ 4. Corrected value for nsslapd-reservedescriptors > low value
|
||||
"""
|
||||
|
||||
- # Set nsslapd-reservedescriptors to a low value (RESRV_DESC_VAL:10)
|
||||
- topology_st.standalone.config.set(RESRV_FD_ATTR, RESRV_DESC_VAL)
|
||||
- resrv_fd = topology_st.standalone.config.get_attr_val_utf8(RESRV_FD_ATTR)
|
||||
- assert resrv_fd == RESRV_DESC_VAL
|
||||
+ # Set nsslapd-reservedescriptors to a low value (10)
|
||||
+ topology_st.standalone.config.set(RESRV_FD_ATTR, str(RESRV_DESC_VAL_LOW))
|
||||
+ resrv_fd = int(topology_st.standalone.config.get_attr_val_utf8(RESRV_FD_ATTR))
|
||||
+ assert resrv_fd == RESRV_DESC_VAL_LOW
|
||||
|
||||
# An instance restart triggers a validation of the configured nsslapd-reservedescriptors attribute
|
||||
topology_st.standalone.restart()
|
||||
|
||||
- """
|
||||
- A standalone instance contains a single backend with default indexes
|
||||
- so we only check these. TODO add tests for repl, chaining, PTA, SSL
|
||||
- """
|
||||
- STANDALONE_INST_RESRV_DESCS = 20 # 20 = Reserve descriptor constant
|
||||
- backends = Backends(topology_st.standalone)
|
||||
- STANDALONE_INST_RESRV_DESCS += (len(backends.list()) * 4) # 4 = Backend descriptor constant
|
||||
- for be in backends.list() :
|
||||
- STANDALONE_INST_RESRV_DESCS += len(be.get_indexes().list())
|
||||
-
|
||||
- # Varify reservedescriptors has been updated
|
||||
- resrv_fd = topology_st.standalone.config.get_attr_val_utf8(RESRV_FD_ATTR)
|
||||
- assert resrv_fd == str(STANDALONE_INST_RESRV_DESCS)
|
||||
+ # Get the corrected value
|
||||
+ corrected_fd = int(topology_st.standalone.config.get_attr_val_utf8(RESRV_FD_ATTR))
|
||||
+ assert corrected_fd > RESRV_DESC_VAL_LOW
|
||||
|
||||
- log.info("test_reserve_descriptor_validation PASSED")
|
||||
+ log.info(f"test_reserve_descriptor_validation PASSED (corrected from {RESRV_DESC_VAL_LOW} to {corrected_fd})")
|
||||
|
||||
@pytest.mark.skipif(ds_is_older("1.4.1.2"), reason="Not implemented")
|
||||
def test_reserve_descriptors_high(topology_st):
|
||||
--
|
||||
2.52.0
|
||||
|
||||
@ -1,236 +0,0 @@
|
||||
From 1845aed98becaba6b975342229cb5e0de79d208d Mon Sep 17 00:00:00 2001
|
||||
From: James Chapman <jachapma@redhat.com>
|
||||
Date: Wed, 29 Jan 2025 17:41:55 +0000
|
||||
Subject: [PATCH] Issue 6436 - MOD on a large group slow if substring index is
|
||||
present (#6437)
|
||||
|
||||
Bug Description: If the substring index is configured for the group
|
||||
membership attribute ( member or uniqueMember ), the removal of a
|
||||
member from a large static group is pretty slow.
|
||||
|
||||
Fix Description: A solution to this issue would be to introduce
|
||||
a new index to track a membership atttribute index. In the interm,
|
||||
we add a check to healthcheck to inform the user of the implications
|
||||
of this configuration.
|
||||
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/6436
|
||||
|
||||
Reviewed by: @Firstyear, @tbordaz, @droideck (Thanks)
|
||||
---
|
||||
.../suites/healthcheck/health_config_test.py | 89 ++++++++++++++++++-
|
||||
src/lib389/lib389/lint.py | 15 ++++
|
||||
src/lib389/lib389/plugins.py | 37 +++++++-
|
||||
3 files changed, 137 insertions(+), 4 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/healthcheck/health_config_test.py b/dirsrvtests/tests/suites/healthcheck/health_config_test.py
|
||||
index 6d3d08bfa..747699486 100644
|
||||
--- a/dirsrvtests/tests/suites/healthcheck/health_config_test.py
|
||||
+++ b/dirsrvtests/tests/suites/healthcheck/health_config_test.py
|
||||
@@ -212,6 +212,7 @@ def test_healthcheck_RI_plugin_missing_indexes(topology_st):
|
||||
MEMBER_DN = 'cn=member,cn=index,cn=userroot,cn=ldbm database,cn=plugins,cn=config'
|
||||
|
||||
standalone = topology_st.standalone
|
||||
+ standalone.config.set("nsslapd-accesslog-logbuffering", "on")
|
||||
|
||||
log.info('Enable RI plugin')
|
||||
plugin = ReferentialIntegrityPlugin(standalone)
|
||||
@@ -233,7 +234,7 @@ def test_healthcheck_RI_plugin_missing_indexes(topology_st):
|
||||
|
||||
|
||||
def test_healthcheck_MO_plugin_missing_indexes(topology_st):
|
||||
- """Check if HealthCheck returns DSMOLE0002 code
|
||||
+ """Check if HealthCheck returns DSMOLE0001 code
|
||||
|
||||
:id: 236b0ec2-13da-48fb-b65a-db7406d56d5d
|
||||
:setup: Standalone instance
|
||||
@@ -248,8 +249,8 @@ def test_healthcheck_MO_plugin_missing_indexes(topology_st):
|
||||
:expectedresults:
|
||||
1. Success
|
||||
2. Success
|
||||
- 3. Healthcheck reports DSMOLE0002 code and related details
|
||||
- 4. Healthcheck reports DSMOLE0002 code and related details
|
||||
+ 3. Healthcheck reports DSMOLE0001 code and related details
|
||||
+ 4. Healthcheck reports DSMOLE0001 code and related details
|
||||
5. Success
|
||||
6. Healthcheck reports no issue found
|
||||
7. Healthcheck reports no issue found
|
||||
@@ -259,6 +260,7 @@ def test_healthcheck_MO_plugin_missing_indexes(topology_st):
|
||||
MO_GROUP_ATTR = 'creatorsname'
|
||||
|
||||
standalone = topology_st.standalone
|
||||
+ standalone.config.set("nsslapd-accesslog-logbuffering", "on")
|
||||
|
||||
log.info('Enable MO plugin')
|
||||
plugin = MemberOfPlugin(standalone)
|
||||
@@ -279,6 +281,87 @@ def test_healthcheck_MO_plugin_missing_indexes(topology_st):
|
||||
run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
|
||||
|
||||
|
||||
+def test_healthcheck_MO_plugin_substring_index(topology_st):
|
||||
+ """Check if HealthCheck returns DSMOLE0002 code when the
|
||||
+ member, uniquemember attribute contains a substring index type
|
||||
+
|
||||
+ :id: 10954811-24ac-4886-8183-e30892f8e02d
|
||||
+ :setup: Standalone instance
|
||||
+ :steps:
|
||||
+ 1. Create DS instance
|
||||
+ 2. Configure the instance with MO Plugin
|
||||
+ 3. Change index type to substring for member attribute
|
||||
+ 4. Use HealthCheck without --json option
|
||||
+ 5. Use HealthCheck with --json option
|
||||
+ 6. Change index type back to equality for member attribute
|
||||
+ 7. Use HealthCheck without --json option
|
||||
+ 8. Use HealthCheck with --json option
|
||||
+ 9. Change index type to substring for uniquemember attribute
|
||||
+ 10. Use HealthCheck without --json option
|
||||
+ 11. Use HealthCheck with --json option
|
||||
+ 12. Change index type back to equality for uniquemember attribute
|
||||
+ 13. Use HealthCheck without --json option
|
||||
+ 14. Use HealthCheck with --json option
|
||||
+
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Success
|
||||
+ 4. Healthcheck reports DSMOLE0002 code and related details
|
||||
+ 5. Healthcheck reports DSMOLE0002 code and related details
|
||||
+ 6. Success
|
||||
+ 7. Healthcheck reports no issue found
|
||||
+ 8. Healthcheck reports no issue found
|
||||
+ 9. Success
|
||||
+ 10. Healthcheck reports DSMOLE0002 code and related details
|
||||
+ 11. Healthcheck reports DSMOLE0002 code and related details
|
||||
+ 12. Success
|
||||
+ 13. Healthcheck reports no issue found
|
||||
+ 14. Healthcheck reports no issue found
|
||||
+ """
|
||||
+
|
||||
+ RET_CODE = 'DSMOLE0002'
|
||||
+ MEMBER_DN = 'cn=member,cn=index,cn=userroot,cn=ldbm database,cn=plugins,cn=config'
|
||||
+ UNIQUE_MEMBER_DN = 'cn=uniquemember,cn=index,cn=userroot,cn=ldbm database,cn=plugins,cn=config'
|
||||
+
|
||||
+ standalone = topology_st.standalone
|
||||
+ standalone.config.set("nsslapd-accesslog-logbuffering", "on")
|
||||
+
|
||||
+ log.info('Enable MO plugin')
|
||||
+ plugin = MemberOfPlugin(standalone)
|
||||
+ plugin.disable()
|
||||
+ plugin.enable()
|
||||
+
|
||||
+ log.info('Change the index type of the member attribute index to substring')
|
||||
+ index = Index(topology_st.standalone, MEMBER_DN)
|
||||
+ index.replace('nsIndexType', 'sub')
|
||||
+
|
||||
+ run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=RET_CODE)
|
||||
+ run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=RET_CODE)
|
||||
+
|
||||
+ log.info('Set the index type of the member attribute index back to eq')
|
||||
+ index.replace('nsIndexType', 'eq')
|
||||
+
|
||||
+ run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT)
|
||||
+ run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
|
||||
+
|
||||
+ log.info('Change the index type of the uniquemember attribute index to substring')
|
||||
+ index = Index(topology_st.standalone, UNIQUE_MEMBER_DN)
|
||||
+ index.replace('nsIndexType', 'sub')
|
||||
+
|
||||
+ run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=RET_CODE)
|
||||
+ run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=RET_CODE)
|
||||
+
|
||||
+ log.info('Set the index type of the uniquemember attribute index back to eq')
|
||||
+ index.replace('nsIndexType', 'eq')
|
||||
+
|
||||
+ run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT)
|
||||
+ run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
|
||||
+
|
||||
+ # Restart the instance after changing the plugin to avoid breaking the other tests
|
||||
+ standalone.restart()
|
||||
+
|
||||
+
|
||||
@pytest.mark.ds50873
|
||||
@pytest.mark.bz1685160
|
||||
@pytest.mark.xfail(ds_is_older("1.4.1"), reason="Not implemented")
|
||||
diff --git a/src/lib389/lib389/lint.py b/src/lib389/lib389/lint.py
|
||||
index 4d9cbb666..3d3c79ea3 100644
|
||||
--- a/src/lib389/lib389/lint.py
|
||||
+++ b/src/lib389/lib389/lint.py
|
||||
@@ -231,6 +231,21 @@ database after adding the missing index type. Here is an example using dsconf:
|
||||
"""
|
||||
}
|
||||
|
||||
+DSMOLE0002 = {
|
||||
+ 'dsle': 'DSMOLE0002',
|
||||
+ 'severity': 'LOW',
|
||||
+ 'description': 'Removal of a member can be slow ',
|
||||
+ 'items': ['cn=memberof plugin,cn=plugins,cn=config', ],
|
||||
+ 'detail': """If the substring index is configured for a membership attribute. The removal of a member
|
||||
+from the large group can be slow.
|
||||
+
|
||||
+""",
|
||||
+ 'fix': """If not required, you can remove the substring index type using dsconf:
|
||||
+
|
||||
+ # dsconf slapd-YOUR_INSTANCE backend index set --attr=ATTR BACKEND --del-type=sub
|
||||
+"""
|
||||
+}
|
||||
+
|
||||
# Disk Space check. Note - PARTITION is replaced by the calling function
|
||||
DSDSLE0001 = {
|
||||
'dsle': 'DSDSLE0001',
|
||||
diff --git a/src/lib389/lib389/plugins.py b/src/lib389/lib389/plugins.py
|
||||
index 6bf1843ad..185398e5b 100644
|
||||
--- a/src/lib389/lib389/plugins.py
|
||||
+++ b/src/lib389/lib389/plugins.py
|
||||
@@ -12,7 +12,7 @@ import copy
|
||||
import os.path
|
||||
from lib389 import tasks
|
||||
from lib389._mapped_object import DSLdapObjects, DSLdapObject
|
||||
-from lib389.lint import DSRILE0001, DSRILE0002, DSMOLE0001
|
||||
+from lib389.lint import DSRILE0001, DSRILE0002, DSMOLE0001, DSMOLE0002
|
||||
from lib389.utils import ensure_str, ensure_list_bytes
|
||||
from lib389.schema import Schema
|
||||
from lib389._constants import (
|
||||
@@ -827,6 +827,41 @@ class MemberOfPlugin(Plugin):
|
||||
report['check'] = f'memberof:attr_indexes'
|
||||
yield report
|
||||
|
||||
+ def _lint_member_substring_index(self):
|
||||
+ if self.status():
|
||||
+ from lib389.backend import Backends
|
||||
+ backends = Backends(self._instance).list()
|
||||
+ membership_attrs = ['member', 'uniquemember']
|
||||
+ container = self.get_attr_val_utf8_l("nsslapd-plugincontainerscope")
|
||||
+ for backend in backends:
|
||||
+ suffix = backend.get_attr_val_utf8_l('nsslapd-suffix')
|
||||
+ if suffix == "cn=changelog":
|
||||
+ # Always skip retro changelog
|
||||
+ continue
|
||||
+ if container is not None:
|
||||
+ # Check if this backend is in the scope
|
||||
+ if not container.endswith(suffix):
|
||||
+ # skip this backend that is not in the scope
|
||||
+ continue
|
||||
+ indexes = backend.get_indexes()
|
||||
+ for attr in membership_attrs:
|
||||
+ report = copy.deepcopy(DSMOLE0002)
|
||||
+ try:
|
||||
+ index = indexes.get(attr)
|
||||
+ types = index.get_attr_vals_utf8_l("nsIndexType")
|
||||
+ if "sub" in types:
|
||||
+ report['detail'] = report['detail'].replace('ATTR', attr)
|
||||
+ report['detail'] = report['detail'].replace('BACKEND', suffix)
|
||||
+ report['fix'] = report['fix'].replace('ATTR', attr)
|
||||
+ report['fix'] = report['fix'].replace('BACKEND', suffix)
|
||||
+ report['fix'] = report['fix'].replace('YOUR_INSTANCE', self._instance.serverid)
|
||||
+ report['items'].append(suffix)
|
||||
+ report['items'].append(attr)
|
||||
+ report['check'] = f'attr:substring_index'
|
||||
+ yield report
|
||||
+ except KeyError:
|
||||
+ continue
|
||||
+
|
||||
def get_attr(self):
|
||||
"""Get memberofattr attribute"""
|
||||
|
||||
--
|
||||
2.48.1
|
||||
|
||||
@ -0,0 +1,92 @@
|
||||
From 91e03f431d8ee0b1d316ebd7add232bf49360db2 Mon Sep 17 00:00:00 2001
|
||||
From: James Chapman <jachapma@redhat.com>
|
||||
Date: Thu, 12 Feb 2026 10:42:09 +0000
|
||||
Subject: [PATCH] Issue 7231 - Sync repl tests fail in FIPS mode due to non
|
||||
FIPS compliant crypto (#7232)
|
||||
|
||||
Description:
|
||||
Several sync_repl tests fail when running on a FIPS enabled system. The failures
|
||||
are caused by the sync repl client (Sync_persist), using TLS options and ciphers
|
||||
that are not FIPS compatible.
|
||||
|
||||
Fix:
|
||||
Update the sync repl client to use FIPS approved TLS version.
|
||||
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/7231
|
||||
|
||||
Reviewed by: @progier389, @droideck (Thank you)
|
||||
---
|
||||
.../tests/suites/syncrepl_plugin/basic_test.py | 15 +++++++++++++--
|
||||
1 file changed, 13 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/syncrepl_plugin/basic_test.py b/dirsrvtests/tests/suites/syncrepl_plugin/basic_test.py
|
||||
index c22331eb5..1e9a530bb 100644
|
||||
--- a/dirsrvtests/tests/suites/syncrepl_plugin/basic_test.py
|
||||
+++ b/dirsrvtests/tests/suites/syncrepl_plugin/basic_test.py
|
||||
@@ -20,7 +20,7 @@ from lib389.idm.group import Groups
|
||||
from lib389.topologies import topology_st as topology
|
||||
from lib389.topologies import topology_m2 as topo_m2
|
||||
from lib389.paths import Paths
|
||||
-from lib389.utils import ds_is_older
|
||||
+from lib389.utils import ds_is_older, is_fips
|
||||
from lib389.plugins import RetroChangelogPlugin, ContentSyncPlugin, AutoMembershipPlugin, MemberOfPlugin, MemberOfSharedConfig, AutoMembershipDefinitions, MEPTemplates, MEPConfigs, ManagedEntriesPlugin, MEPTemplate
|
||||
from lib389._constants import *
|
||||
|
||||
@@ -214,6 +214,12 @@ class Sync_persist(threading.Thread, ReconnectLDAPObject, SyncreplConsumer):
|
||||
def run(self):
|
||||
"""Start a sync repl client"""
|
||||
ldap_connection = TestSyncer(self.inst.toLDAPURL())
|
||||
+ ldap_connection.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_DEMAND)
|
||||
+ ldap_connection.set_option(ldap.OPT_X_TLS_CACERTFILE, os.path.join(self.inst.get_config_dir(), "ca.crt"))
|
||||
+ if is_fips():
|
||||
+ ldap_connection.set_option(ldap.OPT_X_TLS_PROTOCOL_MIN, ldap.OPT_X_TLS_PROTOCOL_TLS1_2)
|
||||
+ ldap_connection.set_option(ldap.OPT_X_TLS_NEWCTX, 0)
|
||||
+
|
||||
ldap_connection.simple_bind_s('cn=directory manager', 'password')
|
||||
ldap_search = ldap_connection.syncrepl_search(
|
||||
"dc=example,dc=com",
|
||||
@@ -253,6 +259,7 @@ def test_sync_repl_mep(topology, request):
|
||||
5. Success
|
||||
"""
|
||||
inst = topology[0]
|
||||
+ inst.enable_tls()
|
||||
|
||||
# Enable/configure retroCL
|
||||
plugin = RetroChangelogPlugin(inst)
|
||||
@@ -338,6 +345,7 @@ def test_sync_repl_cookie(topology, init_sync_repl_plugins, request):
|
||||
5.: succeeds
|
||||
"""
|
||||
inst = topology[0]
|
||||
+ inst.enable_tls()
|
||||
|
||||
# create a sync repl client and wait 5 seconds to be sure it is running
|
||||
sync_repl = Sync_persist(inst)
|
||||
@@ -404,6 +412,8 @@ def test_sync_repl_cookie_add_del(topology, init_sync_repl_plugins, request):
|
||||
6.: succeeds
|
||||
"""
|
||||
inst = topology[0]
|
||||
+ inst.enable_tls()
|
||||
+
|
||||
# create a sync repl client and wait 5 seconds to be sure it is running
|
||||
sync_repl = Sync_persist(inst)
|
||||
sync_repl.start()
|
||||
@@ -547,6 +557,7 @@ def test_sync_repl_cenotaph(topo_m2, request):
|
||||
5. Should succeeds
|
||||
"""
|
||||
m1 = topo_m2.ms["supplier1"]
|
||||
+ m1.enable_tls()
|
||||
# Enable/configure retroCL
|
||||
plugin = RetroChangelogPlugin(m1)
|
||||
plugin.disable()
|
||||
@@ -605,7 +616,7 @@ def test_sync_repl_dynamic_plugin(topology, request):
|
||||
3. Should succeeds
|
||||
4. Should succeeds
|
||||
"""
|
||||
-
|
||||
+ topology.standalone.enable_tls()
|
||||
# Reset the instance in a default config
|
||||
# Disable content sync plugin
|
||||
topology.standalone.plugins.disable(name=PLUGIN_REPL_SYNC)
|
||||
--
|
||||
2.52.0
|
||||
|
||||
@ -1,651 +0,0 @@
|
||||
From dba27e56161943fbcf54ecbc28337e2c81b07979 Mon Sep 17 00:00:00 2001
|
||||
From: progier389 <progier@redhat.com>
|
||||
Date: Mon, 13 Jan 2025 18:03:07 +0100
|
||||
Subject: [PATCH] Issue 6494 - Various errors when using extended matching rule
|
||||
on vlv sort filter (#6495)
|
||||
|
||||
* Issue 6494 - Various errors when using extended matching rule on vlv sort filter
|
||||
|
||||
Various issues when configuring and using extended matching rule within a vlv sort filter:
|
||||
|
||||
Race condition about the keys storage while indexing leading to various heap and data corruption. (lmdb only)
|
||||
Crash while indexing if vlv are misconfigured because NULL key is not checked.
|
||||
Read after block because of data type mismatch between SlapiValue and berval
|
||||
Memory leaks
|
||||
Solution:
|
||||
|
||||
Serialize the vlv index key generation if vlv filter has an extended matching rule.
|
||||
Check null keys
|
||||
Always provides SlapiValue even ifg we want to get keys as bervals
|
||||
Free properly the resources
|
||||
Issue: #6494
|
||||
|
||||
Reviewed by: @mreynolds389 (Thanks!)
|
||||
|
||||
(cherry picked from commit 4bd27ecc4e1d21c8af5ab8cad795d70477179a98)
|
||||
(cherry picked from commit 223a20250cbf29a546dcb398cfc76024d2f91347)
|
||||
(cherry picked from commit 280043740a525eaf0438129fd8b99ca251c62366)
|
||||
---
|
||||
.../tests/suites/indexes/regression_test.py | 29 +++
|
||||
.../tests/suites/vlv/regression_test.py | 183 ++++++++++++++++++
|
||||
ldap/servers/slapd/back-ldbm/cleanup.c | 8 +
|
||||
ldap/servers/slapd/back-ldbm/dblayer.c | 22 ++-
|
||||
ldap/servers/slapd/back-ldbm/ldbm_attr.c | 2 +-
|
||||
ldap/servers/slapd/back-ldbm/matchrule.c | 8 +-
|
||||
.../servers/slapd/back-ldbm/proto-back-ldbm.h | 3 +-
|
||||
ldap/servers/slapd/back-ldbm/sort.c | 37 ++--
|
||||
ldap/servers/slapd/back-ldbm/vlv.c | 26 +--
|
||||
ldap/servers/slapd/back-ldbm/vlv_srch.c | 4 +-
|
||||
ldap/servers/slapd/generation.c | 5 +
|
||||
ldap/servers/slapd/plugin_mr.c | 12 +-
|
||||
src/lib389/lib389/backend.py | 10 +
|
||||
13 files changed, 292 insertions(+), 57 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/indexes/regression_test.py b/dirsrvtests/tests/suites/indexes/regression_test.py
|
||||
index fc6db727f..2196fb2ed 100644
|
||||
--- a/dirsrvtests/tests/suites/indexes/regression_test.py
|
||||
+++ b/dirsrvtests/tests/suites/indexes/regression_test.py
|
||||
@@ -227,6 +227,35 @@ def test_reject_virtual_attr_for_indexing(topo):
|
||||
break
|
||||
|
||||
|
||||
+def test_reindex_extended_matching_rule(topo, add_backend_and_ldif_50K_users):
|
||||
+ """Check that index with extended matching rule are reindexed properly.
|
||||
+
|
||||
+ :id: 8a3198e8-cc5a-11ef-a3e7-482ae39447e5
|
||||
+ :setup: Standalone instance + a second backend with 50K users
|
||||
+ :steps:
|
||||
+ 1. Configure uid with 2.5.13.2 matching rule
|
||||
+ 1. Configure cn with 2.5.13.2 matching rule
|
||||
+ 2. Reindex
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ """
|
||||
+
|
||||
+ inst = topo.standalone
|
||||
+ tasks = Tasks(inst)
|
||||
+ be2 = Backends(topo.standalone).get_backend(SUFFIX2)
|
||||
+ index = be2.get_index('uid')
|
||||
+ index.replace('nsMatchingRule', '2.5.13.2')
|
||||
+ index = be2.get_index('cn')
|
||||
+ index.replace('nsMatchingRule', '2.5.13.2')
|
||||
+
|
||||
+ assert tasks.reindex(
|
||||
+ suffix=SUFFIX2,
|
||||
+ args={TASK_WAIT: True}
|
||||
+ ) == 0
|
||||
+
|
||||
+
|
||||
+
|
||||
if __name__ == "__main__":
|
||||
# Run isolated
|
||||
# -s for DEBUG mode
|
||||
diff --git a/dirsrvtests/tests/suites/vlv/regression_test.py b/dirsrvtests/tests/suites/vlv/regression_test.py
|
||||
index 3b66de8b5..6ab709bd3 100644
|
||||
--- a/dirsrvtests/tests/suites/vlv/regression_test.py
|
||||
+++ b/dirsrvtests/tests/suites/vlv/regression_test.py
|
||||
@@ -22,6 +22,146 @@ logging.getLogger(__name__).setLevel(logging.DEBUG)
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
+class BackendHandler:
|
||||
+ def __init__(self, inst, bedict, scope=ldap.SCOPE_ONELEVEL):
|
||||
+ self.inst = inst
|
||||
+ self.bedict = bedict
|
||||
+ self.bes = Backends(inst)
|
||||
+ self.scope = scope
|
||||
+ self.data = {}
|
||||
+
|
||||
+ def find_backend(self, bename):
|
||||
+ for be in self.bes.list():
|
||||
+ if be.get_attr_val_utf8_l('cn') == bename:
|
||||
+ return be
|
||||
+ return None
|
||||
+
|
||||
+ def cleanup(self):
|
||||
+ benames = list(self.bedict.keys())
|
||||
+ benames.reverse()
|
||||
+ for bename in benames:
|
||||
+ be = self.find_backend(bename)
|
||||
+ if be:
|
||||
+ be.delete()
|
||||
+
|
||||
+ def setup(self):
|
||||
+ # Create backends, add vlv index and populate the backends.
|
||||
+ for bename,suffix in self.bedict.items():
|
||||
+ be = self.bes.create(properties={
|
||||
+ 'cn': bename,
|
||||
+ 'nsslapd-suffix': suffix,
|
||||
+ })
|
||||
+ # Add suffix entry
|
||||
+ Organization(self.inst, dn=suffix).create(properties={ 'o': bename, })
|
||||
+ # Configure vlv
|
||||
+ vlv_search, vlv_index = create_vlv_search_and_index(
|
||||
+ self.inst, basedn=suffix,
|
||||
+ bename=bename, scope=self.scope,
|
||||
+ prefix=f'vlv_1lvl_{bename}')
|
||||
+ # Reindex
|
||||
+ reindex_task = Tasks(self.inst)
|
||||
+ assert reindex_task.reindex(
|
||||
+ suffix=suffix,
|
||||
+ attrname=vlv_index.rdn,
|
||||
+ args={TASK_WAIT: True},
|
||||
+ vlv=True
|
||||
+ ) == 0
|
||||
+ # Add ou=People entry
|
||||
+ OrganizationalUnits(self.inst, suffix).create(properties={'ou': 'People'})
|
||||
+ # Add another ou that will be deleted before the export
|
||||
+ # so that import will change the vlv search basedn entryid
|
||||
+ ou2 = OrganizationalUnits(self.inst, suffix).create(properties={'ou': 'dummy ou'})
|
||||
+ # Add a demo user so that vlv_check is happy
|
||||
+ dn = f'uid=demo_user,ou=people,{suffix}'
|
||||
+ UserAccount(self.inst, dn=dn).create( properties= {
|
||||
+ 'uid': 'demo_user',
|
||||
+ 'cn': 'Demo user',
|
||||
+ 'sn': 'Demo user',
|
||||
+ 'uidNumber': '99998',
|
||||
+ 'gidNumber': '99998',
|
||||
+ 'homeDirectory': '/var/empty',
|
||||
+ 'loginShell': '/bin/false',
|
||||
+ 'userpassword': DEMO_PW })
|
||||
+ # Add regular user
|
||||
+ add_users(self.inst, 10, suffix=suffix)
|
||||
+ # Removing ou2
|
||||
+ ou2.delete()
|
||||
+ # And export
|
||||
+ tasks = Tasks(self.inst)
|
||||
+ ldif = f'{self.inst.get_ldif_dir()}/db-{bename}.ldif'
|
||||
+ assert tasks.exportLDIF(suffix=suffix,
|
||||
+ output_file=ldif,
|
||||
+ args={TASK_WAIT: True}) == 0
|
||||
+ # Add the various parameters in topology_st.belist
|
||||
+ self.data[bename] = { 'be': be,
|
||||
+ 'suffix': suffix,
|
||||
+ 'ldif': ldif,
|
||||
+ 'vlv_search' : vlv_search,
|
||||
+ 'vlv_index' : vlv_index,
|
||||
+ 'dn' : dn}
|
||||
+
|
||||
+
|
||||
+def create_vlv_search_and_index(inst, basedn=DEFAULT_SUFFIX, bename='userRoot',
|
||||
+ scope=ldap.SCOPE_SUBTREE, prefix="vlv", vlvsort="cn"):
|
||||
+ vlv_searches = VLVSearch(inst)
|
||||
+ vlv_search_properties = {
|
||||
+ "objectclass": ["top", "vlvSearch"],
|
||||
+ "cn": f"{prefix}Srch",
|
||||
+ "vlvbase": basedn,
|
||||
+ "vlvfilter": "(uid=*)",
|
||||
+ "vlvscope": str(scope),
|
||||
+ }
|
||||
+ vlv_searches.create(
|
||||
+ basedn=f"cn={bename},cn=ldbm database,cn=plugins,cn=config",
|
||||
+ properties=vlv_search_properties
|
||||
+ )
|
||||
+
|
||||
+ vlv_index = VLVIndex(inst)
|
||||
+ vlv_index_properties = {
|
||||
+ "objectclass": ["top", "vlvIndex"],
|
||||
+ "cn": f"{prefix}Idx",
|
||||
+ "vlvsort": vlvsort,
|
||||
+ }
|
||||
+ vlv_index.create(
|
||||
+ basedn=f"cn={prefix}Srch,cn={bename},cn=ldbm database,cn=plugins,cn=config",
|
||||
+ properties=vlv_index_properties
|
||||
+ )
|
||||
+ return vlv_searches, vlv_index
|
||||
+
|
||||
+
|
||||
+@pytest.fixture
|
||||
+def vlv_setup_with_uid_mr(topology_st, request):
|
||||
+ inst = topology_st.standalone
|
||||
+ bename = 'be1'
|
||||
+ besuffix = f'o={bename}'
|
||||
+ beh = BackendHandler(inst, { bename: besuffix })
|
||||
+
|
||||
+ def fin():
|
||||
+ # Cleanup function
|
||||
+ if not DEBUGGING and inst.exists() and inst.status():
|
||||
+ beh.cleanup()
|
||||
+
|
||||
+ request.addfinalizer(fin)
|
||||
+
|
||||
+ # Make sure that our backend are not already present.
|
||||
+ beh.cleanup()
|
||||
+
|
||||
+ # Then add the new backend
|
||||
+ beh.setup()
|
||||
+
|
||||
+ index = Index(inst, f'cn=uid,cn=index,cn={bename},cn=ldbm database,cn=plugins,cn=config')
|
||||
+ index.add('nsMatchingRule', '2.5.13.2')
|
||||
+ reindex_task = Tasks(inst)
|
||||
+ assert reindex_task.reindex(
|
||||
+ suffix=besuffix,
|
||||
+ attrname='uid',
|
||||
+ args={TASK_WAIT: True}
|
||||
+ ) == 0
|
||||
+
|
||||
+ topology_st.beh = beh
|
||||
+ return topology_st
|
||||
+
|
||||
+
|
||||
@pytest.mark.DS47966
|
||||
def test_bulk_import_when_the_backend_with_vlv_was_recreated(topology_m2):
|
||||
"""
|
||||
@@ -105,6 +245,49 @@ def test_bulk_import_when_the_backend_with_vlv_was_recreated(topology_m2):
|
||||
entries = M2.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, "(objectclass=*)")
|
||||
|
||||
|
||||
+def test_vlv_with_mr(vlv_setup_with_uid_mr):
|
||||
+ """
|
||||
+ Testing vlv having specific matching rule
|
||||
+
|
||||
+ :id: 5e04afe2-beec-11ef-aa84-482ae39447e5
|
||||
+ :setup: Standalone with uid have a matching rule index
|
||||
+ :steps:
|
||||
+ 1. Append vlvIndex entries then vlvSearch entry in the dse.ldif
|
||||
+ 2. Restart the server
|
||||
+ :expectedresults:
|
||||
+ 1. Should Success.
|
||||
+ 2. Should Success.
|
||||
+ """
|
||||
+ inst = vlv_setup_with_uid_mr.standalone
|
||||
+ beh = vlv_setup_with_uid_mr.beh
|
||||
+ bename, besuffix = next(iter(beh.bedict.items()))
|
||||
+ vlv_searches, vlv_index = create_vlv_search_and_index(
|
||||
+ inst, basedn=besuffix, bename=bename,
|
||||
+ vlvsort="uid:2.5.13.2")
|
||||
+ # Reindex the vlv
|
||||
+ reindex_task = Tasks(inst)
|
||||
+ assert reindex_task.reindex(
|
||||
+ suffix=besuffix,
|
||||
+ attrname=vlv_index.rdn,
|
||||
+ args={TASK_WAIT: True},
|
||||
+ vlv=True
|
||||
+ ) == 0
|
||||
+
|
||||
+ inst.restart()
|
||||
+ users = UserAccounts(inst, besuffix)
|
||||
+ user_properties = {
|
||||
+ 'uid': f'a new testuser',
|
||||
+ 'cn': f'a new testuser',
|
||||
+ 'sn': 'user',
|
||||
+ 'uidNumber': '0',
|
||||
+ 'gidNumber': '0',
|
||||
+ 'homeDirectory': 'foo'
|
||||
+ }
|
||||
+ user = users.create(properties=user_properties)
|
||||
+ user.delete()
|
||||
+ assert inst.status()
|
||||
+
|
||||
+
|
||||
if __name__ == "__main__":
|
||||
# Run isolated
|
||||
# -s for DEBUG mode
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/cleanup.c b/ldap/servers/slapd/back-ldbm/cleanup.c
|
||||
index 6b2e9faef..939d8bc4f 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/cleanup.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/cleanup.c
|
||||
@@ -15,12 +15,14 @@
|
||||
|
||||
#include "back-ldbm.h"
|
||||
#include "dblayer.h"
|
||||
+#include "vlv_srch.h"
|
||||
|
||||
int
|
||||
ldbm_back_cleanup(Slapi_PBlock *pb)
|
||||
{
|
||||
struct ldbminfo *li;
|
||||
Slapi_Backend *be;
|
||||
+ struct vlvSearch *nextp;
|
||||
|
||||
slapi_log_err(SLAPI_LOG_TRACE, "ldbm_back_cleanup", "ldbm backend cleaning up\n");
|
||||
slapi_pblock_get(pb, SLAPI_PLUGIN_PRIVATE, &li);
|
||||
@@ -45,6 +47,12 @@ ldbm_back_cleanup(Slapi_PBlock *pb)
|
||||
return 0;
|
||||
}
|
||||
|
||||
+ /* Release the vlv list */
|
||||
+ for (struct vlvSearch *p=be->vlvSearchList; p; p=nextp) {
|
||||
+ nextp = p->vlv_next;
|
||||
+ vlvSearch_delete(&p);
|
||||
+ }
|
||||
+
|
||||
/*
|
||||
* We check if li is NULL. Because of an issue in how we create backends
|
||||
* we share the li and plugin info between many unique backends. This causes
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/dblayer.c b/ldap/servers/slapd/back-ldbm/dblayer.c
|
||||
index 05cc5b891..6b8ce0016 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/dblayer.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/dblayer.c
|
||||
@@ -494,8 +494,12 @@ int
|
||||
dblayer_close(struct ldbminfo *li, int dbmode)
|
||||
{
|
||||
dblayer_private *priv = (dblayer_private *)li->li_dblayer_private;
|
||||
-
|
||||
- return priv->dblayer_close_fn(li, dbmode);
|
||||
+ int rc = priv->dblayer_close_fn(li, dbmode);
|
||||
+ if (rc == 0) {
|
||||
+ /* Clean thread specific data */
|
||||
+ dblayer_destroy_txn_stack();
|
||||
+ }
|
||||
+ return rc;
|
||||
}
|
||||
|
||||
/* Routines for opening and closing random files in the DB_ENV.
|
||||
@@ -621,6 +625,9 @@ dblayer_erase_index_file(backend *be, struct attrinfo *a, PRBool use_lock, int n
|
||||
return 0;
|
||||
}
|
||||
struct ldbminfo *li = (struct ldbminfo *)be->be_database->plg_private;
|
||||
+ if (NULL == li) {
|
||||
+ return 0;
|
||||
+ }
|
||||
dblayer_private *priv = (dblayer_private *)li->li_dblayer_private;
|
||||
|
||||
return priv->dblayer_rm_db_file_fn(be, a, use_lock, no_force_chkpt);
|
||||
@@ -1382,3 +1389,14 @@ dblayer_pop_pvt_txn(void)
|
||||
}
|
||||
return;
|
||||
}
|
||||
+
|
||||
+void
|
||||
+dblayer_destroy_txn_stack(void)
|
||||
+{
|
||||
+ /*
|
||||
+ * Cleanup for the main thread to avoid false/positive leaks from libasan
|
||||
+ * Note: data is freed because PR_SetThreadPrivate calls the
|
||||
+ * dblayer_cleanup_txn_stack callback
|
||||
+ */
|
||||
+ PR_SetThreadPrivate(thread_private_txn_stack, NULL);
|
||||
+}
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_attr.c b/ldap/servers/slapd/back-ldbm/ldbm_attr.c
|
||||
index 708756d3e..70700ca1d 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/ldbm_attr.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/ldbm_attr.c
|
||||
@@ -54,7 +54,7 @@ attrinfo_delete(struct attrinfo **pp)
|
||||
idl_release_private(*pp);
|
||||
(*pp)->ai_key_cmp_fn = NULL;
|
||||
slapi_ch_free((void **)&((*pp)->ai_type));
|
||||
- slapi_ch_free((void **)(*pp)->ai_index_rules);
|
||||
+ charray_free((*pp)->ai_index_rules);
|
||||
slapi_ch_free((void **)&((*pp)->ai_attrcrypt));
|
||||
attr_done(&((*pp)->ai_sattr));
|
||||
attrinfo_delete_idlistinfo(&(*pp)->ai_idlistinfo);
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/matchrule.c b/ldap/servers/slapd/back-ldbm/matchrule.c
|
||||
index 5d516b9f8..5365e8acf 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/matchrule.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/matchrule.c
|
||||
@@ -107,7 +107,7 @@ destroy_matchrule_indexer(Slapi_PBlock *pb)
|
||||
* is destroyed
|
||||
*/
|
||||
int
|
||||
-matchrule_values_to_keys(Slapi_PBlock *pb, struct berval **input_values, struct berval ***output_values)
|
||||
+matchrule_values_to_keys(Slapi_PBlock *pb, Slapi_Value **input_values, struct berval ***output_values)
|
||||
{
|
||||
IFP mrINDEX = NULL;
|
||||
|
||||
@@ -135,10 +135,8 @@ matchrule_values_to_keys_sv(Slapi_PBlock *pb, Slapi_Value **input_values, Slapi_
|
||||
slapi_pblock_get(pb, SLAPI_PLUGIN_MR_INDEX_SV_FN, &mrINDEX);
|
||||
if (NULL == mrINDEX) { /* old school - does not have SV function */
|
||||
int rc;
|
||||
- struct berval **bvi = NULL, **bvo = NULL;
|
||||
- valuearray_get_bervalarray(input_values, &bvi);
|
||||
- rc = matchrule_values_to_keys(pb, bvi, &bvo);
|
||||
- ber_bvecfree(bvi);
|
||||
+ struct berval **bvo = NULL;
|
||||
+ rc = matchrule_values_to_keys(pb, input_values, &bvo);
|
||||
/* note - the indexer owns bvo and will free it when destroyed */
|
||||
valuearray_init_bervalarray(bvo, output_values);
|
||||
/* store output values in SV form - caller expects SLAPI_PLUGIN_MR_KEYS is Slapi_Value** */
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/proto-back-ldbm.h b/ldap/servers/slapd/back-ldbm/proto-back-ldbm.h
|
||||
index d93ff9239..157788fa4 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/proto-back-ldbm.h
|
||||
+++ b/ldap/servers/slapd/back-ldbm/proto-back-ldbm.h
|
||||
@@ -84,6 +84,7 @@ int dblayer_release_index_file(backend *be, struct attrinfo *a, DB *pDB);
|
||||
int dblayer_erase_index_file(backend *be, struct attrinfo *a, PRBool use_lock, int no_force_chkpt);
|
||||
int dblayer_get_id2entry(backend *be, DB **ppDB);
|
||||
int dblayer_release_id2entry(backend *be, DB *pDB);
|
||||
+void dblayer_destroy_txn_stack(void);
|
||||
int dblayer_txn_init(struct ldbminfo *li, back_txn *txn);
|
||||
int dblayer_txn_begin(backend *be, back_txnid parent_txn, back_txn *txn);
|
||||
int dblayer_txn_begin_ext(struct ldbminfo *li, back_txnid parent_txn, back_txn *txn, PRBool use_lock);
|
||||
@@ -560,7 +561,7 @@ int compute_allids_limit(Slapi_PBlock *pb, struct ldbminfo *li);
|
||||
*/
|
||||
int create_matchrule_indexer(Slapi_PBlock **pb, char *matchrule, char *type);
|
||||
int destroy_matchrule_indexer(Slapi_PBlock *pb);
|
||||
-int matchrule_values_to_keys(Slapi_PBlock *pb, struct berval **input_values, struct berval ***output_values);
|
||||
+int matchrule_values_to_keys(Slapi_PBlock *pb, Slapi_Value **input_values, struct berval ***output_values);
|
||||
int matchrule_values_to_keys_sv(Slapi_PBlock *pb, Slapi_Value **input_values, Slapi_Value ***output_values);
|
||||
|
||||
/*
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/sort.c b/ldap/servers/slapd/back-ldbm/sort.c
|
||||
index 70ac60803..196af753f 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/sort.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/sort.c
|
||||
@@ -536,30 +536,18 @@ compare_entries_sv(ID *id_a, ID *id_b, sort_spec *s, baggage_carrier *bc, int *e
|
||||
valuearray_get_bervalarray(valueset_get_valuearray(&attr_b->a_present_values), &value_b);
|
||||
} else {
|
||||
/* Match rule case */
|
||||
- struct berval **actual_value_a = NULL;
|
||||
- struct berval **actual_value_b = NULL;
|
||||
- struct berval **temp_value = NULL;
|
||||
-
|
||||
- valuearray_get_bervalarray(valueset_get_valuearray(&attr_a->a_present_values), &actual_value_a);
|
||||
- valuearray_get_bervalarray(valueset_get_valuearray(&attr_b->a_present_values), &actual_value_b);
|
||||
- matchrule_values_to_keys(this_one->mr_pb, actual_value_a, &temp_value);
|
||||
- /* Now copy it, so the second call doesn't crap on it */
|
||||
- value_a = slapi_ch_bvecdup(temp_value); /* Really, we'd prefer to not call the chXXX variant...*/
|
||||
- matchrule_values_to_keys(this_one->mr_pb, actual_value_b, &value_b);
|
||||
-
|
||||
- if ((actual_value_a && !value_a) ||
|
||||
- (actual_value_b && !value_b)) {
|
||||
- ber_bvecfree(actual_value_a);
|
||||
- ber_bvecfree(actual_value_b);
|
||||
- CACHE_RETURN(&inst->inst_cache, &a);
|
||||
- CACHE_RETURN(&inst->inst_cache, &b);
|
||||
- *error = 1;
|
||||
- return 0;
|
||||
+ Slapi_Value **va_a = valueset_get_valuearray(&attr_a->a_present_values);
|
||||
+ Slapi_Value **va_b = valueset_get_valuearray(&attr_b->a_present_values);
|
||||
+
|
||||
+ matchrule_values_to_keys(this_one->mr_pb, va_a, &value_a);
|
||||
+ /* Plugin owns the memory ==> duplicate the key before next call garble it */
|
||||
+ value_a = slapi_ch_bvecdup(value_a);
|
||||
+ matchrule_values_to_keys(this_one->mr_pb, va_b, &value_b);
|
||||
+
|
||||
+ if ((va_a && !value_a) || (va_b && !value_b)) {
|
||||
+ result = 0;
|
||||
+ goto bail;
|
||||
}
|
||||
- if (actual_value_a)
|
||||
- ber_bvecfree(actual_value_a);
|
||||
- if (actual_value_b)
|
||||
- ber_bvecfree(actual_value_b);
|
||||
}
|
||||
/* Compare them */
|
||||
if (!order) {
|
||||
@@ -582,9 +570,10 @@ compare_entries_sv(ID *id_a, ID *id_b, sort_spec *s, baggage_carrier *bc, int *e
|
||||
}
|
||||
/* If so, proceed to the next attribute for comparison */
|
||||
}
|
||||
+ *error = 0;
|
||||
+bail:
|
||||
CACHE_RETURN(&inst->inst_cache, &a);
|
||||
CACHE_RETURN(&inst->inst_cache, &b);
|
||||
- *error = 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/vlv.c b/ldap/servers/slapd/back-ldbm/vlv.c
|
||||
index 121fb3667..70e0bac85 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/vlv.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/vlv.c
|
||||
@@ -605,7 +605,7 @@ vlv_getindices(IFP callback_fn, void *param, backend *be)
|
||||
* generate the same composite key, so we append the EntryID
|
||||
* to ensure the uniqueness of the key.
|
||||
*
|
||||
- * Always creates a key. Never returns NULL.
|
||||
+ * May return NULL in case of errors (typically in some configuration error cases)
|
||||
*/
|
||||
static struct vlv_key *
|
||||
vlv_create_key(struct vlvIndex *p, struct backentry *e)
|
||||
@@ -659,10 +659,8 @@ vlv_create_key(struct vlvIndex *p, struct backentry *e)
|
||||
/* Matching rule. Do the magic mangling. Plugin owns the memory. */
|
||||
if (p->vlv_mrpb[sortattr] != NULL) {
|
||||
/* xxxPINAKI */
|
||||
- struct berval **bval = NULL;
|
||||
Slapi_Value **va = valueset_get_valuearray(&attr->a_present_values);
|
||||
- valuearray_get_bervalarray(va, &bval);
|
||||
- matchrule_values_to_keys(p->vlv_mrpb[sortattr], bval, &value);
|
||||
+ matchrule_values_to_keys(p->vlv_mrpb[sortattr], va, &value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -779,6 +777,13 @@ do_vlv_update_index(back_txn *txn, struct ldbminfo *li __attribute__((unused)),
|
||||
}
|
||||
|
||||
key = vlv_create_key(pIndex, entry);
|
||||
+ if (key == NULL) {
|
||||
+ slapi_log_err(SLAPI_LOG_ERR, "vlv_create_key", "Unable to generate vlv %s index key."
|
||||
+ " There may be a configuration issue.\n", pIndex->vlv_name);
|
||||
+ dblayer_release_index_file(be, pIndex->vlv_attrinfo, db);
|
||||
+ return rc;
|
||||
+ }
|
||||
+
|
||||
if (NULL != txn) {
|
||||
db_txn = txn->back_txn_txn;
|
||||
} else {
|
||||
@@ -949,11 +954,11 @@ vlv_create_matching_rule_value(Slapi_PBlock *pb, struct berval *original_value)
|
||||
struct berval **value = NULL;
|
||||
if (pb != NULL) {
|
||||
struct berval **outvalue = NULL;
|
||||
- struct berval *invalue[2];
|
||||
- invalue[0] = original_value; /* jcm: cast away const */
|
||||
- invalue[1] = NULL;
|
||||
+ Slapi_Value v_in = {0};
|
||||
+ Slapi_Value *va_in[2] = { &v_in, NULL };
|
||||
+ slapi_value_init_berval(&v_in, original_value);
|
||||
/* The plugin owns the memory it returns in outvalue */
|
||||
- matchrule_values_to_keys(pb, invalue, &outvalue);
|
||||
+ matchrule_values_to_keys(pb, va_in, &outvalue);
|
||||
if (outvalue != NULL) {
|
||||
value = slapi_ch_bvecdup(outvalue);
|
||||
}
|
||||
@@ -1610,11 +1615,8 @@ retry:
|
||||
PRBool needFree = PR_FALSE;
|
||||
|
||||
if (sort_control->mr_pb != NULL) {
|
||||
- struct berval **tmp_entry_value = NULL;
|
||||
-
|
||||
- valuearray_get_bervalarray(csn_value, &tmp_entry_value);
|
||||
/* Matching rule. Do the magic mangling. Plugin owns the memory. */
|
||||
- matchrule_values_to_keys(sort_control->mr_pb, /* xxxPINAKI needs modification attr->a_vals */ tmp_entry_value, &entry_value);
|
||||
+ matchrule_values_to_keys(sort_control->mr_pb, csn_value, &entry_value);
|
||||
} else {
|
||||
valuearray_get_bervalarray(csn_value, &entry_value);
|
||||
needFree = PR_TRUE; /* entry_value is a copy */
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/vlv_srch.c b/ldap/servers/slapd/back-ldbm/vlv_srch.c
|
||||
index fe1208d59..11d1c715b 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/vlv_srch.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/vlv_srch.c
|
||||
@@ -203,6 +203,9 @@ vlvSearch_delete(struct vlvSearch **ppvs)
|
||||
{
|
||||
if (ppvs != NULL && *ppvs != NULL) {
|
||||
struct vlvIndex *pi, *ni;
|
||||
+ if ((*ppvs)->vlv_e) {
|
||||
+ slapi_entry_free((struct slapi_entry *)((*ppvs)->vlv_e));
|
||||
+ }
|
||||
slapi_sdn_free(&((*ppvs)->vlv_dn));
|
||||
slapi_ch_free((void **)&((*ppvs)->vlv_name));
|
||||
slapi_sdn_free(&((*ppvs)->vlv_base));
|
||||
@@ -217,7 +220,6 @@ vlvSearch_delete(struct vlvSearch **ppvs)
|
||||
pi = ni;
|
||||
}
|
||||
slapi_ch_free((void **)ppvs);
|
||||
- *ppvs = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
diff --git a/ldap/servers/slapd/generation.c b/ldap/servers/slapd/generation.c
|
||||
index c4f20f793..89f097322 100644
|
||||
--- a/ldap/servers/slapd/generation.c
|
||||
+++ b/ldap/servers/slapd/generation.c
|
||||
@@ -93,9 +93,13 @@ get_server_dataversion()
|
||||
lenstr *l = NULL;
|
||||
Slapi_Backend *be;
|
||||
char *cookie;
|
||||
+ static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
+ /* Serialize to avoid race condition */
|
||||
+ pthread_mutex_lock(&mutex);
|
||||
/* we already cached the copy - just return it */
|
||||
if (server_dataversion_id != NULL) {
|
||||
+ pthread_mutex_unlock(&mutex);
|
||||
return server_dataversion_id;
|
||||
}
|
||||
|
||||
@@ -130,5 +134,6 @@ get_server_dataversion()
|
||||
server_dataversion_id = slapi_ch_strdup(l->ls_buf);
|
||||
}
|
||||
lenstr_free(&l);
|
||||
+ pthread_mutex_unlock(&mutex);
|
||||
return server_dataversion_id;
|
||||
}
|
||||
diff --git a/ldap/servers/slapd/plugin_mr.c b/ldap/servers/slapd/plugin_mr.c
|
||||
index 13f76fe52..6cf88b7de 100644
|
||||
--- a/ldap/servers/slapd/plugin_mr.c
|
||||
+++ b/ldap/servers/slapd/plugin_mr.c
|
||||
@@ -391,28 +391,18 @@ mr_wrap_mr_index_sv_fn(Slapi_PBlock *pb)
|
||||
return rc;
|
||||
}
|
||||
|
||||
-/* this function takes SLAPI_PLUGIN_MR_VALUES as struct berval ** and
|
||||
+/* this function takes SLAPI_PLUGIN_MR_VALUES as Slapi_Value ** and
|
||||
returns SLAPI_PLUGIN_MR_KEYS as struct berval **
|
||||
*/
|
||||
static int
|
||||
mr_wrap_mr_index_fn(Slapi_PBlock *pb)
|
||||
{
|
||||
int rc = -1;
|
||||
- struct berval **in_vals = NULL;
|
||||
struct berval **out_vals = NULL;
|
||||
struct mr_private *mrpriv = NULL;
|
||||
- Slapi_Value **in_vals_sv = NULL;
|
||||
Slapi_Value **out_vals_sv = NULL;
|
||||
|
||||
- slapi_pblock_get(pb, SLAPI_PLUGIN_MR_VALUES, &in_vals); /* get bervals */
|
||||
- /* convert bervals to sv ary */
|
||||
- valuearray_init_bervalarray(in_vals, &in_vals_sv);
|
||||
- slapi_pblock_set(pb, SLAPI_PLUGIN_MR_VALUES, in_vals_sv); /* use sv */
|
||||
rc = mr_wrap_mr_index_sv_fn(pb);
|
||||
- /* clean up in_vals_sv */
|
||||
- valuearray_free(&in_vals_sv);
|
||||
- /* restore old in_vals */
|
||||
- slapi_pblock_set(pb, SLAPI_PLUGIN_MR_VALUES, in_vals);
|
||||
/* get result sv keys */
|
||||
slapi_pblock_get(pb, SLAPI_PLUGIN_MR_KEYS, &out_vals_sv);
|
||||
/* convert to bvec */
|
||||
diff --git a/src/lib389/lib389/backend.py b/src/lib389/lib389/backend.py
|
||||
index 9acced205..cee073ea7 100644
|
||||
--- a/src/lib389/lib389/backend.py
|
||||
+++ b/src/lib389/lib389/backend.py
|
||||
@@ -1029,6 +1029,16 @@ class Backends(DSLdapObjects):
|
||||
for be in sorted(self.list(), key=lambda be: len(be.get_suffix()), reverse=True):
|
||||
be.delete()
|
||||
|
||||
+ def get_backend(self, suffix):
|
||||
+ """
|
||||
+ Return the backend associated with the provided suffix.
|
||||
+ """
|
||||
+ suffix_l = suffix.lower()
|
||||
+ for be in self.list():
|
||||
+ if be.get_attr_val_utf8_l('nsslapd-suffix') == suffix_l:
|
||||
+ return be
|
||||
+ return None
|
||||
+
|
||||
|
||||
class DatabaseConfig(DSLdapObject):
|
||||
"""Backend Database configuration
|
||||
--
|
||||
2.48.1
|
||||
|
||||
@ -0,0 +1,33 @@
|
||||
From bd57467f6efd8e398eb2e608331711bdd571d3ac Mon Sep 17 00:00:00 2001
|
||||
From: Mark Reynolds <mreynolds@redhat.com>
|
||||
Date: Thu, 12 Feb 2026 09:58:54 -0500
|
||||
Subject: [PATCH] Issue 7248 - CLI - attribute uniqueness - fix usage for
|
||||
exclude subtree option
|
||||
|
||||
Description:
|
||||
|
||||
Fix typo in usage message for the exclude subtree option
|
||||
|
||||
relates: https://github.com/389ds/389-ds-base/issues/7248
|
||||
|
||||
Reviewed by: progier (Thanks!)
|
||||
---
|
||||
src/lib389/lib389/cli_conf/plugins/attruniq.py | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/src/lib389/lib389/cli_conf/plugins/attruniq.py b/src/lib389/lib389/cli_conf/plugins/attruniq.py
|
||||
index bc925eb1c..26ca5d819 100644
|
||||
--- a/src/lib389/lib389/cli_conf/plugins/attruniq.py
|
||||
+++ b/src/lib389/lib389/cli_conf/plugins/attruniq.py
|
||||
@@ -127,7 +127,7 @@ def _add_parser_args(parser):
|
||||
help='Sets the DN under which the plug-in checks for uniqueness of '
|
||||
'the attributes value. This attribute is multi-valued (uniqueness-subtrees)')
|
||||
parser.add_argument('--exclude-subtree', nargs='+',
|
||||
- help='Sets subtrees that should not excludedfrom attribute uniqueness. '
|
||||
+ help='Sets subtrees that should be excluded from attribute uniqueness checks. '
|
||||
'This attribute is multi-valued (uniqueness-exclude-subtrees)')
|
||||
parser.add_argument('--across-all-subtrees', choices=['on', 'off'], type=str.lower,
|
||||
help='If enabled (on), the plug-in checks that the attribute is unique across all subtrees '
|
||||
--
|
||||
2.52.0
|
||||
|
||||
@ -1,230 +0,0 @@
|
||||
From bd2829d04491556c35a0b36b591c09a69baf6546 Mon Sep 17 00:00:00 2001
|
||||
From: progier389 <progier@redhat.com>
|
||||
Date: Mon, 11 Dec 2023 11:58:40 +0100
|
||||
Subject: [PATCH] Issue 6004 - idletimeout may be ignored (#6005)
|
||||
|
||||
* Issue 6004 - idletimeout may be ignored
|
||||
|
||||
Problem: idletimeout is still not handled when binding as non root (unless there are some activity
|
||||
on another connection)
|
||||
Fix:
|
||||
Add a slapi_eq_repeat_rel handler that walks all active connection every seconds and check if the timeout is expired.
|
||||
Note about CI test:
|
||||
Notice that idletimeout is never enforced for connections bound as root (i.e cn=directory manager).
|
||||
|
||||
Issue #6004
|
||||
|
||||
Reviewed by: @droideck, @tbordaz (Thanks!)
|
||||
|
||||
(cherry picked from commit 86b5969acbe124eec8c89bcf1ab2156b2b140c17)
|
||||
(cherry picked from commit bdb0a72b4953678e5418406b3c202dfa2c7469a2)
|
||||
(cherry picked from commit 61cebc191cd4090072dda691b9956dbde4cf7c48)
|
||||
---
|
||||
.../tests/suites/config/regression_test.py | 82 ++++++++++++++++++-
|
||||
ldap/servers/slapd/daemon.c | 52 +++++++++++-
|
||||
2 files changed, 128 insertions(+), 6 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/config/regression_test.py b/dirsrvtests/tests/suites/config/regression_test.py
|
||||
index 0000dd82d..8dbba8cd2 100644
|
||||
--- a/dirsrvtests/tests/suites/config/regression_test.py
|
||||
+++ b/dirsrvtests/tests/suites/config/regression_test.py
|
||||
@@ -6,20 +6,49 @@
|
||||
# See LICENSE for details.
|
||||
# --- END COPYRIGHT BLOCK ---
|
||||
#
|
||||
+import os
|
||||
import logging
|
||||
import pytest
|
||||
+import time
|
||||
from lib389.utils import *
|
||||
from lib389.dseldif import DSEldif
|
||||
-from lib389.config import LDBMConfig
|
||||
+from lib389.config import BDB_LDBMConfig, LDBMConfig, Config
|
||||
from lib389.backend import Backends
|
||||
from lib389.topologies import topology_st as topo
|
||||
+from lib389.idm.user import UserAccounts, TEST_USER_PROPERTIES
|
||||
+from lib389._constants import DEFAULT_SUFFIX, PASSWORD, DN_DM
|
||||
|
||||
pytestmark = pytest.mark.tier0
|
||||
|
||||
logging.getLogger(__name__).setLevel(logging.INFO)
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
+DEBUGGING = os.getenv("DEBUGGING", default=False)
|
||||
CUSTOM_MEM = '9100100100'
|
||||
+IDLETIMEOUT = 5
|
||||
+DN_TEST_USER = f'uid={TEST_USER_PROPERTIES["uid"]},ou=People,{DEFAULT_SUFFIX}'
|
||||
+
|
||||
+
|
||||
+@pytest.fixture(scope="module")
|
||||
+def idletimeout_topo(topo, request):
|
||||
+ """Create an instance with a test user and set idletimeout"""
|
||||
+ inst = topo.standalone
|
||||
+ config = Config(inst)
|
||||
+
|
||||
+ users = UserAccounts(inst, DEFAULT_SUFFIX)
|
||||
+ user = users.create(properties={
|
||||
+ **TEST_USER_PROPERTIES,
|
||||
+ 'userpassword' : PASSWORD,
|
||||
+ })
|
||||
+ config.replace('nsslapd-idletimeout', str(IDLETIMEOUT))
|
||||
+
|
||||
+ def fin():
|
||||
+ if not DEBUGGING:
|
||||
+ config.reset('nsslapd-idletimeout')
|
||||
+ user.delete()
|
||||
+
|
||||
+ request.addfinalizer(fin)
|
||||
+ return topo
|
||||
|
||||
|
||||
# Function to return value of available memory in kb
|
||||
@@ -79,7 +108,7 @@ def test_maxbersize_repl(topo):
|
||||
nsslapd-errorlog-logmaxdiskspace are set in certain order
|
||||
|
||||
:id: 743e912c-2be4-4f5f-9c2a-93dcb18f51a0
|
||||
- :setup: MMR with two suppliers
|
||||
+ :setup: Standalone Instance
|
||||
:steps:
|
||||
1. Stop the instance
|
||||
2. Set nsslapd-errorlog-maxlogsize before/after
|
||||
@@ -112,3 +141,52 @@ def test_maxbersize_repl(topo):
|
||||
log.info("Assert no init_dse_file errors in the error log")
|
||||
assert not inst.ds_error_log.match('.*ERR - init_dse_file.*')
|
||||
|
||||
+
|
||||
+def test_bdb_config(topo):
|
||||
+ """Check that bdb config entry exists
|
||||
+
|
||||
+ :id: edbc6f54-7c98-11ee-b1c0-482ae39447e5
|
||||
+ :setup: standalone
|
||||
+ :steps:
|
||||
+ 1. Check that bdb config instance exists.
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ """
|
||||
+
|
||||
+ inst = topo.standalone
|
||||
+ assert BDB_LDBMConfig(inst).exists()
|
||||
+
|
||||
+
|
||||
+@pytest.mark.parametrize("dn,expected_result", [(DN_TEST_USER, True), (DN_DM, False)])
|
||||
+def test_idletimeout(idletimeout_topo, dn, expected_result):
|
||||
+ """Check that bdb config entry exists
|
||||
+
|
||||
+ :id: b20f2826-942a-11ee-827b-482ae39447e5
|
||||
+ :parametrized: yes
|
||||
+ :setup: Standalone Instance with test user and idletimeout
|
||||
+ :steps:
|
||||
+ 1. Open new ldap connection
|
||||
+ 2. Bind with the provided dn
|
||||
+ 3. Wait longer than idletimeout
|
||||
+ 4. Try to bind again the provided dn and check if
|
||||
+ connection is closed or not.
|
||||
+ 5. Check if result is the expected one.
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Success
|
||||
+ 4. Success
|
||||
+ 5. Success
|
||||
+ """
|
||||
+
|
||||
+ inst = idletimeout_topo.standalone
|
||||
+
|
||||
+ l = ldap.initialize(f'ldap://localhost:{inst.port}')
|
||||
+ l.bind_s(dn, PASSWORD)
|
||||
+ time.sleep(IDLETIMEOUT+1)
|
||||
+ try:
|
||||
+ l.bind_s(dn, PASSWORD)
|
||||
+ result = False
|
||||
+ except ldap.SERVER_DOWN:
|
||||
+ result = True
|
||||
+ assert expected_result == result
|
||||
diff --git a/ldap/servers/slapd/daemon.c b/ldap/servers/slapd/daemon.c
|
||||
index 57e07e5f5..6df109760 100644
|
||||
--- a/ldap/servers/slapd/daemon.c
|
||||
+++ b/ldap/servers/slapd/daemon.c
|
||||
@@ -68,6 +68,8 @@
|
||||
#define SLAPD_ACCEPT_WAKEUP_TIMER 250
|
||||
#endif
|
||||
|
||||
+#define MILLISECONDS_PER_SECOND 1000
|
||||
+
|
||||
int slapd_wakeup_timer = SLAPD_WAKEUP_TIMER; /* time in ms to wakeup */
|
||||
int slapd_accept_wakeup_timer = SLAPD_ACCEPT_WAKEUP_TIMER; /* time in ms to wakeup */
|
||||
#ifdef notdef /* GGOODREPL */
|
||||
@@ -1045,6 +1047,48 @@ slapd_sockets_ports_free(daemon_ports_t *ports_info)
|
||||
#endif
|
||||
}
|
||||
|
||||
+/*
|
||||
+ * Tells if idle timeout has expired
|
||||
+ */
|
||||
+static inline int __attribute__((always_inline))
|
||||
+has_idletimeout_expired(Connection *c, time_t curtime)
|
||||
+{
|
||||
+ return (c->c_state != CONN_STATE_FREE && !c->c_gettingber &&
|
||||
+ c->c_idletimeout > 0 && NULL == c->c_ops &&
|
||||
+ curtime - c->c_idlesince >= c->c_idletimeout);
|
||||
+}
|
||||
+
|
||||
+/*
|
||||
+ * slapi_eq_repeat_rel callback that checks that idletimeout has not expired.
|
||||
+ */
|
||||
+void
|
||||
+check_idletimeout(time_t when __attribute__((unused)), void *arg __attribute__((unused)) )
|
||||
+{
|
||||
+ Connection_Table *ct = the_connection_table;
|
||||
+ time_t curtime = slapi_current_rel_time_t();
|
||||
+ /* Walk all active connections of all connection listeners */
|
||||
+ for (int list_num = 0; list_num < ct->list_num; list_num++) {
|
||||
+ for (Connection *c = connection_table_get_first_active_connection(ct, list_num);
|
||||
+ c != NULL; c = connection_table_get_next_active_connection(ct, c)) {
|
||||
+ if (!has_idletimeout_expired(c, curtime)) {
|
||||
+ continue;
|
||||
+ }
|
||||
+ /* Looks like idletimeout has expired, lets acquire the lock
|
||||
+ * and double check.
|
||||
+ */
|
||||
+ if (pthread_mutex_trylock(&(c->c_mutex)) == EBUSY) {
|
||||
+ continue;
|
||||
+ }
|
||||
+ if (has_idletimeout_expired(c, curtime)) {
|
||||
+ /* idle timeout has expired */
|
||||
+ disconnect_server_nomutex(c, c->c_connid, -1,
|
||||
+ SLAPD_DISCONNECT_IDLE_TIMEOUT, ETIMEDOUT);
|
||||
+ }
|
||||
+ pthread_mutex_unlock(&(c->c_mutex));
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
void
|
||||
slapd_daemon(daemon_ports_t *ports)
|
||||
{
|
||||
@@ -1258,7 +1302,9 @@ slapd_daemon(daemon_ports_t *ports)
|
||||
"MAINPID=%lu",
|
||||
(unsigned long)getpid());
|
||||
#endif
|
||||
-
|
||||
+ slapi_eq_repeat_rel(check_idletimeout, NULL,
|
||||
+ slapi_current_rel_time_t(),
|
||||
+ MILLISECONDS_PER_SECOND);
|
||||
/* The meat of the operation is in a loop on a call to select */
|
||||
while (!g_get_shutdown()) {
|
||||
int select_return = 0;
|
||||
@@ -1734,9 +1780,7 @@ handle_pr_read_ready(Connection_Table *ct, PRIntn num_poll __attribute__((unused
|
||||
disconnect_server_nomutex(c, c->c_connid, -1,
|
||||
SLAPD_DISCONNECT_POLL, EPIPE);
|
||||
}
|
||||
- } else if (c->c_idletimeout > 0 &&
|
||||
- (curtime - c->c_idlesince) >= c->c_idletimeout &&
|
||||
- NULL == c->c_ops) {
|
||||
+ } else if (has_idletimeout_expired(c, curtime)) {
|
||||
/* idle timeout */
|
||||
disconnect_server_nomutex(c, c->c_connid, -1,
|
||||
SLAPD_DISCONNECT_IDLE_TIMEOUT, ETIMEDOUT);
|
||||
--
|
||||
2.48.1
|
||||
|
||||
@ -0,0 +1,201 @@
|
||||
From 180329d9ca672306f8e90d599890be3a6a8aa95c Mon Sep 17 00:00:00 2001
|
||||
From: Mark Reynolds <mreynolds@redhat.com>
|
||||
Date: Thu, 12 Feb 2026 11:13:45 -0500
|
||||
Subject: [PATCH] Issue - CLI - dsctl db2index needs some hardening with MBD
|
||||
|
||||
Description:
|
||||
|
||||
The usage for dsctl db2index was confusing. The way the attr options and
|
||||
backend name were displayed it looks like the backend name could come after
|
||||
the attributes, but instead the backend name was treated as an attribute.
|
||||
|
||||
Instead make the backend name required, and change the attribute naming to
|
||||
require individual options instead of a list of values.
|
||||
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/7250
|
||||
|
||||
Reviewed by: progier(Thanks!)
|
||||
---
|
||||
.../tests/suites/import/import_test.py | 2 +-
|
||||
src/lib389/lib389/__init__.py | 40 ++++++-------------
|
||||
src/lib389/lib389/cli_ctl/dbtasks.py | 37 +++++++----------
|
||||
3 files changed, 27 insertions(+), 52 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/import/import_test.py b/dirsrvtests/tests/suites/import/import_test.py
|
||||
index 18caec633..058a4b3c7 100644
|
||||
--- a/dirsrvtests/tests/suites/import/import_test.py
|
||||
+++ b/dirsrvtests/tests/suites/import/import_test.py
|
||||
@@ -512,7 +512,7 @@ def test_entry_with_escaped_characters_fails_to_import_and_index(topo, _import_c
|
||||
count += 1
|
||||
# Now re-index the database
|
||||
topo.standalone.stop()
|
||||
- topo.standalone.db2index()
|
||||
+ topo.standalone.db2index(bename="userroot")
|
||||
topo.standalone.start()
|
||||
# Should not return error.
|
||||
assert not topo.standalone.searchErrorsLog('error')
|
||||
diff --git a/src/lib389/lib389/__init__.py b/src/lib389/lib389/__init__.py
|
||||
index 39a2852e5..ec47987be 100644
|
||||
--- a/src/lib389/lib389/__init__.py
|
||||
+++ b/src/lib389/lib389/__init__.py
|
||||
@@ -2985,7 +2985,7 @@ class DirSrv(SimpleLDAPObject, object):
|
||||
|
||||
return True
|
||||
|
||||
- def db2index(self, bename=None, suffixes=None, attrs=None, vlvTag=None):
|
||||
+ def db2index(self, bename, suffixes=None, attrs=None, vlvTag=None):
|
||||
"""
|
||||
@param bename - The backend name to reindex
|
||||
@param suffixes - List/tuple of suffixes to reindex, currently unused
|
||||
@@ -2998,34 +2998,18 @@ class DirSrv(SimpleLDAPObject, object):
|
||||
if self.status():
|
||||
self.log.error("db2index: Can not operate while directory server is running")
|
||||
return False
|
||||
- cmd = [prog, ]
|
||||
- # No backend specified, do an upgrade on all backends
|
||||
- # Backend and no attrs specified, reindex with all backend indexes
|
||||
- # Backend and attr/s specified, reindex backend with attr/s
|
||||
- if bename:
|
||||
- cmd.append('db2index')
|
||||
- cmd.append('-n')
|
||||
- cmd.append(bename)
|
||||
- if attrs:
|
||||
- for attr in attrs:
|
||||
- cmd.append('-t')
|
||||
- cmd.append(attr)
|
||||
- else:
|
||||
- dse_ldif = DSEldif(self)
|
||||
- indexes = dse_ldif.get_indexes(bename)
|
||||
- if indexes:
|
||||
- for idx in indexes:
|
||||
- cmd.append('-t')
|
||||
- cmd.append(idx)
|
||||
+ cmd = [prog, 'db2index', '-n', bename, '-D', self.get_config_dir()]
|
||||
+ if attrs:
|
||||
+ for attr in attrs:
|
||||
+ cmd.append('-t')
|
||||
+ cmd.append(attr)
|
||||
else:
|
||||
- cmd.append('upgradedb')
|
||||
- cmd.append('-a')
|
||||
- now = datetime.now().isoformat()
|
||||
- cmd.append(os.path.join(self.get_bak_dir(), 'reindex_%s' % now))
|
||||
- cmd.append('-f')
|
||||
-
|
||||
- cmd.append('-D')
|
||||
- cmd.append(self.get_config_dir())
|
||||
+ dse_ldif = DSEldif(self)
|
||||
+ indexes = dse_ldif.get_indexes(bename)
|
||||
+ if indexes:
|
||||
+ for idx in indexes:
|
||||
+ cmd.append('-t')
|
||||
+ cmd.append(idx)
|
||||
|
||||
try:
|
||||
result = subprocess.check_output(cmd, encoding='utf-8')
|
||||
diff --git a/src/lib389/lib389/cli_ctl/dbtasks.py b/src/lib389/lib389/cli_ctl/dbtasks.py
|
||||
index 16da966d1..cd96cdaf7 100644
|
||||
--- a/src/lib389/lib389/cli_ctl/dbtasks.py
|
||||
+++ b/src/lib389/lib389/cli_ctl/dbtasks.py
|
||||
@@ -26,32 +26,18 @@ class IndexOrdering(Enum):
|
||||
|
||||
|
||||
def dbtasks_db2index(inst, log, args):
|
||||
- rtn = False
|
||||
- if not args.backend:
|
||||
- if not inst.db2index():
|
||||
- rtn = False
|
||||
- else:
|
||||
- rtn = True
|
||||
- elif args.backend and not args.attr:
|
||||
- if not inst.db2index(bename=args.backend):
|
||||
- rtn = False
|
||||
- else:
|
||||
- rtn = True
|
||||
+ inst.log = log
|
||||
+ if not inst.db2index(bename=args.backend, attrs=args.attr):
|
||||
+ log.fatal("db2index failed")
|
||||
+ return False
|
||||
else:
|
||||
- if not inst.db2index(bename=args.backend, attrs=args.attr):
|
||||
- rtn = False
|
||||
- else:
|
||||
- rtn = True
|
||||
- if rtn:
|
||||
log.info("db2index successful")
|
||||
- return rtn
|
||||
- else:
|
||||
- log.fatal("db2index failed")
|
||||
- return rtn
|
||||
+ return True
|
||||
|
||||
|
||||
def dbtasks_db2bak(inst, log, args):
|
||||
# Needs an output name?
|
||||
+ inst.log = log
|
||||
if not inst.db2bak(args.archive):
|
||||
log.fatal("db2bak failed")
|
||||
return False
|
||||
@@ -61,6 +47,7 @@ def dbtasks_db2bak(inst, log, args):
|
||||
|
||||
def dbtasks_bak2db(inst, log, args):
|
||||
# Needs the archive to restore.
|
||||
+ inst.log = log
|
||||
if not inst.bak2db(args.archive):
|
||||
log.fatal("bak2db failed")
|
||||
return False
|
||||
@@ -70,6 +57,7 @@ def dbtasks_bak2db(inst, log, args):
|
||||
|
||||
def dbtasks_db2ldif(inst, log, args):
|
||||
# If export filename is provided, check if file path exists
|
||||
+ inst.log = log
|
||||
if args.ldif:
|
||||
path = Path(args.ldif)
|
||||
parent = path.parent.absolute()
|
||||
@@ -88,6 +76,7 @@ def dbtasks_db2ldif(inst, log, args):
|
||||
|
||||
def dbtasks_ldif2db(inst, log, args):
|
||||
# Check if ldif file exists
|
||||
+ inst.log = log
|
||||
if not os.path.exists(args.ldif):
|
||||
raise ValueError("The LDIF file does not exist: " + args.ldif)
|
||||
|
||||
@@ -103,6 +92,7 @@ def dbtasks_ldif2db(inst, log, args):
|
||||
|
||||
|
||||
def dbtasks_backups(inst, log, args):
|
||||
+ inst.log = log
|
||||
if args.delete:
|
||||
# Delete backup
|
||||
inst.del_backup(args.delete[0])
|
||||
@@ -117,6 +107,7 @@ def dbtasks_backups(inst, log, args):
|
||||
|
||||
|
||||
def dbtasks_ldifs(inst, log, args):
|
||||
+ inst.log = log
|
||||
if args.delete:
|
||||
# Delete LDIF file
|
||||
inst.del_ldif(args.delete[0])
|
||||
@@ -131,6 +122,7 @@ def dbtasks_ldifs(inst, log, args):
|
||||
|
||||
|
||||
def dbtasks_verify(inst, log, args):
|
||||
+ inst.log = log
|
||||
if not inst.dbverify(bename=args.backend):
|
||||
log.fatal("dbverify failed")
|
||||
return False
|
||||
@@ -521,9 +513,8 @@ def dbtasks_index_check(inst, log, args):
|
||||
|
||||
def create_parser(subcommands):
|
||||
db2index_parser = subcommands.add_parser('db2index', help="Initialise a reindex of the server database. The server must be stopped for this to proceed.", formatter_class=CustomHelpFormatter)
|
||||
- # db2index_parser.add_argument('suffix', help="The suffix to reindex. IE dc=example,dc=com.")
|
||||
- db2index_parser.add_argument('backend', nargs="?", help="The backend to reindex. IE userRoot", default=False)
|
||||
- db2index_parser.add_argument('--attr', nargs="*", help="The attribute's to reindex. IE --attr aci cn givenname", default=False)
|
||||
+ db2index_parser.add_argument('backend', help="The backend to reindex. IE userRoot")
|
||||
+ db2index_parser.add_argument('--attr', action='append', help="An attribute to reindex. IE: --attr member --attr cn ...")
|
||||
db2index_parser.set_defaults(func=dbtasks_db2index)
|
||||
|
||||
db2bak_parser = subcommands.add_parser('db2bak', help="Initialise a BDB backup of the database. The server must be stopped for this to proceed.", formatter_class=CustomHelpFormatter)
|
||||
--
|
||||
2.52.0
|
||||
|
||||
@ -1,69 +0,0 @@
|
||||
From e9fe6e074130406328b8e932a5c2efa814d190a0 Mon Sep 17 00:00:00 2001
|
||||
From: tbordaz <tbordaz@redhat.com>
|
||||
Date: Wed, 5 Feb 2025 09:41:30 +0100
|
||||
Subject: [PATCH] Issue 6004 - (2nd) idletimeout may be ignored (#6569)
|
||||
|
||||
Problem:
|
||||
multiple listener threads was implemented in 2.x and after
|
||||
This is missing in 1.4.3 so the cherry pick should be adapted
|
||||
Fix:
|
||||
skip the loop with listeners
|
||||
|
||||
Issue #6004
|
||||
|
||||
Reviewed by: Jamie Chapman (Thanks !)
|
||||
---
|
||||
ldap/servers/slapd/daemon.c | 36 +++++++++++++++++-------------------
|
||||
1 file changed, 17 insertions(+), 19 deletions(-)
|
||||
|
||||
diff --git a/ldap/servers/slapd/daemon.c b/ldap/servers/slapd/daemon.c
|
||||
index 6df109760..bef75e4a3 100644
|
||||
--- a/ldap/servers/slapd/daemon.c
|
||||
+++ b/ldap/servers/slapd/daemon.c
|
||||
@@ -1066,26 +1066,24 @@ check_idletimeout(time_t when __attribute__((unused)), void *arg __attribute__((
|
||||
{
|
||||
Connection_Table *ct = the_connection_table;
|
||||
time_t curtime = slapi_current_rel_time_t();
|
||||
- /* Walk all active connections of all connection listeners */
|
||||
- for (int list_num = 0; list_num < ct->list_num; list_num++) {
|
||||
- for (Connection *c = connection_table_get_first_active_connection(ct, list_num);
|
||||
- c != NULL; c = connection_table_get_next_active_connection(ct, c)) {
|
||||
- if (!has_idletimeout_expired(c, curtime)) {
|
||||
- continue;
|
||||
- }
|
||||
- /* Looks like idletimeout has expired, lets acquire the lock
|
||||
- * and double check.
|
||||
- */
|
||||
- if (pthread_mutex_trylock(&(c->c_mutex)) == EBUSY) {
|
||||
- continue;
|
||||
- }
|
||||
- if (has_idletimeout_expired(c, curtime)) {
|
||||
- /* idle timeout has expired */
|
||||
- disconnect_server_nomutex(c, c->c_connid, -1,
|
||||
- SLAPD_DISCONNECT_IDLE_TIMEOUT, ETIMEDOUT);
|
||||
- }
|
||||
- pthread_mutex_unlock(&(c->c_mutex));
|
||||
+ /* Walk all active connections */
|
||||
+ for (Connection *c = connection_table_get_first_active_connection(ct);
|
||||
+ c != NULL; c = connection_table_get_next_active_connection(ct, c)) {
|
||||
+ if (!has_idletimeout_expired(c, curtime)) {
|
||||
+ continue;
|
||||
+ }
|
||||
+ /* Looks like idletimeout has expired, lets acquire the lock
|
||||
+ * and double check.
|
||||
+ */
|
||||
+ if (pthread_mutex_trylock(&(c->c_mutex)) == EBUSY) {
|
||||
+ continue;
|
||||
+ }
|
||||
+ if (has_idletimeout_expired(c, curtime)) {
|
||||
+ /* idle timeout has expired */
|
||||
+ disconnect_server_nomutex(c, c->c_connid, -1,
|
||||
+ SLAPD_DISCONNECT_IDLE_TIMEOUT, ETIMEDOUT);
|
||||
}
|
||||
+ pthread_mutex_unlock(&(c->c_mutex));
|
||||
}
|
||||
}
|
||||
|
||||
--
|
||||
2.48.1
|
||||
|
||||
@ -0,0 +1,41 @@
|
||||
From c831a7af2afb7cfd2eb958476a7a8d421ac5d339 Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Fri, 13 Feb 2026 15:38:52 +0100
|
||||
Subject: [PATCH] Issue 7184 - (2nd) argparse.HelpFormatter
|
||||
_format_actions_usage() is deprecated (#7257)
|
||||
|
||||
Description:
|
||||
`_format_actions_usage()` was also removed in Python 3.14.3.
|
||||
Replace version check with `isinstance()` to handle the return type of
|
||||
`_get_actions_usage_parts()` more robustly across Python versions.
|
||||
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/7184
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/7253
|
||||
|
||||
Reviewed by: @progier389 (Thanks!)
|
||||
---
|
||||
src/lib389/lib389/cli_base/__init__.py | 6 +++---
|
||||
1 file changed, 3 insertions(+), 3 deletions(-)
|
||||
|
||||
diff --git a/src/lib389/lib389/cli_base/__init__.py b/src/lib389/lib389/cli_base/__init__.py
|
||||
index f1055aadc..3af8a46e6 100644
|
||||
--- a/src/lib389/lib389/cli_base/__init__.py
|
||||
+++ b/src/lib389/lib389/cli_base/__init__.py
|
||||
@@ -420,11 +420,11 @@ class CustomHelpFormatter(argparse.HelpFormatter):
|
||||
else:
|
||||
# Use _get_actions_usage_parts() for Python 3.13 and later
|
||||
action_parts = self._get_actions_usage_parts(parent_arguments, [])
|
||||
- if sys.version_info >= (3, 15):
|
||||
- # Python 3.15 returns a tuple (list of actions, count of actions)
|
||||
+ if isinstance(action_parts, tuple):
|
||||
+ # Python 3.14.3+ and 3.15+ return a tuple (list of actions, count of actions)
|
||||
formatted_options = ' '.join(action_parts[0])
|
||||
else:
|
||||
- # Python 3.13 and 3.14 return a list of actions
|
||||
+ # Earlier versions return a list of actions
|
||||
formatted_options = ' '.join(action_parts)
|
||||
|
||||
# If formatted_options already in usage - remove them
|
||||
--
|
||||
2.52.0
|
||||
|
||||
@ -1,52 +0,0 @@
|
||||
From b2edc371c5ca4fd24ef469c64829c48824098e7f Mon Sep 17 00:00:00 2001
|
||||
From: Mark Reynolds <mreynolds@redhat.com>
|
||||
Date: Wed, 8 Jan 2025 12:57:52 -0500
|
||||
Subject: [PATCH] Issue 6485 - Fix double free in USN cleanup task
|
||||
|
||||
Description:
|
||||
|
||||
ASAN report shows double free of bind dn in the USN cleanup task data. The bind
|
||||
dn was passed as a reference so it should never have to be freed by the cleanup
|
||||
task.
|
||||
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/6485
|
||||
|
||||
Reviewed by: tbordaz(Thanks!)
|
||||
---
|
||||
ldap/servers/plugins/usn/usn_cleanup.c | 6 ++----
|
||||
1 file changed, 2 insertions(+), 4 deletions(-)
|
||||
|
||||
diff --git a/ldap/servers/plugins/usn/usn_cleanup.c b/ldap/servers/plugins/usn/usn_cleanup.c
|
||||
index bdb55e6b1..7eaf0f88f 100644
|
||||
--- a/ldap/servers/plugins/usn/usn_cleanup.c
|
||||
+++ b/ldap/servers/plugins/usn/usn_cleanup.c
|
||||
@@ -240,7 +240,7 @@ usn_cleanup_add(Slapi_PBlock *pb,
|
||||
char *suffix = NULL;
|
||||
char *backend = NULL;
|
||||
char *maxusn = NULL;
|
||||
- char *bind_dn;
|
||||
+ char *bind_dn = NULL;
|
||||
struct usn_cleanup_data *cleanup_data = NULL;
|
||||
int rv = SLAPI_DSE_CALLBACK_OK;
|
||||
Slapi_Task *task = NULL;
|
||||
@@ -323,8 +323,7 @@ usn_cleanup_add(Slapi_PBlock *pb,
|
||||
suffix = NULL; /* don't free in this function */
|
||||
cleanup_data->maxusn_to_delete = maxusn;
|
||||
maxusn = NULL; /* don't free in this function */
|
||||
- cleanup_data->bind_dn = bind_dn;
|
||||
- bind_dn = NULL; /* don't free in this function */
|
||||
+ cleanup_data->bind_dn = slapi_ch_strdup(bind_dn);
|
||||
slapi_task_set_data(task, cleanup_data);
|
||||
|
||||
/* start the USN tombstone cleanup task as a separate thread */
|
||||
@@ -363,7 +362,6 @@ usn_cleanup_task_destructor(Slapi_Task *task)
|
||||
slapi_ch_free_string(&mydata->suffix);
|
||||
slapi_ch_free_string(&mydata->maxusn_to_delete);
|
||||
slapi_ch_free_string(&mydata->bind_dn);
|
||||
- /* Need to cast to avoid a compiler warning */
|
||||
slapi_ch_free((void **)&mydata);
|
||||
}
|
||||
}
|
||||
--
|
||||
2.48.1
|
||||
|
||||
@ -0,0 +1,32 @@
|
||||
From a7f8eb79dde03ac771de2335a2acb4aed48483a2 Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Fri, 13 Feb 2026 16:27:25 +0100
|
||||
Subject: [PATCH] Issue 7213 - (2nd) MDB_BAD_VALSIZE error while handling VLV
|
||||
(#7258)
|
||||
|
||||
Decription:
|
||||
Disable test_vlv_long_attribute_value on BDB as it hangs sometimes in
|
||||
CI, blocking other pipelines.
|
||||
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/7213
|
||||
|
||||
Reviewed by: @progier389 (Thanks!)
|
||||
---
|
||||
dirsrvtests/tests/suites/vlv/regression_test.py | 1 +
|
||||
1 file changed, 1 insertion(+)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/vlv/regression_test.py b/dirsrvtests/tests/suites/vlv/regression_test.py
|
||||
index 1cc03c303..94001af62 100644
|
||||
--- a/dirsrvtests/tests/suites/vlv/regression_test.py
|
||||
+++ b/dirsrvtests/tests/suites/vlv/regression_test.py
|
||||
@@ -1178,6 +1178,7 @@ def test_vlv_with_mr(vlv_setup_with_uid_mr):
|
||||
|
||||
|
||||
|
||||
+@pytest.mark.skipif(get_default_db_lib() == "bdb", reason="Hangs on BDB")
|
||||
def test_vlv_long_attribute_value(topology_st, request):
|
||||
"""
|
||||
Test VLV with an entry containing a very long attribute value (2K).
|
||||
--
|
||||
2.52.0
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
From 384dbcaba418afe9e4de798d16a68d0468632a57 Mon Sep 17 00:00:00 2001
|
||||
From 75b1213dfb94495160a735c2688cee03d6be5a9a Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Fri, 13 Feb 2026 13:08:40 +0100
|
||||
Subject: [PATCH] Issue 7223 - Use lexicographical order for ancestorid
|
||||
Date: Fri, 13 Feb 2026 16:58:24 +0100
|
||||
Subject: [PATCH] Issue 7223 - Use lexicographical order for ancestorid (#7256)
|
||||
|
||||
Description:
|
||||
`ldbm_instance_create_default_indexes()` configured ancestorid with
|
||||
@ -11,13 +11,13 @@ ldif2db import).
|
||||
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/7223
|
||||
|
||||
Reviewed by: @progier389, @tbordaz (Thanks!)
|
||||
Reviewed by: @tbordaz (Thanks!)
|
||||
---
|
||||
ldap/servers/slapd/back-ldbm/instance.c | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/instance.c b/ldap/servers/slapd/back-ldbm/instance.c
|
||||
index 388dd6efb..f5342b99a 100644
|
||||
index 3fcdb5554..563cf96db 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/instance.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/instance.c
|
||||
@@ -239,7 +239,7 @@ ldbm_instance_create_default_indexes(backend *be)
|
||||
@ -1,38 +0,0 @@
|
||||
From 679262c0c292413851d2d004b588ecfd7d91c85a Mon Sep 17 00:00:00 2001
|
||||
From: James Chapman <jachapma@redhat.com>
|
||||
Date: Tue, 11 Feb 2025 18:06:34 +0000
|
||||
Subject: [PATCH] Issue 5841 - dsconf incorrectly setting up Pass-Through
|
||||
Authentication (#6601)
|
||||
|
||||
Bug description:
|
||||
During init, PAMPassThroughAuthConfigs defines an "objectclass=nsslapdplugin"
|
||||
plugin object. During filter creation, dsconf fails as objectclass=nsslapdplugin
|
||||
is not present in the PAM PT config entry. This objectclass has been removed in
|
||||
all other branches, branch 1.4.3 was skipped as there are cherry pick conflicts.
|
||||
|
||||
Fix description:
|
||||
Remove nsslapdplugin from the plugin objecti, objectclass list.
|
||||
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/5841
|
||||
|
||||
Reviewed by: @progier389 (Thank you)
|
||||
---
|
||||
src/lib389/lib389/plugins.py | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/src/lib389/lib389/plugins.py b/src/lib389/lib389/plugins.py
|
||||
index 185398e5b..25b49dae4 100644
|
||||
--- a/src/lib389/lib389/plugins.py
|
||||
+++ b/src/lib389/lib389/plugins.py
|
||||
@@ -1579,7 +1579,7 @@ class PAMPassThroughAuthConfigs(DSLdapObjects):
|
||||
|
||||
def __init__(self, instance, basedn="cn=PAM Pass Through Auth,cn=plugins,cn=config"):
|
||||
super(PAMPassThroughAuthConfigs, self).__init__(instance)
|
||||
- self._objectclasses = ['top', 'extensibleObject', 'nsslapdplugin', 'pamConfig']
|
||||
+ self._objectclasses = ['top', 'extensibleObject', 'pamConfig']
|
||||
self._filterattrs = ['cn']
|
||||
self._scope = ldap.SCOPE_ONELEVEL
|
||||
self._childobject = PAMPassThroughAuthConfig
|
||||
--
|
||||
2.48.1
|
||||
|
||||
@ -0,0 +1,89 @@
|
||||
From 7809104b9edb2d05ceee7ca819152f365cc1beb0 Mon Sep 17 00:00:00 2001
|
||||
From: Mark Reynolds <mreynolds@redhat.com>
|
||||
Date: Wed, 11 Feb 2026 15:51:47 -0500
|
||||
Subject: [PATCH] Issue 7066/7052 - allow password history to be set to zero
|
||||
and remove history
|
||||
|
||||
Description:
|
||||
|
||||
For local password policies the server was incorrectly rejecting updates that
|
||||
set the value to zero. When password history is set to zero the old passwords
|
||||
in the entry history are not cleaned as expected.
|
||||
|
||||
relates: https://github.com/389ds/389-ds-base/issues/7052
|
||||
relates: https://github.com/389ds/389-ds-base/issues/7066
|
||||
|
||||
Reviewed by: progier(Thanks!)
|
||||
---
|
||||
.../tests/suites/password/pwp_history_test.py | 9 ++++++---
|
||||
ldap/servers/slapd/modify.c | 2 +-
|
||||
ldap/servers/slapd/pw.c | 13 ++++++++++++-
|
||||
3 files changed, 19 insertions(+), 5 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/password/pwp_history_test.py b/dirsrvtests/tests/suites/password/pwp_history_test.py
|
||||
index 8a15a1986..387824bc5 100644
|
||||
--- a/dirsrvtests/tests/suites/password/pwp_history_test.py
|
||||
+++ b/dirsrvtests/tests/suites/password/pwp_history_test.py
|
||||
@@ -108,8 +108,12 @@ def test_history_is_not_overwritten(topology_st, user):
|
||||
user.set('userpassword', USER_PWD)
|
||||
|
||||
|
||||
-def test_basic(topology_st, user):
|
||||
- """Test basic password policy history feature functionality
|
||||
+@pytest.mark.parametrize('policy',
|
||||
+ [(pytest.param('global')),
|
||||
+ (pytest.param('subtree')),
|
||||
+ (pytest.param('user'))])
|
||||
+def test_basic(topology_st, user, policy):
|
||||
+ """Test basic password policy history feature functionality with dynamic count reduction
|
||||
|
||||
:id: 83d74f7d-3036-4944-8839-1b40bbf265ff
|
||||
:setup: Standalone instance, a test user
|
||||
@@ -238,7 +242,6 @@ def test_basic(topology_st, user):
|
||||
|
||||
#
|
||||
# Reset password by Directory Manager(admin reset)
|
||||
- #
|
||||
dm = DirectoryManager(topology_st.standalone)
|
||||
dm.rebind()
|
||||
time.sleep(.5)
|
||||
diff --git a/ldap/servers/slapd/modify.c b/ldap/servers/slapd/modify.c
|
||||
index b0066faf8..d76427886 100644
|
||||
--- a/ldap/servers/slapd/modify.c
|
||||
+++ b/ldap/servers/slapd/modify.c
|
||||
@@ -87,7 +87,7 @@ static struct attr_value_check
|
||||
{CONFIG_PW_WARNING_ATTRIBUTE, check_pw_duration_value, 0, -1},
|
||||
{CONFIG_PW_MINLENGTH_ATTRIBUTE, attr_check_minmax, 2, 512},
|
||||
{CONFIG_PW_MAXFAILURE_ATTRIBUTE, attr_check_minmax, 1, 32767},
|
||||
- {CONFIG_PW_INHISTORY_ATTRIBUTE, attr_check_minmax, 1, 24},
|
||||
+ {CONFIG_PW_INHISTORY_ATTRIBUTE, attr_check_minmax, 0, 24},
|
||||
{CONFIG_PW_LOCKDURATION_ATTRIBUTE, check_pw_duration_value, -1, -1},
|
||||
{CONFIG_PW_RESETFAILURECOUNT_ATTRIBUTE, check_pw_resetfailurecount_value, -1, -1},
|
||||
{CONFIG_PW_GRACELIMIT_ATTRIBUTE, attr_check_minmax, 0, -1},
|
||||
diff --git a/ldap/servers/slapd/pw.c b/ldap/servers/slapd/pw.c
|
||||
index cda1c404f..12cf759ce 100644
|
||||
--- a/ldap/servers/slapd/pw.c
|
||||
+++ b/ldap/servers/slapd/pw.c
|
||||
@@ -1532,7 +1532,18 @@ update_pw_history(Slapi_PBlock *pb, const Slapi_DN *sdn, char *old_pw)
|
||||
pwpolicy = new_passwdPolicy(pb, dn);
|
||||
|
||||
if (pwpolicy->pw_inhistory == 0){
|
||||
- /* We are only enforcing the current password, just return */
|
||||
+ /* We are only enforcing the current password, just return but first
|
||||
+ * cleanup any old passwords in the history */
|
||||
+ attribute.mod_type = "passwordHistory";
|
||||
+ attribute.mod_op = LDAP_MOD_REPLACE;
|
||||
+ attribute.mod_values = NULL;
|
||||
+ list_of_mods[0] = &attribute;
|
||||
+ list_of_mods[1] = NULL;
|
||||
+ mod_pb = slapi_pblock_new();
|
||||
+ slapi_modify_internal_set_pb_ext(mod_pb, sdn, list_of_mods, NULL, NULL, pw_get_componentID(), 0);
|
||||
+ slapi_modify_internal_pb(mod_pb);
|
||||
+ slapi_pblock_destroy(mod_pb);
|
||||
+
|
||||
return res;
|
||||
}
|
||||
|
||||
--
|
||||
2.52.0
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
260
SOURCES/0031-Issue-7243-UI-fix-certificate-table-and-modal.patch
Normal file
260
SOURCES/0031-Issue-7243-UI-fix-certificate-table-and-modal.patch
Normal file
@ -0,0 +1,260 @@
|
||||
From 4209ff65310c54b6ab55594984201b1214fb75b2 Mon Sep 17 00:00:00 2001
|
||||
From: Mark Reynolds <mreynolds@redhat.com>
|
||||
Date: Mon, 16 Feb 2026 16:52:52 -0500
|
||||
Subject: [PATCH] Issue 7243 - UI - fix certificate table and modal
|
||||
|
||||
Description:
|
||||
|
||||
The certificate table was not handling sorting and pagination correctly, and
|
||||
the add cert modal was not correctly selecting certs on disk
|
||||
|
||||
This was fixed upstream for the hot cetificate features, but only backporting
|
||||
the table/modal changes to older branches
|
||||
|
||||
relates: https://github.com/389ds/389-ds-base/issues/7243
|
||||
|
||||
Reviewed by: spichugi(Thanks!)
|
||||
---
|
||||
.../src/lib/security/securityModals.jsx | 4 +-
|
||||
.../src/lib/security/securityTables.jsx | 85 ++++++-------------
|
||||
2 files changed, 29 insertions(+), 60 deletions(-)
|
||||
|
||||
diff --git a/src/cockpit/389-console/src/lib/security/securityModals.jsx b/src/cockpit/389-console/src/lib/security/securityModals.jsx
|
||||
index a42aa50d6..c4af753c0 100644
|
||||
--- a/src/cockpit/389-console/src/lib/security/securityModals.jsx
|
||||
+++ b/src/cockpit/389-console/src/lib/security/securityModals.jsx
|
||||
@@ -298,7 +298,9 @@ export class SecurityAddCertModal extends React.Component {
|
||||
<FormSelect
|
||||
value={selectCertName}
|
||||
id="selectCertName"
|
||||
- onChange={handleCertSelect}
|
||||
+ onChange={(e, str) => {
|
||||
+ handleCertSelect(str);
|
||||
+ }}
|
||||
aria-label="FormSelect Input"
|
||||
className="ds-cert-select"
|
||||
validated={selectValidated}
|
||||
diff --git a/src/cockpit/389-console/src/lib/security/securityTables.jsx b/src/cockpit/389-console/src/lib/security/securityTables.jsx
|
||||
index fce4cb04e..a6c64e6c1 100644
|
||||
--- a/src/cockpit/389-console/src/lib/security/securityTables.jsx
|
||||
+++ b/src/cockpit/389-console/src/lib/security/securityTables.jsx
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
Grid,
|
||||
GridItem,
|
||||
Pagination,
|
||||
- PaginationVariant,
|
||||
SearchInput,
|
||||
Tooltip,
|
||||
} from '@patternfly/react-core';
|
||||
@@ -94,10 +93,10 @@ class KeyTable extends React.Component {
|
||||
];
|
||||
|
||||
handleSort(_event, index, direction) {
|
||||
- const sortedRows = [...this.state.rows].sort((a, b) =>
|
||||
+ const sortedRows = [...this.state.rows].sort((a, b) =>
|
||||
(a[index] < b[index] ? -1 : a[index] > b[index] ? 1 : 0)
|
||||
);
|
||||
-
|
||||
+
|
||||
this.setState({
|
||||
sortBy: {
|
||||
index,
|
||||
@@ -127,7 +126,7 @@ class KeyTable extends React.Component {
|
||||
>
|
||||
<a className="ds-font-size-sm">{_("What is an orphan key?")}</a>
|
||||
</Tooltip>
|
||||
- <Table
|
||||
+ <Table
|
||||
className="ds-margin-top"
|
||||
aria-label="orph key table"
|
||||
variant="compact"
|
||||
@@ -135,7 +134,7 @@ class KeyTable extends React.Component {
|
||||
<Thead>
|
||||
<Tr>
|
||||
{columns.map((column, idx) => (
|
||||
- <Th
|
||||
+ <Th
|
||||
key={idx}
|
||||
sort={column.sortable ? {
|
||||
sortBy,
|
||||
@@ -163,7 +162,7 @@ class KeyTable extends React.Component {
|
||||
)}
|
||||
{hasRows && (
|
||||
<Td isActionCell>
|
||||
- <ActionsColumn
|
||||
+ <ActionsColumn
|
||||
items={this.getActionsForRow(row)}
|
||||
/>
|
||||
</Td>
|
||||
@@ -224,7 +223,7 @@ class CSRTable extends React.Component {
|
||||
}
|
||||
|
||||
handleSort(_event, index, direction) {
|
||||
- const sortedRows = [...this.state.rows].sort((a, b) =>
|
||||
+ const sortedRows = [...this.state.rows].sort((a, b) =>
|
||||
(a[index] < b[index] ? -1 : a[index] > b[index] ? 1 : 0)
|
||||
);
|
||||
this.setState({
|
||||
@@ -316,7 +315,7 @@ class CSRTable extends React.Component {
|
||||
onClear={(evt) => this.handleSearchChange(evt, '')}
|
||||
/>
|
||||
}
|
||||
- <Table
|
||||
+ <Table
|
||||
className="ds-margin-top"
|
||||
aria-label="csr table"
|
||||
variant="compact"
|
||||
@@ -324,7 +323,7 @@ class CSRTable extends React.Component {
|
||||
<Thead>
|
||||
<Tr>
|
||||
{columns.map((column, idx) => (
|
||||
- <Th
|
||||
+ <Th
|
||||
key={idx}
|
||||
sort={column.sortable ? {
|
||||
sortBy,
|
||||
@@ -352,7 +351,7 @@ class CSRTable extends React.Component {
|
||||
)}
|
||||
{hasRows && (
|
||||
<Td isActionCell>
|
||||
- <ActionsColumn
|
||||
+ <ActionsColumn
|
||||
items={this.getActionsForRow(row)}
|
||||
/>
|
||||
</Td>
|
||||
@@ -418,41 +417,11 @@ class CertTable extends React.Component {
|
||||
}
|
||||
|
||||
handleSort(_event, columnIndex, direction) {
|
||||
- const sorted_rows = [];
|
||||
- const rows = [];
|
||||
- let count = 0;
|
||||
-
|
||||
- // Convert the rows pairings into a sortable array
|
||||
- for (let idx = 0; idx < this.state.rows.length; idx += 2) {
|
||||
- sorted_rows.push({
|
||||
- expandedRow: this.state.rows[idx + 1],
|
||||
- 1: this.state.rows[idx].cells[0].content,
|
||||
- 2: this.state.rows[idx].cells[1].content,
|
||||
- 3: this.state.rows[idx].cells[2].content,
|
||||
- issuer: this.state.rows[idx].issuer,
|
||||
- flags: this.state.rows[idx].flags
|
||||
- });
|
||||
- }
|
||||
+ const rows = [...this.state.rows];
|
||||
|
||||
- sorted_rows.sort((a, b) => (a[columnIndex + 1] > b[columnIndex + 1]) ? 1 : -1);
|
||||
+ rows.sort((a, b) => (a.cells[columnIndex].content > b.cells[columnIndex].content) ? 1 : -1);
|
||||
if (direction !== SortByDirection.asc) {
|
||||
- sorted_rows.reverse();
|
||||
- }
|
||||
-
|
||||
- for (const srow of sorted_rows) {
|
||||
- rows.push({
|
||||
- isOpen: false,
|
||||
- cells: [
|
||||
- { content: srow[1] },
|
||||
- { content: srow[2] },
|
||||
- { content: srow[3] }
|
||||
- ],
|
||||
- issuer: srow.issuer,
|
||||
- flags: srow.flags,
|
||||
- });
|
||||
- srow.expandedRow.parent = count;
|
||||
- rows.push(srow.expandedRow);
|
||||
- count += 2;
|
||||
+ rows.reverse();
|
||||
}
|
||||
|
||||
this.setState({
|
||||
@@ -534,18 +503,16 @@ class CertTable extends React.Component {
|
||||
rows.push(
|
||||
{
|
||||
isOpen: false,
|
||||
- cells: [cert.attrs.nickname, cert.attrs.subject, cert.attrs.expires],
|
||||
+ cells: [
|
||||
+ { content: cert.attrs.nickname },
|
||||
+ { content: cert.attrs.subject },
|
||||
+ { content: cert.attrs.expires }
|
||||
+ ],
|
||||
issuer: cert.attrs.issuer,
|
||||
flags: cert.attrs.flags,
|
||||
-
|
||||
- },
|
||||
- {
|
||||
- parent: count,
|
||||
- fullWidth: true,
|
||||
- cells: [{ title: this.getExpandedRow(cert.attrs.issuer, cert.attrs.flags) }]
|
||||
- },
|
||||
+ }
|
||||
);
|
||||
- count += 2;
|
||||
+ count += 1;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
@@ -587,7 +554,7 @@ class CertTable extends React.Component {
|
||||
onChange={this.handleSearchChange}
|
||||
onClear={(evt) => this.handleSearchChange(evt, '')}
|
||||
/>}
|
||||
- <Table
|
||||
+ <Table
|
||||
aria-label="cert table"
|
||||
variant='compact'
|
||||
>
|
||||
@@ -626,7 +593,7 @@ class CertTable extends React.Component {
|
||||
</Td>
|
||||
))}
|
||||
<Td isActionCell>
|
||||
- <ActionsColumn
|
||||
+ <ActionsColumn
|
||||
items={this.getActionsForRow(row)}
|
||||
/>
|
||||
</Td>
|
||||
@@ -646,7 +613,7 @@ class CertTable extends React.Component {
|
||||
</Table>
|
||||
{hasRows &&
|
||||
<Pagination
|
||||
- itemCount={this.state.rows.length / 2}
|
||||
+ itemCount={this.state.rows.length}
|
||||
widgetId="pagination-options-menu-bottom"
|
||||
perPage={perPage}
|
||||
page={page}
|
||||
@@ -728,7 +695,7 @@ class CRLTable extends React.Component {
|
||||
if (direction !== SortByDirection.asc) {
|
||||
sorted_rows.reverse();
|
||||
}
|
||||
-
|
||||
+
|
||||
for (const srow of sorted_rows) {
|
||||
rows.push({
|
||||
isOpen: false,
|
||||
@@ -806,14 +773,14 @@ class CRLTable extends React.Component {
|
||||
onChange={this.handleSearchChange}
|
||||
onClear={(evt) => this.handleSearchChange(evt, '')}
|
||||
/>
|
||||
- <Table
|
||||
+ <Table
|
||||
aria-label="CRL Table"
|
||||
variant="compact"
|
||||
>
|
||||
<Thead>
|
||||
<Tr>
|
||||
{this.state.columns.map((column, idx) => (
|
||||
- <Th
|
||||
+ <Th
|
||||
key={idx}
|
||||
sort={column.sortable ? {
|
||||
sortBy: this.state.sortBy,
|
||||
@@ -836,7 +803,7 @@ class CRLTable extends React.Component {
|
||||
))}
|
||||
{this.state.hasRows && (
|
||||
<Td isActionCell>
|
||||
- <ActionsColumn
|
||||
+ <ActionsColumn
|
||||
items={this.getActionsForRow(row)}
|
||||
/>
|
||||
</Td>
|
||||
--
|
||||
2.52.0
|
||||
|
||||
@ -1,319 +0,0 @@
|
||||
From 7d534efdcd96b13524dae587c3c5994ed01924ab Mon Sep 17 00:00:00 2001
|
||||
From: Simon Pichugin <spichugi@redhat.com>
|
||||
Date: Fri, 16 Feb 2024 13:52:36 -0800
|
||||
Subject: [PATCH] Issue 6067 - Improve dsidm CLI No Such Entry handling (#6079)
|
||||
|
||||
Description: Add additional error processing to dsidm CLI tool for when basedn
|
||||
or OU subentries are absent.
|
||||
|
||||
Related: https://github.com/389ds/389-ds-base/issues/6067
|
||||
|
||||
Reviewed by: @vashirov (Thanks!)
|
||||
---
|
||||
src/lib389/cli/dsidm | 21 ++++++++-------
|
||||
src/lib389/lib389/cli_idm/__init__.py | 38 ++++++++++++++++++++++++++-
|
||||
src/lib389/lib389/cli_idm/account.py | 4 +--
|
||||
src/lib389/lib389/cli_idm/service.py | 4 ++-
|
||||
src/lib389/lib389/idm/group.py | 10 ++++---
|
||||
src/lib389/lib389/idm/posixgroup.py | 5 ++--
|
||||
src/lib389/lib389/idm/services.py | 5 ++--
|
||||
src/lib389/lib389/idm/user.py | 5 ++--
|
||||
8 files changed, 67 insertions(+), 25 deletions(-)
|
||||
|
||||
diff --git a/src/lib389/cli/dsidm b/src/lib389/cli/dsidm
|
||||
index 1b739b103..970973f4f 100755
|
||||
--- a/src/lib389/cli/dsidm
|
||||
+++ b/src/lib389/cli/dsidm
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
# --- BEGIN COPYRIGHT BLOCK ---
|
||||
# Copyright (C) 2016, William Brown <william at blackhats.net.au>
|
||||
-# Copyright (C) 2023 Red Hat, Inc.
|
||||
+# Copyright (C) 2024 Red Hat, Inc.
|
||||
# All rights reserved.
|
||||
#
|
||||
# License: GPL (version 3 or any later version).
|
||||
@@ -19,6 +19,7 @@ import argparse
|
||||
import argcomplete
|
||||
from lib389.utils import get_instance_list, instance_choices
|
||||
from lib389._constants import DSRC_HOME
|
||||
+from lib389.cli_idm import _get_basedn_arg
|
||||
from lib389.cli_idm import account as cli_account
|
||||
from lib389.cli_idm import initialise as cli_init
|
||||
from lib389.cli_idm import organizationalunit as cli_ou
|
||||
@@ -117,14 +118,6 @@ if __name__ == '__main__':
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
- if dsrc_inst['basedn'] is None:
|
||||
- errmsg = "Must provide a basedn!"
|
||||
- if args.json:
|
||||
- sys.stderr.write('{"desc": "%s"}\n' % errmsg)
|
||||
- else:
|
||||
- log.error(errmsg)
|
||||
- sys.exit(1)
|
||||
-
|
||||
if not args.verbose:
|
||||
signal.signal(signal.SIGINT, signal_handler)
|
||||
|
||||
@@ -135,7 +128,15 @@ if __name__ == '__main__':
|
||||
result = False
|
||||
try:
|
||||
inst = connect_instance(dsrc_inst=dsrc_inst, verbose=args.verbose, args=args)
|
||||
- result = args.func(inst, dsrc_inst['basedn'], log, args)
|
||||
+ basedn = _get_basedn_arg(inst, args, log, msg="Enter basedn")
|
||||
+ if basedn is None:
|
||||
+ errmsg = "Must provide a basedn!"
|
||||
+ if args.json:
|
||||
+ sys.stderr.write('{"desc": "%s"}\n' % errmsg)
|
||||
+ else:
|
||||
+ log.error(errmsg)
|
||||
+ sys.exit(1)
|
||||
+ result = args.func(inst, basedn, log, args)
|
||||
if args.verbose:
|
||||
log.info("Command successful.")
|
||||
except Exception as e:
|
||||
diff --git a/src/lib389/lib389/cli_idm/__init__.py b/src/lib389/lib389/cli_idm/__init__.py
|
||||
index 0dab54847..e3622246d 100644
|
||||
--- a/src/lib389/lib389/cli_idm/__init__.py
|
||||
+++ b/src/lib389/lib389/cli_idm/__init__.py
|
||||
@@ -1,15 +1,30 @@
|
||||
# --- BEGIN COPYRIGHT BLOCK ---
|
||||
# Copyright (C) 2016, William Brown <william at blackhats.net.au>
|
||||
-# Copyright (C) 2023 Red Hat, Inc.
|
||||
+# Copyright (C) 2024 Red Hat, Inc.
|
||||
# All rights reserved.
|
||||
#
|
||||
# License: GPL (version 3 or any later version).
|
||||
# See LICENSE for details.
|
||||
# --- END COPYRIGHT BLOCK ---
|
||||
|
||||
+import sys
|
||||
import ldap
|
||||
from getpass import getpass
|
||||
import json
|
||||
+from lib389._mapped_object import DSLdapObject
|
||||
+from lib389.cli_base import _get_dn_arg
|
||||
+from lib389.idm.user import DEFAULT_BASEDN_RDN as DEFAULT_BASEDN_RDN_USER
|
||||
+from lib389.idm.group import DEFAULT_BASEDN_RDN as DEFAULT_BASEDN_RDN_GROUP
|
||||
+from lib389.idm.posixgroup import DEFAULT_BASEDN_RDN as DEFAULT_BASEDN_RDN_POSIXGROUP
|
||||
+from lib389.idm.services import DEFAULT_BASEDN_RDN as DEFAULT_BASEDN_RDN_SERVICES
|
||||
+
|
||||
+# The key is module name, the value is default RDN
|
||||
+BASEDN_RDNS = {
|
||||
+ 'user': DEFAULT_BASEDN_RDN_USER,
|
||||
+ 'group': DEFAULT_BASEDN_RDN_GROUP,
|
||||
+ 'posixgroup': DEFAULT_BASEDN_RDN_POSIXGROUP,
|
||||
+ 'service': DEFAULT_BASEDN_RDN_SERVICES,
|
||||
+}
|
||||
|
||||
|
||||
def _get_arg(args, msg=None):
|
||||
@@ -37,6 +52,27 @@ def _get_args(args, kws):
|
||||
return kwargs
|
||||
|
||||
|
||||
+def _get_basedn_arg(inst, args, log, msg=None):
|
||||
+ basedn_arg = _get_dn_arg(args.basedn, msg="Enter basedn")
|
||||
+ if not DSLdapObject(inst, basedn_arg).exists():
|
||||
+ raise ValueError(f'The base DN "{basedn_arg}" does not exist.')
|
||||
+
|
||||
+ # Get the RDN based on the last part of the module name if applicable
|
||||
+ # (lib389.cli_idm.user -> user)
|
||||
+ try:
|
||||
+ command_name = args.func.__module__.split('.')[-1]
|
||||
+ object_rdn = BASEDN_RDNS[command_name]
|
||||
+ # Check if the DN for our command exists
|
||||
+ command_basedn = f'{object_rdn},{basedn_arg}'
|
||||
+ if not DSLdapObject(inst, command_basedn).exists():
|
||||
+ errmsg = f'The DN "{command_basedn}" does not exist.'
|
||||
+ errmsg += f' It is required for "{command_name}" subcommand. Please create it first.'
|
||||
+ raise ValueError(errmsg)
|
||||
+ except KeyError:
|
||||
+ pass
|
||||
+ return basedn_arg
|
||||
+
|
||||
+
|
||||
# This is really similar to get_args, but generates from an array
|
||||
def _get_attributes(args, attrs):
|
||||
kwargs = {}
|
||||
diff --git a/src/lib389/lib389/cli_idm/account.py b/src/lib389/lib389/cli_idm/account.py
|
||||
index 5d7b9cc77..15f766588 100644
|
||||
--- a/src/lib389/lib389/cli_idm/account.py
|
||||
+++ b/src/lib389/lib389/cli_idm/account.py
|
||||
@@ -1,5 +1,5 @@
|
||||
# --- BEGIN COPYRIGHT BLOCK ---
|
||||
-# Copyright (C) 2023, Red Hat inc,
|
||||
+# Copyright (C) 2024, Red Hat inc,
|
||||
# Copyright (C) 2018, William Brown <william@blackhats.net.au>
|
||||
# All rights reserved.
|
||||
#
|
||||
@@ -91,7 +91,6 @@ def entry_status(inst, basedn, log, args):
|
||||
|
||||
|
||||
def subtree_status(inst, basedn, log, args):
|
||||
- basedn = _get_dn_arg(args.basedn, msg="Enter basedn to check")
|
||||
filter = ""
|
||||
scope = ldap.SCOPE_SUBTREE
|
||||
epoch_inactive_time = None
|
||||
@@ -121,7 +120,6 @@ def subtree_status(inst, basedn, log, args):
|
||||
|
||||
|
||||
def bulk_update(inst, basedn, log, args):
|
||||
- basedn = _get_dn_arg(args.basedn, msg="Enter basedn to search")
|
||||
search_filter = "(objectclass=*)"
|
||||
scope = ldap.SCOPE_SUBTREE
|
||||
scope_str = "sub"
|
||||
diff --git a/src/lib389/lib389/cli_idm/service.py b/src/lib389/lib389/cli_idm/service.py
|
||||
index c62fc12d1..c2b2c8c84 100644
|
||||
--- a/src/lib389/lib389/cli_idm/service.py
|
||||
+++ b/src/lib389/lib389/cli_idm/service.py
|
||||
@@ -57,7 +57,9 @@ def rename(inst, basedn, log, args, warn=True):
|
||||
_generic_rename(inst, basedn, log.getChild('_generic_rename'), MANY, rdn, args)
|
||||
|
||||
def create_parser(subparsers):
|
||||
- service_parser = subparsers.add_parser('service', help='Manage service accounts', formatter_class=CustomHelpFormatter)
|
||||
+ service_parser = subparsers.add_parser('service',
|
||||
+ help='Manage service accounts. The organizationalUnit (by default "ou=Services") '
|
||||
+ 'needs to exist prior to managing service accounts.', formatter_class=CustomHelpFormatter)
|
||||
|
||||
subcommands = service_parser.add_subparsers(help='action')
|
||||
|
||||
diff --git a/src/lib389/lib389/idm/group.py b/src/lib389/lib389/idm/group.py
|
||||
index 1b60a1f51..2cf2c7b23 100644
|
||||
--- a/src/lib389/lib389/idm/group.py
|
||||
+++ b/src/lib389/lib389/idm/group.py
|
||||
@@ -1,6 +1,6 @@
|
||||
# --- BEGIN COPYRIGHT BLOCK ---
|
||||
# Copyright (C) 2016, William Brown <william at blackhats.net.au>
|
||||
-# Copyright (C) 2023 Red Hat, Inc.
|
||||
+# Copyright (C) 2024 Red Hat, Inc.
|
||||
# All rights reserved.
|
||||
#
|
||||
# License: GPL (version 3 or any later version).
|
||||
@@ -16,6 +16,8 @@ MUST_ATTRIBUTES = [
|
||||
'cn',
|
||||
]
|
||||
RDN = 'cn'
|
||||
+DEFAULT_BASEDN_RDN = 'ou=Groups'
|
||||
+DEFAULT_BASEDN_RDN_ADMIN_GROUPS = 'ou=People'
|
||||
|
||||
|
||||
class Group(DSLdapObject):
|
||||
@@ -93,7 +95,7 @@ class Groups(DSLdapObjects):
|
||||
:type basedn: str
|
||||
"""
|
||||
|
||||
- def __init__(self, instance, basedn, rdn='ou=Groups'):
|
||||
+ def __init__(self, instance, basedn, rdn=DEFAULT_BASEDN_RDN):
|
||||
super(Groups, self).__init__(instance)
|
||||
self._objectclasses = [
|
||||
'groupOfNames',
|
||||
@@ -140,7 +142,7 @@ class UniqueGroup(DSLdapObject):
|
||||
class UniqueGroups(DSLdapObjects):
|
||||
# WARNING!!!
|
||||
# Use group, not unique group!!!
|
||||
- def __init__(self, instance, basedn, rdn='ou=Groups'):
|
||||
+ def __init__(self, instance, basedn, rdn=DEFAULT_BASEDN_RDN):
|
||||
super(UniqueGroups, self).__init__(instance)
|
||||
self._objectclasses = [
|
||||
'groupOfUniqueNames',
|
||||
@@ -203,7 +205,7 @@ class nsAdminGroups(DSLdapObjects):
|
||||
:type rdn: str
|
||||
"""
|
||||
|
||||
- def __init__(self, instance, basedn, rdn='ou=People'):
|
||||
+ def __init__(self, instance, basedn, rdn=DEFAULT_BASEDN_RDN_ADMIN_GROUPS):
|
||||
super(nsAdminGroups, self).__init__(instance)
|
||||
self._objectclasses = [
|
||||
'nsAdminGroup'
|
||||
diff --git a/src/lib389/lib389/idm/posixgroup.py b/src/lib389/lib389/idm/posixgroup.py
|
||||
index d1debcf12..45735c579 100644
|
||||
--- a/src/lib389/lib389/idm/posixgroup.py
|
||||
+++ b/src/lib389/lib389/idm/posixgroup.py
|
||||
@@ -1,6 +1,6 @@
|
||||
# --- BEGIN COPYRIGHT BLOCK ---
|
||||
# Copyright (C) 2016, William Brown <william at blackhats.net.au>
|
||||
-# Copyright (C) 2023 Red Hat, Inc.
|
||||
+# Copyright (C) 2024 Red Hat, Inc.
|
||||
# All rights reserved.
|
||||
#
|
||||
# License: GPL (version 3 or any later version).
|
||||
@@ -17,6 +17,7 @@ MUST_ATTRIBUTES = [
|
||||
'gidNumber',
|
||||
]
|
||||
RDN = 'cn'
|
||||
+DEFAULT_BASEDN_RDN = 'ou=Groups'
|
||||
|
||||
|
||||
class PosixGroup(DSLdapObject):
|
||||
@@ -72,7 +73,7 @@ class PosixGroups(DSLdapObjects):
|
||||
:type basedn: str
|
||||
"""
|
||||
|
||||
- def __init__(self, instance, basedn, rdn='ou=Groups'):
|
||||
+ def __init__(self, instance, basedn, rdn=DEFAULT_BASEDN_RDN):
|
||||
super(PosixGroups, self).__init__(instance)
|
||||
self._objectclasses = [
|
||||
'groupOfNames',
|
||||
diff --git a/src/lib389/lib389/idm/services.py b/src/lib389/lib389/idm/services.py
|
||||
index d1e5b4693..e750a32c4 100644
|
||||
--- a/src/lib389/lib389/idm/services.py
|
||||
+++ b/src/lib389/lib389/idm/services.py
|
||||
@@ -1,6 +1,6 @@
|
||||
# --- BEGIN COPYRIGHT BLOCK ---
|
||||
# Copyright (C) 2016, William Brown <william at blackhats.net.au>
|
||||
-# Copyright (C) 2021 Red Hat, Inc.
|
||||
+# Copyright (C) 2024 Red Hat, Inc.
|
||||
# All rights reserved.
|
||||
#
|
||||
# License: GPL (version 3 or any later version).
|
||||
@@ -16,6 +16,7 @@ RDN = 'cn'
|
||||
MUST_ATTRIBUTES = [
|
||||
'cn',
|
||||
]
|
||||
+DEFAULT_BASEDN_RDN = 'ou=Services'
|
||||
|
||||
class ServiceAccount(Account):
|
||||
"""A single instance of Service entry
|
||||
@@ -59,7 +60,7 @@ class ServiceAccounts(DSLdapObjects):
|
||||
:type basedn: str
|
||||
"""
|
||||
|
||||
- def __init__(self, instance, basedn, rdn='ou=Services'):
|
||||
+ def __init__(self, instance, basedn, rdn=DEFAULT_BASEDN_RDN):
|
||||
super(ServiceAccounts, self).__init__(instance)
|
||||
self._objectclasses = [
|
||||
'applicationProcess',
|
||||
diff --git a/src/lib389/lib389/idm/user.py b/src/lib389/lib389/idm/user.py
|
||||
index 1206a6e08..3b21ccf1c 100644
|
||||
--- a/src/lib389/lib389/idm/user.py
|
||||
+++ b/src/lib389/lib389/idm/user.py
|
||||
@@ -1,6 +1,6 @@
|
||||
# --- BEGIN COPYRIGHT BLOCK ---
|
||||
# Copyright (C) 2016, William Brown <william at blackhats.net.au>
|
||||
-# Copyright (C) 2023 Red Hat, Inc.
|
||||
+# Copyright (C) 2024 Red Hat, Inc.
|
||||
# All rights reserved.
|
||||
#
|
||||
# License: GPL (version 3 or any later version).
|
||||
@@ -23,6 +23,7 @@ MUST_ATTRIBUTES = [
|
||||
'homeDirectory',
|
||||
]
|
||||
RDN = 'uid'
|
||||
+DEFAULT_BASEDN_RDN = 'ou=People'
|
||||
|
||||
TEST_USER_PROPERTIES = {
|
||||
'uid': 'testuser',
|
||||
@@ -201,7 +202,7 @@ class UserAccounts(DSLdapObjects):
|
||||
:type rdn: str
|
||||
"""
|
||||
|
||||
- def __init__(self, instance, basedn, rdn='ou=People'):
|
||||
+ def __init__(self, instance, basedn, rdn=DEFAULT_BASEDN_RDN):
|
||||
super(UserAccounts, self).__init__(instance)
|
||||
self._objectclasses = [
|
||||
'account',
|
||||
--
|
||||
2.48.1
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
From 67c3183380888f4af093b546e717f3e7451a41d6 Mon Sep 17 00:00:00 2001
|
||||
From 09b6f770f15ee8d333377b7902e18b78f4f7008c Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Wed, 18 Feb 2026 09:26:57 +0100
|
||||
Subject: [PATCH] Issue 7223 - Remove integerOrderingMatch requirement for
|
||||
@ -19,17 +19,17 @@ Relates: https://github.com/389ds/389-ds-base/pull/7223
|
||||
Reviewed by: @progier389, @tbordaz (Thanks!)
|
||||
---
|
||||
.../healthcheck/health_system_indexes_test.py | 83 ++++----------
|
||||
ldap/servers/slapd/upgrade.c | 106 ------------------
|
||||
ldap/servers/slapd/upgrade.c | 105 ------------------
|
||||
rpm/389-ds-base.spec.in | 3 -
|
||||
src/lib389/lib389/backend.py | 5 +-
|
||||
src/lib389/lib389/cli_ctl/dbtasks.py | 99 ++++++++--------
|
||||
5 files changed, 73 insertions(+), 223 deletions(-)
|
||||
src/lib389/lib389/cli_ctl/dbtasks.py | 99 ++++++++---------
|
||||
5 files changed, 73 insertions(+), 222 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
|
||||
index ce86239e5..088b48587 100644
|
||||
index 3b3651b38..71a9f590a 100644
|
||||
--- a/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
|
||||
+++ b/dirsrvtests/tests/suites/healthcheck/health_system_indexes_test.py
|
||||
@@ -179,7 +179,8 @@ def test_missing_parentid(topology_st, log_buffering_enabled):
|
||||
@@ -180,7 +180,8 @@ def test_missing_parentid(topology_st, log_buffering_enabled):
|
||||
|
||||
|
||||
def test_missing_matching_rule(topology_st, log_buffering_enabled):
|
||||
@ -39,7 +39,7 @@ index ce86239e5..088b48587 100644
|
||||
|
||||
:id: 7ffa71db-8995-430a-bed8-59bce944221c
|
||||
:setup: Standalone instance
|
||||
@@ -189,19 +190,14 @@ def test_missing_matching_rule(topology_st, log_buffering_enabled):
|
||||
@@ -190,19 +191,14 @@ def test_missing_matching_rule(topology_st, log_buffering_enabled):
|
||||
3. Use healthcheck without --json option
|
||||
4. Use healthcheck with --json option
|
||||
5. Re-add the matching rule
|
||||
@ -61,7 +61,7 @@ index ce86239e5..088b48587 100644
|
||||
PARENTID_DN = "cn=parentid,cn=index,cn=userroot,cn=ldbm database,cn=plugins,cn=config"
|
||||
|
||||
standalone = topology_st.standalone
|
||||
@@ -210,16 +206,13 @@ def test_missing_matching_rule(topology_st, log_buffering_enabled):
|
||||
@@ -211,17 +207,14 @@ def test_missing_matching_rule(topology_st, log_buffering_enabled):
|
||||
parentid_index = Index(standalone, PARENTID_DN)
|
||||
parentid_index.remove("nsMatchingRule", "integerOrderingMatch")
|
||||
|
||||
@ -73,6 +73,7 @@ index ce86239e5..088b48587 100644
|
||||
log.info("Re-add the integerOrderingMatch matching rule")
|
||||
parentid_index = Index(standalone, PARENTID_DN)
|
||||
parentid_index.add("nsMatchingRule", "integerOrderingMatch")
|
||||
standalone.restart()
|
||||
|
||||
- run_healthcheck_and_flush_log(topology_st, standalone, json=False, searched_code=CMD_OUTPUT)
|
||||
- run_healthcheck_and_flush_log(topology_st, standalone, json=True, searched_code=JSON_OUTPUT)
|
||||
@ -80,7 +81,7 @@ index ce86239e5..088b48587 100644
|
||||
|
||||
def test_usn_plugin_missing_entryusn(topology_st, usn_plugin_enabled, log_buffering_enabled):
|
||||
"""Check if healthcheck returns DSBLE0007 code when USN plugin is enabled but entryusn index is missing
|
||||
@@ -908,7 +901,9 @@ def test_index_check_fixes_ancestorid_config(topology_st):
|
||||
@@ -911,7 +904,9 @@ def test_index_check_fixes_ancestorid_config(topology_st):
|
||||
|
||||
|
||||
def test_index_check_fixes_missing_matching_rule(topology_st):
|
||||
@ -91,7 +92,7 @@ index ce86239e5..088b48587 100644
|
||||
|
||||
:id: 6c1d4e9f-0a3b-4d5c-1e7f-8a9b0c2d3e4f
|
||||
:setup: Standalone instance
|
||||
@@ -916,18 +911,14 @@ def test_index_check_fixes_missing_matching_rule(topology_st):
|
||||
@@ -919,18 +914,14 @@ def test_index_check_fixes_missing_matching_rule(topology_st):
|
||||
1. Create DS instance
|
||||
2. Stop the server
|
||||
3. Remove integerOrderingMatch from parentid index using DSEldif
|
||||
@ -114,7 +115,7 @@ index ce86239e5..088b48587 100644
|
||||
"""
|
||||
from lib389.cli_ctl.dbtasks import dbtasks_index_check
|
||||
from lib389.dseldif import DSEldif
|
||||
@@ -961,34 +952,20 @@ def test_index_check_fixes_missing_matching_rule(topology_st):
|
||||
@@ -964,34 +955,20 @@ def test_index_check_fixes_missing_matching_rule(topology_st):
|
||||
f"integerOrderingMatch should be removed, but found: {mr}"
|
||||
log.info("integerOrderingMatch removed from parentid index")
|
||||
|
||||
@ -156,7 +157,7 @@ index ce86239e5..088b48587 100644
|
||||
|
||||
log.info("Start the server")
|
||||
standalone.start()
|
||||
@@ -1078,7 +1055,7 @@ def test_index_check_fixes_multiple_issues(topology_st):
|
||||
@@ -1081,7 +1058,7 @@ def test_index_check_fixes_multiple_issues(topology_st):
|
||||
:steps:
|
||||
1. Create DS instance
|
||||
2. Stop the server
|
||||
@ -165,7 +166,7 @@ index ce86239e5..088b48587 100644
|
||||
4. Run dsctl index-check (should detect all issues)
|
||||
5. Run dsctl index-check --fix
|
||||
6. Verify all issues were fixed
|
||||
@@ -1120,14 +1097,6 @@ def test_index_check_fixes_multiple_issues(topology_st):
|
||||
@@ -1123,14 +1100,6 @@ def test_index_check_fixes_multiple_issues(topology_st):
|
||||
]
|
||||
dse_ldif.add_entry(ancestorid_entry)
|
||||
|
||||
@ -180,7 +181,7 @@ index ce86239e5..088b48587 100644
|
||||
log.info("Run index-check without --fix (should detect all issues)")
|
||||
args = FakeArgs()
|
||||
args.backend = "userRoot"
|
||||
@@ -1158,16 +1127,6 @@ def test_index_check_fixes_multiple_issues(topology_st):
|
||||
@@ -1161,16 +1130,6 @@ def test_index_check_fixes_multiple_issues(topology_st):
|
||||
cn_value = dse_ldif.get(ancestorid_dn, "cn", single=True)
|
||||
assert cn_value is None, f"ancestorid config should be removed, got: {cn_value}"
|
||||
|
||||
@ -198,15 +199,15 @@ index ce86239e5..088b48587 100644
|
||||
|
||||
log.info("Run index-check again to confirm all clear")
|
||||
diff --git a/ldap/servers/slapd/upgrade.c b/ldap/servers/slapd/upgrade.c
|
||||
index aa51e72d2..4eb09ca38 100644
|
||||
index d9156cae9..537c38feb 100644
|
||||
--- a/ldap/servers/slapd/upgrade.c
|
||||
+++ b/ldap/servers/slapd/upgrade.c
|
||||
@@ -243,108 +243,6 @@ upgrade_remove_ancestorid_index_config(void)
|
||||
@@ -500,107 +500,6 @@ upgrade_remove_ancestorid_index_config(void)
|
||||
return uresult;
|
||||
}
|
||||
|
||||
-/*
|
||||
- * Check if parentid indexes are missing the integerOrderingMatch
|
||||
- * Check if parentid/ancestorid indexes are missing the integerOrderingMatch
|
||||
- * matching rule.
|
||||
- *
|
||||
- * This function logs a warning if we detect this condition, advising
|
||||
@ -306,11 +307,10 @@ index aa51e72d2..4eb09ca38 100644
|
||||
-
|
||||
- return uresult;
|
||||
-}
|
||||
-
|
||||
|
||||
upgrade_status
|
||||
upgrade_server(void)
|
||||
{
|
||||
@@ -356,9 +254,5 @@ upgrade_server(void)
|
||||
@@ -637,10 +536,6 @@ upgrade_server(void)
|
||||
return UPGRADE_FAILURE;
|
||||
}
|
||||
|
||||
@ -320,11 +320,12 @@ index aa51e72d2..4eb09ca38 100644
|
||||
-
|
||||
return UPGRADE_SUCCESS;
|
||||
}
|
||||
|
||||
diff --git a/rpm/389-ds-base.spec.in b/rpm/389-ds-base.spec.in
|
||||
index 16b8d14c5..59e3a748f 100644
|
||||
index 0175dfa7c..f78258f6a 100644
|
||||
--- a/rpm/389-ds-base.spec.in
|
||||
+++ b/rpm/389-ds-base.spec.in
|
||||
@@ -529,9 +529,6 @@ for dir in "$instbase"/slapd-* ; do
|
||||
@@ -667,9 +667,6 @@ for dir in "$instbase"/slapd-* ; do
|
||||
else
|
||||
echo "instance $inst is not running" >> "$output" 2>&1 || :
|
||||
fi
|
||||
@ -335,10 +336,10 @@ index 16b8d14c5..59e3a748f 100644
|
||||
done
|
||||
|
||||
diff --git a/src/lib389/lib389/backend.py b/src/lib389/lib389/backend.py
|
||||
index 4babf6850..376596cd6 100644
|
||||
index 1d9be4683..29a1796ac 100644
|
||||
--- a/src/lib389/lib389/backend.py
|
||||
+++ b/src/lib389/lib389/backend.py
|
||||
@@ -541,9 +541,10 @@ class Backend(DSLdapObject):
|
||||
@@ -617,9 +617,10 @@ class Backend(DSLdapObject):
|
||||
# Default system indexes taken from ldap/servers/slapd/back-ldbm/instance.c
|
||||
# Note: entryrdn and ancestorid are internal system indexes that are not
|
||||
# exposed in cn=config - they are managed internally by the server.
|
||||
@ -352,7 +353,7 @@ index 4babf6850..376596cd6 100644
|
||||
'aci': {'types': ['pres'], 'matching_rule': None},
|
||||
'nscpEntryDN': {'types': ['eq'], 'matching_rule': None},
|
||||
diff --git a/src/lib389/lib389/cli_ctl/dbtasks.py b/src/lib389/lib389/cli_ctl/dbtasks.py
|
||||
index 16da966d1..ea8a00cc3 100644
|
||||
index cd96cdaf7..b02de203f 100644
|
||||
--- a/src/lib389/lib389/cli_ctl/dbtasks.py
|
||||
+++ b/src/lib389/lib389/cli_ctl/dbtasks.py
|
||||
@@ -10,6 +10,7 @@
|
||||
@ -363,7 +364,7 @@ index 16da966d1..ea8a00cc3 100644
|
||||
import subprocess
|
||||
from enum import Enum
|
||||
from lib389._constants import TaskWarning
|
||||
@@ -271,45 +272,53 @@ def _check_disk_ordering(db_dir, backend, index_name, dbscan_path, is_mdb, log):
|
||||
@@ -263,45 +264,53 @@ def _check_disk_ordering(db_dir, backend, index_name, dbscan_path, is_mdb, log):
|
||||
if not index_file:
|
||||
return IndexOrdering.UNKNOWN
|
||||
|
||||
@ -430,7 +431,7 @@ index 16da966d1..ea8a00cc3 100644
|
||||
except OSError as e:
|
||||
log.warning(" Error running dbscan: %s", e)
|
||||
return IndexOrdering.UNKNOWN
|
||||
@@ -383,8 +392,7 @@ def dbtasks_index_check(inst, log, args):
|
||||
@@ -375,8 +384,7 @@ def dbtasks_index_check(inst, log, args):
|
||||
|
||||
# Track all issues found
|
||||
all_ok = True
|
||||
@ -440,7 +441,7 @@ index 16da966d1..ea8a00cc3 100644
|
||||
scan_limits_to_remove = [] # (backend, index_name) tuples with nsIndexIDListScanLimit
|
||||
ancestorid_configs_to_remove = [] # backend names with ancestorid config entries
|
||||
remove_ancestorid_from_defaults = False # Flag to remove from cn=default indexes
|
||||
@@ -417,13 +425,6 @@ def dbtasks_index_check(inst, log, args):
|
||||
@@ -409,13 +417,6 @@ def dbtasks_index_check(inst, log, args):
|
||||
|
||||
if disk_ordering == IndexOrdering.UNKNOWN:
|
||||
log.info(" %s - could not determine disk ordering, skipping", index_name)
|
||||
@ -454,7 +455,7 @@ index 16da966d1..ea8a00cc3 100644
|
||||
continue
|
||||
|
||||
config_has_int_order = _has_integer_ordering_match(dse_ldif, backend, index_name)
|
||||
@@ -431,18 +432,15 @@ def dbtasks_index_check(inst, log, args):
|
||||
@@ -423,18 +424,15 @@ def dbtasks_index_check(inst, log, args):
|
||||
log.info(" %s - config: %s, disk: %s",
|
||||
index_name, config_desc, disk_ordering.value)
|
||||
|
||||
@ -480,7 +481,7 @@ index 16da966d1..ea8a00cc3 100644
|
||||
all_ok = False
|
||||
|
||||
# Handle issues
|
||||
@@ -488,26 +486,27 @@ def dbtasks_index_check(inst, log, args):
|
||||
@@ -480,26 +478,27 @@ def dbtasks_index_check(inst, log, args):
|
||||
log.error(" Failed to remove ancestorid config from backend %s: %s", backend, e)
|
||||
return False
|
||||
|
||||
@ -525,7 +526,7 @@ index 16da966d1..ea8a00cc3 100644
|
||||
|
||||
log.info("All issues fixed")
|
||||
return True
|
||||
@@ -572,5 +571,5 @@ def create_parser(subcommands):
|
||||
@@ -563,5 +562,5 @@ def create_parser(subcommands):
|
||||
index_check_parser.add_argument('backend', nargs='?', default=None,
|
||||
help="Backend to check. If not specified, all backends are checked.")
|
||||
index_check_parser.add_argument('--fix', action='store_true', default=False,
|
||||
@ -1,52 +0,0 @@
|
||||
From ee03e8443a108cff0cc4c7a03962fdc3a1fbf94d Mon Sep 17 00:00:00 2001
|
||||
From: Simon Pichugin <spichugi@redhat.com>
|
||||
Date: Wed, 16 Oct 2024 19:24:55 -0700
|
||||
Subject: [PATCH] Issue 6067 - Update dsidm to prioritize basedn from .dsrc
|
||||
over interactive input (#6362)
|
||||
|
||||
Description: Modifies dsidm CLI tool to check for the basedn in the .dsrc configuration file
|
||||
when the -b option is not provided.
|
||||
Previously, users were required to always specify the basedn interactively if -b was omitted,
|
||||
even if it was available in .dsrc.
|
||||
Now, the basedn is determined by first checking the -b option, then the .dsrc file, and finally
|
||||
prompting the user if neither is set.
|
||||
|
||||
Related: https://github.com/389ds/389-ds-base/issues/6067
|
||||
|
||||
Reviewed by: @Firstyear (Thanks!)
|
||||
---
|
||||
src/lib389/cli/dsidm | 2 +-
|
||||
src/lib389/lib389/cli_idm/__init__.py | 4 ++--
|
||||
2 files changed, 3 insertions(+), 3 deletions(-)
|
||||
|
||||
diff --git a/src/lib389/cli/dsidm b/src/lib389/cli/dsidm
|
||||
index 970973f4f..d318664bc 100755
|
||||
--- a/src/lib389/cli/dsidm
|
||||
+++ b/src/lib389/cli/dsidm
|
||||
@@ -128,7 +128,7 @@ if __name__ == '__main__':
|
||||
result = False
|
||||
try:
|
||||
inst = connect_instance(dsrc_inst=dsrc_inst, verbose=args.verbose, args=args)
|
||||
- basedn = _get_basedn_arg(inst, args, log, msg="Enter basedn")
|
||||
+ basedn = _get_basedn_arg(inst, args, dsrc_inst['basedn'], log, msg="Enter basedn")
|
||||
if basedn is None:
|
||||
errmsg = "Must provide a basedn!"
|
||||
if args.json:
|
||||
diff --git a/src/lib389/lib389/cli_idm/__init__.py b/src/lib389/lib389/cli_idm/__init__.py
|
||||
index e3622246d..1f3e2dc86 100644
|
||||
--- a/src/lib389/lib389/cli_idm/__init__.py
|
||||
+++ b/src/lib389/lib389/cli_idm/__init__.py
|
||||
@@ -52,8 +52,8 @@ def _get_args(args, kws):
|
||||
return kwargs
|
||||
|
||||
|
||||
-def _get_basedn_arg(inst, args, log, msg=None):
|
||||
- basedn_arg = _get_dn_arg(args.basedn, msg="Enter basedn")
|
||||
+def _get_basedn_arg(inst, args, basedn, log, msg=None):
|
||||
+ basedn_arg = _get_dn_arg(basedn, msg="Enter basedn")
|
||||
if not DSLdapObject(inst, basedn_arg).exists():
|
||||
raise ValueError(f'The base DN "{basedn_arg}" does not exist.')
|
||||
|
||||
--
|
||||
2.48.1
|
||||
|
||||
@ -0,0 +1,202 @@
|
||||
From 31bef3852f7064c73f66974d7c41262d2a724eab Mon Sep 17 00:00:00 2001
|
||||
From: Alex Kulberg <vectinx@yandex.ru>
|
||||
Date: Tue, 9 Dec 2025 17:11:56 +0300
|
||||
Subject: [PATCH] Issue 7053 - Remove memberof_del_dn_from_groups from MemberOf
|
||||
plugin (#7064)
|
||||
|
||||
Bug Description:
|
||||
|
||||
The member plugin creates redundant changes to the member attribute
|
||||
in groups when deleting a user, although the referential integrity
|
||||
of the member attribute should be controlled by the Referential Integrity plugin.
|
||||
Furthermore, memberof doesn't take replication of operations into account
|
||||
and performs the change on every server instance in the topology.
|
||||
|
||||
Fix Description:
|
||||
|
||||
Remove the `memberof_del_dn_from_groups` function from the MemberOf plugin,
|
||||
completely transferring responsibility for deleting users from groups
|
||||
to the Referential Integrity plugin.
|
||||
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/7053
|
||||
|
||||
Reviewed by: @tbordaz
|
||||
---
|
||||
.../memberof_include_scopes_test.py | 13 ++-
|
||||
ldap/servers/plugins/memberof/memberof.c | 79 -------------------
|
||||
2 files changed, 9 insertions(+), 83 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/memberof_plugin/memberof_include_scopes_test.py b/dirsrvtests/tests/suites/memberof_plugin/memberof_include_scopes_test.py
|
||||
index e1d3b0a96..347eb880f 100644
|
||||
--- a/dirsrvtests/tests/suites/memberof_plugin/memberof_include_scopes_test.py
|
||||
+++ b/dirsrvtests/tests/suites/memberof_plugin/memberof_include_scopes_test.py
|
||||
@@ -13,7 +13,7 @@ import time
|
||||
from lib389.utils import ensure_str
|
||||
from lib389.topologies import topology_st as topo
|
||||
from lib389._constants import *
|
||||
-from lib389.plugins import MemberOfPlugin
|
||||
+from lib389.plugins import MemberOfPlugin, ReferentialIntegrityPlugin
|
||||
from lib389.idm.user import UserAccount, UserAccounts
|
||||
from lib389.idm.group import Group, Groups
|
||||
from lib389.idm.nscontainer import nsContainers
|
||||
@@ -77,6 +77,13 @@ def test_multiple_scopes(topo):
|
||||
|
||||
inst = topo.standalone
|
||||
|
||||
+ EXCLUDED_SUBTREE = 'cn=exclude,%s' % SUFFIX
|
||||
+ # enable Referential Integrity plugin
|
||||
+ # to correctly process the 'member' attribute
|
||||
+ refint = ReferentialIntegrityPlugin(inst)
|
||||
+ refint.add_excludescope(EXCLUDED_SUBTREE)
|
||||
+ refint.enable()
|
||||
+
|
||||
# configure plugin
|
||||
memberof = MemberOfPlugin(inst)
|
||||
memberof.enable()
|
||||
@@ -106,7 +113,6 @@ def test_multiple_scopes(topo):
|
||||
check_membership(inst, f'uid=test_m3,{SUBTREE_3}', f'cn=g3,{SUBTREE_3}', False)
|
||||
|
||||
# Set exclude scope
|
||||
- EXCLUDED_SUBTREE = 'cn=exclude,%s' % SUFFIX
|
||||
EXCLUDED_USER = f"uid=test_m1,{EXCLUDED_SUBTREE}"
|
||||
INCLUDED_USER = f"uid=test_m1,{SUBTREE_1}"
|
||||
GROUP_DN = f'cn=g1,{SUBTREE_1}'
|
||||
@@ -122,9 +128,8 @@ def test_multiple_scopes(topo):
|
||||
# Check memberOf and group are cleaned up
|
||||
check_membership(inst, EXCLUDED_USER, GROUP_DN, False)
|
||||
group = Group(topo.standalone, dn=GROUP_DN)
|
||||
- assert not group.present("member", EXCLUDED_USER)
|
||||
assert not group.present("member", INCLUDED_USER)
|
||||
-
|
||||
+ assert not group.present("member", EXCLUDED_USER)
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Run isolated
|
||||
diff --git a/ldap/servers/plugins/memberof/memberof.c b/ldap/servers/plugins/memberof/memberof.c
|
||||
index c4b5afee7..5de2e9e72 100644
|
||||
--- a/ldap/servers/plugins/memberof/memberof.c
|
||||
+++ b/ldap/servers/plugins/memberof/memberof.c
|
||||
@@ -151,7 +151,6 @@ static void memberof_set_plugin_id(void *plugin_id);
|
||||
static int memberof_compare(MemberOfConfig *config, const void *a, const void *b);
|
||||
static int memberof_qsort_compare(const void *a, const void *b);
|
||||
static void memberof_load_array(Slapi_Value **array, Slapi_Attr *attr);
|
||||
-static int memberof_del_dn_from_groups(Slapi_PBlock *pb, MemberOfConfig *config, Slapi_Entry *e, Slapi_DN *sdn);
|
||||
static int memberof_call_foreach_dn(Slapi_PBlock *pb, Slapi_Entry *e, Slapi_DN *sdn, MemberOfConfig *config, char **types, plugin_search_entry_callback callback, void *callback_data, int *cached, PRBool use_grp_cache);
|
||||
static int memberof_is_direct_member(MemberOfConfig *config, Slapi_Value *groupdn, Slapi_Value *memberdn);
|
||||
static int memberof_is_grouping_attr(char *type, MemberOfConfig *config);
|
||||
@@ -540,21 +539,6 @@ deferred_modrdn_func(MemberofDeferredModrdnTask *task)
|
||||
* attributes to refer to the new name. */
|
||||
if (ret == LDAP_SUCCESS && pre_sdn && post_sdn) {
|
||||
if (!memberof_entry_in_scope(&configCopy, &post_entry_info)) {
|
||||
- /*
|
||||
- * After modrdn the group contains both the pre and post DN's as
|
||||
- * members, so we need to cleanup both in this case.
|
||||
- */
|
||||
- if ((ret = memberof_del_dn_from_groups(pb, &configCopy, pre_e, pre_sdn))) {
|
||||
- slapi_log_err(SLAPI_LOG_ERR, MEMBEROF_PLUGIN_SUBSYSTEM,
|
||||
- "deferred_modrdn_func - Delete dn failed for preop entry(%s), error (%d)\n",
|
||||
- slapi_sdn_get_dn(pre_sdn), ret);
|
||||
- }
|
||||
- if ((ret = memberof_del_dn_from_groups(pb, &configCopy, post_e, post_sdn))) {
|
||||
- slapi_log_err(SLAPI_LOG_ERR, MEMBEROF_PLUGIN_SUBSYSTEM,
|
||||
- "deferred_modrdn_func - Delete dn failed for postop entry(%s), error (%d)\n",
|
||||
- slapi_sdn_get_dn(post_sdn), ret);
|
||||
- }
|
||||
-
|
||||
if (ret == LDAP_SUCCESS && pre_e && configCopy.group_filter &&
|
||||
0 == slapi_filter_test_simple(pre_e, configCopy.group_filter))
|
||||
{
|
||||
@@ -638,16 +622,6 @@ deferred_del_func(MemberofDeferredDelTask *task)
|
||||
free_configCopy = PR_TRUE;
|
||||
memberof_unlock_config();
|
||||
|
||||
- /* remove this DN from the
|
||||
- * membership lists of groups
|
||||
- */
|
||||
- if ((ret = memberof_del_dn_from_groups(pb, &configCopy, e, sdn))) {
|
||||
- slapi_log_err(SLAPI_LOG_ERR, MEMBEROF_PLUGIN_SUBSYSTEM,
|
||||
- "deferred_del_func - Error deleting dn (%s) from group. Error (%d)\n",
|
||||
- slapi_sdn_get_dn(sdn), ret);
|
||||
- goto bail;
|
||||
- }
|
||||
-
|
||||
/* is the entry of interest as a group? */
|
||||
if (e && configCopy.group_filter && 0 == slapi_filter_test_simple(e, configCopy.group_filter)) {
|
||||
Slapi_Attr *attr = 0;
|
||||
@@ -1456,16 +1430,6 @@ memberof_postop_del(Slapi_PBlock *pb)
|
||||
memberof_copy_config(&configCopy, memberof_get_config());
|
||||
memberof_unlock_config();
|
||||
|
||||
- /* remove this DN from the
|
||||
- * membership lists of groups
|
||||
- */
|
||||
- if ((ret = memberof_del_dn_from_groups(pb, &configCopy, e, sdn))) {
|
||||
- slapi_log_err(SLAPI_LOG_ERR, MEMBEROF_PLUGIN_SUBSYSTEM,
|
||||
- "memberof_postop_del - Error deleting dn (%s) from group. Error (%d)\n",
|
||||
- slapi_sdn_get_dn(sdn), ret);
|
||||
- goto bail;
|
||||
- }
|
||||
-
|
||||
/* is the entry of interest as a group? */
|
||||
if (e && configCopy.group_filter && 0 == slapi_filter_test_simple(e, configCopy.group_filter)) {
|
||||
Slapi_Attr *attr = 0;
|
||||
@@ -1496,34 +1460,6 @@ done:
|
||||
}
|
||||
|
||||
|
||||
-/* Deletes a member dn from all groups that refer to it. */
|
||||
-static int
|
||||
-memberof_del_dn_from_groups(Slapi_PBlock *pb, MemberOfConfig *config, Slapi_Entry *e, Slapi_DN *sdn)
|
||||
-{
|
||||
- char *groupattrs[2] = {0, 0};
|
||||
- int rc = LDAP_SUCCESS;
|
||||
- int cached = 0;
|
||||
-
|
||||
- /* Loop through each grouping attribute to find groups that have
|
||||
- * dn as a member. For any matches, delete the dn value from the
|
||||
- * same grouping attribute. */
|
||||
- for (size_t i = 0; config->groupattrs && config->groupattrs[i] && rc == LDAP_SUCCESS; i++) {
|
||||
- memberof_del_dn_data data = {(char *)slapi_sdn_get_dn(sdn),
|
||||
- config->groupattrs[i], config};
|
||||
-
|
||||
- groupattrs[0] = config->groupattrs[i];
|
||||
-
|
||||
- slapi_log_err(SLAPI_LOG_PLUGIN, MEMBEROF_PLUGIN_SUBSYSTEM,
|
||||
- "memberof_del_dn_from_groups: Ancestors of %s attr: %s\n",
|
||||
- slapi_sdn_get_dn(sdn),
|
||||
- groupattrs[0]);
|
||||
- rc = memberof_call_foreach_dn(pb, e, sdn, config, groupattrs,
|
||||
- memberof_del_dn_type_callback, &data, &cached, PR_FALSE);
|
||||
- }
|
||||
-
|
||||
- return rc;
|
||||
-}
|
||||
-
|
||||
int
|
||||
memberof_del_dn_type_callback(Slapi_Entry *e, void *callback_data)
|
||||
{
|
||||
@@ -1904,21 +1840,6 @@ memberof_postop_modrdn(Slapi_PBlock *pb)
|
||||
* attributes to refer to the new name. */
|
||||
if (ret == LDAP_SUCCESS && pre_sdn && post_sdn) {
|
||||
if (!memberof_entry_in_scope(&configCopy, &post_entry_info)) {
|
||||
- /*
|
||||
- * After modrdn the group contains both the pre and post DN's as
|
||||
- * members, so we need to cleanup both in this case.
|
||||
- */
|
||||
- if ((ret = memberof_del_dn_from_groups(pb, &configCopy, pre_e, pre_sdn))) {
|
||||
- slapi_log_err(SLAPI_LOG_ERR, MEMBEROF_PLUGIN_SUBSYSTEM,
|
||||
- "memberof_postop_modrdn - Delete dn failed for preop entry(%s), error (%d)\n",
|
||||
- slapi_sdn_get_dn(pre_sdn), ret);
|
||||
- }
|
||||
- if ((ret = memberof_del_dn_from_groups(pb, &configCopy, post_e, post_sdn))) {
|
||||
- slapi_log_err(SLAPI_LOG_ERR, MEMBEROF_PLUGIN_SUBSYSTEM,
|
||||
- "memberof_postop_modrdn - Delete dn failed for postop entry(%s), error (%d)\n",
|
||||
- slapi_sdn_get_dn(post_sdn), ret);
|
||||
- }
|
||||
-
|
||||
if (ret == LDAP_SUCCESS && pre_e && configCopy.group_filter &&
|
||||
0 == slapi_filter_test_simple(pre_e, configCopy.group_filter)) {
|
||||
/* is the entry of interest as a group? */
|
||||
--
|
||||
2.52.0
|
||||
|
||||
952
SOURCES/0034-Issue-5853-Update-concread-to-0.5.10.patch
Normal file
952
SOURCES/0034-Issue-5853-Update-concread-to-0.5.10.patch
Normal file
@ -0,0 +1,952 @@
|
||||
From 0c5f7b9909ababc53e73e9469da6e30b81637cd0 Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Mon, 23 Feb 2026 09:49:52 +0100
|
||||
Subject: [PATCH] Issue 5853 - Update concread to 0.5.10
|
||||
|
||||
Description:
|
||||
Update concread to 0.5.10 and update Cargo.lock
|
||||
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/5853
|
||||
|
||||
Reviewed by: @droideck (Thanks!)
|
||||
---
|
||||
src/Cargo.lock | 515 +++++++++++++++++++++++----------------
|
||||
src/librslapd/Cargo.toml | 2 +-
|
||||
2 files changed, 312 insertions(+), 205 deletions(-)
|
||||
|
||||
diff --git a/src/Cargo.lock b/src/Cargo.lock
|
||||
index 87aeee852..425371478 100644
|
||||
--- a/src/Cargo.lock
|
||||
+++ b/src/Cargo.lock
|
||||
@@ -2,27 +2,18 @@
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
-[[package]]
|
||||
-name = "addr2line"
|
||||
-version = "0.24.2"
|
||||
-source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
-checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
|
||||
-dependencies = [
|
||||
- "gimli",
|
||||
-]
|
||||
-
|
||||
-[[package]]
|
||||
-name = "adler2"
|
||||
-version = "2.0.1"
|
||||
-source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
-checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||
-
|
||||
[[package]]
|
||||
name = "allocator-api2"
|
||||
version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
||||
|
||||
+[[package]]
|
||||
+name = "anyhow"
|
||||
+version = "1.0.102"
|
||||
+source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
+checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
||||
+
|
||||
[[package]]
|
||||
name = "atty"
|
||||
version = "0.2.14"
|
||||
@@ -40,21 +31,6 @@ version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
|
||||
-[[package]]
|
||||
-name = "backtrace"
|
||||
-version = "0.3.75"
|
||||
-source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
-checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002"
|
||||
-dependencies = [
|
||||
- "addr2line",
|
||||
- "cfg-if",
|
||||
- "libc",
|
||||
- "miniz_oxide",
|
||||
- "object",
|
||||
- "rustc-demangle",
|
||||
- "windows-targets",
|
||||
-]
|
||||
-
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.13.1"
|
||||
@@ -69,9 +45,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
-version = "2.9.1"
|
||||
+version = "2.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
-checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
|
||||
+checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
@@ -86,8 +62,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da6bc11b07529f16944307272d5bd9b22530bc7d05751717c9d416586cedab49"
|
||||
dependencies = [
|
||||
"clap",
|
||||
- "heck",
|
||||
- "indexmap",
|
||||
+ "heck 0.4.1",
|
||||
+ "indexmap 1.9.3",
|
||||
"log",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -100,10 +76,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
-version = "1.2.27"
|
||||
+version = "1.2.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
-checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc"
|
||||
+checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2"
|
||||
dependencies = [
|
||||
+ "find-msvc-tools",
|
||||
"jobserver",
|
||||
"libc",
|
||||
"shlex",
|
||||
@@ -111,9 +88,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
-version = "1.0.1"
|
||||
+version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
-checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
|
||||
+checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
@@ -124,7 +101,7 @@ dependencies = [
|
||||
"atty",
|
||||
"bitflags 1.3.2",
|
||||
"clap_lex",
|
||||
- "indexmap",
|
||||
+ "indexmap 1.9.3",
|
||||
"strsim",
|
||||
"termcolor",
|
||||
"textwrap",
|
||||
@@ -141,14 +118,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "concread"
|
||||
-version = "0.5.6"
|
||||
+version = "0.5.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
-checksum = "7b639eeaa550eba0c8be45b292d5e272e6d29bfdffb4df6925d651ed9ed10fd6"
|
||||
+checksum = "6588e9e68e11207fb9a5aabd88765187969e6bcba98763c40bcad87b2a73e9f5"
|
||||
dependencies = [
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-queue",
|
||||
"crossbeam-utils",
|
||||
- "foldhash",
|
||||
+ "foldhash 0.2.0",
|
||||
"lru",
|
||||
"smallvec",
|
||||
"sptr",
|
||||
@@ -210,9 +187,9 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
-version = "0.3.12"
|
||||
+version = "0.3.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
-checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18"
|
||||
+checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys",
|
||||
@@ -232,17 +209,29 @@ checksum = "93804560e638370a8be6d59ce71ed803e55e230abdbf42598e666b41adda9b1f"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"byteorder",
|
||||
- "getrandom 0.2.16",
|
||||
+ "getrandom 0.2.17",
|
||||
"openssl",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
+[[package]]
|
||||
+name = "find-msvc-tools"
|
||||
+version = "0.1.9"
|
||||
+source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
+checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
||||
+
|
||||
[[package]]
|
||||
name = "foldhash"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||
|
||||
+[[package]]
|
||||
+name = "foldhash"
|
||||
+version = "0.2.0"
|
||||
+source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
+checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
|
||||
+
|
||||
[[package]]
|
||||
name = "foreign-types"
|
||||
version = "0.3.2"
|
||||
@@ -260,32 +249,39 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
-version = "0.2.16"
|
||||
+version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
-checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
|
||||
+checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
- "wasi 0.11.1+wasi-snapshot-preview1",
|
||||
+ "wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
-version = "0.3.3"
|
||||
+version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
-checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
|
||||
+checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi",
|
||||
- "wasi 0.14.2+wasi-0.2.4",
|
||||
+ "wasip2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
-name = "gimli"
|
||||
-version = "0.31.1"
|
||||
+name = "getrandom"
|
||||
+version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
-checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
|
||||
+checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec"
|
||||
+dependencies = [
|
||||
+ "cfg-if",
|
||||
+ "libc",
|
||||
+ "r-efi",
|
||||
+ "wasip2",
|
||||
+ "wasip3",
|
||||
+]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
@@ -295,13 +291,22 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
-version = "0.15.4"
|
||||
+version = "0.15.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
-checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5"
|
||||
+checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
|
||||
+dependencies = [
|
||||
+ "foldhash 0.1.5",
|
||||
+]
|
||||
+
|
||||
+[[package]]
|
||||
+name = "hashbrown"
|
||||
+version = "0.16.1"
|
||||
+source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
+checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
|
||||
dependencies = [
|
||||
"allocator-api2",
|
||||
"equivalent",
|
||||
- "foldhash",
|
||||
+ "foldhash 0.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -310,6 +315,12 @@ version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||
|
||||
+[[package]]
|
||||
+name = "heck"
|
||||
+version = "0.5.0"
|
||||
+source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
+
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.19"
|
||||
@@ -319,6 +330,12 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
+[[package]]
|
||||
+name = "id-arena"
|
||||
+version = "2.3.0"
|
||||
+source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
+checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
|
||||
+
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.9.3"
|
||||
@@ -329,27 +346,45 @@ dependencies = [
|
||||
"hashbrown 0.12.3",
|
||||
]
|
||||
|
||||
+[[package]]
|
||||
+name = "indexmap"
|
||||
+version = "2.13.0"
|
||||
+source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
+checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
|
||||
+dependencies = [
|
||||
+ "equivalent",
|
||||
+ "hashbrown 0.16.1",
|
||||
+ "serde",
|
||||
+ "serde_core",
|
||||
+]
|
||||
+
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
-version = "1.0.15"
|
||||
+version = "1.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
-checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
||||
+checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
|
||||
|
||||
[[package]]
|
||||
name = "jobserver"
|
||||
-version = "0.1.33"
|
||||
+version = "0.1.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
-checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a"
|
||||
+checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
|
||||
dependencies = [
|
||||
- "getrandom 0.3.3",
|
||||
+ "getrandom 0.3.4",
|
||||
"libc",
|
||||
]
|
||||
|
||||
+[[package]]
|
||||
+name = "leb128fmt"
|
||||
+version = "0.1.0"
|
||||
+source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
+checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
|
||||
+
|
||||
[[package]]
|
||||
name = "libc"
|
||||
-version = "0.2.174"
|
||||
+version = "0.2.182"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
-checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
|
||||
+checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112"
|
||||
|
||||
[[package]]
|
||||
name = "librnsslapd"
|
||||
@@ -372,48 +407,30 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
-version = "0.9.4"
|
||||
+version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
-checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
|
||||
+checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
-version = "0.4.27"
|
||||
+version = "0.4.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
-checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
|
||||
+checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
||||
|
||||
[[package]]
|
||||
name = "lru"
|
||||
-version = "0.13.0"
|
||||
+version = "0.16.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
-checksum = "227748d55f2f0ab4735d87fd623798cb6b664512fe979705f829c9f81c934465"
|
||||
+checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593"
|
||||
dependencies = [
|
||||
- "hashbrown 0.15.4",
|
||||
+ "hashbrown 0.16.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
-version = "2.7.5"
|
||||
-source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
-checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
|
||||
-
|
||||
-[[package]]
|
||||
-name = "miniz_oxide"
|
||||
-version = "0.8.9"
|
||||
-source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
-checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
|
||||
-dependencies = [
|
||||
- "adler2",
|
||||
-]
|
||||
-
|
||||
-[[package]]
|
||||
-name = "object"
|
||||
-version = "0.36.7"
|
||||
+version = "2.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
-checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
|
||||
-dependencies = [
|
||||
- "memchr",
|
||||
-]
|
||||
+checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
@@ -423,11 +440,11 @@ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
-version = "0.10.73"
|
||||
+version = "0.10.75"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
-checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8"
|
||||
+checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328"
|
||||
dependencies = [
|
||||
- "bitflags 2.9.1",
|
||||
+ "bitflags 2.11.0",
|
||||
"cfg-if",
|
||||
"foreign-types",
|
||||
"libc",
|
||||
@@ -444,14 +461,14 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
- "syn 2.0.103",
|
||||
+ "syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
-version = "0.9.109"
|
||||
+version = "0.9.111"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
-checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571"
|
||||
+checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
@@ -496,6 +513,16 @@ version = "0.3.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
||||
|
||||
+[[package]]
|
||||
+name = "prettyplease"
|
||||
+version = "0.2.37"
|
||||
+source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
+checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
|
||||
+dependencies = [
|
||||
+ "proc-macro2",
|
||||
+ "syn 2.0.117",
|
||||
+]
|
||||
+
|
||||
[[package]]
|
||||
name = "proc-macro-hack"
|
||||
version = "0.5.20+deprecated"
|
||||
@@ -504,9 +531,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
-version = "1.0.95"
|
||||
+version = "1.0.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
-checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
|
||||
+checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -526,9 +553,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
-version = "1.0.40"
|
||||
+version = "1.0.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
-checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
|
||||
+checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
@@ -539,19 +566,13 @@ version = "5.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||
|
||||
-[[package]]
|
||||
-name = "rustc-demangle"
|
||||
-version = "0.1.25"
|
||||
-source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
-checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f"
|
||||
-
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
-version = "1.0.7"
|
||||
+version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
-checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266"
|
||||
+checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34"
|
||||
dependencies = [
|
||||
- "bitflags 2.9.1",
|
||||
+ "bitflags 2.11.0",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
@@ -559,41 +580,52 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
-name = "ryu"
|
||||
-version = "1.0.20"
|
||||
+name = "semver"
|
||||
+version = "1.0.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
-checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||
+checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
-version = "1.0.219"
|
||||
+version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
-checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
|
||||
+checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
||||
+dependencies = [
|
||||
+ "serde_core",
|
||||
+ "serde_derive",
|
||||
+]
|
||||
+
|
||||
+[[package]]
|
||||
+name = "serde_core"
|
||||
+version = "1.0.228"
|
||||
+source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
+checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
-version = "1.0.219"
|
||||
+version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
-checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
|
||||
+checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
- "syn 2.0.103",
|
||||
+ "syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
-version = "1.0.140"
|
||||
+version = "1.0.149"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
-checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
|
||||
+checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
- "ryu",
|
||||
"serde",
|
||||
+ "serde_core",
|
||||
+ "zmij",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -649,9 +681,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
-version = "2.0.103"
|
||||
+version = "2.0.117"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
-checksum = "e4307e30089d6fd6aff212f2da3a1f9e32f3223b1f010fb09b7c95f90f3ca1e8"
|
||||
+checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -660,12 +692,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
-version = "3.20.0"
|
||||
+version = "3.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
-checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1"
|
||||
+checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
- "getrandom 0.3.3",
|
||||
+ "getrandom 0.4.1",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys",
|
||||
@@ -688,11 +720,10 @@ checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057"
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
-version = "1.45.1"
|
||||
+version = "1.49.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
-checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779"
|
||||
+checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86"
|
||||
dependencies = [
|
||||
- "backtrace",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
@@ -707,9 +738,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
-version = "0.1.41"
|
||||
+version = "0.1.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
-checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
|
||||
+checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
|
||||
dependencies = [
|
||||
"pin-project-lite",
|
||||
"tracing-attributes",
|
||||
@@ -718,29 +749,35 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tracing-attributes"
|
||||
-version = "0.1.30"
|
||||
+version = "0.1.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
-checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903"
|
||||
+checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
- "syn 2.0.103",
|
||||
+ "syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
-version = "0.1.34"
|
||||
+version = "0.1.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
-checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678"
|
||||
+checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
-version = "1.0.18"
|
||||
+version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
-checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
||||
+checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||
+
|
||||
+[[package]]
|
||||
+name = "unicode-xid"
|
||||
+version = "0.2.6"
|
||||
+source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
+checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
@@ -748,7 +785,7 @@ version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
|
||||
dependencies = [
|
||||
- "getrandom 0.2.16",
|
||||
+ "getrandom 0.2.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -764,12 +801,55 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||
|
||||
[[package]]
|
||||
-name = "wasi"
|
||||
-version = "0.14.2+wasi-0.2.4"
|
||||
+name = "wasip2"
|
||||
+version = "1.0.2+wasi-0.2.9"
|
||||
+source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
+checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5"
|
||||
+dependencies = [
|
||||
+ "wit-bindgen",
|
||||
+]
|
||||
+
|
||||
+[[package]]
|
||||
+name = "wasip3"
|
||||
+version = "0.4.0+wasi-0.3.0-rc-2026-01-06"
|
||||
+source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
+checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
|
||||
+dependencies = [
|
||||
+ "wit-bindgen",
|
||||
+]
|
||||
+
|
||||
+[[package]]
|
||||
+name = "wasm-encoder"
|
||||
+version = "0.244.0"
|
||||
+source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
+checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319"
|
||||
+dependencies = [
|
||||
+ "leb128fmt",
|
||||
+ "wasmparser",
|
||||
+]
|
||||
+
|
||||
+[[package]]
|
||||
+name = "wasm-metadata"
|
||||
+version = "0.244.0"
|
||||
+source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
+checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
|
||||
+dependencies = [
|
||||
+ "anyhow",
|
||||
+ "indexmap 2.13.0",
|
||||
+ "wasm-encoder",
|
||||
+ "wasmparser",
|
||||
+]
|
||||
+
|
||||
+[[package]]
|
||||
+name = "wasmparser"
|
||||
+version = "0.244.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
-checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
|
||||
+checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
|
||||
dependencies = [
|
||||
- "wit-bindgen-rt",
|
||||
+ "bitflags 2.11.0",
|
||||
+ "hashbrown 0.15.5",
|
||||
+ "indexmap 2.13.0",
|
||||
+ "semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -790,9 +870,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
-version = "0.1.9"
|
||||
+version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
-checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
||||
+checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
@@ -804,103 +884,130 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
-name = "windows-sys"
|
||||
-version = "0.59.0"
|
||||
+name = "windows-link"
|
||||
+version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
-checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||
-dependencies = [
|
||||
- "windows-targets",
|
||||
-]
|
||||
+checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
-name = "windows-targets"
|
||||
-version = "0.52.6"
|
||||
+name = "windows-sys"
|
||||
+version = "0.61.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
-checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||
+checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||
dependencies = [
|
||||
- "windows_aarch64_gnullvm",
|
||||
- "windows_aarch64_msvc",
|
||||
- "windows_i686_gnu",
|
||||
- "windows_i686_gnullvm",
|
||||
- "windows_i686_msvc",
|
||||
- "windows_x86_64_gnu",
|
||||
- "windows_x86_64_gnullvm",
|
||||
- "windows_x86_64_msvc",
|
||||
+ "windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
-name = "windows_aarch64_gnullvm"
|
||||
-version = "0.52.6"
|
||||
+name = "wit-bindgen"
|
||||
+version = "0.51.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
-checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
-
|
||||
-[[package]]
|
||||
-name = "windows_aarch64_msvc"
|
||||
-version = "0.52.6"
|
||||
-source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
-checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
-
|
||||
-[[package]]
|
||||
-name = "windows_i686_gnu"
|
||||
-version = "0.52.6"
|
||||
-source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
-checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||
-
|
||||
-[[package]]
|
||||
-name = "windows_i686_gnullvm"
|
||||
-version = "0.52.6"
|
||||
-source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
-checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
+checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
|
||||
+dependencies = [
|
||||
+ "wit-bindgen-rust-macro",
|
||||
+]
|
||||
|
||||
[[package]]
|
||||
-name = "windows_i686_msvc"
|
||||
-version = "0.52.6"
|
||||
+name = "wit-bindgen-core"
|
||||
+version = "0.51.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
-checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
+checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc"
|
||||
+dependencies = [
|
||||
+ "anyhow",
|
||||
+ "heck 0.5.0",
|
||||
+ "wit-parser",
|
||||
+]
|
||||
|
||||
[[package]]
|
||||
-name = "windows_x86_64_gnu"
|
||||
-version = "0.52.6"
|
||||
+name = "wit-bindgen-rust"
|
||||
+version = "0.51.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
-checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
+checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
|
||||
+dependencies = [
|
||||
+ "anyhow",
|
||||
+ "heck 0.5.0",
|
||||
+ "indexmap 2.13.0",
|
||||
+ "prettyplease",
|
||||
+ "syn 2.0.117",
|
||||
+ "wasm-metadata",
|
||||
+ "wit-bindgen-core",
|
||||
+ "wit-component",
|
||||
+]
|
||||
|
||||
[[package]]
|
||||
-name = "windows_x86_64_gnullvm"
|
||||
-version = "0.52.6"
|
||||
+name = "wit-bindgen-rust-macro"
|
||||
+version = "0.51.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
-checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
+checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a"
|
||||
+dependencies = [
|
||||
+ "anyhow",
|
||||
+ "prettyplease",
|
||||
+ "proc-macro2",
|
||||
+ "quote",
|
||||
+ "syn 2.0.117",
|
||||
+ "wit-bindgen-core",
|
||||
+ "wit-bindgen-rust",
|
||||
+]
|
||||
|
||||
[[package]]
|
||||
-name = "windows_x86_64_msvc"
|
||||
-version = "0.52.6"
|
||||
+name = "wit-component"
|
||||
+version = "0.244.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
-checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
+checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
|
||||
+dependencies = [
|
||||
+ "anyhow",
|
||||
+ "bitflags 2.11.0",
|
||||
+ "indexmap 2.13.0",
|
||||
+ "log",
|
||||
+ "serde",
|
||||
+ "serde_derive",
|
||||
+ "serde_json",
|
||||
+ "wasm-encoder",
|
||||
+ "wasm-metadata",
|
||||
+ "wasmparser",
|
||||
+ "wit-parser",
|
||||
+]
|
||||
|
||||
[[package]]
|
||||
-name = "wit-bindgen-rt"
|
||||
-version = "0.39.0"
|
||||
+name = "wit-parser"
|
||||
+version = "0.244.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
-checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
|
||||
+checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
|
||||
dependencies = [
|
||||
- "bitflags 2.9.1",
|
||||
+ "anyhow",
|
||||
+ "id-arena",
|
||||
+ "indexmap 2.13.0",
|
||||
+ "log",
|
||||
+ "semver",
|
||||
+ "serde",
|
||||
+ "serde_derive",
|
||||
+ "serde_json",
|
||||
+ "unicode-xid",
|
||||
+ "wasmparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
-version = "1.8.1"
|
||||
+version = "1.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
-checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
|
||||
+checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
|
||||
dependencies = [
|
||||
"zeroize_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize_derive"
|
||||
-version = "1.4.2"
|
||||
+version = "1.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
-checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
|
||||
+checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
- "syn 2.0.103",
|
||||
+ "syn 2.0.117",
|
||||
]
|
||||
+
|
||||
+[[package]]
|
||||
+name = "zmij"
|
||||
+version = "1.0.21"
|
||||
+source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
+checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
||||
diff --git a/src/librslapd/Cargo.toml b/src/librslapd/Cargo.toml
|
||||
index f38f93f4c..61100f381 100644
|
||||
--- a/src/librslapd/Cargo.toml
|
||||
+++ b/src/librslapd/Cargo.toml
|
||||
@@ -16,7 +16,7 @@ crate-type = ["staticlib", "lib"]
|
||||
[dependencies]
|
||||
slapd = { path = "../slapd" }
|
||||
libc = "0.2"
|
||||
-concread = "0.5.6"
|
||||
+concread = "0.5.10"
|
||||
|
||||
[build-dependencies]
|
||||
cbindgen = "0.26"
|
||||
--
|
||||
2.53.0
|
||||
|
||||
@ -1,520 +0,0 @@
|
||||
From b8c079c770d3eaa4de49e997d42e1501c28a153b Mon Sep 17 00:00:00 2001
|
||||
From: progier389 <progier@redhat.com>
|
||||
Date: Mon, 8 Jul 2024 11:19:09 +0200
|
||||
Subject: [PATCH] Issue 6155 - ldap-agent fails to start because of permission
|
||||
error (#6179)
|
||||
|
||||
Issue: dirsrv-snmp service fails to starts when SELinux is enforced because of AVC preventing to open some files
|
||||
One workaround is to use the dac_override capability but it is a bad practice.
|
||||
Fix: Setting proper permissions:
|
||||
|
||||
Running ldap-agent with uid=root and gid=dirsrv to be able to access both snmp and dirsrv resources.
|
||||
Setting read permission on the group for the dse.ldif file
|
||||
Setting r/w permissions on the group for the snmp semaphore and mmap file
|
||||
For that one special care is needed because ns-slapd umask overrides the file creation permission
|
||||
as is better to avoid changing the umask (changing umask within the code is not thread safe,
|
||||
and the current 0022 umask value is correct for most of the files) so the safest way is to chmod the snmp file
|
||||
if the needed permission are not set.
|
||||
Issue: #6155
|
||||
|
||||
Reviewed by: @droideck , @vashirov (Thanks ! )
|
||||
|
||||
(cherry picked from commit eb7e57d77b557b63c65fdf38f9069893b021f049)
|
||||
---
|
||||
.github/scripts/generate_matrix.py | 4 +-
|
||||
dirsrvtests/tests/suites/snmp/snmp.py | 214 ++++++++++++++++++++++++++
|
||||
ldap/servers/slapd/agtmmap.c | 72 ++++++++-
|
||||
ldap/servers/slapd/agtmmap.h | 13 ++
|
||||
ldap/servers/slapd/dse.c | 6 +-
|
||||
ldap/servers/slapd/slap.h | 6 +
|
||||
ldap/servers/slapd/snmp_collator.c | 4 +-
|
||||
src/lib389/lib389/instance/setup.py | 5 +
|
||||
wrappers/systemd-snmp.service.in | 1 +
|
||||
9 files changed, 313 insertions(+), 12 deletions(-)
|
||||
create mode 100644 dirsrvtests/tests/suites/snmp/snmp.py
|
||||
|
||||
diff --git a/.github/scripts/generate_matrix.py b/.github/scripts/generate_matrix.py
|
||||
index 584374597..8d67a1dc7 100644
|
||||
--- a/.github/scripts/generate_matrix.py
|
||||
+++ b/.github/scripts/generate_matrix.py
|
||||
@@ -21,8 +21,8 @@ else:
|
||||
# Use tests from the source
|
||||
suites = next(os.walk('dirsrvtests/tests/suites/'))[1]
|
||||
|
||||
- # Filter out snmp as it is an empty directory:
|
||||
- suites.remove('snmp')
|
||||
+ # Filter out webui because of broken tests
|
||||
+ suites.remove('webui')
|
||||
|
||||
# Run each replication test module separately to speed things up
|
||||
suites.remove('replication')
|
||||
diff --git a/dirsrvtests/tests/suites/snmp/snmp.py b/dirsrvtests/tests/suites/snmp/snmp.py
|
||||
new file mode 100644
|
||||
index 000000000..0952deb40
|
||||
--- /dev/null
|
||||
+++ b/dirsrvtests/tests/suites/snmp/snmp.py
|
||||
@@ -0,0 +1,214 @@
|
||||
+# --- BEGIN COPYRIGHT BLOCK ---
|
||||
+# Copyright (C) 2024 Red Hat, Inc.
|
||||
+# All rights reserved.
|
||||
+#
|
||||
+# License: GPL (version 3 or any later version).
|
||||
+# See LICENSE for details.
|
||||
+# --- END COPYRIGHT BLOCK ---
|
||||
+#
|
||||
+import os
|
||||
+import pytest
|
||||
+import logging
|
||||
+import subprocess
|
||||
+import ldap
|
||||
+from datetime import datetime
|
||||
+from shutil import copyfile
|
||||
+from lib389.topologies import topology_m2 as topo_m2
|
||||
+from lib389.utils import selinux_present
|
||||
+
|
||||
+
|
||||
+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__)
|
||||
+
|
||||
+
|
||||
+SNMP_USER = 'user_name'
|
||||
+SNMP_PASSWORD = 'authentication_password'
|
||||
+SNMP_PRIVATE = 'private_password'
|
||||
+
|
||||
+# LDAP OID in MIB
|
||||
+LDAP_OID = '.1.3.6.1.4.1.2312.6.1.1'
|
||||
+LDAPCONNECTIONS_OID = f'{LDAP_OID}.21'
|
||||
+
|
||||
+
|
||||
+def run_cmd(cmd, check_returncode=True):
|
||||
+ """Run a command"""
|
||||
+
|
||||
+ log.info(f'Run: {cmd}')
|
||||
+ result = subprocess.run(cmd, capture_output=True, universal_newlines=True)
|
||||
+ log.info(f'STDOUT of {cmd} is:\n{result.stdout}')
|
||||
+ log.info(f'STDERR of {cmd} is:\n{result.stderr}')
|
||||
+ if check_returncode:
|
||||
+ result.check_returncode()
|
||||
+ return result
|
||||
+
|
||||
+
|
||||
+def add_lines(lines, filename):
|
||||
+ """Add lines that are not already present at the end of a file"""
|
||||
+
|
||||
+ log.info(f'add_lines({lines}, {filename})')
|
||||
+ try:
|
||||
+ with open(filename, 'r') as fd:
|
||||
+ for line in fd:
|
||||
+ try:
|
||||
+ lines.remove(line.strip())
|
||||
+ except ValueError:
|
||||
+ pass
|
||||
+ except FileNotFoundError:
|
||||
+ pass
|
||||
+ if lines:
|
||||
+ with open(filename, 'a') as fd:
|
||||
+ for line in lines:
|
||||
+ fd.write(f'{line}\n')
|
||||
+
|
||||
+
|
||||
+def remove_lines(lines, filename):
|
||||
+ """Remove lines in a file"""
|
||||
+
|
||||
+ log.info(f'remove_lines({lines}, {filename})')
|
||||
+ file_lines = []
|
||||
+ with open(filename, 'r') as fd:
|
||||
+ for line in fd:
|
||||
+ if not line.strip() in lines:
|
||||
+ file_lines.append(line)
|
||||
+ with open(filename, 'w') as fd:
|
||||
+ for line in file_lines:
|
||||
+ fd.write(line)
|
||||
+
|
||||
+
|
||||
+@pytest.fixture(scope="module")
|
||||
+def setup_snmp(topo_m2, request):
|
||||
+ """Install snmp and configure it
|
||||
+
|
||||
+ Returns the time just before dirsrv-snmp get restarted
|
||||
+ """
|
||||
+
|
||||
+ inst1 = topo_m2.ms["supplier1"]
|
||||
+ inst2 = topo_m2.ms["supplier2"]
|
||||
+
|
||||
+ # Check for the test prerequisites
|
||||
+ if os.getuid() != 0:
|
||||
+ pytest.skip('This test should be run by root superuser')
|
||||
+ return None
|
||||
+ if not inst1.with_systemd_running():
|
||||
+ pytest.skip('This test requires systemd')
|
||||
+ return None
|
||||
+ required_packages = {
|
||||
+ '389-ds-base-snmp': os.path.join(inst1.get_sbin_dir(), 'ldap-agent'),
|
||||
+ 'net-snmp': '/etc/snmp/snmpd.conf', }
|
||||
+ skip_msg = ""
|
||||
+ for package,file in required_packages.items():
|
||||
+ if not os.path.exists(file):
|
||||
+ skip_msg += f"Package {package} is not installed ({file} is missing).\n"
|
||||
+ if skip_msg != "":
|
||||
+ pytest.skip(f'This test requires the following package(s): {skip_msg}')
|
||||
+ return None
|
||||
+
|
||||
+ # Install snmp
|
||||
+ # run_cmd(['/usr/bin/dnf', 'install', '-y', 'net-snmp', 'net-snmp-utils', '389-ds-base-snmp'])
|
||||
+
|
||||
+ # Prepare the lines to add/remove in files:
|
||||
+ # master agentx
|
||||
+ # snmp user (user_name - authentication_password - private_password)
|
||||
+ # ldap_agent ds instances
|
||||
+ #
|
||||
+ # Adding rwuser and createUser lines is the same as running:
|
||||
+ # net-snmp-create-v3-user -A authentication_password -a SHA -X private_password -x AES user_name
|
||||
+ # but has the advantage of removing the user at cleanup phase
|
||||
+ #
|
||||
+ agent_cfg = '/etc/dirsrv/config/ldap-agent.conf'
|
||||
+ lines_dict = { '/etc/snmp/snmpd.conf' : ['master agentx', f'rwuser {SNMP_USER}'],
|
||||
+ '/var/lib/net-snmp/snmpd.conf' : [
|
||||
+ f'createUser {SNMP_USER} SHA "{SNMP_PASSWORD}" AES "{SNMP_PRIVATE}"',],
|
||||
+ agent_cfg : [] }
|
||||
+ for inst in topo_m2:
|
||||
+ lines_dict[agent_cfg].append(f'server slapd-{inst.serverid}')
|
||||
+
|
||||
+ # Prepare the cleanup
|
||||
+ def fin():
|
||||
+ run_cmd(['systemctl', 'stop', 'dirsrv-snmp'])
|
||||
+ if not DEBUGGING:
|
||||
+ run_cmd(['systemctl', 'stop', 'snmpd'])
|
||||
+ try:
|
||||
+ os.remove('/usr/share/snmp/mibs/redhat-directory.mib')
|
||||
+ except FileNotFoundError:
|
||||
+ pass
|
||||
+ for filename,lines in lines_dict.items():
|
||||
+ remove_lines(lines, filename)
|
||||
+ run_cmd(['systemctl', 'start', 'snmpd'])
|
||||
+
|
||||
+ request.addfinalizer(fin)
|
||||
+
|
||||
+ # Copy RHDS MIB in default MIB search path (Ugly because I have not found how to change the search path)
|
||||
+ copyfile('/usr/share/dirsrv/mibs/redhat-directory.mib', '/usr/share/snmp/mibs/redhat-directory.mib')
|
||||
+
|
||||
+ run_cmd(['systemctl', 'stop', 'snmpd'])
|
||||
+ for filename,lines in lines_dict.items():
|
||||
+ add_lines(lines, filename)
|
||||
+
|
||||
+ run_cmd(['systemctl', 'start', 'snmpd'])
|
||||
+
|
||||
+ curtime = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
+
|
||||
+ run_cmd(['systemctl', 'start', 'dirsrv-snmp'])
|
||||
+ return curtime
|
||||
+
|
||||
+
|
||||
+@pytest.mark.skipif(not os.path.exists('/usr/bin/snmpwalk'), reason="net-snmp-utils package is not installed")
|
||||
+def test_snmpwalk(topo_m2, setup_snmp):
|
||||
+ """snmp smoke tests.
|
||||
+
|
||||
+ :id: e5d29998-1c21-11ef-a654-482ae39447e5
|
||||
+ :setup: Two suppliers replication setup, snmp
|
||||
+ :steps:
|
||||
+ 1. use snmpwalk to display LDAP statistics
|
||||
+ 2. use snmpwalk to get the number of open connections
|
||||
+ :expectedresults:
|
||||
+ 1. Success and no messages in stderr
|
||||
+ 2. The number of open connections should be positive
|
||||
+ """
|
||||
+
|
||||
+ inst1 = topo_m2.ms["supplier1"]
|
||||
+ inst2 = topo_m2.ms["supplier2"]
|
||||
+
|
||||
+
|
||||
+ cmd = [ '/usr/bin/snmpwalk', '-v3', '-u', SNMP_USER, '-l', 'AuthPriv',
|
||||
+ '-m', '+RHDS-MIB', '-A', SNMP_PASSWORD, '-a', 'SHA',
|
||||
+ '-X', SNMP_PRIVATE, '-x', 'AES', 'localhost',
|
||||
+ LDAP_OID ]
|
||||
+ result = run_cmd(cmd)
|
||||
+ assert not result.stderr
|
||||
+
|
||||
+ cmd = [ '/usr/bin/snmpwalk', '-v3', '-u', SNMP_USER, '-l', 'AuthPriv',
|
||||
+ '-m', '+RHDS-MIB', '-A', SNMP_PASSWORD, '-a', 'SHA',
|
||||
+ '-X', SNMP_PRIVATE, '-x', 'AES', 'localhost',
|
||||
+ f'{LDAPCONNECTIONS_OID}.{inst1.port}', '-Ov' ]
|
||||
+ result = run_cmd(cmd)
|
||||
+ nbconns = int(result.stdout.split()[1])
|
||||
+ log.info(f'There are {nbconns} open connections on {inst1.serverid}')
|
||||
+ assert nbconns > 0
|
||||
+
|
||||
+
|
||||
+@pytest.mark.skipif(not selinux_present(), reason="SELinux is not enabled")
|
||||
+def test_snmp_avc(topo_m2, setup_snmp):
|
||||
+ """snmp smoke tests.
|
||||
+
|
||||
+ :id: fb79728e-1d0d-11ef-9213-482ae39447e5
|
||||
+ :setup: Two suppliers replication setup, snmp
|
||||
+ :steps:
|
||||
+ 1. Get the system journal about ldap-agent
|
||||
+ :expectedresults:
|
||||
+ 1. No AVC should be present
|
||||
+ """
|
||||
+ result = run_cmd(['journalctl', '-S', setup_snmp, '-g', 'ldap-agent'])
|
||||
+ assert not 'AVC' in result.stdout
|
||||
+
|
||||
+
|
||||
+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/slapd/agtmmap.c b/ldap/servers/slapd/agtmmap.c
|
||||
index bc5fe1ee1..4dc67dcfb 100644
|
||||
--- a/ldap/servers/slapd/agtmmap.c
|
||||
+++ b/ldap/servers/slapd/agtmmap.c
|
||||
@@ -34,6 +34,70 @@
|
||||
agt_mmap_context_t mmap_tbl[2] = {{AGT_MAP_UNINIT, -1, (caddr_t)-1},
|
||||
{AGT_MAP_UNINIT, -1, (caddr_t)-1}};
|
||||
|
||||
+#define CHECK_MAP_FAILURE(addr) ((addr)==NULL || (addr) == (caddr_t) -1)
|
||||
+
|
||||
+
|
||||
+/****************************************************************************
|
||||
+ *
|
||||
+ * agt_set_fmode () - try to increase file mode if some flags are missing.
|
||||
+ *
|
||||
+ *
|
||||
+ * Inputs:
|
||||
+ * fd -> The file descriptor.
|
||||
+ *
|
||||
+ * mode -> the wanted mode
|
||||
+ *
|
||||
+ * Outputs: None
|
||||
+ * Return Values: None
|
||||
+ *
|
||||
+ ****************************************************************************/
|
||||
+static void
|
||||
+agt_set_fmode(int fd, mode_t mode)
|
||||
+{
|
||||
+ /* ns-slapd umask is 0022 which is usually fine.
|
||||
+ * but ldap-agen needs S_IWGRP permission on snmp semaphore and mmap file
|
||||
+ * ( when SELinux is enforced process with uid=0 does not bypass the file permission
|
||||
+ * (unless the unfamous dac_override capability is set)
|
||||
+ * Changing umask could lead to race conditions so it is better to check the
|
||||
+ * file permission and change them if needed and if the process own the file.
|
||||
+ */
|
||||
+ struct stat fileinfo = {0};
|
||||
+ if (fstat(fd, &fileinfo) == 0 && fileinfo.st_uid == getuid() &&
|
||||
+ (fileinfo.st_mode & mode) != mode) {
|
||||
+ (void) fchmod(fd, fileinfo.st_mode | mode);
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+/****************************************************************************
|
||||
+ *
|
||||
+ * agt_sem_open () - Like sem_open but ignores umask
|
||||
+ *
|
||||
+ *
|
||||
+ * Inputs: see sem_open man page.
|
||||
+ * Outputs: see sem_open man page.
|
||||
+ * Return Values: see sem_open man page.
|
||||
+ *
|
||||
+ ****************************************************************************/
|
||||
+sem_t *
|
||||
+agt_sem_open(const char *name, int oflag, mode_t mode, unsigned int value)
|
||||
+{
|
||||
+ sem_t *sem = sem_open(name, oflag, mode, value);
|
||||
+ char *semname = NULL;
|
||||
+
|
||||
+ if (sem != NULL) {
|
||||
+ if (asprintf(&semname, "/dev/shm/sem.%s", name+1) > 0) {
|
||||
+ int fd = open(semname, O_RDONLY);
|
||||
+ if (fd >= 0) {
|
||||
+ agt_set_fmode(fd, mode);
|
||||
+ (void) close(fd);
|
||||
+ }
|
||||
+ free(semname);
|
||||
+ semname = NULL;
|
||||
+ }
|
||||
+ }
|
||||
+ return sem;
|
||||
+}
|
||||
+
|
||||
/****************************************************************************
|
||||
*
|
||||
* agt_mopen_stats () - open and Memory Map the stats file. agt_mclose_stats()
|
||||
@@ -52,7 +116,6 @@ agt_mmap_context_t mmap_tbl[2] = {{AGT_MAP_UNINIT, -1, (caddr_t)-1},
|
||||
* as defined in <errno.h>, otherwise.
|
||||
*
|
||||
****************************************************************************/
|
||||
-
|
||||
int
|
||||
agt_mopen_stats(char *statsfile, int mode, int *hdl)
|
||||
{
|
||||
@@ -64,6 +127,7 @@ agt_mopen_stats(char *statsfile, int mode, int *hdl)
|
||||
int err;
|
||||
size_t sz;
|
||||
struct stat fileinfo;
|
||||
+ mode_t rw_mode = S_IWUSR | S_IRUSR | S_IRGRP | S_IWGRP | S_IROTH;
|
||||
|
||||
switch (mode) {
|
||||
case O_RDONLY:
|
||||
@@ -128,10 +192,7 @@ agt_mopen_stats(char *statsfile, int mode, int *hdl)
|
||||
break;
|
||||
|
||||
case O_RDWR:
|
||||
- fd = open(path,
|
||||
- O_RDWR | O_CREAT,
|
||||
- S_IWUSR | S_IRUSR | S_IRGRP | S_IROTH);
|
||||
-
|
||||
+ fd = open(path, O_RDWR | O_CREAT, rw_mode);
|
||||
if (fd < 0) {
|
||||
err = errno;
|
||||
#if (0)
|
||||
@@ -140,6 +201,7 @@ agt_mopen_stats(char *statsfile, int mode, int *hdl)
|
||||
rc = err;
|
||||
goto bail;
|
||||
}
|
||||
+ agt_set_fmode(fd, rw_mode);
|
||||
|
||||
if (fstat(fd, &fileinfo) != 0) {
|
||||
close(fd);
|
||||
diff --git a/ldap/servers/slapd/agtmmap.h b/ldap/servers/slapd/agtmmap.h
|
||||
index fb27ab2c4..99a8584a3 100644
|
||||
--- a/ldap/servers/slapd/agtmmap.h
|
||||
+++ b/ldap/servers/slapd/agtmmap.h
|
||||
@@ -28,6 +28,7 @@
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
+#include <semaphore.h>
|
||||
#include <errno.h>
|
||||
#include "nspr.h"
|
||||
|
||||
@@ -188,6 +189,18 @@ int agt_mclose_stats(int hdl);
|
||||
|
||||
int agt_mread_stats(int hdl, struct hdr_stats_t *, struct ops_stats_t *, struct entries_stats_t *);
|
||||
|
||||
+/****************************************************************************
|
||||
+ *
|
||||
+ * agt_sem_open () - Like sem_open but ignores umask
|
||||
+ *
|
||||
+ *
|
||||
+ * Inputs: see sem_open man page.
|
||||
+ * Outputs: see sem_open man page.
|
||||
+ * Return Values: see sem_open man page.
|
||||
+ *
|
||||
+ ****************************************************************************/
|
||||
+sem_t *agt_sem_open(const char *name, int oflag, mode_t mode, unsigned int value);
|
||||
+
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
diff --git a/ldap/servers/slapd/dse.c b/ldap/servers/slapd/dse.c
|
||||
index b04fafde6..f1e48c6b1 100644
|
||||
--- a/ldap/servers/slapd/dse.c
|
||||
+++ b/ldap/servers/slapd/dse.c
|
||||
@@ -683,7 +683,7 @@ dse_read_one_file(struct dse *pdse, const char *filename, Slapi_PBlock *pb, int
|
||||
"The configuration file %s could not be accessed, error %d\n",
|
||||
filename, rc);
|
||||
rc = 0; /* Fail */
|
||||
- } else if ((prfd = PR_Open(filename, PR_RDONLY, SLAPD_DEFAULT_FILE_MODE)) == NULL) {
|
||||
+ } else if ((prfd = PR_Open(filename, PR_RDONLY, SLAPD_DEFAULT_DSE_FILE_MODE)) == NULL) {
|
||||
slapi_log_err(SLAPI_LOG_ERR, "dse_read_one_file",
|
||||
"The configuration file %s could not be read. " SLAPI_COMPONENT_NAME_NSPR " %d (%s)\n",
|
||||
filename,
|
||||
@@ -871,7 +871,7 @@ dse_rw_permission_to_one_file(const char *name, int loglevel)
|
||||
PRFileDesc *prfd;
|
||||
|
||||
prfd = PR_Open(name, PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE,
|
||||
- SLAPD_DEFAULT_FILE_MODE);
|
||||
+ SLAPD_DEFAULT_DSE_FILE_MODE);
|
||||
if (NULL == prfd) {
|
||||
prerr = PR_GetError();
|
||||
accesstype = "create";
|
||||
@@ -970,7 +970,7 @@ dse_write_file_nolock(struct dse *pdse)
|
||||
fpw.fpw_prfd = NULL;
|
||||
|
||||
if (NULL != pdse->dse_filename) {
|
||||
- if ((fpw.fpw_prfd = PR_Open(pdse->dse_tmpfile, PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE, SLAPD_DEFAULT_FILE_MODE)) == NULL) {
|
||||
+ if ((fpw.fpw_prfd = PR_Open(pdse->dse_tmpfile, PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE, SLAPD_DEFAULT_DSE_FILE_MODE)) == NULL) {
|
||||
rc = PR_GetOSError();
|
||||
slapi_log_err(SLAPI_LOG_ERR, "dse_write_file_nolock", "Cannot open "
|
||||
"temporary DSE file \"%s\" for update: OS error %d (%s)\n",
|
||||
diff --git a/ldap/servers/slapd/slap.h b/ldap/servers/slapd/slap.h
|
||||
index 469874fd1..927576b70 100644
|
||||
--- a/ldap/servers/slapd/slap.h
|
||||
+++ b/ldap/servers/slapd/slap.h
|
||||
@@ -238,6 +238,12 @@ typedef void (*VFPV)(); /* takes undefined arguments */
|
||||
*/
|
||||
|
||||
#define SLAPD_DEFAULT_FILE_MODE S_IRUSR | S_IWUSR
|
||||
+/* ldap_agent run as uid=root gid=dirsrv and requires S_IRGRP | S_IWGRP
|
||||
+ * on semaphore and mmap file if SELinux is enforced.
|
||||
+ */
|
||||
+#define SLAPD_DEFAULT_SNMP_FILE_MODE S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP
|
||||
+/* ldap_agent run as uid=root gid=dirsrv and requires S_IRGRP on dse.ldif if SELinux is enforced. */
|
||||
+#define SLAPD_DEFAULT_DSE_FILE_MODE S_IRUSR | S_IWUSR | S_IRGRP
|
||||
#define SLAPD_DEFAULT_DIR_MODE S_IRWXU
|
||||
#define SLAPD_DEFAULT_IDLE_TIMEOUT 3600 /* seconds - 0 == never */
|
||||
#define SLAPD_DEFAULT_IDLE_TIMEOUT_STR "3600"
|
||||
diff --git a/ldap/servers/slapd/snmp_collator.c b/ldap/servers/slapd/snmp_collator.c
|
||||
index c998d4262..bd7020585 100644
|
||||
--- a/ldap/servers/slapd/snmp_collator.c
|
||||
+++ b/ldap/servers/slapd/snmp_collator.c
|
||||
@@ -474,7 +474,7 @@ static void
|
||||
snmp_collator_create_semaphore(void)
|
||||
{
|
||||
/* First just try to create the semaphore. This should usually just work. */
|
||||
- if ((stats_sem = sem_open(stats_sem_name, O_CREAT | O_EXCL, SLAPD_DEFAULT_FILE_MODE, 1)) == SEM_FAILED) {
|
||||
+ if ((stats_sem = agt_sem_open(stats_sem_name, O_CREAT | O_EXCL, SLAPD_DEFAULT_SNMP_FILE_MODE, 1)) == SEM_FAILED) {
|
||||
if (errno == EEXIST) {
|
||||
/* It appears that we didn't exit cleanly last time and left the semaphore
|
||||
* around. Recreate it since we don't know what state it is in. */
|
||||
@@ -486,7 +486,7 @@ snmp_collator_create_semaphore(void)
|
||||
exit(1);
|
||||
}
|
||||
|
||||
- if ((stats_sem = sem_open(stats_sem_name, O_CREAT | O_EXCL, SLAPD_DEFAULT_FILE_MODE, 1)) == SEM_FAILED) {
|
||||
+ if ((stats_sem = agt_sem_open(stats_sem_name, O_CREAT | O_EXCL, SLAPD_DEFAULT_SNMP_FILE_MODE, 1)) == SEM_FAILED) {
|
||||
/* No dice */
|
||||
slapi_log_err(SLAPI_LOG_EMERG, "snmp_collator_create_semaphore",
|
||||
"Failed to create semaphore for stats file (/dev/shm/sem.%s). Error %d (%s).\n",
|
||||
diff --git a/src/lib389/lib389/instance/setup.py b/src/lib389/lib389/instance/setup.py
|
||||
index 036664447..fca03383e 100644
|
||||
--- a/src/lib389/lib389/instance/setup.py
|
||||
+++ b/src/lib389/lib389/instance/setup.py
|
||||
@@ -10,6 +10,7 @@
|
||||
import os
|
||||
import sys
|
||||
import shutil
|
||||
+import stat
|
||||
import pwd
|
||||
import grp
|
||||
import re
|
||||
@@ -773,6 +774,10 @@ class SetupDs(object):
|
||||
ldapi_autobind="on",
|
||||
)
|
||||
file_dse.write(dse_fmt)
|
||||
+ # Set minimum permission required by snmp ldap-agent
|
||||
+ status = os.fstat(file_dse.fileno())
|
||||
+ os.fchmod(file_dse.fileno(), status.st_mode | stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP)
|
||||
+ os.chown(os.path.join(slapd['config_dir'], 'dse.ldif'), slapd['user_uid'], slapd['group_gid'])
|
||||
|
||||
self.log.info("Create file system structures ...")
|
||||
# Create all the needed paths
|
||||
diff --git a/wrappers/systemd-snmp.service.in b/wrappers/systemd-snmp.service.in
|
||||
index f18766cb4..d344367a0 100644
|
||||
--- a/wrappers/systemd-snmp.service.in
|
||||
+++ b/wrappers/systemd-snmp.service.in
|
||||
@@ -9,6 +9,7 @@ After=network.target
|
||||
|
||||
[Service]
|
||||
Type=forking
|
||||
+Group=@defaultgroup@
|
||||
PIDFile=/run/dirsrv/ldap-agent.pid
|
||||
ExecStart=@sbindir@/ldap-agent @configdir@/ldap-agent.conf
|
||||
|
||||
--
|
||||
2.49.0
|
||||
|
||||
@ -1,60 +0,0 @@
|
||||
From 12870f410545fb055f664b588df2a2b7ab1c228e Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Mon, 4 Mar 2024 07:22:00 +0100
|
||||
Subject: [PATCH] Issue 5305 - OpenLDAP version autodetection doesn't work
|
||||
|
||||
Bug Description:
|
||||
An error is logged during a build in `mock` with Bash 4.4:
|
||||
|
||||
```
|
||||
checking for --with-libldap-r... ./configure: command substitution: line 22848: syntax error near unexpected token `>'
|
||||
./configure: command substitution: line 22848: `ldapsearch -VV 2> >(sed -n '/ldapsearch/ s/.*ldapsearch \([0-9]\+\.[0-9]\+\.[0-9]\+\) .*/\1/p')'
|
||||
no
|
||||
```
|
||||
|
||||
`mock` runs Bash as `sh` (POSIX mode). Support for process substitution
|
||||
in POSIX mode was added in version 5.1:
|
||||
https://lists.gnu.org/archive/html/bug-bash/2020-12/msg00002.html
|
||||
|
||||
> Process substitution is now available in posix mode.
|
||||
|
||||
Fix Description:
|
||||
* Add missing `BuildRequires` for openldap-clients
|
||||
* Replace process substitution with a pipe
|
||||
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/5305
|
||||
|
||||
Reviewed by: @progier389, @tbordaz (Thanks!)
|
||||
---
|
||||
configure.ac | 2 +-
|
||||
rpm/389-ds-base.spec.in | 1 +
|
||||
2 files changed, 2 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/configure.ac b/configure.ac
|
||||
index ffc2aac14..a690765a3 100644
|
||||
--- a/configure.ac
|
||||
+++ b/configure.ac
|
||||
@@ -912,7 +912,7 @@ AC_ARG_WITH(libldap-r, AS_HELP_STRING([--with-libldap-r],[Use lldap_r shared lib
|
||||
AC_SUBST(with_libldap_r)
|
||||
fi
|
||||
],
|
||||
-OPENLDAP_VERSION=`ldapsearch -VV 2> >(sed -n '/ldapsearch/ s/.*ldapsearch \([[[0-9]]]\+\.[[[0-9]]]\+\.[[[0-9]]]\+\) .*/\1/p')`
|
||||
+OPENLDAP_VERSION=`ldapsearch -VV 2>&1 | sed -n '/ldapsearch/ s/.*ldapsearch \([[[0-9]]]\+\.[[[0-9]]]\+\.[[[0-9]]]\+\) .*/\1/p'`
|
||||
AX_COMPARE_VERSION([$OPENLDAP_VERSION], [lt], [2.5], [ with_libldap_r=yes ], [ with_libldap_r=no ])
|
||||
AC_MSG_RESULT($with_libldap_r))
|
||||
|
||||
diff --git a/rpm/389-ds-base.spec.in b/rpm/389-ds-base.spec.in
|
||||
index cd86138ea..b8c14cd14 100644
|
||||
--- a/rpm/389-ds-base.spec.in
|
||||
+++ b/rpm/389-ds-base.spec.in
|
||||
@@ -65,6 +65,7 @@ Provides: ldif2ldbm
|
||||
# Attach the buildrequires to the top level package:
|
||||
BuildRequires: nspr-devel
|
||||
BuildRequires: nss-devel >= 3.34
|
||||
+BuildRequires: openldap-clients
|
||||
BuildRequires: openldap-devel
|
||||
BuildRequires: libdb-devel
|
||||
BuildRequires: cyrus-sasl-devel
|
||||
--
|
||||
2.49.0
|
||||
|
||||
@ -0,0 +1,86 @@
|
||||
From a3fd3897ede4166625194d48b010b6dbdcdf9f70 Mon Sep 17 00:00:00 2001
|
||||
From: Mark Reynolds <mreynolds@redhat.com>
|
||||
Date: Mon, 23 Feb 2026 12:37:26 -0500
|
||||
Subject: [PATCH] Issue 7271 - plugins that create threads need to update
|
||||
active thread count
|
||||
|
||||
Description:
|
||||
|
||||
Plugins that create threads need to up to the global active thread count.
|
||||
Otherwise when the server is being stopped the plugin's close function gets
|
||||
called while these threads are still running and still using the plugin
|
||||
configuration. This can lead to crashes.
|
||||
|
||||
relates: https://github.com/389ds/389-ds-base/issues/7271
|
||||
|
||||
Reviewed by: progier & tbordaz (Thanks!!)
|
||||
---
|
||||
ldap/servers/plugins/replication/repl5_protocol.c | 5 +++++
|
||||
ldap/servers/plugins/retrocl/retrocl_trim.c | 7 ++++++-
|
||||
2 files changed, 11 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/ldap/servers/plugins/replication/repl5_protocol.c b/ldap/servers/plugins/replication/repl5_protocol.c
|
||||
index 5d4a0e455..bc9580319 100644
|
||||
--- a/ldap/servers/plugins/replication/repl5_protocol.c
|
||||
+++ b/ldap/servers/plugins/replication/repl5_protocol.c
|
||||
@@ -23,6 +23,7 @@
|
||||
|
||||
#include "repl5.h"
|
||||
#include "repl5_prot_private.h"
|
||||
+#include "slap.h"
|
||||
|
||||
#define PROTOCOL_5_INCREMENTAL 1
|
||||
#define PROTOCOL_5_TOTAL 2
|
||||
@@ -237,6 +238,8 @@ prot_thread_main(void *arg)
|
||||
return;
|
||||
}
|
||||
|
||||
+ g_incr_active_threadcnt();
|
||||
+
|
||||
set_thread_private_agmtname(agmt_get_long_name(agmt));
|
||||
|
||||
done = 0;
|
||||
@@ -301,6 +304,8 @@ prot_thread_main(void *arg)
|
||||
done = 1;
|
||||
}
|
||||
}
|
||||
+
|
||||
+ g_decr_active_threadcnt();
|
||||
}
|
||||
|
||||
/*
|
||||
diff --git a/ldap/servers/plugins/retrocl/retrocl_trim.c b/ldap/servers/plugins/retrocl/retrocl_trim.c
|
||||
index 8fcd3d32b..8fdc75c62 100644
|
||||
--- a/ldap/servers/plugins/retrocl/retrocl_trim.c
|
||||
+++ b/ldap/servers/plugins/retrocl/retrocl_trim.c
|
||||
@@ -243,6 +243,8 @@ trim_changelog(void)
|
||||
|
||||
now_interval = slapi_current_rel_time_t(); /* monotonic time for interval */
|
||||
|
||||
+ g_incr_active_threadcnt();
|
||||
+
|
||||
PR_Lock(ts.ts_s_trim_mutex);
|
||||
max_age = ts.ts_c_max_age;
|
||||
trim_interval = ts.ts_c_trim_interval;
|
||||
@@ -257,7 +259,7 @@ trim_changelog(void)
|
||||
*/
|
||||
done = 0;
|
||||
now_maxage = slapi_current_utc_time(); /* real time for trim candidates */
|
||||
- while (!done && retrocl_trimming == 1) {
|
||||
+ while (!done && retrocl_trimming == 1 && !slapi_is_shutting_down()) {
|
||||
int did_delete;
|
||||
|
||||
did_delete = 0;
|
||||
@@ -309,6 +311,9 @@ trim_changelog(void)
|
||||
"trim_changelog: removed %d change records\n",
|
||||
num_deleted);
|
||||
}
|
||||
+
|
||||
+ g_decr_active_threadcnt();
|
||||
+
|
||||
return rc;
|
||||
}
|
||||
|
||||
--
|
||||
2.53.0
|
||||
|
||||
@ -1,245 +0,0 @@
|
||||
From eca6f5fe18f768fd407d38c85624a5212bcf16ab Mon Sep 17 00:00:00 2001
|
||||
From: Simon Pichugin <spichugi@redhat.com>
|
||||
Date: Wed, 27 Sep 2023 15:40:33 -0700
|
||||
Subject: [PATCH] Issue 1925 - Add a CI test (#5936)
|
||||
|
||||
Description: Verify that the issue is not present. Cover the scenario when
|
||||
we remove existing VLVs, create new VLVs (with the same name) and then
|
||||
we do online re-indexing.
|
||||
|
||||
Related: https://github.com/389ds/389-ds-base/issues/1925
|
||||
|
||||
Reviewed by: @progier389 (Thanks!)
|
||||
|
||||
(cherry picked from the 9633e8d32d28345409680f8e462fb4a53d3b4f83)
|
||||
---
|
||||
.../tests/suites/vlv/regression_test.py | 175 +++++++++++++++---
|
||||
1 file changed, 145 insertions(+), 30 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/vlv/regression_test.py b/dirsrvtests/tests/suites/vlv/regression_test.py
|
||||
index 6ab709bd3..536fe950f 100644
|
||||
--- a/dirsrvtests/tests/suites/vlv/regression_test.py
|
||||
+++ b/dirsrvtests/tests/suites/vlv/regression_test.py
|
||||
@@ -1,5 +1,5 @@
|
||||
# --- BEGIN COPYRIGHT BLOCK ---
|
||||
-# Copyright (C) 2018 Red Hat, Inc.
|
||||
+# Copyright (C) 2023 Red Hat, Inc.
|
||||
# All rights reserved.
|
||||
#
|
||||
# License: GPL (version 3 or any later version).
|
||||
@@ -9,12 +9,16 @@
|
||||
import pytest, time
|
||||
from lib389.tasks import *
|
||||
from lib389.utils import *
|
||||
-from lib389.topologies import topology_m2
|
||||
+from lib389.topologies import topology_m2, topology_st
|
||||
from lib389.replica import *
|
||||
from lib389._constants import *
|
||||
+from lib389.properties import TASK_WAIT
|
||||
from lib389.index import *
|
||||
from lib389.mappingTree import *
|
||||
from lib389.backend import *
|
||||
+from lib389.idm.user import UserAccounts
|
||||
+from ldap.controls.vlv import VLVRequestControl
|
||||
+from ldap.controls.sss import SSSRequestControl
|
||||
|
||||
pytestmark = pytest.mark.tier1
|
||||
|
||||
@@ -22,6 +26,88 @@ logging.getLogger(__name__).setLevel(logging.DEBUG)
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
+def open_new_ldapi_conn(dsinstance):
|
||||
+ ldapurl, certdir = get_ldapurl_from_serverid(dsinstance)
|
||||
+ assert 'ldapi://' in ldapurl
|
||||
+ conn = ldap.initialize(ldapurl)
|
||||
+ conn.sasl_interactive_bind_s("", ldap.sasl.external())
|
||||
+ return conn
|
||||
+
|
||||
+
|
||||
+def check_vlv_search(conn):
|
||||
+ before_count=1
|
||||
+ after_count=3
|
||||
+ offset=3501
|
||||
+
|
||||
+ vlv_control = VLVRequestControl(criticality=True,
|
||||
+ before_count=before_count,
|
||||
+ after_count=after_count,
|
||||
+ offset=offset,
|
||||
+ content_count=0,
|
||||
+ greater_than_or_equal=None,
|
||||
+ context_id=None)
|
||||
+
|
||||
+ sss_control = SSSRequestControl(criticality=True, ordering_rules=['cn'])
|
||||
+ result = conn.search_ext_s(
|
||||
+ base='dc=example,dc=com',
|
||||
+ scope=ldap.SCOPE_SUBTREE,
|
||||
+ filterstr='(uid=*)',
|
||||
+ serverctrls=[vlv_control, sss_control]
|
||||
+ )
|
||||
+ imin = offset + 998 - before_count
|
||||
+ imax = offset + 998 + after_count
|
||||
+
|
||||
+ for i, (dn, entry) in enumerate(result, start=imin):
|
||||
+ assert i <= imax
|
||||
+ expected_dn = f'uid=testuser{i},ou=People,dc=example,dc=com'
|
||||
+ log.debug(f'found {dn} expected {expected_dn}')
|
||||
+ assert dn.lower() == expected_dn.lower()
|
||||
+
|
||||
+
|
||||
+def add_users(inst, users_num):
|
||||
+ users = UserAccounts(inst, DEFAULT_SUFFIX)
|
||||
+ log.info(f'Adding {users_num} users')
|
||||
+ for i in range(0, users_num):
|
||||
+ uid = 1000 + i
|
||||
+ user_properties = {
|
||||
+ 'uid': f'testuser{uid}',
|
||||
+ 'cn': f'testuser{uid}',
|
||||
+ 'sn': 'user',
|
||||
+ 'uidNumber': str(uid),
|
||||
+ 'gidNumber': str(uid),
|
||||
+ 'homeDirectory': f'/home/testuser{uid}'
|
||||
+ }
|
||||
+ users.create(properties=user_properties)
|
||||
+
|
||||
+
|
||||
+
|
||||
+def create_vlv_search_and_index(inst, basedn=DEFAULT_SUFFIX, bename='userRoot',
|
||||
+ scope=ldap.SCOPE_SUBTREE, prefix="vlv", vlvsort="cn"):
|
||||
+ vlv_searches = VLVSearch(inst)
|
||||
+ vlv_search_properties = {
|
||||
+ "objectclass": ["top", "vlvSearch"],
|
||||
+ "cn": f"{prefix}Srch",
|
||||
+ "vlvbase": basedn,
|
||||
+ "vlvfilter": "(uid=*)",
|
||||
+ "vlvscope": str(scope),
|
||||
+ }
|
||||
+ vlv_searches.create(
|
||||
+ basedn=f"cn={bename},cn=ldbm database,cn=plugins,cn=config",
|
||||
+ properties=vlv_search_properties
|
||||
+ )
|
||||
+
|
||||
+ vlv_index = VLVIndex(inst)
|
||||
+ vlv_index_properties = {
|
||||
+ "objectclass": ["top", "vlvIndex"],
|
||||
+ "cn": f"{prefix}Idx",
|
||||
+ "vlvsort": vlvsort,
|
||||
+ }
|
||||
+ vlv_index.create(
|
||||
+ basedn=f"cn={prefix}Srch,cn={bename},cn=ldbm database,cn=plugins,cn=config",
|
||||
+ properties=vlv_index_properties
|
||||
+ )
|
||||
+ return vlv_searches, vlv_index
|
||||
+
|
||||
class BackendHandler:
|
||||
def __init__(self, inst, bedict, scope=ldap.SCOPE_ONELEVEL):
|
||||
self.inst = inst
|
||||
@@ -101,34 +187,6 @@ class BackendHandler:
|
||||
'dn' : dn}
|
||||
|
||||
|
||||
-def create_vlv_search_and_index(inst, basedn=DEFAULT_SUFFIX, bename='userRoot',
|
||||
- scope=ldap.SCOPE_SUBTREE, prefix="vlv", vlvsort="cn"):
|
||||
- vlv_searches = VLVSearch(inst)
|
||||
- vlv_search_properties = {
|
||||
- "objectclass": ["top", "vlvSearch"],
|
||||
- "cn": f"{prefix}Srch",
|
||||
- "vlvbase": basedn,
|
||||
- "vlvfilter": "(uid=*)",
|
||||
- "vlvscope": str(scope),
|
||||
- }
|
||||
- vlv_searches.create(
|
||||
- basedn=f"cn={bename},cn=ldbm database,cn=plugins,cn=config",
|
||||
- properties=vlv_search_properties
|
||||
- )
|
||||
-
|
||||
- vlv_index = VLVIndex(inst)
|
||||
- vlv_index_properties = {
|
||||
- "objectclass": ["top", "vlvIndex"],
|
||||
- "cn": f"{prefix}Idx",
|
||||
- "vlvsort": vlvsort,
|
||||
- }
|
||||
- vlv_index.create(
|
||||
- basedn=f"cn={prefix}Srch,cn={bename},cn=ldbm database,cn=plugins,cn=config",
|
||||
- properties=vlv_index_properties
|
||||
- )
|
||||
- return vlv_searches, vlv_index
|
||||
-
|
||||
-
|
||||
@pytest.fixture
|
||||
def vlv_setup_with_uid_mr(topology_st, request):
|
||||
inst = topology_st.standalone
|
||||
@@ -245,6 +303,62 @@ def test_bulk_import_when_the_backend_with_vlv_was_recreated(topology_m2):
|
||||
entries = M2.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, "(objectclass=*)")
|
||||
|
||||
|
||||
+def test_vlv_recreation_reindex(topology_st):
|
||||
+ """Test VLV recreation and reindexing.
|
||||
+
|
||||
+ :id: 29f4567f-4ac0-410f-bc99-a32e217a939f
|
||||
+ :setup: Standalone instance.
|
||||
+ :steps:
|
||||
+ 1. Create new VLVs and do the reindex.
|
||||
+ 2. Test the new VLVs.
|
||||
+ 3. Remove the existing VLVs.
|
||||
+ 4. Create new VLVs (with the same name).
|
||||
+ 5. Perform online re-indexing of the new VLVs.
|
||||
+ 6. Test the new VLVs.
|
||||
+ :expectedresults:
|
||||
+ 1. Should Success.
|
||||
+ 2. Should Success.
|
||||
+ 3. Should Success.
|
||||
+ 4. Should Success.
|
||||
+ 5. Should Success.
|
||||
+ 6. Should Success.
|
||||
+ """
|
||||
+
|
||||
+ inst = topology_st.standalone
|
||||
+ reindex_task = Tasks(inst)
|
||||
+
|
||||
+ # Create and test VLVs
|
||||
+ vlv_search, vlv_index = create_vlv_search_and_index(inst)
|
||||
+ assert reindex_task.reindex(
|
||||
+ suffix=DEFAULT_SUFFIX,
|
||||
+ attrname=vlv_index.rdn,
|
||||
+ args={TASK_WAIT: True},
|
||||
+ vlv=True
|
||||
+ ) == 0
|
||||
+
|
||||
+ add_users(inst, 5000)
|
||||
+
|
||||
+ conn = open_new_ldapi_conn(inst.serverid)
|
||||
+ assert len(conn.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, "(cn=*)")) > 0
|
||||
+ check_vlv_search(conn)
|
||||
+
|
||||
+ # Remove and recreate VLVs
|
||||
+ vlv_index.delete()
|
||||
+ vlv_search.delete()
|
||||
+
|
||||
+ vlv_search, vlv_index = create_vlv_search_and_index(inst)
|
||||
+ assert reindex_task.reindex(
|
||||
+ suffix=DEFAULT_SUFFIX,
|
||||
+ attrname=vlv_index.rdn,
|
||||
+ args={TASK_WAIT: True},
|
||||
+ vlv=True
|
||||
+ ) == 0
|
||||
+
|
||||
+ conn = open_new_ldapi_conn(inst.serverid)
|
||||
+ assert len(conn.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, "(cn=*)")) > 0
|
||||
+ check_vlv_search(conn)
|
||||
+
|
||||
+
|
||||
def test_vlv_with_mr(vlv_setup_with_uid_mr):
|
||||
"""
|
||||
Testing vlv having specific matching rule
|
||||
@@ -288,6 +402,7 @@ def test_vlv_with_mr(vlv_setup_with_uid_mr):
|
||||
assert inst.status()
|
||||
|
||||
|
||||
+
|
||||
if __name__ == "__main__":
|
||||
# Run isolated
|
||||
# -s for DEBUG mode
|
||||
--
|
||||
2.49.0
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
From 582c4916a80660e3ea876ae4b6355a81c29a791d Mon Sep 17 00:00:00 2001
|
||||
From f4817944e43e080b1ab15c3fa13359252429e500 Mon Sep 17 00:00:00 2001
|
||||
From: tbordaz <tbordaz@redhat.com>
|
||||
Date: Wed, 25 Feb 2026 14:06:42 +0100
|
||||
Subject: [PATCH] Security fix for CVE-2025-14905
|
||||
@ -17,7 +17,7 @@ References:
|
||||
1 file changed, 38 insertions(+), 9 deletions(-)
|
||||
|
||||
diff --git a/ldap/servers/slapd/schema.c b/ldap/servers/slapd/schema.c
|
||||
index 16f1861cf..ff92d2b33 100644
|
||||
index 9ef4ee4bf..7712a720d 100644
|
||||
--- a/ldap/servers/slapd/schema.c
|
||||
+++ b/ldap/servers/slapd/schema.c
|
||||
@@ -1410,6 +1410,7 @@ schema_attr_enum_callback(struct asyntaxinfo *asip, void *arg)
|
||||
@ -89,5 +89,5 @@ index 16f1861cf..ff92d2b33 100644
|
||||
/*
|
||||
* Overall strategy is to maintain a pointer to the next location in
|
||||
--
|
||||
2.52.0
|
||||
2.53.0
|
||||
|
||||
@ -1,75 +0,0 @@
|
||||
From af3fa90f91efda86f4337e8823bca6581ab61792 Mon Sep 17 00:00:00 2001
|
||||
From: Thierry Bordaz <tbordaz@redhat.com>
|
||||
Date: Fri, 7 Feb 2025 09:43:08 +0100
|
||||
Subject: [PATCH] Issue 6494 - (2nd) Various errors when using extended
|
||||
matching rule on vlv sort filter
|
||||
|
||||
---
|
||||
.../tests/suites/indexes/regression_test.py | 40 +++++++++++++++++++
|
||||
1 file changed, 40 insertions(+)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/indexes/regression_test.py b/dirsrvtests/tests/suites/indexes/regression_test.py
|
||||
index 2196fb2ed..b5bcccc8f 100644
|
||||
--- a/dirsrvtests/tests/suites/indexes/regression_test.py
|
||||
+++ b/dirsrvtests/tests/suites/indexes/regression_test.py
|
||||
@@ -11,17 +11,57 @@ import os
|
||||
import pytest
|
||||
import ldap
|
||||
from lib389._constants import DEFAULT_BENAME, DEFAULT_SUFFIX
|
||||
+from lib389.backend import Backend, Backends, DatabaseConfig
|
||||
from lib389.cos import CosClassicDefinition, CosClassicDefinitions, CosTemplate
|
||||
+from lib389.dbgen import dbgen_users
|
||||
from lib389.index import Indexes
|
||||
from lib389.backend import Backends
|
||||
from lib389.idm.user import UserAccounts
|
||||
from lib389.topologies import topology_st as topo
|
||||
from lib389.utils import ds_is_older
|
||||
from lib389.idm.nscontainer import nsContainer
|
||||
+from lib389.properties import TASK_WAIT
|
||||
+from lib389.tasks import Tasks, Task
|
||||
|
||||
pytestmark = pytest.mark.tier1
|
||||
|
||||
|
||||
+SUFFIX2 = 'dc=example2,dc=com'
|
||||
+BENAME2 = 'be2'
|
||||
+
|
||||
+DEBUGGING = os.getenv("DEBUGGING", default=False)
|
||||
+
|
||||
+@pytest.fixture(scope="function")
|
||||
+def add_backend_and_ldif_50K_users(request, topo):
|
||||
+ """
|
||||
+ Add an empty backend and associated 50K users ldif file
|
||||
+ """
|
||||
+
|
||||
+ tasks = Tasks(topo.standalone)
|
||||
+ import_ldif = f'{topo.standalone.ldifdir}/be2_50K_users.ldif'
|
||||
+ be2 = Backend(topo.standalone)
|
||||
+ be2.create(properties={
|
||||
+ 'cn': BENAME2,
|
||||
+ 'nsslapd-suffix': SUFFIX2,
|
||||
+ },
|
||||
+ )
|
||||
+
|
||||
+ def fin():
|
||||
+ nonlocal be2
|
||||
+ if not DEBUGGING:
|
||||
+ be2.delete()
|
||||
+
|
||||
+ request.addfinalizer(fin)
|
||||
+ parent = f'ou=people,{SUFFIX2}'
|
||||
+ dbgen_users(topo.standalone, 50000, import_ldif, SUFFIX2, generic=True, parent=parent)
|
||||
+ assert tasks.importLDIF(
|
||||
+ suffix=SUFFIX2,
|
||||
+ input_file=import_ldif,
|
||||
+ args={TASK_WAIT: True}
|
||||
+ ) == 0
|
||||
+
|
||||
+ return import_ldif
|
||||
+
|
||||
@pytest.fixture(scope="function")
|
||||
def add_a_group_with_users(request, topo):
|
||||
"""
|
||||
--
|
||||
2.49.0
|
||||
|
||||
@ -0,0 +1,262 @@
|
||||
From c6bb96c95dcbff0052fc2decab7020b0f833f313 Mon Sep 17 00:00:00 2001
|
||||
From: Mark Reynolds <mreynolds@redhat.com>
|
||||
Date: Mon, 2 Mar 2026 14:31:29 -0500
|
||||
Subject: [PATCH] Issue 7271 - implement a pre-close plugin function
|
||||
|
||||
Description:
|
||||
|
||||
replication protocol could benefit from being notifioed the shutdown process
|
||||
has started before calling the "close" plugin function. This would allow the
|
||||
replication protocol to wake up and adjust its active thread count to prevent
|
||||
a hang during shutdown.
|
||||
|
||||
relates: https://github.com/389ds/389-ds-base/issues/7271
|
||||
|
||||
Reviewed by: progier, spichugi, and vashirov(Thanks!!!)
|
||||
---
|
||||
ldap/servers/plugins/replication/repl5_init.c | 19 ++++++++----
|
||||
ldap/servers/slapd/daemon.c | 6 +++-
|
||||
ldap/servers/slapd/pblock.c | 30 +++++++++++++------
|
||||
ldap/servers/slapd/plugin.c | 24 ++++++++++++++-
|
||||
ldap/servers/slapd/proto-slap.h | 3 +-
|
||||
ldap/servers/slapd/slap.h | 3 +-
|
||||
ldap/servers/slapd/slapi-plugin.h | 3 +-
|
||||
7 files changed, 69 insertions(+), 19 deletions(-)
|
||||
|
||||
diff --git a/ldap/servers/plugins/replication/repl5_init.c b/ldap/servers/plugins/replication/repl5_init.c
|
||||
index 5047fb8dc..395744db8 100644
|
||||
--- a/ldap/servers/plugins/replication/repl5_init.c
|
||||
+++ b/ldap/servers/plugins/replication/repl5_init.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) 2026 Red Hat, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* License: GPL (version 3 or any later version).
|
||||
@@ -122,6 +122,8 @@ static PRUintn thread_private_agmtname; /* thread private index for logging*/
|
||||
static PRUintn thread_private_cache;
|
||||
static PRUintn thread_primary_csn;
|
||||
|
||||
+static int multisupplier_pre_stop(Slapi_PBlock *pb __attribute__((unused)));
|
||||
+
|
||||
char *
|
||||
get_thread_private_agmtname()
|
||||
{
|
||||
@@ -836,10 +838,6 @@ multisupplier_stop(Slapi_PBlock *pb __attribute__((unused)))
|
||||
int rc = 0; /* OK */
|
||||
|
||||
if (!multisupplier_stopped_flag) {
|
||||
- if (!is_ldif_dump) {
|
||||
- /* Shut down replication agreements */
|
||||
- agmtlist_shutdown();
|
||||
- }
|
||||
/* if we are cleaning a ruv, stop */
|
||||
stop_ruv_cleaning();
|
||||
/* unregister backend state change notification */
|
||||
@@ -853,6 +851,16 @@ multisupplier_stop(Slapi_PBlock *pb __attribute__((unused)))
|
||||
return rc;
|
||||
}
|
||||
|
||||
+static int
|
||||
+multisupplier_pre_stop(Slapi_PBlock *pb __attribute__((unused)))
|
||||
+{
|
||||
+ if (!multisupplier_stopped_flag) {
|
||||
+ /* Shut down replication agreements which will stop all the
|
||||
+ * replication protocol threads */
|
||||
+ agmtlist_shutdown();
|
||||
+ }
|
||||
+ return 0;
|
||||
+}
|
||||
|
||||
PRBool
|
||||
multisupplier_started()
|
||||
@@ -900,6 +908,7 @@ replication_multisupplier_plugin_init(Slapi_PBlock *pb)
|
||||
rc = slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&multisupplierdesc);
|
||||
rc = slapi_pblock_set(pb, SLAPI_PLUGIN_START_FN, (void *)multisupplier_start);
|
||||
rc = slapi_pblock_set(pb, SLAPI_PLUGIN_CLOSE_FN, (void *)multisupplier_stop);
|
||||
+ rc = slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_CLOSE_FN, (void *)multisupplier_pre_stop);
|
||||
|
||||
/* Register the plugin interfaces we implement */
|
||||
/* preop acquires csn generator handle */
|
||||
diff --git a/ldap/servers/slapd/daemon.c b/ldap/servers/slapd/daemon.c
|
||||
index d6911f03c..7fc1a16f1 100644
|
||||
--- a/ldap/servers/slapd/daemon.c
|
||||
+++ b/ldap/servers/slapd/daemon.c
|
||||
@@ -1,6 +1,6 @@
|
||||
/** BEGIN COPYRIGHT BLOCK
|
||||
* Copyright (C) 2001 Sun Microsystems, Inc. Used by permission.
|
||||
- * Copyright (C) 2021 Red Hat, Inc.
|
||||
+ * Copyright (C) 2026 Red Hat, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* License: GPL (version 3 or any later version).
|
||||
@@ -1216,6 +1216,10 @@ slapd_daemon(daemon_ports_t *ports)
|
||||
task_cancel_all();
|
||||
}
|
||||
|
||||
+ /* Call plugin pre close functions */
|
||||
+ plugin_pre_closeall();
|
||||
+
|
||||
+ /* Now wait for active threads to terminate */
|
||||
threads = g_get_active_threadcnt();
|
||||
if (threads > 0) {
|
||||
slapi_log_err(SLAPI_LOG_INFO, "slapd_daemon",
|
||||
diff --git a/ldap/servers/slapd/pblock.c b/ldap/servers/slapd/pblock.c
|
||||
index 76e26cb86..90114dee1 100644
|
||||
--- a/ldap/servers/slapd/pblock.c
|
||||
+++ b/ldap/servers/slapd/pblock.c
|
||||
@@ -1,12 +1,12 @@
|
||||
-/** BEGIN COPYRIGHT BLOCK
|
||||
- * Copyright (C) 2001 Sun Microsystems, Inc. Used by permission.
|
||||
- * Copyright (C) 2005-2025 Red Hat, Inc.
|
||||
- * Copyright (C) 2009 Hewlett-Packard Development Company, L.P.
|
||||
- * All rights reserved.
|
||||
- *
|
||||
- * License: GPL (version 3 or any later version).
|
||||
- * See LICENSE for details.
|
||||
- * END COPYRIGHT BLOCK **/
|
||||
+ /** BEGIN COPYRIGHT BLOCK
|
||||
+ * Copyright (C) 2001 Sun Microsystems, Inc. Used by permission.
|
||||
+ * Copyright (C) 2026 Red Hat, Inc.
|
||||
+ * Copyright (C) 2009 Hewlett-Packard Development Company, L.P.
|
||||
+ * All rights reserved.
|
||||
+ *
|
||||
+ * License: GPL (version 3 or any later version).
|
||||
+ * See LICENSE for details.
|
||||
+ * END COPYRIGHT BLOCK **/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
@@ -2524,6 +2524,14 @@ slapi_pblock_get(Slapi_PBlock *pblock, int arg, void *value)
|
||||
}
|
||||
break;
|
||||
|
||||
+ case SLAPI_PLUGIN_PRE_CLOSE_FN:
|
||||
+ if (pblock->pb_plugin != NULL) {
|
||||
+ (*(IFP *)value) = pblock->pb_plugin->plg_pre_close;
|
||||
+ } else {
|
||||
+ (*(IFP *)value) = NULL;
|
||||
+ }
|
||||
+ break;
|
||||
+
|
||||
default:
|
||||
slapi_log_err(SLAPI_LOG_ERR, "slapi_pblock_get", "Unknown parameter block argument %d\n", arg);
|
||||
PR_ASSERT(0);
|
||||
@@ -4209,6 +4217,10 @@ slapi_pblock_set(Slapi_PBlock *pblock, int arg, void *value)
|
||||
pblock->pb_misc->pb_aci_target_check = *((int *)value);
|
||||
break;
|
||||
|
||||
+ case SLAPI_PLUGIN_PRE_CLOSE_FN:
|
||||
+ pblock->pb_plugin->plg_pre_close = (IFP)value;
|
||||
+ break;
|
||||
+
|
||||
default:
|
||||
slapi_log_err(SLAPI_LOG_ERR, "slapi_pblock_set",
|
||||
"Unknown parameter block argument %d\n", arg);
|
||||
diff --git a/ldap/servers/slapd/plugin.c b/ldap/servers/slapd/plugin.c
|
||||
index 52d25e19e..95b8de894 100644
|
||||
--- a/ldap/servers/slapd/plugin.c
|
||||
+++ b/ldap/servers/slapd/plugin.c
|
||||
@@ -1,6 +1,6 @@
|
||||
/** BEGIN COPYRIGHT BLOCK
|
||||
* Copyright (C) 2001 Sun Microsystems, Inc. Used by permission.
|
||||
- * Copyright (C) 2021 Red Hat, Inc.
|
||||
+ * Copyright (C) 2026 Red Hat, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* License: GPL (version 3 or any later version).
|
||||
@@ -1847,6 +1847,28 @@ plugin_dependency_closeall(void)
|
||||
}
|
||||
}
|
||||
|
||||
+/* Call the pre close functions of all the plugins */
|
||||
+void
|
||||
+plugin_pre_closeall(void)
|
||||
+{
|
||||
+ Slapi_PBlock *pb = NULL;
|
||||
+ int plugins_pre_closed = 0;
|
||||
+ int index = 0;
|
||||
+
|
||||
+ while (plugins_pre_closed < global_plugins_started) {
|
||||
+ if (global_plugin_shutdown_order[index].name) {
|
||||
+ if (!global_plugin_shutdown_order[index].removed) {
|
||||
+ pb = slapi_pblock_new();
|
||||
+ plugin_call_one(global_plugin_shutdown_order[index].plugin,
|
||||
+ SLAPI_PLUGIN_PRE_CLOSE_FN, pb);
|
||||
+ slapi_pblock_destroy(pb);
|
||||
+ }
|
||||
+ plugins_pre_closed++;
|
||||
+ }
|
||||
+ index++;
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
void
|
||||
plugin_freeall(void)
|
||||
{
|
||||
diff --git a/ldap/servers/slapd/proto-slap.h b/ldap/servers/slapd/proto-slap.h
|
||||
index 742e3ee14..fd5bc6e71 100644
|
||||
--- a/ldap/servers/slapd/proto-slap.h
|
||||
+++ b/ldap/servers/slapd/proto-slap.h
|
||||
@@ -1,6 +1,6 @@
|
||||
/** BEGIN COPYRIGHT BLOCK
|
||||
* Copyright (C) 2001 Sun Microsystems, Inc. Used by permission.
|
||||
- * Copyright (C) 2021-2025 Red Hat, Inc.
|
||||
+ * Copyright (C) 2026 Red Hat, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* License: GPL (version 3 or any later version).
|
||||
@@ -982,6 +982,7 @@ int plugin_call_exop_plugins(Slapi_PBlock *pb, struct slapdplugin *p);
|
||||
Slapi_Backend *plugin_extended_op_getbackend(Slapi_PBlock *pb, struct slapdplugin *p);
|
||||
const char *plugin_extended_op_oid2string(const char *oid);
|
||||
void plugin_closeall(int close_backends, int close_globals);
|
||||
+void plugin_pre_closeall(void);
|
||||
void plugin_dependency_freeall(void);
|
||||
void plugin_startall(int argc, char **argv, char **plugin_list);
|
||||
void plugin_get_plugin_dependencies(char *plugin_name, char ***names);
|
||||
diff --git a/ldap/servers/slapd/slap.h b/ldap/servers/slapd/slap.h
|
||||
index fee5a6ab5..64f9f465b 100644
|
||||
--- a/ldap/servers/slapd/slap.h
|
||||
+++ b/ldap/servers/slapd/slap.h
|
||||
@@ -1,7 +1,7 @@
|
||||
/** BEGIN COPYRIGHT BLOCK
|
||||
* Copyright (C) 2001 Sun Microsystems, Inc. Used by permission.
|
||||
* Copyright (C) 2009 Hewlett-Packard Development Company, L.P.
|
||||
- * Copyright (C) 2009-2025 Red Hat, Inc.
|
||||
+ * Copyright (C) 2026 Red Hat, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Contributors:
|
||||
@@ -1035,6 +1035,7 @@ struct slapdplugin
|
||||
char *plg_libpath; /* library path for dll/so */
|
||||
char *plg_initfunc; /* init symbol */
|
||||
IFP plg_close; /* close function */
|
||||
+ IFP plg_pre_close; /* pre close function */
|
||||
Slapi_PluginDesc plg_desc; /* vendor's info */
|
||||
char *plg_name; /* used for plugin rdn in cn=config */
|
||||
struct slapdplugin *plg_next; /* for plugin lists */
|
||||
diff --git a/ldap/servers/slapd/slapi-plugin.h b/ldap/servers/slapd/slapi-plugin.h
|
||||
index 5d9ef289d..4661d2cf0 100644
|
||||
--- a/ldap/servers/slapd/slapi-plugin.h
|
||||
+++ b/ldap/servers/slapd/slapi-plugin.h
|
||||
@@ -1,6 +1,6 @@
|
||||
/* BEGIN COPYRIGHT BLOCK
|
||||
* Copyright (C) 2001 Sun Microsystems, Inc. Used by permission.
|
||||
- * Copyright (C) 2021 Red Hat, Inc.
|
||||
+ * Copyright (C) 2026 Red Hat, Inc.
|
||||
* Copyright (C) 2009 Hewlett-Packard Development Company, L.P.
|
||||
* All rights reserved.
|
||||
*
|
||||
@@ -7082,6 +7082,7 @@ typedef struct slapi_plugindesc
|
||||
|
||||
/* miscellaneous plugin functions */
|
||||
#define SLAPI_PLUGIN_CLOSE_FN 210
|
||||
+#define SLAPI_PLUGIN_PRE_CLOSE_FN 211
|
||||
#define SLAPI_PLUGIN_START_FN 212
|
||||
#define SLAPI_PLUGIN_CLEANUP_FN 232
|
||||
#define SLAPI_PLUGIN_POSTSTART_FN 233
|
||||
--
|
||||
2.53.0
|
||||
|
||||
@ -1,45 +0,0 @@
|
||||
From 0ad0eb34972c99f30334d7d420f3056e0e794d74 Mon Sep 17 00:00:00 2001
|
||||
From: Thierry Bordaz <tbordaz@redhat.com>
|
||||
Date: Fri, 7 Feb 2025 14:33:46 +0100
|
||||
Subject: [PATCH] Issue 6494 - (3rd) Various errors when using extended
|
||||
matching rule on vlv sort filter
|
||||
|
||||
(cherry picked from the commit f2f917ca55c34c81b578bce1dd5275abff6abb72)
|
||||
---
|
||||
dirsrvtests/tests/suites/vlv/regression_test.py | 8 ++++++--
|
||||
1 file changed, 6 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/vlv/regression_test.py b/dirsrvtests/tests/suites/vlv/regression_test.py
|
||||
index 536fe950f..d069fdbaf 100644
|
||||
--- a/dirsrvtests/tests/suites/vlv/regression_test.py
|
||||
+++ b/dirsrvtests/tests/suites/vlv/regression_test.py
|
||||
@@ -16,12 +16,16 @@ from lib389.properties import TASK_WAIT
|
||||
from lib389.index import *
|
||||
from lib389.mappingTree import *
|
||||
from lib389.backend import *
|
||||
-from lib389.idm.user import UserAccounts
|
||||
+from lib389.idm.user import UserAccounts, UserAccount
|
||||
+from lib389.idm.organization import Organization
|
||||
+from lib389.idm.organizationalunit import OrganizationalUnits
|
||||
from ldap.controls.vlv import VLVRequestControl
|
||||
from ldap.controls.sss import SSSRequestControl
|
||||
|
||||
pytestmark = pytest.mark.tier1
|
||||
|
||||
+DEMO_PW = 'secret12'
|
||||
+
|
||||
logging.getLogger(__name__).setLevel(logging.DEBUG)
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -169,7 +173,7 @@ class BackendHandler:
|
||||
'loginShell': '/bin/false',
|
||||
'userpassword': DEMO_PW })
|
||||
# Add regular user
|
||||
- add_users(self.inst, 10, suffix=suffix)
|
||||
+ add_users(self.inst, 10)
|
||||
# Removing ou2
|
||||
ou2.delete()
|
||||
# And export
|
||||
--
|
||||
2.49.0
|
||||
|
||||
@ -0,0 +1,34 @@
|
||||
From 21b9e00264437dff3b6b81355cd159485bfdaf37 Mon Sep 17 00:00:00 2001
|
||||
From: Mark Reynolds <mreynolds@redhat.com>
|
||||
Date: Wed, 4 Mar 2026 14:47:01 -0500
|
||||
Subject: [PATCH] Issue 7271 - Add new plugin pre-close function check to
|
||||
plugin_invoke_plugin_pb
|
||||
|
||||
Description:
|
||||
|
||||
In plugin_invoke_plugin_pb we were not checking for the new pre-close function
|
||||
which led to an error in the logs: pb_op is NULL. In a debug build this leads
|
||||
to an assertion error at shutdown.
|
||||
|
||||
relates: https://github.com/389ds/389-ds-base/issues/7271
|
||||
|
||||
Reviewed by: vashirov(Thanks!)
|
||||
---
|
||||
ldap/servers/slapd/plugin.c | 1 +
|
||||
1 file changed, 1 insertion(+)
|
||||
|
||||
diff --git a/ldap/servers/slapd/plugin.c b/ldap/servers/slapd/plugin.c
|
||||
index 95b8de894..c1b0571eb 100644
|
||||
--- a/ldap/servers/slapd/plugin.c
|
||||
+++ b/ldap/servers/slapd/plugin.c
|
||||
@@ -3647,6 +3647,7 @@ plugin_invoke_plugin_pb(struct slapdplugin *plugin, int operation, Slapi_PBlock
|
||||
if (operation == SLAPI_PLUGIN_START_FN ||
|
||||
operation == SLAPI_PLUGIN_POSTSTART_FN ||
|
||||
operation == SLAPI_PLUGIN_CLOSE_FN ||
|
||||
+ operation == SLAPI_PLUGIN_PRE_CLOSE_FN ||
|
||||
operation == SLAPI_PLUGIN_CLEANUP_FN ||
|
||||
operation == SLAPI_PLUGIN_BE_PRE_CLOSE_FN ||
|
||||
operation == SLAPI_PLUGIN_BE_POST_OPEN_FN ||
|
||||
--
|
||||
2.53.0
|
||||
|
||||
@ -1,72 +0,0 @@
|
||||
From 52041811b200292af6670490c9ebc1f599439a22 Mon Sep 17 00:00:00 2001
|
||||
From: Masahiro Matsuya <mmatsuya@redhat.com>
|
||||
Date: Sat, 22 Mar 2025 01:25:25 +0900
|
||||
Subject: [PATCH] Issue 6494 - (4th) Various errors when using extended
|
||||
matching rule on vlv sort filter
|
||||
|
||||
test_vlv_with_mr uses vlv_setup_with_uid_mr fixture to setup backend
|
||||
and testusers. add_users function is called in beh.setup without any
|
||||
suffix for the created backend. As a result, testusers always are
|
||||
created in the DEFAULT_SUFFIX only by add_users function. Another test
|
||||
like test_vlv_recreation_reindex can create the same test user in
|
||||
DEFAULT_SUFFIX, and it caused the ALREADY_EXISTS failure in
|
||||
test_vlv_with_mr test.
|
||||
|
||||
In main branch, add_users have suffix argument. Test users are created
|
||||
on the specific suffix, and the backend is cleaned up after the test.
|
||||
This PR is to follow the same implementation.
|
||||
|
||||
Also, suppressing ldap.ALREADY_EXISTS makes the add_users func to be
|
||||
used easily.
|
||||
|
||||
Related: https://github.com/389ds/389-ds-base/issues/6494
|
||||
---
|
||||
dirsrvtests/tests/suites/vlv/regression_test.py | 11 ++++++-----
|
||||
1 file changed, 6 insertions(+), 5 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/vlv/regression_test.py b/dirsrvtests/tests/suites/vlv/regression_test.py
|
||||
index d069fdbaf..e9408117b 100644
|
||||
--- a/dirsrvtests/tests/suites/vlv/regression_test.py
|
||||
+++ b/dirsrvtests/tests/suites/vlv/regression_test.py
|
||||
@@ -21,6 +21,7 @@ from lib389.idm.organization import Organization
|
||||
from lib389.idm.organizationalunit import OrganizationalUnits
|
||||
from ldap.controls.vlv import VLVRequestControl
|
||||
from ldap.controls.sss import SSSRequestControl
|
||||
+from contextlib import suppress
|
||||
|
||||
pytestmark = pytest.mark.tier1
|
||||
|
||||
@@ -68,8 +69,8 @@ def check_vlv_search(conn):
|
||||
assert dn.lower() == expected_dn.lower()
|
||||
|
||||
|
||||
-def add_users(inst, users_num):
|
||||
- users = UserAccounts(inst, DEFAULT_SUFFIX)
|
||||
+def add_users(inst, users_num, suffix=DEFAULT_SUFFIX):
|
||||
+ users = UserAccounts(inst, suffix)
|
||||
log.info(f'Adding {users_num} users')
|
||||
for i in range(0, users_num):
|
||||
uid = 1000 + i
|
||||
@@ -81,8 +82,8 @@ def add_users(inst, users_num):
|
||||
'gidNumber': str(uid),
|
||||
'homeDirectory': f'/home/testuser{uid}'
|
||||
}
|
||||
- users.create(properties=user_properties)
|
||||
-
|
||||
+ with suppress(ldap.ALREADY_EXISTS):
|
||||
+ users.create(properties=user_properties)
|
||||
|
||||
|
||||
def create_vlv_search_and_index(inst, basedn=DEFAULT_SUFFIX, bename='userRoot',
|
||||
@@ -173,7 +174,7 @@ class BackendHandler:
|
||||
'loginShell': '/bin/false',
|
||||
'userpassword': DEMO_PW })
|
||||
# Add regular user
|
||||
- add_users(self.inst, 10)
|
||||
+ add_users(self.inst, 10, suffix=suffix)
|
||||
# Removing ou2
|
||||
ou2.delete()
|
||||
# And export
|
||||
--
|
||||
2.49.0
|
||||
|
||||
@ -1,357 +0,0 @@
|
||||
From b812afe4da6db134c1221eb48a6155480e4c2cb3 Mon Sep 17 00:00:00 2001
|
||||
From: Simon Pichugin <spichugi@redhat.com>
|
||||
Date: Tue, 14 Jan 2025 13:55:03 -0500
|
||||
Subject: [PATCH] Issue 6497 - lib389 - Configure replication for multiple
|
||||
suffixes (#6498)
|
||||
|
||||
Bug Description: When trying to set up replication across multiple suffixes -
|
||||
particularly if one of those suffixes is a subsuffix - lib389 fails to properly
|
||||
configure the replication agreements, service accounts, and required groups.
|
||||
The references to the replication_managers group and service account
|
||||
naming do not correctly account for non-default additional suffixes.
|
||||
|
||||
Fix Description: Ensure replication DNs and credentials are correctly tied to each suffix.
|
||||
Enable DSLdapObject.present method to compare values as
|
||||
a normalized DNs if they are DNs.
|
||||
Add a test (test_multi_subsuffix_replication) to verify multi-suffix
|
||||
replication across four suppliers.
|
||||
Fix tests that are related to repl service accounts.
|
||||
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/6497
|
||||
|
||||
Reviewed: @progier389 (Thanks!)
|
||||
---
|
||||
.../tests/suites/ds_tools/replcheck_test.py | 4 +-
|
||||
.../suites/replication/acceptance_test.py | 153 ++++++++++++++++++
|
||||
.../cleanallruv_shutdown_crash_test.py | 4 +-
|
||||
.../suites/replication/regression_m2_test.py | 2 +-
|
||||
.../replication/tls_client_auth_repl_test.py | 4 +-
|
||||
src/lib389/lib389/_mapped_object.py | 21 ++-
|
||||
src/lib389/lib389/replica.py | 10 +-
|
||||
7 files changed, 182 insertions(+), 16 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/ds_tools/replcheck_test.py b/dirsrvtests/tests/suites/ds_tools/replcheck_test.py
|
||||
index f61fc432d..dfa1d9423 100644
|
||||
--- a/dirsrvtests/tests/suites/ds_tools/replcheck_test.py
|
||||
+++ b/dirsrvtests/tests/suites/ds_tools/replcheck_test.py
|
||||
@@ -67,10 +67,10 @@ def topo_tls_ldapi(topo):
|
||||
|
||||
# Create the replication dns
|
||||
services = ServiceAccounts(m1, DEFAULT_SUFFIX)
|
||||
- repl_m1 = services.get('%s:%s' % (m1.host, m1.sslport))
|
||||
+ repl_m1 = services.get(f'{DEFAULT_SUFFIX}:{m1.host}:{m1.sslport}')
|
||||
repl_m1.set('nsCertSubjectDN', m1.get_server_tls_subject())
|
||||
|
||||
- repl_m2 = services.get('%s:%s' % (m2.host, m2.sslport))
|
||||
+ repl_m2 = services.get(f'{DEFAULT_SUFFIX}:{m2.host}:{m2.sslport}')
|
||||
repl_m2.set('nsCertSubjectDN', m2.get_server_tls_subject())
|
||||
|
||||
# Check the replication is "done".
|
||||
diff --git a/dirsrvtests/tests/suites/replication/acceptance_test.py b/dirsrvtests/tests/suites/replication/acceptance_test.py
|
||||
index d1cfa8bdb..fc8622051 100644
|
||||
--- a/dirsrvtests/tests/suites/replication/acceptance_test.py
|
||||
+++ b/dirsrvtests/tests/suites/replication/acceptance_test.py
|
||||
@@ -9,6 +9,7 @@
|
||||
import pytest
|
||||
import logging
|
||||
import time
|
||||
+from lib389.backend import Backend
|
||||
from lib389.replica import Replicas
|
||||
from lib389.tasks import *
|
||||
from lib389.utils import *
|
||||
@@ -325,6 +326,158 @@ def test_modify_stripattrs(topo_m4):
|
||||
assert attr_value in entries[0].data['nsds5replicastripattrs']
|
||||
|
||||
|
||||
+def test_multi_subsuffix_replication(topo_m4):
|
||||
+ """Check that replication works with multiple subsuffixes
|
||||
+
|
||||
+ :id: ac1aaeae-173e-48e7-847f-03b9867443c4
|
||||
+ :setup: Four suppliers replication setup
|
||||
+ :steps:
|
||||
+ 1. Create additional suffixes
|
||||
+ 2. Setup replication for all suppliers
|
||||
+ 3. Generate test data for each suffix (add, modify, remove)
|
||||
+ 4. Wait for replication to complete across all suppliers for each suffix
|
||||
+ 5. Check that all expected data is present on all suppliers
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Success
|
||||
+ 4. Success
|
||||
+ 5. Success (the data is replicated everywhere)
|
||||
+ """
|
||||
+
|
||||
+ SUFFIX_2 = "dc=test2"
|
||||
+ SUFFIX_3 = f"dc=test3,{DEFAULT_SUFFIX}"
|
||||
+ all_suffixes = [DEFAULT_SUFFIX, SUFFIX_2, SUFFIX_3]
|
||||
+
|
||||
+ test_users_by_suffix = {suffix: [] for suffix in all_suffixes}
|
||||
+ created_backends = []
|
||||
+
|
||||
+ suppliers = [
|
||||
+ topo_m4.ms["supplier1"],
|
||||
+ topo_m4.ms["supplier2"],
|
||||
+ topo_m4.ms["supplier3"],
|
||||
+ topo_m4.ms["supplier4"]
|
||||
+ ]
|
||||
+
|
||||
+ try:
|
||||
+ # Setup additional backends and replication for the new suffixes
|
||||
+ for suffix in [SUFFIX_2, SUFFIX_3]:
|
||||
+ repl = ReplicationManager(suffix)
|
||||
+ for supplier in suppliers:
|
||||
+ # Create a new backend for this suffix
|
||||
+ props = {
|
||||
+ 'cn': f'userRoot_{suffix.split(",")[0][3:]}',
|
||||
+ 'nsslapd-suffix': suffix
|
||||
+ }
|
||||
+ be = Backend(supplier)
|
||||
+ be.create(properties=props)
|
||||
+ be.create_sample_entries('001004002')
|
||||
+
|
||||
+ # Track the backend so we can remove it later
|
||||
+ created_backends.append((supplier, props['cn']))
|
||||
+
|
||||
+ # Enable replication
|
||||
+ if supplier == suppliers[0]:
|
||||
+ repl.create_first_supplier(supplier)
|
||||
+ else:
|
||||
+ repl.join_supplier(suppliers[0], supplier)
|
||||
+
|
||||
+ # Create a full mesh topology for this suffix
|
||||
+ for i, supplier_i in enumerate(suppliers):
|
||||
+ for j, supplier_j in enumerate(suppliers):
|
||||
+ if i != j:
|
||||
+ repl.ensure_agreement(supplier_i, supplier_j)
|
||||
+
|
||||
+ # Generate test data for each suffix (add, modify, remove)
|
||||
+ for suffix in all_suffixes:
|
||||
+ # Create some user entries in supplier1
|
||||
+ for i in range(20):
|
||||
+ user_dn = f'uid=test_user_{i},{suffix}'
|
||||
+ test_user = UserAccount(suppliers[0], user_dn)
|
||||
+ test_user.create(properties={
|
||||
+ 'uid': f'test_user_{i}',
|
||||
+ 'cn': f'Test User {i}',
|
||||
+ 'sn': f'User{i}',
|
||||
+ 'userPassword': 'password',
|
||||
+ 'uidNumber': str(1000 + i),
|
||||
+ 'gidNumber': '2000',
|
||||
+ 'homeDirectory': f'/home/test_user_{i}'
|
||||
+ })
|
||||
+ test_users_by_suffix[suffix].append(test_user)
|
||||
+
|
||||
+ # Perform modifications on these entries
|
||||
+ for user in test_users_by_suffix[suffix]:
|
||||
+ # Add some attributes
|
||||
+ for j in range(3):
|
||||
+ user.add('description', f'Description {j}')
|
||||
+ # Replace an attribute
|
||||
+ user.replace('cn', f'Modified User {user.get_attr_val_utf8("uid")}')
|
||||
+ # Delete the attributes we added
|
||||
+ for j in range(3):
|
||||
+ try:
|
||||
+ user.remove('description', f'Description {j}')
|
||||
+ except Exception:
|
||||
+ pass
|
||||
+
|
||||
+ # Wait for replication to complete across all suppliers, for each suffix
|
||||
+ for suffix in all_suffixes:
|
||||
+ repl = ReplicationManager(suffix)
|
||||
+ for i, supplier_i in enumerate(suppliers):
|
||||
+ for j, supplier_j in enumerate(suppliers):
|
||||
+ if i != j:
|
||||
+ repl.wait_for_replication(supplier_i, supplier_j)
|
||||
+
|
||||
+ # Verify that each user and modification replicated to all suppliers
|
||||
+ for suffix in all_suffixes:
|
||||
+ for i in range(20):
|
||||
+ user_dn = f'uid=test_user_{i},{suffix}'
|
||||
+ # Retrieve this user from all suppliers
|
||||
+ all_user_objs = topo_m4.all_get_dsldapobject(user_dn, UserAccount)
|
||||
+ # Ensure it exists in all 4 suppliers
|
||||
+ assert len(all_user_objs) == 4, (
|
||||
+ f"User {user_dn} not found on all suppliers. "
|
||||
+ f"Found only on {len(all_user_objs)} suppliers."
|
||||
+ )
|
||||
+ # Check modifications: 'cn' should now be 'Modified User test_user_{i}'
|
||||
+ for user_obj in all_user_objs:
|
||||
+ expected_cn = f"Modified User test_user_{i}"
|
||||
+ actual_cn = user_obj.get_attr_val_utf8("cn")
|
||||
+ assert actual_cn == expected_cn, (
|
||||
+ f"User {user_dn} has unexpected 'cn': {actual_cn} "
|
||||
+ f"(expected '{expected_cn}') on supplier {user_obj._instance.serverid}"
|
||||
+ )
|
||||
+ # And check that 'description' attributes were removed
|
||||
+ desc_vals = user_obj.get_attr_vals_utf8('description')
|
||||
+ for j in range(3):
|
||||
+ assert f"Description {j}" not in desc_vals, (
|
||||
+ f"User {user_dn} on supplier {user_obj._instance.serverid} "
|
||||
+ f"still has 'Description {j}'"
|
||||
+ )
|
||||
+ finally:
|
||||
+ for suffix, test_users in test_users_by_suffix.items():
|
||||
+ for user in test_users:
|
||||
+ try:
|
||||
+ if user.exists():
|
||||
+ user.delete()
|
||||
+ except Exception:
|
||||
+ pass
|
||||
+
|
||||
+ for suffix in [SUFFIX_2, SUFFIX_3]:
|
||||
+ repl = ReplicationManager(suffix)
|
||||
+ for supplier in suppliers:
|
||||
+ try:
|
||||
+ repl.remove_supplier(supplier)
|
||||
+ except Exception:
|
||||
+ pass
|
||||
+
|
||||
+ for (supplier, backend_name) in created_backends:
|
||||
+ be = Backend(supplier, backend_name)
|
||||
+ try:
|
||||
+ be.delete()
|
||||
+ except Exception:
|
||||
+ pass
|
||||
+
|
||||
+
|
||||
def test_new_suffix(topo_m4, new_suffix):
|
||||
"""Check that we can enable replication on a new suffix
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/replication/cleanallruv_shutdown_crash_test.py b/dirsrvtests/tests/suites/replication/cleanallruv_shutdown_crash_test.py
|
||||
index b4b74e339..fe9955e7e 100644
|
||||
--- a/dirsrvtests/tests/suites/replication/cleanallruv_shutdown_crash_test.py
|
||||
+++ b/dirsrvtests/tests/suites/replication/cleanallruv_shutdown_crash_test.py
|
||||
@@ -66,10 +66,10 @@ def test_clean_shutdown_crash(topology_m2):
|
||||
|
||||
log.info('Creating replication dns')
|
||||
services = ServiceAccounts(m1, DEFAULT_SUFFIX)
|
||||
- repl_m1 = services.get('%s:%s' % (m1.host, m1.sslport))
|
||||
+ repl_m1 = services.get(f'{DEFAULT_SUFFIX}:{m1.host}:{m1.sslport}')
|
||||
repl_m1.set('nsCertSubjectDN', m1.get_server_tls_subject())
|
||||
|
||||
- repl_m2 = services.get('%s:%s' % (m2.host, m2.sslport))
|
||||
+ repl_m2 = services.get(f'{DEFAULT_SUFFIX}:{m2.host}:{m2.sslport}')
|
||||
repl_m2.set('nsCertSubjectDN', m2.get_server_tls_subject())
|
||||
|
||||
log.info('Changing auth type')
|
||||
diff --git a/dirsrvtests/tests/suites/replication/regression_m2_test.py b/dirsrvtests/tests/suites/replication/regression_m2_test.py
|
||||
index 72d4b9f89..9c707615f 100644
|
||||
--- a/dirsrvtests/tests/suites/replication/regression_m2_test.py
|
||||
+++ b/dirsrvtests/tests/suites/replication/regression_m2_test.py
|
||||
@@ -64,7 +64,7 @@ class _AgmtHelper:
|
||||
self.binddn = f'cn={cn},cn=config'
|
||||
else:
|
||||
self.usedn = False
|
||||
- self.cn = f'{self.from_inst.host}:{self.from_inst.sslport}'
|
||||
+ self.cn = ldap.dn.escape_dn_chars(f'{DEFAULT_SUFFIX}:{self.from_inst.host}:{self.from_inst.sslport}')
|
||||
self.binddn = f'cn={self.cn}, ou=Services, {DEFAULT_SUFFIX}'
|
||||
self.original_state = []
|
||||
self._pass = False
|
||||
diff --git a/dirsrvtests/tests/suites/replication/tls_client_auth_repl_test.py b/dirsrvtests/tests/suites/replication/tls_client_auth_repl_test.py
|
||||
index a00dc5b78..ca17554c7 100644
|
||||
--- a/dirsrvtests/tests/suites/replication/tls_client_auth_repl_test.py
|
||||
+++ b/dirsrvtests/tests/suites/replication/tls_client_auth_repl_test.py
|
||||
@@ -56,10 +56,10 @@ def tls_client_auth(topo_m2):
|
||||
|
||||
# Create the replication dns
|
||||
services = ServiceAccounts(m1, DEFAULT_SUFFIX)
|
||||
- repl_m1 = services.get('%s:%s' % (m1.host, m1.sslport))
|
||||
+ repl_m1 = services.get(f'{DEFAULT_SUFFIX}:{m1.host}:{m1.sslport}')
|
||||
repl_m1.set('nsCertSubjectDN', m1.get_server_tls_subject())
|
||||
|
||||
- repl_m2 = services.get('%s:%s' % (m2.host, m2.sslport))
|
||||
+ repl_m2 = services.get(f'{DEFAULT_SUFFIX}:{m2.host}:{m2.sslport}')
|
||||
repl_m2.set('nsCertSubjectDN', m2.get_server_tls_subject())
|
||||
|
||||
# Check the replication is "done".
|
||||
diff --git a/src/lib389/lib389/_mapped_object.py b/src/lib389/lib389/_mapped_object.py
|
||||
index b7391d8cc..ae00c95d0 100644
|
||||
--- a/src/lib389/lib389/_mapped_object.py
|
||||
+++ b/src/lib389/lib389/_mapped_object.py
|
||||
@@ -19,7 +19,7 @@ from lib389._constants import DIRSRV_STATE_ONLINE
|
||||
from lib389._mapped_object_lint import DSLint, DSLints
|
||||
from lib389.utils import (
|
||||
ensure_bytes, ensure_str, ensure_int, ensure_list_bytes, ensure_list_str,
|
||||
- ensure_list_int, display_log_value, display_log_data
|
||||
+ ensure_list_int, display_log_value, display_log_data, is_a_dn, normalizeDN
|
||||
)
|
||||
|
||||
# This function filter and term generation provided thanks to
|
||||
@@ -292,15 +292,28 @@ class DSLdapObject(DSLogging, DSLint):
|
||||
_search_ext_s(self._instance,self._dn, ldap.SCOPE_BASE, self._object_filter, attrlist=[attr, ],
|
||||
serverctrls=self._server_controls, clientctrls=self._client_controls,
|
||||
escapehatch='i am sure')[0]
|
||||
- values = self.get_attr_vals_bytes(attr)
|
||||
+ values = self.get_attr_vals_utf8(attr)
|
||||
self._log.debug("%s contains %s" % (self._dn, values))
|
||||
|
||||
if value is None:
|
||||
# We are just checking if SOMETHING is present ....
|
||||
return len(values) > 0
|
||||
+
|
||||
+ # Otherwise, we are checking a specific value
|
||||
+ if is_a_dn(value):
|
||||
+ normalized_value = normalizeDN(value)
|
||||
else:
|
||||
- # Check if a value really does exist.
|
||||
- return ensure_bytes(value).lower() in [x.lower() for x in values]
|
||||
+ normalized_value = ensure_bytes(value).lower()
|
||||
+
|
||||
+ # Normalize each returned value depending on whether it is a DN
|
||||
+ normalized_values = []
|
||||
+ for v in values:
|
||||
+ if is_a_dn(v):
|
||||
+ normalized_values.append(normalizeDN(v))
|
||||
+ else:
|
||||
+ normalized_values.append(ensure_bytes(v.lower()))
|
||||
+
|
||||
+ return normalized_value in normalized_values
|
||||
|
||||
def add(self, key, value):
|
||||
"""Add an attribute with a value
|
||||
diff --git a/src/lib389/lib389/replica.py b/src/lib389/lib389/replica.py
|
||||
index 1f321972d..cd46e86d5 100644
|
||||
--- a/src/lib389/lib389/replica.py
|
||||
+++ b/src/lib389/lib389/replica.py
|
||||
@@ -2011,7 +2011,7 @@ class ReplicationManager(object):
|
||||
return repl_group
|
||||
else:
|
||||
try:
|
||||
- repl_group = groups.get('replication_managers')
|
||||
+ repl_group = groups.get(dn=f'cn=replication_managers,{self._suffix}')
|
||||
return repl_group
|
||||
except ldap.NO_SUCH_OBJECT:
|
||||
self._log.warning("{} doesn't have cn=replication_managers,{} entry \
|
||||
@@ -2035,7 +2035,7 @@ class ReplicationManager(object):
|
||||
services = ServiceAccounts(from_instance, self._suffix)
|
||||
# Generate the password and save the credentials
|
||||
# for putting them into agreements in the future
|
||||
- service_name = '{}:{}'.format(to_instance.host, port)
|
||||
+ service_name = f'{self._suffix}:{to_instance.host}:{port}'
|
||||
creds = password_generate()
|
||||
repl_service = services.ensure_state(properties={
|
||||
'cn': service_name,
|
||||
@@ -2299,7 +2299,7 @@ class ReplicationManager(object):
|
||||
Internal Only.
|
||||
"""
|
||||
|
||||
- rdn = '{}:{}'.format(from_instance.host, from_instance.sslport)
|
||||
+ rdn = f'{self._suffix}:{from_instance.host}:{from_instance.sslport}'
|
||||
try:
|
||||
creds = self._repl_creds[rdn]
|
||||
except KeyError:
|
||||
@@ -2499,8 +2499,8 @@ class ReplicationManager(object):
|
||||
# Touch something then wait_for_replication.
|
||||
from_groups = Groups(from_instance, basedn=self._suffix, rdn=None)
|
||||
to_groups = Groups(to_instance, basedn=self._suffix, rdn=None)
|
||||
- from_group = from_groups.get('replication_managers')
|
||||
- to_group = to_groups.get('replication_managers')
|
||||
+ from_group = from_groups.get(dn=f'cn=replication_managers,{self._suffix}')
|
||||
+ to_group = to_groups.get(dn=f'cn=replication_managers,{self._suffix}')
|
||||
|
||||
change = str(uuid.uuid4())
|
||||
|
||||
--
|
||||
2.49.0
|
||||
|
||||
@ -1,126 +0,0 @@
|
||||
From ebe986c78c6cd4e1f10172d8a8a11faf814fbc22 Mon Sep 17 00:00:00 2001
|
||||
From: Mark Reynolds <mreynolds@redhat.com>
|
||||
Date: Thu, 6 Mar 2025 16:49:53 -0500
|
||||
Subject: [PATCH] Issue 6655 - fix replication release replica decoding error
|
||||
|
||||
Description:
|
||||
|
||||
When a start replication session extended op is received acquire and
|
||||
release exclusive access before returning the result to the client.
|
||||
Otherwise there is a race condition where a "end" replication extended
|
||||
op can arrive before the replica is released and that leads to a
|
||||
decoding error on the other replica.
|
||||
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/6655
|
||||
|
||||
Reviewed by: spichugi, tbordaz, and vashirov(Thanks!!!)
|
||||
---
|
||||
.../suites/replication/acceptance_test.py | 12 ++++++++++
|
||||
ldap/servers/plugins/replication/repl_extop.c | 24 ++++++++++++-------
|
||||
2 files changed, 27 insertions(+), 9 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/replication/acceptance_test.py b/dirsrvtests/tests/suites/replication/acceptance_test.py
|
||||
index fc8622051..0f18edb44 100644
|
||||
--- a/dirsrvtests/tests/suites/replication/acceptance_test.py
|
||||
+++ b/dirsrvtests/tests/suites/replication/acceptance_test.py
|
||||
@@ -1,5 +1,9 @@
|
||||
# --- BEGIN COPYRIGHT BLOCK ---
|
||||
+<<<<<<< HEAD
|
||||
# Copyright (C) 2021 Red Hat, Inc.
|
||||
+=======
|
||||
+# Copyright (C) 2025 Red Hat, Inc.
|
||||
+>>>>>>> a623c3f90 (Issue 6655 - fix replication release replica decoding error)
|
||||
# All rights reserved.
|
||||
#
|
||||
# License: GPL (version 3 or any later version).
|
||||
@@ -453,6 +457,13 @@ def test_multi_subsuffix_replication(topo_m4):
|
||||
f"User {user_dn} on supplier {user_obj._instance.serverid} "
|
||||
f"still has 'Description {j}'"
|
||||
)
|
||||
+
|
||||
+ # Check there are no decoding errors
|
||||
+ assert not topo_m4.ms["supplier1"].ds_error_log.match('.*decoding failed.*')
|
||||
+ assert not topo_m4.ms["supplier2"].ds_error_log.match('.*decoding failed.*')
|
||||
+ assert not topo_m4.ms["supplier3"].ds_error_log.match('.*decoding failed.*')
|
||||
+ assert not topo_m4.ms["supplier4"].ds_error_log.match('.*decoding failed.*')
|
||||
+
|
||||
finally:
|
||||
for suffix, test_users in test_users_by_suffix.items():
|
||||
for user in test_users:
|
||||
@@ -507,6 +518,7 @@ def test_new_suffix(topo_m4, new_suffix):
|
||||
repl.remove_supplier(m1)
|
||||
repl.remove_supplier(m2)
|
||||
|
||||
+
|
||||
def test_many_attrs(topo_m4, create_entry):
|
||||
"""Check a replication with many attributes (add and delete)
|
||||
|
||||
diff --git a/ldap/servers/plugins/replication/repl_extop.c b/ldap/servers/plugins/replication/repl_extop.c
|
||||
index 14b756df1..dacc611c0 100644
|
||||
--- a/ldap/servers/plugins/replication/repl_extop.c
|
||||
+++ b/ldap/servers/plugins/replication/repl_extop.c
|
||||
@@ -1134,6 +1134,12 @@ send_response:
|
||||
slapi_pblock_set(pb, SLAPI_EXT_OP_RET_OID, REPL_NSDS50_REPLICATION_RESPONSE_OID);
|
||||
}
|
||||
|
||||
+ /* connext (release our hold on it at least) */
|
||||
+ if (NULL != connext) {
|
||||
+ /* don't free it, just let go of it */
|
||||
+ consumer_connection_extension_relinquish_exclusive_access(conn, connid, opid, PR_FALSE);
|
||||
+ }
|
||||
+
|
||||
slapi_pblock_set(pb, SLAPI_EXT_OP_RET_VALUE, resp_bval);
|
||||
slapi_log_err(SLAPI_LOG_REPL, repl_plugin_name,
|
||||
"multimaster_extop_StartNSDS50ReplicationRequest - "
|
||||
@@ -1251,12 +1257,6 @@ send_response:
|
||||
if (NULL != ruv_bervals) {
|
||||
ber_bvecfree(ruv_bervals);
|
||||
}
|
||||
- /* connext (our hold on it at least) */
|
||||
- if (NULL != connext) {
|
||||
- /* don't free it, just let go of it */
|
||||
- consumer_connection_extension_relinquish_exclusive_access(conn, connid, opid, PR_FALSE);
|
||||
- connext = NULL;
|
||||
- }
|
||||
|
||||
return return_value;
|
||||
}
|
||||
@@ -1389,6 +1389,13 @@ multimaster_extop_EndNSDS50ReplicationRequest(Slapi_PBlock *pb)
|
||||
}
|
||||
}
|
||||
send_response:
|
||||
+ /* connext (release our hold on it at least) */
|
||||
+ if (NULL != connext) {
|
||||
+ /* don't free it, just let go of it */
|
||||
+ consumer_connection_extension_relinquish_exclusive_access(conn, connid, opid, PR_FALSE);
|
||||
+ connext = NULL;
|
||||
+ }
|
||||
+
|
||||
/* Send the response code */
|
||||
if ((resp_bere = der_alloc()) == NULL) {
|
||||
goto free_and_return;
|
||||
@@ -1419,11 +1426,10 @@ free_and_return:
|
||||
if (NULL != resp_bval) {
|
||||
ber_bvfree(resp_bval);
|
||||
}
|
||||
- /* connext (our hold on it at least) */
|
||||
+ /* connext (release our hold on it if not already released) */
|
||||
if (NULL != connext) {
|
||||
/* don't free it, just let go of it */
|
||||
consumer_connection_extension_relinquish_exclusive_access(conn, connid, opid, PR_FALSE);
|
||||
- connext = NULL;
|
||||
}
|
||||
|
||||
return return_value;
|
||||
@@ -1516,7 +1522,7 @@ multimaster_extop_abort_cleanruv(Slapi_PBlock *pb)
|
||||
rid);
|
||||
}
|
||||
/*
|
||||
- * Get the replica
|
||||
+ * Get the replica
|
||||
*/
|
||||
if ((r = replica_get_replica_from_root(repl_root)) == NULL) {
|
||||
slapi_log_err(SLAPI_LOG_ERR, repl_plugin_name, "multimaster_extop_abort_cleanruv - "
|
||||
--
|
||||
2.49.0
|
||||
|
||||
@ -1,26 +0,0 @@
|
||||
From 5b12463bfeb518f016acb14bc118b5f8ad3eef5e Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Thu, 15 May 2025 09:22:22 +0200
|
||||
Subject: [PATCH] Issue 6655 - fix merge conflict
|
||||
|
||||
---
|
||||
dirsrvtests/tests/suites/replication/acceptance_test.py | 4 ----
|
||||
1 file changed, 4 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/replication/acceptance_test.py b/dirsrvtests/tests/suites/replication/acceptance_test.py
|
||||
index 0f18edb44..6b5186127 100644
|
||||
--- a/dirsrvtests/tests/suites/replication/acceptance_test.py
|
||||
+++ b/dirsrvtests/tests/suites/replication/acceptance_test.py
|
||||
@@ -1,9 +1,5 @@
|
||||
# --- BEGIN COPYRIGHT BLOCK ---
|
||||
-<<<<<<< HEAD
|
||||
-# Copyright (C) 2021 Red Hat, Inc.
|
||||
-=======
|
||||
# Copyright (C) 2025 Red Hat, Inc.
|
||||
->>>>>>> a623c3f90 (Issue 6655 - fix replication release replica decoding error)
|
||||
# All rights reserved.
|
||||
#
|
||||
# License: GPL (version 3 or any later version).
|
||||
--
|
||||
2.49.0
|
||||
|
||||
@ -1,291 +0,0 @@
|
||||
From 8d62124fb4d0700378b6f0669cc9d47338a8151c Mon Sep 17 00:00:00 2001
|
||||
From: tbordaz <tbordaz@redhat.com>
|
||||
Date: Tue, 25 Mar 2025 09:20:50 +0100
|
||||
Subject: [PATCH] Issue 6571 - Nested group does not receive memberOf attribute
|
||||
(#6679)
|
||||
|
||||
Bug description:
|
||||
There is a risk to create a loop in group membership.
|
||||
For example G2 is member of G1 and G1 is member of G2.
|
||||
Memberof plugins iterates from a node to its ancestors
|
||||
to update the 'memberof' values of the node.
|
||||
The plugin uses a valueset ('already_seen_ndn_vals')
|
||||
to keep the track of the nodes it already visited.
|
||||
It uses this valueset to detect a possible loop and
|
||||
in that case it does not add the ancestor as the
|
||||
memberof value of the node.
|
||||
This is an error in case there are multiples paths
|
||||
up to an ancestor.
|
||||
|
||||
Fix description:
|
||||
The ancestor should be added to the node systematically,
|
||||
just in case the ancestor is in 'already_seen_ndn_vals'
|
||||
it skips the final recursion
|
||||
|
||||
fixes: #6571
|
||||
|
||||
Reviewed by: Pierre Rogier, Mark Reynolds (Thanks !!!)
|
||||
---
|
||||
.../suites/memberof_plugin/regression_test.py | 109 ++++++++++++++++++
|
||||
.../tests/suites/plugins/memberof_test.py | 5 +
|
||||
ldap/servers/plugins/memberof/memberof.c | 52 ++++-----
|
||||
3 files changed, 137 insertions(+), 29 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/memberof_plugin/regression_test.py b/dirsrvtests/tests/suites/memberof_plugin/regression_test.py
|
||||
index 4c681a909..dba908975 100644
|
||||
--- a/dirsrvtests/tests/suites/memberof_plugin/regression_test.py
|
||||
+++ b/dirsrvtests/tests/suites/memberof_plugin/regression_test.py
|
||||
@@ -467,6 +467,21 @@ def _find_memberof_ext(server, user_dn=None, group_dn=None, find_result=True):
|
||||
else:
|
||||
assert (not found)
|
||||
|
||||
+def _check_membership(server, entry, expected_members, expected_memberof):
|
||||
+ assert server
|
||||
+ assert entry
|
||||
+
|
||||
+ memberof = entry.get_attr_vals('memberof')
|
||||
+ member = entry.get_attr_vals('member')
|
||||
+ assert len(member) == len(expected_members)
|
||||
+ assert len(memberof) == len(expected_memberof)
|
||||
+ for e in expected_members:
|
||||
+ server.log.info("Checking %s has member %s" % (entry.dn, e.dn))
|
||||
+ assert e.dn.encode() in member
|
||||
+ for e in expected_memberof:
|
||||
+ server.log.info("Checking %s is member of %s" % (entry.dn, e.dn))
|
||||
+ assert e.dn.encode() in memberof
|
||||
+
|
||||
|
||||
@pytest.mark.ds49161
|
||||
def test_memberof_group(topology_st):
|
||||
@@ -535,6 +550,100 @@ def test_memberof_group(topology_st):
|
||||
_find_memberof_ext(inst, dn1, g2n, True)
|
||||
_find_memberof_ext(inst, dn2, g2n, True)
|
||||
|
||||
+def test_multipaths(topology_st, request):
|
||||
+ """Test memberof succeeds to update memberof when
|
||||
+ there are multiple paths from a leaf to an intermediate node
|
||||
+
|
||||
+ :id: 35aa704a-b895-4153-9dcb-1e8a13612ebf
|
||||
+
|
||||
+ :setup: Single instance
|
||||
+
|
||||
+ :steps:
|
||||
+ 1. Create a graph G1->U1, G2->G21->U1
|
||||
+ 2. Add G2 as member of G1: G1->U1, G1->G2->G21->U1
|
||||
+ 3. Check members and memberof in entries G1,G2,G21,User1
|
||||
+
|
||||
+ :expectedresults:
|
||||
+ 1. Graph should be created
|
||||
+ 2. succeed
|
||||
+ 3. Membership is okay
|
||||
+ """
|
||||
+
|
||||
+ inst = topology_st.standalone
|
||||
+ memberof = MemberOfPlugin(inst)
|
||||
+ memberof.enable()
|
||||
+ memberof.replace('memberOfEntryScope', SUFFIX)
|
||||
+ if (memberof.get_memberofdeferredupdate() and memberof.get_memberofdeferredupdate().lower() == "on"):
|
||||
+ delay = 3
|
||||
+ else:
|
||||
+ delay = 0
|
||||
+ inst.restart()
|
||||
+
|
||||
+ #
|
||||
+ # Create the hierarchy
|
||||
+ #
|
||||
+ #
|
||||
+ # Grp1 ---------------> User1
|
||||
+ # ^
|
||||
+ # /
|
||||
+ # Grp2 ----> Grp21 ------/
|
||||
+ #
|
||||
+ users = UserAccounts(inst, SUFFIX, rdn=None)
|
||||
+ user1 = users.create(properties={'uid': "user1",
|
||||
+ 'cn': "user1",
|
||||
+ 'sn': 'SN',
|
||||
+ 'description': 'leaf',
|
||||
+ 'uidNumber': '1000',
|
||||
+ 'gidNumber': '2000',
|
||||
+ 'homeDirectory': '/home/user1'
|
||||
+ })
|
||||
+ group = Groups(inst, SUFFIX, rdn=None)
|
||||
+ g1 = group.create(properties={'cn': 'group1',
|
||||
+ 'member': user1.dn,
|
||||
+ 'description': 'group1'})
|
||||
+ g21 = group.create(properties={'cn': 'group21',
|
||||
+ 'member': user1.dn,
|
||||
+ 'description': 'group21'})
|
||||
+ g2 = group.create(properties={'cn': 'group2',
|
||||
+ 'member': [g21.dn],
|
||||
+ 'description': 'group2'})
|
||||
+
|
||||
+ # Enable debug logs if necessary
|
||||
+ #inst.config.replace('nsslapd-errorlog-level', '65536')
|
||||
+ #inst.config.set('nsslapd-accesslog-level','260')
|
||||
+ #inst.config.set('nsslapd-plugin-logging', 'on')
|
||||
+ #inst.config.set('nsslapd-auditlog-logging-enabled','on')
|
||||
+ #inst.config.set('nsslapd-auditfaillog-logging-enabled','on')
|
||||
+
|
||||
+ #
|
||||
+ # Update the hierarchy
|
||||
+ #
|
||||
+ #
|
||||
+ # Grp1 ----------------> User1
|
||||
+ # \ ^
|
||||
+ # \ /
|
||||
+ # --> Grp2 --> Grp21 --
|
||||
+ #
|
||||
+ g1.add_member(g2.dn)
|
||||
+ time.sleep(delay)
|
||||
+
|
||||
+ #
|
||||
+ # Check G1, G2, G21 and User1 members and memberof
|
||||
+ #
|
||||
+ _check_membership(inst, g1, expected_members=[g2, user1], expected_memberof=[])
|
||||
+ _check_membership(inst, g2, expected_members=[g21], expected_memberof=[g1])
|
||||
+ _check_membership(inst, g21, expected_members=[user1], expected_memberof=[g2, g1])
|
||||
+ _check_membership(inst, user1, expected_members=[], expected_memberof=[g21, g2, g1])
|
||||
+
|
||||
+ def fin():
|
||||
+ try:
|
||||
+ user1.delete()
|
||||
+ g1.delete()
|
||||
+ g2.delete()
|
||||
+ g21.delete()
|
||||
+ except:
|
||||
+ pass
|
||||
+ request.addfinalizer(fin)
|
||||
|
||||
def _config_memberof_entrycache_on_modrdn_failure(server):
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/plugins/memberof_test.py b/dirsrvtests/tests/suites/plugins/memberof_test.py
|
||||
index 2de1389fd..621c45daf 100644
|
||||
--- a/dirsrvtests/tests/suites/plugins/memberof_test.py
|
||||
+++ b/dirsrvtests/tests/suites/plugins/memberof_test.py
|
||||
@@ -2168,9 +2168,14 @@ def test_complex_group_scenario_6(topology_st):
|
||||
|
||||
# add Grp[1-4] (uniqueMember) to grp5
|
||||
# it creates a membership loop !!!
|
||||
+ topology_st.standalone.config.replace('nsslapd-errorlog-level', '65536')
|
||||
mods = [(ldap.MOD_ADD, 'uniqueMember', memofegrp020_5)]
|
||||
for grp in [memofegrp020_1, memofegrp020_2, memofegrp020_3, memofegrp020_4]:
|
||||
topology_st.standalone.modify_s(ensure_str(grp), mods)
|
||||
+ topology_st.standalone.config.replace('nsslapd-errorlog-level', '0')
|
||||
+
|
||||
+ results = topology_st.standalone.ds_error_log.match('.*detecting a loop in group.*')
|
||||
+ assert results
|
||||
|
||||
time.sleep(5)
|
||||
# assert user[1-4] are member of grp20_[1-4]
|
||||
diff --git a/ldap/servers/plugins/memberof/memberof.c b/ldap/servers/plugins/memberof/memberof.c
|
||||
index e75b99b14..32bdcf3f1 100644
|
||||
--- a/ldap/servers/plugins/memberof/memberof.c
|
||||
+++ b/ldap/servers/plugins/memberof/memberof.c
|
||||
@@ -1592,7 +1592,7 @@ memberof_call_foreach_dn(Slapi_PBlock *pb __attribute__((unused)), Slapi_DN *sdn
|
||||
ht_grp = ancestors_cache_lookup(config, (const void *)ndn);
|
||||
if (ht_grp) {
|
||||
#if MEMBEROF_CACHE_DEBUG
|
||||
- slapi_log_err(SLAPI_LOG_PLUGIN, MEMBEROF_PLUGIN_SUBSYSTEM, "memberof_call_foreach_dn: Ancestors of %s already cached (%x)\n", ndn, ht_grp);
|
||||
+ slapi_log_err(SLAPI_LOG_PLUGIN, MEMBEROF_PLUGIN_SUBSYSTEM, "memberof_call_foreach_dn: Ancestors of %s already cached (%lx)\n", ndn, (ulong) ht_grp);
|
||||
#endif
|
||||
add_ancestors_cbdata(ht_grp, callback_data);
|
||||
*cached = 1;
|
||||
@@ -1600,7 +1600,7 @@ memberof_call_foreach_dn(Slapi_PBlock *pb __attribute__((unused)), Slapi_DN *sdn
|
||||
}
|
||||
}
|
||||
#if MEMBEROF_CACHE_DEBUG
|
||||
- slapi_log_err(SLAPI_LOG_PLUGIN, MEMBEROF_PLUGIN_SUBSYSTEM, "memberof_call_foreach_dn: Ancestors of %s not cached\n", ndn);
|
||||
+ slapi_log_err(SLAPI_LOG_PLUGIN, MEMBEROF_PLUGIN_SUBSYSTEM, "memberof_call_foreach_dn: Ancestors of %s not cached\n", slapi_sdn_get_ndn(sdn));
|
||||
#endif
|
||||
|
||||
/* Escape the dn, and build the search filter. */
|
||||
@@ -3233,7 +3233,8 @@ cache_ancestors(MemberOfConfig *config, Slapi_Value **member_ndn_val, memberof_g
|
||||
return;
|
||||
}
|
||||
#if MEMBEROF_CACHE_DEBUG
|
||||
- if (double_check = ancestors_cache_lookup(config, (const void*) key)) {
|
||||
+ double_check = ancestors_cache_lookup(config, (const void*) key);
|
||||
+ if (double_check) {
|
||||
dump_cache_entry(double_check, "read back");
|
||||
}
|
||||
#endif
|
||||
@@ -3263,13 +3264,13 @@ merge_ancestors(Slapi_Value **member_ndn_val, memberof_get_groups_data *v1, memb
|
||||
sval_dn = slapi_value_new_string(slapi_value_get_string(sval));
|
||||
if (sval_dn) {
|
||||
/* Use the normalized dn from v1 to search it
|
||||
- * in v2
|
||||
- */
|
||||
+ * in v2
|
||||
+ */
|
||||
val_sdn = slapi_sdn_new_dn_byval(slapi_value_get_string(sval_dn));
|
||||
sval_ndn = slapi_value_new_string(slapi_sdn_get_ndn(val_sdn));
|
||||
if (!slapi_valueset_find(
|
||||
((memberof_get_groups_data *)v2)->config->group_slapiattrs[0], v2_group_norm_vals, sval_ndn)) {
|
||||
-/* This ancestor was not already present in v2 => Add it
|
||||
+ /* This ancestor was not already present in v2 => Add it
|
||||
* Using slapi_valueset_add_value it consumes val
|
||||
* so do not free sval
|
||||
*/
|
||||
@@ -3318,7 +3319,7 @@ memberof_get_groups_r(MemberOfConfig *config, Slapi_DN *member_sdn, memberof_get
|
||||
|
||||
merge_ancestors(&member_ndn_val, &member_data, data);
|
||||
if (!cached && member_data.use_cache)
|
||||
- cache_ancestors(config, &member_ndn_val, &member_data);
|
||||
+ cache_ancestors(config, &member_ndn_val, data);
|
||||
|
||||
slapi_value_free(&member_ndn_val);
|
||||
slapi_valueset_free(groupvals);
|
||||
@@ -3379,25 +3380,6 @@ memberof_get_groups_callback(Slapi_Entry *e, void *callback_data)
|
||||
goto bail;
|
||||
}
|
||||
|
||||
- /* Have we been here before? Note that we don't loop through all of the group_slapiattrs
|
||||
- * in config. We only need this attribute for it's syntax so the comparison can be
|
||||
- * performed. Since all of the grouping attributes are validated to use the Dinstinguished
|
||||
- * Name syntax, we can safely just use the first group_slapiattr. */
|
||||
- if (slapi_valueset_find(
|
||||
- ((memberof_get_groups_data *)callback_data)->config->group_slapiattrs[0], already_seen_ndn_vals, group_ndn_val)) {
|
||||
- /* we either hit a recursive grouping, or an entry is
|
||||
- * a member of a group through multiple paths. Either
|
||||
- * way, we can just skip processing this entry since we've
|
||||
- * already gone through this part of the grouping hierarchy. */
|
||||
- slapi_log_err(SLAPI_LOG_PLUGIN, MEMBEROF_PLUGIN_SUBSYSTEM,
|
||||
- "memberof_get_groups_callback - Possible group recursion"
|
||||
- " detected in %s\n",
|
||||
- group_ndn);
|
||||
- slapi_value_free(&group_ndn_val);
|
||||
- ((memberof_get_groups_data *)callback_data)->use_cache = PR_FALSE;
|
||||
- goto bail;
|
||||
- }
|
||||
-
|
||||
/* if the group does not belong to an excluded subtree, adds it to the valueset */
|
||||
if (memberof_entry_in_scope(config, group_sdn)) {
|
||||
/* Push group_dn_val into the valueset. This memory is now owned
|
||||
@@ -3407,9 +3389,21 @@ memberof_get_groups_callback(Slapi_Entry *e, void *callback_data)
|
||||
group_dn_val = slapi_value_new_string(group_dn);
|
||||
slapi_valueset_add_value_ext(groupvals, group_dn_val, SLAPI_VALUE_FLAG_PASSIN);
|
||||
|
||||
- /* push this ndn to detect group recursion */
|
||||
- already_seen_ndn_val = slapi_value_new_string(group_ndn);
|
||||
- slapi_valueset_add_value_ext(already_seen_ndn_vals, already_seen_ndn_val, SLAPI_VALUE_FLAG_PASSIN);
|
||||
+ if (slapi_valueset_find(
|
||||
+ ((memberof_get_groups_data *)callback_data)->config->group_slapiattrs[0], already_seen_ndn_vals, group_ndn_val)) {
|
||||
+ /* The group group_ndn_val has already been processed
|
||||
+ * skip the final recursion to prevent infinite loop
|
||||
+ */
|
||||
+ slapi_log_err(SLAPI_LOG_PLUGIN, MEMBEROF_PLUGIN_SUBSYSTEM,
|
||||
+ "memberof_get_groups_callback - detecting a loop in group %s (stop building memberof)\n",
|
||||
+ group_ndn);
|
||||
+ ((memberof_get_groups_data *)callback_data)->use_cache = PR_FALSE;
|
||||
+ goto bail;
|
||||
+ } else {
|
||||
+ /* keep this ndn to detect a possible group recursion */
|
||||
+ already_seen_ndn_val = slapi_value_new_string(group_ndn);
|
||||
+ slapi_valueset_add_value_ext(already_seen_ndn_vals, already_seen_ndn_val, SLAPI_VALUE_FLAG_PASSIN);
|
||||
+ }
|
||||
}
|
||||
if (!config->skip_nested || config->fixup_task) {
|
||||
/* now recurse to find ancestors groups of e */
|
||||
--
|
||||
2.49.0
|
||||
|
||||
@ -1,272 +0,0 @@
|
||||
From 17da0257b24749765777a4e64c3626cb39cca639 Mon Sep 17 00:00:00 2001
|
||||
From: tbordaz <tbordaz@redhat.com>
|
||||
Date: Mon, 31 Mar 2025 11:05:01 +0200
|
||||
Subject: [PATCH] Issue 6571 - (2nd) Nested group does not receive memberOf
|
||||
attribute (#6697)
|
||||
|
||||
Bug description:
|
||||
erroneous debug change made in previous fix
|
||||
where cache_ancestors is called with the wrong parameter
|
||||
|
||||
Fix description:
|
||||
Restore the orginal param 'member_data'
|
||||
Increase the set of tests around multipaths
|
||||
|
||||
fixes: #6571
|
||||
|
||||
review by: Simon Pichugin (Thanks !!)
|
||||
---
|
||||
.../suites/memberof_plugin/regression_test.py | 154 ++++++++++++++++++
|
||||
ldap/servers/plugins/memberof/memberof.c | 50 +++++-
|
||||
2 files changed, 203 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/memberof_plugin/regression_test.py b/dirsrvtests/tests/suites/memberof_plugin/regression_test.py
|
||||
index dba908975..9ba40a0c3 100644
|
||||
--- a/dirsrvtests/tests/suites/memberof_plugin/regression_test.py
|
||||
+++ b/dirsrvtests/tests/suites/memberof_plugin/regression_test.py
|
||||
@@ -598,6 +598,8 @@ def test_multipaths(topology_st, request):
|
||||
'homeDirectory': '/home/user1'
|
||||
})
|
||||
group = Groups(inst, SUFFIX, rdn=None)
|
||||
+ g0 = group.create(properties={'cn': 'group0',
|
||||
+ 'description': 'group0'})
|
||||
g1 = group.create(properties={'cn': 'group1',
|
||||
'member': user1.dn,
|
||||
'description': 'group1'})
|
||||
@@ -635,6 +637,158 @@ def test_multipaths(topology_st, request):
|
||||
_check_membership(inst, g21, expected_members=[user1], expected_memberof=[g2, g1])
|
||||
_check_membership(inst, user1, expected_members=[], expected_memberof=[g21, g2, g1])
|
||||
|
||||
+ #inst.config.replace('nsslapd-errorlog-level', '65536')
|
||||
+ #inst.config.set('nsslapd-accesslog-level','260')
|
||||
+ #inst.config.set('nsslapd-plugin-logging', 'on')
|
||||
+ #inst.config.set('nsslapd-auditlog-logging-enabled','on')
|
||||
+ #inst.config.set('nsslapd-auditfaillog-logging-enabled','on')
|
||||
+ #
|
||||
+ # Update the hierarchy
|
||||
+ #
|
||||
+ #
|
||||
+ # Grp1 ----------------> User1
|
||||
+ # ^
|
||||
+ # /
|
||||
+ # Grp2 --> Grp21 --
|
||||
+ #
|
||||
+ g1.remove_member(g2.dn)
|
||||
+ time.sleep(delay)
|
||||
+
|
||||
+ #
|
||||
+ # Check G1, G2, G21 and User1 members and memberof
|
||||
+ #
|
||||
+ _check_membership(inst, g1, expected_members=[user1], expected_memberof=[])
|
||||
+ _check_membership(inst, g2, expected_members=[g21], expected_memberof=[])
|
||||
+ _check_membership(inst, g21, expected_members=[user1], expected_memberof=[g2])
|
||||
+ _check_membership(inst, user1, expected_members=[], expected_memberof=[g21, g2, g1])
|
||||
+
|
||||
+ #
|
||||
+ # Update the hierarchy
|
||||
+ #
|
||||
+ #
|
||||
+ # Grp1 ----------------> User1
|
||||
+ # \__________ ^
|
||||
+ # | /
|
||||
+ # v /
|
||||
+ # Grp2 --> Grp21 ----
|
||||
+ #
|
||||
+ g1.add_member(g21.dn)
|
||||
+ time.sleep(delay)
|
||||
+
|
||||
+ #
|
||||
+ # Check G1, G2, G21 and User1 members and memberof
|
||||
+ #
|
||||
+ _check_membership(inst, g1, expected_members=[user1, g21], expected_memberof=[])
|
||||
+ _check_membership(inst, g2, expected_members=[g21], expected_memberof=[])
|
||||
+ _check_membership(inst, g21, expected_members=[user1], expected_memberof=[g2, g1])
|
||||
+ _check_membership(inst, user1, expected_members=[], expected_memberof=[g21, g2, g1])
|
||||
+
|
||||
+ #
|
||||
+ # Update the hierarchy
|
||||
+ #
|
||||
+ #
|
||||
+ # Grp1 ----------------> User1
|
||||
+ # ^
|
||||
+ # /
|
||||
+ # Grp2 --> Grp21 --
|
||||
+ #
|
||||
+ g1.remove_member(g21.dn)
|
||||
+ time.sleep(delay)
|
||||
+
|
||||
+ #
|
||||
+ # Check G1, G2, G21 and User1 members and memberof
|
||||
+ #
|
||||
+ _check_membership(inst, g1, expected_members=[user1], expected_memberof=[])
|
||||
+ _check_membership(inst, g2, expected_members=[g21], expected_memberof=[])
|
||||
+ _check_membership(inst, g21, expected_members=[user1], expected_memberof=[g2])
|
||||
+ _check_membership(inst, user1, expected_members=[], expected_memberof=[g21, g2, g1])
|
||||
+
|
||||
+ #
|
||||
+ # Update the hierarchy
|
||||
+ #
|
||||
+ #
|
||||
+ # Grp1 ----------------> User1
|
||||
+ # ^
|
||||
+ # /
|
||||
+ # Grp0 ---> Grp2 ---> Grp21 ---
|
||||
+ #
|
||||
+ g0.add_member(g2.dn)
|
||||
+ time.sleep(delay)
|
||||
+
|
||||
+ #
|
||||
+ # Check G0,G1, G2, G21 and User1 members and memberof
|
||||
+ #
|
||||
+ _check_membership(inst, g0, expected_members=[g2], expected_memberof=[])
|
||||
+ _check_membership(inst, g1, expected_members=[user1], expected_memberof=[])
|
||||
+ _check_membership(inst, g2, expected_members=[g21], expected_memberof=[g0])
|
||||
+ _check_membership(inst, g21, expected_members=[user1], expected_memberof=[g0, g2])
|
||||
+ _check_membership(inst, user1, expected_members=[], expected_memberof=[g21, g2, g1, g0])
|
||||
+
|
||||
+ #
|
||||
+ # Update the hierarchy
|
||||
+ #
|
||||
+ #
|
||||
+ # Grp1 ----------------> User1
|
||||
+ # ^ ^
|
||||
+ # / /
|
||||
+ # Grp0 ---> Grp2 ---> Grp21 ---
|
||||
+ #
|
||||
+ g0.add_member(g1.dn)
|
||||
+ time.sleep(delay)
|
||||
+
|
||||
+ #
|
||||
+ # Check G0,G1, G2, G21 and User1 members and memberof
|
||||
+ #
|
||||
+ _check_membership(inst, g0, expected_members=[g1,g2], expected_memberof=[])
|
||||
+ _check_membership(inst, g1, expected_members=[user1], expected_memberof=[g0])
|
||||
+ _check_membership(inst, g2, expected_members=[g21], expected_memberof=[g0])
|
||||
+ _check_membership(inst, g21, expected_members=[user1], expected_memberof=[g0, g2])
|
||||
+ _check_membership(inst, user1, expected_members=[], expected_memberof=[g21, g2, g1, g0])
|
||||
+
|
||||
+ #
|
||||
+ # Update the hierarchy
|
||||
+ #
|
||||
+ #
|
||||
+ # Grp1 ----------------> User1
|
||||
+ # ^ \_____________ ^
|
||||
+ # / | /
|
||||
+ # / V /
|
||||
+ # Grp0 ---> Grp2 ---> Grp21 ---
|
||||
+ #
|
||||
+ g1.add_member(g21.dn)
|
||||
+ time.sleep(delay)
|
||||
+
|
||||
+ #
|
||||
+ # Check G0,G1, G2, G21 and User1 members and memberof
|
||||
+ #
|
||||
+ _check_membership(inst, g0, expected_members=[g1, g2], expected_memberof=[])
|
||||
+ _check_membership(inst, g1, expected_members=[user1, g21], expected_memberof=[g0])
|
||||
+ _check_membership(inst, g2, expected_members=[g21], expected_memberof=[g0])
|
||||
+ _check_membership(inst, g21, expected_members=[user1], expected_memberof=[g0, g1, g2])
|
||||
+ _check_membership(inst, user1, expected_members=[], expected_memberof=[g21, g2, g1, g0])
|
||||
+
|
||||
+ #
|
||||
+ # Update the hierarchy
|
||||
+ #
|
||||
+ #
|
||||
+ # Grp1 ----------------> User1
|
||||
+ # ^ \_____________ ^
|
||||
+ # / | /
|
||||
+ # / V /
|
||||
+ # Grp0 ---> Grp2 Grp21 ---
|
||||
+ #
|
||||
+ g2.remove_member(g21.dn)
|
||||
+ time.sleep(delay)
|
||||
+
|
||||
+ #
|
||||
+ # Check G0,G1, G2, G21 and User1 members and memberof
|
||||
+ #
|
||||
+ _check_membership(inst, g0, expected_members=[g1, g2], expected_memberof=[])
|
||||
+ _check_membership(inst, g1, expected_members=[user1, g21], expected_memberof=[g0])
|
||||
+ _check_membership(inst, g2, expected_members=[], expected_memberof=[g0])
|
||||
+ _check_membership(inst, g21, expected_members=[user1], expected_memberof=[g0, g1])
|
||||
+ _check_membership(inst, user1, expected_members=[], expected_memberof=[g21, g1, g0])
|
||||
+
|
||||
def fin():
|
||||
try:
|
||||
user1.delete()
|
||||
diff --git a/ldap/servers/plugins/memberof/memberof.c b/ldap/servers/plugins/memberof/memberof.c
|
||||
index 32bdcf3f1..f79b083a9 100644
|
||||
--- a/ldap/servers/plugins/memberof/memberof.c
|
||||
+++ b/ldap/servers/plugins/memberof/memberof.c
|
||||
@@ -3258,6 +3258,35 @@ merge_ancestors(Slapi_Value **member_ndn_val, memberof_get_groups_data *v1, memb
|
||||
Slapi_ValueSet *v2_group_norm_vals = *((memberof_get_groups_data *)v2)->group_norm_vals;
|
||||
int merged_cnt = 0;
|
||||
|
||||
+#if MEMBEROF_CACHE_DEBUG
|
||||
+ {
|
||||
+ Slapi_Value *val = 0;
|
||||
+ int hint = 0;
|
||||
+ struct berval *bv;
|
||||
+ hint = slapi_valueset_first_value(v2_groupvals, &val);
|
||||
+ while (val) {
|
||||
+ /* this makes a copy of the berval */
|
||||
+ bv = slapi_value_get_berval(val);
|
||||
+ if (bv && bv->bv_len) {
|
||||
+ slapi_log_err(SLAPI_LOG_PLUGIN, MEMBEROF_PLUGIN_SUBSYSTEM,
|
||||
+ "merge_ancestors: V2 contains %s\n",
|
||||
+ bv->bv_val);
|
||||
+ }
|
||||
+ hint = slapi_valueset_next_value(v2_groupvals, hint, &val);
|
||||
+ }
|
||||
+ hint = slapi_valueset_first_value(v1_groupvals, &val);
|
||||
+ while (val) {
|
||||
+ /* this makes a copy of the berval */
|
||||
+ bv = slapi_value_get_berval(val);
|
||||
+ if (bv && bv->bv_len) {
|
||||
+ slapi_log_err(SLAPI_LOG_PLUGIN, MEMBEROF_PLUGIN_SUBSYSTEM,
|
||||
+ "merge_ancestors: add %s (from V1)\n",
|
||||
+ bv->bv_val);
|
||||
+ }
|
||||
+ hint = slapi_valueset_next_value(v1_groupvals, hint, &val);
|
||||
+ }
|
||||
+ }
|
||||
+#endif
|
||||
hint = slapi_valueset_first_value(v1_groupvals, &sval);
|
||||
while (sval) {
|
||||
if (memberof_compare(config, member_ndn_val, &sval)) {
|
||||
@@ -3319,7 +3348,7 @@ memberof_get_groups_r(MemberOfConfig *config, Slapi_DN *member_sdn, memberof_get
|
||||
|
||||
merge_ancestors(&member_ndn_val, &member_data, data);
|
||||
if (!cached && member_data.use_cache)
|
||||
- cache_ancestors(config, &member_ndn_val, data);
|
||||
+ cache_ancestors(config, &member_ndn_val, &member_data);
|
||||
|
||||
slapi_value_free(&member_ndn_val);
|
||||
slapi_valueset_free(groupvals);
|
||||
@@ -4285,6 +4314,25 @@ memberof_fix_memberof_callback(Slapi_Entry *e, void *callback_data)
|
||||
|
||||
/* get a list of all of the groups this user belongs to */
|
||||
groups = memberof_get_groups(config, sdn);
|
||||
+#if MEMBEROF_CACHE_DEBUG
|
||||
+ {
|
||||
+ Slapi_Value *val = 0;
|
||||
+ int hint = 0;
|
||||
+ struct berval *bv;
|
||||
+ hint = slapi_valueset_first_value(groups, &val);
|
||||
+ while (val) {
|
||||
+ /* this makes a copy of the berval */
|
||||
+ bv = slapi_value_get_berval(val);
|
||||
+ if (bv && bv->bv_len) {
|
||||
+ slapi_log_err(SLAPI_LOG_PLUGIN, MEMBEROF_PLUGIN_SUBSYSTEM,
|
||||
+ "memberof_fix_memberof_callback: %s belongs to %s\n",
|
||||
+ ndn,
|
||||
+ bv->bv_val);
|
||||
+ }
|
||||
+ hint = slapi_valueset_next_value(groups, hint, &val);
|
||||
+ }
|
||||
+ }
|
||||
+#endif
|
||||
|
||||
if (config->group_filter) {
|
||||
if (slapi_filter_test_simple(e, config->group_filter)) {
|
||||
--
|
||||
2.49.0
|
||||
|
||||
@ -1,192 +0,0 @@
|
||||
From ff364a4b1c88e1a8f678e056af88cce50cd8717c Mon Sep 17 00:00:00 2001
|
||||
From: progier389 <progier@redhat.com>
|
||||
Date: Fri, 28 Mar 2025 17:32:14 +0100
|
||||
Subject: [PATCH] Issue 6698 - NPE after configuring invalid filtered role
|
||||
(#6699)
|
||||
|
||||
Server crash when doing search after configuring filtered role with invalid filter.
|
||||
Reason: The part of the filter that should be overwritten are freed before knowing that the filter is invalid.
|
||||
Solution: Check first that the filter is valid before freeing the filtere bits
|
||||
|
||||
Issue: #6698
|
||||
|
||||
Reviewed by: @tbordaz , @mreynolds389 (Thanks!)
|
||||
|
||||
(cherry picked from commit 31e120d2349eda7a41380cf78fc04cf41e394359)
|
||||
---
|
||||
dirsrvtests/tests/suites/roles/basic_test.py | 80 ++++++++++++++++++--
|
||||
ldap/servers/slapd/filter.c | 17 ++++-
|
||||
2 files changed, 88 insertions(+), 9 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/roles/basic_test.py b/dirsrvtests/tests/suites/roles/basic_test.py
|
||||
index 875ac47c1..b79816c58 100644
|
||||
--- a/dirsrvtests/tests/suites/roles/basic_test.py
|
||||
+++ b/dirsrvtests/tests/suites/roles/basic_test.py
|
||||
@@ -28,6 +28,7 @@ from lib389.dbgen import dbgen_users
|
||||
from lib389.tasks import ImportTask
|
||||
from lib389.utils import get_default_db_lib
|
||||
from lib389.rewriters import *
|
||||
+from lib389._mapped_object import DSLdapObject
|
||||
from lib389.backend import Backends
|
||||
|
||||
logging.getLogger(__name__).setLevel(logging.INFO)
|
||||
@@ -427,7 +428,6 @@ def test_vattr_on_filtered_role_restart(topo, request):
|
||||
log.info("Check the default value of attribute nsslapd-ignore-virtual-attrs should be OFF")
|
||||
assert topo.standalone.config.present('nsslapd-ignore-virtual-attrs', 'off')
|
||||
|
||||
-
|
||||
log.info("Check the virtual attribute definition is found (after a required delay)")
|
||||
topo.standalone.restart()
|
||||
time.sleep(5)
|
||||
@@ -541,7 +541,7 @@ def test_managed_and_filtered_role_rewrite(topo, request):
|
||||
indexes = backend.get_indexes()
|
||||
try:
|
||||
index = indexes.create(properties={
|
||||
- 'cn': attrname,
|
||||
+ 'cn': attrname,
|
||||
'nsSystemIndex': 'false',
|
||||
'nsIndexType': ['eq', 'pres']
|
||||
})
|
||||
@@ -593,7 +593,6 @@ def test_managed_and_filtered_role_rewrite(topo, request):
|
||||
dn = "uid=%s0000%d,%s" % (RDN, i, PARENT)
|
||||
topo.standalone.modify_s(dn, [(ldap.MOD_REPLACE, 'nsRoleDN', [role.dn.encode()])])
|
||||
|
||||
-
|
||||
# Now check that search is fast, evaluating only 4 entries
|
||||
search_start = time.time()
|
||||
entries = topo.standalone.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, "(nsrole=%s)" % role.dn)
|
||||
@@ -676,7 +675,7 @@ def test_not_such_entry_role_rewrite(topo, request):
|
||||
indexes = backend.get_indexes()
|
||||
try:
|
||||
index = indexes.create(properties={
|
||||
- 'cn': attrname,
|
||||
+ 'cn': attrname,
|
||||
'nsSystemIndex': 'false',
|
||||
'nsIndexType': ['eq', 'pres']
|
||||
})
|
||||
@@ -730,7 +729,7 @@ def test_not_such_entry_role_rewrite(topo, request):
|
||||
|
||||
# Enable plugin level to check message
|
||||
topo.standalone.config.loglevel(vals=(ErrorLog.DEFAULT,ErrorLog.PLUGIN))
|
||||
-
|
||||
+
|
||||
# Now check that search is fast, evaluating only 4 entries
|
||||
search_start = time.time()
|
||||
entries = topo.standalone.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, "(|(nsrole=%s)(nsrole=cn=not_such_entry_role,%s))" % (role.dn, DEFAULT_SUFFIX))
|
||||
@@ -758,6 +757,77 @@ def test_not_such_entry_role_rewrite(topo, request):
|
||||
|
||||
request.addfinalizer(fin)
|
||||
|
||||
+
|
||||
+def test_rewriter_with_invalid_filter(topo, request):
|
||||
+ """Test that server does not crash when having
|
||||
+ invalid filter in filtered role
|
||||
+
|
||||
+ :id: 5013b0b2-0af6-11f0-8684-482ae39447e5
|
||||
+ :setup: standalone server
|
||||
+ :steps:
|
||||
+ 1. Setup filtered role with good filter
|
||||
+ 2. Setup nsrole rewriter
|
||||
+ 3. Restart the server
|
||||
+ 4. Search for entries
|
||||
+ 5. Setup filtered role with bad filter
|
||||
+ 6. Search for entries
|
||||
+ :expectedresults:
|
||||
+ 1. Operation should succeed
|
||||
+ 2. Operation should succeed
|
||||
+ 3. Operation should succeed
|
||||
+ 4. Operation should succeed
|
||||
+ 5. Operation should succeed
|
||||
+ 6. Operation should succeed
|
||||
+ """
|
||||
+ inst = topo.standalone
|
||||
+ entries = []
|
||||
+
|
||||
+ def fin():
|
||||
+ inst.start()
|
||||
+ for entry in entries:
|
||||
+ entry.delete()
|
||||
+ request.addfinalizer(fin)
|
||||
+
|
||||
+ # Setup filtered role
|
||||
+ roles = FilteredRoles(inst, f'ou=people,{DEFAULT_SUFFIX}')
|
||||
+ filter_ko = '(&((objectClass=top)(objectClass=nsPerson))'
|
||||
+ filter_ok = '(&(objectClass=top)(objectClass=nsPerson))'
|
||||
+ role_properties = {
|
||||
+ 'cn': 'TestFilteredRole',
|
||||
+ 'nsRoleFilter': filter_ok,
|
||||
+ 'description': 'Test good filter',
|
||||
+ }
|
||||
+ role = roles.create(properties=role_properties)
|
||||
+ entries.append(role)
|
||||
+
|
||||
+ # Setup nsrole rewriter
|
||||
+ rewriters = Rewriters(inst)
|
||||
+ rewriter_properties = {
|
||||
+ "cn": "nsrole",
|
||||
+ "nsslapd-libpath": 'libroles-plugin',
|
||||
+ "nsslapd-filterrewriter": 'role_nsRole_filter_rewriter',
|
||||
+ }
|
||||
+ rewriter = rewriters.ensure_state(properties=rewriter_properties)
|
||||
+ entries.append(rewriter)
|
||||
+
|
||||
+ # Restart thge instance
|
||||
+ inst.restart()
|
||||
+
|
||||
+ # Search for entries
|
||||
+ entries = inst.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, "(nsrole=%s)" % role.dn)
|
||||
+
|
||||
+ # Set bad filter
|
||||
+ role_properties = {
|
||||
+ 'cn': 'TestFilteredRole',
|
||||
+ 'nsRoleFilter': filter_ko,
|
||||
+ 'description': 'Test bad filter',
|
||||
+ }
|
||||
+ role.ensure_state(properties=role_properties)
|
||||
+
|
||||
+ # Search for entries
|
||||
+ entries = inst.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, "(nsrole=%s)" % role.dn)
|
||||
+
|
||||
+
|
||||
if __name__ == "__main__":
|
||||
CURRENT_FILE = os.path.realpath(__file__)
|
||||
pytest.main("-s -v %s" % CURRENT_FILE)
|
||||
diff --git a/ldap/servers/slapd/filter.c b/ldap/servers/slapd/filter.c
|
||||
index ce09891b8..f541b8fc1 100644
|
||||
--- a/ldap/servers/slapd/filter.c
|
||||
+++ b/ldap/servers/slapd/filter.c
|
||||
@@ -1038,9 +1038,11 @@ slapi_filter_get_subfilt(
|
||||
}
|
||||
|
||||
/*
|
||||
- * Before calling this function, you must free all the parts
|
||||
+ * The function does not know how to free all the parts
|
||||
* which will be overwritten (i.e. slapi_free_the_filter_bits),
|
||||
- * this function dosn't know how to do that
|
||||
+ * so the caller must take care of that.
|
||||
+ * But it must do so AFTER calling slapi_filter_replace_ex to
|
||||
+ * avoid getting invalid filter if slapi_filter_replace_ex fails.
|
||||
*/
|
||||
int
|
||||
slapi_filter_replace_ex(Slapi_Filter *f, char *s)
|
||||
@@ -1099,8 +1101,15 @@ slapi_filter_free_bits(Slapi_Filter *f)
|
||||
int
|
||||
slapi_filter_replace_strfilter(Slapi_Filter *f, char *strfilter)
|
||||
{
|
||||
- slapi_filter_free_bits(f);
|
||||
- return (slapi_filter_replace_ex(f, strfilter));
|
||||
+ /* slapi_filter_replace_ex may fail and we cannot
|
||||
+ * free filter bits before calling it.
|
||||
+ */
|
||||
+ Slapi_Filter save_f = *f;
|
||||
+ int ret = slapi_filter_replace_ex(f, strfilter);
|
||||
+ if (ret == 0) {
|
||||
+ slapi_filter_free_bits(&save_f);
|
||||
+ }
|
||||
+ return ret;
|
||||
}
|
||||
|
||||
static void
|
||||
--
|
||||
2.49.0
|
||||
|
||||
@ -1,455 +0,0 @@
|
||||
From 446a23d0ed2d3ffa76c5fb5e9576d6876bdbf04f Mon Sep 17 00:00:00 2001
|
||||
From: Simon Pichugin <spichugi@redhat.com>
|
||||
Date: Fri, 28 Mar 2025 11:28:54 -0700
|
||||
Subject: [PATCH] Issue 6686 - CLI - Re-enabling user accounts that reached
|
||||
inactivity limit fails with error (#6687)
|
||||
|
||||
Description: When attempting to unlock a user account that has been locked due
|
||||
to exceeding the Account Policy Plugin's inactivity limit, the dsidm account
|
||||
unlock command fails with a Python type error: "float() argument must be a
|
||||
string or a number, not 'NoneType'".
|
||||
|
||||
Enhance the unlock method to properly handle different account locking states,
|
||||
including inactivity limit exceeded states.
|
||||
Add test cases to verify account inactivity locking/unlocking functionality
|
||||
with CoS and role-based indirect locking.
|
||||
|
||||
Fix CoS template class to include the required 'ldapsubentry' objectClass.
|
||||
Improv error messages to provide better guidance on unlocking indirectly
|
||||
locked accounts.
|
||||
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/6686
|
||||
|
||||
Reviewed by: @mreynolds389 (Thanks!)
|
||||
---
|
||||
.../clu/dsidm_account_inactivity_test.py | 329 ++++++++++++++++++
|
||||
src/lib389/lib389/cli_idm/account.py | 25 +-
|
||||
src/lib389/lib389/idm/account.py | 28 +-
|
||||
3 files changed, 377 insertions(+), 5 deletions(-)
|
||||
create mode 100644 dirsrvtests/tests/suites/clu/dsidm_account_inactivity_test.py
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/clu/dsidm_account_inactivity_test.py b/dirsrvtests/tests/suites/clu/dsidm_account_inactivity_test.py
|
||||
new file mode 100644
|
||||
index 000000000..88a34abf6
|
||||
--- /dev/null
|
||||
+++ b/dirsrvtests/tests/suites/clu/dsidm_account_inactivity_test.py
|
||||
@@ -0,0 +1,329 @@
|
||||
+# --- BEGIN COPYRIGHT BLOCK ---
|
||||
+# Copyright (C) 2025 Red Hat, Inc.
|
||||
+# All rights reserved.
|
||||
+#
|
||||
+# License: GPL (version 3 or any later version).
|
||||
+# See LICENSE for details.
|
||||
+# --- END COPYRIGHT BLOCK ---
|
||||
+#
|
||||
+import ldap
|
||||
+import time
|
||||
+import pytest
|
||||
+import logging
|
||||
+import os
|
||||
+from datetime import datetime, timedelta
|
||||
+
|
||||
+from lib389 import DEFAULT_SUFFIX, DN_PLUGIN, DN_CONFIG
|
||||
+from lib389.cli_idm.account import entry_status, unlock
|
||||
+from lib389.topologies import topology_st
|
||||
+from lib389.cli_base import FakeArgs
|
||||
+from lib389.utils import ds_is_older
|
||||
+from lib389.plugins import AccountPolicyPlugin, AccountPolicyConfigs
|
||||
+from lib389.idm.role import FilteredRoles
|
||||
+from lib389.idm.user import UserAccounts
|
||||
+from lib389.cos import CosTemplate, CosPointerDefinition
|
||||
+from lib389.idm.domain import Domain
|
||||
+from . import check_value_in_log_and_reset
|
||||
+
|
||||
+pytestmark = pytest.mark.tier0
|
||||
+
|
||||
+logging.getLogger(__name__).setLevel(logging.DEBUG)
|
||||
+log = logging.getLogger(__name__)
|
||||
+
|
||||
+# Constants
|
||||
+PLUGIN_ACCT_POLICY = "Account Policy Plugin"
|
||||
+ACCP_DN = f"cn={PLUGIN_ACCT_POLICY},{DN_PLUGIN}"
|
||||
+ACCP_CONF = f"{DN_CONFIG},{ACCP_DN}"
|
||||
+POLICY_NAME = "Account Inactivity Policy"
|
||||
+POLICY_DN = f"cn={POLICY_NAME},{DEFAULT_SUFFIX}"
|
||||
+COS_TEMPLATE_NAME = "TemplateCoS"
|
||||
+COS_TEMPLATE_DN = f"cn={COS_TEMPLATE_NAME},{DEFAULT_SUFFIX}"
|
||||
+COS_DEFINITION_NAME = "DefinitionCoS"
|
||||
+COS_DEFINITION_DN = f"cn={COS_DEFINITION_NAME},{DEFAULT_SUFFIX}"
|
||||
+TEST_USER_NAME = "test_inactive_user"
|
||||
+TEST_USER_DN = f"uid={TEST_USER_NAME},{DEFAULT_SUFFIX}"
|
||||
+TEST_USER_PW = "password"
|
||||
+INACTIVITY_LIMIT = 30
|
||||
+
|
||||
+
|
||||
+@pytest.fixture(scope="function")
|
||||
+def account_policy_setup(topology_st, request):
|
||||
+ """Set up account policy plugin, configuration, and CoS objects"""
|
||||
+ log.info("Setting up Account Policy Plugin and CoS")
|
||||
+
|
||||
+ # Enable Account Policy Plugin
|
||||
+ plugin = AccountPolicyPlugin(topology_st.standalone)
|
||||
+ if not plugin.status():
|
||||
+ plugin.enable()
|
||||
+ plugin.set('nsslapd-pluginarg0', ACCP_CONF)
|
||||
+
|
||||
+ # Configure Account Policy
|
||||
+ accp_configs = AccountPolicyConfigs(topology_st.standalone)
|
||||
+ accp_config = accp_configs.ensure_state(
|
||||
+ properties={
|
||||
+ 'cn': 'config',
|
||||
+ 'alwaysrecordlogin': 'yes',
|
||||
+ 'stateattrname': 'lastLoginTime',
|
||||
+ 'altstateattrname': '1.1',
|
||||
+ 'specattrname': 'acctPolicySubentry',
|
||||
+ 'limitattrname': 'accountInactivityLimit'
|
||||
+ }
|
||||
+ )
|
||||
+
|
||||
+ # Add ACI for anonymous access if it doesn't exist
|
||||
+ domain = Domain(topology_st.standalone, DEFAULT_SUFFIX)
|
||||
+ anon_aci = '(targetattr="*")(version 3.0; acl "Anonymous read access"; allow (read,search,compare) userdn="ldap:///anyone";)'
|
||||
+ domain.ensure_present('aci', anon_aci)
|
||||
+
|
||||
+ # Restart the server to apply plugin configuration
|
||||
+ topology_st.standalone.restart()
|
||||
+
|
||||
+ # Create or update account policy entry
|
||||
+ accp_configs = AccountPolicyConfigs(topology_st.standalone, basedn=DEFAULT_SUFFIX)
|
||||
+ policy = accp_configs.ensure_state(
|
||||
+ properties={
|
||||
+ 'cn': POLICY_NAME,
|
||||
+ 'objectClass': ['top', 'ldapsubentry', 'extensibleObject', 'accountpolicy'],
|
||||
+ 'accountInactivityLimit': str(INACTIVITY_LIMIT)
|
||||
+ }
|
||||
+ )
|
||||
+
|
||||
+ # Create or update CoS template entry
|
||||
+ cos_template = CosTemplate(topology_st.standalone, dn=COS_TEMPLATE_DN)
|
||||
+ cos_template.ensure_state(
|
||||
+ properties={
|
||||
+ 'cn': COS_TEMPLATE_NAME,
|
||||
+ 'objectClass': ['top', 'cosTemplate', 'extensibleObject'],
|
||||
+ 'acctPolicySubentry': policy.dn
|
||||
+ }
|
||||
+ )
|
||||
+
|
||||
+ # Create or update CoS definition entry
|
||||
+ cos_def = CosPointerDefinition(topology_st.standalone, dn=COS_DEFINITION_DN)
|
||||
+ cos_def.ensure_state(
|
||||
+ properties={
|
||||
+ 'cn': COS_DEFINITION_NAME,
|
||||
+ 'objectClass': ['top', 'ldapsubentry', 'cosSuperDefinition', 'cosPointerDefinition'],
|
||||
+ 'cosTemplateDn': COS_TEMPLATE_DN,
|
||||
+ 'cosAttribute': 'acctPolicySubentry default operational-default'
|
||||
+ }
|
||||
+ )
|
||||
+
|
||||
+ # Restart server to ensure CoS is applied
|
||||
+ topology_st.standalone.restart()
|
||||
+
|
||||
+ def fin():
|
||||
+ log.info('Cleaning up Account Policy settings')
|
||||
+ try:
|
||||
+ # Delete CoS and policy entries
|
||||
+ if cos_def.exists():
|
||||
+ cos_def.delete()
|
||||
+ if cos_template.exists():
|
||||
+ cos_template.delete()
|
||||
+ if policy.exists():
|
||||
+ policy.delete()
|
||||
+
|
||||
+ # Disable the plugin
|
||||
+ if plugin.status():
|
||||
+ plugin.disable()
|
||||
+ topology_st.standalone.restart()
|
||||
+ except Exception as e:
|
||||
+ log.error(f'Failed to clean up: {e}')
|
||||
+
|
||||
+ request.addfinalizer(fin)
|
||||
+
|
||||
+ return topology_st.standalone
|
||||
+
|
||||
+
|
||||
+@pytest.fixture(scope="function")
|
||||
+def create_test_user(topology_st, account_policy_setup, request):
|
||||
+ """Create a test user for the inactivity test"""
|
||||
+ log.info('Creating test user')
|
||||
+
|
||||
+ users = UserAccounts(topology_st.standalone, DEFAULT_SUFFIX)
|
||||
+ user = users.ensure_state(
|
||||
+ properties={
|
||||
+ 'uid': TEST_USER_NAME,
|
||||
+ 'cn': TEST_USER_NAME,
|
||||
+ 'sn': TEST_USER_NAME,
|
||||
+ 'userPassword': TEST_USER_PW,
|
||||
+ 'uidNumber': '1000',
|
||||
+ 'gidNumber': '2000',
|
||||
+ 'homeDirectory': f'/home/{TEST_USER_NAME}'
|
||||
+ }
|
||||
+ )
|
||||
+
|
||||
+ def fin():
|
||||
+ log.info('Deleting test user')
|
||||
+ if user.exists():
|
||||
+ user.delete()
|
||||
+
|
||||
+ request.addfinalizer(fin)
|
||||
+ return user
|
||||
+
|
||||
+
|
||||
+@pytest.mark.skipif(ds_is_older("1.4.2"), reason="Indirect account locking not implemented")
|
||||
+def test_dsidm_account_inactivity_lock_unlock(topology_st, create_test_user):
|
||||
+ """Test dsidm account unlock functionality with indirectly locked accounts
|
||||
+
|
||||
+ :id: d7b57083-6111-4dbf-af84-6fca7fc7fb31
|
||||
+ :setup: Standalone instance with Account Policy Plugin and CoS configured
|
||||
+ :steps:
|
||||
+ 1. Create a test user
|
||||
+ 2. Bind as the test user to set lastLoginTime
|
||||
+ 3. Check account status - should be active
|
||||
+ 4. Set user's lastLoginTime to a time in the past that exceeds inactivity limit
|
||||
+ 5. Check account status - should be locked due to inactivity
|
||||
+ 6. Attempt to bind as the user - should fail with constraint violation
|
||||
+ 7. Unlock the account using dsidm account unlock
|
||||
+ 8. Verify account status is active again
|
||||
+ 9. Verify the user can bind again
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Account status shows as activated
|
||||
+ 4. Success
|
||||
+ 5. Account status shows as inactivity limit exceeded
|
||||
+ 6. Bind attempt fails with constraint violation
|
||||
+ 7. Account unlocked successfully
|
||||
+ 8. Account status shows as activated
|
||||
+ 9. User can bind successfully
|
||||
+ """
|
||||
+ standalone = topology_st.standalone
|
||||
+ user = create_test_user
|
||||
+
|
||||
+ # Set up FakeArgs for dsidm commands
|
||||
+ args = FakeArgs()
|
||||
+ args.dn = user.dn
|
||||
+ args.json = False
|
||||
+ args.details = False
|
||||
+
|
||||
+ # 1. Check initial account status - should be active
|
||||
+ log.info('Step 1: Checking initial account status')
|
||||
+ entry_status(standalone, DEFAULT_SUFFIX, topology_st.logcap.log, args)
|
||||
+ check_value_in_log_and_reset(topology_st, check_value='Entry State: activated')
|
||||
+
|
||||
+ # 2. Bind as test user to set initial lastLoginTime
|
||||
+ log.info('Step 2: Binding as test user to set lastLoginTime')
|
||||
+ try:
|
||||
+ conn = user.bind(TEST_USER_PW)
|
||||
+ conn.unbind()
|
||||
+ log.info("Successfully bound as test user")
|
||||
+ except ldap.LDAPError as e:
|
||||
+ pytest.fail(f"Failed to bind as test user: {e}")
|
||||
+
|
||||
+ # 3. Set lastLoginTime to a time in the past that exceeds inactivity limit
|
||||
+ log.info('Step 3: Setting lastLoginTime to the past')
|
||||
+ past_time = datetime.utcnow() - timedelta(seconds=INACTIVITY_LIMIT * 2)
|
||||
+ past_time_str = past_time.strftime('%Y%m%d%H%M%SZ')
|
||||
+ user.replace('lastLoginTime', past_time_str)
|
||||
+
|
||||
+ # 4. Check account status - should now be locked due to inactivity
|
||||
+ log.info('Step 4: Checking account status after setting old lastLoginTime')
|
||||
+ entry_status(standalone, DEFAULT_SUFFIX, topology_st.logcap.log, args)
|
||||
+ check_value_in_log_and_reset(topology_st, check_value='Entry State: inactivity limit exceeded')
|
||||
+
|
||||
+ # 5. Attempt to bind as the user - should fail
|
||||
+ log.info('Step 5: Attempting to bind as user (should fail)')
|
||||
+ with pytest.raises(ldap.CONSTRAINT_VIOLATION) as excinfo:
|
||||
+ conn = user.bind(TEST_USER_PW)
|
||||
+ assert "Account inactivity limit exceeded" in str(excinfo.value)
|
||||
+
|
||||
+ # 6. Unlock the account using dsidm account unlock
|
||||
+ log.info('Step 6: Unlocking the account with dsidm')
|
||||
+ unlock(standalone, DEFAULT_SUFFIX, topology_st.logcap.log, args)
|
||||
+ check_value_in_log_and_reset(topology_st,
|
||||
+ check_value='now unlocked by resetting lastLoginTime')
|
||||
+
|
||||
+ # 7. Verify account status is active again
|
||||
+ log.info('Step 7: Checking account status after unlock')
|
||||
+ entry_status(standalone, DEFAULT_SUFFIX, topology_st.logcap.log, args)
|
||||
+ check_value_in_log_and_reset(topology_st, check_value='Entry State: activated')
|
||||
+
|
||||
+ # 8. Verify the user can bind again
|
||||
+ log.info('Step 8: Verifying user can bind again')
|
||||
+ try:
|
||||
+ conn = user.bind(TEST_USER_PW)
|
||||
+ conn.unbind()
|
||||
+ log.info("Successfully bound as test user after unlock")
|
||||
+ except ldap.LDAPError as e:
|
||||
+ pytest.fail(f"Failed to bind as test user after unlock: {e}")
|
||||
+
|
||||
+
|
||||
+@pytest.mark.skipif(ds_is_older("1.4.2"), reason="Indirect account locking not implemented")
|
||||
+def test_dsidm_indirectly_locked_via_role(topology_st, create_test_user):
|
||||
+ """Test dsidm account unlock functionality with accounts indirectly locked via role
|
||||
+
|
||||
+ :id: 7bfe69bb-cf99-4214-a763-051ab2b9cf89
|
||||
+ :setup: Standalone instance with Role and user configured
|
||||
+ :steps:
|
||||
+ 1. Create a test user
|
||||
+ 2. Create a Filtered Role that includes the test user
|
||||
+ 3. Lock the role
|
||||
+ 4. Check account status - should be indirectly locked through the role
|
||||
+ 5. Attempt to unlock the account - should fail with appropriate message
|
||||
+ 6. Unlock the role
|
||||
+ 7. Verify account status is active again
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Success
|
||||
+ 4. Account status shows as indirectly locked
|
||||
+ 5. Unlock attempt fails with appropriate error message
|
||||
+ 6. Success
|
||||
+ 7. Account status shows as activated
|
||||
+ """
|
||||
+ standalone = topology_st.standalone
|
||||
+ user = create_test_user
|
||||
+
|
||||
+ # Use FilteredRoles and ensure_state for role creation
|
||||
+ log.info('Step 1: Creating Filtered Role')
|
||||
+ roles = FilteredRoles(standalone, DEFAULT_SUFFIX)
|
||||
+ role = roles.ensure_state(
|
||||
+ properties={
|
||||
+ 'cn': 'TestFilterRole',
|
||||
+ 'nsRoleFilter': f'(uid={TEST_USER_NAME})'
|
||||
+ }
|
||||
+ )
|
||||
+
|
||||
+ # Set up FakeArgs for dsidm commands
|
||||
+ args = FakeArgs()
|
||||
+ args.dn = user.dn
|
||||
+ args.json = False
|
||||
+ args.details = False
|
||||
+
|
||||
+ # 2. Check account status before locking role
|
||||
+ log.info('Step 2: Checking account status before locking role')
|
||||
+ entry_status(standalone, DEFAULT_SUFFIX, topology_st.logcap.log, args)
|
||||
+ check_value_in_log_and_reset(topology_st, check_value='Entry State: activated')
|
||||
+
|
||||
+ # 3. Lock the role
|
||||
+ log.info('Step 3: Locking the role')
|
||||
+ role.lock()
|
||||
+
|
||||
+ # 4. Check account status - should be indirectly locked
|
||||
+ log.info('Step 4: Checking account status after locking role')
|
||||
+ entry_status(standalone, DEFAULT_SUFFIX, topology_st.logcap.log, args)
|
||||
+ check_value_in_log_and_reset(topology_st, check_value='Entry State: indirectly locked through a Role')
|
||||
+
|
||||
+ # 5. Attempt to unlock the account - should fail
|
||||
+ log.info('Step 5: Attempting to unlock indirectly locked account')
|
||||
+ unlock(standalone, DEFAULT_SUFFIX, topology_st.logcap.log, args)
|
||||
+ check_value_in_log_and_reset(topology_st,
|
||||
+ check_value='Account is locked through role')
|
||||
+
|
||||
+ # 6. Unlock the role
|
||||
+ log.info('Step 6: Unlocking the role')
|
||||
+ role.unlock()
|
||||
+
|
||||
+ # 7. Verify account status is active again
|
||||
+ log.info('Step 7: Checking account status after unlocking role')
|
||||
+ entry_status(standalone, DEFAULT_SUFFIX, topology_st.logcap.log, args)
|
||||
+ check_value_in_log_and_reset(topology_st, check_value='Entry State: activated')
|
||||
+
|
||||
+
|
||||
+if __name__ == '__main__':
|
||||
+ # Run isolated
|
||||
+ # -s for DEBUG mode
|
||||
+ CURRENT_FILE = os.path.realpath(__file__)
|
||||
+ pytest.main(["-s", CURRENT_FILE])
|
||||
\ No newline at end of file
|
||||
diff --git a/src/lib389/lib389/cli_idm/account.py b/src/lib389/lib389/cli_idm/account.py
|
||||
index 15f766588..a0dfd8f65 100644
|
||||
--- a/src/lib389/lib389/cli_idm/account.py
|
||||
+++ b/src/lib389/lib389/cli_idm/account.py
|
||||
@@ -176,8 +176,29 @@ def unlock(inst, basedn, log, args):
|
||||
dn = _get_dn_arg(args.dn, msg="Enter dn to unlock")
|
||||
accounts = Accounts(inst, basedn)
|
||||
acct = accounts.get(dn=dn)
|
||||
- acct.unlock()
|
||||
- log.info(f'Entry {dn} is unlocked')
|
||||
+
|
||||
+ try:
|
||||
+ # Get the account status before attempting to unlock
|
||||
+ status = acct.status()
|
||||
+ state = status["state"]
|
||||
+
|
||||
+ # Attempt to unlock the account
|
||||
+ acct.unlock()
|
||||
+
|
||||
+ # Success message
|
||||
+ log.info(f'Entry {dn} is unlocked')
|
||||
+ if state == AccountState.DIRECTLY_LOCKED:
|
||||
+ log.info(f'The entry was directly locked')
|
||||
+ elif state == AccountState.INACTIVITY_LIMIT_EXCEEDED:
|
||||
+ log.info(f'The entry was locked due to inactivity and is now unlocked by resetting lastLoginTime')
|
||||
+
|
||||
+ except ValueError as e:
|
||||
+ # Provide a more detailed error message based on failure reason
|
||||
+ if "through role" in str(e):
|
||||
+ log.error(f"Cannot unlock {dn}: {str(e)}")
|
||||
+ log.info("To unlock this account, you must modify the role that's locking it.")
|
||||
+ else:
|
||||
+ log.error(f"Failed to unlock {dn}: {str(e)}")
|
||||
|
||||
|
||||
def reset_password(inst, basedn, log, args):
|
||||
diff --git a/src/lib389/lib389/idm/account.py b/src/lib389/lib389/idm/account.py
|
||||
index 4b823b662..faf6f6f16 100644
|
||||
--- a/src/lib389/lib389/idm/account.py
|
||||
+++ b/src/lib389/lib389/idm/account.py
|
||||
@@ -140,7 +140,8 @@ class Account(DSLdapObject):
|
||||
"nsAccountLock", state_attr])
|
||||
|
||||
last_login_time = self._dict_get_with_ignore_indexerror(account_data, state_attr)
|
||||
- if not last_login_time:
|
||||
+ # if last_login_time not exist then check alt_state_attr only if its not disabled and exist
|
||||
+ if not last_login_time and alt_state_attr in account_data:
|
||||
last_login_time = self._dict_get_with_ignore_indexerror(account_data, alt_state_attr)
|
||||
|
||||
create_time = self._dict_get_with_ignore_indexerror(account_data, "createTimestamp")
|
||||
@@ -203,12 +204,33 @@ class Account(DSLdapObject):
|
||||
self.replace('nsAccountLock', 'true')
|
||||
|
||||
def unlock(self):
|
||||
- """Unset nsAccountLock"""
|
||||
+ """Unset nsAccountLock if it's set and reset lastLoginTime if account is locked due to inactivity"""
|
||||
|
||||
current_status = self.status()
|
||||
+
|
||||
if current_status["state"] == AccountState.ACTIVATED:
|
||||
raise ValueError("Account is already active")
|
||||
- self.remove('nsAccountLock', None)
|
||||
+
|
||||
+ if current_status["state"] == AccountState.DIRECTLY_LOCKED:
|
||||
+ # Account is directly locked with nsAccountLock attribute
|
||||
+ self.remove('nsAccountLock', None)
|
||||
+ elif current_status["state"] == AccountState.INACTIVITY_LIMIT_EXCEEDED:
|
||||
+ # Account is locked due to inactivity - reset lastLoginTime to current time
|
||||
+ # The lastLoginTime attribute stores its value in GMT/UTC time (Zulu time zone)
|
||||
+ current_time = time.strftime('%Y%m%d%H%M%SZ', time.gmtime())
|
||||
+ self.replace('lastLoginTime', current_time)
|
||||
+ elif current_status["state"] == AccountState.INDIRECTLY_LOCKED:
|
||||
+ # Account is locked through a role
|
||||
+ role_dn = current_status.get("role_dn")
|
||||
+ if role_dn:
|
||||
+ raise ValueError(f"Account is locked through role {role_dn}. "
|
||||
+ f"Please modify the role to unlock this account.")
|
||||
+ else:
|
||||
+ raise ValueError("Account is locked through an unknown role. "
|
||||
+ "Please check the roles configuration to unlock this account.")
|
||||
+ else:
|
||||
+ # Should not happen, but just in case
|
||||
+ raise ValueError(f"Unknown lock state: {current_status['state'].value}")
|
||||
|
||||
# If the account can be bound to, this will attempt to do so. We don't check
|
||||
# for exceptions, just pass them back!
|
||||
--
|
||||
2.49.0
|
||||
|
||||
@ -1,70 +0,0 @@
|
||||
From 09a284ee43c2b4346da892f8756f97accd15ca68 Mon Sep 17 00:00:00 2001
|
||||
From: Simon Pichugin <spichugi@redhat.com>
|
||||
Date: Wed, 4 Dec 2024 21:59:40 -0500
|
||||
Subject: [PATCH] Issue 6302 - Allow to run replication status without a prompt
|
||||
(#6410)
|
||||
|
||||
Description: We should allow running replication status and
|
||||
other similar commands without requesting a password and bind DN.
|
||||
|
||||
This way, the current instance's root DN and root PW will be used on other
|
||||
instances when requesting CSN info. If they are incorrect,
|
||||
then the info won't be printed, but otherwise, the agreement status
|
||||
will be displayed correctly.
|
||||
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/6302
|
||||
|
||||
Reviewed by: @progier389 (Thanks!)
|
||||
---
|
||||
src/lib389/lib389/cli_conf/replication.py | 15 +++------------
|
||||
1 file changed, 3 insertions(+), 12 deletions(-)
|
||||
|
||||
diff --git a/src/lib389/lib389/cli_conf/replication.py b/src/lib389/lib389/cli_conf/replication.py
|
||||
index 399d0d2f8..cd4a331a8 100644
|
||||
--- a/src/lib389/lib389/cli_conf/replication.py
|
||||
+++ b/src/lib389/lib389/cli_conf/replication.py
|
||||
@@ -319,12 +319,9 @@ def list_suffixes(inst, basedn, log, args):
|
||||
def get_repl_status(inst, basedn, log, args):
|
||||
replicas = Replicas(inst)
|
||||
replica = replicas.get(args.suffix)
|
||||
- pw_and_dn_prompt = False
|
||||
if args.bind_passwd_file is not None:
|
||||
args.bind_passwd = get_passwd_from_file(args.bind_passwd_file)
|
||||
- if args.bind_passwd_prompt or args.bind_dn is None or args.bind_passwd is None:
|
||||
- pw_and_dn_prompt = True
|
||||
- status = replica.status(binddn=args.bind_dn, bindpw=args.bind_passwd, pwprompt=pw_and_dn_prompt)
|
||||
+ status = replica.status(binddn=args.bind_dn, bindpw=args.bind_passwd, pwprompt=args.bind_passwd_prompt)
|
||||
if args.json:
|
||||
log.info(json.dumps({"type": "list", "items": status}, indent=4))
|
||||
else:
|
||||
@@ -335,12 +332,9 @@ def get_repl_status(inst, basedn, log, args):
|
||||
def get_repl_winsync_status(inst, basedn, log, args):
|
||||
replicas = Replicas(inst)
|
||||
replica = replicas.get(args.suffix)
|
||||
- pw_and_dn_prompt = False
|
||||
if args.bind_passwd_file is not None:
|
||||
args.bind_passwd = get_passwd_from_file(args.bind_passwd_file)
|
||||
- if args.bind_passwd_prompt or args.bind_dn is None or args.bind_passwd is None:
|
||||
- pw_and_dn_prompt = True
|
||||
- status = replica.status(binddn=args.bind_dn, bindpw=args.bind_passwd, winsync=True, pwprompt=pw_and_dn_prompt)
|
||||
+ status = replica.status(binddn=args.bind_dn, bindpw=args.bind_passwd, winsync=True, pwprompt=args.bind_passwd_prompt)
|
||||
if args.json:
|
||||
log.info(json.dumps({"type": "list", "items": status}, indent=4))
|
||||
else:
|
||||
@@ -874,12 +868,9 @@ def poke_agmt(inst, basedn, log, args):
|
||||
|
||||
def get_agmt_status(inst, basedn, log, args):
|
||||
agmt = get_agmt(inst, args)
|
||||
- pw_and_dn_prompt = False
|
||||
if args.bind_passwd_file is not None:
|
||||
args.bind_passwd = get_passwd_from_file(args.bind_passwd_file)
|
||||
- if args.bind_passwd_prompt or args.bind_dn is None or args.bind_passwd is None:
|
||||
- pw_and_dn_prompt = True
|
||||
- status = agmt.status(use_json=args.json, binddn=args.bind_dn, bindpw=args.bind_passwd, pwprompt=pw_and_dn_prompt)
|
||||
+ status = agmt.status(use_json=args.json, binddn=args.bind_dn, bindpw=args.bind_passwd, pwprompt=args.bind_passwd_prompt)
|
||||
log.info(status)
|
||||
|
||||
|
||||
--
|
||||
2.49.0
|
||||
|
||||
@ -1,45 +0,0 @@
|
||||
From 2b73c3596e724f314b0e09cf6209e0151260f7e5 Mon Sep 17 00:00:00 2001
|
||||
From: Alexander Bokovoy <abokovoy@redhat.com>
|
||||
Date: Wed, 9 Jul 2025 12:08:09 +0300
|
||||
Subject: [PATCH] Issue 6857 - uiduniq: allow specifying match rules in the
|
||||
filter
|
||||
|
||||
Allow uniqueness plugin to work with attributes where uniqueness should
|
||||
be enforced using different matching rule than the one defined for the
|
||||
attribute itself.
|
||||
|
||||
Since uniqueness plugin configuration can contain multiple attributes,
|
||||
add matching rule right to the attribute as it is used in the LDAP rule
|
||||
(e.g. 'attribute:caseIgnoreMatch:' to force 'attribute' to be searched
|
||||
with case-insensitive matching rule instead of the original matching
|
||||
rule.
|
||||
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/6857
|
||||
|
||||
Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
|
||||
---
|
||||
ldap/servers/plugins/uiduniq/uid.c | 7 +++++++
|
||||
1 file changed, 7 insertions(+)
|
||||
|
||||
diff --git a/ldap/servers/plugins/uiduniq/uid.c b/ldap/servers/plugins/uiduniq/uid.c
|
||||
index 5b763b551..15cf88477 100644
|
||||
--- a/ldap/servers/plugins/uiduniq/uid.c
|
||||
+++ b/ldap/servers/plugins/uiduniq/uid.c
|
||||
@@ -1031,7 +1031,14 @@ preop_add(Slapi_PBlock *pb)
|
||||
}
|
||||
|
||||
for (i = 0; attrNames && attrNames[i]; i++) {
|
||||
+ char *attr_match = strchr(attrNames[i], ':');
|
||||
+ if (attr_match != NULL) {
|
||||
+ attr_match[0] = '\0';
|
||||
+ }
|
||||
err = slapi_entry_attr_find(e, attrNames[i], &attr);
|
||||
+ if (attr_match != NULL) {
|
||||
+ attr_match[0] = ':';
|
||||
+ }
|
||||
if (!err) {
|
||||
/*
|
||||
* Passed all the requirements - this is an operation we
|
||||
--
|
||||
2.49.0
|
||||
|
||||
@ -1,399 +0,0 @@
|
||||
From 3ba73d2aa55f18ff73d4b3901ce101133745effc Mon Sep 17 00:00:00 2001
|
||||
From: Mark Reynolds <mreynolds@redhat.com>
|
||||
Date: Wed, 9 Jul 2025 14:18:50 -0400
|
||||
Subject: [PATCH] Issue 6859 - str2filter is not fully applying matching rules
|
||||
|
||||
Description:
|
||||
|
||||
When we have an extended filter, one with a MR applied, it is ignored during
|
||||
internal searches:
|
||||
|
||||
"(cn:CaseExactMatch:=Value)"
|
||||
|
||||
For internal searches we use str2filter() and it doesn't fully apply extended
|
||||
search filter matching rules
|
||||
|
||||
Also needed to update attr uniqueness plugin to apply this change for mod
|
||||
operations (previously only Adds were correctly handling these attribute
|
||||
filters)
|
||||
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/6857
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/6859
|
||||
|
||||
Reviewed by: spichugi & tbordaz(Thanks!!)
|
||||
---
|
||||
.../tests/suites/plugins/attruniq_test.py | 295 +++++++++++++++++-
|
||||
ldap/servers/plugins/uiduniq/uid.c | 7 +
|
||||
ldap/servers/slapd/plugin_mr.c | 2 +-
|
||||
ldap/servers/slapd/str2filter.c | 8 +
|
||||
4 files changed, 309 insertions(+), 3 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/plugins/attruniq_test.py b/dirsrvtests/tests/suites/plugins/attruniq_test.py
|
||||
index b190e0ec1..b338f405f 100644
|
||||
--- a/dirsrvtests/tests/suites/plugins/attruniq_test.py
|
||||
+++ b/dirsrvtests/tests/suites/plugins/attruniq_test.py
|
||||
@@ -1,5 +1,5 @@
|
||||
# --- BEGIN COPYRIGHT BLOCK ---
|
||||
-# Copyright (C) 2021 Red Hat, Inc.
|
||||
+# Copyright (C) 2025 Red Hat, Inc.
|
||||
# All rights reserved.
|
||||
#
|
||||
# License: GPL (version 3 or any later version).
|
||||
@@ -80,4 +80,295 @@ def test_modrdn_attr_uniqueness(topology_st):
|
||||
log.debug(excinfo.value)
|
||||
|
||||
log.debug('Move user2 to group1')
|
||||
- user2.rename(f'uid={user2.rdn}', group1.dn)
|
||||
\ No newline at end of file
|
||||
+
|
||||
+ user2.rename(f'uid={user2.rdn}', group1.dn)
|
||||
+
|
||||
+ # Cleanup for next test
|
||||
+ user1.delete()
|
||||
+ user2.delete()
|
||||
+ attruniq.disable()
|
||||
+ attruniq.delete()
|
||||
+
|
||||
+
|
||||
+def test_multiple_attr_uniqueness(topology_st):
|
||||
+ """ Test that attribute uniqueness works properly with multiple attributes
|
||||
+
|
||||
+ :id: c49aa5c1-7e65-45fd-b064-55e0b815e9bc
|
||||
+ :setup: Standalone instance
|
||||
+ :steps:
|
||||
+ 1. Setup attribute uniqueness plugin to ensure uniqueness of attributes 'mail' and 'mailAlternateAddress'
|
||||
+ 2. Add user with unique 'mail=non-uniq@value.net' and 'mailAlternateAddress=alt-mail@value.net'
|
||||
+ 3. Try adding another user with 'mail=non-uniq@value.net'
|
||||
+ 4. Try adding another user with 'mailAlternateAddress=alt-mail@value.net'
|
||||
+ 5. Try adding another user with 'mail=alt-mail@value.net'
|
||||
+ 6. Try adding another user with 'mailAlternateAddress=non-uniq@value.net'
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Should raise CONSTRAINT_VIOLATION
|
||||
+ 4. Should raise CONSTRAINT_VIOLATION
|
||||
+ 5. Should raise CONSTRAINT_VIOLATION
|
||||
+ 6. Should raise CONSTRAINT_VIOLATION
|
||||
+ """
|
||||
+ attruniq = AttributeUniquenessPlugin(topology_st.standalone, dn="cn=attruniq,cn=plugins,cn=config")
|
||||
+
|
||||
+ try:
|
||||
+ log.debug(f'Setup PLUGIN_ATTR_UNIQUENESS plugin for {MAIL_ATTR_VALUE} attribute for the group2')
|
||||
+ attruniq.create(properties={'cn': 'attruniq'})
|
||||
+ attruniq.add_unique_attribute('mail')
|
||||
+ attruniq.add_unique_attribute('mailAlternateAddress')
|
||||
+ attruniq.add_unique_subtree(DEFAULT_SUFFIX)
|
||||
+ attruniq.enable_all_subtrees()
|
||||
+ log.debug(f'Enable PLUGIN_ATTR_UNIQUENESS plugin as "ON"')
|
||||
+ attruniq.enable()
|
||||
+ except ldap.LDAPError as e:
|
||||
+ log.fatal('test_multiple_attribute_uniqueness: Failed to configure plugin for "mail": error {}'.format(e.args[0]['desc']))
|
||||
+ assert False
|
||||
+
|
||||
+ topology_st.standalone.restart()
|
||||
+
|
||||
+ users = UserAccounts(topology_st.standalone, DEFAULT_SUFFIX)
|
||||
+
|
||||
+ testuser1 = users.create_test_user(100,100)
|
||||
+ testuser1.add('objectclass', 'extensibleObject')
|
||||
+ testuser1.add('mail', MAIL_ATTR_VALUE)
|
||||
+ testuser1.add('mailAlternateAddress', MAIL_ATTR_VALUE_ALT)
|
||||
+
|
||||
+ testuser2 = users.create_test_user(200, 200)
|
||||
+ testuser2.add('objectclass', 'extensibleObject')
|
||||
+
|
||||
+ with pytest.raises(ldap.CONSTRAINT_VIOLATION):
|
||||
+ testuser2.add('mail', MAIL_ATTR_VALUE)
|
||||
+
|
||||
+ with pytest.raises(ldap.CONSTRAINT_VIOLATION):
|
||||
+ testuser2.add('mailAlternateAddress', MAIL_ATTR_VALUE_ALT)
|
||||
+
|
||||
+ with pytest.raises(ldap.CONSTRAINT_VIOLATION):
|
||||
+ testuser2.add('mail', MAIL_ATTR_VALUE_ALT)
|
||||
+
|
||||
+ with pytest.raises(ldap.CONSTRAINT_VIOLATION):
|
||||
+ testuser2.add('mailAlternateAddress', MAIL_ATTR_VALUE)
|
||||
+
|
||||
+ # Cleanup
|
||||
+ testuser1.delete()
|
||||
+ testuser2.delete()
|
||||
+ attruniq.disable()
|
||||
+ attruniq.delete()
|
||||
+
|
||||
+
|
||||
+def test_exclude_subtrees(topology_st):
|
||||
+ """ Test attribute uniqueness with exclude scope
|
||||
+
|
||||
+ :id: 43d29a60-40e1-4ebd-b897-6ef9f20e9f27
|
||||
+ :setup: Standalone instance
|
||||
+ :steps:
|
||||
+ 1. Setup and enable attribute uniqueness plugin for telephonenumber unique attribute
|
||||
+ 2. Create subtrees and test users
|
||||
+ 3. Add a unique attribute to a user within uniqueness scope
|
||||
+ 4. Add exclude subtree
|
||||
+ 5. Try to add existing value attribute to an entry within uniqueness scope
|
||||
+ 6. Try to add existing value attribute to an entry within exclude scope
|
||||
+ 7. Remove the attribute from affected entries
|
||||
+ 8. Add a unique attribute to a user within exclude scope
|
||||
+ 9. Try to add existing value attribute to an entry within uniqueness scope
|
||||
+ 10. Try to add existing value attribute to another entry within uniqueness scope
|
||||
+ 11. Remove the attribute from affected entries
|
||||
+ 12. Add another exclude subtree
|
||||
+ 13. Add a unique attribute to a user within uniqueness scope
|
||||
+ 14. Try to add existing value attribute to an entry within uniqueness scope
|
||||
+ 15. Try to add existing value attribute to an entry within exclude scope
|
||||
+ 16. Try to add existing value attribute to an entry within another exclude scope
|
||||
+ 17. Clean up entries
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Success
|
||||
+ 4. Success
|
||||
+ 5. Should raise CONSTRAINT_VIOLATION
|
||||
+ 6. Success
|
||||
+ 7. Success
|
||||
+ 8. Success
|
||||
+ 9. Success
|
||||
+ 10. Should raise CONSTRAINT_VIOLATION
|
||||
+ 11. Success
|
||||
+ 12. Success
|
||||
+ 13. Success
|
||||
+ 14. Should raise CONSTRAINT_VIOLATION
|
||||
+ 15. Success
|
||||
+ 16. Success
|
||||
+ 17. Success
|
||||
+ """
|
||||
+ log.info('Setup attribute uniqueness plugin')
|
||||
+ attruniq = AttributeUniquenessPlugin(topology_st.standalone, dn="cn=attruniq,cn=plugins,cn=config")
|
||||
+ attruniq.create(properties={'cn': 'attruniq'})
|
||||
+ attruniq.add_unique_attribute('telephonenumber')
|
||||
+ attruniq.add_unique_subtree(DEFAULT_SUFFIX)
|
||||
+ attruniq.enable_all_subtrees()
|
||||
+ attruniq.enable()
|
||||
+ topology_st.standalone.restart()
|
||||
+
|
||||
+ log.info('Create subtrees container')
|
||||
+ containers = nsContainers(topology_st.standalone, DEFAULT_SUFFIX)
|
||||
+ cont1 = containers.create(properties={'cn': EXCLUDED_CONTAINER_CN})
|
||||
+ cont2 = containers.create(properties={'cn': EXCLUDED_BIS_CONTAINER_CN})
|
||||
+ cont3 = containers.create(properties={'cn': ENFORCED_CONTAINER_CN})
|
||||
+
|
||||
+ log.info('Create test users')
|
||||
+ users = UserAccounts(topology_st.standalone, DEFAULT_SUFFIX,
|
||||
+ rdn='cn={}'.format(ENFORCED_CONTAINER_CN))
|
||||
+ users_excluded = UserAccounts(topology_st.standalone, DEFAULT_SUFFIX,
|
||||
+ rdn='cn={}'.format(EXCLUDED_CONTAINER_CN))
|
||||
+ users_excluded2 = UserAccounts(topology_st.standalone, DEFAULT_SUFFIX,
|
||||
+ rdn='cn={}'.format(EXCLUDED_BIS_CONTAINER_CN))
|
||||
+
|
||||
+ user1 = users.create(properties={'cn': USER_1_CN,
|
||||
+ 'uid': USER_1_CN,
|
||||
+ 'sn': USER_1_CN,
|
||||
+ 'uidNumber': '1',
|
||||
+ 'gidNumber': '11',
|
||||
+ 'homeDirectory': '/home/{}'.format(USER_1_CN)})
|
||||
+ user2 = users.create(properties={'cn': USER_2_CN,
|
||||
+ 'uid': USER_2_CN,
|
||||
+ 'sn': USER_2_CN,
|
||||
+ 'uidNumber': '2',
|
||||
+ 'gidNumber': '22',
|
||||
+ 'homeDirectory': '/home/{}'.format(USER_2_CN)})
|
||||
+ user3 = users_excluded.create(properties={'cn': USER_3_CN,
|
||||
+ 'uid': USER_3_CN,
|
||||
+ 'sn': USER_3_CN,
|
||||
+ 'uidNumber': '3',
|
||||
+ 'gidNumber': '33',
|
||||
+ 'homeDirectory': '/home/{}'.format(USER_3_CN)})
|
||||
+ user4 = users_excluded2.create(properties={'cn': USER_4_CN,
|
||||
+ 'uid': USER_4_CN,
|
||||
+ 'sn': USER_4_CN,
|
||||
+ 'uidNumber': '4',
|
||||
+ 'gidNumber': '44',
|
||||
+ 'homeDirectory': '/home/{}'.format(USER_4_CN)})
|
||||
+
|
||||
+ UNIQUE_VALUE = '1234'
|
||||
+
|
||||
+ try:
|
||||
+ log.info('Create user with unique attribute')
|
||||
+ user1.add('telephonenumber', UNIQUE_VALUE)
|
||||
+ assert user1.present('telephonenumber', UNIQUE_VALUE)
|
||||
+
|
||||
+ log.info('Add exclude subtree')
|
||||
+ attruniq.add_exclude_subtree(EXCLUDED_CONTAINER_DN)
|
||||
+ topology_st.standalone.restart()
|
||||
+
|
||||
+ log.info('Verify an already used attribute value cannot be added within the same subtree')
|
||||
+ with pytest.raises(ldap.CONSTRAINT_VIOLATION):
|
||||
+ user2.add('telephonenumber', UNIQUE_VALUE)
|
||||
+
|
||||
+ log.info('Verify an entry with same attribute value can be added within exclude subtree')
|
||||
+ user3.add('telephonenumber', UNIQUE_VALUE)
|
||||
+ assert user3.present('telephonenumber', UNIQUE_VALUE)
|
||||
+
|
||||
+ log.info('Cleanup unique attribute values')
|
||||
+ user1.remove_all('telephonenumber')
|
||||
+ user3.remove_all('telephonenumber')
|
||||
+
|
||||
+ log.info('Add a unique value to an entry in excluded scope')
|
||||
+ user3.add('telephonenumber', UNIQUE_VALUE)
|
||||
+ assert user3.present('telephonenumber', UNIQUE_VALUE)
|
||||
+
|
||||
+ log.info('Verify the same value can be added to an entry within uniqueness scope')
|
||||
+ user1.add('telephonenumber', UNIQUE_VALUE)
|
||||
+ assert user1.present('telephonenumber', UNIQUE_VALUE)
|
||||
+
|
||||
+ log.info('Verify that yet another same value cannot be added to another entry within uniqueness scope')
|
||||
+ with pytest.raises(ldap.CONSTRAINT_VIOLATION):
|
||||
+ user2.add('telephonenumber', UNIQUE_VALUE)
|
||||
+
|
||||
+ log.info('Cleanup unique attribute values')
|
||||
+ user1.remove_all('telephonenumber')
|
||||
+ user3.remove_all('telephonenumber')
|
||||
+
|
||||
+ log.info('Add another exclude subtree')
|
||||
+ attruniq.add_exclude_subtree(EXCLUDED_BIS_CONTAINER_DN)
|
||||
+ topology_st.standalone.restart()
|
||||
+
|
||||
+ user1.add('telephonenumber', UNIQUE_VALUE)
|
||||
+ log.info('Verify an already used attribute value cannot be added within the same subtree')
|
||||
+ with pytest.raises(ldap.CONSTRAINT_VIOLATION):
|
||||
+ user2.add('telephonenumber', UNIQUE_VALUE)
|
||||
+
|
||||
+ log.info('Verify an already used attribute can be added to an entry in exclude scope')
|
||||
+ user3.add('telephonenumber', UNIQUE_VALUE)
|
||||
+ assert user3.present('telephonenumber', UNIQUE_VALUE)
|
||||
+ user4.add('telephonenumber', UNIQUE_VALUE)
|
||||
+ assert user4.present('telephonenumber', UNIQUE_VALUE)
|
||||
+
|
||||
+ finally:
|
||||
+ log.info('Clean up users, containers and attribute uniqueness plugin')
|
||||
+ user1.delete()
|
||||
+ user2.delete()
|
||||
+ user3.delete()
|
||||
+ user4.delete()
|
||||
+ cont1.delete()
|
||||
+ cont2.delete()
|
||||
+ cont3.delete()
|
||||
+ attruniq.disable()
|
||||
+ attruniq.delete()
|
||||
+
|
||||
+
|
||||
+def test_matchingrule_attr(topology_st):
|
||||
+ """ Test list extension MR attribute. Check for "cn" using CES (versus it
|
||||
+ being defined as CIS)
|
||||
+
|
||||
+ :id: 5cde4342-6fa3-4225-b23d-0af918981075
|
||||
+ :setup: Standalone instance
|
||||
+ :steps:
|
||||
+ 1. Setup and enable attribute uniqueness plugin to use CN attribute
|
||||
+ with a matching rule of CaseExactMatch.
|
||||
+ 2. Add user with CN value is lowercase
|
||||
+ 3. Add second user with same lowercase CN which should be rejected
|
||||
+ 4. Add second user with same CN value but with mixed case
|
||||
+ 5. Modify second user replacing CN value to lc which should be rejected
|
||||
+
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Success
|
||||
+ 4. Success
|
||||
+ 5. Success
|
||||
+ """
|
||||
+
|
||||
+ inst = topology_st.standalone
|
||||
+
|
||||
+ attruniq = AttributeUniquenessPlugin(inst,
|
||||
+ dn="cn=attribute uniqueness,cn=plugins,cn=config")
|
||||
+ attruniq.add_unique_attribute('cn:CaseExactMatch:')
|
||||
+ attruniq.enable_all_subtrees()
|
||||
+ attruniq.enable()
|
||||
+ inst.restart()
|
||||
+
|
||||
+ users = UserAccounts(inst, DEFAULT_SUFFIX)
|
||||
+ users.create(properties={'cn': "common_name",
|
||||
+ 'uid': "uid_name",
|
||||
+ 'sn': "uid_name",
|
||||
+ 'uidNumber': '1',
|
||||
+ 'gidNumber': '11',
|
||||
+ 'homeDirectory': '/home/uid_name'})
|
||||
+
|
||||
+ log.info('Add entry with the exact CN value which should be rejected')
|
||||
+ with pytest.raises(ldap.CONSTRAINT_VIOLATION):
|
||||
+ users.create(properties={'cn': "common_name",
|
||||
+ 'uid': "uid_name2",
|
||||
+ 'sn': "uid_name2",
|
||||
+ 'uidNumber': '11',
|
||||
+ 'gidNumber': '111',
|
||||
+ 'homeDirectory': '/home/uid_name2'})
|
||||
+
|
||||
+ log.info('Add entry with the mixed case CN value which should be allowed')
|
||||
+ user = users.create(properties={'cn': "Common_Name",
|
||||
+ 'uid': "uid_name2",
|
||||
+ 'sn': "uid_name2",
|
||||
+ 'uidNumber': '11',
|
||||
+ 'gidNumber': '111',
|
||||
+ 'homeDirectory': '/home/uid_name2'})
|
||||
+
|
||||
+ log.info('Mod entry with exact case CN value which should be rejected')
|
||||
+ with pytest.raises(ldap.CONSTRAINT_VIOLATION):
|
||||
+ user.replace('cn', 'common_name')
|
||||
diff --git a/ldap/servers/plugins/uiduniq/uid.c b/ldap/servers/plugins/uiduniq/uid.c
|
||||
index 15cf88477..5a0d61b86 100644
|
||||
--- a/ldap/servers/plugins/uiduniq/uid.c
|
||||
+++ b/ldap/servers/plugins/uiduniq/uid.c
|
||||
@@ -1179,6 +1179,10 @@ preop_modify(Slapi_PBlock *pb)
|
||||
for (; mods && *mods; mods++) {
|
||||
mod = *mods;
|
||||
for (i = 0; attrNames && attrNames[i]; i++) {
|
||||
+ char *attr_match = strchr(attrNames[i], ':');
|
||||
+ if (attr_match != NULL) {
|
||||
+ attr_match[0] = '\0';
|
||||
+ }
|
||||
if ((slapi_attr_type_cmp(mod->mod_type, attrNames[i], 1) == 0) && /* mod contains target attr */
|
||||
(mod->mod_op & LDAP_MOD_BVALUES) && /* mod is bval encoded (not string val) */
|
||||
(mod->mod_bvalues && mod->mod_bvalues[0]) && /* mod actually contains some values */
|
||||
@@ -1187,6 +1191,9 @@ preop_modify(Slapi_PBlock *pb)
|
||||
{
|
||||
addMod(&checkmods, &checkmodsCapacity, &modcount, mod);
|
||||
}
|
||||
+ if (attr_match != NULL) {
|
||||
+ attr_match[0] = ':';
|
||||
+ }
|
||||
}
|
||||
}
|
||||
if (modcount == 0) {
|
||||
diff --git a/ldap/servers/slapd/plugin_mr.c b/ldap/servers/slapd/plugin_mr.c
|
||||
index 6cf88b7de..f40c9a39b 100644
|
||||
--- a/ldap/servers/slapd/plugin_mr.c
|
||||
+++ b/ldap/servers/slapd/plugin_mr.c
|
||||
@@ -624,7 +624,7 @@ attempt_mr_filter_create(mr_filter_t *f, struct slapdplugin *mrp, Slapi_PBlock *
|
||||
int rc;
|
||||
IFP mrf_create = NULL;
|
||||
f->mrf_match = NULL;
|
||||
- pblock_init(pb);
|
||||
+ slapi_pblock_init(pb);
|
||||
if (!(rc = slapi_pblock_set(pb, SLAPI_PLUGIN, mrp)) &&
|
||||
!(rc = slapi_pblock_get(pb, SLAPI_PLUGIN_MR_FILTER_CREATE_FN, &mrf_create)) &&
|
||||
mrf_create != NULL &&
|
||||
diff --git a/ldap/servers/slapd/str2filter.c b/ldap/servers/slapd/str2filter.c
|
||||
index 9fdc500f7..5620b7439 100644
|
||||
--- a/ldap/servers/slapd/str2filter.c
|
||||
+++ b/ldap/servers/slapd/str2filter.c
|
||||
@@ -344,6 +344,14 @@ str2simple(char *str, int unescape_filter)
|
||||
return NULL; /* error */
|
||||
} else {
|
||||
f->f_choice = LDAP_FILTER_EXTENDED;
|
||||
+ if (f->f_mr_oid) {
|
||||
+ /* apply the MR indexers */
|
||||
+ rc = plugin_mr_filter_create(&f->f_mr);
|
||||
+ if (rc) {
|
||||
+ slapi_filter_free(f, 1);
|
||||
+ return NULL; /* error */
|
||||
+ }
|
||||
+ }
|
||||
}
|
||||
} else if (str_find_star(value) == NULL) {
|
||||
f->f_choice = LDAP_FILTER_EQUALITY;
|
||||
--
|
||||
2.49.0
|
||||
|
||||
@ -1,55 +0,0 @@
|
||||
From 77cc17e5dfb7ed71a320844d14a90c99c1474cc3 Mon Sep 17 00:00:00 2001
|
||||
From: Mark Reynolds <mreynolds@redhat.com>
|
||||
Date: Tue, 20 May 2025 08:13:24 -0400
|
||||
Subject: [PATCH] Issue 6787 - Improve error message when bulk import
|
||||
connection is closed
|
||||
|
||||
Description:
|
||||
|
||||
If an online replication initialization connection is closed a vague error
|
||||
message is reported when the init is aborted:
|
||||
|
||||
factory_destructor - ERROR bulk import abandoned
|
||||
|
||||
It should be clear that the import is being abandoned because the connection
|
||||
was closed and identify the conn id.
|
||||
|
||||
relates: https://github.com/389ds/389-ds-base/issues/6787
|
||||
|
||||
Reviewed by: progier(Thanks!)
|
||||
|
||||
(cherry picked from commit d472dd83d49f8dce6d71e202cbb4d897218ceffb)
|
||||
Signed-off-by: Masahiro Matsuya <mmatsuya@redhat.com>
|
||||
---
|
||||
ldap/servers/slapd/back-ldbm/db-bdb/bdb_import_threads.c | 6 ++++--
|
||||
1 file changed, 4 insertions(+), 2 deletions(-)
|
||||
|
||||
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 67d6e3abc..e433f3db2 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
|
||||
@@ -3432,9 +3432,10 @@ factory_constructor(void *object __attribute__((unused)), void *parent __attribu
|
||||
}
|
||||
|
||||
void
|
||||
-factory_destructor(void *extension, void *object __attribute__((unused)), void *parent __attribute__((unused)))
|
||||
+factory_destructor(void *extension, void *object, void *parent __attribute__((unused)))
|
||||
{
|
||||
ImportJob *job = (ImportJob *)extension;
|
||||
+ Connection *conn = (Connection *)object;
|
||||
PRThread *thread;
|
||||
|
||||
if (extension == NULL)
|
||||
@@ -3446,7 +3447,8 @@ factory_destructor(void *extension, void *object __attribute__((unused)), void *
|
||||
*/
|
||||
thread = job->main_thread;
|
||||
slapi_log_err(SLAPI_LOG_ERR, "factory_destructor",
|
||||
- "ERROR bulk import abandoned\n");
|
||||
+ "ERROR bulk import abandoned: conn=%ld was closed\n",
|
||||
+ conn->c_connid);
|
||||
import_abort_all(job, 1);
|
||||
/* wait for import_main to finish... */
|
||||
PR_JoinThread(thread);
|
||||
--
|
||||
2.51.1
|
||||
|
||||
@ -1,536 +0,0 @@
|
||||
From 8cba0dd699541d562d74502f35176df33f188512 Mon Sep 17 00:00:00 2001
|
||||
From: James Chapman <jachapma@redhat.com>
|
||||
Date: Fri, 30 May 2025 11:12:43 +0000
|
||||
Subject: [PATCH] Issue 6641 - modrdn fails when a user is member of multiple
|
||||
groups (#6643)
|
||||
|
||||
Bug description:
|
||||
Rename of a user that is member of multiple AM groups fail when MO and
|
||||
RI plugins are enabled.
|
||||
|
||||
Fix description:
|
||||
MO plugin - After updating the entry member attribute, check the return
|
||||
value. Retry the delete if the attr value exists and retry the add if the
|
||||
attr value is missing.
|
||||
|
||||
RI plugin - A previous commit checked if the attr value was not present
|
||||
before adding a mod. This commit was reverted in favour of overriding
|
||||
the internal op return value, consistent with other plugins.
|
||||
|
||||
CI test from Viktor Ashirov <vashirov@redhat.com>
|
||||
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/6641
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/6566
|
||||
|
||||
Reviewed by: @progier389, @tbordaz, @vashirov (Thank you)
|
||||
|
||||
(cherry picked from commit 132ce4ab158679475cb83dbe28cc4fd7ced5cd19)
|
||||
Signed-off-by: Masahiro Matsuya <mmatsuya@redhat.com>
|
||||
---
|
||||
.../tests/suites/plugins/modrdn_test.py | 174 ++++++++++++++++++
|
||||
ldap/servers/plugins/automember/automember.c | 11 +-
|
||||
ldap/servers/plugins/memberof/memberof.c | 123 +++++--------
|
||||
ldap/servers/plugins/referint/referint.c | 30 +--
|
||||
ldap/servers/slapd/modify.c | 51 +++++
|
||||
ldap/servers/slapd/slapi-plugin.h | 1 +
|
||||
6 files changed, 301 insertions(+), 89 deletions(-)
|
||||
create mode 100644 dirsrvtests/tests/suites/plugins/modrdn_test.py
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/plugins/modrdn_test.py b/dirsrvtests/tests/suites/plugins/modrdn_test.py
|
||||
new file mode 100644
|
||||
index 000000000..be79b0c3c
|
||||
--- /dev/null
|
||||
+++ b/dirsrvtests/tests/suites/plugins/modrdn_test.py
|
||||
@@ -0,0 +1,174 @@
|
||||
+# --- BEGIN COPYRIGHT BLOCK ---
|
||||
+# Copyright (C) 2025 Red Hat, Inc.
|
||||
+# All rights reserved.
|
||||
+#
|
||||
+# License: GPL (version 3 or any later version).
|
||||
+# See LICENSE for details.
|
||||
+# --- END COPYRIGHT BLOCK ---
|
||||
+#
|
||||
+import pytest
|
||||
+from lib389.topologies import topology_st
|
||||
+from lib389._constants import DEFAULT_SUFFIX
|
||||
+from lib389.idm.group import Groups
|
||||
+from lib389.idm.user import nsUserAccounts
|
||||
+from lib389.plugins import (
|
||||
+ AutoMembershipDefinitions,
|
||||
+ AutoMembershipPlugin,
|
||||
+ AutoMembershipRegexRules,
|
||||
+ MemberOfPlugin,
|
||||
+ ReferentialIntegrityPlugin,
|
||||
+)
|
||||
+
|
||||
+pytestmark = pytest.mark.tier1
|
||||
+
|
||||
+USER_PROPERTIES = {
|
||||
+ "uid": "userwith",
|
||||
+ "cn": "userwith",
|
||||
+ "uidNumber": "1000",
|
||||
+ "gidNumber": "2000",
|
||||
+ "homeDirectory": "/home/testuser",
|
||||
+ "displayName": "test user",
|
||||
+}
|
||||
+
|
||||
+
|
||||
+def test_modrdn_of_a_member_of_2_automember_groups(topology_st):
|
||||
+ """Test that a member of 2 automember groups can be renamed
|
||||
+
|
||||
+ :id: 0e40bdc4-a2d2-4bb8-8368-e02c8920bad2
|
||||
+
|
||||
+ :setup: Standalone instance
|
||||
+
|
||||
+ :steps:
|
||||
+ 1. Enable automember plugin
|
||||
+ 2. Create definiton for users with A in the name
|
||||
+ 3. Create regex rule for users with A in the name
|
||||
+ 4. Create definiton for users with Z in the name
|
||||
+ 5. Create regex rule for users with Z in the name
|
||||
+ 6. Enable memberof plugin
|
||||
+ 7. Enable referential integrity plugin
|
||||
+ 8. Restart the instance
|
||||
+ 9. Create groups
|
||||
+ 10. Create users userwitha, userwithz, userwithaz
|
||||
+ 11. Rename userwithaz
|
||||
+
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Success
|
||||
+ 4. Success
|
||||
+ 5. Success
|
||||
+ 6. Success
|
||||
+ 7. Success
|
||||
+ 8. Success
|
||||
+ 9. Success
|
||||
+ 10. Success
|
||||
+ 11. Success
|
||||
+ """
|
||||
+ inst = topology_st.standalone
|
||||
+
|
||||
+ # Enable automember plugin
|
||||
+ automember_plugin = AutoMembershipPlugin(inst)
|
||||
+ automember_plugin.enable()
|
||||
+
|
||||
+ # Create definiton for users with A in the name
|
||||
+ automembers = AutoMembershipDefinitions(inst)
|
||||
+ automember = automembers.create(
|
||||
+ properties={
|
||||
+ "cn": "userswithA",
|
||||
+ "autoMemberScope": DEFAULT_SUFFIX,
|
||||
+ "autoMemberFilter": "objectclass=posixAccount",
|
||||
+ "autoMemberGroupingAttr": "member:dn",
|
||||
+ }
|
||||
+ )
|
||||
+
|
||||
+ # Create regex rule for users with A in the name
|
||||
+ automembers_regex_rule = AutoMembershipRegexRules(inst, f"{automember.dn}")
|
||||
+ automembers_regex_rule.create(
|
||||
+ properties={
|
||||
+ "cn": "userswithA",
|
||||
+ "autoMemberInclusiveRegex": ["cn=.*a.*"],
|
||||
+ "autoMemberTargetGroup": [f"cn=userswithA,ou=Groups,{DEFAULT_SUFFIX}"],
|
||||
+ }
|
||||
+ )
|
||||
+
|
||||
+ # Create definiton for users with Z in the name
|
||||
+ automember = automembers.create(
|
||||
+ properties={
|
||||
+ "cn": "userswithZ",
|
||||
+ "autoMemberScope": DEFAULT_SUFFIX,
|
||||
+ "autoMemberFilter": "objectclass=posixAccount",
|
||||
+ "autoMemberGroupingAttr": "member:dn",
|
||||
+ }
|
||||
+ )
|
||||
+
|
||||
+ # Create regex rule for users with Z in the name
|
||||
+ automembers_regex_rule = AutoMembershipRegexRules(inst, f"{automember.dn}")
|
||||
+ automembers_regex_rule.create(
|
||||
+ properties={
|
||||
+ "cn": "userswithZ",
|
||||
+ "autoMemberInclusiveRegex": ["cn=.*z.*"],
|
||||
+ "autoMemberTargetGroup": [f"cn=userswithZ,ou=Groups,{DEFAULT_SUFFIX}"],
|
||||
+ }
|
||||
+ )
|
||||
+
|
||||
+ # Enable memberof plugin
|
||||
+ memberof_plugin = MemberOfPlugin(inst)
|
||||
+ memberof_plugin.enable()
|
||||
+
|
||||
+ # Enable referential integrity plugin
|
||||
+ referint_plugin = ReferentialIntegrityPlugin(inst)
|
||||
+ referint_plugin.enable()
|
||||
+
|
||||
+ # Restart the instance
|
||||
+ inst.restart()
|
||||
+
|
||||
+ # Create groups
|
||||
+ groups = Groups(inst, DEFAULT_SUFFIX)
|
||||
+ groupA = groups.create(properties={"cn": "userswithA"})
|
||||
+ groupZ = groups.create(properties={"cn": "userswithZ"})
|
||||
+
|
||||
+ # Create users
|
||||
+ users = nsUserAccounts(inst, DEFAULT_SUFFIX)
|
||||
+
|
||||
+ # userwitha
|
||||
+ user_props = USER_PROPERTIES.copy()
|
||||
+ user_props.update(
|
||||
+ {
|
||||
+ "uid": USER_PROPERTIES["uid"] + "a",
|
||||
+ "cn": USER_PROPERTIES["cn"] + "a",
|
||||
+ }
|
||||
+ )
|
||||
+ user = users.create(properties=user_props)
|
||||
+
|
||||
+ # userwithz
|
||||
+ user_props.update(
|
||||
+ {
|
||||
+ "uid": USER_PROPERTIES["uid"] + "z",
|
||||
+ "cn": USER_PROPERTIES["cn"] + "z",
|
||||
+ }
|
||||
+ )
|
||||
+ user = users.create(properties=user_props)
|
||||
+
|
||||
+ # userwithaz
|
||||
+ user_props.update(
|
||||
+ {
|
||||
+ "uid": USER_PROPERTIES["uid"] + "az",
|
||||
+ "cn": USER_PROPERTIES["cn"] + "az",
|
||||
+ }
|
||||
+ )
|
||||
+ user = users.create(properties=user_props)
|
||||
+ user_orig_dn = user.dn
|
||||
+
|
||||
+ # Rename userwithaz
|
||||
+ user.rename(new_rdn="uid=userwith")
|
||||
+ user_new_dn = user.dn
|
||||
+
|
||||
+ assert user.get_attr_val_utf8("uid") != "userwithaz"
|
||||
+
|
||||
+ # Check groups contain renamed username
|
||||
+ assert groupA.is_member(user_new_dn)
|
||||
+ assert groupZ.is_member(user_new_dn)
|
||||
+
|
||||
+ # Check groups dont contain original username
|
||||
+ assert not groupA.is_member(user_orig_dn)
|
||||
+ assert not groupZ.is_member(user_orig_dn)
|
||||
diff --git a/ldap/servers/plugins/automember/automember.c b/ldap/servers/plugins/automember/automember.c
|
||||
index 419adb052..fde92ee12 100644
|
||||
--- a/ldap/servers/plugins/automember/automember.c
|
||||
+++ b/ldap/servers/plugins/automember/automember.c
|
||||
@@ -1754,13 +1754,12 @@ automember_update_member_value(Slapi_Entry *member_e, const char *group_dn, char
|
||||
}
|
||||
|
||||
mod_pb = slapi_pblock_new();
|
||||
- slapi_modify_internal_set_pb(mod_pb, group_dn,
|
||||
- mods, 0, 0, automember_get_plugin_id(), 0);
|
||||
- slapi_modify_internal_pb(mod_pb);
|
||||
- slapi_pblock_get(mod_pb, SLAPI_PLUGIN_INTOP_RESULT, &result);
|
||||
+ /* Do a single mod with error overrides for DEL/ADD */
|
||||
+ result = slapi_single_modify_internal_override(mod_pb, slapi_sdn_new_dn_byval(group_dn), mods,
|
||||
+ automember_get_plugin_id(), 0);
|
||||
|
||||
if(add){
|
||||
- if ((result != LDAP_SUCCESS) && (result != LDAP_TYPE_OR_VALUE_EXISTS)) {
|
||||
+ if (result != LDAP_SUCCESS) {
|
||||
slapi_log_err(SLAPI_LOG_ERR, AUTOMEMBER_PLUGIN_SUBSYSTEM,
|
||||
"automember_update_member_value - Unable to add \"%s\" as "
|
||||
"a \"%s\" value to group \"%s\" (%s).\n",
|
||||
@@ -1770,7 +1769,7 @@ automember_update_member_value(Slapi_Entry *member_e, const char *group_dn, char
|
||||
}
|
||||
} else {
|
||||
/* delete value */
|
||||
- if ((result != LDAP_SUCCESS) && (result != LDAP_NO_SUCH_ATTRIBUTE)) {
|
||||
+ if (result != LDAP_SUCCESS) {
|
||||
slapi_log_err(SLAPI_LOG_ERR, AUTOMEMBER_PLUGIN_SUBSYSTEM,
|
||||
"automember_update_member_value - Unable to delete \"%s\" as "
|
||||
"a \"%s\" value from group \"%s\" (%s).\n",
|
||||
diff --git a/ldap/servers/plugins/memberof/memberof.c b/ldap/servers/plugins/memberof/memberof.c
|
||||
index f79b083a9..f3dc7cf00 100644
|
||||
--- a/ldap/servers/plugins/memberof/memberof.c
|
||||
+++ b/ldap/servers/plugins/memberof/memberof.c
|
||||
@@ -1482,18 +1482,9 @@ memberof_del_dn_type_callback(Slapi_Entry *e, void *callback_data)
|
||||
mod.mod_op = LDAP_MOD_DELETE;
|
||||
mod.mod_type = ((memberof_del_dn_data *)callback_data)->type;
|
||||
mod.mod_values = val;
|
||||
-
|
||||
- slapi_modify_internal_set_pb_ext(
|
||||
- mod_pb, slapi_entry_get_sdn(e),
|
||||
- mods, 0, 0,
|
||||
- memberof_get_plugin_id(), SLAPI_OP_FLAG_BYPASS_REFERRALS);
|
||||
-
|
||||
- slapi_modify_internal_pb(mod_pb);
|
||||
-
|
||||
- slapi_pblock_get(mod_pb,
|
||||
- SLAPI_PLUGIN_INTOP_RESULT,
|
||||
- &rc);
|
||||
-
|
||||
+ /* Internal mod with error overrides for DEL/ADD */
|
||||
+ rc = slapi_single_modify_internal_override(mod_pb, slapi_entry_get_sdn(e), mods,
|
||||
+ memberof_get_plugin_id(), SLAPI_OP_FLAG_BYPASS_REFERRALS);
|
||||
slapi_pblock_destroy(mod_pb);
|
||||
|
||||
if (rc == LDAP_NO_SUCH_ATTRIBUTE && val[0] == NULL) {
|
||||
@@ -1966,6 +1957,7 @@ memberof_replace_dn_type_callback(Slapi_Entry *e, void *callback_data)
|
||||
|
||||
return rc;
|
||||
}
|
||||
+
|
||||
LDAPMod **
|
||||
my_copy_mods(LDAPMod **orig_mods)
|
||||
{
|
||||
@@ -2774,33 +2766,6 @@ memberof_modop_one_replace_r(Slapi_PBlock *pb, MemberOfConfig *config, int mod_o
|
||||
replace_mod.mod_values = replace_val;
|
||||
}
|
||||
rc = memberof_add_memberof_attr(mods, op_to, config->auto_add_oc);
|
||||
- if (rc == LDAP_NO_SUCH_ATTRIBUTE || rc == LDAP_TYPE_OR_VALUE_EXISTS) {
|
||||
- if (rc == LDAP_TYPE_OR_VALUE_EXISTS) {
|
||||
- /*
|
||||
- * For some reason the new modrdn value is present, so retry
|
||||
- * the delete by itself and ignore the add op by tweaking
|
||||
- * the mod array.
|
||||
- */
|
||||
- mods[1] = NULL;
|
||||
- rc = memberof_add_memberof_attr(mods, op_to, config->auto_add_oc);
|
||||
- } else {
|
||||
- /*
|
||||
- * The memberof value to be replaced does not exist so just
|
||||
- * add the new value. Shuffle the mod array to apply only
|
||||
- * the add operation.
|
||||
- */
|
||||
- mods[0] = mods[1];
|
||||
- mods[1] = NULL;
|
||||
- rc = memberof_add_memberof_attr(mods, op_to, config->auto_add_oc);
|
||||
- if (rc == LDAP_TYPE_OR_VALUE_EXISTS) {
|
||||
- /*
|
||||
- * The entry already has the expected memberOf value, no
|
||||
- * problem just return success.
|
||||
- */
|
||||
- rc = LDAP_SUCCESS;
|
||||
- }
|
||||
- }
|
||||
- }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4454,43 +4419,57 @@ memberof_add_memberof_attr(LDAPMod **mods, const char *dn, char *add_oc)
|
||||
Slapi_PBlock *mod_pb = NULL;
|
||||
int added_oc = 0;
|
||||
int rc = 0;
|
||||
+ LDAPMod *single_mod[2];
|
||||
|
||||
- while (1) {
|
||||
- mod_pb = slapi_pblock_new();
|
||||
- slapi_modify_internal_set_pb(
|
||||
- mod_pb, dn, mods, 0, 0,
|
||||
- memberof_get_plugin_id(), SLAPI_OP_FLAG_BYPASS_REFERRALS);
|
||||
- slapi_modify_internal_pb(mod_pb);
|
||||
-
|
||||
- slapi_pblock_get(mod_pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
|
||||
- if (rc == LDAP_OBJECT_CLASS_VIOLATION) {
|
||||
- if (!add_oc || added_oc) {
|
||||
- /*
|
||||
- * We aren't auto adding an objectclass, or we already
|
||||
- * added the objectclass, and we are still failing.
|
||||
- */
|
||||
+ if (!dn || !mods) {
|
||||
+ slapi_log_err(SLAPI_LOG_ERR, MEMBEROF_PLUGIN_SUBSYSTEM,
|
||||
+ "Invalid argument: %s%s is NULL\n",
|
||||
+ !dn ? "dn " : "",
|
||||
+ !mods ? "mods " : "");
|
||||
+ return LDAP_PARAM_ERROR;
|
||||
+ }
|
||||
+
|
||||
+
|
||||
+ mod_pb = slapi_pblock_new();
|
||||
+ /* Split multiple mods into individual mod operations */
|
||||
+ for (size_t i = 0; (mods != NULL) && (mods[i] != NULL); i++) {
|
||||
+ single_mod[0] = mods[i];
|
||||
+ single_mod[1] = NULL;
|
||||
+
|
||||
+ while (1) {
|
||||
+ slapi_pblock_init(mod_pb);
|
||||
+
|
||||
+ /* Internal mod with error overrides for DEL/ADD */
|
||||
+ rc = slapi_single_modify_internal_override(mod_pb, slapi_sdn_new_normdn_byref(dn), single_mod,
|
||||
+ memberof_get_plugin_id(), SLAPI_OP_FLAG_BYPASS_REFERRALS);
|
||||
+ if (rc == LDAP_OBJECT_CLASS_VIOLATION) {
|
||||
+ if (!add_oc || added_oc) {
|
||||
+ /*
|
||||
+ * We aren't auto adding an objectclass, or we already
|
||||
+ * added the objectclass, and we are still failing.
|
||||
+ */
|
||||
+ break;
|
||||
+ }
|
||||
+ rc = memberof_add_objectclass(add_oc, dn);
|
||||
+ slapi_log_err(SLAPI_LOG_WARNING, MEMBEROF_PLUGIN_SUBSYSTEM,
|
||||
+ "Entry %s - schema violation caught - repair operation %s\n",
|
||||
+ dn ? dn : "unknown",
|
||||
+ rc ? "failed" : "succeeded");
|
||||
+ if (rc) {
|
||||
+ /* Failed to add objectclass */
|
||||
+ rc = LDAP_OBJECT_CLASS_VIOLATION;
|
||||
+ break;
|
||||
+ }
|
||||
+ added_oc = 1;
|
||||
+ } else if (rc) {
|
||||
+ /* Some other fatal error */
|
||||
+ slapi_log_err(SLAPI_LOG_PLUGIN, MEMBEROF_PLUGIN_SUBSYSTEM,
|
||||
+ "memberof_add_memberof_attr - Internal modify failed. rc=%d\n", rc);
|
||||
break;
|
||||
- }
|
||||
- rc = memberof_add_objectclass(add_oc, dn);
|
||||
- slapi_log_err(SLAPI_LOG_WARNING, MEMBEROF_PLUGIN_SUBSYSTEM,
|
||||
- "Entry %s - schema violation caught - repair operation %s\n",
|
||||
- dn ? dn : "unknown",
|
||||
- rc ? "failed" : "succeeded");
|
||||
- if (rc) {
|
||||
- /* Failed to add objectclass */
|
||||
- rc = LDAP_OBJECT_CLASS_VIOLATION;
|
||||
+ } else {
|
||||
+ /* success */
|
||||
break;
|
||||
}
|
||||
- added_oc = 1;
|
||||
- slapi_pblock_destroy(mod_pb);
|
||||
- } else if (rc) {
|
||||
- /* Some other fatal error */
|
||||
- slapi_log_err(SLAPI_LOG_PLUGIN, MEMBEROF_PLUGIN_SUBSYSTEM,
|
||||
- "memberof_add_memberof_attr - Internal modify failed. rc=%d\n", rc);
|
||||
- break;
|
||||
- } else {
|
||||
- /* success */
|
||||
- break;
|
||||
}
|
||||
}
|
||||
slapi_pblock_destroy(mod_pb);
|
||||
diff --git a/ldap/servers/plugins/referint/referint.c b/ldap/servers/plugins/referint/referint.c
|
||||
index 28240c1f6..c5e259d8d 100644
|
||||
--- a/ldap/servers/plugins/referint/referint.c
|
||||
+++ b/ldap/servers/plugins/referint/referint.c
|
||||
@@ -711,19 +711,28 @@ static int
|
||||
_do_modify(Slapi_PBlock *mod_pb, Slapi_DN *entrySDN, LDAPMod **mods)
|
||||
{
|
||||
int rc = 0;
|
||||
+ LDAPMod *mod[2];
|
||||
|
||||
- slapi_pblock_init(mod_pb);
|
||||
+ /* Split multiple modifications into individual modify operations */
|
||||
+ for (size_t i = 0; (mods != NULL) && (mods[i] != NULL); i++) {
|
||||
+ mod[0] = mods[i];
|
||||
+ mod[1] = NULL;
|
||||
|
||||
- if (allow_repl) {
|
||||
- /* Must set as a replicated operation */
|
||||
- slapi_modify_internal_set_pb_ext(mod_pb, entrySDN, mods, NULL, NULL,
|
||||
- referint_plugin_identity, OP_FLAG_REPLICATED);
|
||||
- } else {
|
||||
- slapi_modify_internal_set_pb_ext(mod_pb, entrySDN, mods, NULL, NULL,
|
||||
- referint_plugin_identity, 0);
|
||||
+ slapi_pblock_init(mod_pb);
|
||||
+
|
||||
+ /* Do a single mod with error overrides for DEL/ADD */
|
||||
+ if (allow_repl) {
|
||||
+ rc = slapi_single_modify_internal_override(mod_pb, entrySDN, mod,
|
||||
+ referint_plugin_identity, OP_FLAG_REPLICATED);
|
||||
+ } else {
|
||||
+ rc = slapi_single_modify_internal_override(mod_pb, entrySDN, mod,
|
||||
+ referint_plugin_identity, 0);
|
||||
+ }
|
||||
+
|
||||
+ if (rc != LDAP_SUCCESS) {
|
||||
+ return rc;
|
||||
+ }
|
||||
}
|
||||
- slapi_modify_internal_pb(mod_pb);
|
||||
- slapi_pblock_get(mod_pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
|
||||
|
||||
return rc;
|
||||
}
|
||||
@@ -1033,7 +1042,6 @@ _update_all_per_mod(Slapi_DN *entrySDN, /* DN of the searched entry */
|
||||
/* (case 1) */
|
||||
slapi_mods_add_string(smods, LDAP_MOD_DELETE, attrName, sval);
|
||||
slapi_mods_add_string(smods, LDAP_MOD_ADD, attrName, newDN);
|
||||
-
|
||||
} else if (p) {
|
||||
/* (case 2) */
|
||||
slapi_mods_add_string(smods, LDAP_MOD_DELETE, attrName, sval);
|
||||
diff --git a/ldap/servers/slapd/modify.c b/ldap/servers/slapd/modify.c
|
||||
index 669bb104c..455eb63ec 100644
|
||||
--- a/ldap/servers/slapd/modify.c
|
||||
+++ b/ldap/servers/slapd/modify.c
|
||||
@@ -492,6 +492,57 @@ slapi_modify_internal_set_pb_ext(Slapi_PBlock *pb, const Slapi_DN *sdn, LDAPMod
|
||||
slapi_pblock_set(pb, SLAPI_PLUGIN_IDENTITY, plugin_identity);
|
||||
}
|
||||
|
||||
+/* Performs a single LDAP modify operation with error overrides.
|
||||
+ *
|
||||
+ * If specific errors occur, such as attempting to add an existing attribute or
|
||||
+ * delete a non-existent one, the function overrides the error and returns success:
|
||||
+ * - LDAP_MOD_ADD -> LDAP_TYPE_OR_VALUE_EXISTS (ignored)
|
||||
+ * - LDAP_MOD_DELETE -> LDAP_NO_SUCH_ATTRIBUTE (ignored)
|
||||
+ *
|
||||
+ * Any other errors encountered during the operation will be returned as-is.
|
||||
+ */
|
||||
+int
|
||||
+slapi_single_modify_internal_override(Slapi_PBlock *pb, const Slapi_DN *sdn, LDAPMod **mod, Slapi_ComponentId *plugin_id, int op_flags)
|
||||
+{
|
||||
+ int rc = 0;
|
||||
+ int result = 0;
|
||||
+ int result_reset = 0;
|
||||
+ int mod_op = 0;
|
||||
+
|
||||
+ if (!pb || !sdn || !mod || !mod[0]) {
|
||||
+ slapi_log_err(SLAPI_LOG_ERR, "slapi_single_modify_internal_override",
|
||||
+ "Invalid argument: %s%s%s%s is NULL\n",
|
||||
+ !pb ? "pb " : "",
|
||||
+ !sdn ? "sdn " : "",
|
||||
+ !mod ? "mod " : "",
|
||||
+ !mod[0] ? "mod[0] " : "");
|
||||
+
|
||||
+ return LDAP_PARAM_ERROR;
|
||||
+ }
|
||||
+
|
||||
+ slapi_modify_internal_set_pb_ext(pb, sdn, mod, NULL, NULL, plugin_id, op_flags);
|
||||
+ slapi_modify_internal_pb(pb);
|
||||
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &result);
|
||||
+
|
||||
+ if (result != LDAP_SUCCESS) {
|
||||
+ mod_op = mod[0]->mod_op & LDAP_MOD_OP;
|
||||
+ if ((mod_op == LDAP_MOD_ADD && result == LDAP_TYPE_OR_VALUE_EXISTS) ||
|
||||
+ (mod_op == LDAP_MOD_DELETE && result == LDAP_NO_SUCH_ATTRIBUTE)) {
|
||||
+ slapi_log_err(SLAPI_LOG_PLUGIN, "slapi_single_modify_internal_override",
|
||||
+ "Overriding return code - plugin:%s dn:%s mod_op:%d result:%d\n",
|
||||
+ plugin_id ? plugin_id->sci_component_name : "unknown",
|
||||
+ sdn ? sdn->udn : "unknown", mod_op, result);
|
||||
+
|
||||
+ slapi_pblock_set(pb, SLAPI_PLUGIN_INTOP_RESULT, &result_reset);
|
||||
+ rc = LDAP_SUCCESS;
|
||||
+ } else {
|
||||
+ rc = result;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ return rc;
|
||||
+}
|
||||
+
|
||||
/* Helper functions */
|
||||
|
||||
static int
|
||||
diff --git a/ldap/servers/slapd/slapi-plugin.h b/ldap/servers/slapd/slapi-plugin.h
|
||||
index 9fdcaccc8..a84a60c92 100644
|
||||
--- a/ldap/servers/slapd/slapi-plugin.h
|
||||
+++ b/ldap/servers/slapd/slapi-plugin.h
|
||||
@@ -5965,6 +5965,7 @@ void slapi_add_entry_internal_set_pb(Slapi_PBlock *pb, Slapi_Entry *e, LDAPContr
|
||||
int slapi_add_internal_set_pb(Slapi_PBlock *pb, const char *dn, LDAPMod **attrs, LDAPControl **controls, Slapi_ComponentId *plugin_identity, int operation_flags);
|
||||
void slapi_modify_internal_set_pb(Slapi_PBlock *pb, const char *dn, LDAPMod **mods, LDAPControl **controls, const char *uniqueid, Slapi_ComponentId *plugin_identity, int operation_flags);
|
||||
void slapi_modify_internal_set_pb_ext(Slapi_PBlock *pb, const Slapi_DN *sdn, LDAPMod **mods, LDAPControl **controls, const char *uniqueid, Slapi_ComponentId *plugin_identity, int operation_flags);
|
||||
+int slapi_single_modify_internal_override(Slapi_PBlock *pb, const Slapi_DN *sdn, LDAPMod **mod, Slapi_ComponentId *plugin_identity, int operation_flags);
|
||||
/**
|
||||
* Set \c Slapi_PBlock to perform modrdn/rename internally
|
||||
*
|
||||
--
|
||||
2.51.1
|
||||
|
||||
@ -1,415 +0,0 @@
|
||||
From ccaaaa31a86eb059315580249838d72e4a51bf8b Mon Sep 17 00:00:00 2001
|
||||
From: tbordaz <tbordaz@redhat.com>
|
||||
Date: Tue, 14 Jan 2025 18:12:56 +0100
|
||||
Subject: [PATCH] Issue 6470 - Some replication status data are reset upon a
|
||||
restart (#6471)
|
||||
|
||||
Bug description:
|
||||
The replication agreement contains operational attributes
|
||||
related to the total init: nsds5replicaLastInitStart,
|
||||
nsds5replicaLastInitEnd, nsds5replicaLastInitStatus.
|
||||
Those attributes are reset at restart
|
||||
|
||||
Fix description:
|
||||
When reading the replication agreement from config
|
||||
(agmt_new_from_entry) restore the attributes into
|
||||
the in-memory RA.
|
||||
Updates the RA config entry from the in-memory RA
|
||||
during shutdown/cleanallruv/enable_ra
|
||||
|
||||
fixes: #6470
|
||||
|
||||
Reviewed by: Simon Pichugin (Thanks !!)
|
||||
|
||||
(cherry picked from commit 90071a334517be523e498bded5b663c50c40ee3f)
|
||||
Signed-off-by: Masahiro Matsuya <mmatsuya@redhat.com>
|
||||
---
|
||||
.../suites/replication/single_master_test.py | 128 ++++++++++++++++
|
||||
ldap/servers/plugins/replication/repl5.h | 4 +
|
||||
ldap/servers/plugins/replication/repl5_agmt.c | 140 +++++++++++++++++-
|
||||
.../plugins/replication/repl5_agmtlist.c | 1 +
|
||||
.../replication/repl5_replica_config.c | 1 +
|
||||
.../plugins/replication/repl_globals.c | 3 +
|
||||
6 files changed, 273 insertions(+), 4 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/replication/single_master_test.py b/dirsrvtests/tests/suites/replication/single_master_test.py
|
||||
index e927e6cfd..f448d2342 100644
|
||||
--- a/dirsrvtests/tests/suites/replication/single_master_test.py
|
||||
+++ b/dirsrvtests/tests/suites/replication/single_master_test.py
|
||||
@@ -13,6 +13,7 @@ from lib389.utils import *
|
||||
from lib389.idm.user import UserAccounts, TEST_USER_PROPERTIES
|
||||
|
||||
from lib389.replica import ReplicationManager, Replicas
|
||||
+from lib389.agreement import Agreements
|
||||
from lib389.backend import Backends
|
||||
|
||||
from lib389.topologies import topology_m1c1 as topo_r # Replication
|
||||
@@ -154,6 +155,133 @@ def test_lastupdate_attr_before_init(topo_nr):
|
||||
json_obj = json.loads(json_status)
|
||||
log.debug("JSON status message: {}".format(json_obj))
|
||||
|
||||
+def test_total_init_operational_attr(topo_r):
|
||||
+ """Check that operation attributes nsds5replicaLastInitStatus
|
||||
+ nsds5replicaLastInitStart and nsds5replicaLastInitEnd
|
||||
+ are preserved between restart
|
||||
+
|
||||
+ :id: 6ba00bb1-87c0-47dd-86e0-ccf892b3985b
|
||||
+ :customerscenario: True
|
||||
+ :setup: Replication setup with supplier and consumer instances,
|
||||
+ test user on supplier
|
||||
+ :steps:
|
||||
+ 1. Check that user was replicated to consumer
|
||||
+ 2. Trigger a first total init
|
||||
+ 3. Check status/start/end values are set on the supplier
|
||||
+ 4. Restart supplier
|
||||
+ 5. Check previous status/start/end values are preserved
|
||||
+ 6. Trigger a second total init
|
||||
+ 7. Check status/start/end values are set on the supplier
|
||||
+ 8. Restart supplier
|
||||
+ 9. Check previous status/start/end values are preserved
|
||||
+ 10. Check status/start/end values are different between
|
||||
+ first and second total init
|
||||
+ :expectedresults:
|
||||
+ 1. The user should be replicated to consumer
|
||||
+ 2. Total init should be successful
|
||||
+ 3. It must exist a values
|
||||
+ 4. Operation should be successful
|
||||
+ 5. Check values are identical before/after restart
|
||||
+ 6. Total init should be successful
|
||||
+ 7. It must exist a values
|
||||
+ 8. Operation should be successful
|
||||
+ 9. Check values are identical before/after restart
|
||||
+ 10. values must differ between first/second total init
|
||||
+ """
|
||||
+
|
||||
+ supplier = topo_r.ms["supplier1"]
|
||||
+ consumer = topo_r.cs["consumer1"]
|
||||
+ repl = ReplicationManager(DEFAULT_SUFFIX)
|
||||
+
|
||||
+ # Create a test user
|
||||
+ m_users = UserAccounts(topo_r.ms["supplier1"], DEFAULT_SUFFIX)
|
||||
+ m_user = m_users.ensure_state(properties=TEST_USER_PROPERTIES)
|
||||
+ m_user.ensure_present('mail', 'testuser@redhat.com')
|
||||
+
|
||||
+ # Then check it is replicated
|
||||
+ log.info("Check that replication is working")
|
||||
+ repl.wait_for_replication(supplier, consumer)
|
||||
+ c_users = UserAccounts(topo_r.cs["consumer1"], DEFAULT_SUFFIX)
|
||||
+ c_user = c_users.get('testuser')
|
||||
+ assert c_user
|
||||
+
|
||||
+ # Retrieve the replication agreement S1->C1
|
||||
+ replica_supplier = Replicas(supplier).get(DEFAULT_SUFFIX)
|
||||
+ agmts_supplier = Agreements(supplier, replica_supplier.dn)
|
||||
+ supplier_consumer = None
|
||||
+ for agmt in agmts_supplier.list():
|
||||
+ if (agmt.get_attr_val_utf8('nsDS5ReplicaPort') == str(consumer.port) and
|
||||
+ agmt.get_attr_val_utf8('nsDS5ReplicaHost') == consumer.host):
|
||||
+ supplier_consumer = agmt
|
||||
+ break
|
||||
+ assert supplier_consumer
|
||||
+
|
||||
+ # Trigger a first total init and check that
|
||||
+ # start/end/status is updated AND preserved during a restart
|
||||
+ log.info("First total init")
|
||||
+ supplier_consumer.begin_reinit()
|
||||
+ (done, error) = supplier_consumer.wait_reinit()
|
||||
+ assert done is True
|
||||
+
|
||||
+ status_1 = supplier_consumer.get_attr_val_utf8("nsds5replicaLastInitStatus")
|
||||
+ assert status_1
|
||||
+
|
||||
+ initStart_1 = supplier_consumer.get_attr_val_utf8("nsds5replicaLastInitStart")
|
||||
+ assert initStart_1
|
||||
+
|
||||
+ initEnd_1 = supplier_consumer.get_attr_val_utf8("nsds5replicaLastInitEnd")
|
||||
+ assert initEnd_1
|
||||
+
|
||||
+ log.info("Check values from first total init are preserved")
|
||||
+ supplier.restart()
|
||||
+ post_restart_status_1 = supplier_consumer.get_attr_val_utf8("nsds5replicaLastInitStatus")
|
||||
+ assert post_restart_status_1
|
||||
+ assert post_restart_status_1 == status_1
|
||||
+
|
||||
+ post_restart_initStart_1 = supplier_consumer.get_attr_val_utf8("nsds5replicaLastInitStart")
|
||||
+ assert post_restart_initStart_1
|
||||
+ assert post_restart_initStart_1 == initStart_1
|
||||
+
|
||||
+ post_restart_initEnd_1 = supplier_consumer.get_attr_val_utf8("nsds5replicaLastInitEnd")
|
||||
+ assert post_restart_initEnd_1 == initEnd_1
|
||||
+
|
||||
+ # Trigger a second total init and check that
|
||||
+ # start/end/status is updated (differ from previous values)
|
||||
+ # AND new values are preserved during a restart
|
||||
+ time.sleep(1)
|
||||
+ log.info("Second total init")
|
||||
+ supplier_consumer.begin_reinit()
|
||||
+ (done, error) = supplier_consumer.wait_reinit()
|
||||
+ assert done is True
|
||||
+
|
||||
+ status_2 = supplier_consumer.get_attr_val_utf8("nsds5replicaLastInitStatus")
|
||||
+ assert status_2
|
||||
+
|
||||
+ initStart_2 = supplier_consumer.get_attr_val_utf8("nsds5replicaLastInitStart")
|
||||
+ assert initStart_2
|
||||
+
|
||||
+ initEnd_2 = supplier_consumer.get_attr_val_utf8("nsds5replicaLastInitEnd")
|
||||
+ assert initEnd_2
|
||||
+
|
||||
+ log.info("Check values from second total init are preserved")
|
||||
+ supplier.restart()
|
||||
+ post_restart_status_2 = supplier_consumer.get_attr_val_utf8("nsds5replicaLastInitStatus")
|
||||
+ assert post_restart_status_2
|
||||
+ assert post_restart_status_2 == status_2
|
||||
+
|
||||
+ post_restart_initStart_2 = supplier_consumer.get_attr_val_utf8("nsds5replicaLastInitStart")
|
||||
+ assert post_restart_initStart_2
|
||||
+ assert post_restart_initStart_2 == initStart_2
|
||||
+
|
||||
+ post_restart_initEnd_2 = supplier_consumer.get_attr_val_utf8("nsds5replicaLastInitEnd")
|
||||
+ assert post_restart_initEnd_2 == initEnd_2
|
||||
+
|
||||
+ # Check that values are updated by total init
|
||||
+ log.info("Check values from first/second total init are different")
|
||||
+ assert status_2 == status_1
|
||||
+ assert initStart_2 != initStart_1
|
||||
+ assert initEnd_2 != initEnd_1
|
||||
+
|
||||
if __name__ == '__main__':
|
||||
# Run isolated
|
||||
# -s for DEBUG mode
|
||||
diff --git a/ldap/servers/plugins/replication/repl5.h b/ldap/servers/plugins/replication/repl5.h
|
||||
index c2fbff8c0..65e2059e7 100644
|
||||
--- a/ldap/servers/plugins/replication/repl5.h
|
||||
+++ b/ldap/servers/plugins/replication/repl5.h
|
||||
@@ -165,6 +165,9 @@ extern const char *type_nsds5ReplicaBootstrapCredentials;
|
||||
extern const char *type_nsds5ReplicaBootstrapBindMethod;
|
||||
extern const char *type_nsds5ReplicaBootstrapTransportInfo;
|
||||
extern const char *type_replicaKeepAliveUpdateInterval;
|
||||
+extern const char *type_nsds5ReplicaLastInitStart;
|
||||
+extern const char *type_nsds5ReplicaLastInitEnd;
|
||||
+extern const char *type_nsds5ReplicaLastInitStatus;
|
||||
|
||||
/* Attribute names for windows replication agreements */
|
||||
extern const char *type_nsds7WindowsReplicaArea;
|
||||
@@ -430,6 +433,7 @@ void agmt_notify_change(Repl_Agmt *ra, Slapi_PBlock *pb);
|
||||
Object *agmt_get_consumer_ruv(Repl_Agmt *ra);
|
||||
ReplicaId agmt_get_consumer_rid(Repl_Agmt *ra, void *conn);
|
||||
int agmt_set_consumer_ruv(Repl_Agmt *ra, RUV *ruv);
|
||||
+void agmt_update_init_status(Repl_Agmt *ra);
|
||||
void agmt_update_consumer_ruv(Repl_Agmt *ra);
|
||||
CSN *agmt_get_consumer_schema_csn(Repl_Agmt *ra);
|
||||
void agmt_set_consumer_schema_csn(Repl_Agmt *ra, CSN *csn);
|
||||
diff --git a/ldap/servers/plugins/replication/repl5_agmt.c b/ldap/servers/plugins/replication/repl5_agmt.c
|
||||
index a71343dec..c3b8d298c 100644
|
||||
--- a/ldap/servers/plugins/replication/repl5_agmt.c
|
||||
+++ b/ldap/servers/plugins/replication/repl5_agmt.c
|
||||
@@ -56,6 +56,7 @@
|
||||
#include "repl5_prot_private.h"
|
||||
#include "cl5_api.h"
|
||||
#include "slapi-plugin.h"
|
||||
+#include "slap.h"
|
||||
|
||||
#define DEFAULT_TIMEOUT 120 /* (seconds) default outbound LDAP connection */
|
||||
#define DEFAULT_FLOWCONTROL_WINDOW 1000 /* #entries sent without acknowledgment */
|
||||
@@ -510,10 +511,33 @@ agmt_new_from_entry(Slapi_Entry *e)
|
||||
ra->last_update_status[0] = '\0';
|
||||
ra->update_in_progress = PR_FALSE;
|
||||
ra->stop_in_progress = PR_FALSE;
|
||||
- ra->last_init_end_time = 0UL;
|
||||
- ra->last_init_start_time = 0UL;
|
||||
- ra->last_init_status[0] = '\0';
|
||||
- ra->changecounters = (struct changecounter **)slapi_ch_calloc(MAX_NUM_OF_MASTERS + 1,
|
||||
+ val = (char *)slapi_entry_attr_get_ref(e, type_nsds5ReplicaLastInitEnd);
|
||||
+ if (val) {
|
||||
+ time_t init_end_time;
|
||||
+
|
||||
+ init_end_time = parse_genTime((char *) val);
|
||||
+ if (init_end_time == NO_TIME || init_end_time == SLAPD_END_TIME) {
|
||||
+ ra->last_init_end_time = 0UL;
|
||||
+ } else {
|
||||
+ ra->last_init_end_time = init_end_time;
|
||||
+ }
|
||||
+ }
|
||||
+ val = (char *)slapi_entry_attr_get_ref(e, type_nsds5ReplicaLastInitStart);
|
||||
+ if (val) {
|
||||
+ time_t init_start_time;
|
||||
+
|
||||
+ init_start_time = parse_genTime((char *) val);
|
||||
+ if (init_start_time == NO_TIME || init_start_time == SLAPD_END_TIME) {
|
||||
+ ra->last_init_start_time = 0UL;
|
||||
+ } else {
|
||||
+ ra->last_init_start_time = init_start_time;
|
||||
+ }
|
||||
+ }
|
||||
+ val = (char *)slapi_entry_attr_get_ref(e, type_nsds5ReplicaLastInitStatus);
|
||||
+ if (val) {
|
||||
+ strcpy(ra->last_init_status, val);
|
||||
+ }
|
||||
+ ra->changecounters = (struct changecounter **)slapi_ch_calloc(MAX_NUM_OF_SUPPLIERS + 1,
|
||||
sizeof(struct changecounter *));
|
||||
ra->num_changecounters = 0;
|
||||
ra->max_changecounters = MAX_NUM_OF_MASTERS;
|
||||
@@ -2504,6 +2528,113 @@ agmt_set_consumer_ruv(Repl_Agmt *ra, RUV *ruv)
|
||||
return 0;
|
||||
}
|
||||
|
||||
+void
|
||||
+agmt_update_init_status(Repl_Agmt *ra)
|
||||
+{
|
||||
+ int rc;
|
||||
+ Slapi_PBlock *pb;
|
||||
+ LDAPMod **mods;
|
||||
+ int nb_mods = 0;
|
||||
+ int mod_idx;
|
||||
+ Slapi_Mod smod_start_time = {0};
|
||||
+ Slapi_Mod smod_end_time = {0};
|
||||
+ Slapi_Mod smod_status = {0};
|
||||
+
|
||||
+ PR_ASSERT(ra);
|
||||
+ PR_Lock(ra->lock);
|
||||
+
|
||||
+ if (ra->last_init_start_time) {
|
||||
+ nb_mods++;
|
||||
+ }
|
||||
+ if (ra->last_init_end_time) {
|
||||
+ nb_mods++;
|
||||
+ }
|
||||
+ if (ra->last_init_status[0] != '\0') {
|
||||
+ nb_mods++;
|
||||
+ }
|
||||
+ if (nb_mods == 0) {
|
||||
+ /* shortcut. no need to go further */
|
||||
+ PR_Unlock(ra->lock);
|
||||
+ return;
|
||||
+ }
|
||||
+ mods = (LDAPMod **) slapi_ch_malloc((nb_mods + 1) * sizeof(LDAPMod *));
|
||||
+ mod_idx = 0;
|
||||
+ if (ra->last_init_start_time) {
|
||||
+ struct berval val;
|
||||
+ char *time_tmp = NULL;
|
||||
+ slapi_mod_init(&smod_start_time, 1);
|
||||
+ slapi_mod_set_type(&smod_start_time, type_nsds5ReplicaLastInitStart);
|
||||
+ slapi_mod_set_operation(&smod_start_time, LDAP_MOD_REPLACE | LDAP_MOD_BVALUES);
|
||||
+
|
||||
+ time_tmp = format_genTime(ra->last_init_start_time);
|
||||
+ val.bv_val = time_tmp;
|
||||
+ val.bv_len = strlen(time_tmp);
|
||||
+ slapi_mod_add_value(&smod_start_time, &val);
|
||||
+ slapi_ch_free((void **)&time_tmp);
|
||||
+ mods[mod_idx] = (LDAPMod *)slapi_mod_get_ldapmod_byref(&smod_start_time);
|
||||
+ mod_idx++;
|
||||
+ }
|
||||
+ if (ra->last_init_end_time) {
|
||||
+ struct berval val;
|
||||
+ char *time_tmp = NULL;
|
||||
+ slapi_mod_init(&smod_end_time, 1);
|
||||
+ slapi_mod_set_type(&smod_end_time, type_nsds5ReplicaLastInitEnd);
|
||||
+ slapi_mod_set_operation(&smod_end_time, LDAP_MOD_REPLACE | LDAP_MOD_BVALUES);
|
||||
+
|
||||
+ time_tmp = format_genTime(ra->last_init_end_time);
|
||||
+ val.bv_val = time_tmp;
|
||||
+ val.bv_len = strlen(time_tmp);
|
||||
+ slapi_mod_add_value(&smod_end_time, &val);
|
||||
+ slapi_ch_free((void **)&time_tmp);
|
||||
+ mods[mod_idx] = (LDAPMod *)slapi_mod_get_ldapmod_byref(&smod_end_time);
|
||||
+ mod_idx++;
|
||||
+ }
|
||||
+ if (ra->last_init_status[0] != '\0') {
|
||||
+ struct berval val;
|
||||
+ char *init_status = NULL;
|
||||
+ slapi_mod_init(&smod_status, 1);
|
||||
+ slapi_mod_set_type(&smod_status, type_nsds5ReplicaLastInitStatus);
|
||||
+ slapi_mod_set_operation(&smod_status, LDAP_MOD_REPLACE | LDAP_MOD_BVALUES);
|
||||
+
|
||||
+ init_status = slapi_ch_strdup(ra->last_init_status);
|
||||
+ val.bv_val = init_status;
|
||||
+ val.bv_len = strlen(init_status);
|
||||
+ slapi_mod_add_value(&smod_status, &val);
|
||||
+ slapi_ch_free((void **)&init_status);
|
||||
+ mods[mod_idx] = (LDAPMod *)slapi_mod_get_ldapmod_byref(&smod_status);
|
||||
+ mod_idx++;
|
||||
+ }
|
||||
+
|
||||
+ if (nb_mods) {
|
||||
+ /* it is ok to release the lock here because we are done with the agreement data.
|
||||
+ we have to do it before issuing the modify operation because it causes
|
||||
+ agmtlist_notify_all to be called which uses the same lock - hence the deadlock */
|
||||
+ PR_Unlock(ra->lock);
|
||||
+
|
||||
+ pb = slapi_pblock_new();
|
||||
+ mods[nb_mods] = NULL;
|
||||
+
|
||||
+ slapi_modify_internal_set_pb_ext(pb, ra->dn, mods, NULL, NULL,
|
||||
+ repl_get_plugin_identity(PLUGIN_MULTISUPPLIER_REPLICATION), 0);
|
||||
+ slapi_modify_internal_pb(pb);
|
||||
+
|
||||
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
|
||||
+ if (rc != LDAP_SUCCESS && rc != LDAP_NO_SUCH_ATTRIBUTE) {
|
||||
+ slapi_log_err(SLAPI_LOG_ERR, repl_plugin_name, "agmt_update_consumer_ruv - "
|
||||
+ "%s: agmt_update_consumer_ruv: "
|
||||
+ "failed to update consumer's RUV; LDAP error - %d\n",
|
||||
+ ra->long_name, rc);
|
||||
+ }
|
||||
+
|
||||
+ slapi_pblock_destroy(pb);
|
||||
+ } else {
|
||||
+ PR_Unlock(ra->lock);
|
||||
+ }
|
||||
+ slapi_mod_done(&smod_start_time);
|
||||
+ slapi_mod_done(&smod_end_time);
|
||||
+ slapi_mod_done(&smod_status);
|
||||
+}
|
||||
+
|
||||
void
|
||||
agmt_update_consumer_ruv(Repl_Agmt *ra)
|
||||
{
|
||||
@@ -3123,6 +3254,7 @@ agmt_set_enabled_from_entry(Repl_Agmt *ra, Slapi_Entry *e, char *returntext)
|
||||
PR_Unlock(ra->lock);
|
||||
agmt_stop(ra);
|
||||
agmt_update_consumer_ruv(ra);
|
||||
+ agmt_update_init_status(ra);
|
||||
agmt_set_last_update_status(ra, 0, 0, "agreement disabled");
|
||||
return rc;
|
||||
}
|
||||
diff --git a/ldap/servers/plugins/replication/repl5_agmtlist.c b/ldap/servers/plugins/replication/repl5_agmtlist.c
|
||||
index 18b641f8c..e3b1e814c 100644
|
||||
--- a/ldap/servers/plugins/replication/repl5_agmtlist.c
|
||||
+++ b/ldap/servers/plugins/replication/repl5_agmtlist.c
|
||||
@@ -782,6 +782,7 @@ agmtlist_shutdown()
|
||||
ra = (Repl_Agmt *)object_get_data(ro);
|
||||
agmt_stop(ra);
|
||||
agmt_update_consumer_ruv(ra);
|
||||
+ agmt_update_init_status(ra);
|
||||
next_ro = objset_next_obj(agmt_set, ro);
|
||||
/* Object ro was released in objset_next_obj,
|
||||
* but the address ro can be still used to remove ro from objset. */
|
||||
diff --git a/ldap/servers/plugins/replication/repl5_replica_config.c b/ldap/servers/plugins/replication/repl5_replica_config.c
|
||||
index aea2cf506..8cc7423bf 100644
|
||||
--- a/ldap/servers/plugins/replication/repl5_replica_config.c
|
||||
+++ b/ldap/servers/plugins/replication/repl5_replica_config.c
|
||||
@@ -2006,6 +2006,7 @@ clean_agmts(cleanruv_data *data)
|
||||
cleanruv_log(data->task, data->rid, CLEANALLRUV_ID, SLAPI_LOG_INFO, "Cleaning agmt...");
|
||||
agmt_stop(agmt);
|
||||
agmt_update_consumer_ruv(agmt);
|
||||
+ agmt_update_init_status(agmt);
|
||||
agmt_start(agmt);
|
||||
agmt_obj = agmtlist_get_next_agreement_for_replica(data->replica, agmt_obj);
|
||||
}
|
||||
diff --git a/ldap/servers/plugins/replication/repl_globals.c b/ldap/servers/plugins/replication/repl_globals.c
|
||||
index 797ca957f..e6b89c33b 100644
|
||||
--- a/ldap/servers/plugins/replication/repl_globals.c
|
||||
+++ b/ldap/servers/plugins/replication/repl_globals.c
|
||||
@@ -118,6 +118,9 @@ const char *type_nsds5ReplicaBootstrapBindDN = "nsds5ReplicaBootstrapBindDN";
|
||||
const char *type_nsds5ReplicaBootstrapCredentials = "nsds5ReplicaBootstrapCredentials";
|
||||
const char *type_nsds5ReplicaBootstrapBindMethod = "nsds5ReplicaBootstrapBindMethod";
|
||||
const char *type_nsds5ReplicaBootstrapTransportInfo = "nsds5ReplicaBootstrapTransportInfo";
|
||||
+const char *type_nsds5ReplicaLastInitStart = "nsds5replicaLastInitStart";
|
||||
+const char *type_nsds5ReplicaLastInitEnd = "nsds5replicaLastInitEnd";
|
||||
+const char *type_nsds5ReplicaLastInitStatus = "nsds5replicaLastInitStatus";
|
||||
|
||||
/* windows sync specific attributes */
|
||||
const char *type_nsds7WindowsReplicaArea = "nsds7WindowsReplicaSubtree";
|
||||
--
|
||||
2.51.1
|
||||
|
||||
@ -1,671 +0,0 @@
|
||||
From dfb7e19fdcefe4af683a235ea7113956248571e3 Mon Sep 17 00:00:00 2001
|
||||
From: tbordaz <tbordaz@redhat.com>
|
||||
Date: Thu, 17 Nov 2022 14:21:17 +0100
|
||||
Subject: [PATCH] Issue 3729 - RFE Extend log of operations statistics in
|
||||
access log (#5508)
|
||||
|
||||
Bug description:
|
||||
Create a per operation framework to collect/display
|
||||
statistics about internal ressource consumption
|
||||
|
||||
Fix description:
|
||||
|
||||
The fix contains 2 parts
|
||||
The framework, that registers a per operation object extension
|
||||
(op_stat_init). The extension is used to store/retrieve
|
||||
collected statistics.
|
||||
To reduce the impact of collecting/logging it uses a toggle
|
||||
with config attribute 'nsslapd-statlog-level' that is a bit mask.
|
||||
So that data are collected and logged only if the appropriate
|
||||
statistic level is set.
|
||||
|
||||
An exemple of statistic level regarding indexes fetching
|
||||
during the evaluation of a search filter.
|
||||
it is implemented in filterindex.c (store) and result.c (retrieve/log).
|
||||
This path uses LDAP_STAT_READ_INDEX=0x1.
|
||||
For LDAP_STAT_READ_INDEX, the collected data are:
|
||||
- for each key (attribute, type, value) the number of
|
||||
IDs
|
||||
- the duration to fetch all the values
|
||||
|
||||
design https://www.port389.org/docs/389ds/design/log-operation-stats.html
|
||||
relates: #3729
|
||||
|
||||
Reviewed by: Pierre Rogier, Mark Reynolds (thanks !)
|
||||
|
||||
(cherry picked from commit a480d2cbfa2b1325f44ab3e1c393c5ee348b388e)
|
||||
Signed-off-by: Masahiro Matsuya <mmatsuya@redhat.com>
|
||||
---
|
||||
.../tests/suites/ds_logs/ds_logs_test.py | 73 ++++++++++++++++
|
||||
ldap/servers/slapd/back-ldbm/filterindex.c | 49 +++++++++++
|
||||
ldap/servers/slapd/libglobs.c | 48 +++++++++++
|
||||
ldap/servers/slapd/log.c | 26 ++++++
|
||||
ldap/servers/slapd/log.h | 1 +
|
||||
ldap/servers/slapd/main.c | 1 +
|
||||
ldap/servers/slapd/operation.c | 86 +++++++++++++++++++
|
||||
ldap/servers/slapd/proto-slap.h | 8 ++
|
||||
ldap/servers/slapd/result.c | 64 ++++++++++++++
|
||||
ldap/servers/slapd/slap.h | 4 +
|
||||
ldap/servers/slapd/slapi-private.h | 27 ++++++
|
||||
11 files changed, 387 insertions(+)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/ds_logs/ds_logs_test.py b/dirsrvtests/tests/suites/ds_logs/ds_logs_test.py
|
||||
index 84d721756..43288f67f 100644
|
||||
--- a/dirsrvtests/tests/suites/ds_logs/ds_logs_test.py
|
||||
+++ b/dirsrvtests/tests/suites/ds_logs/ds_logs_test.py
|
||||
@@ -27,6 +27,7 @@ from lib389.idm.group import Groups
|
||||
from lib389.idm.organizationalunit import OrganizationalUnits
|
||||
from lib389._constants import DEFAULT_SUFFIX, LOG_ACCESS_LEVEL, PASSWORD
|
||||
from lib389.utils import ds_is_older, ds_is_newer
|
||||
+from lib389.dseldif import DSEldif
|
||||
import ldap
|
||||
import glob
|
||||
import re
|
||||
@@ -1250,6 +1251,78 @@ def test_missing_backend_suffix(topology_st, request):
|
||||
|
||||
request.addfinalizer(fin)
|
||||
|
||||
+def test_stat_index(topology_st, request):
|
||||
+ """Testing nsslapd-statlog-level with indexing statistics
|
||||
+
|
||||
+ :id: fcabab05-f000-468c-8eb4-02ce3c39c902
|
||||
+ :setup: Standalone instance
|
||||
+ :steps:
|
||||
+ 1. Check that nsslapd-statlog-level is 0 (default)
|
||||
+ 2. Create 20 users with 'cn' starting with 'user\_'
|
||||
+ 3. Check there is no statistic record in the access log with ADD
|
||||
+ 4. Check there is no statistic record in the access log with SRCH
|
||||
+ 5. Set nsslapd-statlog-level=LDAP_STAT_READ_INDEX (0x1) to get
|
||||
+ statistics when reading indexes
|
||||
+ 6. Check there is statistic records in access log with SRCH
|
||||
+ :expectedresults:
|
||||
+ 1. This should pass
|
||||
+ 2. This should pass
|
||||
+ 3. This should pass
|
||||
+ 4. This should pass
|
||||
+ 5. This should pass
|
||||
+ 6. This should pass
|
||||
+ """
|
||||
+ topology_st.standalone.start()
|
||||
+
|
||||
+ # Step 1
|
||||
+ log.info("Assert nsslapd-statlog-level is by default 0")
|
||||
+ assert topology_st.standalone.config.get_attr_val_int("nsslapd-statlog-level") == 0
|
||||
+
|
||||
+ # Step 2
|
||||
+ users = UserAccounts(topology_st.standalone, DEFAULT_SUFFIX)
|
||||
+ users_set = []
|
||||
+ log.info('Adding 20 users')
|
||||
+ for i in range(20):
|
||||
+ name = 'user_%d' % i
|
||||
+ last_user = users.create(properties={
|
||||
+ 'uid': name,
|
||||
+ 'sn': name,
|
||||
+ 'cn': name,
|
||||
+ 'uidNumber': '1000',
|
||||
+ 'gidNumber': '1000',
|
||||
+ 'homeDirectory': '/home/%s' % name,
|
||||
+ 'mail': '%s@example.com' % name,
|
||||
+ 'userpassword': 'pass%s' % name,
|
||||
+ })
|
||||
+ users_set.append(last_user)
|
||||
+
|
||||
+ # Step 3
|
||||
+ assert not topology_st.standalone.ds_access_log.match('.*STAT read index.*')
|
||||
+
|
||||
+ # Step 4
|
||||
+ entries = topology_st.standalone.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, "cn=user_*")
|
||||
+ assert not topology_st.standalone.ds_access_log.match('.*STAT read index.*')
|
||||
+
|
||||
+ # Step 5
|
||||
+ log.info("Set nsslapd-statlog-level: 1 to enable indexing statistics")
|
||||
+ topology_st.standalone.config.set("nsslapd-statlog-level", "1")
|
||||
+
|
||||
+ # Step 6
|
||||
+ entries = topology_st.standalone.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, "cn=user_*")
|
||||
+ topology_st.standalone.stop()
|
||||
+ assert topology_st.standalone.ds_access_log.match('.*STAT read index.*')
|
||||
+ assert topology_st.standalone.ds_access_log.match('.*STAT read index: attribute.*')
|
||||
+ assert topology_st.standalone.ds_access_log.match('.*STAT read index: duration.*')
|
||||
+ topology_st.standalone.start()
|
||||
+
|
||||
+ def fin():
|
||||
+ log.info('Deleting users')
|
||||
+ for user in users_set:
|
||||
+ user.delete()
|
||||
+ topology_st.standalone.config.set("nsslapd-statlog-level", "0")
|
||||
+
|
||||
+ request.addfinalizer(fin)
|
||||
+
|
||||
if __name__ == '__main__':
|
||||
# Run isolated
|
||||
# -s for DEBUG mode
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/filterindex.c b/ldap/servers/slapd/back-ldbm/filterindex.c
|
||||
index 8a79848c3..30550dde7 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/filterindex.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/filterindex.c
|
||||
@@ -1040,13 +1040,57 @@ keys2idl(
|
||||
int allidslimit)
|
||||
{
|
||||
IDList *idl = NULL;
|
||||
+ Op_stat *op_stat;
|
||||
+ PRBool collect_stat = PR_FALSE;
|
||||
|
||||
slapi_log_err(SLAPI_LOG_TRACE, "keys2idl", "=> type %s indextype %s\n", type, indextype);
|
||||
+
|
||||
+ /* Before reading the index take the start time */
|
||||
+ if (LDAP_STAT_READ_INDEX & config_get_statlog_level()) {
|
||||
+ op_stat = op_stat_get_operation_extension(pb);
|
||||
+ if (op_stat->search_stat) {
|
||||
+ collect_stat = PR_TRUE;
|
||||
+ clock_gettime(CLOCK_MONOTONIC, &(op_stat->search_stat->keys_lookup_start));
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
for (uint32_t i = 0; ivals[i] != NULL; i++) {
|
||||
IDList *idl2 = NULL;
|
||||
+ struct component_keys_lookup *key_stat;
|
||||
+ int key_len;
|
||||
|
||||
idl2 = index_read_ext_allids(pb, be, type, indextype, slapi_value_get_berval(ivals[i]), txn, err, unindexed, allidslimit);
|
||||
+ if (collect_stat) {
|
||||
+ /* gather the index lookup statistics */
|
||||
+ key_stat = (struct component_keys_lookup *) slapi_ch_calloc(1, sizeof (struct component_keys_lookup));
|
||||
+
|
||||
+ /* indextype e.g. "eq" or "sub" (see index.c) */
|
||||
+ if (indextype) {
|
||||
+ key_stat->index_type = slapi_ch_strdup(indextype);
|
||||
+ }
|
||||
+ /* key value e.g. '^st' or 'smith'*/
|
||||
+ key_len = slapi_value_get_length(ivals[i]);
|
||||
+ if (key_len) {
|
||||
+ key_stat->key = (char *) slapi_ch_calloc(1, key_len + 1);
|
||||
+ memcpy(key_stat->key, slapi_value_get_string(ivals[i]), key_len);
|
||||
+ }
|
||||
|
||||
+ /* attribute name e.g. 'uid' */
|
||||
+ if (type) {
|
||||
+ key_stat->attribute_type = slapi_ch_strdup(type);
|
||||
+ }
|
||||
+
|
||||
+ /* Number of lookup IDs with the key */
|
||||
+ key_stat->id_lookup_cnt = idl2 ? idl2->b_nids : 0;
|
||||
+ if (op_stat->search_stat->keys_lookup) {
|
||||
+ /* it already exist key stat. add key_stat at the head */
|
||||
+ key_stat->next = op_stat->search_stat->keys_lookup;
|
||||
+ } else {
|
||||
+ /* this is the first key stat record */
|
||||
+ key_stat->next = NULL;
|
||||
+ }
|
||||
+ op_stat->search_stat->keys_lookup = key_stat;
|
||||
+ }
|
||||
#ifdef LDAP_ERROR_LOGGING
|
||||
/* XXX if ( slapd_ldap_debug & LDAP_DEBUG_TRACE ) { XXX */
|
||||
{
|
||||
@@ -1080,5 +1124,10 @@ keys2idl(
|
||||
}
|
||||
}
|
||||
|
||||
+ /* All the keys have been fetch, time to take the completion time */
|
||||
+ if (collect_stat) {
|
||||
+ clock_gettime(CLOCK_MONOTONIC, &(op_stat->search_stat->keys_lookup_end));
|
||||
+ }
|
||||
+
|
||||
return (idl);
|
||||
}
|
||||
diff --git a/ldap/servers/slapd/libglobs.c b/ldap/servers/slapd/libglobs.c
|
||||
index 2097ab93c..99b2c5d8e 100644
|
||||
--- a/ldap/servers/slapd/libglobs.c
|
||||
+++ b/ldap/servers/slapd/libglobs.c
|
||||
@@ -712,6 +712,10 @@ static struct config_get_and_set
|
||||
NULL, 0,
|
||||
(void **)&global_slapdFrontendConfig.accessloglevel,
|
||||
CONFIG_INT, NULL, SLAPD_DEFAULT_ACCESSLOG_LEVEL_STR, NULL},
|
||||
+ {CONFIG_STATLOGLEVEL_ATTRIBUTE, config_set_statlog_level,
|
||||
+ NULL, 0,
|
||||
+ (void **)&global_slapdFrontendConfig.statloglevel,
|
||||
+ CONFIG_INT, NULL, SLAPD_DEFAULT_STATLOG_LEVEL, NULL},
|
||||
{CONFIG_ERRORLOG_LOGROTATIONTIMEUNIT_ATTRIBUTE, NULL,
|
||||
log_set_rotationtimeunit, SLAPD_ERROR_LOG,
|
||||
(void **)&global_slapdFrontendConfig.errorlog_rotationunit,
|
||||
@@ -1748,6 +1752,7 @@ FrontendConfig_init(void)
|
||||
cfg->accessloglevel = SLAPD_DEFAULT_ACCESSLOG_LEVEL;
|
||||
init_accesslogbuffering = cfg->accesslogbuffering = LDAP_ON;
|
||||
init_csnlogging = cfg->csnlogging = LDAP_ON;
|
||||
+ cfg->statloglevel = SLAPD_DEFAULT_STATLOG_LEVEL;
|
||||
|
||||
init_errorlog_logging_enabled = cfg->errorlog_logging_enabled = LDAP_ON;
|
||||
init_external_libs_debug_enabled = cfg->external_libs_debug_enabled = LDAP_OFF;
|
||||
@@ -5382,6 +5387,38 @@ config_set_accesslog_level(const char *attrname, char *value, char *errorbuf, in
|
||||
return retVal;
|
||||
}
|
||||
|
||||
+int
|
||||
+config_set_statlog_level(const char *attrname, char *value, char *errorbuf, int apply)
|
||||
+{
|
||||
+ int retVal = LDAP_SUCCESS;
|
||||
+ long level = 0;
|
||||
+ char *endp = NULL;
|
||||
+
|
||||
+ slapdFrontendConfig_t *slapdFrontendConfig = getFrontendConfig();
|
||||
+
|
||||
+ if (config_value_is_null(attrname, value, errorbuf, 1)) {
|
||||
+ return LDAP_OPERATIONS_ERROR;
|
||||
+ }
|
||||
+
|
||||
+ errno = 0;
|
||||
+ level = strtol(value, &endp, 10);
|
||||
+
|
||||
+ if (*endp != '\0' || errno == ERANGE || level < 0) {
|
||||
+ slapi_create_errormsg(errorbuf, SLAPI_DSE_RETURNTEXT_SIZE, "%s: stat log level \"%s\" is invalid,"
|
||||
+ " access log level must range from 0 to %lld",
|
||||
+ attrname, value, (long long int)LONG_MAX);
|
||||
+ retVal = LDAP_OPERATIONS_ERROR;
|
||||
+ return retVal;
|
||||
+ }
|
||||
+
|
||||
+ if (apply) {
|
||||
+ CFG_LOCK_WRITE(slapdFrontendConfig);
|
||||
+ g_set_statlog_level(level);
|
||||
+ slapdFrontendConfig->statloglevel = level;
|
||||
+ CFG_UNLOCK_WRITE(slapdFrontendConfig);
|
||||
+ }
|
||||
+ return retVal;
|
||||
+}
|
||||
/* set the referral-mode url (which puts us into referral mode) */
|
||||
int
|
||||
config_set_referral_mode(const char *attrname __attribute__((unused)), char *url, char *errorbuf, int apply)
|
||||
@@ -6612,6 +6649,17 @@ config_get_accesslog_level()
|
||||
return retVal;
|
||||
}
|
||||
|
||||
+int
|
||||
+config_get_statlog_level()
|
||||
+{
|
||||
+ slapdFrontendConfig_t *slapdFrontendConfig = getFrontendConfig();
|
||||
+ int retVal;
|
||||
+
|
||||
+ retVal = slapdFrontendConfig->statloglevel;
|
||||
+
|
||||
+ return retVal;
|
||||
+}
|
||||
+
|
||||
/* return integer -- don't worry about locking similar to config_check_referral_mode
|
||||
below */
|
||||
|
||||
diff --git a/ldap/servers/slapd/log.c b/ldap/servers/slapd/log.c
|
||||
index 8074735e2..837a9c6fd 100644
|
||||
--- a/ldap/servers/slapd/log.c
|
||||
+++ b/ldap/servers/slapd/log.c
|
||||
@@ -233,6 +233,17 @@ g_set_accesslog_level(int val)
|
||||
LOG_ACCESS_UNLOCK_WRITE();
|
||||
}
|
||||
|
||||
+/******************************************************************************
|
||||
+* Set the stat level
|
||||
+******************************************************************************/
|
||||
+void
|
||||
+g_set_statlog_level(int val)
|
||||
+{
|
||||
+ LOG_ACCESS_LOCK_WRITE();
|
||||
+ loginfo.log_access_stat_level = val;
|
||||
+ LOG_ACCESS_UNLOCK_WRITE();
|
||||
+}
|
||||
+
|
||||
/******************************************************************************
|
||||
* Set whether the process is alive or dead
|
||||
* If it is detached, then we write the error in 'stderr'
|
||||
@@ -283,6 +294,7 @@ g_log_init()
|
||||
if ((loginfo.log_access_buffer->lock = PR_NewLock()) == NULL) {
|
||||
exit(-1);
|
||||
}
|
||||
+ loginfo.log_access_stat_level = cfg->statloglevel;
|
||||
|
||||
/* ERROR LOG */
|
||||
loginfo.log_error_state = cfg->errorlog_logging_enabled;
|
||||
@@ -2640,7 +2652,21 @@ vslapd_log_access(char *fmt, va_list ap)
|
||||
|
||||
return (rc);
|
||||
}
|
||||
+int
|
||||
+slapi_log_stat(int loglevel, const char *fmt, ...)
|
||||
+{
|
||||
+ char buf[2048];
|
||||
+ va_list args;
|
||||
+ int rc = LDAP_SUCCESS;
|
||||
|
||||
+ if (loglevel & loginfo.log_access_stat_level) {
|
||||
+ va_start(args, fmt);
|
||||
+ PR_vsnprintf(buf, sizeof(buf), fmt, args);
|
||||
+ rc = slapi_log_access(LDAP_DEBUG_STATS, "%s", buf);
|
||||
+ va_end(args);
|
||||
+ }
|
||||
+ return rc;
|
||||
+}
|
||||
int
|
||||
slapi_log_access(int level,
|
||||
char *fmt,
|
||||
diff --git a/ldap/servers/slapd/log.h b/ldap/servers/slapd/log.h
|
||||
index 9fb4e7425..6ac37bd29 100644
|
||||
--- a/ldap/servers/slapd/log.h
|
||||
+++ b/ldap/servers/slapd/log.h
|
||||
@@ -120,6 +120,7 @@ struct logging_opts
|
||||
int log_access_exptime; /* time */
|
||||
int log_access_exptimeunit; /* unit time */
|
||||
int log_access_exptime_secs; /* time in secs */
|
||||
+ int log_access_stat_level; /* statistics level in access log file */
|
||||
|
||||
int log_access_level; /* access log level */
|
||||
char *log_access_file; /* access log file path */
|
||||
diff --git a/ldap/servers/slapd/main.c b/ldap/servers/slapd/main.c
|
||||
index ac45c85d1..9b5b845cb 100644
|
||||
--- a/ldap/servers/slapd/main.c
|
||||
+++ b/ldap/servers/slapd/main.c
|
||||
@@ -1040,6 +1040,7 @@ main(int argc, char **argv)
|
||||
* changes are replicated as soon as the replication plugin is started.
|
||||
*/
|
||||
pw_exp_init();
|
||||
+ op_stat_init();
|
||||
|
||||
plugin_print_lists();
|
||||
plugin_startall(argc, argv, NULL /* specific plugin list */);
|
||||
diff --git a/ldap/servers/slapd/operation.c b/ldap/servers/slapd/operation.c
|
||||
index 4dd3481c7..dacd1838f 100644
|
||||
--- a/ldap/servers/slapd/operation.c
|
||||
+++ b/ldap/servers/slapd/operation.c
|
||||
@@ -652,6 +652,92 @@ slapi_operation_time_expiry(Slapi_Operation *o, time_t timeout, struct timespec
|
||||
slapi_timespec_expire_rel(timeout, &(o->o_hr_time_rel), expiry);
|
||||
}
|
||||
|
||||
+
|
||||
+/*
|
||||
+ * Operation extension for operation statistics
|
||||
+ */
|
||||
+static int op_stat_objtype = -1;
|
||||
+static int op_stat_handle = -1;
|
||||
+
|
||||
+Op_stat *
|
||||
+op_stat_get_operation_extension(Slapi_PBlock *pb)
|
||||
+{
|
||||
+ Slapi_Operation *op;
|
||||
+
|
||||
+ slapi_pblock_get(pb, SLAPI_OPERATION, &op);
|
||||
+ return (Op_stat *)slapi_get_object_extension(op_stat_objtype,
|
||||
+ op, op_stat_handle);
|
||||
+}
|
||||
+
|
||||
+void
|
||||
+op_stat_set_operation_extension(Slapi_PBlock *pb, Op_stat *op_stat)
|
||||
+{
|
||||
+ Slapi_Operation *op;
|
||||
+
|
||||
+ slapi_pblock_get(pb, SLAPI_OPERATION, &op);
|
||||
+ slapi_set_object_extension(op_stat_objtype, op,
|
||||
+ op_stat_handle, (void *)op_stat);
|
||||
+}
|
||||
+
|
||||
+/*
|
||||
+ * constructor for the operation object extension.
|
||||
+ */
|
||||
+static void *
|
||||
+op_stat_constructor(void *object __attribute__((unused)), void *parent __attribute__((unused)))
|
||||
+{
|
||||
+ Op_stat *op_statp = NULL;
|
||||
+ op_statp = (Op_stat *)slapi_ch_calloc(1, sizeof(Op_stat));
|
||||
+ op_statp->search_stat = (Op_search_stat *)slapi_ch_calloc(1, sizeof(Op_search_stat));
|
||||
+
|
||||
+ return op_statp;
|
||||
+}
|
||||
+/*
|
||||
+ * destructor for the operation object extension.
|
||||
+ */
|
||||
+static void
|
||||
+op_stat_destructor(void *extension, void *object __attribute__((unused)), void *parent __attribute__((unused)))
|
||||
+{
|
||||
+ Op_stat *op_statp = (Op_stat *)extension;
|
||||
+
|
||||
+ if (NULL == op_statp) {
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ if (op_statp->search_stat) {
|
||||
+ struct component_keys_lookup *keys, *next;
|
||||
+
|
||||
+ /* free all the individual key counter */
|
||||
+ keys = op_statp->search_stat->keys_lookup;
|
||||
+ while (keys) {
|
||||
+ next = keys->next;
|
||||
+ slapi_ch_free_string(&keys->attribute_type);
|
||||
+ slapi_ch_free_string(&keys->key);
|
||||
+ slapi_ch_free_string(&keys->index_type);
|
||||
+ slapi_ch_free((void **) &keys);
|
||||
+ keys = next;
|
||||
+ }
|
||||
+ slapi_ch_free((void **) &op_statp->search_stat);
|
||||
+ }
|
||||
+ slapi_ch_free((void **) &op_statp);
|
||||
+}
|
||||
+
|
||||
+#define SLAPI_OP_STAT_MODULE "Module to collect operation stat"
|
||||
+/* Called once from main */
|
||||
+void
|
||||
+op_stat_init(void)
|
||||
+{
|
||||
+ if (slapi_register_object_extension(SLAPI_OP_STAT_MODULE,
|
||||
+ SLAPI_EXT_OPERATION,
|
||||
+ op_stat_constructor,
|
||||
+ op_stat_destructor,
|
||||
+ &op_stat_objtype,
|
||||
+ &op_stat_handle) != 0) {
|
||||
+ slapi_log_err(SLAPI_LOG_ERR, "op_stat_init",
|
||||
+ "slapi_register_object_extension failed; "
|
||||
+ "operation statistics is not enabled\n");
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
/* Set the time the operation actually started */
|
||||
void
|
||||
slapi_operation_set_time_started(Slapi_Operation *o)
|
||||
diff --git a/ldap/servers/slapd/proto-slap.h b/ldap/servers/slapd/proto-slap.h
|
||||
index 410a3c5fe..3a049ee76 100644
|
||||
--- a/ldap/servers/slapd/proto-slap.h
|
||||
+++ b/ldap/servers/slapd/proto-slap.h
|
||||
@@ -291,6 +291,7 @@ int config_set_defaultreferral(const char *attrname, struct berval **value, char
|
||||
int config_set_timelimit(const char *attrname, char *value, char *errorbuf, int apply);
|
||||
int config_set_errorlog_level(const char *attrname, char *value, char *errorbuf, int apply);
|
||||
int config_set_accesslog_level(const char *attrname, char *value, char *errorbuf, int apply);
|
||||
+int config_set_statlog_level(const char *attrname, char *value, char *errorbuf, int apply);
|
||||
int config_set_auditlog(const char *attrname, char *value, char *errorbuf, int apply);
|
||||
int config_set_auditfaillog(const char *attrname, char *value, char *errorbuf, int apply);
|
||||
int config_set_userat(const char *attrname, char *value, char *errorbuf, int apply);
|
||||
@@ -510,6 +511,7 @@ long long config_get_pw_minage(void);
|
||||
long long config_get_pw_warning(void);
|
||||
int config_get_errorlog_level(void);
|
||||
int config_get_accesslog_level(void);
|
||||
+int config_get_statlog_level();
|
||||
int config_get_auditlog_logging_enabled(void);
|
||||
int config_get_auditfaillog_logging_enabled(void);
|
||||
char *config_get_auditlog_display_attrs(void);
|
||||
@@ -815,10 +817,15 @@ int lock_fclose(FILE *fp, FILE *lfp);
|
||||
#define LDAP_DEBUG_INFO 0x08000000 /* 134217728 */
|
||||
#define LDAP_DEBUG_DEBUG 0x10000000 /* 268435456 */
|
||||
#define LDAP_DEBUG_ALL_LEVELS 0xFFFFFF
|
||||
+
|
||||
+#define LDAP_STAT_READ_INDEX 0x00000001 /* 1 */
|
||||
+#define LDAP_STAT_FREE_1 0x00000002 /* 2 */
|
||||
+
|
||||
extern int slapd_ldap_debug;
|
||||
|
||||
int loglevel_is_set(int level);
|
||||
int slapd_log_error_proc(int sev_level, char *subsystem, char *fmt, ...);
|
||||
+int slapi_log_stat(int loglevel, const char *fmt, ...);
|
||||
|
||||
int slapi_log_access(int level, char *fmt, ...)
|
||||
#ifdef __GNUC__
|
||||
@@ -874,6 +881,7 @@ int check_log_max_size(
|
||||
|
||||
|
||||
void g_set_accesslog_level(int val);
|
||||
+void g_set_statlog_level(int val);
|
||||
void log__delete_rotated_logs(void);
|
||||
|
||||
/*
|
||||
diff --git a/ldap/servers/slapd/result.c b/ldap/servers/slapd/result.c
|
||||
index adcef9539..e94533d72 100644
|
||||
--- a/ldap/servers/slapd/result.c
|
||||
+++ b/ldap/servers/slapd/result.c
|
||||
@@ -38,6 +38,7 @@ static PRLock *current_conn_count_mutex;
|
||||
|
||||
static int flush_ber(Slapi_PBlock *pb, Connection *conn, Operation *op, BerElement *ber, int type);
|
||||
static char *notes2str(unsigned int notes, char *buf, size_t buflen);
|
||||
+static void log_op_stat(Slapi_PBlock *pb);
|
||||
static void log_result(Slapi_PBlock *pb, Operation *op, int err, ber_tag_t tag, int nentries);
|
||||
static void log_entry(Operation *op, Slapi_Entry *e);
|
||||
static void log_referral(Operation *op);
|
||||
@@ -2050,6 +2051,68 @@ notes2str(unsigned int notes, char *buf, size_t buflen)
|
||||
return (buf);
|
||||
}
|
||||
|
||||
+static void
|
||||
+log_op_stat(Slapi_PBlock *pb)
|
||||
+{
|
||||
+
|
||||
+ Connection *conn = NULL;
|
||||
+ Operation *op = NULL;
|
||||
+ Op_stat *op_stat;
|
||||
+ struct timespec duration;
|
||||
+ char stat_etime[ETIME_BUFSIZ] = {0};
|
||||
+
|
||||
+ if (config_get_statlog_level() == 0) {
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ slapi_pblock_get(pb, SLAPI_CONNECTION, &conn);
|
||||
+ slapi_pblock_get(pb, SLAPI_OPERATION, &op);
|
||||
+ op_stat = op_stat_get_operation_extension(pb);
|
||||
+
|
||||
+ if (conn == NULL || op == NULL || op_stat == NULL) {
|
||||
+ return;
|
||||
+ }
|
||||
+ /* process the operation */
|
||||
+ switch (op->o_tag) {
|
||||
+ case LDAP_REQ_BIND:
|
||||
+ case LDAP_REQ_UNBIND:
|
||||
+ case LDAP_REQ_ADD:
|
||||
+ case LDAP_REQ_DELETE:
|
||||
+ case LDAP_REQ_MODRDN:
|
||||
+ case LDAP_REQ_MODIFY:
|
||||
+ case LDAP_REQ_COMPARE:
|
||||
+ break;
|
||||
+ case LDAP_REQ_SEARCH:
|
||||
+ if ((LDAP_STAT_READ_INDEX & config_get_statlog_level()) &&
|
||||
+ op_stat->search_stat) {
|
||||
+ struct component_keys_lookup *key_info;
|
||||
+ for (key_info = op_stat->search_stat->keys_lookup; key_info; key_info = key_info->next) {
|
||||
+ slapi_log_stat(LDAP_STAT_READ_INDEX,
|
||||
+ "conn=%" PRIu64 " op=%d STAT read index: attribute=%s key(%s)=%s --> count %d\n",
|
||||
+ op->o_connid, op->o_opid,
|
||||
+ key_info->attribute_type, key_info->index_type, key_info->key,
|
||||
+ key_info->id_lookup_cnt);
|
||||
+ }
|
||||
+
|
||||
+ /* total elapsed time */
|
||||
+ slapi_timespec_diff(&op_stat->search_stat->keys_lookup_end, &op_stat->search_stat->keys_lookup_start, &duration);
|
||||
+ snprintf(stat_etime, ETIME_BUFSIZ, "%" PRId64 ".%.09" PRId64 "", (int64_t)duration.tv_sec, (int64_t)duration.tv_nsec);
|
||||
+ slapi_log_stat(LDAP_STAT_READ_INDEX,
|
||||
+ "conn=%" PRIu64 " op=%d STAT read index: duration %s\n",
|
||||
+ op->o_connid, op->o_opid, stat_etime);
|
||||
+ }
|
||||
+ break;
|
||||
+ case LDAP_REQ_ABANDON_30:
|
||||
+ case LDAP_REQ_ABANDON:
|
||||
+ break;
|
||||
+
|
||||
+ default:
|
||||
+ slapi_log_err(SLAPI_LOG_ERR,
|
||||
+ "log_op_stat", "Ignoring unknown LDAP request (conn=%" PRIu64 ", tag=0x%lx)\n",
|
||||
+ conn->c_connid, op->o_tag);
|
||||
+ break;
|
||||
+ }
|
||||
+}
|
||||
|
||||
static void
|
||||
log_result(Slapi_PBlock *pb, Operation *op, int err, ber_tag_t tag, int nentries)
|
||||
@@ -2206,6 +2269,7 @@ log_result(Slapi_PBlock *pb, Operation *op, int err, ber_tag_t tag, int nentries
|
||||
} else {
|
||||
ext_str = "";
|
||||
}
|
||||
+ log_op_stat(pb);
|
||||
slapi_log_access(LDAP_DEBUG_STATS,
|
||||
"conn=%" PRIu64 " op=%d RESULT err=%d"
|
||||
" tag=%" BERTAG_T " nentries=%d wtime=%s optime=%s etime=%s%s%s%s\n",
|
||||
diff --git a/ldap/servers/slapd/slap.h b/ldap/servers/slapd/slap.h
|
||||
index 927576b70..82550527c 100644
|
||||
--- a/ldap/servers/slapd/slap.h
|
||||
+++ b/ldap/servers/slapd/slap.h
|
||||
@@ -348,6 +348,8 @@ typedef void (*VFPV)(); /* takes undefined arguments */
|
||||
#define SLAPD_DEFAULT_FE_ERRORLOG_LEVEL_STR "16384"
|
||||
#define SLAPD_DEFAULT_ACCESSLOG_LEVEL 256
|
||||
#define SLAPD_DEFAULT_ACCESSLOG_LEVEL_STR "256"
|
||||
+#define SLAPD_DEFAULT_STATLOG_LEVEL 0
|
||||
+#define SLAPD_DEFAULT_STATLOG_LEVEL_STR "0"
|
||||
|
||||
#define SLAPD_DEFAULT_DISK_THRESHOLD 2097152
|
||||
#define SLAPD_DEFAULT_DISK_THRESHOLD_STR "2097152"
|
||||
@@ -2082,6 +2084,7 @@ typedef struct _slapdEntryPoints
|
||||
#define CONFIG_SCHEMAREPLACE_ATTRIBUTE "nsslapd-schemareplace"
|
||||
#define CONFIG_LOGLEVEL_ATTRIBUTE "nsslapd-errorlog-level"
|
||||
#define CONFIG_ACCESSLOGLEVEL_ATTRIBUTE "nsslapd-accesslog-level"
|
||||
+#define CONFIG_STATLOGLEVEL_ATTRIBUTE "nsslapd-statlog-level"
|
||||
#define CONFIG_ACCESSLOG_MODE_ATTRIBUTE "nsslapd-accesslog-mode"
|
||||
#define CONFIG_ERRORLOG_MODE_ATTRIBUTE "nsslapd-errorlog-mode"
|
||||
#define CONFIG_AUDITLOG_MODE_ATTRIBUTE "nsslapd-auditlog-mode"
|
||||
@@ -2457,6 +2460,7 @@ typedef struct _slapdFrontendConfig
|
||||
int accessloglevel;
|
||||
slapi_onoff_t accesslogbuffering;
|
||||
slapi_onoff_t csnlogging;
|
||||
+ int statloglevel;
|
||||
|
||||
/* ERROR LOG */
|
||||
slapi_onoff_t errorlog_logging_enabled;
|
||||
diff --git a/ldap/servers/slapd/slapi-private.h b/ldap/servers/slapd/slapi-private.h
|
||||
index 4b6cf29eb..bd7a4b39d 100644
|
||||
--- a/ldap/servers/slapd/slapi-private.h
|
||||
+++ b/ldap/servers/slapd/slapi-private.h
|
||||
@@ -449,6 +449,33 @@ int operation_is_flag_set(Slapi_Operation *op, int flag);
|
||||
unsigned long operation_get_type(Slapi_Operation *op);
|
||||
LDAPMod **copy_mods(LDAPMod **orig_mods);
|
||||
|
||||
+/* Structures use to collect statistics per operation */
|
||||
+/* used for LDAP_STAT_READ_INDEX */
|
||||
+struct component_keys_lookup
|
||||
+{
|
||||
+ char *index_type;
|
||||
+ char *attribute_type;
|
||||
+ char *key;
|
||||
+ int id_lookup_cnt;
|
||||
+ struct component_keys_lookup *next;
|
||||
+};
|
||||
+typedef struct op_search_stat
|
||||
+{
|
||||
+ struct component_keys_lookup *keys_lookup;
|
||||
+ struct timespec keys_lookup_start;
|
||||
+ struct timespec keys_lookup_end;
|
||||
+} Op_search_stat;
|
||||
+
|
||||
+/* structure store in the operation extension */
|
||||
+typedef struct op_stat
|
||||
+{
|
||||
+ Op_search_stat *search_stat;
|
||||
+} Op_stat;
|
||||
+
|
||||
+void op_stat_init(void);
|
||||
+Op_stat *op_stat_get_operation_extension(Slapi_PBlock *pb);
|
||||
+void op_stat_set_operation_extension(Slapi_PBlock *pb, Op_stat *op_stat);
|
||||
+
|
||||
/*
|
||||
* From ldap.h
|
||||
* #define LDAP_MOD_ADD 0x00
|
||||
--
|
||||
2.51.1
|
||||
|
||||
@ -1,297 +0,0 @@
|
||||
From bc2629db166667cdb01fde2b9e249253d5d868b5 Mon Sep 17 00:00:00 2001
|
||||
From: tbordaz <tbordaz@redhat.com>
|
||||
Date: Mon, 21 Nov 2022 11:41:15 +0100
|
||||
Subject: [PATCH] Issue 3729 - (cont) RFE Extend log of operations statistics
|
||||
in access log (#5538)
|
||||
|
||||
Bug description:
|
||||
This is a continuation of the #3729
|
||||
The previous fix did not manage internal SRCH, so
|
||||
statistics of internal SRCH were not logged
|
||||
|
||||
Fix description:
|
||||
For internal operation log_op_stat uses
|
||||
connid/op_id/op_internal_id/op_nested_count that have been
|
||||
computed log_result
|
||||
|
||||
For direct operation log_op_stat uses info from the
|
||||
operation itself (o_connid and o_opid)
|
||||
|
||||
log_op_stat relies on operation_type rather than
|
||||
o_tag that is not available for internal operation
|
||||
|
||||
relates: #3729
|
||||
|
||||
Reviewed by: Pierre Rogier
|
||||
|
||||
(cherry picked from commit 7915e85a55476647ac54330de4f6e89faf6f2934)
|
||||
Signed-off-by: Masahiro Matsuya <mmatsuya@redhat.com>
|
||||
---
|
||||
.../tests/suites/ds_logs/ds_logs_test.py | 90 ++++++++++++++++++-
|
||||
ldap/servers/slapd/proto-slap.h | 2 +-
|
||||
ldap/servers/slapd/result.c | 74 +++++++++------
|
||||
3 files changed, 136 insertions(+), 30 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/ds_logs/ds_logs_test.py b/dirsrvtests/tests/suites/ds_logs/ds_logs_test.py
|
||||
index 43288f67f..fbb8d7bf1 100644
|
||||
--- a/dirsrvtests/tests/suites/ds_logs/ds_logs_test.py
|
||||
+++ b/dirsrvtests/tests/suites/ds_logs/ds_logs_test.py
|
||||
@@ -21,7 +21,7 @@ from lib389.idm.domain import Domain
|
||||
from lib389.configurations.sample import create_base_domain
|
||||
from lib389._mapped_object import DSLdapObject
|
||||
from lib389.topologies import topology_st
|
||||
-from lib389.plugins import AutoMembershipPlugin, ReferentialIntegrityPlugin, AutoMembershipDefinitions
|
||||
+from lib389.plugins import AutoMembershipPlugin, ReferentialIntegrityPlugin, AutoMembershipDefinitions, MemberOfPlugin
|
||||
from lib389.idm.user import UserAccounts, UserAccount
|
||||
from lib389.idm.group import Groups
|
||||
from lib389.idm.organizationalunit import OrganizationalUnits
|
||||
@@ -1323,6 +1323,94 @@ def test_stat_index(topology_st, request):
|
||||
|
||||
request.addfinalizer(fin)
|
||||
|
||||
+def test_stat_internal_op(topology_st, request):
|
||||
+ """Check that statistics can also be collected for internal operations
|
||||
+
|
||||
+ :id: 19f393bd-5866-425a-af7a-4dade06d5c77
|
||||
+ :setup: Standalone Instance
|
||||
+ :steps:
|
||||
+ 1. Check that nsslapd-statlog-level is 0 (default)
|
||||
+ 2. Enable memberof plugins
|
||||
+ 3. Create a user
|
||||
+ 4. Remove access log (to only detect new records)
|
||||
+ 5. Enable statistic logging nsslapd-statlog-level=1
|
||||
+ 6. Check that on direct SRCH there is no 'Internal' Stat records
|
||||
+ 7. Remove access log (to only detect new records)
|
||||
+ 8. Add group with the user, so memberof triggers internal search
|
||||
+ and check it exists 'Internal' Stat records
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Success
|
||||
+ 4. Success
|
||||
+ 5. Success
|
||||
+ 6. Success
|
||||
+ 7. Success
|
||||
+ 8. Success
|
||||
+ """
|
||||
+
|
||||
+ inst = topology_st.standalone
|
||||
+
|
||||
+ # Step 1
|
||||
+ log.info("Assert nsslapd-statlog-level is by default 0")
|
||||
+ assert topology_st.standalone.config.get_attr_val_int("nsslapd-statlog-level") == 0
|
||||
+
|
||||
+ # Step 2
|
||||
+ memberof = MemberOfPlugin(inst)
|
||||
+ memberof.enable()
|
||||
+ inst.restart()
|
||||
+
|
||||
+ # Step 3 Add setup entries
|
||||
+ users = UserAccounts(inst, DEFAULT_SUFFIX, rdn=None)
|
||||
+ user = users.create(properties={'uid': 'test_1',
|
||||
+ 'cn': 'test_1',
|
||||
+ 'sn': 'test_1',
|
||||
+ 'description': 'member',
|
||||
+ 'uidNumber': '1000',
|
||||
+ 'gidNumber': '2000',
|
||||
+ 'homeDirectory': '/home/testuser'})
|
||||
+ # Step 4 reset accesslog
|
||||
+ topology_st.standalone.stop()
|
||||
+ lpath = topology_st.standalone.ds_access_log._get_log_path()
|
||||
+ os.unlink(lpath)
|
||||
+ topology_st.standalone.start()
|
||||
+
|
||||
+ # Step 5 enable statistics
|
||||
+ log.info("Set nsslapd-statlog-level: 1 to enable indexing statistics")
|
||||
+ topology_st.standalone.config.set("nsslapd-statlog-level", "1")
|
||||
+
|
||||
+ # Step 6 for direct SRCH only non internal STAT records
|
||||
+ entries = topology_st.standalone.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, "uid=test_1")
|
||||
+ topology_st.standalone.stop()
|
||||
+ assert topology_st.standalone.ds_access_log.match('.*STAT read index.*')
|
||||
+ assert topology_st.standalone.ds_access_log.match('.*STAT read index: attribute.*')
|
||||
+ assert topology_st.standalone.ds_access_log.match('.*STAT read index: duration.*')
|
||||
+ assert not topology_st.standalone.ds_access_log.match('.*Internal.*STAT.*')
|
||||
+ topology_st.standalone.start()
|
||||
+
|
||||
+ # Step 7 reset accesslog
|
||||
+ topology_st.standalone.stop()
|
||||
+ lpath = topology_st.standalone.ds_access_log._get_log_path()
|
||||
+ os.unlink(lpath)
|
||||
+ topology_st.standalone.start()
|
||||
+
|
||||
+ # Step 8 trigger internal searches and check internal stat records
|
||||
+ groups = Groups(inst, DEFAULT_SUFFIX, rdn=None)
|
||||
+ group = groups.create(properties={'cn': 'mygroup',
|
||||
+ 'member': 'uid=test_1,%s' % DEFAULT_SUFFIX,
|
||||
+ 'description': 'group'})
|
||||
+ topology_st.standalone.restart()
|
||||
+ assert topology_st.standalone.ds_access_log.match('.*Internal.*STAT read index.*')
|
||||
+ assert topology_st.standalone.ds_access_log.match('.*Internal.*STAT read index: attribute.*')
|
||||
+ assert topology_st.standalone.ds_access_log.match('.*Internal.*STAT read index: duration.*')
|
||||
+
|
||||
+ def fin():
|
||||
+ log.info('Deleting user/group')
|
||||
+ user.delete()
|
||||
+ group.delete()
|
||||
+
|
||||
+ request.addfinalizer(fin)
|
||||
+
|
||||
if __name__ == '__main__':
|
||||
# Run isolated
|
||||
# -s for DEBUG mode
|
||||
diff --git a/ldap/servers/slapd/proto-slap.h b/ldap/servers/slapd/proto-slap.h
|
||||
index 3a049ee76..6e473a08e 100644
|
||||
--- a/ldap/servers/slapd/proto-slap.h
|
||||
+++ b/ldap/servers/slapd/proto-slap.h
|
||||
@@ -511,7 +511,7 @@ long long config_get_pw_minage(void);
|
||||
long long config_get_pw_warning(void);
|
||||
int config_get_errorlog_level(void);
|
||||
int config_get_accesslog_level(void);
|
||||
-int config_get_statlog_level();
|
||||
+int config_get_statlog_level(void);
|
||||
int config_get_auditlog_logging_enabled(void);
|
||||
int config_get_auditfaillog_logging_enabled(void);
|
||||
char *config_get_auditlog_display_attrs(void);
|
||||
diff --git a/ldap/servers/slapd/result.c b/ldap/servers/slapd/result.c
|
||||
index e94533d72..87641e92f 100644
|
||||
--- a/ldap/servers/slapd/result.c
|
||||
+++ b/ldap/servers/slapd/result.c
|
||||
@@ -38,7 +38,7 @@ static PRLock *current_conn_count_mutex;
|
||||
|
||||
static int flush_ber(Slapi_PBlock *pb, Connection *conn, Operation *op, BerElement *ber, int type);
|
||||
static char *notes2str(unsigned int notes, char *buf, size_t buflen);
|
||||
-static void log_op_stat(Slapi_PBlock *pb);
|
||||
+static void log_op_stat(Slapi_PBlock *pb, uint64_t connid, int32_t op_id, int32_t op_internal_id, int32_t op_nested_count);
|
||||
static void log_result(Slapi_PBlock *pb, Operation *op, int err, ber_tag_t tag, int nentries);
|
||||
static void log_entry(Operation *op, Slapi_Entry *e);
|
||||
static void log_referral(Operation *op);
|
||||
@@ -2051,65 +2051,82 @@ notes2str(unsigned int notes, char *buf, size_t buflen)
|
||||
return (buf);
|
||||
}
|
||||
|
||||
+#define STAT_LOG_CONN_OP_FMT_INT_INT "conn=Internal(%" PRIu64 ") op=%d(%d)(%d)"
|
||||
+#define STAT_LOG_CONN_OP_FMT_EXT_INT "conn=%" PRIu64 " (Internal) op=%d(%d)(%d)"
|
||||
static void
|
||||
-log_op_stat(Slapi_PBlock *pb)
|
||||
+log_op_stat(Slapi_PBlock *pb, uint64_t connid, int32_t op_id, int32_t op_internal_id, int32_t op_nested_count)
|
||||
{
|
||||
-
|
||||
- Connection *conn = NULL;
|
||||
Operation *op = NULL;
|
||||
Op_stat *op_stat;
|
||||
struct timespec duration;
|
||||
char stat_etime[ETIME_BUFSIZ] = {0};
|
||||
+ int internal_op;
|
||||
|
||||
if (config_get_statlog_level() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
- slapi_pblock_get(pb, SLAPI_CONNECTION, &conn);
|
||||
slapi_pblock_get(pb, SLAPI_OPERATION, &op);
|
||||
+ internal_op = operation_is_flag_set(op, OP_FLAG_INTERNAL);
|
||||
op_stat = op_stat_get_operation_extension(pb);
|
||||
|
||||
- if (conn == NULL || op == NULL || op_stat == NULL) {
|
||||
+ if (op == NULL || op_stat == NULL) {
|
||||
return;
|
||||
}
|
||||
/* process the operation */
|
||||
- switch (op->o_tag) {
|
||||
- case LDAP_REQ_BIND:
|
||||
- case LDAP_REQ_UNBIND:
|
||||
- case LDAP_REQ_ADD:
|
||||
- case LDAP_REQ_DELETE:
|
||||
- case LDAP_REQ_MODRDN:
|
||||
- case LDAP_REQ_MODIFY:
|
||||
- case LDAP_REQ_COMPARE:
|
||||
+ switch (operation_get_type(op)) {
|
||||
+ case SLAPI_OPERATION_BIND:
|
||||
+ case SLAPI_OPERATION_UNBIND:
|
||||
+ case SLAPI_OPERATION_ADD:
|
||||
+ case SLAPI_OPERATION_DELETE:
|
||||
+ case SLAPI_OPERATION_MODRDN:
|
||||
+ case SLAPI_OPERATION_MODIFY:
|
||||
+ case SLAPI_OPERATION_COMPARE:
|
||||
+ case SLAPI_OPERATION_EXTENDED:
|
||||
break;
|
||||
- case LDAP_REQ_SEARCH:
|
||||
+ case SLAPI_OPERATION_SEARCH:
|
||||
if ((LDAP_STAT_READ_INDEX & config_get_statlog_level()) &&
|
||||
op_stat->search_stat) {
|
||||
struct component_keys_lookup *key_info;
|
||||
for (key_info = op_stat->search_stat->keys_lookup; key_info; key_info = key_info->next) {
|
||||
- slapi_log_stat(LDAP_STAT_READ_INDEX,
|
||||
- "conn=%" PRIu64 " op=%d STAT read index: attribute=%s key(%s)=%s --> count %d\n",
|
||||
- op->o_connid, op->o_opid,
|
||||
- key_info->attribute_type, key_info->index_type, key_info->key,
|
||||
- key_info->id_lookup_cnt);
|
||||
+ if (internal_op) {
|
||||
+ slapi_log_stat(LDAP_STAT_READ_INDEX,
|
||||
+ connid == 0 ? STAT_LOG_CONN_OP_FMT_INT_INT "STAT read index: attribute=%s key(%s)=%s --> count %d\n":
|
||||
+ STAT_LOG_CONN_OP_FMT_EXT_INT "STAT read index: attribute=%s key(%s)=%s --> count %d\n",
|
||||
+ connid, op_id, op_internal_id, op_nested_count,
|
||||
+ key_info->attribute_type, key_info->index_type, key_info->key,
|
||||
+ key_info->id_lookup_cnt);
|
||||
+ } else {
|
||||
+ slapi_log_stat(LDAP_STAT_READ_INDEX,
|
||||
+ "conn=%" PRIu64 " op=%d STAT read index: attribute=%s key(%s)=%s --> count %d\n",
|
||||
+ connid, op_id,
|
||||
+ key_info->attribute_type, key_info->index_type, key_info->key,
|
||||
+ key_info->id_lookup_cnt);
|
||||
+ }
|
||||
}
|
||||
|
||||
/* total elapsed time */
|
||||
slapi_timespec_diff(&op_stat->search_stat->keys_lookup_end, &op_stat->search_stat->keys_lookup_start, &duration);
|
||||
snprintf(stat_etime, ETIME_BUFSIZ, "%" PRId64 ".%.09" PRId64 "", (int64_t)duration.tv_sec, (int64_t)duration.tv_nsec);
|
||||
- slapi_log_stat(LDAP_STAT_READ_INDEX,
|
||||
- "conn=%" PRIu64 " op=%d STAT read index: duration %s\n",
|
||||
- op->o_connid, op->o_opid, stat_etime);
|
||||
+ if (internal_op) {
|
||||
+ slapi_log_stat(LDAP_STAT_READ_INDEX,
|
||||
+ connid == 0 ? STAT_LOG_CONN_OP_FMT_INT_INT "STAT read index: duration %s\n":
|
||||
+ STAT_LOG_CONN_OP_FMT_EXT_INT "STAT read index: duration %s\n",
|
||||
+ connid, op_id, op_internal_id, op_nested_count, stat_etime);
|
||||
+ } else {
|
||||
+ slapi_log_stat(LDAP_STAT_READ_INDEX,
|
||||
+ "conn=%" PRIu64 " op=%d STAT read index: duration %s\n",
|
||||
+ op->o_connid, op->o_opid, stat_etime);
|
||||
+ }
|
||||
}
|
||||
break;
|
||||
- case LDAP_REQ_ABANDON_30:
|
||||
- case LDAP_REQ_ABANDON:
|
||||
+ case SLAPI_OPERATION_ABANDON:
|
||||
break;
|
||||
|
||||
default:
|
||||
slapi_log_err(SLAPI_LOG_ERR,
|
||||
- "log_op_stat", "Ignoring unknown LDAP request (conn=%" PRIu64 ", tag=0x%lx)\n",
|
||||
- conn->c_connid, op->o_tag);
|
||||
+ "log_op_stat", "Ignoring unknown LDAP request (conn=%" PRIu64 ", op_type=0x%lx)\n",
|
||||
+ connid, operation_get_type(op));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -2269,7 +2286,7 @@ log_result(Slapi_PBlock *pb, Operation *op, int err, ber_tag_t tag, int nentries
|
||||
} else {
|
||||
ext_str = "";
|
||||
}
|
||||
- log_op_stat(pb);
|
||||
+ log_op_stat(pb, op->o_connid, op->o_opid, 0, 0);
|
||||
slapi_log_access(LDAP_DEBUG_STATS,
|
||||
"conn=%" PRIu64 " op=%d RESULT err=%d"
|
||||
" tag=%" BERTAG_T " nentries=%d wtime=%s optime=%s etime=%s%s%s%s\n",
|
||||
@@ -2284,6 +2301,7 @@ log_result(Slapi_PBlock *pb, Operation *op, int err, ber_tag_t tag, int nentries
|
||||
}
|
||||
} else {
|
||||
int optype;
|
||||
+ log_op_stat(pb, connid, op_id, op_internal_id, op_nested_count);
|
||||
#define LOG_MSG_FMT " tag=%" BERTAG_T " nentries=%d wtime=%s optime=%s etime=%s%s%s\n"
|
||||
slapi_log_access(LDAP_DEBUG_ARGS,
|
||||
connid == 0 ? LOG_CONN_OP_FMT_INT_INT LOG_MSG_FMT :
|
||||
--
|
||||
2.51.1
|
||||
|
||||
@ -1,124 +0,0 @@
|
||||
From f6eca13762139538d974c1cb285ddf1354fe7837 Mon Sep 17 00:00:00 2001
|
||||
From: tbordaz <tbordaz@redhat.com>
|
||||
Date: Tue, 28 Mar 2023 10:27:01 +0200
|
||||
Subject: [PATCH] Issue 5710 - subtree search statistics for index lookup does
|
||||
not report ancestorid/entryrdn lookups (#5711)
|
||||
|
||||
Bug description:
|
||||
The RFE #3729 allows to collect index lookups per search
|
||||
operation. For subtree searches the server lookup ancestorid
|
||||
and those lookup are not recorded
|
||||
|
||||
Fix description:
|
||||
if statistics are enabled, record ancestorid lookup
|
||||
|
||||
relates: #5710
|
||||
|
||||
Reviewed by: Mark Reynolds (thanks)
|
||||
|
||||
(cherry picked from commit fca27c3d0487c9aea9dc7da151a79e3ce0fc7d35)
|
||||
Signed-off-by: Masahiro Matsuya <mmatsuya@redhat.com>
|
||||
---
|
||||
ldap/servers/slapd/back-ldbm/ldbm_search.c | 59 ++++++++++++++++++++++
|
||||
1 file changed, 59 insertions(+)
|
||||
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_search.c b/ldap/servers/slapd/back-ldbm/ldbm_search.c
|
||||
index 8c07d1395..5d98e288e 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/ldbm_search.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/ldbm_search.c
|
||||
@@ -35,6 +35,7 @@ static IDList *onelevel_candidates(Slapi_PBlock *pb, backend *be, const char *ba
|
||||
static back_search_result_set *new_search_result_set(IDList *idl, int vlv, int lookthroughlimit);
|
||||
static void delete_search_result_set(Slapi_PBlock *pb, back_search_result_set **sr);
|
||||
static int can_skip_filter_test(Slapi_PBlock *pb, struct slapi_filter *f, int scope, IDList *idl);
|
||||
+static void stat_add_srch_lookup(Op_stat *op_stat, char * attribute_type, const char* index_type, char *key_value, int lookup_cnt);
|
||||
|
||||
/* This is for performance testing, allows us to disable ACL checking altogether */
|
||||
#if defined(DISABLE_ACL_CHECK)
|
||||
@@ -1167,6 +1168,45 @@ create_subtree_filter(Slapi_Filter *filter, int managedsait, Slapi_Filter **focr
|
||||
return ftop;
|
||||
}
|
||||
|
||||
+static void
|
||||
+stat_add_srch_lookup(Op_stat *op_stat, char * attribute_type, const char* index_type, char *key_value, int lookup_cnt)
|
||||
+{
|
||||
+ struct component_keys_lookup *key_stat;
|
||||
+
|
||||
+ if ((op_stat == NULL) || (op_stat->search_stat == NULL)) {
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ /* gather the index lookup statistics */
|
||||
+ key_stat = (struct component_keys_lookup *) slapi_ch_calloc(1, sizeof (struct component_keys_lookup));
|
||||
+
|
||||
+ /* indextype is "eq" */
|
||||
+ if (index_type) {
|
||||
+ key_stat->index_type = slapi_ch_strdup(index_type);
|
||||
+ }
|
||||
+
|
||||
+ /* key value e.g. '1234' */
|
||||
+ if (key_value) {
|
||||
+ key_stat->key = (char *) slapi_ch_calloc(1, strlen(key_value) + 1);
|
||||
+ memcpy(key_stat->key, key_value, strlen(key_value));
|
||||
+ }
|
||||
+
|
||||
+ /* attribute name is e.g. 'uid' */
|
||||
+ if (attribute_type) {
|
||||
+ key_stat->attribute_type = slapi_ch_strdup(attribute_type);
|
||||
+ }
|
||||
+
|
||||
+ /* Number of lookup IDs with the key */
|
||||
+ key_stat->id_lookup_cnt = lookup_cnt;
|
||||
+ if (op_stat->search_stat->keys_lookup) {
|
||||
+ /* it already exist key stat. add key_stat at the head */
|
||||
+ key_stat->next = op_stat->search_stat->keys_lookup;
|
||||
+ } else {
|
||||
+ /* this is the first key stat record */
|
||||
+ key_stat->next = NULL;
|
||||
+ }
|
||||
+ op_stat->search_stat->keys_lookup = key_stat;
|
||||
+}
|
||||
|
||||
/*
|
||||
* Build a candidate list for a SUBTREE scope search.
|
||||
@@ -1232,6 +1272,17 @@ subtree_candidates(
|
||||
if (candidates != NULL && (idl_length(candidates) > FILTER_TEST_THRESHOLD) && e) {
|
||||
IDList *tmp = candidates, *descendants = NULL;
|
||||
back_txn txn = {NULL};
|
||||
+ Op_stat *op_stat = NULL;
|
||||
+ char key_value[32] = {0};
|
||||
+
|
||||
+ /* statistics for index lookup is enabled */
|
||||
+ if (LDAP_STAT_READ_INDEX & config_get_statlog_level()) {
|
||||
+ op_stat = op_stat_get_operation_extension(pb);
|
||||
+ if (op_stat) {
|
||||
+ /* easier to just record the entry ID */
|
||||
+ PR_snprintf(key_value, sizeof(key_value), "%lu", (u_long) e->ep_id);
|
||||
+ }
|
||||
+ }
|
||||
|
||||
slapi_pblock_get(pb, SLAPI_TXN, &txn.back_txn_txn);
|
||||
if (entryrdn_get_noancestorid()) {
|
||||
@@ -1239,12 +1290,20 @@ subtree_candidates(
|
||||
*err = entryrdn_get_subordinates(be,
|
||||
slapi_entry_get_sdn_const(e->ep_entry),
|
||||
e->ep_id, &descendants, &txn, 0);
|
||||
+ if (op_stat) {
|
||||
+ /* record entryrdn lookups */
|
||||
+ stat_add_srch_lookup(op_stat, LDBM_ENTRYRDN_STR, indextype_EQUALITY, key_value, descendants ? descendants->b_nids : 0);
|
||||
+ }
|
||||
idl_insert(&descendants, e->ep_id);
|
||||
candidates = idl_intersection(be, candidates, descendants);
|
||||
idl_free(&tmp);
|
||||
idl_free(&descendants);
|
||||
} else if (!has_tombstone_filter && !is_bulk_import) {
|
||||
*err = ldbm_ancestorid_read_ext(be, &txn, e->ep_id, &descendants, allidslimit);
|
||||
+ if (op_stat) {
|
||||
+ /* records ancestorid lookups */
|
||||
+ stat_add_srch_lookup(op_stat, LDBM_ANCESTORID_STR, indextype_EQUALITY, key_value, descendants ? descendants->b_nids : 0);
|
||||
+ }
|
||||
idl_insert(&descendants, e->ep_id);
|
||||
candidates = idl_intersection(be, candidates, descendants);
|
||||
idl_free(&tmp);
|
||||
--
|
||||
2.51.1
|
||||
|
||||
@ -1,249 +0,0 @@
|
||||
From aced6f575f3be70f16756860f8b852d3447df867 Mon Sep 17 00:00:00 2001
|
||||
From: tbordaz <tbordaz@redhat.com>
|
||||
Date: Tue, 6 May 2025 16:09:36 +0200
|
||||
Subject: [PATCH] Issue 6764 - statistics about index lookup report a wrong
|
||||
duration (#6765)
|
||||
|
||||
Bug description:
|
||||
During a SRCH statistics about indexes lookup
|
||||
(when nsslapd-statlog-level=1) reports a duration.
|
||||
It is wrong because it should report a duration per filter
|
||||
component.
|
||||
|
||||
Fix description:
|
||||
Record a index lookup duration per key
|
||||
using key_lookup_start/key_lookup_end
|
||||
|
||||
fixes: #6764
|
||||
|
||||
Reviewed by: Pierre Rogier (Thanks !)
|
||||
|
||||
(cherry picked from commit cd8069a76bcbb2d7bb4ac3bb9466019b01cc6db3)
|
||||
Signed-off-by: Masahiro Matsuya <mmatsuya@redhat.com>
|
||||
---
|
||||
ldap/servers/slapd/back-ldbm/filterindex.c | 17 +++++++-----
|
||||
ldap/servers/slapd/back-ldbm/ldbm_search.c | 31 +++++++++++++++-------
|
||||
ldap/servers/slapd/result.c | 12 +++++----
|
||||
ldap/servers/slapd/slapi-plugin.h | 9 +++++++
|
||||
ldap/servers/slapd/slapi-private.h | 2 ++
|
||||
ldap/servers/slapd/time.c | 13 +++++++++
|
||||
6 files changed, 62 insertions(+), 22 deletions(-)
|
||||
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/filterindex.c b/ldap/servers/slapd/back-ldbm/filterindex.c
|
||||
index 30550dde7..abc502b96 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/filterindex.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/filterindex.c
|
||||
@@ -1040,8 +1040,7 @@ keys2idl(
|
||||
int allidslimit)
|
||||
{
|
||||
IDList *idl = NULL;
|
||||
- Op_stat *op_stat;
|
||||
- PRBool collect_stat = PR_FALSE;
|
||||
+ Op_stat *op_stat = NULL;
|
||||
|
||||
slapi_log_err(SLAPI_LOG_TRACE, "keys2idl", "=> type %s indextype %s\n", type, indextype);
|
||||
|
||||
@@ -1049,8 +1048,9 @@ keys2idl(
|
||||
if (LDAP_STAT_READ_INDEX & config_get_statlog_level()) {
|
||||
op_stat = op_stat_get_operation_extension(pb);
|
||||
if (op_stat->search_stat) {
|
||||
- collect_stat = PR_TRUE;
|
||||
clock_gettime(CLOCK_MONOTONIC, &(op_stat->search_stat->keys_lookup_start));
|
||||
+ } else {
|
||||
+ op_stat = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1059,11 +1059,14 @@ keys2idl(
|
||||
struct component_keys_lookup *key_stat;
|
||||
int key_len;
|
||||
|
||||
- idl2 = index_read_ext_allids(pb, be, type, indextype, slapi_value_get_berval(ivals[i]), txn, err, unindexed, allidslimit);
|
||||
- if (collect_stat) {
|
||||
+ if (op_stat) {
|
||||
/* gather the index lookup statistics */
|
||||
key_stat = (struct component_keys_lookup *) slapi_ch_calloc(1, sizeof (struct component_keys_lookup));
|
||||
-
|
||||
+ clock_gettime(CLOCK_MONOTONIC, &(key_stat->key_lookup_start));
|
||||
+ }
|
||||
+ idl2 = index_read_ext_allids(pb, be, type, indextype, slapi_value_get_berval(ivals[i]), txn, err, unindexed, allidslimit);
|
||||
+ if (op_stat) {
|
||||
+ clock_gettime(CLOCK_MONOTONIC, &(key_stat->key_lookup_end));
|
||||
/* indextype e.g. "eq" or "sub" (see index.c) */
|
||||
if (indextype) {
|
||||
key_stat->index_type = slapi_ch_strdup(indextype);
|
||||
@@ -1125,7 +1128,7 @@ keys2idl(
|
||||
}
|
||||
|
||||
/* All the keys have been fetch, time to take the completion time */
|
||||
- if (collect_stat) {
|
||||
+ if (op_stat) {
|
||||
clock_gettime(CLOCK_MONOTONIC, &(op_stat->search_stat->keys_lookup_end));
|
||||
}
|
||||
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_search.c b/ldap/servers/slapd/back-ldbm/ldbm_search.c
|
||||
index 5d98e288e..27301f453 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/ldbm_search.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/ldbm_search.c
|
||||
@@ -35,7 +35,7 @@ static IDList *onelevel_candidates(Slapi_PBlock *pb, backend *be, const char *ba
|
||||
static back_search_result_set *new_search_result_set(IDList *idl, int vlv, int lookthroughlimit);
|
||||
static void delete_search_result_set(Slapi_PBlock *pb, back_search_result_set **sr);
|
||||
static int can_skip_filter_test(Slapi_PBlock *pb, struct slapi_filter *f, int scope, IDList *idl);
|
||||
-static void stat_add_srch_lookup(Op_stat *op_stat, char * attribute_type, const char* index_type, char *key_value, int lookup_cnt);
|
||||
+static void stat_add_srch_lookup(Op_stat *op_stat, struct component_keys_lookup *key_stat, char * attribute_type, const char* index_type, char *key_value, int lookup_cnt);
|
||||
|
||||
/* This is for performance testing, allows us to disable ACL checking altogether */
|
||||
#if defined(DISABLE_ACL_CHECK)
|
||||
@@ -1169,17 +1169,12 @@ create_subtree_filter(Slapi_Filter *filter, int managedsait, Slapi_Filter **focr
|
||||
}
|
||||
|
||||
static void
|
||||
-stat_add_srch_lookup(Op_stat *op_stat, char * attribute_type, const char* index_type, char *key_value, int lookup_cnt)
|
||||
+stat_add_srch_lookup(Op_stat *op_stat, struct component_keys_lookup *key_stat, char * attribute_type, const char* index_type, char *key_value, int lookup_cnt)
|
||||
{
|
||||
- struct component_keys_lookup *key_stat;
|
||||
-
|
||||
- if ((op_stat == NULL) || (op_stat->search_stat == NULL)) {
|
||||
+ if ((op_stat == NULL) || (op_stat->search_stat == NULL) || (key_stat == NULL)) {
|
||||
return;
|
||||
}
|
||||
|
||||
- /* gather the index lookup statistics */
|
||||
- key_stat = (struct component_keys_lookup *) slapi_ch_calloc(1, sizeof (struct component_keys_lookup));
|
||||
-
|
||||
/* indextype is "eq" */
|
||||
if (index_type) {
|
||||
key_stat->index_type = slapi_ch_strdup(index_type);
|
||||
@@ -1286,23 +1281,39 @@ subtree_candidates(
|
||||
|
||||
slapi_pblock_get(pb, SLAPI_TXN, &txn.back_txn_txn);
|
||||
if (entryrdn_get_noancestorid()) {
|
||||
+ struct component_keys_lookup *key_stat;
|
||||
+
|
||||
+ if (op_stat) {
|
||||
+ /* gather the index lookup statistics */
|
||||
+ key_stat = (struct component_keys_lookup *) slapi_ch_calloc(1, sizeof (struct component_keys_lookup));
|
||||
+ clock_gettime(CLOCK_MONOTONIC, &key_stat->key_lookup_start);
|
||||
+ }
|
||||
/* subtree-rename: on && no ancestorid */
|
||||
*err = entryrdn_get_subordinates(be,
|
||||
slapi_entry_get_sdn_const(e->ep_entry),
|
||||
e->ep_id, &descendants, &txn, 0);
|
||||
if (op_stat) {
|
||||
+ clock_gettime(CLOCK_MONOTONIC, &key_stat->key_lookup_end);
|
||||
/* record entryrdn lookups */
|
||||
- stat_add_srch_lookup(op_stat, LDBM_ENTRYRDN_STR, indextype_EQUALITY, key_value, descendants ? descendants->b_nids : 0);
|
||||
+ stat_add_srch_lookup(op_stat, key_stat, LDBM_ENTRYRDN_STR, indextype_EQUALITY, key_value, descendants ? descendants->b_nids : 0);
|
||||
}
|
||||
idl_insert(&descendants, e->ep_id);
|
||||
candidates = idl_intersection(be, candidates, descendants);
|
||||
idl_free(&tmp);
|
||||
idl_free(&descendants);
|
||||
} else if (!has_tombstone_filter && !is_bulk_import) {
|
||||
+ struct component_keys_lookup *key_stat;
|
||||
+
|
||||
+ if (op_stat) {
|
||||
+ /* gather the index lookup statistics */
|
||||
+ key_stat = (struct component_keys_lookup *) slapi_ch_calloc(1, sizeof (struct component_keys_lookup));
|
||||
+ clock_gettime(CLOCK_MONOTONIC, &key_stat->key_lookup_start);
|
||||
+ }
|
||||
*err = ldbm_ancestorid_read_ext(be, &txn, e->ep_id, &descendants, allidslimit);
|
||||
if (op_stat) {
|
||||
+ clock_gettime(CLOCK_MONOTONIC, &key_stat->key_lookup_end);
|
||||
/* records ancestorid lookups */
|
||||
- stat_add_srch_lookup(op_stat, LDBM_ANCESTORID_STR, indextype_EQUALITY, key_value, descendants ? descendants->b_nids : 0);
|
||||
+ stat_add_srch_lookup(op_stat, key_stat, LDBM_ANCESTORID_STR, indextype_EQUALITY, key_value, descendants ? descendants->b_nids : 0);
|
||||
}
|
||||
idl_insert(&descendants, e->ep_id);
|
||||
candidates = idl_intersection(be, candidates, descendants);
|
||||
diff --git a/ldap/servers/slapd/result.c b/ldap/servers/slapd/result.c
|
||||
index 87641e92f..f40556de8 100644
|
||||
--- a/ldap/servers/slapd/result.c
|
||||
+++ b/ldap/servers/slapd/result.c
|
||||
@@ -2089,19 +2089,21 @@ log_op_stat(Slapi_PBlock *pb, uint64_t connid, int32_t op_id, int32_t op_interna
|
||||
op_stat->search_stat) {
|
||||
struct component_keys_lookup *key_info;
|
||||
for (key_info = op_stat->search_stat->keys_lookup; key_info; key_info = key_info->next) {
|
||||
+ slapi_timespec_diff(&key_info->key_lookup_end, &key_info->key_lookup_start, &duration);
|
||||
+ snprintf(stat_etime, ETIME_BUFSIZ, "%" PRId64 ".%.09" PRId64 "", (int64_t)duration.tv_sec, (int64_t)duration.tv_nsec);
|
||||
if (internal_op) {
|
||||
slapi_log_stat(LDAP_STAT_READ_INDEX,
|
||||
- connid == 0 ? STAT_LOG_CONN_OP_FMT_INT_INT "STAT read index: attribute=%s key(%s)=%s --> count %d\n":
|
||||
- STAT_LOG_CONN_OP_FMT_EXT_INT "STAT read index: attribute=%s key(%s)=%s --> count %d\n",
|
||||
+ connid == 0 ? STAT_LOG_CONN_OP_FMT_INT_INT "STAT read index: attribute=%s key(%s)=%s --> count %d (duration %s)\n":
|
||||
+ STAT_LOG_CONN_OP_FMT_EXT_INT "STAT read index: attribute=%s key(%s)=%s --> count %d (duration %s)\n",
|
||||
connid, op_id, op_internal_id, op_nested_count,
|
||||
key_info->attribute_type, key_info->index_type, key_info->key,
|
||||
- key_info->id_lookup_cnt);
|
||||
+ key_info->id_lookup_cnt, stat_etime);
|
||||
} else {
|
||||
slapi_log_stat(LDAP_STAT_READ_INDEX,
|
||||
- "conn=%" PRIu64 " op=%d STAT read index: attribute=%s key(%s)=%s --> count %d\n",
|
||||
+ "conn=%" PRIu64 " op=%d STAT read index: attribute=%s key(%s)=%s --> count %d (duration %s)\n",
|
||||
connid, op_id,
|
||||
key_info->attribute_type, key_info->index_type, key_info->key,
|
||||
- key_info->id_lookup_cnt);
|
||||
+ key_info->id_lookup_cnt, stat_etime);
|
||||
}
|
||||
}
|
||||
|
||||
diff --git a/ldap/servers/slapd/slapi-plugin.h b/ldap/servers/slapd/slapi-plugin.h
|
||||
index a84a60c92..00e9722d2 100644
|
||||
--- a/ldap/servers/slapd/slapi-plugin.h
|
||||
+++ b/ldap/servers/slapd/slapi-plugin.h
|
||||
@@ -8314,6 +8314,15 @@ void DS_Sleep(PRIntervalTime ticks);
|
||||
* \param struct timespec c the difference.
|
||||
*/
|
||||
void slapi_timespec_diff(struct timespec *a, struct timespec *b, struct timespec *diff);
|
||||
+
|
||||
+/**
|
||||
+ * add 'new' timespect into 'cumul'
|
||||
+ * clock_monotonic to find time taken to perform operations.
|
||||
+ *
|
||||
+ * \param struct timespec cumul to compute total duration.
|
||||
+ * \param struct timespec new is a additional duration
|
||||
+ */
|
||||
+void slapi_timespec_add(struct timespec *cumul, struct timespec *new);
|
||||
/**
|
||||
* Given an operation, determine the time elapsed since the op
|
||||
* began.
|
||||
diff --git a/ldap/servers/slapd/slapi-private.h b/ldap/servers/slapd/slapi-private.h
|
||||
index bd7a4b39d..dfb0e272a 100644
|
||||
--- a/ldap/servers/slapd/slapi-private.h
|
||||
+++ b/ldap/servers/slapd/slapi-private.h
|
||||
@@ -457,6 +457,8 @@ struct component_keys_lookup
|
||||
char *attribute_type;
|
||||
char *key;
|
||||
int id_lookup_cnt;
|
||||
+ struct timespec key_lookup_start;
|
||||
+ struct timespec key_lookup_end;
|
||||
struct component_keys_lookup *next;
|
||||
};
|
||||
typedef struct op_search_stat
|
||||
diff --git a/ldap/servers/slapd/time.c b/ldap/servers/slapd/time.c
|
||||
index 0406c3689..0dd457fbe 100644
|
||||
--- a/ldap/servers/slapd/time.c
|
||||
+++ b/ldap/servers/slapd/time.c
|
||||
@@ -272,6 +272,19 @@ slapi_timespec_diff(struct timespec *a, struct timespec *b, struct timespec *dif
|
||||
diff->tv_nsec = nsec;
|
||||
}
|
||||
|
||||
+void
|
||||
+slapi_timespec_add(struct timespec *cumul, struct timespec *new)
|
||||
+{
|
||||
+ /* Now add the two */
|
||||
+ time_t sec = cumul->tv_sec + new->tv_sec;
|
||||
+ long nsec = cumul->tv_nsec + new->tv_nsec;
|
||||
+
|
||||
+ sec += nsec / 1000000000;
|
||||
+ nsec = nsec % 1000000000;
|
||||
+ cumul->tv_sec = sec;
|
||||
+ cumul->tv_nsec = nsec;
|
||||
+}
|
||||
+
|
||||
void
|
||||
slapi_timespec_expire_at(time_t timeout, struct timespec *expire)
|
||||
{
|
||||
--
|
||||
2.51.1
|
||||
|
||||
@ -1,82 +0,0 @@
|
||||
From c8a5594efdb2722b6dceaed16219039d8e59c888 Mon Sep 17 00:00:00 2001
|
||||
From: Thierry Bordaz <tbordaz@redhat.com>
|
||||
Date: Thu, 5 Jun 2025 10:33:29 +0200
|
||||
Subject: [PATCH] Issue 6470 (Cont) - Some replication status data are reset
|
||||
upon a restart
|
||||
|
||||
(cherry picked from commit a8b419dab31f4fa9fca8c33fe04a79e7a34965e5)
|
||||
Signed-off-by: Masahiro Matsuya <mmatsuya@redhat.com>
|
||||
---
|
||||
ldap/servers/plugins/replication/repl5_agmt.c | 4 ++--
|
||||
ldap/servers/slapd/slapi-plugin.h | 8 --------
|
||||
ldap/servers/slapd/time.c | 13 -------------
|
||||
3 files changed, 2 insertions(+), 23 deletions(-)
|
||||
|
||||
diff --git a/ldap/servers/plugins/replication/repl5_agmt.c b/ldap/servers/plugins/replication/repl5_agmt.c
|
||||
index c3b8d298c..229783763 100644
|
||||
--- a/ldap/servers/plugins/replication/repl5_agmt.c
|
||||
+++ b/ldap/servers/plugins/replication/repl5_agmt.c
|
||||
@@ -537,7 +537,7 @@ agmt_new_from_entry(Slapi_Entry *e)
|
||||
if (val) {
|
||||
strcpy(ra->last_init_status, val);
|
||||
}
|
||||
- ra->changecounters = (struct changecounter **)slapi_ch_calloc(MAX_NUM_OF_SUPPLIERS + 1,
|
||||
+ ra->changecounters = (struct changecounter **)slapi_ch_calloc(MAX_NUM_OF_MASTERS + 1,
|
||||
sizeof(struct changecounter *));
|
||||
ra->num_changecounters = 0;
|
||||
ra->max_changecounters = MAX_NUM_OF_MASTERS;
|
||||
@@ -2615,7 +2615,7 @@ agmt_update_init_status(Repl_Agmt *ra)
|
||||
mods[nb_mods] = NULL;
|
||||
|
||||
slapi_modify_internal_set_pb_ext(pb, ra->dn, mods, NULL, NULL,
|
||||
- repl_get_plugin_identity(PLUGIN_MULTISUPPLIER_REPLICATION), 0);
|
||||
+ repl_get_plugin_identity(PLUGIN_MULTIMASTER_REPLICATION), 0);
|
||||
slapi_modify_internal_pb(pb);
|
||||
|
||||
slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
|
||||
diff --git a/ldap/servers/slapd/slapi-plugin.h b/ldap/servers/slapd/slapi-plugin.h
|
||||
index 00e9722d2..677be1db0 100644
|
||||
--- a/ldap/servers/slapd/slapi-plugin.h
|
||||
+++ b/ldap/servers/slapd/slapi-plugin.h
|
||||
@@ -8315,14 +8315,6 @@ void DS_Sleep(PRIntervalTime ticks);
|
||||
*/
|
||||
void slapi_timespec_diff(struct timespec *a, struct timespec *b, struct timespec *diff);
|
||||
|
||||
-/**
|
||||
- * add 'new' timespect into 'cumul'
|
||||
- * clock_monotonic to find time taken to perform operations.
|
||||
- *
|
||||
- * \param struct timespec cumul to compute total duration.
|
||||
- * \param struct timespec new is a additional duration
|
||||
- */
|
||||
-void slapi_timespec_add(struct timespec *cumul, struct timespec *new);
|
||||
/**
|
||||
* Given an operation, determine the time elapsed since the op
|
||||
* began.
|
||||
diff --git a/ldap/servers/slapd/time.c b/ldap/servers/slapd/time.c
|
||||
index 0dd457fbe..0406c3689 100644
|
||||
--- a/ldap/servers/slapd/time.c
|
||||
+++ b/ldap/servers/slapd/time.c
|
||||
@@ -272,19 +272,6 @@ slapi_timespec_diff(struct timespec *a, struct timespec *b, struct timespec *dif
|
||||
diff->tv_nsec = nsec;
|
||||
}
|
||||
|
||||
-void
|
||||
-slapi_timespec_add(struct timespec *cumul, struct timespec *new)
|
||||
-{
|
||||
- /* Now add the two */
|
||||
- time_t sec = cumul->tv_sec + new->tv_sec;
|
||||
- long nsec = cumul->tv_nsec + new->tv_nsec;
|
||||
-
|
||||
- sec += nsec / 1000000000;
|
||||
- nsec = nsec % 1000000000;
|
||||
- cumul->tv_sec = sec;
|
||||
- cumul->tv_nsec = nsec;
|
||||
-}
|
||||
-
|
||||
void
|
||||
slapi_timespec_expire_at(time_t timeout, struct timespec *expire)
|
||||
{
|
||||
--
|
||||
2.51.1
|
||||
|
||||
@ -1,101 +0,0 @@
|
||||
From a7231528b5ad7e887eeed4317de48d054cd046cd Mon Sep 17 00:00:00 2001
|
||||
From: Mark Reynolds <mreynolds@redhat.com>
|
||||
Date: Wed, 23 Jul 2025 19:35:32 -0400
|
||||
Subject: [PATCH] Issue 6895 - Crash if repl keep alive entry can not be
|
||||
created
|
||||
|
||||
Description:
|
||||
|
||||
Heap use after free when logging that the replicaton keep-alive entry can not
|
||||
be created. slapi_add_internal_pb() frees the slapi entry, then
|
||||
we try and get the dn from the entry and we get a use-after-free crash.
|
||||
|
||||
Relates: https://github.com/389ds/389-ds-base/issues/6895
|
||||
|
||||
Reviewed by: spichugi(Thanks!)
|
||||
|
||||
(cherry picked from commit 43ab6b1d1de138d6be03b657f27cbb6ba19ddd14)
|
||||
Signed-off-by: Masahiro Matsuya <mmatsuya@redhat.com>
|
||||
---
|
||||
ldap/servers/plugins/chainingdb/cb_config.c | 3 +--
|
||||
ldap/servers/plugins/posix-winsync/posix-winsync.c | 1 -
|
||||
ldap/servers/plugins/replication/repl5_init.c | 3 ---
|
||||
ldap/servers/plugins/replication/repl5_replica.c | 8 ++++----
|
||||
4 files changed, 5 insertions(+), 10 deletions(-)
|
||||
|
||||
diff --git a/ldap/servers/plugins/chainingdb/cb_config.c b/ldap/servers/plugins/chainingdb/cb_config.c
|
||||
index 40a7088d7..24fa1bcb3 100644
|
||||
--- a/ldap/servers/plugins/chainingdb/cb_config.c
|
||||
+++ b/ldap/servers/plugins/chainingdb/cb_config.c
|
||||
@@ -44,8 +44,7 @@ cb_config_add_dse_entries(cb_backend *cb, char **entries, char *string1, char *s
|
||||
slapi_pblock_get(util_pb, SLAPI_PLUGIN_INTOP_RESULT, &res);
|
||||
if (LDAP_SUCCESS != res && LDAP_ALREADY_EXISTS != res) {
|
||||
slapi_log_err(SLAPI_LOG_ERR, CB_PLUGIN_SUBSYSTEM,
|
||||
- "cb_config_add_dse_entries - Unable to add config entry (%s) to the DSE: %s\n",
|
||||
- slapi_entry_get_dn(e),
|
||||
+ "cb_config_add_dse_entries - Unable to add config entry to the DSE: %s\n",
|
||||
ldap_err2string(res));
|
||||
rc = res;
|
||||
slapi_pblock_destroy(util_pb);
|
||||
diff --git a/ldap/servers/plugins/posix-winsync/posix-winsync.c b/ldap/servers/plugins/posix-winsync/posix-winsync.c
|
||||
index 56efb2330..ab37497cd 100644
|
||||
--- a/ldap/servers/plugins/posix-winsync/posix-winsync.c
|
||||
+++ b/ldap/servers/plugins/posix-winsync/posix-winsync.c
|
||||
@@ -1625,7 +1625,6 @@ posix_winsync_end_update_cb(void *cbdata __attribute__((unused)),
|
||||
"posix_winsync_end_update_cb: "
|
||||
"add task entry\n");
|
||||
}
|
||||
- /* slapi_entry_free(e_task); */
|
||||
slapi_pblock_destroy(pb);
|
||||
pb = NULL;
|
||||
posix_winsync_config_reset_MOFTaskCreated();
|
||||
diff --git a/ldap/servers/plugins/replication/repl5_init.c b/ldap/servers/plugins/replication/repl5_init.c
|
||||
index 5a748e35a..9b6523a2e 100644
|
||||
--- a/ldap/servers/plugins/replication/repl5_init.c
|
||||
+++ b/ldap/servers/plugins/replication/repl5_init.c
|
||||
@@ -682,7 +682,6 @@ create_repl_schema_policy(void)
|
||||
repl_schema_top,
|
||||
ldap_err2string(return_value));
|
||||
rc = -1;
|
||||
- slapi_entry_free(e); /* The entry was not consumed */
|
||||
goto done;
|
||||
}
|
||||
slapi_pblock_destroy(pb);
|
||||
@@ -703,7 +702,6 @@ create_repl_schema_policy(void)
|
||||
repl_schema_supplier,
|
||||
ldap_err2string(return_value));
|
||||
rc = -1;
|
||||
- slapi_entry_free(e); /* The entry was not consumed */
|
||||
goto done;
|
||||
}
|
||||
slapi_pblock_destroy(pb);
|
||||
@@ -724,7 +722,6 @@ create_repl_schema_policy(void)
|
||||
repl_schema_consumer,
|
||||
ldap_err2string(return_value));
|
||||
rc = -1;
|
||||
- slapi_entry_free(e); /* The entry was not consumed */
|
||||
goto done;
|
||||
}
|
||||
slapi_pblock_destroy(pb);
|
||||
diff --git a/ldap/servers/plugins/replication/repl5_replica.c b/ldap/servers/plugins/replication/repl5_replica.c
|
||||
index d67f1bc71..cec140140 100644
|
||||
--- a/ldap/servers/plugins/replication/repl5_replica.c
|
||||
+++ b/ldap/servers/plugins/replication/repl5_replica.c
|
||||
@@ -440,10 +440,10 @@ replica_subentry_create(const char *repl_root, ReplicaId rid)
|
||||
if (return_value != LDAP_SUCCESS &&
|
||||
return_value != LDAP_ALREADY_EXISTS &&
|
||||
return_value != LDAP_REFERRAL /* CONSUMER */) {
|
||||
- slapi_log_err(SLAPI_LOG_ERR, repl_plugin_name, "replica_subentry_create - Unable to "
|
||||
- "create replication keep alive entry %s: error %d - %s\n",
|
||||
- slapi_entry_get_dn_const(e),
|
||||
- return_value, ldap_err2string(return_value));
|
||||
+ slapi_log_err(SLAPI_LOG_ERR, repl_plugin_name, "replica_subentry_create - "
|
||||
+ "Unable to create replication keep alive entry 'cn=%s %d,%s': error %d - %s\n",
|
||||
+ KEEP_ALIVE_ENTRY, rid, repl_root,
|
||||
+ return_value, ldap_err2string(return_value));
|
||||
rc = -1;
|
||||
goto done;
|
||||
}
|
||||
--
|
||||
2.51.1
|
||||
|
||||
@ -1,720 +0,0 @@
|
||||
From 18a807e0e23b1160ea61e05e721da9fbd0c560b1 Mon Sep 17 00:00:00 2001
|
||||
From: Simon Pichugin <spichugi@redhat.com>
|
||||
Date: Mon, 28 Jul 2025 15:41:29 -0700
|
||||
Subject: [PATCH] Issue 6884 - Mask password hashes in audit logs (#6885)
|
||||
|
||||
Description: Fix the audit log functionality to mask password hash values for
|
||||
userPassword, nsslapd-rootpw, nsmultiplexorcredentials, nsds5ReplicaCredentials,
|
||||
and nsds5ReplicaBootstrapCredentials attributes in ADD and MODIFY operations.
|
||||
Update auditlog.c to detect password attributes and replace their values with
|
||||
asterisks (**********************) in both LDIF and JSON audit log formats.
|
||||
Add a comprehensive test suite audit_password_masking_test.py to verify
|
||||
password masking works correctly across all log formats and operation types.
|
||||
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/6884
|
||||
|
||||
Reviewed by: @mreynolds389, @vashirov (Thanks!!)
|
||||
|
||||
(cherry picked from commit 24f9aea1ae7e29bd885212825dc52d2a5db08a03)
|
||||
Signed-off-by: Masahiro Matsuya <mmatsuya@redhat.com>
|
||||
---
|
||||
.../logging/audit_password_masking_test.py | 457 ++++++++++++++++++
|
||||
ldap/servers/slapd/auditlog.c | 144 +++++-
|
||||
ldap/servers/slapd/slapi-private.h | 1 +
|
||||
src/lib389/lib389/chaining.py | 3 +-
|
||||
4 files changed, 586 insertions(+), 19 deletions(-)
|
||||
create mode 100644 dirsrvtests/tests/suites/logging/audit_password_masking_test.py
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/logging/audit_password_masking_test.py b/dirsrvtests/tests/suites/logging/audit_password_masking_test.py
|
||||
new file mode 100644
|
||||
index 000000000..ae379cbba
|
||||
--- /dev/null
|
||||
+++ b/dirsrvtests/tests/suites/logging/audit_password_masking_test.py
|
||||
@@ -0,0 +1,457 @@
|
||||
+# --- BEGIN COPYRIGHT BLOCK ---
|
||||
+# Copyright (C) 2025 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
|
||||
+import re
|
||||
+import time
|
||||
+import ldap
|
||||
+from lib389._constants import DEFAULT_SUFFIX, DN_DM, PW_DM
|
||||
+from lib389.topologies import topology_m2 as topo
|
||||
+from lib389.idm.user import UserAccounts
|
||||
+from lib389.plugins import ChainingBackendPlugin
|
||||
+from lib389.chaining import ChainingLinks
|
||||
+from lib389.agreement import Agreements
|
||||
+from lib389.replica import ReplicationManager, Replicas
|
||||
+from lib389.idm.directorymanager import DirectoryManager
|
||||
+
|
||||
+log = logging.getLogger(__name__)
|
||||
+
|
||||
+MASKED_PASSWORD = "**********************"
|
||||
+TEST_PASSWORD = "MySecret123"
|
||||
+TEST_PASSWORD_2 = "NewPassword789"
|
||||
+TEST_PASSWORD_3 = "NewPassword101"
|
||||
+
|
||||
+
|
||||
+def setup_audit_logging(inst, log_format='default', display_attrs=None):
|
||||
+ """Configure audit logging settings"""
|
||||
+ inst.config.replace('nsslapd-auditlog-logging-enabled', 'on')
|
||||
+
|
||||
+ if display_attrs is not None:
|
||||
+ inst.config.replace('nsslapd-auditlog-display-attrs', display_attrs)
|
||||
+
|
||||
+ inst.deleteAuditLogs()
|
||||
+
|
||||
+
|
||||
+def check_password_masked(inst, log_format, expected_password, actual_password):
|
||||
+ """Helper function to check password masking in audit logs"""
|
||||
+
|
||||
+ inst.restart() # Flush the logs
|
||||
+
|
||||
+ # List of all password/credential attributes that should be masked
|
||||
+ password_attributes = [
|
||||
+ 'userPassword',
|
||||
+ 'nsslapd-rootpw',
|
||||
+ 'nsmultiplexorcredentials',
|
||||
+ 'nsDS5ReplicaCredentials',
|
||||
+ 'nsDS5ReplicaBootstrapCredentials'
|
||||
+ ]
|
||||
+
|
||||
+ # Get password schemes to check for hash leakage
|
||||
+ user_password_scheme = inst.config.get_attr_val_utf8('passwordStorageScheme')
|
||||
+ root_password_scheme = inst.config.get_attr_val_utf8('nsslapd-rootpwstoragescheme')
|
||||
+
|
||||
+ # Check LDIF format logs
|
||||
+ found_masked = False
|
||||
+ found_actual = False
|
||||
+ found_hashed = False
|
||||
+
|
||||
+ # Check each password attribute for masked password
|
||||
+ for attr in password_attributes:
|
||||
+ if inst.ds_audit_log.match(f"{attr}: {re.escape(expected_password)}"):
|
||||
+ found_masked = True
|
||||
+ if inst.ds_audit_log.match(f"{attr}: {actual_password}"):
|
||||
+ found_actual = True
|
||||
+
|
||||
+ # Check for hashed passwords in LDIF format
|
||||
+ if user_password_scheme:
|
||||
+ if inst.ds_audit_log.match(f"userPassword: {{{user_password_scheme}}}"):
|
||||
+ found_hashed = True
|
||||
+ if root_password_scheme:
|
||||
+ if inst.ds_audit_log.match(f"nsslapd-rootpw: {{{root_password_scheme}}}"):
|
||||
+ found_hashed = True
|
||||
+
|
||||
+ # Delete audit logs to avoid interference with other tests
|
||||
+ # We need to reset the root password to default as deleteAuditLogs()
|
||||
+ # opens a new connection with the default password
|
||||
+ dm = DirectoryManager(inst)
|
||||
+ dm.change_password(PW_DM)
|
||||
+ inst.deleteAuditLogs()
|
||||
+
|
||||
+ return found_masked, found_actual, found_hashed
|
||||
+
|
||||
+
|
||||
+@pytest.mark.parametrize("log_format,display_attrs", [
|
||||
+ ("default", None),
|
||||
+ ("default", "*"),
|
||||
+ ("default", "userPassword"),
|
||||
+])
|
||||
+def test_password_masking_add_operation(topo, log_format, display_attrs):
|
||||
+ """Test password masking in ADD operations
|
||||
+
|
||||
+ :id: 4358bd75-bcc7-401c-b492-d3209b10412d
|
||||
+ :parametrized: yes
|
||||
+ :setup: Standalone Instance
|
||||
+ :steps:
|
||||
+ 1. Configure audit logging format
|
||||
+ 2. Add user with password
|
||||
+ 3. Check that password is masked in audit log
|
||||
+ 4. Verify actual password does not appear in log
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Password should be masked with asterisks
|
||||
+ 4. Actual password should not be found in log
|
||||
+ """
|
||||
+ inst = topo.ms['supplier1']
|
||||
+ setup_audit_logging(inst, log_format, display_attrs)
|
||||
+
|
||||
+ users = UserAccounts(inst, DEFAULT_SUFFIX)
|
||||
+ user = None
|
||||
+
|
||||
+ try:
|
||||
+ user = users.create(properties={
|
||||
+ 'uid': 'test_add_pwd_mask',
|
||||
+ 'cn': 'Test Add User',
|
||||
+ 'sn': 'User',
|
||||
+ 'uidNumber': '1000',
|
||||
+ 'gidNumber': '1000',
|
||||
+ 'homeDirectory': '/home/test_add',
|
||||
+ 'userPassword': TEST_PASSWORD
|
||||
+ })
|
||||
+
|
||||
+ found_masked, found_actual, found_hashed = check_password_masked(inst, log_format, MASKED_PASSWORD, TEST_PASSWORD)
|
||||
+
|
||||
+ assert found_masked, f"Masked password not found in {log_format} ADD operation"
|
||||
+ assert not found_actual, f"Actual password found in {log_format} ADD log (should be masked)"
|
||||
+ assert not found_hashed, f"Hashed password found in {log_format} ADD log (should be masked)"
|
||||
+
|
||||
+ finally:
|
||||
+ if user is not None:
|
||||
+ try:
|
||||
+ user.delete()
|
||||
+ except:
|
||||
+ pass
|
||||
+
|
||||
+
|
||||
+@pytest.mark.parametrize("log_format,display_attrs", [
|
||||
+ ("default", None),
|
||||
+ ("default", "*"),
|
||||
+ ("default", "userPassword"),
|
||||
+])
|
||||
+def test_password_masking_modify_operation(topo, log_format, display_attrs):
|
||||
+ """Test password masking in MODIFY operations
|
||||
+
|
||||
+ :id: e6963aa9-7609-419c-aae2-1d517aa434bd
|
||||
+ :parametrized: yes
|
||||
+ :setup: Standalone Instance
|
||||
+ :steps:
|
||||
+ 1. Configure audit logging format
|
||||
+ 2. Add user without password
|
||||
+ 3. Add password via MODIFY operation
|
||||
+ 4. Check that password is masked in audit log
|
||||
+ 5. Modify password to new value
|
||||
+ 6. Check that new password is also masked
|
||||
+ 7. Verify actual passwords do not appear in log
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Success
|
||||
+ 4. Password should be masked with asterisks
|
||||
+ 5. Success
|
||||
+ 6. New password should be masked with asterisks
|
||||
+ 7. No actual password values should be found in log
|
||||
+ """
|
||||
+ inst = topo.ms['supplier1']
|
||||
+ setup_audit_logging(inst, log_format, display_attrs)
|
||||
+
|
||||
+ users = UserAccounts(inst, DEFAULT_SUFFIX)
|
||||
+ user = None
|
||||
+
|
||||
+ try:
|
||||
+ user = users.create(properties={
|
||||
+ 'uid': 'test_modify_pwd_mask',
|
||||
+ 'cn': 'Test Modify User',
|
||||
+ 'sn': 'User',
|
||||
+ 'uidNumber': '2000',
|
||||
+ 'gidNumber': '2000',
|
||||
+ 'homeDirectory': '/home/test_modify'
|
||||
+ })
|
||||
+
|
||||
+ user.replace('userPassword', TEST_PASSWORD)
|
||||
+
|
||||
+ found_masked, found_actual, found_hashed = check_password_masked(inst, log_format, MASKED_PASSWORD, TEST_PASSWORD)
|
||||
+ assert found_masked, f"Masked password not found in {log_format} MODIFY operation (first password)"
|
||||
+ assert not found_actual, f"Actual password found in {log_format} MODIFY log (should be masked)"
|
||||
+ assert not found_hashed, f"Hashed password found in {log_format} MODIFY log (should be masked)"
|
||||
+
|
||||
+ user.replace('userPassword', TEST_PASSWORD_2)
|
||||
+
|
||||
+ found_masked_2, found_actual_2, found_hashed_2 = check_password_masked(inst, log_format, MASKED_PASSWORD, TEST_PASSWORD_2)
|
||||
+ assert found_masked_2, f"Masked password not found in {log_format} MODIFY operation (second password)"
|
||||
+ assert not found_actual_2, f"Second actual password found in {log_format} MODIFY log (should be masked)"
|
||||
+ assert not found_hashed_2, f"Second hashed password found in {log_format} MODIFY log (should be masked)"
|
||||
+
|
||||
+ finally:
|
||||
+ if user is not None:
|
||||
+ try:
|
||||
+ user.delete()
|
||||
+ except:
|
||||
+ pass
|
||||
+
|
||||
+
|
||||
+@pytest.mark.parametrize("log_format,display_attrs", [
|
||||
+ ("default", None),
|
||||
+ ("default", "*"),
|
||||
+ ("default", "nsslapd-rootpw"),
|
||||
+])
|
||||
+def test_password_masking_rootpw_modify_operation(topo, log_format, display_attrs):
|
||||
+ """Test password masking for nsslapd-rootpw MODIFY operations
|
||||
+
|
||||
+ :id: ec8c9fd4-56ba-4663-ab65-58efb3b445e4
|
||||
+ :parametrized: yes
|
||||
+ :setup: Standalone Instance
|
||||
+ :steps:
|
||||
+ 1. Configure audit logging format
|
||||
+ 2. Modify nsslapd-rootpw in configuration
|
||||
+ 3. Check that root password is masked in audit log
|
||||
+ 4. Modify root password to new value
|
||||
+ 5. Check that new root password is also masked
|
||||
+ 6. Verify actual root passwords do not appear in log
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Root password should be masked with asterisks
|
||||
+ 4. Success
|
||||
+ 5. New root password should be masked with asterisks
|
||||
+ 6. No actual root password values should be found in log
|
||||
+ """
|
||||
+ inst = topo.ms['supplier1']
|
||||
+ setup_audit_logging(inst, log_format, display_attrs)
|
||||
+ dm = DirectoryManager(inst)
|
||||
+
|
||||
+ try:
|
||||
+ dm.change_password(TEST_PASSWORD)
|
||||
+ dm.rebind(TEST_PASSWORD)
|
||||
+ dm.change_password(PW_DM)
|
||||
+
|
||||
+ found_masked, found_actual, found_hashed = check_password_masked(inst, log_format, MASKED_PASSWORD, TEST_PASSWORD)
|
||||
+ assert found_masked, f"Masked root password not found in {log_format} MODIFY operation (first root password)"
|
||||
+ assert not found_actual, f"Actual root password found in {log_format} MODIFY log (should be masked)"
|
||||
+ assert not found_hashed, f"Hashed root password found in {log_format} MODIFY log (should be masked)"
|
||||
+
|
||||
+ dm.change_password(TEST_PASSWORD_2)
|
||||
+ dm.rebind(TEST_PASSWORD_2)
|
||||
+ dm.change_password(PW_DM)
|
||||
+
|
||||
+ found_masked_2, found_actual_2, found_hashed_2 = check_password_masked(inst, log_format, MASKED_PASSWORD, TEST_PASSWORD_2)
|
||||
+ assert found_masked_2, f"Masked root password not found in {log_format} MODIFY operation (second root password)"
|
||||
+ assert not found_actual_2, f"Second actual root password found in {log_format} MODIFY log (should be masked)"
|
||||
+ assert not found_hashed_2, f"Second hashed root password found in {log_format} MODIFY log (should be masked)"
|
||||
+
|
||||
+ finally:
|
||||
+ dm.change_password(PW_DM)
|
||||
+ dm.rebind(PW_DM)
|
||||
+
|
||||
+
|
||||
+@pytest.mark.parametrize("log_format,display_attrs", [
|
||||
+ ("default", None),
|
||||
+ ("default", "*"),
|
||||
+ ("default", "nsmultiplexorcredentials"),
|
||||
+])
|
||||
+def test_password_masking_multiplexor_credentials(topo, log_format, display_attrs):
|
||||
+ """Test password masking for nsmultiplexorcredentials in chaining/multiplexor configurations
|
||||
+
|
||||
+ :id: 161a9498-b248-4926-90be-a696a36ed36e
|
||||
+ :parametrized: yes
|
||||
+ :setup: Standalone Instance
|
||||
+ :steps:
|
||||
+ 1. Configure audit logging format
|
||||
+ 2. Create a chaining backend configuration entry with nsmultiplexorcredentials
|
||||
+ 3. Check that multiplexor credentials are masked in audit log
|
||||
+ 4. Modify the credentials
|
||||
+ 5. Check that updated credentials are also masked
|
||||
+ 6. Verify actual credentials do not appear in log
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Multiplexor credentials should be masked with asterisks
|
||||
+ 4. Success
|
||||
+ 5. Updated credentials should be masked with asterisks
|
||||
+ 6. No actual credential values should be found in log
|
||||
+ """
|
||||
+ inst = topo.ms['supplier1']
|
||||
+ setup_audit_logging(inst, log_format, display_attrs)
|
||||
+
|
||||
+ # Enable chaining plugin and create chaining link
|
||||
+ chain_plugin = ChainingBackendPlugin(inst)
|
||||
+ chain_plugin.enable()
|
||||
+
|
||||
+ chains = ChainingLinks(inst)
|
||||
+ chain = None
|
||||
+
|
||||
+ try:
|
||||
+ # Create chaining link with multiplexor credentials
|
||||
+ chain = chains.create(properties={
|
||||
+ 'cn': 'testchain',
|
||||
+ 'nsfarmserverurl': 'ldap://localhost:389/',
|
||||
+ 'nsslapd-suffix': 'dc=example,dc=com',
|
||||
+ 'nsmultiplexorbinddn': 'cn=manager',
|
||||
+ 'nsmultiplexorcredentials': TEST_PASSWORD,
|
||||
+ 'nsCheckLocalACI': 'on',
|
||||
+ 'nsConnectionLife': '30',
|
||||
+ })
|
||||
+
|
||||
+ found_masked, found_actual, found_hashed = check_password_masked(inst, log_format, MASKED_PASSWORD, TEST_PASSWORD)
|
||||
+ assert found_masked, f"Masked multiplexor credentials not found in {log_format} ADD operation"
|
||||
+ assert not found_actual, f"Actual multiplexor credentials found in {log_format} ADD log (should be masked)"
|
||||
+ assert not found_hashed, f"Hashed multiplexor credentials found in {log_format} ADD log (should be masked)"
|
||||
+
|
||||
+ # Modify the credentials
|
||||
+ chain.replace('nsmultiplexorcredentials', TEST_PASSWORD_2)
|
||||
+
|
||||
+ found_masked_2, found_actual_2, found_hashed_2 = check_password_masked(inst, log_format, MASKED_PASSWORD, TEST_PASSWORD_2)
|
||||
+ assert found_masked_2, f"Masked multiplexor credentials not found in {log_format} MODIFY operation"
|
||||
+ assert not found_actual_2, f"Actual multiplexor credentials found in {log_format} MODIFY log (should be masked)"
|
||||
+ assert not found_hashed_2, f"Hashed multiplexor credentials found in {log_format} MODIFY log (should be masked)"
|
||||
+
|
||||
+ finally:
|
||||
+ chain_plugin.disable()
|
||||
+ if chain is not None:
|
||||
+ inst.delete_branch_s(chain.dn, ldap.SCOPE_ONELEVEL)
|
||||
+ chain.delete()
|
||||
+
|
||||
+
|
||||
+@pytest.mark.parametrize("log_format,display_attrs", [
|
||||
+ ("default", None),
|
||||
+ ("default", "*"),
|
||||
+ ("default", "nsDS5ReplicaCredentials"),
|
||||
+])
|
||||
+def test_password_masking_replica_credentials(topo, log_format, display_attrs):
|
||||
+ """Test password masking for nsDS5ReplicaCredentials in replication agreements
|
||||
+
|
||||
+ :id: 7bf9e612-1b7c-49af-9fc0-de4c7df84b2a
|
||||
+ :parametrized: yes
|
||||
+ :setup: Standalone Instance
|
||||
+ :steps:
|
||||
+ 1. Configure audit logging format
|
||||
+ 2. Create a replication agreement entry with nsDS5ReplicaCredentials
|
||||
+ 3. Check that replica credentials are masked in audit log
|
||||
+ 4. Modify the credentials
|
||||
+ 5. Check that updated credentials are also masked
|
||||
+ 6. Verify actual credentials do not appear in log
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Replica credentials should be masked with asterisks
|
||||
+ 4. Success
|
||||
+ 5. Updated credentials should be masked with asterisks
|
||||
+ 6. No actual credential values should be found in log
|
||||
+ """
|
||||
+ inst = topo.ms['supplier2']
|
||||
+ setup_audit_logging(inst, log_format, display_attrs)
|
||||
+ agmt = None
|
||||
+
|
||||
+ try:
|
||||
+ replicas = Replicas(inst)
|
||||
+ replica = replicas.get(DEFAULT_SUFFIX)
|
||||
+ agmts = replica.get_agreements()
|
||||
+ agmt = agmts.create(properties={
|
||||
+ 'cn': 'testagmt',
|
||||
+ 'nsDS5ReplicaHost': 'localhost',
|
||||
+ 'nsDS5ReplicaPort': '389',
|
||||
+ 'nsDS5ReplicaBindDN': 'cn=replication manager,cn=config',
|
||||
+ 'nsDS5ReplicaCredentials': TEST_PASSWORD,
|
||||
+ 'nsDS5ReplicaRoot': DEFAULT_SUFFIX
|
||||
+ })
|
||||
+
|
||||
+ found_masked, found_actual, found_hashed = check_password_masked(inst, log_format, MASKED_PASSWORD, TEST_PASSWORD)
|
||||
+ assert found_masked, f"Masked replica credentials not found in {log_format} ADD operation"
|
||||
+ assert not found_actual, f"Actual replica credentials found in {log_format} ADD log (should be masked)"
|
||||
+ assert not found_hashed, f"Hashed replica credentials found in {log_format} ADD log (should be masked)"
|
||||
+
|
||||
+ # Modify the credentials
|
||||
+ agmt.replace('nsDS5ReplicaCredentials', TEST_PASSWORD_2)
|
||||
+
|
||||
+ found_masked_2, found_actual_2, found_hashed_2 = check_password_masked(inst, log_format, MASKED_PASSWORD, TEST_PASSWORD_2)
|
||||
+ assert found_masked_2, f"Masked replica credentials not found in {log_format} MODIFY operation"
|
||||
+ assert not found_actual_2, f"Actual replica credentials found in {log_format} MODIFY log (should be masked)"
|
||||
+ assert not found_hashed_2, f"Hashed replica credentials found in {log_format} MODIFY log (should be masked)"
|
||||
+
|
||||
+ finally:
|
||||
+ if agmt is not None:
|
||||
+ agmt.delete()
|
||||
+
|
||||
+
|
||||
+@pytest.mark.parametrize("log_format,display_attrs", [
|
||||
+ ("default", None),
|
||||
+ ("default", "*"),
|
||||
+ ("default", "nsDS5ReplicaBootstrapCredentials"),
|
||||
+])
|
||||
+def test_password_masking_bootstrap_credentials(topo, log_format, display_attrs):
|
||||
+ """Test password masking for nsDS5ReplicaCredentials and nsDS5ReplicaBootstrapCredentials in replication agreements
|
||||
+
|
||||
+ :id: 248bd418-ffa4-4733-963d-2314c60b7c5b
|
||||
+ :parametrized: yes
|
||||
+ :setup: Standalone Instance
|
||||
+ :steps:
|
||||
+ 1. Configure audit logging format
|
||||
+ 2. Create a replication agreement entry with both nsDS5ReplicaCredentials and nsDS5ReplicaBootstrapCredentials
|
||||
+ 3. Check that both credentials are masked in audit log
|
||||
+ 4. Modify both credentials
|
||||
+ 5. Check that both updated credentials are also masked
|
||||
+ 6. Verify actual credentials do not appear in log
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Both credentials should be masked with asterisks
|
||||
+ 4. Success
|
||||
+ 5. Both updated credentials should be masked with asterisks
|
||||
+ 6. No actual credential values should be found in log
|
||||
+ """
|
||||
+ inst = topo.ms['supplier2']
|
||||
+ setup_audit_logging(inst, log_format, display_attrs)
|
||||
+ agmt = None
|
||||
+
|
||||
+ try:
|
||||
+ replicas = Replicas(inst)
|
||||
+ replica = replicas.get(DEFAULT_SUFFIX)
|
||||
+ agmts = replica.get_agreements()
|
||||
+ agmt = agmts.create(properties={
|
||||
+ 'cn': 'testbootstrapagmt',
|
||||
+ 'nsDS5ReplicaHost': 'localhost',
|
||||
+ 'nsDS5ReplicaPort': '389',
|
||||
+ 'nsDS5ReplicaBindDN': 'cn=replication manager,cn=config',
|
||||
+ 'nsDS5ReplicaCredentials': TEST_PASSWORD,
|
||||
+ 'nsDS5replicabootstrapbinddn': 'cn=bootstrap manager,cn=config',
|
||||
+ 'nsDS5ReplicaBootstrapCredentials': TEST_PASSWORD_2,
|
||||
+ 'nsDS5ReplicaRoot': DEFAULT_SUFFIX
|
||||
+ })
|
||||
+
|
||||
+ found_masked_bootstrap, found_actual_bootstrap, found_hashed_bootstrap = check_password_masked(inst, log_format, MASKED_PASSWORD, TEST_PASSWORD_2)
|
||||
+ assert found_masked_bootstrap, f"Masked bootstrap credentials not found in {log_format} ADD operation"
|
||||
+ assert not found_actual_bootstrap, f"Actual bootstrap credentials found in {log_format} ADD log (should be masked)"
|
||||
+ assert not found_hashed_bootstrap, f"Hashed bootstrap credentials found in {log_format} ADD log (should be masked)"
|
||||
+
|
||||
+ agmt.replace('nsDS5ReplicaBootstrapCredentials', TEST_PASSWORD_3)
|
||||
+
|
||||
+ found_masked_bootstrap_2, found_actual_bootstrap_2, found_hashed_bootstrap_2 = check_password_masked(inst, log_format, MASKED_PASSWORD, TEST_PASSWORD_3)
|
||||
+ assert found_masked_bootstrap_2, f"Masked bootstrap credentials not found in {log_format} MODIFY operation"
|
||||
+ assert not found_actual_bootstrap_2, f"Actual bootstrap credentials found in {log_format} MODIFY log (should be masked)"
|
||||
+ assert not found_hashed_bootstrap_2, f"Hashed bootstrap credentials found in {log_format} MODIFY log (should be masked)"
|
||||
+
|
||||
+ finally:
|
||||
+ if agmt is not None:
|
||||
+ agmt.delete()
|
||||
+
|
||||
+
|
||||
+
|
||||
+if __name__ == '__main__':
|
||||
+ CURRENT_FILE = os.path.realpath(__file__)
|
||||
+ pytest.main(["-s", CURRENT_FILE])
|
||||
\ No newline at end of file
|
||||
diff --git a/ldap/servers/slapd/auditlog.c b/ldap/servers/slapd/auditlog.c
|
||||
index 0597ecc6f..c41415725 100644
|
||||
--- a/ldap/servers/slapd/auditlog.c
|
||||
+++ b/ldap/servers/slapd/auditlog.c
|
||||
@@ -37,6 +37,89 @@ static void write_audit_file(Slapi_Entry *entry, int logtype, int optype, const
|
||||
|
||||
static const char *modrdn_changes[4];
|
||||
|
||||
+/* Helper function to check if an attribute is a password that needs masking */
|
||||
+static int
|
||||
+is_password_attribute(const char *attr_name)
|
||||
+{
|
||||
+ return (strcasecmp(attr_name, SLAPI_USERPWD_ATTR) == 0 ||
|
||||
+ strcasecmp(attr_name, CONFIG_ROOTPW_ATTRIBUTE) == 0 ||
|
||||
+ strcasecmp(attr_name, SLAPI_MB_CREDENTIALS) == 0 ||
|
||||
+ strcasecmp(attr_name, SLAPI_REP_CREDENTIALS) == 0 ||
|
||||
+ strcasecmp(attr_name, SLAPI_REP_BOOTSTRAP_CREDENTIALS) == 0);
|
||||
+}
|
||||
+
|
||||
+/* Helper function to create a masked string representation of an entry */
|
||||
+static char *
|
||||
+create_masked_entry_string(Slapi_Entry *original_entry, int *len)
|
||||
+{
|
||||
+ Slapi_Attr *attr = NULL;
|
||||
+ char *entry_str = NULL;
|
||||
+ char *current_pos = NULL;
|
||||
+ char *line_start = NULL;
|
||||
+ char *next_line = NULL;
|
||||
+ char *colon_pos = NULL;
|
||||
+ int has_password_attrs = 0;
|
||||
+
|
||||
+ if (original_entry == NULL) {
|
||||
+ return NULL;
|
||||
+ }
|
||||
+
|
||||
+ /* Single pass through attributes to check for password attributes */
|
||||
+ for (slapi_entry_first_attr(original_entry, &attr); attr != NULL;
|
||||
+ slapi_entry_next_attr(original_entry, attr, &attr)) {
|
||||
+
|
||||
+ char *attr_name = NULL;
|
||||
+ slapi_attr_get_type(attr, &attr_name);
|
||||
+
|
||||
+ if (is_password_attribute(attr_name)) {
|
||||
+ has_password_attrs = 1;
|
||||
+ break;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ /* If no password attributes, return original string - no masking needed */
|
||||
+ entry_str = slapi_entry2str(original_entry, len);
|
||||
+ if (!has_password_attrs) {
|
||||
+ return entry_str;
|
||||
+ }
|
||||
+
|
||||
+ /* Process the string in-place, replacing password values */
|
||||
+ current_pos = entry_str;
|
||||
+ while ((line_start = current_pos) != NULL && *line_start != '\0') {
|
||||
+ /* Find the end of current line */
|
||||
+ next_line = strchr(line_start, '\n');
|
||||
+ if (next_line != NULL) {
|
||||
+ *next_line = '\0'; /* Temporarily terminate line */
|
||||
+ current_pos = next_line + 1;
|
||||
+ } else {
|
||||
+ current_pos = NULL; /* Last line */
|
||||
+ }
|
||||
+
|
||||
+ /* Find the colon that separates attribute name from value */
|
||||
+ colon_pos = strchr(line_start, ':');
|
||||
+ if (colon_pos != NULL) {
|
||||
+ char saved_colon = *colon_pos;
|
||||
+ *colon_pos = '\0'; /* Temporarily null-terminate attribute name */
|
||||
+
|
||||
+ /* Check if this is a password attribute that needs masking */
|
||||
+ if (is_password_attribute(line_start)) {
|
||||
+ strcpy(colon_pos + 1, " **********************");
|
||||
+ }
|
||||
+
|
||||
+ *colon_pos = saved_colon; /* Restore colon */
|
||||
+ }
|
||||
+
|
||||
+ /* Restore newline if it was there */
|
||||
+ if (next_line != NULL) {
|
||||
+ *next_line = '\n';
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ /* Update length since we may have shortened the string */
|
||||
+ *len = strlen(entry_str);
|
||||
+ return entry_str; /* Return the modified original string */
|
||||
+}
|
||||
+
|
||||
void
|
||||
write_audit_log_entry(Slapi_PBlock *pb)
|
||||
{
|
||||
@@ -248,7 +331,21 @@ add_entry_attrs(Slapi_Entry *entry, lenstr *l)
|
||||
{
|
||||
slapi_entry_attr_find(entry, req_attr, &entry_attr);
|
||||
if (entry_attr) {
|
||||
- log_entry_attr(entry_attr, req_attr, l);
|
||||
+ if (strcmp(req_attr, PSEUDO_ATTR_UNHASHEDUSERPASSWORD) == 0) {
|
||||
+ /* Do not write the unhashed clear-text password */
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ /* Check if this is a password attribute that needs masking */
|
||||
+ if (is_password_attribute(req_attr)) {
|
||||
+ /* userpassword/rootdn password - mask the value */
|
||||
+ addlenstr(l, "#");
|
||||
+ addlenstr(l, req_attr);
|
||||
+ addlenstr(l, ": **********************\n");
|
||||
+ } else {
|
||||
+ /* Regular attribute - log normally */
|
||||
+ log_entry_attr(entry_attr, req_attr, l);
|
||||
+ }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -262,13 +359,11 @@ add_entry_attrs(Slapi_Entry *entry, lenstr *l)
|
||||
continue;
|
||||
}
|
||||
|
||||
- if (strcasecmp(attr, SLAPI_USERPWD_ATTR) == 0 ||
|
||||
- strcasecmp(attr, CONFIG_ROOTPW_ATTRIBUTE) == 0)
|
||||
- {
|
||||
+ if (is_password_attribute(attr)) {
|
||||
/* userpassword/rootdn password - mask the value */
|
||||
addlenstr(l, "#");
|
||||
addlenstr(l, attr);
|
||||
- addlenstr(l, ": ****************************\n");
|
||||
+ addlenstr(l, ": **********************\n");
|
||||
continue;
|
||||
}
|
||||
log_entry_attr(entry_attr, attr, l);
|
||||
@@ -354,6 +449,10 @@ write_audit_file(
|
||||
break;
|
||||
}
|
||||
}
|
||||
+
|
||||
+ /* Check if this is a password attribute that needs masking */
|
||||
+ int is_password_attr = is_password_attribute(mods[j]->mod_type);
|
||||
+
|
||||
switch (operationtype) {
|
||||
case LDAP_MOD_ADD:
|
||||
addlenstr(l, "add: ");
|
||||
@@ -378,18 +477,27 @@ write_audit_file(
|
||||
break;
|
||||
}
|
||||
if (operationtype != LDAP_MOD_IGNORE) {
|
||||
- for (i = 0; mods[j]->mod_bvalues != NULL && mods[j]->mod_bvalues[i] != NULL; i++) {
|
||||
- char *buf, *bufp;
|
||||
- len = strlen(mods[j]->mod_type);
|
||||
- len = LDIF_SIZE_NEEDED(len, mods[j]->mod_bvalues[i]->bv_len) + 1;
|
||||
- buf = slapi_ch_malloc(len);
|
||||
- bufp = buf;
|
||||
- slapi_ldif_put_type_and_value_with_options(&bufp, mods[j]->mod_type,
|
||||
- mods[j]->mod_bvalues[i]->bv_val,
|
||||
- mods[j]->mod_bvalues[i]->bv_len, 0);
|
||||
- *bufp = '\0';
|
||||
- addlenstr(l, buf);
|
||||
- slapi_ch_free((void **)&buf);
|
||||
+ if (is_password_attr) {
|
||||
+ /* Add masked password */
|
||||
+ for (i = 0; mods[j]->mod_bvalues != NULL && mods[j]->mod_bvalues[i] != NULL; i++) {
|
||||
+ addlenstr(l, mods[j]->mod_type);
|
||||
+ addlenstr(l, ": **********************\n");
|
||||
+ }
|
||||
+ } else {
|
||||
+ /* Add actual values for non-password attributes */
|
||||
+ for (i = 0; mods[j]->mod_bvalues != NULL && mods[j]->mod_bvalues[i] != NULL; i++) {
|
||||
+ char *buf, *bufp;
|
||||
+ len = strlen(mods[j]->mod_type);
|
||||
+ len = LDIF_SIZE_NEEDED(len, mods[j]->mod_bvalues[i]->bv_len) + 1;
|
||||
+ buf = slapi_ch_malloc(len);
|
||||
+ bufp = buf;
|
||||
+ slapi_ldif_put_type_and_value_with_options(&bufp, mods[j]->mod_type,
|
||||
+ mods[j]->mod_bvalues[i]->bv_val,
|
||||
+ mods[j]->mod_bvalues[i]->bv_len, 0);
|
||||
+ *bufp = '\0';
|
||||
+ addlenstr(l, buf);
|
||||
+ slapi_ch_free((void **)&buf);
|
||||
+ }
|
||||
}
|
||||
}
|
||||
addlenstr(l, "-\n");
|
||||
@@ -400,7 +508,7 @@ write_audit_file(
|
||||
e = change;
|
||||
addlenstr(l, attr_changetype);
|
||||
addlenstr(l, ": add\n");
|
||||
- tmp = slapi_entry2str(e, &len);
|
||||
+ tmp = create_masked_entry_string(e, &len);
|
||||
tmpsave = tmp;
|
||||
while ((tmp = strchr(tmp, '\n')) != NULL) {
|
||||
tmp++;
|
||||
diff --git a/ldap/servers/slapd/slapi-private.h b/ldap/servers/slapd/slapi-private.h
|
||||
index dfb0e272a..af2180e55 100644
|
||||
--- a/ldap/servers/slapd/slapi-private.h
|
||||
+++ b/ldap/servers/slapd/slapi-private.h
|
||||
@@ -843,6 +843,7 @@ void task_cleanup(void);
|
||||
/* for reversible encyrption */
|
||||
#define SLAPI_MB_CREDENTIALS "nsmultiplexorcredentials"
|
||||
#define SLAPI_REP_CREDENTIALS "nsds5ReplicaCredentials"
|
||||
+#define SLAPI_REP_BOOTSTRAP_CREDENTIALS "nsds5ReplicaBootstrapCredentials"
|
||||
int pw_rever_encode(Slapi_Value **vals, char *attr_name);
|
||||
int pw_rever_decode(char *cipher, char **plain, const char *attr_name);
|
||||
|
||||
diff --git a/src/lib389/lib389/chaining.py b/src/lib389/lib389/chaining.py
|
||||
index 533b83ebf..33ae78c8b 100644
|
||||
--- a/src/lib389/lib389/chaining.py
|
||||
+++ b/src/lib389/lib389/chaining.py
|
||||
@@ -134,7 +134,7 @@ class ChainingLink(DSLdapObject):
|
||||
"""
|
||||
|
||||
# Create chaining entry
|
||||
- super(ChainingLink, self).create(rdn, properties, basedn)
|
||||
+ link = super(ChainingLink, self).create(rdn, properties, basedn)
|
||||
|
||||
# Create mapping tree entry
|
||||
dn_comps = ldap.explode_dn(properties['nsslapd-suffix'][0])
|
||||
@@ -149,6 +149,7 @@ class ChainingLink(DSLdapObject):
|
||||
self._mts.ensure_state(properties=mt_properties)
|
||||
except ldap.ALREADY_EXISTS:
|
||||
pass
|
||||
+ return link
|
||||
|
||||
|
||||
class ChainingLinks(DSLdapObjects):
|
||||
--
|
||||
2.51.1
|
||||
|
||||
@ -1,137 +0,0 @@
|
||||
From 284da99d0cd1ad16c702f4a4f68d2a479ac41576 Mon Sep 17 00:00:00 2001
|
||||
From: Viktor Ashirov <vashirov@redhat.com>
|
||||
Date: Wed, 18 Jun 2025 11:12:28 +0200
|
||||
Subject: [PATCH] Issue 6819 - Incorrect pwdpolicysubentry returned for an
|
||||
entry with user password policy
|
||||
|
||||
Bug Description:
|
||||
When both subtree and user password policies exist, pwdpolicysubentry
|
||||
points to the subtree password policy instead of user password policy.
|
||||
|
||||
Fix Description:
|
||||
Update the template for CoS pointer definition to use
|
||||
`operational-default` modifier instead of `operational`.
|
||||
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/6819
|
||||
|
||||
Reviewed by: @droideck, @tbordaz (Thanks!)
|
||||
|
||||
(cherry picked from commit 622c191302879035ef7450a29aa7569ee768c3ab)
|
||||
Signed-off-by: Masahiro Matsuya <mmatsuya@redhat.com>
|
||||
---
|
||||
.../password/pwdPolicy_attribute_test.py | 73 +++++++++++++++++--
|
||||
src/lib389/lib389/pwpolicy.py | 2 +-
|
||||
2 files changed, 66 insertions(+), 9 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/password/pwdPolicy_attribute_test.py b/dirsrvtests/tests/suites/password/pwdPolicy_attribute_test.py
|
||||
index c2c1e47fb..0dde8d637 100644
|
||||
--- a/dirsrvtests/tests/suites/password/pwdPolicy_attribute_test.py
|
||||
+++ b/dirsrvtests/tests/suites/password/pwdPolicy_attribute_test.py
|
||||
@@ -59,17 +59,39 @@ def test_user(topology_st, request):
|
||||
return user
|
||||
|
||||
|
||||
-@pytest.fixture(scope="module")
|
||||
-def password_policy(topology_st, test_user):
|
||||
+@pytest.fixture(scope="function")
|
||||
+def password_policy(topology_st, request, test_user):
|
||||
"""Set up password policy for subtree and user"""
|
||||
|
||||
pwp = PwPolicyManager(topology_st.standalone)
|
||||
policy_props = {}
|
||||
- log.info('Create password policy for subtree {}'.format(OU_PEOPLE))
|
||||
- pwp.create_subtree_policy(OU_PEOPLE, policy_props)
|
||||
+ log.info(f"Create password policy for subtree {OU_PEOPLE}")
|
||||
+ try:
|
||||
+ pwp.create_subtree_policy(OU_PEOPLE, policy_props)
|
||||
+ except ldap.ALREADY_EXISTS:
|
||||
+ log.info(f"Subtree password policy for {OU_PEOPLE} already exist, skipping")
|
||||
+
|
||||
+ log.info(f"Create password policy for user {TEST_USER_DN}")
|
||||
+ try:
|
||||
+ pwp.create_user_policy(TEST_USER_DN, policy_props)
|
||||
+ except ldap.ALREADY_EXISTS:
|
||||
+ log.info(f"User password policy for {TEST_USER_DN} already exist, skipping")
|
||||
+
|
||||
+ def fin():
|
||||
+ log.info(f"Delete password policy for subtree {OU_PEOPLE}")
|
||||
+ try:
|
||||
+ pwp.delete_local_policy(OU_PEOPLE)
|
||||
+ except ValueError:
|
||||
+ log.info(f"Subtree password policy for {OU_PEOPLE} doesn't exist, skipping")
|
||||
+
|
||||
+ log.info(f"Delete password policy for user {TEST_USER_DN}")
|
||||
+ try:
|
||||
+ pwp.delete_local_policy(TEST_USER_DN)
|
||||
+ except ValueError:
|
||||
+ log.info(f"User password policy for {TEST_USER_DN} doesn't exist, skipping")
|
||||
+
|
||||
+ request.addfinalizer(fin)
|
||||
|
||||
- log.info('Create password policy for user {}'.format(TEST_USER_DN))
|
||||
- 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, test_user):
|
||||
@@ -257,8 +279,43 @@ def test_pwd_min_age(topology_st, test_user, password_policy):
|
||||
log.info('Bind as DM')
|
||||
topology_st.standalone.simple_bind_s(DN_DM, PASSWORD)
|
||||
user.reset_password(TEST_USER_PWD)
|
||||
- pwp.delete_local_policy(TEST_USER_DN)
|
||||
- pwp.delete_local_policy(OU_PEOPLE)
|
||||
+
|
||||
+
|
||||
+def test_pwdpolicysubentry(topology_st, password_policy):
|
||||
+ """Verify that 'pwdpolicysubentry' attr works as expected
|
||||
+ User should have a priority over a subtree.
|
||||
+
|
||||
+ :id: 4ab0c62a-623b-40b4-af67-99580c77b36c
|
||||
+ :setup: Standalone instance, a test user,
|
||||
+ password policy entries for a user and a subtree
|
||||
+ :steps:
|
||||
+ 1. Create a subtree policy
|
||||
+ 2. Create a user policy
|
||||
+ 3. Search for 'pwdpolicysubentry' in the user entry
|
||||
+ 4. Delete the user policy
|
||||
+ 5. Search for 'pwdpolicysubentry' in the user entry
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Should point to the user policy entry
|
||||
+ 4. Success
|
||||
+ 5. Should point to the subtree policy entry
|
||||
+
|
||||
+ """
|
||||
+
|
||||
+ users = UserAccounts(topology_st.standalone, OU_PEOPLE, rdn=None)
|
||||
+ user = users.get(TEST_USER_NAME)
|
||||
+
|
||||
+ pwp_subentry = user.get_attr_vals_utf8('pwdpolicysubentry')[0]
|
||||
+ assert 'nsPwPolicyEntry_subtree' not in pwp_subentry
|
||||
+ assert 'nsPwPolicyEntry_user' in pwp_subentry
|
||||
+
|
||||
+ pwp = PwPolicyManager(topology_st.standalone)
|
||||
+ pwp.delete_local_policy(TEST_USER_DN)
|
||||
+ pwp_subentry = user.get_attr_vals_utf8('pwdpolicysubentry')[0]
|
||||
+ assert 'nsPwPolicyEntry_subtree' in pwp_subentry
|
||||
+ assert 'nsPwPolicyEntry_user' not in pwp_subentry
|
||||
+
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Run isolated
|
||||
diff --git a/src/lib389/lib389/pwpolicy.py b/src/lib389/lib389/pwpolicy.py
|
||||
index 7ffe449cc..6a47a44fe 100644
|
||||
--- a/src/lib389/lib389/pwpolicy.py
|
||||
+++ b/src/lib389/lib389/pwpolicy.py
|
||||
@@ -168,7 +168,7 @@ class PwPolicyManager(object):
|
||||
|
||||
# The CoS specification entry at the subtree level
|
||||
cos_pointer_defs = CosPointerDefinitions(self._instance, dn)
|
||||
- cos_pointer_defs.create(properties={'cosAttribute': 'pwdpolicysubentry default operational',
|
||||
+ cos_pointer_defs.create(properties={'cosAttribute': 'pwdpolicysubentry default operational-default',
|
||||
'cosTemplateDn': cos_template.dn,
|
||||
'cn': 'nsPwPolicy_CoS'})
|
||||
except ldap.LDAPError as e:
|
||||
--
|
||||
2.51.1
|
||||
|
||||
@ -1,572 +0,0 @@
|
||||
From 23e56fd01eaa24a2fa945430f91600dd9c726d34 Mon Sep 17 00:00:00 2001
|
||||
From: Simon Pichugin <spichugi@redhat.com>
|
||||
Date: Tue, 19 Aug 2025 14:30:15 -0700
|
||||
Subject: [PATCH] Issue 6936 - Make user/subtree policy creation idempotent
|
||||
(#6937)
|
||||
|
||||
Description: Correct the CLI mapping typo to use 'nsslapd-pwpolicy-local',
|
||||
rework subtree policy detection to validate CoS templates and add user-policy detection.
|
||||
Make user/subtree policy creation idempotent via ensure_state, and improve deletion
|
||||
logic to distinguish subtree vs user policies and fail if none exist.
|
||||
|
||||
Add a test suite (pwp_history_local_override_test.py) exercising global-only and local-only
|
||||
history enforcement, local overriding global counts, immediate effect of dsconf updates,
|
||||
and fallback to global after removing a user policy, ensuring reliable behavior
|
||||
and preventing regressions.
|
||||
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/6936
|
||||
|
||||
Reviewed by: @mreynolds389 (Thanks!)
|
||||
|
||||
(cherry picked from commit da4eea126cc9019f540b57c1db9dec7988cade10)
|
||||
Signed-off-by: Masahiro Matsuya <mmatsuya@redhat.com>
|
||||
---
|
||||
.../pwp_history_local_override_test.py | 351 ++++++++++++++++++
|
||||
src/lib389/lib389/cli_conf/pwpolicy.py | 4 +-
|
||||
src/lib389/lib389/pwpolicy.py | 107 ++++--
|
||||
3 files changed, 424 insertions(+), 38 deletions(-)
|
||||
create mode 100644 dirsrvtests/tests/suites/password/pwp_history_local_override_test.py
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/password/pwp_history_local_override_test.py b/dirsrvtests/tests/suites/password/pwp_history_local_override_test.py
|
||||
new file mode 100644
|
||||
index 000000000..6d72725fa
|
||||
--- /dev/null
|
||||
+++ b/dirsrvtests/tests/suites/password/pwp_history_local_override_test.py
|
||||
@@ -0,0 +1,351 @@
|
||||
+# --- BEGIN COPYRIGHT BLOCK ---
|
||||
+# Copyright (C) 2025 Red Hat, Inc.
|
||||
+# All rights reserved.
|
||||
+#
|
||||
+# License: GPL (version 3 or any later version).
|
||||
+# See LICENSE for details.
|
||||
+# --- END COPYRIGHT BLOCK ---
|
||||
+#
|
||||
+import os
|
||||
+import time
|
||||
+import ldap
|
||||
+import pytest
|
||||
+import subprocess
|
||||
+import logging
|
||||
+
|
||||
+from lib389._constants import DEFAULT_SUFFIX, DN_DM, PASSWORD, DN_CONFIG
|
||||
+from lib389.topologies import topology_st
|
||||
+from lib389.idm.user import UserAccounts
|
||||
+from lib389.idm.domain import Domain
|
||||
+from lib389.pwpolicy import PwPolicyManager
|
||||
+
|
||||
+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__)
|
||||
+
|
||||
+OU_DN = f"ou=People,{DEFAULT_SUFFIX}"
|
||||
+USER_ACI = '(targetattr="userpassword || passwordHistory")(version 3.0; acl "pwp test"; allow (all) userdn="ldap:///self";)'
|
||||
+
|
||||
+
|
||||
+@pytest.fixture(autouse=True, scope="function")
|
||||
+def restore_global_policy(topology_st, request):
|
||||
+ """Snapshot and restore global password policy around each test in this file."""
|
||||
+ inst = topology_st.standalone
|
||||
+ inst.simple_bind_s(DN_DM, PASSWORD)
|
||||
+
|
||||
+ attrs = [
|
||||
+ 'nsslapd-pwpolicy-local',
|
||||
+ 'nsslapd-pwpolicy-inherit-global',
|
||||
+ 'passwordHistory',
|
||||
+ 'passwordInHistory',
|
||||
+ 'passwordChange',
|
||||
+ ]
|
||||
+
|
||||
+ entry = inst.getEntry(DN_CONFIG, ldap.SCOPE_BASE, '(objectClass=*)', attrs)
|
||||
+ saved = {attr: entry.getValue(attr) for attr in attrs}
|
||||
+
|
||||
+ def fin():
|
||||
+ inst.simple_bind_s(DN_DM, PASSWORD)
|
||||
+ for attr, value in saved.items():
|
||||
+ inst.config.replace(attr, value)
|
||||
+
|
||||
+ request.addfinalizer(fin)
|
||||
+
|
||||
+
|
||||
+@pytest.fixture(scope="function")
|
||||
+def setup_entries(topology_st, request):
|
||||
+ """Create test OU and user, and install an ACI for self password changes."""
|
||||
+
|
||||
+ inst = topology_st.standalone
|
||||
+
|
||||
+ suffix = Domain(inst, DEFAULT_SUFFIX)
|
||||
+ suffix.add('aci', USER_ACI)
|
||||
+
|
||||
+ users = UserAccounts(inst, DEFAULT_SUFFIX)
|
||||
+ try:
|
||||
+ user = users.create_test_user(uid=1)
|
||||
+ except ldap.ALREADY_EXISTS:
|
||||
+ user = users.get("test_user_1")
|
||||
+
|
||||
+ def fin():
|
||||
+ pwp = PwPolicyManager(inst)
|
||||
+ try:
|
||||
+ pwp.delete_local_policy(OU_DN)
|
||||
+ except Exception as e:
|
||||
+ if "No password policy" in str(e):
|
||||
+ pass
|
||||
+ else:
|
||||
+ raise e
|
||||
+ try:
|
||||
+ pwp.delete_local_policy(user.dn)
|
||||
+ except Exception as e:
|
||||
+ if "No password policy" in str(e):
|
||||
+ pass
|
||||
+ else:
|
||||
+ raise e
|
||||
+ suffix.remove('aci', USER_ACI)
|
||||
+ request.addfinalizer(fin)
|
||||
+
|
||||
+ return user
|
||||
+
|
||||
+
|
||||
+def set_user_password(inst, user, new_password, bind_as_user_password=None, expect_violation=False):
|
||||
+ if bind_as_user_password is not None:
|
||||
+ user.rebind(bind_as_user_password)
|
||||
+ try:
|
||||
+ user.reset_password(new_password)
|
||||
+ if expect_violation:
|
||||
+ pytest.fail("Password change unexpectedly succeeded")
|
||||
+ except ldap.CONSTRAINT_VIOLATION:
|
||||
+ if not expect_violation:
|
||||
+ pytest.fail("Password change unexpectedly rejected with CONSTRAINT_VIOLATION")
|
||||
+ finally:
|
||||
+ inst.simple_bind_s(DN_DM, PASSWORD)
|
||||
+ time.sleep(1)
|
||||
+
|
||||
+
|
||||
+def set_global_history(inst, enabled: bool, count: int, inherit_global: str = 'on'):
|
||||
+ inst.simple_bind_s(DN_DM, PASSWORD)
|
||||
+ inst.config.replace('nsslapd-pwpolicy-local', 'on')
|
||||
+ inst.config.replace('nsslapd-pwpolicy-inherit-global', inherit_global)
|
||||
+ inst.config.replace('passwordHistory', 'on' if enabled else 'off')
|
||||
+ inst.config.replace('passwordInHistory', str(count))
|
||||
+ inst.config.replace('passwordChange', 'on')
|
||||
+ time.sleep(1)
|
||||
+
|
||||
+
|
||||
+def ensure_local_subtree_policy(inst, count: int, track_update_time: str = 'on'):
|
||||
+ pwp = PwPolicyManager(inst)
|
||||
+ pwp.create_subtree_policy(OU_DN, {
|
||||
+ 'passwordChange': 'on',
|
||||
+ 'passwordHistory': 'on',
|
||||
+ 'passwordInHistory': str(count),
|
||||
+ 'passwordTrackUpdateTime': track_update_time,
|
||||
+ })
|
||||
+ time.sleep(1)
|
||||
+
|
||||
+
|
||||
+def set_local_history_via_cli(inst, count: int):
|
||||
+ sbin_dir = inst.get_sbin_dir()
|
||||
+ inst_name = inst.serverid
|
||||
+ cmd = [f"{sbin_dir}/dsconf", inst_name, "localpwp", "set", f"--pwdhistorycount={count}", OU_DN]
|
||||
+ rc = subprocess.call(cmd)
|
||||
+ assert rc == 0, f"dsconf command failed rc={rc}: {' '.join(cmd)}"
|
||||
+ time.sleep(1)
|
||||
+
|
||||
+
|
||||
+def test_global_history_only_enforced(topology_st, setup_entries):
|
||||
+ """Global-only history enforcement with count 2
|
||||
+
|
||||
+ :id: 3d8cf35b-4a33-4587-9814-ebe18b7a1f92
|
||||
+ :setup: Standalone instance, test OU and user, ACI for self password changes
|
||||
+ :steps:
|
||||
+ 1. Remove local policies
|
||||
+ 2. Set global policy: passwordHistory=on, passwordInHistory=2
|
||||
+ 3. Set password to Alpha1, then change to Alpha2 and Alpha3 as the user
|
||||
+ 4. Attempt to change to Alpha1 and Alpha2
|
||||
+ 5. Attempt to change to Alpha4
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Success
|
||||
+ 4. Changes to Welcome1 and Welcome2 are rejected with CONSTRAINT_VIOLATION
|
||||
+ 5. Change to Welcome4 is accepted
|
||||
+ """
|
||||
+ inst = topology_st.standalone
|
||||
+ inst.simple_bind_s(DN_DM, PASSWORD)
|
||||
+
|
||||
+ set_global_history(inst, enabled=True, count=2)
|
||||
+
|
||||
+ user = setup_entries
|
||||
+ user.reset_password('Alpha1')
|
||||
+ set_user_password(inst, user, 'Alpha2', bind_as_user_password='Alpha1')
|
||||
+ set_user_password(inst, user, 'Alpha3', bind_as_user_password='Alpha2')
|
||||
+
|
||||
+ # Within last 2
|
||||
+ set_user_password(inst, user, 'Alpha2', bind_as_user_password='Alpha3', expect_violation=True)
|
||||
+ set_user_password(inst, user, 'Alpha1', bind_as_user_password='Alpha3', expect_violation=True)
|
||||
+
|
||||
+ # New password should be allowed
|
||||
+ set_user_password(inst, user, 'Alpha4', bind_as_user_password='Alpha3', expect_violation=False)
|
||||
+
|
||||
+
|
||||
+def test_local_overrides_global_history(topology_st, setup_entries):
|
||||
+ """Local subtree policy (history=3) overrides global (history=1)
|
||||
+
|
||||
+ :id: 97c22f56-5ea6-40c1-8d8c-1cece3bf46fd
|
||||
+ :setup: Standalone instance, test OU and user
|
||||
+ :steps:
|
||||
+ 1. Set global policy passwordInHistory=1
|
||||
+ 2. Create local subtree policy on the OU with passwordInHistory=3
|
||||
+ 3. Set password to Bravo1, then change to Bravo2 and Bravo3 as the user
|
||||
+ 4. Attempt to change to Bravo1
|
||||
+ 5. Attempt to change to Bravo5
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Success
|
||||
+ 4. Change to Welcome1 is rejected (local policy wins)
|
||||
+ 5. Change to Welcome5 is accepted
|
||||
+ """
|
||||
+ inst = topology_st.standalone
|
||||
+ inst.simple_bind_s(DN_DM, PASSWORD)
|
||||
+
|
||||
+ set_global_history(inst, enabled=True, count=1, inherit_global='on')
|
||||
+
|
||||
+ ensure_local_subtree_policy(inst, count=3)
|
||||
+
|
||||
+ user = setup_entries
|
||||
+ user.reset_password('Bravo1')
|
||||
+ set_user_password(inst, user, 'Bravo2', bind_as_user_password='Bravo1')
|
||||
+ set_user_password(inst, user, 'Bravo3', bind_as_user_password='Bravo2')
|
||||
+
|
||||
+ # Third prior should be rejected under local policy count=3
|
||||
+ set_user_password(inst, user, 'Bravo1', bind_as_user_password='Bravo3', expect_violation=True)
|
||||
+
|
||||
+ # New password allowed
|
||||
+ set_user_password(inst, user, 'Bravo5', bind_as_user_password='Bravo3', expect_violation=False)
|
||||
+
|
||||
+
|
||||
+def test_change_local_history_via_cli_affects_enforcement(topology_st, setup_entries):
|
||||
+ """Changing local policy via CLI is enforced immediately
|
||||
+
|
||||
+ :id: 5a6d0d14-4009-4bad-86e1-cde5000c43dc
|
||||
+ :setup: Standalone instance, test OU and user, dsconf available
|
||||
+ :steps:
|
||||
+ 1. Ensure local subtree policy passwordInHistory=3
|
||||
+ 2. Set password to Charlie1, then change to Charlie2 and Charlie3 as the user
|
||||
+ 3. Attempt to change to Charlie1 (within last 3)
|
||||
+ 4. Run: dsconf <inst> localpwp set --pwdhistorycount=1 "ou=product testing,<suffix>"
|
||||
+ 5. Attempt to change to Charlie1 again
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Change to Welcome1 is rejected
|
||||
+ 4. CLI command succeeds
|
||||
+ 5. Change to Welcome1 now succeeds (only last 1 is disallowed)
|
||||
+ """
|
||||
+ inst = topology_st.standalone
|
||||
+ inst.simple_bind_s(DN_DM, PASSWORD)
|
||||
+
|
||||
+ ensure_local_subtree_policy(inst, count=3)
|
||||
+
|
||||
+ user = setup_entries
|
||||
+ user.reset_password('Charlie1')
|
||||
+ set_user_password(inst, user, 'Charlie2', bind_as_user_password='Charlie1', expect_violation=False)
|
||||
+ set_user_password(inst, user, 'Charlie3', bind_as_user_password='Charlie2', expect_violation=False)
|
||||
+
|
||||
+ # With count=3, Welcome1 is within history
|
||||
+ set_user_password(inst, user, 'Charlie1', bind_as_user_password='Charlie3', expect_violation=True)
|
||||
+
|
||||
+ # Reduce local count to 1 via CLI to exercise CLI mapping and updated code
|
||||
+ set_local_history_via_cli(inst, count=1)
|
||||
+
|
||||
+ # Now Welcome1 should be allowed
|
||||
+ set_user_password(inst, user, 'Charlie1', bind_as_user_password='Charlie3', expect_violation=False)
|
||||
+
|
||||
+
|
||||
+def test_history_local_only_enforced(topology_st, setup_entries):
|
||||
+ """Local-only history enforcement with count 3
|
||||
+
|
||||
+ :id: af6ff34d-ac94-4108-a7b6-2b589c960154
|
||||
+ :setup: Standalone instance, test OU and user
|
||||
+ :steps:
|
||||
+ 1. Disable global password history (passwordHistory=off, passwordInHistory=0, inherit off)
|
||||
+ 2. Ensure local subtree policy with passwordInHistory=3
|
||||
+ 3. Set password to Delta1, then change to Delta2 and Delta3 as the user
|
||||
+ 4. Attempt to change to Delta1
|
||||
+ 5. Attempt to change to Delta5
|
||||
+ 6. Change once more to Delta6, then change to Delta1
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. Success
|
||||
+ 4. Change to Welcome1 is rejected (within last 3)
|
||||
+ 5. Change to Welcome5 is accepted
|
||||
+ 6. Welcome1 is now older than the last 3 and is accepted
|
||||
+ """
|
||||
+ inst = topology_st.standalone
|
||||
+ inst.simple_bind_s(DN_DM, PASSWORD)
|
||||
+
|
||||
+ set_global_history(inst, enabled=False, count=0, inherit_global='off')
|
||||
+
|
||||
+ ensure_local_subtree_policy(inst, count=3)
|
||||
+
|
||||
+ user = setup_entries
|
||||
+ user.reset_password('Delta1')
|
||||
+ set_user_password(inst, user, 'Delta2', bind_as_user_password='Delta1')
|
||||
+ set_user_password(inst, user, 'Delta3', bind_as_user_password='Delta2')
|
||||
+
|
||||
+ # Within last 2
|
||||
+ set_user_password(inst, user, 'Delta1', bind_as_user_password='Delta3', expect_violation=True)
|
||||
+
|
||||
+ # New password allowed
|
||||
+ set_user_password(inst, user, 'Delta5', bind_as_user_password='Delta3', expect_violation=False)
|
||||
+
|
||||
+ # Now Welcome1 is older than last 2 after one more change
|
||||
+ set_user_password(inst, user, 'Delta6', bind_as_user_password='Delta5', expect_violation=False)
|
||||
+ set_user_password(inst, user, 'Delta1', bind_as_user_password='Delta6', expect_violation=False)
|
||||
+
|
||||
+
|
||||
+def test_user_policy_detection_and_enforcement(topology_st, setup_entries):
|
||||
+ """User local policy is detected and enforced; removal falls back to global policy
|
||||
+
|
||||
+ :id: 2213126a-1f47-468c-8337-0d2ee5d2d585
|
||||
+ :setup: Standalone instance, test OU and user
|
||||
+ :steps:
|
||||
+ 1. Set global policy passwordInHistory=1
|
||||
+ 2. Create a user local password policy on the user with passwordInHistory=3
|
||||
+ 3. Verify is_user_policy(USER_DN) is True
|
||||
+ 4. Set password to Echo1, then change to Echo2 and Echo3 as the user
|
||||
+ 5. Attempt to change to Echo1 (within last 3)
|
||||
+ 6. Delete the user local policy
|
||||
+ 7. Verify is_user_policy(USER_DN) is False
|
||||
+ 8. Attempt to change to Echo1 again (now only last 1 disallowed by global)
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Success
|
||||
+ 3. is_user_policy returns True
|
||||
+ 4. Success
|
||||
+ 5. Change to Welcome1 is rejected
|
||||
+ 6. Success
|
||||
+ 7. is_user_policy returns False
|
||||
+ 8. Change to Welcome1 succeeds (two back is allowed by global=1)
|
||||
+ """
|
||||
+ inst = topology_st.standalone
|
||||
+ inst.simple_bind_s(DN_DM, PASSWORD)
|
||||
+
|
||||
+ set_global_history(inst, enabled=True, count=1, inherit_global='on')
|
||||
+
|
||||
+ pwp = PwPolicyManager(inst)
|
||||
+ user = setup_entries
|
||||
+ pwp.create_user_policy(user.dn, {
|
||||
+ 'passwordChange': 'on',
|
||||
+ 'passwordHistory': 'on',
|
||||
+ 'passwordInHistory': '3',
|
||||
+ })
|
||||
+
|
||||
+ assert pwp.is_user_policy(user.dn) is True
|
||||
+
|
||||
+ user.reset_password('Echo1')
|
||||
+ set_user_password(inst, user, 'Echo2', bind_as_user_password='Echo1', expect_violation=False)
|
||||
+ set_user_password(inst, user, 'Echo3', bind_as_user_password='Echo2', expect_violation=False)
|
||||
+ set_user_password(inst, user, 'Echo1', bind_as_user_password='Echo3', expect_violation=True)
|
||||
+
|
||||
+ pwp.delete_local_policy(user.dn)
|
||||
+ assert pwp.is_user_policy(user.dn) is False
|
||||
+
|
||||
+ # With only global=1, Echo1 (two back) is allowed
|
||||
+ set_user_password(inst, user, 'Echo1', bind_as_user_password='Echo3', expect_violation=False)
|
||||
+
|
||||
+
|
||||
+if __name__ == '__main__':
|
||||
+ # Run isolated
|
||||
+ # -s for DEBUG mode
|
||||
+ CURRENT_FILE = os.path.realpath(__file__)
|
||||
+ pytest.main("-s %s" % CURRENT_FILE)
|
||||
diff --git a/src/lib389/lib389/cli_conf/pwpolicy.py b/src/lib389/lib389/cli_conf/pwpolicy.py
|
||||
index 2d4ba9b21..a3e59a90c 100644
|
||||
--- a/src/lib389/lib389/cli_conf/pwpolicy.py
|
||||
+++ b/src/lib389/lib389/cli_conf/pwpolicy.py
|
||||
@@ -1,5 +1,5 @@
|
||||
# --- BEGIN COPYRIGHT BLOCK ---
|
||||
-# Copyright (C) 2023 Red Hat, Inc.
|
||||
+# Copyright (C) 2025 Red Hat, Inc.
|
||||
# All rights reserved.
|
||||
#
|
||||
# License: GPL (version 3 or any later version).
|
||||
@@ -43,7 +43,7 @@ def _get_pw_policy(inst, targetdn, log, use_json=None):
|
||||
targetdn = 'cn=config'
|
||||
policydn = targetdn
|
||||
basedn = targetdn
|
||||
- attr_list.extend(['passwordisglobalpolicy', 'nsslapd-pwpolicy_local'])
|
||||
+ attr_list.extend(['passwordisglobalpolicy', 'nsslapd-pwpolicy-local'])
|
||||
all_attrs = inst.config.get_attrs_vals_utf8(attr_list)
|
||||
attrs = {k: v for k, v in all_attrs.items() if len(v) > 0}
|
||||
else:
|
||||
diff --git a/src/lib389/lib389/pwpolicy.py b/src/lib389/lib389/pwpolicy.py
|
||||
index 6a47a44fe..539c230a9 100644
|
||||
--- a/src/lib389/lib389/pwpolicy.py
|
||||
+++ b/src/lib389/lib389/pwpolicy.py
|
||||
@@ -1,5 +1,5 @@
|
||||
# --- BEGIN COPYRIGHT BLOCK ---
|
||||
-# Copyright (C) 2018 Red Hat, Inc.
|
||||
+# Copyright (C) 2025 Red Hat, Inc.
|
||||
# All rights reserved.
|
||||
#
|
||||
# License: GPL (version 3 or any later version).
|
||||
@@ -7,6 +7,7 @@
|
||||
# --- END COPYRIGHT BLOCK ---
|
||||
|
||||
import ldap
|
||||
+from ldap import filter as ldap_filter
|
||||
from lib389._mapped_object import DSLdapObject, DSLdapObjects
|
||||
from lib389.backend import Backends
|
||||
from lib389.config import Config
|
||||
@@ -74,19 +75,56 @@ class PwPolicyManager(object):
|
||||
}
|
||||
|
||||
def is_subtree_policy(self, dn):
|
||||
- """Check if the entry has a subtree password policy. If we can find a
|
||||
- template entry it is subtree policy
|
||||
+ """Check if a subtree password policy exists for a given entry DN.
|
||||
|
||||
- :param dn: Entry DN with PwPolicy set up
|
||||
+ A subtree policy is indicated by the presence of any CoS template
|
||||
+ (under `cn=nsPwPolicyContainer,<dn>`) that has a `pwdpolicysubentry`
|
||||
+ attribute pointing to an existing entry with objectClass `passwordpolicy`.
|
||||
+
|
||||
+ :param dn: Entry DN to check for subtree policy
|
||||
:type dn: str
|
||||
|
||||
- :returns: True if the entry has a subtree policy, False otherwise
|
||||
+ :returns: True if a subtree policy exists, False otherwise
|
||||
+ :rtype: bool
|
||||
"""
|
||||
- cos_templates = CosTemplates(self._instance, 'cn=nsPwPolicyContainer,{}'.format(dn))
|
||||
try:
|
||||
- cos_templates.get('cn=nsPwTemplateEntry,%s' % dn)
|
||||
- return True
|
||||
- except:
|
||||
+ container_basedn = 'cn=nsPwPolicyContainer,{}'.format(dn)
|
||||
+ templates = CosTemplates(self._instance, container_basedn).list()
|
||||
+ for tmpl in templates:
|
||||
+ pwp_dn = tmpl.get_attr_val_utf8('pwdpolicysubentry')
|
||||
+ if not pwp_dn:
|
||||
+ continue
|
||||
+ # Validate that the referenced entry exists and is a passwordpolicy
|
||||
+ pwp_entry = PwPolicyEntry(self._instance, pwp_dn)
|
||||
+ if pwp_entry.exists() and pwp_entry.present('objectClass', 'passwordpolicy'):
|
||||
+ return True
|
||||
+ except ldap.LDAPError:
|
||||
+ pass
|
||||
+ return False
|
||||
+
|
||||
+ def is_user_policy(self, dn):
|
||||
+ """Check if the entry has a user password policy.
|
||||
+
|
||||
+ A user policy is indicated by the target entry having a
|
||||
+ `pwdpolicysubentry` attribute that points to an existing
|
||||
+ entry with objectClass `passwordpolicy`.
|
||||
+
|
||||
+ :param dn: Entry DN to check
|
||||
+ :type dn: str
|
||||
+
|
||||
+ :returns: True if the entry has a user policy, False otherwise
|
||||
+ :rtype: bool
|
||||
+ """
|
||||
+ try:
|
||||
+ entry = Account(self._instance, dn)
|
||||
+ if not entry.exists():
|
||||
+ return False
|
||||
+ pwp_dn = entry.get_attr_val_utf8('pwdpolicysubentry')
|
||||
+ if not pwp_dn:
|
||||
+ return False
|
||||
+ pwp_entry = PwPolicyEntry(self._instance, pwp_dn)
|
||||
+ return pwp_entry.exists() and pwp_entry.present('objectClass', 'passwordpolicy')
|
||||
+ except ldap.LDAPError:
|
||||
return False
|
||||
|
||||
def create_user_policy(self, dn, properties):
|
||||
@@ -114,10 +152,10 @@ class PwPolicyManager(object):
|
||||
pwp_containers = nsContainers(self._instance, basedn=parentdn)
|
||||
pwp_container = pwp_containers.ensure_state(properties={'cn': 'nsPwPolicyContainer'})
|
||||
|
||||
- # Create policy entry
|
||||
+ # Create or update the policy entry
|
||||
properties['cn'] = 'cn=nsPwPolicyEntry_user,%s' % dn
|
||||
pwp_entries = PwPolicyEntries(self._instance, pwp_container.dn)
|
||||
- pwp_entry = pwp_entries.create(properties=properties)
|
||||
+ pwp_entry = pwp_entries.ensure_state(properties=properties)
|
||||
try:
|
||||
# Add policy to the entry
|
||||
user_entry.replace('pwdpolicysubentry', pwp_entry.dn)
|
||||
@@ -152,32 +190,27 @@ class PwPolicyManager(object):
|
||||
pwp_containers = nsContainers(self._instance, basedn=dn)
|
||||
pwp_container = pwp_containers.ensure_state(properties={'cn': 'nsPwPolicyContainer'})
|
||||
|
||||
- # Create policy entry
|
||||
- pwp_entry = None
|
||||
+ # Create or update the policy entry
|
||||
properties['cn'] = 'cn=nsPwPolicyEntry_subtree,%s' % dn
|
||||
pwp_entries = PwPolicyEntries(self._instance, pwp_container.dn)
|
||||
- pwp_entry = pwp_entries.create(properties=properties)
|
||||
- try:
|
||||
- # The CoS template entry (nsPwTemplateEntry) that has the pwdpolicysubentry
|
||||
- # value pointing to the above (nsPwPolicyEntry) entry
|
||||
- cos_template = None
|
||||
- cos_templates = CosTemplates(self._instance, pwp_container.dn)
|
||||
- cos_template = cos_templates.create(properties={'cosPriority': '1',
|
||||
- 'pwdpolicysubentry': pwp_entry.dn,
|
||||
- 'cn': 'cn=nsPwTemplateEntry,%s' % dn})
|
||||
-
|
||||
- # The CoS specification entry at the subtree level
|
||||
- cos_pointer_defs = CosPointerDefinitions(self._instance, dn)
|
||||
- cos_pointer_defs.create(properties={'cosAttribute': 'pwdpolicysubentry default operational-default',
|
||||
- 'cosTemplateDn': cos_template.dn,
|
||||
- 'cn': 'nsPwPolicy_CoS'})
|
||||
- except ldap.LDAPError as e:
|
||||
- # Something went wrong, remove what we have done
|
||||
- if pwp_entry is not None:
|
||||
- pwp_entry.delete()
|
||||
- if cos_template is not None:
|
||||
- cos_template.delete()
|
||||
- raise e
|
||||
+ pwp_entry = pwp_entries.ensure_state(properties=properties)
|
||||
+
|
||||
+ # Ensure the CoS template entry (nsPwTemplateEntry) that points to the
|
||||
+ # password policy entry
|
||||
+ cos_templates = CosTemplates(self._instance, pwp_container.dn)
|
||||
+ cos_template = cos_templates.ensure_state(properties={
|
||||
+ 'cosPriority': '1',
|
||||
+ 'pwdpolicysubentry': pwp_entry.dn,
|
||||
+ 'cn': 'cn=nsPwTemplateEntry,%s' % dn
|
||||
+ })
|
||||
+
|
||||
+ # Ensure the CoS specification entry at the subtree level
|
||||
+ cos_pointer_defs = CosPointerDefinitions(self._instance, dn)
|
||||
+ cos_pointer_defs.ensure_state(properties={
|
||||
+ 'cosAttribute': 'pwdpolicysubentry default operational-default',
|
||||
+ 'cosTemplateDn': cos_template.dn,
|
||||
+ 'cn': 'nsPwPolicy_CoS'
|
||||
+ })
|
||||
|
||||
# make sure that local policies are enabled
|
||||
self.set_global_policy({'nsslapd-pwpolicy-local': 'on'})
|
||||
@@ -244,10 +277,12 @@ class PwPolicyManager(object):
|
||||
if self.is_subtree_policy(entry.dn):
|
||||
parentdn = dn
|
||||
subtree = True
|
||||
- else:
|
||||
+ elif self.is_user_policy(entry.dn):
|
||||
dn_comps = ldap.dn.explode_dn(dn)
|
||||
dn_comps.pop(0)
|
||||
parentdn = ",".join(dn_comps)
|
||||
+ else:
|
||||
+ raise ValueError('The target entry dn does not have a password policy')
|
||||
|
||||
# Starting deleting the policy, ignore the parts that might already have been removed
|
||||
pwp_container = nsContainer(self._instance, 'cn=nsPwPolicyContainer,%s' % parentdn)
|
||||
--
|
||||
2.51.1
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user