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