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