Compare commits

..

3 Commits

Author SHA1 Message Date
AlmaLinux RelEng Bot
1b2fb8c72e import CS 389-ds-base-3.2.0-6.el10 2026-04-07 06:30:11 -04:00
2760ce3ca2 import CS 389-ds-base-3.1.3-5.el10 2025-10-27 08:58:41 +00:00
89b7694df5 import RHEL 10 Beta 389-ds-base-3.0.4-3.el10 2024-11-25 09:05:59 +00:00
151 changed files with 20762 additions and 24739 deletions

View File

@ -1,3 +0,0 @@
bd9aab32d9cbf9231058d585479813f3420dc872 SOURCES/389-ds-base-1.4.3.39.tar.bz2
1c8f2d0dfbf39fa8cd86363bf3314351ab21f8d4 SOURCES/jemalloc-5.3.0.tar.bz2
8d3275209f2f8e1a69053340930ad1fb037d61fb SOURCES/vendor-1.4.3.39-3.tar.gz

7
.gitignore vendored
View File

@ -1,3 +1,4 @@
SOURCES/389-ds-base-1.4.3.39.tar.bz2
SOURCES/jemalloc-5.3.0.tar.bz2
SOURCES/vendor-1.4.3.39-3.tar.gz
389-ds-base-3.2.0.tar.bz2
jemalloc-5.3.0.tar.bz2
libdb-5.3.28-59.tar.bz2
vendor-3.2.0-4.tar.gz

View File

@ -1,4 +1,4 @@
From 2da3ad24ce028c6b68abef0d115dae4215de558d Mon Sep 17 00:00:00 2001
From 1c9c535888b9a850095794787d67900b04924a76 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 1bc36720d..b187c26bc 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
@@ -282,6 +282,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 91d61098a..30a7aa11f 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);

View File

@ -0,0 +1,765 @@
From 446bc42e7b64a8496c2c3fe486f86bba318bed5e 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 6024fcd31..1f47c531c 100644
--- a/ldap/servers/slapd/abandon.c
+++ b/ldap/servers/slapd/abandon.c
@@ -179,7 +179,7 @@ do_abandon(Slapi_PBlock *pb)
logpb.tv_sec = -1;
logpb.tv_nsec = -1;
- 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)) {
if (log_format != LOG_FORMAT_DEFAULT) {
/* JSON logging */
logpb.target_op = "Simple Paged Results";
diff --git a/ldap/servers/slapd/opshared.c b/ldap/servers/slapd/opshared.c
index a5cddfd23..bf800f7dc 100644
--- a/ldap/servers/slapd/opshared.c
+++ b/ldap/servers/slapd/opshared.c
@@ -572,8 +572,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);
@@ -619,14 +619,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);
}
@@ -743,17 +736,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;
@@ -764,14 +755,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 */
@@ -789,22 +779,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;
}
}
@@ -915,7 +905,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);
#pragma GCC diagnostic pop
}
@@ -954,7 +944,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) {
@@ -967,23 +957,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);
@@ -993,7 +983,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 765c12bf5..455d6d718 100644
--- a/ldap/servers/slapd/proto-slap.h
+++ b/ldap/servers/slapd/proto-slap.h
@@ -1614,20 +1614,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);
@@ -1639,15 +1641,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 11c5602e3..d494931c2 100644
--- a/ldap/servers/slapd/slap.h
+++ b/ldap/servers/slapd/slap.h
@@ -89,6 +89,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>
@@ -1669,7 +1673,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

View File

@ -0,0 +1,183 @@
From 4936f953fa3b0726c2b178f135cd78dcac7463ba Mon Sep 17 00:00:00 2001
From: Simon Pichugin <spichugi@redhat.com>
Date: Thu, 8 Jan 2026 10:02:39 -0800
Subject: [PATCH] Issue 7108 - Fix shutdown crash in entry cache destruction
(#7163)
Description: The entry cache could experience LRU list corruption when
using pinned entries, leading to crashes during cache flush operations.
In entrycache_add_int(), when returning an existing cached entry, the
code checked the wrong entry's state before calling lru_delete(). It
checked the new entry 'e' but operated on the existing entry 'my_alt',
causing lru_delete() to be called on entries not in the LRU list. This
is fixed by checking my_alt's refcnt and pinned state instead.
In flush_hash(), pinned_remove() and lru_delete() were both called on
pinned entries. Since pinned entries are in the pinned list, calling
lru_delete() afterwards corrupted the list. This is fixed by calling
either pinned_remove() or lru_delete() based on the entry's state.
A NULL check is added in entrycache_flush() and dncache_flush() to
gracefully handle corrupted LRU lists and prevent crashes when
traversing backwards through the list encounters an unexpected NULL.
Entry pointers are now always cleared after lru_delete() removal to
prevent stale pointer issues in non-debug builds.
Fixes: https://github.com/389ds/389-ds-base/issues/7108
Reviewed by: @progier389, @vashirov (Thanks!!)
---
ldap/servers/slapd/back-ldbm/cache.c | 48 +++++++++++++++++++++++++---
1 file changed, 43 insertions(+), 5 deletions(-)
diff --git a/ldap/servers/slapd/back-ldbm/cache.c b/ldap/servers/slapd/back-ldbm/cache.c
index 2e4126134..a87f30687 100644
--- a/ldap/servers/slapd/back-ldbm/cache.c
+++ b/ldap/servers/slapd/back-ldbm/cache.c
@@ -458,11 +458,13 @@ static void
lru_delete(struct cache *cache, void *ptr)
{
struct backcommon *e;
+
if (NULL == ptr) {
LOG("=> lru_delete\n<= lru_delete (null entry)\n");
return;
}
e = (struct backcommon *)ptr;
+
#ifdef LDAP_CACHE_DEBUG_LRU
pinned_verify(cache, __LINE__);
lru_verify(cache, e, 1);
@@ -475,8 +477,9 @@ lru_delete(struct cache *cache, void *ptr)
e->ep_lrunext->ep_lruprev = e->ep_lruprev;
else
cache->c_lrutail = e->ep_lruprev;
-#ifdef LDAP_CACHE_DEBUG_LRU
+ /* Always clear pointers after removal to prevent stale pointer issues */
e->ep_lrunext = e->ep_lruprev = NULL;
+#ifdef LDAP_CACHE_DEBUG_LRU
lru_verify(cache, e, 0);
#endif
}
@@ -633,9 +636,14 @@ flush_hash(struct cache *cache, struct timespec *start_time, int32_t type)
if (entry->ep_refcnt == 0) {
entry->ep_refcnt++;
if (entry->ep_state & ENTRY_STATE_PINNED) {
+ /* Entry is in pinned list, not LRU - remove from pinned only.
+ * pinned_remove clears lru pointers and won't add to LRU since refcnt > 0.
+ */
pinned_remove(cache, laste);
+ } else {
+ /* Entry is in LRU list - remove from LRU */
+ lru_delete(cache, laste);
}
- lru_delete(cache, laste);
if (type == ENTRY_CACHE) {
entrycache_remove_int(cache, laste);
entrycache_return(cache, (struct backentry **)&laste, PR_TRUE);
@@ -679,9 +687,14 @@ flush_hash(struct cache *cache, struct timespec *start_time, int32_t type)
if (entry->ep_refcnt == 0) {
entry->ep_refcnt++;
if (entry->ep_state & ENTRY_STATE_PINNED) {
+ /* Entry is in pinned list, not LRU - remove from pinned only.
+ * pinned_remove clears lru pointers and won't add to LRU since refcnt > 0.
+ */
pinned_remove(cache, laste);
+ } else {
+ /* Entry is in LRU list - remove from LRU */
+ lru_delete(cache, laste);
}
- lru_delete(cache, laste);
entrycache_remove_int(cache, laste);
entrycache_return(cache, (struct backentry **)&laste, PR_TRUE);
} else {
@@ -772,6 +785,11 @@ entrycache_flush(struct cache *cache)
} else {
e = BACK_LRU_PREV(e, struct backentry *);
}
+ if (e == NULL) {
+ slapi_log_err(SLAPI_LOG_WARNING, "entrycache_flush",
+ "Unexpected NULL entry while flushing cache - LRU list may be corrupted\n");
+ break;
+ }
ASSERT(e->ep_refcnt == 0);
e->ep_refcnt++;
if (entrycache_remove_int(cache, e) < 0) {
@@ -1160,6 +1178,7 @@ pinned_remove(struct cache *cache, void *ptr)
{
struct backentry *e = (struct backentry *)ptr;
ASSERT(e->ep_state & ENTRY_STATE_PINNED);
+
cache->c_pinned_ctx->npinned--;
cache->c_pinned_ctx->size -= e->ep_size;
e->ep_state &= ~ENTRY_STATE_PINNED;
@@ -1172,13 +1191,23 @@ pinned_remove(struct cache *cache, void *ptr)
cache->c_pinned_ctx->head = cache->c_pinned_ctx->tail = NULL;
} else {
cache->c_pinned_ctx->head = BACK_LRU_NEXT(e, struct backentry *);
+ /* Update new head's prev pointer to NULL */
+ if (cache->c_pinned_ctx->head) {
+ cache->c_pinned_ctx->head->ep_lruprev = NULL;
+ }
}
} else if (cache->c_pinned_ctx->tail == e) {
cache->c_pinned_ctx->tail = BACK_LRU_PREV(e, struct backentry *);
+ /* Update new tail's next pointer to NULL */
+ if (cache->c_pinned_ctx->tail) {
+ cache->c_pinned_ctx->tail->ep_lrunext = NULL;
+ }
} else {
+ /* Middle of list: update both neighbors to point to each other */
BACK_LRU_PREV(e, struct backentry *)->ep_lrunext = BACK_LRU_NEXT(e, struct backcommon *);
BACK_LRU_NEXT(e, struct backentry *)->ep_lruprev = BACK_LRU_PREV(e, struct backcommon *);
}
+ /* Clear the removed entry's pointers */
e->ep_lrunext = e->ep_lruprev = NULL;
if (e->ep_refcnt == 0) {
lru_add(cache, ptr);
@@ -1245,6 +1274,7 @@ pinned_add(struct cache *cache, void *ptr)
return false;
}
/* Now it is time to insert the entry in the pinned list */
+
cache->c_pinned_ctx->npinned++;
cache->c_pinned_ctx->size += e->ep_size;
e->ep_state |= ENTRY_STATE_PINNED;
@@ -1754,7 +1784,7 @@ entrycache_add_int(struct cache *cache, struct backentry *e, int state, struct b
* 3) ep_state: 0 && state: 0
* ==> increase the refcnt
*/
- if (e->ep_refcnt == 0)
+ if (e->ep_refcnt == 0 && (e->ep_state & ENTRY_STATE_PINNED) == 0)
lru_delete(cache, (void *)e);
e->ep_refcnt++;
e->ep_state &= ~ENTRY_STATE_UNAVAILABLE;
@@ -1781,7 +1811,7 @@ entrycache_add_int(struct cache *cache, struct backentry *e, int state, struct b
} else {
if (alt) {
*alt = my_alt;
- if (e->ep_refcnt == 0 && (e->ep_state & ENTRY_STATE_PINNED) == 0)
+ if (my_alt->ep_refcnt == 0 && (my_alt->ep_state & ENTRY_STATE_PINNED) == 0)
lru_delete(cache, (void *)*alt);
(*alt)->ep_refcnt++;
LOG("the entry %s already exists. returning existing entry %s (state: 0x%x)\n",
@@ -2379,6 +2409,14 @@ dncache_flush(struct cache *cache)
} else {
dn = BACK_LRU_PREV(dn, struct backdn *);
}
+ if (dn == NULL) {
+ /* Safety check: we should normally exit via the CACHE_LRU_HEAD check.
+ * If we get here, c_lruhead may be NULL or the LRU list is corrupted.
+ */
+ slapi_log_err(SLAPI_LOG_WARNING, "dncache_flush",
+ "Unexpected NULL entry while flushing cache - LRU list may be corrupted\n");
+ break;
+ }
ASSERT(dn->ep_refcnt == 0);
dn->ep_refcnt++;
if (dncache_remove_int(cache, dn) < 0) {
--
2.52.0

View File

@ -0,0 +1,215 @@
From 742c12e0247ab64e87da000a4de2f3e5c99044ab 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 | 107 +++++++++++++++++++++++-
2 files changed, 123 insertions(+), 9 deletions(-)
diff --git a/ldap/servers/slapd/back-ldbm/instance.c b/ldap/servers/slapd/back-ldbm/instance.c
index cb002c379..71bf0f6fa 100644
--- a/ldap/servers/slapd/back-ldbm/instance.c
+++ b/ldap/servers/slapd/back-ldbm/instance.c
@@ -190,6 +190,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;
@@ -248,10 +249,14 @@ ldbm_instance_create_default_indexes(backend *be)
ldbm_instance_config_add_index_entry(inst, e, flags);
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);
@@ -288,10 +293,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);
slapi_ch_free_string(&parentid_indexes_limit);
diff --git a/ldap/servers/slapd/upgrade.c b/ldap/servers/slapd/upgrade.c
index 858392564..b02e37ed6 100644
--- a/ldap/servers/slapd/upgrade.c
+++ b/ldap/servers/slapd/upgrade.c
@@ -330,6 +330,107 @@ upgrade_remove_subtree_rename(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 the base config of the PAM PTA plugin.
*
@@ -547,7 +648,11 @@ upgrade_server(void)
if (upgrade_pam_pta_default_config() != UPGRADE_SUCCESS) {
return UPGRADE_FAILURE;
}
-
+
+ if (upgrade_check_id_index_matching_rule() != UPGRADE_SUCCESS) {
+ return UPGRADE_FAILURE;
+ }
+
return UPGRADE_SUCCESS;
}
--
2.52.0

View File

@ -1,7 +1,7 @@
From 2d9618da04161d7aecf2a19f5c06cd0b5749921c Mon Sep 17 00:00:00 2001
From f5de84e309d5a4435198c9cc9b31b5722979f1ff 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,10 +30,10 @@ 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 71bf0f6fa..2a6e8cbb8 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)
@@ -190,7 +190,7 @@ ldbm_instance_create_default_indexes(backend *be)
char *ancestorid_indexes_limit = NULL;
char *parentid_indexes_limit = NULL;
struct attrinfo *ai = NULL;
@ -42,8 +42,8 @@ index 04340a992..29643f7e4 100644
struct index_idlistsizeinfo *iter;
int cookie;
int limit;
@@ -257,7 +257,8 @@ ldbm_instance_create_default_indexes(backend *be)
}
@@ -250,7 +250,8 @@ ldbm_instance_create_default_indexes(backend *be)
slapi_entry_free(e);
ainfo_get(be, (char *)LDBM_PARENTID_STR, &ai);
- index_already_configured = ai;
@ -52,16 +52,16 @@ 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)
* but we still want to use the attr index file APIs.
*/
ainfo_get(be, (char *)LDBM_ANCESTORID_STR, &ai);
- index_already_configured = ai;
+ /* Check if the attrinfo is actually for ancestorid, not a fallback to .default */
+ index_already_configured = (ai != NULL && strcmp(ai->ai_type, LDBM_ANCESTORID_STR) == 0);
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);
@@ -294,7 +295,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);
- index_already_configured = ai;
+ /* Check if the attrinfo is actually for ancestorid, not a fallback to .default */
+ index_already_configured = (ai != NULL && strcmp(ai->ai_type, LDBM_ANCESTORID_STR) == 0);
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);
--
2.52.0

View File

@ -0,0 +1,924 @@
From fb23c9e366f5eafa6bdbb8cd71afd78e3edefde2 Mon Sep 17 00:00:00 2001
From: Lenka Doudova <mirielka@users.noreply.github.com>
Date: Tue, 13 Jan 2026 10:44:15 +0100
Subject: [PATCH] Issue 6753 - Port ticket 548 test (#7101)
Description:
Port ticket 548 test into
dirsrvtests/tests/suites/password/pwdPolicy_attribute_test.py
Relates: #6753
Author: Lenka Doudova, aadhikar
Assisted by: Cursor
Reviewer: @droideck(Thanks!)
---
.../password/pwdPolicy_attribute_test.py | 464 +++++++++++++++++-
dirsrvtests/tests/tickets/ticket548_test.py | 408 ---------------
2 files changed, 463 insertions(+), 409 deletions(-)
delete mode 100644 dirsrvtests/tests/tickets/ticket548_test.py
diff --git a/dirsrvtests/tests/suites/password/pwdPolicy_attribute_test.py b/dirsrvtests/tests/suites/password/pwdPolicy_attribute_test.py
index d0c172f94..d021f4720 100644
--- a/dirsrvtests/tests/suites/password/pwdPolicy_attribute_test.py
+++ b/dirsrvtests/tests/suites/password/pwdPolicy_attribute_test.py
@@ -9,12 +9,12 @@
import pytest
from lib389.tasks import *
from lib389.utils import *
-import pdb
from lib389.topologies import topology_st
from lib389.pwpolicy import PwPolicyManager
from lib389.idm.user import UserAccount, UserAccounts, TEST_USER_PROPERTIES
from lib389.idm.organizationalunit import OrganizationalUnits
from lib389._constants import (DEFAULT_SUFFIX, DN_DM, PASSWORD)
+from lib389.idm.directorymanager import DirectoryManager
pytestmark = pytest.mark.tier1
@@ -361,6 +361,468 @@ def test_pwdpolicysubentry(topology_st, password_policy):
assert 'nsPwPolicyEntry_user' not in pwp_subentry
+@pytest.fixture(scope="function")
+def shadowUser(request, topology_st):
+ """ Create a user with shadowAccount objectclass """
+ users = UserAccounts(topology_st.standalone, DEFAULT_SUFFIX)
+ shadowUser = users.create(properties={
+ 'objectclass': ['top', 'person', 'organizationalPerson',
+ 'inetOrgPerson', 'extensibleObject', 'shadowAccount'],
+ 'sn': '1',
+ 'cn': 'shadowUser',
+ 'uid': 'shadowUser',
+ 'uidNumber': '1',
+ 'gidNumber': '11',
+ 'homeDirectory': '/home/shadowUser',
+ 'displayName': 'Shadow User',
+ 'givenname': 'Shadow',
+ 'mail':f'shadowuser@{DEFAULT_SUFFIX}',
+ 'userpassword': 'password'
+ })
+
+ def fin():
+ if shadowUser.exists():
+ shadowUser.delete()
+
+ request.addfinalizer(fin)
+
+ return shadowUser
+
+
+def days_to_secs(days):
+ """ Convert days to seconds """
+ return days * 86400
+
+
+def check_shadow_attr_value(inst, user_dn, attr_type, expected):
+ """ Check that shadowAccount attribute has expected value """
+ dm = DirectoryManager(inst)
+ dm.rebind()
+ user = UserAccount(inst, user_dn)
+ assert user.present(attr_type), f'Entry {user_dn} does not have {attr_type} attribute'
+ actual = int(user.get_attr_val_utf8(attr_type))
+ assert actual == expected, f'{attr_type} of entry {user_dn} is {actual}, expected {expected}'
+ log.info(f'{attr_type} of entry {user_dn} has expected value {actual}')
+
+
+def setup_pwp(inst, pwp_mgr, policy, dn=None, policy_props=None):
+ """ Setup password policy """
+
+ log.info(f'Setting up {policy} password policy for {dn}')
+ dm = DirectoryManager(inst)
+ dm.rebind()
+
+ log.info(f'Configuring {policy} password policy')
+
+ assert policy == 'global' or dn, 'dn is required for non-global policy'
+
+ if not policy_props:
+ policy_props = {
+ 'passwordMinAge': str(days_to_secs(1)),
+ 'passwordExp': 'on',
+ 'passwordMaxAge': str(days_to_secs(10)),
+ 'passwordWarning': str(days_to_secs(3))
+ }
+
+ if policy == 'global':
+ pwp_mgr.set_global_policy(policy_props)
+ elif policy == 'subtree':
+ pwp_mgr.create_subtree_policy(dn, policy_props)
+ elif policy == 'user':
+ pwp_mgr.create_user_policy(dn, policy_props)
+ else:
+ raise ValueError(f'Invalid type of password policy: {policy}')
+
+
+def modify_pwp(inst, pwp_mgr, policy, dn=None, policy_props=None):
+ """ Modify password policy """
+ dm = DirectoryManager(inst)
+ dm.rebind()
+
+ assert policy == 'global' or dn, 'dn is required for non-global policy'
+
+ if not policy_props:
+ policy_props = {
+ 'passwordMinAge': str(days_to_secs(3)),
+ 'passwordMaxAge': str(days_to_secs(30)),
+ 'passwordWarning': str(days_to_secs(9))
+ }
+
+ if policy == 'global':
+ pwp_mgr.set_global_policy(properties=policy_props)
+ elif policy in ['subtree', 'user']:
+ policy_entry = pwp_mgr.get_pwpolicy_entry(dn)
+ policy_entry.replace_many(*policy_props.items())
+ else:
+ raise ValueError(f'Invalid type of password policy: {policy}')
+ log.info(f'Modified {policy} policy with {policy_props}.')
+
+@pytest.mark.skipif(ds_is_older('1.3.6'), reason="Not implemented")
+def test_shadowaccount_no_policy(topology_st, shadowUser):
+ """Check shadowAccount under no password policy
+
+ :id: a1b2c3d4-5e6f-7890-abcd-ef1234567890
+ :setup: Standalone instance
+ :steps:
+ 1. Add a user with shadowAccount objectclass
+ 2. Bind as the user
+ 3. Check shadowLastChange attribute is set correctly
+ :expectedresults:
+ 1. User is added successfully
+ 2. Bind is successful
+ 3. shadowLastChange is set correctly (days since epoch)
+ """
+
+ edate = int(time.time() / (60 * 60 * 24))
+
+ log.info(f"Bind as {shadowUser.dn}")
+ shadowUser.rebind('password')
+ check_shadow_attr_value(topology_st.standalone, shadowUser.dn,
+ 'shadowLastChange', edate)
+
+
+@pytest.mark.skipif(ds_is_older('1.3.6'), reason="Not implemented")
+def test_shadowaccount_global_policy(topology_st, shadowUser, request):
+ """Check shadowAccount with global password policy
+
+ :id: b2c3d4e5-6f7a-8901-bcde-f23456789012
+ :setup: Standalone instance
+ :steps:
+ 1. Set global password policy
+ 2. Add a second shadowAccount user
+ 3. Bind as each user and check shadowAccount attributes
+ 4. Modify global password policy
+ 5. Change user password (as the user, not DM)
+ 6. Re-bind with new password
+ 7. Check shadowAccount attributes are updated
+ 8. Clean up - delete second user and reset policy
+ :expectedresults:
+ 1. Global password policy is set successfully
+ 2. Second user is added
+ 3. shadowAccount attributes match policy values for both users
+ 4. Password policy is modified successfully
+ 5. Password is changed successfully
+ 6. Re-bind with new password is successful
+ 7. shadowAccount attributes are updated to match new policy values
+ 8. Cleanup is successful
+ """
+ inst = topology_st.standalone
+
+ log.info('Create second shadowAccount user')
+ users = UserAccounts(inst, DEFAULT_SUFFIX)
+ shadowUser2 = users.create(properties={
+ 'objectclass': ['top', 'person', 'organizationalPerson',
+ 'inetOrgPerson', 'extensibleObject', 'shadowAccount'],
+ 'sn': '2',
+ 'cn': 'shadowUser2',
+ 'uid': 'shadowUser2',
+ 'uidNumber': '2',
+ 'gidNumber': '22',
+ 'homeDirectory': '/home/shadowUser2',
+ 'displayName': 'Shadow User 2',
+ 'givenname': 'Shadow2',
+ 'mail': f'shadowuser2@{DEFAULT_SUFFIX}',
+ 'userpassword': 'password'
+ })
+
+ def fin():
+ log.info('Clean up - delete second user and reset global policy')
+ dm = DirectoryManager(inst)
+ dm.rebind()
+ try:
+ shadowUser2.delete()
+ except Exception:
+ pass
+ inst.config.replace('passwordMinAge', '0')
+ inst.config.replace('passwordMaxAge', '8640000')
+ inst.config.replace('passwordWarning', '86400')
+ inst.config.replace('passwordExp', 'off')
+ request.addfinalizer(fin)
+
+ log.info('Configure global password policy')
+ pwp_mgr = PwPolicyManager(inst)
+ setup_pwp(inst, pwp_mgr, 'global')
+
+ edate = int(time.time() / (60 * 60 * 24))
+
+ log.info('Verify attributes of shadowUser (user 1)')
+ shadowUser.rebind('password')
+ check_shadow_attr_value(inst, shadowUser.dn,
+ 'shadowLastChange', edate)
+ check_shadow_attr_value(inst, shadowUser.dn,
+ 'shadowMin', 1)
+ check_shadow_attr_value(inst, shadowUser.dn,
+ 'shadowMax', 10)
+ check_shadow_attr_value(inst, shadowUser.dn,
+ 'shadowWarning', 3)
+
+ log.info('Verify attributes of shadowUser2 (user 2)')
+ shadowUser2.rebind('password')
+ check_shadow_attr_value(inst, shadowUser2.dn,
+ 'shadowLastChange', edate)
+ check_shadow_attr_value(inst, shadowUser2.dn,
+ 'shadowMin', 1)
+ check_shadow_attr_value(inst, shadowUser2.dn,
+ 'shadowMax', 10)
+ check_shadow_attr_value(inst, shadowUser2.dn,
+ 'shadowWarning', 3)
+
+ log.info('Modify global password policy')
+ modify_pwp(inst, pwp_mgr, 'global')
+
+ log.info('Change shadowUser2 password as the user')
+ shadowUser2.rebind('password')
+ shadowUser2.replace('userpassword', 'password2')
+ time.sleep(1)
+
+ log.info('Re-bind as shadowUser2 with new password')
+ shadowUser2.rebind('password2')
+
+ log.info('Verify modified shadowUser2 attributes')
+ check_shadow_attr_value(inst, shadowUser2.dn,
+ 'shadowMin', 3)
+ check_shadow_attr_value(inst, shadowUser2.dn,
+ 'shadowMax', 30)
+ check_shadow_attr_value(inst, shadowUser2.dn,
+ 'shadowWarning', 9)
+
+
+@pytest.mark.skipif(ds_is_older('1.3.6'), reason="Not implemented")
+def test_shadowaccount_subtree_policy(topology_st, request):
+ """Check shadowAccount with subtree level password policy
+
+ :id: c3d4e5f6-7a8b-9012-cdef-345678901234
+ :setup: Standalone instance
+ :steps:
+ 1. Create subtree password policy for DEFAULT_SUFFIX with passwordMustChange on
+ 2. Add a new shadowAccount user under the subtree
+ 3. Check shadowLastChange is 0 (since passwordMustChange is on)
+ 4. Verify search as user fails with UNWILLING_TO_PERFORM
+ 5. Change user password (as user)
+ 6. Re-bind with new password
+ 7. Check shadowAccount attributes are updated with correct values
+ 8. Clean up subtree password policy
+ :expectedresults:
+ 1. Subtree password policy is created successfully
+ 2. User is added successfully
+ 3. shadowLastChange is 0 until password is changed
+ 4. Search fails with UNWILLING_TO_PERFORM as expected
+ 5. Password is changed successfully
+ 6. Re-bind with new password is successful
+ 7. shadowAccount attributes are updated to match policy values
+ 8. Subtree password policy is deleted successfully
+ """
+ inst = topology_st.standalone
+ subtree_dn = DEFAULT_SUFFIX
+
+ log.info('Configure subtree password policy with passwordMustChange on')
+ properties = {
+ 'passwordMustChange': 'on',
+ 'passwordExp': 'on',
+ 'passwordMinAge': str(days_to_secs(2)),
+ 'passwordMaxAge': str(days_to_secs(20)),
+ 'passwordWarning': str(days_to_secs(6)),
+ 'passwordChange': 'on',
+ 'passwordStorageScheme': 'clear'
+ }
+
+ pwp_mgr = PwPolicyManager(inst)
+ setup_pwp(inst, pwp_mgr, 'subtree', dn=subtree_dn, policy_props=properties)
+
+ def fin():
+ log.info('Clean up: delete subtree password policy')
+ dm = DirectoryManager(inst)
+ dm.rebind()
+ try:
+ pwp_mgr.delete_local_policy(subtree_dn)
+ except Exception:
+ pass
+ try:
+ subtree_user.delete()
+ except Exception:
+ pass
+ request.addfinalizer(fin)
+
+ log.info('Add a new shadowAccount user under the subtree')
+ users = UserAccounts(inst, DEFAULT_SUFFIX)
+ subtree_user = users.create(properties={
+ 'objectclass': ['top', 'person', 'organizationalPerson',
+ 'inetOrgPerson', 'extensibleObject', 'shadowAccount'],
+ 'sn': '3',
+ 'cn': 'subtreeUser',
+ 'uid': 'subtreeUser',
+ 'uidNumber': '3',
+ 'gidNumber': '33',
+ 'homeDirectory': '/home/subtreeUser',
+ 'displayName': 'Subtree User',
+ 'givenname': 'Subtree',
+ 'mail': f'subtreeuser@{DEFAULT_SUFFIX}',
+ 'userpassword': 'password'
+ })
+
+ dm = DirectoryManager(inst)
+ dm.rebind()
+
+ log.info('Verify shadowLastChange is 0 since passwordMustChange is on')
+ check_shadow_attr_value(inst, subtree_user.dn,
+ 'shadowLastChange', 0)
+ check_shadow_attr_value(inst, subtree_user.dn,
+ 'shadowMin', 2)
+ check_shadow_attr_value(inst, subtree_user.dn,
+ 'shadowMax', 20)
+ check_shadow_attr_value(inst, subtree_user.dn,
+ 'shadowWarning', 6)
+
+ log.info(f'Bind as {subtree_user.dn} and verify search fails with UNWILLING_TO_PERFORM')
+ subtree_user.rebind('password')
+ with pytest.raises(ldap.UNWILLING_TO_PERFORM):
+ subtree_user.exists()
+
+ log.info('Modify subtree password policy')
+ dm.rebind()
+ modify_properties = {
+ 'passwordMinAge': str(days_to_secs(4)),
+ 'passwordMaxAge': str(days_to_secs(40)),
+ 'passwordWarning': str(days_to_secs(12))
+ }
+ modify_pwp(inst, pwp_mgr, 'subtree', dn=subtree_dn, policy_props=modify_properties)
+
+ log.info(f'Change {subtree_user.dn} password as the user')
+ subtree_user.rebind('password')
+ subtree_user.replace('userpassword', 'password0')
+ time.sleep(1)
+
+ log.info(f'Re-bind as {subtree_user.dn} with new password')
+ subtree_user.rebind('password0')
+
+ edate = int(time.time() / (60 * 60 * 24))
+
+ log.info('Verify shadowLastChange is now set to today after password change')
+ check_shadow_attr_value(inst, subtree_user.dn,
+ 'shadowLastChange', edate)
+ check_shadow_attr_value(inst, subtree_user.dn,
+ 'shadowMin', 4)
+ check_shadow_attr_value(inst, subtree_user.dn,
+ 'shadowMax', 40)
+ check_shadow_attr_value(inst, subtree_user.dn,
+ 'shadowWarning', 12)
+
+
+@pytest.mark.skipif(ds_is_older('1.3.6'), reason="Not implemented")
+def test_shadowaccount_user_policy(topology_st, request):
+ """Check shadowAccount with user level password policy
+
+ :id: d4e5f6a7-8b9c-0123-def0-456789012345
+ :setup: Standalone instance
+ :steps:
+ 1. Create a new shadowAccount user
+ 2. Create user password policy
+ 3. Verify shadowAccount attributes match policy values
+ 4. Modify user password policy
+ 5. Change user password
+ 6. Re-bind with new password
+ 7. Check shadowAccount attributes are updated
+ 8. Clean up user password policy
+ :expectedresults:
+ 1. User is created successfully
+ 2. User password policy is created successfully
+ 3. shadowAccount attributes match policy values
+ 4. Password policy is modified successfully
+ 5. Password is changed successfully
+ 6. Re-bind with new password is successful
+ 7. shadowAccount attributes are updated to match new policy values
+ 8. User password policy is deleted successfully
+ """
+ inst = topology_st.standalone
+
+ log.info('Create a new shadowAccount user for user policy test')
+ users = UserAccounts(inst, DEFAULT_SUFFIX)
+ user_policy_user = users.create(properties={
+ 'objectclass': ['top', 'person', 'organizationalPerson',
+ 'inetOrgPerson', 'extensibleObject', 'shadowAccount'],
+ 'sn': '4',
+ 'cn': 'userPolicyUser',
+ 'uid': 'userPolicyUser',
+ 'uidNumber': '4',
+ 'gidNumber': '44',
+ 'homeDirectory': '/home/userPolicyUser',
+ 'displayName': 'User Policy User',
+ 'givenname': 'UserPolicy',
+ 'mail': f'userpolicyuser@{DEFAULT_SUFFIX}',
+ 'userpassword': 'password'
+ })
+
+ pwp_mgr = PwPolicyManager(inst)
+
+ def fin():
+ log.info('Clean up: delete user password policy and user')
+ dm = DirectoryManager(inst)
+ dm.rebind()
+ try:
+ pwp_mgr.delete_local_policy(user_policy_user.dn)
+ except Exception:
+ pass
+ try:
+ user_policy_user.delete()
+ except Exception:
+ pass
+ request.addfinalizer(fin)
+
+ log.info('Configure user password policy')
+ properties = {
+ 'passwordExp': 'on',
+ 'passwordMinAge': str(days_to_secs(2)),
+ 'passwordMaxAge': str(days_to_secs(20)),
+ 'passwordWarning': str(days_to_secs(6)),
+ 'passwordChange': 'on',
+ 'passwordStorageScheme': 'clear'
+ }
+ setup_pwp(inst, pwp_mgr, 'user', dn=user_policy_user.dn, policy_props=properties)
+
+ edate = int(time.time() / (60 * 60 * 24))
+
+ dm = DirectoryManager(inst)
+ dm.rebind()
+
+ log.info('Verify shadowAccount attributes match user policy')
+ check_shadow_attr_value(inst, user_policy_user.dn,
+ 'shadowLastChange', edate)
+ check_shadow_attr_value(inst, user_policy_user.dn,
+ 'shadowMin', 2)
+ check_shadow_attr_value(inst, user_policy_user.dn,
+ 'shadowMax', 20)
+ check_shadow_attr_value(inst, user_policy_user.dn,
+ 'shadowWarning', 6)
+
+ log.info('Modify user password policy')
+ modify_properties = {
+ 'passwordMinAge': str(days_to_secs(4)),
+ 'passwordMaxAge': str(days_to_secs(40)),
+ 'passwordWarning': str(days_to_secs(12))
+ }
+ modify_pwp(inst, pwp_mgr, 'user', dn=user_policy_user.dn, policy_props=modify_properties)
+
+ log.info(f'Change {user_policy_user.dn} password as the user')
+ user_policy_user.rebind('password')
+ user_policy_user.replace('userpassword', 'password0')
+ time.sleep(1)
+
+ log.info(f'Re-bind as {user_policy_user.dn} with new password')
+ user_policy_user.rebind('password0')
+
+ edate = int(time.time() / (60 * 60 * 24))
+
+ log.info('Verify shadowAccount attributes are updated after password change')
+ check_shadow_attr_value(inst, user_policy_user.dn,
+ 'shadowLastChange', edate)
+ check_shadow_attr_value(inst, user_policy_user.dn,
+ 'shadowMin', 4)
+ check_shadow_attr_value(inst, user_policy_user.dn,
+ 'shadowMax', 40)
+ check_shadow_attr_value(inst, user_policy_user.dn,
+ 'shadowWarning', 12)
+
+
if __name__ == '__main__':
# Run isolated
# -s for DEBUG mode
diff --git a/dirsrvtests/tests/tickets/ticket548_test.py b/dirsrvtests/tests/tickets/ticket548_test.py
deleted file mode 100644
index cac3cc5f8..000000000
--- a/dirsrvtests/tests/tickets/ticket548_test.py
+++ /dev/null
@@ -1,408 +0,0 @@
-# --- BEGIN COPYRIGHT BLOCK ---
-# Copyright (C) 2016 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.tasks import *
-from lib389.utils import *
-from lib389.topologies import topology_st
-
-from lib389._constants import DEFAULT_SUFFIX, DN_CONFIG, DN_DM, PASSWORD, DEFAULT_SUFFIX_ESCAPED
-
-# Skip on older versions
-pytestmark = [pytest.mark.tier2,
- pytest.mark.skipif(ds_is_older('1.3.6'), reason="Not implemented")]
-
-log = logging.getLogger(__name__)
-
-# Assuming DEFAULT_SUFFIX is "dc=example,dc=com", otherwise it does not work... :(
-SUBTREE_CONTAINER = 'cn=nsPwPolicyContainer,' + DEFAULT_SUFFIX
-SUBTREE_PWPDN = 'cn=nsPwPolicyEntry,' + DEFAULT_SUFFIX
-SUBTREE_PWP = 'cn=cn\3DnsPwPolicyEntry\2C' + DEFAULT_SUFFIX_ESCAPED + ',' + SUBTREE_CONTAINER
-SUBTREE_COS_TMPLDN = 'cn=nsPwTemplateEntry,' + DEFAULT_SUFFIX
-SUBTREE_COS_TMPL = 'cn=cn\3DnsPwTemplateEntry\2C' + DEFAULT_SUFFIX_ESCAPED + ',' + SUBTREE_CONTAINER
-SUBTREE_COS_DEF = 'cn=nsPwPolicy_CoS,' + DEFAULT_SUFFIX
-
-USER1_DN = 'uid=user1,' + DEFAULT_SUFFIX
-USER2_DN = 'uid=user2,' + DEFAULT_SUFFIX
-USER3_DN = 'uid=user3,' + DEFAULT_SUFFIX
-USER_PW = 'password'
-
-
-def days_to_secs(days):
- # Value of 60 * 60 * 24
- return days * 86400
-
-
-# Values are in days
-def set_global_pwpolicy(topology_st, min_=1, max_=10, warn=3):
- log.info(" +++++ Enable global password policy +++++\n")
- # Enable password policy
- try:
- topology_st.standalone.modify_s(DN_CONFIG, [(ldap.MOD_REPLACE, 'nsslapd-pwpolicy-local', b'on')])
- except ldap.LDAPError as e:
- log.error('Failed to set pwpolicy-local: error ' + e.message['desc'])
- assert False
-
- # Convert our values to seconds
- min_secs = days_to_secs(min_)
- max_secs = days_to_secs(max_)
- warn_secs = days_to_secs(warn)
-
- log.info(" Set global password Min Age -- %s day\n" % min_)
- try:
- topology_st.standalone.modify_s(DN_CONFIG, [(ldap.MOD_REPLACE, 'passwordMinAge', ('%s' % min_secs).encode())])
- except ldap.LDAPError as e:
- log.error('Failed to set passwordMinAge: error ' + e.message['desc'])
- assert False
-
- log.info(" Set global password Expiration -- on\n")
- try:
- topology_st.standalone.modify_s(DN_CONFIG, [(ldap.MOD_REPLACE, 'passwordExp', b'on')])
- except ldap.LDAPError as e:
- log.error('Failed to set passwordExp: error ' + e.message['desc'])
- assert False
-
- log.info(" Set global password Max Age -- %s days\n" % max_)
- try:
- topology_st.standalone.modify_s(DN_CONFIG, [(ldap.MOD_REPLACE, 'passwordMaxAge', ('%s' % max_secs).encode())])
- except ldap.LDAPError as e:
- log.error('Failed to set passwordMaxAge: error ' + e.message['desc'])
- assert False
-
- log.info(" Set global password Warning -- %s days\n" % warn)
- try:
- topology_st.standalone.modify_s(DN_CONFIG, [(ldap.MOD_REPLACE, 'passwordWarning', ('%s' % warn_secs).encode())])
- except ldap.LDAPError as e:
- log.error('Failed to set passwordWarning: error ' + e.message['desc'])
- assert False
-
-
-def set_subtree_pwpolicy(topology_st, min_=2, max_=20, warn=6):
- log.info(" +++++ Enable subtree level password policy +++++\n")
-
- # Convert our values to seconds
- min_secs = days_to_secs(min_)
- max_secs = days_to_secs(max_)
- warn_secs = days_to_secs(warn)
-
- log.info(" Add the container")
- try:
- topology_st.standalone.add_s(Entry((SUBTREE_CONTAINER, {'objectclass': 'top nsContainer'.split(),
- 'cn': 'nsPwPolicyContainer'})))
- except ldap.ALREADY_EXISTS:
- pass
- except ldap.LDAPError as e:
- log.error('Failed to add subtree container: error ' + e.message['desc'])
- # assert False
-
- try:
- # Purge the old policy
- topology_st.standalone.delete_s(SUBTREE_PWP)
- except:
- pass
-
- log.info(
- " Add the password policy subentry {passwordMustChange: on, passwordMinAge: %s, passwordMaxAge: %s, passwordWarning: %s}" % (
- min_, max_, warn))
- try:
- topology_st.standalone.add_s(Entry((SUBTREE_PWP, {'objectclass': 'top ldapsubentry passwordpolicy'.split(),
- 'cn': SUBTREE_PWPDN,
- 'passwordMustChange': 'on',
- 'passwordExp': 'on',
- 'passwordMinAge': '%s' % min_secs,
- 'passwordMaxAge': '%s' % max_secs,
- 'passwordWarning': '%s' % warn_secs,
- 'passwordChange': 'on',
- 'passwordStorageScheme': 'clear'})))
- except ldap.LDAPError as e:
- log.error('Failed to add passwordpolicy: error ' + e.message['desc'])
- assert False
-
- log.info(" Add the COS template")
- try:
- topology_st.standalone.add_s(
- Entry((SUBTREE_COS_TMPL, {'objectclass': 'top ldapsubentry costemplate extensibleObject'.split(),
- 'cn': SUBTREE_PWPDN,
- 'cosPriority': '1',
- 'cn': SUBTREE_COS_TMPLDN,
- 'pwdpolicysubentry': SUBTREE_PWP})))
- except ldap.ALREADY_EXISTS:
- pass
- except ldap.LDAPError as e:
- log.error('Failed to add COS template: error ' + e.message['desc'])
- # assert False
-
- log.info(" Add the COS definition")
- try:
- topology_st.standalone.add_s(
- Entry((SUBTREE_COS_DEF, {'objectclass': 'top ldapsubentry cosSuperDefinition cosPointerDefinition'.split(),
- 'cn': SUBTREE_PWPDN,
- 'costemplatedn': SUBTREE_COS_TMPL,
- 'cosAttribute': 'pwdpolicysubentry default operational-default'})))
- except ldap.ALREADY_EXISTS:
- pass
- except ldap.LDAPError as e:
- log.error('Failed to add COS def: error ' + e.message['desc'])
- # assert False
-
- time.sleep(1)
-
-
-def update_passwd(topology_st, user, passwd, newpasswd):
- log.info(" Bind as {%s,%s}" % (user, passwd))
- topology_st.standalone.simple_bind_s(user, passwd)
- try:
- topology_st.standalone.modify_s(user, [(ldap.MOD_REPLACE, 'userpassword', newpasswd.encode())])
- except ldap.LDAPError as e:
- log.fatal('test_ticket548: Failed to update the password ' + cpw + ' of user ' + user + ': error ' + e.message[
- 'desc'])
- assert False
-
- time.sleep(1)
-
-
-def check_shadow_attr_value(entry, attr_type, expected, dn):
- if entry.hasAttr(attr_type):
- actual = entry.getValue(attr_type)
- if int(actual) == expected:
- log.info('%s of entry %s has expected value %s' % (attr_type, dn, actual))
- assert True
- else:
- log.fatal('%s %s of entry %s does not have expected value %s' % (attr_type, actual, dn, expected))
- assert False
- else:
- log.fatal('entry %s does not have %s attr' % (dn, attr_type))
- assert False
-
-
-def test_ticket548_test_with_no_policy(topology_st):
- """
- Check shadowAccount under no password policy
- """
- log.info("Case 1. No password policy")
-
- log.info("Bind as %s" % DN_DM)
- topology_st.standalone.simple_bind_s(DN_DM, PASSWORD)
-
- log.info('Add an entry' + USER1_DN)
- try:
- topology_st.standalone.add_s(
- Entry((USER1_DN, {'objectclass': "top person organizationalPerson inetOrgPerson shadowAccount".split(),
- 'sn': '1',
- 'cn': 'user 1',
- 'uid': 'user1',
- 'givenname': 'user',
- 'mail': 'user1@' + DEFAULT_SUFFIX,
- 'userpassword': USER_PW})))
- except ldap.LDAPError as e:
- log.fatal('test_ticket548: Failed to add user' + USER1_DN + ': error ' + e.message['desc'])
- assert False
-
- edate = int(time.time() / (60 * 60 * 24))
- log.info('Search entry %s' % USER1_DN)
-
- log.info("Bind as %s" % USER1_DN)
- topology_st.standalone.simple_bind_s(USER1_DN, USER_PW)
- entry = topology_st.standalone.getEntry(USER1_DN, ldap.SCOPE_BASE, "(objectclass=*)", ['shadowLastChange'])
- check_shadow_attr_value(entry, 'shadowLastChange', edate, USER1_DN)
-
- log.info("Check shadowAccount with no policy was successfully verified.")
-
-
-def test_ticket548_test_global_policy(topology_st):
- """
- Check shadowAccount with global password policy
- """
-
- log.info("Case 2. Check shadowAccount with global password policy")
-
- log.info("Bind as %s" % DN_DM)
- topology_st.standalone.simple_bind_s(DN_DM, PASSWORD)
-
- set_global_pwpolicy(topology_st)
-
- log.info('Add an entry' + USER2_DN)
- try:
- topology_st.standalone.add_s(
- Entry((USER2_DN, {'objectclass': "top person organizationalPerson inetOrgPerson shadowAccount".split(),
- 'sn': '2',
- 'cn': 'user 2',
- 'uid': 'user2',
- 'givenname': 'user',
- 'mail': 'user2@' + DEFAULT_SUFFIX,
- 'userpassword': USER_PW})))
- except ldap.LDAPError as e:
- log.fatal('test_ticket548: Failed to add user' + USER2_DN + ': error ' + e.message['desc'])
- assert False
-
- edate = int(time.time() / (60 * 60 * 24))
-
- log.info("Bind as %s" % USER1_DN)
- topology_st.standalone.simple_bind_s(USER1_DN, USER_PW)
-
- log.info('Search entry %s' % USER1_DN)
- entry = topology_st.standalone.getEntry(USER1_DN, ldap.SCOPE_BASE, "(objectclass=*)")
- check_shadow_attr_value(entry, 'shadowLastChange', edate, USER1_DN)
-
- # passwordMinAge -- 1 day
- check_shadow_attr_value(entry, 'shadowMin', 1, USER1_DN)
-
- # passwordMaxAge -- 10 days
- check_shadow_attr_value(entry, 'shadowMax', 10, USER1_DN)
-
- # passwordWarning -- 3 days
- check_shadow_attr_value(entry, 'shadowWarning', 3, USER1_DN)
-
- log.info("Bind as %s" % USER2_DN)
- topology_st.standalone.simple_bind_s(USER2_DN, USER_PW)
-
- log.info('Search entry %s' % USER2_DN)
- entry = topology_st.standalone.getEntry(USER2_DN, ldap.SCOPE_BASE, "(objectclass=*)")
- check_shadow_attr_value(entry, 'shadowLastChange', edate, USER2_DN)
-
- # passwordMinAge -- 1 day
- check_shadow_attr_value(entry, 'shadowMin', 1, USER2_DN)
-
- # passwordMaxAge -- 10 days
- check_shadow_attr_value(entry, 'shadowMax', 10, USER2_DN)
-
- # passwordWarning -- 3 days
- check_shadow_attr_value(entry, 'shadowWarning', 3, USER2_DN)
-
- # Bind as DM again, change policy
- log.info("Bind as %s" % DN_DM)
- topology_st.standalone.simple_bind_s(DN_DM, PASSWORD)
- set_global_pwpolicy(topology_st, 3, 30, 9)
-
- # change the user password, then check again.
- log.info("Bind as %s" % USER2_DN)
- topology_st.standalone.simple_bind_s(USER2_DN, USER_PW)
-
- newpasswd = USER_PW + '2'
- update_passwd(topology_st, USER2_DN, USER_PW, newpasswd)
-
- log.info("Re-bind as %s with new password" % USER2_DN)
- topology_st.standalone.simple_bind_s(USER2_DN, newpasswd)
-
- ## This tests if we update the shadow values on password change.
- log.info('Search entry %s' % USER2_DN)
- entry = topology_st.standalone.getEntry(USER2_DN, ldap.SCOPE_BASE, "(objectclass=*)")
-
- # passwordMinAge -- 1 day
- check_shadow_attr_value(entry, 'shadowMin', 3, USER2_DN)
-
- # passwordMaxAge -- 10 days
- check_shadow_attr_value(entry, 'shadowMax', 30, USER2_DN)
-
- # passwordWarning -- 3 days
- check_shadow_attr_value(entry, 'shadowWarning', 9, USER2_DN)
-
- log.info("Check shadowAccount with global policy was successfully verified.")
-
-
-def test_ticket548_test_subtree_policy(topology_st):
- """
- Check shadowAccount with subtree level password policy
- """
-
- log.info("Case 3. Check shadowAccount with subtree level password policy")
-
- log.info("Bind as %s" % DN_DM)
- topology_st.standalone.simple_bind_s(DN_DM, PASSWORD)
- # Check the global policy values
-
- set_subtree_pwpolicy(topology_st, 2, 20, 6)
-
- log.info('Add an entry' + USER3_DN)
- try:
- topology_st.standalone.add_s(
- Entry((USER3_DN, {'objectclass': "top person organizationalPerson inetOrgPerson shadowAccount".split(),
- 'sn': '3',
- 'cn': 'user 3',
- 'uid': 'user3',
- 'givenname': 'user',
- 'mail': 'user3@' + DEFAULT_SUFFIX,
- 'userpassword': USER_PW})))
- except ldap.LDAPError as e:
- log.fatal('test_ticket548: Failed to add user' + USER3_DN + ': error ' + e.message['desc'])
- assert False
-
- log.info('Search entry %s' % USER3_DN)
- entry0 = topology_st.standalone.getEntry(USER3_DN, ldap.SCOPE_BASE, "(objectclass=*)")
-
- log.info('Expecting shadowLastChange 0 since passwordMustChange is on')
- check_shadow_attr_value(entry0, 'shadowLastChange', 0, USER3_DN)
-
- # passwordMinAge -- 2 day
- check_shadow_attr_value(entry0, 'shadowMin', 2, USER3_DN)
-
- # passwordMaxAge -- 20 days
- check_shadow_attr_value(entry0, 'shadowMax', 20, USER3_DN)
-
- # passwordWarning -- 6 days
- check_shadow_attr_value(entry0, 'shadowWarning', 6, USER3_DN)
-
- log.info("Bind as %s" % USER3_DN)
- topology_st.standalone.simple_bind_s(USER3_DN, USER_PW)
-
- log.info('Search entry %s' % USER3_DN)
- try:
- entry1 = topology_st.standalone.getEntry(USER3_DN, ldap.SCOPE_BASE, "(objectclass=*)")
- except ldap.UNWILLING_TO_PERFORM:
- log.info('test_ticket548: Search by' + USER3_DN + ' failed by UNWILLING_TO_PERFORM as expected')
- except ldap.LDAPError as e:
- log.fatal('test_ticket548: Failed to serch user' + USER3_DN + ' by self: error ' + e.message['desc'])
- assert False
-
- log.info("Bind as %s and updating the password with a new one" % USER3_DN)
- topology_st.standalone.simple_bind_s(USER3_DN, USER_PW)
-
- # Bind as DM again, change policy
- log.info("Bind as %s" % DN_DM)
- topology_st.standalone.simple_bind_s(DN_DM, PASSWORD)
-
- set_subtree_pwpolicy(topology_st, 4, 40, 12)
-
- newpasswd = USER_PW + '0'
- update_passwd(topology_st, USER3_DN, USER_PW, newpasswd)
-
- log.info("Re-bind as %s with new password" % USER3_DN)
- topology_st.standalone.simple_bind_s(USER3_DN, newpasswd)
-
- try:
- entry2 = topology_st.standalone.getEntry(USER3_DN, ldap.SCOPE_BASE, "(objectclass=*)")
- except ldap.LDAPError as e:
- log.fatal('test_ticket548: Failed to serch user' + USER3_DN + ' by self: error ' + e.message['desc'])
- assert False
-
- edate = int(time.time() / (60 * 60 * 24))
-
- log.info('Expecting shadowLastChange %d once userPassword is updated', edate)
- check_shadow_attr_value(entry2, 'shadowLastChange', edate, USER3_DN)
-
- log.info('Search entry %s' % USER3_DN)
- entry = topology_st.standalone.getEntry(USER3_DN, ldap.SCOPE_BASE, "(objectclass=*)")
- check_shadow_attr_value(entry, 'shadowLastChange', edate, USER3_DN)
-
- # passwordMinAge -- 1 day
- check_shadow_attr_value(entry, 'shadowMin', 4, USER3_DN)
-
- # passwordMaxAge -- 10 days
- check_shadow_attr_value(entry, 'shadowMax', 40, USER3_DN)
-
- # passwordWarning -- 3 days
- check_shadow_attr_value(entry, 'shadowWarning', 12, USER3_DN)
-
- log.info("Check shadowAccount with subtree level policy was successfully verified.")
-
-
-if __name__ == '__main__':
- # Run isolated
- # -s for DEBUG mode
- CURRENT_FILE = os.path.realpath(__file__)
- pytest.main("-s %s" % CURRENT_FILE)
--
2.52.0

View File

@ -0,0 +1,82 @@
From 7c8a16c6bed524fb54d18a5b7e93d4bd5bb19d49 Mon Sep 17 00:00:00 2001
From: Viktor Ashirov <vashirov@redhat.com>
Date: Wed, 14 Jan 2026 17:55:29 +0100
Subject: [PATCH] Issue 7152 - ns-slapd fails to shutdown when deferred
memberof update is in progress (#7187)
Bug Description:
When a deferred memberof update is in progress during shutdown, the
backend operations (add, modify, delete, modrdn) wait in a polling loop
for the deferred task to complete. However, if the deferred thread exits
before clearing the SLAPI_DEFERRED_MEMBEROF flag, the loop becomes
infinite, causing the server to hang during shutdown.
Fix Description:
Add additional check to the polling loops so they exit immediately when
the server is shutting down.
Fixes: https://github.com/389ds/389-ds-base/issues/7152
Reviewed by: @tbordaz (Thanks!)
---
ldap/servers/slapd/back-ldbm/ldbm_add.c | 2 +-
ldap/servers/slapd/back-ldbm/ldbm_delete.c | 2 +-
ldap/servers/slapd/back-ldbm/ldbm_modify.c | 2 +-
ldap/servers/slapd/back-ldbm/ldbm_modrdn.c | 2 +-
4 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_add.c b/ldap/servers/slapd/back-ldbm/ldbm_add.c
index db6024636..90d5abc3d 100644
--- a/ldap/servers/slapd/back-ldbm/ldbm_add.c
+++ b/ldap/servers/slapd/back-ldbm/ldbm_add.c
@@ -1452,7 +1452,7 @@ common_return:
slapi_pblock_get(pb, SLAPI_DEFERRED_MEMBEROF, &deferred);
if (deferred) {
PRIntervalTime delay = PR_MillisecondsToInterval(100);
- while (deferred) {
+ while (deferred && !g_get_shutdown()) {
DS_Sleep(delay);
slapi_pblock_get(pb, SLAPI_DEFERRED_MEMBEROF, &deferred);
}
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_delete.c b/ldap/servers/slapd/back-ldbm/ldbm_delete.c
index 498342f2d..cbfb5bca9 100644
--- a/ldap/servers/slapd/back-ldbm/ldbm_delete.c
+++ b/ldap/servers/slapd/back-ldbm/ldbm_delete.c
@@ -1536,7 +1536,7 @@ diskfull_return:
slapi_pblock_get(pb, SLAPI_DEFERRED_MEMBEROF, &deferred);
if (deferred) {
PRIntervalTime delay = PR_MillisecondsToInterval(100);
- while (deferred) {
+ while (deferred && !g_get_shutdown()) {
DS_Sleep(delay);
slapi_pblock_get(pb, SLAPI_DEFERRED_MEMBEROF, &deferred);
}
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_modify.c b/ldap/servers/slapd/back-ldbm/ldbm_modify.c
index ea49a4c56..c57ba43ae 100644
--- a/ldap/servers/slapd/back-ldbm/ldbm_modify.c
+++ b/ldap/servers/slapd/back-ldbm/ldbm_modify.c
@@ -1179,7 +1179,7 @@ common_return:
slapi_pblock_get(pb, SLAPI_DEFERRED_MEMBEROF, &deferred);
if (deferred) {
PRIntervalTime delay = PR_MillisecondsToInterval(100);
- while (deferred) {
+ while (deferred && !g_get_shutdown()) {
DS_Sleep(delay);
slapi_pblock_get(pb, SLAPI_DEFERRED_MEMBEROF, &deferred);
}
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c b/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c
index 018ad9e49..759edb80d 100644
--- a/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c
+++ b/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c
@@ -1469,7 +1469,7 @@ common_return:
slapi_pblock_get(pb, SLAPI_DEFERRED_MEMBEROF, &deferred);
if (deferred) {
PRIntervalTime delay = PR_MillisecondsToInterval(100);
- while (deferred) {
+ while (deferred && !g_get_shutdown()) {
DS_Sleep(delay);
slapi_pblock_get(pb, SLAPI_DEFERRED_MEMBEROF, &deferred);
}
--
2.52.0

View File

@ -0,0 +1,61 @@
From a84a6a7d8323316bfa4055729a3604a0fcfebaf9 Mon Sep 17 00:00:00 2001
From: Akshay Adhikari <aadhikar@redhat.com>
Date: Fri, 16 Jan 2026 19:35:42 +0530
Subject: [PATCH] Issue 7169 - Fix automember_plugin CI test failures (#7181)
Description: Issue 7053 removed member cleanup from MemberOf plugin,
transferring it to Referential Integrity plugin. Enable this plugin
in automember tests and clean up groups before rebuild task tests.
Fixes: #7169
Reviewed by: @progier389
---
.../tests/suites/automember_plugin/basic_test.py | 16 ++++++++++++++--
1 file changed, 14 insertions(+), 2 deletions(-)
diff --git a/dirsrvtests/tests/suites/automember_plugin/basic_test.py b/dirsrvtests/tests/suites/automember_plugin/basic_test.py
index 6f2cf3326..f3629c811 100644
--- a/dirsrvtests/tests/suites/automember_plugin/basic_test.py
+++ b/dirsrvtests/tests/suites/automember_plugin/basic_test.py
@@ -19,7 +19,8 @@ from lib389.idm.organizationalunit import OrganizationalUnits
from lib389.idm.domain import Domain
from lib389.idm.posixgroup import PosixGroups
from lib389.plugins import AutoMembershipPlugin, AutoMembershipDefinitions, \
- MemberOfPlugin, AutoMembershipRegexRules, AutoMembershipDefinition, RetroChangelogPlugin
+ MemberOfPlugin, AutoMembershipRegexRules, AutoMembershipDefinition, RetroChangelogPlugin, \
+ ReferentialIntegrityPlugin
from lib389.backend import Backends
from lib389.config import Config
from lib389._constants import DEFAULT_SUFFIX
@@ -196,6 +197,7 @@ def _create_all_entries(topo):
auto = AutoMembershipPlugin(topo.ms["supplier1"])
auto.add("nsslapd-pluginConfigArea", "cn=autoMembersPlugin,{}".format(BASE_REPL))
MemberOfPlugin(topo.ms["supplier1"]).enable()
+ ReferentialIntegrityPlugin(topo.ms["supplier1"]).enable()
automembers_definitions = AutoMembershipDefinitions(topo.ms["supplier1"])
automembers_definitions.create(properties={
'cn': 'userGroups',
@@ -950,8 +952,18 @@ def _startuptask(topo):
@pytest.fixture(scope="function")
def _fixture_for_build_task(request, topo):
+ supplier = topo.ms['supplier1']
+ managers_grp = "cn=Managers,ou=userGroups,{}".format(BASE_SUFF)
+ contract_grp = "cn=Contractors,ou=userGroups,{}".format(BASE_SUFF)
+
+ for grp in (managers_grp, contract_grp):
+ group = Group(supplier, grp)
+ try:
+ group.remove_all('member')
+ except ldap.NO_SUCH_ATTRIBUTE:
+ pass
+
def finof():
- supplier = topo.ms['supplier1']
auto_mem_scope = "ou=TaskEmployees,{}".format(BASE_SUFF)
for user in nsAdminGroups(supplier, auto_mem_scope, rdn=None).list():
user.delete()
--
2.52.0

View File

@ -0,0 +1,115 @@
From 5b32479abcd68f8b37d2fb207c502113a5b4b16c Mon Sep 17 00:00:00 2001
From: Akshay Adhikari <aadhikar@redhat.com>
Date: Mon, 19 Jan 2026 19:45:29 +0530
Subject: [PATCH] Issue 6758 - Use OUIA selectors for WebUI plugin tests
(#7182)
Description:
Add ouiaId to plugin NavItems and update tests to use OUIA selectors
instead of text matching.
Relates: #6758
Reviewed by: @droideck
---
.../suites/webui/plugins/plugins_test.py | 28 +++++++++----------
src/cockpit/389-console/src/plugins.jsx | 2 +-
2 files changed, 15 insertions(+), 15 deletions(-)
diff --git a/dirsrvtests/tests/suites/webui/plugins/plugins_test.py b/dirsrvtests/tests/suites/webui/plugins/plugins_test.py
index e4bd7f039..a849bfb91 100644
--- a/dirsrvtests/tests/suites/webui/plugins/plugins_test.py
+++ b/dirsrvtests/tests/suites/webui/plugins/plugins_test.py
@@ -168,8 +168,8 @@ def test_linked_attributes_plugin_visibility(topology_st, page, browser_name):
log.info('Click on Plugins tab, click on Linked Attributes plugin and check if element is loaded.')
frame.get_by_role('tab', name='Plugins', exact=True).click()
- frame.get_by_text('Linked Attributes').wait_for()
- frame.get_by_text('Linked Attributes').click()
+ frame.locator('[data-ouia-component-id="linkedAttributes"]').wait_for()
+ frame.locator('[data-ouia-component-id="linkedAttributes"]').click()
frame.get_by_role('button', name='Add Config').wait_for()
assert frame.get_by_role('button', name='Add Config').is_visible()
@@ -254,8 +254,8 @@ def test_ldap_pass_through_auth_plugin_visibility(topology_st, page, browser_nam
log.info('Click on Plugins tab, click on LDAP Pass Through Auth plugin and check if element is loaded.')
frame.get_by_role('tab', name='Plugins', exact=True).click()
- frame.get_by_text('LDAP Pass Through Auth').wait_for()
- frame.get_by_text('LDAP Pass Through Auth').click()
+ frame.locator('[data-ouia-component-id="passthroughAuthentication"]').wait_for()
+ frame.locator('[data-ouia-component-id="passthroughAuthentication"]').click()
frame.get_by_role('button', name='Add URL').wait_for()
assert frame.get_by_role('button', name='Add URL').is_visible()
@@ -280,8 +280,8 @@ def test_pam_pass_through_auth_plugin_visibility(topology_st, page, browser_name
log.info('Click on Plugins tab, click on PAM Pass Through Auth plugin and check if element is loaded.')
frame.get_by_role('tab', name='Plugins', exact=True).click()
- frame.get_by_text('PAM Pass Through Auth').wait_for()
- frame.get_by_text('PAM Pass Through Auth').click()
+ frame.locator('[data-ouia-component-id="pamPassthroughAuthentication"]').wait_for()
+ frame.locator('[data-ouia-component-id="pamPassthroughAuthentication"]').click()
frame.get_by_role('button', name='Add Config').wait_for()
assert frame.get_by_role('button', name='Add Config').is_visible()
@@ -306,8 +306,8 @@ def test_posix_winsync_plugin_visibility(topology_st, page, browser_name):
log.info('Click on Plugins tab, click on Posix Winsync plugin and check if element is loaded.')
frame.get_by_role('tab', name='Plugins', exact=True).click()
- frame.get_by_text('Posix Winsync').wait_for()
- frame.get_by_text('Posix Winsync').click()
+ frame.locator('[data-ouia-component-id="winsync"]').wait_for()
+ frame.locator('[data-ouia-component-id="winsync"]').click()
frame.locator('#posixWinsyncCreateMemberOfTask').wait_for()
assert frame.locator('#posixWinsyncCreateMemberOfTask').is_visible()
@@ -332,8 +332,8 @@ def test_referential_integrity_plugin_visibility(topology_st, page, browser_name
log.info('Click on Plugins tab, click on Referential Integrity plugin and check if element is loaded.')
frame.get_by_role('tab', name='Plugins', exact=True).click()
- frame.get_by_text('Referential Integrity').wait_for()
- frame.get_by_text('Referential Integrity').click()
+ frame.locator('[data-ouia-component-id="referentialIntegrity"]').wait_for()
+ frame.locator('[data-ouia-component-id="referentialIntegrity"]').click()
frame.locator('#entryScope').wait_for()
assert frame.locator('#entryScope').is_visible()
@@ -358,8 +358,8 @@ def test_retro_changelog_plugin_visibility(topology_st, page, browser_name):
log.info('Click on Plugins tab, click on Retro Changelog plugin and check if element is loaded.')
frame.get_by_role('tab', name='Plugins', exact=True).click()
- frame.get_by_text('Retro Changelog').wait_for()
- frame.get_by_text('Retro Changelog').click()
+ frame.locator('[data-ouia-component-id="retroChangelog"]').wait_for()
+ frame.locator('[data-ouia-component-id="retroChangelog"]').click()
frame.locator('#isReplicated').wait_for()
assert frame.locator('#isReplicated').is_visible()
@@ -384,8 +384,8 @@ def test_rootdn_access_control_plugin_visibility(topology_st, page, browser_name
log.info('Click on Plugins tab, click on RootDN Access Control plugin and check if element is loaded.')
frame.get_by_role('tab', name='Plugins', exact=True).click()
- frame.get_by_text('RootDN Access Control').wait_for()
- frame.get_by_text('RootDN Access Control').click()
+ frame.locator('[data-ouia-component-id="rootDnaAccessControl"]').wait_for()
+ frame.locator('[data-ouia-component-id="rootDnaAccessControl"]').click()
frame.locator('#allowMon').wait_for()
assert frame.locator('#allowMon').is_visible()
diff --git a/src/cockpit/389-console/src/plugins.jsx b/src/cockpit/389-console/src/plugins.jsx
index 4124a77ef..e24255c76 100644
--- a/src/cockpit/389-console/src/plugins.jsx
+++ b/src/cockpit/389-console/src/plugins.jsx
@@ -680,7 +680,7 @@ export class Plugins extends React.Component {
<Nav key={this.state.pluginTableKey} theme="light" onSelect={(event, item) => this.handleSelect(event, item)}>
<NavList>
{Object.entries(selectPlugins).map(([id, item]) => (
- <NavItem key={item.name} itemId={item.name} isActive={this.state.activePlugin === item.name}>
+ <NavItem key={item.name} itemId={item.name} ouiaId={id} isActive={this.state.activePlugin === item.name}>
{item.icon}
</NavItem>
))}
--
2.52.0

View File

@ -0,0 +1,32 @@
From 9bd93dc618c261d52222e713c56500abbce4113e Mon Sep 17 00:00:00 2001
From: progier389 <progier@redhat.com>
Date: Mon, 19 Jan 2026 17:24:40 +0100
Subject: [PATCH] Issue 7196 - DynamicCertificates returns empty DER (#7197)
Fixing a mistake done while fixing memory leaks.
Value was freed before being added in the entry rather than after ...
Issue: #7196
Reviewed by: @jchapma (thanks!)
---
ldap/servers/slapd/dyncerts.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/ldap/servers/slapd/dyncerts.c b/ldap/servers/slapd/dyncerts.c
index 50b92aa5f..efeaa6eb6 100644
--- a/ldap/servers/slapd/dyncerts.c
+++ b/ldap/servers/slapd/dyncerts.c
@@ -786,8 +786,8 @@ dyncerts_cert2entry(CERTCertificate *cert)
COND_STR(e, DYCATTR_TYPE, "OBJECT SIGNING CA", cert->nsCertType & NS_CERT_TYPE_OBJECT_SIGNING_CA);
slapi_entry_add_string(e, DYCATTR_TOKEN, PK11_GetTokenName(cert->slot));
secitemv(&cert->derCert, &tmpv);
- value_done(&tmpv);
slapi_entry_add_value(e, DYCATTR_CERTDER, &tmpv);
+ value_done(&tmpv);
tmpstr = secitem2hex(&cert->serialNumber);
slapi_entry_add_string(e, DYCATTR_SN, tmpstr);
slapi_ch_free_string(&tmpstr);
--
2.52.0

View File

@ -1,7 +1,7 @@
From 05ce84ae76e0e71381bcc7b8491a74e7edcd888f Mon Sep 17 00:00:00 2001
From c6f458b421598b18a545582441472b910b5ba56e 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 a977b71d1..486fad44b 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
@@ -408,6 +408,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 fba95987b..db464b43a 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)

View File

@ -0,0 +1,504 @@
From 6ce33b1bedd2cd13d7e6544692354715f6e613b8 Mon Sep 17 00:00:00 2001
From: progier389 <progier@redhat.com>
Date: Tue, 20 Jan 2026 19:41:05 +0100
Subject: [PATCH] Issue 7170 - Support of PQC keys (#7188)
Support of Post Quantum Cryptography Keys in certificates:
Added support of a new key type: ML_DSA
Enable the following policies (that are not enabled by defaut): ML-DSA-44, ML-DSA-65- ML-DSA-87
Replaced deprecated function SSL_ConfigSecureServer by SSL_ConfigServerCert
Added test case for ML-DSA certificate. That test case rely of openssl command because python cryptography module does not yet support ML-DSA keys
Issue: #7170
Reviewed by: @tbordaz, @droideck and @vashirov (Thanks!)
---
dirsrvtests/tests/suites/tls/mldsa_test.py | 322 +++++++++++++++++++++
ldap/servers/slapd/ssl.c | 95 +++++-
2 files changed, 407 insertions(+), 10 deletions(-)
create mode 100644 dirsrvtests/tests/suites/tls/mldsa_test.py
diff --git a/dirsrvtests/tests/suites/tls/mldsa_test.py b/dirsrvtests/tests/suites/tls/mldsa_test.py
new file mode 100644
index 000000000..2c815088b
--- /dev/null
+++ b/dirsrvtests/tests/suites/tls/mldsa_test.py
@@ -0,0 +1,322 @@
+# --- BEGIN COPYRIGHT BLOCK ---
+# Copyright (C) 2026 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 sys
+import itertools
+import rpm
+import socket
+import subprocess
+from lib389.utils import ds_is_older
+from lib389._constants import DN_DM, PW_DM, DEFAULT_SUFFIX
+from lib389.config import Encryption, CertmapLegacy
+from lib389.idm.user import UserAccount
+from lib389.topologies import topology_st as topo
+from tempfile import TemporaryDirectory
+
+pytestmark = pytest.mark.tier1
+
+DEBUGGING = os.getenv("DEBUGGING", default=False)
+if DEBUGGING:
+ logging.getLogger(__name__).setLevel(logging.DEBUG)
+else:
+ logging.getLogger(__name__).setLevel(logging.INFO)
+log = logging.getLogger(__name__)
+
+
+def rpm_is_older(pkg, version):
+ ts = rpm.TransactionSet()
+ mi = ts.dbMatch('name', pkg)
+ for h in mi:
+ print(f"{pkg} {h['version']} {version}")
+ for n1,n2 in itertools.zip_longest(h['version'].split('.'), version.split('.'), fillvalue=""):
+ try:
+ if int(n1) < int(n2):
+ return True
+ except ValueError:
+ if n1 < n2:
+ return True
+ return False
+
+
+script_content="""
+#!/bin/bash
+set -e # Exit if a command fails
+set -x # Log the commands
+
+cd {dir}
+inst={instname}
+url={url}
+rootdn="{rootdn}"
+rootpw="{rootpw}"
+
+################################
+###### GENERATE CA CERT ########
+################################
+
+echo "
+[ req ]
+distinguished_name = req_distinguished_name
+policy = policy_match
+x509_extensions = v3_ca
+
+# For the CA policy
+[ policy_match ]
+countryName = optional
+stateOrProvinceName = optional
+organizationName = optional
+organizationalUnitName = optional
+commonName = supplied
+emailAddress = optional
+
+[ req_distinguished_name ]
+countryName = Country Name (2 letter code)
+countryName_default = FR
+countryName_min = 2
+countryName_max = 2
+
+stateOrProvinceName = State or Province Name (full name)
+stateOrProvinceName_default = test
+
+localityName = Locality Name (eg, city)
+
+0.organizationName = Organization Name (eg, company)
+0.organizationName_default = test-ML-DSA-CA
+
+organizationalUnitName = Organizational Unit Name (eg, section)
+#organizationalUnitName_default =
+
+commonName = Common Name (e.g. server FQDN or YOUR name)
+commonName_max = 64
+
+
+[ v3_ca ]
+subjectKeyIdentifier = hash
+authorityKeyIdentifier = keyid:always,issuer
+basicConstraints = critical,CA:true
+#nsComment = "OpenSSL Generated Certificate"
+keyUsage=critical, keyCertSign
+" >ca.conf
+
+
+openssl genpkey -algorithm ML-DSA-87 -out ca.key
+openssl req -x509 -new -sha256 -key ca.key -nodes -days 3650 -config ca.conf -subj "/CN=`hostname`/O=test-ML-DSA-CA/C=FR" -out ca.pem -keyout ca.key
+openssl x509 -outform der -in ca.pem -out ca.crt
+
+openssl x509 -text -in ca.pem
+
+####################################
+###### GENERATE SERVER CERT ########
+####################################
+
+echo "
+[ req ]
+distinguished_name = req_distinguished_name
+policy = policy_match
+x509_extensions = v3_cert
+
+# For the cert policy
+[ policy_match ]
+countryName = optional
+stateOrProvinceName = optional
+organizationName = optional
+organizationalUnitName = optional
+commonName = supplied
+emailAddress = optional
+
+[ req_distinguished_name ]
+countryName = Country Name (2 letter code)
+countryName_default = FR
+countryName_min = 2
+countryName_max = 2
+
+stateOrProvinceName = State or Province Name (full name)
+
+localityName = Locality Name (eg, city)
+
+0.organizationName = Organization Name (eg, company)
+0.organizationName_default = test-ML-DSA
+
+organizationalUnitName = Organizational Unit Name (eg, section)
+#organizationalUnitName_default =
+
+commonName = Common Name (e.g. server FQDN or YOUR name)
+commonName_max = 64
+
+
+[ v3_cert ]
+basicConstraints = critical,CA:false
+subjectAltName=DNS:`hostname`
+keyUsage=digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
+#nsComment = "OpenSSL Generated Certificate"
+extendedKeyUsage=clientAuth, serverAuth
+nsCertType=client, server
+" >cert.conf
+
+openssl genpkey -algorithm ML-DSA-65 -out cert.key
+openssl req -new -sha256 -key cert.key -nodes -config cert.conf -subj "/CN=`hostname`/O=test-ML-DSA/C=FR" -out cert.csr
+openssl x509 -req -sha256 -days 3650 -extensions v3_cert -extfile cert.conf -in cert.csr -CA ca.pem -CAkey ca.key -CAcreateserial -out cert.pem
+openssl pkcs12 -export -inkey cert.key -in cert.pem -name mldsacert -out cert.p12 -passout pass:secret12
+
+openssl x509 -text -in cert.pem
+
+
+
+####################################
+###### GENERATE CLIENT CERT ########
+####################################
+
+echo "
+[ req ]
+distinguished_name = req_distinguished_name
+policy = policy_match
+x509_extensions = v3_cert
+
+# For the cert policy
+[ policy_match ]
+countryName = optional
+stateOrProvinceName = optional
+organizationName = optional
+organizationalUnitName = optional
+commonName = supplied
+emailAddress = optional
+
+[ req_distinguished_name ]
+countryName = Country Name (2 letter code)
+countryName_default = FR
+countryName_min = 2
+countryName_max = 2
+
+stateOrProvinceName = State or Province Name (full name)
+
+localityName = Locality Name (eg, city)
+
+0.organizationName = Organization Name (eg, company)
+0.organizationName_default = test-ML-DSA
+
+organizationalUnitName = Organizational Unit Name (eg, section)
+#organizationalUnitName_default =
+
+commonName = Common Name (e.g. server FQDN or YOUR name)
+commonName_max = 64
+
+
+[ v3_cert ]
+basicConstraints = critical,CA:false
+subjectAltName=DNS:`hostname`
+keyUsage=digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
+#nsComment = "OpenSSL Generated Certificate"
+extendedKeyUsage=clientAuth
+nsCertType=client, server
+" >client.conf
+
+openssl genpkey -algorithm ML-DSA-65 -out client.key
+openssl req -new -sha256 -key client.key -nodes -config client.conf -subj "/CN=`hostname`/O=client-test-ML-DSA/C=FR" -out client.csr
+openssl x509 -req -sha256 -days 3650 -extensions v3_cert -extfile client.conf -in client.csr -CA ca.pem -CAkey ca.key -CAcreateserial -out client.pem
+openssl pkcs12 -export -inkey client.key -in client.pem -name mldsacert2 -out client.p12 -passout pass:secret12
+
+openssl x509 -text -in client.pem
+
+
+#############################
+###### INSTALL CERTS ########
+#############################
+
+certdbdir=$PREFIX/etc/dirsrv/slapd-$inst
+rm -f $certdbdir/cert9.db $certdbdir/key4.db
+certutil -N -d $certdbdir -f $certdbdir/pwdfile.txt
+
+certutil -A -n Self-Signed-CA -t CT,, -f $certdbdir/pwdfile.txt -d $certdbdir -a -i ca.pem
+certutil -A -n Client-Cert -t u,, -f $certdbdir/pwdfile.txt -d $certdbdir -a -i client.pem
+
+dsctl $inst tls import-server-key-cert cert.pem cert.key
+
+dsctl $inst restart
+
+
+#########################
+###### TEST CERT ########
+#########################
+export LDAPTLS_CACERT=$PWD/ca.pem
+export LDAPTLS_CERT=$PWD/client.pem
+export LDAPTLS_KEY=$PWD/client.key
+
+ldapsearch -x -H $url -D "$rootdn" -w "$rootpw" -b "" -s base
+ldapsearch -Y external -H $url -b "" -s base
+"""
+
+@pytest.mark.skipif(rpm_is_older("openssl", "3.5"), reason="OpenSSL too old to support PQC")
+@pytest.mark.skipif(rpm_is_older("nss", "3.119.1"), reason="NSS too old to support PQC")
+def test_mldsa(topo):
+ """Test using ML-DSA Certificate - (PQC)
+
+ :id: 87fb19ef-672d-4fa7-934d-dfb4397f2312
+ :setup: Standalone Instance
+ :steps:
+ 1. Configure the certmap
+ 2. Create user mapped with theclient certificate
+ 3. Generate the test script
+ 4. Run the test script
+ 5. Check that ldapsearch returned the namingcontext
+ :expectedresults:
+ 1. No error
+ 2. No error
+ 3. No error
+ 4. No error and exit code should be 0
+ 5. namingcontext should be in the script output
+ """
+
+ inst = topo.standalone
+ inst.enable_tls()
+
+ cm = CertmapLegacy(inst)
+ certmaps = cm.list()
+ certmaps['default'].update({'DNComps': None, 'CmapLdapAttr': 'description'})
+ cm.set(certmaps)
+
+ cert_dn = f'C=FR,O=client-test-ML-DSA,CN={socket.gethostname()}'
+ dn = f'uid=test_user,ou=people,{DEFAULT_SUFFIX}'
+ UserAccount(inst, dn=dn).create( properties= {
+ 'uid': 'test_user',
+ 'cn': 'Test user',
+ 'sn': 'Test user',
+ 'uidNumber': '99998',
+ 'gidNumber': '99998',
+ 'homeDirectory': '/var/empty',
+ 'loginShell': '/bin/false',
+ 'description': cert_dn })
+
+ tmpdir_kwargs = {}
+ if sys.version_info >= (3, 12):
+ tmpdir_kwargs['delete'] = not DEBUGGING
+ with TemporaryDirectory(**tmpdir_kwargs) as dir:
+ scriptname = f"{dir}/doit"
+ d = {
+ 'dir': dir,
+ 'instname': inst.serverid,
+ 'url': f"ldaps://localhost:{inst.sslport}",
+ 'rootdn': DN_DM,
+ 'rootpw': PW_DM,
+ }
+ with open(scriptname, 'w') as f:
+ f.write(script_content.format(**d))
+ res = subprocess.run(('/bin/bash', scriptname), stdout=subprocess.PIPE, stderr=subprocess.STDOUT, encoding='utf-8')
+ assert res
+ log.info(res.stdout)
+ res.check_returncode()
+ # If ldapsearch is successful then defaultnamingcontext should be in res.stdout
+ assert "defaultnamingcontext" in res.stdout
+
+
+if __name__ == '__main__':
+ # Run isolated
+ # -s for DEBUG mode
+ CURRENT_FILE = os.path.realpath(__file__)
+ pytest.main(["-s", CURRENT_FILE])
diff --git a/ldap/servers/slapd/ssl.c b/ldap/servers/slapd/ssl.c
index a9c17ef42..053db5424 100644
--- a/ldap/servers/slapd/ssl.c
+++ b/ldap/servers/slapd/ssl.c
@@ -169,6 +169,20 @@ PRBool enableTLS1 = PR_TRUE;
/* CA cert pem file */
static char *CACertPemFile = NULL;
+static const struct {
+ KeyType kt;
+ const char *shortname;
+ const char *fullname;
+} supported_key_types[] = {
+ { rsaKey, "RSA", "RivestShamirAdleman" },
+ { ecKey, "EC", "Elliptic Curve" },
+#ifdef MAX_ML_DSA_PRIVATE_KEY_LEN
+ { mldsaKey, "ML-DSA", "Module-Lattice-Based Digital Signature Algorithm (post-quantum)" },
+#endif
+ { 0 }
+};
+
+
/* helper functions for openldap update. */
static int slapd_extract_cert(Slapi_Entry *entry, int isCA);
static int slapd_extract_key(Slapi_Entry *entry, char *token, PK11SlotInfo *slot);
@@ -718,9 +732,31 @@ SSLPLCY_Install(void)
{
SECStatus s = 0;
+#ifdef MAX_ML_DSA_PRIVATE_KEY_LEN
+ int flags = NSS_USE_ALG_IN_SIGNATURE | NSS_USE_ALG_IN_SSL;
+ static const SECOidTag oids[] = {
+ SEC_OID_ML_DSA_44,
+ SEC_OID_ML_DSA_65,
+ SEC_OID_ML_DSA_87,
+ };
+#endif
s = NSS_SetDomesticPolicy();
+#ifdef MAX_ML_DSA_PRIVATE_KEY_LEN
+ /* Should rely on the crypto module policy in FIPS mode */
+ if (!slapd_pk11_isFIPS()) {
+ /* Set explicitly PQC algorithm policy if it is not set by default */
+ for (size_t i=0; s == SECSuccess && i < PR_ARRAY_SIZE(oids); i++) {
+ int oflags = 0;
+ (void) NSS_GetAlgorithmPolicy(oids[i], &oflags);
+ if ((oflags & flags) != flags) {
+ s = NSS_SetAlgorithmPolicy(oids[i], flags, 0);
+ }
+ }
+ }
+#endif
+
return s ? PR_FAILURE : PR_SUCCESS;
}
@@ -1640,7 +1676,7 @@ slapd_ssl_init2(PRFileDesc **fd, int startTLS)
/*
* Now, get the complete list of cipher families. Each family
* has a token name and personality name which we'll use to find
- * appropriate keys and certs, and call SSL_ConfigSecureServer
+ * appropriate keys and certs, and call SSL_ConfigServerCert
* with.
*/
@@ -1759,8 +1795,6 @@ slapd_ssl_init2(PRFileDesc **fd, int startTLS)
}
if (SECSuccess == rv) {
- SSLKEAType certKEA;
-
/* If we want weak dh params, flag it on the socket now! */
rv = SSL_OptionSet(*fd, SSL_ENABLE_SERVER_DHE, PR_TRUE);
if (rv != SECSuccess) {
@@ -1774,11 +1808,10 @@ slapd_ssl_init2(PRFileDesc **fd, int startTLS)
}
}
- certKEA = NSS_FindCertKEAType(cert);
- rv = SSL_ConfigSecureServer(*fd, cert, key, certKEA);
+ rv = SSL_ConfigServerCert(*fd, cert, key, NULL, 0);
if (SECSuccess != rv) {
errorCode = PR_GetError();
- slapd_SSL_warn("ConfigSecureServer: "
+ slapd_SSL_warn("SSL_ConfigServerCert: "
"Server key/certificate is "
"bad for cert %s of family %s (" SLAPI_COMPONENT_NAME_NSPR " error %d - %s)",
cert_name, *family, errorCode,
@@ -2663,6 +2696,38 @@ bail:
return rv;
}
+/* Helper for get_supported_key_type */
+static char *
+buf_add_str(char *buf, char *bufend, const char *str)
+{
+ /* bufend is sizeof(buf)-4 (to avoid overflow with ...) */
+ char *ret = buf+strlen(str);
+ if (ret > bufend) {
+ ret = bufend;
+ strcpy(buf, "...");
+ } else {
+ strcpy(buf, str);
+ }
+ return ret;
+}
+
+static void
+get_supported_key_type_names(char *buf, size_t bufsize)
+{
+ char *bufend = buf + bufsize - 4;
+ for (size_t i=0; supported_key_types[i].kt; i++) {
+ if (i>0) {
+ if (supported_key_types[i+1].kt == 0) {
+ /* Last supported key type */
+ buf = buf_add_str(buf, bufend, " or ");
+ } else {
+ buf = buf_add_str(buf, bufend, ", ");
+ }
+ }
+ buf = buf_add_str(buf, bufend, supported_key_types[i].shortname);
+ }
+}
+
/*
* Borrowed from keyutil.c (crypto-util)
*
@@ -2723,10 +2788,20 @@ extractKeysAndSubject(
}
keytype = (*privkey)->keyType;
- if (keytype != rsaKey && keytype != ecKey) {
- slapi_log_err(SLAPI_LOG_ERR, "extractKeysAndSubject",
- "Unexpected key algorythm in certificate: %s. Only rsa and ec keys are supported.\n", nickname);
- goto bail;
+ for (size_t i=0; ;i++) {
+ KeyType kt = supported_key_types[i].kt;
+ if (kt == keytype && keytype != 0) {
+ /* Stop looping if the key type is supported */
+ break;
+ }
+ if (kt == 0) {
+ /* No supported key type have been found. */
+ char sktnames[100] = "";
+ get_supported_key_type_names(sktnames, sizeof sktnames);
+ slapi_log_err(SLAPI_LOG_ERR, "extractKeysAndSubject",
+ "Unexpected key algorithm in certificate: %s. Only %s are supported.\n", nickname, sktnames);
+ goto bail;
+ }
}
*subject = CERT_AsciiToName(cert->subjectName);
--
2.52.0

View File

@ -0,0 +1,56 @@
From 4068f68bea77f466f9b3d87c766ea14d2f175b17 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Wed, 21 Jan 2026 19:58:46 -0800
Subject: [PATCH] Bump lodash from 4.17.21 to 4.17.23 in
/src/cockpit/389-console (#7203)
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.21 to 4.17.23.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.21...4.17.23)
---
updated-dependencies:
- dependency-name: lodash
dependency-version: 4.17.23
dependency-type: indirect
...
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
src/cockpit/389-console/package-lock.json | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/src/cockpit/389-console/package-lock.json b/src/cockpit/389-console/package-lock.json
index 0aa5bbbb9..23faef62f 100644
--- a/src/cockpit/389-console/package-lock.json
+++ b/src/cockpit/389-console/package-lock.json
@@ -4833,9 +4833,9 @@
}
},
"node_modules/lodash": {
- "version": "4.17.21",
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
- "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
+ "version": "4.17.23",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
+ "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w=="
},
"node_modules/lodash.merge": {
"version": "4.6.2",
@@ -11087,9 +11087,9 @@
}
},
"lodash": {
- "version": "4.17.21",
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
- "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
+ "version": "4.17.23",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
+ "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w=="
},
"lodash.merge": {
"version": "4.6.2",
--
2.52.0

View File

@ -0,0 +1,517 @@
From 3ff253af76df07fe0519481795b7ed155fefaa9e Mon Sep 17 00:00:00 2001
From: Simon Pichugin <spichugi@redhat.com>
Date: Fri, 23 Jan 2026 17:35:45 -0800
Subject: [PATCH] Issue 7198 - Web console doesn't show sub-suffix when
parent-suffix points to an entry (#7202)
Description: The web console doesn't show sub-suffixes when the
nsslapd-parent-suffix attribute points to an entry rather than a backend
suffix.
For example, creating a sub-suffix ou=foo,ou=people,dc=example,dc=com
with parent-suffix ou=people,dc=example,dc=com (where ou=people is just an
entry, not a suffix) would not appear in the web console tree.
Fix: In backend_build_tree() and get_sub_suffixes(), the code only matched
when nsslapd-parent-suffix exactly equaled an existing backend suffix.
Now it also checks if the parent-suffix is an entry under the current
suffix (ends with ,suffix) and is not itself a backend suffix. This
correctly attaches sub-suffixes to their containing suffix when the
parent-suffix points to an intermediate entry.
Fixes: https://github.com/389ds/389-ds-base/issues/7198
Reviewed by: @progier389 (Thanks!)
---
.../suites/lib389/subsuffix_tree_test.py | 313 ++++++++++++++++++
src/lib389/lib389/backend.py | 47 ++-
src/lib389/lib389/cli_conf/backend.py | 34 +-
3 files changed, 370 insertions(+), 24 deletions(-)
create mode 100644 dirsrvtests/tests/suites/lib389/subsuffix_tree_test.py
diff --git a/dirsrvtests/tests/suites/lib389/subsuffix_tree_test.py b/dirsrvtests/tests/suites/lib389/subsuffix_tree_test.py
new file mode 100644
index 000000000..fa10ba530
--- /dev/null
+++ b/dirsrvtests/tests/suites/lib389/subsuffix_tree_test.py
@@ -0,0 +1,313 @@
+# --- BEGIN COPYRIGHT BLOCK ---
+# Copyright (C) 2026 Red Hat, Inc.
+# All rights reserved.
+#
+# License: GPL (version 3 or any later version).
+# See LICENSE for details.
+# --- END COPYRIGHT BLOCK ---
+#
+import logging
+import os
+import pytest
+from lib389.topologies import topology_st as topo
+from lib389.backend import Backends
+from lib389.idm.organizationalunit import OrganizationalUnits
+from lib389._constants import DEFAULT_SUFFIX
+
+pytestmark = pytest.mark.tier1
+
+logging.getLogger(__name__).setLevel(logging.INFO)
+log = logging.getLogger(__name__)
+
+
+@pytest.fixture(scope="function")
+def setup_subsuffix_with_entry_parent(topo, request):
+ """Setup a sub-suffix whose parent-suffix points to an entry, not a suffix."""
+ inst = topo.standalone
+
+ # Create ou=people entry under the root suffix
+ log.info("Creating ou=people,dc=example,dc=com entry")
+ ous = OrganizationalUnits(inst, DEFAULT_SUFFIX)
+ ou_people = ous.get('people')
+
+ # Create sub-suffix with parent-suffix pointing to the entry
+ log.info("Creating sub-suffix ou=foo,ou=people,dc=example,dc=com")
+ backends = Backends(inst)
+ subsuffix_dn = 'ou=foo,ou=people,dc=example,dc=com'
+ parent_suffix_dn = 'ou=people,dc=example,dc=com'
+
+ foo_backend = backends.create(properties={
+ 'cn': 'foo',
+ 'nsslapd-suffix': subsuffix_dn,
+ 'parent': parent_suffix_dn,
+ })
+
+ # Create the suffix entry
+ foo_ous = OrganizationalUnits(inst, parent_suffix_dn)
+ foo_ou = foo_ous.create(properties={'ou': 'foo'})
+
+ def cleanup():
+ log.info("Cleaning up test backends and entries")
+ try:
+ foo_ou.delete()
+ except Exception as e:
+ log.warning(f"Failed to delete foo_ou: {e}")
+ try:
+ foo_backend.delete()
+ except Exception as e:
+ log.warning(f"Failed to delete foo_backend: {e}")
+
+ request.addfinalizer(cleanup)
+
+ return {
+ 'instance': inst,
+ 'backends': backends,
+ 'foo_backend': foo_backend,
+ 'ou_people': ou_people,
+ 'subsuffix_dn': subsuffix_dn,
+ 'parent_suffix_dn': parent_suffix_dn,
+ }
+
+
+def test_subsuffix_with_entry_parent_in_tree(topo, setup_subsuffix_with_entry_parent):
+ """Test that a sub-suffix with parent pointing to an entry is visible in the tree.
+
+ :id: 256f36f5-76ad-4043-ad8d-1f9e2afc4e1d
+ :setup: Standalone instance with sub-suffix whose parent is an entry
+ :steps:
+ 1. Verify the sub-suffix backend exists
+ 2. Get sub-suffixes of the root backend
+ 3. Verify the sub-suffix appears in the list
+ :expectedresults:
+ 1. Backend should exist
+ 2. Sub-suffixes should be retrievable
+ 3. Sub-suffix should be visible (this is where the bug manifested)
+ """
+ backends = setup_subsuffix_with_entry_parent['backends']
+ foo_backend = setup_subsuffix_with_entry_parent['foo_backend']
+ subsuffix_dn = setup_subsuffix_with_entry_parent['subsuffix_dn']
+
+ # Step 1: Verify the sub-suffix backend exists
+ assert foo_backend.exists(), "The foo backend should exist"
+
+ # Step 2: Get sub-suffixes of the root backend
+ root_backend = backends.get(DEFAULT_SUFFIX)
+ sub_suffixes = root_backend.get_sub_suffixes()
+ log.info(f"Sub-suffixes found: {[s.get_attr_val_utf8('nsslapd-suffix') for s in sub_suffixes]}")
+
+ # Step 3: Verify sub-suffix is in the list
+ sub_suffix_found = any(
+ s.get_attr_val_utf8_l('nsslapd-suffix') == subsuffix_dn.lower()
+ for s in sub_suffixes
+ )
+
+ assert sub_suffix_found, (
+ f"Sub-suffix {subsuffix_dn} should be visible in get_sub_suffixes(). "
+ "The parent-suffix points to an entry, not a backend suffix."
+ )
+
+
+def test_subsuffix_in_backend_list(topo, setup_subsuffix_with_entry_parent):
+ """Test that the sub-suffix appears in the backend list.
+
+ :id: 0ccc49af-91bb-4e8f-b0e1-1bd0b75c041b
+ :setup: Standalone instance with sub-suffix configuration
+ :steps:
+ 1. Get all backends
+ 2. Verify both root suffix and sub-suffix are present
+ :expectedresults:
+ 1. Should retrieve all backends
+ 2. Both suffixes should be listed
+ """
+ backends = setup_subsuffix_with_entry_parent['backends']
+ subsuffix_dn = setup_subsuffix_with_entry_parent['subsuffix_dn']
+
+ be_list = backends.list()
+ suffixes = [be.get_attr_val_utf8_l('nsslapd-suffix') for be in be_list]
+
+ assert DEFAULT_SUFFIX.lower() in suffixes, \
+ f"Root suffix {DEFAULT_SUFFIX} should be in the list"
+ assert subsuffix_dn.lower() in suffixes, \
+ f"Sub-suffix {subsuffix_dn} should be in the list"
+
+
+def test_subsuffix_dn_boundary_matching():
+ """Test that suffix matching respects DN component boundaries.
+
+ :id: 0b856e26-c394-4c36-b9ba-d7894aa2ed11
+ :setup: None (unit test)
+ :steps:
+ 1. Test exact suffix match
+ 2. Test proper DN ancestor match (ends with ,suffix)
+ 3. Test that partial string matches are rejected
+ :expectedresults:
+ 1. Exact match should return True
+ 2. Proper ancestor should return True
+ 3. Partial string match should return False
+ """
+ from lib389.backend import is_subsuffix_of
+
+ all_suffixes = {'dc=com', 'dc=example,dc=com', 'ou=dept,dc=example,dc=com'}
+
+ # Test 1: Exact match
+ assert is_subsuffix_of('dc=example,dc=com', 'dc=example,dc=com', all_suffixes), \
+ "Exact match should return True"
+
+ # Test 2: Parent is an entry under the suffix (not itself a suffix)
+ assert is_subsuffix_of('ou=people,dc=example,dc=com', 'dc=example,dc=com', all_suffixes), \
+ "Parent entry under suffix should return True"
+
+ # Test 3: Parent IS a suffix - should return False (handled separately)
+ assert not is_subsuffix_of('ou=dept,dc=example,dc=com', 'dc=example,dc=com', all_suffixes), \
+ "Parent that is itself a suffix should return False"
+
+ # Test 4: Edge case - wrong DN boundary (string ends with suffix but wrong boundary)
+ edge_suffixes = {'dc=com', 'st,dc=com'}
+ assert is_subsuffix_of('dc=test,dc=com', 'dc=com', edge_suffixes), \
+ "dc=test,dc=com should match dc=com"
+ assert not is_subsuffix_of('dc=test,dc=com', 'st,dc=com', edge_suffixes), \
+ "dc=test,dc=com should NOT match st,dc=com (wrong DN boundary)"
+
+ # Test 5: None input
+ assert not is_subsuffix_of(None, 'dc=com', all_suffixes), \
+ "None parent should return False"
+
+ # Test 6: Closest ancestor - should only match the nearest suffix
+ # Hierarchy: dc=com -> dc=example,dc=com -> ou=branch,dc=example,dc=com (suffix)
+ # -> ou=dept,ou=branch,dc=example,dc=com (entry) -> subsuffix
+ # The subsuffix should only appear under ou=branch, not under dc=example,dc=com
+ nested_suffixes = {'dc=com', 'dc=example,dc=com', 'ou=branch,dc=example,dc=com'}
+ entry_parent = 'ou=dept,ou=branch,dc=example,dc=com'
+ # Should match ou=branch (closest)
+ assert is_subsuffix_of(entry_parent, 'ou=branch,dc=example,dc=com', nested_suffixes), \
+ "Should match closest ancestor suffix (ou=branch)"
+ # Should NOT match dc=example,dc=com (not closest)
+ assert not is_subsuffix_of(entry_parent, 'dc=example,dc=com', nested_suffixes), \
+ "Should NOT match distant ancestor (dc=example) - ou=branch is closer"
+ # Should NOT match dc=com (not closest)
+ assert not is_subsuffix_of(entry_parent, 'dc=com', nested_suffixes), \
+ "Should NOT match distant ancestor (dc=com) - ou=branch is closer"
+
+ log.info("All DN boundary edge cases passed")
+
+
+def test_deep_suffix_hierarchy(topo, request):
+ """Test complex hierarchy: suffix -> suffix -> entry -> suffix -> suffix.
+
+ :id: fd06491a-defa-4780-8472-78c077febdfb
+ :setup: Standalone instance
+ :steps:
+ 1. Create sub-suffix ou=branch (parent=dc=example,dc=com - a suffix)
+ 2. Create entry ou=dept,ou=branch (not a suffix)
+ 3. Create sub-suffix ou=team,ou=dept,ou=branch (parent=ou=dept - an entry)
+ 4. Create sub-suffix ou=sub,ou=team,ou=dept,ou=branch (parent=ou=team - a suffix)
+ 5. Verify all sub-suffixes are correctly placed in the tree
+ :expectedresults:
+ 1. Sub-suffix created successfully
+ 2. Entry created successfully
+ 3. Sub-suffix with entry parent created successfully
+ 4. Sub-suffix with suffix parent created successfully
+ 5. Tree hierarchy is correct
+ """
+ inst = topo.standalone
+ backends = Backends(inst)
+
+ # Define the hierarchy
+ branch_suffix = f'ou=branch,{DEFAULT_SUFFIX}'
+ dept_entry = f'ou=dept,{branch_suffix}' # This is an ENTRY, not a suffix
+ team_suffix = f'ou=team,{dept_entry}'
+ sub_suffix = f'ou=sub,{team_suffix}'
+
+ created_backends = []
+ created_entries = []
+
+ def cleanup():
+ log.info("Cleaning up deep hierarchy test")
+ for entry in reversed(created_entries):
+ try:
+ entry.delete()
+ except Exception as e:
+ log.warning(f"Failed to delete entry: {e}")
+ for be in reversed(created_backends):
+ try:
+ be.delete()
+ except Exception as e:
+ log.warning(f"Failed to delete backend: {e}")
+
+ request.addfinalizer(cleanup)
+
+ # Step 1: Create ou=branch sub-suffix (parent is root suffix)
+ log.info(f"Creating sub-suffix {branch_suffix}")
+ branch_be = backends.create(properties={
+ 'cn': 'branch',
+ 'nsslapd-suffix': branch_suffix,
+ 'parent': DEFAULT_SUFFIX,
+ })
+ created_backends.append(branch_be)
+ branch_ous = OrganizationalUnits(inst, DEFAULT_SUFFIX)
+ branch_ou = branch_ous.create(properties={'ou': 'branch'})
+ created_entries.append(branch_ou)
+
+ # Step 2: Create ou=dept entry under branch (NOT a suffix)
+ log.info(f"Creating entry {dept_entry}")
+ dept_ous = OrganizationalUnits(inst, branch_suffix)
+ dept_ou = dept_ous.create(properties={'ou': 'dept'})
+ created_entries.append(dept_ou)
+
+ # Step 3: Create ou=team sub-suffix (parent is dept ENTRY, not a suffix)
+ log.info(f"Creating sub-suffix {team_suffix} with entry parent {dept_entry}")
+ team_be = backends.create(properties={
+ 'cn': 'team',
+ 'nsslapd-suffix': team_suffix,
+ 'parent': dept_entry, # Parent is an ENTRY!
+ })
+ created_backends.append(team_be)
+ team_ous = OrganizationalUnits(inst, dept_entry)
+ team_ou = team_ous.create(properties={'ou': 'team'})
+ created_entries.append(team_ou)
+
+ # Step 4: Create ou=sub sub-suffix (parent is team suffix)
+ log.info(f"Creating sub-suffix {sub_suffix} with suffix parent {team_suffix}")
+ sub_be = backends.create(properties={
+ 'cn': 'sub',
+ 'nsslapd-suffix': sub_suffix,
+ 'parent': team_suffix, # Parent is a SUFFIX
+ })
+ created_backends.append(sub_be)
+ sub_ous = OrganizationalUnits(inst, team_suffix)
+ sub_ou = sub_ous.create(properties={'ou': 'sub'})
+ created_entries.append(sub_ou)
+
+ # Step 5: Verify the tree hierarchy
+ log.info("Verifying tree hierarchy...")
+
+ # Root should have branch as sub-suffix
+ root_be = backends.get(DEFAULT_SUFFIX)
+ root_subs = root_be.get_sub_suffixes()
+ root_sub_suffixes = [s.get_attr_val_utf8_l('nsslapd-suffix') for s in root_subs]
+ log.info(f"Root sub-suffixes: {root_sub_suffixes}")
+ assert branch_suffix.lower() in root_sub_suffixes, \
+ f"branch should be under root suffix"
+
+ # Branch should have team as sub-suffix (even though team's parent is an entry)
+ branch_be_obj = backends.get(branch_suffix)
+ branch_subs = branch_be_obj.get_sub_suffixes()
+ branch_sub_suffixes = [s.get_attr_val_utf8_l('nsslapd-suffix') for s in branch_subs]
+ log.info(f"Branch sub-suffixes: {branch_sub_suffixes}")
+ assert team_suffix.lower() in branch_sub_suffixes, \
+ f"team should be under branch suffix (parent is entry under branch)"
+
+ # Team should have sub as sub-suffix
+ team_be_obj = backends.get(team_suffix)
+ team_subs = team_be_obj.get_sub_suffixes()
+ team_sub_suffixes = [s.get_attr_val_utf8_l('nsslapd-suffix') for s in team_subs]
+ log.info(f"Team sub-suffixes: {team_sub_suffixes}")
+ assert sub_suffix.lower() in team_sub_suffixes, \
+ f"sub should be under team suffix"
+
+ log.info("Deep hierarchy test passed!")
+
+
+if __name__ == '__main__':
+ CURRENT_FILE = os.path.realpath(__file__)
+ pytest.main(["-s", CURRENT_FILE])
diff --git a/src/lib389/lib389/backend.py b/src/lib389/lib389/backend.py
index db464b43a..274d45abe 100644
--- a/src/lib389/lib389/backend.py
+++ b/src/lib389/lib389/backend.py
@@ -38,6 +38,36 @@ from lib389.lint import DSBLE0001, DSBLE0002, DSBLE0003, DSBLE0004, DSBLE0005, D
from lib389.plugins import USNPlugin
+def is_subsuffix_of(sub_parent, be_suffix, all_suffixes):
+ """Check if sub_parent indicates this is a sub-suffix of be_suffix.
+
+ Returns True only if be_suffix is the CLOSEST ancestor suffix of sub_parent.
+ This prevents a sub-suffix from appearing under multiple ancestors.
+
+ :param sub_parent: The nsslapd-parent-suffix value (lowercase)
+ :param be_suffix: The suffix to check against (lowercase)
+ :param all_suffixes: Set of all backend suffixes (lowercase)
+ :returns: True if be_suffix is the closest ancestor suffix
+ """
+ if not sub_parent:
+ return False
+ if sub_parent == be_suffix:
+ return True
+ if sub_parent in all_suffixes:
+ # sub_parent is itself a suffix, will be handled separately
+ return False
+ if not sub_parent.endswith(',' + be_suffix):
+ return False
+ # Find the closest (longest) matching suffix for this parent
+ best_match = None
+ for sfx in all_suffixes:
+ if sub_parent == sfx or sub_parent.endswith(',' + sfx):
+ if best_match is None or len(sfx) > len(best_match):
+ best_match = sfx
+ # Only return True if be_suffix is the closest match
+ return best_match == be_suffix
+
+
class BackendLegacy(object):
proxied_methods = 'search_s getEntry'.split()
@@ -1104,22 +1134,27 @@ class Backend(DSLdapObject):
vlv.create(rdn="cn=" + vlvname, properties=props, basedn=basedn)
def get_sub_suffixes(self):
- """Return a list of Backend's
- returns: a List of subsuffix entries
+ """Return a list of Backend's that are sub-suffixes of this backend.
+ :returns: A list of Backend instances that are sub-suffixes
"""
subsuffixes = []
top_be_suffix = self.get_attr_val_utf8_l('nsslapd-suffix')
+ if not top_be_suffix:
+ return subsuffixes
+
mts = self._mts.list()
+ be_insts = Backends(self._instance).list()
+ all_suffixes = {be.get_attr_val_utf8_l('nsslapd-suffix') for be in be_insts}
+
for mt in mts:
parent_suffix = mt.get_attr_val_utf8_l('nsslapd-parent-suffix')
if parent_suffix is None:
continue
- if parent_suffix == top_be_suffix:
+
+ if is_subsuffix_of(parent_suffix, top_be_suffix, all_suffixes):
child_suffix = mt.get_attr_val_utf8_l('cn')
- be_insts = Backends(self._instance).list()
for be in be_insts:
- be_suffix = be.get_attr_val_utf8_l('nsslapd-suffix')
- if child_suffix == be_suffix:
+ if child_suffix == be.get_attr_val_utf8_l('nsslapd-suffix'):
subsuffixes.append(be)
break
return subsuffixes
diff --git a/src/lib389/lib389/cli_conf/backend.py b/src/lib389/lib389/cli_conf/backend.py
index d0ec4bd9e..9772e39d4 100644
--- a/src/lib389/lib389/cli_conf/backend.py
+++ b/src/lib389/lib389/cli_conf/backend.py
@@ -7,7 +7,7 @@
# See LICENSE for details.
# --- END COPYRIGHT BLOCK ---
-from lib389.backend import Backend, Backends, DatabaseConfig, BackendSuffixView
+from lib389.backend import Backend, Backends, DatabaseConfig, BackendSuffixView, is_subsuffix_of
from lib389.configurations.sample import (
create_base_domain,
create_base_org,
@@ -338,6 +338,7 @@ def is_db_replicated(inst, suffix):
def backend_get_subsuffixes(inst, basedn, log, args):
subsuffixes = []
be_insts = MANY(inst).list()
+ all_suffixes = {be.get_attr_val_utf8_l('nsslapd-suffix') for be in be_insts}
for be in be_insts:
be_suffix = be.get_attr_val_utf8_l('nsslapd-suffix')
if be_suffix == args.be_name.lower():
@@ -347,7 +348,7 @@ def backend_get_subsuffixes(inst, basedn, log, args):
db_type = "suffix"
sub = mt.get_attr_val_utf8_l('nsslapd-parent-suffix')
sub_be = mt.get_attr_val_utf8_l('nsslapd-backend')
- if sub == be_suffix:
+ if is_subsuffix_of(sub, be_suffix, all_suffixes):
# We have a subsuffix (maybe a db link?)
if is_db_link(inst, sub_be):
db_type = "link"
@@ -399,38 +400,34 @@ def build_node(suffix, be_name, subsuf=False, link=False, replicated=False):
}
-def backend_build_tree(inst, be_insts, nodes):
- """Recursively build the tree
- """
- if len(nodes) == 0:
- # Done
+def backend_build_tree(inst, be_insts, nodes, all_suffixes):
+ """Recursively build the tree."""
+ if not nodes:
return
for node in nodes:
- node_suffix = node['id']
+ node_suffix = node['id'].lower()
# Get sub suffixes and chaining of node
for be in be_insts:
be_suffix = be.get_attr_val_utf8_l('nsslapd-suffix')
- if be_suffix == node_suffix.lower():
+ if be_suffix == node_suffix:
# We have our parent, now find the children
mts = be._mts.list()
-
for mt in mts:
sub_parent = mt.get_attr_val_utf8_l('nsslapd-parent-suffix')
sub_be = mt.get_attr_val_utf8_l('nsslapd-backend')
sub_suffix = mt.get_attr_val_utf8_l('cn')
- if sub_parent == be_suffix:
+ if is_subsuffix_of(sub_parent, be_suffix, all_suffixes):
# We have a subsuffix (maybe a db link?)
link = is_db_link(inst, sub_be)
replicated = is_db_replicated(inst, sub_suffix)
node['children'].append(build_node(sub_suffix,
- sub_be,
- subsuf=True,
- link=link,
- replicated=replicated))
-
+ sub_be,
+ subsuf=True,
+ link=link,
+ replicated=replicated))
# Recurse over the new subsuffixes
- backend_build_tree(inst, be_insts, node['children'])
+ backend_build_tree(inst, be_insts, node['children'], all_suffixes)
break
@@ -471,7 +468,8 @@ def backend_get_tree(inst, basedn, log, args):
else:
# Build the tree
be_insts = Backends(inst).list()
- backend_build_tree(inst, be_insts, nodes)
+ all_suffixes = {be.get_attr_val_utf8_l('nsslapd-suffix') for be in be_insts}
+ backend_build_tree(inst, be_insts, nodes, all_suffixes)
# Done
if args.json:
--
2.52.0

View File

@ -0,0 +1,38 @@
From aa24c00d9ed1ea9730c0e8c5dccc1bbb5b61e312 Mon Sep 17 00:00:00 2001
From: Lenka Doudova <lryznaro@redhat.com>
Date: Mon, 26 Jan 2026 16:21:23 +0100
Subject: [PATCH] Issue 7014 - memberOf - ignored deferred updates with LMDB
Description:
Fix typo in pytest marker reason.
Relates: #7014
Author: Lenka Doudova
Reviewed by: ???
---
.../suites/memberof_plugin/memberof_deferred_lmdb_test.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/dirsrvtests/tests/suites/memberof_plugin/memberof_deferred_lmdb_test.py b/dirsrvtests/tests/suites/memberof_plugin/memberof_deferred_lmdb_test.py
index 0d9f793c1..12fcfa3ec 100644
--- a/dirsrvtests/tests/suites/memberof_plugin/memberof_deferred_lmdb_test.py
+++ b/dirsrvtests/tests/suites/memberof_plugin/memberof_deferred_lmdb_test.py
@@ -1,4 +1,5 @@
# --- BEGIN COPYRIGHT BLOCK ---
+
# Copyright (C) 2025 Red Hat, Inc.
# All rights reserved.
#
@@ -28,7 +29,7 @@ else:
logging.getLogger(__name__).setLevel(logging.INFO)
-@pytest.mark.skipif(get_default_db_lib() != "mdb", reason="Not supported over mdb")
+@pytest.mark.skipif(get_default_db_lib() != "mdb", reason="Not supported over bdb")
def test_memberof_deferred_update_lmdb_rejection(topo):
"""Test that memberOf plugin rejects deferred update configuration with LMDB backend
--
2.52.0

View File

@ -0,0 +1,48 @@
From 4a73a31e6c91e507e5aa2cba1e5bd55d1d07894d 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

View File

@ -0,0 +1,174 @@
From bc28406778534a77064a26b4f0467dffecde33ea Mon Sep 17 00:00:00 2001
From: Viktor Ashirov <vashirov@redhat.com>
Date: Tue, 27 Jan 2026 09:40:12 +0100
Subject: [PATCH] Issue 6947 - Revise time skew check in healthcheck tool - add
tests (#7208)
Description:
Add tests for DSSKEWLE0003 and DSSKEWLE0004 checks.
Relates: https://github.com/389ds/389-ds-base/issues/6947
Reviewed by: @droideck (Thanks!)
---
.../suites/healthcheck/health_skew_test.py | 148 ++++++++++++++++++
1 file changed, 148 insertions(+)
create mode 100644 dirsrvtests/tests/suites/healthcheck/health_skew_test.py
diff --git a/dirsrvtests/tests/suites/healthcheck/health_skew_test.py b/dirsrvtests/tests/suites/healthcheck/health_skew_test.py
new file mode 100644
index 000000000..9ee070364
--- /dev/null
+++ b/dirsrvtests/tests/suites/healthcheck/health_skew_test.py
@@ -0,0 +1,148 @@
+# --- BEGIN COPYRIGHT BLOCK ---
+# Copyright (C) 2026 Red Hat, Inc.
+# All rights reserved.
+#
+# License: GPL (version 3 or any later version).
+# See LICENSE for details.
+# --- END COPYRIGHT BLOCK ---
+#
+
+import logging
+import os
+import pytest
+import subprocess
+from lib389._constants import DEFAULT_SUFFIX
+from lib389.dseldif import DSEldif
+from lib389.replica import ReplicationManager
+from lib389.topologies import topology_m2
+
+pytestmark = pytest.mark.tier1
+
+CMD_OUTPUT = 'No issues found.'
+JSON_OUTPUT = '[]'
+
+log = logging.getLogger(__name__)
+
+
+def run_healthcheck_and_check_result(instance, searched_code, json=False, isnot=False):
+ """Run healthcheck and verify the expected code is in the output"""
+ cmd = ['dsctl']
+ if json:
+ cmd.append('--json')
+ if searched_code == CMD_OUTPUT:
+ searched_code = JSON_OUTPUT
+ cmd.append(instance.serverid)
+ cmd.extend(['healthcheck', '--check', 'dseldif:nsstate'])
+
+ result = subprocess.run(cmd, capture_output=True, universal_newlines=True)
+ log.info(f'Running: {cmd}')
+ log.info(f'Stdout: {result.stdout}')
+ log.info(f'Stderr: {result.stderr}')
+ log.info(f'Return code: {result.returncode}')
+ stdout = result.stdout
+
+ # stdout should not be empty
+ assert stdout is not None
+ assert len(stdout) > 0
+
+ if isnot:
+ assert searched_code not in stdout, \
+ f'{searched_code} should NOT be in healthcheck output but was found'
+ log.info(f'Verified {searched_code} is NOT in healthcheck output')
+ else:
+ assert searched_code in stdout, \
+ f'{searched_code} should be in healthcheck output but was not found'
+ log.info(f'Verified {searched_code} is in healthcheck output')
+
+
+def test_healthcheck_time_skew_extensive(topology_m2):
+ """Check if HealthCheck returns DSSKEWLE0003 and DSSKEWLE0004 codes for extensive time skew
+
+ :id: 7591cd2b-8d66-4d33-9f8d-babff0571086
+ :setup: Two suppliers replication topology
+ :steps:
+ 1. Create a replicated topology
+ 2. Stop supplier1
+ 3. Increase time skew on supplier1 to over 24 hours
+ 4. Start supplier1
+ 5. Use HealthCheck and verify DSSKEWLE0003 is reported
+ 6. Set nsslapd-ignore-time-skew to on
+ 7. Use HealthCheck and verify DSSKEWLE0003 is NOT reported
+ 8. Stop supplier1
+ 9. Increase time skew on supplier1 to over 365 days
+ 10. Start supplier1
+ 11. Use HealthCheck and verify only DSSKEWLE0004 is reported
+ :expectedresults:
+ 1. Success
+ 2. Success
+ 3. Success
+ 4. Success
+ 5. Healthcheck reports DSSKEWLE0003 code
+ 6. Success
+ 7. Healthcheck does not report DSSKEWLE0003 code
+ 8. Success
+ 9. Success
+ 10. Success
+ 11. Healthcheck reports DSSKEWLE0004 code and not DSSKEWLE0003
+ """
+
+ M1 = topology_m2.ms['supplier1']
+ M2 = topology_m2.ms['supplier2']
+
+ # Ensure replication is working first
+ repl = ReplicationManager(DEFAULT_SUFFIX)
+ repl.wait_for_replication(M1, M2)
+
+ # Step 2-4: Stop supplier1, increase time skew to over 24 hours, start supplier1
+ log.info('Stop supplier1 to modify dse.ldif')
+ M1.stop()
+
+ # Set time skew to over 24 hours (86400 seconds)
+ # Add a margin to be safely over the threshold
+ time_skew_24h = 86400 + 3600 # 25 hours
+ log.info(f'Increase time skew on supplier1 by {time_skew_24h} seconds')
+ DSEldif(M1)._increaseTimeSkew(DEFAULT_SUFFIX, time_skew_24h)
+
+ log.info('Start supplier1')
+ M1.start()
+
+ # Step 5: Verify DSSKEWLE0003 is reported
+ log.info('Run healthcheck and verify DSSKEWLE0003 is reported')
+ run_healthcheck_and_check_result(M1, 'DSSKEWLE0003', json=False)
+ run_healthcheck_and_check_result(M1, 'DSSKEWLE0003', json=True)
+
+ # Step 6: Set nsslapd-ignore-time-skew to on
+ log.info('Set nsslapd-ignore-time-skew to on')
+ M1.config.set('nsslapd-ignore-time-skew', 'on')
+
+ # Step 7: Verify DSSKEWLE0003 is NOT reported when ignoring time skew
+ log.info('Run healthcheck and verify DSSKEWLE0003 is NOT reported')
+ run_healthcheck_and_check_result(M1, 'DSSKEWLE0003', json=False, isnot=True)
+ run_healthcheck_and_check_result(M1, 'DSSKEWLE0003', json=True, isnot=True)
+
+ # Step 8-10: Stop supplier1, increase time skew to over 365 days, start supplier1
+ log.info('Stop supplier1 to modify dse.ldif')
+ M1.stop()
+
+ # Increase time skew to over 365 days (31536000 seconds)
+ # We need to add enough to go from current ~25 hours to over 365 days
+ time_skew_year = (86400 * 365) + 86400 # 366 days total additional
+ log.info(f'Increase time skew on supplier1 by {time_skew_year} seconds')
+ DSEldif(M1)._increaseTimeSkew(DEFAULT_SUFFIX, time_skew_year)
+
+ log.info('Start supplier1')
+ M1.start()
+
+ # Step 11: Verify only DSSKEWLE0004 is reported (not DSSKEWLE0003)
+ log.info('Run healthcheck and verify only DSSKEWLE0004 is reported')
+ run_healthcheck_and_check_result(M1, 'DSSKEWLE0004', json=False)
+ run_healthcheck_and_check_result(M1, 'DSSKEWLE0004', json=True)
+ run_healthcheck_and_check_result(M1, 'DSSKEWLE0003', json=False, isnot=True)
+ run_healthcheck_and_check_result(M1, 'DSSKEWLE0003', json=True, isnot=True)
+
+
+if __name__ == '__main__':
+ # Run isolated
+ # -s for DEBUG mode
+ CURRENT_FILE = os.path.realpath(__file__)
+ pytest.main("-s %s" % CURRENT_FILE)
--
2.52.0

View File

@ -0,0 +1,215 @@
From a53c0e4dea5c35fef196500b2a36c92bc8f07a51 Mon Sep 17 00:00:00 2001
From: Viktor Ashirov <vashirov@redhat.com>
Date: Tue, 27 Jan 2026 09:49:16 +0100
Subject: [PATCH] Issue 7201 - Syscall overhead in LMDB import writer thread
(#7204)
Bug Description:
ldif2db import is slower with LMDB than with BDB (~3500 vs ~4500
entries/s) due to 2 issues:
1. The MDB_STAT_STEP macro calls `clock_gettime()` to collect
performance statistics. This was called on every single operation inside
the writer loop, resulting in ~3 syscalls per write or ~6000 syscalls
per transaction (with 2000 as the default batch size).
2. In `dbmdb_import_workerq_push()`, after copying work to a worker
slot, the condition variable was never signaled. This caused workers to
spend up to 100ms in `safe_cond_wait()` before checking for new work,
severely limiting import throughput.
Fix Description:
1. Add a new config parameter `nsslapd-mdb-import-stats` (default: off)
to control whether performance statistics collection is enabled.
2. Add `pthread_cond_broadcast()` immediately after copying data to wake
the workers.
After applying these fixes ldif2db import rate is about ~10000 entries/s.
Fixes: https://github.com/389ds/389-ds-base/issues/7201
Reviewed by: @progier389 (Thanks!)
---
.../slapd/back-ldbm/db-mdb/mdb_config.c | 23 +++++++++++
.../back-ldbm/db-mdb/mdb_import_threads.c | 40 ++++++++++---------
.../slapd/back-ldbm/db-mdb/mdb_layer.h | 2 +
3 files changed, 46 insertions(+), 19 deletions(-)
diff --git a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_config.c b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_config.c
index 96295ed5b..ade42b1d7 100644
--- a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_config.c
+++ b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_config.c
@@ -477,6 +477,28 @@ dbmdb_ctx_t_db_durable_transactions_set(void *arg, void *value, char *errorbuf _
return retval;
}
+static void *
+dbmdb_ctx_t_db_import_stats_get(void *arg)
+{
+ struct ldbminfo *li = (struct ldbminfo *)arg;
+
+ return (void *)((uintptr_t)(MDB_CONFIG(li)->dsecfg.import_stats));
+}
+
+static int
+dbmdb_ctx_t_db_import_stats_set(void *arg, void *value, char *errorbuf __attribute__((unused)), int phase __attribute__((unused)), int apply)
+{
+ struct ldbminfo *li = (struct ldbminfo *)arg;
+ int retval = LDAP_SUCCESS;
+ int val = (int)((uintptr_t)value);
+
+ if (apply) {
+ MDB_CONFIG(li)->dsecfg.import_stats = val;
+ }
+
+ return retval;
+}
+
static int
dbmdb_ctx_t_set_bypass_filter_test(void *arg,
void *value,
@@ -589,6 +611,7 @@ static config_info dbmdb_ctx_t_param[] = {
{CONFIG_MDB_MAX_DBS, CONFIG_TYPE_INT, "512", &dbmdb_ctx_t_db_max_dbs_get, &dbmdb_ctx_t_db_max_dbs_set, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE},
{CONFIG_MAXPASSBEFOREMERGE, CONFIG_TYPE_INT, "100", &dbmdb_ctx_t_maxpassbeforemerge_get, &dbmdb_ctx_t_maxpassbeforemerge_set, 0},
{CONFIG_DB_DURABLE_TRANSACTIONS, CONFIG_TYPE_ONOFF, "on", &dbmdb_ctx_t_db_durable_transactions_get, &dbmdb_ctx_t_db_durable_transactions_set, CONFIG_FLAG_ALWAYS_SHOW},
+ {CONFIG_MDB_IMPORT_STATS, CONFIG_TYPE_ONOFF, "off", &dbmdb_ctx_t_db_import_stats_get, &dbmdb_ctx_t_db_import_stats_set, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE},
{CONFIG_BYPASS_FILTER_TEST, CONFIG_TYPE_STRING, "on", &dbmdb_ctx_t_get_bypass_filter_test, &dbmdb_ctx_t_set_bypass_filter_test, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE},
{CONFIG_SERIAL_LOCK, CONFIG_TYPE_ONOFF, "on", &dbmdb_ctx_t_serial_lock_get, &dbmdb_ctx_t_serial_lock_set, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE},
{CONFIG_CACHE_AUTOSIZE, CONFIG_TYPE_INT, "25", &mdb_config_cache_autosize_get, &mdb_config_cache_autosize_set, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE},
diff --git a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_import_threads.c b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_import_threads.c
index 2270abd69..65b29343e 100644
--- a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_import_threads.c
+++ b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_import_threads.c
@@ -59,9 +59,9 @@
/* import thread usage statistics */
-#define MDB_STAT_INIT(stats) { mdb_stat_collect(&stats, MDB_STAT_RUN, 1); }
-#define MDB_STAT_END(stats) { mdb_stat_collect(&stats, MDB_STAT_RUN, 0); }
-#define MDB_STAT_STEP(stats, step) { mdb_stat_collect(&stats, (step), 0); }
+#define MDB_STAT_INIT(stats, enabled) { if (enabled) mdb_stat_collect(&stats, MDB_STAT_RUN, 1); }
+#define MDB_STAT_END(stats, enabled) { if (enabled) mdb_stat_collect(&stats, MDB_STAT_RUN, 0); }
+#define MDB_STAT_STEP(stats, step, enabled) { if (enabled) mdb_stat_collect(&stats, (step), 0); }
typedef enum {
MDB_STAT_RUN,
@@ -424,6 +424,7 @@ dbmdb_import_workerq_push(ImportQueue_t *q, WorkerQueueData_t *data)
return -1;
}
dbmdb_dup_worker_slot(q, data, slot);
+ pthread_cond_broadcast(&q->cv);
pthread_mutex_unlock(&q->mutex);
return 0;
}
@@ -3914,14 +3915,15 @@ dbmdb_import_writer(void*param)
int count = 0;
int rc = 0;
mdb_stat_info_t stats = {0};
+ int stats_enabled = ctx->ctx->dsecfg.import_stats;
- MDB_STAT_INIT(stats);
+ MDB_STAT_INIT(stats, stats_enabled);
while (!rc && !info_is_finished(info)) {
- MDB_STAT_STEP(stats, MDB_STAT_PAUSE);
+ MDB_STAT_STEP(stats, MDB_STAT_PAUSE, stats_enabled);
wait_for_starting(info);
- MDB_STAT_STEP(stats, MDB_STAT_READ);
+ MDB_STAT_STEP(stats, MDB_STAT_READ, stats_enabled);
slot = dbmdb_import_q_getall(&ctx->writerq);
- MDB_STAT_STEP(stats, MDB_STAT_RUN);
+ MDB_STAT_STEP(stats, MDB_STAT_RUN, stats_enabled);
if (info_is_finished(info)) {
dbmdb_import_q_flush(&ctx->writerq);
break;
@@ -3932,14 +3934,14 @@ dbmdb_import_writer(void*param)
for (; slot; slot = nextslot) {
if (!txn) {
- MDB_STAT_STEP(stats, MDB_STAT_TXNSTART);
+ MDB_STAT_STEP(stats, MDB_STAT_TXNSTART, stats_enabled);
rc = TXN_BEGIN(ctx->ctx->env, NULL, 0, &txn);
}
if (!rc) {
- MDB_STAT_STEP(stats, MDB_STAT_WRITE);
+ MDB_STAT_STEP(stats, MDB_STAT_WRITE, stats_enabled);
rc = MDB_PUT(txn, slot->dbi->dbi, &slot->key, &slot->data, 0);
}
- MDB_STAT_STEP(stats, MDB_STAT_RUN);
+ MDB_STAT_STEP(stats, MDB_STAT_RUN, stats_enabled);
nextslot = slot->next;
slapi_ch_free((void**)&slot);
}
@@ -3947,9 +3949,9 @@ dbmdb_import_writer(void*param)
break;
}
if (count++ >= WRITER_MAX_OPS_IN_TXN) {
- MDB_STAT_STEP(stats, MDB_STAT_TXNSTOP);
+ MDB_STAT_STEP(stats, MDB_STAT_TXNSTOP, stats_enabled);
rc = TXN_COMMIT(txn);
- MDB_STAT_STEP(stats, MDB_STAT_RUN);
+ MDB_STAT_STEP(stats, MDB_STAT_RUN, stats_enabled);
if (rc) {
break;
}
@@ -3958,32 +3960,32 @@ dbmdb_import_writer(void*param)
}
}
if (txn && !rc) {
- MDB_STAT_STEP(stats, MDB_STAT_TXNSTOP);
+ MDB_STAT_STEP(stats, MDB_STAT_TXNSTOP, stats_enabled);
rc = TXN_COMMIT(txn);
- MDB_STAT_STEP(stats, MDB_STAT_RUN);
+ MDB_STAT_STEP(stats, MDB_STAT_RUN, stats_enabled);
if (!rc) {
txn = NULL;
}
}
if (txn) {
- MDB_STAT_STEP(stats, MDB_STAT_TXNSTOP);
+ MDB_STAT_STEP(stats, MDB_STAT_TXNSTOP, stats_enabled);
TXN_ABORT(txn);
- MDB_STAT_STEP(stats, MDB_STAT_RUN);
+ MDB_STAT_STEP(stats, MDB_STAT_RUN, stats_enabled);
txn = NULL;
}
- MDB_STAT_STEP(stats, MDB_STAT_WRITE);
+ MDB_STAT_STEP(stats, MDB_STAT_WRITE, stats_enabled);
if (!rc) {
/* Ensure that all data are written on disk */
rc = mdb_env_sync(ctx->ctx->env, 1);
}
- MDB_STAT_END(stats);
+ MDB_STAT_END(stats, stats_enabled);
if (rc) {
slapi_log_err(SLAPI_LOG_ERR, "dbmdb_import_writer",
"Failed to write in the database. Error is 0x%x: %s.\n",
rc, mdb_strerror(rc));
thread_abort(info);
- } else {
+ } else if (stats_enabled) {
char buf[200];
char *summary = mdb_stat_summarize(&stats, buf, sizeof buf);
if (summary) {
diff --git a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_layer.h b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_layer.h
index 647d00db9..cf6d00dd4 100644
--- a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_layer.h
+++ b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_layer.h
@@ -41,6 +41,7 @@
#define CONFIG_MDB_MAX_SIZE "nsslapd-mdb-max-size"
#define CONFIG_MDB_MAX_READERS "nsslapd-mdb-max-readers"
#define CONFIG_MDB_MAX_DBS "nsslapd-mdb-max-dbs"
+#define CONFIG_MDB_IMPORT_STATS "nsslapd-mdb-import-stats"
#define DBMDB_DB_MINSIZE ( 4LL * MEGABYTE )
#define DBMDB_DISK_RESERVE(disksize) ((disksize)*2ULL/1000ULL)
@@ -76,6 +77,7 @@ typedef struct
int max_readers;
int max_dbs;
uint64_t max_size;
+ int import_stats;
} dbmdb_cfg_t;
/* config parameters limits */
--
2.52.0

View File

@ -0,0 +1,135 @@
From 40484cb0b5034bc3c1e23b2ae1f2d39eedce07e9 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

View File

@ -0,0 +1,149 @@
From 8c6d1cfca22f87b20b79a419ceabdb182846ff4a Mon Sep 17 00:00:00 2001
From: progier389 <progier@redhat.com>
Date: Tue, 27 Jan 2026 15:39:39 +0100
Subject: [PATCH] Issue 7206 - Should log whether TLS key is PQC or not (#7207)
Append [PQC] to the cipher name when logging SSL/TLS if the Key Exchange is one of the KEM group
If connection debug level is enabled, logs in error log the keaType and keaGroup (as integer)
Issue: #7206
Reviewed by: @tbordaz, @droideck (Thanks!)
* Issue 7206 - Should log whether TLS key is PQC or not
* Fix Sourcery A/I comments
* Check PQC in test case
* Update ldap/servers/slapd/auth.c
Co-authored-by: Simon Pichugin <spichugi@redhat.com>
---------
Co-authored-by: Simon Pichugin <spichugi@redhat.com>
---
dirsrvtests/tests/suites/tls/mldsa_test.py | 7 ++++
ldap/servers/slapd/auth.c | 45 +++++++++++++++++++++-
ldap/servers/slapd/ssl.c | 2 +-
3 files changed, 52 insertions(+), 2 deletions(-)
diff --git a/dirsrvtests/tests/suites/tls/mldsa_test.py b/dirsrvtests/tests/suites/tls/mldsa_test.py
index 2c815088b..d6cbaf662 100644
--- a/dirsrvtests/tests/suites/tls/mldsa_test.py
+++ b/dirsrvtests/tests/suites/tls/mldsa_test.py
@@ -274,6 +274,7 @@ def test_mldsa(topo):
"""
inst = topo.standalone
+ inst.config.set('nsslapd-errorlog-level', str(16384 + 8))
inst.enable_tls()
cm = CertmapLegacy(inst)
@@ -293,6 +294,9 @@ def test_mldsa(topo):
'loginShell': '/bin/false',
'description': cert_dn })
+ inst.config.set("nsslapd-accesslog-logbuffering", "off")
+ inst.config.set("nsslapd-errorlog-logbuffering", "off")
+
tmpdir_kwargs = {}
if sys.version_info >= (3, 12):
tmpdir_kwargs['delete'] = not DEBUGGING
@@ -313,6 +317,9 @@ def test_mldsa(topo):
res.check_returncode()
# If ldapsearch is successful then defaultnamingcontext should be in res.stdout
assert "defaultnamingcontext" in res.stdout
+ assert inst.ds_access_log.match('.*RESULT.*dn="uid=test_user,ou=people,dc=example,dc=com".*')
+ assert inst.ds_access_log.match('.*TLS.*[PQC].*')
+ assert inst.ds_error_log.match('.*check_pqc.*')
if __name__ == '__main__':
diff --git a/ldap/servers/slapd/auth.c b/ldap/servers/slapd/auth.c
index 48e4b7129..9e83408d4 100644
--- a/ldap/servers/slapd/auth.c
+++ b/ldap/servers/slapd/auth.c
@@ -395,6 +395,43 @@ handle_bad_certificate(void *clientData, PRFileDesc *prfd)
}
+/*
+ * Determine if the connection key exchange is Post Quantum Cryptography aware.
+ * This function may need to evolve with NSS as more PQC methods get supported.
+ */
+static bool
+check_pqc(uint64_t connid, SSLChannelInfo *sci)
+{
+ /*
+ * FYI: To interpret the values:
+ * KeaType and KeaGroup values are defined in
+ * https://github.com/nss-dev/nss/blob/master/lib/ssl/sslt.h
+ * Respectively in SSLKEAType and SSLNamedGroup enums
+ */
+ slapi_log_err(SLAPI_LOG_CONNS, "check_pqc", "conn=%" PRIu64 " TLS keaType=%d keaGroup=%d\n",
+ connid, sci->keaType, sci->keaGroup);
+#ifdef MAX_ML_DSA_PRIVATE_KEY_LEN
+ /* NSS supports PQC (because of the ifdef). Now lets check if the connection uses it */
+ /* Check that PQC KeaType is hybrid */
+ switch (sci->keaType) {
+ case ssl_kea_ecdh_hybrid:
+ case ssl_kea_ecdh_hybrid_psk:
+ break;
+ default:
+ return false;
+ }
+ /* Check that PQC keaGroup is KEM */
+ switch (sci->keaGroup) {
+ case ssl_grp_kem_secp256r1mlkem768:
+ case ssl_grp_kem_secp384r1mlkem1024:
+ case ssl_grp_kem_mlkem768x25519:
+ case ssl_grp_kem_xyber768d00:
+ return true;
+ }
+#endif
+ return false;
+}
+
/*
* Get an identity from the client's certificate (if any was sent).
*
@@ -417,6 +454,7 @@ handle_handshake_done(PRFileDesc *prfd, void *clientData)
SSLCipherSuiteInfo cipherInfo;
char *subject = NULL;
char sslversion[64];
+ bool pqc = false;
int err = 0;
if ((slapd_ssl_getChannelInfo(prfd, &channelInfo, sizeof(channelInfo))) != SECSuccess) {
@@ -454,7 +492,12 @@ handle_handshake_done(PRFileDesc *prfd, void *clientData)
}
keySize = cipherInfo.effectiveKeyBits;
- cipher = slapi_ch_strdup(cipherInfo.symCipherName);
+ pqc = check_pqc(conn->c_connid, &channelInfo);
+ if (pqc) {
+ cipher = slapi_ch_smprintf("%s[PQC]", cipherInfo.symCipherName);
+ } else {
+ cipher = slapi_ch_strdup(cipherInfo.symCipherName);
+ }
/* If inside an Start TLS operation, perform the privacy level discovery
* and if the security degree achieved after the handshake is not reckoned
diff --git a/ldap/servers/slapd/ssl.c b/ldap/servers/slapd/ssl.c
index 053db5424..7d5db2cdd 100644
--- a/ldap/servers/slapd/ssl.c
+++ b/ldap/servers/slapd/ssl.c
@@ -748,7 +748,7 @@ SSLPLCY_Install(void)
if (!slapd_pk11_isFIPS()) {
/* Set explicitly PQC algorithm policy if it is not set by default */
for (size_t i=0; s == SECSuccess && i < PR_ARRAY_SIZE(oids); i++) {
- int oflags = 0;
+ PRUint32 oflags = 0;
(void) NSS_GetAlgorithmPolicy(oids[i], &oflags);
if ((oflags & flags) != flags) {
s = NSS_SetAlgorithmPolicy(oids[i], flags, 0);
--
2.52.0

View File

@ -0,0 +1,53 @@
From 27d3ea211847fa7ae674c5e4dcf485706e4ac591 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 ea744ac1e..80c07382a 100644
--- a/ldap/servers/slapd/log.c
+++ b/ldap/servers/slapd/log.c
@@ -206,8 +206,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;
}
@@ -217,17 +217,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

View File

@ -0,0 +1,195 @@
From 5ebce22d4214bec5ed94ad84c4448164be99389a 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
---
.../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 f7847ac74..7cdf16a84 100644
--- a/dirsrvtests/tests/suites/vlv/regression_test.py
+++ b/dirsrvtests/tests/suites/vlv/regression_test.py
@@ -1175,6 +1175,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 d320ecbeb..cd797621d 100644
--- a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_layer.c
+++ b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_layer.c
@@ -2134,10 +2134,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 8f9263f25..f2f882b5a 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

View File

@ -0,0 +1,285 @@
From 56f1881b5fb0198af4810eeca9e98736c297a2a5 Mon Sep 17 00:00:00 2001
From: Lenka Doudova <mirielka@users.noreply.github.com>
Date: Tue, 3 Feb 2026 10:28:02 +0100
Subject: [PATCH] Issue 6753 - Port ticket 47781 test (#7210)
Description:
Port ticket 47781 test into dirsrvtests/tests/suites/replication/replication_deadlock_test.py
Relates: #6753
Author: Lenka Doudova
Assisted by: Cursor
Reviewer: Mark Reynolds
---
.../replication/replication_deadlock_test.py | 129 +++++++++++++++++-
dirsrvtests/tests/tickets/ticket47781_test.py | 104 --------------
2 files changed, 128 insertions(+), 105 deletions(-)
delete mode 100644 dirsrvtests/tests/tickets/ticket47781_test.py
diff --git a/dirsrvtests/tests/suites/replication/replication_deadlock_test.py b/dirsrvtests/tests/suites/replication/replication_deadlock_test.py
index 8ddbb43bd..9c122add3 100644
--- a/dirsrvtests/tests/suites/replication/replication_deadlock_test.py
+++ b/dirsrvtests/tests/suites/replication/replication_deadlock_test.py
@@ -15,14 +15,20 @@ with replication data when replication agreements are present.
import logging
import os
import pytest
+import ldap
+from lib389._mapped_object import DSLdapObjects
from lib389.topologies import topology_m2 as topo
+from lib389.topologies import topology_st
from lib389.replica import Replicas, ReplicationManager
from lib389.agreement import Agreements
from lib389.tasks import ImportTask, ExportTask
from lib389.idm.user import UserAccounts
from lib389.tombstone import Tombstones
-from lib389._constants import DEFAULT_SUFFIX
+from lib389.backend import Backends
+from lib389._constants import (DEFAULT_SUFFIX, DEFAULT_BENAME, defaultProperties,
+ REPLICATION_BIND_DN, REPLICATION_BIND_PW,
+ REPLICATION_BIND_METHOD, REPLICATION_TRANSPORT)
pytestmark = pytest.mark.tier2
@@ -200,6 +206,127 @@ def test_replication_deadlock_tombstone_search(topo):
os.remove(export_ldif)
+def test_replication_deadlock_tombstone_invalid_agreement(topology_st):
+ """Test deadlock scenario after importing LDIF with replication data and invalid agreement
+
+ This test verifies that a deadlock does not occur while searching for tombstones after
+ setting up an invalid replication agreement (pointing to a non-existent server)
+
+ :id: a1b2c3d4-e5f6-4781-9abc-def012345678
+ :setup: Standalone instance with replication enabled
+ :steps:
+ 1. Create a supplier with an invalid replication agreement (port 5555, non-existent server)
+ 2. Add two test user entries
+ 3. Export LDIF with replication data
+ 4. Restart the server
+ 5. Import the LDIF back
+ 6. Search for tombstone entries with timeout (should not hang/deadlock)
+ 7. Cleanup: restore original timeout settings, delete invalid agreement, test entries, and export file
+ :expectedresults:
+ 1. Invalid replication agreement is created successfully
+ 2. Test entries are created successfully
+ 3. LDIF export completes successfully
+ 4. Server restarts successfully
+ 5. LDIF import completes successfully
+ 6. Tombstone search completes without deadlock (returns at least one entry; no hang/timeout)
+ 7. Cleanup completes successfully
+ """
+
+ standalone = topology_st.standalone
+ export_ldif = os.path.join(standalone.get_ldif_dir(), 'export.ldif')
+
+ # Step 1: Create supplier with invalid replication agreement
+ log.info('Creating supplier with invalid replication agreement (port 5555 - non-existent server)')
+ repl = ReplicationManager(DEFAULT_SUFFIX)
+ repl.create_first_supplier(standalone)
+
+ replicas = Replicas(standalone)
+ replica = replicas.get(DEFAULT_SUFFIX)
+ agreements = Agreements(standalone, basedn=replica.dn)
+
+ # Create agreement with invalid port (non-existent server)
+ invalid_agreement = agreements.create(properties={
+ 'cn': r'meTo_$host:$port',
+ 'nsDS5ReplicaRoot': DEFAULT_SUFFIX,
+ 'nsDS5ReplicaHost': standalone.host,
+ 'nsDS5ReplicaPort': '5555', # Invalid port - server does not exist
+ 'nsDS5ReplicaBindDN': defaultProperties[REPLICATION_BIND_DN],
+ 'nsDS5ReplicaBindMethod': defaultProperties[REPLICATION_BIND_METHOD],
+ 'nsDS5ReplicaTransportInfo': defaultProperties[REPLICATION_TRANSPORT],
+ 'nsDS5ReplicaCredentials': defaultProperties[REPLICATION_BIND_PW]
+ })
+
+ # Step 2: Add two test entries
+ log.info('Adding two test entries')
+ users = UserAccounts(standalone, DEFAULT_SUFFIX)
+
+ entry1 = users.create(properties={
+ 'uid': 'entry1',
+ 'cn': 'entry1',
+ 'sn': 'user',
+ 'userpassword': 'password',
+ 'uidNumber': '1001',
+ 'gidNumber': '2001',
+ 'homeDirectory': '/home/entry1'
+ })
+
+ entry2 = users.create(properties={
+ 'uid': 'entry2',
+ 'cn': 'entry2',
+ 'sn': 'user',
+ 'userpassword': 'password',
+ 'uidNumber': '1002',
+ 'gidNumber': '2002',
+ 'homeDirectory': '/home/entry2'
+ })
+
+ # Step 3: Export LDIF with replication data
+ log.info('Exporting LDIF with replication data')
+ backends = Backends(standalone)
+ export_task = backends.export_ldif(be_names=DEFAULT_BENAME, ldif=export_ldif, replication=True)
+ export_task.wait()
+
+ # Step 4: Restart the server
+ log.info('Restarting server')
+ standalone.restart()
+
+ # Step 5: Import the LDIF
+ log.info('Importing replication LDIF file')
+ import_task = ImportTask(standalone)
+ import_task.import_suffix_from_ldif(ldiffile=export_ldif, suffix=DEFAULT_SUFFIX)
+ import_task.wait()
+
+ # Step 6: Search for tombstones with timeout - should not hang/deadlock
+ log.info('Searching for tombstone entries (should find entries and not hang)')
+ # Save original timeout settings so we can restore them in cleanup
+ orig_network_timeout = standalone.get_option(ldap.OPT_NETWORK_TIMEOUT)
+ orig_timeout = standalone.get_option(ldap.OPT_TIMEOUT)
+ # Set explicit timeouts to detect deadlocks
+ standalone.set_option(ldap.OPT_NETWORK_TIMEOUT, 5)
+ standalone.set_option(ldap.OPT_TIMEOUT, 5)
+
+ # Verify at least one tombstone exists and the search does not hang/timeout
+ try:
+ results = DSLdapObjects(standalone, DEFAULT_SUFFIX).filter('(objectclass=nsTombstone)')
+ log.info(f'Found {len(results)} tombstone entries')
+ assert len(results) > 0, "Tombstone search should return at least one entry"
+ except Exception as e:
+ log.error(f"Tombstone search failed with error: {e}")
+ pytest.fail(f"Tombstone search failed: {e}")
+ finally:
+ # Restore original timeout settings
+ standalone.set_option(ldap.OPT_NETWORK_TIMEOUT, orig_network_timeout)
+ standalone.set_option(ldap.OPT_TIMEOUT, orig_timeout)
+ # Cleanup test entries and export file
+ log.info('Cleaning up test entries')
+ invalid_agreement.delete()
+ entry1.delete()
+ entry2.delete()
+ if os.path.exists(export_ldif):
+ os.remove(export_ldif)
+
+ log.info('Test completed successfully - no deadlock detected')
+
if __name__ == '__main__':
CURRENT_FILE = os.path.realpath(__file__)
pytest.main(["-s", CURRENT_FILE])
diff --git a/dirsrvtests/tests/tickets/ticket47781_test.py b/dirsrvtests/tests/tickets/ticket47781_test.py
deleted file mode 100644
index ffb9a5e1a..000000000
--- a/dirsrvtests/tests/tickets/ticket47781_test.py
+++ /dev/null
@@ -1,104 +0,0 @@
-# --- BEGIN COPYRIGHT BLOCK ---
-# Copyright (C) 2016 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
-from lib389.tasks import *
-from lib389.topologies import topology_st
-from lib389.replica import ReplicationManager
-
-from lib389._constants import (defaultProperties, DEFAULT_SUFFIX, ReplicaRole,
- REPLICAID_SUPPLIER_1, REPLICATION_BIND_DN, REPLICATION_BIND_PW,
- REPLICATION_BIND_METHOD, REPLICATION_TRANSPORT, RA_NAME,
- RA_BINDDN, RA_BINDPW, RA_METHOD, RA_TRANSPORT_PROT)
-
-pytestmark = pytest.mark.tier2
-
-log = logging.getLogger(__name__)
-
-
-def test_ticket47781(topology_st):
- """
- Testing for a deadlock after doing an online import of an LDIF with
- replication data. The replication agreement should be invalid.
- """
-
- log.info('Testing Ticket 47781 - Testing for deadlock after importing LDIF with replication data')
-
- supplier = topology_st.standalone
- repl = ReplicationManager(DEFAULT_SUFFIX)
- repl.create_first_supplier(supplier)
-
- properties = {RA_NAME: r'meTo_$host:$port',
- RA_BINDDN: defaultProperties[REPLICATION_BIND_DN],
- RA_BINDPW: defaultProperties[REPLICATION_BIND_PW],
- RA_METHOD: defaultProperties[REPLICATION_BIND_METHOD],
- RA_TRANSPORT_PROT: defaultProperties[REPLICATION_TRANSPORT]}
- # The agreement should point to a server that does NOT exist (invalid port)
- repl_agreement = supplier.agreement.create(suffix=DEFAULT_SUFFIX,
- host=supplier.host,
- port=5555,
- properties=properties)
-
- #
- # add two entries
- #
- log.info('Adding two entries...')
-
- supplier.add_s(Entry(('cn=entry1,dc=example,dc=com', {
- 'objectclass': 'top person'.split(),
- 'sn': 'user',
- 'cn': 'entry1'})))
-
- supplier.add_s(Entry(('cn=entry2,dc=example,dc=com', {
- 'objectclass': 'top person'.split(),
- 'sn': 'user',
- 'cn': 'entry2'})))
-
- #
- # export the replication ldif
- #
- log.info('Exporting replication ldif...')
- args = {EXPORT_REPL_INFO: True}
- exportTask = Tasks(supplier)
- exportTask.exportLDIF(DEFAULT_SUFFIX, None, "/tmp/export.ldif", args)
-
- #
- # Restart the server
- #
- log.info('Restarting server...')
- supplier.stop()
- supplier.start()
-
- #
- # Import the ldif
- #
- log.info('Import replication LDIF file...')
- importTask = Tasks(supplier)
- args = {TASK_WAIT: True}
- importTask.importLDIF(DEFAULT_SUFFIX, None, "/tmp/export.ldif", args)
- os.remove("/tmp/export.ldif")
-
- #
- # Search for tombstones - we should not hang/timeout
- #
- log.info('Search for tombstone entries(should find one and not hang)...')
- supplier.set_option(ldap.OPT_NETWORK_TIMEOUT, 5)
- supplier.set_option(ldap.OPT_TIMEOUT, 5)
- entries = supplier.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, 'objectclass=nsTombstone')
- if not entries:
- log.fatal('Search failed to find any entries.')
- assert PR_False
-
-
-if __name__ == '__main__':
- # Run isolated
- # -s for DEBUG mode
- CURRENT_FILE = os.path.realpath(__file__)
- pytest.main("-s %s" % CURRENT_FILE)
--
2.52.0

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,265 @@
From 603f4deb1e87c819c1830f58c7be4281d981fbbd Mon Sep 17 00:00:00 2001
From: Lenka Doudova <lryznaro@redhat.com>
Date: Mon, 2 Feb 2026 16:46:52 +0100
Subject: [PATCH] Issue 6753 - Port ticket 48896 test
Description:
Port ticket 48896 test into dirsrvtests/tests/suites/password/pwdPolicy_token_test.py
Relates: #6753
Author: Lenka Doudova
Assisted by: Cursor
Reviewer: Mark Reynolds
---
.../suites/password/pwdPolicy_token_test.py | 68 ++++++---
dirsrvtests/tests/tickets/ticket48896_test.py | 139 ------------------
2 files changed, 44 insertions(+), 163 deletions(-)
delete mode 100644 dirsrvtests/tests/tickets/ticket48896_test.py
diff --git a/dirsrvtests/tests/suites/password/pwdPolicy_token_test.py b/dirsrvtests/tests/suites/password/pwdPolicy_token_test.py
index ae4eb300f..a3caaa230 100644
--- a/dirsrvtests/tests/suites/password/pwdPolicy_token_test.py
+++ b/dirsrvtests/tests/suites/password/pwdPolicy_token_test.py
@@ -15,6 +15,7 @@ from lib389._constants import *
from lib389.idm.user import UserAccounts
from lib389.idm.organizationalunit import OrganizationalUnits
from lib389.topologies import topology_st as topo
+from lib389.idm.directorymanager import DirectoryManager
pytestmark = pytest.mark.tier1
@@ -25,14 +26,13 @@ else:
logging.getLogger(__name__).setLevel(logging.INFO)
log = logging.getLogger(__name__)
-USER_DN = 'uid=Test_user1,ou=People,dc=example,dc=com'
USER_ACI = '(targetattr="userpassword")(version 3.0; acl "pwp test"; allow (all) userdn="ldap:///self";)'
-TOKEN = 'test_user1'
+TOKEN = 'test_user123'
user_properties = {
- 'uid': 'Test_user1',
- 'cn': 'test_user1',
- 'sn': 'test_user1',
+ 'uid': 'Test_user123',
+ 'cn': 'test_user123',
+ 'sn': 'test_user123',
'uidNumber': '1001',
'gidNumber': '2001',
'userpassword': PASSWORD,
@@ -59,28 +59,48 @@ def test_token_lengths(topo):
:id: dae9d916-2a03-4707-b454-9e901d295b13
:setup: Standalone instance
:steps:
- 1. Test token length rejects password of the same length as rdn value
+ 1. Create user, setup global password policy
+ 2. Bind as user, change password to 'Abcd012+'
+ 3. Bind as user with 'Abcd012+', attempt changes to 'user', 'us123', 'Tuse!1234', 'Tuse!0987', 'Tabc!1234'
+ 4. For each passwordMinTokenLength 4, 6, 10: change settings, rebind as user, attempt password with token of that length from TOKEN
+ 5. Cleanup - delete user
:expectedresults:
- 1. Passwords are rejected
+ 1. User created, password policy enabled and set
+ 2. Success
+ 3. All attempts fail with CONSTRAINT_VIOLATION
+ 4. All attempts fail with CONSTRAINT_VIOLATION
+ 5. User successfully deleted
"""
user = pwd_setup(topo)
- for length in ['4', '6', '10']:
- topo.standalone.simple_bind_s(DN_DM, PASSWORD)
- topo.standalone.config.set('passwordMinTokenLength', length)
- topo.standalone.simple_bind_s(USER_DN, PASSWORD)
- time.sleep(1)
-
- try:
- passwd = TOKEN[:int(length)]
- log.info("Testing password len {} token ({})".format(length, passwd))
- user.replace('userpassword', passwd)
- log.fatal('Password incorrectly allowed!')
- assert False
- except ldap.CONSTRAINT_VIOLATION as e:
- log.info('Password correctly rejected: ' + str(e))
- except ldap.LDAPError as e:
- log.fatal('Unexpected failure ' + str(e))
- assert False
+ dm = DirectoryManager(topo.standalone)
+
+ try:
+ # Verify that the user can change their password
+ user.rebind(PASSWORD)
+ user.replace('userpassword', 'Abcd012+')
+
+ # Verify that the default password policy is enforced
+ user.rebind('Abcd012+')
+ for new_password in ['user', 'us123', 'Tuse!1234', 'Tuse!0987', 'Tabc!1234']:
+ log.info(f"Testing password {new_password}")
+ with pytest.raises(ldap.CONSTRAINT_VIOLATION):
+ user.replace('userpassword', new_password)
+
+ # Verify that the password policy is enforced for different token lengths
+ for length in ['4', '6', '10']:
+ dm.rebind(PASSWORD)
+ topo.standalone.config.set('passwordMinTokenLength', length)
+ user.rebind('Abcd012+')
+ time.sleep(1)
+
+ with pytest.raises(ldap.CONSTRAINT_VIOLATION):
+ passwd = TOKEN[:int(length)]
+ log.info("Testing password len {} token ({})".format(length, passwd))
+ user.replace('userpassword', passwd)
+
+ finally:
+ # Cleanup
+ user.delete()
if __name__ == '__main__':
diff --git a/dirsrvtests/tests/tickets/ticket48896_test.py b/dirsrvtests/tests/tickets/ticket48896_test.py
deleted file mode 100644
index a1897589e..000000000
--- a/dirsrvtests/tests/tickets/ticket48896_test.py
+++ /dev/null
@@ -1,139 +0,0 @@
-# --- BEGIN COPYRIGHT BLOCK ---
-# Copyright (C) 2016 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.tasks import *
-from lib389.utils import *
-from lib389.topologies import topology_st
-
-from lib389._constants import DEFAULT_SUFFIX, DN_DM, PASSWORD
-
-# Skip on older versions
-pytestmark = [pytest.mark.tier2,
- pytest.mark.skipif(ds_is_older('1.3.6'), reason="Not implemented")]
-
-logging.getLogger(__name__).setLevel(logging.DEBUG)
-log = logging.getLogger(__name__)
-
-CONFIG_DN = 'cn=config'
-UID = 'buser123'
-TESTDN = 'uid=%s,' % UID + DEFAULT_SUFFIX
-
-
-def check_attr_val(topology_st, dn, attr, expected):
- try:
- centry = topology_st.standalone.search_s(dn, ldap.SCOPE_BASE, 'cn=*')
- if centry:
- val = centry[0].getValue(attr)
- if val == expected:
- log.info('Default value of %s is %s' % (attr, expected))
- else:
- log.info('Default value of %s is not %s, but %s' % (attr, expected, val))
- assert False
- else:
- log.fatal('Failed to get %s' % dn)
- assert False
- except ldap.LDAPError as e:
- log.fatal('Failed to search ' + dn + ': ' + e.message['desc'])
- assert False
-
-
-def replace_pw(server, curpw, newpw, expstr, rc):
- log.info('Binding as {%s, %s}' % (TESTDN, curpw))
- server.simple_bind_s(TESTDN, curpw)
-
- hit = 0
- log.info('Replacing password: %s -> %s, which should %s' % (curpw, newpw, expstr))
- try:
- server.modify_s(TESTDN, [(ldap.MOD_REPLACE, 'userPassword', ensure_bytes(newpw))])
- except Exception as e:
- log.info("Exception (expected): %s" % type(e).__name__)
- hit = 1
- assert isinstance(e, rc)
-
- if (0 != rc) and (0 == hit):
- log.info('Expected to fail with %s, but passed' % rc.__name__)
- assert False
-
- log.info('PASSED')
-
-
-def test_ticket48896(topology_st):
- """
- """
- log.info('Testing Ticket 48896 - Default Setting for passwordMinTokenLength does not work')
-
- log.info("Setting global password policy with password syntax.")
- topology_st.standalone.simple_bind_s(DN_DM, PASSWORD)
- topology_st.standalone.modify_s(CONFIG_DN, [(ldap.MOD_REPLACE, 'passwordCheckSyntax', b'on'),
- (ldap.MOD_REPLACE, 'nsslapd-pwpolicy-local', b'on')])
-
- config = topology_st.standalone.search_s(CONFIG_DN, ldap.SCOPE_BASE, 'cn=*')
- mintokenlen = config[0].getValue('passwordMinTokenLength')
- history = config[0].getValue('passwordInHistory')
-
- log.info('Default passwordMinTokenLength == %s' % mintokenlen)
- log.info('Default passwordInHistory == %s' % history)
-
- log.info('Adding a user.')
- curpw = 'password'
- topology_st.standalone.add_s(Entry((TESTDN,
- {'objectclass': "top person organizationalPerson inetOrgPerson".split(),
- 'cn': 'test user',
- 'sn': 'user',
- 'userPassword': curpw})))
-
- newpw = 'Abcd012+'
- exp = 'be ok'
- rc = 0
- replace_pw(topology_st.standalone, curpw, newpw, exp, rc)
-
- curpw = 'Abcd012+'
- newpw = 'user'
- exp = 'fail'
- rc = ldap.CONSTRAINT_VIOLATION
- replace_pw(topology_st.standalone, curpw, newpw, exp, rc)
-
- curpw = 'Abcd012+'
- newpw = UID
- exp = 'fail'
- rc = ldap.CONSTRAINT_VIOLATION
- replace_pw(topology_st.standalone, curpw, newpw, exp, rc)
-
- curpw = 'Abcd012+'
- newpw = 'Tuse!1234'
- exp = 'fail'
- rc = ldap.CONSTRAINT_VIOLATION
- replace_pw(topology_st.standalone, curpw, newpw, exp, rc)
-
- curpw = 'Abcd012+'
- newpw = 'Tuse!0987'
- exp = 'fail'
- rc = ldap.CONSTRAINT_VIOLATION
- replace_pw(topology_st.standalone, curpw, newpw, exp, rc)
-
- curpw = 'Abcd012+'
- newpw = 'Tabc!1234'
- exp = 'fail'
- rc = ldap.CONSTRAINT_VIOLATION
- replace_pw(topology_st.standalone, curpw, newpw, exp, rc)
-
- curpw = 'Abcd012+'
- newpw = 'Direc+ory389'
- exp = 'be ok'
- rc = 0
- replace_pw(topology_st.standalone, curpw, newpw, exp, rc)
-
- log.info('SUCCESS')
-
-
-if __name__ == '__main__':
- # Run isolated
- # -s for DEBUG mode
- CURRENT_FILE = os.path.realpath(__file__)
- pytest.main("-s %s" % CURRENT_FILE)
--
2.52.0

View File

@ -0,0 +1,30 @@
From ff44acffcc67c985148c4df280685a674fec010a Mon Sep 17 00:00:00 2001
From: Akshay Adhikari <aadhikar@redhat.com>
Date: Thu, 5 Feb 2026 15:19:58 +0530
Subject: [PATCH] Issue 6810 - Fix PAM PTA test (#7219)
Description: Fix the PAM PTA test by add missing yield in fixture.
Relates: #6810
Reviewed by: @jchapma
---
dirsrvtests/tests/suites/plugins/pam_pta_test.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/dirsrvtests/tests/suites/plugins/pam_pta_test.py b/dirsrvtests/tests/suites/plugins/pam_pta_test.py
index 484c1bc80..e55a7f7ee 100644
--- a/dirsrvtests/tests/suites/plugins/pam_pta_test.py
+++ b/dirsrvtests/tests/suites/plugins/pam_pta_test.py
@@ -104,6 +104,8 @@ def pam_service_ldapserver(migrated_child_config):
f.write(line + "\n")
os.chmod(pam_file, 0o644)
+ yield
+
except Exception as e:
if os.path.exists(backup_file):
# Restore backup on error
--
2.52.0

View File

@ -0,0 +1,57 @@
From 2a0ed9c267fc56a14e84ad53ffaeb0b822594367 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 759edb80d..e859789b3 100644
--- a/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c
+++ b/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c
@@ -102,6 +102,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;
@@ -1181,6 +1182,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) {
@@ -1363,12 +1366,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

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,93 @@
From d5a83e8f2ccd0c9d11792f026947c4785996b4a6 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 c843a4b24..a49e378c3 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 = 25 if is_fips() else 20 # Reserve descriptor constant (higher in FIPS mode)
- 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

View File

@ -0,0 +1,94 @@
From 9bfbec8c4aad1c698fd80b3086e11f06fd9df26d Mon Sep 17 00:00:00 2001
From: Viktor Ashirov <vashirov@redhat.com>
Date: Mon, 9 Feb 2026 13:15:44 +0100
Subject: [PATCH] Issue 7178 - Bundled jemalloc fails to build with GCC 15
(#7216)
Description:
Update spec file to fix build failures on Fedora Rawhide
Fixes: https://github.com/389ds/389-ds-base/issues/7178
Reviewed by: @progier389, @droideck (Thanks!)
---
rpm.mk | 1 +
rpm/389-ds-base.spec.in | 10 +++++-----
rpm/jemalloc-5.3.0_throw_bad_alloc.patch | 12 ++++++++++++
3 files changed, 18 insertions(+), 5 deletions(-)
create mode 100644 rpm/jemalloc-5.3.0_throw_bad_alloc.patch
diff --git a/rpm.mk b/rpm.mk
index f91011814..372cee5a6 100644
--- a/rpm.mk
+++ b/rpm.mk
@@ -143,6 +143,7 @@ srpmdistdir:
rpmbuildprep:
cp dist/sources/$(TARBALL) $(RPMBUILD)/SOURCES/
cp rpm/$(PACKAGE)-* $(RPMBUILD)/SOURCES/
+ cp rpm/jemalloc-5.3.0_throw_bad_alloc.patch $(RPMBUILD)/SOURCES/
if [ $(BUNDLE_JEMALLOC) -eq 1 ]; then \
cp dist/sources/$(JEMALLOC_TARBALL) $(RPMBUILD)/SOURCES/ ; \
fi
diff --git a/rpm/389-ds-base.spec.in b/rpm/389-ds-base.spec.in
index 51bfd7e77..dc8c75dac 100644
--- a/rpm/389-ds-base.spec.in
+++ b/rpm/389-ds-base.spec.in
@@ -152,8 +152,8 @@ BuildRequires: python%{python3_pkgversion}-devel
# For cockpit
%if %{with cockpit}
BuildRequires: rsync
-BuildRequires: npm
-BuildRequires: nodejs
+BuildRequires: /usr/bin/npm
+BuildRequires: /usr/bin/node
%endif
# END BUILD REQUIRES
@@ -188,9 +188,7 @@ Requires: %{name}-robdb-libs = %{version}-%{release}
Requires: libdb
%endif
%endif
-Requires: lmdb
-# This picks up libperl.so as a Requires, so we add this versioned one
-Requires: perl(:MODULE_COMPAT_%(eval "`%{__perl} -V:version`"; echo $version))
+Requires: lmdb-libs
# Needed by logconv.pl
%if %{without libbdb_ro}
%if %{without bundle_libdb}
@@ -217,6 +215,7 @@ Source0: %{name}-%{version}.tar.bz2
Source2: %{name}-devel.README
%if %{with bundle_jemalloc}
Source3: https://github.com/jemalloc/%{jemalloc_name}/releases/download/%{jemalloc_ver}/%{jemalloc_name}-%{jemalloc_ver}.tar.bz2
+Source5: jemalloc-5.3.0_throw_bad_alloc.patch
%endif
%if %{with bundle_libdb}
Source4: https://fedorapeople.org/groups/389ds/libdb-5.3.28-59.tar.bz2
@@ -434,6 +433,7 @@ COCKPIT_FLAGS="--disable-cockpit"
# Build jemalloc
pushd ../%{jemalloc_name}-%{jemalloc_ver}
+patch -p1 -F3 < %{SOURCE5}
%configure \
--libdir=%{_libdir}/%{pkgname}/lib \
--bindir=%{_libdir}/%{pkgname}/bin \
diff --git a/rpm/jemalloc-5.3.0_throw_bad_alloc.patch b/rpm/jemalloc-5.3.0_throw_bad_alloc.patch
new file mode 100644
index 000000000..685e6968c
--- /dev/null
+++ b/rpm/jemalloc-5.3.0_throw_bad_alloc.patch
@@ -0,0 +1,12 @@
+diff --git a/src/jemalloc_cpp.cpp b/src/jemalloc_cpp.cpp
+index fffd6aee..5a682991 100644
+--- a/src/jemalloc_cpp.cpp
++++ b/src/jemalloc_cpp.cpp
+@@ -93,7 +93,7 @@ handleOOM(std::size_t size, bool nothrow) {
+ }
+
+ if (ptr == nullptr && !nothrow)
+- std::__throw_bad_alloc();
++ throw std::bad_alloc();
+ return ptr;
+ }
--
2.52.0

View File

@ -0,0 +1,71 @@
From fb4254a97fe0da25064d2f6296705c9e1810ffda 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

View File

@ -1,4 +1,4 @@
From 850cb2ae142b1dc31081387e8e997699668e70f4 Mon Sep 17 00:00:00 2001
From 3938942a5418add83616b1413f3070d394c30a7f 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 +-----------------
.../healthcheck/health_system_indexes_test.py | 136 +-----------------
.../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 | 104 +++-----------
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, 40 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 cbb8875fa..2c7d949d0 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')
@@ -706,19 +706,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):
@@ -729,10 +727,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):
@@ -743,15 +737,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):
@@ -782,10 +771,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):
@@ -810,12 +795,6 @@ def test_require_internal_index(topo, request):
with pytest.raises(ldap.UNWILLING_TO_PERFORM):
user.delete()
@ -100,23 +100,24 @@ 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 486fad44b..140845a33 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):
@@ -172,9 +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"],
- idlistscanlimit=['limit=5000 type=eq flags=AND'])
- standalone.restart()
+ backend.add_index("parentid", ["eq"], matching_rules=["integerOrderingMatch"])
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
@@ -263,8 +261,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 +127,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
@@ -408,132 +405,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 +260,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):
@@ -574,8 +445,7 @@ def test_multiple_missing_indexes(topology_st, log_buffering_enabled):
log.info("Re-add the missing system indexes")
backend = Backends(standalone).get("userRoot")
@ -267,13 +268,13 @@ index f25de4214..842f7e8dd 100644
- idlistscanlimit=['limit=5000 type=eq flags=AND'])
+ 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)
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 +301,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 +317,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 +327,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 b187c26bc..e23e7ff43 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
@@ -583,7 +583,6 @@ struct ldbminfo
int li_mode;
int li_lookthroughlimit;
int li_allidsthreshold;
@ -338,10 +339,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 0ab82948c..a5004be19 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(
@@ -997,8 +997,6 @@ index_read_ext_allids(
}
if (pb) {
slapi_pblock_get(pb, SLAPI_SEARCH_IS_AND, &is_and);
@ -351,7 +352,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 2a6e8cbb8..2b71cd4f7 100644
--- a/ldap/servers/slapd/back-ldbm/instance.c
+++ b/ldap/servers/slapd/back-ldbm/instance.c
@@ -16,7 +16,7 @@
@ -363,7 +364,7 @@ index 29643f7e4..6098e04fc 100644
/* Creates and initializes a new ldbm_instance structure.
@@ -127,7 +127,7 @@ done:
@@ -126,7 +126,7 @@ done:
* Take a bunch of strings, and create a index config entry
*/
Slapi_Entry *
@ -372,7 +373,7 @@ index 29643f7e4..6098e04fc 100644
{
Slapi_Entry *e = slapi_entry_alloc();
struct berval *vals[2];
@@ -168,11 +168,6 @@ ldbm_instance_init_config_entry(char *cn_val, char *val1, char *val2, char *val3
@@ -167,11 +167,6 @@ ldbm_instance_init_config_entry(char *cn_val, char *val1, char *val2, char *val3
slapi_entry_add_values(e, "nsMatchingRule", vals);
}
@ -384,7 +385,7 @@ index 29643f7e4..6098e04fc 100644
return e;
}
@@ -185,60 +180,8 @@ ldbm_instance_create_default_indexes(backend *be)
@@ -184,60 +179,8 @@ ldbm_instance_create_default_indexes(backend *be)
{
Slapi_Entry *e;
ldbm_instance *inst = (ldbm_instance *)be->be_instance_info;
@ -445,20 +446,14 @@ index 29643f7e4..6098e04fc 100644
/*
* Always index (entrydn or entryrdn), parentid, objectclass,
@@ -247,59 +190,47 @@ ldbm_instance_create_default_indexes(backend *be)
@@ -245,48 +188,42 @@ ldbm_instance_create_default_indexes(backend *be)
* since they are used by some searches, replication and the
* ACL routines.
*/
if (entryrdn_get_switch()) { /* subtree-rename: on */
- e = ldbm_instance_init_config_entry(LDBM_ENTRYRDN_STR, "subtree", 0, 0, 0, 0, 0);
+ e = ldbm_instance_init_config_entry(LDBM_ENTRYRDN_STR, "subtree", 0, 0, 0, 0);
ldbm_instance_config_add_index_entry(inst, e, flags);
slapi_entry_free(e);
} else {
- e = ldbm_instance_init_config_entry(LDBM_ENTRYDN_STR, "eq", 0, 0, 0, 0, 0);
+ e = ldbm_instance_init_config_entry(LDBM_ENTRYDN_STR, "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_ENTRYRDN_STR, "subtree", 0, 0, 0, 0, 0);
+ e = ldbm_instance_init_config_entry(LDBM_ENTRYRDN_STR, "subtree", 0, 0, 0, 0);
ldbm_instance_config_add_index_entry(inst, e, flags);
slapi_entry_free(e);
- ainfo_get(be, (char *)LDBM_PARENTID_STR, &ai);
- /* Check if the attrinfo is actually for parentid, not a fallback to .default */
@ -469,28 +464,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,35 +506,33 @@ 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)
* ancestorid is special, there is actually no such attr type
* but we still want to use the attr index file APIs.
*/
- ainfo_get(be, (char *)LDBM_ANCESTORID_STR, &ai);
- /* Check if the attrinfo is actually for ancestorid, not a fallback to .default */
- index_already_configured = (ai != NULL && strcmp(ai->ai_type, LDBM_ANCESTORID_STR) == 0);
- 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);
- }
+ e = ldbm_instance_init_config_entry(LDBM_ANCESTORID_STR, "eq", 0, 0, 0, "integerOrderingMatch");
+ attr_index_config(be, "ldbm index init", 0, e, 1, 0, NULL);
+ slapi_entry_free(e);
}
@@ -294,18 +231,9 @@ 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.
*/
- ainfo_get(be, (char *)LDBM_ANCESTORID_STR, &ai);
- /* Check if the attrinfo is actually for ancestorid, not a fallback to .default */
- index_already_configured = (ai != NULL && strcmp(ai->ai_type, LDBM_ANCESTORID_STR) == 0);
- 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);
- slapi_ch_free_string(&parentid_indexes_limit);
-
+ e = ldbm_instance_init_config_entry(LDBM_ANCESTORID_STR, "eq", 0, 0, 0, "integerOrderingMatch");
+ attr_index_config(be, "ldbm index init", 0, e, 1, 0, NULL);
+ slapi_entry_free(e);
return 0;
}
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 c24e3d766..6a2ce4c27 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
@@ -385,35 +385,6 @@ ldbm_config_allidsthreshold_set(void *arg, void *value, char *errorbuf __attribu
return retval;
}
@ -579,7 +568,7 @@ index f8d8f7474..b7bceabf2 100644
static void *
ldbm_config_pagedallidsthreshold_get(void *arg)
{
@@ -974,7 +945,6 @@ static config_info ldbm_config[] = {
@@ -1094,7 +1065,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 +577,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 29a3426ab..e69bfeedf 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 +608,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 274d45abe..f3dbe7c92 100644
--- a/src/lib389/lib389/backend.py
+++ b/src/lib389/lib389/backend.py
@@ -539,10 +539,11 @@ class Backend(DSLdapObject):
@@ -645,10 +645,11 @@ class Backend(DSLdapObject):
indexes = self.get_indexes()
# Default system indexes taken from ldap/servers/slapd/back-ldbm/instance.c
@ -637,7 +626,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):
@@ -705,17 +706,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 +645,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):
@@ -730,31 +728,16 @@ class Backend(DSLdapObject):
remediation_commands.append(cmd)
reindex_attrs.add(attr_name)
@ -693,7 +682,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):
@@ -993,13 +976,12 @@ class Backend(DSLdapObject):
return
raise ValueError("Can not delete index because it does not exist")
@ -708,7 +697,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):
@@ -1029,15 +1011,6 @@ class Backend(DSLdapObject):
# Only add if there are actually rules present in the list.
if len(mrs) > 0:
props['nsMatchingRule'] = mrs
@ -724,7 +713,7 @@ index 3cea0df36..4babf6850 100644
new_index.create(properties=props, basedn="cn=index," + self._dn)
if reindex:
@@ -1230,7 +1203,6 @@ class DatabaseConfig(DSLdapObject):
@@ -1349,7 +1322,6 @@ class DatabaseConfig(DSLdapObject):
'nsslapd-lookthroughlimit',
'nsslapd-mode',
'nsslapd-idlistscanlimit',
@ -733,7 +722,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 9772e39d4..68efa795c 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 +733,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 +755,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):
@@ -963,9 +947,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 +765,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):
@@ -1092,7 +1073,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')

View File

@ -1,4 +1,4 @@
From c6e2911dca08aac40e98999b48019eac72cc74a6 Mon Sep 17 00:00:00 2001
From 4c44e4c522afc0f5401754f29a47645889e21aca 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 140845a33..aea88e0e2 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):
@@ -453,6 +453,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 b02e37ed6..dcd16940b 100644
--- a/ldap/servers/slapd/upgrade.c
+++ b/ldap/servers/slapd/upgrade.c
@@ -22,8 +22,218 @@
* or altered.
*/
@@ -330,6 +330,107 @@ upgrade_remove_subtree_rename(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.
@@ -649,6 +750,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

View File

@ -1,6 +1,6 @@
From 1669e65b36d543fb69ef5503e5072ea37e9a0e19 Mon Sep 17 00:00:00 2001
From 41670301ccad5558296a3380a4974f7c0d4baede 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 aea88e0e2..eb727b902 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):
@@ -504,6 +504,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 bb8c71cd9..b6ab6f6c6 100644
--- a/ldap/ldif/template-dse.ldif.in
+++ b/ldap/ldif/template-dse.ldif.in
@@ -973,14 +973,6 @@ cn: aci
@@ -998,14 +998,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 dcd16940b..6b1b012da 100644
--- a/ldap/servers/slapd/upgrade.c
+++ b/ldap/servers/slapd/upgrade.c
@@ -123,6 +123,126 @@ upgrade_remove_index_scanlimit(void)
@@ -431,6 +431,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)
@@ -445,7 +565,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)
@@ -459,8 +579,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)
@@ -469,8 +590,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)
@@ -754,6 +875,10 @@ upgrade_server(void)
return UPGRADE_FAILURE;
}

View File

@ -1,4 +1,4 @@
From f6dd2d6dc94290c85d221951b34792443bb08d80 Mon Sep 17 00:00:00 2001
From a260b50aa0c8e6c5b8b3fd0b164e9bbc4a15983f 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 2b71cd4f7..17bfc09a0 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)
@@ -239,6 +239,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)
@@ -308,6 +568,8 @@ ldbm_instance_startall(struct ldbminfo *li)
ldbm_instance_register_modify_callback(inst);
vlv_init(inst);
slapi_mtn_be_started(inst->inst_be);

View File

@ -1,4 +1,4 @@
From 99470a4befbea7eda777b9096337e29113e1ddda Mon Sep 17 00:00:00 2001
From 9e5f22c94b822fcd3decb8f98ce2eb383cc16a7c 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 eb727b902..dd42cd197 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):
@@ -590,6 +590,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 dc8c75dac..0e0e28285 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
@@ -628,42 +628,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

View File

@ -0,0 +1,62 @@
From 3ebb30a65e2b40610301e5feedda0408ac9f3631 Mon Sep 17 00:00:00 2001
From: James Chapman <jachapma@redhat.com>
Date: Tue, 10 Feb 2026 10:35:48 +0000
Subject: [PATCH] Issue 7230 - Regression in healtcheck NssCheck (#7235)
Description:
Dynamic Certificate lib389 updadates modified get_cert_details() to
return a dict instead of tuple format. _lint_certificate_expiration() and
tls.list_cas() still assumes tuple style access.
Fix:
Update method to use dict key.
Fixes: https://github.com/389ds/389-ds-base/issues/7230
Co-authored-by: @flo-renaud
Reviewed by: @droideck (Thank you)
---
src/lib389/lib389/cli_ctl/tls.py | 4 ++--
src/lib389/lib389/nss_ssl.py | 4 ++--
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/lib389/lib389/cli_ctl/tls.py b/src/lib389/lib389/cli_ctl/tls.py
index 8834f5758..91f1f3a59 100644
--- a/src/lib389/lib389/cli_ctl/tls.py
+++ b/src/lib389/lib389/cli_ctl/tls.py
@@ -25,9 +25,9 @@ def list_client_cas(inst, log, args):
def list_cas(inst, log, args):
tls = NssSsl(dirsrv=inst)
- # This turns an array of [('CA', 'C,,')]
+ # Returns a list of cert dicts, eg {'cn': 'nickname', etc}
for c in tls.list_ca_certs():
- log.info(c[0])
+ log.info(c['cn'])
def show_cert(inst, log, args):
diff --git a/src/lib389/lib389/nss_ssl.py b/src/lib389/lib389/nss_ssl.py
index 764434166..fae65d19c 100644
--- a/src/lib389/lib389/nss_ssl.py
+++ b/src/lib389/lib389/nss_ssl.py
@@ -91,13 +91,13 @@ class NssSsl(DSLint):
if diff_date < timedelta(days=0):
# Expired
report = copy.deepcopy(DSCERTLE0002)
- report['detail'] = report['detail'].replace('CERT', cert[0])
+ report['detail'] = report['detail'].replace('CERT', cert['cn'])
report['check'] = f'tls:certificate_expiration'
yield report
elif diff_date < timedelta(days=30):
# Expiring within 30 days
report = copy.deepcopy(DSCERTLE0001)
- report['detail'] = report['detail'].replace('CERT', cert[0])
+ report['detail'] = report['detail'].replace('CERT', cert['cn'])
report['check'] = f'tls:certificate_expiration'
yield report
--
2.52.0

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,97 @@
From ae4a39474df08d56064453f0fb6c2272e6c3dc8b Mon Sep 17 00:00:00 2001
From: Mark Reynolds <mreynolds@redhat.com>
Date: Fri, 6 Feb 2026 15:13:30 -0500
Subject: [PATCH] Issue 7221 - CI tests - fix some flaky tests
Description:
Try to harden some of the flaky tests with sleeps and more relaxed contraints
Relates: https://github.com/389ds/389-ds-base/issues/7221
Reviewed by: spichugi(Thanks!)
---
dirsrvtests/tests/suites/acl/acivattr_test.py | 7 +++++--
dirsrvtests/tests/suites/import/regression_test.py | 2 +-
.../suites/replication/wait_for_async_feature_test.py | 2 +-
dirsrvtests/tests/suites/retrocl/basic_test.py | 2 +-
4 files changed, 8 insertions(+), 5 deletions(-)
diff --git a/dirsrvtests/tests/suites/acl/acivattr_test.py b/dirsrvtests/tests/suites/acl/acivattr_test.py
index d55eea023..efe2d5ef1 100644
--- a/dirsrvtests/tests/suites/acl/acivattr_test.py
+++ b/dirsrvtests/tests/suites/acl/acivattr_test.py
@@ -1,12 +1,12 @@
# --- BEGIN COPYRIGHT BLOCK ---
-# Copyright (C) 2019 Red Hat, Inc.
+# Copyright (C) 2026 Red Hat, Inc.
# All rights reserved.
#
# License: GPL (version 3 or any later version).
# See LICENSE for details.
# --- END COPYRIGHT BLOCK ---
-import pytest, os, ldap
+import pytest, os, ldap, time
from lib389._constants import DEFAULT_SUFFIX, PW_DM
from lib389.idm.user import UserAccount
from lib389.idm.organization import Organization
@@ -190,6 +190,8 @@ def test_positive(topo, _add_user, aci_of_user, user, entry, aci):
"""
# set aci
Domain(topo.standalone, DNBASE).set("aci", aci)
+ time.sleep(.5)
+
# create connection
conn = UserAccount(topo.standalone, user).bind(PW_DM)
# according to the aci , user will be able to change description
@@ -242,6 +244,7 @@ def test_negative(topo, _add_user, aci_of_user, user, entry, aci):
"""
# set aci
Domain(topo.standalone, DNBASE).set("aci", aci)
+ time.sleep(.5)
# create connection
conn = UserAccount(topo.standalone, user).bind(PW_DM)
# according to the aci , user will not be able to change description
diff --git a/dirsrvtests/tests/suites/import/regression_test.py b/dirsrvtests/tests/suites/import/regression_test.py
index 0e3ba1930..25a7f359d 100644
--- a/dirsrvtests/tests/suites/import/regression_test.py
+++ b/dirsrvtests/tests/suites/import/regression_test.py
@@ -687,7 +687,7 @@ def test_ldif2db_after_backend_create(topo, verify):
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) < 15
+ assert abs(import_time_1 - import_time_2) < 20
def test_ldif_missing_suffix_entry(topo, request, verify):
diff --git a/dirsrvtests/tests/suites/replication/wait_for_async_feature_test.py b/dirsrvtests/tests/suites/replication/wait_for_async_feature_test.py
index c5ab585e4..84ce1ca2b 100644
--- a/dirsrvtests/tests/suites/replication/wait_for_async_feature_test.py
+++ b/dirsrvtests/tests/suites/replication/wait_for_async_feature_test.py
@@ -26,7 +26,7 @@ log = logging.getLogger(__name__)
installation1_prefix = None
# Expected minimum and maximum number of async result in usual cases
-USUAL_MIN_AP = 3
+USUAL_MIN_AP = 2
USUAL_MAX_AP = 11
@pytest.fixture(params=[(None, (USUAL_MIN_AP, USUAL_MAX_AP)),
diff --git a/dirsrvtests/tests/suites/retrocl/basic_test.py b/dirsrvtests/tests/suites/retrocl/basic_test.py
index b53a60851..2fce72049 100644
--- a/dirsrvtests/tests/suites/retrocl/basic_test.py
+++ b/dirsrvtests/tests/suites/retrocl/basic_test.py
@@ -492,7 +492,7 @@ def test_retrocl_trimming_entries(topology_st):
if inst.searchErrorsLog("trim_changelog: removed "):
log.info(f'Trimming detected after {attempt * 6} seconds')
break
-
+
log.info('Verify trimming occurred by checking error log')
assert inst.searchErrorsLog("trim_changelog: removed ")
--
2.52.0

View File

@ -0,0 +1,56 @@
From 58f5d129496cc8b4271daf5d0cd3ab31e8b926a8 Mon Sep 17 00:00:00 2001
From: Akshay Adhikari <aadhikar@redhat.com>
Date: Thu, 12 Feb 2026 12:47:23 +0530
Subject: [PATCH] Issue 7233 - test_produce_division_by_zero fails with
IsADirectoryError in conftest.py (#7234)
Description: glob('/*/*') matches directories causing open() to fail.
Fixes: #7233
Reviewed by: @droideck (Thanks!)
---
dirsrvtests/conftest.py | 2 ++
.../suites/disk_monitoring/disk_monitoring_divide_test.py | 7 ++++---
2 files changed, 6 insertions(+), 3 deletions(-)
diff --git a/dirsrvtests/conftest.py b/dirsrvtests/conftest.py
index 0db6045f4..19b34c4a4 100644
--- a/dirsrvtests/conftest.py
+++ b/dirsrvtests/conftest.py
@@ -126,6 +126,8 @@ def pytest_runtest_makereport(item, call):
text = asan_report.read()
extra.append(pytest_html.extras.text(text, name=os.path.basename(f)))
for f in glob.glob(f'{p.log_dir.split("/slapd",1)[0]}/*/*'):
+ if not os.path.isfile(f):
+ continue
if f.endswith('gz'):
with gzip.open(f, 'rb') as dirsrv_log:
text = dirsrv_log.read()
diff --git a/dirsrvtests/tests/suites/disk_monitoring/disk_monitoring_divide_test.py b/dirsrvtests/tests/suites/disk_monitoring/disk_monitoring_divide_test.py
index 9d952f93a..3e52e7c6b 100644
--- a/dirsrvtests/tests/suites/disk_monitoring/disk_monitoring_divide_test.py
+++ b/dirsrvtests/tests/suites/disk_monitoring/disk_monitoring_divide_test.py
@@ -31,15 +31,16 @@ def create_dummy_mount(topology_st, request):
log.info('Create dummy mount')
for cmd in cmds:
log.info('Command used : %s' % cmd)
- subprocess.Popen(cmd, shell=True)
+ subprocess.run(cmd, shell=True)
def fin():
cmds = ['umount /var/log/dirsrv/slapd-{}/tmp'.format(topology_st.standalone.serverid),
+ 'rmdir /var/log/dirsrv/slapd-{}/tmp'.format(topology_st.standalone.serverid),
'setenforce 1']
for cmd in cmds:
- log.info('Command used : %s' % cmds)
- subprocess.Popen(cmd, shell=True)
+ log.info('Command used : %s' % cmd)
+ subprocess.run(cmd, shell=True)
request.addfinalizer(fin)
--
2.52.0

View File

@ -0,0 +1,226 @@
From bbda49b86f3841ac5100894da426edc541b6226c Mon Sep 17 00:00:00 2001
From: Viktor Ashirov <vashirov@redhat.com>
Date: Thu, 12 Feb 2026 09:15:18 +0100
Subject: [PATCH] Issue 7241 - Drop dateutil (#7242)
Bug Description:
python-dateutil is unmaintained upstream and is marked for deprecation.
Fix Description:
* Replace `dateutil.tz.tzoffset` with `datetime.timezone(datetime.timedelta())`.
* Replace `dateutil.parser.parse` with standard `datetime` calls.
* Import `datetime` as `dt` to avoid confusion between module and class.
* Fix month lookup bug ('Oct': 9 / 'Sep': 10).
Fixes: https://github.com/389ds/389-ds-base/issues/7241
Reviewed by: jchapma, droideck (Thanks!)
---
.../suites/password/pwdPolicy_warning_test.py | 5 +-
src/lib389/lib389/dirsrv_log.py | 48 ++++++++++++-------
src/lib389/lib389/tests/dirsrv_log_test.py | 13 +++--
src/lib389/pyproject.toml | 2 -
src/lib389/requirements.txt | 1 -
5 files changed, 38 insertions(+), 31 deletions(-)
diff --git a/dirsrvtests/tests/suites/password/pwdPolicy_warning_test.py b/dirsrvtests/tests/suites/password/pwdPolicy_warning_test.py
index 154ec01f1..2341da6eb 100644
--- a/dirsrvtests/tests/suites/password/pwdPolicy_warning_test.py
+++ b/dirsrvtests/tests/suites/password/pwdPolicy_warning_test.py
@@ -15,9 +15,8 @@ from lib389.topologies import topology_st
from lib389.idm.user import UserAccounts
from lib389.idm.organizationalunit import OrganizationalUnits
from lib389._constants import (DEFAULT_SUFFIX, DN_CONFIG, PASSWORD, DN_DM)
-from dateutil.parser import parse as dt_parse
from lib389.config import Config
-import datetime
+import datetime as dt
pytestmark = pytest.mark.tier1
@@ -351,7 +350,7 @@ def test_with_different_password_states(topology_st, global_policy, add_user):
old_ts = user.get_attr_val_utf8('passwordExpirationTime')
log.info("Old passwordExpirationTime: {}".format(old_ts))
- new_ts = (dt_parse(old_ts) - datetime.timedelta(31)).strftime('%Y%m%d%H%M%SZ')
+ new_ts = (dt.datetime.strptime(old_ts, '%Y%m%d%H%M%SZ') - dt.timedelta(31)).strftime('%Y%m%d%H%M%SZ')
log.info("New passwordExpirationTime: {}".format(new_ts))
user.replace('passwordExpirationTime', new_ts)
diff --git a/src/lib389/lib389/dirsrv_log.py b/src/lib389/lib389/dirsrv_log.py
index e40105ad3..3d923a8bf 100644
--- a/src/lib389/lib389/dirsrv_log.py
+++ b/src/lib389/lib389/dirsrv_log.py
@@ -10,11 +10,11 @@
"""
import copy
+import datetime as dt
import json
import glob
import re
import gzip
-from dateutil.parser import parse as dt_parse
from lib389.utils import ensure_bytes
from lib389._mapped_object_lint import DSLint
from lib389.lint import (
@@ -34,8 +34,8 @@ MONTH_LOOKUP = {
'Jun': 6,
'Jul': 7,
'Aug': 8,
- 'Oct': 9,
- 'Sep': 10,
+ 'Sep': 9,
+ 'Oct': 10,
'Nov': 11,
'Dec': 12,
}
@@ -50,9 +50,9 @@ class DirsrvLog(DSLint):
"""
self.dirsrv = dirsrv
self.log = self.dirsrv.log
- self.prog_timestamp = re.compile(r'\[(?P<day>\d*)\/(?P<month>\w*)\/(?P<year>\d*):(?P<hour>\d*):(?P<minute>\d*):(?P<second>\d*)(.(?P<nanosecond>\d*))+\s(?P<tz>[\+\-]\d*)') # noqa
+ self.prog_timestamp = re.compile(r'\[(?P<day>\d*)\/(?P<month>\w*)\/(?P<year>\d*):(?P<hour>\d*):(?P<minute>\d*):(?P<second>\d*)(.(?P<nanosecond>\d*))+\s(?P<tz>[\+\-]\d{4})') # noqa
# JSON timestamp uses strftime %FT%T --> 2025-02-12T17:00:47.663123181 -0500
- self.prog_json_timestamp = re.compile(r'(?P<year>\d*)-(?P<month>\w*)-(?P<day>\d*)T(?P<hour>\d*):(?P<minute>\d*):(?P<second>\d*)(.(?P<nanosecond>\d*))+\s(?P<tz>[\+\-]\d*)') # noqa
+ self.prog_json_timestamp = re.compile(r'(?P<year>\d*)-(?P<month>\w*)-(?P<day>\d*)T(?P<hour>\d*):(?P<minute>\d*):(?P<second>\d*)(.(?P<nanosecond>\d*))+\s(?P<tz>[\+\-]\d{4})') # noqa
self.prog_datetime = re.compile(r'^(?P<timestamp>\[.*\])')
self.jsonFormat = False
@@ -157,20 +157,32 @@ class DirsrvLog(DSLint):
else:
timedata = self.prog_timestamp.match(ts).groupdict()
- # Now, have to convert month to an int.
- dt_str = '{YEAR}-{MONTH}-{DAY} {HOUR}-{MINUTE}-{SECOND} {TZ}'.format(
- YEAR=timedata['year'],
- MONTH=timedata['month'],
- DAY=timedata['day'],
- HOUR=timedata['hour'],
- MINUTE=timedata['minute'],
- SECOND=timedata['second'],
- TZ=timedata['tz'],
- )
- dt = dt_parse(dt_str)
+ # Convert month to an int.
+ month = timedata['month']
+ if not month.isdigit():
+ month = MONTH_LOOKUP[month]
+ else:
+ month = int(month)
+
+ # Parse timezone offset string (e.g. "+1000" or "-0500") into a timezone
+ tz_str = timedata['tz']
+ tz_sign = 1 if tz_str[0] == '+' else -1
+ tz_hours = int(tz_str[1:3])
+ tz_minutes = int(tz_str[3:5])
+ tz = dt.timezone(dt.timedelta(hours=tz_sign * tz_hours, minutes=tz_sign * tz_minutes))
+
+ parsed_dt = dt.datetime(
+ int(timedata['year']),
+ month,
+ int(timedata['day']),
+ int(timedata['hour']),
+ int(timedata['minute']),
+ int(timedata['second']),
+ tzinfo=tz
+ )
if timedata['nanosecond']:
- dt = dt.replace(microsecond=int(int(timedata['nanosecond']) / 1000))
- return dt
+ parsed_dt = parsed_dt.replace(microsecond=int(timedata['nanosecond']) // 1000)
+ return parsed_dt
def get_time_in_secs(self, log_line):
"""Take the timestamp (not the date) from a DS access log and convert
diff --git a/src/lib389/lib389/tests/dirsrv_log_test.py b/src/lib389/lib389/tests/dirsrv_log_test.py
index 920e67a01..d0259ced9 100644
--- a/src/lib389/lib389/tests/dirsrv_log_test.py
+++ b/src/lib389/lib389/tests/dirsrv_log_test.py
@@ -12,8 +12,7 @@ from lib389 import DirSrv, Entry
import pytest
import time
import shutil
-import datetime
-from dateutil.tz import tzoffset
+import datetime as dt
INSTANCE_PORT = 54321
INSTANCE_SERVERID = 'standalone'
@@ -74,7 +73,7 @@ def test_access_log(topology):
topology.standalone.ds_access_log.parse_line('[27/Apr/2016:12:49:49.726093186 +1000] conn=1 fd=64 slot=64 connection from ::1 to ::1') ==
{
'slot': '64', 'remote': '::1', 'action': 'CONNECT', 'timestamp': '[27/Apr/2016:12:49:49.726093186 +1000]', 'fd': '64', 'conn': '1', 'local': '::1',
- 'datetime': datetime.datetime(2016, 4, 27, 12, 0, 0, 726093, tzinfo=tzoffset(None, 36000))
+ 'datetime': dt.datetime(2016, 4, 27, 12, 49, 49, 726093, tzinfo=dt.timezone(dt.timedelta(seconds=36000)))
}
)
assert(
@@ -82,21 +81,21 @@ def test_access_log(topology):
{
'rem': 'base="cn=config" scope=0 filter="(objectClass=*)" attrs="nsslapd-instancedir nsslapd-errorlog nsslapd-accesslog nsslapd-auditlog nsslapd-certdir nsslapd-schemadir nsslapd-bakdir nsslapd-ldifdir"', # noqa
'action': 'SRCH', 'timestamp': '[27/Apr/2016:12:49:49.727235997 +1000]', 'conn': '1', 'op': '2',
- 'datetime': datetime.datetime(2016, 4, 27, 12, 0, 0, 727235, tzinfo=tzoffset(None, 36000))
+ 'datetime': dt.datetime(2016, 4, 27, 12, 49, 49, 727235, tzinfo=dt.timezone(dt.timedelta(seconds=36000)))
}
)
assert(
topology.standalone.ds_access_log.parse_line('[27/Apr/2016:12:49:49.736297002 +1000] conn=1 op=4 fd=64 closed - U1') ==
{
'status': 'U1', 'fd': '64', 'action': 'DISCONNECT', 'timestamp': '[27/Apr/2016:12:49:49.736297002 +1000]', 'conn': '1', 'op': '4',
- 'datetime': datetime.datetime(2016, 4, 27, 12, 0, 0, 736297, tzinfo=tzoffset(None, 36000))
+ 'datetime': dt.datetime(2016, 4, 27, 12, 49, 49, 736297, tzinfo=dt.timezone(dt.timedelta(seconds=36000)))
}
)
assert(
topology.standalone.ds_access_log.parse_line('[27/Apr/2016:12:49:49.736297002 -1000] conn=1 op=4 fd=64 closed - U1') ==
{
'status': 'U1', 'fd': '64', 'action': 'DISCONNECT', 'timestamp': '[27/Apr/2016:12:49:49.736297002 -1000]', 'conn': '1', 'op': '4',
- 'datetime': datetime.datetime(2016, 4, 27, 12, 0, 0, 736297, tzinfo=tzoffset(None, -36000))
+ 'datetime': dt.datetime(2016, 4, 27, 12, 49, 49, 736297, tzinfo=dt.timezone(dt.timedelta(seconds=-36000)))
}
)
@@ -113,7 +112,7 @@ def test_error_log(topology):
topology.standalone.ds_error_log.parse_line('[27/Apr/2016:13:46:35.775670167 +1000] slapd started. Listening on All Interfaces port 54321 for LDAP requests') == # noqa
{
'timestamp': '[27/Apr/2016:13:46:35.775670167 +1000]', 'message': 'slapd started. Listening on All Interfaces port 54321 for LDAP requests',
- 'datetime': datetime.datetime(2016, 4, 27, 13, 0, 0, 775670, tzinfo=tzoffset(None, 36000))
+ 'datetime': dt.datetime(2016, 4, 27, 13, 46, 35, 775670, tzinfo=dt.timezone(dt.timedelta(seconds=36000)))
}
)
diff --git a/src/lib389/pyproject.toml b/src/lib389/pyproject.toml
index 63c7c9710..e067d1590 100644
--- a/src/lib389/pyproject.toml
+++ b/src/lib389/pyproject.toml
@@ -5,7 +5,6 @@ requires = [
"argparse-manpage[setuptools]",
"pyasn1",
"pyasn1-modules",
- "python-dateutil",
"argcomplete",
"python-ldap",
"distro",
@@ -43,7 +42,6 @@ classifiers = [
dependencies = [
"pyasn1",
"pyasn1-modules",
- "python-dateutil",
"argcomplete",
"python-ldap",
"distro",
diff --git a/src/lib389/requirements.txt b/src/lib389/requirements.txt
index 5e1b3dad7..94b10e3c2 100644
--- a/src/lib389/requirements.txt
+++ b/src/lib389/requirements.txt
@@ -1,6 +1,5 @@
pyasn1
pyasn1-modules
-python-dateutil
argcomplete
argparse-manpage
python-ldap
--
2.52.0

View File

@ -0,0 +1,94 @@
From d19c50372d5c5d901f05ce6e7dd03313f41fc197 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 | 14 +++++++++++---
1 file changed, 11 insertions(+), 3 deletions(-)
diff --git a/dirsrvtests/tests/suites/syncrepl_plugin/basic_test.py b/dirsrvtests/tests/suites/syncrepl_plugin/basic_test.py
index 85b4ac078..d0e7e8a32 100644
--- a/dirsrvtests/tests/suites/syncrepl_plugin/basic_test.py
+++ b/dirsrvtests/tests/suites/syncrepl_plugin/basic_test.py
@@ -21,7 +21,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,10 +214,13 @@ class Sync_persist(threading.Thread, ReconnectLDAPObject, SyncreplConsumer):
def run(self):
"""Start a sync repl client"""
- ldap.set_option(ldap.OPT_X_TLS_CACERTFILE, os.path.join(self.inst.get_config_dir(), "ca.crt"))
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",
@@ -257,6 +260,7 @@ def test_sync_repl_mep(topology, request):
5. Success
"""
inst = topology[0]
+ inst.enable_tls()
# Enable/configure retroCL
plugin = RetroChangelogPlugin(inst)
@@ -342,6 +346,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)
@@ -408,6 +413,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()
@@ -551,6 +558,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()
@@ -609,7 +617,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

View File

@ -0,0 +1,33 @@
From 1df3852cf0e073cfe006d661aecdd909862fc79a 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

View File

@ -0,0 +1,201 @@
From 63bf648f699b8fcd8f319254b0348d969ceea7a0 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 c7275e4cb..ad6d05a1e 100644
--- a/dirsrvtests/tests/suites/import/import_test.py
+++ b/dirsrvtests/tests/suites/import/import_test.py
@@ -514,7 +514,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 d57a91929..01b3ea23c 100644
--- a/src/lib389/lib389/__init__.py
+++ b/src/lib389/lib389/__init__.py
@@ -2953,7 +2953,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
@@ -2966,34 +2966,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

View File

@ -0,0 +1,74 @@
From d52901f69e9b7952b33b219ec197308a1a20bda9 Mon Sep 17 00:00:00 2001
From: progier389 <progier@redhat.com>
Date: Fri, 13 Feb 2026 15:13:05 +0100
Subject: [PATCH] Issue 7252 - PQC - Need to iterate on SECOidTag instead of
using OID (#7254)
* Issue 7252 - PQC - Need to iterate on SECOidTag instead of using OID
Need to dynamically iterate on SECOidTag instead of using SEC_OID_ML_DSA_* OIDs to avoid issue with upcoming nss versions and fix a RHEL build break with nss 3.112
Issue: #7252
Reviewed by: @mreynolds389, @droideck, @vashirov
* Update ldap/servers/slapd/ssl.c
Co-authored-by: Simon Pichugin <spichugi@redhat.com>
---------
Co-authored-by: Simon Pichugin <spichugi@redhat.com>
---
ldap/servers/slapd/ssl.c | 24 +++++++++---------------
1 file changed, 9 insertions(+), 15 deletions(-)
diff --git a/ldap/servers/slapd/ssl.c b/ldap/servers/slapd/ssl.c
index 7d5db2cdd..d05c64fb1 100644
--- a/ldap/servers/slapd/ssl.c
+++ b/ldap/servers/slapd/ssl.c
@@ -732,31 +732,25 @@ SSLPLCY_Install(void)
{
SECStatus s = 0;
-#ifdef MAX_ML_DSA_PRIVATE_KEY_LEN
int flags = NSS_USE_ALG_IN_SIGNATURE | NSS_USE_ALG_IN_SSL;
- static const SECOidTag oids[] = {
- SEC_OID_ML_DSA_44,
- SEC_OID_ML_DSA_65,
- SEC_OID_ML_DSA_87,
- };
-#endif
+ SECOidData *oid = NULL;
s = NSS_SetDomesticPolicy();
-#ifdef MAX_ML_DSA_PRIVATE_KEY_LEN
/* Should rely on the crypto module policy in FIPS mode */
if (!slapd_pk11_isFIPS()) {
/* Set explicitly PQC algorithm policy if it is not set by default */
- for (size_t i=0; s == SECSuccess && i < PR_ARRAY_SIZE(oids); i++) {
- PRUint32 oflags = 0;
- (void) NSS_GetAlgorithmPolicy(oids[i], &oflags);
- if ((oflags & flags) != flags) {
- s = NSS_SetAlgorithmPolicy(oids[i], flags, 0);
+ for (SECOidTag tag = 1; s == SECSuccess && (oid = SECOID_FindOIDByTag(tag)) != NULL; tag++) {
+ if (oid->mechanism != CKM_INVALID_MECHANISM &&
+ PL_strncasecmp(oid->desc, "ML-DSA-", 7) == 0) {
+ PRUint32 oflags = 0;
+ (void) NSS_GetAlgorithmPolicy(tag, &oflags);
+ if ((oflags & flags) != flags) {
+ s = NSS_SetAlgorithmPolicy(tag, flags, 0);
+ }
}
}
}
-#endif
-
return s ? PR_FAILURE : PR_SUCCESS;
}
--
2.52.0

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,41 @@
From 48ad61231203d9ccb96d0fe542aae93dbb74a9bf 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

View File

@ -0,0 +1,32 @@
From c7ef5b3073bbd94a5d2b544556368c830c165e0d 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 7cdf16a84..89a747199 100644
--- a/dirsrvtests/tests/suites/vlv/regression_test.py
+++ b/dirsrvtests/tests/suites/vlv/regression_test.py
@@ -1175,6 +1175,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

View File

@ -1,7 +1,7 @@
From 384dbcaba418afe9e4de798d16a68d0468632a57 Mon Sep 17 00:00:00 2001
From 7e575cc8cc6f1bf558f50ca0fc55145e469d60d2 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,24 +11,24 @@ 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 17bfc09a0..1569eb7ff 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)
* 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");
+ e = ldbm_instance_init_config_entry(LDBM_ANCESTORID_STR, "eq", 0, 0, 0, 0);
attr_index_config(be, "ldbm index init", 0, e, 1, 0, NULL);
slapi_entry_free(e);
}
@@ -231,7 +231,7 @@ 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");
+ e = ldbm_instance_init_config_entry(LDBM_ANCESTORID_STR, "eq", 0, 0, 0, 0);
attr_index_config(be, "ldbm index init", 0, e, 1, 0, NULL);
slapi_entry_free(e);
--
2.52.0

View File

@ -0,0 +1,38 @@
From 245bc3b53f385e12e4dc9d2cb765a55e10e0fdc5 Mon Sep 17 00:00:00 2001
From: progier389 <progier@redhat.com>
Date: Fri, 13 Feb 2026 17:51:12 +0100
Subject: [PATCH] Issue 3134 - Fix build break (#7260)
Fix build break of PR #7238 related to import rpm
Issue: #3134
Reviewed by: @vashirov (Thanks!)
---
src/lib389/lib389/utils.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/lib389/lib389/utils.py b/src/lib389/lib389/utils.py
index 07cb34d93..4df36cf1f 100644
--- a/src/lib389/lib389/utils.py
+++ b/src/lib389/lib389/utils.py
@@ -28,7 +28,6 @@ from datetime import (datetime, timedelta)
import sys
import filecmp
import pwd
-import rpm
import shlex
import operator
import subprocess
@@ -2135,6 +2134,8 @@ def get_timeout_scale():
def rpm_is_older(pkg, version):
"""Check if an RPM package version is older than specified version"""
+ # rpm module is not installed in build environment so let import it only when used.
+ import rpm
ts = rpm.TransactionSet()
mi = ts.dbMatch('name', pkg)
for h in mi:
--
2.52.0

View File

@ -0,0 +1,87 @@
From ac3d9253e0a7a4b5f0108506bcf25255b302fd16 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 | 7 ++++---
ldap/servers/slapd/modify.c | 2 +-
ldap/servers/slapd/pw.c | 13 ++++++++++++-
3 files changed, 17 insertions(+), 5 deletions(-)
diff --git a/dirsrvtests/tests/suites/password/pwp_history_test.py b/dirsrvtests/tests/suites/password/pwp_history_test.py
index cf68d743c..78b448a87 100644
--- a/dirsrvtests/tests/suites/password/pwp_history_test.py
+++ b/dirsrvtests/tests/suites/password/pwp_history_test.py
@@ -189,9 +189,9 @@ def test_history_is_not_overwritten(topology_st, user):
@pytest.mark.parametrize('policy',
- [(pytest.param('global', marks=pytest.mark.xfail(reason="DS7052"))),
- (pytest.param('subtree', marks=pytest.mark.xfail(reason="DS7066, DS7052"))),
- (pytest.param('user', marks=pytest.mark.xfail(reason="DS7066, DS7052")))])
+ [(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
@@ -282,6 +282,7 @@ def test_basic(topology_st, user, policy):
# Password history [password3, password4], current password is "password1"
# Reset password by Directory Manager(admin reset)
+ dm = DirectoryManager(topology_st.standalone)
dm.rebind()
time.sleep(.5)
change_password(user, 'password-reset', success=True)
diff --git a/ldap/servers/slapd/modify.c b/ldap/servers/slapd/modify.c
index 9e5bce80b..0ecce9bc8 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 055ec0d74..c53ecf23d 100644
--- a/ldap/servers/slapd/pw.c
+++ b/ldap/servers/slapd/pw.c
@@ -1535,7 +1535,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

View File

@ -0,0 +1,32 @@
From b216b86c5607dc0421eb609c46f3004844fb37c0 Mon Sep 17 00:00:00 2001
From: Akshay Adhikari <aadhikar@redhat.com>
Date: Tue, 17 Feb 2026 17:40:44 +0530
Subject: [PATCH] Issue 6758 - Fix Enable Replication dropdown not opening
(#7262)
Description: Removed hardcoded isOpen={false} and empty onToggle handler that
prevented dropdown from opening. Let component manage its own state.
Relates: #6758
Reviewed by: @vashirov
---
src/cockpit/389-console/src/lib/replication/replModals.jsx | 2 --
1 file changed, 2 deletions(-)
diff --git a/src/cockpit/389-console/src/lib/replication/replModals.jsx b/src/cockpit/389-console/src/lib/replication/replModals.jsx
index ba4859617..83a8b75a5 100644
--- a/src/cockpit/389-console/src/lib/replication/replModals.jsx
+++ b/src/cockpit/389-console/src/lib/replication/replModals.jsx
@@ -1757,8 +1757,6 @@ export class EnableReplModal extends React.Component {
handleChange(syntheticEvent);
}}
options={[_("Supplier"), _("Hub"), _("Consumer")]}
- isOpen={false}
- onToggle={() => {}}
placeholder={_("Select role...")}
ariaLabel="Replication role selection"
isMulti={false}
--
2.52.0

View File

@ -1,4 +1,4 @@
From 67c3183380888f4af093b546e717f3e7451a41d6 Mon Sep 17 00:00:00 2001
From 6ce19a9a3e36213a5604144aa5eb3cba666e5ed4 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,14 +19,14 @@ 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 dd42cd197..8dc82c779 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):
@ -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):
@@ -210,17 +206,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):
@@ -910,7 +903,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):
@@ -918,18 +913,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):
@@ -963,34 +954,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):
@@ -1080,7 +1057,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):
@@ -1122,14 +1099,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):
@@ -1160,16 +1129,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 6b1b012da..9557e9066 100644
--- a/ldap/servers/slapd/upgrade.c
+++ b/ldap/servers/slapd/upgrade.c
@@ -243,108 +243,6 @@ upgrade_remove_ancestorid_index_config(void)
@@ -551,107 +551,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)
/*
* Upgrade the base config of the PAM PTA plugin.
@@ -879,10 +778,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 0e0e28285..370e3abd4 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
@@ -650,9 +650,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 f3dbe7c92..6c8cbc018 100644
--- a/src/lib389/lib389/backend.py
+++ b/src/lib389/lib389/backend.py
@@ -541,9 +541,10 @@ class Backend(DSLdapObject):
@@ -647,9 +647,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,

View File

@ -0,0 +1,227 @@
From 26feecae026581e39a43f001faff59e81a92c03d Mon Sep 17 00:00:00 2001
From: Lenka Doudova <mirielka@users.noreply.github.com>
Date: Wed, 18 Feb 2026 14:33:49 +0100
Subject: [PATCH] Issue 7236 - Fix GSSAPI tests (#7237)
* Issue 7236 - Fix GSSAPI tests
Description:
Fix for failing GSSAPI tests
Add GSSAPI_ACK variable to pytest workflow for proper execution in
Github CI
Relates: #7236
Author: Lenka Doudova
Reviewer: Barbora Simonova, Viktor Ashirov
---
.github/workflows/lmdbpytest.yml | 2 +-
.github/workflows/pytest.yml | 2 +-
.../tests/suites/gssapi/simple_gssapi_test.py | 2 +
.../suites/gssapi_repl/gssapi_repl_test.py | 43 +++++---------
src/lib389/lib389/topologies.py | 57 +++++++++++++++++++
5 files changed, 76 insertions(+), 30 deletions(-)
diff --git a/.github/workflows/lmdbpytest.yml b/.github/workflows/lmdbpytest.yml
index 2d0a122bf..376090bf6 100644
--- a/.github/workflows/lmdbpytest.yml
+++ b/.github/workflows/lmdbpytest.yml
@@ -120,7 +120,7 @@ jobs:
sudo docker exec $CID sh -c "systemctl enable --now cockpit.socket"
sudo docker exec $CID sh -c "mkdir -p /workspace/assets/cores && chmod 777 /workspace{,/assets{,/cores}}"
sudo docker exec $CID sh -c "echo '/workspace/assets/cores/core.%e.%P' > /proc/sys/kernel/core_pattern"
- sudo docker exec -e WEBUI=1 -e NSSLAPD_DB_LIB=mdb -e DEBUG=pw:api -e PASSWD="${PASSWD}" $CID py.test --suppress-no-test-exit-code -m "not flaky" --junit-xml=pytest.xml --html=pytest.html --browser=firefox --browser=chromium -v dirsrvtests/tests/suites/${{ matrix.suite }}
+ sudo docker exec -e WEBUI=1 -e NSSLAPD_DB_LIB=mdb -e DEBUG=pw:api -e PASSWD="${PASSWD}" -e GSSAPI_ACK=1 $CID py.test --suppress-no-test-exit-code -m "not flaky" --junit-xml=pytest.xml --html=pytest.html --browser=firefox --browser=chromium -v dirsrvtests/tests/suites/${{ matrix.suite }}
- name: Make the results file readable by all
if: always()
diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml
index 8a543be85..a51553656 100644
--- a/.github/workflows/pytest.yml
+++ b/.github/workflows/pytest.yml
@@ -125,7 +125,7 @@ jobs:
echo "Tests skipped because read-only Berkeley Database is installed." > pytest.html
echo "<?xml version="1.0" encoding="utf-8"?>'Tests skipped because read-only Berkeley Database is installed.'" > pytest.xml
else
- sudo docker exec -e WEBUI=1 -e NSSLAPD_DB_LIB=bdb -e DEBUG=pw:api -e PASSWD="${PASSWD}" $CID py.test --suppress-no-test-exit-code -m "not flaky" --junit-xml=pytest.xml --html=pytest.html --browser=firefox --browser=chromium -v dirsrvtests/tests/suites/${{ matrix.suite }}
+ sudo docker exec -e WEBUI=1 -e NSSLAPD_DB_LIB=bdb -e DEBUG=pw:api -e PASSWD="${PASSWD}" -e GSSAPI_ACK=1 $CID py.test --suppress-no-test-exit-code -m "not flaky" --junit-xml=pytest.xml --html=pytest.html --browser=firefox --browser=chromium -v dirsrvtests/tests/suites/${{ matrix.suite }}
fi
- name: Make the results file readable by all
diff --git a/dirsrvtests/tests/suites/gssapi/simple_gssapi_test.py b/dirsrvtests/tests/suites/gssapi/simple_gssapi_test.py
index be6f68a9a..e48de3491 100644
--- a/dirsrvtests/tests/suites/gssapi/simple_gssapi_test.py
+++ b/dirsrvtests/tests/suites/gssapi/simple_gssapi_test.py
@@ -34,6 +34,8 @@ def testuser(topology_st_gssapi):
})
# Give them a krb princ
user.create_keytab()
+ # Make krb5 config readable by everyone for the tests to work
+ os.chmod(user._instance.realm.krb5confrealm, 0o644)
return user
@gssapi_ack
diff --git a/dirsrvtests/tests/suites/gssapi_repl/gssapi_repl_test.py b/dirsrvtests/tests/suites/gssapi_repl/gssapi_repl_test.py
index 402684aab..fa7fc9c24 100644
--- a/dirsrvtests/tests/suites/gssapi_repl/gssapi_repl_test.py
+++ b/dirsrvtests/tests/suites/gssapi_repl/gssapi_repl_test.py
@@ -10,7 +10,7 @@ import pytest
from lib389.tasks import *
from lib389.utils import *
from lib389.agreement import *
-from lib389.topologies import topology_m2
+from lib389.topologies import topology_m2_gssapi, gssapi_ack
pytestmark = pytest.mark.tier2
@@ -69,25 +69,8 @@ def _allow_machine_account(inst, name):
(ldap.MOD_REPLACE, 'nsDS5ReplicaBindDN', f"uid={name},ou=Machines,{DEFAULT_SUFFIX}".encode('utf-8'))
])
-def _verify_etc_hosts():
- #Check if /etc/hosts is compatible with the test
- NEEDED_HOSTS = ( ('ldapkdc.example.com', '127.0.0.1'),
- ('ldapkdc1.example.com', '127.0.1.1'),
- ('ldapkdc2.example.com', '127.0.2.1'))
- found_hosts = {}
- with open('/etc/hosts','r') as f:
- for l in f:
- s = l.split()
- if len(s) < 2:
- continue
- for nh in NEEDED_HOSTS:
- if (s[0] == nh[1] and s[1] == nh[0]):
- found_hosts[s[1]] = True
- return len(found_hosts) == len(NEEDED_HOSTS)
-
-@pytest.mark.skipif(not _verify_etc_hosts(), reason="/etc/hosts does not contains the needed hosts.")
-@pytest.mark.skipif(True, reason="Test disabled because it requires specific kerberos requirement (server principal, keytab, etc ...")
-def test_gssapi_repl(topology_m2):
+@gssapi_ack
+def test_gssapi_repl(topology_m2_gssapi):
"""Test gssapi authenticated replication agreement of two suppliers using KDC
:id: 552850aa-afc3-473e-9c39-aae802b46f11
@@ -112,8 +95,8 @@ def test_gssapi_repl(topology_m2):
6. Test User should be created on M1 and M2 both
7. Test User should be created on M1 and M2 both
"""
- supplier1 = topology_m2.ms["supplier1"]
- supplier2 = topology_m2.ms["supplier2"]
+ supplier1 = topology_m2_gssapi.ms["supplier1"]
+ supplier2 = topology_m2_gssapi.ms["supplier2"]
# Create the locations on each supplier for the other to bind to.
_create_machine_ou(supplier1)
@@ -134,10 +117,9 @@ def test_gssapi_repl(topology_m2):
# Creating agreement from supplier 1 to supplier 2
# Set the replica bind method to sasl gssapi
- properties = {RA_NAME: r'meTo_$host:$port',
+ properties = {RA_NAME: 'meTo_' + supplier2.host + ':' + str(supplier2.port),
RA_METHOD: 'SASL/GSSAPI',
RA_TRANSPORT_PROT: defaultProperties[REPLICATION_TRANSPORT]}
- supplier1.agreement.delete(suffix=SUFFIX, consumer_host=supplier2.host, consumer_port=supplier2.port)
m1_m2_agmt = supplier1.agreement.create(suffix=SUFFIX, host=supplier2.host, port=supplier2.port, properties=properties)
if not m1_m2_agmt:
log.fatal("Fail to create a supplier -> supplier replica agreement")
@@ -147,10 +129,9 @@ def test_gssapi_repl(topology_m2):
# Creating agreement from supplier 2 to supplier 1
# Set the replica bind method to sasl gssapi
- properties = {RA_NAME: r'meTo_$host:$port',
+ properties = {RA_NAME: 'meTo_' + supplier1.host + ':' + str(supplier1.port),
RA_METHOD: 'SASL/GSSAPI',
RA_TRANSPORT_PROT: defaultProperties[REPLICATION_TRANSPORT]}
- supplier2.agreement.delete(suffix=SUFFIX, consumer_host=supplier1.host, consumer_port=supplier1.port)
m2_m1_agmt = supplier2.agreement.create(suffix=SUFFIX, host=supplier1.host, port=supplier1.port, properties=properties)
if not m2_m1_agmt:
log.fatal("Fail to create a supplier -> supplier replica agreement")
@@ -169,9 +150,15 @@ def test_gssapi_repl(topology_m2):
# Check replication is working...
if supplier1.testReplication(DEFAULT_SUFFIX, supplier2):
- log.info('Replication is working.')
+ log.info('Replication is working: supplier1 -> supplier2')
else:
- log.fatal('Replication is not working.')
+ log.fatal('Replication is not working: supplier1 -> supplier2')
+ assert False
+
+ if supplier2.testReplication(DEFAULT_SUFFIX, supplier1):
+ log.info('Replication is working: supplier2 -> supplier1')
+ else:
+ log.fatal('Replication is not working: supplier2 -> supplier1')
assert False
# Add a user to supplier 1
diff --git a/src/lib389/lib389/topologies.py b/src/lib389/lib389/topologies.py
index 33341f669..84e620cb3 100644
--- a/src/lib389/lib389/topologies.py
+++ b/src/lib389/lib389/topologies.py
@@ -499,6 +499,63 @@ def topology_m2(request):
topology.logcap = LogCapture()
return topology
+@pytest.fixture(scope="module")
+def topology_m2_gssapi(request):
+ """Create Replication Deployment with two suppliers with GSSAPI enabled.
+
+ Similar to topology_st_gssapi but for two suppliers. Configures Kerberos
+ realm, principals and keytabs for ldap/ldapkdc1.<domain> and ldap/ldapkdc2.<domain>,
+ SASL mappings, and disables SSL port on both instances so GSSAPI can be used.
+ """
+ hostname = socket.gethostname().split('.', 1)
+ assert len(hostname) == 2
+ domain = hostname[1]
+ REALM = domain.upper()
+ host_supplier_1 = 'ldapkdc1.' + domain
+ host_supplier_2 = 'ldapkdc2.' + domain
+
+ topology = create_topology({ReplicaRole.SUPPLIER: 2}, request=request,
+ cleanup_cb=lambda x: krb.destroy_realm())
+
+ supplier1 = topology.ms["supplier1"]
+ supplier2 = topology.ms["supplier2"]
+ supplier1.host = host_supplier_1
+ supplier2.host = host_supplier_2
+
+ krb = MitKrb5(realm=REALM, debug=DEBUGGING)
+ if krb.check_realm():
+ krb.destroy_realm()
+ krb.create_realm()
+
+ krb.create_principal(principal=f'ldap/{host_supplier_1}')
+ krb.create_principal(principal=f'ldap/{host_supplier_2}')
+ krb.create_keytab(principal=f'ldap/{host_supplier_1}', keytab='/etc/krb5.keytab')
+ krb.create_keytab(principal=f'ldap/{host_supplier_2}', keytab='/etc/krb5.keytab')
+
+ os.chown('/etc/krb5.keytab', supplier1.get_user_uid(), supplier1.get_group_gid())
+
+ for inst, host in [(supplier1, host_supplier_1), (supplier2, host_supplier_2)]:
+ saslmappings = SaslMappings(inst)
+ for m in saslmappings.list():
+ m.delete()
+ saslmappings.create(properties={
+ 'cn': 'suffix map',
+ 'nsSaslMapRegexString': '\\(.*\\)',
+ 'nsSaslMapBaseDNTemplate': inst.creation_suffix,
+ 'nsSaslMapFilterTemplate': '(uid=\\1)'
+ })
+ inst.realm = krb
+ inst.config.set('nsslapd-localhost', host)
+ inst.sslport = None
+
+ supplier1.restart()
+ supplier2.restart()
+ supplier1.clearTmpDir(__file__)
+ supplier2.clearTmpDir(__file__)
+
+ topology.logcap = LogCapture()
+ return topology
+
@pytest.fixture(scope="module")
def topology_m3(request):
--
2.52.0

View File

@ -0,0 +1,260 @@
From a4ae29afc6547e8231b933cfa1b95d7f7b37a25c Mon Sep 17 00:00:00 2001
From: Lenka Doudova <lryznaro@redhat.com>
Date: Tue, 10 Feb 2026 05:45:32 +0100
Subject: [PATCH] Issue 6753 - Port ticket 49039 test
Description:
Port ticket 49039 test into
dirsrvtests/tests/suites/password/pwp_test.py
Relates: #6753
Author: Lenka Doudova
Assisted by: Cursor
Reviewer: Barbora Simonova, Viktor Ashirov
---
dirsrvtests/tests/suites/password/pwp_test.py | 83 +++++++++++-
dirsrvtests/tests/tickets/ticket49039_test.py | 127 ------------------
2 files changed, 82 insertions(+), 128 deletions(-)
delete mode 100644 dirsrvtests/tests/tickets/ticket49039_test.py
diff --git a/dirsrvtests/tests/suites/password/pwp_test.py b/dirsrvtests/tests/suites/password/pwp_test.py
index 663d9bea9..6dae08cb2 100644
--- a/dirsrvtests/tests/suites/password/pwp_test.py
+++ b/dirsrvtests/tests/suites/password/pwp_test.py
@@ -9,11 +9,14 @@
"""
import os
+import subprocess
import pytest
from lib389.topologies import topology_st as topo
from lib389.idm.user import UserAccounts, UserAccount
-from lib389._constants import DEFAULT_SUFFIX
+from lib389.idm.directorymanager import DirectoryManager
+from lib389._constants import DEFAULT_SUFFIX, PASSWORD
from lib389.config import Config
+from lib389.pwpolicy import PwPolicyManager
from lib389.idm.group import Group
from lib389.utils import ds_is_older
import ldap
@@ -512,6 +515,84 @@ def test_passwordlockout(topo, _fix_password):
_change_password_with_own(topo, user.dn, 'dby3rs2', 'secreter')
+def test_password_must_change_ignores_min_age(topo):
+ """Test that passwordMinAge does not block password update when the password was reset.
+
+ :id: a1b2c3d4-e5f6-4903-9abc-def012345678
+ :setup: Standalone instance
+ :steps:
+ 1. Enable TLS (for ldappasswd StartTLS)
+ 2. Set global policy via PwPolicyManager: passwordMustChange, passwordExp,
+ passwordMaxAge, passwordMinAge (high), passwordChange
+ 3. Bind as Directory Manager
+ 4. Create user
+ 5. Reset user password as Directory Manager
+ 6. User binds and changes own password (must succeed; min age must not block)
+ 7. Rebind as Directory Manager, reset user password again
+ 8. Run ldappasswd as user (StartTLS) to change password to password2
+ 9. Bind as user with password2 to verify
+ 10. Cleanup: delete user
+ :expectedresults:
+ 1. TLS enabled
+ 2. Policy set successfully
+ 3. Bind succeeds
+ 4. User created
+ 5. Reset succeeds
+ 6. User password change succeeds (min age does not block after reset)
+ 7. Reset succeeds
+ 8. ldappasswd succeeds
+ 9. Bind succeeds
+ 10. User deleted
+ """
+
+ topo.standalone.enable_tls()
+
+ policy = PwPolicyManager(topo.standalone)
+ policy.set_global_policy(properties={'nsslapd-pwpolicy-local': 'on',
+ 'passwordMustChange': 'on',
+ 'passwordExp': 'on',
+ 'passwordMaxAge': '86400000',
+ 'passwordMinAge': '8640000',
+ 'passwordChange': 'on'})
+ dm = DirectoryManager(topo.standalone)
+ dm.bind()
+
+ user = _create_user(topo, 'user', 'Test User', '1002', PASSWORD)
+ try:
+ # Reset password as Directory Manager
+ user.replace('userpassword', PASSWORD)
+ time.sleep(1)
+
+ # Reset password as user (must succeed; min age must not block after reset)
+ user.rebind(PASSWORD)
+ user.replace('userpassword', PASSWORD)
+ time.sleep(1)
+
+ # Reset again as Directory Manager
+ dm.rebind(PASSWORD)
+ user.replace('userpassword', PASSWORD)
+ time.sleep(1)
+
+ # Change password through ldappasswd as user to ensure functionality
+ env = os.environ.copy()
+ env['LDAPTLS_CACERTDIR'] = topo.standalone.get_cert_dir()
+ cmd = [
+ 'ldappasswd',
+ '-ZZ','-H', f"ldap://{topo.standalone.host}:{topo.standalone.port}",
+ '-D', user.dn, '-w', PASSWORD,
+ '-a', PASSWORD, '-s', 'password2',
+ user.dn,
+ ]
+ result = subprocess.run(cmd, env=env, capture_output=True, text=True)
+ assert result.returncode == 0, f'ldappasswd failed: {result.stderr}'
+
+ # Bind as user with new password
+ user.bind('password2')
+ finally:
+ dm.rebind(PASSWORD)
+ user.delete()
+
+
if __name__ == "__main__":
CURRENT_FILE = os.path.realpath(__file__)
pytest.main("-s -v %s" % CURRENT_FILE)
diff --git a/dirsrvtests/tests/tickets/ticket49039_test.py b/dirsrvtests/tests/tickets/ticket49039_test.py
deleted file mode 100644
index 0313f69a3..000000000
--- a/dirsrvtests/tests/tickets/ticket49039_test.py
+++ /dev/null
@@ -1,127 +0,0 @@
-# --- BEGIN COPYRIGHT BLOCK ---
-# Copyright (C) 2022 Red Hat, Inc.
-# All rights reserved.
-#
-# License: GPL (version 3 or any later version).
-# See LICENSE for details.
-# --- END COPYRIGHT BLOCK ---
-#
-import time
-import ldap
-import logging
-import pytest
-import os
-from lib389 import Entry
-from lib389._constants import *
-from lib389.properties import *
-from lib389.tasks import *
-from lib389.utils import *
-from lib389.topologies import topology_st as topo
-from lib389.pwpolicy import PwPolicyManager
-
-
-pytestmark = pytest.mark.tier2
-
-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__)
-
-USER_DN = 'uid=user,dc=example,dc=com'
-
-
-def test_ticket49039(topo):
- """Test "password must change" verses "password min age". Min age should not
- block password update if the password was reset.
- """
-
- # Setup SSL (for ldappasswd test)
- topo.standalone.enable_tls()
-
- # Configure password policy
- try:
- policy = PwPolicyManager(topo.standalone)
- policy.set_global_policy(properties={'nsslapd-pwpolicy-local': 'on',
- 'passwordMustChange': 'on',
- 'passwordExp': 'on',
- 'passwordMaxAge': '86400000',
- 'passwordMinAge': '8640000',
- 'passwordChange': 'on'})
- except ldap.LDAPError as e:
- log.fatal('Failed to set password policy: ' + str(e))
-
- # Add user, bind, and set password
- try:
- topo.standalone.add_s(Entry((USER_DN, {
- 'objectclass': 'top extensibleObject'.split(),
- 'uid': 'user1',
- 'userpassword': PASSWORD
- })))
- except ldap.LDAPError as e:
- log.fatal('Failed to add user: error ' + e.args[0]['desc'])
- assert False
-
- # Reset password as RootDN
- try:
- topo.standalone.modify_s(USER_DN, [(ldap.MOD_REPLACE, 'userpassword', ensure_bytes(PASSWORD))])
- except ldap.LDAPError as e:
- log.fatal('Failed to bind: error ' + e.args[0]['desc'])
- assert False
-
- time.sleep(1)
-
- # Reset password as user
- try:
- topo.standalone.simple_bind_s(USER_DN, PASSWORD)
- except ldap.LDAPError as e:
- log.fatal('Failed to bind: error ' + e.args[0]['desc'])
- assert False
-
- try:
- topo.standalone.modify_s(USER_DN, [(ldap.MOD_REPLACE, 'userpassword', ensure_bytes(PASSWORD))])
- except ldap.LDAPError as e:
- log.fatal('Failed to change password: error ' + e.args[0]['desc'])
- assert False
-
- ###################################
- # Make sure ldappasswd also works
- ###################################
-
- # Reset password as RootDN
- try:
- topo.standalone.simple_bind_s(DN_DM, PASSWORD)
- except ldap.LDAPError as e:
- log.fatal('Failed to bind as rootdn: error ' + e.args[0]['desc'])
- assert False
-
- try:
- topo.standalone.modify_s(USER_DN, [(ldap.MOD_REPLACE, 'userpassword', ensure_bytes(PASSWORD))])
- except ldap.LDAPError as e:
- log.fatal('Failed to bind: error ' + e.args[0]['desc'])
- assert False
-
- time.sleep(1)
-
- # Run ldappasswd as the User.
- os.environ["LDAPTLS_CACERTDIR"] = topo.standalone.get_cert_dir()
- cmd = ('ldappasswd' + ' -h ' + topo.standalone.host + ' -Z -p 38901 -D ' + USER_DN +
- ' -w password -a password -s password2 ' + USER_DN)
- os.system(cmd)
- time.sleep(1)
-
- try:
- topo.standalone.simple_bind_s(USER_DN, "password2")
- except ldap.LDAPError as e:
- log.fatal('Failed to bind: error ' + e.args[0]['desc'])
- assert False
-
- log.info('Test Passed')
-
-
-if __name__ == '__main__':
- # Run isolated
- # -s for DEBUG mode
- CURRENT_FILE = os.path.realpath(__file__)
- pytest.main("-s %s" % CURRENT_FILE)
--
2.52.0

View File

@ -0,0 +1,952 @@
From 0e2d9c4288873446dcb3d8bff61c558ff2b6681a 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

View File

@ -0,0 +1,86 @@
From 8b4dbf35ace326e5b836982d428e7029313a2247 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

View File

@ -0,0 +1,40 @@
From ffe1909e69ab2aecef396f31cf95cdcecd992782 Mon Sep 17 00:00:00 2001
From: Mark Reynolds <mreynolds@redhat.com>
Date: Mon, 23 Feb 2026 12:10:32 -0500
Subject: [PATCH] Issue 7273 - In a chaining environment binding as remote user
causes an invalid error in the logs
Description:
In a database link/chaining environment you can bind as a remote user, and
this triggers an error when trying to "upgrade_on_bind" as the user does not
locally have a userpassword since it's remote. There is no strong case to
log an error in this situation.
relates: http://github.com/389ds/389-ds-base/issues/7273
Reviewed by: vashirov(Thanks!)
---
ldap/servers/slapd/pw.c | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/ldap/servers/slapd/pw.c b/ldap/servers/slapd/pw.c
index c53ecf23d..88e32537d 100644
--- a/ldap/servers/slapd/pw.c
+++ b/ldap/servers/slapd/pw.c
@@ -3564,10 +3564,8 @@ int32_t update_pw_encoding(Slapi_PBlock *orig_pb, Slapi_Entry *e, Slapi_DN *sdn,
* Does the entry have a pw?
*/
if (e == NULL || slapi_entry_attr_find(e, SLAPI_USERPWD_ATTR, &pw) != 0 || pw == NULL) {
- slapi_log_err(SLAPI_LOG_WARNING,
- "update_pw_encoding", "Could not read password attribute on '%s'\n",
- dn);
- res = -1;
+ /* The entry does not have a userpassword attribute so there is nothing to do.
+ * This typically happens when chaining is involved. */
goto free_and_return;
}
--
2.53.0

View File

@ -0,0 +1,46 @@
From 3a233116a564fc339aee1021913c995504951d86 Mon Sep 17 00:00:00 2001
From: Simon Pichugin <spichugi@redhat.com>
Date: Tue, 24 Feb 2026 09:28:24 -0800
Subject: [PATCH] Issue 7279 - UI - Fix typo in export certificate dialog
(#7280)
Description: Fix typo "cetificate" -> "certificate" in the
export certificate dialog message.
Fixes: https://github.com/389ds/389-ds-base/issues/7279
Reviewed by: @vashirov (Thanks!)
---
src/cockpit/389-console/po/ja.po | 2 +-
src/cockpit/389-console/src/lib/security/securityModals.jsx | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/cockpit/389-console/po/ja.po b/src/cockpit/389-console/po/ja.po
index 27d21bf7a..12c10ded4 100644
--- a/src/cockpit/389-console/po/ja.po
+++ b/src/cockpit/389-console/po/ja.po
@@ -11560,7 +11560,7 @@ msgstr "証明書のエクスポート:"
#: src/lib/security/securityModals.jsx:58
msgid ""
"Enter the full path and file name, if the path portion is omitted the "
-"cetificate is written to the server's certificate directory "
+"certificate is written to the server's certificate directory "
msgstr ""
"ファイル名を含むフルパスを入力してください。パス部分を省略した場合、証明書は"
"サーバの証明書ディレクトリに書き込まれます。"
diff --git a/src/cockpit/389-console/src/lib/security/securityModals.jsx b/src/cockpit/389-console/src/lib/security/securityModals.jsx
index 4d6631fd2..f32d47596 100644
--- a/src/cockpit/389-console/src/lib/security/securityModals.jsx
+++ b/src/cockpit/389-console/src/lib/security/securityModals.jsx
@@ -55,7 +55,7 @@ export class ExportCertModal extends React.Component {
}
const title = <>{_("Export Certificate:")} &nbsp;&nbsp;<i>{nickName}</i></>;
- const desc = <>{_("Enter the full path and file name, if the path portion is omitted the cetificate is written to the server's certificate directory ")}<i>{certDir}</i></>;
+ const desc = <>{_("Enter the full path and file name, if the path portion is omitted the certificate is written to the server's certificate directory ")}<i>{certDir}</i></>;
return (
<Modal
--
2.53.0

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,34 @@
From f4d8df66187c0d1db6c9f5c9aa55946a8564de4b Mon Sep 17 00:00:00 2001
From: Sam Morris <sam@robots.org.uk>
Date: Wed, 25 Feb 2026 12:30:01 +0000
Subject: [PATCH] Issue 7246 - correct formatting of 'Gen as CSN' in dsctl
get-nsstate output (#7247)
Description: CSNs are formatted as hexadecimal, but the replica id and
sequence number are displayed in decomal.
Fix: use correct format specifiers for hexadecimal output.
Fixes: https://github.com/389ds/389-ds-base/issues/7246
Signed-off-by: Sam Morris <sam@robots.org.uk>
---
src/lib389/lib389/dseldif.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/lib389/lib389/dseldif.py b/src/lib389/lib389/dseldif.py
index 7834d9468..eac17aa4c 100644
--- a/src/lib389/lib389/dseldif.py
+++ b/src/lib389/lib389/dseldif.py
@@ -407,7 +407,7 @@ class DSEldif(DSLint):
'endian': endian,
'rid': str(rid),
'gen_time': str(sampled_time),
- 'gencsn': "%08x%04d%04d0000" % (sampled_time, seq_num, rid),
+ 'gencsn': "%08x%04x%04x0000" % (sampled_time, seq_num, rid),
'gen_time_str': time.ctime(sampled_time),
'local_offset': str(local_offset),
'local_offset_str': print_nice_time(local_offset),
--
2.53.0

View File

@ -1,4 +1,4 @@
From 582c4916a80660e3ea876ae4b6355a81c29a791d Mon Sep 17 00:00:00 2001
From 2e424110def2e3998f6045e136fb0d43f47b7f5a 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

View File

@ -0,0 +1,654 @@
From c7e1eb08eb36fd9a16c745935f56fa4a4b2a99df Mon Sep 17 00:00:00 2001
From: progier389 <progier@redhat.com>
Date: Wed, 25 Feb 2026 18:00:24 +0100
Subject: [PATCH] Issue 7267 - MDB_BAD_VALSIZE error when updating index
(#7268)
* Issue 7267 - MDB_BAD_VALSIZE error when updating index
* Improve import log when writer fails
* Fix Sourcery AI comments
* Fix INDEX_KEY_LENGTH typo
Problem with the key prefix handling when key is too long and must be hashed.
The issue is that the # that is prepended is not reset when iterating over the valueset values (Ending up with very long prefix)
Also refactored the code to avoid duplicate the code that prepare the key from the attribute value (used when updating the index or retrieving a value from an index)
Issue: #7267
Reviewed by: @tbordaz , @vashirov (Thanks!)
Co-authored-by: Viktor Ashirov <vashirov@redhat.com>
---------
Co-authored-by: Viktor Ashirov <vashirov@redhat.com>
---
.../tests/suites/indexes/regression_test.py | 58 +++++++
ldap/servers/slapd/back-ldbm/attrcrypt.h | 2 +-
ldap/servers/slapd/back-ldbm/back-ldbm.h | 2 +
.../slapd/back-ldbm/db-bdb/bdb_import.c | 39 +----
.../back-ldbm/db-mdb/mdb_import_threads.c | 46 ++++-
ldap/servers/slapd/back-ldbm/index.c | 161 +++++++-----------
ldap/servers/slapd/back-ldbm/ldbm_attrcrypt.c | 13 +-
.../servers/slapd/back-ldbm/proto-back-ldbm.h | 2 +-
ldap/servers/slapd/log.c | 43 +++++
ldap/servers/slapd/slapi-private.h | 2 +
10 files changed, 224 insertions(+), 144 deletions(-)
diff --git a/dirsrvtests/tests/suites/indexes/regression_test.py b/dirsrvtests/tests/suites/indexes/regression_test.py
index 8176d6db0..a4218a2b5 100644
--- a/dirsrvtests/tests/suites/indexes/regression_test.py
+++ b/dirsrvtests/tests/suites/indexes/regression_test.py
@@ -1022,6 +1022,64 @@ def test_idl_range_limit(topo, add_some_entries):
assert len(entries) == 3
+def test_large_multivalued_sn_attribute(topo):
+ """Test adding a user entry with 512 values for sn attribute, each 512 bytes
+
+ :id: 8f2a9b3c-e8d7-11ef-9a5f-482ae39447e5
+ :setup: Standalone Instance
+ :steps:
+ 1. Create a user with 512 sn values, each 512 bytes long
+ 2. Verify the user was created successfully
+ 3. Search for the user and verify all sn values are present
+ 4. Clean up the user entry
+ :expectedresults:
+ 1. User is created successfully
+ 2. User entry exists
+ 3. All 512 sn values are present and have correct length
+ 4. User is deleted successfully
+ """
+
+ inst = topo.standalone
+ users = UserAccounts(inst, DEFAULT_SUFFIX)
+
+ log.info("Creating user with 512 sn values, each 512 bytes")
+
+ # Generate 512 unique sn values, each 512 bytes long
+ # Use a pattern that makes each value unique but predictable
+ sn_values = []
+ for i in range(512):
+ # Create a 512-byte value with unique identifier at the start
+ value = f'sn_value_{i:04d}_' + 'x' * (512 - len(f'sn_value_{i:04d}_'))
+ sn_values.append(value)
+
+ # Create the user with first sn value
+ user_name = 'test_user_large_sn'
+ user = users.create(properties={
+ 'uid': user_name,
+ 'cn': user_name,
+ 'sn': sn_values,
+ 'uidNumber': '99999',
+ 'gidNumber': '99999',
+ 'homeDirectory': f'/home/{user_name}'
+ })
+
+ # Verify the entry was created and has all sn values
+ log.info("Verifying all sn values are present")
+ sn_attr_values = user.get_attr_vals_utf8('sn')
+
+ assert len(sn_attr_values) == 512, f"Expected 512 sn values, got {len(sn_attr_values)}"
+
+ # Verify each value has the correct length
+ for idx, value in enumerate(sn_attr_values):
+ assert len(value) == 512, f"sn value {idx} has length {len(value)}, expected 512"
+
+ log.info("Successfully created and verified user with 512 sn values of 512 bytes each")
+
+ # Clean up
+ user.delete()
+ log.info("User entry deleted successfully")
+
+
if __name__ == "__main__":
# Run isolated
# -s for DEBUG mode
diff --git a/ldap/servers/slapd/back-ldbm/attrcrypt.h b/ldap/servers/slapd/back-ldbm/attrcrypt.h
index d653ba951..dcbea80fe 100644
--- a/ldap/servers/slapd/back-ldbm/attrcrypt.h
+++ b/ldap/servers/slapd/back-ldbm/attrcrypt.h
@@ -10,7 +10,7 @@
#include <config.h>
#endif
-/* Private tructures and #defines used in the attribute encryption code. */
+/* Private structures and #defines used in the attribute encryption code. */
#ifndef _ATTRCRYPT_H_
#define _ATTRCRYPT_H_
diff --git a/ldap/servers/slapd/back-ldbm/back-ldbm.h b/ldap/servers/slapd/back-ldbm/back-ldbm.h
index e23e7ff43..92aa1ddbb 100644
--- a/ldap/servers/slapd/back-ldbm/back-ldbm.h
+++ b/ldap/servers/slapd/back-ldbm/back-ldbm.h
@@ -104,6 +104,8 @@ typedef unsigned short u_int16_t;
*/
#define BE_CHANGELOG_FILE "replication_changelog"
+#define INDEX_KEY_LENGTH(lenval,lenprefix) (lenval+lenprefix+2)
+
#define BDB_IMPL "bdb"
#define BDB_BACKEND "libback-ldbm" /* This backend plugin */
#define BDB_NEWIDL "newidl" /* new idl format */
diff --git a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_import.c b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_import.c
index a6cb10aec..489433801 100644
--- a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_import.c
+++ b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_import.c
@@ -75,9 +75,9 @@ static IDList *bdb_idl_union_allids(backend *be, struct attrinfo *ai, IDList *a,
#define DEBUG_SUBCOUNT_MSG(msg, ...) { debug_subcount(__FUNCTION__, __LINE__, (msg), __VA_ARGS__); }
#define DUMP_SUBCOUNT_KEY(msg, key, ret) { debug_subcount(__FUNCTION__, __LINE__, "ret=%d size=%u ulen=%u doff=%u dlen=%u", \
ret, (key).size, (key).ulen, (key).doff, (key).dlen); \
- if (ret == 0) hexadump(msg, (key).data, 0, (key).size); \
+ if (ret == 0) slapi_log_hexadump(SLAPI_LOG_INFO, msg, (key).data, (key).size); \
else if (ret == DB_BUFFER_SMALL) \
- hexadump(msg, (key).data, 0, (key).ulen); }
+ slapi_log_hexadump(SLAPI_LOG_INFO, msg, (key).data, (key).ulen); }
static void
debug_subcount(const char *funcname, int line, char *msg, ...)
@@ -90,41 +90,6 @@ debug_subcount(const char *funcname, int line, char *msg, ...)
slapi_log_err(SLAPI_LOG_INFO, (char*)funcname, "DEBUG SUBCOUNT [%d] %s\n", line, buff);
}
-/*
- * Dump a memory buffer in hexa and ascii in error log
- *
- * addr - The memory buffer address.
- * len - The memory buffer lenght.
- */
-static void
-hexadump(char *msg, const void *addr, size_t offset, size_t len)
-{
-#define HEXADUMP_TAB 4
-/* 4 characters per bytes: 2 hexa digits, 1 space and the ascii */
-#define HEXADUMP_BUF_SIZE (4*16+HEXADUMP_TAB)
- char hexdigit[] = "0123456789ABCDEF";
-
- const unsigned char *pt = addr;
- char buff[HEXADUMP_BUF_SIZE+1];
- memset (buff, ' ', HEXADUMP_BUF_SIZE);
- buff[HEXADUMP_BUF_SIZE] = '\0';
- while (len > 0) {
- int dpl;
- for (dpl = 0; dpl < 16 && len>0; dpl++, len--) {
- buff[3*dpl] = hexdigit[((*pt) >> 4) & 0xf];
- buff[3*dpl+1] = hexdigit[(*pt) & 0xf];
- buff[3*16+HEXADUMP_TAB+dpl] = (*pt>=0x20 && *pt<0x7f) ? *pt : '.';
- pt++;
- }
- for (;dpl < 16; dpl++) {
- buff[3*dpl] = ' ';
- buff[3*dpl+1] = ' ';
- buff[3*16+HEXADUMP_TAB+dpl] = ' ';
- }
- slapi_log_err(SLAPI_LOG_INFO, msg, "[0x%08lx] %s\n", offset, buff);
- offset += 16;
- }
-}
#else
#define DEBUG_SUBCOUNT_MSG(msg, ...)
#define DUMP_SUBCOUNT_KEY(msg, key, ret)
diff --git a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_import_threads.c b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_import_threads.c
index 65b29343e..b969790da 100644
--- a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_import_threads.c
+++ b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_import_threads.c
@@ -1123,6 +1123,21 @@ dbmdb_import_entry_info_by_backentry(mdb_privdb_t *db, BulkQueueData_t *bqdata,
return dnrc;
}
+/* Log wqelmt details */
+void
+log_wqelmt(int loglvl, char *fname, WorkerQueueData_t *wqelmt)
+{
+ if (wqelmt->dn) {
+ slapi_log_err(loglvl, fname, "log_wqelmt: dn=%s\n", wqelmt->dn);
+ }
+ if (wqelmt->filename && wqelmt->lineno) {
+ slapi_log_err(loglvl, fname, "log_wqelmt: ldif=%s[%d]\n", wqelmt->filename, wqelmt->lineno);
+ }
+ if (wqelmt->data) {
+ size_t len = wqelmt->datalen ? wqelmt->datalen : strlen(wqelmt->data);
+ slapi_log_hexadump(loglvl, "log_wqelmt:data", wqelmt->data, len);
+ }
+}
/* producer thread for ldif import case:
* read through the given file list, parsing entries (str2entry), assigning
@@ -1255,6 +1270,7 @@ dbmdb_import_producer(void *param)
import_log_notice(job, SLAPI_LOG_ERR, "dbmdb_import_producer",
"ns_slapd software error: unexpected dbmdb_import_entry_info return code: %d.",
wqelmt.dnrc);
+ log_wqelmt(SLAPI_LOG_ERR, "dbmdb_import_producer", &wqelmt);
abort();
case DNRC_OK:
case DNRC_SUFFIX:
@@ -1758,6 +1774,7 @@ dbmdb_index_producer(void *param)
import_log_notice(job, SLAPI_LOG_ERR, "dbmdb_index_producer",
"ns_slapd software error: unexpected dbmdb_import_entry_info return code: %d.",
tmpslot.dnrc);
+ log_wqelmt(SLAPI_LOG_ERR, "dbmdb_index_producer", &tmpslot);
abort();
case DNRC_OK:
case DNRC_SUFFIX:
@@ -3936,10 +3953,24 @@ dbmdb_import_writer(void*param)
if (!txn) {
MDB_STAT_STEP(stats, MDB_STAT_TXNSTART, stats_enabled);
rc = TXN_BEGIN(ctx->ctx->env, NULL, 0, &txn);
+ if (rc) {
+ slapi_log_err(SLAPI_LOG_ERR, "dbmdb_import_writer",
+ "Failed to begin a txn. Error is 0x%x: %s.\n",
+ rc, mdb_strerror(rc));
+ }
}
if (!rc) {
MDB_STAT_STEP(stats, MDB_STAT_WRITE, stats_enabled);
rc = MDB_PUT(txn, slot->dbi->dbi, &slot->key, &slot->data, 0);
+ if (rc) {
+ slapi_log_err(SLAPI_LOG_ERR, "dbmdb_import_writer",
+ "Failed to write record in dbi %s. Error is 0x%x: %s.\n",
+ slot->dbi->dbname, rc, mdb_strerror(rc));
+ slapi_log_hexadump(SLAPI_LOG_ERR, "dbmdb_import_writer:key",
+ slot->key.mv_data, slot->key.mv_size);
+ slapi_log_hexadump(SLAPI_LOG_ERR, "dbmdb_import_writer:data",
+ slot->data.mv_data, slot->data.mv_size);
+ }
}
MDB_STAT_STEP(stats, MDB_STAT_RUN, stats_enabled);
nextslot = slot->next;
@@ -3953,6 +3984,9 @@ dbmdb_import_writer(void*param)
rc = TXN_COMMIT(txn);
MDB_STAT_STEP(stats, MDB_STAT_RUN, stats_enabled);
if (rc) {
+ slapi_log_err(SLAPI_LOG_ERR, "dbmdb_import_writer",
+ "Failed to commit the txn. Error is 0x%x: %s.\n",
+ rc, mdb_strerror(rc));
break;
}
count = 0;
@@ -3965,6 +3999,10 @@ dbmdb_import_writer(void*param)
MDB_STAT_STEP(stats, MDB_STAT_RUN, stats_enabled);
if (!rc) {
txn = NULL;
+ } else {
+ slapi_log_err(SLAPI_LOG_ERR, "dbmdb_import_writer",
+ "Failed to commit the txn. Error is 0x%x: %s.\n",
+ rc, mdb_strerror(rc));
}
}
if (txn) {
@@ -3977,13 +4015,17 @@ dbmdb_import_writer(void*param)
if (!rc) {
/* Ensure that all data are written on disk */
rc = mdb_env_sync(ctx->ctx->env, 1);
+ if (rc) {
+ slapi_log_err(SLAPI_LOG_ERR, "dbmdb_import_writer",
+ "mdb_env_sync failed. Error is 0x%x: %s.\n",
+ rc, mdb_strerror(rc));
+ }
}
MDB_STAT_END(stats, stats_enabled);
if (rc) {
slapi_log_err(SLAPI_LOG_ERR, "dbmdb_import_writer",
- "Failed to write in the database. Error is 0x%x: %s.\n",
- rc, mdb_strerror(rc));
+ "Aborting import after failure.\n");
thread_abort(info);
} else if (stats_enabled) {
char buf[200];
diff --git a/ldap/servers/slapd/back-ldbm/index.c b/ldap/servers/slapd/back-ldbm/index.c
index a5004be19..c108bce3c 100644
--- a/ldap/servers/slapd/back-ldbm/index.c
+++ b/ldap/servers/slapd/back-ldbm/index.c
@@ -881,6 +881,67 @@ index_read(
return index_read_ext(be, (char *)type, indextype, val, txn, err, NULL);
}
+/* Prepare an index key (hashed if too long, encrypted if needed from attribute value */
+int
+prepare_key(backend *be, struct attrinfo *a, char **buf, size_t *buflen,
+ int flags, const char *prefix, const struct berval *bvp, dbi_val_t *key)
+{
+ /* Key format is [Hash?] [prefix] [val] [\0] */
+ struct ldbminfo *li = (struct ldbminfo *)be->be_database->plg_private;
+ size_t plen = strlen(prefix);
+ struct berval *hashed_bvp = NULL;
+ struct berval *encrypted_bvp = NULL;
+ int rc = 0;
+
+ /* Hash large index key if necessary */
+ if (INDEX_KEY_LENGTH(bvp->bv_len,plen) >= li->li_max_key_len) {
+ rc = attrcrypt_hash_large_index_key(be, prefix, a, bvp, &hashed_bvp);
+ if (rc) {
+ slapi_log_err(SLAPI_LOG_ERR, "index_read_ext_allids",
+ "Failed to hash large index key for %s\n", a->ai_type);
+ return rc;
+ } else {
+ bvp = hashed_bvp;
+ }
+ }
+
+ /* Encrypt the index key if necessary */
+ if (rc == 0 && a->ai_attrcrypt && (0 == (flags & BE_INDEX_DONT_ENCRYPT))) {
+ rc = attrcrypt_encrypt_index_key(be, a, bvp, &encrypted_bvp);
+ if (rc) {
+ slapi_log_err(SLAPI_LOG_ERR, "addordel_values_sv",
+ "Failed to encrypt index key for %s\n", a->ai_type);
+ } else {
+ bvp = encrypted_bvp;
+ }
+ }
+ if (hashed_bvp) {
+ prefix = slapi_ch_smprintf("%c%s",HASH_PREFIX, prefix);
+ plen++;
+ }
+ if (buf && buflen) {
+ if (plen+bvp->bv_len+1 > *buflen) {
+ *buflen = plen+bvp->bv_len+1;
+ *buf = slapi_ch_realloc(*buf, *buflen);
+ }
+ dblayer_value_concat(be, key, *buf, *buflen, prefix, plen, bvp->bv_val, bvp->bv_len, "", 1);
+ } else {
+ dblayer_value_concat(be, key, NULL, 0, prefix, plen, bvp->bv_val, bvp->bv_len, "", 1);
+ }
+
+ if (hashed_bvp) {
+ ber_bvfree(hashed_bvp);
+ hashed_bvp = NULL;
+ slapi_ch_free_string((char**)&prefix);
+ }
+ if (encrypted_bvp) {
+ ber_bvfree(encrypted_bvp);
+ encrypted_bvp = NULL;
+ }
+ return rc;
+}
+
+
/*
* Extended version of index_read.
* The unindexed flag can be used to distinguish between a
@@ -917,7 +978,6 @@ index_read_ext_allids(
struct berval *hashed_val = NULL;
int is_and = 0;
unsigned int ai_flags = 0;
- struct ldbminfo *li = (struct ldbminfo *)be->be_database->plg_private;
*err = 0;
@@ -1028,36 +1088,7 @@ index_read_ext_allids(
}
if (val != NULL) {
- size_t vlen;
- int ret = 0;
-
- /* If necessary, hash this index key */
- if (val->bv_len >= li->li_max_key_len) {
- ret = attrcrypt_hash_large_index_key(be, &prefix, ai, val, &hashed_val);
- if (ret) {
- slapi_log_err(SLAPI_LOG_ERR, "index_read_ext_allids",
- "Failed to hash large index key for %s\n", basetype);
- *err = DBI_RC_OTHER;
- index_free_prefix(prefix);
- slapi_ch_free_string(&basetmp);
- return (NULL);
- }
- if (hashed_val) {
- val = hashed_val;
- }
- }
- /* If necessary, encrypt this index key */
- ret = attrcrypt_encrypt_index_key(be, ai, val, &encrypted_val);
- if (ret) {
- slapi_log_err(SLAPI_LOG_ERR, "index_read_ext_allids",
- "Failed to encrypt index key for %s\n", basetype);
- }
- if (encrypted_val) {
- val = encrypted_val;
- }
- vlen = val->bv_len;
- dblayer_value_concat(be, &key, buf, sizeof(buf),
- prefix, strlen(prefix), val->bv_val, vlen, "", 1);
+ (void) prepare_key(be, ai, NULL, 0, 0, prefix, val, &key);
} else {
dblayer_value_concat(be, &key, buf, sizeof(buf), prefix, strlen(prefix),
"", 1, NULL, 0);
@@ -1824,6 +1855,7 @@ index_range_read(
return index_range_read_ext(pb, be, type, indextype, operator, val, nextval, range, txn, err, 0);
}
+
static int
addordel_values_sv(
backend *be,
@@ -1842,15 +1874,10 @@ addordel_values_sv(
int i = 0;
dbi_val_t key = {0};
dbi_txn_t *db_txn = NULL;
- size_t plen, vlen, len;
char *tmpbuf = NULL;
size_t tmpbuflen = 0;
- char *realbuf;
char *prefix = NULL;
const struct berval *bvp;
- struct berval *hashed_bvp = NULL;
- struct berval *encrypted_bvp = NULL;
- struct ldbminfo *li = (struct ldbminfo *)be->be_database->plg_private;
char *index_id = get_index_name(be, db, a);
slapi_log_err(SLAPI_LOG_TRACE, "addordel_values_sv", "%s_values\n",
@@ -1889,66 +1916,14 @@ addordel_values_sv(
return (rc);
}
- plen = strlen(prefix);
for (i = 0; vals[i] != NULL; i++) {
bvp = slapi_value_get_berval(vals[i]);
- /* Hash large index key if necessary */
- if (bvp->bv_len >= li->li_max_key_len) {
- rc = attrcrypt_hash_large_index_key(be, &prefix, a, bvp, &hashed_bvp);
- if (rc) {
- slapi_log_err(SLAPI_LOG_ERR, "index_read_ext_allids",
- "Failed to hash large index key for %s\n", a->ai_type);
- break;
- } else {
- bvp = hashed_bvp;
- plen = strlen(prefix);
- }
- }
- /* Encrypt the index key if necessary */
- {
- if (a->ai_attrcrypt && (0 == (flags & BE_INDEX_DONT_ENCRYPT))) {
- rc = attrcrypt_encrypt_index_key(be, a, bvp, &encrypted_bvp);
- if (rc) {
- slapi_log_err(SLAPI_LOG_ERR, "addordel_values_sv",
- "Failed to encrypt index key for %s\n", a->ai_type);
- } else {
- bvp = encrypted_bvp;
- }
- }
+ rc = prepare_key(be, a, &tmpbuf, &tmpbuflen, flags, prefix, bvp, &key);
+ if (rc) {
+ break;
}
- vlen = bvp->bv_len;
- len = plen + vlen;
-
- if (len < tmpbuflen) {
- realbuf = tmpbuf;
- } else {
- tmpbuf = slapi_ch_realloc(tmpbuf, len + 1);
- tmpbuflen = len + 1;
- realbuf = tmpbuf;
- }
-
- assert(realbuf); /* For coverity */
- memcpy(realbuf, prefix, plen);
- memcpy(realbuf + plen, bvp->bv_val, vlen);
- realbuf[len] = '\0';
- /* Free the encrypted berval if necessary */
- if (hashed_bvp) {
- ber_bvfree(hashed_bvp);
- hashed_bvp = NULL;
- }
- if (encrypted_bvp) {
- ber_bvfree(encrypted_bvp);
- encrypted_bvp = NULL;
- }
- /* should be okay to use USERMEM here because we know what
- * the key is and it should never return a different value
- * than the one we pass in.
- */
- dblayer_value_set_buffer(be, &key, realbuf, plen + vlen + 1);
- key.ulen = tmpbuflen;
-
if (slapi_is_loglevel_set(LDAP_DEBUG_TRACE)) {
char encbuf[BUFSIZ];
@@ -1981,10 +1956,6 @@ addordel_values_sv(
ldbm_nasty(NASTY_MSG("addordel_values_sv"), index_id, 1130, rc);
break;
}
- if (NULL != key.dptr && realbuf != key.dptr) { /* realloc'ed */
- tmpbuf = key.dptr;
- tmpbuflen = key.size;
- }
}
index_free_prefix(prefix);
if (tmpbuf != NULL) {
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_attrcrypt.c b/ldap/servers/slapd/back-ldbm/ldbm_attrcrypt.c
index 124810426..6655633fc 100644
--- a/ldap/servers/slapd/back-ldbm/ldbm_attrcrypt.c
+++ b/ldap/servers/slapd/back-ldbm/ldbm_attrcrypt.c
@@ -1079,15 +1079,15 @@ attrcrypt_decrypt_index_key(backend *be,
* : NULL - no hash or failure
*/
int
-attrcrypt_hash_large_index_key(backend *be, char **prefix, struct attrinfo *ai, const struct berval *in, struct berval **out)
+attrcrypt_hash_large_index_key(backend *be, const char *prefix, struct attrinfo *ai, const struct berval *in, struct berval **out)
{
int ret = 0;
struct berval *out_berval = NULL;
struct ldbminfo *li = (struct ldbminfo *)be->be_database->plg_private;
- char *new_prefix;
+ size_t final_key_len = INDEX_KEY_LENGTH(in->bv_len, strlen(prefix));
/* If the index key is too long (i.e mdb case) we must hash it */
- if (in->bv_len >= li->li_max_key_len) {
+ if (final_key_len >= li->li_max_key_len) {
PK11Context *c = PK11_CreateDigestContext(SEC_OID_MD5);
if (c != NULL) {
unsigned char hash[32];
@@ -1101,16 +1101,13 @@ attrcrypt_hash_large_index_key(backend *be, char **prefix, struct attrinfo *ai,
return ENOMEM;
}
slapi_log_err(SLAPI_LOG_TRACE, "attrcrypt_hash_large_index_key",
- "Key lenght (%lu) >= max key lenght (%lu) so key must be hashed\n", in->bv_len, li->li_max_key_len);
+ "Key lenght (%lu) >= max key lenght (%lu) so key must be hashed\n", final_key_len, li->li_max_key_len);
slapi_be_set_flag(be, SLAPI_BE_FLAG_DONT_BYPASS_FILTERTEST);
PK11_DigestBegin(c);
/* Compute hash for the key without the prefix */
PK11_DigestOp(c, (unsigned char *)in->bv_val, in->bv_len);
PK11_DigestFinal(c, hash, &hashLen, sizeof hash);
- /* Add HASH_PREFIX before the prefix */
- new_prefix = slapi_ch_smprintf("%c%s", HASH_PREFIX, *prefix);
- index_free_prefix(*prefix);
- *prefix = new_prefix;
+
/* Build the key: hash value in hexa */
hkey = slapi_ch_malloc(1+2*sizeof hash);
out_berval->bv_val = hkey;
diff --git a/ldap/servers/slapd/back-ldbm/proto-back-ldbm.h b/ldap/servers/slapd/back-ldbm/proto-back-ldbm.h
index 30a7aa11f..c882dac7b 100644
--- a/ldap/servers/slapd/back-ldbm/proto-back-ldbm.h
+++ b/ldap/servers/slapd/back-ldbm/proto-back-ldbm.h
@@ -622,7 +622,7 @@ int attrcrypt_encrypt_entry_inplace(backend *be, const struct backentry *inout);
int attrcrypt_encrypt_entry(backend *be, const struct backentry *in, struct backentry **out);
int attrcrypt_encrypt_index_key(backend *be, struct attrinfo *ai, const struct berval *in, struct berval **out);
int attrcrypt_decrypt_index_key(backend *be, struct attrinfo *ai, const struct berval *in, struct berval **out);
-int attrcrypt_hash_large_index_key(backend *be, char **prefix, struct attrinfo *ai, const struct berval *in, struct berval **out);
+int attrcrypt_hash_large_index_key(backend *be, const char *prefix, struct attrinfo *ai, const struct berval *in, struct berval **out);
int attrcrypt_init(ldbm_instance *li);
int attrcrypt_cleanup_private(ldbm_instance *li);
diff --git a/ldap/servers/slapd/log.c b/ldap/servers/slapd/log.c
index 80c07382a..93101494b 100644
--- a/ldap/servers/slapd/log.c
+++ b/ldap/servers/slapd/log.c
@@ -93,6 +93,10 @@ static int slapi_log_map[] = {
#define FLUSH PR_TRUE
#define NO_FLUSH PR_FALSE
+#define HEXADUMP_TAB 4
+/* 4 characters per bytes: 2 hexa digits, 1 space and the ascii */
+#define HEXADUMP_BUF_SIZE (4*16+HEXADUMP_TAB)
+
/**************************************************************************
* PROTOTYPES
*************************************************************************/
@@ -3133,6 +3137,45 @@ slapi_log_backtrace(int loglevel)
}
}
+/*
+ * Dump a memory buffer in hexa and ascii in error log
+ *
+ * addr - The memory buffer address.
+ * len - The memory buffer lenght.
+ */
+void
+slapi_log_hexadump(int loglevel, char *fname, const void *addr, size_t len)
+{
+ char hexdigit[] = "0123456789ABCDEF";
+ const unsigned char *pt = addr;
+ char buff[HEXADUMP_BUF_SIZE+1];
+ size_t offset = 0;
+
+ if (!slapi_is_loglevel_set(loglevel)) {
+ return;
+ }
+ memset (buff, ' ', HEXADUMP_BUF_SIZE);
+ buff[HEXADUMP_BUF_SIZE] = '\0';
+ while (len > 0) {
+ int dpl;
+ for (dpl = 0; dpl < 16 && len>0; dpl++, len--) {
+ buff[3*dpl] = hexdigit[((*pt) >> 4) & 0xf];
+ buff[3*dpl+1] = hexdigit[(*pt) & 0xf];
+ buff[3*16+HEXADUMP_TAB+dpl] = (*pt>=0x20 && *pt<0x7f) ? *pt : '.';
+ pt++;
+ }
+ for (;dpl < 16; dpl++) {
+ buff[3*dpl] = ' ';
+ buff[3*dpl+1] = ' ';
+ buff[3*16+HEXADUMP_TAB+dpl] = ' ';
+ }
+ slapi_log_err(loglevel, fname, "[0x%08lx] %s\n", offset, buff);
+ offset += 16;
+ }
+}
+
+
+
/******************************************************************************
* write in the access log
******************************************************************************/
diff --git a/ldap/servers/slapd/slapi-private.h b/ldap/servers/slapd/slapi-private.h
index 72f4cd6f0..2da37ff6e 100644
--- a/ldap/servers/slapd/slapi-private.h
+++ b/ldap/servers/slapd/slapi-private.h
@@ -1527,6 +1527,8 @@ void slapi_pblock_set_task_warning(Slapi_PBlock *pb, task_warning warning);
int slapi_exists_or_add_internal(Slapi_DN *dn, const char *filter, const char *entry, const char *modifier_name);
void slapi_log_backtrace(int loglevel);
+void slapi_log_hexadump(int loglevel, char *fname, const void *addr, size_t len);
+
/*
* accesslog.c
--
2.53.0

View File

@ -0,0 +1,291 @@
From 6fc10eae5b44989c703b67bdca157202d35d8a92 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 | 8 +++--
ldap/servers/slapd/pblock.c | 36 +++++++++++++------
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, 74 insertions(+), 22 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 19c6d7e48..7a2ca2ae2 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).
@@ -1416,6 +1416,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",
@@ -3178,7 +3182,7 @@ void
wait4certs_refresh(daemon_ports_t *ports)
{
/*
- * Block listening and accept threads until
+ * Block listening and accept threads until
* certificates refresh is complete
* Note:
* Listening threads have a NULL ports
diff --git a/ldap/servers/slapd/pblock.c b/ldap/servers/slapd/pblock.c
index 24cb9b15e..3405e6da6 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>
@@ -1008,6 +1008,13 @@ slapi_pblock_get_plugin_close_fn(Slapi_PBlock *pblock, void *value)
return 0;
}
+static int32_t
+slapi_pblock_get_plugin_pre_close_fn(Slapi_PBlock *pblock, void *value)
+{
+ (*(IFP *)value) = pblock->pb_plugin->plg_pre_close;
+ return 0;
+}
+
static int32_t
slapi_pblock_get_plugin_cleanup_fn(Slapi_PBlock *pblock, void *value)
{
@@ -4100,6 +4107,13 @@ slapi_pblock_set_plugin_close_fn(Slapi_PBlock *pblock, void *value)
return 0;
}
+static int32_t
+slapi_pblock_set_plugin_pre_close_fn(Slapi_PBlock *pblock, void *value)
+{
+ pblock->pb_plugin->plg_pre_close = (IFP)value;
+ return 0;
+}
+
static int32_t
slapi_pblock_set_plugin_cleanup_fn(Slapi_PBlock *pblock, void *value)
{
@@ -6948,7 +6962,7 @@ static int32_t (*get_cbtable[])(Slapi_PBlock *, void *) = {
slapi_pblock_get_plugin_db_abandon_fn,
slapi_pblock_get_plugin_db_config_fn,
slapi_pblock_get_plugin_close_fn,
- NULL, /* slot 211 available */
+ slapi_pblock_get_plugin_pre_close_fn,
slapi_pblock_get_plugin_start_fn,
slapi_pblock_get_plugin_db_seq_fn,
slapi_pblock_get_plugin_db_entry_fn,
@@ -8923,7 +8937,7 @@ static int32_t (*set_cbtable[])(Slapi_PBlock *, void *) = {
slapi_pblock_set_plugin_db_abandon_fn,
slapi_pblock_set_plugin_db_config_fn,
slapi_pblock_set_plugin_close_fn,
- NULL, /* slot 211 available */
+ slapi_pblock_set_plugin_pre_close_fn,
slapi_pblock_set_plugin_start_fn,
slapi_pblock_set_plugin_db_seq_fn,
slapi_pblock_set_plugin_db_entry_fn,
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 455d6d718..ce7e4f047 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).
@@ -997,6 +997,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 d494931c2..3e50f77b2 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:
@@ -1046,6 +1046,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 85bbf8fa1..70677123d 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.
*
@@ -7085,6 +7085,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

View File

@ -0,0 +1,34 @@
From 5cc3edb9732784e8a3edb033962b6312f2bb7b55 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

4
389-ds-base-devel.README Normal file
View File

@ -0,0 +1,4 @@
For detailed information on developing plugins for 389 Directory Server visit
https://www.port389.org/docs/389ds/design/plugins.html
https://github.com/389ds/389-ds-base/blob/main/src/slapi_r_plugin/README.md

1233
389-ds-base.spec Normal file

File diff suppressed because it is too large Load Diff

3
389-ds-base.sysusers Normal file
View File

@ -0,0 +1,3 @@
#Type Name ID GECOS Home directory Shell
g dirsrv 389
u dirsrv 389:389 "user for 389-ds-base" /usr/share/dirsrv/ /sbin/nologin

View File

@ -3,39 +3,10 @@
version = 3
[[package]]
name = "addr2line"
version = "0.24.2"
name = "allocator-api2"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
dependencies = [
"gimli",
]
[[package]]
name = "adler2"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
[[package]]
name = "ahash"
version = "0.7.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9"
dependencies = [
"getrandom 0.2.16",
"once_cell",
"version_check",
]
[[package]]
name = "ansi_term"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
dependencies = [
"winapi",
]
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
[[package]]
name = "atty"
@ -50,24 +21,9 @@ dependencies = [
[[package]]
name = "autocfg"
version = "1.4.0"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[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",
]
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "base64"
@ -83,9 +39,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.9.1"
version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
[[package]]
name = "byteorder"
@ -95,11 +51,13 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "cbindgen"
version = "0.9.1"
version = "0.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9daec6140ab4dcd38c3dd57e580b59a621172a526ac79f1527af760a55afeafd"
checksum = "da6bc11b07529f16944307272d5bd9b22530bc7d05751717c9d416586cedab49"
dependencies = [
"clap",
"heck",
"indexmap",
"log",
"proc-macro2",
"quote",
@ -112,10 +70,11 @@ dependencies = [
[[package]]
name = "cc"
version = "1.2.25"
version = "1.2.51"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0fc897dc1e865cc67c0e05a836d9d3f1df3cbe442aa4a9473b18e12624a4951"
checksum = "7a0aeaff4ff1a90589618835a598e545176939b97874f7abc7851caa0618f203"
dependencies = [
"find-msvc-tools",
"jobserver",
"libc",
"shlex",
@ -123,72 +82,49 @@ dependencies = [
[[package]]
name = "cfg-if"
version = "1.0.0"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "clap"
version = "2.34.0"
version = "3.2.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123"
dependencies = [
"ansi_term",
"atty",
"bitflags 1.3.2",
"clap_lex",
"indexmap",
"strsim",
"termcolor",
"textwrap",
"unicode-width",
"vec_map",
]
[[package]]
name = "clap_lex"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5"
dependencies = [
"os_str_bytes",
]
[[package]]
name = "concread"
version = "0.2.21"
version = "0.5.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcc9816f5ac93ebd51c37f7f9a6bf2b40dfcd42978ad2aea5d542016e9244cf6"
checksum = "6588e9e68e11207fb9a5aabd88765187969e6bcba98763c40bcad87b2a73e9f5"
dependencies = [
"ahash",
"crossbeam",
"crossbeam-epoch",
"crossbeam-utils",
"lru",
"parking_lot",
"rand",
"smallvec",
"tokio",
]
[[package]]
name = "crossbeam"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8"
dependencies = [
"crossbeam-channel",
"crossbeam-deque",
"crossbeam-epoch",
"crossbeam-queue",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-channel"
version = "0.5.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
dependencies = [
"crossbeam-epoch",
"crossbeam-utils",
"foldhash",
"lru",
"smallvec",
"sptr",
"tokio",
"tracing",
]
[[package]]
@ -238,10 +174,16 @@ dependencies = [
]
[[package]]
name = "errno"
version = "0.3.12"
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "errno"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys",
@ -266,6 +208,18 @@ dependencies = [
"zeroize",
]
[[package]]
name = "find-msvc-tools"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "645cbb3a84e60b7531617d5ae4e57f7e27308f6445f5abf653209ea76dec8dff"
[[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"
@ -289,36 +243,44 @@ checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
dependencies = [
"cfg-if",
"libc",
"wasi 0.11.0+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"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
[[package]]
name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]]
name = "hashbrown"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
dependencies = [
"ahash",
"allocator-api2",
"equivalent",
"foldhash",
]
[[package]]
name = "heck"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "hermit-abi"
version = "0.1.19"
@ -329,35 +291,36 @@ dependencies = [
]
[[package]]
name = "instant"
version = "0.1.13"
name = "indexmap"
version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222"
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
dependencies = [
"cfg-if",
"autocfg",
"hashbrown 0.12.3",
]
[[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 = "libc"
version = "0.2.172"
version = "0.2.179"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
checksum = "c5a2d376baa530d1238d133232d15e239abad80d05838b4b59354e5268af431f"
[[package]]
name = "librnsslapd"
@ -380,58 +343,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"
[[package]]
name = "lock_api"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765"
dependencies = [
"autocfg",
"scopeguard",
]
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.7.8"
version = "0.16.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e999beba7b6e8345721bd280141ed958096a2e4abdf74f67ff4ce49b4b54e47a"
checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593"
dependencies = [
"hashbrown",
"hashbrown 0.16.1",
]
[[package]]
name = "memchr"
version = "2.7.4"
version = "2.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "miniz_oxide"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a"
dependencies = [
"adler2",
]
[[package]]
name = "object"
version = "0.36.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
dependencies = [
"memchr",
]
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
[[package]]
name = "once_cell"
@ -441,11 +376,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.10.0",
"cfg-if",
"foreign-types",
"libc",
@ -462,14 +397,14 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.101",
"syn 2.0.114",
]
[[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",
@ -478,29 +413,10 @@ dependencies = [
]
[[package]]
name = "parking_lot"
version = "0.11.2"
name = "os_str_bytes"
version = "6.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
dependencies = [
"instant",
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc"
dependencies = [
"cfg-if",
"instant",
"libc",
"redox_syscall",
"smallvec",
"winapi",
]
checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1"
[[package]]
name = "paste"
@ -533,15 +449,6 @@ version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]]
name = "ppv-lite86"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
dependencies = [
"zerocopy",
]
[[package]]
name = "proc-macro-hack"
version = "0.5.20+deprecated"
@ -550,9 +457,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068"
[[package]]
name = "proc-macro2"
version = "1.0.95"
version = "1.0.105"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7"
dependencies = [
"unicode-ident",
]
@ -572,123 +479,73 @@ dependencies = [
[[package]]
name = "quote"
version = "1.0.40"
version = "1.0.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a"
dependencies = [
"proc-macro2",
]
[[package]]
name = "r-efi"
version = "5.2.0"
version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom 0.2.16",
]
[[package]]
name = "redox_syscall"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
dependencies = [
"bitflags 1.3.2",
]
[[package]]
name = "rsds"
version = "0.1.0"
[[package]]
name = "rustc-demangle"
version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[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.10.0",
"errno",
"libc",
"linux-raw-sys",
"windows-sys",
]
[[package]]
name = "ryu"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[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.101",
"syn 2.0.114",
]
[[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]]
@ -715,15 +572,21 @@ dependencies = [
[[package]]
name = "smallvec"
version = "1.15.0"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
name = "sptr"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b9b39299b249ad65f3b7e96443bad61c02ca5cd3589f46cb6d610a0fd6c0d6a"
[[package]]
name = "strsim"
version = "0.8.0"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "syn"
@ -738,9 +601,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.101"
version = "2.0.114"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf"
checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a"
dependencies = [
"proc-macro2",
"quote",
@ -749,46 +612,39 @@ dependencies = [
[[package]]
name = "tempfile"
version = "3.20.0"
version = "3.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1"
checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c"
dependencies = [
"fastrand",
"getrandom 0.3.3",
"getrandom 0.3.4",
"once_cell",
"rustix",
"windows-sys",
]
[[package]]
name = "textwrap"
version = "0.11.0"
name = "termcolor"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
dependencies = [
"unicode-width",
"winapi-util",
]
[[package]]
name = "textwrap"
version = "0.16.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
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",
"tokio-macros",
]
[[package]]
name = "tokio-macros"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.101",
]
[[package]]
@ -801,16 +657,41 @@ dependencies = [
]
[[package]]
name = "unicode-ident"
version = "1.0.18"
name = "tracing"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
dependencies = [
"pin-project-lite",
"tracing-attributes",
"tracing-core",
]
[[package]]
name = "unicode-width"
version = "0.1.14"
name = "tracing-attributes"
version = "0.1.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.114",
]
[[package]]
name = "tracing-core"
version = "0.1.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
dependencies = [
"once_cell",
]
[[package]]
name = "unicode-ident"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
[[package]]
name = "uuid"
@ -827,31 +708,19 @@ version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "vec_map"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "wasi"
version = "0.14.2+wasi-0.2.4"
name = "wasip2"
version = "1.0.1+wasi-0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
dependencies = [
"wit-bindgen-rt",
"wit-bindgen",
]
[[package]]
@ -870,130 +739,64 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
"windows-sys",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-sys"
version = "0.59.0"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows-targets",
"windows-link",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
name = "wit-bindgen"
version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
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",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
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"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "wit-bindgen-rt"
version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
dependencies = [
"bitflags 2.9.1",
]
[[package]]
name = "zerocopy"
version = "0.8.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.101",
]
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
[[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.101",
"syn 2.0.114",
]
[[package]]
name = "zmij"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fc5a66a20078bf1251bde995aa2fdcc4b800c70b5d92dd2c62abc5c60f679f8"

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

Some files were not shown because too many files have changed in this diff Show More