map.c revision 82017
1/*
2 * Copyright (c) 1998-2001 Sendmail, Inc. and its suppliers.
3 *	All rights reserved.
4 * Copyright (c) 1992, 1995-1997 Eric P. Allman.  All rights reserved.
5 * Copyright (c) 1992, 1993
6 *	The Regents of the University of California.  All rights reserved.
7 *
8 * By using this file, you agree to the terms and conditions set
9 * forth in the LICENSE file which can be found at the top level of
10 * the sendmail distribution.
11 *
12 */
13
14#ifndef lint
15static char id[] = "@(#)$Id: map.c,v 8.414.4.55 2001/08/15 22:08:58 gshapiro Exp $";
16#endif /* ! lint */
17
18#include <sendmail.h>
19
20
21#ifdef NDBM
22# include <ndbm.h>
23# ifdef R_FIRST
24  ERROR README:	You are running the Berkeley DB version of ndbm.h.  See
25  ERROR README:	the README file about tweaking Berkeley DB so it can
26  ERROR README:	coexist with NDBM, or delete -DNDBM from the Makefile
27  ERROR README: and use -DNEWDB instead.
28# endif /* R_FIRST */
29#endif /* NDBM */
30#ifdef NEWDB
31# include <db.h>
32# ifndef DB_VERSION_MAJOR
33#  define DB_VERSION_MAJOR 1
34# endif /* ! DB_VERSION_MAJOR */
35#endif /* NEWDB */
36#ifdef NIS
37  struct dom_binding;	/* forward reference needed on IRIX */
38# include <rpcsvc/ypclnt.h>
39# ifdef NDBM
40#  define NDBM_YP_COMPAT	/* create YP-compatible NDBM files */
41# endif /* NDBM */
42#endif /* NIS */
43
44#ifdef NEWDB
45# if DB_VERSION_MAJOR < 2
46static bool	db_map_open __P((MAP *, int, char *, DBTYPE, const void *));
47# endif /* DB_VERSION_MAJOR < 2 */
48# if DB_VERSION_MAJOR == 2
49static bool	db_map_open __P((MAP *, int, char *, DBTYPE, DB_INFO *));
50# endif /* DB_VERSION_MAJOR == 2 */
51# if DB_VERSION_MAJOR > 2
52static bool	db_map_open __P((MAP *, int, char *, DBTYPE, void **));
53# endif /* DB_VERSION_MAJOR > 2 */
54#endif /* NEWDB */
55static bool	extract_canonname __P((char *, char *, char *, char[], int));
56#ifdef LDAPMAP
57static void	ldapmap_clear __P((LDAPMAP_STRUCT *));
58static STAB	*ldapmap_findconn __P((LDAPMAP_STRUCT *));
59static int	ldapmap_geterrno __P((LDAP *));
60static void	ldapmap_setopts __P((LDAP *, LDAPMAP_STRUCT *));
61static bool	ldapmap_start __P((MAP *));
62static void	ldaptimeout __P((int));
63#endif /* LDAPMAP */
64static void	map_close __P((STAB *, int));
65static void	map_init __P((STAB *, int));
66#ifdef NISPLUS
67static bool	nisplus_getcanonname __P((char *, int, int *));
68#endif /* NISPLUS */
69#ifdef NIS
70static bool	nis_getcanonname __P((char *, int, int *));
71#endif /* NIS */
72#if NETINFO
73static bool	ni_getcanonname __P((char *, int, int *));
74#endif /* NETINFO */
75static bool	text_getcanonname __P((char *, int, int *));
76
77/*
78**  MAP.C -- implementations for various map classes.
79**
80**	Each map class implements a series of functions:
81**
82**	bool map_parse(MAP *map, char *args)
83**		Parse the arguments from the config file.  Return TRUE
84**		if they were ok, FALSE otherwise.  Fill in map with the
85**		values.
86**
87**	char *map_lookup(MAP *map, char *key, char **args, int *pstat)
88**		Look up the key in the given map.  If found, do any
89**		rewriting the map wants (including "args" if desired)
90**		and return the value.  Set *pstat to the appropriate status
91**		on error and return NULL.  Args will be NULL if called
92**		from the alias routines, although this should probably
93**		not be relied upon.  It is suggested you call map_rewrite
94**		to return the results -- it takes care of null termination
95**		and uses a dynamically expanded buffer as needed.
96**
97**	void map_store(MAP *map, char *key, char *value)
98**		Store the key:value pair in the map.
99**
100**	bool map_open(MAP *map, int mode)
101**		Open the map for the indicated mode.  Mode should
102**		be either O_RDONLY or O_RDWR.  Return TRUE if it
103**		was opened successfully, FALSE otherwise.  If the open
104**		failed an the MF_OPTIONAL flag is not set, it should
105**		also print an error.  If the MF_ALIAS bit is set
106**		and this map class understands the @:@ convention, it
107**		should call aliaswait() before returning.
108**
109**	void map_close(MAP *map)
110**		Close the map.
111**
112**	This file also includes the implementation for getcanonname.
113**	It is currently implemented in a pretty ad-hoc manner; it ought
114**	to be more properly integrated into the map structure.
115*/
116
117#define DBMMODE		0644
118
119#ifndef EX_NOTFOUND
120# define EX_NOTFOUND	EX_NOHOST
121#endif /* ! EX_NOTFOUND */
122
123#if O_EXLOCK && HASFLOCK && !BOGUS_O_EXCL
124# define LOCK_ON_OPEN	1	/* we can open/create a locked file */
125#else /* O_EXLOCK && HASFLOCK && !BOGUS_O_EXCL */
126# define LOCK_ON_OPEN	0	/* no such luck -- bend over backwards */
127#endif /* O_EXLOCK && HASFLOCK && !BOGUS_O_EXCL */
128
129/*
130**  MAP_PARSEARGS -- parse config line arguments for database lookup
131**
132**	This is a generic version of the map_parse method.
133**
134**	Parameters:
135**		map -- the map being initialized.
136**		ap -- a pointer to the args on the config line.
137**
138**	Returns:
139**		TRUE -- if everything parsed OK.
140**		FALSE -- otherwise.
141**
142**	Side Effects:
143**		null terminates the filename; stores it in map
144*/
145
146bool
147map_parseargs(map, ap)
148	MAP *map;
149	char *ap;
150{
151	register char *p = ap;
152
153	/*
154	**  there is no check whether there is really an argument,
155	**  but that's not important enough to warrant extra code
156	*/
157	map->map_mflags |= MF_TRY0NULL | MF_TRY1NULL;
158	map->map_spacesub = SpaceSub;	/* default value */
159	for (;;)
160	{
161		while (isascii(*p) && isspace(*p))
162			p++;
163		if (*p != '-')
164			break;
165		switch (*++p)
166		{
167		  case 'N':
168			map->map_mflags |= MF_INCLNULL;
169			map->map_mflags &= ~MF_TRY0NULL;
170			break;
171
172		  case 'O':
173			map->map_mflags &= ~MF_TRY1NULL;
174			break;
175
176		  case 'o':
177			map->map_mflags |= MF_OPTIONAL;
178			break;
179
180		  case 'f':
181			map->map_mflags |= MF_NOFOLDCASE;
182			break;
183
184		  case 'm':
185			map->map_mflags |= MF_MATCHONLY;
186			break;
187
188		  case 'A':
189			map->map_mflags |= MF_APPEND;
190			break;
191
192		  case 'q':
193			map->map_mflags |= MF_KEEPQUOTES;
194			break;
195
196		  case 'a':
197			map->map_app = ++p;
198			break;
199
200		  case 'T':
201			map->map_tapp = ++p;
202			break;
203
204		  case 'k':
205			while (isascii(*++p) && isspace(*p))
206				continue;
207			map->map_keycolnm = p;
208			break;
209
210		  case 'v':
211			while (isascii(*++p) && isspace(*p))
212				continue;
213			map->map_valcolnm = p;
214			break;
215
216		  case 'z':
217			if (*++p != '\\')
218				map->map_coldelim = *p;
219			else
220			{
221				switch (*++p)
222				{
223				  case 'n':
224					map->map_coldelim = '\n';
225					break;
226
227				  case 't':
228					map->map_coldelim = '\t';
229					break;
230
231				  default:
232					map->map_coldelim = '\\';
233				}
234			}
235			break;
236
237		  case 't':
238			map->map_mflags |= MF_NODEFER;
239			break;
240
241
242		  case 'S':
243			map->map_spacesub = *++p;
244			break;
245
246		  case 'D':
247			map->map_mflags |= MF_DEFER;
248			break;
249
250		  default:
251			syserr("Illegal option %c map %s", *p, map->map_mname);
252			break;
253		}
254		while (*p != '\0' && !(isascii(*p) && isspace(*p)))
255			p++;
256		if (*p != '\0')
257			*p++ = '\0';
258	}
259	if (map->map_app != NULL)
260		map->map_app = newstr(map->map_app);
261	if (map->map_tapp != NULL)
262		map->map_tapp = newstr(map->map_tapp);
263	if (map->map_keycolnm != NULL)
264		map->map_keycolnm = newstr(map->map_keycolnm);
265	if (map->map_valcolnm != NULL)
266		map->map_valcolnm = newstr(map->map_valcolnm);
267
268	if (*p != '\0')
269	{
270		map->map_file = p;
271		while (*p != '\0' && !(isascii(*p) && isspace(*p)))
272			p++;
273		if (*p != '\0')
274			*p++ = '\0';
275		map->map_file = newstr(map->map_file);
276	}
277
278	while (*p != '\0' && isascii(*p) && isspace(*p))
279		p++;
280	if (*p != '\0')
281		map->map_rebuild = newstr(p);
282
283	if (map->map_file == NULL &&
284	    !bitset(MCF_OPTFILE, map->map_class->map_cflags))
285	{
286		syserr("No file name for %s map %s",
287			map->map_class->map_cname, map->map_mname);
288		return FALSE;
289	}
290	return TRUE;
291}
292/*
293**  MAP_REWRITE -- rewrite a database key, interpolating %n indications.
294**
295**	It also adds the map_app string.  It can be used as a utility
296**	in the map_lookup method.
297**
298**	Parameters:
299**		map -- the map that causes this.
300**		s -- the string to rewrite, NOT necessarily null terminated.
301**		slen -- the length of s.
302**		av -- arguments to interpolate into buf.
303**
304**	Returns:
305**		Pointer to rewritten result.  This is static data that
306**		should be copied if it is to be saved!
307**
308**	Side Effects:
309**		none.
310*/
311
312char *
313map_rewrite(map, s, slen, av)
314	register MAP *map;
315	register const char *s;
316	size_t slen;
317	char **av;
318{
319	register char *bp;
320	register char c;
321	char **avp;
322	register char *ap;
323	size_t l;
324	size_t len;
325	static size_t buflen = 0;
326	static char *buf = NULL;
327
328	if (tTd(39, 1))
329	{
330		dprintf("map_rewrite(%.*s), av =", (int)slen, s);
331		if (av == NULL)
332			dprintf(" (nullv)");
333		else
334		{
335			for (avp = av; *avp != NULL; avp++)
336				dprintf("\n\t%s", *avp);
337		}
338		dprintf("\n");
339	}
340
341	/* count expected size of output (can safely overestimate) */
342	l = len = slen;
343	if (av != NULL)
344	{
345		const char *sp = s;
346
347		while (l-- > 0 && (c = *sp++) != '\0')
348		{
349			if (c != '%')
350				continue;
351			if (l-- <= 0)
352				break;
353			c = *sp++;
354			if (!(isascii(c) && isdigit(c)))
355				continue;
356			for (avp = av; --c >= '0' && *avp != NULL; avp++)
357				continue;
358			if (*avp == NULL)
359				continue;
360			len += strlen(*avp);
361		}
362	}
363	if (map->map_app != NULL)
364		len += strlen(map->map_app);
365	if (buflen < ++len)
366	{
367		/* need to malloc additional space */
368		buflen = len;
369		if (buf != NULL)
370			sm_free(buf);
371		buf = xalloc(buflen);
372	}
373
374	bp = buf;
375	if (av == NULL)
376	{
377		memmove(bp, s, slen);
378		bp += slen;
379
380		/* assert(len > slen); */
381		len -= slen;
382	}
383	else
384	{
385		while (slen-- > 0 && (c = *s++) != '\0')
386		{
387			if (c != '%')
388			{
389  pushc:
390				if (--len <= 0)
391					break;
392				*bp++ = c;
393				continue;
394			}
395			if (slen-- <= 0 || (c = *s++) == '\0')
396				c = '%';
397			if (c == '%')
398				goto pushc;
399			if (!(isascii(c) && isdigit(c)))
400			{
401				*bp++ = '%';
402				--len;
403				goto pushc;
404			}
405			for (avp = av; --c >= '0' && *avp != NULL; avp++)
406				continue;
407			if (*avp == NULL)
408				continue;
409
410			/* transliterate argument into output string */
411			for (ap = *avp; (c = *ap++) != '\0' && len > 0; --len)
412				*bp++ = c;
413		}
414	}
415	if (map->map_app != NULL && len > 0)
416		(void) strlcpy(bp, map->map_app, len);
417	else
418		*bp = '\0';
419	if (tTd(39, 1))
420		dprintf("map_rewrite => %s\n", buf);
421	return buf;
422}
423/*
424**  INITMAPS -- rebuild alias maps
425**
426**	Parameters:
427**		none.
428**
429**	Returns:
430**		none.
431*/
432
433void
434initmaps()
435{
436#if XDEBUG
437	checkfd012("entering initmaps");
438#endif /* XDEBUG */
439	stabapply(map_init, 0);
440#if XDEBUG
441	checkfd012("exiting initmaps");
442#endif /* XDEBUG */
443}
444/*
445**  MAP_INIT -- rebuild a map
446**
447**	Parameters:
448**		s -- STAB entry: if map: try to rebuild
449**		unused -- unused variable
450**
451**	Returns:
452**		none.
453**
454**	Side Effects:
455**		will close already open rebuildable map.
456*/
457
458/* ARGSUSED1 */
459static void
460map_init(s, unused)
461	register STAB *s;
462	int unused;
463{
464	register MAP *map;
465
466	/* has to be a map */
467	if (s->s_type != ST_MAP)
468		return;
469
470	map = &s->s_map;
471	if (!bitset(MF_VALID, map->map_mflags))
472		return;
473
474	if (tTd(38, 2))
475		dprintf("map_init(%s:%s, %s)\n",
476			map->map_class->map_cname == NULL ? "NULL" :
477				map->map_class->map_cname,
478			map->map_mname == NULL ? "NULL" : map->map_mname,
479			map->map_file == NULL ? "NULL" : map->map_file);
480
481	if (!bitset(MF_ALIAS, map->map_mflags) ||
482	    !bitset(MCF_REBUILDABLE, map->map_class->map_cflags))
483	{
484		if (tTd(38, 3))
485			dprintf("\tnot rebuildable\n");
486		return;
487	}
488
489	/* if already open, close it (for nested open) */
490	if (bitset(MF_OPEN, map->map_mflags))
491	{
492		map->map_mflags |= MF_CLOSING;
493		map->map_class->map_close(map);
494		map->map_mflags &= ~(MF_OPEN|MF_WRITABLE|MF_CLOSING);
495	}
496
497	(void) rebuildaliases(map, FALSE);
498	return;
499}
500/*
501**  OPENMAP -- open a map
502**
503**	Parameters:
504**		map -- map to open (it must not be open).
505**
506**	Returns:
507**		whether open succeeded.
508**
509*/
510
511bool
512openmap(map)
513	MAP *map;
514{
515	bool restore = FALSE;
516	bool savehold = HoldErrs;
517	bool savequick = QuickAbort;
518	int saveerrors = Errors;
519
520	if (!bitset(MF_VALID, map->map_mflags))
521		return FALSE;
522
523	/* better safe than sorry... */
524	if (bitset(MF_OPEN, map->map_mflags))
525		return TRUE;
526
527	/* Don't send a map open error out via SMTP */
528	if ((OnlyOneError || QuickAbort) &&
529	    (OpMode == MD_SMTP || OpMode == MD_DAEMON))
530	{
531		restore = TRUE;
532		HoldErrs = TRUE;
533		QuickAbort = FALSE;
534	}
535
536	errno = 0;
537	if (map->map_class->map_open(map, O_RDONLY))
538	{
539		if (tTd(38, 4))
540			dprintf("openmap()\t%s:%s %s: valid\n",
541				map->map_class->map_cname == NULL ? "NULL" :
542					map->map_class->map_cname,
543				map->map_mname == NULL ? "NULL" :
544					map->map_mname,
545				map->map_file == NULL ? "NULL" :
546					map->map_file);
547		map->map_mflags |= MF_OPEN;
548		map->map_pid = getpid();
549	}
550	else
551	{
552		if (tTd(38, 4))
553			dprintf("openmap()\t%s:%s %s: invalid%s%s\n",
554				map->map_class->map_cname == NULL ? "NULL" :
555					map->map_class->map_cname,
556				map->map_mname == NULL ? "NULL" :
557					map->map_mname,
558				map->map_file == NULL ? "NULL" :
559					map->map_file,
560				errno == 0 ? "" : ": ",
561				errno == 0 ? "" : errstring(errno));
562		if (!bitset(MF_OPTIONAL, map->map_mflags))
563		{
564			extern MAPCLASS BogusMapClass;
565
566			map->map_class = &BogusMapClass;
567			map->map_mflags |= MF_OPEN;
568			map->map_pid = getpid();
569			MapOpenErr = TRUE;
570		}
571		else
572		{
573			/* don't try again */
574			map->map_mflags &= ~MF_VALID;
575		}
576	}
577
578	if (restore)
579	{
580		Errors = saveerrors;
581		HoldErrs = savehold;
582		QuickAbort = savequick;
583	}
584
585	return bitset(MF_OPEN, map->map_mflags);
586}
587/*
588**  CLOSEMAPS -- close all open maps opened by the current pid.
589**
590**	Parameters:
591**		none
592**
593**	Returns:
594**		none.
595*/
596
597void
598closemaps()
599{
600	stabapply(map_close, 0);
601}
602/*
603**  MAP_CLOSE -- close a map opened by the current pid.
604**
605**	Parameters:
606**		s -- STAB entry: if map: try to open
607**		second parameter is unused (required by stabapply())
608**
609**	Returns:
610**		none.
611*/
612
613/* ARGSUSED1 */
614static void
615map_close(s, unused)
616	register STAB *s;
617	int unused;
618{
619	MAP *map;
620
621	if (s->s_type != ST_MAP)
622		return;
623
624	map = &s->s_map;
625
626	if (!bitset(MF_VALID, map->map_mflags) ||
627	    !bitset(MF_OPEN, map->map_mflags) ||
628	    bitset(MF_CLOSING, map->map_mflags) ||
629	    map->map_pid != getpid())
630		return;
631
632	if (tTd(38, 5))
633		dprintf("closemaps: closing %s (%s)\n",
634			map->map_mname == NULL ? "NULL" : map->map_mname,
635			map->map_file == NULL ? "NULL" : map->map_file);
636
637	map->map_mflags |= MF_CLOSING;
638	map->map_class->map_close(map);
639	map->map_mflags &= ~(MF_OPEN|MF_WRITABLE|MF_CLOSING);
640}
641/*
642**  GETCANONNAME -- look up name using service switch
643**
644**	Parameters:
645**		host -- the host name to look up.
646**		hbsize -- the size of the host buffer.
647**		trymx -- if set, try MX records.
648**
649**	Returns:
650**		TRUE -- if the host was found.
651**		FALSE -- otherwise.
652*/
653
654bool
655getcanonname(host, hbsize, trymx)
656	char *host;
657	int hbsize;
658	bool trymx;
659{
660	int nmaps;
661	int mapno;
662	bool found = FALSE;
663	bool got_tempfail = FALSE;
664	auto int status;
665	char *maptype[MAXMAPSTACK];
666	short mapreturn[MAXMAPACTIONS];
667
668	nmaps = switch_map_find("hosts", maptype, mapreturn);
669	for (mapno = 0; mapno < nmaps; mapno++)
670	{
671		int i;
672
673		if (tTd(38, 20))
674			dprintf("getcanonname(%s), trying %s\n",
675				host, maptype[mapno]);
676		if (strcmp("files", maptype[mapno]) == 0)
677		{
678			found = text_getcanonname(host, hbsize, &status);
679		}
680#ifdef NIS
681		else if (strcmp("nis", maptype[mapno]) == 0)
682		{
683			found = nis_getcanonname(host, hbsize, &status);
684		}
685#endif /* NIS */
686#ifdef NISPLUS
687		else if (strcmp("nisplus", maptype[mapno]) == 0)
688		{
689			found = nisplus_getcanonname(host, hbsize, &status);
690		}
691#endif /* NISPLUS */
692#if NAMED_BIND
693		else if (strcmp("dns", maptype[mapno]) == 0)
694		{
695			found = dns_getcanonname(host, hbsize, trymx, &status);
696		}
697#endif /* NAMED_BIND */
698#if NETINFO
699		else if (strcmp("netinfo", maptype[mapno]) == 0)
700		{
701			found = ni_getcanonname(host, hbsize, &status);
702		}
703#endif /* NETINFO */
704		else
705		{
706			found = FALSE;
707			status = EX_UNAVAILABLE;
708		}
709
710		/*
711		**  Heuristic: if $m is not set, we are running during system
712		**  startup.  In this case, when a name is apparently found
713		**  but has no dot, treat is as not found.  This avoids
714		**  problems if /etc/hosts has no FQDN but is listed first
715		**  in the service switch.
716		*/
717
718		if (found &&
719		    (macvalue('m', CurEnv) != NULL || strchr(host, '.') != NULL))
720			break;
721
722		/* see if we should continue */
723		if (status == EX_TEMPFAIL)
724		{
725			i = MA_TRYAGAIN;
726			got_tempfail = TRUE;
727		}
728		else if (status == EX_NOTFOUND)
729			i = MA_NOTFOUND;
730		else
731			i = MA_UNAVAIL;
732		if (bitset(1 << mapno, mapreturn[i]))
733			break;
734	}
735
736	if (found)
737	{
738		char *d;
739
740		if (tTd(38, 20))
741			dprintf("getcanonname(%s), found\n", host);
742
743		/*
744		**  If returned name is still single token, compensate
745		**  by tagging on $m.  This is because some sites set
746		**  up their DNS or NIS databases wrong.
747		*/
748
749		if ((d = strchr(host, '.')) == NULL || d[1] == '\0')
750		{
751			d = macvalue('m', CurEnv);
752			if (d != NULL &&
753			    hbsize > (int) (strlen(host) + strlen(d) + 1))
754			{
755				if (host[strlen(host) - 1] != '.')
756					(void) strlcat(host, ".", hbsize);
757				(void) strlcat(host, d, hbsize);
758			}
759			else
760				return FALSE;
761		}
762		return TRUE;
763	}
764
765	if (tTd(38, 20))
766		dprintf("getcanonname(%s), failed, status=%d\n", host, status);
767
768#if NAMED_BIND
769	if (got_tempfail)
770		SM_SET_H_ERRNO(TRY_AGAIN);
771	else
772		SM_SET_H_ERRNO(HOST_NOT_FOUND);
773#endif /* NAMED_BIND */
774	return FALSE;
775}
776/*
777**  EXTRACT_CANONNAME -- extract canonical name from /etc/hosts entry
778**
779**	Parameters:
780**		name -- the name against which to match.
781**		dot -- where to reinsert '.' to get FQDN
782**		line -- the /etc/hosts line.
783**		cbuf -- the location to store the result.
784**		cbuflen -- the size of cbuf.
785**
786**	Returns:
787**		TRUE -- if the line matched the desired name.
788**		FALSE -- otherwise.
789*/
790
791static bool
792extract_canonname(name, dot, line, cbuf, cbuflen)
793	char *name;
794	char *dot;
795	char *line;
796	char cbuf[];
797	int cbuflen;
798{
799	int i;
800	char *p;
801	bool found = FALSE;
802
803	cbuf[0] = '\0';
804	if (line[0] == '#')
805		return FALSE;
806
807	for (i = 1; ; i++)
808	{
809		char nbuf[MAXNAME + 1];
810
811		p = get_column(line, i, '\0', nbuf, sizeof nbuf);
812		if (p == NULL)
813			break;
814		if (*p == '\0')
815			continue;
816		if (cbuf[0] == '\0' ||
817		    (strchr(cbuf, '.') == NULL && strchr(p, '.') != NULL))
818		{
819			snprintf(cbuf, cbuflen, "%s", p);
820		}
821		if (strcasecmp(name, p) == 0)
822			found = TRUE;
823		else if (dot != NULL)
824		{
825			/* try looking for the FQDN as well */
826			*dot = '.';
827			if (strcasecmp(name, p) == 0)
828				found = TRUE;
829			*dot = '\0';
830		}
831	}
832	if (found && strchr(cbuf, '.') == NULL)
833	{
834		/* try to add a domain on the end of the name */
835		char *domain = macvalue('m', CurEnv);
836
837		if (domain != NULL &&
838		    strlen(domain) + (i = strlen(cbuf)) + 1 < (size_t) cbuflen)
839		{
840			p = &cbuf[i];
841			*p++ = '.';
842			(void) strlcpy(p, domain, cbuflen - i - 1);
843		}
844	}
845	return found;
846}
847/*
848**  NDBM modules
849*/
850
851#ifdef NDBM
852
853/*
854**  NDBM_MAP_OPEN -- DBM-style map open
855*/
856
857bool
858ndbm_map_open(map, mode)
859	MAP *map;
860	int mode;
861{
862	register DBM *dbm;
863	int save_errno;
864	int dfd;
865	int pfd;
866	long sff;
867	int ret;
868	int smode = S_IREAD;
869	char dirfile[MAXNAME + 1];
870	char pagfile[MAXNAME + 1];
871	struct stat st;
872	struct stat std, stp;
873
874	if (tTd(38, 2))
875		dprintf("ndbm_map_open(%s, %s, %d)\n",
876			map->map_mname, map->map_file, mode);
877	map->map_lockfd = -1;
878	mode &= O_ACCMODE;
879
880	/* do initial file and directory checks */
881	snprintf(dirfile, sizeof dirfile, "%s.dir", map->map_file);
882	snprintf(pagfile, sizeof pagfile, "%s.pag", map->map_file);
883	sff = SFF_ROOTOK|SFF_REGONLY;
884	if (mode == O_RDWR)
885	{
886		sff |= SFF_CREAT;
887		if (!bitnset(DBS_WRITEMAPTOSYMLINK, DontBlameSendmail))
888			sff |= SFF_NOSLINK;
889		if (!bitnset(DBS_WRITEMAPTOHARDLINK, DontBlameSendmail))
890			sff |= SFF_NOHLINK;
891		smode = S_IWRITE;
892	}
893	else
894	{
895		if (!bitnset(DBS_LINKEDMAPINWRITABLEDIR, DontBlameSendmail))
896			sff |= SFF_NOWLINK;
897	}
898	if (!bitnset(DBS_MAPINUNSAFEDIRPATH, DontBlameSendmail))
899		sff |= SFF_SAFEDIRPATH;
900	ret = safefile(dirfile, RunAsUid, RunAsGid, RunAsUserName,
901			    sff, smode, &std);
902	if (ret == 0)
903		ret = safefile(pagfile, RunAsUid, RunAsGid, RunAsUserName,
904			       sff, smode, &stp);
905
906# if !_FFR_REMOVE_AUTOREBUILD
907	if (ret == ENOENT && AutoRebuild &&
908	    bitset(MCF_REBUILDABLE, map->map_class->map_cflags) &&
909	    (bitset(MF_IMPL_NDBM, map->map_mflags) ||
910	     bitset(MF_ALIAS, map->map_mflags)) &&
911	    mode == O_RDONLY)
912	{
913		bool impl = bitset(MF_IMPL_NDBM, map->map_mflags);
914
915		/* may be able to rebuild */
916		map->map_mflags &= ~MF_IMPL_NDBM;
917		if (!rebuildaliases(map, TRUE))
918			return FALSE;
919		if (impl)
920			return impl_map_open(map, O_RDONLY);
921		else
922			return ndbm_map_open(map, O_RDONLY);
923	}
924# endif /* !_FFR_REMOVE_AUTOREBUILD */
925
926	if (ret != 0)
927	{
928		char *prob = "unsafe";
929
930		/* cannot open this map */
931		if (ret == ENOENT)
932			prob = "missing";
933		if (tTd(38, 2))
934			dprintf("\t%s map file: %d\n", prob, ret);
935		if (!bitset(MF_OPTIONAL, map->map_mflags))
936			syserr("dbm map \"%s\": %s map file %s",
937				map->map_mname, prob, map->map_file);
938		return FALSE;
939	}
940	if (std.st_mode == ST_MODE_NOFILE)
941		mode |= O_CREAT|O_EXCL;
942
943# if LOCK_ON_OPEN
944	if (mode == O_RDONLY)
945		mode |= O_SHLOCK;
946	else
947		mode |= O_TRUNC|O_EXLOCK;
948# else /* LOCK_ON_OPEN */
949	if ((mode & O_ACCMODE) == O_RDWR)
950	{
951#  if NOFTRUNCATE
952		/*
953		**  Warning: race condition.  Try to lock the file as
954		**  quickly as possible after opening it.
955		**	This may also have security problems on some systems,
956		**	but there isn't anything we can do about it.
957		*/
958
959		mode |= O_TRUNC;
960#  else /* NOFTRUNCATE */
961		/*
962		**  This ugly code opens the map without truncating it,
963		**  locks the file, then truncates it.  Necessary to
964		**  avoid race conditions.
965		*/
966
967		int dirfd;
968		int pagfd;
969		long sff = SFF_CREAT|SFF_OPENASROOT;
970
971		if (!bitnset(DBS_WRITEMAPTOSYMLINK, DontBlameSendmail))
972			sff |= SFF_NOSLINK;
973		if (!bitnset(DBS_WRITEMAPTOHARDLINK, DontBlameSendmail))
974			sff |= SFF_NOHLINK;
975
976		dirfd = safeopen(dirfile, mode, DBMMODE, sff);
977		pagfd = safeopen(pagfile, mode, DBMMODE, sff);
978
979		if (dirfd < 0 || pagfd < 0)
980		{
981			save_errno = errno;
982			if (dirfd >= 0)
983				(void) close(dirfd);
984			if (pagfd >= 0)
985				(void) close(pagfd);
986			errno = save_errno;
987			syserr("ndbm_map_open: cannot create database %s",
988				map->map_file);
989			return FALSE;
990		}
991		if (ftruncate(dirfd, (off_t) 0) < 0 ||
992		    ftruncate(pagfd, (off_t) 0) < 0)
993		{
994			save_errno = errno;
995			(void) close(dirfd);
996			(void) close(pagfd);
997			errno = save_errno;
998			syserr("ndbm_map_open: cannot truncate %s.{dir,pag}",
999				map->map_file);
1000			return FALSE;
1001		}
1002
1003		/* if new file, get "before" bits for later filechanged check */
1004		if (std.st_mode == ST_MODE_NOFILE &&
1005		    (fstat(dirfd, &std) < 0 || fstat(pagfd, &stp) < 0))
1006		{
1007			save_errno = errno;
1008			(void) close(dirfd);
1009			(void) close(pagfd);
1010			errno = save_errno;
1011			syserr("ndbm_map_open(%s.{dir,pag}): cannot fstat pre-opened file",
1012				map->map_file);
1013			return FALSE;
1014		}
1015
1016		/* have to save the lock for the duration (bletch) */
1017		map->map_lockfd = dirfd;
1018		(void) close(pagfd);
1019
1020		/* twiddle bits for dbm_open */
1021		mode &= ~(O_CREAT|O_EXCL);
1022#  endif /* NOFTRUNCATE */
1023	}
1024# endif /* LOCK_ON_OPEN */
1025
1026	/* open the database */
1027	dbm = dbm_open(map->map_file, mode, DBMMODE);
1028	if (dbm == NULL)
1029	{
1030		save_errno = errno;
1031		if (bitset(MF_ALIAS, map->map_mflags) &&
1032		    aliaswait(map, ".pag", FALSE))
1033			return TRUE;
1034# if !LOCK_ON_OPEN && !NOFTRUNCATE
1035		if (map->map_lockfd >= 0)
1036			(void) close(map->map_lockfd);
1037# endif /* !LOCK_ON_OPEN && !NOFTRUNCATE */
1038		errno = save_errno;
1039		if (!bitset(MF_OPTIONAL, map->map_mflags))
1040			syserr("Cannot open DBM database %s", map->map_file);
1041		return FALSE;
1042	}
1043	dfd = dbm_dirfno(dbm);
1044	pfd = dbm_pagfno(dbm);
1045	if (dfd == pfd)
1046	{
1047		/* heuristic: if files are linked, this is actually gdbm */
1048		dbm_close(dbm);
1049# if !LOCK_ON_OPEN && !NOFTRUNCATE
1050		if (map->map_lockfd >= 0)
1051			(void) close(map->map_lockfd);
1052# endif /* !LOCK_ON_OPEN && !NOFTRUNCATE */
1053		errno = 0;
1054		syserr("dbm map \"%s\": cannot support GDBM",
1055			map->map_mname);
1056		return FALSE;
1057	}
1058
1059	if (filechanged(dirfile, dfd, &std) ||
1060	    filechanged(pagfile, pfd, &stp))
1061	{
1062		save_errno = errno;
1063		dbm_close(dbm);
1064# if !LOCK_ON_OPEN && !NOFTRUNCATE
1065		if (map->map_lockfd >= 0)
1066			(void) close(map->map_lockfd);
1067# endif /* !LOCK_ON_OPEN && !NOFTRUNCATE */
1068		errno = save_errno;
1069		syserr("ndbm_map_open(%s): file changed after open",
1070			map->map_file);
1071		return FALSE;
1072	}
1073
1074	map->map_db1 = (ARBPTR_T) dbm;
1075
1076	/*
1077	**  Need to set map_mtime before the call to aliaswait()
1078	**  as aliaswait() will call map_lookup() which requires
1079	**  map_mtime to be set
1080	*/
1081
1082	if (fstat(pfd, &st) >= 0)
1083		map->map_mtime = st.st_mtime;
1084
1085	if (mode == O_RDONLY)
1086	{
1087# if LOCK_ON_OPEN
1088		if (dfd >= 0)
1089			(void) lockfile(dfd, map->map_file, ".dir", LOCK_UN);
1090		if (pfd >= 0)
1091			(void) lockfile(pfd, map->map_file, ".pag", LOCK_UN);
1092# endif /* LOCK_ON_OPEN */
1093		if (bitset(MF_ALIAS, map->map_mflags) &&
1094		    !aliaswait(map, ".pag", TRUE))
1095			return FALSE;
1096	}
1097	else
1098	{
1099		map->map_mflags |= MF_LOCKED;
1100		if (geteuid() == 0 && TrustedUid != 0)
1101		{
1102#  if HASFCHOWN
1103			if (fchown(dfd, TrustedUid, -1) < 0 ||
1104			    fchown(pfd, TrustedUid, -1) < 0)
1105			{
1106				int err = errno;
1107
1108				sm_syslog(LOG_ALERT, NOQID,
1109					  "ownership change on %s failed: %s",
1110					  map->map_file, errstring(err));
1111				message("050 ownership change on %s failed: %s",
1112					map->map_file, errstring(err));
1113			}
1114#  endif /* HASFCHOWN */
1115		}
1116	}
1117	return TRUE;
1118}
1119
1120
1121/*
1122**  NDBM_MAP_LOOKUP -- look up a datum in a DBM-type map
1123*/
1124
1125char *
1126ndbm_map_lookup(map, name, av, statp)
1127	MAP *map;
1128	char *name;
1129	char **av;
1130	int *statp;
1131{
1132	datum key, val;
1133	int dfd, pfd;
1134	char keybuf[MAXNAME + 1];
1135	struct stat stbuf;
1136
1137	if (tTd(38, 20))
1138		dprintf("ndbm_map_lookup(%s, %s)\n",
1139			map->map_mname, name);
1140
1141	key.dptr = name;
1142	key.dsize = strlen(name);
1143	if (!bitset(MF_NOFOLDCASE, map->map_mflags))
1144	{
1145		if (key.dsize > sizeof keybuf - 1)
1146			key.dsize = sizeof keybuf - 1;
1147		memmove(keybuf, key.dptr, key.dsize);
1148		keybuf[key.dsize] = '\0';
1149		makelower(keybuf);
1150		key.dptr = keybuf;
1151	}
1152lockdbm:
1153	dfd = dbm_dirfno((DBM *) map->map_db1);
1154	if (dfd >= 0 && !bitset(MF_LOCKED, map->map_mflags))
1155		(void) lockfile(dfd, map->map_file, ".dir", LOCK_SH);
1156	pfd = dbm_pagfno((DBM *) map->map_db1);
1157	if (pfd < 0 || fstat(pfd, &stbuf) < 0 ||
1158	    stbuf.st_mtime > map->map_mtime)
1159	{
1160		/* Reopen the database to sync the cache */
1161		int omode = bitset(map->map_mflags, MF_WRITABLE) ? O_RDWR
1162								 : O_RDONLY;
1163
1164		if (dfd >= 0 && !bitset(MF_LOCKED, map->map_mflags))
1165			(void) lockfile(dfd, map->map_file, ".dir", LOCK_UN);
1166		map->map_mflags |= MF_CLOSING;
1167		map->map_class->map_close(map);
1168		map->map_mflags &= ~(MF_OPEN|MF_WRITABLE|MF_CLOSING);
1169		if (map->map_class->map_open(map, omode))
1170		{
1171			map->map_mflags |= MF_OPEN;
1172			map->map_pid = getpid();
1173			if ((omode && O_ACCMODE) == O_RDWR)
1174				map->map_mflags |= MF_WRITABLE;
1175			goto lockdbm;
1176		}
1177		else
1178		{
1179			if (!bitset(MF_OPTIONAL, map->map_mflags))
1180			{
1181				extern MAPCLASS BogusMapClass;
1182
1183				*statp = EX_TEMPFAIL;
1184				map->map_class = &BogusMapClass;
1185				map->map_mflags |= MF_OPEN;
1186				map->map_pid = getpid();
1187				syserr("Cannot reopen NDBM database %s",
1188					map->map_file);
1189			}
1190			return NULL;
1191		}
1192	}
1193	val.dptr = NULL;
1194	if (bitset(MF_TRY0NULL, map->map_mflags))
1195	{
1196		val = dbm_fetch((DBM *) map->map_db1, key);
1197		if (val.dptr != NULL)
1198			map->map_mflags &= ~MF_TRY1NULL;
1199	}
1200	if (val.dptr == NULL && bitset(MF_TRY1NULL, map->map_mflags))
1201	{
1202		key.dsize++;
1203		val = dbm_fetch((DBM *) map->map_db1, key);
1204		if (val.dptr != NULL)
1205			map->map_mflags &= ~MF_TRY0NULL;
1206	}
1207	if (dfd >= 0 && !bitset(MF_LOCKED, map->map_mflags))
1208		(void) lockfile(dfd, map->map_file, ".dir", LOCK_UN);
1209	if (val.dptr == NULL)
1210		return NULL;
1211	if (bitset(MF_MATCHONLY, map->map_mflags))
1212		return map_rewrite(map, name, strlen(name), NULL);
1213	else
1214		return map_rewrite(map, val.dptr, val.dsize, av);
1215}
1216
1217
1218/*
1219**  NDBM_MAP_STORE -- store a datum in the database
1220*/
1221
1222void
1223ndbm_map_store(map, lhs, rhs)
1224	register MAP *map;
1225	char *lhs;
1226	char *rhs;
1227{
1228	datum key;
1229	datum data;
1230	int status;
1231	char keybuf[MAXNAME + 1];
1232
1233	if (tTd(38, 12))
1234		dprintf("ndbm_map_store(%s, %s, %s)\n",
1235			map->map_mname, lhs, rhs);
1236
1237	key.dsize = strlen(lhs);
1238	key.dptr = lhs;
1239	if (!bitset(MF_NOFOLDCASE, map->map_mflags))
1240	{
1241		if (key.dsize > sizeof keybuf - 1)
1242			key.dsize = sizeof keybuf - 1;
1243		memmove(keybuf, key.dptr, key.dsize);
1244		keybuf[key.dsize] = '\0';
1245		makelower(keybuf);
1246		key.dptr = keybuf;
1247	}
1248
1249	data.dsize = strlen(rhs);
1250	data.dptr = rhs;
1251
1252	if (bitset(MF_INCLNULL, map->map_mflags))
1253	{
1254		key.dsize++;
1255		data.dsize++;
1256	}
1257
1258	status = dbm_store((DBM *) map->map_db1, key, data, DBM_INSERT);
1259	if (status > 0)
1260	{
1261		if (!bitset(MF_APPEND, map->map_mflags))
1262			message("050 Warning: duplicate alias name %s", lhs);
1263		else
1264		{
1265			static char *buf = NULL;
1266			static int bufsiz = 0;
1267			auto int xstat;
1268			datum old;
1269
1270			old.dptr = ndbm_map_lookup(map, key.dptr,
1271						   (char **)NULL, &xstat);
1272			if (old.dptr != NULL && *(char *) old.dptr != '\0')
1273			{
1274				old.dsize = strlen(old.dptr);
1275				if (data.dsize + old.dsize + 2 > bufsiz)
1276				{
1277					if (buf != NULL)
1278						sm_free(buf);
1279					bufsiz = data.dsize + old.dsize + 2;
1280					buf = xalloc(bufsiz);
1281				}
1282				snprintf(buf, bufsiz, "%s,%s",
1283					data.dptr, old.dptr);
1284				data.dsize = data.dsize + old.dsize + 1;
1285				data.dptr = buf;
1286				if (tTd(38, 9))
1287					dprintf("ndbm_map_store append=%s\n",
1288						data.dptr);
1289			}
1290		}
1291		status = dbm_store((DBM *) map->map_db1,
1292				   key, data, DBM_REPLACE);
1293	}
1294	if (status != 0)
1295		syserr("readaliases: dbm put (%s): %d", lhs, status);
1296}
1297
1298
1299/*
1300**  NDBM_MAP_CLOSE -- close the database
1301*/
1302
1303void
1304ndbm_map_close(map)
1305	register MAP  *map;
1306{
1307	if (tTd(38, 9))
1308		dprintf("ndbm_map_close(%s, %s, %lx)\n",
1309			map->map_mname, map->map_file, map->map_mflags);
1310
1311	if (bitset(MF_WRITABLE, map->map_mflags))
1312	{
1313# ifdef NDBM_YP_COMPAT
1314		bool inclnull;
1315		char buf[MAXHOSTNAMELEN];
1316
1317		inclnull = bitset(MF_INCLNULL, map->map_mflags);
1318		map->map_mflags &= ~MF_INCLNULL;
1319
1320		if (strstr(map->map_file, "/yp/") != NULL)
1321		{
1322			long save_mflags = map->map_mflags;
1323
1324			map->map_mflags |= MF_NOFOLDCASE;
1325
1326			(void) snprintf(buf, sizeof buf, "%010ld", curtime());
1327			ndbm_map_store(map, "YP_LAST_MODIFIED", buf);
1328
1329			(void) gethostname(buf, sizeof buf);
1330			ndbm_map_store(map, "YP_MASTER_NAME", buf);
1331
1332			map->map_mflags = save_mflags;
1333		}
1334
1335		if (inclnull)
1336			map->map_mflags |= MF_INCLNULL;
1337# endif /* NDBM_YP_COMPAT */
1338
1339		/* write out the distinguished alias */
1340		ndbm_map_store(map, "@", "@");
1341	}
1342	dbm_close((DBM *) map->map_db1);
1343
1344	/* release lock (if needed) */
1345# if !LOCK_ON_OPEN
1346	if (map->map_lockfd >= 0)
1347		(void) close(map->map_lockfd);
1348# endif /* !LOCK_ON_OPEN */
1349}
1350
1351#endif /* NDBM */
1352/*
1353**  NEWDB (Hash and BTree) Modules
1354*/
1355
1356#ifdef NEWDB
1357
1358/*
1359**  BT_MAP_OPEN, HASH_MAP_OPEN -- database open primitives.
1360**
1361**	These do rather bizarre locking.  If you can lock on open,
1362**	do that to avoid the condition of opening a database that
1363**	is being rebuilt.  If you don't, we'll try to fake it, but
1364**	there will be a race condition.  If opening for read-only,
1365**	we immediately release the lock to avoid freezing things up.
1366**	We really ought to hold the lock, but guarantee that we won't
1367**	be pokey about it.  That's hard to do.
1368*/
1369
1370/* these should be K line arguments */
1371# if DB_VERSION_MAJOR < 2
1372#  define db_cachesize	cachesize
1373#  define h_nelem	nelem
1374#  ifndef DB_CACHE_SIZE
1375#   define DB_CACHE_SIZE	(1024 * 1024)	/* database memory cache size */
1376#  endif /* ! DB_CACHE_SIZE */
1377#  ifndef DB_HASH_NELEM
1378#   define DB_HASH_NELEM	4096		/* (starting) size of hash table */
1379#  endif /* ! DB_HASH_NELEM */
1380# endif /* DB_VERSION_MAJOR < 2 */
1381
1382bool
1383bt_map_open(map, mode)
1384	MAP *map;
1385	int mode;
1386{
1387# if DB_VERSION_MAJOR < 2
1388	BTREEINFO btinfo;
1389# endif /* DB_VERSION_MAJOR < 2 */
1390# if DB_VERSION_MAJOR == 2
1391	DB_INFO btinfo;
1392# endif /* DB_VERSION_MAJOR == 2 */
1393# if DB_VERSION_MAJOR > 2
1394	void *btinfo = NULL;
1395# endif /* DB_VERSION_MAJOR > 2 */
1396
1397	if (tTd(38, 2))
1398		dprintf("bt_map_open(%s, %s, %d)\n",
1399			map->map_mname, map->map_file, mode);
1400
1401# if DB_VERSION_MAJOR < 3
1402	memset(&btinfo, '\0', sizeof btinfo);
1403#  ifdef DB_CACHE_SIZE
1404	btinfo.db_cachesize = DB_CACHE_SIZE;
1405#  endif /* DB_CACHE_SIZE */
1406# endif /* DB_VERSION_MAJOR < 3 */
1407
1408	return db_map_open(map, mode, "btree", DB_BTREE, &btinfo);
1409}
1410
1411bool
1412hash_map_open(map, mode)
1413	MAP *map;
1414	int mode;
1415{
1416# if DB_VERSION_MAJOR < 2
1417	HASHINFO hinfo;
1418# endif /* DB_VERSION_MAJOR < 2 */
1419# if DB_VERSION_MAJOR == 2
1420	DB_INFO hinfo;
1421# endif /* DB_VERSION_MAJOR == 2 */
1422# if DB_VERSION_MAJOR > 2
1423	void *hinfo = NULL;
1424# endif /* DB_VERSION_MAJOR > 2 */
1425
1426	if (tTd(38, 2))
1427		dprintf("hash_map_open(%s, %s, %d)\n",
1428			map->map_mname, map->map_file, mode);
1429
1430# if DB_VERSION_MAJOR < 3
1431	memset(&hinfo, '\0', sizeof hinfo);
1432#  ifdef DB_HASH_NELEM
1433	hinfo.h_nelem = DB_HASH_NELEM;
1434#  endif /* DB_HASH_NELEM */
1435#  ifdef DB_CACHE_SIZE
1436	hinfo.db_cachesize = DB_CACHE_SIZE;
1437#  endif /* DB_CACHE_SIZE */
1438# endif /* DB_VERSION_MAJOR < 3 */
1439
1440	return db_map_open(map, mode, "hash", DB_HASH, &hinfo);
1441}
1442
1443static bool
1444db_map_open(map, mode, mapclassname, dbtype, openinfo)
1445	MAP *map;
1446	int mode;
1447	char *mapclassname;
1448	DBTYPE dbtype;
1449# if DB_VERSION_MAJOR < 2
1450	const void *openinfo;
1451# endif /* DB_VERSION_MAJOR < 2 */
1452# if DB_VERSION_MAJOR == 2
1453	DB_INFO *openinfo;
1454# endif /* DB_VERSION_MAJOR == 2 */
1455# if DB_VERSION_MAJOR > 2
1456	void **openinfo;
1457# endif /* DB_VERSION_MAJOR > 2 */
1458{
1459	DB *db = NULL;
1460	int i;
1461	int omode;
1462	int smode = S_IREAD;
1463	int fd;
1464	long sff;
1465	int save_errno;
1466	struct stat st;
1467	char buf[MAXNAME + 1];
1468
1469	/* do initial file and directory checks */
1470	(void) strlcpy(buf, map->map_file, sizeof buf - 3);
1471	i = strlen(buf);
1472	if (i < 3 || strcmp(&buf[i - 3], ".db") != 0)
1473		(void) strlcat(buf, ".db", sizeof buf);
1474
1475	mode &= O_ACCMODE;
1476	omode = mode;
1477
1478	sff = SFF_ROOTOK|SFF_REGONLY;
1479	if (mode == O_RDWR)
1480	{
1481		sff |= SFF_CREAT;
1482		if (!bitnset(DBS_WRITEMAPTOSYMLINK, DontBlameSendmail))
1483			sff |= SFF_NOSLINK;
1484		if (!bitnset(DBS_WRITEMAPTOHARDLINK, DontBlameSendmail))
1485			sff |= SFF_NOHLINK;
1486		smode = S_IWRITE;
1487	}
1488	else
1489	{
1490		if (!bitnset(DBS_LINKEDMAPINWRITABLEDIR, DontBlameSendmail))
1491			sff |= SFF_NOWLINK;
1492	}
1493	if (!bitnset(DBS_MAPINUNSAFEDIRPATH, DontBlameSendmail))
1494		sff |= SFF_SAFEDIRPATH;
1495	i = safefile(buf, RunAsUid, RunAsGid, RunAsUserName, sff, smode, &st);
1496
1497# if !_FFR_REMOVE_AUTOREBUILD
1498	if (i == ENOENT && AutoRebuild &&
1499	    bitset(MCF_REBUILDABLE, map->map_class->map_cflags) &&
1500	    (bitset(MF_IMPL_HASH, map->map_mflags) ||
1501	     bitset(MF_ALIAS, map->map_mflags)) &&
1502	    mode == O_RDONLY)
1503	{
1504		bool impl = bitset(MF_IMPL_HASH, map->map_mflags);
1505
1506		/* may be able to rebuild */
1507		map->map_mflags &= ~MF_IMPL_HASH;
1508		if (!rebuildaliases(map, TRUE))
1509			return FALSE;
1510		if (impl)
1511			return impl_map_open(map, O_RDONLY);
1512		else
1513			return db_map_open(map, O_RDONLY, mapclassname,
1514					   dbtype, openinfo);
1515	}
1516# endif /* !_FFR_REMOVE_AUTOREBUILD */
1517
1518	if (i != 0)
1519	{
1520		char *prob = "unsafe";
1521
1522		/* cannot open this map */
1523		if (i == ENOENT)
1524			prob = "missing";
1525		if (tTd(38, 2))
1526			dprintf("\t%s map file: %s\n", prob, errstring(i));
1527		errno = i;
1528		if (!bitset(MF_OPTIONAL, map->map_mflags))
1529			syserr("%s map \"%s\": %s map file %s",
1530				mapclassname, map->map_mname, prob, buf);
1531		return FALSE;
1532	}
1533	if (st.st_mode == ST_MODE_NOFILE)
1534		omode |= O_CREAT|O_EXCL;
1535
1536	map->map_lockfd = -1;
1537
1538# if LOCK_ON_OPEN
1539	if (mode == O_RDWR)
1540		omode |= O_TRUNC|O_EXLOCK;
1541	else
1542		omode |= O_SHLOCK;
1543# else /* LOCK_ON_OPEN */
1544	/*
1545	**  Pre-lock the file to avoid race conditions.  In particular,
1546	**  since dbopen returns NULL if the file is zero length, we
1547	**  must have a locked instance around the dbopen.
1548	*/
1549
1550	fd = open(buf, omode, DBMMODE);
1551	if (fd < 0)
1552	{
1553		if (!bitset(MF_OPTIONAL, map->map_mflags))
1554			syserr("db_map_open: cannot pre-open database %s", buf);
1555		return FALSE;
1556	}
1557
1558	/* make sure no baddies slipped in just before the open... */
1559	if (filechanged(buf, fd, &st))
1560	{
1561		save_errno = errno;
1562		(void) close(fd);
1563		errno = save_errno;
1564		syserr("db_map_open(%s): file changed after pre-open", buf);
1565		return FALSE;
1566	}
1567
1568	/* if new file, get the "before" bits for later filechanged check */
1569	if (st.st_mode == ST_MODE_NOFILE && fstat(fd, &st) < 0)
1570	{
1571		save_errno = errno;
1572		(void) close(fd);
1573		errno = save_errno;
1574		syserr("db_map_open(%s): cannot fstat pre-opened file",
1575			buf);
1576		return FALSE;
1577	}
1578
1579	/* actually lock the pre-opened file */
1580	if (!lockfile(fd, buf, NULL, mode == O_RDONLY ? LOCK_SH : LOCK_EX))
1581		syserr("db_map_open: cannot lock %s", buf);
1582
1583	/* set up mode bits for dbopen */
1584	if (mode == O_RDWR)
1585		omode |= O_TRUNC;
1586	omode &= ~(O_EXCL|O_CREAT);
1587# endif /* LOCK_ON_OPEN */
1588
1589# if DB_VERSION_MAJOR < 2
1590	db = dbopen(buf, omode, DBMMODE, dbtype, openinfo);
1591# else /* DB_VERSION_MAJOR < 2 */
1592	{
1593		int flags = 0;
1594#  if DB_VERSION_MAJOR > 2
1595		int ret;
1596#  endif /* DB_VERSION_MAJOR > 2 */
1597
1598		if (mode == O_RDONLY)
1599			flags |= DB_RDONLY;
1600		if (bitset(O_CREAT, omode))
1601			flags |= DB_CREATE;
1602		if (bitset(O_TRUNC, omode))
1603			flags |= DB_TRUNCATE;
1604
1605#  if !HASFLOCK && defined(DB_FCNTL_LOCKING)
1606		flags |= DB_FCNTL_LOCKING;
1607#  endif /* !HASFLOCK && defined(DB_FCNTL_LOCKING) */
1608
1609#  if DB_VERSION_MAJOR > 2
1610		ret = db_create(&db, NULL, 0);
1611#  ifdef DB_CACHE_SIZE
1612		if (ret == 0 && db != NULL)
1613		{
1614			ret = db->set_cachesize(db, 0, DB_CACHE_SIZE, 0);
1615			if (ret != 0)
1616			{
1617				(void) db->close(db, 0);
1618				db = NULL;
1619			}
1620		}
1621#  endif /* DB_CACHE_SIZE */
1622#  ifdef DB_HASH_NELEM
1623		if (dbtype == DB_HASH && ret == 0 && db != NULL)
1624		{
1625			ret = db->set_h_nelem(db, DB_HASH_NELEM);
1626			if (ret != 0)
1627			{
1628				(void) db->close(db, 0);
1629				db = NULL;
1630			}
1631		}
1632#  endif /* DB_HASH_NELEM */
1633		if (ret == 0 && db != NULL)
1634		{
1635			ret = db->open(db, buf, NULL, dbtype, flags, DBMMODE);
1636			if (ret != 0)
1637			{
1638#ifdef DB_OLD_VERSION
1639				if (ret == DB_OLD_VERSION)
1640					ret = EINVAL;
1641#endif /* DB_OLD_VERSION */
1642				(void) db->close(db, 0);
1643				db = NULL;
1644			}
1645		}
1646		errno = ret;
1647#  else /* DB_VERSION_MAJOR > 2 */
1648		errno = db_open(buf, dbtype, flags, DBMMODE,
1649				NULL, openinfo, &db);
1650#  endif /* DB_VERSION_MAJOR > 2 */
1651	}
1652# endif /* DB_VERSION_MAJOR < 2 */
1653	save_errno = errno;
1654
1655# if !LOCK_ON_OPEN
1656	if (mode == O_RDWR)
1657		map->map_lockfd = fd;
1658	else
1659		(void) close(fd);
1660# endif /* !LOCK_ON_OPEN */
1661
1662	if (db == NULL)
1663	{
1664		if (mode == O_RDONLY && bitset(MF_ALIAS, map->map_mflags) &&
1665		    aliaswait(map, ".db", FALSE))
1666			return TRUE;
1667# if !LOCK_ON_OPEN
1668		if (map->map_lockfd >= 0)
1669			(void) close(map->map_lockfd);
1670# endif /* !LOCK_ON_OPEN */
1671		errno = save_errno;
1672		if (!bitset(MF_OPTIONAL, map->map_mflags))
1673			syserr("Cannot open %s database %s",
1674				mapclassname, buf);
1675		return FALSE;
1676	}
1677
1678# if DB_VERSION_MAJOR < 2
1679	fd = db->fd(db);
1680# else /* DB_VERSION_MAJOR < 2 */
1681	fd = -1;
1682	errno = db->fd(db, &fd);
1683# endif /* DB_VERSION_MAJOR < 2 */
1684	if (filechanged(buf, fd, &st))
1685	{
1686		save_errno = errno;
1687# if DB_VERSION_MAJOR < 2
1688		(void) db->close(db);
1689# else /* DB_VERSION_MAJOR < 2 */
1690		errno = db->close(db, 0);
1691# endif /* DB_VERSION_MAJOR < 2 */
1692# if !LOCK_ON_OPEN
1693		if (map->map_lockfd >= 0)
1694			(void) close(map->map_lockfd);
1695# endif /* !LOCK_ON_OPEN */
1696		errno = save_errno;
1697		syserr("db_map_open(%s): file changed after open", buf);
1698		return FALSE;
1699	}
1700
1701	if (mode == O_RDWR)
1702		map->map_mflags |= MF_LOCKED;
1703# if LOCK_ON_OPEN
1704	if (fd >= 0 && mode == O_RDONLY)
1705	{
1706		(void) lockfile(fd, buf, NULL, LOCK_UN);
1707	}
1708# endif /* LOCK_ON_OPEN */
1709
1710	/* try to make sure that at least the database header is on disk */
1711	if (mode == O_RDWR)
1712	{
1713		(void) db->sync(db, 0);
1714		if (geteuid() == 0 && TrustedUid != 0)
1715		{
1716#  if HASFCHOWN
1717			if (fchown(fd, TrustedUid, -1) < 0)
1718			{
1719				int err = errno;
1720
1721				sm_syslog(LOG_ALERT, NOQID,
1722					  "ownership change on %s failed: %s",
1723					  buf, errstring(err));
1724				message("050 ownership change on %s failed: %s",
1725					buf, errstring(err));
1726			}
1727#  endif /* HASFCHOWN */
1728		}
1729	}
1730
1731	map->map_db2 = (ARBPTR_T) db;
1732
1733	/*
1734	**  Need to set map_mtime before the call to aliaswait()
1735	**  as aliaswait() will call map_lookup() which requires
1736	**  map_mtime to be set
1737	*/
1738
1739	if (fd >= 0 && fstat(fd, &st) >= 0)
1740		map->map_mtime = st.st_mtime;
1741
1742	if (mode == O_RDONLY && bitset(MF_ALIAS, map->map_mflags) &&
1743	    !aliaswait(map, ".db", TRUE))
1744		return FALSE;
1745	return TRUE;
1746}
1747
1748
1749/*
1750**  DB_MAP_LOOKUP -- look up a datum in a BTREE- or HASH-type map
1751*/
1752
1753char *
1754db_map_lookup(map, name, av, statp)
1755	MAP *map;
1756	char *name;
1757	char **av;
1758	int *statp;
1759{
1760	DBT key, val;
1761	register DB *db = (DB *) map->map_db2;
1762	int i;
1763	int st;
1764	int save_errno;
1765	int fd;
1766	struct stat stbuf;
1767	char keybuf[MAXNAME + 1];
1768	char buf[MAXNAME + 1];
1769
1770	memset(&key, '\0', sizeof key);
1771	memset(&val, '\0', sizeof val);
1772
1773	if (tTd(38, 20))
1774		dprintf("db_map_lookup(%s, %s)\n",
1775			map->map_mname, name);
1776
1777	i = strlen(map->map_file);
1778	if (i > MAXNAME)
1779		i = MAXNAME;
1780	(void) strlcpy(buf, map->map_file, i + 1);
1781	if (i > 3 && strcmp(&buf[i - 3], ".db") == 0)
1782		buf[i - 3] = '\0';
1783
1784	key.size = strlen(name);
1785	if (key.size > sizeof keybuf - 1)
1786		key.size = sizeof keybuf - 1;
1787	key.data = keybuf;
1788	memmove(keybuf, name, key.size);
1789	keybuf[key.size] = '\0';
1790	if (!bitset(MF_NOFOLDCASE, map->map_mflags))
1791		makelower(keybuf);
1792  lockdb:
1793# if DB_VERSION_MAJOR < 2
1794	fd = db->fd(db);
1795# else /* DB_VERSION_MAJOR < 2 */
1796	fd = -1;
1797	errno = db->fd(db, &fd);
1798# endif /* DB_VERSION_MAJOR < 2 */
1799	if (fd >= 0 && !bitset(MF_LOCKED, map->map_mflags))
1800		(void) lockfile(fd, buf, ".db", LOCK_SH);
1801	if (fd < 0 || fstat(fd, &stbuf) < 0 || stbuf.st_mtime > map->map_mtime)
1802	{
1803		/* Reopen the database to sync the cache */
1804		int omode = bitset(map->map_mflags, MF_WRITABLE) ? O_RDWR
1805								 : O_RDONLY;
1806
1807		if (fd >= 0 && !bitset(MF_LOCKED, map->map_mflags))
1808			(void) lockfile(fd, buf, ".db", LOCK_UN);
1809		map->map_mflags |= MF_CLOSING;
1810		map->map_class->map_close(map);
1811		map->map_mflags &= ~(MF_OPEN|MF_WRITABLE|MF_CLOSING);
1812		if (map->map_class->map_open(map, omode))
1813		{
1814			map->map_mflags |= MF_OPEN;
1815			map->map_pid = getpid();
1816			if ((omode && O_ACCMODE) == O_RDWR)
1817				map->map_mflags |= MF_WRITABLE;
1818			db = (DB *) map->map_db2;
1819			goto lockdb;
1820		}
1821		else
1822		{
1823			if (!bitset(MF_OPTIONAL, map->map_mflags))
1824			{
1825				extern MAPCLASS BogusMapClass;
1826
1827				*statp = EX_TEMPFAIL;
1828				map->map_class = &BogusMapClass;
1829				map->map_mflags |= MF_OPEN;
1830				map->map_pid = getpid();
1831				syserr("Cannot reopen DB database %s",
1832					map->map_file);
1833			}
1834			return NULL;
1835		}
1836	}
1837
1838	st = 1;
1839	if (bitset(MF_TRY0NULL, map->map_mflags))
1840	{
1841# if DB_VERSION_MAJOR < 2
1842		st = db->get(db, &key, &val, 0);
1843# else /* DB_VERSION_MAJOR < 2 */
1844		errno = db->get(db, NULL, &key, &val, 0);
1845		switch (errno)
1846		{
1847		  case DB_NOTFOUND:
1848		  case DB_KEYEMPTY:
1849			st = 1;
1850			break;
1851
1852		  case 0:
1853			st = 0;
1854			break;
1855
1856		  default:
1857			st = -1;
1858			break;
1859		}
1860# endif /* DB_VERSION_MAJOR < 2 */
1861		if (st == 0)
1862			map->map_mflags &= ~MF_TRY1NULL;
1863	}
1864	if (st != 0 && bitset(MF_TRY1NULL, map->map_mflags))
1865	{
1866		key.size++;
1867# if DB_VERSION_MAJOR < 2
1868		st = db->get(db, &key, &val, 0);
1869# else /* DB_VERSION_MAJOR < 2 */
1870		errno = db->get(db, NULL, &key, &val, 0);
1871		switch (errno)
1872		{
1873		  case DB_NOTFOUND:
1874		  case DB_KEYEMPTY:
1875			st = 1;
1876			break;
1877
1878		  case 0:
1879			st = 0;
1880			break;
1881
1882		  default:
1883			st = -1;
1884			break;
1885		}
1886# endif /* DB_VERSION_MAJOR < 2 */
1887		if (st == 0)
1888			map->map_mflags &= ~MF_TRY0NULL;
1889	}
1890	save_errno = errno;
1891	if (fd >= 0 && !bitset(MF_LOCKED, map->map_mflags))
1892		(void) lockfile(fd, buf, ".db", LOCK_UN);
1893	if (st != 0)
1894	{
1895		errno = save_errno;
1896		if (st < 0)
1897			syserr("db_map_lookup: get (%s)", name);
1898		return NULL;
1899	}
1900	if (bitset(MF_MATCHONLY, map->map_mflags))
1901		return map_rewrite(map, name, strlen(name), NULL);
1902	else
1903		return map_rewrite(map, val.data, val.size, av);
1904}
1905
1906
1907/*
1908**  DB_MAP_STORE -- store a datum in the NEWDB database
1909*/
1910
1911void
1912db_map_store(map, lhs, rhs)
1913	register MAP *map;
1914	char *lhs;
1915	char *rhs;
1916{
1917	int status;
1918	DBT key;
1919	DBT data;
1920	register DB *db = map->map_db2;
1921	char keybuf[MAXNAME + 1];
1922
1923	memset(&key, '\0', sizeof key);
1924	memset(&data, '\0', sizeof data);
1925
1926	if (tTd(38, 12))
1927		dprintf("db_map_store(%s, %s, %s)\n",
1928			map->map_mname, lhs, rhs);
1929
1930	key.size = strlen(lhs);
1931	key.data = lhs;
1932	if (!bitset(MF_NOFOLDCASE, map->map_mflags))
1933	{
1934		if (key.size > sizeof keybuf - 1)
1935			key.size = sizeof keybuf - 1;
1936		memmove(keybuf, key.data, key.size);
1937		keybuf[key.size] = '\0';
1938		makelower(keybuf);
1939		key.data = keybuf;
1940	}
1941
1942	data.size = strlen(rhs);
1943	data.data = rhs;
1944
1945	if (bitset(MF_INCLNULL, map->map_mflags))
1946	{
1947		key.size++;
1948		data.size++;
1949	}
1950
1951# if DB_VERSION_MAJOR < 2
1952	status = db->put(db, &key, &data, R_NOOVERWRITE);
1953# else /* DB_VERSION_MAJOR < 2 */
1954	errno = db->put(db, NULL, &key, &data, DB_NOOVERWRITE);
1955	switch (errno)
1956	{
1957	  case DB_KEYEXIST:
1958		status = 1;
1959		break;
1960
1961	  case 0:
1962		status = 0;
1963		break;
1964
1965	  default:
1966		status = -1;
1967		break;
1968	}
1969# endif /* DB_VERSION_MAJOR < 2 */
1970	if (status > 0)
1971	{
1972		if (!bitset(MF_APPEND, map->map_mflags))
1973			message("050 Warning: duplicate alias name %s", lhs);
1974		else
1975		{
1976			static char *buf = NULL;
1977			static int bufsiz = 0;
1978			DBT old;
1979
1980			memset(&old, '\0', sizeof old);
1981
1982			old.data = db_map_lookup(map, key.data,
1983						 (char **)NULL, &status);
1984			if (old.data != NULL)
1985			{
1986				old.size = strlen(old.data);
1987				if (data.size + old.size + 2 > (size_t)bufsiz)
1988				{
1989					if (buf != NULL)
1990						sm_free(buf);
1991					bufsiz = data.size + old.size + 2;
1992					buf = xalloc(bufsiz);
1993				}
1994				snprintf(buf, bufsiz, "%s,%s",
1995					(char *) data.data, (char *) old.data);
1996				data.size = data.size + old.size + 1;
1997				data.data = buf;
1998				if (tTd(38, 9))
1999					dprintf("db_map_store append=%s\n",
2000						(char *) data.data);
2001			}
2002		}
2003# if DB_VERSION_MAJOR < 2
2004		status = db->put(db, &key, &data, 0);
2005# else /* DB_VERSION_MAJOR < 2 */
2006		status = errno = db->put(db, NULL, &key, &data, 0);
2007# endif /* DB_VERSION_MAJOR < 2 */
2008	}
2009	if (status != 0)
2010		syserr("readaliases: db put (%s)", lhs);
2011}
2012
2013
2014/*
2015**  DB_MAP_CLOSE -- add distinguished entries and close the database
2016*/
2017
2018void
2019db_map_close(map)
2020	MAP *map;
2021{
2022	register DB *db = map->map_db2;
2023
2024	if (tTd(38, 9))
2025		dprintf("db_map_close(%s, %s, %lx)\n",
2026			map->map_mname, map->map_file, map->map_mflags);
2027
2028	if (bitset(MF_WRITABLE, map->map_mflags))
2029	{
2030		/* write out the distinguished alias */
2031		db_map_store(map, "@", "@");
2032	}
2033
2034	(void) db->sync(db, 0);
2035
2036# if !LOCK_ON_OPEN
2037	if (map->map_lockfd >= 0)
2038		(void) close(map->map_lockfd);
2039# endif /* !LOCK_ON_OPEN */
2040
2041# if DB_VERSION_MAJOR < 2
2042	if (db->close(db) != 0)
2043# else /* DB_VERSION_MAJOR < 2 */
2044	/*
2045	**  Berkeley DB can use internal shared memory
2046	**  locking for its memory pool.  Closing a map
2047	**  opened by another process will interfere
2048	**  with the shared memory and locks of the parent
2049	**  process leaving things in a bad state.
2050	*/
2051
2052	/*
2053	**  If this map was not opened by the current
2054	**  process, do not close the map but recover
2055	**  the file descriptor.
2056	*/
2057	if (map->map_pid != getpid())
2058	{
2059		int fd = -1;
2060
2061		errno = db->fd(db, &fd);
2062		if (fd >= 0)
2063			(void) close(fd);
2064		return;
2065	}
2066
2067	if ((errno = db->close(db, 0)) != 0)
2068# endif /* DB_VERSION_MAJOR < 2 */
2069		syserr("db_map_close(%s, %s, %lx): db close failure",
2070			map->map_mname, map->map_file, map->map_mflags);
2071}
2072#endif /* NEWDB */
2073/*
2074**  NIS Modules
2075*/
2076
2077#ifdef NIS
2078
2079# ifndef YPERR_BUSY
2080#  define YPERR_BUSY	16
2081# endif /* ! YPERR_BUSY */
2082
2083/*
2084**  NIS_MAP_OPEN -- open DBM map
2085*/
2086
2087bool
2088nis_map_open(map, mode)
2089	MAP *map;
2090	int mode;
2091{
2092	int yperr;
2093	register char *p;
2094	auto char *vp;
2095	auto int vsize;
2096
2097	if (tTd(38, 2))
2098		dprintf("nis_map_open(%s, %s, %d)\n",
2099			map->map_mname, map->map_file, mode);
2100
2101	mode &= O_ACCMODE;
2102	if (mode != O_RDONLY)
2103	{
2104		/* issue a pseudo-error message */
2105# ifdef ENOSYS
2106		errno = ENOSYS;
2107# else /* ENOSYS */
2108#  ifdef EFTYPE
2109		errno = EFTYPE;
2110#  else /* EFTYPE */
2111		errno = ENXIO;
2112#  endif /* EFTYPE */
2113# endif /* ENOSYS */
2114		return FALSE;
2115	}
2116
2117	p = strchr(map->map_file, '@');
2118	if (p != NULL)
2119	{
2120		*p++ = '\0';
2121		if (*p != '\0')
2122			map->map_domain = p;
2123	}
2124
2125	if (*map->map_file == '\0')
2126		map->map_file = "mail.aliases";
2127
2128	if (map->map_domain == NULL)
2129	{
2130		yperr = yp_get_default_domain(&map->map_domain);
2131		if (yperr != 0)
2132		{
2133			if (!bitset(MF_OPTIONAL, map->map_mflags))
2134				syserr("421 4.3.5 NIS map %s specified, but NIS not running",
2135				       map->map_file);
2136			return FALSE;
2137		}
2138	}
2139
2140	/* check to see if this map actually exists */
2141	vp = NULL;
2142	yperr = yp_match(map->map_domain, map->map_file, "@", 1,
2143			&vp, &vsize);
2144	if (tTd(38, 10))
2145		dprintf("nis_map_open: yp_match(@, %s, %s) => %s\n",
2146			map->map_domain, map->map_file, yperr_string(yperr));
2147	if (vp != NULL)
2148		sm_free(vp);
2149
2150	if (yperr == 0 || yperr == YPERR_KEY || yperr == YPERR_BUSY)
2151	{
2152		/*
2153		**  We ought to be calling aliaswait() here if this is an
2154		**  alias file, but powerful HP-UX NIS servers  apparently
2155		**  don't insert the @:@ token into the alias map when it
2156		**  is rebuilt, so aliaswait() just hangs.  I hate HP-UX.
2157		*/
2158
2159# if 0
2160		if (!bitset(MF_ALIAS, map->map_mflags) ||
2161		    aliaswait(map, NULL, TRUE))
2162# endif /* 0 */
2163			return TRUE;
2164	}
2165
2166	if (!bitset(MF_OPTIONAL, map->map_mflags))
2167	{
2168		syserr("421 4.0.0 Cannot bind to map %s in domain %s: %s",
2169			map->map_file, map->map_domain, yperr_string(yperr));
2170	}
2171
2172	return FALSE;
2173}
2174
2175
2176/*
2177**  NIS_MAP_LOOKUP -- look up a datum in a NIS map
2178*/
2179
2180/* ARGSUSED3 */
2181char *
2182nis_map_lookup(map, name, av, statp)
2183	MAP *map;
2184	char *name;
2185	char **av;
2186	int *statp;
2187{
2188	char *vp;
2189	auto int vsize;
2190	int buflen;
2191	int yperr;
2192	char keybuf[MAXNAME + 1];
2193
2194	if (tTd(38, 20))
2195		dprintf("nis_map_lookup(%s, %s)\n",
2196			map->map_mname, name);
2197
2198	buflen = strlen(name);
2199	if (buflen > sizeof keybuf - 1)
2200		buflen = sizeof keybuf - 1;
2201	memmove(keybuf, name, buflen);
2202	keybuf[buflen] = '\0';
2203	if (!bitset(MF_NOFOLDCASE, map->map_mflags))
2204		makelower(keybuf);
2205	yperr = YPERR_KEY;
2206	vp = NULL;
2207	if (bitset(MF_TRY0NULL, map->map_mflags))
2208	{
2209		yperr = yp_match(map->map_domain, map->map_file, keybuf, buflen,
2210			     &vp, &vsize);
2211		if (yperr == 0)
2212			map->map_mflags &= ~MF_TRY1NULL;
2213	}
2214	if (yperr == YPERR_KEY && bitset(MF_TRY1NULL, map->map_mflags))
2215	{
2216		if (vp != NULL)
2217		{
2218			sm_free(vp);
2219			vp = NULL;
2220		}
2221		buflen++;
2222		yperr = yp_match(map->map_domain, map->map_file, keybuf, buflen,
2223			     &vp, &vsize);
2224		if (yperr == 0)
2225			map->map_mflags &= ~MF_TRY0NULL;
2226	}
2227	if (yperr != 0)
2228	{
2229		if (yperr != YPERR_KEY && yperr != YPERR_BUSY)
2230			map->map_mflags &= ~(MF_VALID|MF_OPEN);
2231		if (vp != NULL)
2232			sm_free(vp);
2233		return NULL;
2234	}
2235	if (bitset(MF_MATCHONLY, map->map_mflags))
2236		return map_rewrite(map, name, strlen(name), NULL);
2237	else
2238	{
2239		char *ret;
2240
2241		ret = map_rewrite(map, vp, vsize, av);
2242		if (vp != NULL)
2243			sm_free(vp);
2244		return ret;
2245	}
2246}
2247
2248
2249/*
2250**  NIS_GETCANONNAME -- look up canonical name in NIS
2251*/
2252
2253static bool
2254nis_getcanonname(name, hbsize, statp)
2255	char *name;
2256	int hbsize;
2257	int *statp;
2258{
2259	char *vp;
2260	auto int vsize;
2261	int keylen;
2262	int yperr;
2263	static bool try0null = TRUE;
2264	static bool try1null = TRUE;
2265	static char *yp_domain = NULL;
2266	char host_record[MAXLINE];
2267	char cbuf[MAXNAME];
2268	char nbuf[MAXNAME + 1];
2269
2270	if (tTd(38, 20))
2271		dprintf("nis_getcanonname(%s)\n", name);
2272
2273	if (strlcpy(nbuf, name, sizeof nbuf) >= sizeof nbuf)
2274	{
2275		*statp = EX_UNAVAILABLE;
2276		return FALSE;
2277	}
2278	(void) shorten_hostname(nbuf);
2279	keylen = strlen(nbuf);
2280
2281	if (yp_domain == NULL)
2282		(void) yp_get_default_domain(&yp_domain);
2283	makelower(nbuf);
2284	yperr = YPERR_KEY;
2285	vp = NULL;
2286	if (try0null)
2287	{
2288		yperr = yp_match(yp_domain, "hosts.byname", nbuf, keylen,
2289			     &vp, &vsize);
2290		if (yperr == 0)
2291			try1null = FALSE;
2292	}
2293	if (yperr == YPERR_KEY && try1null)
2294	{
2295		if (vp != NULL)
2296		{
2297			sm_free(vp);
2298			vp = NULL;
2299		}
2300		keylen++;
2301		yperr = yp_match(yp_domain, "hosts.byname", nbuf, keylen,
2302			     &vp, &vsize);
2303		if (yperr == 0)
2304			try0null = FALSE;
2305	}
2306	if (yperr != 0)
2307	{
2308		if (yperr == YPERR_KEY)
2309			*statp = EX_NOHOST;
2310		else if (yperr == YPERR_BUSY)
2311			*statp = EX_TEMPFAIL;
2312		else
2313			*statp = EX_UNAVAILABLE;
2314		if (vp != NULL)
2315			sm_free(vp);
2316		return FALSE;
2317	}
2318	(void) strlcpy(host_record, vp, sizeof host_record);
2319	sm_free(vp);
2320	if (tTd(38, 44))
2321		dprintf("got record `%s'\n", host_record);
2322	if (!extract_canonname(nbuf, NULL, host_record, cbuf, sizeof cbuf))
2323	{
2324		/* this should not happen, but.... */
2325		*statp = EX_NOHOST;
2326		return FALSE;
2327	}
2328	if (hbsize <= strlen(cbuf))
2329	{
2330		*statp = EX_UNAVAILABLE;
2331		return FALSE;
2332	}
2333	(void) strlcpy(name, cbuf, hbsize);
2334	*statp = EX_OK;
2335	return TRUE;
2336}
2337
2338#endif /* NIS */
2339/*
2340**  NISPLUS Modules
2341**
2342**	This code donated by Sun Microsystems.
2343*/
2344
2345#ifdef NISPLUS
2346
2347# undef NIS		/* symbol conflict in nis.h */
2348# undef T_UNSPEC	/* symbol conflict in nis.h -> ... -> sys/tiuser.h */
2349# include <rpcsvc/nis.h>
2350# include <rpcsvc/nislib.h>
2351
2352# define EN_col(col)	zo_data.objdata_u.en_data.en_cols.en_cols_val[(col)].ec_value.ec_value_val
2353# define COL_NAME(res,i)	((res->objects.objects_val)->TA_data.ta_cols.ta_cols_val)[i].tc_name
2354# define COL_MAX(res)	((res->objects.objects_val)->TA_data.ta_cols.ta_cols_len)
2355# define PARTIAL_NAME(x)	((x)[strlen(x) - 1] != '.')
2356
2357/*
2358**  NISPLUS_MAP_OPEN -- open nisplus table
2359*/
2360
2361bool
2362nisplus_map_open(map, mode)
2363	MAP *map;
2364	int mode;
2365{
2366	nis_result *res = NULL;
2367	int retry_cnt, max_col, i;
2368	char qbuf[MAXLINE + NIS_MAXNAMELEN];
2369
2370	if (tTd(38, 2))
2371		dprintf("nisplus_map_open(%s, %s, %d)\n",
2372			map->map_mname, map->map_file, mode);
2373
2374	mode &= O_ACCMODE;
2375	if (mode != O_RDONLY)
2376	{
2377		errno = EPERM;
2378		return FALSE;
2379	}
2380
2381	if (*map->map_file == '\0')
2382		map->map_file = "mail_aliases.org_dir";
2383
2384	if (PARTIAL_NAME(map->map_file) && map->map_domain == NULL)
2385	{
2386		/* set default NISPLUS Domain to $m */
2387		map->map_domain = newstr(nisplus_default_domain());
2388		if (tTd(38, 2))
2389			dprintf("nisplus_map_open(%s): using domain %s\n",
2390				map->map_file, map->map_domain);
2391	}
2392	if (!PARTIAL_NAME(map->map_file))
2393	{
2394		map->map_domain = newstr("");
2395		snprintf(qbuf, sizeof qbuf, "%s", map->map_file);
2396	}
2397	else
2398	{
2399		/* check to see if this map actually exists */
2400		snprintf(qbuf, sizeof qbuf, "%s.%s",
2401			map->map_file, map->map_domain);
2402	}
2403
2404	retry_cnt = 0;
2405	while (res == NULL || res->status != NIS_SUCCESS)
2406	{
2407		res = nis_lookup(qbuf, FOLLOW_LINKS);
2408		switch (res->status)
2409		{
2410		  case NIS_SUCCESS:
2411			break;
2412
2413		  case NIS_TRYAGAIN:
2414		  case NIS_RPCERROR:
2415		  case NIS_NAMEUNREACHABLE:
2416			if (retry_cnt++ > 4)
2417			{
2418				errno = EAGAIN;
2419				return FALSE;
2420			}
2421			/* try not to overwhelm hosed server */
2422			sleep(2);
2423			break;
2424
2425		  default:		/* all other nisplus errors */
2426# if 0
2427			if (!bitset(MF_OPTIONAL, map->map_mflags))
2428				syserr("421 4.0.0 Cannot find table %s.%s: %s",
2429					map->map_file, map->map_domain,
2430					nis_sperrno(res->status));
2431# endif /* 0 */
2432			errno = EAGAIN;
2433			return FALSE;
2434		}
2435	}
2436
2437	if (NIS_RES_NUMOBJ(res) != 1 ||
2438	    (NIS_RES_OBJECT(res)->zo_data.zo_type != TABLE_OBJ))
2439	{
2440		if (tTd(38, 10))
2441			dprintf("nisplus_map_open: %s is not a table\n", qbuf);
2442# if 0
2443		if (!bitset(MF_OPTIONAL, map->map_mflags))
2444			syserr("421 4.0.0 %s.%s: %s is not a table",
2445				map->map_file, map->map_domain,
2446				nis_sperrno(res->status));
2447# endif /* 0 */
2448		errno = EBADF;
2449		return FALSE;
2450	}
2451	/* default key column is column 0 */
2452	if (map->map_keycolnm == NULL)
2453		map->map_keycolnm = newstr(COL_NAME(res,0));
2454
2455	max_col = COL_MAX(res);
2456
2457	/* verify the key column exist */
2458	for (i = 0; i< max_col; i++)
2459	{
2460		if (strcmp(map->map_keycolnm, COL_NAME(res,i)) == 0)
2461			break;
2462	}
2463	if (i == max_col)
2464	{
2465		if (tTd(38, 2))
2466			dprintf("nisplus_map_open(%s): can not find key column %s\n",
2467				map->map_file, map->map_keycolnm);
2468		errno = ENOENT;
2469		return FALSE;
2470	}
2471
2472	/* default value column is the last column */
2473	if (map->map_valcolnm == NULL)
2474	{
2475		map->map_valcolno = max_col - 1;
2476		return TRUE;
2477	}
2478
2479	for (i = 0; i< max_col; i++)
2480	{
2481		if (strcmp(map->map_valcolnm, COL_NAME(res,i)) == 0)
2482		{
2483			map->map_valcolno = i;
2484			return TRUE;
2485		}
2486	}
2487
2488	if (tTd(38, 2))
2489		dprintf("nisplus_map_open(%s): can not find column %s\n",
2490			map->map_file, map->map_keycolnm);
2491	errno = ENOENT;
2492	return FALSE;
2493}
2494
2495
2496/*
2497**  NISPLUS_MAP_LOOKUP -- look up a datum in a NISPLUS table
2498*/
2499
2500char *
2501nisplus_map_lookup(map, name, av, statp)
2502	MAP *map;
2503	char *name;
2504	char **av;
2505	int *statp;
2506{
2507	char *p;
2508	auto int vsize;
2509	char *skp;
2510	int skleft;
2511	char search_key[MAXNAME + 4];
2512	char qbuf[MAXLINE + NIS_MAXNAMELEN];
2513	nis_result *result;
2514
2515	if (tTd(38, 20))
2516		dprintf("nisplus_map_lookup(%s, %s)\n",
2517			map->map_mname, name);
2518
2519	if (!bitset(MF_OPEN, map->map_mflags))
2520	{
2521		if (nisplus_map_open(map, O_RDONLY))
2522		{
2523			map->map_mflags |= MF_OPEN;
2524			map->map_pid = getpid();
2525		}
2526		else
2527		{
2528			*statp = EX_UNAVAILABLE;
2529			return NULL;
2530		}
2531	}
2532
2533	/*
2534	**  Copy the name to the key buffer, escaping double quote characters
2535	**  by doubling them and quoting "]" and "," to avoid having the
2536	**  NIS+ parser choke on them.
2537	*/
2538
2539	skleft = sizeof search_key - 4;
2540	skp = search_key;
2541	for (p = name; *p != '\0' && skleft > 0; p++)
2542	{
2543		switch (*p)
2544		{
2545		  case ']':
2546		  case ',':
2547			/* quote the character */
2548			*skp++ = '"';
2549			*skp++ = *p;
2550			*skp++ = '"';
2551			skleft -= 3;
2552			break;
2553
2554		  case '"':
2555			/* double the quote */
2556			*skp++ = '"';
2557			skleft--;
2558			/* FALLTHROUGH */
2559
2560		  default:
2561			*skp++ = *p;
2562			skleft--;
2563			break;
2564		}
2565	}
2566	*skp = '\0';
2567	if (!bitset(MF_NOFOLDCASE, map->map_mflags))
2568		makelower(search_key);
2569
2570	/* construct the query */
2571	if (PARTIAL_NAME(map->map_file))
2572		snprintf(qbuf, sizeof qbuf, "[%s=%s],%s.%s",
2573			map->map_keycolnm, search_key, map->map_file,
2574			map->map_domain);
2575	else
2576		snprintf(qbuf, sizeof qbuf, "[%s=%s],%s",
2577			map->map_keycolnm, search_key, map->map_file);
2578
2579	if (tTd(38, 20))
2580		dprintf("qbuf=%s\n", qbuf);
2581	result = nis_list(qbuf, FOLLOW_LINKS | FOLLOW_PATH, NULL, NULL);
2582	if (result->status == NIS_SUCCESS)
2583	{
2584		int count;
2585		char *str;
2586
2587		if ((count = NIS_RES_NUMOBJ(result)) != 1)
2588		{
2589			if (LogLevel > 10)
2590				sm_syslog(LOG_WARNING, CurEnv->e_id,
2591					  "%s: lookup error, expected 1 entry, got %d",
2592					  map->map_file, count);
2593
2594			/* ignore second entry */
2595			if (tTd(38, 20))
2596				dprintf("nisplus_map_lookup(%s), got %d entries, additional entries ignored\n",
2597					name, count);
2598		}
2599
2600		p = ((NIS_RES_OBJECT(result))->EN_col(map->map_valcolno));
2601		/* set the length of the result */
2602		if (p == NULL)
2603			p = "";
2604		vsize = strlen(p);
2605		if (tTd(38, 20))
2606			dprintf("nisplus_map_lookup(%s), found %s\n",
2607				name, p);
2608		if (bitset(MF_MATCHONLY, map->map_mflags))
2609			str = map_rewrite(map, name, strlen(name), NULL);
2610		else
2611			str = map_rewrite(map, p, vsize, av);
2612		nis_freeresult(result);
2613		*statp = EX_OK;
2614		return str;
2615	}
2616	else
2617	{
2618		if (result->status == NIS_NOTFOUND)
2619			*statp = EX_NOTFOUND;
2620		else if (result->status == NIS_TRYAGAIN)
2621			*statp = EX_TEMPFAIL;
2622		else
2623		{
2624			*statp = EX_UNAVAILABLE;
2625			map->map_mflags &= ~(MF_VALID|MF_OPEN);
2626		}
2627	}
2628	if (tTd(38, 20))
2629		dprintf("nisplus_map_lookup(%s), failed\n", name);
2630	nis_freeresult(result);
2631	return NULL;
2632}
2633
2634
2635
2636/*
2637**  NISPLUS_GETCANONNAME -- look up canonical name in NIS+
2638*/
2639
2640static bool
2641nisplus_getcanonname(name, hbsize, statp)
2642	char *name;
2643	int hbsize;
2644	int *statp;
2645{
2646	char *vp;
2647	auto int vsize;
2648	nis_result *result;
2649	char *p;
2650	char nbuf[MAXNAME + 1];
2651	char qbuf[MAXLINE + NIS_MAXNAMELEN];
2652
2653	if (strlen(name) >= sizeof nbuf)
2654	{
2655		*statp = EX_UNAVAILABLE;
2656		return FALSE;
2657	}
2658	(void) strlcpy(nbuf, name, sizeof nbuf);
2659	(void) shorten_hostname(nbuf);
2660
2661	p = strchr(nbuf, '.');
2662	if (p == NULL)
2663	{
2664		/* single token */
2665		snprintf(qbuf, sizeof qbuf, "[name=%s],hosts.org_dir", nbuf);
2666	}
2667	else if (p[1] != '\0')
2668	{
2669		/* multi token -- take only first token in nbuf */
2670		*p = '\0';
2671		snprintf(qbuf, sizeof qbuf, "[name=%s],hosts.org_dir.%s",
2672			nbuf, &p[1]);
2673	}
2674	else
2675	{
2676		*statp = EX_NOHOST;
2677		return FALSE;
2678	}
2679
2680	if (tTd(38, 20))
2681		dprintf("\nnisplus_getcanoname(%s), qbuf=%s\n",
2682			name, qbuf);
2683
2684	result = nis_list(qbuf, EXPAND_NAME|FOLLOW_LINKS|FOLLOW_PATH,
2685		NULL, NULL);
2686
2687	if (result->status == NIS_SUCCESS)
2688	{
2689		int count;
2690		char *domain;
2691
2692		if ((count = NIS_RES_NUMOBJ(result)) != 1)
2693		{
2694			if (LogLevel > 10)
2695				sm_syslog(LOG_WARNING, CurEnv->e_id,
2696					  "nisplus_getcanonname: lookup error, expected 1 entry, got %d",
2697					  count);
2698
2699			/* ignore second entry */
2700			if (tTd(38, 20))
2701				dprintf("nisplus_getcanoname(%s), got %d entries, all but first ignored\n",
2702					name, count);
2703		}
2704
2705		if (tTd(38, 20))
2706			dprintf("nisplus_getcanoname(%s), found in directory \"%s\"\n",
2707				name, (NIS_RES_OBJECT(result))->zo_domain);
2708
2709
2710		vp = ((NIS_RES_OBJECT(result))->EN_col(0));
2711		vsize = strlen(vp);
2712		if (tTd(38, 20))
2713			dprintf("nisplus_getcanonname(%s), found %s\n",
2714				name, vp);
2715		if (strchr(vp, '.') != NULL)
2716		{
2717			domain = "";
2718		}
2719		else
2720		{
2721			domain = macvalue('m', CurEnv);
2722			if (domain == NULL)
2723				domain = "";
2724		}
2725		if (hbsize > vsize + (int) strlen(domain) + 1)
2726		{
2727			if (domain[0] == '\0')
2728				(void) strlcpy(name, vp, hbsize);
2729			else
2730				snprintf(name, hbsize, "%s.%s", vp, domain);
2731			*statp = EX_OK;
2732		}
2733		else
2734			*statp = EX_NOHOST;
2735		nis_freeresult(result);
2736		return TRUE;
2737	}
2738	else
2739	{
2740		if (result->status == NIS_NOTFOUND)
2741			*statp = EX_NOHOST;
2742		else if (result->status == NIS_TRYAGAIN)
2743			*statp = EX_TEMPFAIL;
2744		else
2745			*statp = EX_UNAVAILABLE;
2746	}
2747	if (tTd(38, 20))
2748		dprintf("nisplus_getcanonname(%s), failed, status=%d, nsw_stat=%d\n",
2749			name, result->status, *statp);
2750	nis_freeresult(result);
2751	return FALSE;
2752}
2753
2754char *
2755nisplus_default_domain()
2756{
2757	static char default_domain[MAXNAME + 1] = "";
2758	char *p;
2759
2760	if (default_domain[0] != '\0')
2761		return default_domain;
2762
2763	p = nis_local_directory();
2764	snprintf(default_domain, sizeof default_domain, "%s", p);
2765	return default_domain;
2766}
2767
2768#endif /* NISPLUS */
2769/*
2770**  LDAP Modules
2771*/
2772
2773/*
2774**  LDAPMAP_DEQUOTE - helper routine for ldapmap_parseargs
2775*/
2776
2777#if defined(LDAPMAP) || defined(PH_MAP)
2778
2779# ifdef PH_MAP
2780#  define ph_map_dequote ldapmap_dequote
2781# endif /* PH_MAP */
2782
2783char *
2784ldapmap_dequote(str)
2785	char *str;
2786{
2787	char *p;
2788	char *start;
2789
2790	if (str == NULL)
2791		return NULL;
2792
2793	p = str;
2794	if (*p == '"')
2795	{
2796		/* Should probably swallow initial whitespace here */
2797		start = ++p;
2798	}
2799	else
2800		return str;
2801	while (*p != '"' && *p != '\0')
2802		p++;
2803	if (*p != '\0')
2804		*p = '\0';
2805	return start;
2806}
2807#endif /* defined(LDAPMAP) || defined(PH_MAP) */
2808
2809#ifdef LDAPMAP
2810
2811LDAPMAP_STRUCT *LDAPDefaults = NULL;
2812
2813/*
2814**  LDAPMAP_OPEN -- open LDAP map
2815**
2816**	Connect to the LDAP server.  Re-use existing connections since a
2817**	single server connection to a host (with the same host, port,
2818**	bind DN, and secret) can answer queries for multiple maps.
2819*/
2820
2821bool
2822ldapmap_open(map, mode)
2823	MAP *map;
2824	int mode;
2825{
2826	LDAPMAP_STRUCT *lmap;
2827	STAB *s;
2828
2829	if (tTd(38, 2))
2830		dprintf("ldapmap_open(%s, %d): ", map->map_mname, mode);
2831
2832	mode &= O_ACCMODE;
2833
2834	/* sendmail doesn't have the ability to write to LDAP (yet) */
2835	if (mode != O_RDONLY)
2836	{
2837		/* issue a pseudo-error message */
2838# ifdef ENOSYS
2839		errno = ENOSYS;
2840# else /* ENOSYS */
2841#  ifdef EFTYPE
2842		errno = EFTYPE;
2843#  else /* EFTYPE */
2844		errno = ENXIO;
2845#  endif /* EFTYPE */
2846# endif /* ENOSYS */
2847		return FALSE;
2848	}
2849
2850	/* Comma separate if used as an alias file */
2851	if (map->map_coldelim == '\0' && bitset(MF_ALIAS, map->map_mflags))
2852		map->map_coldelim = ',';
2853
2854	lmap = (LDAPMAP_STRUCT *) map->map_db1;
2855
2856	s = ldapmap_findconn(lmap);
2857	if (s->s_lmap != NULL)
2858	{
2859		/* Already have a connection open to this LDAP server */
2860		lmap->ldap_ld = ((LDAPMAP_STRUCT *)s->s_lmap->map_db1)->ldap_ld;
2861
2862		/* Add this map as head of linked list */
2863		lmap->ldap_next = s->s_lmap;
2864		s->s_lmap = map;
2865
2866		if (tTd(38, 2))
2867			dprintf("using cached connection\n");
2868		return TRUE;
2869	}
2870
2871	if (tTd(38, 2))
2872		dprintf("opening new connection\n");
2873
2874	/* No connection yet, connect */
2875	if (!ldapmap_start(map))
2876		return FALSE;
2877
2878	/* Save connection for reuse */
2879	s->s_lmap = map;
2880	return TRUE;
2881}
2882
2883/*
2884**  LDAPMAP_START -- actually connect to an LDAP server
2885**
2886**	Parameters:
2887**		map -- the map being opened.
2888**
2889**	Returns:
2890**		TRUE if connection is successful, FALSE otherwise.
2891**
2892**	Side Effects:
2893**		Populates lmap->ldap_ld.
2894*/
2895
2896static jmp_buf	LDAPTimeout;
2897
2898static bool
2899ldapmap_start(map)
2900	MAP *map;
2901{
2902	register int bind_result;
2903	int save_errno;
2904	register EVENT *ev = NULL;
2905	LDAPMAP_STRUCT *lmap;
2906	LDAP *ld;
2907
2908	if (tTd(38, 2))
2909		dprintf("ldapmap_start(%s)\n", map->map_mname);
2910
2911	lmap = (LDAPMAP_STRUCT *) map->map_db1;
2912
2913	if (tTd(38,9))
2914		dprintf("ldapmap_start(%s, %d)\n",
2915			lmap->ldap_host == NULL ? "localhost" : lmap->ldap_host,
2916			lmap->ldap_port);
2917
2918# if USE_LDAP_INIT
2919	ld = ldap_init(lmap->ldap_host, lmap->ldap_port);
2920	save_errno = errno;
2921# else /* USE_LDAP_INIT */
2922	/*
2923	**  If using ldap_open(), the actual connection to the server
2924	**  happens now so we need the timeout here.  For ldap_init(),
2925	**  the connection happens at bind time.
2926	*/
2927
2928	/* set the timeout */
2929	if (lmap->ldap_timeout.tv_sec != 0)
2930	{
2931		if (setjmp(LDAPTimeout) != 0)
2932		{
2933			if (LogLevel > 1)
2934				sm_syslog(LOG_NOTICE, CurEnv->e_id,
2935					  "timeout conning to LDAP server %.100s",
2936					  lmap->ldap_host == NULL ? "localhost" : lmap->ldap_host);
2937			return FALSE;
2938		}
2939		ev = setevent(lmap->ldap_timeout.tv_sec, ldaptimeout, 0);
2940	}
2941
2942	ld = ldap_open(lmap->ldap_host, lmap->ldap_port);
2943	save_errno = errno;
2944
2945	/* clear the event if it has not sprung */
2946	if (ev != NULL)
2947		clrevent(ev);
2948# endif /* USE_LDAP_INIT */
2949
2950	errno = save_errno;
2951	if (ld == NULL)
2952	{
2953		if (!bitset(MF_OPTIONAL, map->map_mflags))
2954		{
2955			if (bitset(MF_NODEFER, map->map_mflags))
2956				syserr("%s failed to %s in map %s",
2957# if USE_LDAP_INIT
2958				       "ldap_init",
2959# else /* USE_LDAP_INIT */
2960				       "ldap_open",
2961# endif /* USE_LDAP_INIT */
2962				       lmap->ldap_host == NULL ? "localhost"
2963							       : lmap->ldap_host,
2964				       map->map_mname);
2965			else
2966				syserr("421 4.0.0 %s failed to %s in map %s",
2967# if USE_LDAP_INIT
2968				       "ldap_init",
2969# else /* USE_LDAP_INIT */
2970				       "ldap_open",
2971# endif /* USE_LDAP_INIT */
2972				       lmap->ldap_host == NULL ? "localhost"
2973							       : lmap->ldap_host,
2974				       map->map_mname);
2975		}
2976		return FALSE;
2977	}
2978
2979	ldapmap_setopts(ld, lmap);
2980
2981# if USE_LDAP_INIT
2982	/*
2983	**  If using ldap_init(), the actual connection to the server
2984	**  happens at ldap_bind_s() so we need the timeout here.
2985	*/
2986
2987	/* set the timeout */
2988	if (lmap->ldap_timeout.tv_sec != 0)
2989	{
2990		if (setjmp(LDAPTimeout) != 0)
2991		{
2992			if (LogLevel > 1)
2993				sm_syslog(LOG_NOTICE, CurEnv->e_id,
2994					  "timeout conning to LDAP server %.100s",
2995					  lmap->ldap_host == NULL ? "localhost"
2996								  : lmap->ldap_host);
2997			return FALSE;
2998		}
2999		ev = setevent(lmap->ldap_timeout.tv_sec, ldaptimeout, 0);
3000	}
3001# endif /* USE_LDAP_INIT */
3002
3003# ifdef LDAP_AUTH_KRBV4
3004	if (lmap->ldap_method == LDAP_AUTH_KRBV4 &&
3005	    lmap->ldap_secret != NULL)
3006	{
3007		/*
3008		**  Need to put ticket in environment here instead of
3009		**  during parseargs as there may be different tickets
3010		**  for different LDAP connections.
3011		*/
3012
3013		(void) putenv(lmap->ldap_secret);
3014	}
3015# endif /* LDAP_AUTH_KRBV4 */
3016
3017	bind_result = ldap_bind_s(ld, lmap->ldap_binddn,
3018				  lmap->ldap_secret, lmap->ldap_method);
3019
3020# if USE_LDAP_INIT
3021	/* clear the event if it has not sprung */
3022	if (ev != NULL)
3023		clrevent(ev);
3024# endif /* USE_LDAP_INIT */
3025
3026	if (bind_result != LDAP_SUCCESS)
3027	{
3028		errno = bind_result + E_LDAPBASE;
3029		if (!bitset(MF_OPTIONAL, map->map_mflags))
3030		{
3031			syserr("421 4.0.0 Cannot bind to map %s in ldap server %s",
3032			       map->map_mname,
3033			       lmap->ldap_host == NULL ? "localhost" : lmap->ldap_host);
3034		}
3035		return FALSE;
3036	}
3037
3038	/* We need to cast ld into the map structure */
3039	lmap->ldap_ld = ld;
3040	return TRUE;
3041}
3042
3043/* ARGSUSED */
3044static void
3045ldaptimeout(sig_no)
3046	int sig_no;
3047{
3048	/*
3049	**  NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER.  DO NOT ADD
3050	**	ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
3051	**	DOING.
3052	*/
3053
3054	errno = ETIMEDOUT;
3055	longjmp(LDAPTimeout, 1);
3056}
3057
3058/*
3059**  LDAPMAP_CLOSE -- close ldap map
3060*/
3061
3062void
3063ldapmap_close(map)
3064	MAP *map;
3065{
3066	LDAPMAP_STRUCT *lmap;
3067	STAB *s;
3068
3069	if (tTd(38, 2))
3070		dprintf("ldapmap_close(%s)\n", map->map_mname);
3071
3072	lmap = (LDAPMAP_STRUCT *) map->map_db1;
3073
3074	/* Check if already closed */
3075	if (lmap->ldap_ld == NULL)
3076		return;
3077
3078	/* Close the LDAP connection */
3079	ldap_unbind(lmap->ldap_ld);
3080
3081	/* Mark all the maps that share the connection as closed */
3082	s = ldapmap_findconn(lmap);
3083
3084	while (s->s_lmap != NULL)
3085	{
3086		MAP *smap = s->s_lmap;
3087
3088		if (tTd(38, 2) && smap != map)
3089			dprintf("ldapmap_close(%s): closed %s (shared LDAP connection)\n",
3090				map->map_mname, smap->map_mname);
3091
3092		smap->map_mflags &= ~(MF_OPEN|MF_WRITABLE);
3093		lmap = (LDAPMAP_STRUCT *) smap->map_db1;
3094		lmap->ldap_ld = NULL;
3095		s->s_lmap = lmap->ldap_next;
3096		lmap->ldap_next = NULL;
3097	}
3098}
3099
3100# ifdef SUNET_ID
3101/*
3102**  SUNET_ID_HASH -- Convert a string to it's Sunet_id canonical form
3103**  This only makes sense at Stanford University.
3104*/
3105
3106char *
3107sunet_id_hash(str)
3108	char *str;
3109{
3110	char *p, *p_last;
3111
3112	p = str;
3113	p_last = p;
3114	while (*p != '\0')
3115	{
3116		if (islower(*p) || isdigit(*p))
3117		{
3118			*p_last = *p;
3119			p_last++;
3120		}
3121		else if (isupper(*p))
3122		{
3123			*p_last = tolower(*p);
3124			p_last++;
3125		}
3126		++p;
3127	}
3128	if (*p_last != '\0')
3129		*p_last = '\0';
3130	return str;
3131}
3132# endif /* SUNET_ID */
3133
3134/*
3135**  LDAPMAP_LOOKUP -- look up a datum in a LDAP map
3136*/
3137
3138char *
3139ldapmap_lookup(map, name, av, statp)
3140	MAP *map;
3141	char *name;
3142	char **av;
3143	int *statp;
3144{
3145	int i;
3146	int entries = 0;
3147	int msgid;
3148	int ret;
3149	int vsize;
3150	char *fp, *vp;
3151	char *p, *q;
3152	char *result = NULL;
3153	LDAPMAP_STRUCT *lmap = NULL;
3154	char keybuf[MAXNAME + 1];
3155	char filter[LDAPMAP_MAX_FILTER + 1];
3156
3157	if (tTd(38, 20))
3158		dprintf("ldapmap_lookup(%s, %s)\n", map->map_mname, name);
3159
3160	/* Get ldap struct pointer from map */
3161	lmap = (LDAPMAP_STRUCT *) map->map_db1;
3162	ldapmap_setopts(lmap->ldap_ld, lmap);
3163
3164	(void) strlcpy(keybuf, name, sizeof keybuf);
3165
3166	if (!bitset(MF_NOFOLDCASE, map->map_mflags))
3167	{
3168# ifdef SUNET_ID
3169		sunet_id_hash(keybuf);
3170# else /* SUNET_ID */
3171		makelower(keybuf);
3172# endif /* SUNET_ID */
3173	}
3174
3175	/* substitute keybuf into filter, perhaps multiple times */
3176	memset(filter, '\0', sizeof filter);
3177	fp = filter;
3178	p = lmap->ldap_filter;
3179	while ((q = strchr(p, '%')) != NULL)
3180	{
3181		if (q[1] == 's')
3182		{
3183			snprintf(fp, SPACELEFT(filter, fp), "%.*s%s",
3184				 (int) (q - p), p, keybuf);
3185			fp += strlen(fp);
3186			p = q + 2;
3187		}
3188		else if (q[1] == '0')
3189		{
3190			char *k = keybuf;
3191
3192			snprintf(fp, SPACELEFT(filter, fp), "%.*s",
3193				 (int) (q - p), p);
3194			fp += strlen(fp);
3195			p = q + 2;
3196
3197			/* Properly escape LDAP special characters */
3198			while (SPACELEFT(filter, fp) > 0 &&
3199			       *k != '\0')
3200			{
3201				if (*k == '*' || *k == '(' ||
3202				    *k == ')' || *k == '\\')
3203				{
3204					(void) strlcat(fp,
3205						       (*k == '*' ? "\\2A" :
3206							(*k == '(' ? "\\28" :
3207							 (*k == ')' ? "\\29" :
3208							  (*k == '\\' ? "\\5C" :
3209							   "\00")))),
3210						SPACELEFT(filter, fp));
3211					fp += strlen(fp);
3212					k++;
3213				}
3214				else
3215					*fp++ = *k++;
3216			}
3217		}
3218		else
3219		{
3220			snprintf(fp, SPACELEFT(filter, fp), "%.*s",
3221				 (int) (q - p + 1), p);
3222			p = q + (q[1] == '%' ? 2 : 1);
3223			fp += strlen(fp);
3224		}
3225	}
3226	snprintf(fp, SPACELEFT(filter, fp), "%s", p);
3227	if (tTd(38, 20))
3228		dprintf("ldap search filter=%s\n", filter);
3229
3230	lmap->ldap_res = NULL;
3231	msgid = ldap_search(lmap->ldap_ld, lmap->ldap_base, lmap->ldap_scope,
3232			    filter,
3233			    (lmap->ldap_attr[0] == NULL ? NULL :
3234			     lmap->ldap_attr),
3235			    lmap->ldap_attrsonly);
3236	if (msgid == -1)
3237	{
3238		int save_errno;
3239
3240		errno = ldapmap_geterrno(lmap->ldap_ld) + E_LDAPBASE;
3241		save_errno = errno;
3242		if (!bitset(MF_OPTIONAL, map->map_mflags))
3243		{
3244			if (bitset(MF_NODEFER, map->map_mflags))
3245				syserr("Error in ldap_search using %s in map %s",
3246				       filter, map->map_mname);
3247			else
3248				syserr("421 4.0.0 Error in ldap_search using %s in map %s",
3249				       filter, map->map_mname);
3250		}
3251		*statp = EX_TEMPFAIL;
3252#ifdef LDAP_SERVER_DOWN
3253		errno = save_errno;
3254		if (errno == LDAP_SERVER_DOWN + E_LDAPBASE)
3255		{
3256			/* server disappeared, try reopen on next search */
3257			ldapmap_close(map);
3258		}
3259#endif /* LDAP_SERVER_DOWN */
3260		errno = save_errno;
3261		return NULL;
3262	}
3263
3264	*statp = EX_NOTFOUND;
3265	vp = NULL;
3266
3267	/* Get results (all if MF_NOREWRITE, otherwise one by one) */
3268	while ((ret = ldap_result(lmap->ldap_ld, msgid,
3269				  bitset(MF_NOREWRITE, map->map_mflags),
3270				  (lmap->ldap_timeout.tv_sec == 0 ? NULL :
3271				   &(lmap->ldap_timeout)),
3272				  &(lmap->ldap_res))) == LDAP_RES_SEARCH_ENTRY)
3273	{
3274		LDAPMessage *entry;
3275
3276		if (bitset(MF_SINGLEMATCH, map->map_mflags))
3277		{
3278			entries += ldap_count_entries(lmap->ldap_ld,
3279						      lmap->ldap_res);
3280			if (entries > 1)
3281			{
3282				*statp = EX_NOTFOUND;
3283				if (lmap->ldap_res != NULL)
3284				{
3285					ldap_msgfree(lmap->ldap_res);
3286					lmap->ldap_res = NULL;
3287				}
3288				(void) ldap_abandon(lmap->ldap_ld, msgid);
3289				if (vp != NULL)
3290					sm_free(vp);
3291				if (tTd(38, 25))
3292					dprintf("ldap search found multiple on a single match query\n");
3293				return NULL;
3294			}
3295		}
3296
3297		/* If we don't want multiple values and we have one, break */
3298		if (map->map_coldelim == '\0' && vp != NULL)
3299			break;
3300
3301		/* Cycle through all entries */
3302		for (entry = ldap_first_entry(lmap->ldap_ld, lmap->ldap_res);
3303		     entry != NULL;
3304		     entry = ldap_next_entry(lmap->ldap_ld, lmap->ldap_res))
3305		{
3306			BerElement *ber;
3307			char *attr;
3308			char **vals = NULL;
3309
3310			/*
3311			**  If matching only and found an entry,
3312			**  no need to spin through attributes
3313			*/
3314
3315			if (*statp == EX_OK &&
3316			    bitset(MF_MATCHONLY, map->map_mflags))
3317				continue;
3318
3319# if !defined(LDAP_VERSION_MAX) && !defined(LDAP_OPT_SIZELIMIT)
3320			/*
3321			**  Reset value to prevent lingering
3322			**  LDAP_DECODING_ERROR due to
3323			**  OpenLDAP 1.X's hack (see below)
3324			*/
3325
3326			lmap->ldap_ld->ld_errno = LDAP_SUCCESS;
3327# endif /* !defined(LDAP_VERSION_MAX) !defined(LDAP_OPT_SIZELIMIT) */
3328
3329			for (attr = ldap_first_attribute(lmap->ldap_ld, entry,
3330							 &ber);
3331			     attr != NULL;
3332			     attr = ldap_next_attribute(lmap->ldap_ld, entry,
3333							ber))
3334			{
3335				char *tmp, *vp_tmp;
3336
3337				if (lmap->ldap_attrsonly == LDAPMAP_FALSE)
3338				{
3339					vals = ldap_get_values(lmap->ldap_ld,
3340							       entry,
3341							       attr);
3342					if (vals == NULL)
3343					{
3344						errno = ldapmap_geterrno(lmap->ldap_ld);
3345						if (errno == LDAP_SUCCESS)
3346							continue;
3347
3348						/* Must be an error */
3349						errno += E_LDAPBASE;
3350						if (!bitset(MF_OPTIONAL,
3351							    map->map_mflags))
3352						{
3353							if (bitset(MF_NODEFER,
3354								   map->map_mflags))
3355								syserr("Error getting LDAP values in map %s",
3356								       map->map_mname);
3357							else
3358								syserr("421 4.0.0 Error getting LDAP values in map %s",
3359								       map->map_mname);
3360						}
3361						*statp = EX_TEMPFAIL;
3362# if USING_NETSCAPE_LDAP
3363						ldap_memfree(attr);
3364# endif /* USING_NETSCAPE_LDAP */
3365						if (lmap->ldap_res != NULL)
3366						{
3367							ldap_msgfree(lmap->ldap_res);
3368							lmap->ldap_res = NULL;
3369						}
3370						(void) ldap_abandon(lmap->ldap_ld,
3371								    msgid);
3372						if (vp != NULL)
3373							sm_free(vp);
3374						return NULL;
3375					}
3376				}
3377
3378				*statp = EX_OK;
3379
3380# if !defined(LDAP_VERSION_MAX) && !defined(LDAP_OPT_SIZELIMIT)
3381				/*
3382				**  Reset value to prevent lingering
3383				**  LDAP_DECODING_ERROR due to
3384				**  OpenLDAP 1.X's hack (see below)
3385				*/
3386
3387				lmap->ldap_ld->ld_errno = LDAP_SUCCESS;
3388# endif /* !defined(LDAP_VERSION_MAX) !defined(LDAP_OPT_SIZELIMIT) */
3389
3390				/*
3391				**  If matching only,
3392				**  no need to spin through entries
3393				*/
3394
3395				if (bitset(MF_MATCHONLY, map->map_mflags))
3396					continue;
3397
3398				/*
3399				**  If we don't want multiple values,
3400				**  return first found.
3401				*/
3402
3403				if (map->map_coldelim == '\0')
3404				{
3405					if (lmap->ldap_attrsonly == LDAPMAP_TRUE)
3406					{
3407						vp = newstr(attr);
3408# if USING_NETSCAPE_LDAP
3409						ldap_memfree(attr);
3410# endif /* USING_NETSCAPE_LDAP */
3411						break;
3412					}
3413
3414					if (vals[0] == NULL)
3415					{
3416						ldap_value_free(vals);
3417# if USING_NETSCAPE_LDAP
3418						ldap_memfree(attr);
3419# endif /* USING_NETSCAPE_LDAP */
3420						continue;
3421					}
3422
3423					vp = newstr(vals[0]);
3424					ldap_value_free(vals);
3425# if USING_NETSCAPE_LDAP
3426					ldap_memfree(attr);
3427# endif /* USING_NETSCAPE_LDAP */
3428					break;
3429				}
3430
3431				/* attributes only */
3432				if (lmap->ldap_attrsonly == LDAPMAP_TRUE)
3433				{
3434					if (vp == NULL)
3435						vp = newstr(attr);
3436					else
3437					{
3438						vsize = strlen(vp) +
3439							strlen(attr) + 2;
3440						tmp = xalloc(vsize);
3441						snprintf(tmp, vsize, "%s%c%s",
3442							 vp, map->map_coldelim,
3443							 attr);
3444						sm_free(vp);
3445						vp = tmp;
3446					}
3447# if USING_NETSCAPE_LDAP
3448					ldap_memfree(attr);
3449# endif /* USING_NETSCAPE_LDAP */
3450					continue;
3451				}
3452
3453				/*
3454				**  If there is more than one,
3455				**  munge then into a map_coldelim
3456				**  separated string
3457				*/
3458
3459				vsize = 0;
3460				for (i = 0; vals[i] != NULL; i++)
3461					vsize += strlen(vals[i]) + 1;
3462				vp_tmp = xalloc(vsize);
3463				*vp_tmp = '\0';
3464
3465				p = vp_tmp;
3466				for (i = 0; vals[i] != NULL; i++)
3467				{
3468					p += strlcpy(p, vals[i],
3469						     vsize - (p - vp_tmp));
3470					if (p >= vp_tmp + vsize)
3471						syserr("ldapmap_lookup: Internal error: buffer too small for LDAP values");
3472					if (vals[i + 1] != NULL)
3473						*p++ = map->map_coldelim;
3474				}
3475
3476				ldap_value_free(vals);
3477# if USING_NETSCAPE_LDAP
3478				ldap_memfree(attr);
3479# endif /* USING_NETSCAPE_LDAP */
3480				if (vp == NULL)
3481				{
3482					vp = vp_tmp;
3483					continue;
3484				}
3485				vsize = strlen(vp) + strlen(vp_tmp) + 2;
3486				tmp = xalloc(vsize);
3487				snprintf(tmp, vsize, "%s%c%s",
3488					 vp, map->map_coldelim, vp_tmp);
3489
3490				sm_free(vp);
3491				sm_free(vp_tmp);
3492				vp = tmp;
3493			}
3494			errno = ldapmap_geterrno(lmap->ldap_ld);
3495
3496			/*
3497			**  We check errno != LDAP_DECODING_ERROR since
3498			**  OpenLDAP 1.X has a very ugly *undocumented*
3499			**  hack of returning this error code from
3500			**  ldap_next_attribute() if the library freed the
3501			**  ber attribute.  See:
3502			**  http://www.openldap.org/lists/openldap-devel/9901/msg00064.html
3503			*/
3504
3505			if (errno != LDAP_SUCCESS &&
3506			    errno != LDAP_DECODING_ERROR)
3507			{
3508				/* Must be an error */
3509				errno += E_LDAPBASE;
3510				if (!bitset(MF_OPTIONAL, map->map_mflags))
3511				{
3512					if (bitset(MF_NODEFER, map->map_mflags))
3513						syserr("Error getting LDAP attributes in map %s",
3514						       map->map_mname);
3515					else
3516						syserr("421 4.0.0 Error getting LDAP attributes in map %s",
3517						       map->map_mname);
3518				}
3519				*statp = EX_TEMPFAIL;
3520				if (lmap->ldap_res != NULL)
3521				{
3522					ldap_msgfree(lmap->ldap_res);
3523					lmap->ldap_res = NULL;
3524				}
3525				(void) ldap_abandon(lmap->ldap_ld, msgid);
3526				if (vp != NULL)
3527					sm_free(vp);
3528				return NULL;
3529			}
3530
3531			/* We don't want multiple values and we have one */
3532			if (map->map_coldelim == '\0' && vp != NULL)
3533				break;
3534		}
3535		errno = ldapmap_geterrno(lmap->ldap_ld);
3536		if (errno != LDAP_SUCCESS && errno != LDAP_DECODING_ERROR)
3537		{
3538			/* Must be an error */
3539			errno += E_LDAPBASE;
3540			if (!bitset(MF_OPTIONAL, map->map_mflags))
3541			{
3542				if (bitset(MF_NODEFER, map->map_mflags))
3543					syserr("Error getting LDAP entries in map %s",
3544					       map->map_mname);
3545				else
3546					syserr("421 4.0.0 Error getting LDAP entries in map %s",
3547					       map->map_mname);
3548			}
3549			*statp = EX_TEMPFAIL;
3550			if (lmap->ldap_res != NULL)
3551			{
3552				ldap_msgfree(lmap->ldap_res);
3553				lmap->ldap_res = NULL;
3554			}
3555			(void) ldap_abandon(lmap->ldap_ld, msgid);
3556			if (vp != NULL)
3557				sm_free(vp);
3558			return NULL;
3559		}
3560		ldap_msgfree(lmap->ldap_res);
3561		lmap->ldap_res = NULL;
3562	}
3563
3564	/*
3565	**  If grabbing all results at once for MF_NOREWRITE and
3566	**  only want a single match, make sure that's all we have
3567	*/
3568
3569	if (ret == LDAP_RES_SEARCH_RESULT &&
3570	    bitset(MF_NOREWRITE|MF_SINGLEMATCH, map->map_mflags))
3571	{
3572		entries += ldap_count_entries(lmap->ldap_ld, lmap->ldap_res);
3573		if (entries > 1)
3574		{
3575			*statp = EX_NOTFOUND;
3576			if (lmap->ldap_res != NULL)
3577			{
3578				ldap_msgfree(lmap->ldap_res);
3579				lmap->ldap_res = NULL;
3580			}
3581			if (vp != NULL)
3582				sm_free(vp);
3583			return NULL;
3584		}
3585		*statp = EX_OK;
3586	}
3587
3588	if (ret == 0)
3589		errno = ETIMEDOUT;
3590	else
3591		errno = ldapmap_geterrno(lmap->ldap_ld);
3592	if (errno != LDAP_SUCCESS)
3593	{
3594		int save_errno;
3595
3596		/* Must be an error */
3597		if (ret != 0)
3598			errno += E_LDAPBASE;
3599		save_errno = errno;
3600
3601		if (!bitset(MF_OPTIONAL, map->map_mflags))
3602		{
3603			if (bitset(MF_NODEFER, map->map_mflags))
3604				syserr("Error getting LDAP results in map %s",
3605				       map->map_mname);
3606			else
3607				syserr("421 4.0.0 Error getting LDAP results in map %s",
3608				       map->map_mname);
3609		}
3610		*statp = EX_TEMPFAIL;
3611		if (vp != NULL)
3612			sm_free(vp);
3613#ifdef LDAP_SERVER_DOWN
3614		errno = save_errno;
3615		if (errno == LDAP_SERVER_DOWN + E_LDAPBASE)
3616		{
3617			/* server disappeared, try reopen on next search */
3618			ldapmap_close(map);
3619		}
3620#endif /* LDAP_SERVER_DOWN */
3621		errno = save_errno;
3622		return NULL;
3623	}
3624
3625	/* Did we match anything? */
3626	if (vp == NULL && !bitset(MF_MATCHONLY, map->map_mflags))
3627		return NULL;
3628
3629	/*
3630	**  If MF_NOREWRITE, we are special map which doesn't
3631	**  actually return a map value.  Instead, we don't free
3632	**  ldap_res and let the calling function process the LDAP
3633	**  results.  The caller should ldap_msgfree(lmap->ldap_res).
3634	*/
3635
3636	if (bitset(MF_NOREWRITE, map->map_mflags))
3637	{
3638		if (vp != NULL)
3639			sm_free(vp);
3640		return "";
3641	}
3642
3643	if (*statp == EX_OK)
3644	{
3645		if (LogLevel > 9)
3646			sm_syslog(LOG_INFO, CurEnv->e_id,
3647				  "ldap %.100s => %s", name,
3648				  vp == NULL ? "<NULL>" : vp);
3649		if (bitset(MF_MATCHONLY, map->map_mflags))
3650			result = map_rewrite(map, name, strlen(name), NULL);
3651		else
3652		{
3653			/* vp != NULL according to test above */
3654			result = map_rewrite(map, vp, strlen(vp), av);
3655		}
3656		if (vp != NULL)
3657			sm_free(vp);
3658	}
3659	return result;
3660}
3661
3662/*
3663**  LDAPMAP_FINDCONN -- find an LDAP connection to the server
3664**
3665**	Cache LDAP connections based on the host, port, bind DN,
3666**	secret, and PID so we don't have multiple connections open to
3667**	the same server for different maps.  Need a separate connection
3668**	per PID since a parent process may close the map before the
3669**	child is done with it.
3670**
3671**	Parameters:
3672**		lmap -- LDAP map information
3673**
3674**	Returns:
3675**		Symbol table entry for the LDAP connection.
3676**
3677*/
3678
3679static STAB *
3680ldapmap_findconn(lmap)
3681	LDAPMAP_STRUCT *lmap;
3682{
3683	int len;
3684	char *nbuf;
3685	STAB *s;
3686
3687	len = (lmap->ldap_host == NULL ? strlen("localhost") :
3688					 strlen(lmap->ldap_host)) + 1 + 8 + 1 +
3689		(lmap->ldap_binddn == NULL ? 0 : strlen(lmap->ldap_binddn)) +
3690		1 +
3691		(lmap->ldap_secret == NULL ? 0 : strlen(lmap->ldap_secret)) +
3692		8 + 1;
3693	nbuf = xalloc(len);
3694	snprintf(nbuf, len, "%s%c%d%c%s%c%s%d",
3695		 (lmap->ldap_host == NULL ? "localhost" : lmap->ldap_host),
3696		 CONDELSE,
3697		 lmap->ldap_port,
3698		 CONDELSE,
3699		 (lmap->ldap_binddn == NULL ? "" : lmap->ldap_binddn),
3700		 CONDELSE,
3701		 (lmap->ldap_secret == NULL ? "" : lmap->ldap_secret),
3702		 (int) getpid());
3703	s = stab(nbuf, ST_LMAP, ST_ENTER);
3704	sm_free(nbuf);
3705	return s;
3706}
3707/*
3708**  LDAPMAP_SETOPTS -- set LDAP options
3709**
3710**	Parameters:
3711**		ld -- LDAP session handle
3712**		lmap -- LDAP map information
3713**
3714**	Returns:
3715**		None.
3716**
3717*/
3718
3719static void
3720ldapmap_setopts(ld, lmap)
3721	LDAP *ld;
3722	LDAPMAP_STRUCT *lmap;
3723{
3724# if USE_LDAP_SET_OPTION
3725	ldap_set_option(ld, LDAP_OPT_DEREF, &lmap->ldap_deref);
3726	if (bitset(LDAP_OPT_REFERRALS, lmap->ldap_options))
3727		ldap_set_option(ld, LDAP_OPT_REFERRALS, LDAP_OPT_ON);
3728	else
3729		ldap_set_option(ld, LDAP_OPT_REFERRALS, LDAP_OPT_OFF);
3730	ldap_set_option(ld, LDAP_OPT_SIZELIMIT, &lmap->ldap_sizelimit);
3731	ldap_set_option(ld, LDAP_OPT_TIMELIMIT, &lmap->ldap_timelimit);
3732# else /* USE_LDAP_SET_OPTION */
3733	/* From here on in we can use ldap internal timelimits */
3734	ld->ld_deref = lmap->ldap_deref;
3735	ld->ld_options = lmap->ldap_options;
3736	ld->ld_sizelimit = lmap->ldap_sizelimit;
3737	ld->ld_timelimit = lmap->ldap_timelimit;
3738# endif /* USE_LDAP_SET_OPTION */
3739}
3740/*
3741**  LDAPMAP_GETERRNO -- get ldap errno value
3742**
3743**	Parameters:
3744**		ld -- LDAP session handle
3745**
3746**	Returns:
3747**		LDAP errno.
3748**
3749*/
3750
3751static int
3752ldapmap_geterrno(ld)
3753	LDAP *ld;
3754{
3755	int err = LDAP_SUCCESS;
3756
3757# if defined(LDAP_VERSION_MAX) && LDAP_VERSION_MAX >= 3
3758	(void) ldap_get_option(ld, LDAP_OPT_ERROR_NUMBER, &err);
3759# else /* defined(LDAP_VERSION_MAX) && LDAP_VERSION_MAX >= 3 */
3760#  ifdef LDAP_OPT_SIZELIMIT
3761	err = ldap_get_lderrno(ld, NULL, NULL);
3762#  else /* LDAP_OPT_SIZELIMIT */
3763	err = ld->ld_errno;
3764
3765	/*
3766	**  Reset value to prevent lingering LDAP_DECODING_ERROR due to
3767	**  OpenLDAP 1.X's hack (see above)
3768	*/
3769
3770	ld->ld_errno = LDAP_SUCCESS;
3771#  endif /* LDAP_OPT_SIZELIMIT */
3772# endif /* defined(LDAP_VERSION_MAX) && LDAP_VERSION_MAX >= 3 */
3773	return err;
3774}
3775
3776/*
3777**  LDAPX_MAP_PARSEARGS -- print warning about use of ldapx map.
3778*/
3779
3780bool
3781ldapx_map_parseargs(map, args)
3782	MAP *map;
3783	char *args;
3784{
3785	printf("Warning: The \"ldapx\" map class is deprecated and will be removed in a future\n");
3786	printf("         version.  Use the \"ldap\" map class instead for map \"%s\".\n",
3787	       map->map_mname);
3788	return ldapmap_parseargs(map, args);
3789}
3790
3791/*
3792**  LDAPMAP_PARSEARGS -- parse ldap map definition args.
3793*/
3794
3795struct lamvalues LDAPAuthMethods[] =
3796{
3797	{	"none",		LDAP_AUTH_NONE		},
3798	{	"simple",	LDAP_AUTH_SIMPLE	},
3799# ifdef LDAP_AUTH_KRBV4
3800	{	"krbv4",	LDAP_AUTH_KRBV4		},
3801# endif /* LDAP_AUTH_KRBV4 */
3802	{	NULL,		0			}
3803};
3804
3805struct ladvalues LDAPAliasDereference[] =
3806{
3807	{	"never",	LDAP_DEREF_NEVER	},
3808	{	"always",	LDAP_DEREF_ALWAYS	},
3809	{	"search",	LDAP_DEREF_SEARCHING	},
3810	{	"find",		LDAP_DEREF_FINDING	},
3811	{	NULL,		0			}
3812};
3813
3814struct lssvalues LDAPSearchScope[] =
3815{
3816	{	"base",		LDAP_SCOPE_BASE		},
3817	{	"one",		LDAP_SCOPE_ONELEVEL	},
3818	{	"sub",		LDAP_SCOPE_SUBTREE	},
3819	{	NULL,		0			}
3820};
3821
3822bool
3823ldapmap_parseargs(map, args)
3824	MAP *map;
3825	char *args;
3826{
3827	bool secretread = TRUE;
3828	int i;
3829	register char *p = args;
3830	LDAPMAP_STRUCT *lmap;
3831	struct lamvalues *lam;
3832	struct ladvalues *lad;
3833	struct lssvalues *lss;
3834	char m_tmp[MAXPATHLEN + LDAPMAP_MAX_PASSWD];
3835
3836	/* Get ldap struct pointer from map */
3837	lmap = (LDAPMAP_STRUCT *) map->map_db1;
3838
3839	/* Check if setting the initial LDAP defaults */
3840	if (lmap == NULL || lmap != LDAPDefaults)
3841	{
3842		/* We need to alloc an LDAPMAP_STRUCT struct */
3843		lmap = (LDAPMAP_STRUCT *) xalloc(sizeof *lmap);
3844		if (LDAPDefaults == NULL)
3845			ldapmap_clear(lmap);
3846		else
3847			STRUCTCOPY(*LDAPDefaults, *lmap);
3848	}
3849
3850	/* there is no check whether there is really an argument */
3851	map->map_mflags |= MF_TRY0NULL|MF_TRY1NULL;
3852	map->map_spacesub = SpaceSub;	/* default value */
3853	for (;;)
3854	{
3855		while (isascii(*p) && isspace(*p))
3856			p++;
3857		if (*p != '-')
3858			break;
3859		switch (*++p)
3860		{
3861		  case 'N':
3862			map->map_mflags |= MF_INCLNULL;
3863			map->map_mflags &= ~MF_TRY0NULL;
3864			break;
3865
3866		  case 'O':
3867			map->map_mflags &= ~MF_TRY1NULL;
3868			break;
3869
3870		  case 'o':
3871			map->map_mflags |= MF_OPTIONAL;
3872			break;
3873
3874		  case 'f':
3875			map->map_mflags |= MF_NOFOLDCASE;
3876			break;
3877
3878		  case 'm':
3879			map->map_mflags |= MF_MATCHONLY;
3880			break;
3881
3882		  case 'A':
3883			map->map_mflags |= MF_APPEND;
3884			break;
3885
3886		  case 'q':
3887			map->map_mflags |= MF_KEEPQUOTES;
3888			break;
3889
3890		  case 'a':
3891			map->map_app = ++p;
3892			break;
3893
3894		  case 'T':
3895			map->map_tapp = ++p;
3896			break;
3897
3898		  case 't':
3899			map->map_mflags |= MF_NODEFER;
3900			break;
3901
3902		  case 'S':
3903			map->map_spacesub = *++p;
3904			break;
3905
3906		  case 'D':
3907			map->map_mflags |= MF_DEFER;
3908			break;
3909
3910		  case 'z':
3911			if (*++p != '\\')
3912				map->map_coldelim = *p;
3913			else
3914			{
3915				switch (*++p)
3916				{
3917				  case 'n':
3918					map->map_coldelim = '\n';
3919					break;
3920
3921				  case 't':
3922					map->map_coldelim = '\t';
3923					break;
3924
3925				  default:
3926					map->map_coldelim = '\\';
3927				}
3928			}
3929			break;
3930
3931			/* Start of ldapmap specific args */
3932		  case 'k':		/* search field */
3933			while (isascii(*++p) && isspace(*p))
3934				continue;
3935			lmap->ldap_filter = p;
3936			break;
3937
3938		  case 'v':		/* attr to return */
3939			while (isascii(*++p) && isspace(*p))
3940				continue;
3941			lmap->ldap_attr[0] = p;
3942			lmap->ldap_attr[1] = NULL;
3943			break;
3944
3945		  case '1':
3946			map->map_mflags |= MF_SINGLEMATCH;
3947			break;
3948
3949			/* args stolen from ldapsearch.c */
3950		  case 'R':		/* don't auto chase referrals */
3951# ifdef LDAP_REFERRALS
3952			lmap->ldap_options &= ~LDAP_OPT_REFERRALS;
3953# else /* LDAP_REFERRALS */
3954			syserr("compile with -DLDAP_REFERRALS for referral support\n");
3955# endif /* LDAP_REFERRALS */
3956			break;
3957
3958		  case 'n':		/* retrieve attribute names only */
3959			lmap->ldap_attrsonly = LDAPMAP_TRUE;
3960			break;
3961
3962		  case 'r':		/* alias dereferencing */
3963			while (isascii(*++p) && isspace(*p))
3964				continue;
3965
3966			if (strncasecmp(p, "LDAP_DEREF_", 11) == 0)
3967				p += 11;
3968
3969			for (lad = LDAPAliasDereference;
3970			     lad != NULL && lad->lad_name != NULL; lad++)
3971			{
3972				if (strncasecmp(p, lad->lad_name,
3973						strlen(lad->lad_name)) == 0)
3974					break;
3975			}
3976			if (lad->lad_name != NULL)
3977				lmap->ldap_deref = lad->lad_code;
3978			else
3979			{
3980				/* bad config line */
3981				if (!bitset(MCF_OPTFILE,
3982					    map->map_class->map_cflags))
3983				{
3984					char *ptr;
3985
3986					if ((ptr = strchr(p, ' ')) != NULL)
3987						*ptr = '\0';
3988					syserr("Deref must be [never|always|search|find] (not %s) in map %s",
3989						p, map->map_mname);
3990					if (ptr != NULL)
3991						*ptr = ' ';
3992					return FALSE;
3993				}
3994			}
3995			break;
3996
3997		  case 's':		/* search scope */
3998			while (isascii(*++p) && isspace(*p))
3999				continue;
4000
4001			if (strncasecmp(p, "LDAP_SCOPE_", 11) == 0)
4002				p += 11;
4003
4004			for (lss = LDAPSearchScope;
4005			     lss != NULL && lss->lss_name != NULL; lss++)
4006			{
4007				if (strncasecmp(p, lss->lss_name,
4008						strlen(lss->lss_name)) == 0)
4009					break;
4010			}
4011			if (lss->lss_name != NULL)
4012				lmap->ldap_scope = lss->lss_code;
4013			else
4014			{
4015				/* bad config line */
4016				if (!bitset(MCF_OPTFILE,
4017					    map->map_class->map_cflags))
4018				{
4019					char *ptr;
4020
4021					if ((ptr = strchr(p, ' ')) != NULL)
4022						*ptr = '\0';
4023					syserr("Scope must be [base|one|sub] (not %s) in map %s",
4024						p, map->map_mname);
4025					if (ptr != NULL)
4026						*ptr = ' ';
4027					return FALSE;
4028				}
4029			}
4030			break;
4031
4032		  case 'h':		/* ldap host */
4033			while (isascii(*++p) && isspace(*p))
4034				continue;
4035			lmap->ldap_host = p;
4036			break;
4037
4038		  case 'b':		/* search base */
4039			while (isascii(*++p) && isspace(*p))
4040				continue;
4041			lmap->ldap_base = p;
4042			break;
4043
4044		  case 'p':		/* ldap port */
4045			while (isascii(*++p) && isspace(*p))
4046				continue;
4047			lmap->ldap_port = atoi(p);
4048			break;
4049
4050		  case 'l':		/* time limit */
4051			while (isascii(*++p) && isspace(*p))
4052				continue;
4053			lmap->ldap_timelimit = atoi(p);
4054			lmap->ldap_timeout.tv_sec = lmap->ldap_timelimit;
4055			break;
4056
4057		  case 'Z':
4058			while (isascii(*++p) && isspace(*p))
4059				continue;
4060			lmap->ldap_sizelimit = atoi(p);
4061			break;
4062
4063		  case 'd':		/* Dn to bind to server as */
4064			while (isascii(*++p) && isspace(*p))
4065				continue;
4066			lmap->ldap_binddn = p;
4067			break;
4068
4069		  case 'M':		/* Method for binding */
4070			while (isascii(*++p) && isspace(*p))
4071				continue;
4072
4073			if (strncasecmp(p, "LDAP_AUTH_", 10) == 0)
4074				p += 10;
4075
4076			for (lam = LDAPAuthMethods;
4077			     lam != NULL && lam->lam_name != NULL; lam++)
4078			{
4079				if (strncasecmp(p, lam->lam_name,
4080						strlen(lam->lam_name)) == 0)
4081					break;
4082			}
4083			if (lam->lam_name != NULL)
4084				lmap->ldap_method = lam->lam_code;
4085			else
4086			{
4087				/* bad config line */
4088				if (!bitset(MCF_OPTFILE,
4089					    map->map_class->map_cflags))
4090				{
4091					char *ptr;
4092
4093					if ((ptr = strchr(p, ' ')) != NULL)
4094						*ptr = '\0';
4095					syserr("Method for binding must be [none|simple|krbv4] (not %s) in map %s",
4096						p, map->map_mname);
4097					if (ptr != NULL)
4098						*ptr = ' ';
4099					return FALSE;
4100				}
4101			}
4102
4103			break;
4104
4105			/*
4106			**  This is a string that is dependent on the
4107			**  method used defined above.
4108			*/
4109
4110		  case 'P':		/* Secret password for binding */
4111			 while (isascii(*++p) && isspace(*p))
4112				continue;
4113			lmap->ldap_secret = p;
4114			secretread = FALSE;
4115			break;
4116
4117		  default:
4118			syserr("Illegal option %c map %s", *p, map->map_mname);
4119			break;
4120		}
4121
4122		/* need to account for quoted strings here */
4123		while (*p != '\0' && !(isascii(*p) && isspace(*p)))
4124		{
4125			if (*p == '"')
4126			{
4127				while (*++p != '"' && *p != '\0')
4128					continue;
4129				if (*p != '\0')
4130					p++;
4131			}
4132			else
4133				p++;
4134		}
4135
4136		if (*p != '\0')
4137			*p++ = '\0';
4138	}
4139
4140	if (map->map_app != NULL)
4141		map->map_app = newstr(ldapmap_dequote(map->map_app));
4142	if (map->map_tapp != NULL)
4143		map->map_tapp = newstr(ldapmap_dequote(map->map_tapp));
4144
4145	/*
4146	**  We need to swallow up all the stuff into a struct
4147	**  and dump it into map->map_dbptr1
4148	*/
4149
4150	if (lmap->ldap_host != NULL &&
4151	    (LDAPDefaults == NULL ||
4152	     LDAPDefaults == lmap ||
4153	     LDAPDefaults->ldap_host != lmap->ldap_host))
4154		lmap->ldap_host = newstr(ldapmap_dequote(lmap->ldap_host));
4155	map->map_domain = lmap->ldap_host;
4156
4157	if (lmap->ldap_binddn != NULL &&
4158	    (LDAPDefaults == NULL ||
4159	     LDAPDefaults == lmap ||
4160	     LDAPDefaults->ldap_binddn != lmap->ldap_binddn))
4161		lmap->ldap_binddn = newstr(ldapmap_dequote(lmap->ldap_binddn));
4162
4163	if (lmap->ldap_secret != NULL &&
4164	    (LDAPDefaults == NULL ||
4165	     LDAPDefaults == lmap ||
4166	     LDAPDefaults->ldap_secret != lmap->ldap_secret))
4167	{
4168		FILE *sfd;
4169		long sff = SFF_OPENASROOT|SFF_ROOTOK|SFF_NOWLINK|SFF_NOWWFILES|SFF_NOGWFILES;
4170
4171		if (DontLockReadFiles)
4172			sff |= SFF_NOLOCK;
4173
4174		/* need to use method to map secret to passwd string */
4175		switch (lmap->ldap_method)
4176		{
4177		  case LDAP_AUTH_NONE:
4178			/* Do nothing */
4179			break;
4180
4181		  case LDAP_AUTH_SIMPLE:
4182
4183			/*
4184			**  Secret is the name of a file with
4185			**  the first line as the password.
4186			*/
4187
4188			/* Already read in the secret? */
4189			if (secretread)
4190				break;
4191
4192			sfd = safefopen(ldapmap_dequote(lmap->ldap_secret),
4193					O_RDONLY, 0, sff);
4194			if (sfd == NULL)
4195			{
4196				syserr("LDAP map: cannot open secret %s",
4197				       ldapmap_dequote(lmap->ldap_secret));
4198				return FALSE;
4199			}
4200			lmap->ldap_secret = sfgets(m_tmp, LDAPMAP_MAX_PASSWD,
4201						   sfd, TimeOuts.to_fileopen,
4202						   "ldapmap_parseargs");
4203			(void) fclose(sfd);
4204			if (lmap->ldap_secret != NULL &&
4205			    strlen(m_tmp) > 0)
4206			{
4207				/* chomp newline */
4208				if (m_tmp[strlen(m_tmp) - 1] == '\n')
4209					m_tmp[strlen(m_tmp) - 1] = '\0';
4210
4211				lmap->ldap_secret = m_tmp;
4212			}
4213			break;
4214
4215# ifdef LDAP_AUTH_KRBV4
4216		  case LDAP_AUTH_KRBV4:
4217
4218			/*
4219			**  Secret is where the ticket file is
4220			**  stashed
4221			*/
4222
4223			snprintf(m_tmp, MAXPATHLEN + LDAPMAP_MAX_PASSWD,
4224				 "KRBTKFILE=%s",
4225				 ldapmap_dequote(lmap->ldap_secret));
4226			lmap->ldap_secret = m_tmp;
4227			break;
4228# endif /* LDAP_AUTH_KRBV4 */
4229
4230		  default:	       /* Should NEVER get here */
4231			syserr("LDAP map: Illegal value in lmap method");
4232			return FALSE;
4233			break;
4234		}
4235	}
4236
4237	if (lmap->ldap_secret != NULL &&
4238	    (LDAPDefaults == NULL ||
4239	     LDAPDefaults == lmap ||
4240	     LDAPDefaults->ldap_secret != lmap->ldap_secret))
4241		lmap->ldap_secret = newstr(ldapmap_dequote(lmap->ldap_secret));
4242
4243	if (lmap->ldap_base != NULL &&
4244	    (LDAPDefaults == NULL ||
4245	     LDAPDefaults == lmap ||
4246	     LDAPDefaults->ldap_base != lmap->ldap_base))
4247		lmap->ldap_base = newstr(ldapmap_dequote(lmap->ldap_base));
4248
4249	/*
4250	**  Save the server from extra work.  If request is for a single
4251	**  match, tell the server to only return enough records to
4252	**  determine if there is a single match or not.  This can not
4253	**  be one since the server would only return one and we wouldn't
4254	**  know if there were others available.
4255	*/
4256
4257	if (bitset(MF_SINGLEMATCH, map->map_mflags))
4258		lmap->ldap_sizelimit = 2;
4259
4260	/* If setting defaults, don't process ldap_filter and ldap_attr */
4261	if (lmap == LDAPDefaults)
4262		return TRUE;
4263
4264	if (lmap->ldap_filter != NULL)
4265		lmap->ldap_filter = newstr(ldapmap_dequote(lmap->ldap_filter));
4266	else
4267	{
4268		if (!bitset(MCF_OPTFILE, map->map_class->map_cflags))
4269		{
4270			syserr("No filter given in map %s", map->map_mname);
4271			return FALSE;
4272		}
4273	}
4274
4275	if (lmap->ldap_attr[0] != NULL)
4276	{
4277		i = 0;
4278		p = ldapmap_dequote(lmap->ldap_attr[0]);
4279		lmap->ldap_attr[0] = NULL;
4280
4281		while (p != NULL)
4282		{
4283			char *v;
4284
4285			while (isascii(*p) && isspace(*p))
4286				p++;
4287			if (*p == '\0')
4288				break;
4289			v = p;
4290			p = strchr(v, ',');
4291			if (p != NULL)
4292				*p++ = '\0';
4293
4294			if (i >= LDAPMAP_MAX_ATTR)
4295			{
4296				syserr("Too many return attributes in %s (max %d)",
4297				       map->map_mname, LDAPMAP_MAX_ATTR);
4298				return FALSE;
4299			}
4300			if (*v != '\0')
4301				lmap->ldap_attr[i++] = newstr(v);
4302		}
4303		lmap->ldap_attr[i] = NULL;
4304	}
4305
4306	map->map_db1 = (ARBPTR_T) lmap;
4307	return TRUE;
4308}
4309
4310/*
4311**  LDAPMAP_CLEAR -- set default values for LDAPMAP_STRUCT
4312**
4313**	Parameters:
4314**		lmap -- pointer to LDAPMAP_STRUCT to clear
4315**
4316**	Returns:
4317**		None.
4318**
4319*/
4320
4321static void
4322ldapmap_clear(lmap)
4323	LDAPMAP_STRUCT *lmap;
4324{
4325	lmap->ldap_host = NULL;
4326	lmap->ldap_port = LDAP_PORT;
4327	lmap->ldap_deref = LDAP_DEREF_NEVER;
4328	lmap->ldap_timelimit = LDAP_NO_LIMIT;
4329	lmap->ldap_sizelimit = LDAP_NO_LIMIT;
4330# ifdef LDAP_REFERRALS
4331	lmap->ldap_options = LDAP_OPT_REFERRALS;
4332# else /* LDAP_REFERRALS */
4333	lmap->ldap_options = 0;
4334# endif /* LDAP_REFERRALS */
4335	lmap->ldap_binddn = NULL;
4336	lmap->ldap_secret = NULL;
4337	lmap->ldap_method = LDAP_AUTH_SIMPLE;
4338	lmap->ldap_base = NULL;
4339	lmap->ldap_scope = LDAP_SCOPE_SUBTREE;
4340	lmap->ldap_attrsonly = LDAPMAP_FALSE;
4341	lmap->ldap_timeout.tv_sec = 0;
4342	lmap->ldap_timeout.tv_usec = 0;
4343	lmap->ldap_ld = NULL;
4344	lmap->ldap_filter = NULL;
4345	lmap->ldap_attr[0] = NULL;
4346	lmap->ldap_res = NULL;
4347	lmap->ldap_next = NULL;
4348}
4349/*
4350**  LDAPMAP_SET_DEFAULTS -- Read default map spec from LDAPDefaults in .cf
4351**
4352**	Parameters:
4353**		spec -- map argument string from LDAPDefaults option
4354**
4355**	Returns:
4356**		None.
4357**
4358*/
4359
4360void
4361ldapmap_set_defaults(spec)
4362	char *spec;
4363{
4364	STAB *class;
4365	MAP map;
4366
4367	/* Allocate and set the default values */
4368	if (LDAPDefaults == NULL)
4369		LDAPDefaults = (LDAPMAP_STRUCT *) xalloc(sizeof *LDAPDefaults);
4370	ldapmap_clear(LDAPDefaults);
4371
4372	memset(&map, '\0', sizeof map);
4373
4374	/* look up the class */
4375	class = stab("ldap", ST_MAPCLASS, ST_FIND);
4376	if (class == NULL)
4377	{
4378		syserr("readcf: LDAPDefaultSpec: class ldap not available");
4379		return;
4380	}
4381	map.map_class = &class->s_mapclass;
4382	map.map_db1 = (ARBPTR_T) LDAPDefaults;
4383	map.map_mname = "O LDAPDefaultSpec";
4384
4385	(void) ldapmap_parseargs(&map, spec);
4386
4387	/* These should never be set in LDAPDefaults */
4388	if (map.map_mflags != (MF_TRY0NULL|MF_TRY1NULL) ||
4389	    map.map_spacesub != SpaceSub ||
4390	    map.map_app != NULL ||
4391	    map.map_tapp != NULL)
4392	{
4393		syserr("readcf: option LDAPDefaultSpec: Do not set non-LDAP specific flags");
4394		if (map.map_app != NULL)
4395		{
4396			sm_free(map.map_app);
4397			map.map_app = NULL;
4398		}
4399		if (map.map_tapp != NULL)
4400		{
4401			sm_free(map.map_tapp);
4402			map.map_tapp = NULL;
4403		}
4404	}
4405
4406	if (LDAPDefaults->ldap_filter != NULL)
4407	{
4408		syserr("readcf: option LDAPDefaultSpec: Do not set the LDAP search filter");
4409		/* don't free, it isn't malloc'ed in parseargs */
4410		LDAPDefaults->ldap_filter = NULL;
4411	}
4412
4413	if (LDAPDefaults->ldap_attr[0] != NULL)
4414	{
4415		syserr("readcf: option LDAPDefaultSpec: Do not set the requested LDAP attributes");
4416		/* don't free, they aren't malloc'ed in parseargs */
4417		LDAPDefaults->ldap_attr[0] = NULL;
4418	}
4419}
4420#endif /* LDAPMAP */
4421/*
4422**  PH map
4423*/
4424
4425#ifdef PH_MAP
4426
4427/*
4428**  Support for the CCSO Nameserver (ph/qi).
4429**  This code is intended to replace the so-called "ph mailer".
4430**  Contributed by Mark D. Roth <roth@uiuc.edu>.  Contact him for support.
4431*/
4432
4433# include <qiapi.h>
4434# include <qicode.h>
4435
4436/*
4437**  PH_MAP_PARSEARGS -- parse ph map definition args.
4438*/
4439
4440bool
4441ph_map_parseargs(map, args)
4442	MAP *map;
4443	char *args;
4444{
4445	int i;
4446	register int done;
4447	PH_MAP_STRUCT *pmap = NULL;
4448	register char *p = args;
4449
4450	pmap = (PH_MAP_STRUCT *) xalloc(sizeof *pmap);
4451
4452	/* defaults */
4453	pmap->ph_servers = NULL;
4454	pmap->ph_field_list = NULL;
4455	pmap->ph_to_server = NULL;
4456	pmap->ph_from_server = NULL;
4457	pmap->ph_sockfd = -1;
4458	pmap->ph_timeout = 0;
4459
4460	map->map_mflags |= MF_TRY0NULL|MF_TRY1NULL;
4461	for (;;)
4462	{
4463		while (isascii(*p) && isspace(*p))
4464			p++;
4465		if (*p != '-')
4466			break;
4467		switch (*++p)
4468		{
4469		  case 'N':
4470			map->map_mflags |= MF_INCLNULL;
4471			map->map_mflags &= ~MF_TRY0NULL;
4472			break;
4473
4474		  case 'O':
4475			map->map_mflags &= ~MF_TRY1NULL;
4476			break;
4477
4478		  case 'o':
4479			map->map_mflags |= MF_OPTIONAL;
4480			break;
4481
4482		  case 'f':
4483			map->map_mflags |= MF_NOFOLDCASE;
4484			break;
4485
4486		  case 'm':
4487			map->map_mflags |= MF_MATCHONLY;
4488			break;
4489
4490		  case 'A':
4491			map->map_mflags |= MF_APPEND;
4492			break;
4493
4494		  case 'q':
4495			map->map_mflags |= MF_KEEPQUOTES;
4496			break;
4497
4498		  case 't':
4499			map->map_mflags |= MF_NODEFER;
4500			break;
4501
4502		  case 'a':
4503			map->map_app = ++p;
4504			break;
4505
4506		  case 'T':
4507			map->map_tapp = ++p;
4508			break;
4509
4510#if _FFR_PHMAP_TIMEOUT
4511		  case 'l':
4512			while (isascii(*++p) && isspace(*p))
4513				continue;
4514			pmap->ph_timeout = atoi(p);
4515			break;
4516#endif /* _FFR_PHMAP_TIMEOUT */
4517
4518		  case 'S':
4519			map->map_spacesub = *++p;
4520			break;
4521
4522		  case 'D':
4523			map->map_mflags |= MF_DEFER;
4524			break;
4525
4526		  case 'h':		/* PH server list */
4527			while (isascii(*++p) && isspace(*p))
4528				continue;
4529			pmap->ph_servers = p;
4530			break;
4531
4532		  case 'v':		/* fields to search for */
4533			while (isascii(*++p) && isspace(*p))
4534				continue;
4535			pmap->ph_field_list = p;
4536			break;
4537
4538		  default:
4539			syserr("ph_map_parseargs: unknown option -%c\n", *p);
4540		}
4541
4542		/* try to account for quoted strings */
4543		done = isascii(*p) && isspace(*p);
4544		while (*p != '\0' && !done)
4545		{
4546			if (*p == '"')
4547			{
4548				while (*++p != '"' && *p != '\0')
4549					continue;
4550				if (*p != '\0')
4551					p++;
4552			}
4553			else
4554				p++;
4555			done = isascii(*p) && isspace(*p);
4556		}
4557
4558		if (*p != '\0')
4559			*p++ = '\0';
4560	}
4561
4562	if (map->map_app != NULL)
4563		map->map_app = newstr(ph_map_dequote(map->map_app));
4564	if (map->map_tapp != NULL)
4565		map->map_tapp = newstr(ph_map_dequote(map->map_tapp));
4566
4567	if (pmap->ph_field_list != NULL)
4568		pmap->ph_field_list = newstr(ph_map_dequote(pmap->ph_field_list));
4569	else
4570		pmap->ph_field_list = DEFAULT_PH_MAP_FIELDS;
4571
4572	if (pmap->ph_servers != NULL)
4573		pmap->ph_servers = newstr(ph_map_dequote(pmap->ph_servers));
4574	else
4575	{
4576		syserr("ph_map_parseargs: -h flag is required");
4577		return FALSE;
4578	}
4579
4580	map->map_db1 = (ARBPTR_T) pmap;
4581	return TRUE;
4582}
4583
4584#if _FFR_PHMAP_TIMEOUT
4585/*
4586**  PH_MAP_CLOSE -- close the connection to the ph server
4587*/
4588
4589static void
4590ph_map_safeclose(map)
4591	MAP *map;
4592{
4593	int save_errno = errno;
4594	PH_MAP_STRUCT *pmap;
4595
4596	pmap = (PH_MAP_STRUCT *)map->map_db1;
4597
4598	if (pmap->ph_sockfd != -1)
4599	{
4600		(void) close(pmap->ph_sockfd);
4601		pmap->ph_sockfd = -1;
4602	}
4603	if (pmap->ph_from_server != NULL)
4604	{
4605		(void) fclose(pmap->ph_from_server);
4606		pmap->ph_from_server = NULL;
4607	}
4608	if (pmap->ph_to_server != NULL)
4609	{
4610		(void) fclose(pmap->ph_to_server);
4611		pmap->ph_to_server = NULL;
4612	}
4613	map->map_mflags &= ~(MF_OPEN|MF_WRITABLE);
4614	errno = save_errno;
4615}
4616
4617void
4618ph_map_close(map)
4619	MAP *map;
4620{
4621	PH_MAP_STRUCT *pmap;
4622
4623	pmap = (PH_MAP_STRUCT *)map->map_db1;
4624	(void) fprintf(pmap->ph_to_server, "quit\n");
4625	(void) fflush(pmap->ph_to_server);
4626	ph_map_safeclose(map);
4627}
4628
4629static jmp_buf  PHTimeout;
4630
4631/* ARGSUSED */
4632static void
4633ph_timeout(sig)
4634	int sig;
4635{
4636	/*
4637	**  NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER.  DO NOT ADD
4638	**	ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
4639	**	DOING.
4640	*/
4641
4642	errno = ETIMEDOUT;
4643	longjmp(PHTimeout, 1);
4644}
4645#else /* _FFR_PHMAP_TIMEOUT */
4646/*
4647**  PH_MAP_CLOSE -- close the connection to the ph server
4648*/
4649
4650void
4651ph_map_close(map)
4652	MAP *map;
4653{
4654	PH_MAP_STRUCT *pmap;
4655
4656	pmap = (PH_MAP_STRUCT *)map->map_db1;
4657	CloseQi(pmap->ph_to_server, pmap->ph_from_server);
4658	pmap->ph_to_server = NULL;
4659	pmap->ph_from_server = NULL;
4660}
4661#endif /* _FFR_PHMAP_TIMEOUT */
4662
4663/*
4664**  PH_MAP_OPEN -- sub for opening PH map
4665*/
4666bool
4667ph_map_open(map, mode)
4668	MAP *map;
4669	int mode;
4670{
4671#if !_FFR_PHMAP_TIMEOUT
4672	int save_errno = 0;
4673#endif /* !_FFR_PHMAP_TIMEOUT */
4674	int j;
4675	char *hostlist, *tmp;
4676	QIR *server_data = NULL;
4677	PH_MAP_STRUCT *pmap;
4678#if _FFR_PHMAP_TIMEOUT
4679	register EVENT *ev = NULL;
4680#endif /* _FFR_PHMAP_TIMEOUT */
4681
4682	if (tTd(38, 2))
4683		dprintf("ph_map_open(%s)\n", map->map_mname);
4684
4685	mode &= O_ACCMODE;
4686	if (mode != O_RDONLY)
4687	{
4688		/* issue a pseudo-error message */
4689# ifdef ENOSYS
4690		errno = ENOSYS;
4691# else /* ENOSYS */
4692#  ifdef EFTYPE
4693		errno = EFTYPE;
4694#  else /* EFTYPE */
4695		errno = ENXIO;
4696#  endif /* EFTYPE */
4697# endif /* ENOSYS */
4698		return FALSE;
4699	}
4700
4701	if (CurEnv != NULL && CurEnv->e_sendmode == SM_DEFER &&
4702	    bitset(MF_DEFER, map->map_mflags))
4703	{
4704		if (tTd(9, 1))
4705			dprintf("ph_map_open(%s) => DEFERRED\n",
4706				map->map_mname);
4707
4708		/*
4709		** Unset MF_DEFER here so that map_lookup() returns
4710		** a temporary failure using the bogus map and
4711		** map->map_tapp instead of the default permanent error.
4712		*/
4713
4714		map->map_mflags &= ~MF_DEFER;
4715		return FALSE;
4716	}
4717
4718	pmap = (PH_MAP_STRUCT *)map->map_db1;
4719
4720	hostlist = newstr(pmap->ph_servers);
4721	tmp = strtok(hostlist, " ");
4722	do
4723	{
4724#if _FFR_PHMAP_TIMEOUT
4725		if (pmap->ph_timeout != 0)
4726		{
4727			if (setjmp(PHTimeout) != 0)
4728			{
4729				ev = NULL;
4730				if (LogLevel > 1)
4731					sm_syslog(LOG_NOTICE, CurEnv->e_id,
4732						  "timeout connecting to PH server %.100s",
4733						  tmp);
4734# ifdef ETIMEDOUT
4735				errno = ETIMEDOUT;
4736# else /* ETIMEDOUT */
4737				errno = EAGAIN;
4738# endif /* ETIMEDOUT */
4739				goto ph_map_open_abort;
4740			}
4741			ev = setevent(pmap->ph_timeout, ph_timeout, 0);
4742		}
4743		if (!OpenQiSock(tmp, &(pmap->ph_sockfd)) &&
4744		    !Sock2FILEs(pmap->ph_sockfd, &(pmap->ph_to_server),
4745				&(pmap->ph_from_server)) &&
4746		    fprintf(pmap->ph_to_server, "id sendmail+phmap\n") >= 0 &&
4747		    fflush(pmap->ph_to_server) == 0 &&
4748		    (server_data = ReadQi(pmap->ph_from_server, &j)) != NULL &&
4749		    server_data->code == 200)
4750		{
4751			if (ev != NULL)
4752				clrevent(ev);
4753			FreeQIR(server_data);
4754#else /* _FFR_PHMAP_TIMEOUT */
4755		if (OpenQi(tmp, &(pmap->ph_to_server),
4756			   &(pmap->ph_from_server)) >= 0)
4757		{
4758			if (fprintf(pmap->ph_to_server,
4759				    "id sendmail+phmap\n") < 0 ||
4760			    fflush(pmap->ph_to_server) != 0 ||
4761			    (server_data = ReadQi(pmap->ph_from_server,
4762						  &j)) == NULL ||
4763			    server_data->code != 200)
4764			{
4765				save_errno = errno;
4766				CloseQi(pmap->ph_to_server,
4767					pmap->ph_from_server);
4768				continue;
4769			}
4770			if (server_data != NULL)
4771				FreeQIR(server_data);
4772#endif /* _FFR_PHMAP_TIMEOUT */
4773			sm_free(hostlist);
4774			return TRUE;
4775		}
4776#if _FFR_PHMAP_TIMEOUT
4777  ph_map_open_abort:
4778		if (ev != NULL)
4779			clrevent(ev);
4780		ph_map_safeclose(map);
4781		if (server_data != NULL)
4782		{
4783			FreeQIR(server_data);
4784			server_data = NULL;
4785		}
4786#else /* _FFR_PHMAP_TIMEOUT */
4787		save_errno = errno;
4788#endif /* _FFR_PHMAP_TIMEOUT */
4789	} while (tmp = strtok(NULL, " "));
4790
4791#if !_FFR_PHMAP_TIMEOUT
4792	errno = save_errno;
4793#endif /* !_FFR_PHMAP_TIMEOUT */
4794	if (bitset(MF_NODEFER, map->map_mflags))
4795	{
4796		if (errno == 0)
4797			errno = EAGAIN;
4798		syserr("ph_map_open: %s: cannot connect to PH server",
4799		       map->map_mname);
4800	}
4801	else if (!bitset(MF_OPTIONAL, map->map_mflags) && LogLevel > 1)
4802		sm_syslog(LOG_NOTICE, CurEnv->e_id,
4803			  "ph_map_open: %s: cannot connect to PH server",
4804			  map->map_mname);
4805	sm_free(hostlist);
4806	return FALSE;
4807}
4808
4809/*
4810**  PH_MAP_LOOKUP -- look up key from ph server
4811*/
4812
4813#if _FFR_PHMAP_TIMEOUT
4814# define MAX_PH_FIELDS	20
4815#endif /* _FFR_PHMAP_TIMEOUT */
4816
4817char *
4818ph_map_lookup(map, key, args, pstat)
4819	MAP *map;
4820	char *key;
4821	char **args;
4822	int *pstat;
4823{
4824	int j;
4825	size_t sz;
4826	char *tmp, *tmp2;
4827	char *message = NULL, *field = NULL, *fmtkey;
4828	QIR *server_data = NULL;
4829	QIR *qirp;
4830	char keybuf[MAXKEY + 1], fieldbuf[101];
4831#if _FFR_PHMAP_TIMEOUT
4832	QIR *hold_data[MAX_PH_FIELDS];
4833	int hold_data_idx = 0;
4834	register EVENT *ev = NULL;
4835#endif /* _FFR_PHMAP_TIMEOUT */
4836	PH_MAP_STRUCT *pmap;
4837
4838	pmap = (PH_MAP_STRUCT *)map->map_db1;
4839
4840	*pstat = EX_OK;
4841
4842#if _FFR_PHMAP_TIMEOUT
4843	if (pmap->ph_timeout != 0)
4844	{
4845		if (setjmp(PHTimeout) != 0)
4846		{
4847			ev = NULL;
4848			if (LogLevel > 1)
4849				sm_syslog(LOG_NOTICE, CurEnv->e_id,
4850					  "timeout during PH lookup of %.100s",
4851					  key);
4852# ifdef ETIMEDOUT
4853			errno = ETIMEDOUT;
4854# else /* ETIMEDOUT */
4855			errno = 0;
4856# endif /* ETIMEDOUT */
4857			*pstat = EX_TEMPFAIL;
4858			goto ph_map_lookup_abort;
4859		}
4860		ev = setevent(pmap->ph_timeout, ph_timeout, 0);
4861	}
4862
4863#endif /* _FFR_PHMAP_TIMEOUT */
4864	/* check all relevant fields */
4865	tmp = pmap->ph_field_list;
4866	do
4867	{
4868#if _FFR_PHMAP_TIMEOUT
4869		server_data = NULL;
4870#endif /* _FFR_PHMAP_TIMEOUT */
4871		while (isascii(*tmp) && isspace(*tmp))
4872			tmp++;
4873		if (*tmp == '\0')
4874			break;
4875		sz = strcspn(tmp, " ") + 1;
4876		if (sz > sizeof fieldbuf)
4877			sz = sizeof fieldbuf;
4878		(void) strlcpy(fieldbuf, tmp, sz);
4879		field = fieldbuf;
4880		tmp += sz;
4881
4882		(void) strlcpy(keybuf, key, sizeof keybuf);
4883		fmtkey = keybuf;
4884		if (strcmp(field, "alias") == 0)
4885		{
4886			/*
4887			**  for alias lookups, replace any punctuation
4888			**  characters with '-'
4889			*/
4890
4891			for (tmp2 = fmtkey; *tmp2 !=  '\0'; tmp2++)
4892			{
4893				if (isascii(*tmp2) && ispunct(*tmp2))
4894					*tmp2 = '-';
4895			}
4896			tmp2 = field;
4897		}
4898		else if (strcmp(field,"spacedname") == 0)
4899		{
4900			/*
4901			**  for "spaced" name lookups, replace any
4902			**  punctuation characters with a space
4903			*/
4904
4905			for (tmp2 = fmtkey; *tmp2 != '\0'; tmp2++)
4906			{
4907				if (isascii(*tmp2) && ispunct(*tmp2) &&
4908				    *tmp2 != '*')
4909					*tmp2 = ' ';
4910			}
4911			tmp2 = &(field[6]);
4912		}
4913		else
4914			tmp2 = field;
4915
4916		if (LogLevel > 9)
4917			sm_syslog(LOG_NOTICE, CurEnv->e_id,
4918				  "ph_map_lookup: query %s=\"%s\" return email",
4919				  tmp2, fmtkey);
4920		if (tTd(38, 20))
4921			dprintf("ph_map_lookup: query %s=\"%s\" return email\n",
4922				tmp2, fmtkey);
4923
4924		j = 0;
4925
4926		if (fprintf(pmap->ph_to_server, "query %s=%s return email\n",
4927			    tmp2, fmtkey) < 0)
4928			message = "qi query command failed";
4929		else if (fflush(pmap->ph_to_server) != 0)
4930			message = "qi fflush failed";
4931		else if ((server_data = ReadQi(pmap->ph_from_server,
4932					       &j)) == NULL)
4933			message = "ReadQi() returned NULL";
4934
4935#if _FFR_PHMAP_TIMEOUT
4936		if ((hold_data[hold_data_idx] = server_data) != NULL)
4937		{
4938			/* save pointer for later free() */
4939			hold_data_idx++;
4940		}
4941#endif /* _FFR_PHMAP_TIMEOUT */
4942
4943		if (server_data == NULL ||
4944		    (server_data->code >= 400 &&
4945		     server_data->code < 500))
4946		{
4947			/* temporary failure */
4948			*pstat = EX_TEMPFAIL;
4949#if _FFR_PHMAP_TIMEOUT
4950			break;
4951#else /* _FFR_PHMAP_TIMEOUT */
4952			if (server_data != NULL)
4953			{
4954				FreeQIR(server_data);
4955				server_data = NULL;
4956			}
4957			return NULL;
4958#endif /* _FFR_PHMAP_TIMEOUT */
4959		}
4960
4961		/*
4962		**  if we found a single match, break out.
4963		**  otherwise, try the next field.
4964		*/
4965
4966		if (j == 1)
4967			break;
4968
4969		/*
4970		**  check for a single response which is an error:
4971		**  ReadQi() doesn't set j on error responses,
4972		**  but we should stop here instead of moving on if
4973		**  it happens (e.g., alias found but email field empty)
4974		*/
4975
4976		for (qirp = server_data;
4977		     qirp != NULL && qirp->code < 0;
4978		     qirp++)
4979		{
4980			if (tTd(38, 20))
4981				dprintf("ph_map_lookup: QIR: %d:%d:%d:%s\n",
4982					qirp->code, qirp->subcode, qirp->field,
4983					(qirp->message ? qirp->message
4984					 : "[NULL]"));
4985			if (qirp->code <= -500)
4986			{
4987				j = 0;
4988				goto ph_map_lookup_abort;
4989			}
4990		}
4991
4992#if _FFR_PHMAP_TIMEOUT
4993	} while (*tmp != '\0' && hold_data_idx < MAX_PH_FIELDS);
4994#else /* _FFR_PHMAP_TIMEOUT */
4995	} while (*tmp != '\0');
4996#endif /* _FFR_PHMAP_TIMEOUT */
4997
4998  ph_map_lookup_abort:
4999#if _FFR_PHMAP_TIMEOUT
5000	if (ev != NULL)
5001		clrevent(ev);
5002
5003	/*
5004	**  Return EX_TEMPFAIL if the timer popped
5005	**  or we got a temporary PH error
5006	*/
5007
5008	if (*pstat == EX_TEMPFAIL)
5009		ph_map_safeclose(map);
5010
5011	/* if we didn't find a single match, bail out */
5012	if (*pstat == EX_OK && j != 1)
5013		*pstat = EX_UNAVAILABLE;
5014
5015	if (*pstat == EX_OK)
5016	{
5017		/*
5018		** skip leading whitespace and chop at first address
5019		*/
5020
5021		for (tmp = server_data->message;
5022		     isascii(*tmp) && isspace(*tmp);
5023		     tmp++)
5024			continue;
5025
5026		for (tmp2 = tmp; *tmp2 != '\0'; tmp2++)
5027		{
5028			if (isascii(*tmp2) && isspace(*tmp2))
5029			{
5030				*tmp2 = '\0';
5031				break;
5032			}
5033		}
5034
5035		if (tTd(38,20))
5036			dprintf("ph_map_lookup: %s => %s\n", key, tmp);
5037
5038		if (bitset(MF_MATCHONLY, map->map_mflags))
5039			message = map_rewrite(map, key, strlen(key), NULL);
5040		else
5041			message = map_rewrite(map, tmp, strlen(tmp), args);
5042	}
5043
5044	/*
5045	**  Deferred free() of returned server_data values
5046	**  the deferral is to avoid the risk of a free() being
5047	**  interrupted by the event timer.  By now the timeout event
5048	**  has been cleared and none of the data is still in use.
5049	*/
5050
5051	while (--hold_data_idx >= 0)
5052	{
5053		if (hold_data[hold_data_idx] != NULL)
5054			FreeQIR(hold_data[hold_data_idx]);
5055	}
5056
5057	if (*pstat == EX_OK)
5058		return message;
5059
5060	return NULL;
5061#else /* _FFR_PHMAP_TIMEOUT */
5062	/* if we didn't find a single match, bail out */
5063	if (j != 1)
5064	{
5065		*pstat = EX_UNAVAILABLE;
5066		if (server_data != NULL)
5067		{
5068			FreeQIR(server_data);
5069			server_data = NULL;
5070		}
5071		return NULL;
5072	}
5073
5074	/*
5075	** skip leading whitespace and chop at first address
5076	*/
5077
5078	for (tmp = server_data->message;
5079	     isascii(*tmp) && isspace(*tmp);
5080	     tmp++)
5081		continue;
5082
5083	for (tmp2 = tmp; *tmp2 != '\0'; tmp2++)
5084	{
5085		if (isascii(*tmp2) && isspace(*tmp2))
5086		{
5087			*tmp2 = '\0';
5088			break;
5089		}
5090	}
5091
5092	if (tTd(38,20))
5093		dprintf("ph_map_lookup: %s => %s\n", key, tmp);
5094
5095	if (bitset(MF_MATCHONLY, map->map_mflags))
5096		message = map_rewrite(map, key, strlen(key), NULL);
5097	else
5098		message = map_rewrite(map, tmp, strlen(tmp), args);
5099	if (server_data != NULL)
5100	{
5101		FreeQIR(server_data);
5102		server_data = NULL;
5103	}
5104	return message;
5105#endif /* _FFR_PHMAP_TIMEOUT */
5106}
5107#endif /* PH_MAP */
5108/*
5109**  syslog map
5110*/
5111
5112#define map_prio	map_lockfd	/* overload field */
5113
5114/*
5115**  SYSLOG_MAP_PARSEARGS -- check for priority level to syslog messages.
5116*/
5117
5118bool
5119syslog_map_parseargs(map, args)
5120	MAP *map;
5121	char *args;
5122{
5123	char *p = args;
5124	char *priority = NULL;
5125
5126	/* there is no check whether there is really an argument */
5127	while (*p != '\0')
5128	{
5129		while (isascii(*p) && isspace(*p))
5130			p++;
5131		if (*p != '-')
5132			break;
5133		++p;
5134		if (*p == 'D')
5135		{
5136			map->map_mflags |= MF_DEFER;
5137			++p;
5138		}
5139		else if (*p == 'S')
5140		{
5141			map->map_spacesub = *++p;
5142			if (*p != '\0')
5143				p++;
5144		}
5145		else if (*p == 'L')
5146		{
5147			while (*++p != '\0' && isascii(*p) && isspace(*p))
5148				continue;
5149			if (*p == '\0')
5150				break;
5151			priority = p;
5152			while (*p != '\0' && !(isascii(*p) && isspace(*p)))
5153				p++;
5154			if (*p != '\0')
5155				*p++ = '\0';
5156		}
5157		else
5158		{
5159			syserr("Illegal option %c map syslog", *p);
5160			++p;
5161		}
5162	}
5163
5164	if (priority == NULL)
5165		map->map_prio = LOG_INFO;
5166	else
5167	{
5168		if (strncasecmp("LOG_", priority, 4) == 0)
5169			priority += 4;
5170
5171#ifdef LOG_EMERG
5172		if (strcasecmp("EMERG", priority) == 0)
5173			map->map_prio = LOG_EMERG;
5174		else
5175#endif /* LOG_EMERG */
5176#ifdef LOG_ALERT
5177		if (strcasecmp("ALERT", priority) == 0)
5178			map->map_prio = LOG_ALERT;
5179		else
5180#endif /* LOG_ALERT */
5181#ifdef LOG_CRIT
5182		if (strcasecmp("CRIT", priority) == 0)
5183			map->map_prio = LOG_CRIT;
5184		else
5185#endif /* LOG_CRIT */
5186#ifdef LOG_ERR
5187		if (strcasecmp("ERR", priority) == 0)
5188			map->map_prio = LOG_ERR;
5189		else
5190#endif /* LOG_ERR */
5191#ifdef LOG_WARNING
5192		if (strcasecmp("WARNING", priority) == 0)
5193			map->map_prio = LOG_WARNING;
5194		else
5195#endif /* LOG_WARNING */
5196#ifdef LOG_NOTICE
5197		if (strcasecmp("NOTICE", priority) == 0)
5198			map->map_prio = LOG_NOTICE;
5199		else
5200#endif /* LOG_NOTICE */
5201#ifdef LOG_INFO
5202		if (strcasecmp("INFO", priority) == 0)
5203			map->map_prio = LOG_INFO;
5204		else
5205#endif /* LOG_INFO */
5206#ifdef LOG_DEBUG
5207		if (strcasecmp("DEBUG", priority) == 0)
5208			map->map_prio = LOG_DEBUG;
5209		else
5210#endif /* LOG_DEBUG */
5211		{
5212			syserr("syslog_map_parseargs: Unknown priority %s\n",
5213			       priority);
5214			return FALSE;
5215		}
5216	}
5217	return TRUE;
5218}
5219
5220/*
5221**  SYSLOG_MAP_LOOKUP -- rewrite and syslog message.  Always return empty string
5222*/
5223
5224char *
5225syslog_map_lookup(map, string, args, statp)
5226	MAP *map;
5227	char *string;
5228	char **args;
5229	int *statp;
5230{
5231	char *ptr = map_rewrite(map, string, strlen(string), args);
5232
5233	if (ptr != NULL)
5234	{
5235		if (tTd(38, 20))
5236			dprintf("syslog_map_lookup(%s (priority %d): %s\n",
5237				map->map_mname, map->map_prio, ptr);
5238
5239		sm_syslog(map->map_prio, CurEnv->e_id, "%s", ptr);
5240	}
5241
5242	*statp = EX_OK;
5243	return "";
5244}
5245
5246/*
5247**  HESIOD Modules
5248*/
5249
5250#ifdef HESIOD
5251
5252bool
5253hes_map_open(map, mode)
5254	MAP *map;
5255	int mode;
5256{
5257	if (tTd(38, 2))
5258		dprintf("hes_map_open(%s, %s, %d)\n",
5259			map->map_mname, map->map_file, mode);
5260
5261	if (mode != O_RDONLY)
5262	{
5263		/* issue a pseudo-error message */
5264# ifdef ENOSYS
5265		errno = ENOSYS;
5266# else /* ENOSYS */
5267#  ifdef EFTYPE
5268		errno = EFTYPE;
5269#  else /* EFTYPE */
5270		errno = ENXIO;
5271#  endif /* EFTYPE */
5272# endif /* ENOSYS */
5273		return FALSE;
5274	}
5275
5276# ifdef HESIOD_INIT
5277	if (HesiodContext != NULL || hesiod_init(&HesiodContext) == 0)
5278		return TRUE;
5279
5280	if (!bitset(MF_OPTIONAL, map->map_mflags))
5281		syserr("421 4.0.0 cannot initialize Hesiod map (%s)",
5282			errstring(errno));
5283	return FALSE;
5284# else /* HESIOD_INIT */
5285	if (hes_error() == HES_ER_UNINIT)
5286		hes_init();
5287	switch (hes_error())
5288	{
5289	  case HES_ER_OK:
5290	  case HES_ER_NOTFOUND:
5291		return TRUE;
5292	}
5293
5294	if (!bitset(MF_OPTIONAL, map->map_mflags))
5295		syserr("421 4.0.0 cannot initialize Hesiod map (%d)", hes_error());
5296
5297	return FALSE;
5298# endif /* HESIOD_INIT */
5299}
5300
5301char *
5302hes_map_lookup(map, name, av, statp)
5303	MAP *map;
5304	char *name;
5305	char **av;
5306	int *statp;
5307{
5308	char **hp;
5309
5310	if (tTd(38, 20))
5311		dprintf("hes_map_lookup(%s, %s)\n", map->map_file, name);
5312
5313	if (name[0] == '\\')
5314	{
5315		char *np;
5316		int nl;
5317		int save_errno;
5318		char nbuf[MAXNAME];
5319
5320		nl = strlen(name);
5321		if (nl < sizeof nbuf - 1)
5322			np = nbuf;
5323		else
5324			np = xalloc(strlen(name) + 2);
5325		np[0] = '\\';
5326		(void) strlcpy(&np[1], name, (sizeof nbuf) - 1);
5327# ifdef HESIOD_INIT
5328		hp = hesiod_resolve(HesiodContext, np, map->map_file);
5329# else /* HESIOD_INIT */
5330		hp = hes_resolve(np, map->map_file);
5331# endif /* HESIOD_INIT */
5332		save_errno = errno;
5333		if (np != nbuf)
5334			sm_free(np);
5335		errno = save_errno;
5336	}
5337	else
5338	{
5339# ifdef HESIOD_INIT
5340		hp = hesiod_resolve(HesiodContext, name, map->map_file);
5341# else /* HESIOD_INIT */
5342		hp = hes_resolve(name, map->map_file);
5343# endif /* HESIOD_INIT */
5344	}
5345# ifdef HESIOD_INIT
5346	if (hp == NULL || *hp == NULL)
5347	{
5348		switch (errno)
5349		{
5350		  case ENOENT:
5351			  *statp = EX_NOTFOUND;
5352			  break;
5353		  case ECONNREFUSED:
5354		  case EMSGSIZE:
5355			  *statp = EX_TEMPFAIL;
5356			  break;
5357		  case ENOMEM:
5358		  default:
5359			  *statp = EX_UNAVAILABLE;
5360			  break;
5361		}
5362		if (hp != NULL)
5363			hesiod_free_list(HesiodContext, hp);
5364		return NULL;
5365	}
5366# else /* HESIOD_INIT */
5367	if (hp == NULL || hp[0] == NULL)
5368	{
5369		switch (hes_error())
5370		{
5371		  case HES_ER_OK:
5372			*statp = EX_OK;
5373			break;
5374
5375		  case HES_ER_NOTFOUND:
5376			*statp = EX_NOTFOUND;
5377			break;
5378
5379		  case HES_ER_CONFIG:
5380			*statp = EX_UNAVAILABLE;
5381			break;
5382
5383		  case HES_ER_NET:
5384			*statp = EX_TEMPFAIL;
5385			break;
5386		}
5387		return NULL;
5388	}
5389# endif /* HESIOD_INIT */
5390
5391	if (bitset(MF_MATCHONLY, map->map_mflags))
5392		return map_rewrite(map, name, strlen(name), NULL);
5393	else
5394		return map_rewrite(map, hp[0], strlen(hp[0]), av);
5395}
5396
5397#endif /* HESIOD */
5398/*
5399**  NeXT NETINFO Modules
5400*/
5401
5402#if NETINFO
5403
5404# define NETINFO_DEFAULT_DIR		"/aliases"
5405# define NETINFO_DEFAULT_PROPERTY	"members"
5406
5407/*
5408**  NI_MAP_OPEN -- open NetInfo Aliases
5409*/
5410
5411bool
5412ni_map_open(map, mode)
5413	MAP *map;
5414	int mode;
5415{
5416	if (tTd(38, 2))
5417		dprintf("ni_map_open(%s, %s, %d)\n",
5418			map->map_mname, map->map_file, mode);
5419	mode &= O_ACCMODE;
5420
5421	if (*map->map_file == '\0')
5422		map->map_file = NETINFO_DEFAULT_DIR;
5423
5424	if (map->map_valcolnm == NULL)
5425		map->map_valcolnm = NETINFO_DEFAULT_PROPERTY;
5426
5427	if (map->map_coldelim == '\0' && bitset(MF_ALIAS, map->map_mflags))
5428		map->map_coldelim = ',';
5429
5430	return TRUE;
5431}
5432
5433
5434/*
5435**  NI_MAP_LOOKUP -- look up a datum in NetInfo
5436*/
5437
5438char *
5439ni_map_lookup(map, name, av, statp)
5440	MAP *map;
5441	char *name;
5442	char **av;
5443	int *statp;
5444{
5445	char *res;
5446	char *propval;
5447
5448	if (tTd(38, 20))
5449		dprintf("ni_map_lookup(%s, %s)\n", map->map_mname, name);
5450
5451	propval = ni_propval(map->map_file, map->map_keycolnm, name,
5452			     map->map_valcolnm, map->map_coldelim);
5453
5454	if (propval == NULL)
5455		return NULL;
5456
5457	if (bitset(MF_MATCHONLY, map->map_mflags))
5458		res = map_rewrite(map, name, strlen(name), NULL);
5459	else
5460		res = map_rewrite(map, propval, strlen(propval), av);
5461	sm_free(propval);
5462	return res;
5463}
5464
5465
5466static bool
5467ni_getcanonname(name, hbsize, statp)
5468	char *name;
5469	int hbsize;
5470	int *statp;
5471{
5472	char *vptr;
5473	char *ptr;
5474	char nbuf[MAXNAME + 1];
5475
5476	if (tTd(38, 20))
5477		dprintf("ni_getcanonname(%s)\n", name);
5478
5479	if (strlcpy(nbuf, name, sizeof nbuf) >= sizeof nbuf)
5480	{
5481		*statp = EX_UNAVAILABLE;
5482		return FALSE;
5483	}
5484	(void) shorten_hostname(nbuf);
5485
5486	/* we only accept single token search key */
5487	if (strchr(nbuf, '.'))
5488	{
5489		*statp = EX_NOHOST;
5490		return FALSE;
5491	}
5492
5493	/* Do the search */
5494	vptr = ni_propval("/machines", NULL, nbuf, "name", '\n');
5495
5496	if (vptr == NULL)
5497	{
5498		*statp = EX_NOHOST;
5499		return FALSE;
5500	}
5501
5502	/* Only want the first machine name */
5503	if ((ptr = strchr(vptr, '\n')) != NULL)
5504		*ptr = '\0';
5505
5506	if (hbsize >= strlen(vptr))
5507	{
5508		(void) strlcpy(name, vptr, hbsize);
5509		sm_free(vptr);
5510		*statp = EX_OK;
5511		return TRUE;
5512	}
5513	*statp = EX_UNAVAILABLE;
5514	sm_free(vptr);
5515	return FALSE;
5516}
5517
5518
5519/*
5520**  NI_PROPVAL -- NetInfo property value lookup routine
5521**
5522**	Parameters:
5523**		keydir -- the NetInfo directory name in which to search
5524**			for the key.
5525**		keyprop -- the name of the property in which to find the
5526**			property we are interested.  Defaults to "name".
5527**		keyval -- the value for which we are really searching.
5528**		valprop -- the property name for the value in which we
5529**			are interested.
5530**		sepchar -- if non-nil, this can be multiple-valued, and
5531**			we should return a string separated by this
5532**			character.
5533**
5534**	Returns:
5535**		NULL -- if:
5536**			1. the directory is not found
5537**			2. the property name is not found
5538**			3. the property contains multiple values
5539**			4. some error occurred
5540**		else -- the value of the lookup.
5541**
5542**	Example:
5543**		To search for an alias value, use:
5544**		  ni_propval("/aliases", "name", aliasname, "members", ',')
5545**
5546**	Notes:
5547**		Caller should free the return value of ni_proval
5548*/
5549
5550# include <netinfo/ni.h>
5551
5552# define LOCAL_NETINFO_DOMAIN	"."
5553# define PARENT_NETINFO_DOMAIN	".."
5554# define MAX_NI_LEVELS		256
5555
5556char *
5557ni_propval(keydir, keyprop, keyval, valprop, sepchar)
5558	char *keydir;
5559	char *keyprop;
5560	char *keyval;
5561	char *valprop;
5562	int sepchar;
5563{
5564	char *propval = NULL;
5565	int i;
5566	int j, alen, l;
5567	void *ni = NULL;
5568	void *lastni = NULL;
5569	ni_status nis;
5570	ni_id nid;
5571	ni_namelist ninl;
5572	register char *p;
5573	char keybuf[1024];
5574
5575	/*
5576	**  Create the full key from the two parts.
5577	**
5578	**	Note that directory can end with, e.g., "name=" to specify
5579	**	an alternate search property.
5580	*/
5581
5582	i = strlen(keydir) + strlen(keyval) + 2;
5583	if (keyprop != NULL)
5584		i += strlen(keyprop) + 1;
5585	if (i >= sizeof keybuf)
5586		return NULL;
5587	(void) strlcpy(keybuf, keydir, sizeof keybuf);
5588	(void) strlcat(keybuf, "/", sizeof keybuf);
5589	if (keyprop != NULL)
5590	{
5591		(void) strlcat(keybuf, keyprop, sizeof keybuf);
5592		(void) strlcat(keybuf, "=", sizeof keybuf);
5593	}
5594	(void) strlcat(keybuf, keyval, sizeof keybuf);
5595
5596	if (tTd(38, 21))
5597		dprintf("ni_propval(%s, %s, %s, %s, %d) keybuf='%s'\n",
5598			keydir, keyprop, keyval, valprop, sepchar, keybuf);
5599	/*
5600	**  If the passed directory and property name are found
5601	**  in one of netinfo domains we need to search (starting
5602	**  from the local domain moving all the way back to the
5603	**  root domain) set propval to the property's value
5604	**  and return it.
5605	*/
5606
5607	for (i = 0; i < MAX_NI_LEVELS && propval == NULL; i++)
5608	{
5609		if (i == 0)
5610		{
5611			nis = ni_open(NULL, LOCAL_NETINFO_DOMAIN, &ni);
5612			if (tTd(38, 20))
5613				dprintf("ni_open(LOCAL) = %d\n", nis);
5614		}
5615		else
5616		{
5617			if (lastni != NULL)
5618				ni_free(lastni);
5619			lastni = ni;
5620			nis = ni_open(lastni, PARENT_NETINFO_DOMAIN, &ni);
5621			if (tTd(38, 20))
5622				dprintf("ni_open(PARENT) = %d\n", nis);
5623		}
5624
5625		/*
5626		**  Don't bother if we didn't get a handle on a
5627		**  proper domain.  This is not necessarily an error.
5628		**  We would get a positive ni_status if, for instance
5629		**  we never found the directory or property and tried
5630		**  to open the parent of the root domain!
5631		*/
5632
5633		if (nis != 0)
5634			break;
5635
5636		/*
5637		**  Find the path to the server information.
5638		*/
5639
5640		if (ni_pathsearch(ni, &nid, keybuf) != 0)
5641			continue;
5642
5643		/*
5644		**  Find associated value information.
5645		*/
5646
5647		if (ni_lookupprop(ni, &nid, valprop, &ninl) != 0)
5648			continue;
5649
5650		if (tTd(38, 20))
5651			dprintf("ni_lookupprop: len=%d\n",
5652				ninl.ni_namelist_len);
5653
5654		/*
5655		**  See if we have an acceptable number of values.
5656		*/
5657
5658		if (ninl.ni_namelist_len <= 0)
5659			continue;
5660
5661		if (sepchar == '\0' && ninl.ni_namelist_len > 1)
5662		{
5663			ni_namelist_free(&ninl);
5664			continue;
5665		}
5666
5667		/*
5668		**  Calculate number of bytes needed and build result
5669		*/
5670
5671		alen = 1;
5672		for (j = 0; j < ninl.ni_namelist_len; j++)
5673			alen += strlen(ninl.ni_namelist_val[j]) + 1;
5674		propval = p = xalloc(alen);
5675		for (j = 0; j < ninl.ni_namelist_len; j++)
5676		{
5677			(void) strlcpy(p, ninl.ni_namelist_val[j], alen);
5678			l = strlen(p);
5679			p += l;
5680			*p++ = sepchar;
5681			alen -= l + 1;
5682		}
5683		*--p = '\0';
5684
5685		ni_namelist_free(&ninl);
5686	}
5687
5688	/*
5689	**  Clean up.
5690	*/
5691
5692	if (ni != NULL)
5693		ni_free(ni);
5694	if (lastni != NULL && ni != lastni)
5695		ni_free(lastni);
5696	if (tTd(38, 20))
5697		dprintf("ni_propval returns: '%s'\n", propval);
5698
5699	return propval;
5700}
5701
5702#endif /* NETINFO */
5703/*
5704**  TEXT (unindexed text file) Modules
5705**
5706**	This code donated by Sun Microsystems.
5707*/
5708
5709#define map_sff		map_lockfd	/* overload field */
5710
5711
5712/*
5713**  TEXT_MAP_OPEN -- open text table
5714*/
5715
5716bool
5717text_map_open(map, mode)
5718	MAP *map;
5719	int mode;
5720{
5721	long sff;
5722	int i;
5723
5724	if (tTd(38, 2))
5725		dprintf("text_map_open(%s, %s, %d)\n",
5726			map->map_mname, map->map_file, mode);
5727
5728	mode &= O_ACCMODE;
5729	if (mode != O_RDONLY)
5730	{
5731		errno = EPERM;
5732		return FALSE;
5733	}
5734
5735	if (*map->map_file == '\0')
5736	{
5737		syserr("text map \"%s\": file name required",
5738			map->map_mname);
5739		return FALSE;
5740	}
5741
5742	if (map->map_file[0] != '/')
5743	{
5744		syserr("text map \"%s\": file name must be fully qualified",
5745			map->map_mname);
5746		return FALSE;
5747	}
5748
5749	sff = SFF_ROOTOK|SFF_REGONLY;
5750	if (!bitnset(DBS_LINKEDMAPINWRITABLEDIR, DontBlameSendmail))
5751		sff |= SFF_NOWLINK;
5752	if (!bitnset(DBS_MAPINUNSAFEDIRPATH, DontBlameSendmail))
5753		sff |= SFF_SAFEDIRPATH;
5754	if ((i = safefile(map->map_file, RunAsUid, RunAsGid, RunAsUserName,
5755			  sff, S_IRUSR, NULL)) != 0)
5756	{
5757		int save_errno = errno;
5758
5759		/* cannot open this map */
5760		if (tTd(38, 2))
5761			dprintf("\tunsafe map file: %d\n", i);
5762		errno = save_errno;
5763		if (!bitset(MF_OPTIONAL, map->map_mflags))
5764			syserr("text map \"%s\": unsafe map file %s",
5765				map->map_mname, map->map_file);
5766		return FALSE;
5767	}
5768
5769	if (map->map_keycolnm == NULL)
5770		map->map_keycolno = 0;
5771	else
5772	{
5773		if (!(isascii(*map->map_keycolnm) && isdigit(*map->map_keycolnm)))
5774		{
5775			syserr("text map \"%s\", file %s: -k should specify a number, not %s",
5776				map->map_mname, map->map_file,
5777				map->map_keycolnm);
5778			return FALSE;
5779		}
5780		map->map_keycolno = atoi(map->map_keycolnm);
5781	}
5782
5783	if (map->map_valcolnm == NULL)
5784		map->map_valcolno = 0;
5785	else
5786	{
5787		if (!(isascii(*map->map_valcolnm) && isdigit(*map->map_valcolnm)))
5788		{
5789			syserr("text map \"%s\", file %s: -v should specify a number, not %s",
5790					map->map_mname, map->map_file,
5791					map->map_valcolnm);
5792			return FALSE;
5793		}
5794		map->map_valcolno = atoi(map->map_valcolnm);
5795	}
5796
5797	if (tTd(38, 2))
5798	{
5799		dprintf("text_map_open(%s, %s): delimiter = ",
5800			map->map_mname, map->map_file);
5801		if (map->map_coldelim == '\0')
5802			dprintf("(white space)\n");
5803		else
5804			dprintf("%c\n", map->map_coldelim);
5805	}
5806
5807	map->map_sff = sff;
5808	return TRUE;
5809}
5810
5811
5812/*
5813**  TEXT_MAP_LOOKUP -- look up a datum in a TEXT table
5814*/
5815
5816char *
5817text_map_lookup(map, name, av, statp)
5818	MAP *map;
5819	char *name;
5820	char **av;
5821	int *statp;
5822{
5823	char *vp;
5824	auto int vsize;
5825	int buflen;
5826	FILE *f;
5827	char delim;
5828	int key_idx;
5829	bool found_it;
5830	long sff = map->map_sff;
5831	char search_key[MAXNAME + 1];
5832	char linebuf[MAXLINE];
5833	char buf[MAXNAME + 1];
5834
5835	found_it = FALSE;
5836	if (tTd(38, 20))
5837		dprintf("text_map_lookup(%s, %s)\n", map->map_mname,  name);
5838
5839	buflen = strlen(name);
5840	if (buflen > sizeof search_key - 1)
5841		buflen = sizeof search_key - 1;
5842	memmove(search_key, name, buflen);
5843	search_key[buflen] = '\0';
5844	if (!bitset(MF_NOFOLDCASE, map->map_mflags))
5845		makelower(search_key);
5846
5847	f = safefopen(map->map_file, O_RDONLY, FileMode, sff);
5848	if (f == NULL)
5849	{
5850		map->map_mflags &= ~(MF_VALID|MF_OPEN);
5851		*statp = EX_UNAVAILABLE;
5852		return NULL;
5853	}
5854	key_idx = map->map_keycolno;
5855	delim = map->map_coldelim;
5856	while (fgets(linebuf, MAXLINE, f) != NULL)
5857	{
5858		char *p;
5859
5860		/* skip comment line */
5861		if (linebuf[0] == '#')
5862			continue;
5863		p = strchr(linebuf, '\n');
5864		if (p != NULL)
5865			*p = '\0';
5866		p = get_column(linebuf, key_idx, delim, buf, sizeof buf);
5867		if (p != NULL && strcasecmp(search_key, p) == 0)
5868		{
5869			found_it = TRUE;
5870			break;
5871		}
5872	}
5873	(void) fclose(f);
5874	if (!found_it)
5875	{
5876		*statp = EX_NOTFOUND;
5877		return NULL;
5878	}
5879	vp = get_column(linebuf, map->map_valcolno, delim, buf, sizeof buf);
5880	if (vp == NULL)
5881	{
5882		*statp = EX_NOTFOUND;
5883		return NULL;
5884	}
5885	vsize = strlen(vp);
5886	*statp = EX_OK;
5887	if (bitset(MF_MATCHONLY, map->map_mflags))
5888		return map_rewrite(map, name, strlen(name), NULL);
5889	else
5890		return map_rewrite(map, vp, vsize, av);
5891}
5892
5893/*
5894**  TEXT_GETCANONNAME -- look up canonical name in hosts file
5895*/
5896
5897static bool
5898text_getcanonname(name, hbsize, statp)
5899	char *name;
5900	int hbsize;
5901	int *statp;
5902{
5903	bool found;
5904	char *dot;
5905	FILE *f;
5906	char linebuf[MAXLINE];
5907	char cbuf[MAXNAME + 1];
5908	char nbuf[MAXNAME + 1];
5909
5910	if (tTd(38, 20))
5911		dprintf("text_getcanonname(%s)\n", name);
5912
5913	if (strlen(name) >= (SIZE_T) sizeof nbuf)
5914	{
5915		*statp = EX_UNAVAILABLE;
5916		return FALSE;
5917	}
5918	(void) strlcpy(nbuf, name, sizeof nbuf);
5919	dot = shorten_hostname(nbuf);
5920
5921	f = fopen(HostsFile, "r");
5922	if (f == NULL)
5923	{
5924		*statp = EX_UNAVAILABLE;
5925		return FALSE;
5926	}
5927	found = FALSE;
5928	while (!found && fgets(linebuf, MAXLINE, f) != NULL)
5929	{
5930		char *p = strpbrk(linebuf, "#\n");
5931
5932		if (p != NULL)
5933			*p = '\0';
5934		if (linebuf[0] != '\0')
5935			found = extract_canonname(nbuf, dot, linebuf,
5936						  cbuf, sizeof cbuf);
5937	}
5938	(void) fclose(f);
5939	if (!found)
5940	{
5941		*statp = EX_NOHOST;
5942		return FALSE;
5943	}
5944
5945	if ((SIZE_T) hbsize >= strlen(cbuf))
5946	{
5947		(void) strlcpy(name, cbuf, hbsize);
5948		*statp = EX_OK;
5949		return TRUE;
5950	}
5951	*statp = EX_UNAVAILABLE;
5952	return FALSE;
5953}
5954/*
5955**  STAB (Symbol Table) Modules
5956*/
5957
5958
5959/*
5960**  STAB_MAP_LOOKUP -- look up alias in symbol table
5961*/
5962
5963/* ARGSUSED2 */
5964char *
5965stab_map_lookup(map, name, av, pstat)
5966	register MAP *map;
5967	char *name;
5968	char **av;
5969	int *pstat;
5970{
5971	register STAB *s;
5972
5973	if (tTd(38, 20))
5974		dprintf("stab_lookup(%s, %s)\n",
5975			map->map_mname, name);
5976
5977	s = stab(name, ST_ALIAS, ST_FIND);
5978	if (s != NULL)
5979		return s->s_alias;
5980	return NULL;
5981}
5982
5983
5984/*
5985**  STAB_MAP_STORE -- store in symtab (actually using during init, not rebuild)
5986*/
5987
5988void
5989stab_map_store(map, lhs, rhs)
5990	register MAP *map;
5991	char *lhs;
5992	char *rhs;
5993{
5994	register STAB *s;
5995
5996	s = stab(lhs, ST_ALIAS, ST_ENTER);
5997	s->s_alias = newstr(rhs);
5998}
5999
6000
6001/*
6002**  STAB_MAP_OPEN -- initialize (reads data file)
6003**
6004**	This is a wierd case -- it is only intended as a fallback for
6005**	aliases.  For this reason, opens for write (only during a
6006**	"newaliases") always fails, and opens for read open the
6007**	actual underlying text file instead of the database.
6008*/
6009
6010bool
6011stab_map_open(map, mode)
6012	register MAP *map;
6013	int mode;
6014{
6015	FILE *af;
6016	long sff;
6017	struct stat st;
6018
6019	if (tTd(38, 2))
6020		dprintf("stab_map_open(%s, %s, %d)\n",
6021			map->map_mname, map->map_file, mode);
6022
6023	mode &= O_ACCMODE;
6024	if (mode != O_RDONLY)
6025	{
6026		errno = EPERM;
6027		return FALSE;
6028	}
6029
6030	sff = SFF_ROOTOK|SFF_REGONLY;
6031	if (!bitnset(DBS_LINKEDMAPINWRITABLEDIR, DontBlameSendmail))
6032		sff |= SFF_NOWLINK;
6033	if (!bitnset(DBS_MAPINUNSAFEDIRPATH, DontBlameSendmail))
6034		sff |= SFF_SAFEDIRPATH;
6035	af = safefopen(map->map_file, O_RDONLY, 0444, sff);
6036	if (af == NULL)
6037		return FALSE;
6038	readaliases(map, af, FALSE, FALSE);
6039
6040	if (fstat(fileno(af), &st) >= 0)
6041		map->map_mtime = st.st_mtime;
6042	(void) fclose(af);
6043
6044	return TRUE;
6045}
6046/*
6047**  Implicit Modules
6048**
6049**	Tries several types.  For back compatibility of aliases.
6050*/
6051
6052
6053/*
6054**  IMPL_MAP_LOOKUP -- lookup in best open database
6055*/
6056
6057char *
6058impl_map_lookup(map, name, av, pstat)
6059	MAP *map;
6060	char *name;
6061	char **av;
6062	int *pstat;
6063{
6064	if (tTd(38, 20))
6065		dprintf("impl_map_lookup(%s, %s)\n",
6066			map->map_mname, name);
6067
6068#ifdef NEWDB
6069	if (bitset(MF_IMPL_HASH, map->map_mflags))
6070		return db_map_lookup(map, name, av, pstat);
6071#endif /* NEWDB */
6072#ifdef NDBM
6073	if (bitset(MF_IMPL_NDBM, map->map_mflags))
6074		return ndbm_map_lookup(map, name, av, pstat);
6075#endif /* NDBM */
6076	return stab_map_lookup(map, name, av, pstat);
6077}
6078
6079/*
6080**  IMPL_MAP_STORE -- store in open databases
6081*/
6082
6083void
6084impl_map_store(map, lhs, rhs)
6085	MAP *map;
6086	char *lhs;
6087	char *rhs;
6088{
6089	if (tTd(38, 12))
6090		dprintf("impl_map_store(%s, %s, %s)\n",
6091			map->map_mname, lhs, rhs);
6092#ifdef NEWDB
6093	if (bitset(MF_IMPL_HASH, map->map_mflags))
6094		db_map_store(map, lhs, rhs);
6095#endif /* NEWDB */
6096#ifdef NDBM
6097	if (bitset(MF_IMPL_NDBM, map->map_mflags))
6098		ndbm_map_store(map, lhs, rhs);
6099#endif /* NDBM */
6100	stab_map_store(map, lhs, rhs);
6101}
6102
6103/*
6104**  IMPL_MAP_OPEN -- implicit database open
6105*/
6106
6107bool
6108impl_map_open(map, mode)
6109	MAP *map;
6110	int mode;
6111{
6112	if (tTd(38, 2))
6113		dprintf("impl_map_open(%s, %s, %d)\n",
6114			map->map_mname, map->map_file, mode);
6115
6116	mode &= O_ACCMODE;
6117#ifdef NEWDB
6118	map->map_mflags |= MF_IMPL_HASH;
6119	if (hash_map_open(map, mode))
6120	{
6121# ifdef NDBM_YP_COMPAT
6122		if (mode == O_RDONLY || strstr(map->map_file, "/yp/") == NULL)
6123# endif /* NDBM_YP_COMPAT */
6124			return TRUE;
6125	}
6126	else
6127		map->map_mflags &= ~MF_IMPL_HASH;
6128#endif /* NEWDB */
6129#ifdef NDBM
6130	map->map_mflags |= MF_IMPL_NDBM;
6131	if (ndbm_map_open(map, mode))
6132	{
6133		return TRUE;
6134	}
6135	else
6136		map->map_mflags &= ~MF_IMPL_NDBM;
6137#endif /* NDBM */
6138
6139#if defined(NEWDB) || defined(NDBM)
6140	if (Verbose)
6141		message("WARNING: cannot open alias database %s%s",
6142			map->map_file,
6143			mode == O_RDONLY ? "; reading text version" : "");
6144#else /* defined(NEWDB) || defined(NDBM) */
6145	if (mode != O_RDONLY)
6146		usrerr("Cannot rebuild aliases: no database format defined");
6147#endif /* defined(NEWDB) || defined(NDBM) */
6148
6149	if (mode == O_RDONLY)
6150		return stab_map_open(map, mode);
6151	else
6152		return FALSE;
6153}
6154
6155
6156/*
6157**  IMPL_MAP_CLOSE -- close any open database(s)
6158*/
6159
6160void
6161impl_map_close(map)
6162	MAP *map;
6163{
6164	if (tTd(38, 9))
6165		dprintf("impl_map_close(%s, %s, %lx)\n",
6166			map->map_mname, map->map_file, map->map_mflags);
6167#ifdef NEWDB
6168	if (bitset(MF_IMPL_HASH, map->map_mflags))
6169	{
6170		db_map_close(map);
6171		map->map_mflags &= ~MF_IMPL_HASH;
6172	}
6173#endif /* NEWDB */
6174
6175#ifdef NDBM
6176	if (bitset(MF_IMPL_NDBM, map->map_mflags))
6177	{
6178		ndbm_map_close(map);
6179		map->map_mflags &= ~MF_IMPL_NDBM;
6180	}
6181#endif /* NDBM */
6182}
6183/*
6184**  User map class.
6185**
6186**	Provides access to the system password file.
6187*/
6188
6189/*
6190**  USER_MAP_OPEN -- open user map
6191**
6192**	Really just binds field names to field numbers.
6193*/
6194
6195bool
6196user_map_open(map, mode)
6197	MAP *map;
6198	int mode;
6199{
6200	if (tTd(38, 2))
6201		dprintf("user_map_open(%s, %d)\n",
6202			map->map_mname, mode);
6203
6204	mode &= O_ACCMODE;
6205	if (mode != O_RDONLY)
6206	{
6207		/* issue a pseudo-error message */
6208#ifdef ENOSYS
6209		errno = ENOSYS;
6210#else /* ENOSYS */
6211# ifdef EFTYPE
6212		errno = EFTYPE;
6213# else /* EFTYPE */
6214		errno = ENXIO;
6215# endif /* EFTYPE */
6216#endif /* ENOSYS */
6217		return FALSE;
6218	}
6219	if (map->map_valcolnm == NULL)
6220		/* EMPTY */
6221		/* nothing */ ;
6222	else if (strcasecmp(map->map_valcolnm, "name") == 0)
6223		map->map_valcolno = 1;
6224	else if (strcasecmp(map->map_valcolnm, "passwd") == 0)
6225		map->map_valcolno = 2;
6226	else if (strcasecmp(map->map_valcolnm, "uid") == 0)
6227		map->map_valcolno = 3;
6228	else if (strcasecmp(map->map_valcolnm, "gid") == 0)
6229		map->map_valcolno = 4;
6230	else if (strcasecmp(map->map_valcolnm, "gecos") == 0)
6231		map->map_valcolno = 5;
6232	else if (strcasecmp(map->map_valcolnm, "dir") == 0)
6233		map->map_valcolno = 6;
6234	else if (strcasecmp(map->map_valcolnm, "shell") == 0)
6235		map->map_valcolno = 7;
6236	else
6237	{
6238		syserr("User map %s: unknown column name %s",
6239			map->map_mname, map->map_valcolnm);
6240		return FALSE;
6241	}
6242	return TRUE;
6243}
6244
6245
6246/*
6247**  USER_MAP_LOOKUP -- look up a user in the passwd file.
6248*/
6249
6250/* ARGSUSED3 */
6251char *
6252user_map_lookup(map, key, av, statp)
6253	MAP *map;
6254	char *key;
6255	char **av;
6256	int *statp;
6257{
6258	struct passwd *pw;
6259	auto bool fuzzy;
6260
6261	if (tTd(38, 20))
6262		dprintf("user_map_lookup(%s, %s)\n",
6263			map->map_mname, key);
6264
6265	pw = finduser(key, &fuzzy);
6266	if (pw == NULL)
6267		return NULL;
6268	if (bitset(MF_MATCHONLY, map->map_mflags))
6269		return map_rewrite(map, key, strlen(key), NULL);
6270	else
6271	{
6272		char *rwval = NULL;
6273		char buf[30];
6274
6275		switch (map->map_valcolno)
6276		{
6277		  case 0:
6278		  case 1:
6279			rwval = pw->pw_name;
6280			break;
6281
6282		  case 2:
6283			rwval = pw->pw_passwd;
6284			break;
6285
6286		  case 3:
6287			snprintf(buf, sizeof buf, "%d", (int) pw->pw_uid);
6288			rwval = buf;
6289			break;
6290
6291		  case 4:
6292			snprintf(buf, sizeof buf, "%d", (int) pw->pw_gid);
6293			rwval = buf;
6294			break;
6295
6296		  case 5:
6297			rwval = pw->pw_gecos;
6298			break;
6299
6300		  case 6:
6301			rwval = pw->pw_dir;
6302			break;
6303
6304		  case 7:
6305			rwval = pw->pw_shell;
6306			break;
6307		}
6308		return map_rewrite(map, rwval, strlen(rwval), av);
6309	}
6310}
6311/*
6312**  Program map type.
6313**
6314**	This provides access to arbitrary programs.  It should be used
6315**	only very sparingly, since there is no way to bound the cost
6316**	of invoking an arbitrary program.
6317*/
6318
6319char *
6320prog_map_lookup(map, name, av, statp)
6321	MAP *map;
6322	char *name;
6323	char **av;
6324	int *statp;
6325{
6326	int i;
6327	int save_errno;
6328	int fd;
6329	int status;
6330	auto pid_t pid;
6331	register char *p;
6332	char *rval;
6333	char *argv[MAXPV + 1];
6334	char buf[MAXLINE];
6335
6336	if (tTd(38, 20))
6337		dprintf("prog_map_lookup(%s, %s) %s\n",
6338			map->map_mname, name, map->map_file);
6339
6340	i = 0;
6341	argv[i++] = map->map_file;
6342	if (map->map_rebuild != NULL)
6343	{
6344		snprintf(buf, sizeof buf, "%s", map->map_rebuild);
6345		for (p = strtok(buf, " \t"); p != NULL; p = strtok(NULL, " \t"))
6346		{
6347			if (i >= MAXPV - 1)
6348				break;
6349			argv[i++] = p;
6350		}
6351	}
6352	argv[i++] = name;
6353	argv[i] = NULL;
6354	if (tTd(38, 21))
6355	{
6356		dprintf("prog_open:");
6357		for (i = 0; argv[i] != NULL; i++)
6358			dprintf(" %s", argv[i]);
6359		dprintf("\n");
6360	}
6361	(void) blocksignal(SIGCHLD);
6362	pid = prog_open(argv, &fd, CurEnv);
6363	if (pid < 0)
6364	{
6365		if (!bitset(MF_OPTIONAL, map->map_mflags))
6366			syserr("prog_map_lookup(%s) failed (%s) -- closing",
6367				map->map_mname, errstring(errno));
6368		else if (tTd(38, 9))
6369			dprintf("prog_map_lookup(%s) failed (%s) -- closing",
6370				map->map_mname, errstring(errno));
6371		map->map_mflags &= ~(MF_VALID|MF_OPEN);
6372		*statp = EX_OSFILE;
6373		return NULL;
6374	}
6375	i = read(fd, buf, sizeof buf - 1);
6376	if (i < 0)
6377	{
6378		syserr("prog_map_lookup(%s): read error %s\n",
6379			map->map_mname, errstring(errno));
6380		rval = NULL;
6381	}
6382	else if (i == 0)
6383	{
6384		if (tTd(38, 20))
6385			dprintf("prog_map_lookup(%s): empty answer\n",
6386				map->map_mname);
6387		rval = NULL;
6388	}
6389	else
6390	{
6391		buf[i] = '\0';
6392		p = strchr(buf, '\n');
6393		if (p != NULL)
6394			*p = '\0';
6395
6396		/* collect the return value */
6397		if (bitset(MF_MATCHONLY, map->map_mflags))
6398			rval = map_rewrite(map, name, strlen(name), NULL);
6399		else
6400			rval = map_rewrite(map, buf, strlen(buf), av);
6401
6402		/* now flush any additional output */
6403		while ((i = read(fd, buf, sizeof buf)) > 0)
6404			continue;
6405	}
6406
6407	/* wait for the process to terminate */
6408	(void) close(fd);
6409	status = waitfor(pid);
6410	save_errno = errno;
6411	(void) releasesignal(SIGCHLD);
6412	errno = save_errno;
6413
6414	if (status == -1)
6415	{
6416		syserr("prog_map_lookup(%s): wait error %s\n",
6417			map->map_mname, errstring(errno));
6418		*statp = EX_SOFTWARE;
6419		rval = NULL;
6420	}
6421	else if (WIFEXITED(status))
6422	{
6423		if ((*statp = WEXITSTATUS(status)) != EX_OK)
6424			rval = NULL;
6425	}
6426	else
6427	{
6428		syserr("prog_map_lookup(%s): child died on signal %d",
6429			map->map_mname, status);
6430		*statp = EX_UNAVAILABLE;
6431		rval = NULL;
6432	}
6433	return rval;
6434}
6435/*
6436**  Sequenced map type.
6437**
6438**	Tries each map in order until something matches, much like
6439**	implicit.  Stores go to the first map in the list that can
6440**	support storing.
6441**
6442**	This is slightly unusual in that there are two interfaces.
6443**	The "sequence" interface lets you stack maps arbitrarily.
6444**	The "switch" interface builds a sequence map by looking
6445**	at a system-dependent configuration file such as
6446**	/etc/nsswitch.conf on Solaris or /etc/svc.conf on Ultrix.
6447**
6448**	We don't need an explicit open, since all maps are
6449**	opened during startup, including underlying maps.
6450*/
6451
6452/*
6453**  SEQ_MAP_PARSE -- Sequenced map parsing
6454*/
6455
6456bool
6457seq_map_parse(map, ap)
6458	MAP *map;
6459	char *ap;
6460{
6461	int maxmap;
6462
6463	if (tTd(38, 2))
6464		dprintf("seq_map_parse(%s, %s)\n", map->map_mname, ap);
6465	maxmap = 0;
6466	while (*ap != '\0')
6467	{
6468		register char *p;
6469		STAB *s;
6470
6471		/* find beginning of map name */
6472		while (isascii(*ap) && isspace(*ap))
6473			ap++;
6474		for (p = ap;
6475		     (isascii(*p) && isalnum(*p)) || *p == '_' || *p == '.';
6476		     p++)
6477			continue;
6478		if (*p != '\0')
6479			*p++ = '\0';
6480		while (*p != '\0' && (!isascii(*p) || !isalnum(*p)))
6481			p++;
6482		if (*ap == '\0')
6483		{
6484			ap = p;
6485			continue;
6486		}
6487		s = stab(ap, ST_MAP, ST_FIND);
6488		if (s == NULL)
6489		{
6490			syserr("Sequence map %s: unknown member map %s",
6491				map->map_mname, ap);
6492		}
6493		else if (maxmap == MAXMAPSTACK)
6494		{
6495			syserr("Sequence map %s: too many member maps (%d max)",
6496				map->map_mname, MAXMAPSTACK);
6497			maxmap++;
6498		}
6499		else if (maxmap < MAXMAPSTACK)
6500		{
6501			map->map_stack[maxmap++] = &s->s_map;
6502		}
6503		ap = p;
6504	}
6505	return TRUE;
6506}
6507
6508
6509/*
6510**  SWITCH_MAP_OPEN -- open a switched map
6511**
6512**	This looks at the system-dependent configuration and builds
6513**	a sequence map that does the same thing.
6514**
6515**	Every system must define a switch_map_find routine in conf.c
6516**	that will return the list of service types associated with a
6517**	given service class.
6518*/
6519
6520bool
6521switch_map_open(map, mode)
6522	MAP *map;
6523	int mode;
6524{
6525	int mapno;
6526	int nmaps;
6527	char *maptype[MAXMAPSTACK];
6528
6529	if (tTd(38, 2))
6530		dprintf("switch_map_open(%s, %s, %d)\n",
6531			map->map_mname, map->map_file, mode);
6532
6533	mode &= O_ACCMODE;
6534	nmaps = switch_map_find(map->map_file, maptype, map->map_return);
6535	if (tTd(38, 19))
6536	{
6537		dprintf("\tswitch_map_find => %d\n", nmaps);
6538		for (mapno = 0; mapno < nmaps; mapno++)
6539			dprintf("\t\t%s\n", maptype[mapno]);
6540	}
6541	if (nmaps <= 0 || nmaps > MAXMAPSTACK)
6542		return FALSE;
6543
6544	for (mapno = 0; mapno < nmaps; mapno++)
6545	{
6546		register STAB *s;
6547		char nbuf[MAXNAME + 1];
6548
6549		if (maptype[mapno] == NULL)
6550			continue;
6551		(void) snprintf(nbuf, sizeof nbuf, "%s.%s",
6552			map->map_mname, maptype[mapno]);
6553		s = stab(nbuf, ST_MAP, ST_FIND);
6554		if (s == NULL)
6555		{
6556			syserr("Switch map %s: unknown member map %s",
6557				map->map_mname, nbuf);
6558		}
6559		else
6560		{
6561			map->map_stack[mapno] = &s->s_map;
6562			if (tTd(38, 4))
6563				dprintf("\tmap_stack[%d] = %s:%s\n",
6564					mapno, s->s_map.map_class->map_cname,
6565					nbuf);
6566		}
6567	}
6568	return TRUE;
6569}
6570
6571
6572/*
6573**  SEQ_MAP_CLOSE -- close all underlying maps
6574*/
6575
6576void
6577seq_map_close(map)
6578	MAP *map;
6579{
6580	int mapno;
6581
6582	if (tTd(38, 9))
6583		dprintf("seq_map_close(%s)\n", map->map_mname);
6584
6585	for (mapno = 0; mapno < MAXMAPSTACK; mapno++)
6586	{
6587		MAP *mm = map->map_stack[mapno];
6588
6589		if (mm == NULL || !bitset(MF_OPEN, mm->map_mflags))
6590			continue;
6591		mm->map_mflags |= MF_CLOSING;
6592		mm->map_class->map_close(mm);
6593		mm->map_mflags &= ~(MF_OPEN|MF_WRITABLE|MF_CLOSING);
6594	}
6595}
6596
6597
6598/*
6599**  SEQ_MAP_LOOKUP -- sequenced map lookup
6600*/
6601
6602char *
6603seq_map_lookup(map, key, args, pstat)
6604	MAP *map;
6605	char *key;
6606	char **args;
6607	int *pstat;
6608{
6609	int mapno;
6610	int mapbit = 0x01;
6611	bool tempfail = FALSE;
6612
6613	if (tTd(38, 20))
6614		dprintf("seq_map_lookup(%s, %s)\n", map->map_mname, key);
6615
6616	for (mapno = 0; mapno < MAXMAPSTACK; mapbit <<= 1, mapno++)
6617	{
6618		MAP *mm = map->map_stack[mapno];
6619		char *rv;
6620
6621		if (mm == NULL)
6622			continue;
6623		if (!bitset(MF_OPEN, mm->map_mflags) &&
6624		    !openmap(mm))
6625		{
6626			if (bitset(mapbit, map->map_return[MA_UNAVAIL]))
6627			{
6628				*pstat = EX_UNAVAILABLE;
6629				return NULL;
6630			}
6631			continue;
6632		}
6633		*pstat = EX_OK;
6634		rv = mm->map_class->map_lookup(mm, key, args, pstat);
6635		if (rv != NULL)
6636			return rv;
6637		if (*pstat == EX_TEMPFAIL)
6638		{
6639			if (bitset(mapbit, map->map_return[MA_TRYAGAIN]))
6640				return NULL;
6641			tempfail = TRUE;
6642		}
6643		else if (bitset(mapbit, map->map_return[MA_NOTFOUND]))
6644			break;
6645	}
6646	if (tempfail)
6647		*pstat = EX_TEMPFAIL;
6648	else if (*pstat == EX_OK)
6649		*pstat = EX_NOTFOUND;
6650	return NULL;
6651}
6652
6653
6654/*
6655**  SEQ_MAP_STORE -- sequenced map store
6656*/
6657
6658void
6659seq_map_store(map, key, val)
6660	MAP *map;
6661	char *key;
6662	char *val;
6663{
6664	int mapno;
6665
6666	if (tTd(38, 12))
6667		dprintf("seq_map_store(%s, %s, %s)\n",
6668			map->map_mname, key, val);
6669
6670	for (mapno = 0; mapno < MAXMAPSTACK; mapno++)
6671	{
6672		MAP *mm = map->map_stack[mapno];
6673
6674		if (mm == NULL || !bitset(MF_WRITABLE, mm->map_mflags))
6675			continue;
6676
6677		mm->map_class->map_store(mm, key, val);
6678		return;
6679	}
6680	syserr("seq_map_store(%s, %s, %s): no writable map",
6681		map->map_mname, key, val);
6682}
6683/*
6684**  NULL stubs
6685*/
6686
6687/* ARGSUSED */
6688bool
6689null_map_open(map, mode)
6690	MAP *map;
6691	int mode;
6692{
6693	return TRUE;
6694}
6695
6696/* ARGSUSED */
6697void
6698null_map_close(map)
6699	MAP *map;
6700{
6701	return;
6702}
6703
6704char *
6705null_map_lookup(map, key, args, pstat)
6706	MAP *map;
6707	char *key;
6708	char **args;
6709	int *pstat;
6710{
6711	*pstat = EX_NOTFOUND;
6712	return NULL;
6713}
6714
6715/* ARGSUSED */
6716void
6717null_map_store(map, key, val)
6718	MAP *map;
6719	char *key;
6720	char *val;
6721{
6722	return;
6723}
6724
6725
6726/*
6727**  BOGUS stubs
6728*/
6729
6730char *
6731bogus_map_lookup(map, key, args, pstat)
6732	MAP *map;
6733	char *key;
6734	char **args;
6735	int *pstat;
6736{
6737	*pstat = EX_TEMPFAIL;
6738	return NULL;
6739}
6740
6741MAPCLASS	BogusMapClass =
6742{
6743	"bogus-map",		NULL,		0,
6744	NULL,		bogus_map_lookup,	null_map_store,
6745	null_map_open,	null_map_close,
6746};
6747/*
6748**  MACRO modules
6749*/
6750
6751char *
6752macro_map_lookup(map, name, av, statp)
6753	MAP *map;
6754	char *name;
6755	char **av;
6756	int *statp;
6757{
6758	int mid;
6759
6760	if (tTd(38, 20))
6761		dprintf("macro_map_lookup(%s, %s)\n", map->map_mname,
6762			name == NULL ? "NULL" : name);
6763
6764	if (name == NULL ||
6765	    *name == '\0' ||
6766	    (mid = macid(name, NULL)) == '\0')
6767	{
6768		*statp = EX_CONFIG;
6769		return NULL;
6770	}
6771
6772	if (av[1] == NULL)
6773		define(mid, NULL, CurEnv);
6774	else
6775		define(mid, newstr(av[1]), CurEnv);
6776
6777	*statp = EX_OK;
6778	return "";
6779}
6780/*
6781**  REGEX modules
6782*/
6783
6784#ifdef MAP_REGEX
6785
6786# include <regex.h>
6787
6788# define DEFAULT_DELIM	CONDELSE
6789
6790# define END_OF_FIELDS	-1
6791
6792# define ERRBUF_SIZE	80
6793# define MAX_MATCH	32
6794
6795# define xnalloc(s)	memset(xalloc(s), '\0', s);
6796
6797struct regex_map
6798{
6799	regex_t	*regex_pattern_buf;	/* xalloc it */
6800	int	*regex_subfields;	/* move to type MAP */
6801	char	*regex_delim;		/* move to type MAP */
6802};
6803
6804static int
6805parse_fields(s, ibuf, blen, nr_substrings)
6806	char *s;
6807	int *ibuf;		/* array */
6808	int blen;		/* number of elements in ibuf */
6809	int nr_substrings;	/* number of substrings in the pattern */
6810{
6811	register char *cp;
6812	int i = 0;
6813	bool lastone = FALSE;
6814
6815	blen--;		/* for terminating END_OF_FIELDS */
6816	cp = s;
6817	do
6818	{
6819		for (;; cp++)
6820		{
6821			if (*cp == ',')
6822			{
6823				*cp = '\0';
6824				break;
6825			}
6826			if (*cp == '\0')
6827			{
6828				lastone = TRUE;
6829				break;
6830			}
6831		}
6832		if (i < blen)
6833		{
6834			int val = atoi(s);
6835
6836			if (val < 0 || val >= nr_substrings)
6837			{
6838				syserr("field (%d) out of range, only %d substrings in pattern",
6839				       val, nr_substrings);
6840				return -1;
6841			}
6842			ibuf[i++] = val;
6843		}
6844		else
6845		{
6846			syserr("too many fields, %d max\n", blen);
6847			return -1;
6848		}
6849		s = ++cp;
6850	} while (!lastone);
6851	ibuf[i] = END_OF_FIELDS;
6852	return i;
6853}
6854
6855bool
6856regex_map_init(map, ap)
6857	MAP *map;
6858	char *ap;
6859{
6860	int regerr;
6861	struct regex_map *map_p;
6862	register char *p;
6863	char *sub_param = NULL;
6864	int pflags;
6865	static char defdstr[] = { (char)DEFAULT_DELIM, '\0' };
6866
6867	if (tTd(38, 2))
6868		dprintf("regex_map_init: mapname '%s', args '%s'\n",
6869			map->map_mname, ap);
6870
6871	pflags = REG_ICASE | REG_EXTENDED | REG_NOSUB;
6872
6873	p = ap;
6874
6875	map_p = (struct regex_map *) xnalloc(sizeof *map_p);
6876	map_p->regex_pattern_buf = (regex_t *)xnalloc(sizeof(regex_t));
6877
6878	for (;;)
6879	{
6880		while (isascii(*p) && isspace(*p))
6881			p++;
6882		if (*p != '-')
6883			break;
6884		switch (*++p)
6885		{
6886		  case 'n':	/* not */
6887			map->map_mflags |= MF_REGEX_NOT;
6888			break;
6889
6890		  case 'f':	/* case sensitive */
6891			map->map_mflags |= MF_NOFOLDCASE;
6892			pflags &= ~REG_ICASE;
6893			break;
6894
6895		  case 'b':	/* basic regular expressions */
6896			pflags &= ~REG_EXTENDED;
6897			break;
6898
6899		  case 's':	/* substring match () syntax */
6900			sub_param = ++p;
6901			pflags &= ~REG_NOSUB;
6902			break;
6903
6904		  case 'd':	/* delimiter */
6905			map_p->regex_delim = ++p;
6906			break;
6907
6908		  case 'a':	/* map append */
6909			map->map_app = ++p;
6910			break;
6911
6912		  case 'm':	/* matchonly */
6913			map->map_mflags |= MF_MATCHONLY;
6914			break;
6915
6916		  case 'S':
6917			map->map_spacesub = *++p;
6918			break;
6919
6920		  case 'D':
6921			map->map_mflags |= MF_DEFER;
6922			break;
6923
6924		}
6925		while (*p != '\0' && !(isascii(*p) && isspace(*p)))
6926			p++;
6927		if (*p != '\0')
6928			*p++ = '\0';
6929	}
6930	if (tTd(38, 3))
6931		dprintf("regex_map_init: compile '%s' 0x%x\n", p, pflags);
6932
6933	if ((regerr = regcomp(map_p->regex_pattern_buf, p, pflags)) != 0)
6934	{
6935		/* Errorhandling */
6936		char errbuf[ERRBUF_SIZE];
6937
6938		(void) regerror(regerr, map_p->regex_pattern_buf,
6939			 errbuf, ERRBUF_SIZE);
6940		syserr("pattern-compile-error: %s\n", errbuf);
6941		sm_free(map_p->regex_pattern_buf);
6942		sm_free(map_p);
6943		return FALSE;
6944	}
6945
6946	if (map->map_app != NULL)
6947		map->map_app = newstr(map->map_app);
6948	if (map_p->regex_delim != NULL)
6949		map_p->regex_delim = newstr(map_p->regex_delim);
6950	else
6951		map_p->regex_delim = defdstr;
6952
6953	if (!bitset(REG_NOSUB, pflags))
6954	{
6955		/* substring matching */
6956		int substrings;
6957		int *fields = (int *) xalloc(sizeof(int) * (MAX_MATCH + 1));
6958
6959		substrings = map_p->regex_pattern_buf->re_nsub + 1;
6960
6961		if (tTd(38, 3))
6962			dprintf("regex_map_init: nr of substrings %d\n",
6963				substrings);
6964
6965		if (substrings >= MAX_MATCH)
6966		{
6967			syserr("too many substrings, %d max\n", MAX_MATCH);
6968			sm_free(map_p->regex_pattern_buf);
6969			sm_free(map_p);
6970			return FALSE;
6971		}
6972		if (sub_param != NULL && sub_param[0] != '\0')
6973		{
6974			/* optional parameter -sfields */
6975			if (parse_fields(sub_param, fields,
6976					 MAX_MATCH + 1, substrings) == -1)
6977				return FALSE;
6978		}
6979		else
6980		{
6981			/* set default fields */
6982			int i;
6983
6984			for (i = 0; i < substrings; i++)
6985				fields[i] = i;
6986			fields[i] = END_OF_FIELDS;
6987		}
6988		map_p->regex_subfields = fields;
6989		if (tTd(38, 3))
6990		{
6991			int *ip;
6992
6993			dprintf("regex_map_init: subfields");
6994			for (ip = fields; *ip != END_OF_FIELDS; ip++)
6995				dprintf(" %d", *ip);
6996			dprintf("\n");
6997		}
6998	}
6999	map->map_db1 = (ARBPTR_T)map_p;	/* dirty hack */
7000
7001	return TRUE;
7002}
7003
7004static char *
7005regex_map_rewrite(map, s, slen, av)
7006	MAP *map;
7007	const char *s;
7008	size_t slen;
7009	char **av;
7010{
7011	if (bitset(MF_MATCHONLY, map->map_mflags))
7012		return map_rewrite(map, av[0], strlen(av[0]), NULL);
7013	else
7014		return map_rewrite(map, s, slen, av);
7015}
7016
7017char *
7018regex_map_lookup(map, name, av, statp)
7019	MAP *map;
7020	char *name;
7021	char **av;
7022	int *statp;
7023{
7024	int reg_res;
7025	struct regex_map *map_p;
7026	regmatch_t pmatch[MAX_MATCH];
7027
7028	if (tTd(38, 20))
7029	{
7030		char **cpp;
7031
7032		dprintf("regex_map_lookup: key '%s'\n", name);
7033		for (cpp = av; cpp != NULL && *cpp != NULL; cpp++)
7034			dprintf("regex_map_lookup: arg '%s'\n", *cpp);
7035	}
7036
7037	map_p = (struct regex_map *)(map->map_db1);
7038	reg_res = regexec(map_p->regex_pattern_buf,
7039			  name, MAX_MATCH, pmatch, 0);
7040
7041	if (bitset(MF_REGEX_NOT, map->map_mflags))
7042	{
7043		/* option -n */
7044		if (reg_res == REG_NOMATCH)
7045			return regex_map_rewrite(map, "", (size_t)0, av);
7046		else
7047			return NULL;
7048	}
7049	if (reg_res == REG_NOMATCH)
7050		return NULL;
7051
7052	if (map_p->regex_subfields != NULL)
7053	{
7054		/* option -s */
7055		static char retbuf[MAXNAME];
7056		int fields[MAX_MATCH + 1];
7057		bool first = TRUE;
7058		int anglecnt = 0, cmntcnt = 0, spacecnt = 0;
7059		bool quotemode = FALSE, bslashmode = FALSE;
7060		register char *dp, *sp;
7061		char *endp, *ldp;
7062		int *ip;
7063
7064		dp = retbuf;
7065		ldp = retbuf + sizeof(retbuf) - 1;
7066
7067		if (av[1] != NULL)
7068		{
7069			if (parse_fields(av[1], fields, MAX_MATCH + 1,
7070					 (int) map_p->regex_pattern_buf->re_nsub + 1) == -1)
7071			{
7072				*statp = EX_CONFIG;
7073				return NULL;
7074			}
7075			ip = fields;
7076		}
7077		else
7078			ip = map_p->regex_subfields;
7079
7080		for ( ; *ip != END_OF_FIELDS; ip++)
7081		{
7082			if (!first)
7083			{
7084				for (sp = map_p->regex_delim; *sp; sp++)
7085				{
7086					if (dp < ldp)
7087						*dp++ = *sp;
7088				}
7089			}
7090			else
7091				first = FALSE;
7092
7093
7094			if (*ip >= MAX_MATCH ||
7095			    pmatch[*ip].rm_so < 0 || pmatch[*ip].rm_eo < 0)
7096				continue;
7097
7098			sp = name + pmatch[*ip].rm_so;
7099			endp = name + pmatch[*ip].rm_eo;
7100			for (; endp > sp; sp++)
7101			{
7102				if (dp < ldp)
7103				{
7104					if (bslashmode)
7105					{
7106						*dp++ = *sp;
7107						bslashmode = FALSE;
7108					}
7109					else if (quotemode && *sp != '"' &&
7110						*sp != '\\')
7111					{
7112						*dp++ = *sp;
7113					}
7114					else switch(*dp++ = *sp)
7115					{
7116						case '\\':
7117						bslashmode = TRUE;
7118						break;
7119
7120						case '(':
7121						cmntcnt++;
7122						break;
7123
7124						case ')':
7125						cmntcnt--;
7126						break;
7127
7128						case '<':
7129						anglecnt++;
7130						break;
7131
7132						case '>':
7133						anglecnt--;
7134						break;
7135
7136						case ' ':
7137						spacecnt++;
7138						break;
7139
7140						case '"':
7141						quotemode = !quotemode;
7142						break;
7143					}
7144				}
7145			}
7146		}
7147		if (anglecnt != 0 || cmntcnt != 0 || quotemode ||
7148		    bslashmode || spacecnt != 0)
7149		{
7150			sm_syslog(LOG_WARNING, NOQID,
7151				  "Warning: regex may cause prescan() failure map=%s lookup=%s",
7152				  map->map_mname, name);
7153			return NULL;
7154		}
7155
7156		*dp = '\0';
7157
7158		return regex_map_rewrite(map, retbuf, strlen(retbuf), av);
7159	}
7160	return regex_map_rewrite(map, "", (size_t)0, av);
7161}
7162#endif /* MAP_REGEX */
7163/*
7164**  NSD modules
7165*/
7166#ifdef MAP_NSD
7167
7168# include <ndbm.h>
7169# define _DATUM_DEFINED
7170# include <ns_api.h>
7171
7172typedef struct ns_map_list
7173{
7174	ns_map_t *map;
7175	char *mapname;
7176	struct ns_map_list *next;
7177} ns_map_list_t;
7178
7179static ns_map_t *
7180ns_map_t_find(mapname)
7181	char *mapname;
7182{
7183	static ns_map_list_t *ns_maps = NULL;
7184	ns_map_list_t *ns_map;
7185
7186	/* walk the list of maps looking for the correctly named map */
7187	for (ns_map = ns_maps; ns_map != NULL; ns_map = ns_map->next)
7188	{
7189		if (strcmp(ns_map->mapname, mapname) == 0)
7190			break;
7191	}
7192
7193	/* if we are looking at a NULL ns_map_list_t, then create a new one */
7194	if (ns_map == NULL)
7195	{
7196		ns_map = (ns_map_list_t *) xalloc(sizeof *ns_map);
7197		ns_map->mapname = newstr(mapname);
7198		ns_map->map = (ns_map_t *) xalloc(sizeof *ns_map->map);
7199		ns_map->next = ns_maps;
7200		ns_maps = ns_map;
7201	}
7202	return ns_map->map;
7203}
7204
7205char *
7206nsd_map_lookup(map, name, av, statp)
7207	MAP *map;
7208	char *name;
7209	char **av;
7210	int *statp;
7211{
7212	int buflen, r;
7213	char *p;
7214	ns_map_t *ns_map;
7215	char keybuf[MAXNAME + 1];
7216	char buf[MAXLINE];
7217
7218	if (tTd(38, 20))
7219		dprintf("nsd_map_lookup(%s, %s)\n", map->map_mname, name);
7220
7221	buflen = strlen(name);
7222	if (buflen > sizeof keybuf - 1)
7223		buflen = sizeof keybuf - 1;
7224	memmove(keybuf, name, buflen);
7225	keybuf[buflen] = '\0';
7226	if (!bitset(MF_NOFOLDCASE, map->map_mflags))
7227		makelower(keybuf);
7228
7229	ns_map = ns_map_t_find(map->map_file);
7230	if (ns_map == NULL)
7231	{
7232		if (tTd(38, 20))
7233			dprintf("nsd_map_t_find failed\n");
7234		*statp = EX_UNAVAILABLE;
7235		return NULL;
7236	}
7237	r = ns_lookup(ns_map, NULL, map->map_file, keybuf, NULL, buf, MAXLINE);
7238	if (r == NS_UNAVAIL || r == NS_TRYAGAIN)
7239	{
7240		*statp = EX_TEMPFAIL;
7241		return NULL;
7242	}
7243	if (r == NS_BADREQ
7244# ifdef NS_NOPERM
7245	    || r == NS_NOPERM
7246# endif /* NS_NOPERM */
7247	    )
7248	{
7249		*statp = EX_CONFIG;
7250		return NULL;
7251	}
7252	if (r != NS_SUCCESS)
7253	{
7254		*statp = EX_NOTFOUND;
7255		return NULL;
7256	}
7257
7258	*statp = EX_OK;
7259
7260	/* Null out trailing \n */
7261	if ((p = strchr(buf, '\n')) != NULL)
7262		*p = '\0';
7263
7264	return map_rewrite(map, buf, strlen(buf), av);
7265}
7266#endif /* MAP_NSD */
7267
7268char *
7269arith_map_lookup(map, name, av, statp)
7270	MAP *map;
7271	char *name;
7272	char **av;
7273	int *statp;
7274{
7275	long r;
7276	long v[2];
7277	bool res = FALSE;
7278	bool boolres;
7279	static char result[16];
7280	char **cpp;
7281
7282	if (tTd(38, 2))
7283	{
7284		dprintf("arith_map_lookup: key '%s'\n", name);
7285		for (cpp = av; cpp != NULL && *cpp != NULL; cpp++)
7286			dprintf("arith_map_lookup: arg '%s'\n", *cpp);
7287	}
7288	r = 0;
7289	boolres = FALSE;
7290	cpp = av;
7291	*statp = EX_OK;
7292
7293	/*
7294	**  read arguments for arith map
7295	**  - no check is made whether they are really numbers
7296	**  - just ignores args after the second
7297	*/
7298	for (++cpp; cpp != NULL && *cpp != NULL && r < 2; cpp++)
7299		v[r++] = strtol(*cpp, NULL, 0);
7300
7301	/* operator and (at least) two operands given? */
7302	if (name != NULL && r == 2)
7303	{
7304		switch(*name)
7305		{
7306#if _FFR_ARITH
7307		  case '|':
7308			r = v[0] | v[1];
7309			break;
7310
7311		  case '&':
7312			r = v[0] & v[1];
7313			break;
7314
7315		  case '%':
7316			if (v[1] == 0)
7317				return NULL;
7318			r = v[0] % v[1];
7319			break;
7320#endif /* _FFR_ARITH */
7321
7322		  case '+':
7323			r = v[0] + v[1];
7324			break;
7325
7326		  case '-':
7327			r = v[0] - v[1];
7328			break;
7329
7330		  case '*':
7331			r = v[0] * v[1];
7332			break;
7333
7334		  case '/':
7335			if (v[1] == 0)
7336				return NULL;
7337			r = v[0] / v[1];
7338			break;
7339
7340		  case 'l':
7341			res = v[0] < v[1];
7342			boolres = TRUE;
7343			break;
7344
7345		  case '=':
7346			res = v[0] == v[1];
7347			boolres = TRUE;
7348			break;
7349
7350		  default:
7351			/* XXX */
7352			*statp = EX_CONFIG;
7353			if (LogLevel > 10)
7354				sm_syslog(LOG_WARNING, NOQID,
7355					  "arith_map: unknown operator %c",
7356					  isprint(*name) ? *name : '?');
7357			return NULL;
7358		}
7359		if (boolres)
7360			snprintf(result, sizeof result, res ? "TRUE" : "FALSE");
7361		else
7362			snprintf(result, sizeof result, "%ld", r);
7363		return result;
7364	}
7365	*statp = EX_CONFIG;
7366	return NULL;
7367}
7368