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