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