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