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