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