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