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