map.c revision 120256
1/*
2 * Copyright (c) 1998-2003 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.10 2003/07/24 18:24:17 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-- <= 1)
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				if (len-- <= 1)
398				     break;
399				*bp++ = '%';
400				goto pushc;
401			}
402			for (avp = av; --c >= '0' && *avp != NULL; avp++)
403				continue;
404			if (*avp == NULL)
405				continue;
406
407			/* transliterate argument into output string */
408			for (ap = *avp; (c = *ap++) != '\0' && len > 0; --len)
409				*bp++ = c;
410		}
411	}
412	if (map->map_app != NULL && len > 0)
413		(void) sm_strlcpy(bp, map->map_app, len);
414	else
415		*bp = '\0';
416	if (tTd(39, 1))
417		sm_dprintf("map_rewrite => %s\n", buf);
418	return buf;
419}
420/*
421**  INITMAPS -- rebuild alias maps
422**
423**	Parameters:
424**		none.
425**
426**	Returns:
427**		none.
428*/
429
430void
431initmaps()
432{
433#if XDEBUG
434	checkfd012("entering initmaps");
435#endif /* XDEBUG */
436	stabapply(map_init, 0);
437#if XDEBUG
438	checkfd012("exiting initmaps");
439#endif /* XDEBUG */
440}
441/*
442**  MAP_INIT -- rebuild a map
443**
444**	Parameters:
445**		s -- STAB entry: if map: try to rebuild
446**		unused -- unused variable
447**
448**	Returns:
449**		none.
450**
451**	Side Effects:
452**		will close already open rebuildable map.
453*/
454
455/* ARGSUSED1 */
456static void
457map_init(s, unused)
458	register STAB *s;
459	int unused;
460{
461	register MAP *map;
462
463	/* has to be a map */
464	if (s->s_symtype != ST_MAP)
465		return;
466
467	map = &s->s_map;
468	if (!bitset(MF_VALID, map->map_mflags))
469		return;
470
471	if (tTd(38, 2))
472		sm_dprintf("map_init(%s:%s, %s)\n",
473			map->map_class->map_cname == NULL ? "NULL" :
474				map->map_class->map_cname,
475			map->map_mname == NULL ? "NULL" : map->map_mname,
476			map->map_file == NULL ? "NULL" : map->map_file);
477
478	if (!bitset(MF_ALIAS, map->map_mflags) ||
479	    !bitset(MCF_REBUILDABLE, map->map_class->map_cflags))
480	{
481		if (tTd(38, 3))
482			sm_dprintf("\tnot rebuildable\n");
483		return;
484	}
485
486	/* if already open, close it (for nested open) */
487	if (bitset(MF_OPEN, map->map_mflags))
488	{
489		map->map_mflags |= MF_CLOSING;
490		map->map_class->map_close(map);
491		map->map_mflags &= ~(MF_OPEN|MF_WRITABLE|MF_CLOSING);
492	}
493
494	(void) rebuildaliases(map, false);
495	return;
496}
497/*
498**  OPENMAP -- open a map
499**
500**	Parameters:
501**		map -- map to open (it must not be open).
502**
503**	Returns:
504**		whether open succeeded.
505*/
506
507bool
508openmap(map)
509	MAP *map;
510{
511	bool restore = false;
512	bool savehold = HoldErrs;
513	bool savequick = QuickAbort;
514	int saveerrors = Errors;
515
516	if (!bitset(MF_VALID, map->map_mflags))
517		return false;
518
519	/* better safe than sorry... */
520	if (bitset(MF_OPEN, map->map_mflags))
521		return true;
522
523	/* Don't send a map open error out via SMTP */
524	if ((OnlyOneError || QuickAbort) &&
525	    (OpMode == MD_SMTP || OpMode == MD_DAEMON))
526	{
527		restore = true;
528		HoldErrs = true;
529		QuickAbort = false;
530	}
531
532	errno = 0;
533	if (map->map_class->map_open(map, O_RDONLY))
534	{
535		if (tTd(38, 4))
536			sm_dprintf("openmap()\t%s:%s %s: valid\n",
537				map->map_class->map_cname == NULL ? "NULL" :
538					map->map_class->map_cname,
539				map->map_mname == NULL ? "NULL" :
540					map->map_mname,
541				map->map_file == NULL ? "NULL" :
542					map->map_file);
543		map->map_mflags |= MF_OPEN;
544		map->map_pid = CurrentPid;
545	}
546	else
547	{
548		if (tTd(38, 4))
549			sm_dprintf("openmap()\t%s:%s %s: invalid%s%s\n",
550				map->map_class->map_cname == NULL ? "NULL" :
551					map->map_class->map_cname,
552				map->map_mname == NULL ? "NULL" :
553					map->map_mname,
554				map->map_file == NULL ? "NULL" :
555					map->map_file,
556				errno == 0 ? "" : ": ",
557				errno == 0 ? "" : sm_errstring(errno));
558		if (!bitset(MF_OPTIONAL, map->map_mflags))
559		{
560			extern MAPCLASS BogusMapClass;
561
562			map->map_orgclass = map->map_class;
563			map->map_class = &BogusMapClass;
564			map->map_mflags |= MF_OPEN|MF_OPENBOGUS;
565			map->map_pid = CurrentPid;
566		}
567		else
568		{
569			/* don't try again */
570			map->map_mflags &= ~MF_VALID;
571		}
572	}
573
574	if (restore)
575	{
576		Errors = saveerrors;
577		HoldErrs = savehold;
578		QuickAbort = savequick;
579	}
580
581	return bitset(MF_OPEN, map->map_mflags);
582}
583/*
584**  CLOSEMAPS -- close all open maps opened by the current pid.
585**
586**	Parameters:
587**		bogus -- only close bogus maps.
588**
589**	Returns:
590**		none.
591*/
592
593void
594closemaps(bogus)
595	bool bogus;
596{
597	stabapply(map_close, bogus);
598}
599/*
600**  MAP_CLOSE -- close a map opened by the current pid.
601**
602**	Parameters:
603**		s -- STAB entry: if map: try to close
604**		bogus -- only close bogus maps or MCF_NOTPERSIST maps.
605**
606**	Returns:
607**		none.
608*/
609
610/* ARGSUSED1 */
611static void
612map_close(s, bogus)
613	register STAB *s;
614	int bogus;	/* int because of stabapply(), used as bool */
615{
616	MAP *map;
617	extern MAPCLASS BogusMapClass;
618
619	if (s->s_symtype != ST_MAP)
620		return;
621
622	map = &s->s_map;
623
624	/*
625	**  close the map iff:
626	**  it is valid and open and opened by this process
627	**  and (!bogus or it's a bogus map or it is not persistent)
628	**  negate this: return iff
629	**  it is not valid or it is not open or not opened by this process
630	**  or (bogus and it's not a bogus map and it's not not-persistent)
631	*/
632
633	if (!bitset(MF_VALID, map->map_mflags) ||
634	    !bitset(MF_OPEN, map->map_mflags) ||
635	    bitset(MF_CLOSING, map->map_mflags) ||
636	    map->map_pid != CurrentPid ||
637	    (bogus && map->map_class != &BogusMapClass &&
638	     !bitset(MCF_NOTPERSIST, map->map_class->map_cflags)))
639		return;
640
641	if (map->map_class == &BogusMapClass && map->map_orgclass != NULL &&
642	    map->map_orgclass != &BogusMapClass)
643		map->map_class = map->map_orgclass;
644	if (tTd(38, 5))
645		sm_dprintf("closemaps: closing %s (%s)\n",
646			map->map_mname == NULL ? "NULL" : map->map_mname,
647			map->map_file == NULL ? "NULL" : map->map_file);
648
649	if (!bitset(MF_OPENBOGUS, map->map_mflags))
650	{
651		map->map_mflags |= MF_CLOSING;
652		map->map_class->map_close(map);
653	}
654	map->map_mflags &= ~(MF_OPEN|MF_WRITABLE|MF_OPENBOGUS|MF_CLOSING);
655}
656/*
657**  GETCANONNAME -- look up name using service switch
658**
659**	Parameters:
660**		host -- the host name to look up.
661**		hbsize -- the size of the host buffer.
662**		trymx -- if set, try MX records.
663**		pttl -- pointer to return TTL (can be NULL).
664**
665**	Returns:
666**		true -- if the host was found.
667**		false -- otherwise.
668*/
669
670bool
671getcanonname(host, hbsize, trymx, pttl)
672	char *host;
673	int hbsize;
674	bool trymx;
675	int *pttl;
676{
677	int nmaps;
678	int mapno;
679	bool found = false;
680	bool got_tempfail = false;
681	auto int status;
682	char *maptype[MAXMAPSTACK];
683	short mapreturn[MAXMAPACTIONS];
684
685	nmaps = switch_map_find("hosts", maptype, mapreturn);
686	if (pttl != 0)
687		*pttl = SM_DEFAULT_TTL;
688	for (mapno = 0; mapno < nmaps; mapno++)
689	{
690		int i;
691
692		if (tTd(38, 20))
693			sm_dprintf("getcanonname(%s), trying %s\n",
694				host, maptype[mapno]);
695		if (strcmp("files", maptype[mapno]) == 0)
696		{
697			found = text_getcanonname(host, hbsize, &status);
698		}
699#if NIS
700		else if (strcmp("nis", maptype[mapno]) == 0)
701		{
702			found = nis_getcanonname(host, hbsize, &status);
703		}
704#endif /* NIS */
705#if NISPLUS
706		else if (strcmp("nisplus", maptype[mapno]) == 0)
707		{
708			found = nisplus_getcanonname(host, hbsize, &status);
709		}
710#endif /* NISPLUS */
711#if NAMED_BIND
712		else if (strcmp("dns", maptype[mapno]) == 0)
713		{
714			found = dns_getcanonname(host, hbsize, trymx, &status,							 pttl);
715		}
716#endif /* NAMED_BIND */
717#if NETINFO
718		else if (strcmp("netinfo", maptype[mapno]) == 0)
719		{
720			found = ni_getcanonname(host, hbsize, &status);
721		}
722#endif /* NETINFO */
723		else
724		{
725			found = false;
726			status = EX_UNAVAILABLE;
727		}
728
729		/*
730		**  Heuristic: if $m is not set, we are running during system
731		**  startup.  In this case, when a name is apparently found
732		**  but has no dot, treat is as not found.  This avoids
733		**  problems if /etc/hosts has no FQDN but is listed first
734		**  in the service switch.
735		*/
736
737		if (found &&
738		    (macvalue('m', CurEnv) != NULL || strchr(host, '.') != NULL))
739			break;
740
741		/* see if we should continue */
742		if (status == EX_TEMPFAIL)
743		{
744			i = MA_TRYAGAIN;
745			got_tempfail = true;
746		}
747		else if (status == EX_NOTFOUND)
748			i = MA_NOTFOUND;
749		else
750			i = MA_UNAVAIL;
751		if (bitset(1 << mapno, mapreturn[i]))
752			break;
753	}
754
755	if (found)
756	{
757		char *d;
758
759		if (tTd(38, 20))
760			sm_dprintf("getcanonname(%s), found\n", host);
761
762		/*
763		**  If returned name is still single token, compensate
764		**  by tagging on $m.  This is because some sites set
765		**  up their DNS or NIS databases wrong.
766		*/
767
768		if ((d = strchr(host, '.')) == NULL || d[1] == '\0')
769		{
770			d = macvalue('m', CurEnv);
771			if (d != NULL &&
772			    hbsize > (int) (strlen(host) + strlen(d) + 1))
773			{
774				if (host[strlen(host) - 1] != '.')
775					(void) sm_strlcat2(host, ".", d,
776							   hbsize);
777				else
778					(void) sm_strlcat(host, d, hbsize);
779			}
780			else
781				return false;
782		}
783		return true;
784	}
785
786	if (tTd(38, 20))
787		sm_dprintf("getcanonname(%s), failed, status=%d\n", host,
788			status);
789
790	if (got_tempfail)
791		SM_SET_H_ERRNO(TRY_AGAIN);
792	else
793		SM_SET_H_ERRNO(HOST_NOT_FOUND);
794
795	return false;
796}
797/*
798**  EXTRACT_CANONNAME -- extract canonical name from /etc/hosts entry
799**
800**	Parameters:
801**		name -- the name against which to match.
802**		dot -- where to reinsert '.' to get FQDN
803**		line -- the /etc/hosts line.
804**		cbuf -- the location to store the result.
805**		cbuflen -- the size of cbuf.
806**
807**	Returns:
808**		true -- if the line matched the desired name.
809**		false -- otherwise.
810*/
811
812static bool
813extract_canonname(name, dot, line, cbuf, cbuflen)
814	char *name;
815	char *dot;
816	char *line;
817	char cbuf[];
818	int cbuflen;
819{
820	int i;
821	char *p;
822	bool found = false;
823
824	cbuf[0] = '\0';
825	if (line[0] == '#')
826		return false;
827
828	for (i = 1; ; i++)
829	{
830		char nbuf[MAXNAME + 1];
831
832		p = get_column(line, i, '\0', nbuf, sizeof nbuf);
833		if (p == NULL)
834			break;
835		if (*p == '\0')
836			continue;
837		if (cbuf[0] == '\0' ||
838		    (strchr(cbuf, '.') == NULL && strchr(p, '.') != NULL))
839		{
840			(void) sm_strlcpy(cbuf, p, cbuflen);
841		}
842		if (sm_strcasecmp(name, p) == 0)
843			found = true;
844		else if (dot != NULL)
845		{
846			/* try looking for the FQDN as well */
847			*dot = '.';
848			if (sm_strcasecmp(name, p) == 0)
849				found = true;
850			*dot = '\0';
851		}
852	}
853	if (found && strchr(cbuf, '.') == NULL)
854	{
855		/* try to add a domain on the end of the name */
856		char *domain = macvalue('m', CurEnv);
857
858		if (domain != NULL &&
859		    strlen(domain) + (i = strlen(cbuf)) + 1 < (size_t) cbuflen)
860		{
861			p = &cbuf[i];
862			*p++ = '.';
863			(void) sm_strlcpy(p, domain, cbuflen - i - 1);
864		}
865	}
866	return found;
867}
868
869/*
870**  DNS modules
871*/
872
873#if NAMED_BIND
874# if DNSMAP
875
876#  include "sm_resolve.h"
877#  if NETINET || NETINET6
878#   include <arpa/inet.h>
879#  endif /* NETINET || NETINET6 */
880
881/*
882**  DNS_MAP_OPEN -- stub to check proper value for dns map type
883*/
884
885bool
886dns_map_open(map, mode)
887	MAP *map;
888	int mode;
889{
890	if (tTd(38,2))
891		sm_dprintf("dns_map_open(%s, %d)\n", map->map_mname, mode);
892
893	mode &= O_ACCMODE;
894	if (mode != O_RDONLY)
895	{
896		/* issue a pseudo-error message */
897		errno = SM_EMAPCANTWRITE;
898		return false;
899	}
900	return true;
901}
902
903/*
904**  DNS_MAP_PARSEARGS -- parse dns map definition args.
905**
906**	Parameters:
907**		map -- pointer to MAP
908**		args -- pointer to the args on the config line.
909**
910**	Returns:
911**		true -- if everything parsed OK.
912**		false -- otherwise.
913*/
914
915#  if _FFR_DNSMAP_MULTILIMIT
916#   if !_FFR_DNSMAP_MULTI
917  ERROR README:	You must define _FFR_DNSMAP_MULTI to use _FFR_DNSMAP_MULTILIMIT
918#   endif /* ! _FFR_DNSMAP_MULTI */
919#  endif /* _FFR_DNSMAP_MULTILIMIT */
920
921#  if _FFR_DNSMAP_MULTI
922#   if _FFR_DNSMAP_MULTILIMIT
923#    define map_sizelimit	map_lockfd	/* overload field */
924#   endif /* _FFR_DNSMAP_MULTILIMIT */
925#  endif /* _FFR_DNSMAP_MULTI */
926
927struct dns_map
928{
929	int dns_m_type;
930};
931
932bool
933dns_map_parseargs(map,args)
934	MAP *map;
935	char *args;
936{
937	register char *p = args;
938	struct dns_map *map_p;
939
940	map_p = (struct dns_map *) xalloc(sizeof *map_p);
941	map_p->dns_m_type = -1;
942	map->map_mflags |= MF_TRY0NULL|MF_TRY1NULL;
943
944	for (;;)
945	{
946		while (isascii(*p) && isspace(*p))
947			p++;
948		if (*p != '-')
949			break;
950		switch (*++p)
951		{
952		  case 'N':
953			map->map_mflags |= MF_INCLNULL;
954			map->map_mflags &= ~MF_TRY0NULL;
955			break;
956
957		  case 'O':
958			map->map_mflags &= ~MF_TRY1NULL;
959			break;
960
961		  case 'o':
962			map->map_mflags |= MF_OPTIONAL;
963			break;
964
965		  case 'f':
966			map->map_mflags |= MF_NOFOLDCASE;
967			break;
968
969		  case 'm':
970			map->map_mflags |= MF_MATCHONLY;
971			break;
972
973		  case 'A':
974			map->map_mflags |= MF_APPEND;
975			break;
976
977		  case 'q':
978			map->map_mflags |= MF_KEEPQUOTES;
979			break;
980
981		  case 't':
982			map->map_mflags |= MF_NODEFER;
983			break;
984
985		  case 'a':
986			map->map_app = ++p;
987			break;
988
989		  case 'T':
990			map->map_tapp = ++p;
991			break;
992
993		  case 'd':
994			{
995				char *h;
996
997				++p;
998				h = strchr(p, ' ');
999				if (h != NULL)
1000					*h = '\0';
1001				map->map_timeout = convtime(p, 's');
1002				if (h != NULL)
1003					*h = ' ';
1004			}
1005			break;
1006
1007		  case 'r':
1008			while (isascii(*++p) && isspace(*p))
1009				continue;
1010			map->map_retry = atoi(p);
1011			break;
1012
1013#  if _FFR_DNSMAP_MULTI
1014		  case 'z':
1015			if (*++p != '\\')
1016				map->map_coldelim = *p;
1017			else
1018			{
1019				switch (*++p)
1020				{
1021				  case 'n':
1022					map->map_coldelim = '\n';
1023					break;
1024
1025				  case 't':
1026					map->map_coldelim = '\t';
1027					break;
1028
1029				  default:
1030					map->map_coldelim = '\\';
1031				}
1032			}
1033			break;
1034
1035#   if _FFR_DNSMAP_MULTILIMIT
1036		  case 'Z':
1037			while (isascii(*++p) && isspace(*p))
1038				continue;
1039			map->map_sizelimit = atoi(p);
1040			break;
1041#   endif /* _FFR_DNSMAP_MULTILIMIT */
1042#  endif /* _FFR_DNSMAP_MULTI */
1043
1044			/* Start of dns_map specific args */
1045		  case 'R':		/* search field */
1046			{
1047				char *h;
1048
1049				while (isascii(*++p) && isspace(*p))
1050					continue;
1051				h = strchr(p, ' ');
1052				if (h != NULL)
1053					*h = '\0';
1054				map_p->dns_m_type = dns_string_to_type(p);
1055				if (h != NULL)
1056					*h = ' ';
1057				if (map_p->dns_m_type < 0)
1058					syserr("dns map %s: wrong type %s",
1059						map->map_mname, p);
1060			}
1061			break;
1062
1063#  if _FFR_DNSMAP_BASE
1064		  case 'B':		/* base domain */
1065			{
1066				char *h;
1067
1068				while (isascii(*++p) && isspace(*p))
1069					continue;
1070				h = strchr(p, ' ');
1071				if (h != NULL)
1072					*h = '\0';
1073
1074				/*
1075				**  slight abuse of map->map_file; it isn't
1076				**	used otherwise in this map type.
1077				*/
1078
1079				map->map_file = newstr(p);
1080				if (h != NULL)
1081					*h = ' ';
1082			}
1083			break;
1084#  endif /* _FFR_DNSMAP_BASE */
1085
1086		}
1087		while (*p != '\0' && !(isascii(*p) && isspace(*p)))
1088			p++;
1089		if (*p != '\0')
1090			*p++ = '\0';
1091	}
1092	if (map_p->dns_m_type < 0)
1093		syserr("dns map %s: missing -R type", map->map_mname);
1094	if (map->map_app != NULL)
1095		map->map_app = newstr(map->map_app);
1096	if (map->map_tapp != NULL)
1097		map->map_tapp = newstr(map->map_tapp);
1098
1099	/*
1100	**  Assumption: assert(sizeof int <= sizeof(ARBPTR_T));
1101	**  Even if this assumption is wrong, we use only one byte,
1102	**  so it doesn't really matter.
1103	*/
1104
1105	map->map_db1 = (ARBPTR_T) map_p;
1106	return true;
1107}
1108
1109/*
1110**  DNS_MAP_LOOKUP -- perform dns map lookup.
1111**
1112**	Parameters:
1113**		map -- pointer to MAP
1114**		name -- name to lookup
1115**		av -- arguments to interpolate into buf.
1116**		statp -- pointer to status (EX_)
1117**
1118**	Returns:
1119**		result of lookup if succeeded.
1120**		NULL -- otherwise.
1121*/
1122
1123char *
1124dns_map_lookup(map, name, av, statp)
1125	MAP *map;
1126	char *name;
1127	char **av;
1128	int *statp;
1129{
1130#  if _FFR_DNSMAP_MULTI
1131#   if _FFR_DNSMAP_MULTILIMIT
1132	int resnum = 0;
1133#   endif /* _FFR_DNSMAP_MULTILIMIT */
1134#  endif /* _FFR_DNSMAP_MULTI */
1135	char *vp = NULL, *result = NULL;
1136	size_t vsize;
1137	struct dns_map *map_p;
1138	RESOURCE_RECORD_T *rr = NULL;
1139	DNS_REPLY_T *r = NULL;
1140#  if NETINET6
1141	static char buf6[INET6_ADDRSTRLEN];
1142#  endif /* NETINET6 */
1143
1144	if (tTd(38, 20))
1145		sm_dprintf("dns_map_lookup(%s, %s)\n",
1146			   map->map_mname, name);
1147
1148	map_p = (struct dns_map *)(map->map_db1);
1149#  if _FFR_DNSMAP_BASE
1150	if (map->map_file != NULL && *map->map_file != '\0')
1151	{
1152		size_t len;
1153		char *appdomain;
1154
1155		len = strlen(map->map_file) + strlen(name) + 2;
1156		appdomain = (char *) sm_malloc(len);
1157		if (appdomain == NULL)
1158		{
1159			*statp = EX_UNAVAILABLE;
1160			return NULL;
1161		}
1162		(void) sm_strlcpyn(appdomain, len, 3, name, ".", map->map_file);
1163		r = dns_lookup_int(appdomain, C_IN, map_p->dns_m_type,
1164				   map->map_timeout, map->map_retry);
1165		sm_free(appdomain);
1166	}
1167	else
1168#  endif /* _FFR_DNSMAP_BASE */
1169	{
1170		r = dns_lookup_int(name, C_IN, map_p->dns_m_type,
1171				   map->map_timeout, map->map_retry);
1172	}
1173
1174	if (r == NULL)
1175	{
1176		result = NULL;
1177		if (h_errno == TRY_AGAIN || transienterror(errno))
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 'q':
6948			map->map_mflags |= MF_KEEPQUOTES;
6949			break;
6950
6951		  case 'S':
6952			map->map_spacesub = *++p;
6953			break;
6954
6955		  case 'D':
6956			map->map_mflags |= MF_DEFER;
6957			break;
6958
6959		}
6960		while (*p != '\0' && !(isascii(*p) && isspace(*p)))
6961			p++;
6962		if (*p != '\0')
6963			*p++ = '\0';
6964	}
6965	if (tTd(38, 3))
6966		sm_dprintf("regex_map_init: compile '%s' 0x%x\n", p, pflags);
6967
6968	if ((regerr = regcomp(map_p->regex_pattern_buf, p, pflags)) != 0)
6969	{
6970		/* Errorhandling */
6971		char errbuf[ERRBUF_SIZE];
6972
6973		(void) regerror(regerr, map_p->regex_pattern_buf,
6974			 errbuf, sizeof errbuf);
6975		syserr("pattern-compile-error: %s", errbuf);
6976		sm_free(map_p->regex_pattern_buf); /* XXX */
6977		sm_free(map_p); /* XXX */
6978		return false;
6979	}
6980
6981	if (map->map_app != NULL)
6982		map->map_app = newstr(map->map_app);
6983	if (map_p->regex_delim != NULL)
6984		map_p->regex_delim = newstr(map_p->regex_delim);
6985	else
6986		map_p->regex_delim = defdstr;
6987
6988	if (!bitset(REG_NOSUB, pflags))
6989	{
6990		/* substring matching */
6991		int substrings;
6992		int *fields = (int *) xalloc(sizeof(int) * (MAX_MATCH + 1));
6993
6994		substrings = map_p->regex_pattern_buf->re_nsub + 1;
6995
6996		if (tTd(38, 3))
6997			sm_dprintf("regex_map_init: nr of substrings %d\n",
6998				substrings);
6999
7000		if (substrings >= MAX_MATCH)
7001		{
7002			syserr("too many substrings, %d max", MAX_MATCH);
7003			sm_free(map_p->regex_pattern_buf); /* XXX */
7004			sm_free(map_p); /* XXX */
7005			return false;
7006		}
7007		if (sub_param != NULL && sub_param[0] != '\0')
7008		{
7009			/* optional parameter -sfields */
7010			if (parse_fields(sub_param, fields,
7011					 MAX_MATCH + 1, substrings) == -1)
7012				return false;
7013		}
7014		else
7015		{
7016			int i;
7017
7018			/* set default fields */
7019			for (i = 0; i < substrings; i++)
7020				fields[i] = i;
7021			fields[i] = END_OF_FIELDS;
7022		}
7023		map_p->regex_subfields = fields;
7024		if (tTd(38, 3))
7025		{
7026			int *ip;
7027
7028			sm_dprintf("regex_map_init: subfields");
7029			for (ip = fields; *ip != END_OF_FIELDS; ip++)
7030				sm_dprintf(" %d", *ip);
7031			sm_dprintf("\n");
7032		}
7033	}
7034	map->map_db1 = (ARBPTR_T) map_p;	/* dirty hack */
7035	return true;
7036}
7037
7038static char *
7039regex_map_rewrite(map, s, slen, av)
7040	MAP *map;
7041	const char *s;
7042	size_t slen;
7043	char **av;
7044{
7045	if (bitset(MF_MATCHONLY, map->map_mflags))
7046		return map_rewrite(map, av[0], strlen(av[0]), NULL);
7047	else
7048		return map_rewrite(map, s, slen, av);
7049}
7050
7051char *
7052regex_map_lookup(map, name, av, statp)
7053	MAP *map;
7054	char *name;
7055	char **av;
7056	int *statp;
7057{
7058	int reg_res;
7059	struct regex_map *map_p;
7060	regmatch_t pmatch[MAX_MATCH];
7061
7062	if (tTd(38, 20))
7063	{
7064		char **cpp;
7065
7066		sm_dprintf("regex_map_lookup: key '%s'\n", name);
7067		for (cpp = av; cpp != NULL && *cpp != NULL; cpp++)
7068			sm_dprintf("regex_map_lookup: arg '%s'\n", *cpp);
7069	}
7070
7071	map_p = (struct regex_map *)(map->map_db1);
7072	reg_res = regexec(map_p->regex_pattern_buf,
7073			  name, MAX_MATCH, pmatch, 0);
7074
7075	if (bitset(MF_REGEX_NOT, map->map_mflags))
7076	{
7077		/* option -n */
7078		if (reg_res == REG_NOMATCH)
7079			return regex_map_rewrite(map, "", (size_t) 0, av);
7080		else
7081			return NULL;
7082	}
7083	if (reg_res == REG_NOMATCH)
7084		return NULL;
7085
7086	if (map_p->regex_subfields != NULL)
7087	{
7088		/* option -s */
7089		static char retbuf[MAXNAME];
7090		int fields[MAX_MATCH + 1];
7091		bool first = true;
7092		int anglecnt = 0, cmntcnt = 0, spacecnt = 0;
7093		bool quotemode = false, bslashmode = false;
7094		register char *dp, *sp;
7095		char *endp, *ldp;
7096		int *ip;
7097
7098		dp = retbuf;
7099		ldp = retbuf + sizeof(retbuf) - 1;
7100
7101		if (av[1] != NULL)
7102		{
7103			if (parse_fields(av[1], fields, MAX_MATCH + 1,
7104					 (int) map_p->regex_pattern_buf->re_nsub + 1) == -1)
7105			{
7106				*statp = EX_CONFIG;
7107				return NULL;
7108			}
7109			ip = fields;
7110		}
7111		else
7112			ip = map_p->regex_subfields;
7113
7114		for ( ; *ip != END_OF_FIELDS; ip++)
7115		{
7116			if (!first)
7117			{
7118				for (sp = map_p->regex_delim; *sp; sp++)
7119				{
7120					if (dp < ldp)
7121						*dp++ = *sp;
7122				}
7123			}
7124			else
7125				first = false;
7126
7127			if (*ip >= MAX_MATCH ||
7128			    pmatch[*ip].rm_so < 0 || pmatch[*ip].rm_eo < 0)
7129				continue;
7130
7131			sp = name + pmatch[*ip].rm_so;
7132			endp = name + pmatch[*ip].rm_eo;
7133			for (; endp > sp; sp++)
7134			{
7135				if (dp < ldp)
7136				{
7137					if (bslashmode)
7138					{
7139						*dp++ = *sp;
7140						bslashmode = false;
7141					}
7142					else if (quotemode && *sp != '"' &&
7143						*sp != '\\')
7144					{
7145						*dp++ = *sp;
7146					}
7147					else switch (*dp++ = *sp)
7148					{
7149					  case '\\':
7150						bslashmode = true;
7151						break;
7152
7153					  case '(':
7154						cmntcnt++;
7155						break;
7156
7157					  case ')':
7158						cmntcnt--;
7159						break;
7160
7161					  case '<':
7162						anglecnt++;
7163						break;
7164
7165					  case '>':
7166						anglecnt--;
7167						break;
7168
7169					  case ' ':
7170						spacecnt++;
7171						break;
7172
7173					  case '"':
7174						quotemode = !quotemode;
7175						break;
7176					}
7177				}
7178			}
7179		}
7180		if (anglecnt != 0 || cmntcnt != 0 || quotemode ||
7181		    bslashmode || spacecnt != 0)
7182		{
7183			sm_syslog(LOG_WARNING, NOQID,
7184				  "Warning: regex may cause prescan() failure map=%s lookup=%s",
7185				  map->map_mname, name);
7186			return NULL;
7187		}
7188
7189		*dp = '\0';
7190
7191		return regex_map_rewrite(map, retbuf, strlen(retbuf), av);
7192	}
7193	return regex_map_rewrite(map, "", (size_t)0, av);
7194}
7195#endif /* MAP_REGEX */
7196/*
7197**  NSD modules
7198*/
7199#if MAP_NSD
7200
7201# include <ndbm.h>
7202# define _DATUM_DEFINED
7203# include <ns_api.h>
7204
7205typedef struct ns_map_list
7206{
7207	ns_map_t		*map;		/* XXX ns_ ? */
7208	char			*mapname;
7209	struct ns_map_list	*next;
7210} ns_map_list_t;
7211
7212static ns_map_t *
7213ns_map_t_find(mapname)
7214	char *mapname;
7215{
7216	static ns_map_list_t *ns_maps = NULL;
7217	ns_map_list_t *ns_map;
7218
7219	/* walk the list of maps looking for the correctly named map */
7220	for (ns_map = ns_maps; ns_map != NULL; ns_map = ns_map->next)
7221	{
7222		if (strcmp(ns_map->mapname, mapname) == 0)
7223			break;
7224	}
7225
7226	/* if we are looking at a NULL ns_map_list_t, then create a new one */
7227	if (ns_map == NULL)
7228	{
7229		ns_map = (ns_map_list_t *) xalloc(sizeof *ns_map);
7230		ns_map->mapname = newstr(mapname);
7231		ns_map->map = (ns_map_t *) xalloc(sizeof *ns_map->map);
7232		memset(ns_map->map, '\0', sizeof *ns_map->map);
7233		ns_map->next = ns_maps;
7234		ns_maps = ns_map;
7235	}
7236	return ns_map->map;
7237}
7238
7239char *
7240nsd_map_lookup(map, name, av, statp)
7241	MAP *map;
7242	char *name;
7243	char **av;
7244	int *statp;
7245{
7246	int buflen, r;
7247	char *p;
7248	ns_map_t *ns_map;
7249	char keybuf[MAXNAME + 1];
7250	char buf[MAXLINE];
7251
7252	if (tTd(38, 20))
7253		sm_dprintf("nsd_map_lookup(%s, %s)\n", map->map_mname, name);
7254
7255	buflen = strlen(name);
7256	if (buflen > sizeof keybuf - 1)
7257		buflen = sizeof keybuf - 1;	/* XXX simply cut off? */
7258	memmove(keybuf, name, buflen);
7259	keybuf[buflen] = '\0';
7260	if (!bitset(MF_NOFOLDCASE, map->map_mflags))
7261		makelower(keybuf);
7262
7263	ns_map = ns_map_t_find(map->map_file);
7264	if (ns_map == NULL)
7265	{
7266		if (tTd(38, 20))
7267			sm_dprintf("nsd_map_t_find failed\n");
7268		*statp = EX_UNAVAILABLE;
7269		return NULL;
7270	}
7271	r = ns_lookup(ns_map, NULL, map->map_file, keybuf, NULL,
7272		      buf, sizeof buf);
7273	if (r == NS_UNAVAIL || r == NS_TRYAGAIN)
7274	{
7275		*statp = EX_TEMPFAIL;
7276		return NULL;
7277	}
7278	if (r == NS_BADREQ
7279# ifdef NS_NOPERM
7280	    || r == NS_NOPERM
7281# endif /* NS_NOPERM */
7282	    )
7283	{
7284		*statp = EX_CONFIG;
7285		return NULL;
7286	}
7287	if (r != NS_SUCCESS)
7288	{
7289		*statp = EX_NOTFOUND;
7290		return NULL;
7291	}
7292
7293	*statp = EX_OK;
7294
7295	/* Null out trailing \n */
7296	if ((p = strchr(buf, '\n')) != NULL)
7297		*p = '\0';
7298
7299	return map_rewrite(map, buf, strlen(buf), av);
7300}
7301#endif /* MAP_NSD */
7302
7303char *
7304arith_map_lookup(map, name, av, statp)
7305	MAP *map;
7306	char *name;
7307	char **av;
7308	int *statp;
7309{
7310	long r;
7311	long v[2];
7312	bool res = false;
7313	bool boolres;
7314	static char result[16];
7315	char **cpp;
7316
7317	if (tTd(38, 2))
7318	{
7319		sm_dprintf("arith_map_lookup: key '%s'\n", name);
7320		for (cpp = av; cpp != NULL && *cpp != NULL; cpp++)
7321			sm_dprintf("arith_map_lookup: arg '%s'\n", *cpp);
7322	}
7323	r = 0;
7324	boolres = false;
7325	cpp = av;
7326	*statp = EX_OK;
7327
7328	/*
7329	**  read arguments for arith map
7330	**  - no check is made whether they are really numbers
7331	**  - just ignores args after the second
7332	*/
7333
7334	for (++cpp; cpp != NULL && *cpp != NULL && r < 2; cpp++)
7335		v[r++] = strtol(*cpp, NULL, 0);
7336
7337	/* operator and (at least) two operands given? */
7338	if (name != NULL && r == 2)
7339	{
7340		switch (*name)
7341		{
7342		  case '|':
7343			r = v[0] | v[1];
7344			break;
7345
7346		  case '&':
7347			r = v[0] & v[1];
7348			break;
7349
7350		  case '%':
7351			if (v[1] == 0)
7352				return NULL;
7353			r = v[0] % v[1];
7354			break;
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			r = v[0] * v[1];
7365			break;
7366
7367		  case '/':
7368			if (v[1] == 0)
7369				return NULL;
7370			r = v[0] / v[1];
7371			break;
7372
7373		  case 'l':
7374			res = v[0] < v[1];
7375			boolres = true;
7376			break;
7377
7378		  case '=':
7379			res = v[0] == v[1];
7380			boolres = true;
7381			break;
7382
7383		  default:
7384			/* XXX */
7385			*statp = EX_CONFIG;
7386			if (LogLevel > 10)
7387				sm_syslog(LOG_WARNING, NOQID,
7388					  "arith_map: unknown operator %c",
7389					  isprint(*name) ? *name : '?');
7390			return NULL;
7391		}
7392		if (boolres)
7393			(void) sm_snprintf(result, sizeof result,
7394				res ? "TRUE" : "FALSE");
7395		else
7396			(void) sm_snprintf(result, sizeof result, "%ld", r);
7397		return result;
7398	}
7399	*statp = EX_CONFIG;
7400	return NULL;
7401}
7402