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