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