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, version 2 if the
4 * License as published by the Free Software Foundation.
6 * This program is distributed in the hope that it will be useful,
7 * but WITHOUT ANY WARRANTY; without even the implied warranty of
8 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 * GNU General Public License for more details.
11 * You should have received a copy of the GNU General Public License
12 * along with this program; if not, write to the Free Software
13 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19 * @brief LDAP authorization and authentication module.
21 * @author Arran Cudbard-Bell <a.cudbardb@freeradius.org>
22 * @author Alan DeKok <aland@freeradius.org>
24 * @copyright 2013 Network RADIUS SARL <info@networkradius.com>
25 * @copyright 2012-2013 Arran Cudbard-Bell <a.cudbardb@freeradius.org>
26 * @copyright 2012 Alan DeKok <aland@freeradius.org>
27 * @copyright 1999-2013 The FreeRADIUS Server Project.
29 #include <freeradius-devel/ident.h>
32 #include <freeradius-devel/rad_assert.h>
42 static CONF_PARSER tls_config[] = {
43 {"start_tls", PW_TYPE_BOOLEAN, offsetof(ldap_instance_t, start_tls), NULL, "no"},
44 {"cacertfile", PW_TYPE_FILENAME, offsetof(ldap_instance_t, tls_cacertfile), NULL, NULL},
45 {"cacertdir", PW_TYPE_FILENAME, offsetof(ldap_instance_t, tls_cacertdir), NULL, NULL},
46 {"certfile", PW_TYPE_FILENAME, offsetof(ldap_instance_t, tls_certfile), NULL, NULL},
47 {"keyfile", PW_TYPE_FILENAME, offsetof(ldap_instance_t, tls_keyfile), NULL, NULL}, // OK if it changes on HUP
48 {"randfile", PW_TYPE_STRING_PTR, offsetof(ldap_instance_t, tls_randfile), NULL, NULL},
49 {"require_cert",PW_TYPE_STRING_PTR, offsetof(ldap_instance_t, tls_require_cert), NULL, TLS_DEFAULT_VERIFY},
50 { NULL, -1, 0, NULL, NULL }
56 static CONF_PARSER attr_config[] = {
57 /* LDAP attribute name that controls remote access */
58 {"userobj_access_attr", PW_TYPE_STRING_PTR, offsetof(ldap_instance_t,userobj_access_attr), NULL, NULL},
59 {"access_positive", PW_TYPE_BOOLEAN, offsetof(ldap_instance_t,access_positive), NULL, "yes"},
60 {"base_filter", PW_TYPE_STRING_PTR, offsetof(ldap_instance_t,base_filter), NULL, "(objectclass=radiusprofile)"},
61 {"default_profile", PW_TYPE_STRING_PTR, offsetof(ldap_instance_t,default_profile), NULL, NULL},
62 {"profile_attribute", PW_TYPE_STRING_PTR, offsetof(ldap_instance_t,profile_attr), NULL, NULL},
64 { NULL, -1, 0, NULL, NULL }
70 static CONF_PARSER group_config[] = {
72 * Group checks. These could probably be done via dynamic xlat's.
74 {"name_attribute", PW_TYPE_STRING_PTR, offsetof(ldap_instance_t,groupobj_name_attr), NULL, "cn"},
75 {"membership_filter", PW_TYPE_STRING_PTR, offsetof(ldap_instance_t,groupobj_membership_filter), NULL,
76 "(|(&(objectClass=GroupOfNames)(member=%{Ldap-UserDn}))(&(objectClass=GroupOfUniqueNames)"
77 "(uniquemember=%{Ldap-UserDn})))"},
78 {"membership_attribute", PW_TYPE_STRING_PTR, offsetof(ldap_instance_t,userobj_membership_attr), NULL, NULL},
79 {"name_cacheable", PW_TYPE_STRING_PTR, offsetof(ldap_instance_t,cacheable_group_name), NULL, NULL},
80 {"dn_cacheable", PW_TYPE_STRING_PTR, offsetof(ldap_instance_t,cacheable_group_dn), NULL, NULL},
81 { NULL, -1, 0, NULL, NULL }
85 * Reference for accounting updates
87 static const CONF_PARSER acct_section_config[] = {
88 {"reference", PW_TYPE_STRING_PTR, offsetof(ldap_acct_section_t, reference), NULL, "."},
89 {NULL, -1, 0, NULL, NULL}
93 * Various options that don't belong in the main configuration.
95 * Note that these overlap a bit with the connection pool code!
97 static CONF_PARSER option_config[] = {
99 * Debugging flags to the server
101 {"ldap_debug", PW_TYPE_INTEGER, offsetof(ldap_instance_t,ldap_debug), NULL, "0x0000"},
103 {"chase_referrals", PW_TYPE_BOOLEAN, offsetof(ldap_instance_t,chase_referrals), NULL, NULL},
105 {"rebind", PW_TYPE_BOOLEAN,offsetof(ldap_instance_t,rebind), NULL, NULL},
107 /* timeout on network activity */
108 {"net_timeout", PW_TYPE_INTEGER, offsetof(ldap_instance_t,net_timeout), NULL, "10"},
110 /* timeout for search results */
111 {"timeout", PW_TYPE_INTEGER, offsetof(ldap_instance_t,timeout), NULL, "20"},
113 /* allow server unlimited time for search (server-side limit) */
114 {"timelimit", PW_TYPE_INTEGER, offsetof(ldap_instance_t,timelimit), NULL, "20"},
116 #ifdef LDAP_OPT_X_KEEPALIVE_IDLE
117 {"idle", PW_TYPE_INTEGER, offsetof(ldap_instance_t,keepalive_idle), NULL, "60"},
119 #ifdef LDAP_OPT_X_KEEPALIVE_PROBES
120 {"probes", PW_TYPE_INTEGER, offsetof(ldap_instance_t,keepalive_probes), NULL, "3"},
122 #ifdef LDAP_OPT_ERROR_NUMBER
123 {"interval", PW_TYPE_INTEGER, offsetof(ldap_instance_t,keepalive_interval), NULL, "30"},
125 { NULL, -1, 0, NULL, NULL }
129 static const CONF_PARSER module_config[] = {
130 {"server", PW_TYPE_STRING_PTR, offsetof(ldap_instance_t,server), NULL, "localhost"},
131 {"port", PW_TYPE_INTEGER, offsetof(ldap_instance_t,port), NULL, "389"},
133 {"password", PW_TYPE_STRING_PTR, offsetof(ldap_instance_t,password), NULL, ""},
134 {"identity", PW_TYPE_STRING_PTR, offsetof(ldap_instance_t,login), NULL, ""},
139 {"basedn", PW_TYPE_STRING_PTR, offsetof(ldap_instance_t,basedn), NULL, "o=notexist"},
141 {"filter", PW_TYPE_STRING_PTR, offsetof(ldap_instance_t,userobj_filter), NULL, "(uid=%u)"},
144 /* support for eDirectory Universal Password */
145 {"edir", PW_TYPE_BOOLEAN, offsetof(ldap_instance_t,edir), NULL, NULL}, /* NULL defaults to "no" */
148 * Attempt to bind with the Cleartext password we got from eDirectory
149 * Universal password for additional authorization checks.
151 {"edir_autz", PW_TYPE_BOOLEAN, offsetof(ldap_instance_t,edir_autz), NULL, NULL}, /* NULL defaults to "no" */
155 * Terrible things which should be deleted.
157 { "profiles", PW_TYPE_SUBSECTION, 0, NULL, (const void *) attr_config },
159 { "group", PW_TYPE_SUBSECTION, 0, NULL, (const void *) group_config },
161 { "options", PW_TYPE_SUBSECTION, 0, NULL,
162 (const void *) option_config },
164 { "tls", PW_TYPE_SUBSECTION, 0, NULL, (const void *) tls_config },
166 {NULL, -1, 0, NULL, NULL}
169 /** Expand an LDAP URL into a query, and return a string result from that query.
172 static size_t ldap_xlat(void *instance, REQUEST *request, const char *fmt,
173 char *out, size_t freespace)
177 ldap_instance_t *inst = instance;
178 LDAPURLDesc *ldap_url;
179 LDAPMessage *result = NULL;
180 LDAPMessage *entry = NULL;
186 char buffer[LDAP_MAX_FILTER_STR_LEN];
188 if (strchr(fmt, '%') != NULL) {
189 if (!radius_xlat(buffer, sizeof(buffer), fmt, request, rlm_ldap_escape_func, NULL)) {
190 RDEBUGE("Unable to create LDAP URL");
198 if (!ldap_is_ldap_url(url)) {
199 RDEBUGE("String passed does not look like an LDAP URL");
203 if (ldap_url_parse(url, &ldap_url)){
204 RDEBUGE("Parsing LDAP URL failed");
209 * Nothing, empty string, "*" string, or got 2 things, die.
211 if (!ldap_url->lud_attrs || !ldap_url->lud_attrs[0] ||
212 !*ldap_url->lud_attrs[0] ||
213 (strcmp(ldap_url->lud_attrs[0], "*") == 0) ||
214 ldap_url->lud_attrs[1]) {
215 RDEBUGE("Bad attributes list in LDAP URL. "
216 "URL must specify exactly one attribute to "
222 if (ldap_url->lud_host &&
223 ((strncmp(inst->server, ldap_url->lud_host, strlen(inst->server)) != 0) ||
224 (ldap_url->lud_port != inst->port))) {
225 RDEBUG("Requested server/port is \"%s:%i\"", ldap_url->lud_host, inst->port);
230 conn = rlm_ldap_get_socket(inst, request);
231 if (!conn) goto free_urldesc;
233 memcpy(&attrs, &ldap_url->lud_attrs, sizeof(attrs));
235 status = rlm_ldap_search(inst, request, &conn, ldap_url->lud_dn, ldap_url->lud_scope, ldap_url->lud_filter,
238 case LDAP_PROC_SUCCESS:
240 case LDAP_PROC_NO_RESULT:
241 RDEBUG("Search returned not found");
249 entry = ldap_first_entry(conn->handle, result);
251 ldap_get_option(conn->handle, LDAP_OPT_RESULT_CODE, &ldap_errno);
252 RDEBUGE("Failed retrieving entry: %s", ldap_err2string(ldap_errno));
256 vals = ldap_get_values(conn->handle, entry, ldap_url->lud_attrs[0]);
258 RDEBUG("No \"%s\" attributes found in specified object", ldap_url->lud_attrs[0]);
262 length = strlen(vals[0]);
263 if (length >= freespace){
268 strlcpy(out, vals[0], freespace);
271 ldap_value_free(vals);
273 ldap_msgfree(result);
275 rlm_ldap_release_socket(inst, conn);
277 ldap_free_urldesc(ldap_url);
282 /** Perform LDAP-Group comparison checking
284 * Attempts to match users to groups using a variety of methods.
286 * @param instance of the rlm_ldap module.
287 * @param request Current request.
288 * @param thing Unknown.
289 * @param check Which group to check for user membership.
290 * @param check_pairs Unknown.
291 * @param reply_pairs Unknown.
292 * @return 1 on failure (or if the user is not a member), else 0.
294 static int rlm_ldap_groupcmp(void *instance, REQUEST *request, UNUSED VALUE_PAIR *thing, VALUE_PAIR *check,
295 UNUSED VALUE_PAIR *check_pairs, UNUSED VALUE_PAIR **reply_pairs)
297 ldap_instance_t *inst = instance;
300 int i, found = FALSE;
301 LDAPMessage *result = NULL;
302 LDAPMessage *entry = NULL;
304 int check_is_dn = FALSE, value_is_dn = FALSE;
306 const char *group_attrs[] = {inst->userobj_membership_attr, NULL};
310 char gr_filter[LDAP_MAX_FILTER_STR_LEN];
311 char filter[LDAP_MAX_FILTER_STR_LEN];
312 char basedn[LDAP_MAX_FILTER_STR_LEN];
314 RDEBUG("Searching for user in group \"%s\"", check->vp_strvalue);
316 if (check->length == 0) {
317 RDEBUG("Cannot do comparison (group name is empty)");
321 conn = rlm_ldap_get_socket(inst, request);
325 * This is used in the default membership filter.
327 user_dn = rlm_ldap_find_user(inst, request, &conn, group_attrs, FALSE, &result, &rcode);
329 rlm_ldap_release_socket(inst, conn);
335 if (!inst->groupobj_membership_filter) goto check_attr;
337 if (!radius_xlat(gr_filter, sizeof(gr_filter),
338 inst->groupobj_membership_filter, request, rlm_ldap_escape_func,
340 RDEBUGE("Failed creating group filter");
346 * If it's a DN, use that.
348 check_is_dn = rlm_ldap_is_dn(check->vp_strvalue);
350 strlcpy(filter, gr_filter, sizeof(filter));
351 strlcpy(basedn, check->vp_strvalue, sizeof(basedn));
353 snprintf(filter, sizeof(filter), "(&(%s=%s)%s%s)",
354 inst->groupobj_name_attr,
355 check->vp_strvalue, inst->base_filter, gr_filter);
358 * rlm_ldap_find_user does this, too. Oh well.
360 if (!radius_xlat(basedn, sizeof(basedn), inst->basedn, request, rlm_ldap_escape_func, NULL)) {
361 RDEBUGE("Failed creating basedn");
367 status = rlm_ldap_search(inst, request, &conn, basedn, LDAP_SCOPE_SUBTREE, filter, NULL, NULL);
369 case LDAP_PROC_SUCCESS:
370 RDEBUG("User found in group object");
373 case LDAP_PROC_NO_RESULT:
374 RDEBUG("Search returned not found");
384 * Else the search returned NOTFOUND. See if we're
385 * configured to search for group membership using user
388 if (!inst->userobj_membership_attr) {
389 RDEBUG("Group object \"%s\" not found, or user is not a member", check->vp_strvalue);
390 rlm_ldap_release_socket(inst, conn);
396 RDEBUG2("Checking user object membership (%s) attributes", inst->userobj_membership_attr);
398 snprintf(filter ,sizeof(filter), "(objectclass=*)");
400 status = rlm_ldap_search(inst, request, &conn, user_dn, LDAP_SCOPE_BASE, filter, group_attrs, &result);
402 case LDAP_PROC_SUCCESS:
404 case LDAP_PROC_NO_RESULT:
405 RDEBUG("Can't check membership attributes, user object not found");
409 rlm_ldap_release_socket(inst, conn);
413 entry = ldap_first_entry(conn->handle, result);
415 ldap_get_option(conn->handle, LDAP_OPT_RESULT_CODE, &ldap_errno);
416 RDEBUGE("Failed retrieving entry: %s", ldap_err2string(ldap_errno));
418 rlm_ldap_release_socket(inst, conn);
419 ldap_msgfree(result);
423 vals = ldap_get_values(conn->handle, entry, inst->userobj_membership_attr);
425 RDEBUG("No group membership attribute(s) found in user object");
426 rlm_ldap_release_socket(inst, conn);
427 ldap_msgfree(result);
432 * Loop over the list of groups the user is a member of,
433 * looking for a match.
435 for (i = 0; i < ldap_count_values(vals); i++) {
436 value_is_dn = rlm_ldap_is_dn(vals[i]);
438 RDEBUG2("Processing group membership value \"%s\"", vals[i]);
441 * Both literal group names, do case sensitive comparison
443 if (!check_is_dn && !value_is_dn) {
444 if (strcmp(vals[i], check->vp_strvalue) == 0){
445 RDEBUG("User found (membership value matches check value)");
455 * Both DNs, do case insensitive comparison
457 if (check_is_dn && value_is_dn) {
458 if (strcasecmp(vals[i], check->vp_strvalue) == 0){
459 RDEBUG("User found (membership DN matches check DN)");
469 * If the value is not a DN, or the check item is a DN
470 * there's nothing more we can do.
472 if (!value_is_dn && check_is_dn) continue;
475 * We have a value which is a DN, and a check item which
476 * specifies the name of a group, search using the value
477 * DN for the group, and see if it has a groupobj_name_attr
478 * which matches our check val.
480 RDEBUG2("Searching with membership DN and group name");
482 snprintf(filter,sizeof(filter), "(%s=%s)", inst->groupobj_name_attr, check->vp_strvalue);
484 status = rlm_ldap_search(inst, request, &conn, vals[i], LDAP_SCOPE_BASE, filter, NULL, NULL);
486 case LDAP_PROC_SUCCESS:
488 RDEBUG("User found (group name in membership DN matches check value)");
491 case LDAP_PROC_NO_RESULT:
502 ldap_value_free(vals);
503 ldap_msgfree(result);
504 rlm_ldap_release_socket(inst, conn);
507 RDEBUG("User is not a member of specified group");
514 /** Detach from the LDAP server and cleanup internal state.
517 static int mod_detach(void *instance)
519 ldap_instance_t *inst = instance;
521 fr_connection_pool_delete(inst->pool);
523 if (inst->user_map) {
524 radius_mapfree(&inst->user_map);
530 /** Parse an accounting sub section.
532 * Allocate a new ldap_acct_section_t and write the config data into it.
534 * @param[in] inst rlm_ldap configuration.
535 * @param[in] parent of the config section.
536 * @param[out] config to write the sub section parameters to.
537 * @param[in] comp The section name were parsing the config for.
538 * @return 0 on success, else < 0 on failure.
540 static int parse_sub_section(ldap_instance_t *inst, CONF_SECTION *parent, ldap_acct_section_t **config,
541 rlm_components_t comp)
545 const char *name = section_type_value[comp].section;
547 cs = cf_section_sub_find(parent, name);
549 radlog(L_INFO, "rlm_ldap (%s): Couldn't find configuration for %s, will return NOOP for calls "
550 "from this section", inst->xlat_name, name);
555 *config = talloc_zero(inst, ldap_acct_section_t);
556 if (cf_section_parse(cs, *config, acct_section_config) < 0) {
557 LDAP_ERR("Failed parsing configuration for section %s", name);
567 /** Instantiate the module
569 * Creates a new instance of the module reading parameters from a configuration section.
571 * @param conf to parse.
572 * @param instance Where to write pointer to configuration data.
573 * @return 0 on success < 0 on failure.
575 static int mod_instantiate(CONF_SECTION *conf, void **instance)
577 ldap_instance_t *inst;
579 *instance = inst = talloc_zero(conf, ldap_instance_t);
580 if (!inst) return -1;
584 inst->chase_referrals = 2; /* use OpenLDAP defaults */
587 inst->xlat_name = cf_section_name2(conf);
588 if (!inst->xlat_name) {
589 inst->xlat_name = cf_section_name1(conf);
593 * If the configuration parameters can't be parsed, then fail.
595 if ((cf_section_parse(conf, inst, module_config) < 0) ||
596 (parse_sub_section(inst, conf, &inst->accounting, RLM_COMPONENT_ACCT) < 0) ||
597 (parse_sub_section(inst, conf, &inst->postauth, RLM_COMPONENT_POST_AUTH) < 0)) {
598 LDAP_ERR("Failed parsing configuration");
604 LDAP_ERR("Missing 'server' directive");
609 if ((inst->cacheable_group_dn || inst->cacheable_group_name) && !inst->groupobj_name_attr) {
610 DEBUGW("Directive 'group.name_attribute' should be set if cacheable group information is enabled");
614 * Check for URLs. If they're used and the library doesn't support them, then complain.
617 if (ldap_is_ldap_url(inst->server)) {
618 #ifdef HAVE_LDAP_INITIALIZE
622 LDAP_ERR("'server' directive is in URL form but ldap_initialize() is not available");
628 * Workaround for servers which support LDAPS but not START TLS
630 if (inst->port == LDAPS_PORT || inst->tls_mode) {
631 inst->tls_mode = LDAP_OPT_X_TLS_HARD;
636 #if LDAP_SET_REBIND_PROC_ARGS != 3
638 * The 2-argument rebind doesn't take an instance variable. Our rebind function needs the instance
639 * variable for the username, password, etc.
641 if (inst->rebind == 1) {
642 LDAP_ERR("Cannot use 'rebind' directive as this version of libldap does not support the API "
650 * Build the attribute map
652 if (rlm_ldap_map_verify(inst, &(inst->user_map)) < 0) {
657 * Group comparison checks.
659 paircompare_register(PW_LDAP_GROUP, PW_USER_NAME, rlm_ldap_groupcmp, inst);
660 if (cf_section_name2(conf)) {
665 snprintf(buffer, sizeof(buffer), "%s-Ldap-Group",
667 memset(&flags, 0, sizeof(flags));
669 dict_addattr(buffer, -1, 0, PW_TYPE_STRING, flags);
670 da = dict_attrbyname(buffer);
672 LDAP_ERR("Failed creating attribute %s", buffer);
677 paircompare_register(da->attr, PW_USER_NAME, rlm_ldap_groupcmp, inst);
680 xlat_register(inst->xlat_name, ldap_xlat, inst);
683 * Initialize the socket pool.
685 inst->pool = fr_connection_pool_init(inst->cs, inst, rlm_ldap_conn_create, NULL, rlm_ldap_conn_delete);
698 /** Check the user's password against ldap database
700 * @param instance rlm_ldap configuration.
701 * @param request Current request.
702 * @return one of the RLM_MODULE_* values.
704 static rlm_rcode_t mod_authenticate(void *instance, REQUEST *request)
709 ldap_instance_t *inst = instance;
713 * Ensure that we're being passed a plain-text password, and not
717 if (!request->username) {
718 RDEBUGE("Attribute \"User-Name\" is required for authentication");
720 return RLM_MODULE_INVALID;
723 if (!request->password ||
724 (request->password->da->attr != PW_USER_PASSWORD)) {
725 RDEBUGW(" You have set \"Auth-Type := LDAP\" somewhere.");
726 RDEBUGW(" *********************************************");
727 RDEBUGW(" * THAT CONFIGURATION IS WRONG. DELETE IT. ");
728 RDEBUGW(" * YOU ARE PREVENTING THE SERVER FROM WORKING.");
729 RDEBUGW(" *********************************************");
731 RDEBUGE("Attribute \"User-Password\" is required for authentication.");
733 return RLM_MODULE_INVALID;
736 if (request->password->length == 0) {
737 RDEBUGE("Empty password supplied");
739 return RLM_MODULE_INVALID;
742 RDEBUG("Login attempt by \"%s\"", request->username->vp_strvalue);
744 conn = rlm_ldap_get_socket(inst, request);
745 if (!conn) return RLM_MODULE_FAIL;
748 * Get the DN by doing a search.
750 dn = rlm_ldap_find_user(inst, request, &conn, NULL, FALSE, NULL, &rcode);
752 rlm_ldap_release_socket(inst, conn);
760 conn->rebound = TRUE;
761 status = rlm_ldap_bind(inst, request, &conn, dn, request->password->vp_strvalue, TRUE);
763 case LDAP_PROC_SUCCESS:
764 rcode = RLM_MODULE_OK;
765 RDEBUG("Bind as user \"%s\" was successful", dn);
768 case LDAP_PROC_NOT_PERMITTED:
769 rcode = RLM_MODULE_USERLOCK;
772 case LDAP_PROC_REJECT:
773 rcode = RLM_MODULE_REJECT;
776 case LDAP_PROC_BAD_DN:
777 rcode = RLM_MODULE_INVALID;
780 case LDAP_PROC_NO_RESULT:
781 rcode = RLM_MODULE_NOTFOUND;
785 rcode = RLM_MODULE_FAIL;
789 rlm_ldap_release_socket(inst, conn);
794 /** Check if user is authorized for remote access
797 static rlm_rcode_t mod_authorize(void *instance, REQUEST *request)
799 rlm_rcode_t rcode = RLM_MODULE_OK;
803 ldap_instance_t *inst = instance;
807 LDAPMessage *result, *entry;
808 const char *dn = NULL;
809 rlm_ldap_map_xlat_t expanded; /* faster that mallocing every time */
811 if (!request->username) {
812 RDEBUG2("Attribute \"User-Name\" is required for authorization.");
814 return RLM_MODULE_NOOP;
818 * Check for valid input, zero length names not permitted
820 if (request->username->length == 0) {
821 RDEBUG2("Zero length username not permitted");
823 return RLM_MODULE_INVALID;
826 if (rlm_ldap_map_xlat(request, inst->user_map, &expanded) < 0) {
827 return RLM_MODULE_FAIL;
830 conn = rlm_ldap_get_socket(inst, request);
831 if (!conn) return RLM_MODULE_FAIL;
834 * Add any additional attributes we need for checking access, memberships, and profiles
836 if (inst->access_positive) {
837 expanded.attrs[expanded.count++] = inst->userobj_access_attr;
840 if (inst->userobj_membership_attr) {
841 expanded.attrs[expanded.count++] = inst->userobj_membership_attr;
844 if (inst->profile_attr) {
845 expanded.attrs[expanded.count++] = inst->profile_attr;
848 expanded.attrs[expanded.count] = NULL;
850 dn = rlm_ldap_find_user(inst, request, &conn, expanded.attrs, TRUE, &result, &rcode);
855 entry = ldap_first_entry(conn->handle, result);
857 ldap_get_option(conn->handle, LDAP_OPT_RESULT_CODE, &ldap_errno);
858 RDEBUGE("Failed retrieving entry: %s", ldap_err2string(ldap_errno));
866 if (inst->userobj_access_attr) {
867 rcode = rlm_ldap_check_access(inst, request, conn, entry);
868 if (rcode != RLM_MODULE_OK) {
875 * We already have a Cleartext-Password. Skip edir.
877 if (pairfind(request->config_items, PW_CLEARTEXT_PASSWORD, 0, TAG_ANY)) {
882 * Retrieve Universal Password if we use eDirectory
887 size_t pass_size = sizeof(password);
890 * Retrive universal password
892 res = nmasldap_get_password(conn->handle, dn, password, &pass_size);
894 RDEBUGW("Failed to retrieve eDirectory password");
895 rcode = RLM_MODULE_NOOP;
901 * Add Cleartext-Password attribute to the request
903 vp = radius_paircreate(request, &request->config_items, PW_CLEARTEXT_PASSWORD, 0);
904 strlcpy(vp->vp_strvalue, password, sizeof(vp->vp_strvalue));
905 vp->length = pass_size;
907 RDEBUG2("Added eDirectory password in check items as %s = %s", vp->da->name, vp->vp_strvalue);
909 if (inst->edir_autz) {
910 RDEBUG2("Binding as user for eDirectory authorization checks");
914 conn->rebound = TRUE;
915 status = rlm_ldap_bind(inst, request, &conn, dn, vp->vp_strvalue, TRUE);
917 case LDAP_PROC_SUCCESS:
918 rcode = RLM_MODULE_OK;
919 RDEBUG("Bind as user \"%s\" was successful", dn);
922 case LDAP_PROC_NOT_PERMITTED:
923 rcode = RLM_MODULE_USERLOCK;
926 case LDAP_PROC_REJECT:
927 rcode = RLM_MODULE_REJECT;
930 case LDAP_PROC_BAD_DN:
931 rcode = RLM_MODULE_INVALID;
934 case LDAP_PROC_NO_RESULT:
935 rcode = RLM_MODULE_NOTFOUND;
939 rcode = RLM_MODULE_FAIL;
950 * Apply ONE user profile, or a default user profile.
952 vp = pairfind(request->config_items, PW_USER_PROFILE, 0, TAG_ANY);
953 if (vp || inst->default_profile) {
954 const char *profile = inst->default_profile;
956 if (vp) profile = vp->vp_strvalue;
958 rlm_ldap_map_profile(inst, request, &conn, profile, &expanded);
962 * Apply a SET of user profiles.
964 if (inst->profile_attr) {
965 vals = ldap_get_values(conn->handle, entry, inst->profile_attr);
967 for (i = 0; vals[i] != NULL; i++) {
968 rlm_ldap_map_profile(inst, request, &conn, vals[i], &expanded);
971 ldap_value_free(vals);
975 if (inst->user_map) {
976 rlm_ldap_map_do(inst, request, conn->handle, &expanded, entry);
977 rlm_ldap_check_reply(inst, request);
981 rlm_ldap_map_xlat_free(&expanded);
983 ldap_msgfree(result);
985 rlm_ldap_release_socket(inst, conn);
990 /** Modify user's object in LDAP
992 * Process a modifcation map to update a user object in the LDAP directory.
994 * @param inst rlm_ldap instance.
995 * @param request Current request.
996 * @param section that holds the map to process.
997 * @return one of the RLM_MODULE_* values.
999 static rlm_rcode_t user_modify(ldap_instance_t *inst, REQUEST *request, ldap_acct_section_t *section)
1001 rlm_rcode_t rcode = RLM_MODULE_OK;
1003 ldap_handle_t *conn = NULL;
1005 LDAPMod *mod_p[LDAP_MAX_ATTRMAP + 1], mod_s[LDAP_MAX_ATTRMAP];
1006 LDAPMod **modify = mod_p;
1008 char *passed[LDAP_MAX_ATTRMAP * 2];
1009 int i, total = 0, last_pass = 0;
1011 char *expanded[LDAP_MAX_ATTRMAP];
1019 * Build our set of modifications using the update sections in
1026 char path[MAX_STRING_LEN];
1030 rad_assert(section);
1033 * Locate the update section were going to be using
1035 if (section->reference[0] != '.') {
1039 if (!radius_xlat(p, (sizeof(path) - (p - path)) - 1, section->reference, request, NULL, NULL)) {
1043 ci = cf_reference_item(NULL, section->cs, path);
1048 if (!cf_item_is_section(ci)){
1049 RDEBUGE("Reference must resolve to a section");
1054 cs = cf_section_sub_find(cf_itemtosection(ci), "update");
1056 RDEBUGE("Section must contain 'update' subsection");
1062 * Iterate over all the pairs, building our mods array
1064 for (ci = cf_item_find_next(cs, NULL); ci != NULL; ci = cf_item_find_next(cs, ci)) {
1065 int do_xlat = FALSE;
1067 if (total == LDAP_MAX_ATTRMAP) {
1068 RDEBUGE("Modify map size exceeded");
1073 if (!cf_item_is_pair(ci)) {
1074 RDEBUGE("Entry is not in \"ldap-attribute = value\" format");
1080 * Retrieve all the information we need about the pair
1082 cp = cf_itemtopair(ci);
1083 value = cf_pair_value(cp);
1084 attr = cf_pair_attr(cp);
1085 op = cf_pair_operator(cp);
1087 if (!value || (*value == '\0')) {
1088 RDEBUG("Empty value string, skipping attribute \"%s\"", attr);
1093 switch (cf_pair_value_type(cp))
1096 case T_SINGLE_QUOTED_STRING:
1098 case T_BACK_QUOTED_STRING:
1099 case T_DOUBLE_QUOTED_STRING:
1107 if (op == T_OP_CMP_FALSE) {
1108 passed[last_pass] = NULL;
1109 } else if (do_xlat) {
1110 p = rad_malloc(1024);
1111 if (radius_xlat(p, 1024, value, request, NULL, NULL) <= 0) {
1112 RDEBUG("xlat failed or empty value string, skipping attribute \"%s\"", attr);
1119 expanded[last_exp++] = p;
1120 passed[last_pass] = p;
1125 memcpy(&(passed[last_pass]), &value, sizeof(passed[last_pass]));
1128 passed[last_pass + 1] = NULL;
1130 mod_s[total].mod_values = &(passed[last_pass]);
1137 * T_OP_EQ is *NOT* supported, it is impossible to
1138 * support because of the lack of transactions in LDAP
1141 mod_s[total].mod_op = LDAP_MOD_ADD;
1145 mod_s[total].mod_op = LDAP_MOD_REPLACE;
1149 case T_OP_CMP_FALSE:
1150 mod_s[total].mod_op = LDAP_MOD_DELETE;
1153 #ifdef LDAP_MOD_INCREMENT
1155 mod_s[total].mod_op = LDAP_MOD_INCREMENT;
1159 RDEBUGE("Operator '%s' is not supported for LDAP modify operations",
1160 fr_int2str(fr_tokens, op, "¿unknown?"));
1166 * Now we know the value is ok, copy the pointers into
1167 * the ldapmod struct.
1169 memcpy(&(mod_s[total].mod_type), &(attr), sizeof(mod_s[total].mod_type));
1171 mod_p[total] = &(mod_s[total]);
1176 rcode = RLM_MODULE_NOOP;
1180 mod_p[total] = NULL;
1182 conn = rlm_ldap_get_socket(inst, request);
1183 if (!conn) return RLM_MODULE_FAIL;
1186 dn = rlm_ldap_find_user(inst, request, &conn, NULL, FALSE, NULL, &rcode);
1187 if (!dn || (rcode != RLM_MODULE_OK)) {
1191 rcode = rlm_ldap_modify(inst, request, &conn, dn, modify);
1196 * Free up any buffers we allocated for xlat expansion
1198 for (i = 0; i < last_exp; i++) {
1202 rlm_ldap_release_socket(inst, conn);
1207 static rlm_rcode_t mod_accounting(void *instance, REQUEST * request) {
1208 ldap_instance_t *inst = instance;
1210 if (inst->accounting) {
1211 return user_modify(inst, request, inst->accounting);
1214 return RLM_MODULE_NOOP;
1218 /** Check the user's password against ldap database
1221 static rlm_rcode_t mod_post_auth(void *instance, REQUEST * request)
1223 ldap_instance_t *inst = instance;
1225 if (inst->postauth) {
1226 return user_modify(inst, request, inst->postauth);
1229 return RLM_MODULE_NOOP;
1233 /* globally exported name */
1234 module_t rlm_ldap = {
1237 RLM_TYPE_THREAD_SAFE, /* type: reserved */
1238 mod_instantiate, /* instantiation */
1239 mod_detach, /* detach */
1241 mod_authenticate, /* authentication */
1242 mod_authorize, /* authorization */
1243 NULL, /* preaccounting */
1244 mod_accounting, /* accounting */
1245 NULL, /* checksimul */
1246 NULL, /* pre-proxy */
1247 NULL, /* post-proxy */
1248 mod_post_auth /* post-auth */