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