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