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