xref: /netbsd-src/crypto/external/bsd/openssh/dist/ldapauth.c (revision f5a3e1b0221655db9f9a8f38b0311a438f5544ec)
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 /*
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 */
tokenize(char ** o,size_t size,char * input)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 
ldap_close(ldap_opt_t * ldap)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 */
ldap_xconnect(ldap_opt_t * ldap)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 */
ldap_build_host(char * host,int port)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 
ldap_count_group(const char * input)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 */
ldap_parse_groups(const char * groups)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  */
ldap_parse_servers(const char * servers)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 
ldap_options_print(ldap_opt_t * ldap)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 
ldap_options_free(ldap_opt_t * l)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 */
ldap_keys_free(ldap_key_t * k)358 void ldap_keys_free(ldap_key_t * k) {
359     ldap_value_free_len(k->keys);
360     free(k);
361     return;
362 }
363 
ldap_getuserkey(ldap_opt_t * l,const char * user)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  */
ldap_ismember(ldap_opt_t * l,const char * user)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  */
ldap_parse_lconf(ldap_opt_t * l)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