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