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.
31 #include <freeradius-devel/rad_assert.h>
41 FR_NAME_NUMBER const ldap_scope[] = {
42 { "sub", LDAP_SCOPE_SUB },
43 { "one", LDAP_SCOPE_ONE },
44 { "base", LDAP_SCOPE_BASE },
45 #ifdef LDAP_SCOPE_CHILDREN
46 { "children", LDAP_SCOPE_CHILDREN },
51 #ifdef LDAP_OPT_X_TLS_NEVER
52 FR_NAME_NUMBER const ldap_tls_require_cert[] = {
53 { "never", LDAP_OPT_X_TLS_NEVER },
54 { "demand", LDAP_OPT_X_TLS_DEMAND },
55 { "allow", LDAP_OPT_X_TLS_ALLOW },
56 { "try", LDAP_OPT_X_TLS_TRY },
57 { "hard", LDAP_OPT_X_TLS_HARD }, /* oh yes, just like that */
63 FR_NAME_NUMBER const ldap_dereference[] = {
64 { "never", LDAP_DEREF_NEVER },
65 { "searching", LDAP_DEREF_SEARCHING },
66 { "finding", LDAP_DEREF_FINDING },
67 { "always", LDAP_DEREF_ALWAYS },
75 static CONF_PARSER tls_config[] = {
77 * Deprecated attributes
79 { "cacertfile", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT | PW_TYPE_DEPRECATED, ldap_instance_t, tls_ca_file), NULL },
80 { "ca_file", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, ldap_instance_t, tls_ca_file), NULL },
82 { "cacertdir", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT | PW_TYPE_DEPRECATED, ldap_instance_t, tls_ca_path), NULL },
83 { "ca_path", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, ldap_instance_t, tls_ca_path), NULL },
85 { "certfile", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT | PW_TYPE_DEPRECATED, ldap_instance_t, tls_certificate_file), NULL },
86 { "certificate_file", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, ldap_instance_t, tls_certificate_file), NULL },
88 { "keyfile", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT | PW_TYPE_DEPRECATED, ldap_instance_t, tls_private_key_file), NULL }, // OK if it changes on HUP
89 { "private_key_file", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, ldap_instance_t, tls_private_key_file), NULL }, // OK if it changes on HUP
91 { "randfile", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT | PW_TYPE_DEPRECATED, ldap_instance_t, tls_random_file), NULL },
92 { "random_file", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, ldap_instance_t, tls_random_file), NULL },
95 * LDAP Specific TLS attributes
97 { "start_tls", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, ldap_instance_t, start_tls), "no" },
98 { "require_cert", FR_CONF_OFFSET(PW_TYPE_STRING, ldap_instance_t, tls_require_cert_str), NULL },
100 { NULL, -1, 0, NULL, NULL }
104 static CONF_PARSER profile_config[] = {
105 { "filter", FR_CONF_OFFSET(PW_TYPE_STRING, ldap_instance_t, profile_filter), "(&)" }, //!< Correct filter for
108 { "attribute", FR_CONF_OFFSET(PW_TYPE_STRING, ldap_instance_t, profile_attr), NULL },
109 { "default", FR_CONF_OFFSET(PW_TYPE_STRING, ldap_instance_t, default_profile), NULL },
111 { NULL, -1, 0, NULL, NULL }
117 static CONF_PARSER user_config[] = {
118 { "filter", FR_CONF_OFFSET(PW_TYPE_STRING, ldap_instance_t, userobj_filter), "(uid=%u)" },
119 { "scope", FR_CONF_OFFSET(PW_TYPE_STRING, ldap_instance_t, userobj_scope_str), "sub" },
120 { "base_dn", FR_CONF_OFFSET(PW_TYPE_STRING, ldap_instance_t, userobj_base_dn), "" },
122 { "access_attribute", FR_CONF_OFFSET(PW_TYPE_STRING, ldap_instance_t, userobj_access_attr), NULL },
123 { "access_positive", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, ldap_instance_t, access_positive), "yes" },
125 { NULL, -1, 0, NULL, NULL }
129 * Group configuration
131 static CONF_PARSER group_config[] = {
132 { "filter", FR_CONF_OFFSET(PW_TYPE_STRING, ldap_instance_t, groupobj_filter), NULL },
133 { "scope", FR_CONF_OFFSET(PW_TYPE_STRING, ldap_instance_t, groupobj_scope_str), "sub" },
134 { "base_dn", FR_CONF_OFFSET(PW_TYPE_STRING, ldap_instance_t, groupobj_base_dn), "" },
136 { "name_attribute", FR_CONF_OFFSET(PW_TYPE_STRING, ldap_instance_t, groupobj_name_attr), "cn" },
137 { "membership_attribute", FR_CONF_OFFSET(PW_TYPE_STRING, ldap_instance_t, userobj_membership_attr), NULL },
138 { "membership_filter", FR_CONF_OFFSET(PW_TYPE_STRING, ldap_instance_t, groupobj_membership_filter), NULL },
139 { "cacheable_name", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, ldap_instance_t, cacheable_group_name), "no" },
140 { "cacheable_dn", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, ldap_instance_t, cacheable_group_dn), "no" },
141 { "cache_attribute", FR_CONF_OFFSET(PW_TYPE_STRING, ldap_instance_t, cache_attribute), NULL },
143 { NULL, -1, 0, NULL, NULL }
147 * Client configuration
149 static CONF_PARSER client_attribute[] = {
150 { "identifier", FR_CONF_OFFSET(PW_TYPE_STRING, ldap_instance_t, clientobj_identifier), "host" },
151 { "shortname", FR_CONF_OFFSET(PW_TYPE_STRING, ldap_instance_t, clientobj_shortname), "cn" },
152 { "nas_type", FR_CONF_OFFSET(PW_TYPE_STRING, ldap_instance_t, clientobj_type), NULL },
153 { "secret", FR_CONF_OFFSET(PW_TYPE_STRING, ldap_instance_t, clientobj_secret), NULL },
154 { "virtual_server", FR_CONF_OFFSET(PW_TYPE_STRING, ldap_instance_t, clientobj_server), NULL },
155 { "require_message_authenticator", FR_CONF_OFFSET(PW_TYPE_STRING, ldap_instance_t, clientobj_require_ma), NULL },
157 { NULL, -1, 0, NULL, NULL }
160 static CONF_PARSER client_config[] = {
161 { "filter", FR_CONF_OFFSET(PW_TYPE_STRING, ldap_instance_t, clientobj_filter), NULL },
162 { "scope", FR_CONF_OFFSET(PW_TYPE_STRING, ldap_instance_t, clientobj_scope_str), "sub" },
163 { "base_dn", FR_CONF_OFFSET(PW_TYPE_STRING, ldap_instance_t, clientobj_base_dn), "" },
164 { "attribute", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) client_attribute },
166 { NULL, -1, 0, NULL, NULL }
170 * Reference for accounting updates
172 static const CONF_PARSER acct_section_config[] = {
173 { "reference", FR_CONF_OFFSET(PW_TYPE_STRING, ldap_acct_section_t, reference), "." },
175 {NULL, -1, 0, NULL, NULL}
179 * Various options that don't belong in the main configuration.
181 * Note that these overlap a bit with the connection pool code!
183 static CONF_PARSER option_config[] = {
185 * Debugging flags to the server
187 { "ldap_debug", FR_CONF_OFFSET(PW_TYPE_INTEGER, ldap_instance_t, ldap_debug), "0x0000" },
189 { "dereference", FR_CONF_OFFSET(PW_TYPE_STRING, ldap_instance_t, dereference_str), NULL },
191 { "chase_referrals", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, ldap_instance_t, chase_referrals), NULL },
193 { "rebind", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, ldap_instance_t, rebind), NULL },
195 #ifdef LDAP_OPT_NETWORK_TIMEOUT
196 /* timeout on network activity */
197 { "net_timeout", FR_CONF_OFFSET(PW_TYPE_INTEGER, ldap_instance_t, net_timeout), "10" },
200 /* timeout for search results */
201 { "res_timeout", FR_CONF_OFFSET(PW_TYPE_INTEGER, ldap_instance_t, res_timeout), "20" },
203 /* allow server unlimited time for search (server-side limit) */
204 { "srv_timelimit", FR_CONF_OFFSET(PW_TYPE_INTEGER, ldap_instance_t, srv_timelimit), "20" },
206 #ifdef LDAP_OPT_X_KEEPALIVE_IDLE
207 { "idle", FR_CONF_OFFSET(PW_TYPE_INTEGER, ldap_instance_t, keepalive_idle), "60" },
209 #ifdef LDAP_OPT_X_KEEPALIVE_PROBES
210 { "probes", FR_CONF_OFFSET(PW_TYPE_INTEGER, ldap_instance_t, keepalive_probes), "3" },
212 #ifdef LDAP_OPT_X_KEEPALIVE_INTERVAL
213 { "interval", FR_CONF_OFFSET(PW_TYPE_INTEGER, ldap_instance_t, keepalive_interval), "30" },
216 { NULL, -1, 0, NULL, NULL }
220 static const CONF_PARSER module_config[] = {
221 { "server", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_REQUIRED, ldap_instance_t, server), "localhost" },
222 { "port", FR_CONF_OFFSET(PW_TYPE_SHORT, ldap_instance_t, port), "389" },
224 { "password", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_SECRET, ldap_instance_t, password), "" },
225 { "identity", FR_CONF_OFFSET(PW_TYPE_STRING, ldap_instance_t, admin_dn), "" },
227 { "valuepair_attribute", FR_CONF_OFFSET(PW_TYPE_STRING, ldap_instance_t, valuepair_attr), NULL },
230 /* support for eDirectory Universal Password */
231 { "edir", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, ldap_instance_t, edir), NULL }, /* NULL defaults to "no" */
234 * Attempt to bind with the cleartext password we got from eDirectory
235 * Universal password for additional authorization checks.
237 { "edir_autz", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, ldap_instance_t, edir_autz), NULL }, /* NULL defaults to "no" */
240 { "read_clients", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, ldap_instance_t, do_clients), NULL }, /* NULL defaults to "no" */
242 { "user", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) user_config },
244 { "group", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) group_config },
246 { "client", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) client_config },
248 { "profile", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) profile_config },
250 { "options", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) option_config },
252 { "tls", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) tls_config },
254 {NULL, -1, 0, NULL, NULL}
257 /** Expand an LDAP URL into a query, and return a string result from that query.
260 static ssize_t ldap_xlat(void *instance, REQUEST *request, char const *fmt, char *out, size_t freespace)
264 ldap_instance_t *inst = instance;
265 LDAPURLDesc *ldap_url;
266 LDAPMessage *result = NULL;
267 LDAPMessage *entry = NULL;
276 if (!ldap_is_ldap_url(url)) {
277 REDEBUG("String passed does not look like an LDAP URL");
281 if (ldap_url_parse(url, &ldap_url)){
282 REDEBUG("Parsing LDAP URL failed");
287 * Nothing, empty string, "*" string, or got 2 things, die.
289 if (!ldap_url->lud_attrs || !ldap_url->lud_attrs[0] ||
290 !*ldap_url->lud_attrs[0] ||
291 (strcmp(ldap_url->lud_attrs[0], "*") == 0) ||
292 ldap_url->lud_attrs[1]) {
293 REDEBUG("Bad attributes list in LDAP URL. URL must specify exactly one attribute to retrieve");
298 if (ldap_url->lud_host &&
299 ((strncmp(inst->server, ldap_url->lud_host, strlen(inst->server)) != 0) ||
300 ((uint32_t) ldap_url->lud_port != inst->port))) {
301 RDEBUG("Requested server/port is \"%s:%i\"", ldap_url->lud_host, inst->port);
306 conn = rlm_ldap_get_socket(inst, request);
307 if (!conn) goto free_urldesc;
309 memcpy(&attrs, &ldap_url->lud_attrs, sizeof(attrs));
311 status = rlm_ldap_search(inst, request, &conn, ldap_url->lud_dn, ldap_url->lud_scope, ldap_url->lud_filter,
314 case LDAP_PROC_SUCCESS:
316 case LDAP_PROC_NO_RESULT:
317 RDEBUG("Search returned not found");
325 entry = ldap_first_entry(conn->handle, result);
327 ldap_get_option(conn->handle, LDAP_OPT_RESULT_CODE, &ldap_errno);
328 REDEBUG("Failed retrieving entry: %s", ldap_err2string(ldap_errno));
333 vals = ldap_get_values(conn->handle, entry, ldap_url->lud_attrs[0]);
335 RDEBUG("No \"%s\" attributes found in specified object", ldap_url->lud_attrs[0]);
339 len = strlen(vals[0]);
340 if (len >= freespace){
344 strlcpy(out, vals[0], freespace);
347 ldap_value_free(vals);
349 ldap_msgfree(result);
351 rlm_ldap_release_socket(inst, conn);
353 ldap_free_urldesc(ldap_url);
358 /** Perform LDAP-Group comparison checking
360 * Attempts to match users to groups using a variety of methods.
362 * @param instance of the rlm_ldap module.
363 * @param request Current request.
364 * @param thing Unknown.
365 * @param check Which group to check for user membership.
366 * @param check_pairs Unknown.
367 * @param reply_pairs Unknown.
368 * @return 1 on failure (or if the user is not a member), else 0.
370 static int rlm_ldap_groupcmp(void *instance, REQUEST *request, UNUSED VALUE_PAIR *thing, VALUE_PAIR *check,
371 UNUSED VALUE_PAIR *check_pairs, UNUSED VALUE_PAIR **reply_pairs)
373 ldap_instance_t *inst = instance;
379 ldap_handle_t *conn = NULL;
382 rad_assert(inst->groupobj_base_dn);
384 RDEBUG("Searching for user in group \"%s\"", check->vp_strvalue);
386 if (check->length == 0) {
387 RDEBUG("Cannot do comparison (group name is empty)");
392 * Check if we can do cached membership verification
394 check_is_dn = rlm_ldap_is_dn(check->vp_strvalue);
395 if ((check_is_dn && inst->cacheable_group_dn) || (!check_is_dn && inst->cacheable_group_name)) {
396 switch(rlm_ldap_check_cached(inst, request, check)) {
397 case RLM_MODULE_NOTFOUND:
404 * Fallback to dynamic search on failure
406 case RLM_MODULE_FAIL:
407 case RLM_MODULE_INVALID:
413 conn = rlm_ldap_get_socket(inst, request);
417 * This is used in the default membership filter.
419 user_dn = rlm_ldap_find_user(inst, request, &conn, NULL, false, NULL, &rcode);
421 rlm_ldap_release_socket(inst, conn);
428 * Check groupobj user membership
430 if (inst->groupobj_membership_filter) {
431 switch(rlm_ldap_check_groupobj_dynamic(inst, request, &conn, check)) {
432 case RLM_MODULE_NOTFOUND:
444 * Check userobj group membership
446 if (inst->userobj_membership_attr) {
447 switch(rlm_ldap_check_userobj_dynamic(inst, request, &conn, user_dn, check)) {
448 case RLM_MODULE_NOTFOUND:
461 rlm_ldap_release_socket(inst, conn);
465 RDEBUG("User is not a member of specified group");
473 /** Detach from the LDAP server and cleanup internal state.
476 static int mod_detach(void *instance)
478 ldap_instance_t *inst = instance;
480 fr_connection_pool_delete(inst->pool);
482 if (inst->user_map) {
483 talloc_free(inst->user_map);
489 /** Parse an accounting sub section.
491 * Allocate a new ldap_acct_section_t and write the config data into it.
493 * @param[in] inst rlm_ldap configuration.
494 * @param[in] parent of the config section.
495 * @param[out] config to write the sub section parameters to.
496 * @param[in] comp The section name were parsing the config for.
497 * @return 0 on success, else < 0 on failure.
499 static int parse_sub_section(ldap_instance_t *inst, CONF_SECTION *parent, ldap_acct_section_t **config,
500 rlm_components_t comp)
504 char const *name = section_type_value[comp].section;
506 cs = cf_section_sub_find(parent, name);
508 INFO("rlm_ldap (%s): Couldn't find configuration for %s, will return NOOP for calls "
509 "from this section", inst->xlat_name, name);
514 *config = talloc_zero(inst, ldap_acct_section_t);
515 if (cf_section_parse(cs, *config, acct_section_config) < 0) {
516 LDAP_ERR("Failed parsing configuration for section %s", name);
526 /** Instantiate the module
528 * Creates a new instance of the module reading parameters from a configuration section.
530 * @param conf to parse.
531 * @param instance Where to write pointer to configuration data.
532 * @return 0 on success < 0 on failure.
534 static int mod_instantiate(CONF_SECTION *conf, void *instance)
536 static bool version_done;
538 CONF_SECTION *options;
539 ldap_instance_t *inst = instance;
543 options = cf_section_sub_find(conf, "options");
544 if (!options || !cf_pair_find(options, "chase_referrals")) {
545 inst->chase_referrals_unset = true; /* use OpenLDAP defaults */
548 inst->xlat_name = cf_section_name2(conf);
549 if (!inst->xlat_name) {
550 inst->xlat_name = cf_section_name1(conf);
554 * Get version info from the LDAP API.
562 ldap_errno = ldap_get_option(NULL, LDAP_OPT_API_INFO, &info);
563 if (ldap_errno == LDAP_OPT_SUCCESS) {
564 if (strcmp(info.ldapai_vendor_name, LDAP_VENDOR_NAME) != 0) {
565 WARN("rlm_ldap: libldap vendor changed since the server was built");
566 WARN("rlm_ldap: linked: %s built: %s", info.ldapai_vendor_name, LDAP_VENDOR_NAME);
569 if (info.ldapai_vendor_version != LDAP_VENDOR_VERSION) {
570 WARN("rlm_ldap: libldap version changed since the server was built");
571 WARN("rlm_ldap: linked: %i built: %i",
572 info.ldapai_vendor_version, LDAP_VENDOR_VERSION);
575 INFO("rlm_ldap: libldap vendor: %s version: %i", info.ldapai_vendor_name,
576 info.ldapai_vendor_version);
577 ldap_memfree(info.ldapai_vendor_name);
578 ldap_memfree(info.ldapai_extensions);
580 WARN("rlm_ldap: Falling back to build time libldap version info. Query for LDAP_OPT_API_INFO "
581 "returned: %i", ldap_errno);
582 INFO("rlm_ldap: libldap vendor: %s version: %i", LDAP_VENDOR_NAME, LDAP_VENDOR_VERSION);
587 * If the configuration parameters can't be parsed, then fail.
589 if ((parse_sub_section(inst, conf, &inst->accounting, RLM_COMPONENT_ACCT) < 0) ||
590 (parse_sub_section(inst, conf, &inst->postauth, RLM_COMPONENT_POST_AUTH) < 0)) {
591 LDAP_ERR("Failed parsing configuration");
597 * Sanity checks for cacheable groups code.
599 if (inst->cacheable_group_name && inst->groupobj_membership_filter) {
600 if (!inst->groupobj_name_attr) {
601 LDAP_ERR("Directive 'group.name_attribute' must be set if cacheable group names are enabled");
608 * Check for URLs. If they're used and the library doesn't support them, then complain.
611 if (ldap_is_ldap_url(inst->server)) {
612 #ifdef HAVE_LDAP_INITIALIZE
616 LDAP_ERR("Directive 'server' is in URL form but ldap_initialize() is not available");
621 #ifdef LDAP_OPT_X_TLS_NEVER
623 * Workaround for servers which support LDAPS but not START TLS
625 if (inst->port == LDAPS_PORT || inst->tls_mode) {
626 inst->tls_mode = LDAP_OPT_X_TLS_HARD;
633 * Convert dereference strings to enumerated constants
635 if (inst->dereference_str) {
636 inst->dereference = fr_str2int(ldap_dereference, inst->dereference_str, -1);
637 if (inst->dereference < 0) {
638 LDAP_ERR("Invalid 'dereference' value \"%s\", expected 'never', 'searching', "
639 "'finding' or 'always'", inst->dereference_str);
644 #if LDAP_SET_REBIND_PROC_ARGS != 3
646 * The 2-argument rebind doesn't take an instance variable. Our rebind function needs the instance
647 * variable for the username, password, etc.
649 if (inst->rebind == true) {
650 LDAP_ERR("Cannot use 'rebind' directive as this version of libldap does not support the API "
658 * Convert scope strings to enumerated constants
660 inst->userobj_scope = fr_str2int(ldap_scope, inst->userobj_scope_str, -1);
661 if (inst->userobj_scope < 0) {
662 LDAP_ERR("Invalid 'user.scope' value \"%s\", expected 'sub', 'one'"
663 #ifdef LDAP_SCOPE_CHILDREN
664 ", 'base' or 'children'"
668 , inst->userobj_scope_str);
672 inst->groupobj_scope = fr_str2int(ldap_scope, inst->groupobj_scope_str, -1);
673 if (inst->groupobj_scope < 0) {
674 LDAP_ERR("Invalid 'group.scope' value \"%s\", expected 'sub', 'one'"
675 #ifdef LDAP_SCOPE_CHILDREN
676 ", 'base' or 'children'"
680 , inst->groupobj_scope_str);
684 inst->clientobj_scope = fr_str2int(ldap_scope, inst->clientobj_scope_str, -1);
685 if (inst->clientobj_scope < 0) {
686 LDAP_ERR("Invalid 'client.scope' value \"%s\", expected 'sub', 'one'"
687 #ifdef LDAP_SCOPE_CHILDREN
688 ", 'base' or 'children'"
692 , inst->clientobj_scope_str);
696 if (inst->tls_require_cert_str) {
697 #ifdef LDAP_OPT_X_TLS_NEVER
699 * Convert cert strictness to enumerated constants
701 inst->tls_require_cert = fr_str2int(ldap_tls_require_cert, inst->tls_require_cert_str, -1);
702 if (inst->tls_require_cert < 0) {
703 LDAP_ERR("Invalid 'tls.require_cert' value \"%s\", expected 'never', 'demand', 'allow', "
704 "'try' or 'hard'", inst->tls_require_cert_str);
708 LDAP_ERR("Modifying 'tls.require_cert' is not supported by current version of libldap. "
709 "Please upgrade or substitute current libldap and rebuild this module");
715 * Build the attribute map
717 if (rlm_ldap_map_verify(inst, &(inst->user_map)) < 0) {
722 * Group comparison checks.
724 if (cf_section_name2(conf)) {
725 static ATTR_FLAGS flags;
728 snprintf(buffer, sizeof(buffer), "%s-Ldap-Group", inst->xlat_name);
729 if (dict_addattr(buffer, -1, 0, PW_TYPE_STRING, flags) < 0) {
730 LDAP_ERR("Error creating group attribute: %s", fr_strerror());
734 inst->group_da = dict_attrbyname(buffer);
735 if (!inst->group_da) {
736 LDAP_ERR("Failed creating attribute %s", buffer);
741 paircompare_register(inst->group_da, dict_attrbyvalue(PW_USER_NAME, 0), false, rlm_ldap_groupcmp, inst);
743 * Were the default instance
746 inst->group_da = dict_attrbyvalue(PW_LDAP_GROUP, 0);
747 paircompare_register(dict_attrbyvalue(PW_LDAP_GROUP, 0), dict_attrbyvalue(PW_USER_NAME, 0),
748 false, rlm_ldap_groupcmp, inst);
751 xlat_register(inst->xlat_name, ldap_xlat, rlm_ldap_escape_func, inst);
754 * Setup the cache attribute
756 if (inst->cache_attribute) {
757 static ATTR_FLAGS flags;
759 if (dict_addattr(inst->cache_attribute, -1, 0, PW_TYPE_STRING, flags) < 0) {
760 LDAP_ERR("Error creating cache attribute: %s", fr_strerror());
764 inst->cache_da = dict_attrbyname(inst->cache_attribute);
766 inst->cache_da = inst->group_da; /* Default to the group_da */
770 * Initialize the socket pool.
772 inst->pool = fr_connection_pool_init(inst->cs, inst, mod_conn_create, NULL, NULL, NULL);
778 * Bulk load dynamic clients.
780 if (inst->do_clients) {
781 if (rlm_ldap_load_clients(inst) < 0) {
782 LDAP_ERR("Error loading clients");
794 static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(void *instance, REQUEST *request)
799 ldap_instance_t *inst = instance;
803 * Ensure that we're being passed a plain-text password, and not
807 if (!request->username) {
808 REDEBUG("Attribute \"User-Name\" is required for authentication");
810 return RLM_MODULE_INVALID;
813 if (!request->password ||
814 (request->password->da->attr != PW_USER_PASSWORD)) {
815 RWDEBUG("You have set \"Auth-Type := LDAP\" somewhere");
816 RWDEBUG("*********************************************");
817 RWDEBUG("* THAT CONFIGURATION IS WRONG. DELETE IT. ");
818 RWDEBUG("* YOU ARE PREVENTING THE SERVER FROM WORKING");
819 RWDEBUG("*********************************************");
821 REDEBUG("Attribute \"User-Password\" is required for authentication");
823 return RLM_MODULE_INVALID;
826 if (request->password->length == 0) {
827 REDEBUG("Empty password supplied");
829 return RLM_MODULE_INVALID;
832 RDEBUG("Login attempt by \"%s\"", request->username->vp_strvalue);
834 conn = rlm_ldap_get_socket(inst, request);
835 if (!conn) return RLM_MODULE_FAIL;
838 * Get the DN by doing a search.
840 dn = rlm_ldap_find_user(inst, request, &conn, NULL, false, NULL, &rcode);
842 rlm_ldap_release_socket(inst, conn);
850 conn->rebound = true;
851 status = rlm_ldap_bind(inst, request, &conn, dn, request->password->vp_strvalue, true);
853 case LDAP_PROC_SUCCESS:
854 rcode = RLM_MODULE_OK;
855 RDEBUG("Bind as user \"%s\" was successful", dn);
858 case LDAP_PROC_NOT_PERMITTED:
859 rcode = RLM_MODULE_USERLOCK;
862 case LDAP_PROC_REJECT:
863 rcode = RLM_MODULE_REJECT;
866 case LDAP_PROC_BAD_DN:
867 rcode = RLM_MODULE_INVALID;
870 case LDAP_PROC_NO_RESULT:
871 rcode = RLM_MODULE_NOTFOUND;
875 rcode = RLM_MODULE_FAIL;
879 rlm_ldap_release_socket(inst, conn);
884 static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void *instance, REQUEST *request)
886 rlm_rcode_t rcode = RLM_MODULE_OK;
890 ldap_instance_t *inst = instance;
894 LDAPMessage *result, *entry;
895 char const *dn = NULL;
896 rlm_ldap_map_xlat_t expanded; /* faster than mallocing every time */
898 if (!request->username) {
899 RDEBUG2("Attribute \"User-Name\" is required for authorization");
901 return RLM_MODULE_NOOP;
905 * Check for valid input, zero length names not permitted
907 if (request->username->length == 0) {
908 RDEBUG2("Zero length username not permitted");
910 return RLM_MODULE_INVALID;
913 if (rlm_ldap_map_xlat(request, inst->user_map, &expanded) < 0) {
914 return RLM_MODULE_FAIL;
917 conn = rlm_ldap_get_socket(inst, request);
918 if (!conn) return RLM_MODULE_FAIL;
921 * Add any additional attributes we need for checking access, memberships, and profiles
923 if (inst->userobj_access_attr) {
924 expanded.attrs[expanded.count++] = inst->userobj_access_attr;
927 if (inst->userobj_membership_attr && (inst->cacheable_group_dn || inst->cacheable_group_name)) {
928 expanded.attrs[expanded.count++] = inst->userobj_membership_attr;
931 if (inst->profile_attr) {
932 expanded.attrs[expanded.count++] = inst->profile_attr;
935 if (inst->valuepair_attr) {
936 expanded.attrs[expanded.count++] = inst->valuepair_attr;
939 expanded.attrs[expanded.count] = NULL;
941 dn = rlm_ldap_find_user(inst, request, &conn, expanded.attrs, true, &result, &rcode);
946 entry = ldap_first_entry(conn->handle, result);
948 ldap_get_option(conn->handle, LDAP_OPT_RESULT_CODE, &ldap_errno);
949 REDEBUG("Failed retrieving entry: %s", ldap_err2string(ldap_errno));
957 if (inst->userobj_access_attr) {
958 rcode = rlm_ldap_check_access(inst, request, conn, entry);
959 if (rcode != RLM_MODULE_OK) {
965 * Check if we need to cache group memberships
967 if (inst->cacheable_group_dn || inst->cacheable_group_name) {
968 if (inst->userobj_membership_attr) {
969 rcode = rlm_ldap_cacheable_userobj(inst, request, &conn, entry, inst->userobj_membership_attr);
970 if (rcode != RLM_MODULE_OK) {
975 rcode = rlm_ldap_cacheable_groupobj(inst, request, &conn);
976 if (rcode != RLM_MODULE_OK) {
983 * We already have a Cleartext-Password. Skip edir.
985 if (pairfind(request->config_items, PW_CLEARTEXT_PASSWORD, 0, TAG_ANY)) {
990 * Retrieve Universal Password if we use eDirectory
995 size_t pass_size = sizeof(password);
998 * Retrive universal password
1000 res = nmasldap_get_password(conn->handle, dn, password, &pass_size);
1002 REDEBUG("Failed to retrieve eDirectory password: (%i) %s", res, edir_errstr(res));
1003 rcode = RLM_MODULE_FAIL;
1009 * Add Cleartext-Password attribute to the request
1011 vp = radius_paircreate(request, &request->config_items, PW_CLEARTEXT_PASSWORD, 0);
1012 pairstrcpy(vp, password);
1013 vp->length = pass_size;
1015 if (RDEBUG_ENABLED3) {
1016 RDEBUG3("Added eDirectory password. control:%s += '%s'", vp->da->name, vp->vp_strvalue);
1018 RDEBUG2("Added eDirectory password");
1021 if (inst->edir_autz) {
1022 RDEBUG2("Binding as user for eDirectory authorization checks");
1026 conn->rebound = true;
1027 status = rlm_ldap_bind(inst, request, &conn, dn, vp->vp_strvalue, true);
1029 case LDAP_PROC_SUCCESS:
1030 rcode = RLM_MODULE_OK;
1031 RDEBUG("Bind as user '%s' was successful", dn);
1034 case LDAP_PROC_NOT_PERMITTED:
1035 rcode = RLM_MODULE_USERLOCK;
1038 case LDAP_PROC_REJECT:
1039 rcode = RLM_MODULE_REJECT;
1042 case LDAP_PROC_BAD_DN:
1043 rcode = RLM_MODULE_INVALID;
1046 case LDAP_PROC_NO_RESULT:
1047 rcode = RLM_MODULE_NOTFOUND;
1051 rcode = RLM_MODULE_FAIL;
1061 * Apply ONE user profile, or a default user profile.
1063 if (inst->default_profile) {
1066 if (radius_xlat(profile, sizeof(profile), request, inst->default_profile, NULL, NULL) < 0) {
1067 REDEBUG("Failed creating default profile string");
1069 rcode = RLM_MODULE_INVALID;
1073 rlm_ldap_map_profile(inst, request, &conn, profile, &expanded);
1077 * Apply a SET of user profiles.
1079 if (inst->profile_attr) {
1080 vals = ldap_get_values(conn->handle, entry, inst->profile_attr);
1082 for (i = 0; vals[i] != NULL; i++) {
1083 rlm_ldap_map_profile(inst, request, &conn, vals[i], &expanded);
1086 ldap_value_free(vals);
1090 if (inst->user_map || inst->valuepair_attr) {
1091 RDEBUG("Processing user attributes");
1092 rlm_ldap_map_do(inst, request, conn->handle, &expanded, entry);
1093 rlm_ldap_check_reply(inst, request);
1097 rlm_ldap_map_xlat_free(&expanded);
1099 ldap_msgfree(result);
1101 rlm_ldap_release_socket(inst, conn);
1106 /** Modify user's object in LDAP
1108 * Process a modifcation map to update a user object in the LDAP directory.
1110 * @param inst rlm_ldap instance.
1111 * @param request Current request.
1112 * @param section that holds the map to process.
1113 * @return one of the RLM_MODULE_* values.
1115 static rlm_rcode_t user_modify(ldap_instance_t *inst, REQUEST *request, ldap_acct_section_t *section)
1117 rlm_rcode_t rcode = RLM_MODULE_OK;
1118 ldap_rcode_t status;
1120 ldap_handle_t *conn = NULL;
1122 LDAPMod *mod_p[LDAP_MAX_ATTRMAP + 1], mod_s[LDAP_MAX_ATTRMAP];
1123 LDAPMod **modify = mod_p;
1125 char *passed[LDAP_MAX_ATTRMAP * 2];
1126 int i, total = 0, last_pass = 0;
1128 char *expanded[LDAP_MAX_ATTRMAP];
1136 * Build our set of modifications using the update sections in
1143 char path[MAX_STRING_LEN];
1147 rad_assert(section);
1150 * Locate the update section were going to be using
1152 if (section->reference[0] != '.') {
1156 if (radius_xlat(p, (sizeof(path) - (p - path)) - 1, request, section->reference, NULL, NULL) < 0) {
1160 ci = cf_reference_item(NULL, section->cs, path);
1165 if (!cf_item_is_section(ci)){
1166 REDEBUG("Reference must resolve to a section");
1171 cs = cf_section_sub_find(cf_itemtosection(ci), "update");
1173 REDEBUG("Section must contain 'update' subsection");
1179 * Iterate over all the pairs, building our mods array
1181 for (ci = cf_item_find_next(cs, NULL); ci != NULL; ci = cf_item_find_next(cs, ci)) {
1182 bool do_xlat = false;
1184 if (total == LDAP_MAX_ATTRMAP) {
1185 REDEBUG("Modify map size exceeded");
1190 if (!cf_item_is_pair(ci)) {
1191 REDEBUG("Entry is not in \"ldap-attribute = value\" format");
1197 * Retrieve all the information we need about the pair
1199 cp = cf_itemtopair(ci);
1200 value = cf_pair_value(cp);
1201 attr = cf_pair_attr(cp);
1202 op = cf_pair_operator(cp);
1204 if (!value || (*value == '\0')) {
1205 RDEBUG("Empty value string, skipping attribute \"%s\"", attr);
1210 switch (cf_pair_value_type(cp)) {
1212 case T_SINGLE_QUOTED_STRING:
1215 case T_BACK_QUOTED_STRING:
1216 case T_DOUBLE_QUOTED_STRING:
1225 if (op == T_OP_CMP_FALSE) {
1226 passed[last_pass] = NULL;
1227 } else if (do_xlat) {
1230 if (radius_axlat(&exp, request, value, NULL, NULL) <= 0) {
1231 RDEBUG("Skipping attribute \"%s\"", attr);
1238 expanded[last_exp++] = exp;
1239 passed[last_pass] = exp;
1244 memcpy(&(passed[last_pass]), &value, sizeof(passed[last_pass]));
1247 passed[last_pass + 1] = NULL;
1249 mod_s[total].mod_values = &(passed[last_pass]);
1255 * T_OP_EQ is *NOT* supported, it is impossible to
1256 * support because of the lack of transactions in LDAP
1259 mod_s[total].mod_op = LDAP_MOD_ADD;
1263 mod_s[total].mod_op = LDAP_MOD_REPLACE;
1267 case T_OP_CMP_FALSE:
1268 mod_s[total].mod_op = LDAP_MOD_DELETE;
1271 #ifdef LDAP_MOD_INCREMENT
1273 mod_s[total].mod_op = LDAP_MOD_INCREMENT;
1277 REDEBUG("Operator '%s' is not supported for LDAP modify operations",
1278 fr_int2str(fr_tokens, op, "<INVALID>"));
1284 * Now we know the value is ok, copy the pointers into
1285 * the ldapmod struct.
1287 memcpy(&(mod_s[total].mod_type), &attr, sizeof(mod_s[total].mod_type));
1289 mod_p[total] = &(mod_s[total]);
1294 rcode = RLM_MODULE_NOOP;
1298 mod_p[total] = NULL;
1300 conn = rlm_ldap_get_socket(inst, request);
1301 if (!conn) return RLM_MODULE_FAIL;
1304 dn = rlm_ldap_find_user(inst, request, &conn, NULL, false, NULL, &rcode);
1305 if (!dn || (rcode != RLM_MODULE_OK)) {
1309 status = rlm_ldap_modify(inst, request, &conn, dn, modify);
1311 case LDAP_PROC_SUCCESS:
1314 case LDAP_PROC_REJECT:
1315 case LDAP_PROC_BAD_DN:
1316 rcode = RLM_MODULE_INVALID;
1320 rcode = RLM_MODULE_FAIL;
1327 * Free up any buffers we allocated for xlat expansion
1329 for (i = 0; i < last_exp; i++) {
1330 talloc_free(expanded[i]);
1333 rlm_ldap_release_socket(inst, conn);
1338 static rlm_rcode_t CC_HINT(nonnull) mod_accounting(void *instance, REQUEST * request) {
1339 ldap_instance_t *inst = instance;
1341 if (inst->accounting) {
1342 return user_modify(inst, request, inst->accounting);
1345 return RLM_MODULE_NOOP;
1348 static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST * request)
1350 ldap_instance_t *inst = instance;
1352 if (inst->postauth) {
1353 return user_modify(inst, request, inst->postauth);
1356 return RLM_MODULE_NOOP;
1360 /* globally exported name */
1361 module_t rlm_ldap = {
1364 RLM_TYPE_THREAD_SAFE, /* type: reserved */
1365 sizeof(ldap_instance_t),
1367 mod_instantiate, /* instantiation */
1368 mod_detach, /* detach */
1370 mod_authenticate, /* authentication */
1371 mod_authorize, /* authorization */
1372 NULL, /* preaccounting */
1373 mod_accounting, /* accounting */
1374 NULL, /* checksimul */
1375 NULL, /* pre-proxy */
1376 NULL, /* post-proxy */
1377 mod_post_auth /* post-auth */