Home | History | Annotate | Line # | Download | only in dist
      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 (at) 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 /*
     56 XXX 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 /*
     68 XXX 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 */
     91 static 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 
    112 void 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 */
    127 int 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 */
    187 static 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 
    200 static 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 */
    219 char * 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  */
    261 char * 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 
    316 void 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 
    333 void 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 */
    358 void ldap_keys_free(ldap_key_t * k) {
    359     ldap_value_free_len(k->keys);
    360     free(k);
    361     return;
    362 }
    363 
    364 ldap_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  */
    436 int 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  */
    496 int 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