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