xref: /freebsd/contrib/sendmail/libsm/ldap.c (revision eacee0ff7ec955b32e09515246bd97b6edcd2b0f)
1 /*
2  * Copyright (c) 2001-2002 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 #include <sm/gen.h>
11 SM_RCSID("@(#)$Id: ldap.c,v 1.18 2002/01/11 22:06:51 gshapiro Exp $")
12 
13 #if LDAPMAP
14 # include <sys/types.h>
15 # include <errno.h>
16 # include <setjmp.h>
17 # include <stdlib.h>
18 # include <unistd.h>
19 
20 # include <sm/bitops.h>
21 # include <sm/clock.h>
22 # include <sm/conf.h>
23 # include <sm/debug.h>
24 # include <sm/errstring.h>
25 # include <sm/ldap.h>
26 # include <sm/string.h>
27 # include <sm/sysexits.h>
28 
29 SM_DEBUG_T SmLDAPTrace = SM_DEBUG_INITIALIZER("sm_trace_ldap",
30 	"@(#)$Debug: sm_trace_ldap - trace LDAP operations $");
31 
32 static void	ldaptimeout __P((int));
33 
34 /*
35 **  SM_LDAP_CLEAR -- set default values for SM_LDAP_STRUCT
36 **
37 **	Parameters:
38 **		lmap -- pointer to SM_LDAP_STRUCT to clear
39 **
40 **	Returns:
41 **		None.
42 **
43 */
44 
45 void
46 sm_ldap_clear(lmap)
47 	SM_LDAP_STRUCT *lmap;
48 {
49 	if (lmap == NULL)
50 		return;
51 
52 	lmap->ldap_host = NULL;
53 	lmap->ldap_port = LDAP_PORT;
54 	lmap->ldap_deref = LDAP_DEREF_NEVER;
55 	lmap->ldap_timelimit = LDAP_NO_LIMIT;
56 	lmap->ldap_sizelimit = LDAP_NO_LIMIT;
57 # ifdef LDAP_REFERRALS
58 	lmap->ldap_options = LDAP_OPT_REFERRALS;
59 # else /* LDAP_REFERRALS */
60 	lmap->ldap_options = 0;
61 # endif /* LDAP_REFERRALS */
62 	lmap->ldap_attrsep = '\0';
63 	lmap->ldap_binddn = NULL;
64 	lmap->ldap_secret = NULL;
65 	lmap->ldap_method = LDAP_AUTH_SIMPLE;
66 	lmap->ldap_base = NULL;
67 	lmap->ldap_scope = LDAP_SCOPE_SUBTREE;
68 	lmap->ldap_attrsonly = LDAPMAP_FALSE;
69 	lmap->ldap_timeout.tv_sec = 0;
70 	lmap->ldap_timeout.tv_usec = 0;
71 	lmap->ldap_ld = NULL;
72 	lmap->ldap_filter = NULL;
73 	lmap->ldap_attr[0] = NULL;
74 #if _FFR_LDAP_RECURSION
75 	lmap->ldap_attr_type[0] = LDAPMAP_ATTR_NORMAL;
76 	lmap->ldap_attr_final[0] = NULL;
77 #endif /* _FFR_LDAP_RECURSION */
78 	lmap->ldap_res = NULL;
79 	lmap->ldap_next = NULL;
80 	lmap->ldap_pid = 0;
81 }
82 
83 /*
84 **  SM_LDAP_START -- actually connect to an LDAP server
85 **
86 **	Parameters:
87 **		name -- name of map for debug output.
88 **		lmap -- the LDAP map being opened.
89 **
90 **	Returns:
91 **		true if connection is successful, false otherwise.
92 **
93 **	Side Effects:
94 **		Populates lmap->ldap_ld.
95 */
96 
97 static jmp_buf	LDAPTimeout;
98 
99 #define SM_LDAP_SETTIMEOUT(to)						\
100 do									\
101 {									\
102 	if (to != 0)							\
103 	{								\
104 		if (setjmp(LDAPTimeout) != 0)				\
105 		{							\
106 			errno = ETIMEDOUT;				\
107 			return false;					\
108 		}							\
109 		ev = sm_setevent(to, ldaptimeout, 0);			\
110 	}								\
111 } while (0)
112 
113 #define SM_LDAP_CLEARTIMEOUT()						\
114 do									\
115 {									\
116 	if (ev != NULL)							\
117 		sm_clrevent(ev);					\
118 } while (0)
119 
120 bool
121 sm_ldap_start(name, lmap)
122 	char *name;
123 	SM_LDAP_STRUCT *lmap;
124 {
125 	int bind_result;
126 	int save_errno;
127 	SM_EVENT *ev = NULL;
128 	LDAP *ld;
129 
130 	if (sm_debug_active(&SmLDAPTrace, 2))
131 		sm_dprintf("ldapmap_start(%s)\n", name == NULL ? "" : name);
132 
133 	if (sm_debug_active(&SmLDAPTrace, 9))
134 		sm_dprintf("ldapmap_start(%s, %d)\n",
135 			   lmap->ldap_host == NULL ? "localhost" : lmap->ldap_host,
136 			   lmap->ldap_port);
137 
138 # if USE_LDAP_INIT
139 	ld = ldap_init(lmap->ldap_host, lmap->ldap_port);
140 	save_errno = errno;
141 # else /* USE_LDAP_INIT */
142 	/*
143 	**  If using ldap_open(), the actual connection to the server
144 	**  happens now so we need the timeout here.  For ldap_init(),
145 	**  the connection happens at bind time.
146 	*/
147 
148 	SM_LDAP_SETTIMEOUT(lmap->ldap_timeout.tv_sec);
149 	ld = ldap_open(lmap->ldap_host, lmap->ldap_port);
150 	save_errno = errno;
151 
152 	/* clear the event if it has not sprung */
153 	SM_LDAP_CLEARTIMEOUT();
154 # endif /* USE_LDAP_INIT */
155 
156 	errno = save_errno;
157 	if (ld == NULL)
158 		return false;
159 
160 	sm_ldap_setopts(ld, lmap);
161 
162 # if USE_LDAP_INIT
163 	/*
164 	**  If using ldap_init(), the actual connection to the server
165 	**  happens at ldap_bind_s() so we need the timeout here.
166 	*/
167 
168 	SM_LDAP_SETTIMEOUT(lmap->ldap_timeout.tv_sec);
169 # endif /* USE_LDAP_INIT */
170 
171 # ifdef LDAP_AUTH_KRBV4
172 	if (lmap->ldap_method == LDAP_AUTH_KRBV4 &&
173 	    lmap->ldap_secret != NULL)
174 	{
175 		/*
176 		**  Need to put ticket in environment here instead of
177 		**  during parseargs as there may be different tickets
178 		**  for different LDAP connections.
179 		*/
180 
181 		(void) putenv(lmap->ldap_secret);
182 	}
183 # endif /* LDAP_AUTH_KRBV4 */
184 
185 	bind_result = ldap_bind_s(ld, lmap->ldap_binddn,
186 				  lmap->ldap_secret, lmap->ldap_method);
187 
188 # if USE_LDAP_INIT
189 	/* clear the event if it has not sprung */
190 	SM_LDAP_CLEARTIMEOUT();
191 # endif /* USE_LDAP_INIT */
192 
193 	if (bind_result != LDAP_SUCCESS)
194 	{
195 		errno = bind_result + E_LDAPBASE;
196 		return false;
197 	}
198 
199 	/* Save PID to make sure only this PID closes the LDAP connection */
200 	lmap->ldap_pid = getpid();
201 	lmap->ldap_ld = ld;
202 	return true;
203 }
204 
205 /* ARGSUSED */
206 static void
207 ldaptimeout(unused)
208 	int unused;
209 {
210 	/*
211 	**  NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER.  DO NOT ADD
212 	**	ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
213 	**	DOING.
214 	*/
215 
216 	errno = ETIMEDOUT;
217 	longjmp(LDAPTimeout, 1);
218 }
219 
220 /*
221 **  SM_LDAP_SEARCH -- iniate LDAP search
222 **
223 **	Initiate an LDAP search, return the msgid.
224 **	The calling function must collect the results.
225 **
226 **	Parameters:
227 **		lmap -- LDAP map information
228 **		key -- key to substitute in LDAP filter
229 **
230 **	Returns:
231 **		-1 on failure, msgid on success
232 **
233 */
234 
235 int
236 sm_ldap_search(lmap, key)
237 	SM_LDAP_STRUCT *lmap;
238 	char *key;
239 {
240 	int msgid;
241 	char *fp, *p, *q;
242 	char filter[LDAPMAP_MAX_FILTER + 1];
243 
244 	/* substitute key into filter, perhaps multiple times */
245 	memset(filter, '\0', sizeof filter);
246 	fp = filter;
247 	p = lmap->ldap_filter;
248 	while ((q = strchr(p, '%')) != NULL)
249 	{
250 		if (q[1] == 's')
251 		{
252 			(void) sm_snprintf(fp, SPACELEFT(filter, fp),
253 					   "%.*s%s", (int) (q - p), p, key);
254 			fp += strlen(fp);
255 			p = q + 2;
256 		}
257 		else if (q[1] == '0')
258 		{
259 			char *k = key;
260 
261 			(void) sm_snprintf(fp, SPACELEFT(filter, fp),
262 					   "%.*s", (int) (q - p), p);
263 			fp += strlen(fp);
264 			p = q + 2;
265 
266 			/* Properly escape LDAP special characters */
267 			while (SPACELEFT(filter, fp) > 0 &&
268 			       *k != '\0')
269 			{
270 				if (*k == '*' || *k == '(' ||
271 				    *k == ')' || *k == '\\')
272 				{
273 					(void) sm_strlcat(fp,
274 						       (*k == '*' ? "\\2A" :
275 							(*k == '(' ? "\\28" :
276 							 (*k == ')' ? "\\29" :
277 							  (*k == '\\' ? "\\5C" :
278 							   "\00")))),
279 						SPACELEFT(filter, fp));
280 					fp += strlen(fp);
281 					k++;
282 				}
283 				else
284 					*fp++ = *k++;
285 			}
286 		}
287 		else
288 		{
289 			(void) sm_snprintf(fp, SPACELEFT(filter, fp),
290 				"%.*s", (int) (q - p + 1), p);
291 			p = q + (q[1] == '%' ? 2 : 1);
292 			fp += strlen(fp);
293 		}
294 	}
295 	(void) sm_strlcpy(fp, p, SPACELEFT(filter, fp));
296 	if (sm_debug_active(&SmLDAPTrace, 20))
297 		sm_dprintf("ldap search filter=%s\n", filter);
298 
299 	lmap->ldap_res = NULL;
300 	msgid = ldap_search(lmap->ldap_ld, lmap->ldap_base, lmap->ldap_scope,
301 			    filter,
302 			    (lmap->ldap_attr[0] == NULL ? NULL :
303 			     lmap->ldap_attr),
304 			    lmap->ldap_attrsonly);
305 	return msgid;
306 }
307 
308 # if _FFR_LDAP_RECURSION
309 /*
310 **  SM_LDAP_RESULTS -- return results from an LDAP lookup in result
311 **
312 **	Parameters:
313 **		lmap -- pointer to SM_LDAP_STRUCT in use
314 **		msgid -- msgid returned by sm_ldap_search()
315 **		flags -- flags for the lookup
316 **		delim -- delimiter for result concatenation
317 **		rpool -- memory pool for storage
318 **		result -- return string
319 **		recurse -- recursion list
320 **
321 **	Returns:
322 **		status (sysexit)
323 */
324 
325 # define LDAPMAP_ERROR_CLEANUP()				\
326 {								\
327 	if (lmap->ldap_res != NULL)				\
328 	{							\
329 		ldap_msgfree(lmap->ldap_res);			\
330 		lmap->ldap_res = NULL;				\
331 	}							\
332 	(void) ldap_abandon(lmap->ldap_ld, msgid);		\
333 }
334 
335 static int
336 ldapmap_add_recurse(top, item, type, rpool)
337 	SM_LDAP_RECURSE_LIST **top;
338 	char *item;
339 	int type;
340 	SM_RPOOL_T *rpool;
341 {
342 	SM_LDAP_RECURSE_LIST *p;
343 	SM_LDAP_RECURSE_LIST *last;
344 
345 	last = NULL;
346 	for (p = *top; p != NULL; p = p->lr_next)
347 	{
348 		if (strcasecmp(item, p->lr_search) == 0 &&
349 		    type == p->lr_type)
350 		{
351 			/* already on list */
352 			return 1;
353 		}
354 		last = p;
355 	}
356 
357 	/* not on list, add it */
358 	p = sm_rpool_malloc_x(rpool, sizeof *p);
359 	p->lr_search = sm_rpool_strdup_x(rpool, item);
360 	p->lr_type = type;
361 	p->lr_next = NULL;
362 	if (last == NULL)
363 		*top = p;
364 	else
365 		last->lr_next = p;
366 	return 0;
367 }
368 
369 int
370 sm_ldap_results(lmap, msgid, flags, delim, rpool, result, recurse)
371 	SM_LDAP_STRUCT *lmap;
372 	int msgid;
373 	int flags;
374 	char delim;
375 	SM_RPOOL_T *rpool;
376 	char **result;
377 	SM_LDAP_RECURSE_LIST *recurse;
378 {
379 	bool toplevel;
380 	int i;
381 	int entries = 0;
382 	int statp;
383 	int vsize;
384 	int ret;
385 	int save_errno;
386 	char *p;
387 
388 	/* Are we the top top level of the search? */
389 	toplevel = (recurse == NULL);
390 
391 	/* Get results */
392 	statp = EX_NOTFOUND;
393 	while ((ret = ldap_result(lmap->ldap_ld, msgid, 0,
394 				  (lmap->ldap_timeout.tv_sec == 0 ? NULL :
395 				   &(lmap->ldap_timeout)),
396 				  &(lmap->ldap_res))) == LDAP_RES_SEARCH_ENTRY)
397 	{
398 		LDAPMessage *entry;
399 
400 		if (bitset(SM_LDAP_SINGLEMATCH, flags))
401 		{
402 			entries += ldap_count_entries(lmap->ldap_ld,
403 						      lmap->ldap_res);
404 			if (entries > 1)
405 			{
406 				LDAPMAP_ERROR_CLEANUP();
407 				errno = ENOENT;
408 				return EX_NOTFOUND;
409 			}
410 		}
411 
412 		/* If we don't want multiple values and we have one, break */
413 		if (delim == '\0' && *result != NULL)
414 			break;
415 
416 		/* Cycle through all entries */
417 		for (entry = ldap_first_entry(lmap->ldap_ld, lmap->ldap_res);
418 		     entry != NULL;
419 		     entry = ldap_next_entry(lmap->ldap_ld, lmap->ldap_res))
420 		{
421 			BerElement *ber;
422 			char *attr;
423 			char **vals = NULL;
424 			char *dn;
425 
426 			/*
427 			**  If matching only and found an entry,
428 			**  no need to spin through attributes
429 			*/
430 
431 			if (statp == EX_OK &&
432 			    bitset(SM_LDAP_MATCHONLY, flags))
433 				continue;
434 
435 			/* record completed DN's to prevent loops */
436 			dn = ldap_get_dn(lmap->ldap_ld, entry);
437 			if (dn == NULL)
438 			{
439 				save_errno = sm_ldap_geterrno(lmap->ldap_ld);
440 				save_errno += E_LDAPBASE;
441 				LDAPMAP_ERROR_CLEANUP();
442 				errno = save_errno;
443 				return EX_OSERR;
444 			}
445 
446 			switch (ldapmap_add_recurse(&recurse, dn,
447 						    LDAPMAP_ATTR_NORMAL,
448 						    rpool))
449 			{
450 			  case -1:
451 				/* error adding */
452 				ldap_memfree(dn);
453 				LDAPMAP_ERROR_CLEANUP();
454 				errno = ENOMEM;
455 				return EX_OSERR;
456 
457 			  case 1:
458 				/* already on list, skip it */
459 				ldap_memfree(dn);
460 				continue;
461 			}
462 			ldap_memfree(dn);
463 
464 # if !defined(LDAP_VERSION_MAX) && !defined(LDAP_OPT_SIZELIMIT)
465 			/*
466 			**  Reset value to prevent lingering
467 			**  LDAP_DECODING_ERROR due to
468 			**  OpenLDAP 1.X's hack (see below)
469 			*/
470 
471 			lmap->ldap_ld->ld_errno = LDAP_SUCCESS;
472 # endif /* !defined(LDAP_VERSION_MAX) !defined(LDAP_OPT_SIZELIMIT) */
473 
474 			for (attr = ldap_first_attribute(lmap->ldap_ld, entry,
475 							 &ber);
476 			     attr != NULL;
477 			     attr = ldap_next_attribute(lmap->ldap_ld, entry,
478 							ber))
479 			{
480 				char *tmp, *vp_tmp;
481 				int type;
482 
483 				for (i = 0; lmap->ldap_attr[i] != NULL; i++)
484 				{
485 					if (sm_strcasecmp(lmap->ldap_attr[i],
486 							  attr) == 0)
487 					{
488 						type = lmap->ldap_attr_type[i];
489 						break;
490 					}
491 				}
492 				if (lmap->ldap_attr[i] == NULL)
493 				{
494 					/* attribute not requested */
495 # if USING_NETSCAPE_LDAP
496 					ldap_memfree(attr);
497 # endif /* USING_NETSCAPE_LDAP */
498 					LDAPMAP_ERROR_CLEANUP();
499 					errno = EFAULT;
500 					return EX_SOFTWARE;
501 				}
502 
503 				if (lmap->ldap_attrsonly == LDAPMAP_FALSE)
504 				{
505 					vals = ldap_get_values(lmap->ldap_ld,
506 							       entry,
507 							       attr);
508 					if (vals == NULL)
509 					{
510 						save_errno = sm_ldap_geterrno(lmap->ldap_ld);
511 						if (save_errno == LDAP_SUCCESS)
512 						{
513 # if USING_NETSCAPE_LDAP
514 							ldap_memfree(attr);
515 # endif /* USING_NETSCAPE_LDAP */
516 							continue;
517 						}
518 
519 						/* Must be an error */
520 						save_errno += E_LDAPBASE;
521 # if USING_NETSCAPE_LDAP
522 						ldap_memfree(attr);
523 # endif /* USING_NETSCAPE_LDAP */
524 						LDAPMAP_ERROR_CLEANUP();
525 						errno = save_errno;
526 						return EX_TEMPFAIL;
527 					}
528 				}
529 
530 				statp = EX_OK;
531 
532 # if !defined(LDAP_VERSION_MAX) && !defined(LDAP_OPT_SIZELIMIT)
533 				/*
534 				**  Reset value to prevent lingering
535 				**  LDAP_DECODING_ERROR due to
536 				**  OpenLDAP 1.X's hack (see below)
537 				*/
538 
539 				lmap->ldap_ld->ld_errno = LDAP_SUCCESS;
540 # endif /* !defined(LDAP_VERSION_MAX) !defined(LDAP_OPT_SIZELIMIT) */
541 
542 				/*
543 				**  If matching only,
544 				**  no need to spin through entries
545 				*/
546 
547 				if (bitset(SM_LDAP_MATCHONLY, flags))
548 				{
549 					if (lmap->ldap_attrsonly == LDAPMAP_FALSE)
550 						ldap_value_free(vals);
551 
552 # if USING_NETSCAPE_LDAP
553 					ldap_memfree(attr);
554 # endif /* USING_NETSCAPE_LDAP */
555 					continue;
556 				}
557 
558 				/*
559 				**  If we don't want multiple values,
560 				**  return first found.
561 				*/
562 
563 				if (delim == '\0')
564 				{
565 					if (lmap->ldap_attrsonly == LDAPMAP_TRUE)
566 					{
567 						*result = sm_rpool_strdup_x(rpool,
568 									    attr);
569 # if USING_NETSCAPE_LDAP
570 						ldap_memfree(attr);
571 # endif /* USING_NETSCAPE_LDAP */
572 						break;
573 					}
574 
575 					if (vals[0] == NULL)
576 					{
577 						ldap_value_free(vals);
578 # if USING_NETSCAPE_LDAP
579 						ldap_memfree(attr);
580 # endif /* USING_NETSCAPE_LDAP */
581 						continue;
582 					}
583 
584 					vsize = strlen(vals[0]) + 1;
585 					if (lmap->ldap_attrsep != '\0')
586 						vsize += strlen(attr) + 1;
587 					*result = sm_rpool_malloc_x(rpool,
588 								    vsize);
589 					if (lmap->ldap_attrsep != '\0')
590 						sm_snprintf(*result, vsize,
591 							    "%s%c%s",
592 							    attr,
593 							    lmap->ldap_attrsep,
594 							    vals[0]);
595 					else
596 						sm_strlcpy(*result, vals[0],
597 							   vsize);
598 					ldap_value_free(vals);
599 # if USING_NETSCAPE_LDAP
600 					ldap_memfree(attr);
601 # endif /* USING_NETSCAPE_LDAP */
602 					break;
603 				}
604 
605 				/* attributes only */
606 				if (lmap->ldap_attrsonly == LDAPMAP_TRUE)
607 				{
608 					if (*result == NULL)
609 						*result = sm_rpool_strdup_x(rpool,
610 									    attr);
611 					else
612 					{
613 						vsize = strlen(*result) +
614 							strlen(attr) + 2;
615 						tmp = sm_rpool_malloc_x(rpool,
616 									vsize);
617 						(void) sm_snprintf(tmp,
618 							vsize, "%s%c%s",
619 							*result, delim,
620 							attr);
621 						*result = tmp;
622 					}
623 # if USING_NETSCAPE_LDAP
624 					ldap_memfree(attr);
625 # endif /* USING_NETSCAPE_LDAP */
626 					continue;
627 				}
628 
629 				/*
630 				**  If there is more than one,
631 				**  munge then into a map_coldelim
632 				**  separated string
633 				*/
634 
635 				vsize = 0;
636 				for (i = 0; vals[i] != NULL; i++)
637 				{
638 					if (type == LDAPMAP_ATTR_DN ||
639 					    type == LDAPMAP_ATTR_FILTER ||
640 					    type == LDAPMAP_ATTR_URL)
641 					{
642 						if (ldapmap_add_recurse(&recurse,
643 									vals[i],
644 									type) < 0)
645 						{
646 							LDAPMAP_ERROR_CLEANUP();
647 							errno = ENOMEM;
648 							return EX_OSERR;
649 						}
650 					}
651 					if (type != LDAPMAP_ATTR_NORMAL)
652 					{
653 #  if USING_NETSCAPE_LDAP
654 						ldap_memfree(attr);
655 #  endif /* USING_NETSCAPE_LDAP */
656 						continue;
657 					}
658 					vsize += strlen(vals[i]) + 1;
659 					if (lmap->ldap_attrsep != '\0')
660 						vsize += strlen(attr) + 1;
661 				}
662 				vp_tmp = sm_rpool_malloc_x(rpool, vsize);
663 				*vp_tmp = '\0';
664 
665 				p = vp_tmp;
666 				for (i = 0; vals[i] != NULL; i++)
667 				{
668 					if (lmap->ldap_attrsep != '\0')
669 					{
670 						p += sm_strlcpy(p, attr,
671 								vsize - (p - vp_tmp));
672 						*p++ = lmap->ldap_attrsep;
673 					}
674 					p += sm_strlcpy(p, vals[i],
675 							vsize - (p - vp_tmp));
676 					if (p >= vp_tmp + vsize)
677 					{
678 						/* Internal error: buffer too small for LDAP values */
679 						LDAPMAP_ERROR_CLEANUP();
680 						errno = ENOMEM;
681 						return EX_OSERR;
682 					}
683 					if (vals[i + 1] != NULL)
684 						*p++ = delim;
685 				}
686 
687 				ldap_value_free(vals);
688 # if USING_NETSCAPE_LDAP
689 				ldap_memfree(attr);
690 # endif /* USING_NETSCAPE_LDAP */
691 				if (*result == NULL)
692 				{
693 					*result = vp_tmp;
694 					continue;
695 				}
696 				vsize = strlen(*result) + strlen(vp_tmp) + 2;
697 				tmp = sm_rpool_malloc_x(rpool, vsize);
698 				(void) sm_snprintf(tmp, vsize, "%s%c%s",
699 						   *result, delim, vp_tmp);
700 				*result = tmp;
701 			}
702 			save_errno = sm_ldap_geterrno(lmap->ldap_ld);
703 
704 			/*
705 			**  We check save_errno != LDAP_DECODING_ERROR since
706 			**  OpenLDAP 1.X has a very ugly *undocumented*
707 			**  hack of returning this error code from
708 			**  ldap_next_attribute() if the library freed the
709 			**  ber attribute.  See:
710 			**  http://www.openldap.org/lists/openldap-devel/9901/msg00064.html
711 			*/
712 
713 			if (save_errno != LDAP_SUCCESS &&
714 			    save_errno != LDAP_DECODING_ERROR)
715 			{
716 				/* Must be an error */
717 				save_errno += E_LDAPBASE;
718 				LDAPMAP_ERROR_CLEANUP();
719 				errno = save_errno;
720 				return EX_TEMPFAIL;
721 			}
722 
723 			/* We don't want multiple values and we have one */
724 			if (delim == '\0' && *result != NULL)
725 				break;
726 		}
727 		save_errno = sm_ldap_geterrno(lmap->ldap_ld);
728 		if (save_errno != LDAP_SUCCESS &&
729 		    save_errno != LDAP_DECODING_ERROR)
730 		{
731 			/* Must be an error */
732 			save_errno += E_LDAPBASE;
733 			LDAPMAP_ERROR_CLEANUP();
734 			errno = save_errno;
735 			return EX_TEMPFAIL;
736 		}
737 		ldap_msgfree(lmap->ldap_res);
738 		lmap->ldap_res = NULL;
739 	}
740 
741 	if (ret == 0)
742 		save_errno = ETIMEDOUT;
743 	else
744 		save_errno = sm_ldap_geterrno(lmap->ldap_ld);
745 	if (save_errno != LDAP_SUCCESS)
746 	{
747 		statp = EX_TEMPFAIL;
748 		if (ret != 0)
749 		{
750 			switch (save_errno)
751 			{
752 #ifdef LDAP_SERVER_DOWN
753 			  case LDAP_SERVER_DOWN:
754 #endif /* LDAP_SERVER_DOWN */
755 			  case LDAP_TIMEOUT:
756 			  case LDAP_UNAVAILABLE:
757 				/* server disappeared, try reopen on next search */
758 				statp = EX_RESTART;
759 				break;
760 			}
761 			save_errno += E_LDAPBASE;
762 		}
763 		LDAPMAP_ERROR_CLEANUP();
764 		errno = save_errno;
765 		return statp;
766 	}
767 
768 	if (lmap->ldap_res != NULL)
769 	{
770 		ldap_msgfree(lmap->ldap_res);
771 		lmap->ldap_res = NULL;
772 	}
773 
774 	if (toplevel)
775 	{
776 		SM_LDAP_RECURSE_LIST *rl;
777 
778 		/*
779 		**  Spin through the built-up recurse list at the top
780 		**  of the recursion.  Since new items are added at the
781 		**  end of the shared list, we actually only ever get
782 		**  one level of recursion before things pop back to the
783 		**  top.  Any items added to the list during that recursion
784 		**  will be expanded by the top level.
785 		*/
786 
787 		for (rl = recurse; rl != NULL; rl = rl->lr_next)
788 		{
789 			int sid;
790 			int status;
791 
792 			if (rl->lr_type == LDAPMAP_ATTR_NORMAL)
793 			{
794 				/* already expanded */
795 				continue;
796 			}
797 			else if (rl->lr_type == LDAPMAP_ATTR_DN)
798 			{
799 				/* do DN search */
800 				sid = ldap_search(lmap->ldap_ld,
801 						  rl->lr_search,
802 						  lmap->ldap_scope,
803 						  "(objectClass=*)",
804 						  lmap->ldap_attr_final,
805 						  lmap->ldap_attrsonly);
806 			}
807 			else if (rl->lr_type == LDAPMAP_ATTR_FILTER)
808 			{
809 				/* do new search */
810 				sid = ldap_search(lmap->ldap_ld,
811 						  lmap->ldap_base,
812 						  lmap->ldap_scope,
813 						  rl->lr_search,
814 						  lmap->ldap_attr_final,
815 						  lmap->ldap_attrsonly);
816 			}
817 			else if (rl->lr_type == LDAPMAP_ATTR_URL)
818 			{
819 				/* do new URL search */
820 				sid = ldap_url_search(lmap->ldap_ld,
821 						      rl->lr_search,
822 						      lmap->ldap_attrsonly);
823 			}
824 			else
825 			{
826 				/* unknown or illegal attribute type */
827 				errno = EFAULT;
828 				return EX_SOFTWARE;
829 			}
830 
831 			/* Collect results */
832 			if (sid == -1)
833 			{
834 				save_errno = sm_ldap_geterrno(lmap->ldap_ld);
835 				statp = EX_TEMPFAIL;
836 				switch (save_errno)
837 				{
838 #ifdef LDAP_SERVER_DOWN
839 				  case LDAP_SERVER_DOWN:
840 #endif /* LDAP_SERVER_DOWN */
841 				  case LDAP_TIMEOUT:
842 				  case LDAP_UNAVAILABLE:
843 					/* server disappeared, try reopen on next search */
844 					statp = EX_RESTART;
845 					break;
846 				}
847 				errno = save_errno + E_LDAPBASE;
848 				return statp;
849 			}
850 
851 			status = sm_ldap_results(lmap, sid, flags, delim,
852 						 rpool, result, recurse);
853 			save_errno = errno;
854 			if (status != EX_OK && status != EX_NOTFOUND)
855 			{
856 				errno = save_errno;
857 				return status;
858 			}
859 
860 			/* Mark as done */
861 			rl->lr_type = LDAPMAP_ATTR_NORMAL;
862 		}
863 	}
864 	return statp;
865 }
866 #endif /* _FFR_LDAP_RECURSION */
867 
868 /*
869 **  SM_LDAP_CLOSE -- close LDAP connection
870 **
871 **	Parameters:
872 **		lmap -- LDAP map information
873 **
874 **	Returns:
875 **		None.
876 **
877 */
878 
879 void
880 sm_ldap_close(lmap)
881 	SM_LDAP_STRUCT *lmap;
882 {
883 	if (lmap->ldap_ld == NULL)
884 		return;
885 
886 	if (lmap->ldap_pid == getpid())
887 		ldap_unbind(lmap->ldap_ld);
888 	lmap->ldap_ld = NULL;
889 	lmap->ldap_pid = 0;
890 }
891 
892 /*
893 **  SM_LDAP_SETOPTS -- set LDAP options
894 **
895 **	Parameters:
896 **		ld -- LDAP session handle
897 **		lmap -- LDAP map information
898 **
899 **	Returns:
900 **		None.
901 **
902 */
903 
904 void
905 sm_ldap_setopts(ld, lmap)
906 	LDAP *ld;
907 	SM_LDAP_STRUCT *lmap;
908 {
909 # if USE_LDAP_SET_OPTION
910 	ldap_set_option(ld, LDAP_OPT_DEREF, &lmap->ldap_deref);
911 	if (bitset(LDAP_OPT_REFERRALS, lmap->ldap_options))
912 		ldap_set_option(ld, LDAP_OPT_REFERRALS, LDAP_OPT_ON);
913 	else
914 		ldap_set_option(ld, LDAP_OPT_REFERRALS, LDAP_OPT_OFF);
915 	ldap_set_option(ld, LDAP_OPT_SIZELIMIT, &lmap->ldap_sizelimit);
916 	ldap_set_option(ld, LDAP_OPT_TIMELIMIT, &lmap->ldap_timelimit);
917 # else /* USE_LDAP_SET_OPTION */
918 	/* From here on in we can use ldap internal timelimits */
919 	ld->ld_deref = lmap->ldap_deref;
920 	ld->ld_options = lmap->ldap_options;
921 	ld->ld_sizelimit = lmap->ldap_sizelimit;
922 	ld->ld_timelimit = lmap->ldap_timelimit;
923 # endif /* USE_LDAP_SET_OPTION */
924 }
925 
926 /*
927 **  SM_LDAP_GETERRNO -- get ldap errno value
928 **
929 **	Parameters:
930 **		ld -- LDAP session handle
931 **
932 **	Returns:
933 **		LDAP errno.
934 **
935 */
936 
937 int
938 sm_ldap_geterrno(ld)
939 	LDAP *ld;
940 {
941 	int err = LDAP_SUCCESS;
942 
943 # if defined(LDAP_VERSION_MAX) && LDAP_VERSION_MAX >= 3
944 	(void) ldap_get_option(ld, LDAP_OPT_ERROR_NUMBER, &err);
945 # else /* defined(LDAP_VERSION_MAX) && LDAP_VERSION_MAX >= 3 */
946 #  ifdef LDAP_OPT_SIZELIMIT
947 	err = ldap_get_lderrno(ld, NULL, NULL);
948 #  else /* LDAP_OPT_SIZELIMIT */
949 	err = ld->ld_errno;
950 
951 	/*
952 	**  Reset value to prevent lingering LDAP_DECODING_ERROR due to
953 	**  OpenLDAP 1.X's hack (see above)
954 	*/
955 
956 	ld->ld_errno = LDAP_SUCCESS;
957 #  endif /* LDAP_OPT_SIZELIMIT */
958 # endif /* defined(LDAP_VERSION_MAX) && LDAP_VERSION_MAX >= 3 */
959 	return err;
960 }
961 # endif /* LDAPMAP */
962