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