Handle connection error in rlm_ldap_cacheable_groupobj
[freeradius.git] / src / modules / rlm_ldap / groups.c
1 /*
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.
6  *
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.
11  *
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
15  */
16
17 /**
18  * $Id$
19  * @file groups.c
20  * @brief LDAP module group functions.
21  *
22  * @author Arran Cudbard-Bell <a.cudbardb@freeradius.org>
23  *
24  * @copyright 2013 Network RADIUS SARL <info@networkradius.com>
25  * @copyright 2013-2015 The FreeRADIUS Server Project.
26  */
27 #include        <freeradius-devel/rad_assert.h>
28 #include        <ctype.h>
29
30 #include        "ldap.h"
31
32 /** Convert multiple group names into a DNs
33  *
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.
36  *
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.
44  */
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)
47 {
48         rlm_rcode_t rcode = RLM_MODULE_OK;
49         ldap_rcode_t status;
50         int ldap_errno;
51
52         unsigned int name_cnt = 0;
53         unsigned int entry_cnt;
54         char const *attrs[] = { NULL };
55
56         LDAPMessage *result = NULL, *entry;
57
58         char **name = names;
59         char **dn = out;
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];
63
64         char *filter;
65
66         *dn = NULL;
67
68         if (!*names) {
69                 return RLM_MODULE_OK;
70         }
71
72         if (!inst->groupobj_name_attr) {
73                 REDEBUG("Told to convert group names to DNs but missing 'group.name_attribute' directive");
74
75                 return RLM_MODULE_INVALID;
76         }
77
78         RDEBUG("Converting group name(s) to group DN(s)");
79
80         /*
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.
83          */
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] ? "(|" : "");
88         while (*name) {
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);
91
92                 name_cnt++;
93         }
94         filter = talloc_asprintf_append_buffer(filter, "%s%s",
95                                                inst->groupobj_filter ? ")" : "",
96                                                names[0] && names[1] ? ")" : "");
97
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");
101
102                 return RLM_MODULE_INVALID;
103         }
104
105         status = rlm_ldap_search(&result, inst, request, pconn, base_dn, inst->groupobj_scope,
106                                  filter, attrs, NULL, NULL);
107         switch (status) {
108         case LDAP_PROC_SUCCESS:
109                 break;
110
111         case LDAP_PROC_NO_RESULT:
112                 RDEBUG("Tried to resolve group name(s) to DNs but got no results");
113                 goto finish;
114
115         default:
116                 rcode = RLM_MODULE_FAIL;
117                 goto finish;
118         }
119
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;
124
125                 goto finish;
126         }
127
128         if (entry_cnt > (outlen - 1)) {
129                 REDEBUG("Number of DNs exceeds limit (%zu)", outlen - 1);
130                 rcode = RLM_MODULE_INVALID;
131
132                 goto finish;
133         }
134
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);
138         }
139
140         entry = ldap_first_entry((*pconn)->handle, result);
141         if (!entry) {
142                 ldap_get_option((*pconn)->handle, LDAP_OPT_RESULT_CODE, &ldap_errno);
143                 REDEBUG("Failed retrieving entry: %s", ldap_err2string(ldap_errno));
144
145                 rcode = RLM_MODULE_FAIL;
146                 goto finish;
147         }
148
149         do {
150                 *dn = ldap_get_dn((*pconn)->handle, entry);
151                 if (!*dn) {
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));
154
155                         rcode = RLM_MODULE_FAIL;
156                         goto finish;
157                 }
158                 rlm_ldap_normalise_dn(*dn, *dn);
159
160                 RDEBUG("Got group DN \"%s\"", *dn);
161                 dn++;
162         } while((entry = ldap_next_entry((*pconn)->handle, entry)));
163
164         *dn = NULL;
165
166 finish:
167         talloc_free(filter);
168         if (result) {
169                 ldap_msgfree(result);
170         }
171
172         /*
173          *      Be nice and cleanup the output array if we error out.
174          */
175         if (rcode != RLM_MODULE_OK) {
176                 dn = out;
177                 while(*dn) ldap_memfree(*dn++);
178                 *dn = NULL;
179         }
180
181         return rcode;
182 }
183
184 /** Convert a single group name into a DN
185  *
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.
188  *
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.
195  */
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)
198 {
199         rlm_rcode_t rcode = RLM_MODULE_OK;
200         ldap_rcode_t status;
201         int ldap_errno;
202
203         struct berval **values = NULL;
204         char const *attrs[] = { inst->groupobj_name_attr, NULL };
205         LDAPMessage *result = NULL, *entry;
206
207         *out = NULL;
208
209         if (!inst->groupobj_name_attr) {
210                 REDEBUG("Told to resolve group DN to name but missing 'group.name_attribute' directive");
211
212                 return RLM_MODULE_INVALID;
213         }
214
215         RDEBUG("Resolving group DN \"%s\" to group name", dn);
216
217         status = rlm_ldap_search(&result, inst, request, pconn, dn, LDAP_SCOPE_BASE, NULL, attrs, NULL, NULL);
218         switch (status) {
219         case LDAP_PROC_SUCCESS:
220                 break;
221
222         case LDAP_PROC_NO_RESULT:
223                 REDEBUG("Group DN \"%s\" did not resolve to an object", dn);
224                 return RLM_MODULE_INVALID;
225
226         default:
227                 return RLM_MODULE_FAIL;
228         }
229
230         entry = ldap_first_entry((*pconn)->handle, result);
231         if (!entry) {
232                 ldap_get_option((*pconn)->handle, LDAP_OPT_RESULT_CODE, &ldap_errno);
233                 REDEBUG("Failed retrieving entry: %s", ldap_err2string(ldap_errno));
234
235                 rcode = RLM_MODULE_INVALID;
236                 goto finish;
237         }
238
239         values = ldap_get_values_len((*pconn)->handle, entry, inst->groupobj_name_attr);
240         if (!values) {
241                 REDEBUG("No %s attributes found in object", inst->groupobj_name_attr);
242
243                 rcode = RLM_MODULE_INVALID;
244
245                 goto finish;
246         }
247
248         *out = rlm_ldap_berval_to_string(request, values[0]);
249         RDEBUG("Group DN \"%s\" resolves to name \"%s\"", dn, *out);
250
251 finish:
252         if (result) ldap_msgfree(result);
253         if (values) ldap_value_free_len(values);
254
255         return rcode;
256 }
257
258 /** Convert group membership information into attributes
259  *
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.
266  */
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)
269 {
270         rlm_rcode_t rcode = RLM_MODULE_OK;
271
272         struct berval **values;
273
274         char *group_name[LDAP_MAX_CACHEABLE + 1];
275         char **name_p = group_name;
276
277         char *group_dn[LDAP_MAX_CACHEABLE + 1];
278         char **dn_p;
279
280         char *name;
281
282         VALUE_PAIR *vp, **list, *groups = NULL;
283         TALLOC_CTX *list_ctx, *value_ctx;
284         vp_cursor_t list_cursor, groups_cursor;
285
286         int is_dn, i, count;
287
288         rad_assert(entry);
289         rad_assert(attr);
290
291         /*
292          *      Parse the membership information we got in the initial user query.
293          */
294         values = ldap_get_values_len((*pconn)->handle, entry, attr);
295         if (!values) {
296                 RDEBUG2("No cacheable group memberships found in user object");
297
298                 return RLM_MODULE_OK;
299         }
300         count = ldap_count_values_len(values);
301
302         list = radius_list(request, PAIR_LIST_CONTROL);
303         list_ctx = radius_list_ctx(request, PAIR_LIST_CONTROL);
304
305         /*
306          *      Simplifies freeing temporary values
307          */
308         value_ctx = talloc_new(request);
309
310         /*
311          *      Temporary list to hold new group VPs, will be merged
312          *      once all group info has been gathered/resolved
313          *      successfully.
314          */
315         fr_cursor_init(&groups_cursor, &groups);
316
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);
319
320                 if (inst->cacheable_group_dn) {
321                         /*
322                          *      The easy case, we're caching DNs and we got a DN.
323                          */
324                         if (is_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);
328                         /*
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.
331                          */
332                         } else {
333                                 *name_p++ = rlm_ldap_berval_to_string(value_ctx, values[i]);
334                         }
335                 }
336
337                 if (inst->cacheable_group_name) {
338                         /*
339                          *      The easy case, we're caching names and we got a name.
340                          */
341                         if (!is_dn) {
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);
345                         /*
346                          *      We were told to cache names but we got a DN, we now need to resolve
347                          *      this to a name.
348                          *      Only Active Directory supports filtering on DN, so we have to search
349                          *      for each individual group.
350                          */
351                         } else {
352                                 char *dn;
353
354                                 dn = rlm_ldap_berval_to_string(value_ctx, values[i]);
355                                 rcode = rlm_ldap_group_dn2name(inst, request, pconn, dn, &name);
356                                 talloc_free(dn);
357                                 if (rcode != RLM_MODULE_OK) {
358                                         ldap_value_free_len(values);
359                                         talloc_free(value_ctx);
360                                         fr_pair_list_free(&groups);
361
362                                         return rcode;
363                                 }
364
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);
368                                 talloc_free(name);
369                         }
370                 }
371         }
372         *name_p = NULL;
373
374         rcode = rlm_ldap_group_name2dn(inst, request, pconn, group_name, group_dn, sizeof(group_dn));
375
376         ldap_value_free_len(values);
377         talloc_free(value_ctx);
378
379         if (rcode != RLM_MODULE_OK) return rcode;
380
381         fr_cursor_init(&list_cursor, list);
382
383         RDEBUG("Adding cacheable user object memberships");
384         RINDENT();
385         if (RDEBUG_ENABLED) {
386                 for (vp = fr_cursor_first(&groups_cursor);
387                      vp;
388                      vp = fr_cursor_next(&groups_cursor)) {
389                         RDEBUG("&control:%s += \"%s\"", inst->cache_da->name, vp->vp_strvalue);
390                 }
391         }
392
393         fr_cursor_merge(&list_cursor, groups);
394
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);
399
400                 RDEBUG("&control:%s += \"%s\"", inst->cache_da->name, vp->vp_strvalue);
401                 ldap_memfree(*dn_p);
402         }
403         REXDENT();
404
405         return rcode;
406 }
407
408 /** Convert group membership information into attributes
409  *
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.
414  */
415 rlm_rcode_t rlm_ldap_cacheable_groupobj(rlm_ldap_t const *inst, REQUEST *request, ldap_handle_t **pconn)
416 {
417         rlm_rcode_t rcode = RLM_MODULE_OK;
418         ldap_rcode_t status;
419         int ldap_errno;
420
421         LDAPMessage *result = NULL;
422         LDAPMessage *entry;
423
424         char const *base_dn;
425         char base_dn_buff[LDAP_MAX_DN_STR_LEN];
426
427         char const *filters[] = { inst->groupobj_filter, inst->groupobj_membership_filter };
428         char filter[LDAP_MAX_FILTER_STR_LEN + 1];
429
430         char const *attrs[] = { inst->groupobj_name_attr, NULL };
431
432         VALUE_PAIR *vp;
433         char *dn;
434
435         rad_assert(inst->groupobj_base_dn);
436
437         if (!inst->groupobj_membership_filter) {
438                 RDEBUG2("Skipping caching group objects as directive 'group.membership_filter' is not set");
439
440                 return RLM_MODULE_OK;
441         }
442
443         if (rlm_ldap_xlat_filter(request,
444                                  filters, sizeof(filters) / sizeof(*filters),
445                                  filter, sizeof(filter)) < 0) {
446                 return RLM_MODULE_INVALID;
447         }
448
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");
452
453                 return RLM_MODULE_INVALID;
454         }
455
456         status = rlm_ldap_search(&result, inst, request, pconn, base_dn,
457                                  inst->groupobj_scope, filter, attrs, NULL, NULL);
458         switch (status) {
459         case LDAP_PROC_SUCCESS:
460                 break;
461
462         case LDAP_PROC_NO_RESULT:
463                 RDEBUG2("No cacheable group memberships found in group objects");
464                 goto finish;
465
466         default:
467                 rcode = RLM_MODULE_FAIL;
468                 goto finish;
469         }
470
471         entry = ldap_first_entry((*pconn)->handle, result);
472         if (!entry) {
473                 ldap_get_option((*pconn)->handle, LDAP_OPT_RESULT_CODE, &ldap_errno);
474                 REDEBUG("Failed retrieving entry: %s", ldap_err2string(ldap_errno));
475
476                 goto finish;
477         }
478
479         RDEBUG("Adding cacheable group object memberships");
480         do {
481                 if (inst->cacheable_group_dn) {
482                         dn = ldap_get_dn((*pconn)->handle, entry);
483                         if (!dn) {
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));
486
487                                 goto finish;
488                         }
489                         rlm_ldap_normalise_dn(dn, dn);
490
491                         MEM(vp = pair_make_config(inst->cache_da->name, NULL, T_OP_ADD));
492                         fr_pair_value_strcpy(vp, dn);
493
494                         RINDENT();
495                         RDEBUG("&control:%s += \"%s\"", inst->cache_da->name, dn);
496                         REXDENT();
497                         ldap_memfree(dn);
498                 }
499
500                 if (inst->cacheable_group_name) {
501                         struct berval **values;
502
503                         values = ldap_get_values_len((*pconn)->handle, entry, inst->groupobj_name_attr);
504                         if (!values) continue;
505
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);
508
509                         RINDENT();
510                         RDEBUG("&control:%s += \"%.*s\"", inst->cache_da->name,
511                                (int)values[0]->bv_len, values[0]->bv_val);
512                         REXDENT();
513
514                         ldap_value_free_len(values);
515                 }
516         } while ((entry = ldap_next_entry((*pconn)->handle, entry)));
517
518 finish:
519         if (result) ldap_msgfree(result);
520
521         return rcode;
522 }
523
524 /** Query the LDAP directory to check if a group object includes a user object as a member
525  *
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.
531  */
532 rlm_rcode_t rlm_ldap_check_groupobj_dynamic(rlm_ldap_t const *inst, REQUEST *request, ldap_handle_t **pconn,
533                                             VALUE_PAIR *check)
534
535 {
536         ldap_rcode_t    status;
537
538         char const      *base_dn;
539         char            base_dn_buff[LDAP_MAX_DN_STR_LEN + 1];
540         char            filter[LDAP_MAX_FILTER_STR_LEN + 1];
541         int             ret;
542
543         rad_assert(inst->groupobj_base_dn);
544
545         switch (check->op) {
546         case T_OP_CMP_EQ:
547         case T_OP_CMP_FALSE:
548         case T_OP_CMP_TRUE:
549         case T_OP_REG_EQ:
550         case T_OP_REG_NE:
551                 break;
552
553         default:
554                 REDEBUG("Operator \"%s\" not allowed for LDAP group comparisons",
555                         fr_int2str(fr_tokens, check->op, "<INVALID>"));
556                 return 1;
557         }
558
559         RDEBUG2("Checking for user in group objects");
560
561         if (rlm_ldap_is_dn(check->vp_strvalue, check->vp_length)) {
562                 char const *filters[] = { inst->groupobj_filter, inst->groupobj_membership_filter };
563
564                 RINDENT();
565                 ret = rlm_ldap_xlat_filter(request,
566                                            filters, sizeof(filters) / sizeof(*filters),
567                                            filter, sizeof(filter));
568                 REXDENT();
569
570                 if (ret < 0) return RLM_MODULE_INVALID;
571
572                 base_dn = check->vp_strvalue;
573         } else {
574                 char name_filter[LDAP_MAX_FILTER_STR_LEN];
575                 char const *filters[] = { name_filter, inst->groupobj_filter, inst->groupobj_membership_filter };
576
577                 if (!inst->groupobj_name_attr) {
578                         REDEBUG("Told to search for group by name, but missing 'group.name_attribute' "
579                                 "directive");
580
581                         return RLM_MODULE_INVALID;
582                 }
583
584                 snprintf(name_filter, sizeof(name_filter), "(%s=%s)", inst->groupobj_name_attr, check->vp_strvalue);
585                 RINDENT();
586                 ret = rlm_ldap_xlat_filter(request,
587                                            filters, sizeof(filters) / sizeof(*filters),
588                                            filter, sizeof(filter));
589                 REXDENT();
590                 if (ret < 0) return RLM_MODULE_INVALID;
591
592
593                 /*
594                  *      rlm_ldap_find_user does this, too.  Oh well.
595                  */
596                 RINDENT();
597                 ret = tmpl_expand(&base_dn, base_dn_buff, sizeof(base_dn_buff), request, inst->groupobj_base_dn,
598                                   rlm_ldap_escape_func, NULL);
599                 REXDENT();
600                 if (ret < 0) {
601                         REDEBUG("Failed creating base_dn");
602
603                         return RLM_MODULE_INVALID;
604                 }
605         }
606
607         RINDENT();
608         status = rlm_ldap_search(NULL, inst, request, pconn, base_dn, inst->groupobj_scope, filter, NULL, NULL, NULL);
609         REXDENT();
610         switch (status) {
611         case LDAP_PROC_SUCCESS:
612                 RDEBUG("User found in group object \"%s\"", base_dn);
613                 break;
614
615         case LDAP_PROC_NO_RESULT:
616                 return RLM_MODULE_NOTFOUND;
617
618         default:
619                 return RLM_MODULE_FAIL;
620         }
621
622         return RLM_MODULE_OK;
623 }
624
625 /** Query the LDAP directory to check if a user object is a member of a group
626  *
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.
633  */
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)
636 {
637         rlm_rcode_t     rcode = RLM_MODULE_NOTFOUND, ret;
638         ldap_rcode_t    status;
639         bool            name_is_dn = false, value_is_dn = false;
640
641         LDAPMessage     *result = NULL;
642         LDAPMessage     *entry = NULL;
643         struct berval   **values = NULL;
644
645         char const      *attrs[] = { inst->userobj_membership_attr, NULL };
646         int             i, count, ldap_errno;
647
648         RDEBUG2("Checking user object's %s attributes", inst->userobj_membership_attr);
649         RINDENT();
650         status = rlm_ldap_search(&result, inst, request, pconn, dn, LDAP_SCOPE_BASE, NULL, attrs, NULL, NULL);
651         REXDENT();
652         switch (status) {
653         case LDAP_PROC_SUCCESS:
654                 break;
655
656         case LDAP_PROC_NO_RESULT:
657                 RDEBUG("Can't check membership attributes, user object not found");
658
659                 rcode = RLM_MODULE_NOTFOUND;
660
661                 /* FALL-THROUGH */
662         default:
663                 goto finish;
664         }
665
666         entry = ldap_first_entry((*pconn)->handle, result);
667         if (!entry) {
668                 ldap_get_option((*pconn)->handle, LDAP_OPT_RESULT_CODE, &ldap_errno);
669                 REDEBUG("Failed retrieving entry: %s", ldap_err2string(ldap_errno));
670
671                 rcode = RLM_MODULE_FAIL;
672
673                 goto finish;
674         }
675
676         values = ldap_get_values_len((*pconn)->handle, entry, inst->userobj_membership_attr);
677         if (!values) {
678                 RDEBUG("No group membership attribute(s) found in user object");
679
680                 goto finish;
681         }
682
683         /*
684          *      Loop over the list of groups the user is a member of,
685          *      looking for a match.
686          */
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);
691
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");
694
695                 /*
696                  *      Both literal group names, do case sensitive comparison
697                  */
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",
702                                        check->vp_strvalue);
703                                 rcode = RLM_MODULE_OK;
704
705                                 goto finish;
706                         }
707
708                         continue;
709                 }
710
711                 /*
712                  *      Both DNs, do case insensitive, binary safe comparison
713                  */
714                 if (name_is_dn && value_is_dn) {
715                         if (check->vp_length == values[i]->bv_len) {
716                                 int j;
717
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;
720                                 }
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;
725
726                                         goto finish;
727                                 }
728                         }
729
730                         continue;
731                 }
732
733                 /*
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.
736                  */
737                 if (!value_is_dn && name_is_dn) {
738                         char *resolved;
739                         bool eq = false;
740
741                         RINDENT();
742                         ret = rlm_ldap_group_dn2name(inst, request, pconn, check->vp_strvalue, &resolved);
743                         REXDENT();
744                         if (ret != RLM_MODULE_OK) {
745                                 rcode = ret;
746                                 goto finish;
747                         }
748
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);
752                         if (eq) {
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;
757
758                                 goto finish;
759                         }
760
761                         continue;
762                 }
763
764                 /*
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.
767                  */
768                 if (value_is_dn && !name_is_dn) {
769                         char *resolved;
770                         char *value;
771                         bool eq = false;
772
773                         value = rlm_ldap_berval_to_string(request, values[i]);
774                         RINDENT();
775                         ret = rlm_ldap_group_dn2name(inst, request, pconn, value, &resolved);
776                         REXDENT();
777                         talloc_free(value);
778                         if (ret != RLM_MODULE_OK) {
779                                 rcode = ret;
780                                 goto finish;
781                         }
782
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);
786                         if (eq) {
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;
790
791                                 goto finish;
792                         }
793
794                         continue;
795                 }
796                 rad_assert(0);
797         }
798
799 finish:
800         if (values) ldap_value_free_len(values);
801         if (result) ldap_msgfree(result);
802
803         return rcode;
804 }
805
806 /** Check group membership attributes to see if a user is a member.
807  *
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).
811  *
812  * @return One of the RLM_MODULE_* values.
813  */
814 rlm_rcode_t rlm_ldap_check_cached(rlm_ldap_t const *inst, REQUEST *request, VALUE_PAIR *check)
815 {
816         VALUE_PAIR      *vp;
817         int             ret;
818         vp_cursor_t     cursor;
819
820         fr_cursor_init(&cursor, &request->config);
821
822         /*
823          *      We return RLM_MODULE_INVALID here as an indication
824          *      the caller should try a dynamic group lookup instead.
825          */
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);
829
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);
832                 if (ret == 1) {
833                         RDEBUG2("User found. Matched cached membership");
834                         return RLM_MODULE_OK;
835                 }
836
837                 if (ret < -1) {
838                         return RLM_MODULE_FAIL;
839                 }
840         }
841
842         RDEBUG2("Cached membership not found");
843         return RLM_MODULE_NOTFOUND;
844 }