1 /*	$NetBSD: ldapauth.c,v 1.1 2010/11/21 18:59:04 adam Exp $	*/
2 /* $Id: ldapauth.c,v 1.1 2010/11/21 18:59:04 adam Exp $
3  */
4 
5 /*
6  *
7  * Copyright (c) 2005, Eric AUGE <eau@phear.org>
8  * All rights reserved.
9  *
10  * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
11  *
12  * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
13  * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
14  * Neither the name of the phear.org nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
17  * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18  * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
19  * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
21  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
22  *
23  *
24  */
25 #include "includes.h"
26 __RCSID("$NetBSD: ldapauth.c,v 1.1 2010/11/21 18:59:04 adam Exp $");
27 
28 #ifdef WITH_LDAP_PUBKEY
29 #include <stdarg.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <unistd.h>
33 #include <string.h>
34 
35 #include "ldapauth.h"
36 #include "log.h"
37 
38 /* filter building infos */
39 #define FILTER_GROUP_PREFIX "(&(objectclass=posixGroup)"
40 #define FILTER_OR_PREFIX "(|"
41 #define FILTER_OR_SUFFIX ")"
42 #define FILTER_CN_PREFIX "(cn="
43 #define FILTER_CN_SUFFIX ")"
44 #define FILTER_UID_FORMAT "(memberUid=%s)"
45 #define FILTER_GROUP_SUFFIX ")"
46 #define FILTER_GROUP_SIZE(group) (size_t) (strlen(group)+(ldap_count_group(group)*5)+52)
47 
48 /* just filter building stuff */
49 #define REQUEST_GROUP_SIZE(filter, uid) (size_t) (strlen(filter)+strlen(uid)+1)
50 #define REQUEST_GROUP(buffer, prefilter, pwname) \
51     buffer = (char *) calloc(REQUEST_GROUP_SIZE(prefilter, pwname), sizeof(char)); \
52     if (!buffer) { \
53         perror("calloc()"); \
54         return FAILURE; \
55     } \
56     snprintf(buffer, REQUEST_GROUP_SIZE(prefilter,pwname), prefilter, pwname)
57 /*
58 XXX OLD group building macros
59 #define REQUEST_GROUP_SIZE(grp, uid) (size_t) (strlen(grp)+strlen(uid)+46)
60 #define REQUEST_GROUP(buffer,pwname,grp) \
61     buffer = (char *) calloc(REQUEST_GROUP_SIZE(grp, pwname), sizeof(char)); \
62     if (!buffer) { \
63         perror("calloc()"); \
64         return FAILURE; \
65     } \
66     snprintf(buffer,REQUEST_GROUP_SIZE(grp,pwname),"(&(objectclass=posixGroup)(cn=%s)(memberUid=%s))",grp,pwname)
67     */
68 
69 /*
70 XXX stock upstream version without extra filter support
71 #define REQUEST_USER_SIZE(uid) (size_t) (strlen(uid)+64)
72 #define REQUEST_USER(buffer, pwname) \
73     buffer = (char *) calloc(REQUEST_USER_SIZE(pwname), sizeof(char)); \
74     if (!buffer) { \
75         perror("calloc()"); \
76         return NULL; \
77     } \
78     snprintf(buffer,REQUEST_USER_SIZE(pwname),"(&(objectclass=posixAccount)(objectclass=ldapPublicKey)(uid=%s))",pwname)
79    */
80 
81 #define REQUEST_USER_SIZE(uid, filter) (size_t) (strlen(uid)+64+(filter != NULL ? strlen(filter) : 0))
82 #define REQUEST_USER(buffer, pwname, customfilter) \
83     buffer = (char *) calloc(REQUEST_USER_SIZE(pwname, customfilter), sizeof(char)); \
84     if (!buffer) { \
85         perror("calloc()"); \
86         return NULL; \
87     } \
88     snprintf(buffer, REQUEST_USER_SIZE(pwname, customfilter), \
89     	"(&(objectclass=posixAccount)(objectclass=ldapPublicKey)(uid=%s)%s)", \
90 	pwname, (customfilter != NULL ? customfilter : ""))
91 
92 /* some portable and working tokenizer, lame though */
93 static int tokenize(char ** o, size_t size, char * input) {
94     unsigned int i = 0, num;
95     const char * charset = " \t";
96     char * ptr = input;
97 
98     /* leading white spaces are ignored */
99     num = strspn(ptr, charset);
100     ptr += num;
101 
102     while ((num = strcspn(ptr, charset))) {
103         if (i < size-1) {
104             o[i++] = ptr;
105             ptr += num;
106             if (*ptr)
107                 *ptr++ = '\0';
108         }
109     }
110     o[i] = NULL;
111     return SUCCESS;
112 }
113 
114 void ldap_close(ldap_opt_t * ldap) {
115 
116     if (!ldap)
117         return;
118 
119     if ( ldap_unbind_ext(ldap->ld, NULL, NULL) < 0)
120 	ldap_perror(ldap->ld, "ldap_unbind()");
121 
122     ldap->ld = NULL;
123     FLAG_SET_DISCONNECTED(ldap->flags);
124 
125     return;
126 }
127 
128 /* init && bind */
129 int ldap_connect(ldap_opt_t * ldap) {
130     int version = LDAP_VERSION3;
131 
132     if (!ldap->servers)
133         return FAILURE;
134 
135     /* Connection Init and setup */
136     ldap->ld = ldap_init(ldap->servers, LDAP_PORT);
137     if (!ldap->ld) {
138         ldap_perror(ldap->ld, "ldap_init()");
139         return FAILURE;
140     }
141 
142     if ( ldap_set_option(ldap->ld, LDAP_OPT_PROTOCOL_VERSION, &version) != LDAP_OPT_SUCCESS) {
143         ldap_perror(ldap->ld, "ldap_set_option(LDAP_OPT_PROTOCOL_VERSION)");
144         return FAILURE;
145     }
146 
147     /* Timeouts setup */
148     if (ldap_set_option(ldap->ld, LDAP_OPT_NETWORK_TIMEOUT, &ldap->b_timeout) != LDAP_SUCCESS) {
149         ldap_perror(ldap->ld, "ldap_set_option(LDAP_OPT_NETWORK_TIMEOUT)");
150     }
151     if (ldap_set_option(ldap->ld, LDAP_OPT_TIMEOUT, &ldap->s_timeout) != LDAP_SUCCESS) {
152         ldap_perror(ldap->ld, "ldap_set_option(LDAP_OPT_TIMEOUT)");
153     }
154 
155     /* TLS support */
156     if ( (ldap->tls == -1) || (ldap->tls == 1) ) {
157         if (ldap_start_tls_s(ldap->ld, NULL, NULL ) != LDAP_SUCCESS) {
158             /* failed then reinit the initial connect */
159             ldap_perror(ldap->ld, "ldap_connect: (TLS) ldap_start_tls()");
160             if (ldap->tls == 1)
161                 return FAILURE;
162 
163             ldap->ld = ldap_init(ldap->servers, LDAP_PORT);
164             if (!ldap->ld) {
165                 ldap_perror(ldap->ld, "ldap_init()");
166                 return FAILURE;
167             }
168 
169             if ( ldap_set_option(ldap->ld, LDAP_OPT_PROTOCOL_VERSION, &version) != LDAP_OPT_SUCCESS) {
170                  ldap_perror(ldap->ld, "ldap_set_option()");
171                  return FAILURE;
172             }
173         }
174     }
175 
176 
177     if ( ldap_simple_bind_s(ldap->ld, ldap->binddn, ldap->bindpw) != LDAP_SUCCESS) {
178         ldap_perror(ldap->ld, "ldap_simple_bind_s()");
179         return FAILURE;
180     }
181 
182     /* says it is connected */
183     FLAG_SET_CONNECTED(ldap->flags);
184 
185     return SUCCESS;
186 }
187 
188 /* must free allocated ressource */
189 static char * ldap_build_host(char *host, int port) {
190     unsigned int size = strlen(host)+11;
191     char * h = (char *) calloc (size, sizeof(char));
192     int rc;
193     if (!h)
194          return NULL;
195 
196     rc = snprintf(h, size, "%s:%d ", host, port);
197     if (rc == -1)
198         return NULL;
199     return h;
200 }
201 
202 static int ldap_count_group(const char * input) {
203     const char * charset = " \t";
204     const char * ptr = input;
205     unsigned int count = 0;
206     unsigned int num;
207 
208     num = strspn(ptr, charset);
209     ptr += num;
210 
211     while ((num = strcspn(ptr, charset))) {
212     count++;
213     ptr += num;
214     ptr++;
215     }
216 
217     return count;
218 }
219 
220 /* format filter */
221 char * ldap_parse_groups(const char * groups) {
222     unsigned int buffer_size = FILTER_GROUP_SIZE(groups);
223     char * buffer = (char *) calloc(buffer_size, sizeof(char));
224     char * g = NULL;
225     char * garray[32];
226     unsigned int i = 0;
227 
228     if ((!groups)||(!buffer))
229         return NULL;
230 
231     g = strdup(groups);
232     if (!g) {
233         free(buffer);
234         return NULL;
235     }
236 
237     /* first separate into n tokens */
238     if ( tokenize(garray, sizeof(garray)/sizeof(*garray), g) < 0) {
239         free(g);
240         free(buffer);
241         return NULL;
242     }
243 
244     /* build the final filter format */
245     strlcat(buffer, FILTER_GROUP_PREFIX, buffer_size);
246     strlcat(buffer, FILTER_OR_PREFIX, buffer_size);
247     i = 0;
248     while (garray[i]) {
249         strlcat(buffer, FILTER_CN_PREFIX, buffer_size);
250         strlcat(buffer, garray[i], buffer_size);
251         strlcat(buffer, FILTER_CN_SUFFIX, buffer_size);
252         i++;
253     }
254     strlcat(buffer, FILTER_OR_SUFFIX, buffer_size);
255     strlcat(buffer, FILTER_UID_FORMAT, buffer_size);
256     strlcat(buffer, FILTER_GROUP_SUFFIX, buffer_size);
257 
258     free(g);
259     return buffer;
260 }
261 
262 /* a bit dirty but leak free  */
263 char * ldap_parse_servers(const char * servers) {
264     char * s = NULL;
265     char * tmp = NULL, *urls[32];
266     unsigned int num = 0 , i = 0 , asize = 0;
267     LDAPURLDesc *urld[32];
268 
269     if (!servers)
270         return NULL;
271 
272     /* local copy of the arg */
273     s = strdup(servers);
274     if (!s)
275         return NULL;
276 
277     /* first separate into URL tokens */
278     if ( tokenize(urls, sizeof(urls)/sizeof(*urls), s) < 0)
279         return NULL;
280 
281     i = 0;
282     while (urls[i]) {
283         if (! ldap_is_ldap_url(urls[i]) ||
284            (ldap_url_parse(urls[i], &urld[i]) != 0)) {
285                 return NULL;
286         }
287         i++;
288     }
289 
290     /* now free(s) */
291     free (s);
292 
293     /* how much memory do we need */
294     num = i;
295     for (i = 0 ; i < num ; i++)
296         asize += strlen(urld[i]->lud_host)+11;
297 
298     /* alloc */
299     s = (char *) calloc( asize+1 , sizeof(char));
300     if (!s) {
301         for (i = 0 ; i < num ; i++)
302             ldap_free_urldesc(urld[i]);
303         return NULL;
304     }
305 
306     /* then build the final host string */
307     for (i = 0 ; i < num ; i++) {
308         /* built host part */
309         tmp = ldap_build_host(urld[i]->lud_host, urld[i]->lud_port);
310         strncat(s, tmp, strlen(tmp));
311         ldap_free_urldesc(urld[i]);
312         free(tmp);
313     }
314 
315     return s;
316 }
317 
318 void ldap_options_print(ldap_opt_t * ldap) {
319     debug("ldap options:");
320     debug("servers: %s", ldap->servers);
321     if (ldap->u_basedn)
322         debug("user basedn: %s", ldap->u_basedn);
323     if (ldap->g_basedn)
324         debug("group basedn: %s", ldap->g_basedn);
325     if (ldap->binddn)
326         debug("binddn: %s", ldap->binddn);
327     if (ldap->bindpw)
328         debug("bindpw: %s", ldap->bindpw);
329     if (ldap->sgroup)
330         debug("group: %s", ldap->sgroup);
331     if (ldap->filter)
332         debug("filter: %s", ldap->filter);
333 }
334 
335 void ldap_options_free(ldap_opt_t * l) {
336     if (!l)
337         return;
338     if (l->servers)
339         free(l->servers);
340     if (l->u_basedn)
341         free(l->u_basedn);
342     if (l->g_basedn)
343         free(l->g_basedn);
344     if (l->binddn)
345         free(l->binddn);
346     if (l->bindpw)
347         free(l->bindpw);
348     if (l->sgroup)
349         free(l->sgroup);
350     if (l->fgroup)
351         free(l->fgroup);
352     if (l->filter)
353         free(l->filter);
354     if (l->l_conf)
355         free(l->l_conf);
356     free(l);
357 }
358 
359 /* free keys */
360 void ldap_keys_free(ldap_key_t * k) {
361     ldap_value_free_len(k->keys);
362     free(k);
363     return;
364 }
365 
366 ldap_key_t * ldap_getuserkey(ldap_opt_t *l, const char * user) {
367     ldap_key_t * k = (ldap_key_t *) calloc (1, sizeof(ldap_key_t));
368     LDAPMessage *res, *e;
369     char * filter;
370     int i;
371     char *attrs[] = {
372       l->pub_key_attr,
373       NULL
374     };
375 
376     if ((!k) || (!l))
377          return NULL;
378 
379     /* Am i still connected ? RETRY n times */
380     /* XXX TODO: setup some conf value for retrying */
381     if (!(l->flags & FLAG_CONNECTED))
382         for (i = 0 ; i < 2 ; i++)
383             if (ldap_connect(l) == 0)
384                 break;
385 
386     /* quick check for attempts to be evil */
387     if ((strchr(user, '(') != NULL) || (strchr(user, ')') != NULL) ||
388         (strchr(user, '*') != NULL) || (strchr(user, '\\') != NULL))
389         return NULL;
390 
391     /* build  filter for LDAP request */
392     REQUEST_USER(filter, user, l->filter);
393 
394     if ( ldap_search_st( l->ld,
395         l->u_basedn,
396         LDAP_SCOPE_SUBTREE,
397         filter,
398         attrs, 0, &l->s_timeout, &res ) != LDAP_SUCCESS) {
399 
400         ldap_perror(l->ld, "ldap_search_st()");
401 
402         free(filter);
403         free(k);
404 
405         /* XXX error on search, timeout etc.. close ask for reconnect */
406         ldap_close(l);
407 
408         return NULL;
409     }
410 
411     /* free */
412     free(filter);
413 
414     /* check if any results */
415     i = ldap_count_entries(l->ld,res);
416     if (i <= 0) {
417         ldap_msgfree(res);
418         free(k);
419         return NULL;
420     }
421 
422     if (i > 1)
423         debug("[LDAP] duplicate entries, using the FIRST entry returned");
424 
425     e = ldap_first_entry(l->ld, res);
426     k->keys = ldap_get_values_len(l->ld, e, l->pub_key_attr);
427     k->num = ldap_count_values_len(k->keys);
428 
429     ldap_msgfree(res);
430     return k;
431 }
432 
433 
434 /* -1 if trouble
435    0 if user is NOT member of current server group
436    1 if user IS MEMBER of current server group
437  */
438 int ldap_ismember(ldap_opt_t * l, const char * user) {
439     LDAPMessage *res;
440     char * filter;
441     int i;
442 
443     if ((!l->sgroup) || !(l->g_basedn))
444         return 1;
445 
446     /* Am i still connected ? RETRY n times */
447     /* XXX TODO: setup some conf value for retrying */
448     if (!(l->flags & FLAG_CONNECTED))
449         for (i = 0 ; i < 2 ; i++)
450             if (ldap_connect(l) == 0)
451                  break;
452 
453     /* quick check for attempts to be evil */
454     if ((strchr(user, '(') != NULL) || (strchr(user, ')') != NULL) ||
455         (strchr(user, '*') != NULL) || (strchr(user, '\\') != NULL))
456         return FAILURE;
457 
458     /* build filter for LDAP request */
459     REQUEST_GROUP(filter, l->fgroup, user);
460 
461     if (ldap_search_st( l->ld,
462         l->g_basedn,
463         LDAP_SCOPE_SUBTREE,
464         filter,
465         NULL, 0, &l->s_timeout, &res) != LDAP_SUCCESS) {
466 
467         ldap_perror(l->ld, "ldap_search_st()");
468 
469         free(filter);
470 
471         /* XXX error on search, timeout etc.. close ask for reconnect */
472         ldap_close(l);
473 
474         return FAILURE;
475     }
476 
477     free(filter);
478 
479     /* check if any results */
480     if (ldap_count_entries(l->ld, res) > 0) {
481         ldap_msgfree(res);
482         return 1;
483     }
484 
485     ldap_msgfree(res);
486     return 0;
487 }
488 
489 /*
490  * ldap.conf simple parser
491  * XXX TODO:  sanity checks
492  * must either
493  * - free the previous ldap_opt_before replacing entries
494  * - free each necessary previously parsed elements
495  * ret:
496  * -1 on FAILURE, 0 on SUCCESS
497  */
498 int ldap_parse_lconf(ldap_opt_t * l) {
499     FILE * lcd; /* ldap.conf descriptor */
500     char buf[BUFSIZ];
501     char * s = NULL, * k = NULL, * v = NULL;
502     int li, len;
503 
504     lcd = fopen (l->l_conf, "r");
505     if (lcd == NULL) {
506         /* debug("Cannot open %s", l->l_conf); */
507         perror("ldap_parse_lconf()");
508         return FAILURE;
509     }
510 
511     while (fgets (buf, sizeof (buf), lcd) != NULL) {
512 
513         if (*buf == '\n' || *buf == '#')
514             continue;
515 
516         k = buf;
517         v = k;
518         while (*v != '\0' && *v != ' ' && *v != '\t')
519             v++;
520 
521         if (*v == '\0')
522             continue;
523 
524         *(v++) = '\0';
525 
526         while (*v == ' ' || *v == '\t')
527             v++;
528 
529         li = strlen (v) - 1;
530         while (v[li] == ' ' || v[li] == '\t' || v[li] == '\n')
531             --li;
532         v[li + 1] = '\0';
533 
534         if (!strcasecmp (k, "uri")) {
535             if ((l->servers = ldap_parse_servers(v)) == NULL) {
536                 fatal("error in ldap servers");
537             return FAILURE;
538             }
539 
540         }
541         else if (!strcasecmp (k, "base")) {
542             s = strchr (v, '?');
543             if (s != NULL) {
544                 len = s - v;
545                 l->u_basedn = malloc (len + 1);
546                 strncpy (l->u_basedn, v, len);
547                 l->u_basedn[len] = '\0';
548             } else {
549                 l->u_basedn = strdup (v);
550             }
551         }
552         else if (!strcasecmp (k, "binddn")) {
553             l->binddn = strdup (v);
554         }
555         else if (!strcasecmp (k, "bindpw")) {
556             l->bindpw = strdup (v);
557         }
558         else if (!strcasecmp (k, "timelimit")) {
559             l->s_timeout.tv_sec = atoi (v);
560                 }
561         else if (!strcasecmp (k, "bind_timelimit")) {
562             l->b_timeout.tv_sec = atoi (v);
563         }
564         else if (!strcasecmp (k, "ssl")) {
565             if (!strcasecmp (v, "start_tls"))
566                 l->tls = 1;
567         }
568     }
569 
570     fclose (lcd);
571     return SUCCESS;
572 }
573 
574 #endif /* WITH_LDAP_PUBKEY */
575