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