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