2 * This program is is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License as published by
4 * the Free Software Foundation; either version 2 of the License, or (at
5 * your option) any later version.
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20 * @brief LDAP module group functions.
22 * @author Arran Cudbard-Bell <a.cudbardb@freeradius.org>
24 * @copyright 2013 Network RADIUS SARL <info@networkradius.com>
25 * @copyright 2013-2015 The FreeRADIUS Server Project.
27 #include <freeradius-devel/rad_assert.h>
32 /** Convert multiple group names into a DNs
34 * Given an array of group names, builds a filter matching all names, then retrieves all group objects
35 * and stores the DN associated with each group object.
37 * @param[in] inst rlm_ldap configuration.
38 * @param[in] request Current request.
39 * @param[in,out] pconn to use. May change as this function calls functions which auto re-connect.
40 * @param[in] names to covert to DNs (NULL terminated).
41 * @param[out] out Where to write the DNs. DNs must be freed with ldap_memfree(). Will be NULL terminated.
42 * @param[in] outlen Size of out.
43 * @return One of the RLM_MODULE_* values.
45 static rlm_rcode_t rlm_ldap_group_name2dn(rlm_ldap_t const *inst, REQUEST *request, ldap_handle_t **pconn,
46 char **names, char **out, size_t outlen)
48 rlm_rcode_t rcode = RLM_MODULE_OK;
52 unsigned int name_cnt = 0;
53 unsigned int entry_cnt;
54 char const *attrs[] = { NULL };
56 LDAPMessage *result = NULL, *entry;
60 char const *base_dn = NULL;
61 char base_dn_buff[LDAP_MAX_DN_STR_LEN];
62 char buffer[LDAP_MAX_GROUP_NAME_LEN + 1];
72 if (!inst->groupobj_name_attr) {
73 REDEBUG("Told to convert group names to DNs but missing 'group.name_attribute' directive");
75 return RLM_MODULE_INVALID;
78 RDEBUG("Converting group name(s) to group DN(s)");
81 * It'll probably only save a few ms in network latency, but it means we can send a query
82 * for the entire group list at once.
84 filter = talloc_typed_asprintf(request, "%s%s%s",
85 inst->groupobj_filter ? "(&" : "",
86 inst->groupobj_filter ? inst->groupobj_filter : "",
87 names[0] && names[1] ? "(|" : "");
89 rlm_ldap_escape_func(request, buffer, sizeof(buffer), *name++, NULL);
90 filter = talloc_asprintf_append_buffer(filter, "(%s=%s)", inst->groupobj_name_attr, buffer);
94 filter = talloc_asprintf_append_buffer(filter, "%s%s",
95 inst->groupobj_filter ? ")" : "",
96 names[0] && names[1] ? ")" : "");
98 if (tmpl_expand(&base_dn, base_dn_buff, sizeof(base_dn_buff), request,
99 inst->groupobj_base_dn, rlm_ldap_escape_func, NULL) < 0) {
100 REDEBUG("Failed creating base_dn");
102 return RLM_MODULE_INVALID;
105 status = rlm_ldap_search(&result, inst, request, pconn, base_dn, inst->groupobj_scope,
106 filter, attrs, NULL, NULL);
108 case LDAP_PROC_SUCCESS:
111 case LDAP_PROC_NO_RESULT:
112 RDEBUG("Tried to resolve group name(s) to DNs but got no results");
116 rcode = RLM_MODULE_FAIL;
120 entry_cnt = ldap_count_entries((*pconn)->handle, result);
121 if (entry_cnt > name_cnt) {
122 REDEBUG("Number of DNs exceeds number of names, group and/or dn should be more restrictive");
123 rcode = RLM_MODULE_INVALID;
128 if (entry_cnt > (outlen - 1)) {
129 REDEBUG("Number of DNs exceeds limit (%zu)", outlen - 1);
130 rcode = RLM_MODULE_INVALID;
135 if (entry_cnt < name_cnt) {
136 RWDEBUG("Got partial mapping of group names (%i) to DNs (%i), membership information may be incomplete",
137 name_cnt, entry_cnt);
140 entry = ldap_first_entry((*pconn)->handle, result);
142 ldap_get_option((*pconn)->handle, LDAP_OPT_RESULT_CODE, &ldap_errno);
143 REDEBUG("Failed retrieving entry: %s", ldap_err2string(ldap_errno));
145 rcode = RLM_MODULE_FAIL;
150 *dn = ldap_get_dn((*pconn)->handle, entry);
152 ldap_get_option((*pconn)->handle, LDAP_OPT_RESULT_CODE, &ldap_errno);
153 REDEBUG("Retrieving object DN from entry failed: %s", ldap_err2string(ldap_errno));
155 rcode = RLM_MODULE_FAIL;
158 rlm_ldap_normalise_dn(*dn, *dn);
160 RDEBUG("Got group DN \"%s\"", *dn);
162 } while((entry = ldap_next_entry((*pconn)->handle, entry)));
169 ldap_msgfree(result);
173 * Be nice and cleanup the output array if we error out.
175 if (rcode != RLM_MODULE_OK) {
177 while(*dn) ldap_memfree(*dn++);
184 /** Convert a single group name into a DN
186 * Unlike the inverse conversion of a name to a DN, most LDAP directories don't allow filtering by DN,
187 * so we need to search for each DN individually.
189 * @param[in] inst rlm_ldap configuration.
190 * @param[in] request Current request.
191 * @param[in,out] pconn to use. May change as this function calls functions which auto re-connect.
192 * @param[in] dn to resolve.
193 * @param[out] out Where to write group name (must be freed with talloc_free).
194 * @return One of the RLM_MODULE_* values.
196 static rlm_rcode_t rlm_ldap_group_dn2name(rlm_ldap_t const *inst, REQUEST *request,
197 ldap_handle_t **pconn, char const *dn, char **out)
199 rlm_rcode_t rcode = RLM_MODULE_OK;
203 struct berval **values = NULL;
204 char const *attrs[] = { inst->groupobj_name_attr, NULL };
205 LDAPMessage *result = NULL, *entry;
209 if (!inst->groupobj_name_attr) {
210 REDEBUG("Told to resolve group DN to name but missing 'group.name_attribute' directive");
212 return RLM_MODULE_INVALID;
215 RDEBUG("Resolving group DN \"%s\" to group name", dn);
217 status = rlm_ldap_search(&result, inst, request, pconn, dn, LDAP_SCOPE_BASE, NULL, attrs, NULL, NULL);
219 case LDAP_PROC_SUCCESS:
222 case LDAP_PROC_NO_RESULT:
223 REDEBUG("Group DN \"%s\" did not resolve to an object", dn);
224 return RLM_MODULE_INVALID;
227 return RLM_MODULE_FAIL;
230 entry = ldap_first_entry((*pconn)->handle, result);
232 ldap_get_option((*pconn)->handle, LDAP_OPT_RESULT_CODE, &ldap_errno);
233 REDEBUG("Failed retrieving entry: %s", ldap_err2string(ldap_errno));
235 rcode = RLM_MODULE_INVALID;
239 values = ldap_get_values_len((*pconn)->handle, entry, inst->groupobj_name_attr);
241 REDEBUG("No %s attributes found in object", inst->groupobj_name_attr);
243 rcode = RLM_MODULE_INVALID;
248 *out = rlm_ldap_berval_to_string(request, values[0]);
249 RDEBUG("Group DN \"%s\" resolves to name \"%s\"", dn, *out);
252 if (result) ldap_msgfree(result);
253 if (values) ldap_value_free_len(values);
258 /** Convert group membership information into attributes
260 * @param[in] inst rlm_ldap configuration.
261 * @param[in] request Current request.
262 * @param[in,out] pconn to use. May change as this function calls functions which auto re-connect.
263 * @param[in] entry retrieved by rlm_ldap_find_user or rlm_ldap_search.
264 * @param[in] attr membership attribute to look for in the entry.
265 * @return One of the RLM_MODULE_* values.
267 rlm_rcode_t rlm_ldap_cacheable_userobj(rlm_ldap_t const *inst, REQUEST *request, ldap_handle_t **pconn,
268 LDAPMessage *entry, char const *attr)
270 rlm_rcode_t rcode = RLM_MODULE_OK;
272 struct berval **values;
274 char *group_name[LDAP_MAX_CACHEABLE + 1];
275 char **name_p = group_name;
277 char *group_dn[LDAP_MAX_CACHEABLE + 1];
282 VALUE_PAIR *vp, **list, *groups = NULL;
283 TALLOC_CTX *list_ctx, *value_ctx;
284 vp_cursor_t list_cursor, groups_cursor;
292 * Parse the membership information we got in the initial user query.
294 values = ldap_get_values_len((*pconn)->handle, entry, attr);
296 RDEBUG2("No cacheable group memberships found in user object");
298 return RLM_MODULE_OK;
300 count = ldap_count_values_len(values);
302 list = radius_list(request, PAIR_LIST_CONTROL);
303 list_ctx = radius_list_ctx(request, PAIR_LIST_CONTROL);
306 * Simplifies freeing temporary values
308 value_ctx = talloc_new(request);
311 * Temporary list to hold new group VPs, will be merged
312 * once all group info has been gathered/resolved
315 fr_cursor_init(&groups_cursor, &groups);
317 for (i = 0; (i < LDAP_MAX_CACHEABLE) && (i < count); i++) {
318 is_dn = rlm_ldap_is_dn(values[i]->bv_val, values[i]->bv_len);
320 if (inst->cacheable_group_dn) {
322 * The easy case, we're caching DNs and we got a DN.
325 MEM(vp = fr_pair_afrom_da(list_ctx, inst->cache_da));
326 fr_pair_value_bstrncpy(vp, values[i]->bv_val, values[i]->bv_len);
327 fr_cursor_insert(&groups_cursor, vp);
329 * We were told to cache DNs but we got a name, we now need to resolve
330 * this to a DN. Store all the group names in an array so we can do one query.
333 *name_p++ = rlm_ldap_berval_to_string(value_ctx, values[i]);
337 if (inst->cacheable_group_name) {
339 * The easy case, we're caching names and we got a name.
342 MEM(vp = fr_pair_afrom_da(list_ctx, inst->cache_da));
343 fr_pair_value_bstrncpy(vp, values[i]->bv_val, values[i]->bv_len);
344 fr_cursor_insert(&groups_cursor, vp);
346 * We were told to cache names but we got a DN, we now need to resolve
348 * Only Active Directory supports filtering on DN, so we have to search
349 * for each individual group.
354 dn = rlm_ldap_berval_to_string(value_ctx, values[i]);
355 rcode = rlm_ldap_group_dn2name(inst, request, pconn, dn, &name);
357 if (rcode != RLM_MODULE_OK) {
358 ldap_value_free_len(values);
359 talloc_free(value_ctx);
360 fr_pair_list_free(&groups);
365 MEM(vp = fr_pair_afrom_da(list_ctx, inst->cache_da));
366 fr_pair_value_bstrncpy(vp, name, talloc_array_length(name) - 1);
367 fr_cursor_insert(&groups_cursor, vp);
374 rcode = rlm_ldap_group_name2dn(inst, request, pconn, group_name, group_dn, sizeof(group_dn));
376 ldap_value_free_len(values);
377 talloc_free(value_ctx);
379 if (rcode != RLM_MODULE_OK) return rcode;
381 fr_cursor_init(&list_cursor, list);
383 RDEBUG("Adding cacheable user object memberships");
385 if (RDEBUG_ENABLED) {
386 for (vp = fr_cursor_first(&groups_cursor);
388 vp = fr_cursor_next(&groups_cursor)) {
389 RDEBUG("&control:%s += \"%s\"", inst->cache_da->name, vp->vp_strvalue);
393 fr_cursor_merge(&list_cursor, groups);
395 for (dn_p = group_dn; *dn_p; dn_p++) {
396 MEM(vp = fr_pair_afrom_da(list_ctx, inst->cache_da));
397 fr_pair_value_strcpy(vp, *dn_p);
398 fr_cursor_insert(&list_cursor, vp);
400 RDEBUG("&control:%s += \"%s\"", inst->cache_da->name, vp->vp_strvalue);
408 /** Convert group membership information into attributes
410 * @param[in] inst rlm_ldap configuration.
411 * @param[in] request Current request.
412 * @param[in,out] pconn to use. May change as this function calls functions which auto re-connect.
413 * @return One of the RLM_MODULE_* values.
415 rlm_rcode_t rlm_ldap_cacheable_groupobj(rlm_ldap_t const *inst, REQUEST *request, ldap_handle_t **pconn)
417 rlm_rcode_t rcode = RLM_MODULE_OK;
421 LDAPMessage *result = NULL;
425 char base_dn_buff[LDAP_MAX_DN_STR_LEN];
427 char const *filters[] = { inst->groupobj_filter, inst->groupobj_membership_filter };
428 char filter[LDAP_MAX_FILTER_STR_LEN + 1];
430 char const *attrs[] = { inst->groupobj_name_attr, NULL };
435 rad_assert(inst->groupobj_base_dn);
437 if (!inst->groupobj_membership_filter) {
438 RDEBUG2("Skipping caching group objects as directive 'group.membership_filter' is not set");
440 return RLM_MODULE_OK;
443 if (rlm_ldap_xlat_filter(request,
444 filters, sizeof(filters) / sizeof(*filters),
445 filter, sizeof(filter)) < 0) {
446 return RLM_MODULE_INVALID;
449 if (tmpl_expand(&base_dn, base_dn_buff, sizeof(base_dn_buff), request,
450 inst->groupobj_base_dn, rlm_ldap_escape_func, NULL) < 0) {
451 REDEBUG("Failed creating base_dn");
453 return RLM_MODULE_INVALID;
456 status = rlm_ldap_search(&result, inst, request, pconn, base_dn,
457 inst->groupobj_scope, filter, attrs, NULL, NULL);
459 case LDAP_PROC_SUCCESS:
462 case LDAP_PROC_NO_RESULT:
463 RDEBUG2("No cacheable group memberships found in group objects");
467 rcode = RLM_MODULE_FAIL;
471 entry = ldap_first_entry((*pconn)->handle, result);
473 ldap_get_option((*pconn)->handle, LDAP_OPT_RESULT_CODE, &ldap_errno);
474 REDEBUG("Failed retrieving entry: %s", ldap_err2string(ldap_errno));
479 RDEBUG("Adding cacheable group object memberships");
481 if (inst->cacheable_group_dn) {
482 dn = ldap_get_dn((*pconn)->handle, entry);
484 ldap_get_option((*pconn)->handle, LDAP_OPT_RESULT_CODE, &ldap_errno);
485 REDEBUG("Retrieving object DN from entry failed: %s", ldap_err2string(ldap_errno));
489 rlm_ldap_normalise_dn(dn, dn);
491 MEM(vp = pair_make_config(inst->cache_da->name, NULL, T_OP_ADD));
492 fr_pair_value_strcpy(vp, dn);
495 RDEBUG("&control:%s += \"%s\"", inst->cache_da->name, dn);
500 if (inst->cacheable_group_name) {
501 struct berval **values;
503 values = ldap_get_values_len((*pconn)->handle, entry, inst->groupobj_name_attr);
504 if (!values) continue;
506 MEM(vp = pair_make_config(inst->cache_da->name, NULL, T_OP_ADD));
507 fr_pair_value_bstrncpy(vp, values[0]->bv_val, values[0]->bv_len);
510 RDEBUG("&control:%s += \"%.*s\"", inst->cache_da->name,
511 (int)values[0]->bv_len, values[0]->bv_val);
514 ldap_value_free_len(values);
516 } while ((entry = ldap_next_entry((*pconn)->handle, entry)));
519 if (result) ldap_msgfree(result);
524 /** Query the LDAP directory to check if a group object includes a user object as a member
526 * @param[in] inst rlm_ldap configuration.
527 * @param[in] request Current request.
528 * @param[in,out] pconn to use. May change as this function calls functions which auto re-connect.
529 * @param[in] check vp containing the group value (name or dn).
530 * @return One of the RLM_MODULE_* values.
532 rlm_rcode_t rlm_ldap_check_groupobj_dynamic(rlm_ldap_t const *inst, REQUEST *request, ldap_handle_t **pconn,
539 char base_dn_buff[LDAP_MAX_DN_STR_LEN + 1];
540 char filter[LDAP_MAX_FILTER_STR_LEN + 1];
543 rad_assert(inst->groupobj_base_dn);
554 REDEBUG("Operator \"%s\" not allowed for LDAP group comparisons",
555 fr_int2str(fr_tokens, check->op, "<INVALID>"));
559 RDEBUG2("Checking for user in group objects");
561 if (rlm_ldap_is_dn(check->vp_strvalue, check->vp_length)) {
562 char const *filters[] = { inst->groupobj_filter, inst->groupobj_membership_filter };
565 ret = rlm_ldap_xlat_filter(request,
566 filters, sizeof(filters) / sizeof(*filters),
567 filter, sizeof(filter));
570 if (ret < 0) return RLM_MODULE_INVALID;
572 base_dn = check->vp_strvalue;
574 char name_filter[LDAP_MAX_FILTER_STR_LEN];
575 char const *filters[] = { name_filter, inst->groupobj_filter, inst->groupobj_membership_filter };
577 if (!inst->groupobj_name_attr) {
578 REDEBUG("Told to search for group by name, but missing 'group.name_attribute' "
581 return RLM_MODULE_INVALID;
584 snprintf(name_filter, sizeof(name_filter), "(%s=%s)", inst->groupobj_name_attr, check->vp_strvalue);
586 ret = rlm_ldap_xlat_filter(request,
587 filters, sizeof(filters) / sizeof(*filters),
588 filter, sizeof(filter));
590 if (ret < 0) return RLM_MODULE_INVALID;
594 * rlm_ldap_find_user does this, too. Oh well.
597 ret = tmpl_expand(&base_dn, base_dn_buff, sizeof(base_dn_buff), request, inst->groupobj_base_dn,
598 rlm_ldap_escape_func, NULL);
601 REDEBUG("Failed creating base_dn");
603 return RLM_MODULE_INVALID;
608 status = rlm_ldap_search(NULL, inst, request, pconn, base_dn, inst->groupobj_scope, filter, NULL, NULL, NULL);
611 case LDAP_PROC_SUCCESS:
612 RDEBUG("User found in group object \"%s\"", base_dn);
615 case LDAP_PROC_NO_RESULT:
616 return RLM_MODULE_NOTFOUND;
619 return RLM_MODULE_FAIL;
622 return RLM_MODULE_OK;
625 /** Query the LDAP directory to check if a user object is a member of a group
627 * @param[in] inst rlm_ldap configuration.
628 * @param[in] request Current request.
629 * @param[in,out] pconn to use. May change as this function calls functions which auto re-connect.
630 * @param[in] dn of user object.
631 * @param[in] check vp containing the group value (name or dn).
632 * @return One of the RLM_MODULE_* values.
634 rlm_rcode_t rlm_ldap_check_userobj_dynamic(rlm_ldap_t const *inst, REQUEST *request, ldap_handle_t **pconn,
635 char const *dn, VALUE_PAIR *check)
637 rlm_rcode_t rcode = RLM_MODULE_NOTFOUND, ret;
639 bool name_is_dn = false, value_is_dn = false;
641 LDAPMessage *result = NULL;
642 LDAPMessage *entry = NULL;
643 struct berval **values = NULL;
645 char const *attrs[] = { inst->userobj_membership_attr, NULL };
646 int i, count, ldap_errno;
648 RDEBUG2("Checking user object's %s attributes", inst->userobj_membership_attr);
650 status = rlm_ldap_search(&result, inst, request, pconn, dn, LDAP_SCOPE_BASE, NULL, attrs, NULL, NULL);
653 case LDAP_PROC_SUCCESS:
656 case LDAP_PROC_NO_RESULT:
657 RDEBUG("Can't check membership attributes, user object not found");
659 rcode = RLM_MODULE_NOTFOUND;
666 entry = ldap_first_entry((*pconn)->handle, result);
668 ldap_get_option((*pconn)->handle, LDAP_OPT_RESULT_CODE, &ldap_errno);
669 REDEBUG("Failed retrieving entry: %s", ldap_err2string(ldap_errno));
671 rcode = RLM_MODULE_FAIL;
676 values = ldap_get_values_len((*pconn)->handle, entry, inst->userobj_membership_attr);
678 RDEBUG("No group membership attribute(s) found in user object");
684 * Loop over the list of groups the user is a member of,
685 * looking for a match.
687 name_is_dn = rlm_ldap_is_dn(check->vp_strvalue, check->vp_length);
688 count = ldap_count_values_len(values);
689 for (i = 0; i < count; i++) {
690 value_is_dn = rlm_ldap_is_dn(values[i]->bv_val, values[i]->bv_len);
692 RDEBUG2("Processing %s value \"%.*s\" as a %s", inst->userobj_membership_attr,
693 (int)values[i]->bv_len, values[i]->bv_val, value_is_dn ? "DN" : "group name");
696 * Both literal group names, do case sensitive comparison
698 if (!name_is_dn && !value_is_dn) {
699 if ((check->vp_length == values[i]->bv_len) &&
700 (memcmp(values[i]->bv_val, check->vp_strvalue, values[i]->bv_len) == 0)) {
701 RDEBUG("User found in group \"%s\". Comparison between membership: name, check: name",
703 rcode = RLM_MODULE_OK;
712 * Both DNs, do case insensitive, binary safe comparison
714 if (name_is_dn && value_is_dn) {
715 if (check->vp_length == values[i]->bv_len) {
718 for (j = 0; j < (int)values[i]->bv_len; j++) {
719 if (tolower(values[i]->bv_val[j]) != tolower(check->vp_strvalue[j])) break;
721 if (j == (int)values[i]->bv_len) {
722 RDEBUG("User found in group DN \"%s\". "
723 "Comparison between membership: dn, check: dn", check->vp_strvalue);
724 rcode = RLM_MODULE_OK;
734 * If the value is not a DN, and the name we were given is a dn
735 * convert the value to a DN and do a comparison.
737 if (!value_is_dn && name_is_dn) {
742 ret = rlm_ldap_group_dn2name(inst, request, pconn, check->vp_strvalue, &resolved);
744 if (ret != RLM_MODULE_OK) {
749 if (((talloc_array_length(resolved) - 1) == values[i]->bv_len) &&
750 (memcmp(values[i]->bv_val, resolved, values[i]->bv_len) == 0)) eq = true;
751 talloc_free(resolved);
753 RDEBUG("User found in group \"%.*s\". Comparison between membership: name, check: name "
754 "(resolved from DN \"%s\")", (int)values[i]->bv_len,
755 values[i]->bv_val, check->vp_strvalue);
756 rcode = RLM_MODULE_OK;
765 * We have a value which is a DN, and a check item which specifies the name of a group,
766 * convert the value to a name so we can do a comparison.
768 if (value_is_dn && !name_is_dn) {
773 value = rlm_ldap_berval_to_string(request, values[i]);
775 ret = rlm_ldap_group_dn2name(inst, request, pconn, value, &resolved);
778 if (ret != RLM_MODULE_OK) {
783 if (((talloc_array_length(resolved) - 1) == check->vp_length) &&
784 (memcmp(check->vp_strvalue, resolved, check->vp_length) == 0)) eq = true;
785 talloc_free(resolved);
787 RDEBUG("User found in group \"%s\". Comparison between membership: name "
788 "(resolved from DN \"%s\"), check: name", check->vp_strvalue, value);
789 rcode = RLM_MODULE_OK;
800 if (values) ldap_value_free_len(values);
801 if (result) ldap_msgfree(result);
806 /** Check group membership attributes to see if a user is a member.
808 * @param[in] inst rlm_ldap configuration.
809 * @param[in] request Current request.
810 * @param[in] check vp containing the group value (name or dn).
812 * @return One of the RLM_MODULE_* values.
814 rlm_rcode_t rlm_ldap_check_cached(rlm_ldap_t const *inst, REQUEST *request, VALUE_PAIR *check)
820 fr_cursor_init(&cursor, &request->config);
823 * We return RLM_MODULE_INVALID here as an indication
824 * the caller should try a dynamic group lookup instead.
826 vp = fr_cursor_next_by_num(&cursor, inst->cache_da->attr, inst->cache_da->vendor, TAG_ANY);
827 if (!vp) return RLM_MODULE_INVALID;
828 fr_cursor_first(&cursor);
830 while ((vp = fr_cursor_next_by_num(&cursor, inst->cache_da->attr, inst->cache_da->vendor, TAG_ANY))) {
831 ret = fr_pair_cmp_op(T_OP_CMP_EQ, vp, check);
833 RDEBUG2("User found. Matched cached membership");
834 return RLM_MODULE_OK;
838 return RLM_MODULE_FAIL;
842 RDEBUG2("Cached membership not found");
843 return RLM_MODULE_NOTFOUND;