parent
92d7626784
commit
561d98a735
@ -1,2 +1,2 @@
|
||||
e1146536caf20cdf178f8b9bca4f01be89dbcacb SOURCES/389-ds-base-2.4.5.tar.bz2
|
||||
274dec37976c1efde9cbeb458d50bbcd6b244974 SOURCES/389-ds-base-2.5.2.tar.bz2
|
||||
1c8f2d0dfbf39fa8cd86363bf3314351ab21f8d4 SOURCES/jemalloc-5.3.0.tar.bz2
|
||||
|
@ -1,2 +1,2 @@
|
||||
SOURCES/389-ds-base-2.4.5.tar.bz2
|
||||
SOURCES/389-ds-base-2.5.2.tar.bz2
|
||||
SOURCES/jemalloc-5.3.0.tar.bz2
|
||||
|
@ -1,83 +0,0 @@
|
||||
From fcdeec3b876a28e06bb53a60fe502cb702403931 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 a30511c97..07d629475 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.43.0
|
||||
|
@ -0,0 +1,60 @@
|
||||
From 0ff5aa641d619bdcc154c2c94f8f8180bcaec776 Mon Sep 17 00:00:00 2001
|
||||
From: tbordaz <tbordaz@redhat.com>
|
||||
Date: Thu, 29 Aug 2024 10:49:57 +0200
|
||||
Subject: [PATCH] Issue 6312 - In branch 2.5, healthcheck report an invalid
|
||||
warning regarding BDB deprecation (#6313)
|
||||
|
||||
Bug description:
|
||||
during healthcheck, _lint_backend_implementation checks that
|
||||
the instance is not running a BDB backend.
|
||||
This check only applies for instance after 3.0.0
|
||||
|
||||
Fix description:
|
||||
If the instance is newer than 3.0.0 the health check
|
||||
just returns
|
||||
|
||||
relates: #6312
|
||||
|
||||
Reviewed by:
|
||||
---
|
||||
dirsrvtests/tests/suites/healthcheck/healthcheck_test.py | 1 +
|
||||
src/lib389/lib389/backend.py | 4 ++--
|
||||
2 files changed, 3 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/healthcheck/healthcheck_test.py b/dirsrvtests/tests/suites/healthcheck/healthcheck_test.py
|
||||
index 29cca187e..66cf3c7d3 100644
|
||||
--- a/dirsrvtests/tests/suites/healthcheck/healthcheck_test.py
|
||||
+++ b/dirsrvtests/tests/suites/healthcheck/healthcheck_test.py
|
||||
@@ -556,6 +556,7 @@ def test_lint_backend_implementation_wrong_files(topology_st):
|
||||
|
||||
|
||||
@pytest.mark.skipif(get_default_db_lib() == "mdb", reason="Not needed for mdb")
|
||||
+@pytest.mark.skipif(ds_is_older("3.0.0"), reason="mdb and bdb are both supported")
|
||||
def test_lint_backend_implementation(topology_st):
|
||||
"""Test the lint for backend implementation mismatch
|
||||
|
||||
diff --git a/src/lib389/lib389/backend.py b/src/lib389/lib389/backend.py
|
||||
index caee88e6a..0ed00a4a7 100644
|
||||
--- a/src/lib389/lib389/backend.py
|
||||
+++ b/src/lib389/lib389/backend.py
|
||||
@@ -14,7 +14,7 @@ from lib389._constants import DN_LDBM, DN_CHAIN, DN_PLUGIN, DEFAULT_BENAME
|
||||
from lib389.properties import BACKEND_OBJECTCLASS_VALUE, BACKEND_PROPNAME_TO_ATTRNAME, BACKEND_CHAIN_BIND_DN, \
|
||||
BACKEND_CHAIN_BIND_PW, BACKEND_CHAIN_URLS, BACKEND_PROPNAME_TO_ATTRNAME, BACKEND_NAME, \
|
||||
BACKEND_SUFFIX, BACKEND_SAMPLE_ENTRIES, TASK_WAIT
|
||||
-from lib389.utils import normalizeDN, ensure_str, assert_c
|
||||
+from lib389.utils import normalizeDN, ensure_str, assert_c, ds_is_newer
|
||||
from lib389 import Entry
|
||||
|
||||
# Need to fix this ....
|
||||
@@ -513,7 +513,7 @@ class Backend(DSLdapObject):
|
||||
|
||||
def _lint_backend_implementation(self):
|
||||
backend_impl = self._instance.get_db_lib()
|
||||
- if backend_impl == 'bdb':
|
||||
+ if backend_impl == 'bdb' and ds_is_newer('3.0.0', instance=self._instance):
|
||||
result = DSBLE0006
|
||||
result['items'] = [self.lint_uid()]
|
||||
yield result
|
||||
--
|
||||
2.46.0
|
||||
|
@ -1,237 +0,0 @@
|
||||
From 3cd7d30628007f839436c417af6dd8a056c6a165 Mon Sep 17 00:00:00 2001
|
||||
From: Mark Reynolds <mreynolds@redhat.com>
|
||||
Date: Fri, 1 Mar 2024 11:28:17 -0500
|
||||
Subject: [PATCH 2/3] Issue 6112 - RFE - add new operation note for MFA
|
||||
authentications
|
||||
|
||||
Add a new operation note to indicate that a MFA plugin performed the
|
||||
BIND. This implies that the plugin must set the note itself as there is
|
||||
no other way to detect this:
|
||||
|
||||
slapi_pblock_set_flag_operation_notes(pb, SLAPI_OP_NOTE_MFA_AUTH);
|
||||
|
||||
The purpose for this is for auditing needs
|
||||
|
||||
Fixes: https://github.com/389ds/389-ds-base/issues/6112
|
||||
|
||||
Reviewed by: spichugi(Thanks!)
|
||||
---
|
||||
ldap/admin/src/logconv.pl | 37 ++++++++++++++++++-------------
|
||||
ldap/servers/slapd/log.c | 6 ++++-
|
||||
ldap/servers/slapd/result.c | 2 +-
|
||||
ldap/servers/slapd/slapi-plugin.h | 1 +
|
||||
4 files changed, 28 insertions(+), 18 deletions(-)
|
||||
|
||||
diff --git a/ldap/admin/src/logconv.pl b/ldap/admin/src/logconv.pl
|
||||
index 5ba91e99c..10bd5d2aa 100755
|
||||
--- a/ldap/admin/src/logconv.pl
|
||||
+++ b/ldap/admin/src/logconv.pl
|
||||
@@ -2,11 +2,11 @@
|
||||
#
|
||||
# BEGIN COPYRIGHT BLOCK
|
||||
# Copyright (C) 2001 Sun Microsystems, Inc. Used by permission.
|
||||
-# Copyright (C) 2022 Red Hat, Inc.
|
||||
+# Copyright (C) 2010-2024 Red Hat, Inc.
|
||||
# All rights reserved.
|
||||
#
|
||||
# License: GPL (version 3 or any later version).
|
||||
-# See LICENSE for details.
|
||||
+# See LICENSE for details.
|
||||
# END COPYRIGHT BLOCK
|
||||
#
|
||||
|
||||
@@ -218,6 +218,7 @@ my $sslClientFailedCount = 0;
|
||||
my $objectclassTopCount= 0;
|
||||
my $pagedSearchCount = 0;
|
||||
my $invalidFilterCount = 0;
|
||||
+my $mfaCount = 0;
|
||||
my $bindCount = 0;
|
||||
my $filterCount = 0;
|
||||
my $baseCount = 0;
|
||||
@@ -407,7 +408,7 @@ sub statusreport {
|
||||
##########################################
|
||||
# #
|
||||
# Parse Access Logs #
|
||||
-# #
|
||||
+# #
|
||||
##########################################
|
||||
|
||||
if ($files[$#files] =~ m/access.rotationinfo/) {
|
||||
@@ -709,7 +710,7 @@ if($endTime){
|
||||
|
||||
#
|
||||
# Get the start time in seconds
|
||||
-#
|
||||
+#
|
||||
my $logStart = $start;
|
||||
my $startTotal = convertTimeToNanoseconds($logStart);
|
||||
|
||||
@@ -890,6 +891,7 @@ $etimeAvg = $totalEtime / $etimeCount;
|
||||
print sprintf "Average etime (elapsed time): %.9f\n", $etimeAvg;
|
||||
|
||||
print "\n";
|
||||
+print "Multi-factor Authentications: $mfaCount\n";
|
||||
print "Proxied Auth Operations: $proxiedAuthCount\n";
|
||||
print "Persistent Searches: $persistentSrchCount\n";
|
||||
print "Internal Operations: $internalOpCount\n";
|
||||
@@ -1758,7 +1760,7 @@ if ($usage =~ /j/i || $verb eq "yes"){
|
||||
$recCount++;
|
||||
}
|
||||
if ($objectclassTopCount > ($srchCount *.25)){
|
||||
- print "\n $recCount. You have a high number of searches that query the entire search base. Although this is not necessarily bad, it could be resource intensive if the search base contains many entries.\n";
|
||||
+ print "\n $recCount. You have a high number of searches that query the entire search base. Although this is not necessarily bad, it could be resource intensive if the search base contains many entries.\n";
|
||||
$recCount++;
|
||||
}
|
||||
if ($recCount == 1){
|
||||
@@ -1792,7 +1794,7 @@ sub displayUsage {
|
||||
|
||||
print " -h, --help help/usage\n";
|
||||
print " -d, --rootDN <Directory Managers DN> default is \"cn=directory manager\"\n";
|
||||
- print " -D, --data <Location for temporary data files> default is \"/tmp\"\n";
|
||||
+ print " -D, --data <Location for temporary data files> default is \"/tmp\"\n";
|
||||
print " -s, --sizeLimit <Number of results to return per catagory> default is 20\n";
|
||||
print " -X, --excludeIP <IP address to exclude from connection stats> E.g. Load balancers\n";
|
||||
print " -v, --version show version of tool\n";
|
||||
@@ -1800,8 +1802,8 @@ sub displayUsage {
|
||||
print " E.g. \"[28/Mar/2002:13:14:22 -0800]\"\n";
|
||||
print " -E, --endTime <time to stop analyzing logfile>\n";
|
||||
print " E.g. \"[28/Mar/2002:13:24:62 -0800]\"\n";
|
||||
- print " -m, --reportFileSecs <CSV output file - per second stats>\n";
|
||||
- print " -M, --reportFileMins <CSV output file - per minute stats>\n";
|
||||
+ print " -m, --reportFileSecs <CSV output file - per second stats>\n";
|
||||
+ print " -M, --reportFileMins <CSV output file - per minute stats>\n";
|
||||
print " -B, --bind <ALL | ANONYMOUS | \"Actual Bind DN\">\n";
|
||||
print " -T, --minEtime <minimum etime to report unindexed searches>\n";
|
||||
print " -V, --verbose <enable verbose output - includes all stats listed below>\n";
|
||||
@@ -2288,6 +2290,9 @@ sub parseLineNormal
|
||||
if (m/ RESULT err=/ && m/ notes=[A-Z,]*P/){
|
||||
$pagedSearchCount++;
|
||||
}
|
||||
+ if (m/ RESULT err=/ && m/ notes=[A-Z,]*M/){
|
||||
+ $mfaCount++;
|
||||
+ }
|
||||
if (m/ RESULT err=/ && m/ notes=[A-Z,]*F/){
|
||||
$invalidFilterCount++;
|
||||
$con = "";
|
||||
@@ -2318,7 +2323,7 @@ sub parseLineNormal
|
||||
if ($vlvconn[$i] eq $con && $vlvop[$i] eq $op){ $vlvNotesACount++; $isVlvNotes="1";}
|
||||
}
|
||||
if($isVlvNotes == 0){
|
||||
- # We don't want to record vlv unindexed searches for our regular "bad"
|
||||
+ # We don't want to record vlv unindexed searches for our regular "bad"
|
||||
# unindexed search stat, as VLV unindexed searches aren't that bad
|
||||
$unindexedSrchCountNotesA++;
|
||||
if($reportStats){ inc_stats('notesA',$s_stats,$m_stats); }
|
||||
@@ -2345,7 +2350,7 @@ sub parseLineNormal
|
||||
if ($vlvconn[$i] eq $con && $vlvop[$i] eq $op){ $vlvNotesUCount++; $isVlvNotes="1";}
|
||||
}
|
||||
if($isVlvNotes == 0){
|
||||
- # We don't want to record vlv unindexed searches for our regular "bad"
|
||||
+ # We don't want to record vlv unindexed searches for our regular "bad"
|
||||
# unindexed search stat, as VLV unindexed searches aren't that bad
|
||||
$unindexedSrchCountNotesU++;
|
||||
if($reportStats){ inc_stats('notesU',$s_stats,$m_stats); }
|
||||
@@ -2586,7 +2591,7 @@ sub parseLineNormal
|
||||
if ($errcode ne "0"){ $errorCount++;}
|
||||
else { $successCount++;}
|
||||
}
|
||||
- if ($_ =~ /etime= *([0-9.]+)/ ) {
|
||||
+ if ($_ =~ /etime= *([0-9.]+)/ ) {
|
||||
my $etime_val = $1;
|
||||
$totalEtime = $totalEtime + $1;
|
||||
$etimeCount++;
|
||||
@@ -2608,10 +2613,10 @@ sub parseLineNormal
|
||||
if ($reportStats){ inc_stats_val('optime',$optime_val,$s_stats,$m_stats); }
|
||||
}
|
||||
if ($_ =~ / tag=101 / || $_ =~ / tag=111 / || $_ =~ / tag=100 / || $_ =~ / tag=115 /){
|
||||
- if ($_ =~ / nentries= *([0-9]+)/i ){
|
||||
+ if ($_ =~ / nentries= *([0-9]+)/i ){
|
||||
my $nents = $1;
|
||||
- if ($usage =~ /n/i || $verb eq "yes"){
|
||||
- $hashes->{nentries}->{$nents}++;
|
||||
+ if ($usage =~ /n/i || $verb eq "yes"){
|
||||
+ $hashes->{nentries}->{$nents}++;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2621,7 +2626,7 @@ sub parseLineNormal
|
||||
if (m/ EXT oid=/){
|
||||
$extopCount++;
|
||||
my $oid;
|
||||
- if ($_ =~ /oid=\" *([0-9\.]+)/i ){
|
||||
+ if ($_ =~ /oid=\" *([0-9\.]+)/i ){
|
||||
$oid = $1;
|
||||
if ($usage =~ /x/i || $verb eq "yes"){$hashes->{oid}->{$oid}++; }
|
||||
}
|
||||
@@ -2921,7 +2926,7 @@ printClients
|
||||
my $IPcount = "1";
|
||||
|
||||
foreach my $ip ( keys %connList ){ # Loop over all the IP addresses
|
||||
- foreach my $bc (@bindConns){ # Loop over each bind conn number and compare it
|
||||
+ foreach my $bc (@bindConns){ # Loop over each bind conn number and compare it
|
||||
if($connList{$ip} =~ / $bc /){
|
||||
print(" [$IPcount] $ip\n");
|
||||
$IPcount++;
|
||||
diff --git a/ldap/servers/slapd/log.c b/ldap/servers/slapd/log.c
|
||||
index 4aa905576..2c7bd933b 100644
|
||||
--- a/ldap/servers/slapd/log.c
|
||||
+++ b/ldap/servers/slapd/log.c
|
||||
@@ -3892,6 +3892,7 @@ slapi_log_security(Slapi_PBlock *pb, const char *event_type, const char *msg)
|
||||
int isroot = 0;
|
||||
int rc = 0;
|
||||
uint64_t conn_id = 0;
|
||||
+ uint32_t operation_notes = 0;
|
||||
int32_t op_id = 0;
|
||||
json_object *log_json = NULL;
|
||||
|
||||
@@ -3916,6 +3917,8 @@ slapi_log_security(Slapi_PBlock *pb, const char *event_type, const char *msg)
|
||||
client_ip = pb_conn->c_ipaddr;
|
||||
server_ip = pb_conn->c_serveripaddr;
|
||||
ldap_version = pb_conn->c_ldapversion;
|
||||
+ operation_notes = slapi_pblock_get_operation_notes(pb);
|
||||
+
|
||||
if (saslmech) {
|
||||
external_bind = !strcasecmp(saslmech, LDAP_SASL_EXTERNAL);
|
||||
}
|
||||
@@ -3982,7 +3985,8 @@ slapi_log_security(Slapi_PBlock *pb, const char *event_type, const char *msg)
|
||||
break;
|
||||
default:
|
||||
/* Simple auth */
|
||||
- PR_snprintf(method_and_mech, sizeof(method_and_mech), "SIMPLE");
|
||||
+ PR_snprintf(method_and_mech, sizeof(method_and_mech), "%s",
|
||||
+ (operation_notes & SLAPI_OP_NOTE_MFA_AUTH) ? "SIMPLE/MFA" : "SIMPLE");
|
||||
}
|
||||
|
||||
/* Get the time */
|
||||
diff --git a/ldap/servers/slapd/result.c b/ldap/servers/slapd/result.c
|
||||
index 56ba6db8b..97af5a2b8 100644
|
||||
--- a/ldap/servers/slapd/result.c
|
||||
+++ b/ldap/servers/slapd/result.c
|
||||
@@ -1946,11 +1946,11 @@ static struct slapi_note_map notemap[] = {
|
||||
{SLAPI_OP_NOTE_SIMPLEPAGED, "P", "Paged Search"},
|
||||
{SLAPI_OP_NOTE_FULL_UNINDEXED, "A", "Fully Unindexed Filter"},
|
||||
{SLAPI_OP_NOTE_FILTER_INVALID, "F", "Filter Element Missing From Schema"},
|
||||
+ {SLAPI_OP_NOTE_MFA_AUTH, "M", "Multi-factor Authentication"},
|
||||
};
|
||||
|
||||
#define SLAPI_NOTEMAP_COUNT (sizeof(notemap) / sizeof(struct slapi_note_map))
|
||||
|
||||
-
|
||||
/*
|
||||
* fill buf with a string representation of the bits present in notes.
|
||||
*
|
||||
diff --git a/ldap/servers/slapd/slapi-plugin.h b/ldap/servers/slapd/slapi-plugin.h
|
||||
index 4853e143b..12bc1f0aa 100644
|
||||
--- a/ldap/servers/slapd/slapi-plugin.h
|
||||
+++ b/ldap/servers/slapd/slapi-plugin.h
|
||||
@@ -7323,6 +7323,7 @@ typedef enum _slapi_op_note_t {
|
||||
SLAPI_OP_NOTE_SIMPLEPAGED = 0x02,
|
||||
SLAPI_OP_NOTE_FULL_UNINDEXED = 0x04,
|
||||
SLAPI_OP_NOTE_FILTER_INVALID = 0x08,
|
||||
+ SLAPI_OP_NOTE_MFA_AUTH = 0x10,
|
||||
} slapi_op_note_t;
|
||||
|
||||
|
||||
--
|
||||
2.44.0
|
||||
|
@ -0,0 +1,237 @@
|
||||
From af27f433ec14bcaf070108ab0b6af64ad1153a11 Mon Sep 17 00:00:00 2001
|
||||
From: progier389 <progier@redhat.com>
|
||||
Date: Fri, 6 Sep 2024 18:07:17 +0200
|
||||
Subject: [PATCH] Issue 6316 - lmdb reindex is broken if index type is
|
||||
specified (#6318)
|
||||
|
||||
While reindexing using task or offline reindex, if the attribute name contains the index type (for example :eq,pres)
|
||||
Then the attribute is not reindexed. Problem occurs when lmdb is used, things are working fine with bdb.
|
||||
Solution: strip the index type in reindex as it is done in bdb case.
|
||||
Anyway the reindex design requires that for a given attribute all the configured index types must be rebuild.
|
||||
|
||||
Issue: #6316
|
||||
|
||||
Reviewed by: @tbordaz, @droideck (Thanks!)
|
||||
---
|
||||
.../tests/suites/indexes/regression_test.py | 141 +++++++++++++++++-
|
||||
.../slapd/back-ldbm/db-mdb/mdb_import.c | 10 +-
|
||||
2 files changed, 147 insertions(+), 4 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/indexes/regression_test.py b/dirsrvtests/tests/suites/indexes/regression_test.py
|
||||
index c385f5ca4..b077b529a 100644
|
||||
--- a/dirsrvtests/tests/suites/indexes/regression_test.py
|
||||
+++ b/dirsrvtests/tests/suites/indexes/regression_test.py
|
||||
@@ -10,6 +10,9 @@ import time
|
||||
import os
|
||||
import pytest
|
||||
import ldap
|
||||
+import logging
|
||||
+import glob
|
||||
+import re
|
||||
from lib389._constants import DEFAULT_BENAME, DEFAULT_SUFFIX
|
||||
from lib389.backend import Backend, Backends, DatabaseConfig
|
||||
from lib389.cos import CosClassicDefinition, CosClassicDefinitions, CosTemplate
|
||||
@@ -31,6 +34,8 @@ SUFFIX2 = 'dc=example2,dc=com'
|
||||
BENAME2 = 'be2'
|
||||
|
||||
DEBUGGING = os.getenv("DEBUGGING", default=False)
|
||||
+logging.getLogger(__name__).setLevel(logging.INFO)
|
||||
+log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
@@ -83,6 +88,7 @@ def add_a_group_with_users(request, topo):
|
||||
'cn': USER_NAME,
|
||||
'uidNumber': f'{num}',
|
||||
'gidNumber': f'{num}',
|
||||
+ 'description': f'Description for {USER_NAME}',
|
||||
'homeDirectory': f'/home/{USER_NAME}'
|
||||
})
|
||||
users_list.append(user)
|
||||
@@ -95,9 +101,10 @@ def add_a_group_with_users(request, topo):
|
||||
# If the server crashed, start it again to do the cleanup
|
||||
if not topo.standalone.status():
|
||||
topo.standalone.start()
|
||||
- for user in users_list:
|
||||
- user.delete()
|
||||
- group.delete()
|
||||
+ if not DEBUGGING:
|
||||
+ for user in users_list:
|
||||
+ user.delete()
|
||||
+ group.delete()
|
||||
|
||||
request.addfinalizer(fin)
|
||||
|
||||
@@ -124,6 +131,38 @@ def set_small_idlistscanlimit(request, topo):
|
||||
|
||||
request.addfinalizer(fin)
|
||||
|
||||
+
|
||||
+@pytest.fixture(scope="function")
|
||||
+def set_description_index(request, topo, add_a_group_with_users):
|
||||
+ """
|
||||
+ Set some description values and description index without reindexing.
|
||||
+ """
|
||||
+ inst = topo.standalone
|
||||
+ backends = Backends(inst)
|
||||
+ backend = backends.get(DEFAULT_BENAME)
|
||||
+ indexes = backend.get_indexes()
|
||||
+ attr = 'description'
|
||||
+
|
||||
+ def fin(always=False):
|
||||
+ if always or not DEBUGGING:
|
||||
+ try:
|
||||
+ idx = indexes.get(attr)
|
||||
+ idx.delete()
|
||||
+ except ldap.NO_SUCH_OBJECT:
|
||||
+ pass
|
||||
+
|
||||
+ request.addfinalizer(fin)
|
||||
+ fin(always=True)
|
||||
+ index = indexes.create(properties={
|
||||
+ 'cn': attr,
|
||||
+ 'nsSystemIndex': 'false',
|
||||
+ 'nsIndexType': ['eq', 'pres', 'sub']
|
||||
+ })
|
||||
+ # Restart needed with lmdb (to open the dbi handle)
|
||||
+ inst.restart()
|
||||
+ return (indexes, attr)
|
||||
+
|
||||
+
|
||||
#unstable or unstatus tests, skipped for now
|
||||
@pytest.mark.flaky(max_runs=2, min_passes=1)
|
||||
@pytest.mark.skipif(ds_is_older("1.4.4.4"), reason="Not implemented")
|
||||
@@ -347,6 +386,102 @@ def test_task_status(topo):
|
||||
assert reindex_task.get_exit_code() == 0
|
||||
|
||||
|
||||
+def count_keys(inst, bename, attr, prefix=''):
|
||||
+ indexfile = os.path.join(inst.dbdir, bename, attr + '.db')
|
||||
+ # (bdb - we should also accept a version number for .db suffix)
|
||||
+ for f in glob.glob(f'{indexfile}*'):
|
||||
+ indexfile = f
|
||||
+
|
||||
+ inst.stop()
|
||||
+ output = inst.dbscan(None, None, args=['-f', indexfile, '-A'], stopping=False).decode()
|
||||
+ inst.start()
|
||||
+ count = 0
|
||||
+ regexp = f'^KEY: {re.escape(prefix)}'
|
||||
+ for match in re.finditer(regexp, output, flags=re.MULTILINE):
|
||||
+ count += 1
|
||||
+ log.info(f"count_keys found {count} keys starting with '{prefix}' in {indexfile}")
|
||||
+ return count
|
||||
+
|
||||
+
|
||||
+def test_reindex_task_with_type(topo, set_description_index):
|
||||
+ """Check that reindex task works as expected when index type is specified.
|
||||
+
|
||||
+ :id: 0c7f2fda-69f6-11ef-9eb8-083a88554478
|
||||
+ :setup: Standalone instance
|
||||
+ - with 100 users having description attribute
|
||||
+ - with description:eq,pres,sub index entry but not yet reindexed
|
||||
+ :steps:
|
||||
+ 1. Set description in suffix entry
|
||||
+ 2. Count number of equality keys in description index
|
||||
+ 3. Start a Reindex task on description:eq,pres and wait for completion
|
||||
+ 4. Check the task status and exit code
|
||||
+ 5. Count the equality, presence and substring keys in description index
|
||||
+ 6. Start a Reindex task on description and wait for completion
|
||||
+ 7. Check the task status and exit code
|
||||
+ 8. Count the equality, presence and substring keys in description index
|
||||
+
|
||||
+ :expectedresults:
|
||||
+ 1. Success
|
||||
+ 2. Should be either no key (bdb) or a single one (lmdb)
|
||||
+ 3. Success
|
||||
+ 4. Success
|
||||
+ 5. Should have: more equality keys than in step 2
|
||||
+ one presence key
|
||||
+ some substrings keys
|
||||
+ 6. Success
|
||||
+ 7. Success
|
||||
+ 8. Should have same counts than in step 5
|
||||
+ """
|
||||
+ (indexes, attr) = set_description_index
|
||||
+ inst = topo.standalone
|
||||
+ if not inst.is_dbi_supported():
|
||||
+ pytest.skip('This test requires that dbscan supports -A option')
|
||||
+ # modify indexed value
|
||||
+ Domain(inst, DEFAULT_SUFFIX).replace(attr, f'test_before_reindex')
|
||||
+
|
||||
+ keys1 = count_keys(inst, DEFAULT_BENAME, attr, prefix='=')
|
||||
+ assert keys1 <= 1
|
||||
+
|
||||
+ tasks = Tasks(topo.standalone)
|
||||
+ # completed reindex tasks MUST have a status because freeipa check it.
|
||||
+
|
||||
+ # Reindex attr with eq,pres types
|
||||
+ log.info(f'Reindex {attr} with eq,pres types')
|
||||
+ tasks.reindex(
|
||||
+ suffix=DEFAULT_SUFFIX,
|
||||
+ attrname=f'{attr}:eq,pres',
|
||||
+ args={TASK_WAIT: True}
|
||||
+ )
|
||||
+ reindex_task = Task(topo.standalone, tasks.dn)
|
||||
+ assert reindex_task.status()
|
||||
+ assert reindex_task.get_exit_code() == 0
|
||||
+
|
||||
+ keys2e = count_keys(inst, DEFAULT_BENAME, attr, prefix='=')
|
||||
+ keys2p = count_keys(inst, DEFAULT_BENAME, attr, prefix='+')
|
||||
+ keys2s = count_keys(inst, DEFAULT_BENAME, attr, prefix='*')
|
||||
+ assert keys2e > keys1
|
||||
+ assert keys2p > 0
|
||||
+ assert keys2s > 0
|
||||
+
|
||||
+ # Reindex attr without types
|
||||
+ log.info(f'Reindex {attr} without types')
|
||||
+ tasks.reindex(
|
||||
+ suffix=DEFAULT_SUFFIX,
|
||||
+ attrname=attr,
|
||||
+ args={TASK_WAIT: True}
|
||||
+ )
|
||||
+ reindex_task = Task(topo.standalone, tasks.dn)
|
||||
+ assert reindex_task.status()
|
||||
+ assert reindex_task.get_exit_code() == 0
|
||||
+
|
||||
+ keys3e = count_keys(inst, DEFAULT_BENAME, attr, prefix='=')
|
||||
+ keys3p = count_keys(inst, DEFAULT_BENAME, attr, prefix='+')
|
||||
+ keys3s = count_keys(inst, DEFAULT_BENAME, attr, prefix='*')
|
||||
+ assert keys3e == keys2e
|
||||
+ assert keys3p == keys2p
|
||||
+ assert keys3s == keys2s
|
||||
+
|
||||
+
|
||||
def test_task_and_be(topo, add_backend_and_ldif_50K_users):
|
||||
"""Check that backend is writable after finishing a tasks
|
||||
|
||||
diff --git a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_import.c b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_import.c
|
||||
index d57146953..ce2151174 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/db-mdb/mdb_import.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/db-mdb/mdb_import.c
|
||||
@@ -1150,6 +1150,8 @@ process_db2index_attrs(Slapi_PBlock *pb, ImportCtx_t *ctx)
|
||||
* TBD
|
||||
*/
|
||||
char **attrs = NULL;
|
||||
+ char *attrname = NULL;
|
||||
+ char *pt = NULL;
|
||||
int i;
|
||||
|
||||
slapi_pblock_get(pb, SLAPI_DB2INDEX_ATTRS, &attrs);
|
||||
@@ -1157,7 +1159,13 @@ process_db2index_attrs(Slapi_PBlock *pb, ImportCtx_t *ctx)
|
||||
for (i = 0; attrs && attrs[i]; i++) {
|
||||
switch (attrs[i][0]) {
|
||||
case 't': /* attribute type to index */
|
||||
- slapi_ch_array_add(&ctx->indexAttrs, slapi_ch_strdup(attrs[i] + 1));
|
||||
+ attrname = slapi_ch_strdup(attrs[i] + 1);
|
||||
+ /* Strip index type */
|
||||
+ pt = strchr(attrname, ':');
|
||||
+ if (pt != NULL) {
|
||||
+ *pt = '\0';
|
||||
+ }
|
||||
+ slapi_ch_array_add(&ctx->indexAttrs, attrname);
|
||||
break;
|
||||
case 'T': /* VLV Search to index */
|
||||
slapi_ch_array_add(&ctx->indexVlvs, get_vlv_dbname(attrs[i] + 1));
|
||||
--
|
||||
2.46.0
|
||||
|
@ -1,54 +0,0 @@
|
||||
From 189e078f574f586f6cff6f80081eded2c22c8868 Mon Sep 17 00:00:00 2001
|
||||
From: Mark Reynolds <mreynolds@redhat.com>
|
||||
Date: Tue, 26 Mar 2024 11:19:10 -0400
|
||||
Subject: [PATCH 3/3] Issue 6133 - Move slapi_pblock_set_flag_operation_notes()
|
||||
to slapi-plugin.h
|
||||
|
||||
Description:
|
||||
|
||||
slapi_pblock_set_flag_operation_notes() is currently only available in slapi-private.h, but with the latest changes at add "notes=M" it needs to be available to plugins.
|
||||
|
||||
relates: https://github.com/389ds/389-ds-base/issues/6133
|
||||
|
||||
Reviewed by: spichugi(Thanks!)
|
||||
---
|
||||
ldap/servers/slapd/slapi-plugin.h | 10 ++++++++++
|
||||
ldap/servers/slapd/slapi-private.h | 1 -
|
||||
2 files changed, 10 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/ldap/servers/slapd/slapi-plugin.h b/ldap/servers/slapd/slapi-plugin.h
|
||||
index 12bc1f0aa..5d4af7c20 100644
|
||||
--- a/ldap/servers/slapd/slapi-plugin.h
|
||||
+++ b/ldap/servers/slapd/slapi-plugin.h
|
||||
@@ -7326,6 +7326,16 @@ typedef enum _slapi_op_note_t {
|
||||
SLAPI_OP_NOTE_MFA_AUTH = 0x10,
|
||||
} slapi_op_note_t;
|
||||
|
||||
+/**
|
||||
+ * Set an operation note on an operation. This will append a notes keyword
|
||||
+ * in the access log result line for this operation
|
||||
+ *
|
||||
+ * \param pb - The slapi_pblock structure
|
||||
+ * \param opnotes
|
||||
+ * \return void
|
||||
+ */
|
||||
+void slapi_pblock_set_operation_notes(Slapi_PBlock *pb, uint32_t opnotes);
|
||||
+
|
||||
|
||||
/* Allows controls to be passed before operation object is created */
|
||||
#define SLAPI_CONTROLS_ARG 58
|
||||
diff --git a/ldap/servers/slapd/slapi-private.h b/ldap/servers/slapd/slapi-private.h
|
||||
index 17eedc2de..ee7659ac0 100644
|
||||
--- a/ldap/servers/slapd/slapi-private.h
|
||||
+++ b/ldap/servers/slapd/slapi-private.h
|
||||
@@ -1510,7 +1510,6 @@ struct slapi_entry *slapi_pblock_get_pw_entry(Slapi_PBlock *pb);
|
||||
void slapi_pblock_set_pw_entry(Slapi_PBlock *pb, struct slapi_entry *entry);
|
||||
|
||||
uint32_t slapi_pblock_get_operation_notes(Slapi_PBlock *pb);
|
||||
-void slapi_pblock_set_operation_notes(Slapi_PBlock *pb, uint32_t opnotes);
|
||||
void slapi_pblock_set_flag_operation_notes(Slapi_PBlock *pb, uint32_t opflag);
|
||||
void slapi_pblock_set_result_text_if_empty(Slapi_PBlock *pb, char *text);
|
||||
|
||||
--
|
||||
2.44.0
|
||||
|
@ -1,108 +0,0 @@
|
||||
From 23956cfb86a312318667fb9376322574fa8ec7f4 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 1245feb31..e4abd9907 100644
|
||||
--- a/dirsrvtests/tests/suites/password/password_test.py
|
||||
+++ b/dirsrvtests/tests/suites/password/password_test.py
|
||||
@@ -63,6 +63,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 a20984e0b..fb65d58b3 100644
|
||||
--- a/ldap/servers/slapd/modify.c
|
||||
+++ b/ldap/servers/slapd/modify.c
|
||||
@@ -762,8 +762,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 */
|
||||
@@ -930,8 +932,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.41.0
|
||||
|
@ -1,213 +0,0 @@
|
||||
From 5cfa136c48c477765cb20b007ad441ed21534e86 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 964facae5..5390a0f9c 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):
|
||||
"""
|
||||
@@ -159,6 +169,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 86bc825fe..bdac0a616 100644
|
||||
--- a/ldap/servers/slapd/back-ldbm/index.c
|
||||
+++ b/ldap/servers/slapd/back-ldbm/index.c
|
||||
@@ -74,6 +74,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
|
||||
@@ -802,65 +828,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.44.0
|
||||
|
@ -1,145 +0,0 @@
|
||||
From 52a9ee6556a0467f5134fb6392ff1681a38f3252 Mon Sep 17 00:00:00 2001
|
||||
From: Pierre Rogier <progier@redhat.com>
|
||||
Date: Fri, 14 Jun 2024 13:27:10 +0200
|
||||
Subject: [PATCH] CVE-2024-5953
|
||||
|
||||
---
|
||||
.../tests/suites/password/regression_test.py | 51 ++++++++++++++++++-
|
||||
ldap/servers/plugins/pwdstorage/md5_pwd.c | 9 +++-
|
||||
ldap/servers/plugins/pwdstorage/pbkdf2_pwd.c | 6 +++
|
||||
3 files changed, 64 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/password/regression_test.py b/dirsrvtests/tests/suites/password/regression_test.py
|
||||
index 4876ff435..160d6f01d 100644
|
||||
--- a/dirsrvtests/tests/suites/password/regression_test.py
|
||||
+++ b/dirsrvtests/tests/suites/password/regression_test.py
|
||||
@@ -8,11 +8,12 @@
|
||||
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, DEFAULT_BENAME
|
||||
from lib389 import Entry
|
||||
from lib389.topologies import topology_m1 as topo_supplier
|
||||
-from lib389.idm.user import UserAccounts
|
||||
+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
|
||||
@@ -40,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 +327,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.44.0
|
||||
|
@ -1,25 +0,0 @@
|
||||
From 323f74c69f84a8482413ecd73cf61d09cfc4a0a1 Mon Sep 17 00:00:00 2001
|
||||
From: Thierry Bordaz <tbordaz@redhat.com>
|
||||
Date: Mon, 24 Jun 2024 15:51:28 +0200
|
||||
Subject: [PATCH] CVE-2024-6237
|
||||
|
||||
---
|
||||
ldap/servers/plugins/syntaxes/inchain.c | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/ldap/servers/plugins/syntaxes/inchain.c b/ldap/servers/plugins/syntaxes/inchain.c
|
||||
index df19c973b..0a6a04e9f 100644
|
||||
--- a/ldap/servers/plugins/syntaxes/inchain.c
|
||||
+++ b/ldap/servers/plugins/syntaxes/inchain.c
|
||||
@@ -277,7 +277,7 @@ inchain_values2keys(Slapi_PBlock *pb, Slapi_Value **vals, Slapi_Value ***ivals,
|
||||
slapi_pblock_get(pb, SLAPI_SEARCH_TARGET_SDN, &base_sdn);
|
||||
|
||||
if (! slapi_attr_is_dn_syntax_type(mrTYPE)) {
|
||||
- slapi_log_err(SLAPI_LOG_ERR, "inchain", "Requires distinguishedName syntax. AttributeDescription %s is not distinguishedName\n");
|
||||
+ slapi_log_err(SLAPI_LOG_ERR, "inchain", "Requires distinguishedName syntax. AttributeDescription %s is not distinguishedName\n", mrTYPE);
|
||||
result = (Slapi_Value **)slapi_ch_calloc(1, sizeof(Slapi_Value *));
|
||||
*ivals = result;
|
||||
return(0);
|
||||
--
|
||||
2.44.0
|
||||
|
@ -1,192 +0,0 @@
|
||||
From 9501c34df01e35f483201a4bba12a93091b2b13f Mon Sep 17 00:00:00 2001
|
||||
From: progier389 <progier@redhat.com>
|
||||
Date: Thu, 13 Jun 2024 15:17:36 +0200
|
||||
Subject: [PATCH] Issue 5772 - ONE LEVEL search fails to return sub-suffixes
|
||||
(#6219)
|
||||
|
||||
Problem: ONE LEVEL scoped search fails to return sub-suffixes entries
|
||||
Reason: When such search is done, a one level search is done on the main suffix and base search are done on any matching sub-suffix. But main suffix is processed search (to ensure that parent entries are returned before children ones when searching subtree) and ldbm_back_search change the filter to (&(parentid=xxx)old_filter) so the filter test reject the entry on the sub-suffixes.
|
||||
Solution: Revert the backend list when doing one level search so that the sub-suffixes are processed first
|
||||
and restore the base dn for the main suffix.
|
||||
Alternative rejected: reset the filter when discivering a sub-suffix. Not so easy because filter is altered by the rewriteres.
|
||||
And systematic duplication is an useless overhead if there is no matching sub-suffixes (which is the usual case)
|
||||
|
||||
Issue: #5772
|
||||
|
||||
Reviewed by: @tbordaz, @droideck (Thanks!)
|
||||
|
||||
(cherry picked from commit 407bdaa00d1da9f5ff53d66a2e012b17ad658907)
|
||||
---
|
||||
.../suites/mapping_tree/regression_test.py | 36 +++++++++++++++++-
|
||||
ldap/servers/slapd/filterentry.c | 38 ++++++++++++++++++-
|
||||
ldap/servers/slapd/opshared.c | 22 ++++++++++-
|
||||
3 files changed, 92 insertions(+), 4 deletions(-)
|
||||
|
||||
diff --git a/dirsrvtests/tests/suites/mapping_tree/regression_test.py b/dirsrvtests/tests/suites/mapping_tree/regression_test.py
|
||||
index f4877da2b..c3fc2c0a2 100644
|
||||
--- a/dirsrvtests/tests/suites/mapping_tree/regression_test.py
|
||||
+++ b/dirsrvtests/tests/suites/mapping_tree/regression_test.py
|
||||
@@ -92,7 +92,6 @@ EXPECTED_ENTRIES = (("dc=parent", 39), ("dc=child1,dc=parent", 13), ("dc=child2,
|
||||
@pytest.mark.skipif(not has_orphan_attribute, reason = "compatibility attribute not yet implemented in this version")
|
||||
def test_sub_suffixes(topo, orphan_param):
|
||||
""" check the entries found on suffix/sub-suffix
|
||||
- used int
|
||||
|
||||
:id: 5b4421c2-d851-11ec-a760-482ae39447e5
|
||||
:feature: mapping-tree
|
||||
@@ -122,8 +121,41 @@ def test_sub_suffixes(topo, orphan_param):
|
||||
log.info(f'Verifying domain component entries count for search under {suffix} ...')
|
||||
entries = topo.standalone.search_s(suffix, ldap.SCOPE_SUBTREE, "(dc=*)")
|
||||
assert len(entries) == expected
|
||||
- log.info('Found {expected} domain component entries as expected while searching {suffix}')
|
||||
+ log.info(f'Found {expected} domain component entries as expected while searching {suffix}')
|
||||
|
||||
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)
|
||||
+
|
||||
diff --git a/ldap/servers/slapd/filterentry.c b/ldap/servers/slapd/filterentry.c
|
||||
index 4de4aa66e..d2c7e3082 100644
|
||||
--- a/ldap/servers/slapd/filterentry.c
|
||||
+++ b/ldap/servers/slapd/filterentry.c
|
||||
@@ -240,6 +240,36 @@ slapi_filter_test_ext(
|
||||
}
|
||||
|
||||
|
||||
+static const char *
|
||||
+filter_type_as_string(int filter_type)
|
||||
+{
|
||||
+ switch (filter_type) {
|
||||
+ case LDAP_FILTER_AND:
|
||||
+ return "&";
|
||||
+ case LDAP_FILTER_OR:
|
||||
+ return "|";
|
||||
+ case LDAP_FILTER_NOT:
|
||||
+ return "!";
|
||||
+ case LDAP_FILTER_EQUALITY:
|
||||
+ return "=";
|
||||
+ case LDAP_FILTER_SUBSTRINGS:
|
||||
+ return "*";
|
||||
+ case LDAP_FILTER_GE:
|
||||
+ return ">=";
|
||||
+ case LDAP_FILTER_LE:
|
||||
+ return "<=";
|
||||
+ case LDAP_FILTER_PRESENT:
|
||||
+ return "=*";
|
||||
+ case LDAP_FILTER_APPROX:
|
||||
+ return "~";
|
||||
+ case LDAP_FILTER_EXT:
|
||||
+ return "EXT";
|
||||
+ default:
|
||||
+ return "?";
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+
|
||||
int
|
||||
test_ava_filter(
|
||||
Slapi_PBlock *pb,
|
||||
@@ -253,7 +283,13 @@ test_ava_filter(
|
||||
{
|
||||
int rc;
|
||||
|
||||
- slapi_log_err(SLAPI_LOG_FILTER, "test_ava_filter", "=>\n");
|
||||
+ if (slapi_is_loglevel_set(SLAPI_LOG_FILTER)) {
|
||||
+ char *val = slapi_berval_get_string_copy(&ava->ava_value);
|
||||
+ char buf[BUFSIZ];
|
||||
+ slapi_log_err(SLAPI_LOG_FILTER, "test_ava_filter", "=> AVA: %s%s%s\n",
|
||||
+ ava->ava_type, filter_type_as_string(ftype), escape_string(val, buf));
|
||||
+ slapi_ch_free_string(&val);
|
||||
+ }
|
||||
|
||||
*access_check_done = 0;
|
||||
|
||||
diff --git a/ldap/servers/slapd/opshared.c b/ldap/servers/slapd/opshared.c
|
||||
index f77043afa..540597f45 100644
|
||||
--- a/ldap/servers/slapd/opshared.c
|
||||
+++ b/ldap/servers/slapd/opshared.c
|
||||
@@ -219,6 +219,7 @@ cache_return_target_entry(Slapi_PBlock *pb, Slapi_Backend *be, Slapi_Operation *
|
||||
operation_set_target_entry_id(operation, 0);
|
||||
}
|
||||
}
|
||||
+
|
||||
/*
|
||||
* Returns: 0 - if the operation is successful
|
||||
* < 0 - if operation fails.
|
||||
@@ -481,6 +482,20 @@ op_shared_search(Slapi_PBlock *pb, int send_result)
|
||||
while (be_list[index] && be_list[index + 1]) {
|
||||
index++;
|
||||
}
|
||||
+ if (scope == LDAP_SCOPE_ONELEVEL) {
|
||||
+ /*
|
||||
+ * ONE LEVEL searches may ends up on multiple backends
|
||||
+ * with a ONE LEVEL search on a suffix and a BASE search on its
|
||||
+ * subsuffixes. Because LDAP_SCOPE_ONELEVEL rewrite the filter
|
||||
+ * the backends should be reversed so that the BASE search(es)
|
||||
+ * are done first (with the original filter).
|
||||
+ */
|
||||
+ for (int idx = 0; idx <= index/2; idx++) {
|
||||
+ be = be_list[index-idx];
|
||||
+ be_list[index-idx] = be_list[idx];
|
||||
+ be_list[idx] = be;
|
||||
+ }
|
||||
+ }
|
||||
be = be_list[index];
|
||||
} else {
|
||||
be = NULL;
|
||||
@@ -779,7 +794,6 @@ op_shared_search(Slapi_PBlock *pb, int send_result)
|
||||
(slapi_sdn_get_ndn_len(basesdn) == 0)) {
|
||||
int tmp_scope = LDAP_SCOPE_BASE;
|
||||
slapi_pblock_set(pb, SLAPI_SEARCH_SCOPE, &tmp_scope);
|
||||
-
|
||||
if (free_sdn) {
|
||||
slapi_pblock_get(pb, SLAPI_SEARCH_TARGET_SDN, &sdn);
|
||||
slapi_sdn_free(&sdn);
|
||||
@@ -790,6 +804,12 @@ op_shared_search(Slapi_PBlock *pb, int send_result)
|
||||
} else if (slapi_sdn_issuffix(basesdn, be_suffix)) {
|
||||
int tmp_scope = LDAP_SCOPE_ONELEVEL;
|
||||
slapi_pblock_set(pb, SLAPI_SEARCH_SCOPE, &tmp_scope);
|
||||
+ if (free_sdn) {
|
||||
+ slapi_pblock_get(pb, SLAPI_SEARCH_TARGET_SDN, &sdn);
|
||||
+ slapi_sdn_free(&sdn);
|
||||
+ sdn = slapi_sdn_dup(basesdn);
|
||||
+ slapi_pblock_set(pb, SLAPI_SEARCH_TARGET_SDN, (void *)sdn);
|
||||
+ }
|
||||
} else {
|
||||
slapi_sdn_done(&monitorsdn);
|
||||
goto next_be;
|
||||
--
|
||||
2.45.2
|
||||
|
@ -1,220 +0,0 @@
|
||||
From 45e14d64c47080951d9bfa3bcfe8c267d01ce251 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 2a7102828..4de4aa66e 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.45.2
|
||||
|
Loading…
Reference in new issue