Patch from Thiago Rondon <maluco@mileniumnet.com.br>
[freeradius.git] / src / modules / rlm_ldap / rlm_ldap.c
1 /*
2  * rlm_ldap.c LDAP authorization and authentication module.
3  * 
4  * 
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
7  * 
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.
19  */
20 static const char rcsid[] = "$Id$";
21
22 #include "autoconf.h"
23
24 #include        <sys/types.h>
25 #include        <sys/socket.h>
26 #include        <sys/time.h>
27 #include        <netinet/in.h>
28
29 #include        <stdio.h>
30 #include        <stdlib.h>
31 #include        <netdb.h>
32 #include        <pwd.h>
33 #include        <time.h>
34 #include        <ctype.h>
35 #include        <string.h>
36
37 #include        <lber.h>
38 #include        <ldap.h>
39
40 #include        <errno.h>
41 #include        <unistd.h>
42 #include        <pthread.h>
43
44 #include        "radiusd.h"
45 #include        "conffile.h"
46 #include        "modules.h"
47
48
49 #define MAX_AUTH_QUERY_LEN      256
50 #define TIMELIMIT 5
51
52 typedef struct {
53         const char     *attr;
54         const char     *radius_attr;
55 }               TLDAP_RADIUS;
56
57 static char    *make_filter(char *, char *);
58 #ifdef FIELDCPY
59 static void     fieldcpy(char *, char **);
60 #endif
61 static VALUE_PAIR *ldap_pairget(LDAP *, LDAPMessage *, TLDAP_RADIUS *);
62 static LDAP    *ldap_connect(void *instance, const char *, const char *, int, int *);
63
64 #define MAX_SERVER_LINE 1024
65 /*
66  * These should really be in a module-specific data structure, which is
67  * passed to the module with every request.
68  */
69
70 static struct timeval *timeout = NULL;
71
72 typedef struct {
73         char           *server;
74         int             port;
75         int             timelimit;
76         struct timeval  net_timeout;
77         struct timeval  timeout;
78         int             debug;
79         int             tls_mode;
80         char           *login;
81         char           *password;
82         char           *filter;
83         char           *basedn;
84         char           *access_group;
85         char           *access_attr;
86         LDAP           *ld;
87         int             bound;
88 }               ldap_instance;
89
90 static ldap_instance config;
91
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"},
101
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 */
111
112         {NULL, -1, NULL, NULL}
113 };
114
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 )
118
119 /*
120  * Mappings of LDAP radius* attributes to RADIUS attributes
121  * 
122  * Hmm... these should really be read in from the configuration file
123  */
124 static TLDAP_RADIUS check_item_map[] = {
125         {"radiusAuthType", "Auth-Type"},
126         {"npSessionsAllowed", "Simultaneous-Use"},
127         {NULL, NULL}
128 };
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"},
159         {NULL, NULL}
160 };
161
162 /*************************************************************************
163  *
164  *      Function: rlm_ldap_instantiate
165  *
166  *      Purpose: Uses section of radiusd config file passed as parameter
167  *               to create an instance of the module.
168  *
169  *************************************************************************/
170 static int 
171 ldap_instantiate(CONF_SECTION * conf, void **instance)
172 {
173         ldap_instance  *inst;
174
175         inst = rad_malloc(sizeof *inst);
176
177         if (cf_section_parse(conf, module_config) < 0) {
178                 free(inst);
179                 return -1;
180         }
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;
196         inst->bound = 0;
197         
198         config.server = NULL;
199         config.login = NULL;
200         config.password = NULL;
201         config.filter = NULL;
202         config.basedn = NULL;
203         config.access_group = NULL;
204         config.access_attr = NULL;
205
206         *instance = inst;
207
208         return 0;
209 }
210
211 static int 
212 perform_search(void *instance, char *search_basedn, int scope, char *filter, char **attrs, LDAPMessage ** result)
213 {
214         int             msgid;
215         int             res = RLM_MODULE_OK;
216         int             rc;
217         ldap_instance  *inst = instance;
218
219         if (!inst->bound) {
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);
224                 }
225                 inst->bound = 1;
226         }
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);
229         if (msgid == -1) {
230                 radlog(L_ERR, "rlm_ldap: ldap_search() API failed\n");
231                 inst->bound = 0;
232                 return (RLM_MODULE_FAIL);
233         }
234         rc = ldap_result(inst->ld, msgid, 1, timeout, result);
235
236         if (rc < 1) {
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);
241         }
242         switch (ldap_result2error(inst->ld, *result, 0)) {
243         case LDAP_SUCCESS:
244                 break;
245
246         case LDAP_TIMELIMIT_EXCEEDED:
247                 radlog(L_ERR, "rlm_ldap: Warning timelimit exceeded, using partial results\n");
248                 break;
249
250         default:
251                 DEBUG("rlm_ldap: ldap_search() failed");
252                 inst->bound = 0;
253                 ldap_msgfree(*result);
254                 return (RLM_MODULE_FAIL);
255         }
256
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;
260         }
261         return res;
262 }
263
264 /******************************************************************************
265  *
266  *      Function: rlm_ldap_authorize
267  *
268  *      Purpose: Check if user is authorized for remote access
269  *
270  ******************************************************************************/
271 static int 
272 ldap_authorize(void *instance, REQUEST * request)
273 {
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;
280         int             res;
281         VALUE_PAIR    **check_pairs, **reply_pairs;
282         char          **vals;
283
284         check_pairs = &request->config_items;
285         reply_pairs = &request->reply->vps;
286
287         DEBUG("rlm_ldap: - authorize");
288         name = request->username->strvalue;
289
290         /*
291          * Check for valid input, zero length names not permitted
292          */
293         if (name[0] == 0) {
294                 radlog(L_ERR, "rlm_ldap: zero length username not permitted\n");
295                 return RLM_MODULE_INVALID;
296         }
297         DEBUG("rlm_ldap: performing user authorization for %s", name);
298
299         filter = make_filter(inst->filter, name);
300
301         if ((res = perform_search(instance, inst->basedn, LDAP_SCOPE_SUBTREE, filter, attrs, &result)) != RLM_MODULE_OK) {
302                 DEBUG("rlm_ldap: search failed");
303                 return (res);
304         }
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;
309         }
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;
314         }
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;
323                         }
324                 } else {
325                         DEBUG("rlm_ldap: no %s attribute - access denied by default", inst->access_attr);
326                         ldap_msgfree(result);
327                         return RLM_MODULE_USERLOCK;
328                 }
329         }
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);
333                 /*
334                  * uniquemember appears in Netscape Directory Server's groups
335                  * since we have objectclass groupOfNames and
336                  * groupOfUniqueNames
337                  */
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);
341
342
343                 if (res != RLM_MODULE_OK) {
344                         if (res == RLM_MODULE_NOTFOUND)
345                                 return (RLM_MODULE_USERLOCK);
346                         else
347                                 return (res);
348                 }
349         }
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);
353
354
355         /*
356          * Module should default to LDAP authentication if no Auth-Type
357          * specified
358          */
359         if (pairfind(*check_pairs, PW_AUTHTYPE) == NULL)
360                 pairadd(check_pairs, pairmake("Auth-Type", "LDAP", T_OP_CMP_EQ));
361
362         /*
363          * Adding new attribute containing DN for LDAP object associated with
364          * given username
365          */
366         pairadd(&request->packet->vps, pairmake("Ldap-UserDn", user_dn, T_OP_EQ));
367
368         DEBUG("rlm_ldap: looking for reply items in directory...");
369
370         if ((reply_tmp = ldap_pairget(inst->ld, msg, reply_item_map)) != NULL)
371                 pairadd(reply_pairs, reply_tmp);
372
373         DEBUG("rlm_ldap: user %s authorized to use remote access", name);
374         ldap_msgfree(result);
375         return RLM_MODULE_OK;
376 }
377
378 /*****************************************************************************
379  *
380  *      Function: rlm_ldap_authenticate
381  *
382  *      Purpose: Check the user's password against ldap database
383  *
384  *****************************************************************************/
385 static int 
386 ldap_authenticate(void *instance, REQUEST * request)
387 {
388         LDAP           *ld_user;
389         LDAPMessage    *result, *msg;
390         ldap_instance  *inst = instance;
391         char           *filter, *passwd, *user_dn, *name, *attrs[] = {"uid", NULL};
392         int             res;
393         VALUE_PAIR     *vp_user_dn;
394
395         DEBUG("rlm_ldap: - authenticate");
396         /*
397          * Ensure that we're being passed a plain-text password, and not
398          * anything else.
399          */
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;
403         }
404         name = request->username->strvalue;
405         passwd = request->password->strvalue;
406
407         if (strlen(passwd) == 0) {
408                 radlog(L_ERR, "rlm_ldap: empty password supplied");
409                 return RLM_MODULE_INVALID;
410         }
411         DEBUG("rlm_ldap: login attempt by \"%s\" with password \"%s\"", name, passwd);
412         filter = make_filter(inst->filter, name);
413
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");
417                         return (res);
418                 }
419                 if ((msg = ldap_first_entry(inst->ld, result)) == NULL) {
420                         ldap_msgfree(result);
421                         return RLM_MODULE_FAIL;
422                 }
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;
427                 }
428                 pairadd(&request->packet->vps, pairmake("Ldap-UserDn", user_dn, T_OP_EQ));
429                 ldap_msgfree(result);
430         } else {
431                 user_dn = vp_user_dn->strvalue;
432         }
433
434         DEBUG("rlm_ldap: user DN: %s", user_dn);
435
436         ld_user = ldap_connect(instance, user_dn, passwd, 1, &res);
437
438         if (ld_user == NULL)
439                 return (res);
440
441         DEBUG("rlm_ldap: user %s authenticated succesfully", name);
442         ldap_unbind_s(ld_user);
443         return RLM_MODULE_OK;
444 }
445
446 static LDAP    *
447 ldap_connect(void *instance, const char *dn, const char *password, int auth, int *result)
448 {
449         ldap_instance  *inst = instance;
450         LDAP           *ld;
451         int             msgid, rc;
452         LDAPMessage    *res;
453
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;
458                 return (NULL);
459         }
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);
462         }
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);
465         }
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);
468         }
469 #ifdef HAVE_TLS
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");
472         }
473 #endif
474
475         DEBUG("rlm_ldap: connect as %s/%s", dn, password);
476         msgid = ldap_bind(ld, dn, password, LDAP_AUTH_SIMPLE);
477         if (msgid == -1) {
478                 ldap_perror(ld, "rlm_ldap: ldap_connect()");
479                 *result = RLM_MODULE_FAIL;
480                 ldap_unbind_s(ld);
481                 return (NULL);
482         }
483         DEBUG("rlm_ldap: ldap_connect() waiting for bind result ...");
484
485         if (inst->timeout.tv_sec < 0)
486                 rc = ldap_result(ld, msgid, 1, NULL, &res);
487         else
488                 rc = ldap_result(ld, msgid, 1, &(inst->timeout), &res);
489
490         if (rc < 1) {
491                 ldap_perror(ld, "rlm_ldap: ldap_result()");
492                 *result = RLM_MODULE_FAIL;
493                 ldap_unbind_s(ld);
494                 return (NULL);
495         }
496         DEBUG("rlm_ldap: ldap_connect() bind finished");
497         switch (ldap_result2error(ld, res, 1)) {
498         case LDAP_SUCCESS:
499                 *result = RLM_MODULE_OK;
500                 break;
501
502         case LDAP_INVALID_CREDENTIALS:
503                 if (auth) {
504                         *result = RLM_MODULE_REJECT;
505                         break;
506                 }
507         default:
508                 DEBUG("rlm_ldap: LDAP FAILURE");
509                 *result = RLM_MODULE_FAIL;
510         }
511         if (*result != RLM_MODULE_OK) {
512                 ldap_unbind_s(ld);
513                 ld = NULL;
514         }
515         return ld;
516 }
517
518 /*****************************************************************************
519  *
520  *      Detach from the LDAP server and cleanup internal state.
521  *
522  *****************************************************************************/
523 static int 
524 ldap_detach(void *instance)
525 {
526         ldap_instance  *inst = instance;
527
528         if (inst->server)
529                 free((char *) inst->server);
530         if (inst->login)
531                 free((char *) inst->login);
532         if (inst->password)
533                 free((char *) inst->password);
534         if (inst->basedn)
535                 free((char *) inst->basedn);
536         if (inst->access_group)
537                 free((char *) inst->access_group);
538         if (inst->filter)
539                 free((char *) inst->filter);
540         if (inst->ld)
541                 ldap_memfree(inst->ld);
542
543         free(inst);
544
545         return 0;
546 }
547
548 /*****************************************************************************
549  *      Replace %<whatever> in a string.
550  *
551  *      %u   User name
552  *
553  *****************************************************************************/
554 static char    *
555 make_filter(char *str, char *name)
556 {
557         static char     buf[MAX_AUTH_QUERY_LEN];
558         int             i = 0, c;
559         char           *p;
560
561         for (p = str; *p; p++) {
562                 c = *p;
563                 if (c != '%' && c != '\\') {
564                         buf[i++] = *p;
565                         continue;
566                 }
567                 if (*++p == 0)
568                         break;
569                 if (c == '%')
570                         switch (*p) {
571                         case '%':
572                                 buf[i++] = *p;
573                                 break;
574                         case 'u':       /* User name */
575                                 if (name != NULL)
576                                         strcpy(buf + i, name);
577                                 else
578                                         strcpy(buf + i, " ");
579                                 i += strlen(buf + i);
580                                 break;
581                         default:
582                                 buf[i++] = '%';
583                                 buf[i++] = *p;
584                                 break;
585                         }
586                 if (c == '\\')
587                         switch (*p) {
588                         case 'n':
589                                 buf[i++] = '\n';
590                                 break;
591                         case 'r':
592                                 buf[i++] = '\r';
593                                 break;
594                         case 't':
595                                 buf[i++] = '\t';
596                                 break;
597                         default:
598                                 buf[i++] = '\\';
599                                 buf[i++] = *p;
600                                 break;
601                         }
602         }
603         if (i >= MAX_AUTH_QUERY_LEN)
604                 i = MAX_AUTH_QUERY_LEN - 1;
605         buf[i++] = 0;
606         return buf;
607 }
608
609 #ifdef FIELDCPY
610 static void 
611 fieldcpy(char *string, char **uptr)
612 {
613         char           *ptr;
614
615         ptr = *uptr;
616         while (*ptr == ' ' || *ptr == '\t') {
617                 ptr++;
618         }
619         if (*ptr == '"') {
620                 ptr++;
621                 while (*ptr != '"' && *ptr != '\0' && *ptr != '\n') {
622                         *string++ = *ptr++;
623                 }
624                 *string = '\0';
625                 if (*ptr == '"') {
626                         ptr++;
627                 }
628                 *uptr = ptr;
629                 return;
630         }
631         while (*ptr != ' ' && *ptr != '\t' && *ptr != '\0' && *ptr != '\n' &&
632                *ptr != '=' && *ptr != ',') {
633                 *string++ = *ptr++;
634         }
635         *string = '\0';
636         *uptr = ptr;
637         return;
638 }
639 #endif
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> )
644  *
645  *****************************************************************************/
646
647 static VALUE_PAIR *
648 ldap_pairget(LDAP * ld, LDAPMessage * entry,
649              TLDAP_RADIUS * item_map)
650 {
651         BerElement     *berptr;
652         char           *attr;
653         char          **vals;
654         int             vals_count;
655         int             vals_idx;
656         char           *ptr;
657         TLDAP_RADIUS   *element;
658         int             token;
659         char            value[64];
660         VALUE_PAIR     *pairlist;
661         VALUE_PAIR     *newpair = NULL;
662         pairlist = NULL;
663         if ((attr = ldap_first_attribute(ld, entry, &berptr)) == NULL) {
664                 DEBUG("rlm_ldap: Object has no attributes");
665                 return NULL;
666         }
667         do {
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);
673                                         break;
674                                 }
675
676                                 /* find out how many values there are for the attribute and extract all of them */
677
678                                 vals_count = ldap_count_values(vals);
679
680                                 for (vals_idx = 0; vals_idx < vals_count; vals_idx++) {
681                                         ptr = vals[vals_idx];
682
683                                         token = gettoken(&ptr, value, sizeof(value));
684                                         if (token < T_EQSTART || token > T_EQEND) {
685                                                 token = T_OP_EQ;
686                                         } else {
687                                                 gettoken(&ptr, value, sizeof(value));
688                                         }
689                                         if (value[0] == 0) {
690                                                 DEBUG("rlm_ldap: Attribute %s has no value", attr);
691                                                 break;
692                                         }
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)
695                                                 continue;
696                                         pairadd(&pairlist, newpair);
697                                 }
698                                 ldap_value_free(vals);
699                         }
700                 }
701         } while ((attr = ldap_next_attribute(ld, entry, berptr)) != NULL);
702
703         ber_free(berptr, 0);
704         return (pairlist);
705 }
706
707 /* globally exported name */
708 module_t        rlm_ldap = {
709         "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                */
719         NULL,                   /* destroy               */
720 };