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