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