xref: /illumos-gate/usr/src/cmd/sendmail/libsm/ldap.c (revision 22028508fd28d36ff74dc02c5774a8ba1f0db045)
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
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
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
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
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
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
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 *
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
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
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
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
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