/** Search for and apply an LDAP profile * * LDAP profiles are mapped using the same attribute map as user objects, they're used to add common sets of attributes * to the request. * * @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 profile object to apply. * @param[in] expanded Structure containing a list of xlat expanded attribute names and mapping information. * @return One of the RLM_MODULE_* values. */ rlm_rcode_t rlm_ldap_map_profile(ldap_instance_t const *inst, REQUEST *request, ldap_handle_t **pconn, char const *dn, rlm_ldap_map_xlat_t const *expanded) { rlm_rcode_t rcode = RLM_MODULE_OK; ldap_rcode_t status; LDAPMessage *result = NULL, *entry = NULL; int ldap_errno; LDAP *handle = (*pconn)->handle; char filter[LDAP_MAX_FILTER_STR_LEN]; rad_assert(inst->profile_filter); /* We always have a default filter set */ if (!dn || !*dn) { return RLM_MODULE_OK; } if (radius_xlat(filter, sizeof(filter), request, inst->profile_filter, rlm_ldap_escape_func, NULL) < 0) { REDEBUG("Failed creating profile filter"); return RLM_MODULE_INVALID; } status = rlm_ldap_search(inst, request, pconn, dn, LDAP_SCOPE_BASE, filter, expanded->attrs, &result); switch (status) { case LDAP_PROC_SUCCESS: break; case LDAP_PROC_NO_RESULT: RDEBUG("Profile object \"%s\" not found", dn); return RLM_MODULE_NOTFOUND; default: return RLM_MODULE_FAIL; } rad_assert(*pconn); rad_assert(result); entry = ldap_first_entry(handle, result); if (!entry) { ldap_get_option(handle, LDAP_OPT_RESULT_CODE, &ldap_errno); REDEBUG("Failed retrieving entry: %s", ldap_err2string(ldap_errno)); rcode = RLM_MODULE_NOTFOUND; goto free_result; } RDEBUG("Processing profile attributes"); rlm_ldap_map_do(inst, request, handle, expanded, entry); free_result: ldap_msgfree(result); return rcode; }
/** Expand an LDAP URL into a query, and return a string result from that query. * */ static ssize_t ldap_xlat(void *instance, REQUEST *request, char const *fmt, char *out, size_t freespace) { ldap_rcode_t status; size_t len = 0; ldap_instance_t *inst = instance; LDAPURLDesc *ldap_url; LDAPMessage *result = NULL; LDAPMessage *entry = NULL; struct berval **values; ldap_handle_t *conn; int ldap_errno; char const *url; char const **attrs; url = fmt; if (!ldap_is_ldap_url(url)) { REDEBUG("String passed does not look like an LDAP URL"); return -1; } if (ldap_url_parse(url, &ldap_url)){ REDEBUG("Parsing LDAP URL failed"); return -1; } /* * Nothing, empty string, "*" string, or got 2 things, die. */ if (!ldap_url->lud_attrs || !ldap_url->lud_attrs[0] || !*ldap_url->lud_attrs[0] || (strcmp(ldap_url->lud_attrs[0], "*") == 0) || ldap_url->lud_attrs[1]) { REDEBUG("Bad attributes list in LDAP URL. URL must specify exactly one attribute to retrieve"); goto free_urldesc; } if (ldap_url->lud_host && ((strcmp(inst->server, ldap_url->lud_host) != 0) || ((uint32_t) ldap_url->lud_port != inst->port))) { REDEBUG("LDAP expansions must specify the same host and port as the their module instance"); goto free_urldesc; } conn = mod_conn_get(inst, request); if (!conn) goto free_urldesc; memcpy(&attrs, &ldap_url->lud_attrs, sizeof(attrs)); status = rlm_ldap_search(inst, request, &conn, ldap_url->lud_dn, ldap_url->lud_scope, ldap_url->lud_filter, attrs, &result); switch (status) { case LDAP_PROC_SUCCESS: break; default: goto free_socket; } rad_assert(conn); rad_assert(result); 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)); len = -1; goto free_result; } values = ldap_get_values_len(conn->handle, entry, ldap_url->lud_attrs[0]); if (!values) { RDEBUG("No \"%s\" attributes found in specified object", ldap_url->lud_attrs[0]); goto free_result; } if (values[0]->bv_len >= freespace) goto free_values; memcpy(out, values[0]->bv_val, values[0]->bv_len + 1); /* +1 as strlcpy expects buffer size */ len = values[0]->bv_len; free_values: ldap_value_free_len(values); free_result: ldap_msgfree(result); free_socket: mod_conn_release(inst, conn); free_urldesc: ldap_free_urldesc(ldap_url); return len; }
/** Load clients from LDAP on server start * * @param[in] inst rlm_ldap configuration. * @return -1 on error else 0. */ int rlm_ldap_load_clients(ldap_instance_t const *inst) { int ret = 0; ldap_rcode_t status; ldap_handle_t *conn = NULL; /* This needs to be updated if additional attributes need to be retrieved */ char const *attrs[7]; char const **attrs_p; LDAPMessage *result = NULL; LDAPMessage *entry; RADCLIENT *c; LDAP_DBG("Loading dynamic clients"); /* * Basic sanity checks. */ if (!inst->clientobj_identifier) { LDAP_ERR("Told to load clients but 'client.identifier_attribute' not specified"); return -1; } if (!inst->clientobj_secret) { LDAP_ERR("Told to load clients but 'client.secret_attribute' not specified"); return -1; } if (!inst->clientobj_base_dn) { LDAP_ERR("Told to load clients but 'client.base_dn' not specified"); return -1; } if (!inst->clientobj_filter) { LDAP_ERR("Told to load clients but 'client.filter' not specified"); return -1; } /* * Construct the attribute array */ attrs[0] = inst->clientobj_identifier; attrs[1] = inst->clientobj_secret; attrs_p = attrs + 2; if (inst->clientobj_shortname) { /* 2 */ *attrs_p++ = inst->clientobj_shortname; } if (inst->clientobj_type) { /* 3 */ *attrs_p++ = inst->clientobj_type; } if (inst->clientobj_server) { /* 4 */ *attrs_p++ = inst->clientobj_server; } if (inst->clientobj_require_ma) { /* 5 */ *attrs_p++ = inst->clientobj_require_ma; } *attrs_p = NULL; /* 6 - array needs to be NULL terminated */ conn = rlm_ldap_get_socket(inst, NULL); if (!conn) return -1; /* * Perform all searches as the admin user. */ if (conn->rebound) { status = rlm_ldap_bind(inst, NULL, &conn, inst->admin_dn, inst->password, true); if (status != LDAP_PROC_SUCCESS) { return -1; } rad_assert(conn); conn->rebound = false; } status = rlm_ldap_search(inst, NULL, &conn, inst->clientobj_base_dn, inst->clientobj_scope, inst->clientobj_filter, attrs, &result); switch (status) { case LDAP_PROC_SUCCESS: break; case LDAP_PROC_NO_RESULT: LDAP_INFO("No clients were found in the directory"); return 0; default: return -1; } rad_assert(conn); entry = ldap_first_entry(conn->handle, result); if (!entry) { int ldap_errno; ldap_get_option(conn->handle, LDAP_OPT_RESULT_CODE, &ldap_errno); LDAP_ERR("Failed retrieving entry: %s", ldap_err2string(ldap_errno)); ret = -1; goto finish; } do { char *dn; char **identifier = NULL; char **shortname = NULL; char **secret = NULL; char **type = NULL; char **server = NULL; char **require_ma = NULL; dn = ldap_get_dn(conn->handle, entry); /* * Check for the required attributes first */ identifier = ldap_get_values(conn->handle, entry, inst->clientobj_identifier); if (!identifier) { LDAP_WARN("Client \"%s\" missing required attribute 'identifier', skipping...", dn); goto next; } secret = ldap_get_values(conn->handle, entry, inst->clientobj_secret); if (!secret) { LDAP_WARN("Client \"%s\" missing required attribute 'secret', skipping...", dn); goto next; } if (inst->clientobj_shortname) { shortname = ldap_get_values(conn->handle, entry, inst->clientobj_shortname); if (!shortname) { LDAP_DBG("Client \"%s\" missing optional attribute 'shortname'", dn); } } if (inst->clientobj_type) { type = ldap_get_values(conn->handle, entry, inst->clientobj_type); if (!type) { LDAP_DBG("Client \"%s\" missing optional attribute 'type'", dn); } } if (inst->clientobj_server) { server = ldap_get_values(conn->handle, entry, inst->clientobj_server); if (!server) { LDAP_DBG("Client \"%s\" missing optional attribute 'server'", dn); } } if (inst->clientobj_require_ma) { require_ma = ldap_get_values(conn->handle, entry, inst->clientobj_require_ma); if (!require_ma) { LDAP_DBG("Client \"%s\" missing optional attribute 'require_ma'", dn); } } /* FIXME: We should really pass a proper ctx */ c = client_from_query(NULL, identifier[0], secret[0], shortname ? shortname[0] : NULL, type ? type[0] : NULL, server ? server[0] : NULL, require_ma ? strncmp(require_ma[0], "true", 4) == 0 : false); if (!c) { goto next; } if (!client_add(NULL, c)) { WARN("Failed to add client, possible duplicate?"); client_free(c); goto next; } LDAP_DBG("Client \"%s\" added", dn); next: ldap_memfree(dn); if (identifier) ldap_value_free(identifier); if (shortname) ldap_value_free(shortname); if (secret) ldap_value_free(secret); if (type) ldap_value_free(type); if (server) ldap_value_free(server); } while((entry = ldap_next_entry(conn->handle, entry))); finish: if (result) { ldap_msgfree(result); } return ret; }
/** Expand an LDAP URL into a query, and return a string result from that query. * */ static ssize_t ldap_xlat(void *instance, REQUEST *request, char const *fmt, char *out, size_t freespace) { ldap_rcode_t status; size_t len = 0; ldap_instance_t *inst = instance; LDAPURLDesc *ldap_url; LDAPMessage *result = NULL; LDAPMessage *entry = NULL; char **vals; ldap_handle_t *conn; int ldap_errno; char const *url; char const **attrs; url = fmt; if (!ldap_is_ldap_url(url)) { REDEBUG("String passed does not look like an LDAP URL"); return -1; } if (ldap_url_parse(url, &ldap_url)){ REDEBUG("Parsing LDAP URL failed"); return -1; } /* * Nothing, empty string, "*" string, or got 2 things, die. */ if (!ldap_url->lud_attrs || !ldap_url->lud_attrs[0] || !*ldap_url->lud_attrs[0] || (strcmp(ldap_url->lud_attrs[0], "*") == 0) || ldap_url->lud_attrs[1]) { REDEBUG("Bad attributes list in LDAP URL. URL must specify exactly one attribute to retrieve"); goto free_urldesc; } if (ldap_url->lud_host && ((strncmp(inst->server, ldap_url->lud_host, strlen(inst->server)) != 0) || (ldap_url->lud_port != inst->port))) { RDEBUG("Requested server/port is \"%s:%i\"", ldap_url->lud_host, inst->port); goto free_urldesc; } conn = rlm_ldap_get_socket(inst, request); if (!conn) goto free_urldesc; memcpy(&attrs, &ldap_url->lud_attrs, sizeof(attrs)); status = rlm_ldap_search(inst, request, &conn, ldap_url->lud_dn, ldap_url->lud_scope, ldap_url->lud_filter, attrs, &result); switch (status) { case LDAP_PROC_SUCCESS: break; case LDAP_PROC_NO_RESULT: RDEBUG("Search returned not found"); default: goto free_socket; } rad_assert(conn); rad_assert(result); 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)); len = -1; goto free_result; } vals = ldap_get_values(conn->handle, entry, ldap_url->lud_attrs[0]); if (!vals) { RDEBUG("No \"%s\" attributes found in specified object", ldap_url->lud_attrs[0]); goto free_result; } len = strlen(vals[0]); if (len >= freespace){ goto free_vals; } strlcpy(out, vals[0], freespace); free_vals: ldap_value_free(vals); free_result: ldap_msgfree(result); free_socket: rlm_ldap_release_socket(inst, conn); free_urldesc: ldap_free_urldesc(ldap_url); return len; }
/** 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; int name_is_dn = false, value_is_dn = false; LDAPMessage *result = NULL; LDAPMessage *entry = NULL; char **vals = NULL; char const *name = check->vp_strvalue; char const *attrs[] = { inst->userobj_membership_attr, NULL }; int i, count, ldap_errno; RDEBUG2("Checking user object membership (%s) attributes", inst->userobj_membership_attr); 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: 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; } vals = ldap_get_values((*pconn)->handle, entry, inst->userobj_membership_attr); if (!vals) { 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(name); count = ldap_count_values(vals); for (i = 0; i < count; i++) { value_is_dn = rlm_ldap_is_dn(vals[i]); RDEBUG2("Processing group membership value \"%s\"", vals[i]); /* * Both literal group names, do case sensitive comparison */ if (!name_is_dn && !value_is_dn) { if (strcmp(vals[i], name) == 0){ RDEBUG("User found. Comparison between membership: name, check: name]"); rcode = RLM_MODULE_OK; goto finish; } continue; } /* * Both DNs, do case insensitive comparison */ if (name_is_dn && value_is_dn) { if (strcasecmp(vals[i], name) == 0){ RDEBUG("User found. Comparison between membership: dn, check: dn"); 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; int eq; ret = rlm_ldap_group_dn2name(inst, request, pconn, name, &resolved); if (ret != RLM_MODULE_OK) { rcode = ret; goto finish; } eq = strcmp(vals[i], resolved); talloc_free(resolved); if (eq == 0){ RDEBUG("User found. Comparison between membership: name, check: name " "(resolved from DN)"); 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; int eq; ret = rlm_ldap_group_dn2name(inst, request, pconn, vals[i], &resolved); if (ret != RLM_MODULE_OK) { rcode = ret; goto finish; } eq = strcmp(resolved, name); talloc_free(resolved); if (eq == 0){ RDEBUG("User found. Comparison between membership: name (resolved from DN), " "check: name"); rcode = RLM_MODULE_OK; goto finish; } continue; } rad_assert(0); } finish: if (vals) { ldap_value_free(vals); } if (result) { ldap_msgfree(result); } return rcode; }
/** Query the LDAP directory to check if a group object includes a user object as a member * * @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] check vp containing the group value (name or dn). * @return One of the RLM_MODULE_* values. */ rlm_rcode_t rlm_ldap_check_groupobj_dynamic(ldap_instance_t const *inst, REQUEST *request, ldap_handle_t **pconn, VALUE_PAIR *check) { ldap_rcode_t status; char base_dn[LDAP_MAX_DN_STR_LEN + 1]; char filter[LDAP_MAX_FILTER_STR_LEN + 1]; char const *dn = base_dn; char const *name = check->vp_strvalue; rad_assert(inst->groupobj_base_dn); RDEBUG2("Checking for user in group objects"); if (rlm_ldap_is_dn(name)) { char const *filters[] = { inst->groupobj_filter, inst->groupobj_membership_filter }; if (rlm_ldap_xlat_filter(request, filters, sizeof(filters) / sizeof(*filters), filter, sizeof(filter)) < 0) { return RLM_MODULE_INVALID; } dn = name; } else { char name_filter[LDAP_MAX_FILTER_STR_LEN]; char const *filters[] = { name_filter, inst->groupobj_filter, inst->groupobj_membership_filter }; if (!inst->groupobj_name_attr) { REDEBUG("Told to search for group by name, but missing 'group.name_attribute' " "directive"); return RLM_MODULE_INVALID; } snprintf(name_filter, sizeof(name_filter), "(%s=%s)", inst->groupobj_name_attr, name); if (rlm_ldap_xlat_filter(request, filters, sizeof(filters) / sizeof(*filters), filter, sizeof(filter)) < 0) { return RLM_MODULE_INVALID; } /* * rlm_ldap_find_user does this, too. Oh well. */ if (radius_xlat(base_dn, sizeof(base_dn), request, inst->groupobj_base_dn, rlm_ldap_escape_func, NULL) < 0) { REDEBUG("Failed creating base_dn"); return RLM_MODULE_INVALID; } } status = rlm_ldap_search(inst, request, pconn, dn, inst->groupobj_scope, filter, NULL, NULL); switch (status) { case LDAP_PROC_SUCCESS: RDEBUG("User found in group object"); break; case LDAP_PROC_NO_RESULT: RDEBUG("Search returned not found"); return RLM_MODULE_NOTFOUND; default: return RLM_MODULE_FAIL; } return RLM_MODULE_OK; }
/** Convert multiple group names into a DNs * * Given an array of group names, builds a filter matching all names, then retrieves all group objects * and stores the DN associated with each group object. * * @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] names to covert to DNs (NULL terminated). * @param[out] out Where to write the DNs. DNs must be freed with ldap_memfree(). Will be NULL terminated. * @param[in] outlen Size of out. * @return One of the RLM_MODULE_* values. */ static rlm_rcode_t rlm_ldap_group_name2dn(ldap_instance_t const *inst, REQUEST *request, ldap_handle_t **pconn, char **names, char **out, size_t outlen) { rlm_rcode_t rcode = RLM_MODULE_OK; ldap_rcode_t status; int ldap_errno; unsigned int name_cnt = 0; unsigned int entry_cnt; char const *attrs[] = { NULL }; LDAPMessage *result = NULL, *entry; char **name = names; char **dn = out; char buffer[LDAP_MAX_GROUP_NAME_LEN + 1]; char *filter; *dn = NULL; if (!*names) { return RLM_MODULE_OK; } if (!inst->groupobj_name_attr) { REDEBUG("Told to convert group names to DNs but missing 'group.name_attribute' directive"); return RLM_MODULE_INVALID; } RDEBUG("Converting group name(s) to group DN(s)"); /* * It'll probably only save a few ms in network latency, but it means we can send a query * for the entire group list at once. */ filter = talloc_asprintf(request, "%s%s%s", inst->groupobj_filter ? "(&" : "", inst->groupobj_filter ? inst->groupobj_filter : "", names[0] && names[1] ? "(|" : ""); while (*name) { rlm_ldap_escape_func(request, buffer, sizeof(buffer), *name++, NULL); filter = talloc_asprintf_append_buffer(filter, "(%s=%s)", inst->groupobj_name_attr, buffer); name_cnt++; } filter = talloc_asprintf_append_buffer(filter, "%s%s", inst->groupobj_filter ? ")" : "", names[0] && names[1] ? ")" : ""); status = rlm_ldap_search(inst, request, pconn, inst->groupobj_base_dn, inst->groupobj_scope, filter, attrs, &result); switch (status) { case LDAP_PROC_SUCCESS: break; case LDAP_PROC_NO_RESULT: RDEBUG("Tried to resolve group name(s) to DNs but got no results"); goto finish; default: rcode = RLM_MODULE_FAIL; goto finish; } entry_cnt = ldap_count_entries((*pconn)->handle, result); if (entry_cnt > name_cnt) { REDEBUG("Number of DNs exceeds number of names, group and/or dn should be more restrictive"); rcode = RLM_MODULE_INVALID; goto finish; } if (entry_cnt > (outlen - 1)) { REDEBUG("Number of DNs exceeds limit (%zu)", outlen - 1); rcode = RLM_MODULE_INVALID; goto finish; } if (entry_cnt < name_cnt) { RWDEBUG("Got partial mapping of group names (%i) to DNs (%i), membership information may be incomplete", name_cnt, entry_cnt); } 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; } do { *dn = ldap_get_dn((*pconn)->handle, entry); RDEBUG("Got group DN \"%s\"", *dn); dn++; } while((entry = ldap_next_entry((*pconn)->handle, entry))); *dn = NULL; finish: talloc_free(filter); if (result) { ldap_msgfree(result); } /* * Be nice and cleanup the output array if we error out. */ if (rcode != RLM_MODULE_OK) { dn = out; while(*dn) ldap_memfree(*dn++); *dn = NULL; } 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. * @return One of the RLM_MODULE_* values. */ rlm_rcode_t rlm_ldap_cacheable_groupobj(ldap_instance_t const *inst, REQUEST *request, ldap_handle_t **pconn) { rlm_rcode_t rcode = RLM_MODULE_OK; ldap_rcode_t status; int ldap_errno; char **vals; LDAPMessage *result = NULL; LDAPMessage *entry; char base_dn[LDAP_MAX_DN_STR_LEN]; char const *filters[] = { inst->groupobj_filter, inst->groupobj_membership_filter }; char filter[LDAP_MAX_FILTER_STR_LEN + 1]; char const *attrs[] = { inst->groupobj_name_attr, NULL }; char *dn; rad_assert(inst->groupobj_base_dn); if (!inst->groupobj_membership_filter) { RDEBUG2("Skipping caching group objects as directive 'group.membership_filter' is not set"); return RLM_MODULE_OK; } if (rlm_ldap_xlat_filter(request, filters, sizeof(filters) / sizeof(*filters), filter, sizeof(filter)) < 0) { return RLM_MODULE_INVALID; } if (radius_xlat(base_dn, sizeof(base_dn), request, inst->groupobj_base_dn, rlm_ldap_escape_func, NULL) < 0) { REDEBUG("Failed creating base_dn"); return RLM_MODULE_INVALID; } status = rlm_ldap_search(inst, request, pconn, base_dn, inst->groupobj_scope, filter, attrs, &result); switch (status) { case LDAP_PROC_SUCCESS: break; case LDAP_PROC_NO_RESULT: RDEBUG2("No cacheable group memberships found in group objects"); 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)); goto finish; } do { if (inst->cacheable_group_dn) { dn = ldap_get_dn((*pconn)->handle, entry); pairmake(request, &request->config_items, inst->cache_da->name, dn, T_OP_ADD); RDEBUG("Added %s with value \"%s\" to control list", inst->cache_da->name, dn); ldap_memfree(dn); } if (inst->cacheable_group_name) { vals = ldap_get_values((*pconn)->handle, entry, inst->groupobj_name_attr); if (!vals) { continue; } pairmake(request, &request->config_items, inst->cache_da->name, *vals, T_OP_ADD); RDEBUG("Added %s with value \"%s\" to control list", inst->cache_da->name, *vals); ldap_value_free(vals); } } while((entry = ldap_next_entry((*pconn)->handle, entry))); finish: if (result) { ldap_msgfree(result); } 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; char **vals = NULL; char const *attrs[] = { inst->groupobj_name_attr, NULL }; LDAPMessage *result = NULL, *entry; *out = NULL; if (!inst->groupobj_name_attr) { REDEBUG("Told to convert group DN to name but missing 'group.name_attribute' directive"); return RLM_MODULE_INVALID; } RDEBUG("Converting group DN to group Name"); 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("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; } vals = ldap_get_values((*pconn)->handle, entry, inst->groupobj_name_attr); if (!vals) { REDEBUG("No %s attributes found in object", inst->groupobj_name_attr); rcode = RLM_MODULE_INVALID; goto finish; } RDEBUG("Group name is \"%s\"", vals[0]); *out = talloc_strdup(request, vals[0]); finish: if (result) { ldap_msgfree(result); } if (vals) { ldap_value_free(vals); } return rcode; }
/** Load clients from LDAP on server start * * @param[in] inst rlm_ldap configuration. * @param[in] cs to load client attribute/LDAP attribute mappings from. * @return -1 on error else 0. */ int rlm_ldap_client_load(ldap_instance_t const *inst, CONF_SECTION *cs) { int ret = 0; ldap_rcode_t status; ldap_handle_t *conn = NULL; char const **attrs = NULL; CONF_PAIR *cp; int count = 0, idx = 0; LDAPMessage *result = NULL; LDAPMessage *entry; char *dn = NULL; RADCLIENT *c; LDAP_DBG("Loading dynamic clients"); rad_assert(inst->clientobj_base_dn); if (!inst->clientobj_filter) { LDAP_ERR("Told to load clients but 'client.filter' not specified"); return -1; } count = cf_pair_count(cs); count++; /* * Create an array of LDAP attributes to feed to rlm_ldap_search. */ attrs = talloc_array(inst, char const *, count); if (rlm_ldap_client_get_attrs(attrs, &idx, cs) < 0) return -1; conn = rlm_ldap_get_socket(inst, NULL); if (!conn) return -1; /* * Perform all searches as the admin user. */ if (conn->rebound) { status = rlm_ldap_bind(inst, NULL, &conn, inst->admin_dn, inst->password, true); if (status != LDAP_PROC_SUCCESS) { ret = -1; goto finish; } rad_assert(conn); conn->rebound = false; } status = rlm_ldap_search(inst, NULL, &conn, inst->clientobj_base_dn, inst->clientobj_scope, inst->clientobj_filter, attrs, &result); switch (status) { case LDAP_PROC_SUCCESS: break; case LDAP_PROC_NO_RESULT: LDAP_INFO("No clients were found in the directory"); ret = 0; goto finish; default: ret = -1; goto finish; } rad_assert(conn); entry = ldap_first_entry(conn->handle, result); if (!entry) { int ldap_errno; ldap_get_option(conn->handle, LDAP_OPT_RESULT_CODE, &ldap_errno); LDAP_ERR("Failed retrieving entry: %s", ldap_err2string(ldap_errno)); ret = -1; goto finish; } do { CONF_SECTION *cc; char *id; char **value; id = dn = ldap_get_dn(conn->handle, entry); cp = cf_pair_find(cs, "identifier"); if (cp) { value = ldap_get_values(conn->handle, entry, cf_pair_value(cp)); if (value) id = value[0]; } /* * Iterate over mapping sections */ cc = cf_section_alloc(NULL, "client", id); if (rlm_ldap_client_map_section(inst, cc, cs, conn, entry) < 0) { talloc_free(cc); ret = -1; goto finish; } /* *@todo these should be parented from something */ c = client_afrom_cs(NULL, cc, false); if (!c) { talloc_free(cc); ret = -1; goto finish; } /* * Client parents the CONF_SECTION which defined it */ talloc_steal(c, cc); if (!client_add(NULL, c)) { LDAP_ERR("Failed to add client \"%s\", possible duplicate?", dn); ret = -1; client_free(c); goto finish; } LDAP_DBG("Client \"%s\" added", dn); ldap_memfree(dn); dn = NULL; } while ((entry = ldap_next_entry(conn->handle, entry))); finish: talloc_free(attrs); if (dn) ldap_memfree(dn); if (result) ldap_msgfree(result); rlm_ldap_release_socket(inst, conn); return ret; }
/** 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; }
/** Query the LDAP directory to check if a group object includes a user object as a member * * @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] check vp containing the group value (name or dn). * @return One of the RLM_MODULE_* values. */ rlm_rcode_t rlm_ldap_check_groupobj_dynamic(ldap_instance_t const *inst, REQUEST *request, ldap_handle_t **pconn, VALUE_PAIR *check) { ldap_rcode_t status; char base_dn[LDAP_MAX_DN_STR_LEN + 1]; char filter[LDAP_MAX_FILTER_STR_LEN + 1]; char const *dn = base_dn; int ret; rad_assert(inst->groupobj_base_dn); switch (check->op) { case T_OP_CMP_EQ: case T_OP_CMP_FALSE: case T_OP_CMP_TRUE: case T_OP_REG_EQ: case T_OP_REG_NE: break; default: REDEBUG("Operator \"%s\" not allowed for LDAP group comparisons", fr_int2str(fr_tokens, check->op, "<INVALID>")); return 1; } RDEBUG2("Checking for user in group objects"); if (rlm_ldap_is_dn(check->vp_strvalue, check->vp_length)) { char const *filters[] = { inst->groupobj_filter, inst->groupobj_membership_filter }; RINDENT(); ret = rlm_ldap_xlat_filter(request, filters, sizeof(filters) / sizeof(*filters), filter, sizeof(filter)); REXDENT(); if (ret < 0) return RLM_MODULE_INVALID; dn = check->vp_strvalue; } else { char name_filter[LDAP_MAX_FILTER_STR_LEN]; char const *filters[] = { name_filter, inst->groupobj_filter, inst->groupobj_membership_filter }; if (!inst->groupobj_name_attr) { REDEBUG("Told to search for group by name, but missing 'group.name_attribute' " "directive"); return RLM_MODULE_INVALID; } snprintf(name_filter, sizeof(name_filter), "(%s=%s)", inst->groupobj_name_attr, check->vp_strvalue); RINDENT(); ret = rlm_ldap_xlat_filter(request, filters, sizeof(filters) / sizeof(*filters), filter, sizeof(filter)); REXDENT(); if (ret < 0) return RLM_MODULE_INVALID; /* * rlm_ldap_find_user does this, too. Oh well. */ RINDENT(); ret = radius_xlat(base_dn, sizeof(base_dn), request, inst->groupobj_base_dn, rlm_ldap_escape_func, NULL); REXDENT(); if (ret < 0) { REDEBUG("Failed creating base_dn"); return RLM_MODULE_INVALID; } } RINDENT(); status = rlm_ldap_search(inst, request, pconn, dn, inst->groupobj_scope, filter, NULL, NULL); REXDENT(); switch (status) { case LDAP_PROC_SUCCESS: RDEBUG("User found in group object \"%s\"", dn); break; case LDAP_PROC_NO_RESULT: return RLM_MODULE_NOTFOUND; default: return RLM_MODULE_FAIL; } return RLM_MODULE_OK; }
/** 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. * @return One of the RLM_MODULE_* values. */ rlm_rcode_t rlm_ldap_cacheable_groupobj(rlm_ldap_t const *inst, REQUEST *request, ldap_handle_t **pconn) { rlm_rcode_t rcode = RLM_MODULE_OK; ldap_rcode_t status; int ldap_errno; LDAPMessage *result = NULL; LDAPMessage *entry; char const *base_dn; char base_dn_buff[LDAP_MAX_DN_STR_LEN]; char const *filters[] = { inst->groupobj_filter, inst->groupobj_membership_filter }; char filter[LDAP_MAX_FILTER_STR_LEN + 1]; char const *attrs[] = { inst->groupobj_name_attr, NULL }; VALUE_PAIR *vp; char *dn; rad_assert(inst->groupobj_base_dn); if (!inst->groupobj_membership_filter) { RDEBUG2("Skipping caching group objects as directive 'group.membership_filter' is not set"); return RLM_MODULE_OK; } if (rlm_ldap_xlat_filter(request, filters, sizeof(filters) / sizeof(*filters), filter, sizeof(filter)) < 0) { return RLM_MODULE_INVALID; } if (tmpl_expand(&base_dn, base_dn_buff, sizeof(base_dn_buff), request, inst->groupobj_base_dn, rlm_ldap_escape_func, NULL) < 0) { REDEBUG("Failed creating base_dn"); return RLM_MODULE_INVALID; } status = rlm_ldap_search(&result, inst, request, pconn, base_dn, inst->groupobj_scope, filter, attrs, NULL, NULL); switch (status) { case LDAP_PROC_SUCCESS: break; case LDAP_PROC_NO_RESULT: RDEBUG2("No cacheable group memberships found in group objects"); goto finish; default: rcode = RLM_MODULE_FAIL; 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)); goto finish; } RDEBUG("Adding cacheable group object memberships"); do { if (inst->cacheable_group_dn) { dn = ldap_get_dn((*pconn)->handle, entry); if (!dn) { ldap_get_option((*pconn)->handle, LDAP_OPT_RESULT_CODE, &ldap_errno); REDEBUG("Retrieving object DN from entry failed: %s", ldap_err2string(ldap_errno)); goto finish; } rlm_ldap_normalise_dn(dn, dn); MEM(vp = pair_make_config(inst->cache_da->name, NULL, T_OP_ADD)); fr_pair_value_strcpy(vp, dn); RINDENT(); RDEBUG("&control:%s += \"%s\"", inst->cache_da->name, dn); REXDENT(); ldap_memfree(dn); } if (inst->cacheable_group_name) { struct berval **values; values = ldap_get_values_len((*pconn)->handle, entry, inst->groupobj_name_attr); if (!values) continue; MEM(vp = pair_make_config(inst->cache_da->name, NULL, T_OP_ADD)); fr_pair_value_bstrncpy(vp, values[0]->bv_val, values[0]->bv_len); RINDENT(); RDEBUG("&control:%s += \"%.*s\"", inst->cache_da->name, (int)values[0]->bv_len, values[0]->bv_val); REXDENT(); ldap_value_free_len(values); } } while ((entry = ldap_next_entry((*pconn)->handle, entry))); finish: if (result) ldap_msgfree(result); return rcode; }