All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
groups.c
Go to the documentation of this file.
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: f6f79a0000f892613bccc75730d0f49328581e15 $
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  */
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  */
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  */
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  */
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 
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 
465  default:
466  goto finish;
467  }
468 
469  entry = ldap_first_entry((*pconn)->handle, result);
470  if (!entry) {
471  ldap_get_option((*pconn)->handle, LDAP_OPT_RESULT_CODE, &ldap_errno);
472  REDEBUG("Failed retrieving entry: %s", ldap_err2string(ldap_errno));
473 
474  goto finish;
475  }
476 
477  RDEBUG("Adding cacheable group object memberships");
478  do {
479  if (inst->cacheable_group_dn) {
480  dn = ldap_get_dn((*pconn)->handle, entry);
481  if (!dn) {
482  ldap_get_option((*pconn)->handle, LDAP_OPT_RESULT_CODE, &ldap_errno);
483  REDEBUG("Retrieving object DN from entry failed: %s", ldap_err2string(ldap_errno));
484 
485  goto finish;
486  }
487  rlm_ldap_normalise_dn(dn, dn);
488 
489  MEM(vp = pair_make_config(inst->cache_da->name, NULL, T_OP_ADD));
490  fr_pair_value_strcpy(vp, dn);
491 
492  RINDENT();
493  RDEBUG("&control:%s += \"%s\"", inst->cache_da->name, dn);
494  REXDENT();
495  ldap_memfree(dn);
496  }
497 
498  if (inst->cacheable_group_name) {
499  struct berval **values;
500 
501  values = ldap_get_values_len((*pconn)->handle, entry, inst->groupobj_name_attr);
502  if (!values) continue;
503 
504  MEM(vp = pair_make_config(inst->cache_da->name, NULL, T_OP_ADD));
505  fr_pair_value_bstrncpy(vp, values[0]->bv_val, values[0]->bv_len);
506 
507  RINDENT();
508  RDEBUG("&control:%s += \"%.*s\"", inst->cache_da->name,
509  (int)values[0]->bv_len, values[0]->bv_val);
510  REXDENT();
511 
512  ldap_value_free_len(values);
513  }
514  } while ((entry = ldap_next_entry((*pconn)->handle, entry)));
515 
516 finish:
517  if (result) ldap_msgfree(result);
518 
519  return rcode;
520 }
521 
522 /** Query the LDAP directory to check if a group object includes a user object as a member
523  *
524  * @param[in] inst rlm_ldap configuration.
525  * @param[in] request Current request.
526  * @param[in,out] pconn to use. May change as this function calls functions which auto re-connect.
527  * @param[in] check vp containing the group value (name or dn).
528  * @return One of the RLM_MODULE_* values.
529  */
531  VALUE_PAIR *check)
532 
533 {
534  ldap_rcode_t status;
535 
536  char const *base_dn;
537  char base_dn_buff[LDAP_MAX_DN_STR_LEN + 1];
538  char filter[LDAP_MAX_FILTER_STR_LEN + 1];
539  int ret;
540 
542 
543  switch (check->op) {
544  case T_OP_CMP_EQ:
545  case T_OP_CMP_FALSE:
546  case T_OP_CMP_TRUE:
547  case T_OP_REG_EQ:
548  case T_OP_REG_NE:
549  break;
550 
551  default:
552  REDEBUG("Operator \"%s\" not allowed for LDAP group comparisons",
553  fr_int2str(fr_tokens_table, check->op, "<INVALID>"));
554  return 1;
555  }
556 
557  RDEBUG2("Checking for user in group objects");
558 
559  if (rlm_ldap_is_dn(check->vp_strvalue, check->vp_length)) {
560  char const *filters[] = { inst->groupobj_filter, inst->groupobj_membership_filter };
561 
562  RINDENT();
563  ret = rlm_ldap_xlat_filter(request,
564  filters, sizeof(filters) / sizeof(*filters),
565  filter, sizeof(filter));
566  REXDENT();
567 
568  if (ret < 0) return RLM_MODULE_INVALID;
569 
570  base_dn = check->vp_strvalue;
571  } else {
572  char name_filter[LDAP_MAX_FILTER_STR_LEN];
573  char const *filters[] = { name_filter, inst->groupobj_filter, inst->groupobj_membership_filter };
574 
575  if (!inst->groupobj_name_attr) {
576  REDEBUG("Told to search for group by name, but missing 'group.name_attribute' "
577  "directive");
578 
579  return RLM_MODULE_INVALID;
580  }
581 
582  snprintf(name_filter, sizeof(name_filter), "(%s=%s)", inst->groupobj_name_attr, check->vp_strvalue);
583  RINDENT();
584  ret = rlm_ldap_xlat_filter(request,
585  filters, sizeof(filters) / sizeof(*filters),
586  filter, sizeof(filter));
587  REXDENT();
588  if (ret < 0) return RLM_MODULE_INVALID;
589 
590 
591  /*
592  * rlm_ldap_find_user does this, too. Oh well.
593  */
594  RINDENT();
595  ret = tmpl_expand(&base_dn, base_dn_buff, sizeof(base_dn_buff), request, inst->groupobj_base_dn,
596  rlm_ldap_escape_func, NULL);
597  REXDENT();
598  if (ret < 0) {
599  REDEBUG("Failed creating base_dn");
600 
601  return RLM_MODULE_INVALID;
602  }
603  }
604 
605  RINDENT();
606  status = rlm_ldap_search(NULL, inst, request, pconn, base_dn, inst->groupobj_scope, filter, NULL, NULL, NULL);
607  REXDENT();
608  switch (status) {
609  case LDAP_PROC_SUCCESS:
610  RDEBUG("User found in group object \"%s\"", base_dn);
611  break;
612 
613  case LDAP_PROC_NO_RESULT:
614  return RLM_MODULE_NOTFOUND;
615 
616  default:
617  return RLM_MODULE_FAIL;
618  }
619 
620  return RLM_MODULE_OK;
621 }
622 
623 /** Query the LDAP directory to check if a user object is a member of a group
624  *
625  * @param[in] inst rlm_ldap configuration.
626  * @param[in] request Current request.
627  * @param[in,out] pconn to use. May change as this function calls functions which auto re-connect.
628  * @param[in] dn of user object.
629  * @param[in] check vp containing the group value (name or dn).
630  * @return One of the RLM_MODULE_* values.
631  */
633  char const *dn, VALUE_PAIR *check)
634 {
635  rlm_rcode_t rcode = RLM_MODULE_NOTFOUND, ret;
636  ldap_rcode_t status;
637  bool name_is_dn = false, value_is_dn = false;
638 
639  LDAPMessage *result = NULL;
640  LDAPMessage *entry = NULL;
641  struct berval **values = NULL;
642 
643  char const *attrs[] = { inst->userobj_membership_attr, NULL };
644  int i, count, ldap_errno;
645 
646  RDEBUG2("Checking user object's %s attributes", inst->userobj_membership_attr);
647  RINDENT();
648  status = rlm_ldap_search(&result, inst, request, pconn, dn, LDAP_SCOPE_BASE, NULL, attrs, NULL, NULL);
649  REXDENT();
650  switch (status) {
651  case LDAP_PROC_SUCCESS:
652  break;
653 
654  case LDAP_PROC_NO_RESULT:
655  RDEBUG("Can't check membership attributes, user object not found");
656 
657  rcode = RLM_MODULE_NOTFOUND;
658 
659  /* FALL-THROUGH */
660  default:
661  goto finish;
662  }
663 
664  entry = ldap_first_entry((*pconn)->handle, result);
665  if (!entry) {
666  ldap_get_option((*pconn)->handle, LDAP_OPT_RESULT_CODE, &ldap_errno);
667  REDEBUG("Failed retrieving entry: %s", ldap_err2string(ldap_errno));
668 
669  rcode = RLM_MODULE_FAIL;
670 
671  goto finish;
672  }
673 
674  values = ldap_get_values_len((*pconn)->handle, entry, inst->userobj_membership_attr);
675  if (!values) {
676  RDEBUG("No group membership attribute(s) found in user object");
677 
678  goto finish;
679  }
680 
681  /*
682  * Loop over the list of groups the user is a member of,
683  * looking for a match.
684  */
685  name_is_dn = rlm_ldap_is_dn(check->vp_strvalue, check->vp_length);
686  count = ldap_count_values_len(values);
687  for (i = 0; i < count; i++) {
688  value_is_dn = rlm_ldap_is_dn(values[i]->bv_val, values[i]->bv_len);
689 
690  RDEBUG2("Processing %s value \"%.*s\" as a %s", inst->userobj_membership_attr,
691  (int)values[i]->bv_len, values[i]->bv_val, value_is_dn ? "DN" : "group name");
692 
693  /*
694  * Both literal group names, do case sensitive comparison
695  */
696  if (!name_is_dn && !value_is_dn) {
697  if ((check->vp_length == values[i]->bv_len) &&
698  (memcmp(values[i]->bv_val, check->vp_strvalue, values[i]->bv_len) == 0)) {
699  RDEBUG("User found in group \"%s\". Comparison between membership: name, check: name",
700  check->vp_strvalue);
701  rcode = RLM_MODULE_OK;
702 
703  goto finish;
704  }
705 
706  continue;
707  }
708 
709  /*
710  * Both DNs, do case insensitive, binary safe comparison
711  */
712  if (name_is_dn && value_is_dn) {
713  if (check->vp_length == values[i]->bv_len) {
714  int j;
715 
716  for (j = 0; j < (int)values[i]->bv_len; j++) {
717  if (tolower(values[i]->bv_val[j]) != tolower(check->vp_strvalue[j])) break;
718  }
719  if (j == (int)values[i]->bv_len) {
720  RDEBUG("User found in group DN \"%s\". "
721  "Comparison between membership: dn, check: dn", check->vp_strvalue);
722  rcode = RLM_MODULE_OK;
723 
724  goto finish;
725  }
726  }
727 
728  continue;
729  }
730 
731  /*
732  * If the value is not a DN, and the name we were given is a dn
733  * convert the value to a DN and do a comparison.
734  */
735  if (!value_is_dn && name_is_dn) {
736  char *resolved;
737  bool eq = false;
738 
739  RINDENT();
740  ret = rlm_ldap_group_dn2name(inst, request, pconn, check->vp_strvalue, &resolved);
741  REXDENT();
742  if (ret != RLM_MODULE_OK) {
743  rcode = ret;
744  goto finish;
745  }
746 
747  if (((talloc_array_length(resolved) - 1) == values[i]->bv_len) &&
748  (memcmp(values[i]->bv_val, resolved, values[i]->bv_len) == 0)) eq = true;
749  talloc_free(resolved);
750  if (eq) {
751  RDEBUG("User found in group \"%.*s\". Comparison between membership: name, check: name "
752  "(resolved from DN \"%s\")", (int)values[i]->bv_len,
753  values[i]->bv_val, check->vp_strvalue);
754  rcode = RLM_MODULE_OK;
755 
756  goto finish;
757  }
758 
759  continue;
760  }
761 
762  /*
763  * We have a value which is a DN, and a check item which specifies the name of a group,
764  * convert the value to a name so we can do a comparison.
765  */
766  if (value_is_dn && !name_is_dn) {
767  char *resolved;
768  char *value;
769  bool eq = false;
770 
771  value = rlm_ldap_berval_to_string(request, values[i]);
772  RINDENT();
773  ret = rlm_ldap_group_dn2name(inst, request, pconn, value, &resolved);
774  REXDENT();
775  talloc_free(value);
776  if (ret != RLM_MODULE_OK) {
777  rcode = ret;
778  goto finish;
779  }
780 
781  if (((talloc_array_length(resolved) - 1) == check->vp_length) &&
782  (memcmp(check->vp_strvalue, resolved, check->vp_length) == 0)) eq = true;
783  talloc_free(resolved);
784  if (eq) {
785  RDEBUG("User found in group \"%s\". Comparison between membership: name "
786  "(resolved from DN \"%s\"), check: name", check->vp_strvalue, value);
787  rcode = RLM_MODULE_OK;
788 
789  goto finish;
790  }
791 
792  continue;
793  }
794  rad_assert(0);
795  }
796 
797 finish:
798  if (values) ldap_value_free_len(values);
799  if (result) ldap_msgfree(result);
800 
801  return rcode;
802 }
803 
804 /** Check group membership attributes to see if a user is a member.
805  *
806  * @param[in] inst rlm_ldap configuration.
807  * @param[in] request Current request.
808  * @param[in] check vp containing the group value (name or dn).
809  *
810  * @return One of the RLM_MODULE_* values.
811  */
813 {
814  VALUE_PAIR *vp;
815  int ret;
816  vp_cursor_t cursor;
817 
818  fr_cursor_init(&cursor, &request->config);
819 
820  /*
821  * We return RLM_MODULE_INVALID here as an indication
822  * the caller should try a dynamic group lookup instead.
823  */
824  vp = fr_cursor_next_by_num(&cursor, inst->cache_da->vendor, inst->cache_da->attr, TAG_ANY);
825  if (!vp) return RLM_MODULE_INVALID;
826  fr_cursor_first(&cursor);
827 
828  while ((vp = fr_cursor_next_by_num(&cursor, inst->cache_da->vendor, inst->cache_da->attr, TAG_ANY))) {
829  ret = fr_pair_cmp_op(T_OP_CMP_EQ, vp, check);
830  if (ret == 1) {
831  RDEBUG2("User found. Matched cached membership");
832  return RLM_MODULE_OK;
833  }
834 
835  if (ret < -1) {
836  return RLM_MODULE_FAIL;
837  }
838  }
839 
840  RDEBUG2("Cached membership not found");
841  return RLM_MODULE_NOTFOUND;
842 }
void fr_pair_list_free(VALUE_PAIR **)
Free memory used by a valuepair list.
Definition: pair.c:544
ssize_t tmpl_expand(char const **out, char *buff, size_t outlen, REQUEST *request, vp_tmpl_t const *vpt, xlat_escape_t escape, void *escape_ctx)
Expand a vp_tmpl_t to a string writing the result to a buffer.
Definition: tmpl.c:1479
Tracks the state of a libldap connection handle.
Definition: ldap.h:163
VALUE_PAIR * fr_cursor_first(vp_cursor_t *cursor)
Rewind cursor to the start of the list.
Definition: cursor.c:105
VALUE_PAIR * config
VALUE_PAIR (s) used to set per request parameters for modules and the server core at runtime...
Definition: radiusd.h:227
Operation was successfull.
Definition: ldap.h:397
#define RINDENT()
Indent R* messages by one level.
Definition: log.h:265
#define LDAP_MAX_GROUP_NAME_LEN
Maximum name of a group name.
Definition: ldap.h:118
The module is OK, continue.
Definition: radiusd.h:91
VALUE_PAIR * fr_cursor_next_by_num(vp_cursor_t *cursor, unsigned int vendor, unsigned int attr, int8_t tag)
Iterate over a collection of VALUE_PAIRs of a given type in the pairlist.
Definition: cursor.c:200
static rlm_rcode_t rlm_ldap_group_name2dn(rlm_ldap_t const *inst, REQUEST *request, ldap_handle_t **pconn, char **names, char **out, size_t outlen)
Convert multiple group names into a DNs.
Definition: groups.c:45
#define MEM(x)
Definition: radiusd.h:396
vp_tmpl_t * groupobj_base_dn
DN to search for users under.
Definition: ldap.h:247
TALLOC_CTX * radius_list_ctx(REQUEST *request, pair_lists_t list_name)
Return the correct TALLOC_CTX to alloc VALUE_PAIR in, for a list.
Definition: tmpl.c:331
VALUE_PAIR ** radius_list(REQUEST *request, pair_lists_t list)
Resolve attribute pair_lists_t value to an attribute list.
Definition: tmpl.c:195
static char const * name
int groupobj_scope
Search scope.
Definition: ldap.h:249
#define RDEBUG_ENABLED
True if request debug level 1 messages are enabled.
Definition: log.h:237
const FR_NAME_NUMBER fr_tokens_table[]
Definition: token.c:30
VALUE_PAIR * fr_cursor_init(vp_cursor_t *cursor, VALUE_PAIR *const *node)
Setup a cursor to iterate over attribute pairs.
Definition: cursor.c:60
PUBLIC int snprintf(char *string, size_t length, char *format, va_alist)
Definition: snprintf.c:686
#define inst
bool cacheable_group_name
If true the server will determine complete set of group memberships for the current user object...
Definition: ldap.h:255
The module considers the request invalid.
Definition: radiusd.h:93
bool cacheable_group_dn
If true the server will determine complete set of group memberships for the current user object...
Definition: ldap.h:260
ldap_rcode_t
Codes returned by rlm_ldap internal functions.
Definition: ldap.h:395
#define LDAP_MAX_FILTER_STR_LEN
Maximum length of an xlat expanded filter.
Definition: ldap.h:120
char const * groupobj_filter
Filter to retrieve only group objects.
Definition: ldap.h:246
size_t rlm_ldap_normalise_dn(char *out, char const *in)
Normalise escape sequences in a DN.
Definition: ldap.c:312
Abstraction to allow iterating over different configurations of VALUE_PAIRs.
Definition: pair.h:144
rlm_rcode_t rlm_ldap_check_userobj_dynamic(rlm_ldap_t const *inst, REQUEST *request, ldap_handle_t **pconn, char const *dn, VALUE_PAIR *check)
Query the LDAP directory to check if a user object is a member of a group.
Definition: groups.c:632
#define rad_assert(expr)
Definition: rad_assert.h:38
char const * userobj_membership_attr
Attribute that describes groups the user is a member of.
Definition: ldap.h:233
rlm_rcode_t rlm_ldap_cacheable_userobj(rlm_ldap_t const *inst, REQUEST *request, ldap_handle_t **pconn, LDAPMessage *entry, char const *attr)
Convert group membership information into attributes.
Definition: groups.c:267
#define LDAP_MAX_DN_STR_LEN
Maximum length of an xlat expanded DN.
Definition: ldap.h:121
char const * groupobj_name_attr
The name of the group.
Definition: ldap.h:251
void fr_pair_value_strcpy(VALUE_PAIR *vp, char const *src)
Copy data into an "string" data type.
Definition: pair.c:2013
void fr_cursor_merge(vp_cursor_t *cursor, VALUE_PAIR *vp)
Merges multiple VALUE_PAIR into the cursor.
Definition: cursor.c:394
fr_dict_attr_t const * cache_da
The DA associated with this specific instance of the.
Definition: ldap.h:268
void fr_cursor_insert(vp_cursor_t *cursor, VALUE_PAIR *vp)
Insert a single VALUE_PAIR at the end of the list.
Definition: cursor.c:321
#define pair_make_config(_a, _b, _c)
Definition: radiusd.h:547
unsigned int attr
Attribute number.
Definition: dict.h:79
Definition: token.h:43
unsigned int vendor
Vendor that defines this attribute.
Definition: dict.h:78
#define fr_pair_cmp_op(_op, _a, _b)
Compare two attributes using and operator.
Definition: pair.h:236
char const * groupobj_membership_filter
Filter to only retrieve groups which contain the user as a member.
Definition: ldap.h:252
Stores an attribute, a value and various bits of other data.
Definition: pair.h:112
ssize_t rlm_ldap_xlat_filter(REQUEST *request, char const **sub, size_t sublen, char *out, size_t outlen)
Combine and expand filters.
Definition: ldap.c:416
char * talloc_typed_asprintf(void const *t, char const *fmt,...)
Call talloc vasprintf, setting the type on the new chunk correctly.
Definition: missing.c:611
ldap_rcode_t rlm_ldap_search(LDAPMessage **result, rlm_ldap_t const *inst, REQUEST *request, ldap_handle_t **pconn, char const *dn, int scope, char const *filter, char const *const *attrs, LDAPControl **serverctrls, LDAPControl **clientctrls)
Search for something in the LDAP directory.
Definition: ldap.c:880
#define REXDENT()
Exdent (unindent) R* messages by one level.
Definition: log.h:272
FR_TOKEN op
Operator to use when moving or inserting valuepair into a list.
Definition: pair.h:118
enum rlm_rcodes rlm_rcode_t
Return codes indicating the result of the module call.
rlm_rcode_t rlm_ldap_cacheable_groupobj(rlm_ldap_t const *inst, REQUEST *request, ldap_handle_t **pconn)
Convert group membership information into attributes.
Definition: groups.c:415
#define RDEBUG2(fmt,...)
Definition: log.h:244
char name[1]
Attribute name.
Definition: dict.h:89
Module failed, don't reply.
Definition: radiusd.h:90
#define TAG_ANY
Definition: pair.h:191
rlm_rcode_t rlm_ldap_check_cached(rlm_ldap_t const *inst, REQUEST *request, VALUE_PAIR *check)
Check group membership attributes to see if a user is a member.
Definition: groups.c:812
VALUE_PAIR * fr_cursor_next(vp_cursor_t *cursor)
Advanced the cursor to the next VALUE_PAIR.
Definition: cursor.c:263
static rlm_rcode_t rlm_ldap_group_dn2name(rlm_ldap_t const *inst, REQUEST *request, ldap_handle_t **pconn, char const *dn, char **out)
Convert a single group name into a DN.
Definition: groups.c:196
size_t rlm_ldap_escape_func(UNUSED REQUEST *request, char *out, size_t outlen, char const *in, UNUSED void *arg)
Converts "bad" strings into ones which are safe for LDAP.
Definition: ldap.c:65
#define REDEBUG(fmt,...)
Definition: log.h:254
VALUE_PAIR * fr_pair_afrom_da(TALLOC_CTX *ctx, fr_dict_attr_t const *da)
Dynamically allocate a new attribute.
Definition: pair.c:58
char const * fr_int2str(FR_NAME_NUMBER const *table, int number, char const *def)
Definition: token.c:506
char * rlm_ldap_berval_to_string(TALLOC_CTX *ctx, struct berval const *in)
Convert a berval to a talloced string.
Definition: ldap.c:281
#define LDAP_MAX_CACHEABLE
Maximum number of groups we retrieve from the server for.
Definition: ldap.h:114
void fr_pair_value_bstrncpy(VALUE_PAIR *vp, void const *src, size_t len)
Copy data into an "string" data type.
Definition: pair.c:2043
Attributes that change the behaviour of modules.
Definition: tmpl.h:85
#define RWDEBUG(fmt,...)
Definition: log.h:251
User not found.
Definition: radiusd.h:95
rlm_rcode_t rlm_ldap_check_groupobj_dynamic(rlm_ldap_t const *inst, REQUEST *request, ldap_handle_t **pconn, VALUE_PAIR *check)
Query the LDAP directory to check if a group object includes a user object as a member.
Definition: groups.c:530
LDAP authorization and authentication module headers.
#define RDEBUG(fmt,...)
Definition: log.h:243
bool rlm_ldap_is_dn(char const *in, size_t inlen)
Check whether a string looks like a DN.
Definition: ldap.c:168
Got no results.
Definition: ldap.h:412