From bbbf1457b1f04886efc686b35c61671011fc6499 Mon Sep 17 00:00:00 2001 From: Tomas Halman Date: Tue, 9 Sep 2025 16:07:27 +0200 Subject: [PATCH 1/6] sdap: Create list of attributes for range retrieval --- src/providers/ldap/sdap.c | 39 +++++++++++++++++++++++++++++---- src/providers/ldap/sdap.h | 3 ++- src/providers/ldap/sdap_async.c | 6 ++--- src/tests/cmocka/test_sdap.c | 18 +++++++-------- 4 files changed, 49 insertions(+), 17 deletions(-) diff --git a/src/providers/ldap/sdap.c b/src/providers/ldap/sdap.c index a64d8c157ee..149ffc52d3d 100644 --- a/src/providers/ldap/sdap.c +++ b/src/providers/ldap/sdap.c @@ -405,7 +405,8 @@ int sdap_parse_entry(TALLOC_CTX *memctx, struct sdap_handle *sh, struct sdap_msg *sm, struct sdap_attr_map *map, int attrs_num, struct sysdb_attrs **_attrs, - bool disable_range_retrieval) + bool disable_range_retrieval, + const char ***_next_attrs) { struct sysdb_attrs *attrs; BerElement *ber = NULL; @@ -420,9 +421,14 @@ int sdap_parse_entry(TALLOC_CTX *memctx, bool base64; char *base_attr; uint32_t range_offset; + const char **next_attrs = NULL; + size_t next_attrs_count = 0; TALLOC_CTX *tmp_ctx = talloc_new(NULL); if (!tmp_ctx) return ENOMEM; + if (_next_attrs != NULL) { + *_next_attrs = NULL; + } lerrno = 0; ret = ldap_set_option(sh->ldap, LDAP_OPT_RESULT_CODE, &lerrno); if (ret != LDAP_OPT_SUCCESS) { @@ -499,9 +505,31 @@ int sdap_parse_entry(TALLOC_CTX *memctx, /* This attribute contained range values and needs more to * be retrieved */ - /* TODO: return the set of attributes that need additional retrieval - * For now, we'll continue below and treat it as regular values. - */ + next_attrs = talloc_realloc(tmp_ctx, next_attrs, const char *, + next_attrs_count + 2 + + (next_attrs_count > 0 ? 0 : 1)); + if (next_attrs == NULL) { + ret = ENOMEM; + goto done; + } + if (next_attrs_count == 0) { + /* we need to ask objectClass to correctly + * identify the object later + */ + next_attrs[next_attrs_count++] = "objectClass"; + } + next_attrs[next_attrs_count] = talloc_asprintf(next_attrs, + "%s;range=%d-*", + base_attr, + range_offset); + if (next_attrs[next_attrs_count] == NULL) { + ret = ENOMEM; + goto done; + } + DEBUG(SSSDBG_TRACE_INTERNAL, + "Attribute [%s] for next range request found\n", + next_attrs[next_attrs_count]); + next_attrs[++next_attrs_count] = NULL; /* FALLTHROUGH */ case ECANCELED: /* FALLTHROUGH */ @@ -633,6 +661,9 @@ int sdap_parse_entry(TALLOC_CTX *memctx, PROBE(SDAP_PARSE_ENTRY_DONE); *_attrs = talloc_steal(memctx, attrs); + if (_next_attrs != NULL && disable_range_retrieval == false) { + *_next_attrs = talloc_steal(memctx, next_attrs); + } ret = EOK; done: diff --git a/src/providers/ldap/sdap.h b/src/providers/ldap/sdap.h index aaed7d0fd74..598a1096d86 100644 --- a/src/providers/ldap/sdap.h +++ b/src/providers/ldap/sdap.h @@ -652,7 +652,8 @@ int sdap_parse_entry(TALLOC_CTX *memctx, struct sdap_handle *sh, struct sdap_msg *sm, struct sdap_attr_map *map, int attrs_num, struct sysdb_attrs **_attrs, - bool disable_range_retrieval); + bool disable_range_retrieval, + const char *** _next_attrs); errno_t sdap_parse_deref(TALLOC_CTX *mem_ctx, struct sdap_attr_map_info *minfo, diff --git a/src/providers/ldap/sdap_async.c b/src/providers/ldap/sdap_async.c index 6d662fbdea8..f34bc0a5665 100644 --- a/src/providers/ldap/sdap_async.c +++ b/src/providers/ldap/sdap_async.c @@ -2075,7 +2075,7 @@ static errno_t sdap_get_and_parse_generic_parse_entry(struct sdap_handle *sh, ret = sdap_parse_entry(state, sh, msg, state->map, state->map_num_attrs, - &attrs, disable_range_rtrvl); + &attrs, disable_range_rtrvl, NULL); if (ret != EOK) { DEBUG(SSSDBG_MINOR_FAILURE, "sdap_parse_entry failed [%d]: %s\n", ret, strerror(ret)); @@ -2563,7 +2563,7 @@ static errno_t sdap_sd_search_parse_entry(struct sdap_handle *sh, ret = sdap_parse_entry(state, sh, msg, NULL, 0, - &attrs, disable_range_rtrvl); + &attrs, disable_range_rtrvl, NULL); if (ret != EOK) { DEBUG(SSSDBG_MINOR_FAILURE, "sdap_parse_entry failed [%d]: %s\n", ret, strerror(ret)); @@ -2844,7 +2844,7 @@ static errno_t sdap_asq_search_parse_entry(struct sdap_handle *sh, ret = sdap_parse_entry(res[mi], sh, msg, map, num_attrs, - &res[mi]->attrs, disable_range_rtrvl); + &res[mi]->attrs, disable_range_rtrvl, NULL); if (ret != EOK) { DEBUG(SSSDBG_MINOR_FAILURE, "sdap_parse_entry failed [%d]: %s\n", ret, strerror(ret)); diff --git a/src/tests/cmocka/test_sdap.c b/src/tests/cmocka/test_sdap.c index 047591e9e84..a9149cf5b8d 100644 --- a/src/tests/cmocka/test_sdap.c +++ b/src/tests/cmocka/test_sdap.c @@ -307,7 +307,7 @@ void test_parse_with_map(void **state) ret = sdap_parse_entry(test_ctx, &test_ctx->sh, &test_ctx->sm, map, SDAP_OPTS_USER, - &attrs, false); + &attrs, false, NULL); assert_int_equal(ret, ERR_OK); assert_int_equal(attrs->num, 4); @@ -368,7 +368,7 @@ void test_parse_no_map(void **state) set_entry_parse(&test_nomap_entry); ret = sdap_parse_entry(test_ctx, &test_ctx->sh, &test_ctx->sm, - NULL, 0, &attrs, false); + NULL, 0, &attrs, false, NULL); assert_int_equal(ret, ERR_OK); assert_int_equal(attrs->num, 3); @@ -413,7 +413,7 @@ void test_parse_no_attrs(void **state) ret = sdap_parse_entry(test_ctx, &test_ctx->sh, &test_ctx->sm, map, SDAP_OPTS_USER, - &attrs, false); + &attrs, false, NULL); assert_int_equal(ret, ERR_OK); assert_int_equal(attrs->num, 1); @@ -460,7 +460,7 @@ void test_parse_dups(void **state) ret = sdap_parse_entry(test_ctx, &test_ctx->sh, &test_ctx->sm, map, SDAP_OPTS_USER, - &attrs, false); + &attrs, false, NULL); assert_int_equal(ret, ERR_OK); assert_int_equal(attrs->num, 3); @@ -613,7 +613,7 @@ void test_parse_secondary_oc(void **state) ret = sdap_parse_entry(test_ctx, &test_ctx->sh, &test_ctx->sm, map, SDAP_OPTS_GROUP, - &attrs, false); + &attrs, false, NULL); assert_int_equal(ret, ERR_OK); talloc_free(map); @@ -647,7 +647,7 @@ void test_parse_bad_oc(void **state) ret = sdap_parse_entry(test_ctx, &test_ctx->sh, &test_ctx->sm, map, SDAP_OPTS_USER, - &attrs, false); + &attrs, false, NULL); assert_int_not_equal(ret, ERR_OK); talloc_free(map); @@ -680,7 +680,7 @@ void test_parse_no_oc(void **state) ret = sdap_parse_entry(test_ctx, &test_ctx->sh, &test_ctx->sm, map, SDAP_OPTS_USER, - &attrs, false); + &attrs, false, NULL); assert_int_not_equal(ret, ERR_OK); talloc_free(map); @@ -715,7 +715,7 @@ void test_parse_no_dn(void **state) ret = sdap_parse_entry(test_ctx, &test_ctx->sh, &test_ctx->sm, map, SDAP_OPTS_USER, - &attrs, false); + &attrs, false, NULL); assert_int_not_equal(ret, ERR_OK); talloc_free(map); @@ -1159,7 +1159,7 @@ static void test_sdap_get_primary_name(void **state) assert_int_equal(ret, ERR_OK); ret = sdap_parse_entry(test_ctx, &test_ctx->sh, &test_ctx->sm, - map, SDAP_OPTS_IPHOST, &attrs, false); + map, SDAP_OPTS_IPHOST, &attrs, false, NULL); assert_int_equal(ret, ERR_OK); assert_int_equal(attrs->num, 3); From ec1b0ad470fc28352cf27c31001596ba111c632e Mon Sep 17 00:00:00 2001 From: thalman Date: Thu, 11 Sep 2025 15:43:01 +0000 Subject: [PATCH 2/6] sdap: implement range retrieval for generic search Implement support for range retrieval of attribute values. When a multi-valued attribute has a large number of values Active Directory response only with first batch of values. A common case is the `memberOf` attribute when a user belongs to many groups. This is controlled by AD `MaxValRange` values (default 1500) policy. Range retrieval is indicated in the response and provides information which part of the batch is provided (e. g., memberOf;range=0-1499). SSSD must then perform additional requests to fetch the remaining values (e. g., `memberOf;range=1500-*`).. The implementation is written to allow extension to other search types (e. g., root DSE search), although that is not currently required. Resolves: https://github.com/SSSD/sssd/issues/8102 --- src/providers/ldap/sdap.c | 2 +- src/providers/ldap/sdap_async.c | 169 +++++++++++++++++++++++++------- 2 files changed, 137 insertions(+), 34 deletions(-) diff --git a/src/providers/ldap/sdap.c b/src/providers/ldap/sdap.c index 149ffc52d3d..76119d53079 100644 --- a/src/providers/ldap/sdap.c +++ b/src/providers/ldap/sdap.c @@ -530,7 +530,7 @@ int sdap_parse_entry(TALLOC_CTX *memctx, "Attribute [%s] for next range request found\n", next_attrs[next_attrs_count]); next_attrs[++next_attrs_count] = NULL; - /* FALLTHROUGH */ + break; case ECANCELED: /* FALLTHROUGH */ case EOK: diff --git a/src/providers/ldap/sdap_async.c b/src/providers/ldap/sdap_async.c index f34bc0a5665..a99d5b71e5b 100644 --- a/src/providers/ldap/sdap_async.c +++ b/src/providers/ldap/sdap_async.c @@ -1277,8 +1277,22 @@ struct sdap_reply { static errno_t add_to_reply(TALLOC_CTX *mem_ctx, struct sdap_reply *sreply, - struct sysdb_attrs *msg) + struct sysdb_attrs *msg, + bool range_retrieval_update) { + int ret; + + if (range_retrieval_update) { + if (sreply->reply_count == 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Can't update empty array\n"); + return EINVAL; + } + + ret = sysdb_attrs_copy(msg, sreply->reply[sreply->reply_count - 1]); + talloc_free(msg); + return ret; + } + if (sreply->reply == NULL || sreply->reply_max == sreply->reply_count) { sreply->reply_max += REPLY_REALLOC_INCREMENT; sreply->reply = talloc_realloc(mem_ctx, sreply->reply, @@ -1452,7 +1466,9 @@ static void sdap_print_server(struct sdap_handle *sh) /* ==Generic Search exposing all options======================= */ typedef errno_t (*sdap_parse_cb)(struct sdap_handle *sh, struct sdap_msg *msg, - void *pvt); + bool range_retrieval_step, + void *pvt, + const char ***_next_attrs); struct sdap_get_generic_ext_state { struct tevent_context *ev; @@ -1462,6 +1478,8 @@ struct sdap_get_generic_ext_state { int scope; const char *filter; const char **attrs; + const char **next_attrs; + bool range_retrieval_step; int timeout; int sizelimit; @@ -1508,6 +1526,7 @@ sdap_get_generic_ext_send(TALLOC_CTX *memctx, int scope, const char *filter, const char **attrs, + bool range_retrieval_step, LDAPControl **serverctrls, LDAPControl **clientctrls, int sizelimit, @@ -1532,6 +1551,8 @@ sdap_get_generic_ext_send(TALLOC_CTX *memctx, state->scope = scope; state->filter = filter; state->attrs = attrs; + state->range_retrieval_step = range_retrieval_step; + state->next_attrs = NULL; state->op = NULL; state->sizelimit = sizelimit; state->timeout = timeout; @@ -1757,6 +1778,7 @@ static void sdap_get_generic_op_finished(struct sdap_op *op, struct sdap_get_generic_ext_state); char *errmsg = NULL; char **refs = NULL; + const char **next_attrs = NULL; int result; int ret; int lret; @@ -1799,13 +1821,14 @@ static void sdap_get_generic_op_finished(struct sdap_op *op, break; case LDAP_RES_SEARCH_ENTRY: - ret = state->parse_cb(state->sh, reply, state->cb_data); + ret = state->parse_cb(state->sh, reply, state->range_retrieval_step, + state->cb_data, &next_attrs); if (ret != EOK) { DEBUG(SSSDBG_CRIT_FAILURE, "reply parsing callback failed.\n"); tevent_req_error(req, ret); return; } - + state->next_attrs = talloc_steal(state, next_attrs); sdap_unlock_next_reply(state->op); break; @@ -1930,7 +1953,8 @@ static int sdap_get_generic_ext_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, size_t *ref_count, - char ***refs) + char ***refs, + const char ***next_attrs) { struct sdap_get_generic_ext_state *state = tevent_req_data(req, struct sdap_get_generic_ext_state); @@ -1948,21 +1972,58 @@ sdap_get_generic_ext_recv(struct tevent_req *req, *refs = talloc_steal(mem_ctx, state->refs); } + if (next_attrs) { + *next_attrs = talloc_steal(mem_ctx, state->next_attrs); + } + return EOK; } +static struct tevent_req * +sdap_get_generic_ext_send_next(TALLOC_CTX *ctx, struct tevent_req *subreq, + const char **attrs, bool range_retrieval_step) +{ + struct tevent_req *next_subreq = NULL; + struct sdap_get_generic_ext_state *state; + + state = tevent_req_data(subreq, struct sdap_get_generic_ext_state); + next_subreq = sdap_get_generic_ext_send(ctx, + state->ev, + state->opts, + state->sh, + state->search_base, + state->scope, + state->filter, + attrs, + range_retrieval_step, + state->serverctrls, + state->clientctrls, + state->sizelimit, + state->timeout, + state->parse_cb, + state->cb_data, + state->flags); + if (next_subreq) { + talloc_steal(next_subreq, attrs); + } + return next_subreq; +} + /* This search handler can be used by most calls */ static void generic_ext_search_handler(struct tevent_req *subreq, - struct sdap_options *opts) + struct sdap_options *opts, + tevent_req_fn done_callback) { struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); int ret; size_t ref_count, i; - char **refs; + char **refs = NULL; + const char **next_attrs = NULL; + struct tevent_req *next_subreq = NULL; - ret = sdap_get_generic_ext_recv(subreq, req, &ref_count, &refs); - talloc_zfree(subreq); + ret = sdap_get_generic_ext_recv(subreq, req, + &ref_count, &refs, &next_attrs); if (ret != EOK) { if (ret == ETIMEDOUT) { @@ -1975,8 +2036,7 @@ static void generic_ext_search_handler(struct tevent_req *subreq, "sdap_get_generic_ext_recv request failed: [%d]: %s\n", ret, sss_strerror(ret)); } - tevent_req_error(req, ret); - return; + goto done; } if (ref_count > 0) { @@ -1991,8 +2051,32 @@ static void generic_ext_search_handler(struct tevent_req *subreq, } } + if (next_attrs != NULL) { + if (done_callback == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Range retrieval is not implemented " + "for this type of request.\n"); + goto done; + } + next_subreq = sdap_get_generic_ext_send_next(req, subreq, + next_attrs, true); + if (next_subreq == NULL) { + ret = ENOMEM; + goto done; + } + tevent_req_set_callback(next_subreq, done_callback, req); + } + + done: + talloc_zfree(subreq); talloc_free(refs); - tevent_req_done(req); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + if (next_subreq == NULL) { + tevent_req_done(req); + } } /* ==Generic Search exposing all options======================= */ @@ -2007,7 +2091,9 @@ struct sdap_get_and_parse_generic_state { static void sdap_get_and_parse_generic_done(struct tevent_req *subreq); static errno_t sdap_get_and_parse_generic_parse_entry(struct sdap_handle *sh, struct sdap_msg *msg, - void *pvt); + bool range_retrieval_step, + void *pvt, + const char ***next_attrs); struct tevent_req *sdap_get_and_parse_generic_send(TALLOC_CTX *memctx, struct tevent_context *ev, @@ -2048,7 +2134,7 @@ struct tevent_req *sdap_get_and_parse_generic_send(TALLOC_CTX *memctx, } subreq = sdap_get_generic_ext_send(state, ev, opts, sh, search_base, - scope, filter, attrs, serverctrls, + scope, filter, attrs, false, serverctrls, clientctrls, sizelimit, timeout, sdap_get_and_parse_generic_parse_entry, state, flags); @@ -2063,7 +2149,9 @@ struct tevent_req *sdap_get_and_parse_generic_send(TALLOC_CTX *memctx, static errno_t sdap_get_and_parse_generic_parse_entry(struct sdap_handle *sh, struct sdap_msg *msg, - void *pvt) + bool range_retrieval_step, + void *pvt, + const char ***_next_attrs) { errno_t ret; struct sysdb_attrs *attrs; @@ -2075,14 +2163,14 @@ static errno_t sdap_get_and_parse_generic_parse_entry(struct sdap_handle *sh, ret = sdap_parse_entry(state, sh, msg, state->map, state->map_num_attrs, - &attrs, disable_range_rtrvl, NULL); + &attrs, disable_range_rtrvl, _next_attrs); if (ret != EOK) { DEBUG(SSSDBG_MINOR_FAILURE, "sdap_parse_entry failed [%d]: %s\n", ret, strerror(ret)); return ret; } - ret = add_to_reply(state, &state->sreply, attrs); + ret = add_to_reply(state, &state->sreply, attrs, range_retrieval_step); if (ret != EOK) { talloc_free(attrs); DEBUG(SSSDBG_CRIT_FAILURE, "add_to_reply failed.\n"); @@ -2100,7 +2188,7 @@ static void sdap_get_and_parse_generic_done(struct tevent_req *subreq) struct sdap_get_and_parse_generic_state *state = tevent_req_data(req, struct sdap_get_and_parse_generic_state); - return generic_ext_search_handler(subreq, state->opts); + return generic_ext_search_handler(subreq, state->opts, sdap_get_and_parse_generic_done); } int sdap_get_and_parse_generic_recv(struct tevent_req *req, @@ -2206,7 +2294,9 @@ static int sdap_x_deref_search_ctrls_destructor(void *ptr); static errno_t sdap_x_deref_parse_entry(struct sdap_handle *sh, struct sdap_msg *msg, - void *pvt); + bool not_used, + void *pvt, + const char ***_not_used); struct sdap_x_deref_search_state { struct sdap_handle *sh; struct sdap_op *op; @@ -2264,7 +2354,7 @@ sdap_x_deref_search_send(TALLOC_CTX *memctx, struct tevent_context *ev, subreq = sdap_get_generic_ext_send(state, ev, opts, sh, base_dn, filter == NULL ? LDAP_SCOPE_BASE : LDAP_SCOPE_SUBTREE, - filter, attrs, + filter, attrs, false, state->ctrls, NULL, 0, timeout, sdap_x_deref_parse_entry, state, SDAP_SRCH_FLG_PAGING); @@ -2311,7 +2401,9 @@ static int sdap_x_deref_create_control(struct sdap_handle *sh, static errno_t sdap_x_deref_parse_entry(struct sdap_handle *sh, struct sdap_msg *msg, - void *pvt) + bool not_used, + void *pvt, + const char ***_not_used) { errno_t ret; LDAPControl **ctrls = NULL; @@ -2410,7 +2502,7 @@ static void sdap_x_deref_search_done(struct tevent_req *subreq) struct sdap_x_deref_search_state *state = tevent_req_data(req, struct sdap_x_deref_search_state); - return generic_ext_search_handler(subreq, state->opts); + return generic_ext_search_handler(subreq, state->opts, NULL); } static int sdap_x_deref_search_ctrls_destructor(void *ptr) @@ -2455,12 +2547,14 @@ struct sdap_sd_search_state { }; static int sdap_sd_search_create_control(struct sdap_handle *sh, - int val, - LDAPControl **ctrl); + int val, + LDAPControl **ctrl); static int sdap_sd_search_ctrls_destructor(void *ptr); static errno_t sdap_sd_search_parse_entry(struct sdap_handle *sh, struct sdap_msg *msg, - void *pvt); + bool not_used, + void *pvt, + const char ***_not_used); static void sdap_sd_search_done(struct tevent_req *subreq); struct tevent_req * @@ -2495,7 +2589,8 @@ sdap_sd_search_send(TALLOC_CTX *memctx, struct tevent_context *ev, DEBUG(SSSDBG_TRACE_FUNC, "Searching entry [%s] using SD\n", base_dn); subreq = sdap_get_generic_ext_send(state, ev, opts, sh, base_dn, - LDAP_SCOPE_BASE, "(objectclass=*)", attrs, + LDAP_SCOPE_BASE, "(objectclass=*)", + attrs, false, state->ctrls, NULL, 0, timeout, sdap_sd_search_parse_entry, state, SDAP_SRCH_FLG_PAGING); @@ -2551,7 +2646,9 @@ static int sdap_sd_search_create_control(struct sdap_handle *sh, static errno_t sdap_sd_search_parse_entry(struct sdap_handle *sh, struct sdap_msg *msg, - void *pvt) + bool not_used, + void *pvt, + const char ***_not_used) { errno_t ret; struct sysdb_attrs *attrs; @@ -2570,7 +2667,7 @@ static errno_t sdap_sd_search_parse_entry(struct sdap_handle *sh, return ret; } - ret = add_to_reply(state, &state->sreply, attrs); + ret = add_to_reply(state, &state->sreply, attrs, false); if (ret != EOK) { talloc_free(attrs); DEBUG(SSSDBG_CRIT_FAILURE, "add_to_reply failed.\n"); @@ -2592,7 +2689,8 @@ static void sdap_sd_search_done(struct tevent_req *subreq) ret = sdap_get_generic_ext_recv(subreq, state, &state->ref_count, - &state->refs); + &state->refs, + NULL); talloc_zfree(subreq); if (ret != EOK) { @@ -2665,7 +2763,9 @@ static int sdap_asq_search_create_control(struct sdap_handle *sh, static int sdap_asq_search_ctrls_destructor(void *ptr); static errno_t sdap_asq_search_parse_entry(struct sdap_handle *sh, struct sdap_msg *msg, - void *pvt); + bool not_used, + void *pvt, + const char ***_not_used); static void sdap_asq_search_done(struct tevent_req *subreq); static struct tevent_req * @@ -2706,7 +2806,8 @@ sdap_asq_search_send(TALLOC_CTX *memctx, struct tevent_context *ev, DEBUG(SSSDBG_TRACE_FUNC, "Dereferencing entry [%s] using ASQ\n", base_dn); subreq = sdap_get_generic_ext_send(state, ev, opts, sh, base_dn, - LDAP_SCOPE_BASE, NULL, attrs, + LDAP_SCOPE_BASE, NULL, + attrs, false, state->ctrls, NULL, 0, timeout, sdap_asq_search_parse_entry, state, SDAP_SRCH_FLG_PAGING); @@ -2760,7 +2861,9 @@ static int sdap_asq_search_create_control(struct sdap_handle *sh, static errno_t sdap_asq_search_parse_entry(struct sdap_handle *sh, struct sdap_msg *msg, - void *pvt) + bool not_used, + void *pvt, + const char ***_not_used) { errno_t ret; struct sdap_asq_search_state *state = @@ -2880,7 +2983,7 @@ static void sdap_asq_search_done(struct tevent_req *subreq) struct sdap_asq_search_state *state = tevent_req_data(req, struct sdap_asq_search_state); - return generic_ext_search_handler(subreq, state->opts); + return generic_ext_search_handler(subreq, state->opts, NULL); } static int sdap_asq_search_ctrls_destructor(void *ptr) From 3e30a6c6459332ffa2fb1a1061c3e958f5339eef Mon Sep 17 00:00:00 2001 From: Tomas Halman Date: Fri, 19 Sep 2025 15:51:44 +0200 Subject: [PATCH 3/6] sysdb: Join two attribute lists without checking When performing range retrieval Active Directory guaranties that the follwing response does not contain overlaps (in the same connection). It is expensive to compare all the attribute values against each other. We can relay on AD and append the subsequent response without checking. --- src/db/sysdb.c | 28 +++++++++++++++++++++++++--- src/db/sysdb.h | 3 +++ src/providers/ldap/sdap_async.c | 4 +++- 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/db/sysdb.c b/src/db/sysdb.c index 1faa11b16e0..db4b72671a2 100644 --- a/src/db/sysdb.c +++ b/src/db/sysdb.c @@ -758,7 +758,11 @@ int sysdb_attrs_copy_values(struct sysdb_attrs *src, return ret; } -errno_t sysdb_attrs_copy(struct sysdb_attrs *src, struct sysdb_attrs *dst) +static +errno_t sysdb_attrs_copy_ext(struct sysdb_attrs *src, + struct sysdb_attrs *dst, + const char **exclude, + bool check) { int ret; size_t c; @@ -769,9 +773,14 @@ errno_t sysdb_attrs_copy(struct sysdb_attrs *src, struct sysdb_attrs *dst) } for (c = 0; c < src->num; c++) { + if (string_in_list(src->a[c].name, discard_const(exclude), false)) { + continue; + } for (d = 0; d < src->a[c].num_values; d++) { - ret = sysdb_attrs_add_val_safe(dst, src->a[c].name, - &src->a[c].values[d]); + ret = sysdb_attrs_add_val_int(dst, + src->a[c].name, + check, + &src->a[c].values[d]); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_add_val failed.\n"); return ret; @@ -782,6 +791,19 @@ errno_t sysdb_attrs_copy(struct sysdb_attrs *src, struct sysdb_attrs *dst) return EOK; } +errno_t sysdb_attrs_copy(struct sysdb_attrs *src, + struct sysdb_attrs *dst) +{ + return sysdb_attrs_copy_ext(src, dst, NULL, true); +} + +errno_t sysdb_attrs_join(struct sysdb_attrs *src, + struct sysdb_attrs *dst, + const char **exclude) +{ + return sysdb_attrs_copy_ext(src, dst, exclude, false); +} + int sysdb_attrs_users_from_str_list(struct sysdb_attrs *attrs, const char *attr_name, const char *domain, diff --git a/src/db/sysdb.h b/src/db/sysdb.h index d0f10e2ccc6..4f6ea28acc5 100644 --- a/src/db/sysdb.h +++ b/src/db/sysdb.h @@ -461,6 +461,9 @@ int sysdb_attrs_copy_values(struct sysdb_attrs *src, struct sysdb_attrs *dst, const char *name); errno_t sysdb_attrs_copy(struct sysdb_attrs *src, struct sysdb_attrs *dst); +errno_t sysdb_attrs_join(struct sysdb_attrs *src, + struct sysdb_attrs *dst, + const char **exclude); int sysdb_attrs_get_el(struct sysdb_attrs *attrs, const char *name, struct ldb_message_element **el); int sysdb_attrs_get_el_ext(struct sysdb_attrs *attrs, const char *name, diff --git a/src/providers/ldap/sdap_async.c b/src/providers/ldap/sdap_async.c index a99d5b71e5b..9afac0599ca 100644 --- a/src/providers/ldap/sdap_async.c +++ b/src/providers/ldap/sdap_async.c @@ -1281,6 +1281,7 @@ static errno_t add_to_reply(TALLOC_CTX *mem_ctx, bool range_retrieval_update) { int ret; + static const char *exclude[] = {"objectClass", NULL}; if (range_retrieval_update) { if (sreply->reply_count == 0) { @@ -1288,7 +1289,8 @@ static errno_t add_to_reply(TALLOC_CTX *mem_ctx, return EINVAL; } - ret = sysdb_attrs_copy(msg, sreply->reply[sreply->reply_count - 1]); + ret = sysdb_attrs_join(msg, sreply->reply[sreply->reply_count - 1], + exclude); talloc_free(msg); return ret; } From 46ffe03c62c9f6318b952de7e4131747c11f2c25 Mon Sep 17 00:00:00 2001 From: Tomas Halman Date: Mon, 20 Oct 2025 14:21:42 +0200 Subject: [PATCH 4/6] man: Update range retrieval information --- src/man/sssd-ldap.5.xml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/man/sssd-ldap.5.xml b/src/man/sssd-ldap.5.xml index cc26458e758..413ca018d42 100644 --- a/src/man/sssd-ldap.5.xml +++ b/src/man/sssd-ldap.5.xml @@ -729,9 +729,10 @@ this option prevents SSSD from parsing the range extension. As a result large groups will appear as they have no members. - This option does not enable SSSD to read subsequent - ranges. To retrieve all members of a group, you must - increase the MaxValRange setting in Active Directory. + + + By default, SSSD performs additional queries to obtain + subsequent ranges and to complete information. Default: False From 6e637c7aee669f48297facc3b816e81427f5fe60 Mon Sep 17 00:00:00 2001 From: Tomas Halman Date: Tue, 21 Oct 2025 21:59:03 +0200 Subject: [PATCH 5/6] sdap: replace objectClass string with SYSDB_OBJECTCLASS --- src/providers/ldap/sdap.c | 13 +++++++------ src/providers/ldap/sdap_async.c | 9 +++++---- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/providers/ldap/sdap.c b/src/providers/ldap/sdap.c index 76119d53079..cc19da7ced2 100644 --- a/src/providers/ldap/sdap.c +++ b/src/providers/ldap/sdap.c @@ -458,7 +458,7 @@ int sdap_parse_entry(TALLOC_CTX *memctx, if (ret) goto done; if (map) { - vals = ldap_get_values_len(sh->ldap, sm->msg, "objectClass"); + vals = ldap_get_values_len(sh->ldap, sm->msg, SYSDB_OBJECTCLASS); if (!vals) { DEBUG(SSSDBG_CRIT_FAILURE, "Unknown entry type, no objectClasses found!\n"); @@ -516,7 +516,7 @@ int sdap_parse_entry(TALLOC_CTX *memctx, /* we need to ask objectClass to correctly * identify the object later */ - next_attrs[next_attrs_count++] = "objectClass"; + next_attrs[next_attrs_count++] = SYSDB_OBJECTCLASS; } next_attrs[next_attrs_count] = talloc_asprintf(next_attrs, "%s;range=%d-*", @@ -753,7 +753,7 @@ errno_t sdap_parse_deref(TALLOC_CTX *mem_ctx, ocs = NULL; for (dval = dref->attrVals; dval != NULL; dval = dval->next) { - if (strcasecmp("objectClass", dval->type) == 0) { + if (strcasecmp(SYSDB_OBJECTCLASS, dval->type) == 0) { if (dval->vals == NULL) { DEBUG(SSSDBG_CONF_SETTINGS, "No value for objectClass, skipping\n"); @@ -1644,7 +1644,7 @@ int build_attrs_from_map(TALLOC_CTX *memctx, } /* first attribute is "objectclass" not the specific one */ - attrs[0] = talloc_strdup(memctx, "objectClass"); + attrs[0] = talloc_strdup(memctx, SYSDB_OBJECTCLASS); if (!attrs[0]) return ENOMEM; /* add the others */ @@ -2053,11 +2053,12 @@ errno_t sdap_get_primary_fqdn_list(struct sss_domain_info *domain, char *sdap_make_oc_list(TALLOC_CTX *mem_ctx, struct sdap_attr_map *map) { if (map[SDAP_OC_GROUP_ALT].name == NULL) { - return talloc_asprintf(mem_ctx, "objectClass=%s", + return talloc_asprintf(mem_ctx, SYSDB_OBJECTCLASS"=%s", map[SDAP_OC_GROUP].name); } else { return talloc_asprintf(mem_ctx, - "|(objectClass=%s)(objectClass=%s)", + "|("SYSDB_OBJECTCLASS"=%s)" + "("SYSDB_OBJECTCLASS"=%s)", map[SDAP_OC_GROUP].name, map[SDAP_OC_GROUP_ALT].name); } diff --git a/src/providers/ldap/sdap_async.c b/src/providers/ldap/sdap_async.c index 9afac0599ca..cdd67b647df 100644 --- a/src/providers/ldap/sdap_async.c +++ b/src/providers/ldap/sdap_async.c @@ -1197,7 +1197,7 @@ struct tevent_req *sdap_get_rootdse_send(TALLOC_CTX *memctx, subreq = sdap_get_generic_send(state, ev, opts, sh, "", LDAP_SCOPE_BASE, - "(objectclass=*)", attrs, NULL, 0, + "("SYSDB_OBJECTCLASS"=*)", attrs, NULL, 0, dp_opt_get_int(state->opts->basic, SDAP_SEARCH_TIMEOUT), false); @@ -1281,7 +1281,7 @@ static errno_t add_to_reply(TALLOC_CTX *mem_ctx, bool range_retrieval_update) { int ret; - static const char *exclude[] = {"objectClass", NULL}; + static const char *exclude[] = {SYSDB_OBJECTCLASS, NULL}; if (range_retrieval_update) { if (sreply->reply_count == 0) { @@ -2591,7 +2591,8 @@ sdap_sd_search_send(TALLOC_CTX *memctx, struct tevent_context *ev, DEBUG(SSSDBG_TRACE_FUNC, "Searching entry [%s] using SD\n", base_dn); subreq = sdap_get_generic_ext_send(state, ev, opts, sh, base_dn, - LDAP_SCOPE_BASE, "(objectclass=*)", + LDAP_SCOPE_BASE, + "("SYSDB_OBJECTCLASS"=*)", attrs, false, state->ctrls, NULL, 0, timeout, sdap_sd_search_parse_entry, @@ -2915,7 +2916,7 @@ static errno_t sdap_asq_search_parse_entry(struct sdap_handle *sh, } /* Find all suitable maps in the list */ - vals = ldap_get_values_len(sh->ldap, msg->msg, "objectClass"); + vals = ldap_get_values_len(sh->ldap, msg->msg, SYSDB_OBJECTCLASS); if (!vals) { DEBUG(SSSDBG_OP_FAILURE, "Unknown entry type, no objectClass found for DN [%s]!\n", dn); From 323ec0a8d21484501a3daca284a1ed56067ef79c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20V=C3=A1vra?= Date: Thu, 16 Oct 2025 11:17:39 +0200 Subject: [PATCH 6/6] Tests: Add range retrieval test for RHEL-75484 --- src/tests/system/tests/test_ad.py | 75 +++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/src/tests/system/tests/test_ad.py b/src/tests/system/tests/test_ad.py index 6c30ecbcd2b..ae69e4bdeb3 100644 --- a/src/tests/system/tests/test_ad.py +++ b/src/tests/system/tests/test_ad.py @@ -111,3 +111,78 @@ def test_authentication__using_the_users_email_address(client: Client, ad: AD, m assert client.auth.parametrize(method).password( "uSEr_3@alias-dOMain.com", "Secret123" ), "User uSEr_3@alias-dOMain.com failed login!" + + +@pytest.mark.topology(KnownTopology.AD) +@pytest.mark.ticket(jira="RHEL-75484") +@pytest.mark.importance("high") +def test_range_retrieval__group_membership(client: Client, ad: AD): + """ + :title: Users with with more groups than MaxValRange can be retrieved + :description: + Testing the feature to retrieve users with more groups than MaxValRange. + :setup: + 1. Set MaxValRange in lDAPAdminLimits on AD. + 2. Create an ad user with number of groups > MaxValRange + 3. Clear caches and restart sssd + :steps: + 1. Retrieve the user running id -u user and check cached content + :expectedresults: + 1. No groups are missing from the cache + :customerscenario: True + """ + size = 152 + # The lDAPAdminLimits list contains mutiple items in key=value format. + # We retrieve the current lDAPAdminLimits, remove MaxValRange from it. + # Then we append new MaxValRange and write it back. + ad.host.conn.run( + rf""" + $basedn = '{ad.naming_context}' + $policyDN = "CN=Default Query Policy,CN=Query-Policies,CN=Directory"+ + " Service,CN=Windows NT,CN=Services,CN=Configuration,$basedn" + $currentLimits = (Get-ADObject -Identity $policyDN ` + -Properties lDAPAdminLimits).lDAPAdminLimits + Write-Output "Current limits: $currentLimits" + $regexPattern = "MaxValRange=.*" + $newLimits = @() + $newLimits += $currentLimits -notmatch $regexPattern + $newLimits += "MaxValRange={size - 2}" + Write-Output "New limits: $newLimits" + Set-ADObject -Identity $policyDN -Replace @{{lDAPAdminLimits = $newLimits}} + """ + ) + client.sssd.domain["ldap_id_mapping"] = "false" + client.sssd.domain["use_fully_qualified_names"] = "True" + client.sssd.start() + # Provisioning groups one by one using the framework functions + # each in its own ssh call takes too long. + ad.host.conn.run( + rf""" + Import-Module ActiveDirectory + $basedn = '{ad.naming_context}' + New-ADUser -Name "big-user" -AccountPassword (ConvertTo-SecureString ` + "Secret123" -AsPlainText -force) -OtherAttributes @{{"uid"="big-user"` + ;"uidNumber"=10001;"gidNumber"=20001;"gecos"="big-user";"loginShell"=` + "/bin/bash"}} -Enabled $True -Path "cn=users,$basedn" -EmailAddress ` + big-user@$basedn -GivenName dummyfirstname -Surname dummylastname ` + -UserPrincipalName big-user@$basedn + $count = 0 + while ($count -lt {size}) {{ + $gidNumber = 30000 + $count + New-ADGroup -Name "group-$count" -GroupScope 'Global' -GroupCategory ` + 'Security' -OtherAttributes @{{"gidNumber"="$gidNumber"}} -Path "cn=users,$basedn" + Add-ADGroupMember -Identity "cn=group-$count,cn=users,$basedn" ` + -Members "cn=big-user,cn=users,$basedn" + $count++ + }} + """ + ) + client.sssctl.cache_expire(everything=True) + client.host.conn.run(f"id -u big-user@{client.sssd.default_domain}") + grps = client.host.conn.run( + f"ldbsearch -H /var/lib/sss/db/cache_{client.sssd.default_domain}.ldb" + f" '(name=big-user@{client.sssd.default_domain})' | grep -Pio " + f"'originalMemberOf: CN=\\K([a-zA-Z0-9-]+)'" + ).stdout.splitlines() + assert len(grps) != 0, "User is not a member of any group!" + assert len(grps) == size, "User's membership is not complete!"