2 * rlm_ldap.c LDAP authorization and authentication module.
5 * This module is based on LDAP patch to Cistron radiusd, which in turn was
6 * based mostly on a Mysql+Cistron patch from oyarzun@wilmington.net
8 * 17 Jan 2000: - OpenLDAP SDK porting, basic TLS support, LDAP authorization,
9 * fault tolerance with multiple LDAP server support done by Adrian
10 * Pavlykevych <pam@polynet.lviv.ua> 24 May 2000: - Converting to new
11 * configuration file format, futher improvements in fault tolerance,
12 * threaded operation Adrian Pavlykevych <pam@polynet.lviv.ua> 12 Dec 2000: -
13 * Added preliminary support for multiple instances - moved all instance
14 * configuration into dynamicly allocated structure - Removed connection
15 * maintenance thread and all attempts for multihreading the module itself.
16 * OpenLDAP SDK is not thread safe when used with shared LDAP connection. -
17 * Added configuration option for defining LDAP attribute of user object,
18 * which controls remote access.
20 static const char rcsid[] = "$Id$";
24 #include <sys/types.h>
25 #include <sys/socket.h>
27 #include <netinet/in.h>
49 #define MAX_AUTH_QUERY_LEN 256
54 const char *radius_attr;
57 static char *make_filter(char *, char *);
59 static void fieldcpy(char *, char **);
61 static VALUE_PAIR *ldap_pairget(LDAP *, LDAPMessage *, TLDAP_RADIUS *);
62 static LDAP *ldap_connect(void *instance, const char *, const char *, int, int *);
64 #define MAX_SERVER_LINE 1024
66 * These should really be in a module-specific data structure, which is
67 * passed to the module with every request.
70 static struct timeval *timeout = NULL;
76 struct timeval net_timeout;
77 struct timeval timeout;
90 static ldap_instance config;
92 static CONF_PARSER module_config[] = {
93 {"server", PW_TYPE_STRING_PTR, &config.server, NULL},
94 {"port", PW_TYPE_INTEGER, &config.port, "389"},
95 /* wait forever on network activity */
96 {"net_timeout", PW_TYPE_INTEGER, &config.net_timeout.tv_sec, "-1"},
97 /* wait forever for search results */
98 {"timeout", PW_TYPE_INTEGER, &config.timeout.tv_sec, "-1"},
99 /* allow server unlimited time for search (server-side limit) */
100 {"timelimit", PW_TYPE_INTEGER, &config.timelimit, "-1"},
102 {"identity", PW_TYPE_STRING_PTR, &config.login, NULL},
103 {"password", PW_TYPE_STRING_PTR, &config.password, NULL},
104 {"basedn", PW_TYPE_STRING_PTR, &config.basedn, NULL},
105 {"filter", PW_TYPE_STRING_PTR, &config.filter, NULL},
106 {"access_group", PW_TYPE_STRING_PTR, &config.access_group, NULL},
107 /* LDAP attribute name that controls remote access */
108 {"access_attr", PW_TYPE_STRING_PTR, &config.access_attr, NULL},
109 /* cache size limited only by TTL */
110 /* cache objects TTL 30 secs */
112 {NULL, -1, NULL, NULL}
115 #define ld_valid ld_options.ldo_valid
116 #define LDAP_VALID_SESSION 0x2
117 #define LDAP_VALID(ld) ( (ld)->ld_valid == LDAP_VALID_SESSION )
120 * Mappings of LDAP radius* attributes to RADIUS attributes
122 * Hmm... these should really be read in from the configuration file
124 static TLDAP_RADIUS check_item_map[] = {
125 {"radiusAuthType", "Auth-Type"},
126 {"npSessionsAllowed", "Simultaneous-Use"},
129 static TLDAP_RADIUS reply_item_map[] = {
130 {"radiusServiceType", "Service-Type"},
131 {"radiusFramedProtocol", "Framed-Protocol"},
132 {"radiusFramedIPAddress", "Framed-IP-Address"},
133 {"radiusFramedIPNetmask", "Framed-IP-Netmask"},
134 {"radiusFramedRoute", "Framed-Route"},
135 {"radiusFramedRouting", "Framed-Routing"},
136 {"radiusFilterId", "Filter-Id"},
137 {"radiusFramedMTU", "Framed-MTU"},
138 {"radiusFramedCompression", "Framed-Compression"},
139 {"radiusLoginIPHost", "Login-IP-Host"},
140 {"radiusLoginService", "Login-Service"},
141 {"radiusLoginTCPPort", "Login-TCP-Port"},
142 {"radiusCallbackNumber", "Callback-Number"},
143 {"radiusCallbackId", "Callback-Id"},
144 {"radiusFramedIPXNetwork", "Framed-IPX-Network"},
145 {"radiusClass", "Class"},
146 {"radiusSessionTimeout", "Session-Timeout"},
147 {"radiusIdleTimeout", "Idle-Timeout"},
148 {"radiusTerminationAction", "Termination-Action"},
149 {"radiusCalledStationId", "Called-Station-Id"},
150 {"radiusCallingStationId", "Calling-Station-Id"},
151 {"radiusLoginLATService", "Login-LAT-Service"},
152 {"radiusLoginLATNode", "Login-LAT-Node"},
153 {"radiusLoginLATGroup", "Login-LAT-Group"},
154 {"radiusFramedAppleTalkLink", "Framed-AppleTalk-Link"},
155 {"radiusFramedAppleTalkNetwork", "Framed-AppleTalk-Network"},
156 {"radiusFramedAppleTalkZone", "Framed-AppleTalk-Zone"},
157 {"radiusPortLimit", "Port-Limit"},
158 {"radiusLoginLATPort", "Login-LAT-Port"},
162 /*************************************************************************
164 * Function: rlm_ldap_instantiate
166 * Purpose: Uses section of radiusd config file passed as parameter
167 * to create an instance of the module.
169 *************************************************************************/
171 ldap_instantiate(CONF_SECTION * conf, void **instance)
175 inst = rad_malloc(sizeof *inst);
177 if (cf_section_parse(conf, module_config) < 0) {
181 inst->server = config.server;
182 inst->port = config.port;
183 inst->timeout.tv_sec = config.timeout.tv_sec;
184 inst->timeout.tv_usec = 0;
185 inst->net_timeout.tv_sec = config.net_timeout.tv_sec;
186 inst->net_timeout.tv_usec = 0;
187 inst->timelimit = config.timelimit;
188 inst->debug = config.debug;
189 inst->tls_mode = LDAP_OPT_X_TLS_TRY;
190 inst->login = config.login;
191 inst->password = config.password;
192 inst->filter = config.filter;
193 inst->basedn = config.basedn;
194 inst->access_group = config.access_group;
195 inst->access_attr = config.access_attr;
198 config.server = NULL;
200 config.password = NULL;
201 config.filter = NULL;
202 config.basedn = NULL;
203 config.access_group = NULL;
204 config.access_attr = NULL;
212 perform_search(void *instance, char *search_basedn, int scope, char *filter, char **attrs, LDAPMessage ** result)
215 int res = RLM_MODULE_OK;
217 ldap_instance *inst = instance;
220 DEBUG2("rlm_ldap: attempting LDAP reconnection");
221 if ((inst->ld = ldap_connect(instance, inst->login, inst->password, 0, &res)) == NULL) {
222 radlog(L_ERR, "rlm_ldap: (re)connection attempt failed");
223 return (RLM_MODULE_FAIL);
227 DEBUG2("rlm_ldap: performing search in %s, with filter %s", search_basedn, filter);
228 msgid = ldap_search(inst->ld, search_basedn, scope, filter, attrs, 0);
230 radlog(L_ERR, "rlm_ldap: ldap_search() API failed\n");
232 return (RLM_MODULE_FAIL);
234 rc = ldap_result(inst->ld, msgid, 1, timeout, result);
237 ldap_perror(inst->ld, "rlm_ldap: ldap_result()");
238 radlog(L_ERR, "rlm_ldap: ldap_result() failed - %s\n", strerror(errno));
239 ldap_msgfree(*result);
240 return (RLM_MODULE_FAIL);
242 switch (ldap_result2error(inst->ld, *result, 0)) {
246 case LDAP_TIMELIMIT_EXCEEDED:
247 radlog(L_ERR, "rlm_ldap: Warning timelimit exceeded, using partial results\n");
251 DEBUG("rlm_ldap: ldap_search() failed");
253 ldap_msgfree(*result);
254 return (RLM_MODULE_FAIL);
257 if ((ldap_count_entries(inst->ld, *result)) != 1) {
258 DEBUG("rlm_ldap: object not found or got ambiguous search result");
259 res = RLM_MODULE_NOTFOUND;
264 /******************************************************************************
266 * Function: rlm_ldap_authorize
268 * Purpose: Check if user is authorized for remote access
270 ******************************************************************************/
272 ldap_authorize(void *instance, REQUEST * request)
274 LDAPMessage *result, *msg, *gr_result;
275 ldap_instance *inst = instance;
276 char *filter, *name, *user_dn;
277 char *attrs[] = {"*", NULL};
278 VALUE_PAIR *check_tmp;
279 VALUE_PAIR *reply_tmp;
281 VALUE_PAIR **check_pairs, **reply_pairs;
284 check_pairs = &request->config_items;
285 reply_pairs = &request->reply->vps;
287 DEBUG("rlm_ldap: - authorize");
288 name = request->username->strvalue;
291 * Check for valid input, zero length names not permitted
294 radlog(L_ERR, "rlm_ldap: zero length username not permitted\n");
295 return RLM_MODULE_INVALID;
297 DEBUG("rlm_ldap: performing user authorization for %s", name);
299 filter = make_filter(inst->filter, name);
301 if ((res = perform_search(instance, inst->basedn, LDAP_SCOPE_SUBTREE, filter, attrs, &result)) != RLM_MODULE_OK) {
302 DEBUG("rlm_ldap: search failed");
305 if ((msg = ldap_first_entry(inst->ld, result)) == NULL) {
306 DEBUG("rlm_ldap: ldap_first_entry() failed");
307 ldap_msgfree(result);
308 return RLM_MODULE_FAIL;
310 if ((user_dn = ldap_get_dn(inst->ld, msg)) == NULL) {
311 DEBUG("rlm_ldap: ldap_get_dn() failed");
312 ldap_msgfree(result);
313 return RLM_MODULE_FAIL;
315 /* Remote access is controled by attribute of the user object */
316 if (inst->access_attr) {
317 if ((vals = ldap_get_values(inst->ld, msg, inst->access_attr)) != NULL) {
318 DEBUG("rlm_ldap: checking if remote access for %s is allowed by %s", name, inst->access_attr);
319 if (!strncmp(vals[0], "FALSE", 5)) {
320 DEBUG("rlm_ldap: dialup access disabled");
321 ldap_msgfree(result);
322 return RLM_MODULE_USERLOCK;
325 DEBUG("rlm_ldap: no %s attribute - access denied by default", inst->access_attr);
326 ldap_msgfree(result);
327 return RLM_MODULE_USERLOCK;
330 /* Remote access controled by group membership of the user object */
331 if (inst->access_group != NULL) {
332 DEBUG("rlm_ldap: checking user membership in dialup-enabling group %s", inst->access_group);
334 * uniquemember appears in Netscape Directory Server's groups
335 * since we have objectclass groupOfNames and
338 filter = make_filter("(| (& (objectClass=GroupOfNames) (member=%u)) (& (objectClass=GroupOfUniqueNames) (uniquemember=%u)))", user_dn);
339 res = perform_search(instance, inst->access_group, LDAP_SCOPE_BASE, filter, NULL, &gr_result);
340 ldap_msgfree(gr_result);
343 if (res != RLM_MODULE_OK) {
344 if (res == RLM_MODULE_NOTFOUND)
345 return (RLM_MODULE_USERLOCK);
350 DEBUG("rlm_ldap: looking for check items in directory...");
351 if ((check_tmp = ldap_pairget(inst->ld, msg, check_item_map)) != NULL)
352 pairadd(check_pairs, check_tmp);
356 * Module should default to LDAP authentication if no Auth-Type
359 if (pairfind(*check_pairs, PW_AUTHTYPE) == NULL)
360 pairadd(check_pairs, pairmake("Auth-Type", "LDAP", T_OP_CMP_EQ));
363 * Adding new attribute containing DN for LDAP object associated with
366 pairadd(&request->packet->vps, pairmake("Ldap-UserDn", user_dn, T_OP_EQ));
368 DEBUG("rlm_ldap: looking for reply items in directory...");
370 if ((reply_tmp = ldap_pairget(inst->ld, msg, reply_item_map)) != NULL)
371 pairadd(reply_pairs, reply_tmp);
373 DEBUG("rlm_ldap: user %s authorized to use remote access", name);
374 ldap_msgfree(result);
375 return RLM_MODULE_OK;
378 /*****************************************************************************
380 * Function: rlm_ldap_authenticate
382 * Purpose: Check the user's password against ldap database
384 *****************************************************************************/
386 ldap_authenticate(void *instance, REQUEST * request)
389 LDAPMessage *result, *msg;
390 ldap_instance *inst = instance;
391 char *filter, *passwd, *user_dn, *name, *attrs[] = {"uid", NULL};
393 VALUE_PAIR *vp_user_dn;
395 DEBUG("rlm_ldap: - authenticate");
397 * Ensure that we're being passed a plain-text password, and not
400 if (request->password->attribute != PW_PASSWORD) {
401 radlog(L_AUTH, "rlm_ldap: Attribute \"Password\" is required for authentication. Cannot use \"%s\".", request->password->name);
402 return RLM_MODULE_INVALID;
404 name = request->username->strvalue;
405 passwd = request->password->strvalue;
407 if (strlen(passwd) == 0) {
408 radlog(L_ERR, "rlm_ldap: empty password supplied");
409 return RLM_MODULE_INVALID;
411 DEBUG("rlm_ldap: login attempt by \"%s\" with password \"%s\"", name, passwd);
412 filter = make_filter(inst->filter, name);
414 if ((vp_user_dn = pairfind(request->packet->vps, LDAP_USERDN)) == NULL) {
415 if ((res = perform_search(instance, inst->basedn, LDAP_SCOPE_SUBTREE, filter, attrs, &result)) != RLM_MODULE_OK) {
416 DEBUG("rlm_ldap: search did not return ok value");
419 if ((msg = ldap_first_entry(inst->ld, result)) == NULL) {
420 ldap_msgfree(result);
421 return RLM_MODULE_FAIL;
423 if ((user_dn = ldap_get_dn(inst->ld, msg)) == NULL) {
424 DEBUG("rlm_ldap: ldap_get_dn() failed");
425 ldap_msgfree(result);
426 return RLM_MODULE_FAIL;
428 pairadd(&request->packet->vps, pairmake("Ldap-UserDn", user_dn, T_OP_EQ));
429 ldap_msgfree(result);
431 user_dn = vp_user_dn->strvalue;
434 DEBUG("rlm_ldap: user DN: %s", user_dn);
436 ld_user = ldap_connect(instance, user_dn, passwd, 1, &res);
441 DEBUG("rlm_ldap: user %s authenticated succesfully", name);
442 ldap_unbind_s(ld_user);
443 return RLM_MODULE_OK;
447 ldap_connect(void *instance, const char *dn, const char *password, int auth, int *result)
449 ldap_instance *inst = instance;
454 DEBUG("rlm_ldap: (re)connect to %s:%d, authentication %d", inst->server, inst->port, auth);
455 if ((ld = ldap_init(inst->server, inst->port)) == NULL) {
456 radlog(L_ERR, "rlm_ldap: ldap_init() failed");
457 *result = RLM_MODULE_FAIL;
460 if (inst->net_timeout.tv_sec != -1 && ldap_set_option(ld, LDAP_OPT_NETWORK_TIMEOUT, (void *) &(inst->net_timeout)) != LDAP_OPT_SUCCESS) {
461 radlog(L_ERR, "rlm_ldap: Could not set LDAP_OPT_NETWORK_TIMEOUT %ld.%ld", inst->net_timeout.tv_sec, inst->net_timeout.tv_usec);
463 if (inst->timelimit != -1 && ldap_set_option(ld, LDAP_OPT_TIMELIMIT, (void *) &(inst->timelimit)) != LDAP_OPT_SUCCESS) {
464 radlog(L_ERR, "rlm_ldap: Could not set LDAP_OPT_TIMELIMIT %d", inst->timelimit);
466 if (inst->debug && ldap_set_option(NULL, LDAP_OPT_DEBUG_LEVEL, &(inst->debug)) != LDAP_OPT_SUCCESS) {
467 radlog(L_ERR, "rlm_ldap: Could not set LDAP_OPT_DEBUG_LEVEL %d", inst->debug);
470 if (inst->tls_mode && ldap_set_option(ld, LDAP_OPT_X_TLS, (void *) &(inst->tls_mode)) != LDAP_OPT_SUCCESS) {
471 radlog(L_ERR, "rlm_ldap: Could not set LDAP_OPT_X_TLS_TRY");
475 DEBUG("rlm_ldap: connect as %s/%s", dn, password);
476 msgid = ldap_bind(ld, dn, password, LDAP_AUTH_SIMPLE);
478 ldap_perror(ld, "rlm_ldap: ldap_connect()");
479 *result = RLM_MODULE_FAIL;
483 DEBUG("rlm_ldap: ldap_connect() waiting for bind result ...");
485 if (inst->timeout.tv_sec < 0)
486 rc = ldap_result(ld, msgid, 1, NULL, &res);
488 rc = ldap_result(ld, msgid, 1, &(inst->timeout), &res);
491 ldap_perror(ld, "rlm_ldap: ldap_result()");
492 *result = RLM_MODULE_FAIL;
496 DEBUG("rlm_ldap: ldap_connect() bind finished");
497 switch (ldap_result2error(ld, res, 1)) {
499 *result = RLM_MODULE_OK;
502 case LDAP_INVALID_CREDENTIALS:
504 *result = RLM_MODULE_REJECT;
508 DEBUG("rlm_ldap: LDAP FAILURE");
509 *result = RLM_MODULE_FAIL;
511 if (*result != RLM_MODULE_OK) {
518 /*****************************************************************************
520 * Detach from the LDAP server and cleanup internal state.
522 *****************************************************************************/
524 ldap_detach(void *instance)
526 ldap_instance *inst = instance;
529 free((char *) inst->server);
531 free((char *) inst->login);
533 free((char *) inst->password);
535 free((char *) inst->basedn);
536 if (inst->access_group)
537 free((char *) inst->access_group);
539 free((char *) inst->filter);
541 ldap_memfree(inst->ld);
548 /*****************************************************************************
549 * Replace %<whatever> in a string.
553 *****************************************************************************/
555 make_filter(char *str, char *name)
557 static char buf[MAX_AUTH_QUERY_LEN];
561 for (p = str; *p; p++) {
563 if (c != '%' && c != '\\') {
574 case 'u': /* User name */
576 strcpy(buf + i, name);
578 strcpy(buf + i, " ");
579 i += strlen(buf + i);
603 if (i >= MAX_AUTH_QUERY_LEN)
604 i = MAX_AUTH_QUERY_LEN - 1;
611 fieldcpy(char *string, char **uptr)
616 while (*ptr == ' ' || *ptr == '\t') {
621 while (*ptr != '"' && *ptr != '\0' && *ptr != '\n') {
631 while (*ptr != ' ' && *ptr != '\t' && *ptr != '\0' && *ptr != '\n' &&
632 *ptr != '=' && *ptr != ',') {
640 /*****************************************************************************
641 * Get RADIUS attributes from LDAP object
642 * ( according to draft-adoba-radius-05.txt
643 * <http://www.ietf.org/internet-drafts/draft-adoba-radius-05.txt> )
645 *****************************************************************************/
648 ldap_pairget(LDAP * ld, LDAPMessage * entry,
649 TLDAP_RADIUS * item_map)
657 TLDAP_RADIUS *element;
660 VALUE_PAIR *pairlist;
661 VALUE_PAIR *newpair = NULL;
663 if ((attr = ldap_first_attribute(ld, entry, &berptr)) == NULL) {
664 DEBUG("rlm_ldap: Object has no attributes");
668 for (element = item_map; element->attr != NULL; element++) {
669 if (!strcasecmp(attr, element->attr)) {
670 /* mapping found, get the values */
671 if (((vals = ldap_get_values(ld, entry, attr)) == NULL)) {
672 DEBUG("rlm_ldap: ldap_get_values returned NULL for attribute %s", attr);
676 /* find out how many values there are for the attribute and extract all of them */
678 vals_count = ldap_count_values(vals);
680 for (vals_idx = 0; vals_idx < vals_count; vals_idx++) {
681 ptr = vals[vals_idx];
683 token = gettoken(&ptr, value, sizeof(value));
684 if (token < T_EQSTART || token > T_EQEND) {
687 gettoken(&ptr, value, sizeof(value));
690 DEBUG("rlm_ldap: Attribute %s has no value", attr);
693 DEBUG("rlm_ldap: Adding %s as %s, value %s & op=%d", attr, element->radius_attr, value, token);
694 if ((newpair = pairmake(element->radius_attr, value, token)) == NULL)
696 pairadd(&pairlist, newpair);
698 ldap_value_free(vals);
701 } while ((attr = ldap_next_attribute(ld, entry, berptr)) != NULL);
707 /* globally exported name */
708 module_t rlm_ldap = {
710 RLM_TYPE_THREAD_UNSAFE, /* type: reserved */
711 NULL, /* initialization */
712 ldap_instantiate, /* instantiation */
713 ldap_authorize, /* authorization */
714 ldap_authenticate, /* authentication */
715 NULL, /* preaccounting */
716 NULL, /* accounting */
717 NULL, /* checksimul */
718 ldap_detach, /* detach */