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