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