map.c revision 90792
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.618 2002/01/11 22:06:52 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("421 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("421 4.0.0 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("421 4.0.0 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("421 4.0.0 %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_getcanoname(%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_getcanoname(%s), got %d entries, all but first ignored\n",
3156					   name, count);
3157		}
3158
3159		if (tTd(38, 20))
3160			sm_dprintf("nisplus_getcanoname(%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_host == NULL ? "localhost" : lmap->ldap_host);
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_host == NULL ? "localhost"
3341							       : lmap->ldap_host,
3342				       map->map_mname);
3343			else
3344				syserr("421 4.0.0 %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_host == NULL ? "localhost"
3351							       : lmap->ldap_host,
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	int i;
3449	int entries = 0;
3450	int msgid;
3451	int ret;
3452	int save_errno;
3453	int vsize;
3454	char *vp, *p;
3455	char *result = NULL;
3456	SM_LDAP_STRUCT *lmap = NULL;
3457	char keybuf[MAXNAME + 1];
3458
3459	if (tTd(38, 20))
3460		sm_dprintf("ldapmap_lookup(%s, %s)\n", map->map_mname, name);
3461
3462	/* Get ldap struct pointer from map */
3463	lmap = (SM_LDAP_STRUCT *) map->map_db1;
3464	sm_ldap_setopts(lmap->ldap_ld, lmap);
3465
3466	(void) sm_strlcpy(keybuf, name, sizeof keybuf);
3467
3468	if (!bitset(MF_NOFOLDCASE, map->map_mflags))
3469	{
3470# ifdef SUNET_ID
3471		sunet_id_hash(keybuf);
3472# else /* SUNET_ID */
3473		makelower(keybuf);
3474# endif /* SUNET_ID */
3475	}
3476
3477	msgid = sm_ldap_search(lmap, keybuf);
3478	if (msgid == -1)
3479	{
3480		errno = sm_ldap_geterrno(lmap->ldap_ld) + E_LDAPBASE;
3481		save_errno = errno;
3482		if (!bitset(MF_OPTIONAL, map->map_mflags))
3483		{
3484			if (bitset(MF_NODEFER, map->map_mflags))
3485				syserr("Error in ldap_search using %s in map %s",
3486				       keybuf, map->map_mname);
3487			else
3488				syserr("421 4.0.0 Error in ldap_search using %s in map %s",
3489				       keybuf, map->map_mname);
3490		}
3491		*statp = EX_TEMPFAIL;
3492		switch (save_errno - E_LDAPBASE)
3493		{
3494#ifdef LDAP_SERVER_DOWN
3495		  case LDAP_SERVER_DOWN:
3496#endif /* LDAP_SERVER_DOWN */
3497		  case LDAP_TIMEOUT:
3498		  case LDAP_UNAVAILABLE:
3499			/* server disappeared, try reopen on next search */
3500			ldapmap_close(map);
3501			break;
3502		}
3503		errno = save_errno;
3504		return NULL;
3505	}
3506
3507	*statp = EX_NOTFOUND;
3508	vp = NULL;
3509
3510# if _FFR_LDAP_RECURSION
3511	{
3512		int flags;
3513		SM_RPOOL_T *rpool;
3514
3515		flags = 0;
3516		if (bitset(MF_SINGLEMATCH, map->map_mflags))
3517			flags |= SM_LDAP_SINGLEMATCH;
3518		if (bitset(MF_MATCHONLY, map->map_mflags))
3519			flags |= SM_LDAP_MATCHONLY;
3520
3521		/* Create an rpool for search related memory usage */
3522		rpool = sm_rpool_new_x(NULL);
3523
3524		p = NULL;
3525		*statp = sm_ldap_results(lmap, msgid, flags, map->map_coldelim,
3526					 rpool, &p, NULL);
3527		save_errno = errno;
3528
3529		/* Copy result so rpool can be freed */
3530		if (*statp == EX_OK && p != NULL)
3531			vp = newstr(p);
3532		sm_rpool_free(rpool);
3533
3534		/* need to restart LDAP connection? */
3535		if (*statp == EX_RESTART)
3536		{
3537			*statp = EX_TEMPFAIL;
3538			ldapmap_close(map);
3539		}
3540
3541		errno = save_errno;
3542		if (*statp != EX_OK && *statp != EX_NOTFOUND)
3543		{
3544			if (!bitset(MF_OPTIONAL, map->map_mflags))
3545			{
3546				if (bitset(MF_NODEFER, map->map_mflags))
3547					syserr("Error getting LDAP results in map %s",
3548					       map->map_mname);
3549				else
3550					syserr("421 4.0.0 Error getting LDAP results in map %s",
3551					       map->map_mname);
3552			}
3553			errno = save_errno;
3554			return NULL;
3555		}
3556		goto finishlookup;
3557	}
3558# endif /* _FFR_LDAP_RECURSION */
3559
3560	/* Get results */
3561	while ((ret = ldap_result(lmap->ldap_ld, msgid, 0,
3562				  (lmap->ldap_timeout.tv_sec == 0 ? NULL :
3563				   &(lmap->ldap_timeout)),
3564				  &(lmap->ldap_res))) == LDAP_RES_SEARCH_ENTRY)
3565	{
3566		LDAPMessage *entry;
3567
3568		if (bitset(MF_SINGLEMATCH, map->map_mflags))
3569		{
3570			entries += ldap_count_entries(lmap->ldap_ld,
3571						      lmap->ldap_res);
3572			if (entries > 1)
3573			{
3574				*statp = EX_NOTFOUND;
3575				if (lmap->ldap_res != NULL)
3576				{
3577					ldap_msgfree(lmap->ldap_res);
3578					lmap->ldap_res = NULL;
3579				}
3580				(void) ldap_abandon(lmap->ldap_ld, msgid);
3581				if (vp != NULL)
3582					sm_free(vp); /* XXX */
3583				if (tTd(38, 25))
3584					sm_dprintf("ldap search found multiple on a single match query\n");
3585				return NULL;
3586			}
3587		}
3588
3589		/* If we don't want multiple values and we have one, break */
3590		if (map->map_coldelim == '\0' && vp != NULL)
3591			break;
3592
3593		/* Cycle through all entries */
3594		for (entry = ldap_first_entry(lmap->ldap_ld, lmap->ldap_res);
3595		     entry != NULL;
3596		     entry = ldap_next_entry(lmap->ldap_ld, lmap->ldap_res))
3597		{
3598			BerElement *ber;
3599			char *attr;
3600			char **vals = NULL;
3601
3602			/*
3603			**  If matching only and found an entry,
3604			**  no need to spin through attributes
3605			*/
3606
3607			if (*statp == EX_OK &&
3608			    bitset(MF_MATCHONLY, map->map_mflags))
3609				continue;
3610
3611# if !defined(LDAP_VERSION_MAX) && !defined(LDAP_OPT_SIZELIMIT)
3612			/*
3613			**  Reset value to prevent lingering
3614			**  LDAP_DECODING_ERROR due to
3615			**  OpenLDAP 1.X's hack (see below)
3616			*/
3617
3618			lmap->ldap_ld->ld_errno = LDAP_SUCCESS;
3619# endif /* !defined(LDAP_VERSION_MAX) !defined(LDAP_OPT_SIZELIMIT) */
3620
3621			for (attr = ldap_first_attribute(lmap->ldap_ld, entry,
3622							 &ber);
3623			     attr != NULL;
3624			     attr = ldap_next_attribute(lmap->ldap_ld, entry,
3625							ber))
3626			{
3627				char *tmp, *vp_tmp;
3628
3629				if (lmap->ldap_attrsonly == LDAPMAP_FALSE)
3630				{
3631					vals = ldap_get_values(lmap->ldap_ld,
3632							       entry,
3633							       attr);
3634					if (vals == NULL)
3635					{
3636						save_errno = sm_ldap_geterrno(lmap->ldap_ld);
3637						if (save_errno == LDAP_SUCCESS)
3638						{
3639# if USING_NETSCAPE_LDAP
3640							ldap_memfree(attr);
3641# endif /* USING_NETSCAPE_LDAP */
3642							continue;
3643						}
3644
3645						/* Must be an error */
3646						save_errno += E_LDAPBASE;
3647						if (!bitset(MF_OPTIONAL,
3648							    map->map_mflags))
3649						{
3650							errno = save_errno;
3651							if (bitset(MF_NODEFER,
3652								   map->map_mflags))
3653								syserr("Error getting LDAP values in map %s",
3654								       map->map_mname);
3655							else
3656								syserr("421 4.0.0 Error getting LDAP values in map %s",
3657								       map->map_mname);
3658						}
3659						*statp = EX_TEMPFAIL;
3660# if USING_NETSCAPE_LDAP
3661						ldap_memfree(attr);
3662# endif /* USING_NETSCAPE_LDAP */
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# if USING_NETSCAPE_LDAP
3700					ldap_memfree(attr);
3701# endif /* USING_NETSCAPE_LDAP */
3702					continue;
3703				}
3704
3705				/*
3706				**  If we don't want multiple values,
3707				**  return first found.
3708				*/
3709
3710				if (map->map_coldelim == '\0')
3711				{
3712					if (lmap->ldap_attrsonly == LDAPMAP_TRUE)
3713					{
3714						vp = newstr(attr);
3715# if USING_NETSCAPE_LDAP
3716						ldap_memfree(attr);
3717# endif /* USING_NETSCAPE_LDAP */
3718						break;
3719					}
3720
3721					if (vals[0] == NULL)
3722					{
3723						ldap_value_free(vals);
3724# if USING_NETSCAPE_LDAP
3725						ldap_memfree(attr);
3726# endif /* USING_NETSCAPE_LDAP */
3727						continue;
3728					}
3729
3730					vsize = strlen(vals[0]) + 1;
3731					if (lmap->ldap_attrsep != '\0')
3732						vsize += strlen(attr) + 1;
3733					vp = xalloc(vsize);
3734					if (lmap->ldap_attrsep != '\0')
3735						sm_snprintf(vp, vsize,
3736							    "%s%c%s",
3737							    attr,
3738							    lmap->ldap_attrsep,
3739							    vals[0]);
3740					else
3741						sm_strlcpy(vp, vals[0], vsize);
3742					ldap_value_free(vals);
3743# if USING_NETSCAPE_LDAP
3744					ldap_memfree(attr);
3745# endif /* USING_NETSCAPE_LDAP */
3746					break;
3747				}
3748
3749				/* attributes only */
3750				if (lmap->ldap_attrsonly == LDAPMAP_TRUE)
3751				{
3752					if (vp == NULL)
3753						vp = newstr(attr);
3754					else
3755					{
3756						vsize = strlen(vp) +
3757							strlen(attr) + 2;
3758						tmp = xalloc(vsize);
3759						(void) sm_snprintf(tmp,
3760							vsize, "%s%c%s",
3761							vp, map->map_coldelim,
3762							attr);
3763						sm_free(vp); /* XXX */
3764						vp = tmp;
3765					}
3766# if USING_NETSCAPE_LDAP
3767					ldap_memfree(attr);
3768# endif /* USING_NETSCAPE_LDAP */
3769					continue;
3770				}
3771
3772				/*
3773				**  If there is more than one,
3774				**  munge then into a map_coldelim
3775				**  separated string
3776				*/
3777
3778				vsize = 0;
3779				for (i = 0; vals[i] != NULL; i++)
3780				{
3781					vsize += strlen(vals[i]) + 1;
3782					if (lmap->ldap_attrsep != '\0')
3783						vsize += strlen(attr) + 1;
3784				}
3785				vp_tmp = xalloc(vsize);
3786				*vp_tmp = '\0';
3787
3788				p = vp_tmp;
3789				for (i = 0; vals[i] != NULL; i++)
3790				{
3791					if (lmap->ldap_attrsep != '\0')
3792					{
3793						p += sm_strlcpy(p, attr,
3794								vsize - (p - vp_tmp));
3795						*p++ = lmap->ldap_attrsep;
3796					}
3797					p += sm_strlcpy(p, vals[i],
3798							vsize - (p - vp_tmp));
3799					if (p >= vp_tmp + vsize)
3800						syserr("ldapmap_lookup: Internal error: buffer too small for LDAP values");
3801					if (vals[i + 1] != NULL)
3802						*p++ = map->map_coldelim;
3803				}
3804
3805				ldap_value_free(vals);
3806# if USING_NETSCAPE_LDAP
3807				ldap_memfree(attr);
3808# endif /* USING_NETSCAPE_LDAP */
3809				if (vp == NULL)
3810				{
3811					vp = vp_tmp;
3812					continue;
3813				}
3814				vsize = strlen(vp) + strlen(vp_tmp) + 2;
3815				tmp = xalloc(vsize);
3816				(void) sm_snprintf(tmp, vsize, "%s%c%s",
3817					 vp, map->map_coldelim, vp_tmp);
3818
3819				sm_free(vp); /* XXX */
3820				sm_free(vp_tmp); /* XXX */
3821				vp = tmp;
3822			}
3823			save_errno = sm_ldap_geterrno(lmap->ldap_ld);
3824
3825			/*
3826			**  We check errno != LDAP_DECODING_ERROR since
3827			**  OpenLDAP 1.X has a very ugly *undocumented*
3828			**  hack of returning this error code from
3829			**  ldap_next_attribute() if the library freed the
3830			**  ber attribute.  See:
3831			**  http://www.openldap.org/lists/openldap-devel/9901/msg00064.html
3832			*/
3833
3834			if (save_errno != LDAP_SUCCESS &&
3835			    save_errno != LDAP_DECODING_ERROR)
3836			{
3837				/* Must be an error */
3838				save_errno += E_LDAPBASE;
3839				if (!bitset(MF_OPTIONAL, map->map_mflags))
3840				{
3841					errno = save_errno;
3842					if (bitset(MF_NODEFER, map->map_mflags))
3843						syserr("Error getting LDAP attributes in map %s",
3844						       map->map_mname);
3845					else
3846						syserr("421 4.0.0 Error getting LDAP attributes in map %s",
3847						       map->map_mname);
3848				}
3849				*statp = EX_TEMPFAIL;
3850				if (lmap->ldap_res != NULL)
3851				{
3852					ldap_msgfree(lmap->ldap_res);
3853					lmap->ldap_res = NULL;
3854				}
3855				(void) ldap_abandon(lmap->ldap_ld, msgid);
3856				if (vp != NULL)
3857					sm_free(vp); /* XXX */
3858				errno = save_errno;
3859				return NULL;
3860			}
3861
3862			/* We don't want multiple values and we have one */
3863			if (map->map_coldelim == '\0' && vp != NULL)
3864				break;
3865		}
3866		save_errno = sm_ldap_geterrno(lmap->ldap_ld);
3867		if (save_errno != LDAP_SUCCESS &&
3868		    save_errno != LDAP_DECODING_ERROR)
3869		{
3870			/* Must be an error */
3871			save_errno += E_LDAPBASE;
3872			if (!bitset(MF_OPTIONAL, map->map_mflags))
3873			{
3874				errno = save_errno;
3875				if (bitset(MF_NODEFER, map->map_mflags))
3876					syserr("Error getting LDAP entries in map %s",
3877					       map->map_mname);
3878				else
3879					syserr("421 4.0.0 Error getting LDAP entries in map %s",
3880					       map->map_mname);
3881			}
3882			*statp = EX_TEMPFAIL;
3883			if (lmap->ldap_res != NULL)
3884			{
3885				ldap_msgfree(lmap->ldap_res);
3886				lmap->ldap_res = NULL;
3887			}
3888			(void) ldap_abandon(lmap->ldap_ld, msgid);
3889			if (vp != NULL)
3890				sm_free(vp); /* XXX */
3891			errno = save_errno;
3892			return NULL;
3893		}
3894		ldap_msgfree(lmap->ldap_res);
3895		lmap->ldap_res = NULL;
3896	}
3897
3898	if (ret == 0)
3899		save_errno = ETIMEDOUT;
3900	else
3901		save_errno = sm_ldap_geterrno(lmap->ldap_ld);
3902	if (save_errno != LDAP_SUCCESS)
3903	{
3904		if (ret != 0)
3905			save_errno += E_LDAPBASE;
3906
3907		if (!bitset(MF_OPTIONAL, map->map_mflags))
3908		{
3909			errno = save_errno;
3910			if (bitset(MF_NODEFER, map->map_mflags))
3911				syserr("Error getting LDAP results in map %s",
3912				       map->map_mname);
3913			else
3914				syserr("421 4.0.0 Error getting LDAP results in map %s",
3915				       map->map_mname);
3916		}
3917		*statp = EX_TEMPFAIL;
3918		if (vp != NULL)
3919			sm_free(vp); /* XXX */
3920
3921		switch (save_errno - E_LDAPBASE)
3922		{
3923#ifdef LDAP_SERVER_DOWN
3924		  case LDAP_SERVER_DOWN:
3925#endif /* LDAP_SERVER_DOWN */
3926		  case LDAP_TIMEOUT:
3927		  case LDAP_UNAVAILABLE:
3928			/* server disappeared, try reopen on next search */
3929			ldapmap_close(map);
3930			break;
3931		}
3932		errno = save_errno;
3933		return NULL;
3934	}
3935
3936# if _FFR_LDAP_RECURSION
3937finishlookup:
3938# endif /* _FFR_LDAP_RECURSION */
3939
3940	/* Did we match anything? */
3941	if (vp == NULL && !bitset(MF_MATCHONLY, map->map_mflags))
3942		return NULL;
3943
3944	if (*statp == EX_OK)
3945	{
3946		if (LogLevel > 9)
3947			sm_syslog(LOG_INFO, CurEnv->e_id,
3948				  "ldap %.100s => %s", name,
3949				  vp == NULL ? "<NULL>" : vp);
3950		if (bitset(MF_MATCHONLY, map->map_mflags))
3951			result = map_rewrite(map, name, strlen(name), NULL);
3952		else
3953		{
3954			/* vp != NULL according to test above */
3955			result = map_rewrite(map, vp, strlen(vp), av);
3956		}
3957		if (vp != NULL)
3958			sm_free(vp); /* XXX */
3959	}
3960	return result;
3961}
3962
3963/*
3964**  LDAPMAP_FINDCONN -- find an LDAP connection to the server
3965**
3966**	Cache LDAP connections based on the host, port, bind DN,
3967**	secret, and PID so we don't have multiple connections open to
3968**	the same server for different maps.  Need a separate connection
3969**	per PID since a parent process may close the map before the
3970**	child is done with it.
3971**
3972**	Parameters:
3973**		lmap -- LDAP map information
3974**
3975**	Returns:
3976**		Symbol table entry for the LDAP connection.
3977*/
3978
3979static STAB *
3980ldapmap_findconn(lmap)
3981	SM_LDAP_STRUCT *lmap;
3982{
3983	char *nbuf;
3984	STAB *SM_NONVOLATILE s = NULL;
3985
3986	nbuf = sm_stringf_x("%s%c%d%c%s%c%s%d",
3987			    (lmap->ldap_host == NULL ? "localhost"
3988						     : lmap->ldap_host),
3989			    CONDELSE,
3990			    lmap->ldap_port,
3991			    CONDELSE,
3992			    (lmap->ldap_binddn == NULL ? ""
3993						       : lmap->ldap_binddn),
3994			    CONDELSE,
3995			    (lmap->ldap_secret == NULL ? ""
3996						       : lmap->ldap_secret),
3997			    (int) CurrentPid);
3998	SM_TRY
3999		s = stab(nbuf, ST_LMAP, ST_ENTER);
4000	SM_FINALLY
4001		sm_free(nbuf);
4002	SM_END_TRY
4003	return s;
4004}
4005/*
4006**  LDAPMAP_PARSEARGS -- parse ldap map definition args.
4007*/
4008
4009static struct lamvalues LDAPAuthMethods[] =
4010{
4011	{	"none",		LDAP_AUTH_NONE		},
4012	{	"simple",	LDAP_AUTH_SIMPLE	},
4013# ifdef LDAP_AUTH_KRBV4
4014	{	"krbv4",	LDAP_AUTH_KRBV4		},
4015# endif /* LDAP_AUTH_KRBV4 */
4016	{	NULL,		0			}
4017};
4018
4019static struct ladvalues LDAPAliasDereference[] =
4020{
4021	{	"never",	LDAP_DEREF_NEVER	},
4022	{	"always",	LDAP_DEREF_ALWAYS	},
4023	{	"search",	LDAP_DEREF_SEARCHING	},
4024	{	"find",		LDAP_DEREF_FINDING	},
4025	{	NULL,		0			}
4026};
4027
4028static struct lssvalues LDAPSearchScope[] =
4029{
4030	{	"base",		LDAP_SCOPE_BASE		},
4031	{	"one",		LDAP_SCOPE_ONELEVEL	},
4032	{	"sub",		LDAP_SCOPE_SUBTREE	},
4033	{	NULL,		0			}
4034};
4035
4036bool
4037ldapmap_parseargs(map, args)
4038	MAP *map;
4039	char *args;
4040{
4041	bool secretread = true;
4042	int i;
4043	register char *p = args;
4044	SM_LDAP_STRUCT *lmap;
4045	struct lamvalues *lam;
4046	struct ladvalues *lad;
4047	struct lssvalues *lss;
4048	char ldapfilt[MAXLINE];
4049	char m_tmp[MAXPATHLEN + LDAPMAP_MAX_PASSWD];
4050
4051	/* Get ldap struct pointer from map */
4052	lmap = (SM_LDAP_STRUCT *) map->map_db1;
4053
4054	/* Check if setting the initial LDAP defaults */
4055	if (lmap == NULL || lmap != LDAPDefaults)
4056	{
4057		/* We need to alloc an SM_LDAP_STRUCT struct */
4058		lmap = (SM_LDAP_STRUCT *) xalloc(sizeof *lmap);
4059		if (LDAPDefaults == NULL)
4060			sm_ldap_clear(lmap);
4061		else
4062			STRUCTCOPY(*LDAPDefaults, *lmap);
4063	}
4064
4065	/* there is no check whether there is really an argument */
4066	map->map_mflags |= MF_TRY0NULL|MF_TRY1NULL;
4067	map->map_spacesub = SpaceSub;	/* default value */
4068
4069	/* Check if setting up an alias or file class LDAP map */
4070	if (bitset(MF_ALIAS, map->map_mflags))
4071	{
4072		/* Comma separate if used as an alias file */
4073		map->map_coldelim = ',';
4074		if (*args == '\0')
4075		{
4076			int n;
4077			char *lc;
4078			char jbuf[MAXHOSTNAMELEN];
4079			char lcbuf[MAXLINE];
4080
4081			/* Get $j */
4082			expand("\201j", jbuf, sizeof jbuf, &BlankEnvelope);
4083			if (jbuf[0] == '\0')
4084			{
4085				(void) sm_strlcpy(jbuf, "localhost",
4086						  sizeof jbuf);
4087			}
4088
4089			lc = macvalue(macid("{sendmailMTACluster}"), CurEnv);
4090			if (lc == NULL)
4091				lc = "";
4092			else
4093			{
4094				expand(lc, lcbuf, sizeof lcbuf, CurEnv);
4095				lc = lcbuf;
4096			}
4097
4098			n = sm_snprintf(ldapfilt, sizeof ldapfilt,
4099					"(&(objectClass=sendmailMTAAliasObject)(sendmailMTAAliasGrouping=aliases)(|(sendmailMTACluster=%s)(sendmailMTAHost=%s))(sendmailMTAKey=%%0))",
4100					lc, jbuf);
4101			if (n >= sizeof ldapfilt)
4102			{
4103				syserr("%s: Default LDAP string too long",
4104				       map->map_mname);
4105				return false;
4106			}
4107
4108			/* default args for an alias LDAP entry */
4109			lmap->ldap_filter = ldapfilt;
4110			lmap->ldap_attr[0] = "sendmailMTAAliasValue";
4111			lmap->ldap_attr[1] = NULL;
4112		}
4113	}
4114	else if (bitset(MF_FILECLASS, map->map_mflags))
4115	{
4116		/* Space separate if used as a file class file */
4117		map->map_coldelim = ' ';
4118	}
4119
4120	for (;;)
4121	{
4122		while (isascii(*p) && isspace(*p))
4123			p++;
4124		if (*p != '-')
4125			break;
4126		switch (*++p)
4127		{
4128		  case 'N':
4129			map->map_mflags |= MF_INCLNULL;
4130			map->map_mflags &= ~MF_TRY0NULL;
4131			break;
4132
4133		  case 'O':
4134			map->map_mflags &= ~MF_TRY1NULL;
4135			break;
4136
4137		  case 'o':
4138			map->map_mflags |= MF_OPTIONAL;
4139			break;
4140
4141		  case 'f':
4142			map->map_mflags |= MF_NOFOLDCASE;
4143			break;
4144
4145		  case 'm':
4146			map->map_mflags |= MF_MATCHONLY;
4147			break;
4148
4149		  case 'A':
4150			map->map_mflags |= MF_APPEND;
4151			break;
4152
4153		  case 'q':
4154			map->map_mflags |= MF_KEEPQUOTES;
4155			break;
4156
4157		  case 'a':
4158			map->map_app = ++p;
4159			break;
4160
4161		  case 'T':
4162			map->map_tapp = ++p;
4163			break;
4164
4165		  case 't':
4166			map->map_mflags |= MF_NODEFER;
4167			break;
4168
4169		  case 'S':
4170			map->map_spacesub = *++p;
4171			break;
4172
4173		  case 'D':
4174			map->map_mflags |= MF_DEFER;
4175			break;
4176
4177		  case 'z':
4178			if (*++p != '\\')
4179				map->map_coldelim = *p;
4180			else
4181			{
4182				switch (*++p)
4183				{
4184				  case 'n':
4185					map->map_coldelim = '\n';
4186					break;
4187
4188				  case 't':
4189					map->map_coldelim = '\t';
4190					break;
4191
4192				  default:
4193					map->map_coldelim = '\\';
4194				}
4195			}
4196			break;
4197
4198			/* Start of ldapmap specific args */
4199		  case 'V':
4200			if (*++p != '\\')
4201				lmap->ldap_attrsep = *p;
4202			else
4203			{
4204				switch (*++p)
4205				{
4206				  case 'n':
4207					lmap->ldap_attrsep = '\n';
4208					break;
4209
4210				  case 't':
4211					lmap->ldap_attrsep = '\t';
4212					break;
4213
4214				  default:
4215					lmap->ldap_attrsep = '\\';
4216				}
4217			}
4218			break;
4219
4220		  case 'k':		/* search field */
4221			while (isascii(*++p) && isspace(*p))
4222				continue;
4223			lmap->ldap_filter = p;
4224			break;
4225
4226		  case 'v':		/* attr to return */
4227			while (isascii(*++p) && isspace(*p))
4228				continue;
4229			lmap->ldap_attr[0] = p;
4230			lmap->ldap_attr[1] = NULL;
4231			break;
4232
4233		  case '1':
4234			map->map_mflags |= MF_SINGLEMATCH;
4235			break;
4236
4237			/* args stolen from ldapsearch.c */
4238		  case 'R':		/* don't auto chase referrals */
4239# ifdef LDAP_REFERRALS
4240			lmap->ldap_options &= ~LDAP_OPT_REFERRALS;
4241# else /* LDAP_REFERRALS */
4242			syserr("compile with -DLDAP_REFERRALS for referral support");
4243# endif /* LDAP_REFERRALS */
4244			break;
4245
4246		  case 'n':		/* retrieve attribute names only */
4247			lmap->ldap_attrsonly = LDAPMAP_TRUE;
4248			break;
4249
4250		  case 'r':		/* alias dereferencing */
4251			while (isascii(*++p) && isspace(*p))
4252				continue;
4253
4254			if (sm_strncasecmp(p, "LDAP_DEREF_", 11) == 0)
4255				p += 11;
4256
4257			for (lad = LDAPAliasDereference;
4258			     lad != NULL && lad->lad_name != NULL; lad++)
4259			{
4260				if (sm_strncasecmp(p, lad->lad_name,
4261						   strlen(lad->lad_name)) == 0)
4262					break;
4263			}
4264			if (lad->lad_name != NULL)
4265				lmap->ldap_deref = lad->lad_code;
4266			else
4267			{
4268				/* bad config line */
4269				if (!bitset(MCF_OPTFILE,
4270					    map->map_class->map_cflags))
4271				{
4272					char *ptr;
4273
4274					if ((ptr = strchr(p, ' ')) != NULL)
4275						*ptr = '\0';
4276					syserr("Deref must be [never|always|search|find] (not %s) in map %s",
4277						p, map->map_mname);
4278					if (ptr != NULL)
4279						*ptr = ' ';
4280					return false;
4281				}
4282			}
4283			break;
4284
4285		  case 's':		/* search scope */
4286			while (isascii(*++p) && isspace(*p))
4287				continue;
4288
4289			if (sm_strncasecmp(p, "LDAP_SCOPE_", 11) == 0)
4290				p += 11;
4291
4292			for (lss = LDAPSearchScope;
4293			     lss != NULL && lss->lss_name != NULL; lss++)
4294			{
4295				if (sm_strncasecmp(p, lss->lss_name,
4296						   strlen(lss->lss_name)) == 0)
4297					break;
4298			}
4299			if (lss->lss_name != NULL)
4300				lmap->ldap_scope = lss->lss_code;
4301			else
4302			{
4303				/* bad config line */
4304				if (!bitset(MCF_OPTFILE,
4305					    map->map_class->map_cflags))
4306				{
4307					char *ptr;
4308
4309					if ((ptr = strchr(p, ' ')) != NULL)
4310						*ptr = '\0';
4311					syserr("Scope must be [base|one|sub] (not %s) in map %s",
4312						p, map->map_mname);
4313					if (ptr != NULL)
4314						*ptr = ' ';
4315					return false;
4316				}
4317			}
4318			break;
4319
4320		  case 'h':		/* ldap host */
4321			while (isascii(*++p) && isspace(*p))
4322				continue;
4323			lmap->ldap_host = p;
4324			break;
4325
4326		  case 'b':		/* search base */
4327			while (isascii(*++p) && isspace(*p))
4328				continue;
4329			lmap->ldap_base = p;
4330			break;
4331
4332		  case 'p':		/* ldap port */
4333			while (isascii(*++p) && isspace(*p))
4334				continue;
4335			lmap->ldap_port = atoi(p);
4336			break;
4337
4338		  case 'l':		/* time limit */
4339			while (isascii(*++p) && isspace(*p))
4340				continue;
4341			lmap->ldap_timelimit = atoi(p);
4342			lmap->ldap_timeout.tv_sec = lmap->ldap_timelimit;
4343			break;
4344
4345		  case 'Z':
4346			while (isascii(*++p) && isspace(*p))
4347				continue;
4348			lmap->ldap_sizelimit = atoi(p);
4349			break;
4350
4351		  case 'd':		/* Dn to bind to server as */
4352			while (isascii(*++p) && isspace(*p))
4353				continue;
4354			lmap->ldap_binddn = p;
4355			break;
4356
4357		  case 'M':		/* Method for binding */
4358			while (isascii(*++p) && isspace(*p))
4359				continue;
4360
4361			if (sm_strncasecmp(p, "LDAP_AUTH_", 10) == 0)
4362				p += 10;
4363
4364			for (lam = LDAPAuthMethods;
4365			     lam != NULL && lam->lam_name != NULL; lam++)
4366			{
4367				if (sm_strncasecmp(p, lam->lam_name,
4368						   strlen(lam->lam_name)) == 0)
4369					break;
4370			}
4371			if (lam->lam_name != NULL)
4372				lmap->ldap_method = lam->lam_code;
4373			else
4374			{
4375				/* bad config line */
4376				if (!bitset(MCF_OPTFILE,
4377					    map->map_class->map_cflags))
4378				{
4379					char *ptr;
4380
4381					if ((ptr = strchr(p, ' ')) != NULL)
4382						*ptr = '\0';
4383					syserr("Method for binding must be [none|simple|krbv4] (not %s) in map %s",
4384						p, map->map_mname);
4385					if (ptr != NULL)
4386						*ptr = ' ';
4387					return false;
4388				}
4389			}
4390
4391			break;
4392
4393			/*
4394			**  This is a string that is dependent on the
4395			**  method used defined above.
4396			*/
4397
4398		  case 'P':		/* Secret password for binding */
4399			 while (isascii(*++p) && isspace(*p))
4400				continue;
4401			lmap->ldap_secret = p;
4402			secretread = false;
4403			break;
4404
4405		  default:
4406			syserr("Illegal option %c map %s", *p, map->map_mname);
4407			break;
4408		}
4409
4410		/* need to account for quoted strings here */
4411		while (*p != '\0' && !(isascii(*p) && isspace(*p)))
4412		{
4413			if (*p == '"')
4414			{
4415				while (*++p != '"' && *p != '\0')
4416					continue;
4417				if (*p != '\0')
4418					p++;
4419			}
4420			else
4421				p++;
4422		}
4423
4424		if (*p != '\0')
4425			*p++ = '\0';
4426	}
4427
4428	if (map->map_app != NULL)
4429		map->map_app = newstr(ldapmap_dequote(map->map_app));
4430	if (map->map_tapp != NULL)
4431		map->map_tapp = newstr(ldapmap_dequote(map->map_tapp));
4432
4433	/*
4434	**  We need to swallow up all the stuff into a struct
4435	**  and dump it into map->map_dbptr1
4436	*/
4437
4438	if (lmap->ldap_host != NULL &&
4439	    (LDAPDefaults == NULL ||
4440	     LDAPDefaults == lmap ||
4441	     LDAPDefaults->ldap_host != lmap->ldap_host))
4442		lmap->ldap_host = newstr(ldapmap_dequote(lmap->ldap_host));
4443	map->map_domain = lmap->ldap_host;
4444
4445	if (lmap->ldap_binddn != NULL &&
4446	    (LDAPDefaults == NULL ||
4447	     LDAPDefaults == lmap ||
4448	     LDAPDefaults->ldap_binddn != lmap->ldap_binddn))
4449		lmap->ldap_binddn = newstr(ldapmap_dequote(lmap->ldap_binddn));
4450
4451	if (lmap->ldap_secret != NULL &&
4452	    (LDAPDefaults == NULL ||
4453	     LDAPDefaults == lmap ||
4454	     LDAPDefaults->ldap_secret != lmap->ldap_secret))
4455	{
4456		SM_FILE_T *sfd;
4457		long sff = SFF_OPENASROOT|SFF_ROOTOK|SFF_NOWLINK|SFF_NOWWFILES|SFF_NOGWFILES;
4458
4459		if (DontLockReadFiles)
4460			sff |= SFF_NOLOCK;
4461
4462		/* need to use method to map secret to passwd string */
4463		switch (lmap->ldap_method)
4464		{
4465		  case LDAP_AUTH_NONE:
4466			/* Do nothing */
4467			break;
4468
4469		  case LDAP_AUTH_SIMPLE:
4470
4471			/*
4472			**  Secret is the name of a file with
4473			**  the first line as the password.
4474			*/
4475
4476			/* Already read in the secret? */
4477			if (secretread)
4478				break;
4479
4480			sfd = safefopen(ldapmap_dequote(lmap->ldap_secret),
4481					O_RDONLY, 0, sff);
4482			if (sfd == NULL)
4483			{
4484				syserr("LDAP map: cannot open secret %s",
4485				       ldapmap_dequote(lmap->ldap_secret));
4486				return false;
4487			}
4488			lmap->ldap_secret = sfgets(m_tmp, LDAPMAP_MAX_PASSWD,
4489						   sfd, TimeOuts.to_fileopen,
4490						   "ldapmap_parseargs");
4491			(void) sm_io_close(sfd, SM_TIME_DEFAULT);
4492			if (lmap->ldap_secret != NULL &&
4493			    strlen(m_tmp) > 0)
4494			{
4495				/* chomp newline */
4496				if (m_tmp[strlen(m_tmp) - 1] == '\n')
4497					m_tmp[strlen(m_tmp) - 1] = '\0';
4498
4499				lmap->ldap_secret = m_tmp;
4500			}
4501			break;
4502
4503# ifdef LDAP_AUTH_KRBV4
4504		  case LDAP_AUTH_KRBV4:
4505
4506			/*
4507			**  Secret is where the ticket file is
4508			**  stashed
4509			*/
4510
4511			(void) sm_snprintf(m_tmp,
4512				MAXPATHLEN + LDAPMAP_MAX_PASSWD,
4513				"KRBTKFILE=%s",
4514				ldapmap_dequote(lmap->ldap_secret));
4515			lmap->ldap_secret = m_tmp;
4516			break;
4517# endif /* LDAP_AUTH_KRBV4 */
4518
4519		  default:	       /* Should NEVER get here */
4520			syserr("LDAP map: Illegal value in lmap method");
4521			return false;
4522			/* NOTREACHED */
4523			break;
4524		}
4525	}
4526
4527	if (lmap->ldap_secret != NULL &&
4528	    (LDAPDefaults == NULL ||
4529	     LDAPDefaults == lmap ||
4530	     LDAPDefaults->ldap_secret != lmap->ldap_secret))
4531		lmap->ldap_secret = newstr(ldapmap_dequote(lmap->ldap_secret));
4532
4533	if (lmap->ldap_base != NULL &&
4534	    (LDAPDefaults == NULL ||
4535	     LDAPDefaults == lmap ||
4536	     LDAPDefaults->ldap_base != lmap->ldap_base))
4537		lmap->ldap_base = newstr(ldapmap_dequote(lmap->ldap_base));
4538
4539	/*
4540	**  Save the server from extra work.  If request is for a single
4541	**  match, tell the server to only return enough records to
4542	**  determine if there is a single match or not.  This can not
4543	**  be one since the server would only return one and we wouldn't
4544	**  know if there were others available.
4545	*/
4546
4547	if (bitset(MF_SINGLEMATCH, map->map_mflags))
4548		lmap->ldap_sizelimit = 2;
4549
4550	/* If setting defaults, don't process ldap_filter and ldap_attr */
4551	if (lmap == LDAPDefaults)
4552		return true;
4553
4554	if (lmap->ldap_filter != NULL)
4555		lmap->ldap_filter = newstr(ldapmap_dequote(lmap->ldap_filter));
4556	else
4557	{
4558		if (!bitset(MCF_OPTFILE, map->map_class->map_cflags))
4559		{
4560			syserr("No filter given in map %s", map->map_mname);
4561			return false;
4562		}
4563	}
4564
4565	if (lmap->ldap_attr[0] != NULL)
4566	{
4567#if _FFR_LDAP_RECURSION
4568		bool recurse = false;
4569		int final = 0;
4570#endif /* _FFR_LDAP_RECURSION */
4571
4572		i = 0;
4573		p = ldapmap_dequote(lmap->ldap_attr[0]);
4574		lmap->ldap_attr[0] = NULL;
4575
4576		while (p != NULL)
4577		{
4578			char *v;
4579
4580			while (isascii(*p) && isspace(*p))
4581				p++;
4582			if (*p == '\0')
4583				break;
4584			v = p;
4585			p = strchr(v, ',');
4586			if (p != NULL)
4587				*p++ = '\0';
4588
4589			if (i >= LDAPMAP_MAX_ATTR)
4590			{
4591				syserr("Too many return attributes in %s (max %d)",
4592				       map->map_mname, LDAPMAP_MAX_ATTR);
4593				return false;
4594			}
4595			if (*v != '\0')
4596			{
4597#if _FFR_LDAP_RECURSION
4598				char *type;
4599
4600				type = strchr(v, ':');
4601				if (type != NULL)
4602					*type++ = '\0';
4603#endif /* _FFR_LDAP_RECURSION */
4604
4605				lmap->ldap_attr[i] = newstr(v);
4606
4607#if _FFR_LDAP_RECURSION
4608				if (type != NULL)
4609				{
4610					if (sm_strcasecmp(type, "normal") == 0)
4611					{
4612						lmap->ldap_attr_type[i] = LDAPMAP_ATTR_NORMAL;
4613					}
4614					else if (sm_strcasecmp(type, "dn") == 0)
4615					{
4616						recurse = true;
4617						lmap->ldap_attr_type[i] = LDAPMAP_ATTR_DN;
4618					}
4619					else if (sm_strcasecmp(type, "filter") == 0)
4620					{
4621						recurse = true;
4622						lmap->ldap_attr_type[i] = LDAPMAP_ATTR_FILTER;
4623					}
4624					else if (sm_strcasecmp(type, "url") == 0)
4625					{
4626						recurse = true;
4627						lmap->ldap_attr_type[i] = LDAPMAP_ATTR_URL;
4628					}
4629					else if (sm_strcasecmp(type, "final") == 0)
4630					{
4631						lmap->ldap_attr_type[i] = LDAPMAP_ATTR_FINAL;
4632						if (final >= LDAPMAP_MAX_ATTR)
4633						{
4634							syserr("Too many FINAL attributes in %s (max %d)",
4635							       map->map_mname, LDAPMAP_MAX_ATTR);
4636							return false;
4637						}
4638						lmap->ldap_attr_final[final++] = lmap->ldap_attr[i];
4639					}
4640					else
4641					{
4642						syserr("Unknown attribute type (%s) in %s",
4643						       type, map->map_mname);
4644						return false;
4645					}
4646				}
4647				else
4648					lmap->ldap_attr_type[i] = LDAPMAP_ATTR_NORMAL;
4649#endif /* _FFR_LDAP_RECURSION */
4650				i++;
4651			}
4652		}
4653		lmap->ldap_attr[i] = NULL;
4654#if _FFR_LDAP_RECURSION
4655		lmap->ldap_attr_final[final] = NULL;
4656		if (recurse && lmap->ldap_attr_final[0] == NULL)
4657		{
4658			syserr("LDAP recursion requested in %s but no FINAL attribute given",
4659			       map->map_mname);
4660			return false;
4661		}
4662		if (recurse && lmap->ldap_attrsonly == LDAPMAP_TRUE)
4663		{
4664			syserr("LDAP recursion requested in %s can not be used with -n",
4665			       map->map_mname);
4666			return false;
4667		}
4668#endif /* _FFR_LDAP_RECURSION */
4669	}
4670	map->map_db1 = (ARBPTR_T) lmap;
4671	return true;
4672}
4673
4674/*
4675**  LDAPMAP_SET_DEFAULTS -- Read default map spec from LDAPDefaults in .cf
4676**
4677**	Parameters:
4678**		spec -- map argument string from LDAPDefaults option
4679**
4680**	Returns:
4681**		None.
4682*/
4683
4684void
4685ldapmap_set_defaults(spec)
4686	char *spec;
4687{
4688	STAB *class;
4689	MAP map;
4690
4691	/* Allocate and set the default values */
4692	if (LDAPDefaults == NULL)
4693		LDAPDefaults = (SM_LDAP_STRUCT *) xalloc(sizeof *LDAPDefaults);
4694	sm_ldap_clear(LDAPDefaults);
4695
4696	memset(&map, '\0', sizeof map);
4697
4698	/* look up the class */
4699	class = stab("ldap", ST_MAPCLASS, ST_FIND);
4700	if (class == NULL)
4701	{
4702		syserr("readcf: LDAPDefaultSpec: class ldap not available");
4703		return;
4704	}
4705	map.map_class = &class->s_mapclass;
4706	map.map_db1 = (ARBPTR_T) LDAPDefaults;
4707	map.map_mname = "O LDAPDefaultSpec";
4708
4709	(void) ldapmap_parseargs(&map, spec);
4710
4711	/* These should never be set in LDAPDefaults */
4712	if (map.map_mflags != (MF_TRY0NULL|MF_TRY1NULL) ||
4713	    map.map_spacesub != SpaceSub ||
4714	    map.map_app != NULL ||
4715	    map.map_tapp != NULL)
4716	{
4717		syserr("readcf: option LDAPDefaultSpec: Do not set non-LDAP specific flags");
4718		SM_FREE_CLR(map.map_app);
4719		SM_FREE_CLR(map.map_tapp);
4720	}
4721
4722	if (LDAPDefaults->ldap_filter != NULL)
4723	{
4724		syserr("readcf: option LDAPDefaultSpec: Do not set the LDAP search filter");
4725		/* don't free, it isn't malloc'ed in parseargs */
4726		LDAPDefaults->ldap_filter = NULL;
4727	}
4728
4729	if (LDAPDefaults->ldap_attr[0] != NULL)
4730	{
4731		syserr("readcf: option LDAPDefaultSpec: Do not set the requested LDAP attributes");
4732		/* don't free, they aren't malloc'ed in parseargs */
4733		LDAPDefaults->ldap_attr[0] = NULL;
4734	}
4735}
4736#endif /* LDAPMAP */
4737/*
4738**  PH map
4739*/
4740
4741#if PH_MAP
4742
4743/*
4744**  Support for the CCSO Nameserver (ph/qi).
4745**  This code is intended to replace the so-called "ph mailer".
4746**  Contributed by Mark D. Roth <roth@uiuc.edu>.  Contact him for support.
4747*/
4748
4749/* what version of the ph map code we're running */
4750static char phmap_id[PH_BUF_SIZE];
4751
4752/* sendmail version for phmap id string */
4753extern const char Version[];
4754
4755/*
4756**  PH_MAP_PARSEARGS -- parse ph map definition args.
4757*/
4758
4759bool
4760ph_map_parseargs(map, args)
4761	MAP *map;
4762	char *args;
4763{
4764	register bool done;
4765	register char *p = args;
4766	PH_MAP_STRUCT *pmap = NULL;
4767
4768	/* initialize version string */
4769	(void) sm_snprintf(phmap_id, sizeof phmap_id,
4770			   "sendmail-%s phmap-20010529 libphclient-%s",
4771			   Version, libphclient_version);
4772
4773	pmap = (PH_MAP_STRUCT *) xalloc(sizeof *pmap);
4774
4775	/* defaults */
4776	pmap->ph_servers = NULL;
4777	pmap->ph_field_list = NULL;
4778	pmap->ph = NULL;
4779	pmap->ph_timeout = 0;
4780	pmap->ph_fastclose = 0;
4781
4782	map->map_mflags |= MF_TRY0NULL|MF_TRY1NULL;
4783	for (;;)
4784	{
4785		while (isascii(*p) && isspace(*p))
4786			p++;
4787		if (*p != '-')
4788			break;
4789		switch (*++p)
4790		{
4791		  case 'N':
4792			map->map_mflags |= MF_INCLNULL;
4793			map->map_mflags &= ~MF_TRY0NULL;
4794			break;
4795
4796		  case 'O':
4797			map->map_mflags &= ~MF_TRY1NULL;
4798			break;
4799
4800		  case 'o':
4801			map->map_mflags |= MF_OPTIONAL;
4802			break;
4803
4804		  case 'f':
4805			map->map_mflags |= MF_NOFOLDCASE;
4806			break;
4807
4808		  case 'm':
4809			map->map_mflags |= MF_MATCHONLY;
4810			break;
4811
4812		  case 'A':
4813			map->map_mflags |= MF_APPEND;
4814			break;
4815
4816		  case 'q':
4817			map->map_mflags |= MF_KEEPQUOTES;
4818			break;
4819
4820		  case 't':
4821			map->map_mflags |= MF_NODEFER;
4822			break;
4823
4824		  case 'a':
4825			map->map_app = ++p;
4826			break;
4827
4828		  case 'T':
4829			map->map_tapp = ++p;
4830			break;
4831
4832		  case 'l':
4833			while (isascii(*++p) && isspace(*p))
4834				continue;
4835			pmap->ph_timeout = atoi(p);
4836			break;
4837
4838		  case 'S':
4839			map->map_spacesub = *++p;
4840			break;
4841
4842		  case 'D':
4843			map->map_mflags |= MF_DEFER;
4844			break;
4845
4846		  case 'h':		/* PH server list */
4847			while (isascii(*++p) && isspace(*p))
4848				continue;
4849			pmap->ph_servers = p;
4850			break;
4851
4852		  case 'v':
4853			sm_syslog(LOG_WARNING, NULL,
4854				  "ph_map_parseargs: WARNING: -v option will be removed in a future release - please use -k instead");
4855			/* intentional fallthrough for backward compatibility */
4856			/* FALLTHROUGH */
4857
4858		  case 'k':		/* fields to search for */
4859			while (isascii(*++p) && isspace(*p))
4860				continue;
4861			pmap->ph_field_list = p;
4862			break;
4863
4864		  default:
4865			syserr("ph_map_parseargs: unknown option -%c", *p);
4866		}
4867
4868		/* try to account for quoted strings */
4869		done = isascii(*p) && isspace(*p);
4870		while (*p != '\0' && !done)
4871		{
4872			if (*p == '"')
4873			{
4874				while (*++p != '"' && *p != '\0')
4875					continue;
4876				if (*p != '\0')
4877					p++;
4878			}
4879			else
4880				p++;
4881			done = isascii(*p) && isspace(*p);
4882		}
4883
4884		if (*p != '\0')
4885			*p++ = '\0';
4886	}
4887
4888	if (map->map_app != NULL)
4889		map->map_app = newstr(ph_map_dequote(map->map_app));
4890	if (map->map_tapp != NULL)
4891		map->map_tapp = newstr(ph_map_dequote(map->map_tapp));
4892
4893	if (pmap->ph_field_list != NULL)
4894		pmap->ph_field_list = newstr(ph_map_dequote(pmap->ph_field_list));
4895
4896	if (pmap->ph_servers != NULL)
4897		pmap->ph_servers = newstr(ph_map_dequote(pmap->ph_servers));
4898	else
4899	{
4900		syserr("ph_map_parseargs: -h flag is required");
4901		return false;
4902	}
4903
4904	map->map_db1 = (ARBPTR_T) pmap;
4905	return true;
4906}
4907
4908/*
4909**  PH_MAP_CLOSE -- close the connection to the ph server
4910*/
4911
4912void
4913ph_map_close(map)
4914	MAP *map;
4915{
4916	PH_MAP_STRUCT *pmap;
4917
4918	pmap = (PH_MAP_STRUCT *)map->map_db1;
4919	if (tTd(38, 9))
4920		sm_dprintf("ph_map_close(%s): pmap->ph_fastclose=%d",
4921			   map->map_mname, pmap->ph_fastclose);
4922
4923
4924	if (pmap->ph != NULL)
4925	{
4926		ph_set_sendhook(pmap->ph, NULL);
4927		ph_set_recvhook(pmap->ph, NULL);
4928		ph_close(pmap->ph, pmap->ph_fastclose);
4929	}
4930
4931	map->map_mflags &= ~(MF_OPEN|MF_WRITABLE);
4932}
4933
4934static jmp_buf  PHTimeout;
4935
4936/* ARGSUSED */
4937static void
4938ph_timeout(unused)
4939	int unused;
4940{
4941	/*
4942	**  NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER.  DO NOT ADD
4943	**	ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
4944	**	DOING.
4945	*/
4946
4947	errno = ETIMEDOUT;
4948	longjmp(PHTimeout, 1);
4949}
4950
4951static void
4952ph_map_send_debug(text)
4953	char *text;
4954{
4955	if (LogLevel > 9)
4956		sm_syslog(LOG_NOTICE, CurEnv->e_id,
4957			  "ph_map_send_debug: ==> %s", text);
4958	if (tTd(38, 20))
4959		sm_dprintf("ph_map_send_debug: ==> %s\n", text);
4960}
4961
4962static void
4963ph_map_recv_debug(text)
4964	char *text;
4965{
4966	if (LogLevel > 10)
4967		sm_syslog(LOG_NOTICE, CurEnv->e_id,
4968			  "ph_map_recv_debug: <== %s", text);
4969	if (tTd(38, 21))
4970		sm_dprintf("ph_map_recv_debug: <== %s\n", text);
4971}
4972
4973/*
4974**  PH_MAP_OPEN -- sub for opening PH map
4975*/
4976bool
4977ph_map_open(map, mode)
4978	MAP *map;
4979	int mode;
4980{
4981	PH_MAP_STRUCT *pmap;
4982	register SM_EVENT *ev = NULL;
4983	int save_errno = 0;
4984	char *hostlist, *host;
4985
4986	if (tTd(38, 2))
4987		sm_dprintf("ph_map_open(%s)\n", map->map_mname);
4988
4989	mode &= O_ACCMODE;
4990	if (mode != O_RDONLY)
4991	{
4992		/* issue a pseudo-error message */
4993		errno = SM_EMAPCANTWRITE;
4994		return false;
4995	}
4996
4997	if (CurEnv != NULL && CurEnv->e_sendmode == SM_DEFER &&
4998	    bitset(MF_DEFER, map->map_mflags))
4999	{
5000		if (tTd(9, 1))
5001			sm_dprintf("ph_map_open(%s) => DEFERRED\n",
5002				   map->map_mname);
5003
5004		/*
5005		**  Unset MF_DEFER here so that map_lookup() returns
5006		**  a temporary failure using the bogus map and
5007		**  map->map_tapp instead of the default permanent error.
5008		*/
5009
5010		map->map_mflags &= ~MF_DEFER;
5011		return false;
5012	}
5013
5014	pmap = (PH_MAP_STRUCT *)map->map_db1;
5015	pmap->ph_fastclose = 0;		/* refresh field for reopen */
5016
5017	/* try each host in the list */
5018	hostlist = newstr(pmap->ph_servers);
5019	for (host = strtok(hostlist, " ");
5020	     host != NULL;
5021	     host = strtok(NULL, " "))
5022	{
5023		/* set timeout */
5024		if (pmap->ph_timeout != 0)
5025		{
5026			if (setjmp(PHTimeout) != 0)
5027			{
5028				ev = NULL;
5029				if (LogLevel > 1)
5030					sm_syslog(LOG_NOTICE, CurEnv->e_id,
5031						  "timeout connecting to PH server %.100s",
5032						  host);
5033				errno = ETIMEDOUT;
5034				goto ph_map_open_abort;
5035			}
5036			ev = sm_setevent(pmap->ph_timeout, ph_timeout, 0);
5037		}
5038
5039		/* open connection to server */
5040		if (!ph_open(&(pmap->ph), host, PH_ROUNDROBIN|PH_DONTID,
5041			     ph_map_send_debug, ph_map_recv_debug) &&
5042		    !ph_id(pmap->ph, phmap_id))
5043		{
5044			if (ev != NULL)
5045				sm_clrevent(ev);
5046			sm_free(hostlist); /* XXX */
5047			return true;
5048		}
5049
5050  ph_map_open_abort:
5051		save_errno = errno;
5052		if (ev != NULL)
5053			sm_clrevent(ev);
5054		pmap->ph_fastclose = PH_FASTCLOSE;
5055		ph_map_close(map);
5056		errno = save_errno;
5057	}
5058
5059	if (bitset(MF_NODEFER, map->map_mflags))
5060	{
5061		if (errno == 0)
5062			errno = EAGAIN;
5063		syserr("ph_map_open: %s: cannot connect to PH server",
5064		       map->map_mname);
5065	}
5066	else if (!bitset(MF_OPTIONAL, map->map_mflags) && LogLevel > 1)
5067		sm_syslog(LOG_NOTICE, CurEnv->e_id,
5068			  "ph_map_open: %s: cannot connect to PH server",
5069			  map->map_mname);
5070	sm_free(hostlist); /* XXX */
5071	return false;
5072}
5073
5074/*
5075**  PH_MAP_LOOKUP -- look up key from ph server
5076*/
5077
5078char *
5079ph_map_lookup(map, key, args, pstat)
5080	MAP *map;
5081	char *key;
5082	char **args;
5083	int *pstat;
5084{
5085	int i, save_errno = 0;
5086	register SM_EVENT *ev = NULL;
5087	PH_MAP_STRUCT *pmap;
5088	char *value = NULL;
5089
5090	pmap = (PH_MAP_STRUCT *)map->map_db1;
5091
5092	*pstat = EX_OK;
5093
5094	/* set timeout */
5095	if (pmap->ph_timeout != 0)
5096	{
5097		if (setjmp(PHTimeout) != 0)
5098		{
5099			ev = NULL;
5100			if (LogLevel > 1)
5101				sm_syslog(LOG_NOTICE, CurEnv->e_id,
5102					  "timeout during PH lookup of %.100s",
5103					  key);
5104			errno = ETIMEDOUT;
5105			*pstat = EX_TEMPFAIL;
5106			goto ph_map_lookup_abort;
5107		}
5108		ev = sm_setevent(pmap->ph_timeout, ph_timeout, 0);
5109	}
5110
5111	/* perform lookup */
5112	i = ph_email_resolve(pmap->ph, key, pmap->ph_field_list, &value);
5113	if (i == -1)
5114		*pstat = EX_TEMPFAIL;
5115	else if (i == PH_NOMATCH || i == PH_DATAERR)
5116		*pstat = EX_UNAVAILABLE;
5117
5118  ph_map_lookup_abort:
5119	if (ev != NULL)
5120		sm_clrevent(ev);
5121
5122	/*
5123	**  Close the connection if the timer popped
5124	**  or we got a temporary PH error
5125	*/
5126
5127	if (*pstat == EX_TEMPFAIL)
5128	{
5129		save_errno = errno;
5130		pmap->ph_fastclose = PH_FASTCLOSE;
5131		ph_map_close(map);
5132		errno = save_errno;
5133	}
5134
5135	if (*pstat == EX_OK)
5136	{
5137		if (tTd(38,20))
5138			sm_dprintf("ph_map_lookup: %s => %s\n", key, value);
5139
5140		if (bitset(MF_MATCHONLY, map->map_mflags))
5141			return map_rewrite(map, key, strlen(key), NULL);
5142		else
5143			return map_rewrite(map, value, strlen(value), args);
5144	}
5145
5146	return NULL;
5147}
5148#endif /* PH_MAP */
5149/*
5150**  syslog map
5151*/
5152
5153#define map_prio	map_lockfd	/* overload field */
5154
5155/*
5156**  SYSLOG_MAP_PARSEARGS -- check for priority level to syslog messages.
5157*/
5158
5159bool
5160syslog_map_parseargs(map, args)
5161	MAP *map;
5162	char *args;
5163{
5164	char *p = args;
5165	char *priority = NULL;
5166
5167	/* there is no check whether there is really an argument */
5168	while (*p != '\0')
5169	{
5170		while (isascii(*p) && isspace(*p))
5171			p++;
5172		if (*p != '-')
5173			break;
5174		++p;
5175		if (*p == 'D')
5176		{
5177			map->map_mflags |= MF_DEFER;
5178			++p;
5179		}
5180		else if (*p == 'S')
5181		{
5182			map->map_spacesub = *++p;
5183			if (*p != '\0')
5184				p++;
5185		}
5186		else if (*p == 'L')
5187		{
5188			while (*++p != '\0' && isascii(*p) && isspace(*p))
5189				continue;
5190			if (*p == '\0')
5191				break;
5192			priority = p;
5193			while (*p != '\0' && !(isascii(*p) && isspace(*p)))
5194				p++;
5195			if (*p != '\0')
5196				*p++ = '\0';
5197		}
5198		else
5199		{
5200			syserr("Illegal option %c map syslog", *p);
5201			++p;
5202		}
5203	}
5204
5205	if (priority == NULL)
5206		map->map_prio = LOG_INFO;
5207	else
5208	{
5209		if (sm_strncasecmp("LOG_", priority, 4) == 0)
5210			priority += 4;
5211
5212#ifdef LOG_EMERG
5213		if (sm_strcasecmp("EMERG", priority) == 0)
5214			map->map_prio = LOG_EMERG;
5215		else
5216#endif /* LOG_EMERG */
5217#ifdef LOG_ALERT
5218		if (sm_strcasecmp("ALERT", priority) == 0)
5219			map->map_prio = LOG_ALERT;
5220		else
5221#endif /* LOG_ALERT */
5222#ifdef LOG_CRIT
5223		if (sm_strcasecmp("CRIT", priority) == 0)
5224			map->map_prio = LOG_CRIT;
5225		else
5226#endif /* LOG_CRIT */
5227#ifdef LOG_ERR
5228		if (sm_strcasecmp("ERR", priority) == 0)
5229			map->map_prio = LOG_ERR;
5230		else
5231#endif /* LOG_ERR */
5232#ifdef LOG_WARNING
5233		if (sm_strcasecmp("WARNING", priority) == 0)
5234			map->map_prio = LOG_WARNING;
5235		else
5236#endif /* LOG_WARNING */
5237#ifdef LOG_NOTICE
5238		if (sm_strcasecmp("NOTICE", priority) == 0)
5239			map->map_prio = LOG_NOTICE;
5240		else
5241#endif /* LOG_NOTICE */
5242#ifdef LOG_INFO
5243		if (sm_strcasecmp("INFO", priority) == 0)
5244			map->map_prio = LOG_INFO;
5245		else
5246#endif /* LOG_INFO */
5247#ifdef LOG_DEBUG
5248		if (sm_strcasecmp("DEBUG", priority) == 0)
5249			map->map_prio = LOG_DEBUG;
5250		else
5251#endif /* LOG_DEBUG */
5252		{
5253			syserr("syslog_map_parseargs: Unknown priority %s",
5254			       priority);
5255			return false;
5256		}
5257	}
5258	return true;
5259}
5260
5261/*
5262**  SYSLOG_MAP_LOOKUP -- rewrite and syslog message.  Always return empty string
5263*/
5264
5265char *
5266syslog_map_lookup(map, string, args, statp)
5267	MAP *map;
5268	char *string;
5269	char **args;
5270	int *statp;
5271{
5272	char *ptr = map_rewrite(map, string, strlen(string), args);
5273
5274	if (ptr != NULL)
5275	{
5276		if (tTd(38, 20))
5277			sm_dprintf("syslog_map_lookup(%s (priority %d): %s\n",
5278				map->map_mname, map->map_prio, ptr);
5279
5280		sm_syslog(map->map_prio, CurEnv->e_id, "%s", ptr);
5281	}
5282
5283	*statp = EX_OK;
5284	return "";
5285}
5286
5287/*
5288**  HESIOD Modules
5289*/
5290
5291#if HESIOD
5292
5293bool
5294hes_map_open(map, mode)
5295	MAP *map;
5296	int mode;
5297{
5298	if (tTd(38, 2))
5299		sm_dprintf("hes_map_open(%s, %s, %d)\n",
5300			map->map_mname, map->map_file, mode);
5301
5302	if (mode != O_RDONLY)
5303	{
5304		/* issue a pseudo-error message */
5305		errno = SM_EMAPCANTWRITE;
5306		return false;
5307	}
5308
5309# ifdef HESIOD_INIT
5310	if (HesiodContext != NULL || hesiod_init(&HesiodContext) == 0)
5311		return true;
5312
5313	if (!bitset(MF_OPTIONAL, map->map_mflags))
5314		syserr("421 4.0.0 cannot initialize Hesiod map (%s)",
5315			sm_errstring(errno));
5316	return false;
5317# else /* HESIOD_INIT */
5318	if (hes_error() == HES_ER_UNINIT)
5319		hes_init();
5320	switch (hes_error())
5321	{
5322	  case HES_ER_OK:
5323	  case HES_ER_NOTFOUND:
5324		return true;
5325	}
5326
5327	if (!bitset(MF_OPTIONAL, map->map_mflags))
5328		syserr("421 4.0.0 cannot initialize Hesiod map (%d)", hes_error());
5329
5330	return false;
5331# endif /* HESIOD_INIT */
5332}
5333
5334char *
5335hes_map_lookup(map, name, av, statp)
5336	MAP *map;
5337	char *name;
5338	char **av;
5339	int *statp;
5340{
5341	char **hp;
5342
5343	if (tTd(38, 20))
5344		sm_dprintf("hes_map_lookup(%s, %s)\n", map->map_file, name);
5345
5346	if (name[0] == '\\')
5347	{
5348		char *np;
5349		int nl;
5350		int save_errno;
5351		char nbuf[MAXNAME];
5352
5353		nl = strlen(name);
5354		if (nl < sizeof nbuf - 1)
5355			np = nbuf;
5356		else
5357			np = xalloc(strlen(name) + 2);
5358		np[0] = '\\';
5359		(void) sm_strlcpy(&np[1], name, (sizeof nbuf) - 1);
5360# ifdef HESIOD_INIT
5361		hp = hesiod_resolve(HesiodContext, np, map->map_file);
5362# else /* HESIOD_INIT */
5363		hp = hes_resolve(np, map->map_file);
5364# endif /* HESIOD_INIT */
5365		save_errno = errno;
5366		if (np != nbuf)
5367			sm_free(np); /* XXX */
5368		errno = save_errno;
5369	}
5370	else
5371	{
5372# ifdef HESIOD_INIT
5373		hp = hesiod_resolve(HesiodContext, name, map->map_file);
5374# else /* HESIOD_INIT */
5375		hp = hes_resolve(name, map->map_file);
5376# endif /* HESIOD_INIT */
5377	}
5378# ifdef HESIOD_INIT
5379	if (hp == NULL || *hp == NULL)
5380	{
5381		switch (errno)
5382		{
5383		  case ENOENT:
5384			  *statp = EX_NOTFOUND;
5385			  break;
5386		  case ECONNREFUSED:
5387			  *statp = EX_TEMPFAIL;
5388			  break;
5389		  case EMSGSIZE:
5390		  case ENOMEM:
5391		  default:
5392			  *statp = EX_UNAVAILABLE;
5393			  break;
5394		}
5395		if (hp != NULL)
5396			hesiod_free_list(HesiodContext, hp);
5397		return NULL;
5398	}
5399# else /* HESIOD_INIT */
5400	if (hp == NULL || hp[0] == NULL)
5401	{
5402		switch (hes_error())
5403		{
5404		  case HES_ER_OK:
5405			*statp = EX_OK;
5406			break;
5407
5408		  case HES_ER_NOTFOUND:
5409			*statp = EX_NOTFOUND;
5410			break;
5411
5412		  case HES_ER_CONFIG:
5413			*statp = EX_UNAVAILABLE;
5414			break;
5415
5416		  case HES_ER_NET:
5417			*statp = EX_TEMPFAIL;
5418			break;
5419		}
5420		return NULL;
5421	}
5422# endif /* HESIOD_INIT */
5423
5424	if (bitset(MF_MATCHONLY, map->map_mflags))
5425		return map_rewrite(map, name, strlen(name), NULL);
5426	else
5427		return map_rewrite(map, hp[0], strlen(hp[0]), av);
5428}
5429
5430/*
5431**  HES_MAP_CLOSE -- free the Hesiod context
5432*/
5433
5434void
5435hes_map_close(map)
5436	MAP *map;
5437{
5438	if (tTd(38, 20))
5439		sm_dprintf("hes_map_close(%s)\n", map->map_file);
5440
5441# ifdef HESIOD_INIT
5442	/* Free the hesiod context */
5443	if (HesiodContext != NULL)
5444	{
5445		hesiod_end(HesiodContext);
5446		HesiodContext = NULL;
5447	}
5448# endif /* HESIOD_INIT */
5449}
5450
5451#endif /* HESIOD */
5452/*
5453**  NeXT NETINFO Modules
5454*/
5455
5456#if NETINFO
5457
5458# define NETINFO_DEFAULT_DIR		"/aliases"
5459# define NETINFO_DEFAULT_PROPERTY	"members"
5460
5461/*
5462**  NI_MAP_OPEN -- open NetInfo Aliases
5463*/
5464
5465bool
5466ni_map_open(map, mode)
5467	MAP *map;
5468	int mode;
5469{
5470	if (tTd(38, 2))
5471		sm_dprintf("ni_map_open(%s, %s, %d)\n",
5472			map->map_mname, map->map_file, mode);
5473	mode &= O_ACCMODE;
5474
5475	if (*map->map_file == '\0')
5476		map->map_file = NETINFO_DEFAULT_DIR;
5477
5478	if (map->map_valcolnm == NULL)
5479		map->map_valcolnm = NETINFO_DEFAULT_PROPERTY;
5480
5481	if (map->map_coldelim == '\0')
5482	{
5483		if (bitset(MF_ALIAS, map->map_mflags))
5484			map->map_coldelim = ',';
5485		else if (bitset(MF_FILECLASS, map->map_mflags))
5486			map->map_coldelim = ' ';
5487	}
5488	return true;
5489}
5490
5491
5492/*
5493**  NI_MAP_LOOKUP -- look up a datum in NetInfo
5494*/
5495
5496char *
5497ni_map_lookup(map, name, av, statp)
5498	MAP *map;
5499	char *name;
5500	char **av;
5501	int *statp;
5502{
5503	char *res;
5504	char *propval;
5505
5506	if (tTd(38, 20))
5507		sm_dprintf("ni_map_lookup(%s, %s)\n", map->map_mname, name);
5508
5509	propval = ni_propval(map->map_file, map->map_keycolnm, name,
5510			     map->map_valcolnm, map->map_coldelim);
5511
5512	if (propval == NULL)
5513		return NULL;
5514
5515	SM_TRY
5516		if (bitset(MF_MATCHONLY, map->map_mflags))
5517			res = map_rewrite(map, name, strlen(name), NULL);
5518		else
5519			res = map_rewrite(map, propval, strlen(propval), av);
5520	SM_FINALLY
5521		sm_free(propval);
5522	SM_END_TRY
5523	return res;
5524}
5525
5526
5527static bool
5528ni_getcanonname(name, hbsize, statp)
5529	char *name;
5530	int hbsize;
5531	int *statp;
5532{
5533	char *vptr;
5534	char *ptr;
5535	char nbuf[MAXNAME + 1];
5536
5537	if (tTd(38, 20))
5538		sm_dprintf("ni_getcanonname(%s)\n", name);
5539
5540	if (sm_strlcpy(nbuf, name, sizeof nbuf) >= sizeof nbuf)
5541	{
5542		*statp = EX_UNAVAILABLE;
5543		return false;
5544	}
5545	(void) shorten_hostname(nbuf);
5546
5547	/* we only accept single token search key */
5548	if (strchr(nbuf, '.'))
5549	{
5550		*statp = EX_NOHOST;
5551		return false;
5552	}
5553
5554	/* Do the search */
5555	vptr = ni_propval("/machines", NULL, nbuf, "name", '\n');
5556
5557	if (vptr == NULL)
5558	{
5559		*statp = EX_NOHOST;
5560		return false;
5561	}
5562
5563	/* Only want the first machine name */
5564	if ((ptr = strchr(vptr, '\n')) != NULL)
5565		*ptr = '\0';
5566
5567	if (sm_strlcpy(name, vptr, hbsize) >= hbsize)
5568	{
5569		sm_free(vptr);
5570		*statp = EX_UNAVAILABLE;
5571		return true;
5572	}
5573	sm_free(vptr);
5574	*statp = EX_OK;
5575	return false;
5576}
5577#endif /* NETINFO */
5578/*
5579**  TEXT (unindexed text file) Modules
5580**
5581**	This code donated by Sun Microsystems.
5582*/
5583
5584#define map_sff		map_lockfd	/* overload field */
5585
5586
5587/*
5588**  TEXT_MAP_OPEN -- open text table
5589*/
5590
5591bool
5592text_map_open(map, mode)
5593	MAP *map;
5594	int mode;
5595{
5596	long sff;
5597	int i;
5598
5599	if (tTd(38, 2))
5600		sm_dprintf("text_map_open(%s, %s, %d)\n",
5601			map->map_mname, map->map_file, mode);
5602
5603	mode &= O_ACCMODE;
5604	if (mode != O_RDONLY)
5605	{
5606		errno = EPERM;
5607		return false;
5608	}
5609
5610	if (*map->map_file == '\0')
5611	{
5612		syserr("text map \"%s\": file name required",
5613			map->map_mname);
5614		return false;
5615	}
5616
5617	if (map->map_file[0] != '/')
5618	{
5619		syserr("text map \"%s\": file name must be fully qualified",
5620			map->map_mname);
5621		return false;
5622	}
5623
5624	sff = SFF_ROOTOK|SFF_REGONLY;
5625	if (!bitnset(DBS_LINKEDMAPINWRITABLEDIR, DontBlameSendmail))
5626		sff |= SFF_NOWLINK;
5627	if (!bitnset(DBS_MAPINUNSAFEDIRPATH, DontBlameSendmail))
5628		sff |= SFF_SAFEDIRPATH;
5629	if ((i = safefile(map->map_file, RunAsUid, RunAsGid, RunAsUserName,
5630			  sff, S_IRUSR, NULL)) != 0)
5631	{
5632		int save_errno = errno;
5633
5634		/* cannot open this map */
5635		if (tTd(38, 2))
5636			sm_dprintf("\tunsafe map file: %d\n", i);
5637		errno = save_errno;
5638		if (!bitset(MF_OPTIONAL, map->map_mflags))
5639			syserr("text map \"%s\": unsafe map file %s",
5640				map->map_mname, map->map_file);
5641		return false;
5642	}
5643
5644	if (map->map_keycolnm == NULL)
5645		map->map_keycolno = 0;
5646	else
5647	{
5648		if (!(isascii(*map->map_keycolnm) && isdigit(*map->map_keycolnm)))
5649		{
5650			syserr("text map \"%s\", file %s: -k should specify a number, not %s",
5651				map->map_mname, map->map_file,
5652				map->map_keycolnm);
5653			return false;
5654		}
5655		map->map_keycolno = atoi(map->map_keycolnm);
5656	}
5657
5658	if (map->map_valcolnm == NULL)
5659		map->map_valcolno = 0;
5660	else
5661	{
5662		if (!(isascii(*map->map_valcolnm) && isdigit(*map->map_valcolnm)))
5663		{
5664			syserr("text map \"%s\", file %s: -v should specify a number, not %s",
5665					map->map_mname, map->map_file,
5666					map->map_valcolnm);
5667			return false;
5668		}
5669		map->map_valcolno = atoi(map->map_valcolnm);
5670	}
5671
5672	if (tTd(38, 2))
5673	{
5674		sm_dprintf("text_map_open(%s, %s): delimiter = ",
5675			map->map_mname, map->map_file);
5676		if (map->map_coldelim == '\0')
5677			sm_dprintf("(white space)\n");
5678		else
5679			sm_dprintf("%c\n", map->map_coldelim);
5680	}
5681
5682	map->map_sff = sff;
5683	return true;
5684}
5685
5686
5687/*
5688**  TEXT_MAP_LOOKUP -- look up a datum in a TEXT table
5689*/
5690
5691char *
5692text_map_lookup(map, name, av, statp)
5693	MAP *map;
5694	char *name;
5695	char **av;
5696	int *statp;
5697{
5698	char *vp;
5699	auto int vsize;
5700	int buflen;
5701	SM_FILE_T *f;
5702	char delim;
5703	int key_idx;
5704	bool found_it;
5705	long sff = map->map_sff;
5706	char search_key[MAXNAME + 1];
5707	char linebuf[MAXLINE];
5708	char buf[MAXNAME + 1];
5709
5710	found_it = false;
5711	if (tTd(38, 20))
5712		sm_dprintf("text_map_lookup(%s, %s)\n", map->map_mname,  name);
5713
5714	buflen = strlen(name);
5715	if (buflen > sizeof search_key - 1)
5716		buflen = sizeof search_key - 1;	/* XXX just cut if off? */
5717	memmove(search_key, name, buflen);
5718	search_key[buflen] = '\0';
5719	if (!bitset(MF_NOFOLDCASE, map->map_mflags))
5720		makelower(search_key);
5721
5722	f = safefopen(map->map_file, O_RDONLY, FileMode, sff);
5723	if (f == NULL)
5724	{
5725		map->map_mflags &= ~(MF_VALID|MF_OPEN);
5726		*statp = EX_UNAVAILABLE;
5727		return NULL;
5728	}
5729	key_idx = map->map_keycolno;
5730	delim = map->map_coldelim;
5731	while (sm_io_fgets(f, SM_TIME_DEFAULT, linebuf, MAXLINE) != NULL)
5732	{
5733		char *p;
5734
5735		/* skip comment line */
5736		if (linebuf[0] == '#')
5737			continue;
5738		p = strchr(linebuf, '\n');
5739		if (p != NULL)
5740			*p = '\0';
5741		p = get_column(linebuf, key_idx, delim, buf, sizeof buf);
5742		if (p != NULL && sm_strcasecmp(search_key, p) == 0)
5743		{
5744			found_it = true;
5745			break;
5746		}
5747	}
5748	(void) sm_io_close(f, SM_TIME_DEFAULT);
5749	if (!found_it)
5750	{
5751		*statp = EX_NOTFOUND;
5752		return NULL;
5753	}
5754	vp = get_column(linebuf, map->map_valcolno, delim, buf, sizeof buf);
5755	if (vp == NULL)
5756	{
5757		*statp = EX_NOTFOUND;
5758		return NULL;
5759	}
5760	vsize = strlen(vp);
5761	*statp = EX_OK;
5762	if (bitset(MF_MATCHONLY, map->map_mflags))
5763		return map_rewrite(map, name, strlen(name), NULL);
5764	else
5765		return map_rewrite(map, vp, vsize, av);
5766}
5767
5768/*
5769**  TEXT_GETCANONNAME -- look up canonical name in hosts file
5770*/
5771
5772static bool
5773text_getcanonname(name, hbsize, statp)
5774	char *name;
5775	int hbsize;
5776	int *statp;
5777{
5778	bool found;
5779	char *dot;
5780	SM_FILE_T *f;
5781	char linebuf[MAXLINE];
5782	char cbuf[MAXNAME + 1];
5783	char nbuf[MAXNAME + 1];
5784
5785	if (tTd(38, 20))
5786		sm_dprintf("text_getcanonname(%s)\n", name);
5787
5788	if (sm_strlcpy(nbuf, name, sizeof nbuf) >= sizeof nbuf)
5789	{
5790		*statp = EX_UNAVAILABLE;
5791		return false;
5792	}
5793	dot = shorten_hostname(nbuf);
5794
5795	f = sm_io_open(SmFtStdio, SM_TIME_DEFAULT, HostsFile, SM_IO_RDONLY,
5796		       NULL);
5797	if (f == NULL)
5798	{
5799		*statp = EX_UNAVAILABLE;
5800		return false;
5801	}
5802	found = false;
5803	while (!found &&
5804		sm_io_fgets(f, SM_TIME_DEFAULT, linebuf, MAXLINE) != NULL)
5805	{
5806		char *p = strpbrk(linebuf, "#\n");
5807
5808		if (p != NULL)
5809			*p = '\0';
5810		if (linebuf[0] != '\0')
5811			found = extract_canonname(nbuf, dot, linebuf,
5812						  cbuf, sizeof cbuf);
5813	}
5814	(void) sm_io_close(f, SM_TIME_DEFAULT);
5815	if (!found)
5816	{
5817		*statp = EX_NOHOST;
5818		return false;
5819	}
5820
5821	if (sm_strlcpy(name, cbuf, hbsize) >= hbsize)
5822	{
5823		*statp = EX_UNAVAILABLE;
5824		return false;
5825	}
5826	*statp = EX_OK;
5827	return true;
5828}
5829/*
5830**  STAB (Symbol Table) Modules
5831*/
5832
5833
5834/*
5835**  STAB_MAP_LOOKUP -- look up alias in symbol table
5836*/
5837
5838/* ARGSUSED2 */
5839char *
5840stab_map_lookup(map, name, av, pstat)
5841	register MAP *map;
5842	char *name;
5843	char **av;
5844	int *pstat;
5845{
5846	register STAB *s;
5847
5848	if (tTd(38, 20))
5849		sm_dprintf("stab_lookup(%s, %s)\n",
5850			map->map_mname, name);
5851
5852	s = stab(name, ST_ALIAS, ST_FIND);
5853	if (s != NULL)
5854		return s->s_alias;
5855	return NULL;
5856}
5857
5858
5859/*
5860**  STAB_MAP_STORE -- store in symtab (actually using during init, not rebuild)
5861*/
5862
5863void
5864stab_map_store(map, lhs, rhs)
5865	register MAP *map;
5866	char *lhs;
5867	char *rhs;
5868{
5869	register STAB *s;
5870
5871	s = stab(lhs, ST_ALIAS, ST_ENTER);
5872	s->s_alias = newstr(rhs);
5873}
5874
5875
5876/*
5877**  STAB_MAP_OPEN -- initialize (reads data file)
5878**
5879**	This is a wierd case -- it is only intended as a fallback for
5880**	aliases.  For this reason, opens for write (only during a
5881**	"newaliases") always fails, and opens for read open the
5882**	actual underlying text file instead of the database.
5883*/
5884
5885bool
5886stab_map_open(map, mode)
5887	register MAP *map;
5888	int mode;
5889{
5890	SM_FILE_T *af;
5891	long sff;
5892	struct stat st;
5893
5894	if (tTd(38, 2))
5895		sm_dprintf("stab_map_open(%s, %s, %d)\n",
5896			map->map_mname, map->map_file, mode);
5897
5898	mode &= O_ACCMODE;
5899	if (mode != O_RDONLY)
5900	{
5901		errno = EPERM;
5902		return false;
5903	}
5904
5905	sff = SFF_ROOTOK|SFF_REGONLY;
5906	if (!bitnset(DBS_LINKEDMAPINWRITABLEDIR, DontBlameSendmail))
5907		sff |= SFF_NOWLINK;
5908	if (!bitnset(DBS_MAPINUNSAFEDIRPATH, DontBlameSendmail))
5909		sff |= SFF_SAFEDIRPATH;
5910	af = safefopen(map->map_file, O_RDONLY, 0444, sff);
5911	if (af == NULL)
5912		return false;
5913	readaliases(map, af, false, false);
5914
5915	if (fstat(sm_io_getinfo(af, SM_IO_WHAT_FD, NULL), &st) >= 0)
5916		map->map_mtime = st.st_mtime;
5917	(void) sm_io_close(af, SM_TIME_DEFAULT);
5918
5919	return true;
5920}
5921/*
5922**  Implicit Modules
5923**
5924**	Tries several types.  For back compatibility of aliases.
5925*/
5926
5927
5928/*
5929**  IMPL_MAP_LOOKUP -- lookup in best open database
5930*/
5931
5932char *
5933impl_map_lookup(map, name, av, pstat)
5934	MAP *map;
5935	char *name;
5936	char **av;
5937	int *pstat;
5938{
5939	if (tTd(38, 20))
5940		sm_dprintf("impl_map_lookup(%s, %s)\n",
5941			map->map_mname, name);
5942
5943#if NEWDB
5944	if (bitset(MF_IMPL_HASH, map->map_mflags))
5945		return db_map_lookup(map, name, av, pstat);
5946#endif /* NEWDB */
5947#if NDBM
5948	if (bitset(MF_IMPL_NDBM, map->map_mflags))
5949		return ndbm_map_lookup(map, name, av, pstat);
5950#endif /* NDBM */
5951	return stab_map_lookup(map, name, av, pstat);
5952}
5953
5954/*
5955**  IMPL_MAP_STORE -- store in open databases
5956*/
5957
5958void
5959impl_map_store(map, lhs, rhs)
5960	MAP *map;
5961	char *lhs;
5962	char *rhs;
5963{
5964	if (tTd(38, 12))
5965		sm_dprintf("impl_map_store(%s, %s, %s)\n",
5966			map->map_mname, lhs, rhs);
5967#if NEWDB
5968	if (bitset(MF_IMPL_HASH, map->map_mflags))
5969		db_map_store(map, lhs, rhs);
5970#endif /* NEWDB */
5971#if NDBM
5972	if (bitset(MF_IMPL_NDBM, map->map_mflags))
5973		ndbm_map_store(map, lhs, rhs);
5974#endif /* NDBM */
5975	stab_map_store(map, lhs, rhs);
5976}
5977
5978/*
5979**  IMPL_MAP_OPEN -- implicit database open
5980*/
5981
5982bool
5983impl_map_open(map, mode)
5984	MAP *map;
5985	int mode;
5986{
5987	if (tTd(38, 2))
5988		sm_dprintf("impl_map_open(%s, %s, %d)\n",
5989			map->map_mname, map->map_file, mode);
5990
5991	mode &= O_ACCMODE;
5992#if NEWDB
5993	map->map_mflags |= MF_IMPL_HASH;
5994	if (hash_map_open(map, mode))
5995	{
5996# ifdef NDBM_YP_COMPAT
5997		if (mode == O_RDONLY || strstr(map->map_file, "/yp/") == NULL)
5998# endif /* NDBM_YP_COMPAT */
5999			return true;
6000	}
6001	else
6002		map->map_mflags &= ~MF_IMPL_HASH;
6003#endif /* NEWDB */
6004#if NDBM
6005	map->map_mflags |= MF_IMPL_NDBM;
6006	if (ndbm_map_open(map, mode))
6007	{
6008		return true;
6009	}
6010	else
6011		map->map_mflags &= ~MF_IMPL_NDBM;
6012#endif /* NDBM */
6013
6014#if defined(NEWDB) || defined(NDBM)
6015	if (Verbose)
6016		message("WARNING: cannot open alias database %s%s",
6017			map->map_file,
6018			mode == O_RDONLY ? "; reading text version" : "");
6019#else /* defined(NEWDB) || defined(NDBM) */
6020	if (mode != O_RDONLY)
6021		usrerr("Cannot rebuild aliases: no database format defined");
6022#endif /* defined(NEWDB) || defined(NDBM) */
6023
6024	if (mode == O_RDONLY)
6025		return stab_map_open(map, mode);
6026	else
6027		return false;
6028}
6029
6030
6031/*
6032**  IMPL_MAP_CLOSE -- close any open database(s)
6033*/
6034
6035void
6036impl_map_close(map)
6037	MAP *map;
6038{
6039	if (tTd(38, 9))
6040		sm_dprintf("impl_map_close(%s, %s, %lx)\n",
6041			map->map_mname, map->map_file, map->map_mflags);
6042#if NEWDB
6043	if (bitset(MF_IMPL_HASH, map->map_mflags))
6044	{
6045		db_map_close(map);
6046		map->map_mflags &= ~MF_IMPL_HASH;
6047	}
6048#endif /* NEWDB */
6049
6050#if NDBM
6051	if (bitset(MF_IMPL_NDBM, map->map_mflags))
6052	{
6053		ndbm_map_close(map);
6054		map->map_mflags &= ~MF_IMPL_NDBM;
6055	}
6056#endif /* NDBM */
6057}
6058/*
6059**  User map class.
6060**
6061**	Provides access to the system password file.
6062*/
6063
6064/*
6065**  USER_MAP_OPEN -- open user map
6066**
6067**	Really just binds field names to field numbers.
6068*/
6069
6070bool
6071user_map_open(map, mode)
6072	MAP *map;
6073	int mode;
6074{
6075	if (tTd(38, 2))
6076		sm_dprintf("user_map_open(%s, %d)\n",
6077			map->map_mname, mode);
6078
6079	mode &= O_ACCMODE;
6080	if (mode != O_RDONLY)
6081	{
6082		/* issue a pseudo-error message */
6083		errno = SM_EMAPCANTWRITE;
6084		return false;
6085	}
6086	if (map->map_valcolnm == NULL)
6087		/* EMPTY */
6088		/* nothing */ ;
6089	else if (sm_strcasecmp(map->map_valcolnm, "name") == 0)
6090		map->map_valcolno = 1;
6091	else if (sm_strcasecmp(map->map_valcolnm, "passwd") == 0)
6092		map->map_valcolno = 2;
6093	else if (sm_strcasecmp(map->map_valcolnm, "uid") == 0)
6094		map->map_valcolno = 3;
6095	else if (sm_strcasecmp(map->map_valcolnm, "gid") == 0)
6096		map->map_valcolno = 4;
6097	else if (sm_strcasecmp(map->map_valcolnm, "gecos") == 0)
6098		map->map_valcolno = 5;
6099	else if (sm_strcasecmp(map->map_valcolnm, "dir") == 0)
6100		map->map_valcolno = 6;
6101	else if (sm_strcasecmp(map->map_valcolnm, "shell") == 0)
6102		map->map_valcolno = 7;
6103	else
6104	{
6105		syserr("User map %s: unknown column name %s",
6106			map->map_mname, map->map_valcolnm);
6107		return false;
6108	}
6109	return true;
6110}
6111
6112
6113/*
6114**  USER_MAP_LOOKUP -- look up a user in the passwd file.
6115*/
6116
6117/* ARGSUSED3 */
6118char *
6119user_map_lookup(map, key, av, statp)
6120	MAP *map;
6121	char *key;
6122	char **av;
6123	int *statp;
6124{
6125	auto bool fuzzy;
6126	SM_MBDB_T user;
6127
6128	if (tTd(38, 20))
6129		sm_dprintf("user_map_lookup(%s, %s)\n",
6130			map->map_mname, key);
6131
6132	*statp = finduser(key, &fuzzy, &user);
6133	if (*statp != EX_OK)
6134		return NULL;
6135	if (bitset(MF_MATCHONLY, map->map_mflags))
6136		return map_rewrite(map, key, strlen(key), NULL);
6137	else
6138	{
6139		char *rwval = NULL;
6140		char buf[30];
6141
6142		switch (map->map_valcolno)
6143		{
6144		  case 0:
6145		  case 1:
6146			rwval = user.mbdb_name;
6147			break;
6148
6149		  case 2:
6150			rwval = "x";	/* passwd no longer supported */
6151			break;
6152
6153		  case 3:
6154			(void) sm_snprintf(buf, sizeof buf, "%d",
6155					   (int) user.mbdb_uid);
6156			rwval = buf;
6157			break;
6158
6159		  case 4:
6160			(void) sm_snprintf(buf, sizeof buf, "%d",
6161					   (int) user.mbdb_gid);
6162			rwval = buf;
6163			break;
6164
6165		  case 5:
6166			rwval = user.mbdb_fullname;
6167			break;
6168
6169		  case 6:
6170			rwval = user.mbdb_homedir;
6171			break;
6172
6173		  case 7:
6174			rwval = user.mbdb_shell;
6175			break;
6176		}
6177		return map_rewrite(map, rwval, strlen(rwval), av);
6178	}
6179}
6180/*
6181**  Program map type.
6182**
6183**	This provides access to arbitrary programs.  It should be used
6184**	only very sparingly, since there is no way to bound the cost
6185**	of invoking an arbitrary program.
6186*/
6187
6188char *
6189prog_map_lookup(map, name, av, statp)
6190	MAP *map;
6191	char *name;
6192	char **av;
6193	int *statp;
6194{
6195	int i;
6196	int save_errno;
6197	int fd;
6198	int status;
6199	auto pid_t pid;
6200	register char *p;
6201	char *rval;
6202	char *argv[MAXPV + 1];
6203	char buf[MAXLINE];
6204
6205	if (tTd(38, 20))
6206		sm_dprintf("prog_map_lookup(%s, %s) %s\n",
6207			map->map_mname, name, map->map_file);
6208
6209	i = 0;
6210	argv[i++] = map->map_file;
6211	if (map->map_rebuild != NULL)
6212	{
6213		(void) sm_strlcpy(buf, map->map_rebuild, sizeof buf);
6214		for (p = strtok(buf, " \t"); p != NULL; p = strtok(NULL, " \t"))
6215		{
6216			if (i >= MAXPV - 1)
6217				break;
6218			argv[i++] = p;
6219		}
6220	}
6221	argv[i++] = name;
6222	argv[i] = NULL;
6223	if (tTd(38, 21))
6224	{
6225		sm_dprintf("prog_open:");
6226		for (i = 0; argv[i] != NULL; i++)
6227			sm_dprintf(" %s", argv[i]);
6228		sm_dprintf("\n");
6229	}
6230	(void) sm_blocksignal(SIGCHLD);
6231	pid = prog_open(argv, &fd, CurEnv);
6232	if (pid < 0)
6233	{
6234		if (!bitset(MF_OPTIONAL, map->map_mflags))
6235			syserr("prog_map_lookup(%s) failed (%s) -- closing",
6236			       map->map_mname, sm_errstring(errno));
6237		else if (tTd(38, 9))
6238			sm_dprintf("prog_map_lookup(%s) failed (%s) -- closing",
6239				   map->map_mname, sm_errstring(errno));
6240		map->map_mflags &= ~(MF_VALID|MF_OPEN);
6241		*statp = EX_OSFILE;
6242		return NULL;
6243	}
6244	i = read(fd, buf, sizeof buf - 1);
6245	if (i < 0)
6246	{
6247		syserr("prog_map_lookup(%s): read error %s",
6248		       map->map_mname, sm_errstring(errno));
6249		rval = NULL;
6250	}
6251	else if (i == 0)
6252	{
6253		if (tTd(38, 20))
6254			sm_dprintf("prog_map_lookup(%s): empty answer\n",
6255				   map->map_mname);
6256		rval = NULL;
6257	}
6258	else
6259	{
6260		buf[i] = '\0';
6261		p = strchr(buf, '\n');
6262		if (p != NULL)
6263			*p = '\0';
6264
6265		/* collect the return value */
6266		if (bitset(MF_MATCHONLY, map->map_mflags))
6267			rval = map_rewrite(map, name, strlen(name), NULL);
6268		else
6269			rval = map_rewrite(map, buf, strlen(buf), av);
6270
6271		/* now flush any additional output */
6272		while ((i = read(fd, buf, sizeof buf)) > 0)
6273			continue;
6274	}
6275
6276	/* wait for the process to terminate */
6277	(void) close(fd);
6278	status = waitfor(pid);
6279	save_errno = errno;
6280	(void) sm_releasesignal(SIGCHLD);
6281	errno = save_errno;
6282
6283	if (status == -1)
6284	{
6285		syserr("prog_map_lookup(%s): wait error %s",
6286		       map->map_mname, sm_errstring(errno));
6287		*statp = EX_SOFTWARE;
6288		rval = NULL;
6289	}
6290	else if (WIFEXITED(status))
6291	{
6292		if ((*statp = WEXITSTATUS(status)) != EX_OK)
6293			rval = NULL;
6294	}
6295	else
6296	{
6297		syserr("prog_map_lookup(%s): child died on signal %d",
6298		       map->map_mname, status);
6299		*statp = EX_UNAVAILABLE;
6300		rval = NULL;
6301	}
6302	return rval;
6303}
6304/*
6305**  Sequenced map type.
6306**
6307**	Tries each map in order until something matches, much like
6308**	implicit.  Stores go to the first map in the list that can
6309**	support storing.
6310**
6311**	This is slightly unusual in that there are two interfaces.
6312**	The "sequence" interface lets you stack maps arbitrarily.
6313**	The "switch" interface builds a sequence map by looking
6314**	at a system-dependent configuration file such as
6315**	/etc/nsswitch.conf on Solaris or /etc/svc.conf on Ultrix.
6316**
6317**	We don't need an explicit open, since all maps are
6318**	opened on demand.
6319*/
6320
6321/*
6322**  SEQ_MAP_PARSE -- Sequenced map parsing
6323*/
6324
6325bool
6326seq_map_parse(map, ap)
6327	MAP *map;
6328	char *ap;
6329{
6330	int maxmap;
6331
6332	if (tTd(38, 2))
6333		sm_dprintf("seq_map_parse(%s, %s)\n", map->map_mname, ap);
6334	maxmap = 0;
6335	while (*ap != '\0')
6336	{
6337		register char *p;
6338		STAB *s;
6339
6340		/* find beginning of map name */
6341		while (isascii(*ap) && isspace(*ap))
6342			ap++;
6343		for (p = ap;
6344		     (isascii(*p) && isalnum(*p)) || *p == '_' || *p == '.';
6345		     p++)
6346			continue;
6347		if (*p != '\0')
6348			*p++ = '\0';
6349		while (*p != '\0' && (!isascii(*p) || !isalnum(*p)))
6350			p++;
6351		if (*ap == '\0')
6352		{
6353			ap = p;
6354			continue;
6355		}
6356		s = stab(ap, ST_MAP, ST_FIND);
6357		if (s == NULL)
6358		{
6359			syserr("Sequence map %s: unknown member map %s",
6360				map->map_mname, ap);
6361		}
6362		else if (maxmap >= MAXMAPSTACK)
6363		{
6364			syserr("Sequence map %s: too many member maps (%d max)",
6365				map->map_mname, MAXMAPSTACK);
6366			maxmap++;
6367		}
6368		else if (maxmap < MAXMAPSTACK)
6369		{
6370			map->map_stack[maxmap++] = &s->s_map;
6371		}
6372		ap = p;
6373	}
6374	return true;
6375}
6376
6377/*
6378**  SWITCH_MAP_OPEN -- open a switched map
6379**
6380**	This looks at the system-dependent configuration and builds
6381**	a sequence map that does the same thing.
6382**
6383**	Every system must define a switch_map_find routine in conf.c
6384**	that will return the list of service types associated with a
6385**	given service class.
6386*/
6387
6388bool
6389switch_map_open(map, mode)
6390	MAP *map;
6391	int mode;
6392{
6393	int mapno;
6394	int nmaps;
6395	char *maptype[MAXMAPSTACK];
6396
6397	if (tTd(38, 2))
6398		sm_dprintf("switch_map_open(%s, %s, %d)\n",
6399			map->map_mname, map->map_file, mode);
6400
6401	mode &= O_ACCMODE;
6402	nmaps = switch_map_find(map->map_file, maptype, map->map_return);
6403	if (tTd(38, 19))
6404	{
6405		sm_dprintf("\tswitch_map_find => %d\n", nmaps);
6406		for (mapno = 0; mapno < nmaps; mapno++)
6407			sm_dprintf("\t\t%s\n", maptype[mapno]);
6408	}
6409	if (nmaps <= 0 || nmaps > MAXMAPSTACK)
6410		return false;
6411
6412	for (mapno = 0; mapno < nmaps; mapno++)
6413	{
6414		register STAB *s;
6415		char nbuf[MAXNAME + 1];
6416
6417		if (maptype[mapno] == NULL)
6418			continue;
6419		(void) sm_strlcpyn(nbuf, sizeof nbuf, 3,
6420				   map->map_mname, ".", maptype[mapno]);
6421		s = stab(nbuf, ST_MAP, ST_FIND);
6422		if (s == NULL)
6423		{
6424			syserr("Switch map %s: unknown member map %s",
6425				map->map_mname, nbuf);
6426		}
6427		else
6428		{
6429			map->map_stack[mapno] = &s->s_map;
6430			if (tTd(38, 4))
6431				sm_dprintf("\tmap_stack[%d] = %s:%s\n",
6432					   mapno,
6433					   s->s_map.map_class->map_cname,
6434					   nbuf);
6435		}
6436	}
6437	return true;
6438}
6439
6440#if 0
6441/*
6442**  SEQ_MAP_CLOSE -- close all underlying maps
6443*/
6444
6445void
6446seq_map_close(map)
6447	MAP *map;
6448{
6449	int mapno;
6450
6451	if (tTd(38, 9))
6452		sm_dprintf("seq_map_close(%s)\n", map->map_mname);
6453
6454	for (mapno = 0; mapno < MAXMAPSTACK; mapno++)
6455	{
6456		MAP *mm = map->map_stack[mapno];
6457
6458		if (mm == NULL || !bitset(MF_OPEN, mm->map_mflags))
6459			continue;
6460		mm->map_mflags |= MF_CLOSING;
6461		mm->map_class->map_close(mm);
6462		mm->map_mflags &= ~(MF_OPEN|MF_WRITABLE|MF_CLOSING);
6463	}
6464}
6465#endif /* 0 */
6466
6467/*
6468**  SEQ_MAP_LOOKUP -- sequenced map lookup
6469*/
6470
6471char *
6472seq_map_lookup(map, key, args, pstat)
6473	MAP *map;
6474	char *key;
6475	char **args;
6476	int *pstat;
6477{
6478	int mapno;
6479	int mapbit = 0x01;
6480	bool tempfail = false;
6481
6482	if (tTd(38, 20))
6483		sm_dprintf("seq_map_lookup(%s, %s)\n", map->map_mname, key);
6484
6485	for (mapno = 0; mapno < MAXMAPSTACK; mapbit <<= 1, mapno++)
6486	{
6487		MAP *mm = map->map_stack[mapno];
6488		char *rv;
6489
6490		if (mm == NULL)
6491			continue;
6492		if (!bitset(MF_OPEN, mm->map_mflags) &&
6493		    !openmap(mm))
6494		{
6495			if (bitset(mapbit, map->map_return[MA_UNAVAIL]))
6496			{
6497				*pstat = EX_UNAVAILABLE;
6498				return NULL;
6499			}
6500			continue;
6501		}
6502		*pstat = EX_OK;
6503		rv = mm->map_class->map_lookup(mm, key, args, pstat);
6504		if (rv != NULL)
6505			return rv;
6506		if (*pstat == EX_TEMPFAIL)
6507		{
6508			if (bitset(mapbit, map->map_return[MA_TRYAGAIN]))
6509				return NULL;
6510			tempfail = true;
6511		}
6512		else if (bitset(mapbit, map->map_return[MA_NOTFOUND]))
6513			break;
6514	}
6515	if (tempfail)
6516		*pstat = EX_TEMPFAIL;
6517	else if (*pstat == EX_OK)
6518		*pstat = EX_NOTFOUND;
6519	return NULL;
6520}
6521
6522/*
6523**  SEQ_MAP_STORE -- sequenced map store
6524*/
6525
6526void
6527seq_map_store(map, key, val)
6528	MAP *map;
6529	char *key;
6530	char *val;
6531{
6532	int mapno;
6533
6534	if (tTd(38, 12))
6535		sm_dprintf("seq_map_store(%s, %s, %s)\n",
6536			map->map_mname, key, val);
6537
6538	for (mapno = 0; mapno < MAXMAPSTACK; mapno++)
6539	{
6540		MAP *mm = map->map_stack[mapno];
6541
6542		if (mm == NULL || !bitset(MF_WRITABLE, mm->map_mflags))
6543			continue;
6544
6545		mm->map_class->map_store(mm, key, val);
6546		return;
6547	}
6548	syserr("seq_map_store(%s, %s, %s): no writable map",
6549		map->map_mname, key, val);
6550}
6551/*
6552**  NULL stubs
6553*/
6554
6555/* ARGSUSED */
6556bool
6557null_map_open(map, mode)
6558	MAP *map;
6559	int mode;
6560{
6561	return true;
6562}
6563
6564/* ARGSUSED */
6565void
6566null_map_close(map)
6567	MAP *map;
6568{
6569	return;
6570}
6571
6572char *
6573null_map_lookup(map, key, args, pstat)
6574	MAP *map;
6575	char *key;
6576	char **args;
6577	int *pstat;
6578{
6579	*pstat = EX_NOTFOUND;
6580	return NULL;
6581}
6582
6583/* ARGSUSED */
6584void
6585null_map_store(map, key, val)
6586	MAP *map;
6587	char *key;
6588	char *val;
6589{
6590	return;
6591}
6592
6593/*
6594**  BOGUS stubs
6595*/
6596
6597char *
6598bogus_map_lookup(map, key, args, pstat)
6599	MAP *map;
6600	char *key;
6601	char **args;
6602	int *pstat;
6603{
6604	*pstat = EX_TEMPFAIL;
6605	return NULL;
6606}
6607
6608MAPCLASS	BogusMapClass =
6609{
6610	"bogus-map",		NULL,			0,
6611	NULL,			bogus_map_lookup,	null_map_store,
6612	null_map_open,		null_map_close,
6613};
6614/*
6615**  MACRO modules
6616*/
6617
6618char *
6619macro_map_lookup(map, name, av, statp)
6620	MAP *map;
6621	char *name;
6622	char **av;
6623	int *statp;
6624{
6625	int mid;
6626
6627	if (tTd(38, 20))
6628		sm_dprintf("macro_map_lookup(%s, %s)\n", map->map_mname,
6629			name == NULL ? "NULL" : name);
6630
6631	if (name == NULL ||
6632	    *name == '\0' ||
6633	    (mid = macid(name)) == 0)
6634	{
6635		*statp = EX_CONFIG;
6636		return NULL;
6637	}
6638
6639	if (av[1] == NULL)
6640		macdefine(&CurEnv->e_macro, A_PERM, mid, NULL);
6641	else
6642		macdefine(&CurEnv->e_macro, A_TEMP, mid, av[1]);
6643
6644	*statp = EX_OK;
6645	return "";
6646}
6647/*
6648**  REGEX modules
6649*/
6650
6651#if MAP_REGEX
6652
6653# include <regex.h>
6654
6655# define DEFAULT_DELIM	CONDELSE
6656# define END_OF_FIELDS	-1
6657# define ERRBUF_SIZE	80
6658# define MAX_MATCH	32
6659
6660# define xnalloc(s)	memset(xalloc(s), '\0', s);
6661
6662struct regex_map
6663{
6664	regex_t	*regex_pattern_buf;	/* xalloc it */
6665	int	*regex_subfields;	/* move to type MAP */
6666	char	*regex_delim;		/* move to type MAP */
6667};
6668
6669static int
6670parse_fields(s, ibuf, blen, nr_substrings)
6671	char *s;
6672	int *ibuf;		/* array */
6673	int blen;		/* number of elements in ibuf */
6674	int nr_substrings;	/* number of substrings in the pattern */
6675{
6676	register char *cp;
6677	int i = 0;
6678	bool lastone = false;
6679
6680	blen--;		/* for terminating END_OF_FIELDS */
6681	cp = s;
6682	do
6683	{
6684		for (;; cp++)
6685		{
6686			if (*cp == ',')
6687			{
6688				*cp = '\0';
6689				break;
6690			}
6691			if (*cp == '\0')
6692			{
6693				lastone = true;
6694				break;
6695			}
6696		}
6697		if (i < blen)
6698		{
6699			int val = atoi(s);
6700
6701			if (val < 0 || val >= nr_substrings)
6702			{
6703				syserr("field (%d) out of range, only %d substrings in pattern",
6704				       val, nr_substrings);
6705				return -1;
6706			}
6707			ibuf[i++] = val;
6708		}
6709		else
6710		{
6711			syserr("too many fields, %d max", blen);
6712			return -1;
6713		}
6714		s = ++cp;
6715	} while (!lastone);
6716	ibuf[i] = END_OF_FIELDS;
6717	return i;
6718}
6719
6720bool
6721regex_map_init(map, ap)
6722	MAP *map;
6723	char *ap;
6724{
6725	int regerr;
6726	struct regex_map *map_p;
6727	register char *p;
6728	char *sub_param = NULL;
6729	int pflags;
6730	static char defdstr[] = { (char) DEFAULT_DELIM, '\0' };
6731
6732	if (tTd(38, 2))
6733		sm_dprintf("regex_map_init: mapname '%s', args '%s'\n",
6734			map->map_mname, ap);
6735
6736	pflags = REG_ICASE | REG_EXTENDED | REG_NOSUB;
6737	p = ap;
6738	map_p = (struct regex_map *) xnalloc(sizeof *map_p);
6739	map_p->regex_pattern_buf = (regex_t *)xnalloc(sizeof(regex_t));
6740
6741	for (;;)
6742	{
6743		while (isascii(*p) && isspace(*p))
6744			p++;
6745		if (*p != '-')
6746			break;
6747		switch (*++p)
6748		{
6749		  case 'n':	/* not */
6750			map->map_mflags |= MF_REGEX_NOT;
6751			break;
6752
6753		  case 'f':	/* case sensitive */
6754			map->map_mflags |= MF_NOFOLDCASE;
6755			pflags &= ~REG_ICASE;
6756			break;
6757
6758		  case 'b':	/* basic regular expressions */
6759			pflags &= ~REG_EXTENDED;
6760			break;
6761
6762		  case 's':	/* substring match () syntax */
6763			sub_param = ++p;
6764			pflags &= ~REG_NOSUB;
6765			break;
6766
6767		  case 'd':	/* delimiter */
6768			map_p->regex_delim = ++p;
6769			break;
6770
6771		  case 'a':	/* map append */
6772			map->map_app = ++p;
6773			break;
6774
6775		  case 'm':	/* matchonly */
6776			map->map_mflags |= MF_MATCHONLY;
6777			break;
6778
6779		  case 'S':
6780			map->map_spacesub = *++p;
6781			break;
6782
6783		  case 'D':
6784			map->map_mflags |= MF_DEFER;
6785			break;
6786
6787		}
6788		while (*p != '\0' && !(isascii(*p) && isspace(*p)))
6789			p++;
6790		if (*p != '\0')
6791			*p++ = '\0';
6792	}
6793	if (tTd(38, 3))
6794		sm_dprintf("regex_map_init: compile '%s' 0x%x\n", p, pflags);
6795
6796	if ((regerr = regcomp(map_p->regex_pattern_buf, p, pflags)) != 0)
6797	{
6798		/* Errorhandling */
6799		char errbuf[ERRBUF_SIZE];
6800
6801		(void) regerror(regerr, map_p->regex_pattern_buf,
6802			 errbuf, sizeof errbuf);
6803		syserr("pattern-compile-error: %s", errbuf);
6804		sm_free(map_p->regex_pattern_buf); /* XXX */
6805		sm_free(map_p); /* XXX */
6806		return false;
6807	}
6808
6809	if (map->map_app != NULL)
6810		map->map_app = newstr(map->map_app);
6811	if (map_p->regex_delim != NULL)
6812		map_p->regex_delim = newstr(map_p->regex_delim);
6813	else
6814		map_p->regex_delim = defdstr;
6815
6816	if (!bitset(REG_NOSUB, pflags))
6817	{
6818		/* substring matching */
6819		int substrings;
6820		int *fields = (int *) xalloc(sizeof(int) * (MAX_MATCH + 1));
6821
6822		substrings = map_p->regex_pattern_buf->re_nsub + 1;
6823
6824		if (tTd(38, 3))
6825			sm_dprintf("regex_map_init: nr of substrings %d\n",
6826				substrings);
6827
6828		if (substrings >= MAX_MATCH)
6829		{
6830			syserr("too many substrings, %d max", MAX_MATCH);
6831			sm_free(map_p->regex_pattern_buf); /* XXX */
6832			sm_free(map_p); /* XXX */
6833			return false;
6834		}
6835		if (sub_param != NULL && sub_param[0] != '\0')
6836		{
6837			/* optional parameter -sfields */
6838			if (parse_fields(sub_param, fields,
6839					 MAX_MATCH + 1, substrings) == -1)
6840				return false;
6841		}
6842		else
6843		{
6844			int i;
6845
6846			/* set default fields */
6847			for (i = 0; i < substrings; i++)
6848				fields[i] = i;
6849			fields[i] = END_OF_FIELDS;
6850		}
6851		map_p->regex_subfields = fields;
6852		if (tTd(38, 3))
6853		{
6854			int *ip;
6855
6856			sm_dprintf("regex_map_init: subfields");
6857			for (ip = fields; *ip != END_OF_FIELDS; ip++)
6858				sm_dprintf(" %d", *ip);
6859			sm_dprintf("\n");
6860		}
6861	}
6862	map->map_db1 = (ARBPTR_T) map_p;	/* dirty hack */
6863	return true;
6864}
6865
6866static char *
6867regex_map_rewrite(map, s, slen, av)
6868	MAP *map;
6869	const char *s;
6870	size_t slen;
6871	char **av;
6872{
6873	if (bitset(MF_MATCHONLY, map->map_mflags))
6874		return map_rewrite(map, av[0], strlen(av[0]), NULL);
6875	else
6876		return map_rewrite(map, s, slen, av);
6877}
6878
6879char *
6880regex_map_lookup(map, name, av, statp)
6881	MAP *map;
6882	char *name;
6883	char **av;
6884	int *statp;
6885{
6886	int reg_res;
6887	struct regex_map *map_p;
6888	regmatch_t pmatch[MAX_MATCH];
6889
6890	if (tTd(38, 20))
6891	{
6892		char **cpp;
6893
6894		sm_dprintf("regex_map_lookup: key '%s'\n", name);
6895		for (cpp = av; cpp != NULL && *cpp != NULL; cpp++)
6896			sm_dprintf("regex_map_lookup: arg '%s'\n", *cpp);
6897	}
6898
6899	map_p = (struct regex_map *)(map->map_db1);
6900	reg_res = regexec(map_p->regex_pattern_buf,
6901			  name, MAX_MATCH, pmatch, 0);
6902
6903	if (bitset(MF_REGEX_NOT, map->map_mflags))
6904	{
6905		/* option -n */
6906		if (reg_res == REG_NOMATCH)
6907			return regex_map_rewrite(map, "", (size_t) 0, av);
6908		else
6909			return NULL;
6910	}
6911	if (reg_res == REG_NOMATCH)
6912		return NULL;
6913
6914	if (map_p->regex_subfields != NULL)
6915	{
6916		/* option -s */
6917		static char retbuf[MAXNAME];
6918		int fields[MAX_MATCH + 1];
6919		bool first = true;
6920		int anglecnt = 0, cmntcnt = 0, spacecnt = 0;
6921		bool quotemode = false, bslashmode = false;
6922		register char *dp, *sp;
6923		char *endp, *ldp;
6924		int *ip;
6925
6926		dp = retbuf;
6927		ldp = retbuf + sizeof(retbuf) - 1;
6928
6929		if (av[1] != NULL)
6930		{
6931			if (parse_fields(av[1], fields, MAX_MATCH + 1,
6932					 (int) map_p->regex_pattern_buf->re_nsub + 1) == -1)
6933			{
6934				*statp = EX_CONFIG;
6935				return NULL;
6936			}
6937			ip = fields;
6938		}
6939		else
6940			ip = map_p->regex_subfields;
6941
6942		for ( ; *ip != END_OF_FIELDS; ip++)
6943		{
6944			if (!first)
6945			{
6946				for (sp = map_p->regex_delim; *sp; sp++)
6947				{
6948					if (dp < ldp)
6949						*dp++ = *sp;
6950				}
6951			}
6952			else
6953				first = false;
6954
6955			if (*ip >= MAX_MATCH ||
6956			    pmatch[*ip].rm_so < 0 || pmatch[*ip].rm_eo < 0)
6957				continue;
6958
6959			sp = name + pmatch[*ip].rm_so;
6960			endp = name + pmatch[*ip].rm_eo;
6961			for (; endp > sp; sp++)
6962			{
6963				if (dp < ldp)
6964				{
6965					if (bslashmode)
6966					{
6967						*dp++ = *sp;
6968						bslashmode = false;
6969					}
6970					else if (quotemode && *sp != '"' &&
6971						*sp != '\\')
6972					{
6973						*dp++ = *sp;
6974					}
6975					else switch (*dp++ = *sp)
6976					{
6977					  case '\\':
6978						bslashmode = true;
6979						break;
6980
6981					  case '(':
6982						cmntcnt++;
6983						break;
6984
6985					  case ')':
6986						cmntcnt--;
6987						break;
6988
6989					  case '<':
6990						anglecnt++;
6991						break;
6992
6993					  case '>':
6994						anglecnt--;
6995						break;
6996
6997					  case ' ':
6998						spacecnt++;
6999						break;
7000
7001					  case '"':
7002						quotemode = !quotemode;
7003						break;
7004					}
7005				}
7006			}
7007		}
7008		if (anglecnt != 0 || cmntcnt != 0 || quotemode ||
7009		    bslashmode || spacecnt != 0)
7010		{
7011			sm_syslog(LOG_WARNING, NOQID,
7012				  "Warning: regex may cause prescan() failure map=%s lookup=%s",
7013				  map->map_mname, name);
7014			return NULL;
7015		}
7016
7017		*dp = '\0';
7018
7019		return regex_map_rewrite(map, retbuf, strlen(retbuf), av);
7020	}
7021	return regex_map_rewrite(map, "", (size_t)0, av);
7022}
7023#endif /* MAP_REGEX */
7024/*
7025**  NSD modules
7026*/
7027#if MAP_NSD
7028
7029# include <ndbm.h>
7030# define _DATUM_DEFINED
7031# include <ns_api.h>
7032
7033typedef struct ns_map_list
7034{
7035	ns_map_t		*map;		/* XXX ns_ ? */
7036	char			*mapname;
7037	struct ns_map_list	*next;
7038} ns_map_list_t;
7039
7040static ns_map_t *
7041ns_map_t_find(mapname)
7042	char *mapname;
7043{
7044	static ns_map_list_t *ns_maps = NULL;
7045	ns_map_list_t *ns_map;
7046
7047	/* walk the list of maps looking for the correctly named map */
7048	for (ns_map = ns_maps; ns_map != NULL; ns_map = ns_map->next)
7049	{
7050		if (strcmp(ns_map->mapname, mapname) == 0)
7051			break;
7052	}
7053
7054	/* if we are looking at a NULL ns_map_list_t, then create a new one */
7055	if (ns_map == NULL)
7056	{
7057		ns_map = (ns_map_list_t *) xalloc(sizeof *ns_map);
7058		ns_map->mapname = newstr(mapname);
7059		ns_map->map = (ns_map_t *) xalloc(sizeof *ns_map->map);
7060		ns_map->next = ns_maps;
7061		ns_maps = ns_map;
7062	}
7063	return ns_map->map;
7064}
7065
7066char *
7067nsd_map_lookup(map, name, av, statp)
7068	MAP *map;
7069	char *name;
7070	char **av;
7071	int *statp;
7072{
7073	int buflen, r;
7074	char *p;
7075	ns_map_t *ns_map;
7076	char keybuf[MAXNAME + 1];
7077	char buf[MAXLINE];
7078
7079	if (tTd(38, 20))
7080		sm_dprintf("nsd_map_lookup(%s, %s)\n", map->map_mname, name);
7081
7082	buflen = strlen(name);
7083	if (buflen > sizeof keybuf - 1)
7084		buflen = sizeof keybuf - 1;	/* XXX simply cut off? */
7085	memmove(keybuf, name, buflen);
7086	keybuf[buflen] = '\0';
7087	if (!bitset(MF_NOFOLDCASE, map->map_mflags))
7088		makelower(keybuf);
7089
7090	ns_map = ns_map_t_find(map->map_file);
7091	if (ns_map == NULL)
7092	{
7093		if (tTd(38, 20))
7094			sm_dprintf("nsd_map_t_find failed\n");
7095		*statp = EX_UNAVAILABLE;
7096		return NULL;
7097	}
7098	r = ns_lookup(ns_map, NULL, map->map_file, keybuf, NULL, buf, MAXLINE);
7099	if (r == NS_UNAVAIL || r == NS_TRYAGAIN)
7100	{
7101		*statp = EX_TEMPFAIL;
7102		return NULL;
7103	}
7104	if (r == NS_BADREQ
7105# ifdef NS_NOPERM
7106	    || r == NS_NOPERM
7107# endif /* NS_NOPERM */
7108	    )
7109	{
7110		*statp = EX_CONFIG;
7111		return NULL;
7112	}
7113	if (r != NS_SUCCESS)
7114	{
7115		*statp = EX_NOTFOUND;
7116		return NULL;
7117	}
7118
7119	*statp = EX_OK;
7120
7121	/* Null out trailing \n */
7122	if ((p = strchr(buf, '\n')) != NULL)
7123		*p = '\0';
7124
7125	return map_rewrite(map, buf, strlen(buf), av);
7126}
7127#endif /* MAP_NSD */
7128
7129char *
7130arith_map_lookup(map, name, av, statp)
7131	MAP *map;
7132	char *name;
7133	char **av;
7134	int *statp;
7135{
7136	long r;
7137	long v[2];
7138	bool res = false;
7139	bool boolres;
7140	static char result[16];
7141	char **cpp;
7142
7143	if (tTd(38, 2))
7144	{
7145		sm_dprintf("arith_map_lookup: key '%s'\n", name);
7146		for (cpp = av; cpp != NULL && *cpp != NULL; cpp++)
7147			sm_dprintf("arith_map_lookup: arg '%s'\n", *cpp);
7148	}
7149	r = 0;
7150	boolres = false;
7151	cpp = av;
7152	*statp = EX_OK;
7153
7154	/*
7155	**  read arguments for arith map
7156	**  - no check is made whether they are really numbers
7157	**  - just ignores args after the second
7158	*/
7159
7160	for (++cpp; cpp != NULL && *cpp != NULL && r < 2; cpp++)
7161		v[r++] = strtol(*cpp, NULL, 0);
7162
7163	/* operator and (at least) two operands given? */
7164	if (name != NULL && r == 2)
7165	{
7166		switch (*name)
7167		{
7168		  case '|':
7169			r = v[0] | v[1];
7170			break;
7171
7172		  case '&':
7173			r = v[0] & v[1];
7174			break;
7175
7176		  case '%':
7177			if (v[1] == 0)
7178				return NULL;
7179			r = v[0] % v[1];
7180			break;
7181		  case '+':
7182			r = v[0] + v[1];
7183			break;
7184
7185		  case '-':
7186			r = v[0] - v[1];
7187			break;
7188
7189		  case '*':
7190			r = v[0] * v[1];
7191			break;
7192
7193		  case '/':
7194			if (v[1] == 0)
7195				return NULL;
7196			r = v[0] / v[1];
7197			break;
7198
7199		  case 'l':
7200			res = v[0] < v[1];
7201			boolres = true;
7202			break;
7203
7204		  case '=':
7205			res = v[0] == v[1];
7206			boolres = true;
7207			break;
7208
7209		  default:
7210			/* XXX */
7211			*statp = EX_CONFIG;
7212			if (LogLevel > 10)
7213				sm_syslog(LOG_WARNING, NOQID,
7214					  "arith_map: unknown operator %c",
7215					  isprint(*name) ? *name : '?');
7216			return NULL;
7217		}
7218		if (boolres)
7219			(void) sm_snprintf(result, sizeof result,
7220				res ? "TRUE" : "FALSE");
7221		else
7222			(void) sm_snprintf(result, sizeof result, "%ld", r);
7223		return result;
7224	}
7225	*statp = EX_CONFIG;
7226	return NULL;
7227}
7228