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