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