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