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