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