static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void *instance, REQUEST *request) { rlm_rcode_t rcode = RLM_MODULE_OK; ldap_rcode_t status; int ldap_errno; int i; ldap_instance_t *inst = instance; struct berval **values; VALUE_PAIR *vp; ldap_handle_t *conn; LDAPMessage *result, *entry; char const *dn = NULL; rlm_ldap_map_xlat_t expanded; /* faster than mallocing every time */ if (!request->username) { RDEBUG2("Attribute \"User-Name\" is required for authorization"); return RLM_MODULE_NOOP; } /* * Check for valid input, zero length names not permitted */ if (request->username->vp_length == 0) { RDEBUG2("Zero length username not permitted"); return RLM_MODULE_INVALID; } if (rlm_ldap_map_xlat(request, inst->user_map, &expanded) < 0) { return RLM_MODULE_FAIL; } conn = mod_conn_get(inst, request); if (!conn) return RLM_MODULE_FAIL; /* * Add any additional attributes we need for checking access, memberships, and profiles */ if (inst->userobj_access_attr) { expanded.attrs[expanded.count++] = inst->userobj_access_attr; } if (inst->userobj_membership_attr && (inst->cacheable_group_dn || inst->cacheable_group_name)) { expanded.attrs[expanded.count++] = inst->userobj_membership_attr; } if (inst->profile_attr) { expanded.attrs[expanded.count++] = inst->profile_attr; } if (inst->valuepair_attr) { expanded.attrs[expanded.count++] = inst->valuepair_attr; } expanded.attrs[expanded.count] = NULL; dn = rlm_ldap_find_user(inst, request, &conn, expanded.attrs, true, &result, &rcode); if (!dn) { goto finish; } entry = ldap_first_entry(conn->handle, result); if (!entry) { ldap_get_option(conn->handle, LDAP_OPT_RESULT_CODE, &ldap_errno); REDEBUG("Failed retrieving entry: %s", ldap_err2string(ldap_errno)); goto finish; } /* * Check for access. */ if (inst->userobj_access_attr) { rcode = rlm_ldap_check_access(inst, request, conn, entry); if (rcode != RLM_MODULE_OK) { goto finish; } } /* * Check if we need to cache group memberships */ if (inst->cacheable_group_dn || inst->cacheable_group_name) { if (inst->userobj_membership_attr) { rcode = rlm_ldap_cacheable_userobj(inst, request, &conn, entry, inst->userobj_membership_attr); if (rcode != RLM_MODULE_OK) { goto finish; } } rcode = rlm_ldap_cacheable_groupobj(inst, request, &conn); if (rcode != RLM_MODULE_OK) { goto finish; } } #ifdef WITH_EDIR /* * We already have a Cleartext-Password. Skip edir. */ if (pairfind(request->config_items, PW_CLEARTEXT_PASSWORD, 0, TAG_ANY)) { goto skip_edir; } /* * Retrieve Universal Password if we use eDirectory */ if (inst->edir) { int res = 0; char password[256]; size_t pass_size = sizeof(password); /* * Retrive universal password */ res = nmasldap_get_password(conn->handle, dn, password, &pass_size); if (res != 0) { REDEBUG("Failed to retrieve eDirectory password: (%i) %s", res, edir_errstr(res)); rcode = RLM_MODULE_FAIL; goto finish; } /* * Add Cleartext-Password attribute to the request */ vp = radius_paircreate(request, &request->config_items, PW_CLEARTEXT_PASSWORD, 0); pairstrcpy(vp, password); vp->vp_length = pass_size; if (RDEBUG_ENABLED3) { RDEBUG3("Added eDirectory password. control:%s += '%s'", vp->da->name, vp->vp_strvalue); } else { RDEBUG2("Added eDirectory password"); } if (inst->edir_autz) { RDEBUG2("Binding as user for eDirectory authorization checks"); /* * Bind as the user */ conn->rebound = true; status = rlm_ldap_bind(inst, request, &conn, dn, vp->vp_strvalue, true); switch (status) { case LDAP_PROC_SUCCESS: rcode = RLM_MODULE_OK; RDEBUG("Bind as user '%s' was successful", dn); break; case LDAP_PROC_NOT_PERMITTED: rcode = RLM_MODULE_USERLOCK; goto finish; case LDAP_PROC_REJECT: rcode = RLM_MODULE_REJECT; goto finish; case LDAP_PROC_BAD_DN: rcode = RLM_MODULE_INVALID; goto finish; case LDAP_PROC_NO_RESULT: rcode = RLM_MODULE_NOTFOUND; goto finish; default: rcode = RLM_MODULE_FAIL; goto finish; }; } } skip_edir: #endif /* * Apply ONE user profile, or a default user profile. */ if (inst->default_profile) { char profile[1024]; if (radius_xlat(profile, sizeof(profile), request, inst->default_profile, NULL, NULL) < 0) { REDEBUG("Failed creating default profile string"); rcode = RLM_MODULE_INVALID; goto finish; } rlm_ldap_map_profile(inst, request, &conn, profile, &expanded); } /* * Apply a SET of user profiles. */ if (inst->profile_attr) { values = ldap_get_values_len(conn->handle, entry, inst->profile_attr); if (values != NULL) { for (i = 0; values[i] != NULL; i++) { char *value; value = rlm_ldap_berval_to_string(request, values[i]); rlm_ldap_map_profile(inst, request, &conn, value, &expanded); talloc_free(value); } ldap_value_free_len(values); } } if (inst->user_map || inst->valuepair_attr) { RDEBUG("Processing user attributes"); RINDENT(); rlm_ldap_map_do(inst, request, conn->handle, &expanded, entry); REXDENT(); rlm_ldap_check_reply(inst, request); } finish: rlm_ldap_map_xlat_free(&expanded); if (result) ldap_msgfree(result); mod_conn_release(inst, conn); return rcode; }
/** Convert attribute map into valuepairs * * Use the attribute map built earlier to convert LDAP values into valuepairs and insert them into whichever * list they need to go into. * * This is *NOT* atomic, but there's no condition for which we should error out... * * @param[in] inst rlm_ldap configuration. * @param[in] request Current request. * @param[in] handle associated with entry. * @param[in] expanded attributes (rhs of map). * @param[in] entry to retrieve attributes from. * @return * - Number of maps successfully applied. * - -1 on failure. */ int rlm_ldap_map_do(const rlm_ldap_t *inst, REQUEST *request, LDAP *handle, rlm_ldap_map_exp_t const *expanded, LDAPMessage *entry) { vp_map_t const *map; unsigned int total = 0; int applied = 0; /* How many maps have been applied to the current request */ rlm_ldap_result_t result; char const *name; RINDENT(); for (map = expanded->maps; map != NULL; map = map->next) { int ret; name = expanded->attrs[total++]; /* * Binary safe */ result.values = ldap_get_values_len(handle, entry, name); if (!result.values) { RDEBUG3("Attribute \"%s\" not found in LDAP object", name); goto next; } /* * Find out how many values there are for the * attribute and extract all of them. */ result.count = ldap_count_values_len(result.values); /* * If something bad happened, just skip, this is probably * a case of the dst being incorrect for the current * request context */ ret = map_to_request(request, map, rlm_ldap_map_getvalue, &result); if (ret == -1) return -1; /* Fail */ /* * How many maps we've processed */ applied++; next: ldap_value_free_len(result.values); } REXDENT(); /* * Retrieve any valuepair attributes from the result, these are generic values specifying * a radius list, operator and value. */ if (inst->valuepair_attr) { struct berval **values; int count, i; values = ldap_get_values_len(handle, entry, inst->valuepair_attr); count = ldap_count_values_len(values); RINDENT(); for (i = 0; i < count; i++) { vp_map_t *attr; char *value; value = rlm_ldap_berval_to_string(request, values[i]); RDEBUG3("Parsing attribute string '%s'", value); if (map_afrom_attr_str(request, &attr, value, REQUEST_CURRENT, PAIR_LIST_REPLY, REQUEST_CURRENT, PAIR_LIST_REQUEST) < 0) { RWDEBUG("Failed parsing '%s' value \"%s\" as valuepair (%s), skipping...", fr_strerror(), inst->valuepair_attr, value); talloc_free(value); continue; } if (map_to_request(request, attr, map_to_vp, NULL) < 0) { RWDEBUG("Failed adding \"%s\" to request, skipping...", value); } else { applied++; } talloc_free(attr); talloc_free(value); } REXDENT(); ldap_value_free_len(values); } return applied; }
/** Query the LDAP directory to check if a user object is a member of a group * * @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] dn of user object. * @param[in] check vp containing the group value (name or dn). * @return One of the RLM_MODULE_* values. */ rlm_rcode_t rlm_ldap_check_userobj_dynamic(ldap_instance_t const *inst, REQUEST *request, ldap_handle_t **pconn, char const *dn, VALUE_PAIR *check) { rlm_rcode_t rcode = RLM_MODULE_NOTFOUND, ret; ldap_rcode_t status; bool name_is_dn = false, value_is_dn = false; LDAPMessage *result = NULL; LDAPMessage *entry = NULL; struct berval **values = NULL; char const *attrs[] = { inst->userobj_membership_attr, NULL }; int i, count, ldap_errno; RDEBUG2("Checking user object's %s attributes", inst->userobj_membership_attr); RINDENT(); status = rlm_ldap_search(inst, request, pconn, dn, LDAP_SCOPE_BASE, NULL, attrs, &result); REXDENT(); switch (status) { case LDAP_PROC_SUCCESS: break; case LDAP_PROC_NO_RESULT: RDEBUG("Can't check membership attributes, user object not found"); rcode = RLM_MODULE_NOTFOUND; /* FALL-THROUGH */ default: goto finish; } entry = ldap_first_entry((*pconn)->handle, result); if (!entry) { ldap_get_option((*pconn)->handle, LDAP_OPT_RESULT_CODE, &ldap_errno); REDEBUG("Failed retrieving entry: %s", ldap_err2string(ldap_errno)); rcode = RLM_MODULE_FAIL; goto finish; } values = ldap_get_values_len((*pconn)->handle, entry, inst->userobj_membership_attr); if (!values) { RDEBUG("No group membership attribute(s) found in user object"); goto finish; } /* * Loop over the list of groups the user is a member of, * looking for a match. */ name_is_dn = rlm_ldap_is_dn(check->vp_strvalue, check->vp_length); count = ldap_count_values_len(values); for (i = 0; i < count; i++) { value_is_dn = rlm_ldap_is_dn(values[i]->bv_val, values[i]->bv_len); RDEBUG2("Processing %s value \"%.*s\" as a %s", inst->userobj_membership_attr, (int)values[i]->bv_len, values[i]->bv_val, value_is_dn ? "DN" : "group name"); /* * Both literal group names, do case sensitive comparison */ if (!name_is_dn && !value_is_dn) { if ((check->vp_length == values[i]->bv_len) && (memcmp(values[i]->bv_val, check->vp_strvalue, values[i]->bv_len) == 0)) { RDEBUG("User found in group \"%s\". Comparison between membership: name, check: name", check->vp_strvalue); rcode = RLM_MODULE_OK; goto finish; } continue; } /* * Both DNs, do case insensitive, binary safe comparison */ if (name_is_dn && value_is_dn) { if (check->vp_length == values[i]->bv_len) { int j; for (j = 0; j < (int)values[i]->bv_len; j++) { if (tolower(values[i]->bv_val[j]) != tolower(check->vp_strvalue[j])) break; } if (j == (int)values[i]->bv_len) { RDEBUG("User found in group DN \"%s\". " "Comparison between membership: dn, check: dn", check->vp_strvalue); rcode = RLM_MODULE_OK; goto finish; } } continue; } /* * If the value is not a DN, and the name we were given is a dn * convert the value to a DN and do a comparison. */ if (!value_is_dn && name_is_dn) { char *resolved; bool eq = false; RINDENT(); ret = rlm_ldap_group_dn2name(inst, request, pconn, check->vp_strvalue, &resolved); REXDENT(); if (ret != RLM_MODULE_OK) { rcode = ret; goto finish; } if (((talloc_array_length(resolved) - 1) == values[i]->bv_len) && (memcmp(values[i]->bv_val, resolved, values[i]->bv_len) == 0)) eq = true; talloc_free(resolved); if (eq) { RDEBUG("User found in group \"%.*s\". Comparison between membership: name, check: name " "(resolved from DN \"%s\")", (int)values[i]->bv_len, values[i]->bv_val, check->vp_strvalue); rcode = RLM_MODULE_OK; goto finish; } continue; } /* * We have a value which is a DN, and a check item which specifies the name of a group, * convert the value to a name so we can do a comparison. */ if (value_is_dn && !name_is_dn) { char *resolved; char *value; bool eq = false; value = rlm_ldap_berval_to_string(request, values[i]); RINDENT(); ret = rlm_ldap_group_dn2name(inst, request, pconn, value, &resolved); REXDENT(); talloc_free(value); if (ret != RLM_MODULE_OK) { rcode = ret; goto finish; } if (((talloc_array_length(resolved) - 1) == check->vp_length) && (memcmp(check->vp_strvalue, resolved, check->vp_length) == 0)) eq = true; talloc_free(resolved); if (eq) { RDEBUG("User found in group \"%s\". Comparison between membership: name " "(resolved from DN \"%s\"), check: name", check->vp_strvalue, value); rcode = RLM_MODULE_OK; goto finish; } continue; } rad_assert(0); } finish: if (values) ldap_value_free_len(values); if (result) ldap_msgfree(result); return rcode; }
/** 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 a single group name into a DN * * Unlike the inverse conversion of a name to a DN, most LDAP directories don't allow filtering by DN, * so we need to search for each DN individually. * * @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] dn to resolve. * @param[out] out Where to write group name (must be freed with talloc_free). * @return One of the RLM_MODULE_* values. */ static rlm_rcode_t rlm_ldap_group_dn2name(ldap_instance_t const *inst, REQUEST *request, ldap_handle_t **pconn, char const *dn, char **out) { rlm_rcode_t rcode = RLM_MODULE_OK; ldap_rcode_t status; int ldap_errno; struct berval **values = NULL; char const *attrs[] = { inst->groupobj_name_attr, NULL }; LDAPMessage *result = NULL, *entry; *out = NULL; if (!inst->groupobj_name_attr) { REDEBUG("Told to resolve group DN to name but missing 'group.name_attribute' directive"); return RLM_MODULE_INVALID; } RDEBUG("Resolving group DN \"%s\" to group name", dn); status = rlm_ldap_search(inst, request, pconn, dn, LDAP_SCOPE_BASE, NULL, attrs, &result); switch (status) { case LDAP_PROC_SUCCESS: break; case LDAP_PROC_NO_RESULT: REDEBUG("Group DN \"%s\" did not resolve to an object", dn); return RLM_MODULE_INVALID; default: return RLM_MODULE_FAIL; } entry = ldap_first_entry((*pconn)->handle, result); if (!entry) { ldap_get_option((*pconn)->handle, LDAP_OPT_RESULT_CODE, &ldap_errno); REDEBUG("Failed retrieving entry: %s", ldap_err2string(ldap_errno)); rcode = RLM_MODULE_INVALID; goto finish; } values = ldap_get_values_len((*pconn)->handle, entry, inst->groupobj_name_attr); if (!values) { REDEBUG("No %s attributes found in object", inst->groupobj_name_attr); rcode = RLM_MODULE_INVALID; goto finish; } *out = rlm_ldap_berval_to_string(request, values[0]); RDEBUG("Group DN \"%s\" resolves to name \"%s\"", dn, *out); finish: if (result) ldap_msgfree(result); if (values) ldap_value_free_len(values); return rcode; }
/** 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; }