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