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