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