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