/** Convert group membership information into attributes * * @param[in] inst rlm_ldap configuration. * @param[in] request Current request. * @param[in,out] pconn to use. May change as this function calls functions which auto re-connect. * @param[in] entry retrieved by rlm_ldap_find_user or rlm_ldap_search. * @param[in] attr membership attribute to look for in the entry. * @return One of the RLM_MODULE_* values. */ rlm_rcode_t rlm_ldap_cacheable_userobj(ldap_instance_t const *inst, REQUEST *request, ldap_handle_t **pconn, LDAPMessage *entry, char const *attr) { rlm_rcode_t rcode = RLM_MODULE_OK; struct berval **values; size_t value_len = 0; TALLOC_CTX *value_pool; char *group_name[LDAP_MAX_CACHEABLE + 1]; char **name_p = group_name; char *group_dn[LDAP_MAX_CACHEABLE + 1]; char **dn_p; char *name; VALUE_PAIR *vp, **vps; TALLOC_CTX *ctx; vp_cursor_t cursor; int is_dn, i, count; rad_assert(entry); rad_assert(attr); /* * Parse the membership information we got in the initial user query. */ values = ldap_get_values_len((*pconn)->handle, entry, attr); if (!values) { RDEBUG2("No cacheable group memberships found in user object"); return RLM_MODULE_OK; } count = ldap_count_values_len(values); vps = radius_list(request, PAIR_LIST_CONTROL); ctx = radius_list_ctx(request, PAIR_LIST_CONTROL); fr_cursor_init(&cursor, vps); /* * Avoid allocing buffers for each value. * * The old code used ldap_get_values, which was likely doing * a very similar thing internally to produce \0 terminated * buffers from bervalues. */ for (i = 0; (i < LDAP_MAX_CACHEABLE) && (i < count); i++) value_len += values[i]->bv_len + 1; value_pool = talloc_pool(request, value_len); for (i = 0; (i < LDAP_MAX_CACHEABLE) && (i < count); i++) { is_dn = rlm_ldap_is_dn(values[i]->bv_val, values[i]->bv_len); if (inst->cacheable_group_dn) { /* * The easy case, we're caching DNs and we got a DN. */ if (is_dn) { MEM(vp = pairalloc(ctx, inst->cache_da)); pairstrncpy(vp, values[i]->bv_val, values[i]->bv_len); fr_cursor_insert(&cursor, vp); RDEBUG("Added %s with value \"%s\" to control list", inst->cache_da->name, vp->vp_strvalue); /* * We were told to cache DNs but we got a name, we now need to resolve * this to a DN. Store all the group names in an array so we can do one query. */ } else { *name_p++ = rlm_ldap_berval_to_string(value_pool, values[i]); } } if (inst->cacheable_group_name) { /* * The easy case, we're caching names and we got a name. */ if (!is_dn) { MEM(vp = pairalloc(ctx, inst->cache_da)); pairstrncpy(vp, values[i]->bv_val, values[i]->bv_len); fr_cursor_insert(&cursor, vp); RDEBUG("Added control:%s with value \"%s\"", inst->cache_da->name, vp->vp_strvalue); /* * We were told to cache names but we got a DN, we now need to resolve * this to a name. * Only Active Directory supports filtering on DN, so we have to search * for each individual group. */ } else { char *dn; dn = rlm_ldap_berval_to_string(value_pool, values[i]); rcode = rlm_ldap_group_dn2name(inst, request, pconn, dn, &name); talloc_free(dn); if (rcode != RLM_MODULE_OK) { ldap_value_free_len(values); talloc_free(value_pool); return rcode; } MEM(vp = pairalloc(ctx, inst->cache_da)); pairstrncpy(vp, name, talloc_array_length(name) - 1); fr_cursor_insert(&cursor, vp); RDEBUG("Added control:%s with value \"%s\"", inst->cache_da->name, name); talloc_free(name); } } } *name_p = NULL; rcode = rlm_ldap_group_name2dn(inst, request, pconn, group_name, group_dn, sizeof(group_dn)); ldap_value_free_len(values); talloc_free(value_pool); if (rcode != RLM_MODULE_OK) return rcode; dn_p = group_dn; while (*dn_p) { MEM(vp = pairalloc(ctx, inst->cache_da)); pairstrcpy(vp, *dn_p); fr_cursor_insert(&cursor, vp); RDEBUG("Added control:%s with value \"%s\"", inst->cache_da->name, *dn_p); ldap_memfree(*dn_p); dn_p++; } return rcode; }
/** Convert value_pair_map_t to VALUE_PAIR(s) and add them to a REQUEST. * * Takes a single value_pair_map_t, resolves request and list identifiers * to pointers in the current request, then attempts to retrieve module * specific value(s) using callback, and adds the resulting values to the * correct request/list. * * @param request The current request. * @param map specifying destination attribute and location and src identifier. * @param func to retrieve module specific values and convert them to * VALUE_PAIRS. * @param ctx to be passed to func. * @param src name to be used in debugging if different from map value. * @return -1 if the operation failed, -2 in the source attribute wasn't valid, 0 on success. */ int radius_map2request(REQUEST *request, value_pair_map_t const *map, UNUSED char const *src, radius_tmpl_getvalue_t func, void *ctx) { int rcode, num; VALUE_PAIR **list, *vp, *head = NULL; REQUEST *context; TALLOC_CTX *parent; vp_cursor_t cursor; /* * Sanity check inputs. We can have a list or attribute * as a destination. */ if ((map->dst->type != VPT_TYPE_LIST) && (map->dst->type != VPT_TYPE_ATTR)) { REDEBUG("Invalid mapping destination"); return -2; } context = request; if (radius_request(&context, map->dst->request) < 0) { REDEBUG("Mapping \"%s\" -> \"%s\" invalid in this context", map->src->name, map->dst->name); return -2; } /* * If there's no CoA packet and we're updating it, * auto-allocate it. */ if (((map->dst->list == PAIR_LIST_COA) || (map->dst->list == PAIR_LIST_DM)) && !request->coa) { request_alloc_coa(context); if (map->dst->list == PAIR_LIST_COA) { context->coa->proxy->code = PW_CODE_COA_REQUEST; } else { context->coa->proxy->code = PW_CODE_DISCONNECT_REQUEST; } } list = radius_list(context, map->dst->list); if (!list) { REDEBUG("Mapping \"%s\" -> \"%s\" invalid in this context", map->src->name, map->dst->name); return -2; } parent = radius_list_ctx(context, map->dst->list); /* * The callback should either return -1 to signify operations error, -2 when it can't find the * attribute or list being referenced, or 0 to signify success. * It may return "sucess", but still have no VPs to work with. */ rcode = func(&head, request, map, ctx); if (rcode < 0) { rad_assert(!head); return rcode; } if (!head) return 0; /* * Reparent the VP */ for (vp = fr_cursor_init(&cursor, &head); vp; vp = fr_cursor_next(&cursor)) { VERIFY_VP(vp); if (debug_flag) debug_map(request, map, vp); (void) talloc_steal(parent, vp); } /* * List to list copies. */ if (map->dst->type == VPT_TYPE_LIST) { switch (map->op) { case T_OP_CMP_FALSE: rad_assert(head == NULL); pairfree(list); if (map->dst->list == PAIR_LIST_REQUEST) { context->username = NULL; context->password = NULL; } break; case T_OP_SET: if (map->src->type == VPT_TYPE_LIST) { pairfree(list); *list = head; } else { case T_OP_EQ: rad_assert(map->src->type == VPT_TYPE_EXEC); pairmove(parent, list, &head); pairfree(&head); } if (map->dst->list == PAIR_LIST_REQUEST) { context->username = pairfind(head, PW_USER_NAME, 0, TAG_ANY); context->password = pairfind(head, PW_USER_PASSWORD, 0, TAG_ANY); } break; case T_OP_ADD: pairadd(list, head); break; default: pairfree(&head); return -1; } return 0; } /* * We now should have only one destination attribute, and * only one source attribute. */ rad_assert(head->next == NULL); /* * Find the destination attribute. We leave with either * the cursor and vp pointing to the attribute, or vp is * NULL. */ num = map->dst->num; for (vp = fr_cursor_init(&cursor, list); vp != NULL; vp = fr_cursor_next(&cursor)) { VERIFY_VP(vp); if ((vp->da == map->dst->da) && (!vp->da->flags.has_tag || (map->dst->tag == TAG_ANY) || (vp->tag == map->dst->tag))) { if (num == 0) break; num--; } } /* * Figure out what to do with the source attribute. */ switch (map->op) { case T_OP_CMP_FALSE: /* remove matching attributes */ pairfree(&head); if (!vp) return 0; /* * Wildcard: delete all of the matching ones, * based on tag. */ if (!map->dst->num) { pairdelete(list, map->dst->da->attr, map->dst->da->vendor, map->dst->tag); vp = NULL; } else { /* * We've found the Nth one. Delete it, and only * it. */ vp = fr_cursor_remove(&cursor); } /* * Check that the User-Name and User-Password * caches point to the correct attribute. */ fixup: if (map->dst->list == PAIR_LIST_REQUEST) { context->username = pairfind(*list, PW_USER_NAME, 0, TAG_ANY); context->password = pairfind(*list, PW_USER_PASSWORD, 0, TAG_ANY); } pairfree(&vp); return 0; case T_OP_EQ: /* set only if not already set */ if (vp) { pairfree(&head); return 0; } fr_cursor_insert(&cursor, head); goto fixup; case T_OP_SET: /* over-write if existing, or else add */ if (vp) vp = fr_cursor_remove(&cursor); fr_cursor_insert(&cursor, head); goto fixup; case T_OP_ADD: /* append no matter what */ vp = NULL; pairadd(list, head); goto fixup; case T_OP_SUB: /* delete if it matches */ head->op = T_OP_CMP_EQ; rcode = radius_compare_vps(NULL, head, vp); pairfree(&head); if (rcode == 0) { vp = fr_cursor_remove(&cursor); goto fixup; } return 0; default: /* filtering operators */ /* * If the VP doesn't exist, the filters will add * it with the given value. */ if (!vp) { fr_cursor_insert(&cursor, head); goto fixup; } break; } /* * The LHS exists. We need to limit it's value based on * the operator, and the value of the RHS. */ head->op = map->op; rcode = radius_compare_vps(NULL, head, vp); head->op = T_OP_SET; switch (map->op) { case T_OP_CMP_EQ: if (rcode == 0) { leave: pairfree(&head); break; } replace: vp = fr_cursor_remove(&cursor); fr_cursor_insert(&cursor, head); goto fixup; case T_OP_LE: if (rcode <= 0) goto leave; goto replace; case T_OP_GE: if (rcode >= 0) goto leave; goto replace; default: pairfree(&head); return -1; } return 0; }
/** Convert group membership information into attributes * * @param[in] inst rlm_ldap configuration. * @param[in] request Current request. * @param[in,out] pconn to use. May change as this function calls functions which auto re-connect. * @param[in] entry retrieved by rlm_ldap_find_user or rlm_ldap_search. * @param[in] attr membership attribute to look for in the entry. * @return One of the RLM_MODULE_* values. */ rlm_rcode_t rlm_ldap_cacheable_userobj(rlm_ldap_t const *inst, REQUEST *request, ldap_handle_t **pconn, LDAPMessage *entry, char const *attr) { rlm_rcode_t rcode = RLM_MODULE_OK; struct berval **values; char *group_name[LDAP_MAX_CACHEABLE + 1]; char **name_p = group_name; char *group_dn[LDAP_MAX_CACHEABLE + 1]; char **dn_p; char *name; VALUE_PAIR *vp, **list, *groups = NULL; TALLOC_CTX *list_ctx, *value_ctx; vp_cursor_t list_cursor, groups_cursor; int is_dn, i, count; rad_assert(entry); rad_assert(attr); /* * Parse the membership information we got in the initial user query. */ values = ldap_get_values_len((*pconn)->handle, entry, attr); if (!values) { RDEBUG2("No cacheable group memberships found in user object"); return RLM_MODULE_OK; } count = ldap_count_values_len(values); list = radius_list(request, PAIR_LIST_CONTROL); list_ctx = radius_list_ctx(request, PAIR_LIST_CONTROL); /* * Simplifies freeing temporary values */ value_ctx = talloc_new(request); /* * Temporary list to hold new group VPs, will be merged * once all group info has been gathered/resolved * successfully. */ fr_cursor_init(&groups_cursor, &groups); for (i = 0; (i < LDAP_MAX_CACHEABLE) && (i < count); i++) { is_dn = rlm_ldap_is_dn(values[i]->bv_val, values[i]->bv_len); if (inst->cacheable_group_dn) { /* * The easy case, we're caching DNs and we got a DN. */ if (is_dn) { MEM(vp = fr_pair_afrom_da(list_ctx, inst->cache_da)); fr_pair_value_bstrncpy(vp, values[i]->bv_val, values[i]->bv_len); fr_cursor_insert(&groups_cursor, vp); /* * We were told to cache DNs but we got a name, we now need to resolve * this to a DN. Store all the group names in an array so we can do one query. */ } else { *name_p++ = rlm_ldap_berval_to_string(value_ctx, values[i]); } } if (inst->cacheable_group_name) { /* * The easy case, we're caching names and we got a name. */ if (!is_dn) { MEM(vp = fr_pair_afrom_da(list_ctx, inst->cache_da)); fr_pair_value_bstrncpy(vp, values[i]->bv_val, values[i]->bv_len); fr_cursor_insert(&groups_cursor, vp); /* * We were told to cache names but we got a DN, we now need to resolve * this to a name. * Only Active Directory supports filtering on DN, so we have to search * for each individual group. */ } else { char *dn; dn = rlm_ldap_berval_to_string(value_ctx, values[i]); rcode = rlm_ldap_group_dn2name(inst, request, pconn, dn, &name); talloc_free(dn); if (rcode != RLM_MODULE_OK) { ldap_value_free_len(values); talloc_free(value_ctx); fr_pair_list_free(&groups); return rcode; } MEM(vp = fr_pair_afrom_da(list_ctx, inst->cache_da)); fr_pair_value_bstrncpy(vp, name, talloc_array_length(name) - 1); fr_cursor_insert(&groups_cursor, vp); talloc_free(name); } } } *name_p = NULL; rcode = rlm_ldap_group_name2dn(inst, request, pconn, group_name, group_dn, sizeof(group_dn)); ldap_value_free_len(values); talloc_free(value_ctx); if (rcode != RLM_MODULE_OK) return rcode; fr_cursor_init(&list_cursor, list); RDEBUG("Adding cacheable user object memberships"); RINDENT(); if (RDEBUG_ENABLED) { for (vp = fr_cursor_first(&groups_cursor); vp; vp = fr_cursor_next(&groups_cursor)) { RDEBUG("&control:%s += \"%s\"", inst->cache_da->name, vp->vp_strvalue); } } fr_cursor_merge(&list_cursor, groups); for (dn_p = group_dn; *dn_p; dn_p++) { MEM(vp = fr_pair_afrom_da(list_ctx, inst->cache_da)); fr_pair_value_strcpy(vp, *dn_p); fr_cursor_insert(&list_cursor, vp); RDEBUG("&control:%s += \"%s\"", inst->cache_da->name, vp->vp_strvalue); ldap_memfree(*dn_p); } REXDENT(); return rcode; }