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