1 /*
2 * Copyright (c) 2001-2009 Sendmail, Inc. and its suppliers.
3 * All rights reserved.
4 *
5 * By using this file, you agree to the terms and conditions set
6 * forth in the LICENSE file which can be found at the top level of
7 * the sendmail distribution.
8 */
9
10 /* some "deprecated" calls are used, e.g., ldap_get_values() */
11 #define LDAP_DEPRECATED 1
12
13 #include <sm/gen.h>
14 SM_RCSID("@(#)$Id: ldap.c,v 1.83 2009/06/19 22:02:26 guenther Exp $")
15
16 #if LDAPMAP
17 # include <sys/types.h>
18 # include <errno.h>
19 # include <setjmp.h>
20 # include <stdlib.h>
21 # include <unistd.h>
22
23 # include <sm/bitops.h>
24 # include <sm/clock.h>
25 # include <sm/conf.h>
26 # include <sm/debug.h>
27 # include <sm/errstring.h>
28 # include <sm/ldap.h>
29 # include <sm/string.h>
30 # ifdef EX_OK
31 # undef EX_OK /* for SVr4.2 SMP */
32 # endif /* EX_OK */
33 # include <sm/sysexits.h>
34
35 SM_DEBUG_T SmLDAPTrace = SM_DEBUG_INITIALIZER("sm_trace_ldap",
36 "@(#)$Debug: sm_trace_ldap - trace LDAP operations $");
37
38 static void ldaptimeout __P((int));
39 static bool sm_ldap_has_objectclass __P((SM_LDAP_STRUCT *, LDAPMessage *, char *));
40 static SM_LDAP_RECURSE_ENTRY *sm_ldap_add_recurse __P((SM_LDAP_RECURSE_LIST **, char *, int, SM_RPOOL_T *));
41
42 /*
43 ** SM_LDAP_CLEAR -- set default values for SM_LDAP_STRUCT
44 **
45 ** Parameters:
46 ** lmap -- pointer to SM_LDAP_STRUCT to clear
47 **
48 ** Returns:
49 ** None.
50 **
51 */
52
53 #if _FFR_LDAP_VERSION
54 # if defined(LDAP_VERSION_MAX) && _FFR_LDAP_VERSION > LDAP_VERSION_MAX
55 ERROR FFR_LDAP_VERSION > _LDAP_VERSION_MAX
56 # endif /* defined(LDAP_VERSION_MAX) && _FFR_LDAP_VERSION > LDAP_VERSION_MAX */
57 # if defined(LDAP_VERSION_MIN) && _FFR_LDAP_VERSION < LDAP_VERSION_MIN
58 ERROR FFR_LDAP_VERSION < _LDAP_VERSION_MIN
59 # endif /* defined(LDAP_VERSION_MIN) && _FFR_LDAP_VERSION < LDAP_VERSION_MIN */
60 # define SM_LDAP_VERSION_DEFAULT _FFR_LDAP_VERSION
61 #else /* _FFR_LDAP_VERSION */
62 # define SM_LDAP_VERSION_DEFAULT 0
63 #endif /* _FFR_LDAP_VERSION */
64
65 void
sm_ldap_clear(lmap)66 sm_ldap_clear(lmap)
67 SM_LDAP_STRUCT *lmap;
68 {
69 if (lmap == NULL)
70 return;
71
72 lmap->ldap_host = NULL;
73 lmap->ldap_port = LDAP_PORT;
74 lmap->ldap_uri = NULL;
75 lmap->ldap_version = SM_LDAP_VERSION_DEFAULT;
76 lmap->ldap_deref = LDAP_DEREF_NEVER;
77 lmap->ldap_timelimit = LDAP_NO_LIMIT;
78 lmap->ldap_sizelimit = LDAP_NO_LIMIT;
79 # ifdef LDAP_REFERRALS
80 lmap->ldap_options = LDAP_OPT_REFERRALS;
81 # else /* LDAP_REFERRALS */
82 lmap->ldap_options = 0;
83 # endif /* LDAP_REFERRALS */
84 lmap->ldap_attrsep = '\0';
85 lmap->ldap_binddn = NULL;
86 lmap->ldap_secret = NULL;
87 lmap->ldap_method = LDAP_AUTH_SIMPLE;
88 lmap->ldap_base = NULL;
89 lmap->ldap_scope = LDAP_SCOPE_SUBTREE;
90 lmap->ldap_attrsonly = LDAPMAP_FALSE;
91 lmap->ldap_timeout.tv_sec = 0;
92 lmap->ldap_timeout.tv_usec = 0;
93 lmap->ldap_ld = NULL;
94 lmap->ldap_filter = NULL;
95 lmap->ldap_attr[0] = NULL;
96 lmap->ldap_attr_type[0] = SM_LDAP_ATTR_NONE;
97 lmap->ldap_attr_needobjclass[0] = NULL;
98 lmap->ldap_res = NULL;
99 lmap->ldap_next = NULL;
100 lmap->ldap_pid = 0;
101 lmap->ldap_multi_args = false;
102 }
103
104 /*
105 ** SM_LDAP_START -- actually connect to an LDAP server
106 **
107 ** Parameters:
108 ** name -- name of map for debug output.
109 ** lmap -- the LDAP map being opened.
110 **
111 ** Returns:
112 ** true if connection is successful, false otherwise.
113 **
114 ** Side Effects:
115 ** Populates lmap->ldap_ld.
116 */
117
118 static jmp_buf LDAPTimeout;
119
120 #define SM_LDAP_SETTIMEOUT(to) \
121 do \
122 { \
123 if (to != 0) \
124 { \
125 if (setjmp(LDAPTimeout) != 0) \
126 { \
127 errno = ETIMEDOUT; \
128 return false; \
129 } \
130 ev = sm_setevent(to, ldaptimeout, 0); \
131 } \
132 } while (0)
133
134 #define SM_LDAP_CLEARTIMEOUT() \
135 do \
136 { \
137 if (ev != NULL) \
138 sm_clrevent(ev); \
139 } while (0)
140
141 bool
sm_ldap_start(name,lmap)142 sm_ldap_start(name, lmap)
143 char *name;
144 SM_LDAP_STRUCT *lmap;
145 {
146 int bind_result;
147 int save_errno = 0;
148 char *id;
149 SM_EVENT *ev = NULL;
150 LDAP *ld = NULL;
151
152 if (sm_debug_active(&SmLDAPTrace, 2))
153 sm_dprintf("ldapmap_start(%s)\n", name == NULL ? "" : name);
154
155 if (lmap->ldap_host != NULL)
156 id = lmap->ldap_host;
157 else if (lmap->ldap_uri != NULL)
158 id = lmap->ldap_uri;
159 else
160 id = "localhost";
161
162 if (sm_debug_active(&SmLDAPTrace, 9))
163 {
164 /* Don't print a port number for LDAP URIs */
165 if (lmap->ldap_uri != NULL)
166 sm_dprintf("ldapmap_start(%s)\n", id);
167 else
168 sm_dprintf("ldapmap_start(%s, %d)\n", id,
169 lmap->ldap_port);
170 }
171
172 if (lmap->ldap_uri != NULL)
173 {
174 #if SM_CONF_LDAP_INITIALIZE
175 /* LDAP server supports URIs so use them directly */
176 save_errno = ldap_initialize(&ld, lmap->ldap_uri);
177 #else /* SM_CONF_LDAP_INITIALIZE */
178 int err;
179 LDAPURLDesc *ludp = NULL;
180
181 /* Blast apart URL and use the ldap_init/ldap_open below */
182 err = ldap_url_parse(lmap->ldap_uri, &ludp);
183 if (err != 0)
184 {
185 errno = err + E_LDAPURLBASE;
186 return false;
187 }
188 lmap->ldap_host = sm_strdup_x(ludp->lud_host);
189 if (lmap->ldap_host == NULL)
190 {
191 save_errno = errno;
192 ldap_free_urldesc(ludp);
193 errno = save_errno;
194 return false;
195 }
196 lmap->ldap_port = ludp->lud_port;
197 ldap_free_urldesc(ludp);
198 #endif /* SM_CONF_LDAP_INITIALIZE */
199 }
200
201 if (ld == NULL)
202 {
203 # if USE_LDAP_INIT
204 ld = ldap_init(lmap->ldap_host, lmap->ldap_port);
205 save_errno = errno;
206 # else /* USE_LDAP_INIT */
207 /*
208 ** If using ldap_open(), the actual connection to the server
209 ** happens now so we need the timeout here. For ldap_init(),
210 ** the connection happens at bind time.
211 */
212
213 SM_LDAP_SETTIMEOUT(lmap->ldap_timeout.tv_sec);
214 ld = ldap_open(lmap->ldap_host, lmap->ldap_port);
215 save_errno = errno;
216
217 /* clear the event if it has not sprung */
218 SM_LDAP_CLEARTIMEOUT();
219 # endif /* USE_LDAP_INIT */
220 }
221
222 errno = save_errno;
223 if (ld == NULL)
224 return false;
225
226 sm_ldap_setopts(ld, lmap);
227
228 # if USE_LDAP_INIT
229 /*
230 ** If using ldap_init(), the actual connection to the server
231 ** happens at ldap_bind_s() so we need the timeout here.
232 */
233
234 SM_LDAP_SETTIMEOUT(lmap->ldap_timeout.tv_sec);
235 # endif /* USE_LDAP_INIT */
236
237 # ifdef LDAP_AUTH_KRBV4
238 if (lmap->ldap_method == LDAP_AUTH_KRBV4 &&
239 lmap->ldap_secret != NULL)
240 {
241 /*
242 ** Need to put ticket in environment here instead of
243 ** during parseargs as there may be different tickets
244 ** for different LDAP connections.
245 */
246
247 (void) putenv(lmap->ldap_secret);
248 }
249 # endif /* LDAP_AUTH_KRBV4 */
250
251 bind_result = ldap_bind_s(ld, lmap->ldap_binddn,
252 lmap->ldap_secret, lmap->ldap_method);
253
254 # if USE_LDAP_INIT
255 /* clear the event if it has not sprung */
256 SM_LDAP_CLEARTIMEOUT();
257 # endif /* USE_LDAP_INIT */
258
259 if (bind_result != LDAP_SUCCESS)
260 {
261 errno = bind_result + E_LDAPBASE;
262 return false;
263 }
264
265 /* Save PID to make sure only this PID closes the LDAP connection */
266 lmap->ldap_pid = getpid();
267 lmap->ldap_ld = ld;
268 return true;
269 }
270
271 /* ARGSUSED */
272 static void
ldaptimeout(unused)273 ldaptimeout(unused)
274 int unused;
275 {
276 /*
277 ** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
278 ** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
279 ** DOING.
280 */
281
282 errno = ETIMEDOUT;
283 longjmp(LDAPTimeout, 1);
284 }
285
286 /*
287 ** SM_LDAP_SEARCH_M -- initiate multi-key LDAP search
288 **
289 ** Initiate an LDAP search, return the msgid.
290 ** The calling function must collect the results.
291 **
292 ** Parameters:
293 ** lmap -- LDAP map information
294 ** argv -- key vector of substitutions in LDAP filter
295 ** NOTE: argv must have SM_LDAP_ARGS elements to prevent
296 ** out of bound array references
297 **
298 ** Returns:
299 ** <0 on failure (SM_LDAP_ERR*), msgid on success
300 **
301 */
302
303 int
sm_ldap_search_m(lmap,argv)304 sm_ldap_search_m(lmap, argv)
305 SM_LDAP_STRUCT *lmap;
306 char **argv;
307 {
308 int msgid;
309 char *fp, *p, *q;
310 char filter[LDAPMAP_MAX_FILTER + 1];
311
312 SM_REQUIRE(lmap != NULL);
313 SM_REQUIRE(argv != NULL);
314 SM_REQUIRE(argv[0] != NULL);
315
316 memset(filter, '\0', sizeof filter);
317 fp = filter;
318 p = lmap->ldap_filter;
319 while ((q = strchr(p, '%')) != NULL)
320 {
321 char *key;
322
323 if (lmap->ldap_multi_args)
324 {
325 #if SM_LDAP_ARGS < 10
326 # ERROR _SM_LDAP_ARGS must be 10
327 #endif /* SM_LDAP_ARGS < 10 */
328 if (q[1] == 's')
329 key = argv[0];
330 else if (q[1] >= '0' && q[1] <= '9')
331 {
332 key = argv[q[1] - '0'];
333 if (key == NULL)
334 {
335 # if SM_LDAP_ERROR_ON_MISSING_ARGS
336 return SM_LDAP_ERR_ARG_MISS;
337 # else /* SM_LDAP_ERROR_ON_MISSING_ARGS */
338 key = "";
339 # endif /* SM_LDAP_ERROR_ON_MISSING_ARGS */
340 }
341 }
342 else
343 key = NULL;
344 }
345 else
346 key = argv[0];
347
348 if (q[1] == 's')
349 {
350 (void) sm_snprintf(fp, SPACELEFT(filter, fp),
351 "%.*s%s", (int) (q - p), p, key);
352 fp += strlen(fp);
353 p = q + 2;
354 }
355 else if (q[1] == '0' ||
356 (lmap->ldap_multi_args && q[1] >= '0' && q[1] <= '9'))
357 {
358 char *k = key;
359
360 (void) sm_snprintf(fp, SPACELEFT(filter, fp),
361 "%.*s", (int) (q - p), p);
362 fp += strlen(fp);
363 p = q + 2;
364
365 /* Properly escape LDAP special characters */
366 while (SPACELEFT(filter, fp) > 0 &&
367 *k != '\0')
368 {
369 if (*k == '*' || *k == '(' ||
370 *k == ')' || *k == '\\')
371 {
372 (void) sm_strlcat(fp,
373 (*k == '*' ? "\\2A" :
374 (*k == '(' ? "\\28" :
375 (*k == ')' ? "\\29" :
376 (*k == '\\' ? "\\5C" :
377 "\00")))),
378 SPACELEFT(filter, fp));
379 fp += strlen(fp);
380 k++;
381 }
382 else
383 *fp++ = *k++;
384 }
385 }
386 else
387 {
388 (void) sm_snprintf(fp, SPACELEFT(filter, fp),
389 "%.*s", (int) (q - p + 1), p);
390 p = q + (q[1] == '%' ? 2 : 1);
391 fp += strlen(fp);
392 }
393 }
394 (void) sm_strlcpy(fp, p, SPACELEFT(filter, fp));
395 if (sm_debug_active(&SmLDAPTrace, 20))
396 sm_dprintf("ldap search filter=%s\n", filter);
397
398 lmap->ldap_res = NULL;
399 msgid = ldap_search(lmap->ldap_ld, lmap->ldap_base,
400 lmap->ldap_scope, filter,
401 (lmap->ldap_attr[0] == NULL ? NULL :
402 lmap->ldap_attr),
403 lmap->ldap_attrsonly);
404 return msgid;
405 }
406
407 /*
408 ** SM_LDAP_SEARCH -- initiate LDAP search
409 **
410 ** Initiate an LDAP search, return the msgid.
411 ** The calling function must collect the results.
412 ** Note this is just a wrapper into sm_ldap_search_m()
413 **
414 ** Parameters:
415 ** lmap -- LDAP map information
416 ** key -- key to substitute in LDAP filter
417 **
418 ** Returns:
419 ** <0 on failure, msgid on success
420 **
421 */
422
423 int
sm_ldap_search(lmap,key)424 sm_ldap_search(lmap, key)
425 SM_LDAP_STRUCT *lmap;
426 char *key;
427 {
428 char *argv[SM_LDAP_ARGS];
429
430 memset(argv, '\0', sizeof argv);
431 argv[0] = key;
432 return sm_ldap_search_m(lmap, argv);
433 }
434
435 /*
436 ** SM_LDAP_HAS_OBJECTCLASS -- determine if an LDAP entry is part of a
437 ** particular objectClass
438 **
439 ** Parameters:
440 ** lmap -- pointer to SM_LDAP_STRUCT in use
441 ** entry -- current LDAP entry struct
442 ** ocvalue -- particular objectclass in question.
443 ** may be of form (fee|foo|fum) meaning
444 ** any entry can be part of either fee,
445 ** foo or fum objectclass
446 **
447 ** Returns:
448 ** true if item has that objectClass
449 */
450
451 static bool
sm_ldap_has_objectclass(lmap,entry,ocvalue)452 sm_ldap_has_objectclass(lmap, entry, ocvalue)
453 SM_LDAP_STRUCT *lmap;
454 LDAPMessage *entry;
455 char *ocvalue;
456 {
457 char **vals = NULL;
458 int i;
459
460 if (ocvalue == NULL)
461 return false;
462
463 vals = ldap_get_values(lmap->ldap_ld, entry, "objectClass");
464 if (vals == NULL)
465 return false;
466
467 for (i = 0; vals[i] != NULL; i++)
468 {
469 char *p;
470 char *q;
471
472 p = q = ocvalue;
473 while (*p != '\0')
474 {
475 while (*p != '\0' && *p != '|')
476 p++;
477
478 if ((p - q) == strlen(vals[i]) &&
479 sm_strncasecmp(vals[i], q, p - q) == 0)
480 {
481 ldap_value_free(vals);
482 return true;
483 }
484
485 while (*p == '|')
486 p++;
487 q = p;
488 }
489 }
490
491 ldap_value_free(vals);
492 return false;
493 }
494
495 /*
496 ** SM_LDAP_RESULTS -- return results from an LDAP lookup in result
497 **
498 ** Parameters:
499 ** lmap -- pointer to SM_LDAP_STRUCT in use
500 ** msgid -- msgid returned by sm_ldap_search()
501 ** flags -- flags for the lookup
502 ** delim -- delimiter for result concatenation
503 ** rpool -- memory pool for storage
504 ** result -- return string
505 ** recurse -- recursion list
506 **
507 ** Returns:
508 ** status (sysexit)
509 */
510
511 # define SM_LDAP_ERROR_CLEANUP() \
512 { \
513 if (lmap->ldap_res != NULL) \
514 { \
515 ldap_msgfree(lmap->ldap_res); \
516 lmap->ldap_res = NULL; \
517 } \
518 (void) ldap_abandon(lmap->ldap_ld, msgid); \
519 }
520
521 static SM_LDAP_RECURSE_ENTRY *
sm_ldap_add_recurse(top,item,type,rpool)522 sm_ldap_add_recurse(top, item, type, rpool)
523 SM_LDAP_RECURSE_LIST **top;
524 char *item;
525 int type;
526 SM_RPOOL_T *rpool;
527 {
528 int n;
529 int m;
530 int p;
531 int insertat;
532 int moveb;
533 int oldsizeb;
534 int rc;
535 SM_LDAP_RECURSE_ENTRY *newe;
536 SM_LDAP_RECURSE_ENTRY **olddata;
537
538 /*
539 ** This code will maintain a list of
540 ** SM_LDAP_RECURSE_ENTRY structures
541 ** in ascending order.
542 */
543
544 if (*top == NULL)
545 {
546 /* Allocate an initial SM_LDAP_RECURSE_LIST struct */
547 *top = sm_rpool_malloc_x(rpool, sizeof **top);
548 (*top)->lrl_cnt = 0;
549 (*top)->lrl_size = 0;
550 (*top)->lrl_data = NULL;
551 }
552
553 if ((*top)->lrl_cnt >= (*top)->lrl_size)
554 {
555 /* Grow the list of SM_LDAP_RECURSE_ENTRY ptrs */
556 olddata = (*top)->lrl_data;
557 if ((*top)->lrl_size == 0)
558 {
559 oldsizeb = 0;
560 (*top)->lrl_size = 256;
561 }
562 else
563 {
564 oldsizeb = (*top)->lrl_size * sizeof *((*top)->lrl_data);
565 (*top)->lrl_size *= 2;
566 }
567 (*top)->lrl_data = sm_rpool_malloc_x(rpool,
568 (*top)->lrl_size * sizeof *((*top)->lrl_data));
569 if (oldsizeb > 0)
570 memcpy((*top)->lrl_data, olddata, oldsizeb);
571 }
572
573 /*
574 ** Binary search/insert item:type into list.
575 ** Return current entry pointer if already exists.
576 */
577
578 n = 0;
579 m = (*top)->lrl_cnt - 1;
580 if (m < 0)
581 insertat = 0;
582 else
583 insertat = -1;
584
585 while (insertat == -1)
586 {
587 p = (m + n) / 2;
588
589 rc = sm_strcasecmp(item, (*top)->lrl_data[p]->lr_search);
590 if (rc == 0)
591 rc = type - (*top)->lrl_data[p]->lr_type;
592
593 if (rc < 0)
594 m = p - 1;
595 else if (rc > 0)
596 n = p + 1;
597 else
598 return (*top)->lrl_data[p];
599
600 if (m == -1)
601 insertat = 0;
602 else if (n >= (*top)->lrl_cnt)
603 insertat = (*top)->lrl_cnt;
604 else if (m < n)
605 insertat = m + 1;
606 }
607
608 /*
609 ** Not found in list, make room
610 ** at insert point and add it.
611 */
612
613 newe = sm_rpool_malloc_x(rpool, sizeof *newe);
614 if (newe != NULL)
615 {
616 moveb = ((*top)->lrl_cnt - insertat) * sizeof *((*top)->lrl_data);
617 if (moveb > 0)
618 memmove(&((*top)->lrl_data[insertat + 1]),
619 &((*top)->lrl_data[insertat]),
620 moveb);
621
622 newe->lr_search = sm_rpool_strdup_x(rpool, item);
623 newe->lr_type = type;
624 newe->lr_ludp = NULL;
625 newe->lr_attrs = NULL;
626 newe->lr_done = false;
627
628 ((*top)->lrl_data)[insertat] = newe;
629 (*top)->lrl_cnt++;
630 }
631 return newe;
632 }
633
634 int
sm_ldap_results(lmap,msgid,flags,delim,rpool,result,resultln,resultsz,recurse)635 sm_ldap_results(lmap, msgid, flags, delim, rpool, result,
636 resultln, resultsz, recurse)
637 SM_LDAP_STRUCT *lmap;
638 int msgid;
639 int flags;
640 int delim;
641 SM_RPOOL_T *rpool;
642 char **result;
643 int *resultln;
644 int *resultsz;
645 SM_LDAP_RECURSE_LIST *recurse;
646 {
647 bool toplevel;
648 int i;
649 int statp;
650 int vsize;
651 int ret;
652 int save_errno;
653 char *p;
654 SM_LDAP_RECURSE_ENTRY *rl;
655
656 /* Are we the top top level of the search? */
657 toplevel = (recurse == NULL);
658
659 /* Get results */
660 statp = EX_NOTFOUND;
661 while ((ret = ldap_result(lmap->ldap_ld, msgid, 0,
662 (lmap->ldap_timeout.tv_sec == 0 ? NULL :
663 &(lmap->ldap_timeout)),
664 &(lmap->ldap_res))) == LDAP_RES_SEARCH_ENTRY)
665 {
666 LDAPMessage *entry;
667
668 /* If we don't want multiple values and we have one, break */
669 if ((char) delim == '\0' &&
670 !bitset(SM_LDAP_SINGLEMATCH, flags) &&
671 *result != NULL)
672 break;
673
674 /* Cycle through all entries */
675 for (entry = ldap_first_entry(lmap->ldap_ld, lmap->ldap_res);
676 entry != NULL;
677 entry = ldap_next_entry(lmap->ldap_ld, lmap->ldap_res))
678 {
679 BerElement *ber;
680 char *attr;
681 char **vals = NULL;
682 char *dn;
683
684 /*
685 ** If matching only and found an entry,
686 ** no need to spin through attributes
687 */
688
689 if (bitset(SM_LDAP_MATCHONLY, flags))
690 {
691 statp = EX_OK;
692 continue;
693 }
694
695 #if _FFR_LDAP_SINGLEDN
696 if (bitset(SM_LDAP_SINGLEDN, flags) && *result != NULL)
697 {
698 /* only wanted one match */
699 SM_LDAP_ERROR_CLEANUP();
700 errno = ENOENT;
701 return EX_NOTFOUND;
702 }
703 #endif /* _FFR_LDAP_SINGLEDN */
704
705 /* record completed DN's to prevent loops */
706 dn = ldap_get_dn(lmap->ldap_ld, entry);
707 if (dn == NULL)
708 {
709 save_errno = sm_ldap_geterrno(lmap->ldap_ld);
710 save_errno += E_LDAPBASE;
711 SM_LDAP_ERROR_CLEANUP();
712 errno = save_errno;
713 return EX_TEMPFAIL;
714 }
715
716 rl = sm_ldap_add_recurse(&recurse, dn,
717 SM_LDAP_ATTR_DN,
718 rpool);
719
720 if (rl == NULL)
721 {
722 ldap_memfree(dn);
723 SM_LDAP_ERROR_CLEANUP();
724 errno = ENOMEM;
725 return EX_OSERR;
726 }
727 else if (rl->lr_done)
728 {
729 /* already on list, skip it */
730 ldap_memfree(dn);
731 continue;
732 }
733 ldap_memfree(dn);
734
735 # if !defined(LDAP_VERSION_MAX) && !defined(LDAP_OPT_SIZELIMIT)
736 /*
737 ** Reset value to prevent lingering
738 ** LDAP_DECODING_ERROR due to
739 ** OpenLDAP 1.X's hack (see below)
740 */
741
742 lmap->ldap_ld->ld_errno = LDAP_SUCCESS;
743 # endif /* !defined(LDAP_VERSION_MAX) !defined(LDAP_OPT_SIZELIMIT) */
744
745 for (attr = ldap_first_attribute(lmap->ldap_ld, entry,
746 &ber);
747 attr != NULL;
748 attr = ldap_next_attribute(lmap->ldap_ld, entry,
749 ber))
750 {
751 char *tmp, *vp_tmp;
752 int type;
753 char *needobjclass = NULL;
754
755 type = SM_LDAP_ATTR_NONE;
756 for (i = 0; lmap->ldap_attr[i] != NULL; i++)
757 {
758 if (sm_strcasecmp(lmap->ldap_attr[i],
759 attr) == 0)
760 {
761 type = lmap->ldap_attr_type[i];
762 needobjclass = lmap->ldap_attr_needobjclass[i];
763 break;
764 }
765 }
766
767 if (bitset(SM_LDAP_USE_ALLATTR, flags) &&
768 type == SM_LDAP_ATTR_NONE)
769 {
770 /* URL lookups specify attrs to use */
771 type = SM_LDAP_ATTR_NORMAL;
772 needobjclass = NULL;
773 }
774
775 if (type == SM_LDAP_ATTR_NONE)
776 {
777 /* attribute not requested */
778 ldap_memfree(attr);
779 SM_LDAP_ERROR_CLEANUP();
780 errno = EFAULT;
781 return EX_SOFTWARE;
782 }
783
784 /*
785 ** For recursion on a particular attribute,
786 ** we may need to see if this entry is
787 ** part of a particular objectclass.
788 ** Also, ignore objectClass attribute.
789 ** Otherwise we just ignore this attribute.
790 */
791
792 if (type == SM_LDAP_ATTR_OBJCLASS ||
793 (needobjclass != NULL &&
794 !sm_ldap_has_objectclass(lmap, entry,
795 needobjclass)))
796 {
797 ldap_memfree(attr);
798 continue;
799 }
800
801 if (lmap->ldap_attrsonly == LDAPMAP_FALSE)
802 {
803 vals = ldap_get_values(lmap->ldap_ld,
804 entry,
805 attr);
806 if (vals == NULL)
807 {
808 save_errno = sm_ldap_geterrno(lmap->ldap_ld);
809 if (save_errno == LDAP_SUCCESS)
810 {
811 ldap_memfree(attr);
812 continue;
813 }
814
815 /* Must be an error */
816 save_errno += E_LDAPBASE;
817 ldap_memfree(attr);
818 SM_LDAP_ERROR_CLEANUP();
819 errno = save_errno;
820 return EX_TEMPFAIL;
821 }
822 }
823
824 statp = EX_OK;
825
826 # if !defined(LDAP_VERSION_MAX) && !defined(LDAP_OPT_SIZELIMIT)
827 /*
828 ** Reset value to prevent lingering
829 ** LDAP_DECODING_ERROR due to
830 ** OpenLDAP 1.X's hack (see below)
831 */
832
833 lmap->ldap_ld->ld_errno = LDAP_SUCCESS;
834 # endif /* !defined(LDAP_VERSION_MAX) !defined(LDAP_OPT_SIZELIMIT) */
835
836 /*
837 ** If matching only,
838 ** no need to spin through entries
839 */
840
841 if (bitset(SM_LDAP_MATCHONLY, flags))
842 {
843 if (lmap->ldap_attrsonly == LDAPMAP_FALSE)
844 ldap_value_free(vals);
845 ldap_memfree(attr);
846 continue;
847 }
848
849 /*
850 ** If we don't want multiple values,
851 ** return first found.
852 */
853
854 if ((char) delim == '\0')
855 {
856 if (*result != NULL)
857 {
858 /* already have a value */
859 if (bitset(SM_LDAP_SINGLEMATCH,
860 flags))
861 {
862 /* only wanted one match */
863 SM_LDAP_ERROR_CLEANUP();
864 errno = ENOENT;
865 return EX_NOTFOUND;
866 }
867 break;
868 }
869
870 if (lmap->ldap_attrsonly == LDAPMAP_TRUE)
871 {
872 *result = sm_rpool_strdup_x(rpool,
873 attr);
874 ldap_memfree(attr);
875 break;
876 }
877
878 if (vals[0] == NULL)
879 {
880 ldap_value_free(vals);
881 ldap_memfree(attr);
882 continue;
883 }
884
885 vsize = strlen(vals[0]) + 1;
886 if (lmap->ldap_attrsep != '\0')
887 vsize += strlen(attr) + 1;
888 *result = sm_rpool_malloc_x(rpool,
889 vsize);
890 if (lmap->ldap_attrsep != '\0')
891 sm_snprintf(*result, vsize,
892 "%s%c%s",
893 attr,
894 lmap->ldap_attrsep,
895 vals[0]);
896 else
897 sm_strlcpy(*result, vals[0],
898 vsize);
899 ldap_value_free(vals);
900 ldap_memfree(attr);
901 break;
902 }
903
904 /* attributes only */
905 if (lmap->ldap_attrsonly == LDAPMAP_TRUE)
906 {
907 if (*result == NULL)
908 *result = sm_rpool_strdup_x(rpool,
909 attr);
910 else
911 {
912 if (bitset(SM_LDAP_SINGLEMATCH,
913 flags) &&
914 *result != NULL)
915 {
916 /* only wanted one match */
917 SM_LDAP_ERROR_CLEANUP();
918 errno = ENOENT;
919 return EX_NOTFOUND;
920 }
921
922 vsize = strlen(*result) +
923 strlen(attr) + 2;
924 tmp = sm_rpool_malloc_x(rpool,
925 vsize);
926 (void) sm_snprintf(tmp,
927 vsize, "%s%c%s",
928 *result, (char) delim,
929 attr);
930 *result = tmp;
931 }
932 ldap_memfree(attr);
933 continue;
934 }
935
936 /*
937 ** If there is more than one, munge then
938 ** into a map_coldelim separated string.
939 ** If we are recursing we may have an entry
940 ** with no 'normal' values to put in the
941 ** string.
942 ** This is not an error.
943 */
944
945 if (type == SM_LDAP_ATTR_NORMAL &&
946 bitset(SM_LDAP_SINGLEMATCH, flags) &&
947 *result != NULL)
948 {
949 /* only wanted one match */
950 SM_LDAP_ERROR_CLEANUP();
951 errno = ENOENT;
952 return EX_NOTFOUND;
953 }
954
955 vsize = 0;
956 for (i = 0; vals[i] != NULL; i++)
957 {
958 if (type == SM_LDAP_ATTR_DN ||
959 type == SM_LDAP_ATTR_FILTER ||
960 type == SM_LDAP_ATTR_URL)
961 {
962 /* add to recursion */
963 if (sm_ldap_add_recurse(&recurse,
964 vals[i],
965 type,
966 rpool) == NULL)
967 {
968 SM_LDAP_ERROR_CLEANUP();
969 errno = ENOMEM;
970 return EX_OSERR;
971 }
972 continue;
973 }
974
975 vsize += strlen(vals[i]) + 1;
976 if (lmap->ldap_attrsep != '\0')
977 vsize += strlen(attr) + 1;
978 }
979
980 /*
981 ** Create/Append to string any normal
982 ** attribute values. Otherwise, just free
983 ** memory and move on to the next
984 ** attribute in this entry.
985 */
986
987 if (type == SM_LDAP_ATTR_NORMAL && vsize > 0)
988 {
989 char *pe;
990
991 /* Grow result string if needed */
992 if ((*resultln + vsize) >= *resultsz)
993 {
994 while ((*resultln + vsize) >= *resultsz)
995 {
996 if (*resultsz == 0)
997 *resultsz = 1024;
998 else
999 *resultsz *= 2;
1000 }
1001
1002 vp_tmp = sm_rpool_malloc_x(rpool, *resultsz);
1003 *vp_tmp = '\0';
1004
1005 if (*result != NULL)
1006 sm_strlcpy(vp_tmp,
1007 *result,
1008 *resultsz);
1009 *result = vp_tmp;
1010 }
1011
1012 p = *result + *resultln;
1013 pe = *result + *resultsz;
1014
1015 for (i = 0; vals[i] != NULL; i++)
1016 {
1017 if (*resultln > 0 &&
1018 p < pe)
1019 *p++ = (char) delim;
1020
1021 if (lmap->ldap_attrsep != '\0')
1022 {
1023 p += sm_strlcpy(p, attr,
1024 pe - p);
1025 if (p < pe)
1026 *p++ = lmap->ldap_attrsep;
1027 }
1028
1029 p += sm_strlcpy(p, vals[i],
1030 pe - p);
1031 *resultln = p - (*result);
1032 if (p >= pe)
1033 {
1034 /* Internal error: buffer too small for LDAP values */
1035 SM_LDAP_ERROR_CLEANUP();
1036 errno = ENOMEM;
1037 return EX_OSERR;
1038 }
1039 }
1040 }
1041
1042 ldap_value_free(vals);
1043 ldap_memfree(attr);
1044 }
1045 save_errno = sm_ldap_geterrno(lmap->ldap_ld);
1046
1047 /*
1048 ** We check save_errno != LDAP_DECODING_ERROR since
1049 ** OpenLDAP 1.X has a very ugly *undocumented*
1050 ** hack of returning this error code from
1051 ** ldap_next_attribute() if the library freed the
1052 ** ber attribute. See:
1053 ** http://www.openldap.org/lists/openldap-devel/9901/msg00064.html
1054 */
1055
1056 if (save_errno != LDAP_SUCCESS &&
1057 save_errno != LDAP_DECODING_ERROR)
1058 {
1059 /* Must be an error */
1060 save_errno += E_LDAPBASE;
1061 SM_LDAP_ERROR_CLEANUP();
1062 errno = save_errno;
1063 return EX_TEMPFAIL;
1064 }
1065
1066 /* mark this DN as done */
1067 rl->lr_done = true;
1068 if (rl->lr_ludp != NULL)
1069 {
1070 ldap_free_urldesc(rl->lr_ludp);
1071 rl->lr_ludp = NULL;
1072 }
1073 if (rl->lr_attrs != NULL)
1074 {
1075 free(rl->lr_attrs);
1076 rl->lr_attrs = NULL;
1077 }
1078
1079 /* We don't want multiple values and we have one */
1080 if ((char) delim == '\0' &&
1081 !bitset(SM_LDAP_SINGLEMATCH, flags) &&
1082 *result != NULL)
1083 break;
1084 }
1085 save_errno = sm_ldap_geterrno(lmap->ldap_ld);
1086 if (save_errno != LDAP_SUCCESS &&
1087 save_errno != LDAP_DECODING_ERROR)
1088 {
1089 /* Must be an error */
1090 save_errno += E_LDAPBASE;
1091 SM_LDAP_ERROR_CLEANUP();
1092 errno = save_errno;
1093 return EX_TEMPFAIL;
1094 }
1095 ldap_msgfree(lmap->ldap_res);
1096 lmap->ldap_res = NULL;
1097 }
1098
1099 if (ret == 0)
1100 save_errno = ETIMEDOUT;
1101 else
1102 {
1103 int rc;
1104
1105 /*
1106 ** We may have gotten an LDAP_RES_SEARCH_RESULT response
1107 ** with an error inside it, so we have to extract that
1108 ** with ldap_parse_result(). This can happen when talking
1109 ** to an LDAP proxy whose backend has gone down.
1110 */
1111
1112 save_errno = ldap_parse_result(lmap->ldap_ld, lmap->ldap_res,
1113 &rc, NULL, NULL, NULL, NULL, 0);
1114 if (save_errno == LDAP_SUCCESS)
1115 save_errno = rc;
1116 }
1117 if (save_errno != LDAP_SUCCESS)
1118 {
1119 statp = EX_TEMPFAIL;
1120 switch (save_errno)
1121 {
1122 #ifdef LDAP_SERVER_DOWN
1123 case LDAP_SERVER_DOWN:
1124 #endif /* LDAP_SERVER_DOWN */
1125 case LDAP_TIMEOUT:
1126 case ETIMEDOUT:
1127 case LDAP_UNAVAILABLE:
1128
1129 /*
1130 ** server disappeared,
1131 ** try reopen on next search
1132 */
1133
1134 statp = EX_RESTART;
1135 break;
1136 }
1137 if (ret != 0)
1138 save_errno += E_LDAPBASE;
1139 SM_LDAP_ERROR_CLEANUP();
1140 errno = save_errno;
1141 return statp;
1142 }
1143
1144 if (lmap->ldap_res != NULL)
1145 {
1146 ldap_msgfree(lmap->ldap_res);
1147 lmap->ldap_res = NULL;
1148 }
1149
1150 if (toplevel)
1151 {
1152 int rlidx;
1153
1154 /*
1155 ** Spin through the built-up recurse list at the top
1156 ** of the recursion. Since new items are added at the
1157 ** end of the shared list, we actually only ever get
1158 ** one level of recursion before things pop back to the
1159 ** top. Any items added to the list during that recursion
1160 ** will be expanded by the top level.
1161 */
1162
1163 for (rlidx = 0; recurse != NULL && rlidx < recurse->lrl_cnt;
1164 rlidx++)
1165 {
1166 int newflags;
1167 int sid;
1168 int status;
1169
1170 rl = recurse->lrl_data[rlidx];
1171
1172 newflags = flags;
1173 if (rl->lr_done)
1174 {
1175 /* already expanded */
1176 continue;
1177 }
1178
1179 if (rl->lr_type == SM_LDAP_ATTR_DN)
1180 {
1181 /* do DN search */
1182 sid = ldap_search(lmap->ldap_ld,
1183 rl->lr_search,
1184 lmap->ldap_scope,
1185 "(objectClass=*)",
1186 (lmap->ldap_attr[0] == NULL ?
1187 NULL : lmap->ldap_attr),
1188 lmap->ldap_attrsonly);
1189 }
1190 else if (rl->lr_type == SM_LDAP_ATTR_FILTER)
1191 {
1192 /* do new search */
1193 sid = ldap_search(lmap->ldap_ld,
1194 lmap->ldap_base,
1195 lmap->ldap_scope,
1196 rl->lr_search,
1197 (lmap->ldap_attr[0] == NULL ?
1198 NULL : lmap->ldap_attr),
1199 lmap->ldap_attrsonly);
1200 }
1201 else if (rl->lr_type == SM_LDAP_ATTR_URL)
1202 {
1203 /* Parse URL */
1204 sid = ldap_url_parse(rl->lr_search,
1205 &rl->lr_ludp);
1206
1207 if (sid != 0)
1208 {
1209 errno = sid + E_LDAPURLBASE;
1210 return EX_TEMPFAIL;
1211 }
1212
1213 /* We need to add objectClass */
1214 if (rl->lr_ludp->lud_attrs != NULL)
1215 {
1216 int attrnum = 0;
1217
1218 while (rl->lr_ludp->lud_attrs[attrnum] != NULL)
1219 {
1220 if (strcasecmp(rl->lr_ludp->lud_attrs[attrnum],
1221 "objectClass") == 0)
1222 {
1223 /* already requested */
1224 attrnum = -1;
1225 break;
1226 }
1227 attrnum++;
1228 }
1229
1230 if (attrnum >= 0)
1231 {
1232 int i;
1233
1234 rl->lr_attrs = (char **)malloc(sizeof(char *) * (attrnum + 2));
1235 if (rl->lr_attrs == NULL)
1236 {
1237 save_errno = errno;
1238 ldap_free_urldesc(rl->lr_ludp);
1239 errno = save_errno;
1240 return EX_TEMPFAIL;
1241 }
1242 for (i = 0 ; i < attrnum; i++)
1243 {
1244 rl->lr_attrs[i] = rl->lr_ludp->lud_attrs[i];
1245 }
1246 rl->lr_attrs[i++] = "objectClass";
1247 rl->lr_attrs[i++] = NULL;
1248 }
1249 }
1250
1251 /*
1252 ** Use the existing connection
1253 ** for this search. It really
1254 ** should use lud_scheme://lud_host:lud_port/
1255 ** instead but that would require
1256 ** opening a new connection.
1257 ** This should be fixed ASAP.
1258 */
1259
1260 sid = ldap_search(lmap->ldap_ld,
1261 rl->lr_ludp->lud_dn,
1262 rl->lr_ludp->lud_scope,
1263 rl->lr_ludp->lud_filter,
1264 rl->lr_attrs,
1265 lmap->ldap_attrsonly);
1266
1267 /* Use the attributes specified by URL */
1268 newflags |= SM_LDAP_USE_ALLATTR;
1269 }
1270 else
1271 {
1272 /* unknown or illegal attribute type */
1273 errno = EFAULT;
1274 return EX_SOFTWARE;
1275 }
1276
1277 /* Collect results */
1278 if (sid == -1)
1279 {
1280 save_errno = sm_ldap_geterrno(lmap->ldap_ld);
1281 statp = EX_TEMPFAIL;
1282 switch (save_errno)
1283 {
1284 #ifdef LDAP_SERVER_DOWN
1285 case LDAP_SERVER_DOWN:
1286 #endif /* LDAP_SERVER_DOWN */
1287 case LDAP_TIMEOUT:
1288 case ETIMEDOUT:
1289 case LDAP_UNAVAILABLE:
1290
1291 /*
1292 ** server disappeared,
1293 ** try reopen on next search
1294 */
1295
1296 statp = EX_RESTART;
1297 break;
1298 }
1299 errno = save_errno + E_LDAPBASE;
1300 return statp;
1301 }
1302
1303 status = sm_ldap_results(lmap, sid, newflags, delim,
1304 rpool, result, resultln,
1305 resultsz, recurse);
1306 save_errno = errno;
1307 if (status != EX_OK && status != EX_NOTFOUND)
1308 {
1309 errno = save_errno;
1310 return status;
1311 }
1312
1313 /* Mark as done */
1314 rl->lr_done = true;
1315 if (rl->lr_ludp != NULL)
1316 {
1317 ldap_free_urldesc(rl->lr_ludp);
1318 rl->lr_ludp = NULL;
1319 }
1320 if (rl->lr_attrs != NULL)
1321 {
1322 free(rl->lr_attrs);
1323 rl->lr_attrs = NULL;
1324 }
1325
1326 /* Reset rlidx as new items may have been added */
1327 rlidx = -1;
1328 }
1329 }
1330 return statp;
1331 }
1332
1333 /*
1334 ** SM_LDAP_CLOSE -- close LDAP connection
1335 **
1336 ** Parameters:
1337 ** lmap -- LDAP map information
1338 **
1339 ** Returns:
1340 ** None.
1341 **
1342 */
1343
1344 void
sm_ldap_close(lmap)1345 sm_ldap_close(lmap)
1346 SM_LDAP_STRUCT *lmap;
1347 {
1348 if (lmap->ldap_ld == NULL)
1349 return;
1350
1351 if (lmap->ldap_pid == getpid())
1352 ldap_unbind(lmap->ldap_ld);
1353 lmap->ldap_ld = NULL;
1354 lmap->ldap_pid = 0;
1355 }
1356
1357 /*
1358 ** SM_LDAP_SETOPTS -- set LDAP options
1359 **
1360 ** Parameters:
1361 ** ld -- LDAP session handle
1362 ** lmap -- LDAP map information
1363 **
1364 ** Returns:
1365 ** None.
1366 **
1367 */
1368
1369 void
sm_ldap_setopts(ld,lmap)1370 sm_ldap_setopts(ld, lmap)
1371 LDAP *ld;
1372 SM_LDAP_STRUCT *lmap;
1373 {
1374 # if USE_LDAP_SET_OPTION
1375 if (lmap->ldap_version != 0)
1376 {
1377 ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION,
1378 &lmap->ldap_version);
1379 }
1380 ldap_set_option(ld, LDAP_OPT_DEREF, &lmap->ldap_deref);
1381 if (bitset(LDAP_OPT_REFERRALS, lmap->ldap_options))
1382 ldap_set_option(ld, LDAP_OPT_REFERRALS, LDAP_OPT_ON);
1383 else
1384 ldap_set_option(ld, LDAP_OPT_REFERRALS, LDAP_OPT_OFF);
1385 ldap_set_option(ld, LDAP_OPT_SIZELIMIT, &lmap->ldap_sizelimit);
1386 ldap_set_option(ld, LDAP_OPT_TIMELIMIT, &lmap->ldap_timelimit);
1387 # if _FFR_LDAP_NETWORK_TIMEOUT && defined(LDAP_OPT_NETWORK_TIMEOUT)
1388 if (lmap->ldap_networktmo > 0)
1389 {
1390 struct timeval tmo;
1391
1392 tmo.tv_sec = lmap->ldap_networktmo;
1393 tmo.tv_usec = 0;
1394 ldap_set_option(ld, LDAP_OPT_NETWORK_TIMEOUT, &tmo);
1395 }
1396 # endif /* _FFR_LDAP_NETWORK_TIMEOUT && defined(LDAP_OPT_NETWORK_TIMEOUT) */
1397 # ifdef LDAP_OPT_RESTART
1398 ldap_set_option(ld, LDAP_OPT_RESTART, LDAP_OPT_ON);
1399 # endif /* LDAP_OPT_RESTART */
1400 # else /* USE_LDAP_SET_OPTION */
1401 /* From here on in we can use ldap internal timelimits */
1402 ld->ld_deref = lmap->ldap_deref;
1403 ld->ld_options = lmap->ldap_options;
1404 ld->ld_sizelimit = lmap->ldap_sizelimit;
1405 ld->ld_timelimit = lmap->ldap_timelimit;
1406 # endif /* USE_LDAP_SET_OPTION */
1407 }
1408
1409 /*
1410 ** SM_LDAP_GETERRNO -- get ldap errno value
1411 **
1412 ** Parameters:
1413 ** ld -- LDAP session handle
1414 **
1415 ** Returns:
1416 ** LDAP errno.
1417 **
1418 */
1419
1420 int
sm_ldap_geterrno(ld)1421 sm_ldap_geterrno(ld)
1422 LDAP *ld;
1423 {
1424 int err = LDAP_SUCCESS;
1425
1426 # if defined(LDAP_VERSION_MAX) && LDAP_VERSION_MAX >= 3
1427 (void) ldap_get_option(ld, LDAP_OPT_ERROR_NUMBER, &err);
1428 # else /* defined(LDAP_VERSION_MAX) && LDAP_VERSION_MAX >= 3 */
1429 # ifdef LDAP_OPT_SIZELIMIT
1430 err = ldap_get_lderrno(ld, NULL, NULL);
1431 # else /* LDAP_OPT_SIZELIMIT */
1432 err = ld->ld_errno;
1433
1434 /*
1435 ** Reset value to prevent lingering LDAP_DECODING_ERROR due to
1436 ** OpenLDAP 1.X's hack (see above)
1437 */
1438
1439 ld->ld_errno = LDAP_SUCCESS;
1440 # endif /* LDAP_OPT_SIZELIMIT */
1441 # endif /* defined(LDAP_VERSION_MAX) && LDAP_VERSION_MAX >= 3 */
1442 return err;
1443 }
1444 # endif /* LDAPMAP */
1445