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