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