xref: /freebsd/contrib/sendmail/src/map.c (revision 77a0943ded95b9e6438f7db70c4a28e4d93946d4)
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.24 2000/09/27 04:11:29 gshapiro Exp $";
16 #endif /* ! lint */
17 
18 #include <sendmail.h>
19 
20 
21 #ifdef NDBM
22 # include <ndbm.h>
23 # ifdef R_FIRST
24   ERROR README:	You are running the Berkeley DB version of ndbm.h.  See
25   ERROR README:	the README file about tweaking Berkeley DB so it can
26   ERROR README:	coexist with NDBM, or delete -DNDBM from the Makefile
27   ERROR README: and use -DNEWDB instead.
28 # endif /* R_FIRST */
29 #endif /* NDBM */
30 #ifdef NEWDB
31 # include <db.h>
32 # ifndef DB_VERSION_MAJOR
33 #  define DB_VERSION_MAJOR 1
34 # endif /* ! DB_VERSION_MAJOR */
35 #endif /* NEWDB */
36 #ifdef NIS
37   struct dom_binding;	/* forward reference needed on IRIX */
38 # include <rpcsvc/ypclnt.h>
39 # ifdef NDBM
40 #  define NDBM_YP_COMPAT	/* create YP-compatible NDBM files */
41 # endif /* NDBM */
42 #endif /* NIS */
43 
44 #ifdef NEWDB
45 # if DB_VERSION_MAJOR < 2
46 static bool	db_map_open __P((MAP *, int, char *, DBTYPE, const void *));
47 # endif /* DB_VERSION_MAJOR < 2 */
48 # if DB_VERSION_MAJOR == 2
49 static bool	db_map_open __P((MAP *, int, char *, DBTYPE, DB_INFO *));
50 # endif /* DB_VERSION_MAJOR == 2 */
51 # if DB_VERSION_MAJOR > 2
52 static bool	db_map_open __P((MAP *, int, char *, DBTYPE, void **));
53 # endif /* DB_VERSION_MAJOR > 2 */
54 #endif /* NEWDB */
55 static bool	extract_canonname __P((char *, char *, char[], 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 # else /* USE_LDAP_INIT */
2899 	/*
2900 	**  If using ldap_open(), the actual connection to the server
2901 	**  happens now so we need the timeout here.  For ldap_init(),
2902 	**  the connection happens at bind time.
2903 	*/
2904 
2905 	/* set the timeout */
2906 	if (lmap->ldap_timeout.tv_sec != 0)
2907 	{
2908 		if (setjmp(LDAPTimeout) != 0)
2909 		{
2910 			if (LogLevel > 1)
2911 				sm_syslog(LOG_NOTICE, CurEnv->e_id,
2912 					  "timeout conning to LDAP server %.100s",
2913 					  lmap->ldap_host == NULL ? "localhost" : lmap->ldap_host);
2914 			return FALSE;
2915 		}
2916 		ev = setevent(lmap->ldap_timeout.tv_sec, ldaptimeout, 0);
2917 	}
2918 
2919 	ld = ldap_open(lmap->ldap_host, lmap->ldap_port);
2920 	save_errno = errno;
2921 
2922 	/* clear the event if it has not sprung */
2923 	if (ev != NULL)
2924 		clrevent(ev);
2925 # endif /* USE_LDAP_INIT */
2926 
2927 	errno = save_errno;
2928 	if (ld == NULL)
2929 	{
2930 		if (!bitset(MF_OPTIONAL, map->map_mflags))
2931 		{
2932 			if (bitset(MF_NODEFER, map->map_mflags))
2933 				syserr("%s failed to %s in map %s",
2934 # if USE_LDAP_INIT
2935 				       "ldap_init",
2936 # else /* USE_LDAP_INIT */
2937 				       "ldap_open",
2938 # endif /* USE_LDAP_INIT */
2939 				       lmap->ldap_host == NULL ? "localhost"
2940 							       : lmap->ldap_host,
2941 				       map->map_mname);
2942 			else
2943 				syserr("421 4.0.0 %s failed to %s in map %s",
2944 # if USE_LDAP_INIT
2945 				       "ldap_init",
2946 # else /* USE_LDAP_INIT */
2947 				       "ldap_open",
2948 # endif /* USE_LDAP_INIT */
2949 				       lmap->ldap_host == NULL ? "localhost"
2950 							       : lmap->ldap_host,
2951 				       map->map_mname);
2952 		}
2953 		return FALSE;
2954 	}
2955 
2956 	ldapmap_setopts(ld, lmap);
2957 
2958 # if USE_LDAP_INIT
2959 	/*
2960 	**  If using ldap_init(), the actual connection to the server
2961 	**  happens at ldap_bind_s() so we need the timeout here.
2962 	*/
2963 
2964 	/* set the timeout */
2965 	if (lmap->ldap_timeout.tv_sec != 0)
2966 	{
2967 		if (setjmp(LDAPTimeout) != 0)
2968 		{
2969 			if (LogLevel > 1)
2970 				sm_syslog(LOG_NOTICE, CurEnv->e_id,
2971 					  "timeout conning to LDAP server %.100s",
2972 					  lmap->ldap_host == NULL ? "localhost"
2973 								  : lmap->ldap_host);
2974 			return FALSE;
2975 		}
2976 		ev = setevent(lmap->ldap_timeout.tv_sec, ldaptimeout, 0);
2977 	}
2978 # endif /* USE_LDAP_INIT */
2979 
2980 # ifdef LDAP_AUTH_KRBV4
2981 	if (lmap->ldap_method == LDAP_AUTH_KRBV4 &&
2982 	    lmap->ldap_secret != NULL)
2983 	{
2984 		/*
2985 		**  Need to put ticket in environment here instead of
2986 		**  during parseargs as there may be different tickets
2987 		**  for different LDAP connections.
2988 		*/
2989 
2990 		(void) putenv(lmap->ldap_secret);
2991 	}
2992 # endif /* LDAP_AUTH_KRBV4 */
2993 
2994 	bind_result = ldap_bind_s(ld, lmap->ldap_binddn,
2995 				  lmap->ldap_secret, lmap->ldap_method);
2996 
2997 # if USE_LDAP_INIT
2998 	/* clear the event if it has not sprung */
2999 	if (ev != NULL)
3000 		clrevent(ev);
3001 # endif /* USE_LDAP_INIT */
3002 
3003 	if (bind_result != LDAP_SUCCESS)
3004 	{
3005 		errno = bind_result + E_LDAPBASE;
3006 		if (!bitset(MF_OPTIONAL, map->map_mflags))
3007 		{
3008 			syserr("421 4.0.0 Cannot bind to map %s in ldap server %s",
3009 			       map->map_mname,
3010 			       lmap->ldap_host == NULL ? "localhost" : lmap->ldap_host);
3011 		}
3012 		return FALSE;
3013 	}
3014 
3015 	/* We need to cast ld into the map structure */
3016 	lmap->ldap_ld = ld;
3017 	return TRUE;
3018 }
3019 
3020 /* ARGSUSED */
3021 static void
3022 ldaptimeout(sig_no)
3023 	int sig_no;
3024 {
3025 	longjmp(LDAPTimeout, 1);
3026 }
3027 
3028 /*
3029 **  LDAPMAP_CLOSE -- close ldap map
3030 */
3031 
3032 void
3033 ldapmap_close(map)
3034 	MAP *map;
3035 {
3036 	LDAPMAP_STRUCT *lmap;
3037 	STAB *s;
3038 
3039 	if (tTd(38, 2))
3040 		dprintf("ldapmap_close(%s)\n", map->map_mname);
3041 
3042 	lmap = (LDAPMAP_STRUCT *) map->map_db1;
3043 
3044 	/* Check if already closed */
3045 	if (lmap->ldap_ld == NULL)
3046 		return;
3047 
3048 	s = ldapmap_findconn(lmap);
3049 
3050 	/* Check if already closed */
3051 	if (s->s_ldap == NULL)
3052 		return;
3053 
3054 	/* If same as saved connection, stored connection is going away */
3055 	if (s->s_ldap == lmap->ldap_ld)
3056 		s->s_ldap = NULL;
3057 
3058 	if (lmap->ldap_ld != NULL)
3059 	{
3060 		ldap_unbind(lmap->ldap_ld);
3061 		lmap->ldap_ld = NULL;
3062 	}
3063 }
3064 
3065 # ifdef SUNET_ID
3066 /*
3067 **  SUNET_ID_HASH -- Convert a string to it's Sunet_id canonical form
3068 **  This only makes sense at Stanford University.
3069 */
3070 
3071 char *
3072 sunet_id_hash(str)
3073 	char *str;
3074 {
3075 	char *p, *p_last;
3076 
3077 	p = str;
3078 	p_last = p;
3079 	while (*p != '\0')
3080 	{
3081 		if (islower(*p) || isdigit(*p))
3082 		{
3083 			*p_last = *p;
3084 			p_last++;
3085 		}
3086 		else if (isupper(*p))
3087 		{
3088 			*p_last = tolower(*p);
3089 			p_last++;
3090 		}
3091 		++p;
3092 	}
3093 	if (*p_last != '\0')
3094 		*p_last = '\0';
3095 	return str;
3096 }
3097 # endif /* SUNET_ID */
3098 
3099 /*
3100 **  LDAPMAP_LOOKUP -- look up a datum in a LDAP map
3101 */
3102 
3103 char *
3104 ldapmap_lookup(map, name, av, statp)
3105 	MAP *map;
3106 	char *name;
3107 	char **av;
3108 	int *statp;
3109 {
3110 	int i;
3111 	int entries = 0;
3112 	int msgid;
3113 	int ret;
3114 	int vsize;
3115 	char *fp, *vp;
3116 	char *p, *q;
3117 	char *result = NULL;
3118 	LDAPMAP_STRUCT *lmap = NULL;
3119 	char keybuf[MAXNAME + 1];
3120 	char filter[LDAPMAP_MAX_FILTER + 1];
3121 
3122 	if (tTd(38, 20))
3123 		dprintf("ldapmap_lookup(%s, %s)\n", map->map_mname, name);
3124 
3125 	/* Get ldap struct pointer from map */
3126 	lmap = (LDAPMAP_STRUCT *) map->map_db1;
3127 	ldapmap_setopts(lmap->ldap_ld, lmap);
3128 
3129 	(void) strlcpy(keybuf, name, sizeof keybuf);
3130 
3131 	if (!bitset(MF_NOFOLDCASE, map->map_mflags))
3132 	{
3133 # ifdef SUNET_ID
3134 		sunet_id_hash(keybuf);
3135 # else /* SUNET_ID */
3136 		makelower(keybuf);
3137 # endif /* SUNET_ID */
3138 	}
3139 
3140 	/* substitute keybuf into filter, perhaps multiple times */
3141 	memset(filter, '\0', sizeof filter);
3142 	fp = filter;
3143 	p = lmap->ldap_filter;
3144 	while ((q = strchr(p, '%')) != NULL)
3145 	{
3146 		if (q[1] == 's')
3147 		{
3148 			snprintf(fp, SPACELEFT(filter, fp), "%.*s%s",
3149 				 (int) (q - p), p, keybuf);
3150 			fp += strlen(fp);
3151 			p = q + 2;
3152 		}
3153 		else if (q[1] == '0')
3154 		{
3155 			char *k = keybuf;
3156 
3157 			snprintf(fp, SPACELEFT(filter, fp), "%.*s",
3158 				 (int) (q - p), p);
3159 			fp += strlen(fp);
3160 			p = q + 2;
3161 
3162 			/* Properly escape LDAP special characters */
3163 			while (SPACELEFT(filter, fp) > 0 &&
3164 			       *k != '\0')
3165 			{
3166 				if (*k == '*' || *k == '(' ||
3167 				    *k == ')' || *k == '\\')
3168 				{
3169 					(void) strlcat(fp,
3170 						       (*k == '*' ? "\\2A" :
3171 							(*k == '(' ? "\\28" :
3172 							 (*k == ')' ? "\\29" :
3173 							  (*k == '\\' ? "\\5C" :
3174 							   "\00")))),
3175 						SPACELEFT(filter, fp));
3176 					fp += strlen(fp);
3177 					k++;
3178 				}
3179 				else
3180 					*fp++ = *k++;
3181 			}
3182 		}
3183 		else
3184 		{
3185 			snprintf(fp, SPACELEFT(filter, fp), "%.*s",
3186 				 (int) (q - p + 1), p);
3187 			p = q + (q[1] == '%' ? 2 : 1);
3188 			fp += strlen(fp);
3189 		}
3190 	}
3191 	snprintf(fp, SPACELEFT(filter, fp), "%s", p);
3192 	if (tTd(38, 20))
3193 		dprintf("ldap search filter=%s\n", filter);
3194 
3195 	lmap->ldap_res = NULL;
3196 	msgid = ldap_search(lmap->ldap_ld, lmap->ldap_base, lmap->ldap_scope,
3197 			    filter,
3198 			    (lmap->ldap_attr[0] == NULL ? NULL :
3199 			     lmap->ldap_attr),
3200 			    lmap->ldap_attrsonly);
3201 	if (msgid == -1)
3202 	{
3203 		errno = ldapmap_geterrno(lmap->ldap_ld) + E_LDAPBASE;
3204 		if (!bitset(MF_OPTIONAL, map->map_mflags))
3205 		{
3206 			if (bitset(MF_NODEFER, map->map_mflags))
3207 				syserr("Error in ldap_search using %s in map %s",
3208 				       filter, map->map_mname);
3209 			else
3210 				syserr("421 4.0.0 Error in ldap_search using %s in map %s",
3211 				       filter, map->map_mname);
3212 		}
3213 		*statp = EX_TEMPFAIL;
3214 #ifdef LDAP_SERVER_DOWN
3215 		if (errno == LDAP_SERVER_DOWN)
3216 		{
3217 			int save_errno = errno;
3218 
3219 			/* server disappeared, try reopen on next search */
3220 			map->map_class->map_close(map);
3221 			map->map_mflags &= ~(MF_OPEN|MF_WRITABLE);
3222 			errno = save_errno;
3223 		}
3224 #endif /* LDAP_SERVER_DOWN */
3225 		return NULL;
3226 	}
3227 
3228 	*statp = EX_NOTFOUND;
3229 	vp = NULL;
3230 
3231 	/* Get results (all if MF_NOREWRITE, otherwise one by one) */
3232 	while ((ret = ldap_result(lmap->ldap_ld, msgid,
3233 				  bitset(MF_NOREWRITE, map->map_mflags),
3234 				  (lmap->ldap_timeout.tv_sec == 0 ? NULL :
3235 				   &(lmap->ldap_timeout)),
3236 				  &(lmap->ldap_res))) == LDAP_RES_SEARCH_ENTRY)
3237 	{
3238 		LDAPMessage *entry;
3239 
3240 		if (bitset(MF_SINGLEMATCH, map->map_mflags))
3241 		{
3242 			entries += ldap_count_entries(lmap->ldap_ld,
3243 						      lmap->ldap_res);
3244 			if (entries > 1)
3245 			{
3246 				*statp = EX_NOTFOUND;
3247 				if (lmap->ldap_res != NULL)
3248 				{
3249 					ldap_msgfree(lmap->ldap_res);
3250 					lmap->ldap_res = NULL;
3251 				}
3252 				(void) ldap_abandon(lmap->ldap_ld, msgid);
3253 				if (vp != NULL)
3254 					free(vp);
3255 				if (tTd(38, 25))
3256 					dprintf("ldap search found multiple on a single match query\n");
3257 				return NULL;
3258 			}
3259 		}
3260 
3261 		/* If we don't want multiple values and we have one, break */
3262 		if (map->map_coldelim == '\0' && vp != NULL)
3263 			break;
3264 
3265 		/* Cycle through all entries */
3266 		for (entry = ldap_first_entry(lmap->ldap_ld, lmap->ldap_res);
3267 		     entry != NULL;
3268 		     entry = ldap_next_entry(lmap->ldap_ld, lmap->ldap_res))
3269 		{
3270 			BerElement *ber;
3271 			char *attr;
3272 			char **vals = NULL;
3273 
3274 			/*
3275 			**  If matching only and found an entry,
3276 			**  no need to spin through attributes
3277 			*/
3278 
3279 			if (*statp == EX_OK &&
3280 			    bitset(MF_MATCHONLY, map->map_mflags))
3281 				continue;
3282 
3283 # if !defined(LDAP_VERSION_MAX) && !defined(LDAP_OPT_SIZELIMIT)
3284 			/*
3285 			**  Reset value to prevent lingering
3286 			**  LDAP_DECODING_ERROR due to
3287 			**  OpenLDAP 1.X's hack (see below)
3288 			*/
3289 
3290 			lmap->ldap_ld->ld_errno = LDAP_SUCCESS;
3291 # endif /* !defined(LDAP_VERSION_MAX) !defined(LDAP_OPT_SIZELIMIT) */
3292 
3293 			for (attr = ldap_first_attribute(lmap->ldap_ld, entry,
3294 							 &ber);
3295 			     attr != NULL;
3296 			     attr = ldap_next_attribute(lmap->ldap_ld, entry,
3297 							ber))
3298 			{
3299 				char *tmp, *vp_tmp;
3300 
3301 				if (lmap->ldap_attrsonly == LDAPMAP_FALSE)
3302 				{
3303 					vals = ldap_get_values(lmap->ldap_ld,
3304 							       entry,
3305 							       attr);
3306 					if (vals == NULL)
3307 					{
3308 						errno = ldapmap_geterrno(lmap->ldap_ld);
3309 						if (errno == LDAP_SUCCESS)
3310 							continue;
3311 
3312 						/* Must be an error */
3313 						errno += E_LDAPBASE;
3314 						if (!bitset(MF_OPTIONAL,
3315 							    map->map_mflags))
3316 						{
3317 							if (bitset(MF_NODEFER,
3318 								   map->map_mflags))
3319 								syserr("Error getting LDAP values in map %s",
3320 								       map->map_mname);
3321 							else
3322 								syserr("421 4.0.0 Error getting LDAP values in map %s",
3323 								       map->map_mname);
3324 						}
3325 						*statp = EX_TEMPFAIL;
3326 # if USING_NETSCAPE_LDAP
3327 						ldap_memfree(attr);
3328 # endif /* USING_NETSCAPE_LDAP */
3329 						if (lmap->ldap_res != NULL)
3330 						{
3331 							ldap_msgfree(lmap->ldap_res);
3332 							lmap->ldap_res = NULL;
3333 						}
3334 						(void) ldap_abandon(lmap->ldap_ld,
3335 								    msgid);
3336 						if (vp != NULL)
3337 							free(vp);
3338 						return NULL;
3339 					}
3340 				}
3341 
3342 				*statp = EX_OK;
3343 
3344 # if !defined(LDAP_VERSION_MAX) && !defined(LDAP_OPT_SIZELIMIT)
3345 				/*
3346 				**  Reset value to prevent lingering
3347 				**  LDAP_DECODING_ERROR due to
3348 				**  OpenLDAP 1.X's hack (see below)
3349 				*/
3350 
3351 				lmap->ldap_ld->ld_errno = LDAP_SUCCESS;
3352 # endif /* !defined(LDAP_VERSION_MAX) !defined(LDAP_OPT_SIZELIMIT) */
3353 
3354 				/*
3355 				**  If matching only,
3356 				**  no need to spin through entries
3357 				*/
3358 
3359 				if (bitset(MF_MATCHONLY, map->map_mflags))
3360 					continue;
3361 
3362 				/*
3363 				**  If we don't want multiple values,
3364 				**  return first found.
3365 				*/
3366 
3367 				if (map->map_coldelim == '\0')
3368 				{
3369 					if (lmap->ldap_attrsonly == LDAPMAP_TRUE)
3370 					{
3371 						vp = newstr(attr);
3372 # if USING_NETSCAPE_LDAP
3373 						ldap_memfree(attr);
3374 # endif /* USING_NETSCAPE_LDAP */
3375 						break;
3376 					}
3377 
3378 					if (vals[0] == NULL)
3379 					{
3380 						ldap_value_free(vals);
3381 # if USING_NETSCAPE_LDAP
3382 						ldap_memfree(attr);
3383 # endif /* USING_NETSCAPE_LDAP */
3384 						continue;
3385 					}
3386 
3387 					vp = newstr(vals[0]);
3388 					ldap_value_free(vals);
3389 # if USING_NETSCAPE_LDAP
3390 					ldap_memfree(attr);
3391 # endif /* USING_NETSCAPE_LDAP */
3392 					break;
3393 				}
3394 
3395 				/* attributes only */
3396 				if (lmap->ldap_attrsonly == LDAPMAP_TRUE)
3397 				{
3398 					if (vp == NULL)
3399 						vp = newstr(attr);
3400 					else
3401 					{
3402 						vsize = strlen(vp) +
3403 							strlen(attr) + 2;
3404 						tmp = xalloc(vsize);
3405 						snprintf(tmp, vsize, "%s%c%s",
3406 							 vp, map->map_coldelim,
3407 							 attr);
3408 						free(vp);
3409 						vp = tmp;
3410 					}
3411 # if USING_NETSCAPE_LDAP
3412 					ldap_memfree(attr);
3413 # endif /* USING_NETSCAPE_LDAP */
3414 					continue;
3415 				}
3416 
3417 				/*
3418 				**  If there is more than one,
3419 				**  munge then into a map_coldelim
3420 				**  separated string
3421 				*/
3422 
3423 				vsize = 0;
3424 				for (i = 0; vals[i] != NULL; i++)
3425 					vsize += strlen(vals[i]) + 1;
3426 				vp_tmp = xalloc(vsize);
3427 				*vp_tmp = '\0';
3428 
3429 				p = vp_tmp;
3430 				for (i = 0; vals[i] != NULL; i++)
3431 				{
3432 					p += strlcpy(p, vals[i],
3433 						     vsize - (p - vp_tmp));
3434 					if (p >= vp_tmp + vsize)
3435 						syserr("ldapmap_lookup: Internal error: buffer too small for LDAP values");
3436 					if (vals[i + 1] != NULL)
3437 						*p++ = map->map_coldelim;
3438 				}
3439 
3440 				ldap_value_free(vals);
3441 # if USING_NETSCAPE_LDAP
3442 				ldap_memfree(attr);
3443 # endif /* USING_NETSCAPE_LDAP */
3444 				if (vp == NULL)
3445 				{
3446 					vp = vp_tmp;
3447 					continue;
3448 				}
3449 				vsize = strlen(vp) + strlen(vp_tmp) + 2;
3450 				tmp = xalloc(vsize);
3451 				snprintf(tmp, vsize, "%s%c%s",
3452 					 vp, map->map_coldelim, vp_tmp);
3453 
3454 				free(vp);
3455 				free(vp_tmp);
3456 				vp = tmp;
3457 			}
3458 			errno = ldapmap_geterrno(lmap->ldap_ld);
3459 
3460 			/*
3461 			**  We check errno != LDAP_DECODING_ERROR since
3462 			**  OpenLDAP 1.X has a very ugly *undocumented*
3463 			**  hack of returning this error code from
3464 			**  ldap_next_attribute() if the library freed the
3465 			**  ber attribute.  See:
3466 			**  http://www.openldap.org/lists/openldap-devel/9901/msg00064.html
3467 			*/
3468 
3469 			if (errno != LDAP_SUCCESS &&
3470 			    errno != LDAP_DECODING_ERROR)
3471 			{
3472 				/* Must be an error */
3473 				errno += E_LDAPBASE;
3474 				if (!bitset(MF_OPTIONAL, map->map_mflags))
3475 				{
3476 					if (bitset(MF_NODEFER, map->map_mflags))
3477 						syserr("Error getting LDAP attributes in map %s",
3478 						       map->map_mname);
3479 					else
3480 						syserr("421 4.0.0 Error getting LDAP attributes in map %s",
3481 						       map->map_mname);
3482 				}
3483 				*statp = EX_TEMPFAIL;
3484 				if (lmap->ldap_res != NULL)
3485 				{
3486 					ldap_msgfree(lmap->ldap_res);
3487 					lmap->ldap_res = NULL;
3488 				}
3489 				(void) ldap_abandon(lmap->ldap_ld, msgid);
3490 				if (vp != NULL)
3491 					free(vp);
3492 				return NULL;
3493 			}
3494 
3495 			/* We don't want multiple values and we have one */
3496 			if (map->map_coldelim == '\0' && vp != NULL)
3497 				break;
3498 		}
3499 		errno = ldapmap_geterrno(lmap->ldap_ld);
3500 		if (errno != LDAP_SUCCESS && errno != LDAP_DECODING_ERROR)
3501 		{
3502 			/* Must be an error */
3503 			errno += E_LDAPBASE;
3504 			if (!bitset(MF_OPTIONAL, map->map_mflags))
3505 			{
3506 				if (bitset(MF_NODEFER, map->map_mflags))
3507 					syserr("Error getting LDAP entries in map %s",
3508 					       map->map_mname);
3509 				else
3510 					syserr("421 4.0.0 Error getting LDAP entries in map %s",
3511 					       map->map_mname);
3512 			}
3513 			*statp = EX_TEMPFAIL;
3514 			if (lmap->ldap_res != NULL)
3515 			{
3516 				ldap_msgfree(lmap->ldap_res);
3517 				lmap->ldap_res = NULL;
3518 			}
3519 			(void) ldap_abandon(lmap->ldap_ld, msgid);
3520 			if (vp != NULL)
3521 				free(vp);
3522 			return NULL;
3523 		}
3524 		ldap_msgfree(lmap->ldap_res);
3525 		lmap->ldap_res = NULL;
3526 	}
3527 
3528 	/*
3529 	**  If grabbing all results at once for MF_NOREWRITE and
3530 	**  only want a single match, make sure that's all we have
3531 	*/
3532 
3533 	if (ret == LDAP_RES_SEARCH_RESULT &&
3534 	    bitset(MF_NOREWRITE|MF_SINGLEMATCH, map->map_mflags))
3535 	{
3536 		entries += ldap_count_entries(lmap->ldap_ld, lmap->ldap_res);
3537 		if (entries > 1)
3538 		{
3539 			*statp = EX_NOTFOUND;
3540 			if (lmap->ldap_res != NULL)
3541 			{
3542 				ldap_msgfree(lmap->ldap_res);
3543 				lmap->ldap_res = NULL;
3544 			}
3545 			if (vp != NULL)
3546 				free(vp);
3547 			return NULL;
3548 		}
3549 		*statp = EX_OK;
3550 	}
3551 
3552 	if (ret == 0)
3553 		errno = ETIMEDOUT;
3554 	else
3555 		errno = ldapmap_geterrno(lmap->ldap_ld);
3556 	if (errno != LDAP_SUCCESS)
3557 	{
3558 		/* Must be an error */
3559 		if (ret != 0)
3560 			errno += E_LDAPBASE;
3561 		if (!bitset(MF_OPTIONAL, map->map_mflags))
3562 		{
3563 			if (bitset(MF_NODEFER, map->map_mflags))
3564 				syserr("Error getting LDAP results in map %s",
3565 				       map->map_mname);
3566 			else
3567 				syserr("421 4.0.0 Error getting LDAP results in map %s",
3568 				       map->map_mname);
3569 		}
3570 		*statp = EX_TEMPFAIL;
3571 		if (vp != NULL)
3572 			free(vp);
3573 		return NULL;
3574 	}
3575 
3576 	/* Did we match anything? */
3577 	if (vp == NULL)
3578 		return NULL;
3579 
3580 	/*
3581 	**  If MF_NOREWRITE, we are special map which doesn't
3582 	**  actually return a map value.  Instead, we don't free
3583 	**  ldap_res and let the calling function process the LDAP
3584 	**  results.  The caller should ldap_msgfree(lmap->ldap_res).
3585 	*/
3586 
3587 	if (bitset(MF_NOREWRITE, map->map_mflags))
3588 	{
3589 		/* vp != NULL due to test above */
3590 		free(vp);
3591 		return "";
3592 	}
3593 
3594 	if (*statp == EX_OK)
3595 	{
3596 		/* vp != NULL due to test above */
3597 		if (LogLevel > 9)
3598 			sm_syslog(LOG_INFO, CurEnv->e_id,
3599 				  "ldap %.100s => %s", name, vp);
3600 		if (bitset(MF_MATCHONLY, map->map_mflags))
3601 			result = map_rewrite(map, name, strlen(name), NULL);
3602 		else
3603 			result = map_rewrite(map, vp, strlen(vp), av);
3604 		free(vp);
3605 	}
3606 	return result;
3607 }
3608 
3609 /*
3610 **  LDAPMAP_FINDCONN -- find an LDAP connection to the server
3611 **
3612 **	Cache LDAP connections based on the host, port, bind DN,
3613 **	secret, and PID so we don't have multiple connections open to
3614 **	the same server for different maps.  Need a separate connection
3615 **	per PID since a parent process may close the map before the
3616 **	child is done with it.
3617 **
3618 **	Parameters:
3619 **		lmap -- LDAP map information
3620 **
3621 **	Returns:
3622 **		Symbol table entry for the LDAP connection.
3623 **
3624 */
3625 
3626 static STAB *
3627 ldapmap_findconn(lmap)
3628 	LDAPMAP_STRUCT *lmap;
3629 {
3630 	int len;
3631 	char *nbuf;
3632 	STAB *s;
3633 
3634 	len = (lmap->ldap_host == NULL ? strlen("localhost") :
3635 					 strlen(lmap->ldap_host)) + 1 + 8 + 1 +
3636 		(lmap->ldap_binddn == NULL ? 0 : strlen(lmap->ldap_binddn)) +
3637 		1 +
3638 		(lmap->ldap_secret == NULL ? 0 : strlen(lmap->ldap_secret)) +
3639 		8 + 1;
3640 	nbuf = xalloc(len);
3641 	snprintf(nbuf, len, "%s%c%d%c%s%c%s%d",
3642 		 (lmap->ldap_host == NULL ? "localhost" : lmap->ldap_host),
3643 		 CONDELSE,
3644 		 lmap->ldap_port,
3645 		 CONDELSE,
3646 		 (lmap->ldap_binddn == NULL ? "" : lmap->ldap_binddn),
3647 		 CONDELSE,
3648 		 (lmap->ldap_secret == NULL ? "" : lmap->ldap_secret),
3649 		 getpid());
3650 	s = stab(nbuf, ST_LDAP, ST_ENTER);
3651 	free(nbuf);
3652 	return s;
3653 }
3654 /*
3655 **  LDAPMAP_SETOPTS -- set LDAP options
3656 **
3657 **	Parameters:
3658 **		ld -- LDAP session handle
3659 **		lmap -- LDAP map information
3660 **
3661 **	Returns:
3662 **		None.
3663 **
3664 */
3665 
3666 static void
3667 ldapmap_setopts(ld, lmap)
3668 	LDAP *ld;
3669 	LDAPMAP_STRUCT *lmap;
3670 {
3671 # if USE_LDAP_SET_OPTION
3672 	ldap_set_option(ld, LDAP_OPT_DEREF, &lmap->ldap_deref);
3673 	if (bitset(LDAP_OPT_REFERRALS, lmap->ldap_options))
3674 		ldap_set_option(ld, LDAP_OPT_REFERRALS, LDAP_OPT_ON);
3675 	else
3676 		ldap_set_option(ld, LDAP_OPT_REFERRALS, LDAP_OPT_OFF);
3677 	ldap_set_option(ld, LDAP_OPT_SIZELIMIT, &lmap->ldap_sizelimit);
3678 	ldap_set_option(ld, LDAP_OPT_TIMELIMIT, &lmap->ldap_timelimit);
3679 # else /* USE_LDAP_SET_OPTION */
3680 	/* From here on in we can use ldap internal timelimits */
3681 	ld->ld_deref = lmap->ldap_deref;
3682 	ld->ld_options = lmap->ldap_options;
3683 	ld->ld_sizelimit = lmap->ldap_sizelimit;
3684 	ld->ld_timelimit = lmap->ldap_timelimit;
3685 # endif /* USE_LDAP_SET_OPTION */
3686 }
3687 /*
3688 **  LDAPMAP_GETERRNO -- get ldap errno value
3689 **
3690 **	Parameters:
3691 **		ld -- LDAP session handle
3692 **
3693 **	Returns:
3694 **		LDAP errno.
3695 **
3696 */
3697 
3698 static int
3699 ldapmap_geterrno(ld)
3700 	LDAP *ld;
3701 {
3702 	int err = LDAP_SUCCESS;
3703 
3704 # if defined(LDAP_VERSION_MAX) && LDAP_VERSION_MAX >= 3
3705 	(void) ldap_get_option(ld, LDAP_OPT_ERROR_NUMBER, &err);
3706 # else /* defined(LDAP_VERSION_MAX) && LDAP_VERSION_MAX >= 3 */
3707 #  ifdef LDAP_OPT_SIZELIMIT
3708 	err = ldap_get_lderrno(ld, NULL, NULL);
3709 #  else /* LDAP_OPT_SIZELIMIT */
3710 	err = ld->ld_errno;
3711 
3712 	/*
3713 	**  Reset value to prevent lingering LDAP_DECODING_ERROR due to
3714 	**  OpenLDAP 1.X's hack (see above)
3715 	*/
3716 
3717 	ld->ld_errno = LDAP_SUCCESS;
3718 #  endif /* LDAP_OPT_SIZELIMIT */
3719 # endif /* defined(LDAP_VERSION_MAX) && LDAP_VERSION_MAX >= 3 */
3720 	return err;
3721 }
3722 
3723 /*
3724 **  LDAPX_MAP_PARSEARGS -- print warning about use of ldapx map.
3725 */
3726 
3727 bool
3728 ldapx_map_parseargs(map, args)
3729 	MAP *map;
3730 	char *args;
3731 {
3732 	printf("Warning: The \"ldapx\" map class is deprecated and will be removed in a future\n");
3733 	printf("         version.  Use the \"ldap\" map class instead for map \"%s\".\n",
3734 	       map->map_mname);
3735 	return ldapmap_parseargs(map, args);
3736 }
3737 
3738 /*
3739 **  LDAPMAP_PARSEARGS -- parse ldap map definition args.
3740 */
3741 
3742 struct lamvalues LDAPAuthMethods[] =
3743 {
3744 	{	"none",		LDAP_AUTH_NONE		},
3745 	{	"simple",	LDAP_AUTH_SIMPLE	},
3746 # ifdef LDAP_AUTH_KRBV4
3747 	{	"krbv4",	LDAP_AUTH_KRBV4		},
3748 # endif /* LDAP_AUTH_KRBV4 */
3749 	{	NULL,		0			}
3750 };
3751 
3752 struct ladvalues LDAPAliasDereference[] =
3753 {
3754 	{	"never",	LDAP_DEREF_NEVER	},
3755 	{	"always",	LDAP_DEREF_ALWAYS	},
3756 	{	"search",	LDAP_DEREF_SEARCHING	},
3757 	{	"find",		LDAP_DEREF_FINDING	},
3758 	{	NULL,		0			}
3759 };
3760 
3761 struct lssvalues LDAPSearchScope[] =
3762 {
3763 	{	"base",		LDAP_SCOPE_BASE		},
3764 	{	"one",		LDAP_SCOPE_ONELEVEL	},
3765 	{	"sub",		LDAP_SCOPE_SUBTREE	},
3766 	{	NULL,		0			}
3767 };
3768 
3769 bool
3770 ldapmap_parseargs(map, args)
3771 	MAP *map;
3772 	char *args;
3773 {
3774 	bool secretread = TRUE;
3775 	int i;
3776 	register char *p = args;
3777 	LDAPMAP_STRUCT *lmap;
3778 	struct lamvalues *lam;
3779 	struct ladvalues *lad;
3780 	struct lssvalues *lss;
3781 	char m_tmp[MAXPATHLEN + LDAPMAP_MAX_PASSWD];
3782 
3783 	/* Get ldap struct pointer from map */
3784 	lmap = (LDAPMAP_STRUCT *) map->map_db1;
3785 
3786 	/* Check if setting the initial LDAP defaults */
3787 	if (lmap == NULL || lmap != LDAPDefaults)
3788 	{
3789 		/* We need to alloc an LDAPMAP_STRUCT struct */
3790 		lmap = (LDAPMAP_STRUCT *) xalloc(sizeof *lmap);
3791 		if (LDAPDefaults == NULL)
3792 			ldapmap_clear(lmap);
3793 		else
3794 			STRUCTCOPY(*LDAPDefaults, *lmap);
3795 	}
3796 
3797 	/* there is no check whether there is really an argument */
3798 	map->map_mflags |= MF_TRY0NULL|MF_TRY1NULL;
3799 	map->map_spacesub = SpaceSub;	/* default value */
3800 	for (;;)
3801 	{
3802 		while (isascii(*p) && isspace(*p))
3803 			p++;
3804 		if (*p != '-')
3805 			break;
3806 		switch (*++p)
3807 		{
3808 		  case 'N':
3809 			map->map_mflags |= MF_INCLNULL;
3810 			map->map_mflags &= ~MF_TRY0NULL;
3811 			break;
3812 
3813 		  case 'O':
3814 			map->map_mflags &= ~MF_TRY1NULL;
3815 			break;
3816 
3817 		  case 'o':
3818 			map->map_mflags |= MF_OPTIONAL;
3819 			break;
3820 
3821 		  case 'f':
3822 			map->map_mflags |= MF_NOFOLDCASE;
3823 			break;
3824 
3825 		  case 'm':
3826 			map->map_mflags |= MF_MATCHONLY;
3827 			break;
3828 
3829 		  case 'A':
3830 			map->map_mflags |= MF_APPEND;
3831 			break;
3832 
3833 		  case 'q':
3834 			map->map_mflags |= MF_KEEPQUOTES;
3835 			break;
3836 
3837 		  case 'a':
3838 			map->map_app = ++p;
3839 			break;
3840 
3841 		  case 'T':
3842 			map->map_tapp = ++p;
3843 			break;
3844 
3845 		  case 't':
3846 			map->map_mflags |= MF_NODEFER;
3847 			break;
3848 
3849 		  case 'S':
3850 			map->map_spacesub = *++p;
3851 			break;
3852 
3853 		  case 'D':
3854 			map->map_mflags |= MF_DEFER;
3855 			break;
3856 
3857 		  case 'z':
3858 			if (*++p != '\\')
3859 				map->map_coldelim = *p;
3860 			else
3861 			{
3862 				switch (*++p)
3863 				{
3864 				  case 'n':
3865 					map->map_coldelim = '\n';
3866 					break;
3867 
3868 				  case 't':
3869 					map->map_coldelim = '\t';
3870 					break;
3871 
3872 				  default:
3873 					map->map_coldelim = '\\';
3874 				}
3875 			}
3876 			break;
3877 
3878 			/* Start of ldapmap specific args */
3879 		  case 'k':		/* search field */
3880 			while (isascii(*++p) && isspace(*p))
3881 				continue;
3882 			lmap->ldap_filter = p;
3883 			break;
3884 
3885 		  case 'v':		/* attr to return */
3886 			while (isascii(*++p) && isspace(*p))
3887 				continue;
3888 			lmap->ldap_attr[0] = p;
3889 			lmap->ldap_attr[1] = NULL;
3890 			break;
3891 
3892 		  case '1':
3893 			map->map_mflags |= MF_SINGLEMATCH;
3894 			break;
3895 
3896 			/* args stolen from ldapsearch.c */
3897 		  case 'R':		/* don't auto chase referrals */
3898 # ifdef LDAP_REFERRALS
3899 			lmap->ldap_options &= ~LDAP_OPT_REFERRALS;
3900 # else /* LDAP_REFERRALS */
3901 			syserr("compile with -DLDAP_REFERRALS for referral support\n");
3902 # endif /* LDAP_REFERRALS */
3903 			break;
3904 
3905 		  case 'n':		/* retrieve attribute names only */
3906 			lmap->ldap_attrsonly = LDAPMAP_TRUE;
3907 			break;
3908 
3909 		  case 'r':		/* alias dereferencing */
3910 			while (isascii(*++p) && isspace(*p))
3911 				continue;
3912 
3913 			if (strncasecmp(p, "LDAP_DEREF_", 11) == 0)
3914 				p += 11;
3915 
3916 			for (lad = LDAPAliasDereference;
3917 			     lad != NULL && lad->lad_name != NULL; lad++)
3918 			{
3919 				if (strncasecmp(p, lad->lad_name,
3920 						strlen(lad->lad_name)) == 0)
3921 					break;
3922 			}
3923 			if (lad->lad_name != NULL)
3924 				lmap->ldap_deref = lad->lad_code;
3925 			else
3926 			{
3927 				/* bad config line */
3928 				if (!bitset(MCF_OPTFILE,
3929 					    map->map_class->map_cflags))
3930 				{
3931 					char *ptr;
3932 
3933 					if ((ptr = strchr(p, ' ')) != NULL)
3934 						*ptr = '\0';
3935 					syserr("Deref must be [never|always|search|find] not %s in map %s",
3936 						p, map->map_mname);
3937 					if (ptr != NULL)
3938 						*ptr = ' ';
3939 					return FALSE;
3940 				}
3941 			}
3942 			break;
3943 
3944 		  case 's':		/* search scope */
3945 			while (isascii(*++p) && isspace(*p))
3946 				continue;
3947 
3948 			if (strncasecmp(p, "LDAP_SCOPE_", 11) == 0)
3949 				p += 11;
3950 
3951 			for (lss = LDAPSearchScope;
3952 			     lss != NULL && lss->lss_name != NULL; lss++)
3953 			{
3954 				if (strncasecmp(p, lss->lss_name,
3955 						strlen(lss->lss_name)) == 0)
3956 					break;
3957 			}
3958 			if (lss->lss_name != NULL)
3959 				lmap->ldap_scope = lss->lss_code;
3960 			else
3961 			{
3962 				/* bad config line */
3963 				if (!bitset(MCF_OPTFILE,
3964 					    map->map_class->map_cflags))
3965 				{
3966 					char *ptr;
3967 
3968 					if ((ptr = strchr(p, ' ')) != NULL)
3969 						*ptr = '\0';
3970 					syserr("Scope must be [base|one|sub] not %s in map %s",
3971 						p, map->map_mname);
3972 					if (ptr != NULL)
3973 						*ptr = ' ';
3974 					return FALSE;
3975 				}
3976 			}
3977 			break;
3978 
3979 		  case 'h':		/* ldap host */
3980 			while (isascii(*++p) && isspace(*p))
3981 				continue;
3982 			lmap->ldap_host = p;
3983 			break;
3984 
3985 		  case 'b':		/* search base */
3986 			while (isascii(*++p) && isspace(*p))
3987 				continue;
3988 			lmap->ldap_base = p;
3989 			break;
3990 
3991 		  case 'p':		/* ldap port */
3992 			while (isascii(*++p) && isspace(*p))
3993 				continue;
3994 			lmap->ldap_port = atoi(p);
3995 			break;
3996 
3997 		  case 'l':		/* time limit */
3998 			while (isascii(*++p) && isspace(*p))
3999 				continue;
4000 			lmap->ldap_timelimit = atoi(p);
4001 			lmap->ldap_timeout.tv_sec = lmap->ldap_timelimit;
4002 			break;
4003 
4004 		  case 'Z':
4005 			while (isascii(*++p) && isspace(*p))
4006 				continue;
4007 			lmap->ldap_sizelimit = atoi(p);
4008 			break;
4009 
4010 		  case 'd':		/* Dn to bind to server as */
4011 			while (isascii(*++p) && isspace(*p))
4012 				continue;
4013 			lmap->ldap_binddn = p;
4014 			break;
4015 
4016 		  case 'M':		/* Method for binding */
4017 			while (isascii(*++p) && isspace(*p))
4018 				continue;
4019 
4020 			if (strncasecmp(p, "LDAP_AUTH_", 10) == 0)
4021 				p += 10;
4022 
4023 			for (lam = LDAPAuthMethods;
4024 			     lam != NULL && lam->lam_name != NULL; lam++)
4025 			{
4026 				if (strncasecmp(p, lam->lam_name,
4027 						strlen(lam->lam_name)) == 0)
4028 					break;
4029 			}
4030 			if (lam->lam_name != NULL)
4031 				lmap->ldap_method = lam->lam_code;
4032 			else
4033 			{
4034 				/* bad config line */
4035 				if (!bitset(MCF_OPTFILE,
4036 					    map->map_class->map_cflags))
4037 				{
4038 					char *ptr;
4039 
4040 					if ((ptr = strchr(p, ' ')) != NULL)
4041 						*ptr = '\0';
4042 					syserr("Method for binding must be [none|simple|krbv4] not %s in map %s",
4043 						p, map->map_mname);
4044 					if (ptr != NULL)
4045 						*ptr = ' ';
4046 					return FALSE;
4047 				}
4048 			}
4049 
4050 			break;
4051 
4052 			/*
4053 			**  This is a string that is dependent on the
4054 			**  method used defined above.
4055 			*/
4056 
4057 		  case 'P':		/* Secret password for binding */
4058 			 while (isascii(*++p) && isspace(*p))
4059 				continue;
4060 			lmap->ldap_secret = p;
4061 			secretread = FALSE;
4062 			break;
4063 
4064 		  default:
4065 			syserr("Illegal option %c map %s", *p, map->map_mname);
4066 			break;
4067 		}
4068 
4069 		/* need to account for quoted strings here */
4070 		while (*p != '\0' && !(isascii(*p) && isspace(*p)))
4071 		{
4072 			if (*p == '"')
4073 			{
4074 				while (*++p != '"' && *p != '\0')
4075 					continue;
4076 				if (*p != '\0')
4077 					p++;
4078 			}
4079 			else
4080 				p++;
4081 		}
4082 
4083 		if (*p != '\0')
4084 			*p++ = '\0';
4085 	}
4086 
4087 	if (map->map_app != NULL)
4088 		map->map_app = newstr(ldapmap_dequote(map->map_app));
4089 	if (map->map_tapp != NULL)
4090 		map->map_tapp = newstr(ldapmap_dequote(map->map_tapp));
4091 
4092 	/*
4093 	**  We need to swallow up all the stuff into a struct
4094 	**  and dump it into map->map_dbptr1
4095 	*/
4096 
4097 	if (lmap->ldap_host != NULL &&
4098 	    (LDAPDefaults == NULL ||
4099 	     LDAPDefaults == lmap ||
4100 	     LDAPDefaults->ldap_host != lmap->ldap_host))
4101 		lmap->ldap_host = newstr(ldapmap_dequote(lmap->ldap_host));
4102 	map->map_domain = lmap->ldap_host;
4103 
4104 	if (lmap->ldap_binddn != NULL &&
4105 	    (LDAPDefaults == NULL ||
4106 	     LDAPDefaults == lmap ||
4107 	     LDAPDefaults->ldap_binddn != lmap->ldap_binddn))
4108 		lmap->ldap_binddn = newstr(ldapmap_dequote(lmap->ldap_binddn));
4109 
4110 	if (lmap->ldap_secret != NULL &&
4111 	    (LDAPDefaults == NULL ||
4112 	     LDAPDefaults == lmap ||
4113 	     LDAPDefaults->ldap_secret != lmap->ldap_secret))
4114 	{
4115 		FILE *sfd;
4116 		long sff = SFF_OPENASROOT|SFF_ROOTOK|SFF_NOWLINK|SFF_NOWWFILES|SFF_NOGWFILES;
4117 
4118 		if (DontLockReadFiles)
4119 			sff |= SFF_NOLOCK;
4120 
4121 		/* need to use method to map secret to passwd string */
4122 		switch (lmap->ldap_method)
4123 		{
4124 		  case LDAP_AUTH_NONE:
4125 			/* Do nothing */
4126 			break;
4127 
4128 		  case LDAP_AUTH_SIMPLE:
4129 
4130 			/*
4131 			**  Secret is the name of a file with
4132 			**  the first line as the password.
4133 			*/
4134 
4135 			/* Already read in the secret? */
4136 			if (secretread)
4137 				break;
4138 
4139 			sfd = safefopen(ldapmap_dequote(lmap->ldap_secret),
4140 					O_RDONLY, 0, sff);
4141 			if (sfd == NULL)
4142 			{
4143 				syserr("LDAP map: cannot open secret %s",
4144 				       ldapmap_dequote(lmap->ldap_secret));
4145 				return FALSE;
4146 			}
4147 			lmap->ldap_secret = sfgets(m_tmp, LDAPMAP_MAX_PASSWD,
4148 						   sfd, TimeOuts.to_fileopen,
4149 						   "ldapmap_parseargs");
4150 			(void) fclose(sfd);
4151 			if (lmap->ldap_secret != NULL &&
4152 			    strlen(m_tmp) > 0)
4153 			{
4154 				/* chomp newline */
4155 				if (m_tmp[strlen(m_tmp) - 1] == '\n')
4156 					m_tmp[strlen(m_tmp) - 1] = '\0';
4157 
4158 				lmap->ldap_secret = m_tmp;
4159 			}
4160 			break;
4161 
4162 # ifdef LDAP_AUTH_KRBV4
4163 		  case LDAP_AUTH_KRBV4:
4164 
4165 			/*
4166 			**  Secret is where the ticket file is
4167 			**  stashed
4168 			*/
4169 
4170 			snprintf(m_tmp, MAXPATHLEN + LDAPMAP_MAX_PASSWD,
4171 				 "KRBTKFILE=%s",
4172 				 ldapmap_dequote(lmap->ldap_secret));
4173 			lmap->ldap_secret = m_tmp;
4174 			break;
4175 # endif /* LDAP_AUTH_KRBV4 */
4176 
4177 		  default:	       /* Should NEVER get here */
4178 			syserr("LDAP map: Illegal value in lmap method");
4179 			return FALSE;
4180 			break;
4181 		}
4182 	}
4183 
4184 	if (lmap->ldap_secret != NULL &&
4185 	    (LDAPDefaults == NULL ||
4186 	     LDAPDefaults == lmap ||
4187 	     LDAPDefaults->ldap_secret != lmap->ldap_secret))
4188 		lmap->ldap_secret = newstr(ldapmap_dequote(lmap->ldap_secret));
4189 
4190 	if (lmap->ldap_base != NULL &&
4191 	    (LDAPDefaults == NULL ||
4192 	     LDAPDefaults == lmap ||
4193 	     LDAPDefaults->ldap_base != lmap->ldap_base))
4194 		lmap->ldap_base = newstr(ldapmap_dequote(lmap->ldap_base));
4195 
4196 	/*
4197 	**  Save the server from extra work.  If request is for a single
4198 	**  match, tell the server to only return enough records to
4199 	**  determine if there is a single match or not.  This can not
4200 	**  be one since the server would only return one and we wouldn't
4201 	**  know if there were others available.
4202 	*/
4203 
4204 	if (bitset(MF_SINGLEMATCH, map->map_mflags))
4205 		lmap->ldap_sizelimit = 2;
4206 
4207 	/* If setting defaults, don't process ldap_filter and ldap_attr */
4208 	if (lmap == LDAPDefaults)
4209 		return TRUE;
4210 
4211 	if (lmap->ldap_filter != NULL)
4212 		lmap->ldap_filter = newstr(ldapmap_dequote(lmap->ldap_filter));
4213 	else
4214 	{
4215 		if (!bitset(MCF_OPTFILE, map->map_class->map_cflags))
4216 		{
4217 			syserr("No filter given in map %s", map->map_mname);
4218 			return FALSE;
4219 		}
4220 	}
4221 
4222 	if (lmap->ldap_attr[0] != NULL)
4223 	{
4224 		i = 0;
4225 		p = ldapmap_dequote(lmap->ldap_attr[0]);
4226 		lmap->ldap_attr[0] = NULL;
4227 
4228 		while (p != NULL)
4229 		{
4230 			char *v;
4231 
4232 			while (isascii(*p) && isspace(*p))
4233 				p++;
4234 			if (*p == '\0')
4235 				break;
4236 			v = p;
4237 			p = strchr(v, ',');
4238 			if (p != NULL)
4239 				*p++ = '\0';
4240 
4241 			if (i == LDAPMAP_MAX_ATTR)
4242 			{
4243 				syserr("Too many return attributes in %s (max %d)",
4244 				       map->map_mname, LDAPMAP_MAX_ATTR);
4245 				return FALSE;
4246 			}
4247 			if (*v != '\0')
4248 				lmap->ldap_attr[i++] = newstr(v);
4249 		}
4250 		lmap->ldap_attr[i] = NULL;
4251 	}
4252 
4253 	map->map_db1 = (ARBPTR_T) lmap;
4254 	return TRUE;
4255 }
4256 
4257 /*
4258 **  LDAPMAP_CLEAR -- set default values for LDAPMAP_STRUCT
4259 **
4260 **	Parameters:
4261 **		lmap -- pointer to LDAPMAP_STRUCT to clear
4262 **
4263 **	Returns:
4264 **		None.
4265 **
4266 */
4267 
4268 static void
4269 ldapmap_clear(lmap)
4270 	LDAPMAP_STRUCT *lmap;
4271 {
4272 	lmap->ldap_host = NULL;
4273 	lmap->ldap_port = LDAP_PORT;
4274 	lmap->ldap_deref = LDAP_DEREF_NEVER;
4275 	lmap->ldap_timelimit = LDAP_NO_LIMIT;
4276 	lmap->ldap_sizelimit = LDAP_NO_LIMIT;
4277 # ifdef LDAP_REFERRALS
4278 	lmap->ldap_options = LDAP_OPT_REFERRALS;
4279 # else /* LDAP_REFERRALS */
4280 	lmap->ldap_options = 0;
4281 # endif /* LDAP_REFERRALS */
4282 	lmap->ldap_binddn = NULL;
4283 	lmap->ldap_secret = NULL;
4284 	lmap->ldap_method = LDAP_AUTH_SIMPLE;
4285 	lmap->ldap_base = NULL;
4286 	lmap->ldap_scope = LDAP_SCOPE_SUBTREE;
4287 	lmap->ldap_attrsonly = LDAPMAP_FALSE;
4288 	lmap->ldap_timeout.tv_sec = 0;
4289 	lmap->ldap_timeout.tv_usec = 0;
4290 	lmap->ldap_ld = NULL;
4291 	lmap->ldap_filter = NULL;
4292 	lmap->ldap_attr[0] = NULL;
4293 	lmap->ldap_res = NULL;
4294 }
4295 /*
4296 **  LDAPMAP_SET_DEFAULTS -- Read default map spec from LDAPDefaults in .cf
4297 **
4298 **	Parameters:
4299 **		spec -- map argument string from LDAPDefaults option
4300 **
4301 **	Returns:
4302 **		None.
4303 **
4304 */
4305 
4306 void
4307 ldapmap_set_defaults(spec)
4308 	char *spec;
4309 {
4310 	MAP map;
4311 
4312 	/* Allocate and set the default values */
4313 	if (LDAPDefaults == NULL)
4314 		LDAPDefaults = (LDAPMAP_STRUCT *) xalloc(sizeof *LDAPDefaults);
4315 	ldapmap_clear(LDAPDefaults);
4316 
4317 	memset(&map, '\0', sizeof map);
4318 	map.map_db1 = (ARBPTR_T) LDAPDefaults;
4319 
4320 	(void) ldapmap_parseargs(&map, spec);
4321 
4322 	/* These should never be set in LDAPDefaults */
4323 	if (map.map_mflags != (MF_TRY0NULL|MF_TRY1NULL) ||
4324 	    map.map_spacesub != SpaceSub ||
4325 	    map.map_app != NULL ||
4326 	    map.map_tapp != NULL)
4327 	{
4328 		syserr("readcf: option LDAPDefaultSpec: Do not set non-LDAP specific flags");
4329 		if (map.map_app != NULL)
4330 		{
4331 			free(map.map_app);
4332 			map.map_app = NULL;
4333 		}
4334 		if (map.map_tapp != NULL)
4335 		{
4336 			free(map.map_tapp);
4337 			map.map_tapp = NULL;
4338 		}
4339 	}
4340 
4341 	if (LDAPDefaults->ldap_filter != NULL)
4342 	{
4343 		syserr("readcf: option LDAPDefaultSpec: Do not set the LDAP search filter");
4344 		/* don't free, it isn't malloc'ed in parseargs */
4345 		LDAPDefaults->ldap_filter = NULL;
4346 	}
4347 
4348 	if (LDAPDefaults->ldap_attr[0] != NULL)
4349 	{
4350 		syserr("readcf: option LDAPDefaultSpec: Do not set the requested LDAP attributes");
4351 		/* don't free, they aren't malloc'ed in parseargs */
4352 		LDAPDefaults->ldap_attr[0] = NULL;
4353 	}
4354 }
4355 #endif /* LDAPMAP */
4356 /*
4357 **  PH map
4358 */
4359 
4360 #ifdef PH_MAP
4361 
4362 /*
4363 **  Support for the CCSO Nameserver (ph/qi).
4364 **  This code is intended to replace the so-called "ph mailer".
4365 **  Contributed by Mark D. Roth <roth@uiuc.edu>.  Contact him for support.
4366 */
4367 
4368 # include <qiapi.h>
4369 # include <qicode.h>
4370 
4371 /*
4372 **  PH_MAP_PARSEARGS -- parse ph map definition args.
4373 */
4374 
4375 bool
4376 ph_map_parseargs(map, args)
4377 	MAP *map;
4378 	char *args;
4379 {
4380 	int i;
4381 	register int done;
4382 	PH_MAP_STRUCT *pmap = NULL;
4383 	register char *p = args;
4384 
4385 	pmap = (PH_MAP_STRUCT *) xalloc(sizeof *pmap);
4386 
4387 	/* defaults */
4388 	pmap->ph_servers = NULL;
4389 	pmap->ph_field_list = NULL;
4390 	pmap->ph_to_server = NULL;
4391 	pmap->ph_from_server = NULL;
4392 	pmap->ph_sockfd = -1;
4393 	pmap->ph_timeout = 0;
4394 
4395 	map->map_mflags |= MF_TRY0NULL|MF_TRY1NULL;
4396 	for (;;)
4397 	{
4398 		while (isascii(*p) && isspace(*p))
4399 			p++;
4400 		if (*p != '-')
4401 			break;
4402 		switch (*++p)
4403 		{
4404 		  case 'N':
4405 			map->map_mflags |= MF_INCLNULL;
4406 			map->map_mflags &= ~MF_TRY0NULL;
4407 			break;
4408 
4409 		  case 'O':
4410 			map->map_mflags &= ~MF_TRY1NULL;
4411 			break;
4412 
4413 		  case 'o':
4414 			map->map_mflags |= MF_OPTIONAL;
4415 			break;
4416 
4417 		  case 'f':
4418 			map->map_mflags |= MF_NOFOLDCASE;
4419 			break;
4420 
4421 		  case 'm':
4422 			map->map_mflags |= MF_MATCHONLY;
4423 			break;
4424 
4425 		  case 'A':
4426 			map->map_mflags |= MF_APPEND;
4427 			break;
4428 
4429 		  case 'q':
4430 			map->map_mflags |= MF_KEEPQUOTES;
4431 			break;
4432 
4433 		  case 't':
4434 			map->map_mflags |= MF_NODEFER;
4435 			break;
4436 
4437 		  case 'a':
4438 			map->map_app = ++p;
4439 			break;
4440 
4441 		  case 'T':
4442 			map->map_tapp = ++p;
4443 			break;
4444 
4445 #if _FFR_PHMAP_TIMEOUT
4446 		  case 'l':
4447 			while (isascii(*++p) && isspace(*p))
4448 				continue;
4449 			pmap->ph_timeout = atoi(p);
4450 			break;
4451 #endif /* _FFR_PHMAP_TIMEOUT */
4452 
4453 		  case 'S':
4454 			map->map_spacesub = *++p;
4455 			break;
4456 
4457 		  case 'D':
4458 			map->map_mflags |= MF_DEFER;
4459 			break;
4460 
4461 		  case 'h':		/* PH server list */
4462 			while (isascii(*++p) && isspace(*p))
4463 				continue;
4464 			pmap->ph_servers = p;
4465 			break;
4466 
4467 		  case 'v':		/* fields to search for */
4468 			while (isascii(*++p) && isspace(*p))
4469 				continue;
4470 			pmap->ph_field_list = p;
4471 			break;
4472 
4473 		  default:
4474 			syserr("ph_map_parseargs: unknown option -%c\n", *p);
4475 		}
4476 
4477 		/* try to account for quoted strings */
4478 		done = isascii(*p) && isspace(*p);
4479 		while (*p != '\0' && !done)
4480 		{
4481 			if (*p == '"')
4482 			{
4483 				while (*++p != '"' && *p != '\0')
4484 					continue;
4485 				if (*p != '\0')
4486 					p++;
4487 			}
4488 			else
4489 				p++;
4490 			done = isascii(*p) && isspace(*p);
4491 		}
4492 
4493 		if (*p != '\0')
4494 			*p++ = '\0';
4495 	}
4496 
4497 	if (map->map_app != NULL)
4498 		map->map_app = newstr(ph_map_dequote(map->map_app));
4499 	if (map->map_tapp != NULL)
4500 		map->map_tapp = newstr(ph_map_dequote(map->map_tapp));
4501 
4502 	if (pmap->ph_field_list != NULL)
4503 		pmap->ph_field_list = newstr(ph_map_dequote(pmap->ph_field_list));
4504 	else
4505 		pmap->ph_field_list = DEFAULT_PH_MAP_FIELDS;
4506 
4507 	if (pmap->ph_servers != NULL)
4508 		pmap->ph_servers = newstr(ph_map_dequote(pmap->ph_servers));
4509 	else
4510 	{
4511 		syserr("ph_map_parseargs: -h flag is required");
4512 		return FALSE;
4513 	}
4514 
4515 	map->map_db1 = (ARBPTR_T) pmap;
4516 	return TRUE;
4517 }
4518 
4519 #if _FFR_PHMAP_TIMEOUT
4520 /*
4521 **  PH_MAP_CLOSE -- close the connection to the ph server
4522 */
4523 
4524 static void
4525 ph_map_safeclose(map)
4526 	MAP *map;
4527 {
4528 	int save_errno = errno;
4529 	PH_MAP_STRUCT *pmap;
4530 
4531 	pmap = (PH_MAP_STRUCT *)map->map_db1;
4532 
4533 	if (pmap->ph_sockfd != -1)
4534 	{
4535 		(void) close(pmap->ph_sockfd);
4536 		pmap->ph_sockfd = -1;
4537 	}
4538 	if (pmap->ph_from_server != NULL)
4539 	{
4540 		(void) fclose(pmap->ph_from_server);
4541 		pmap->ph_from_server = NULL;
4542 	}
4543 	if (pmap->ph_to_server != NULL)
4544 	{
4545 		(void) fclose(pmap->ph_to_server);
4546 		pmap->ph_to_server = NULL;
4547 	}
4548 	map->map_mflags &= ~(MF_OPEN|MF_WRITABLE);
4549 	errno = save_errno;
4550 }
4551 
4552 void
4553 ph_map_close(map)
4554 	MAP *map;
4555 {
4556 	PH_MAP_STRUCT *pmap;
4557 
4558 	pmap = (PH_MAP_STRUCT *)map->map_db1;
4559 	(void) fprintf(pmap->ph_to_server, "quit\n");
4560 	(void) fflush(pmap->ph_to_server);
4561 	ph_map_safeclose(map);
4562 }
4563 
4564 static jmp_buf  PHTimeout;
4565 
4566 /* ARGSUSED */
4567 static void
4568 ph_timeout_func(sig_no)
4569 	int sig_no;
4570 {
4571 	longjmp(PHTimeout, 1);
4572 }
4573 #else /* _FFR_PHMAP_TIMEOUT */
4574 /*
4575 **  PH_MAP_CLOSE -- close the connection to the ph server
4576 */
4577 
4578 void
4579 ph_map_close(map)
4580 	MAP *map;
4581 {
4582 	PH_MAP_STRUCT *pmap;
4583 
4584 	pmap = (PH_MAP_STRUCT *)map->map_db1;
4585 	CloseQi(pmap->ph_to_server, pmap->ph_from_server);
4586 	pmap->ph_to_server = NULL;
4587 	pmap->ph_from_server = NULL;
4588 }
4589 #endif /* _FFR_PHMAP_TIMEOUT */
4590 
4591 /*
4592 **  PH_MAP_OPEN -- sub for opening PH map
4593 */
4594 bool
4595 ph_map_open(map, mode)
4596 	MAP *map;
4597 	int mode;
4598 {
4599 #if !_FFR_PHMAP_TIMEOUT
4600 	int save_errno = 0;
4601 #endif /* !_FFR_PHMAP_TIMEOUT */
4602 	int j;
4603 	char *hostlist, *tmp;
4604 	QIR *server_data = NULL;
4605 	PH_MAP_STRUCT *pmap;
4606 #if _FFR_PHMAP_TIMEOUT
4607 	register EVENT *ev = NULL;
4608 #endif /* _FFR_PHMAP_TIMEOUT */
4609 
4610 	if (tTd(38, 2))
4611 		dprintf("ph_map_open(%s)\n", map->map_mname);
4612 
4613 	mode &= O_ACCMODE;
4614 	if (mode != O_RDONLY)
4615 	{
4616 		/* issue a pseudo-error message */
4617 # ifdef ENOSYS
4618 		errno = ENOSYS;
4619 # else /* ENOSYS */
4620 #  ifdef EFTYPE
4621 		errno = EFTYPE;
4622 #  else /* EFTYPE */
4623 		errno = ENXIO;
4624 #  endif /* EFTYPE */
4625 # endif /* ENOSYS */
4626 		return FALSE;
4627 	}
4628 
4629 	if (CurEnv != NULL && CurEnv->e_sendmode == SM_DEFER &&
4630 	    bitset(MF_DEFER, map->map_mflags))
4631 	{
4632 		if (tTd(9, 1))
4633 			dprintf("ph_map_open(%s) => DEFERRED\n",
4634 			        map->map_mname);
4635 
4636 		/*
4637 		** Unset MF_DEFER here so that map_lookup() returns
4638 		** a temporary failure using the bogus map and
4639 		** map->map_tapp instead of the default permanent error.
4640 		*/
4641 
4642 		map->map_mflags &= ~MF_DEFER;
4643 		return FALSE;
4644 	}
4645 
4646 	pmap = (PH_MAP_STRUCT *)map->map_db1;
4647 
4648 	hostlist = newstr(pmap->ph_servers);
4649 	tmp = strtok(hostlist, " ");
4650 	do
4651 	{
4652 #if _FFR_PHMAP_TIMEOUT
4653 		if (pmap->ph_timeout != 0)
4654 		{
4655 			if (setjmp(PHTimeout) != 0)
4656 			{
4657 				ev = NULL;
4658 				if (LogLevel > 1)
4659 					sm_syslog(LOG_NOTICE, CurEnv->e_id,
4660 						  "timeout connecting to PH server %.100s",
4661 						  tmp);
4662 # ifdef ETIMEDOUT
4663 				errno = ETIMEDOUT;
4664 # else /* ETIMEDOUT */
4665 				errno = EAGAIN;
4666 # endif /* ETIMEDOUT */
4667 				goto ph_map_open_abort;
4668 			}
4669 			ev = setevent(pmap->ph_timeout, ph_timeout_func, 0);
4670 		}
4671 		if (!OpenQiSock(tmp, &(pmap->ph_sockfd)) &&
4672 		    !Sock2FILEs(pmap->ph_sockfd, &(pmap->ph_to_server),
4673 				&(pmap->ph_from_server)) &&
4674 		    fprintf(pmap->ph_to_server, "id sendmail+phmap\n") >= 0 &&
4675 		    fflush(pmap->ph_to_server) == 0 &&
4676 		    (server_data = ReadQi(pmap->ph_from_server, &j)) != NULL &&
4677 		    server_data->code == 200)
4678 		{
4679 			if (ev != NULL)
4680 				clrevent(ev);
4681 			FreeQIR(server_data);
4682 #else /* _FFR_PHMAP_TIMEOUT */
4683 		if (OpenQi(tmp, &(pmap->ph_to_server),
4684 			   &(pmap->ph_from_server)) >= 0)
4685 		{
4686 			if (fprintf(pmap->ph_to_server,
4687 				    "id sendmail+phmap\n") < 0 ||
4688 			    fflush(pmap->ph_to_server) < 0 ||
4689 			    (server_data = ReadQi(pmap->ph_from_server,
4690 						  &j)) == NULL ||
4691 			    server_data->code != 200)
4692 			{
4693 				save_errno = errno;
4694 				CloseQi(pmap->ph_to_server,
4695 					pmap->ph_from_server);
4696 				continue;
4697 			}
4698 			if (server_data != NULL)
4699 				FreeQIR(server_data);
4700 #endif /* _FFR_PHMAP_TIMEOUT */
4701 			free(hostlist);
4702 			return TRUE;
4703 		}
4704 #if _FFR_PHMAP_TIMEOUT
4705   ph_map_open_abort:
4706 		if (ev != NULL)
4707 			clrevent(ev);
4708 		ph_map_safeclose(map);
4709 		if (server_data != NULL)
4710 		{
4711 			FreeQIR(server_data);
4712 			server_data = NULL;
4713 		}
4714 #else /* _FFR_PHMAP_TIMEOUT */
4715 		save_errno = errno;
4716 #endif /* _FFR_PHMAP_TIMEOUT */
4717 	} while (tmp = strtok(NULL, " "));
4718 
4719 #if !_FFR_PHMAP_TIMEOUT
4720 	errno = save_errno;
4721 #endif /* !_FFR_PHMAP_TIMEOUT */
4722 	if (bitset(MF_NODEFER, map->map_mflags))
4723 	{
4724 		if (errno == 0)
4725 			errno = EAGAIN;
4726 		syserr("ph_map_open: %s: cannot connect to PH server",
4727 		       map->map_mname);
4728 	}
4729 	else if (!bitset(MF_OPTIONAL, map->map_mflags) && LogLevel > 1)
4730 		sm_syslog(LOG_NOTICE, CurEnv->e_id,
4731 			  "ph_map_open: %s: cannot connect to PH server",
4732 			  map->map_mname);
4733 	free(hostlist);
4734 	return FALSE;
4735 }
4736 
4737 /*
4738 **  PH_MAP_LOOKUP -- look up key from ph server
4739 */
4740 
4741 #if _FFR_PHMAP_TIMEOUT
4742 # define MAX_PH_FIELDS	20
4743 #endif /* _FFR_PHMAP_TIMEOUT */
4744 
4745 char *
4746 ph_map_lookup(map, key, args, pstat)
4747 	MAP *map;
4748 	char *key;
4749 	char **args;
4750 	int *pstat;
4751 {
4752 	int j;
4753 	size_t sz;
4754 	char *tmp, *tmp2;
4755 	char *message = NULL, *field = NULL, *fmtkey;
4756 	QIR *server_data = NULL;
4757 	QIR *qirp;
4758 	char keybuf[MAXKEY + 1], fieldbuf[101];
4759 #if _FFR_PHMAP_TIMEOUT
4760 	QIR *hold_data[MAX_PH_FIELDS];
4761 	int hold_data_idx = 0;
4762 	register EVENT *ev = NULL;
4763 #endif /* _FFR_PHMAP_TIMEOUT */
4764 	PH_MAP_STRUCT *pmap;
4765 
4766 	pmap = (PH_MAP_STRUCT *)map->map_db1;
4767 
4768 	*pstat = EX_OK;
4769 
4770 #if _FFR_PHMAP_TIMEOUT
4771 	if (pmap->ph_timeout != 0)
4772 	{
4773 		if (setjmp(PHTimeout) != 0)
4774 		{
4775 			ev = NULL;
4776 			if (LogLevel > 1)
4777 				sm_syslog(LOG_NOTICE, CurEnv->e_id,
4778 					  "timeout during PH lookup of %.100s",
4779 					  key);
4780 # ifdef ETIMEDOUT
4781 			errno = ETIMEDOUT;
4782 # else /* ETIMEDOUT */
4783 			errno = 0;
4784 # endif /* ETIMEDOUT */
4785 			*pstat = EX_TEMPFAIL;
4786 			goto ph_map_lookup_abort;
4787 		}
4788 		ev = setevent(pmap->ph_timeout, ph_timeout_func, 0);
4789 	}
4790 
4791 #endif /* _FFR_PHMAP_TIMEOUT */
4792 	/* check all relevant fields */
4793 	tmp = pmap->ph_field_list;
4794 	do
4795 	{
4796 #if _FFR_PHMAP_TIMEOUT
4797 		server_data = NULL;
4798 #endif /* _FFR_PHMAP_TIMEOUT */
4799 		while (isascii(*tmp) && isspace(*tmp))
4800 			tmp++;
4801 		if (*tmp == '\0')
4802 			break;
4803 		sz = strcspn(tmp, " ") + 1;
4804 		if (sz > sizeof fieldbuf)
4805 			sz = sizeof fieldbuf;
4806 		(void) strlcpy(fieldbuf, tmp, sz);
4807 		field = fieldbuf;
4808 		tmp += sz;
4809 
4810 		(void) strlcpy(keybuf, key, sizeof keybuf);
4811 		fmtkey = keybuf;
4812 		if (strcmp(field, "alias") == 0)
4813 		{
4814 			/*
4815 			**  for alias lookups, replace any punctuation
4816 			**  characters with '-'
4817 			*/
4818 
4819 			for (tmp2 = fmtkey; *tmp2 !=  '\0'; tmp2++)
4820 			{
4821 				if (isascii(*tmp2) && ispunct(*tmp2))
4822 					*tmp2 = '-';
4823 			}
4824 			tmp2 = field;
4825 		}
4826 		else if (strcmp(field,"spacedname") == 0)
4827 		{
4828 			/*
4829 			**  for "spaced" name lookups, replace any
4830 			**  punctuation characters with a space
4831 			*/
4832 
4833 			for (tmp2 = fmtkey; *tmp2 != '\0'; tmp2++)
4834 			{
4835 				if (isascii(*tmp2) && ispunct(*tmp2) &&
4836 				    *tmp2 != '*')
4837 					*tmp2 = ' ';
4838 			}
4839 			tmp2 = &(field[6]);
4840 		}
4841 		else
4842 			tmp2 = field;
4843 
4844 		if (LogLevel > 9)
4845 			sm_syslog(LOG_NOTICE, CurEnv->e_id,
4846 				  "ph_map_lookup: query %s=\"%s\" return email",
4847 				  tmp2, fmtkey);
4848 		if (tTd(38, 20))
4849 			dprintf("ph_map_lookup: query %s=\"%s\" return email\n",
4850 				tmp2, fmtkey);
4851 
4852 		j = 0;
4853 
4854 		if (fprintf(pmap->ph_to_server, "query %s=%s return email\n",
4855 			    tmp2, fmtkey) < 0)
4856 			message = "qi query command failed";
4857 		else if (fflush(pmap->ph_to_server) < 0)
4858 			message = "qi fflush failed";
4859 		else if ((server_data = ReadQi(pmap->ph_from_server,
4860 					       &j)) == NULL)
4861 			message = "ReadQi() returned NULL";
4862 
4863 #if _FFR_PHMAP_TIMEOUT
4864 		if ((hold_data[hold_data_idx] = server_data) != NULL)
4865 		{
4866 			/* save pointer for later free() */
4867 			hold_data_idx++;
4868 		}
4869 #endif /* _FFR_PHMAP_TIMEOUT */
4870 
4871 		if (server_data == NULL ||
4872 		    (server_data->code >= 400 &&
4873 		     server_data->code < 500))
4874 		{
4875 			/* temporary failure */
4876 			*pstat = EX_TEMPFAIL;
4877 #if _FFR_PHMAP_TIMEOUT
4878 			break;
4879 #else /* _FFR_PHMAP_TIMEOUT */
4880 			if (server_data != NULL)
4881 			{
4882 				FreeQIR(server_data);
4883 				server_data = NULL;
4884 			}
4885 			return NULL;
4886 #endif /* _FFR_PHMAP_TIMEOUT */
4887 		}
4888 
4889 		/*
4890 		**  if we found a single match, break out.
4891 		**  otherwise, try the next field.
4892 		*/
4893 
4894 		if (j == 1)
4895 			break;
4896 
4897 		/*
4898 		**  check for a single response which is an error:
4899 		**  ReadQi() doesn't set j on error responses,
4900 		**  but we should stop here instead of moving on if
4901 		**  it happens (e.g., alias found but email field empty)
4902 		*/
4903 
4904 		for (qirp = server_data;
4905 		     qirp != NULL && qirp->code < 0;
4906 		     qirp++)
4907 		{
4908 			if (tTd(38, 20))
4909 				dprintf("ph_map_lookup: QIR: %d:%d:%d:%s\n",
4910 					qirp->code, qirp->subcode, qirp->field,
4911 					(qirp->message ? qirp->message
4912 					 : "[NULL]"));
4913 			if (qirp->code <= -500)
4914 			{
4915 				j = 0;
4916 				goto ph_map_lookup_abort;
4917 			}
4918 		}
4919 
4920 #if _FFR_PHMAP_TIMEOUT
4921 	} while (*tmp != '\0' && hold_data_idx < MAX_PH_FIELDS);
4922 #else /* _FFR_PHMAP_TIMEOUT */
4923 	} while (*tmp != '\0');
4924 #endif /* _FFR_PHMAP_TIMEOUT */
4925 
4926   ph_map_lookup_abort:
4927 #if _FFR_PHMAP_TIMEOUT
4928 	if (ev != NULL)
4929 		clrevent(ev);
4930 
4931 	/*
4932 	**  Return EX_TEMPFAIL if the timer popped
4933 	**  or we got a temporary PH error
4934 	*/
4935 
4936 	if (*pstat == EX_TEMPFAIL)
4937 		ph_map_safeclose(map);
4938 
4939 	/* if we didn't find a single match, bail out */
4940 	if (*pstat == EX_OK && j != 1)
4941 		*pstat = EX_UNAVAILABLE;
4942 
4943 	if (*pstat == EX_OK)
4944 	{
4945 		/*
4946 		** skip leading whitespace and chop at first address
4947 		*/
4948 
4949 		for (tmp = server_data->message;
4950 		     isascii(*tmp) && isspace(*tmp);
4951 		     tmp++)
4952 			continue;
4953 
4954 		for (tmp2 = tmp; *tmp2 != '\0'; tmp2++)
4955 		{
4956 			if (isascii(*tmp2) && isspace(*tmp2))
4957 			{
4958 				*tmp2 = '\0';
4959 				break;
4960 			}
4961 		}
4962 
4963 		if (tTd(38,20))
4964 			dprintf("ph_map_lookup: %s => %s\n", key, tmp);
4965 
4966 		if (bitset(MF_MATCHONLY, map->map_mflags))
4967 			message = map_rewrite(map, key, strlen(key), NULL);
4968 		else
4969 			message = map_rewrite(map, tmp, strlen(tmp), args);
4970 	}
4971 
4972 	/*
4973 	**  Deferred free() of returned server_data values
4974 	**  the deferral is to avoid the risk of a free() being
4975 	**  interrupted by the event timer.  By now the timeout event
4976 	**  has been cleared and none of the data is still in use.
4977 	*/
4978 
4979 	while (--hold_data_idx >= 0)
4980 	{
4981 		if (hold_data[hold_data_idx] != NULL)
4982 			FreeQIR(hold_data[hold_data_idx]);
4983 	}
4984 
4985 	if (*pstat == EX_OK)
4986 		return message;
4987 
4988 	return NULL;
4989 #else /* _FFR_PHMAP_TIMEOUT */
4990 	/* if we didn't find a single match, bail out */
4991 	if (j != 1)
4992 	{
4993 		*pstat = EX_UNAVAILABLE;
4994 		if (server_data != NULL)
4995 		{
4996 			FreeQIR(server_data);
4997 			server_data = NULL;
4998 		}
4999 		return NULL;
5000 	}
5001 
5002 	/*
5003 	** skip leading whitespace and chop at first address
5004 	*/
5005 
5006 	for (tmp = server_data->message;
5007 	     isascii(*tmp) && isspace(*tmp);
5008 	     tmp++)
5009 		continue;
5010 
5011 	for (tmp2 = tmp; *tmp2 != '\0'; tmp2++)
5012 	{
5013 		if (isascii(*tmp2) && isspace(*tmp2))
5014 		{
5015 			*tmp2 = '\0';
5016 			break;
5017 		}
5018 	}
5019 
5020 	if (tTd(38,20))
5021 		dprintf("ph_map_lookup: %s => %s\n", key, tmp);
5022 
5023 	if (bitset(MF_MATCHONLY, map->map_mflags))
5024 		message = map_rewrite(map, key, strlen(key), NULL);
5025 	else
5026 		message = map_rewrite(map, tmp, strlen(tmp), args);
5027 	if (server_data != NULL)
5028 	{
5029 		FreeQIR(server_data);
5030 		server_data = NULL;
5031 	}
5032 	return message;
5033 #endif /* _FFR_PHMAP_TIMEOUT */
5034 }
5035 #endif /* PH_MAP */
5036 /*
5037 **  syslog map
5038 */
5039 
5040 #define map_prio	map_lockfd	/* overload field */
5041 
5042 /*
5043 **  SYSLOG_MAP_PARSEARGS -- check for priority level to syslog messages.
5044 */
5045 
5046 bool
5047 syslog_map_parseargs(map, args)
5048 	MAP *map;
5049 	char *args;
5050 {
5051 	char *p = args;
5052 	char *priority = NULL;
5053 
5054 	/* there is no check whether there is really an argument */
5055 	while (*p != '\0')
5056 	{
5057 		while (isascii(*p) && isspace(*p))
5058 			p++;
5059 		if (*p != '-')
5060 			break;
5061 		++p;
5062 		if (*p == 'D')
5063 		{
5064 			map->map_mflags |= MF_DEFER;
5065 			++p;
5066 		}
5067 		else if (*p == 'S')
5068 		{
5069 			map->map_spacesub = *++p;
5070 			if (*p != '\0')
5071 				p++;
5072 		}
5073 		else if (*p == 'L')
5074 		{
5075 			while (*++p != '\0' && isascii(*p) && isspace(*p))
5076 				continue;
5077 			if (*p == '\0')
5078 				break;
5079 			priority = p;
5080 			while (*p != '\0' && !(isascii(*p) && isspace(*p)))
5081 				p++;
5082 			if (*p != '\0')
5083 				*p++ = '\0';
5084 		}
5085 		else
5086 		{
5087 			syserr("Illegal option %c map syslog", *p);
5088 			++p;
5089 		}
5090 	}
5091 
5092 	if (priority == NULL)
5093 		map->map_prio = LOG_INFO;
5094 	else
5095 	{
5096 		if (strncasecmp("LOG_", priority, 4) == 0)
5097 			priority += 4;
5098 
5099 #ifdef LOG_EMERG
5100 		if (strcasecmp("EMERG", priority) == 0)
5101 			map->map_prio = LOG_EMERG;
5102 		else
5103 #endif /* LOG_EMERG */
5104 #ifdef LOG_ALERT
5105 		if (strcasecmp("ALERT", priority) == 0)
5106 			map->map_prio = LOG_ALERT;
5107 		else
5108 #endif /* LOG_ALERT */
5109 #ifdef LOG_CRIT
5110 		if (strcasecmp("CRIT", priority) == 0)
5111 			map->map_prio = LOG_CRIT;
5112 		else
5113 #endif /* LOG_CRIT */
5114 #ifdef LOG_ERR
5115 		if (strcasecmp("ERR", priority) == 0)
5116 			map->map_prio = LOG_ERR;
5117 		else
5118 #endif /* LOG_ERR */
5119 #ifdef LOG_WARNING
5120 		if (strcasecmp("WARNING", priority) == 0)
5121 			map->map_prio = LOG_WARNING;
5122 		else
5123 #endif /* LOG_WARNING */
5124 #ifdef LOG_NOTICE
5125 		if (strcasecmp("NOTICE", priority) == 0)
5126 			map->map_prio = LOG_NOTICE;
5127 		else
5128 #endif /* LOG_NOTICE */
5129 #ifdef LOG_INFO
5130 		if (strcasecmp("INFO", priority) == 0)
5131 			map->map_prio = LOG_INFO;
5132 		else
5133 #endif /* LOG_INFO */
5134 #ifdef LOG_DEBUG
5135 		if (strcasecmp("DEBUG", priority) == 0)
5136 			map->map_prio = LOG_DEBUG;
5137 		else
5138 #endif /* LOG_DEBUG */
5139 		{
5140 			syserr("syslog_map_parseargs: Unknown priority %s\n",
5141 			       priority);
5142 			return FALSE;
5143 		}
5144 	}
5145 	return TRUE;
5146 }
5147 
5148 /*
5149 **  SYSLOG_MAP_LOOKUP -- rewrite and syslog message.  Always return empty string
5150 */
5151 
5152 char *
5153 syslog_map_lookup(map, string, args, statp)
5154 	MAP *map;
5155 	char *string;
5156 	char **args;
5157 	int *statp;
5158 {
5159 	char *ptr = map_rewrite(map, string, strlen(string), args);
5160 
5161 	if (ptr != NULL)
5162 	{
5163 		if (tTd(38, 20))
5164 			dprintf("syslog_map_lookup(%s (priority %d): %s\n",
5165 				map->map_mname, map->map_prio, ptr);
5166 
5167 		sm_syslog(map->map_prio, CurEnv->e_id, "%s", ptr);
5168 	}
5169 
5170 	*statp = EX_OK;
5171 	return "";
5172 }
5173 
5174 /*
5175 **  HESIOD Modules
5176 */
5177 
5178 #ifdef HESIOD
5179 
5180 bool
5181 hes_map_open(map, mode)
5182 	MAP *map;
5183 	int mode;
5184 {
5185 	if (tTd(38, 2))
5186 		dprintf("hes_map_open(%s, %s, %d)\n",
5187 			map->map_mname, map->map_file, mode);
5188 
5189 	if (mode != O_RDONLY)
5190 	{
5191 		/* issue a pseudo-error message */
5192 # ifdef ENOSYS
5193 		errno = ENOSYS;
5194 # else /* ENOSYS */
5195 #  ifdef EFTYPE
5196 		errno = EFTYPE;
5197 #  else /* EFTYPE */
5198 		errno = ENXIO;
5199 #  endif /* EFTYPE */
5200 # endif /* ENOSYS */
5201 		return FALSE;
5202 	}
5203 
5204 # ifdef HESIOD_INIT
5205 	if (HesiodContext != NULL || hesiod_init(&HesiodContext) == 0)
5206 		return TRUE;
5207 
5208 	if (!bitset(MF_OPTIONAL, map->map_mflags))
5209 		syserr("421 4.0.0 cannot initialize Hesiod map (%s)",
5210 			errstring(errno));
5211 	return FALSE;
5212 # else /* HESIOD_INIT */
5213 	if (hes_error() == HES_ER_UNINIT)
5214 		hes_init();
5215 	switch (hes_error())
5216 	{
5217 	  case HES_ER_OK:
5218 	  case HES_ER_NOTFOUND:
5219 		return TRUE;
5220 	}
5221 
5222 	if (!bitset(MF_OPTIONAL, map->map_mflags))
5223 		syserr("421 4.0.0 cannot initialize Hesiod map (%d)", hes_error());
5224 
5225 	return FALSE;
5226 # endif /* HESIOD_INIT */
5227 }
5228 
5229 char *
5230 hes_map_lookup(map, name, av, statp)
5231 	MAP *map;
5232 	char *name;
5233 	char **av;
5234 	int *statp;
5235 {
5236 	char **hp;
5237 
5238 	if (tTd(38, 20))
5239 		dprintf("hes_map_lookup(%s, %s)\n", map->map_file, name);
5240 
5241 	if (name[0] == '\\')
5242 	{
5243 		char *np;
5244 		int nl;
5245 		char nbuf[MAXNAME];
5246 
5247 		nl = strlen(name);
5248 		if (nl < sizeof nbuf - 1)
5249 			np = nbuf;
5250 		else
5251 			np = xalloc(strlen(name) + 2);
5252 		np[0] = '\\';
5253 		(void) strlcpy(&np[1], name, (sizeof nbuf) - 1);
5254 # ifdef HESIOD_INIT
5255 		hp = hesiod_resolve(HesiodContext, np, map->map_file);
5256 # else /* HESIOD_INIT */
5257 		hp = hes_resolve(np, map->map_file);
5258 # endif /* HESIOD_INIT */
5259 		if (np != nbuf)
5260 			free(np);
5261 	}
5262 	else
5263 	{
5264 # ifdef HESIOD_INIT
5265 		hp = hesiod_resolve(HesiodContext, name, map->map_file);
5266 # else /* HESIOD_INIT */
5267 		hp = hes_resolve(name, map->map_file);
5268 # endif /* HESIOD_INIT */
5269 	}
5270 # ifdef HESIOD_INIT
5271 	if (hp == NULL)
5272 		return NULL;
5273 	if (*hp == NULL)
5274 	{
5275 		hesiod_free_list(HesiodContext, hp);
5276 		switch (errno)
5277 		{
5278 		  case ENOENT:
5279 			  *statp = EX_NOTFOUND;
5280 			  break;
5281 		  case ECONNREFUSED:
5282 		  case EMSGSIZE:
5283 			  *statp = EX_TEMPFAIL;
5284 			  break;
5285 		  case ENOMEM:
5286 		  default:
5287 			  *statp = EX_UNAVAILABLE;
5288 			  break;
5289 		}
5290 		return NULL;
5291 	}
5292 # else /* HESIOD_INIT */
5293 	if (hp == NULL || hp[0] == NULL)
5294 	{
5295 		switch (hes_error())
5296 		{
5297 		  case HES_ER_OK:
5298 			*statp = EX_OK;
5299 			break;
5300 
5301 		  case HES_ER_NOTFOUND:
5302 			*statp = EX_NOTFOUND;
5303 			break;
5304 
5305 		  case HES_ER_CONFIG:
5306 			*statp = EX_UNAVAILABLE;
5307 			break;
5308 
5309 		  case HES_ER_NET:
5310 			*statp = EX_TEMPFAIL;
5311 			break;
5312 		}
5313 		return NULL;
5314 	}
5315 # endif /* HESIOD_INIT */
5316 
5317 	if (bitset(MF_MATCHONLY, map->map_mflags))
5318 		return map_rewrite(map, name, strlen(name), NULL);
5319 	else
5320 		return map_rewrite(map, hp[0], strlen(hp[0]), av);
5321 }
5322 
5323 #endif /* HESIOD */
5324 /*
5325 **  NeXT NETINFO Modules
5326 */
5327 
5328 #if NETINFO
5329 
5330 # define NETINFO_DEFAULT_DIR		"/aliases"
5331 # define NETINFO_DEFAULT_PROPERTY	"members"
5332 
5333 /*
5334 **  NI_MAP_OPEN -- open NetInfo Aliases
5335 */
5336 
5337 bool
5338 ni_map_open(map, mode)
5339 	MAP *map;
5340 	int mode;
5341 {
5342 	if (tTd(38, 2))
5343 		dprintf("ni_map_open(%s, %s, %d)\n",
5344 			map->map_mname, map->map_file, mode);
5345 	mode &= O_ACCMODE;
5346 
5347 	if (*map->map_file == '\0')
5348 		map->map_file = NETINFO_DEFAULT_DIR;
5349 
5350 	if (map->map_valcolnm == NULL)
5351 		map->map_valcolnm = NETINFO_DEFAULT_PROPERTY;
5352 
5353 	if (map->map_coldelim == '\0' && bitset(MF_ALIAS, map->map_mflags))
5354 		map->map_coldelim = ',';
5355 
5356 	return TRUE;
5357 }
5358 
5359 
5360 /*
5361 **  NI_MAP_LOOKUP -- look up a datum in NetInfo
5362 */
5363 
5364 char *
5365 ni_map_lookup(map, name, av, statp)
5366 	MAP *map;
5367 	char *name;
5368 	char **av;
5369 	int *statp;
5370 {
5371 	char *res;
5372 	char *propval;
5373 
5374 	if (tTd(38, 20))
5375 		dprintf("ni_map_lookup(%s, %s)\n", map->map_mname, name);
5376 
5377 	propval = ni_propval(map->map_file, map->map_keycolnm, name,
5378 			     map->map_valcolnm, map->map_coldelim);
5379 
5380 	if (propval == NULL)
5381 		return NULL;
5382 
5383 	if (bitset(MF_MATCHONLY, map->map_mflags))
5384 		res = map_rewrite(map, name, strlen(name), NULL);
5385 	else
5386 		res = map_rewrite(map, propval, strlen(propval), av);
5387 	free(propval);
5388 	return res;
5389 }
5390 
5391 
5392 static bool
5393 ni_getcanonname(name, hbsize, statp)
5394 	char *name;
5395 	int hbsize;
5396 	int *statp;
5397 {
5398 	char *vptr;
5399 	char *ptr;
5400 	char nbuf[MAXNAME + 1];
5401 
5402 	if (tTd(38, 20))
5403 		dprintf("ni_getcanonname(%s)\n", name);
5404 
5405 	if (strlcpy(nbuf, name, sizeof nbuf) >= sizeof nbuf)
5406 	{
5407 		*statp = EX_UNAVAILABLE;
5408 		return FALSE;
5409 	}
5410 	shorten_hostname(nbuf);
5411 
5412 	/* we only accept single token search key */
5413 	if (strchr(nbuf, '.'))
5414 	{
5415 		*statp = EX_NOHOST;
5416 		return FALSE;
5417 	}
5418 
5419 	/* Do the search */
5420 	vptr = ni_propval("/machines", NULL, nbuf, "name", '\n');
5421 
5422 	if (vptr == NULL)
5423 	{
5424 		*statp = EX_NOHOST;
5425 		return FALSE;
5426 	}
5427 
5428 	/* Only want the first machine name */
5429 	if ((ptr = strchr(vptr, '\n')) != NULL)
5430 		*ptr = '\0';
5431 
5432 	if (hbsize >= strlen(vptr))
5433 	{
5434 		(void) strlcpy(name, vptr, hbsize);
5435 		free(vptr);
5436 		*statp = EX_OK;
5437 		return TRUE;
5438 	}
5439 	*statp = EX_UNAVAILABLE;
5440 	free(vptr);
5441 	return FALSE;
5442 }
5443 
5444 
5445 /*
5446 **  NI_PROPVAL -- NetInfo property value lookup routine
5447 **
5448 **	Parameters:
5449 **		keydir -- the NetInfo directory name in which to search
5450 **			for the key.
5451 **		keyprop -- the name of the property in which to find the
5452 **			property we are interested.  Defaults to "name".
5453 **		keyval -- the value for which we are really searching.
5454 **		valprop -- the property name for the value in which we
5455 **			are interested.
5456 **		sepchar -- if non-nil, this can be multiple-valued, and
5457 **			we should return a string separated by this
5458 **			character.
5459 **
5460 **	Returns:
5461 **		NULL -- if:
5462 **			1. the directory is not found
5463 **			2. the property name is not found
5464 **			3. the property contains multiple values
5465 **			4. some error occurred
5466 **		else -- the value of the lookup.
5467 **
5468 **	Example:
5469 **		To search for an alias value, use:
5470 **		  ni_propval("/aliases", "name", aliasname, "members", ',')
5471 **
5472 **	Notes:
5473 **		Caller should free the return value of ni_proval
5474 */
5475 
5476 # include <netinfo/ni.h>
5477 
5478 # define LOCAL_NETINFO_DOMAIN	"."
5479 # define PARENT_NETINFO_DOMAIN	".."
5480 # define MAX_NI_LEVELS		256
5481 
5482 char *
5483 ni_propval(keydir, keyprop, keyval, valprop, sepchar)
5484 	char *keydir;
5485 	char *keyprop;
5486 	char *keyval;
5487 	char *valprop;
5488 	int sepchar;
5489 {
5490 	char *propval = NULL;
5491 	int i;
5492 	int j, alen, l;
5493 	void *ni = NULL;
5494 	void *lastni = NULL;
5495 	ni_status nis;
5496 	ni_id nid;
5497 	ni_namelist ninl;
5498 	register char *p;
5499 	char keybuf[1024];
5500 
5501 	/*
5502 	**  Create the full key from the two parts.
5503 	**
5504 	**	Note that directory can end with, e.g., "name=" to specify
5505 	**	an alternate search property.
5506 	*/
5507 
5508 	i = strlen(keydir) + strlen(keyval) + 2;
5509 	if (keyprop != NULL)
5510 		i += strlen(keyprop) + 1;
5511 	if (i >= sizeof keybuf)
5512 		return NULL;
5513 	(void) strlcpy(keybuf, keydir, sizeof keybuf);
5514 	(void) strlcat(keybuf, "/", sizeof keybuf);
5515 	if (keyprop != NULL)
5516 	{
5517 		(void) strlcat(keybuf, keyprop, sizeof keybuf);
5518 		(void) strlcat(keybuf, "=", sizeof keybuf);
5519 	}
5520 	(void) strlcat(keybuf, keyval, sizeof keybuf);
5521 
5522 	if (tTd(38, 21))
5523 		dprintf("ni_propval(%s, %s, %s, %s, %d) keybuf='%s'\n",
5524 			keydir, keyprop, keyval, valprop, sepchar, keybuf);
5525 	/*
5526 	**  If the passed directory and property name are found
5527 	**  in one of netinfo domains we need to search (starting
5528 	**  from the local domain moving all the way back to the
5529 	**  root domain) set propval to the property's value
5530 	**  and return it.
5531 	*/
5532 
5533 	for (i = 0; i < MAX_NI_LEVELS && propval == NULL; i++)
5534 	{
5535 		if (i == 0)
5536 		{
5537 			nis = ni_open(NULL, LOCAL_NETINFO_DOMAIN, &ni);
5538 			if (tTd(38, 20))
5539 				dprintf("ni_open(LOCAL) = %d\n", nis);
5540 		}
5541 		else
5542 		{
5543 			if (lastni != NULL)
5544 				ni_free(lastni);
5545 			lastni = ni;
5546 			nis = ni_open(lastni, PARENT_NETINFO_DOMAIN, &ni);
5547 			if (tTd(38, 20))
5548 				dprintf("ni_open(PARENT) = %d\n", nis);
5549 		}
5550 
5551 		/*
5552 		**  Don't bother if we didn't get a handle on a
5553 		**  proper domain.  This is not necessarily an error.
5554 		**  We would get a positive ni_status if, for instance
5555 		**  we never found the directory or property and tried
5556 		**  to open the parent of the root domain!
5557 		*/
5558 
5559 		if (nis != 0)
5560 			break;
5561 
5562 		/*
5563 		**  Find the path to the server information.
5564 		*/
5565 
5566 		if (ni_pathsearch(ni, &nid, keybuf) != 0)
5567 			continue;
5568 
5569 		/*
5570 		**  Find associated value information.
5571 		*/
5572 
5573 		if (ni_lookupprop(ni, &nid, valprop, &ninl) != 0)
5574 			continue;
5575 
5576 		if (tTd(38, 20))
5577 			dprintf("ni_lookupprop: len=%d\n",
5578 				ninl.ni_namelist_len);
5579 
5580 		/*
5581 		**  See if we have an acceptable number of values.
5582 		*/
5583 
5584 		if (ninl.ni_namelist_len <= 0)
5585 			continue;
5586 
5587 		if (sepchar == '\0' && ninl.ni_namelist_len > 1)
5588 		{
5589 			ni_namelist_free(&ninl);
5590 			continue;
5591 		}
5592 
5593 		/*
5594 		**  Calculate number of bytes needed and build result
5595 		*/
5596 
5597 		alen = 1;
5598 		for (j = 0; j < ninl.ni_namelist_len; j++)
5599 			alen += strlen(ninl.ni_namelist_val[j]) + 1;
5600 		propval = p = xalloc(alen);
5601 		for (j = 0; j < ninl.ni_namelist_len; j++)
5602 		{
5603 			(void) strlcpy(p, ninl.ni_namelist_val[j], alen);
5604 			l = strlen(p);
5605 			p += l;
5606 			*p++ = sepchar;
5607 			alen -= l + 1;
5608 		}
5609 		*--p = '\0';
5610 
5611 		ni_namelist_free(&ninl);
5612 	}
5613 
5614 	/*
5615 	**  Clean up.
5616 	*/
5617 
5618 	if (ni != NULL)
5619 		ni_free(ni);
5620 	if (lastni != NULL && ni != lastni)
5621 		ni_free(lastni);
5622 	if (tTd(38, 20))
5623 		dprintf("ni_propval returns: '%s'\n", propval);
5624 
5625 	return propval;
5626 }
5627 
5628 #endif /* NETINFO */
5629 /*
5630 **  TEXT (unindexed text file) Modules
5631 **
5632 **	This code donated by Sun Microsystems.
5633 */
5634 
5635 #define map_sff		map_lockfd	/* overload field */
5636 
5637 
5638 /*
5639 **  TEXT_MAP_OPEN -- open text table
5640 */
5641 
5642 bool
5643 text_map_open(map, mode)
5644 	MAP *map;
5645 	int mode;
5646 {
5647 	long sff;
5648 	int i;
5649 
5650 	if (tTd(38, 2))
5651 		dprintf("text_map_open(%s, %s, %d)\n",
5652 			map->map_mname, map->map_file, mode);
5653 
5654 	mode &= O_ACCMODE;
5655 	if (mode != O_RDONLY)
5656 	{
5657 		errno = EPERM;
5658 		return FALSE;
5659 	}
5660 
5661 	if (*map->map_file == '\0')
5662 	{
5663 		syserr("text map \"%s\": file name required",
5664 			map->map_mname);
5665 		return FALSE;
5666 	}
5667 
5668 	if (map->map_file[0] != '/')
5669 	{
5670 		syserr("text map \"%s\": file name must be fully qualified",
5671 			map->map_mname);
5672 		return FALSE;
5673 	}
5674 
5675 	sff = SFF_ROOTOK|SFF_REGONLY;
5676 	if (!bitnset(DBS_LINKEDMAPINWRITABLEDIR, DontBlameSendmail))
5677 		sff |= SFF_NOWLINK;
5678 	if (!bitnset(DBS_MAPINUNSAFEDIRPATH, DontBlameSendmail))
5679 		sff |= SFF_SAFEDIRPATH;
5680 	if ((i = safefile(map->map_file, RunAsUid, RunAsGid, RunAsUserName,
5681 			  sff, S_IRUSR, NULL)) != 0)
5682 	{
5683 		int save_errno = errno;
5684 
5685 		/* cannot open this map */
5686 		if (tTd(38, 2))
5687 			dprintf("\tunsafe map file: %d\n", i);
5688 		errno = save_errno;
5689 		if (!bitset(MF_OPTIONAL, map->map_mflags))
5690 			syserr("text map \"%s\": unsafe map file %s",
5691 				map->map_mname, map->map_file);
5692 		return FALSE;
5693 	}
5694 
5695 	if (map->map_keycolnm == NULL)
5696 		map->map_keycolno = 0;
5697 	else
5698 	{
5699 		if (!(isascii(*map->map_keycolnm) && isdigit(*map->map_keycolnm)))
5700 		{
5701 			syserr("text map \"%s\", file %s: -k should specify a number, not %s",
5702 				map->map_mname, map->map_file,
5703 				map->map_keycolnm);
5704 			return FALSE;
5705 		}
5706 		map->map_keycolno = atoi(map->map_keycolnm);
5707 	}
5708 
5709 	if (map->map_valcolnm == NULL)
5710 		map->map_valcolno = 0;
5711 	else
5712 	{
5713 		if (!(isascii(*map->map_valcolnm) && isdigit(*map->map_valcolnm)))
5714 		{
5715 			syserr("text map \"%s\", file %s: -v should specify a number, not %s",
5716 					map->map_mname, map->map_file,
5717 					map->map_valcolnm);
5718 			return FALSE;
5719 		}
5720 		map->map_valcolno = atoi(map->map_valcolnm);
5721 	}
5722 
5723 	if (tTd(38, 2))
5724 	{
5725 		dprintf("text_map_open(%s, %s): delimiter = ",
5726 			map->map_mname, map->map_file);
5727 		if (map->map_coldelim == '\0')
5728 			dprintf("(white space)\n");
5729 		else
5730 			dprintf("%c\n", map->map_coldelim);
5731 	}
5732 
5733 	map->map_sff = sff;
5734 	return TRUE;
5735 }
5736 
5737 
5738 /*
5739 **  TEXT_MAP_LOOKUP -- look up a datum in a TEXT table
5740 */
5741 
5742 char *
5743 text_map_lookup(map, name, av, statp)
5744 	MAP *map;
5745 	char *name;
5746 	char **av;
5747 	int *statp;
5748 {
5749 	char *vp;
5750 	auto int vsize;
5751 	int buflen;
5752 	FILE *f;
5753 	char delim;
5754 	int key_idx;
5755 	bool found_it;
5756 	long sff = map->map_sff;
5757 	char search_key[MAXNAME + 1];
5758 	char linebuf[MAXLINE];
5759 	char buf[MAXNAME + 1];
5760 
5761 	found_it = FALSE;
5762 	if (tTd(38, 20))
5763 		dprintf("text_map_lookup(%s, %s)\n", map->map_mname,  name);
5764 
5765 	buflen = strlen(name);
5766 	if (buflen > sizeof search_key - 1)
5767 		buflen = sizeof search_key - 1;
5768 	memmove(search_key, name, buflen);
5769 	search_key[buflen] = '\0';
5770 	if (!bitset(MF_NOFOLDCASE, map->map_mflags))
5771 		makelower(search_key);
5772 
5773 	f = safefopen(map->map_file, O_RDONLY, FileMode, sff);
5774 	if (f == NULL)
5775 	{
5776 		map->map_mflags &= ~(MF_VALID|MF_OPEN);
5777 		*statp = EX_UNAVAILABLE;
5778 		return NULL;
5779 	}
5780 	key_idx = map->map_keycolno;
5781 	delim = map->map_coldelim;
5782 	while (fgets(linebuf, MAXLINE, f) != NULL)
5783 	{
5784 		char *p;
5785 
5786 		/* skip comment line */
5787 		if (linebuf[0] == '#')
5788 			continue;
5789 		p = strchr(linebuf, '\n');
5790 		if (p != NULL)
5791 			*p = '\0';
5792 		p = get_column(linebuf, key_idx, delim, buf, sizeof buf);
5793 		if (p != NULL && strcasecmp(search_key, p) == 0)
5794 		{
5795 			found_it = TRUE;
5796 			break;
5797 		}
5798 	}
5799 	(void) fclose(f);
5800 	if (!found_it)
5801 	{
5802 		*statp = EX_NOTFOUND;
5803 		return NULL;
5804 	}
5805 	vp = get_column(linebuf, map->map_valcolno, delim, buf, sizeof buf);
5806 	if (vp == NULL)
5807 	{
5808 		*statp = EX_NOTFOUND;
5809 		return NULL;
5810 	}
5811 	vsize = strlen(vp);
5812 	*statp = EX_OK;
5813 	if (bitset(MF_MATCHONLY, map->map_mflags))
5814 		return map_rewrite(map, name, strlen(name), NULL);
5815 	else
5816 		return map_rewrite(map, vp, vsize, av);
5817 }
5818 
5819 /*
5820 **  TEXT_GETCANONNAME -- look up canonical name in hosts file
5821 */
5822 
5823 static bool
5824 text_getcanonname(name, hbsize, statp)
5825 	char *name;
5826 	int hbsize;
5827 	int *statp;
5828 {
5829 	bool found;
5830 	FILE *f;
5831 	char linebuf[MAXLINE];
5832 	char cbuf[MAXNAME + 1];
5833 	char nbuf[MAXNAME + 1];
5834 
5835 	if (tTd(38, 20))
5836 		dprintf("text_getcanonname(%s)\n", name);
5837 
5838 	if (strlen(name) >= (SIZE_T) sizeof nbuf)
5839 	{
5840 		*statp = EX_UNAVAILABLE;
5841 		return FALSE;
5842 	}
5843 	(void) strlcpy(nbuf, name, sizeof nbuf);
5844 	shorten_hostname(nbuf);
5845 
5846 	f = fopen(HostsFile, "r");
5847 	if (f == NULL)
5848 	{
5849 		*statp = EX_UNAVAILABLE;
5850 		return FALSE;
5851 	}
5852 	found = FALSE;
5853 	while (!found && fgets(linebuf, MAXLINE, f) != NULL)
5854 	{
5855 		char *p = strpbrk(linebuf, "#\n");
5856 
5857 		if (p != NULL)
5858 			*p = '\0';
5859 		if (linebuf[0] != '\0')
5860 			found = extract_canonname(nbuf, linebuf, cbuf, sizeof cbuf);
5861 	}
5862 	(void) fclose(f);
5863 	if (!found)
5864 	{
5865 		*statp = EX_NOHOST;
5866 		return FALSE;
5867 	}
5868 
5869 	if ((SIZE_T) hbsize >= strlen(cbuf))
5870 	{
5871 		(void) strlcpy(name, cbuf, hbsize);
5872 		*statp = EX_OK;
5873 		return TRUE;
5874 	}
5875 	*statp = EX_UNAVAILABLE;
5876 	return FALSE;
5877 }
5878 /*
5879 **  STAB (Symbol Table) Modules
5880 */
5881 
5882 
5883 /*
5884 **  STAB_MAP_LOOKUP -- look up alias in symbol table
5885 */
5886 
5887 /* ARGSUSED2 */
5888 char *
5889 stab_map_lookup(map, name, av, pstat)
5890 	register MAP *map;
5891 	char *name;
5892 	char **av;
5893 	int *pstat;
5894 {
5895 	register STAB *s;
5896 
5897 	if (tTd(38, 20))
5898 		dprintf("stab_lookup(%s, %s)\n",
5899 			map->map_mname, name);
5900 
5901 	s = stab(name, ST_ALIAS, ST_FIND);
5902 	if (s != NULL)
5903 		return s->s_alias;
5904 	return NULL;
5905 }
5906 
5907 
5908 /*
5909 **  STAB_MAP_STORE -- store in symtab (actually using during init, not rebuild)
5910 */
5911 
5912 void
5913 stab_map_store(map, lhs, rhs)
5914 	register MAP *map;
5915 	char *lhs;
5916 	char *rhs;
5917 {
5918 	register STAB *s;
5919 
5920 	s = stab(lhs, ST_ALIAS, ST_ENTER);
5921 	s->s_alias = newstr(rhs);
5922 }
5923 
5924 
5925 /*
5926 **  STAB_MAP_OPEN -- initialize (reads data file)
5927 **
5928 **	This is a wierd case -- it is only intended as a fallback for
5929 **	aliases.  For this reason, opens for write (only during a
5930 **	"newaliases") always fails, and opens for read open the
5931 **	actual underlying text file instead of the database.
5932 */
5933 
5934 bool
5935 stab_map_open(map, mode)
5936 	register MAP *map;
5937 	int mode;
5938 {
5939 	FILE *af;
5940 	long sff;
5941 	struct stat st;
5942 
5943 	if (tTd(38, 2))
5944 		dprintf("stab_map_open(%s, %s, %d)\n",
5945 			map->map_mname, map->map_file, mode);
5946 
5947 	mode &= O_ACCMODE;
5948 	if (mode != O_RDONLY)
5949 	{
5950 		errno = EPERM;
5951 		return FALSE;
5952 	}
5953 
5954 	sff = SFF_ROOTOK|SFF_REGONLY;
5955 	if (!bitnset(DBS_LINKEDMAPINWRITABLEDIR, DontBlameSendmail))
5956 		sff |= SFF_NOWLINK;
5957 	if (!bitnset(DBS_MAPINUNSAFEDIRPATH, DontBlameSendmail))
5958 		sff |= SFF_SAFEDIRPATH;
5959 	af = safefopen(map->map_file, O_RDONLY, 0444, sff);
5960 	if (af == NULL)
5961 		return FALSE;
5962 	readaliases(map, af, FALSE, FALSE);
5963 
5964 	if (fstat(fileno(af), &st) >= 0)
5965 		map->map_mtime = st.st_mtime;
5966 	(void) fclose(af);
5967 
5968 	return TRUE;
5969 }
5970 /*
5971 **  Implicit Modules
5972 **
5973 **	Tries several types.  For back compatibility of aliases.
5974 */
5975 
5976 
5977 /*
5978 **  IMPL_MAP_LOOKUP -- lookup in best open database
5979 */
5980 
5981 char *
5982 impl_map_lookup(map, name, av, pstat)
5983 	MAP *map;
5984 	char *name;
5985 	char **av;
5986 	int *pstat;
5987 {
5988 	if (tTd(38, 20))
5989 		dprintf("impl_map_lookup(%s, %s)\n",
5990 			map->map_mname, name);
5991 
5992 #ifdef NEWDB
5993 	if (bitset(MF_IMPL_HASH, map->map_mflags))
5994 		return db_map_lookup(map, name, av, pstat);
5995 #endif /* NEWDB */
5996 #ifdef NDBM
5997 	if (bitset(MF_IMPL_NDBM, map->map_mflags))
5998 		return ndbm_map_lookup(map, name, av, pstat);
5999 #endif /* NDBM */
6000 	return stab_map_lookup(map, name, av, pstat);
6001 }
6002 
6003 /*
6004 **  IMPL_MAP_STORE -- store in open databases
6005 */
6006 
6007 void
6008 impl_map_store(map, lhs, rhs)
6009 	MAP *map;
6010 	char *lhs;
6011 	char *rhs;
6012 {
6013 	if (tTd(38, 12))
6014 		dprintf("impl_map_store(%s, %s, %s)\n",
6015 			map->map_mname, lhs, rhs);
6016 #ifdef NEWDB
6017 	if (bitset(MF_IMPL_HASH, map->map_mflags))
6018 		db_map_store(map, lhs, rhs);
6019 #endif /* NEWDB */
6020 #ifdef NDBM
6021 	if (bitset(MF_IMPL_NDBM, map->map_mflags))
6022 		ndbm_map_store(map, lhs, rhs);
6023 #endif /* NDBM */
6024 	stab_map_store(map, lhs, rhs);
6025 }
6026 
6027 /*
6028 **  IMPL_MAP_OPEN -- implicit database open
6029 */
6030 
6031 bool
6032 impl_map_open(map, mode)
6033 	MAP *map;
6034 	int mode;
6035 {
6036 	if (tTd(38, 2))
6037 		dprintf("impl_map_open(%s, %s, %d)\n",
6038 			map->map_mname, map->map_file, mode);
6039 
6040 	mode &= O_ACCMODE;
6041 #ifdef NEWDB
6042 	map->map_mflags |= MF_IMPL_HASH;
6043 	if (hash_map_open(map, mode))
6044 	{
6045 # ifdef NDBM_YP_COMPAT
6046 		if (mode == O_RDONLY || strstr(map->map_file, "/yp/") == NULL)
6047 # endif /* NDBM_YP_COMPAT */
6048 			return TRUE;
6049 	}
6050 	else
6051 		map->map_mflags &= ~MF_IMPL_HASH;
6052 #endif /* NEWDB */
6053 #ifdef NDBM
6054 	map->map_mflags |= MF_IMPL_NDBM;
6055 	if (ndbm_map_open(map, mode))
6056 	{
6057 		return TRUE;
6058 	}
6059 	else
6060 		map->map_mflags &= ~MF_IMPL_NDBM;
6061 #endif /* NDBM */
6062 
6063 #if defined(NEWDB) || defined(NDBM)
6064 	if (Verbose)
6065 		message("WARNING: cannot open alias database %s%s",
6066 			map->map_file,
6067 			mode == O_RDONLY ? "; reading text version" : "");
6068 #else /* defined(NEWDB) || defined(NDBM) */
6069 	if (mode != O_RDONLY)
6070 		usrerr("Cannot rebuild aliases: no database format defined");
6071 #endif /* defined(NEWDB) || defined(NDBM) */
6072 
6073 	if (mode == O_RDONLY)
6074 		return stab_map_open(map, mode);
6075 	else
6076 		return FALSE;
6077 }
6078 
6079 
6080 /*
6081 **  IMPL_MAP_CLOSE -- close any open database(s)
6082 */
6083 
6084 void
6085 impl_map_close(map)
6086 	MAP *map;
6087 {
6088 	if (tTd(38, 9))
6089 		dprintf("impl_map_close(%s, %s, %lx)\n",
6090 			map->map_mname, map->map_file, map->map_mflags);
6091 #ifdef NEWDB
6092 	if (bitset(MF_IMPL_HASH, map->map_mflags))
6093 	{
6094 		db_map_close(map);
6095 		map->map_mflags &= ~MF_IMPL_HASH;
6096 	}
6097 #endif /* NEWDB */
6098 
6099 #ifdef NDBM
6100 	if (bitset(MF_IMPL_NDBM, map->map_mflags))
6101 	{
6102 		ndbm_map_close(map);
6103 		map->map_mflags &= ~MF_IMPL_NDBM;
6104 	}
6105 #endif /* NDBM */
6106 }
6107 /*
6108 **  User map class.
6109 **
6110 **	Provides access to the system password file.
6111 */
6112 
6113 /*
6114 **  USER_MAP_OPEN -- open user map
6115 **
6116 **	Really just binds field names to field numbers.
6117 */
6118 
6119 bool
6120 user_map_open(map, mode)
6121 	MAP *map;
6122 	int mode;
6123 {
6124 	if (tTd(38, 2))
6125 		dprintf("user_map_open(%s, %d)\n",
6126 			map->map_mname, mode);
6127 
6128 	mode &= O_ACCMODE;
6129 	if (mode != O_RDONLY)
6130 	{
6131 		/* issue a pseudo-error message */
6132 #ifdef ENOSYS
6133 		errno = ENOSYS;
6134 #else /* ENOSYS */
6135 # ifdef EFTYPE
6136 		errno = EFTYPE;
6137 # else /* EFTYPE */
6138 		errno = ENXIO;
6139 # endif /* EFTYPE */
6140 #endif /* ENOSYS */
6141 		return FALSE;
6142 	}
6143 	if (map->map_valcolnm == NULL)
6144 		/* EMPTY */
6145 		/* nothing */ ;
6146 	else if (strcasecmp(map->map_valcolnm, "name") == 0)
6147 		map->map_valcolno = 1;
6148 	else if (strcasecmp(map->map_valcolnm, "passwd") == 0)
6149 		map->map_valcolno = 2;
6150 	else if (strcasecmp(map->map_valcolnm, "uid") == 0)
6151 		map->map_valcolno = 3;
6152 	else if (strcasecmp(map->map_valcolnm, "gid") == 0)
6153 		map->map_valcolno = 4;
6154 	else if (strcasecmp(map->map_valcolnm, "gecos") == 0)
6155 		map->map_valcolno = 5;
6156 	else if (strcasecmp(map->map_valcolnm, "dir") == 0)
6157 		map->map_valcolno = 6;
6158 	else if (strcasecmp(map->map_valcolnm, "shell") == 0)
6159 		map->map_valcolno = 7;
6160 	else
6161 	{
6162 		syserr("User map %s: unknown column name %s",
6163 			map->map_mname, map->map_valcolnm);
6164 		return FALSE;
6165 	}
6166 	return TRUE;
6167 }
6168 
6169 
6170 /*
6171 **  USER_MAP_LOOKUP -- look up a user in the passwd file.
6172 */
6173 
6174 /* ARGSUSED3 */
6175 char *
6176 user_map_lookup(map, key, av, statp)
6177 	MAP *map;
6178 	char *key;
6179 	char **av;
6180 	int *statp;
6181 {
6182 	struct passwd *pw;
6183 	auto bool fuzzy;
6184 
6185 	if (tTd(38, 20))
6186 		dprintf("user_map_lookup(%s, %s)\n",
6187 			map->map_mname, key);
6188 
6189 	pw = finduser(key, &fuzzy);
6190 	if (pw == NULL)
6191 		return NULL;
6192 	if (bitset(MF_MATCHONLY, map->map_mflags))
6193 		return map_rewrite(map, key, strlen(key), NULL);
6194 	else
6195 	{
6196 		char *rwval = NULL;
6197 		char buf[30];
6198 
6199 		switch (map->map_valcolno)
6200 		{
6201 		  case 0:
6202 		  case 1:
6203 			rwval = pw->pw_name;
6204 			break;
6205 
6206 		  case 2:
6207 			rwval = pw->pw_passwd;
6208 			break;
6209 
6210 		  case 3:
6211 			snprintf(buf, sizeof buf, "%d", (int) pw->pw_uid);
6212 			rwval = buf;
6213 			break;
6214 
6215 		  case 4:
6216 			snprintf(buf, sizeof buf, "%d", (int) pw->pw_gid);
6217 			rwval = buf;
6218 			break;
6219 
6220 		  case 5:
6221 			rwval = pw->pw_gecos;
6222 			break;
6223 
6224 		  case 6:
6225 			rwval = pw->pw_dir;
6226 			break;
6227 
6228 		  case 7:
6229 			rwval = pw->pw_shell;
6230 			break;
6231 		}
6232 		return map_rewrite(map, rwval, strlen(rwval), av);
6233 	}
6234 }
6235 /*
6236 **  Program map type.
6237 **
6238 **	This provides access to arbitrary programs.  It should be used
6239 **	only very sparingly, since there is no way to bound the cost
6240 **	of invoking an arbitrary program.
6241 */
6242 
6243 char *
6244 prog_map_lookup(map, name, av, statp)
6245 	MAP *map;
6246 	char *name;
6247 	char **av;
6248 	int *statp;
6249 {
6250 	int i;
6251 	int save_errno;
6252 	int fd;
6253 	int status;
6254 	auto pid_t pid;
6255 	register char *p;
6256 	char *rval;
6257 	char *argv[MAXPV + 1];
6258 	char buf[MAXLINE];
6259 
6260 	if (tTd(38, 20))
6261 		dprintf("prog_map_lookup(%s, %s) %s\n",
6262 			map->map_mname, name, map->map_file);
6263 
6264 	i = 0;
6265 	argv[i++] = map->map_file;
6266 	if (map->map_rebuild != NULL)
6267 	{
6268 		snprintf(buf, sizeof buf, "%s", map->map_rebuild);
6269 		for (p = strtok(buf, " \t"); p != NULL; p = strtok(NULL, " \t"))
6270 		{
6271 			if (i >= MAXPV - 1)
6272 				break;
6273 			argv[i++] = p;
6274 		}
6275 	}
6276 	argv[i++] = name;
6277 	argv[i] = NULL;
6278 	if (tTd(38, 21))
6279 	{
6280 		dprintf("prog_open:");
6281 		for (i = 0; argv[i] != NULL; i++)
6282 			dprintf(" %s", argv[i]);
6283 		dprintf("\n");
6284 	}
6285 	(void) blocksignal(SIGCHLD);
6286 	pid = prog_open(argv, &fd, CurEnv);
6287 	if (pid < 0)
6288 	{
6289 		if (!bitset(MF_OPTIONAL, map->map_mflags))
6290 			syserr("prog_map_lookup(%s) failed (%s) -- closing",
6291 				map->map_mname, errstring(errno));
6292 		else if (tTd(38, 9))
6293 			dprintf("prog_map_lookup(%s) failed (%s) -- closing",
6294 				map->map_mname, errstring(errno));
6295 		map->map_mflags &= ~(MF_VALID|MF_OPEN);
6296 		*statp = EX_OSFILE;
6297 		return NULL;
6298 	}
6299 	i = read(fd, buf, sizeof buf - 1);
6300 	if (i < 0)
6301 	{
6302 		syserr("prog_map_lookup(%s): read error %s\n",
6303 			map->map_mname, errstring(errno));
6304 		rval = NULL;
6305 	}
6306 	else if (i == 0)
6307 	{
6308 		if (tTd(38, 20))
6309 			dprintf("prog_map_lookup(%s): empty answer\n",
6310 				map->map_mname);
6311 		rval = NULL;
6312 	}
6313 	else
6314 	{
6315 		buf[i] = '\0';
6316 		p = strchr(buf, '\n');
6317 		if (p != NULL)
6318 			*p = '\0';
6319 
6320 		/* collect the return value */
6321 		if (bitset(MF_MATCHONLY, map->map_mflags))
6322 			rval = map_rewrite(map, name, strlen(name), NULL);
6323 		else
6324 			rval = map_rewrite(map, buf, strlen(buf), NULL);
6325 
6326 		/* now flush any additional output */
6327 		while ((i = read(fd, buf, sizeof buf)) > 0)
6328 			continue;
6329 	}
6330 
6331 	/* wait for the process to terminate */
6332 	(void) close(fd);
6333 	status = waitfor(pid);
6334 	save_errno = errno;
6335 	(void) releasesignal(SIGCHLD);
6336 	errno = save_errno;
6337 
6338 	if (status == -1)
6339 	{
6340 		syserr("prog_map_lookup(%s): wait error %s\n",
6341 			map->map_mname, errstring(errno));
6342 		*statp = EX_SOFTWARE;
6343 		rval = NULL;
6344 	}
6345 	else if (WIFEXITED(status))
6346 	{
6347 		if ((*statp = WEXITSTATUS(status)) != EX_OK)
6348 			rval = NULL;
6349 	}
6350 	else
6351 	{
6352 		syserr("prog_map_lookup(%s): child died on signal %d",
6353 			map->map_mname, status);
6354 		*statp = EX_UNAVAILABLE;
6355 		rval = NULL;
6356 	}
6357 	return rval;
6358 }
6359 /*
6360 **  Sequenced map type.
6361 **
6362 **	Tries each map in order until something matches, much like
6363 **	implicit.  Stores go to the first map in the list that can
6364 **	support storing.
6365 **
6366 **	This is slightly unusual in that there are two interfaces.
6367 **	The "sequence" interface lets you stack maps arbitrarily.
6368 **	The "switch" interface builds a sequence map by looking
6369 **	at a system-dependent configuration file such as
6370 **	/etc/nsswitch.conf on Solaris or /etc/svc.conf on Ultrix.
6371 **
6372 **	We don't need an explicit open, since all maps are
6373 **	opened during startup, including underlying maps.
6374 */
6375 
6376 /*
6377 **  SEQ_MAP_PARSE -- Sequenced map parsing
6378 */
6379 
6380 bool
6381 seq_map_parse(map, ap)
6382 	MAP *map;
6383 	char *ap;
6384 {
6385 	int maxmap;
6386 
6387 	if (tTd(38, 2))
6388 		dprintf("seq_map_parse(%s, %s)\n", map->map_mname, ap);
6389 	maxmap = 0;
6390 	while (*ap != '\0')
6391 	{
6392 		register char *p;
6393 		STAB *s;
6394 
6395 		/* find beginning of map name */
6396 		while (isascii(*ap) && isspace(*ap))
6397 			ap++;
6398 		for (p = ap;
6399 		     (isascii(*p) && isalnum(*p)) || *p == '_' || *p == '.';
6400 		     p++)
6401 			continue;
6402 		if (*p != '\0')
6403 			*p++ = '\0';
6404 		while (*p != '\0' && (!isascii(*p) || !isalnum(*p)))
6405 			p++;
6406 		if (*ap == '\0')
6407 		{
6408 			ap = p;
6409 			continue;
6410 		}
6411 		s = stab(ap, ST_MAP, ST_FIND);
6412 		if (s == NULL)
6413 		{
6414 			syserr("Sequence map %s: unknown member map %s",
6415 				map->map_mname, ap);
6416 		}
6417 		else if (maxmap == MAXMAPSTACK)
6418 		{
6419 			syserr("Sequence map %s: too many member maps (%d max)",
6420 				map->map_mname, MAXMAPSTACK);
6421 			maxmap++;
6422 		}
6423 		else if (maxmap < MAXMAPSTACK)
6424 		{
6425 			map->map_stack[maxmap++] = &s->s_map;
6426 		}
6427 		ap = p;
6428 	}
6429 	return TRUE;
6430 }
6431 
6432 
6433 /*
6434 **  SWITCH_MAP_OPEN -- open a switched map
6435 **
6436 **	This looks at the system-dependent configuration and builds
6437 **	a sequence map that does the same thing.
6438 **
6439 **	Every system must define a switch_map_find routine in conf.c
6440 **	that will return the list of service types associated with a
6441 **	given service class.
6442 */
6443 
6444 bool
6445 switch_map_open(map, mode)
6446 	MAP *map;
6447 	int mode;
6448 {
6449 	int mapno;
6450 	int nmaps;
6451 	char *maptype[MAXMAPSTACK];
6452 
6453 	if (tTd(38, 2))
6454 		dprintf("switch_map_open(%s, %s, %d)\n",
6455 			map->map_mname, map->map_file, mode);
6456 
6457 	mode &= O_ACCMODE;
6458 	nmaps = switch_map_find(map->map_file, maptype, map->map_return);
6459 	if (tTd(38, 19))
6460 	{
6461 		dprintf("\tswitch_map_find => %d\n", nmaps);
6462 		for (mapno = 0; mapno < nmaps; mapno++)
6463 			dprintf("\t\t%s\n", maptype[mapno]);
6464 	}
6465 	if (nmaps <= 0 || nmaps > MAXMAPSTACK)
6466 		return FALSE;
6467 
6468 	for (mapno = 0; mapno < nmaps; mapno++)
6469 	{
6470 		register STAB *s;
6471 		char nbuf[MAXNAME + 1];
6472 
6473 		if (maptype[mapno] == NULL)
6474 			continue;
6475 		(void) snprintf(nbuf, sizeof nbuf, "%s.%s",
6476 			map->map_mname, maptype[mapno]);
6477 		s = stab(nbuf, ST_MAP, ST_FIND);
6478 		if (s == NULL)
6479 		{
6480 			syserr("Switch map %s: unknown member map %s",
6481 				map->map_mname, nbuf);
6482 		}
6483 		else
6484 		{
6485 			map->map_stack[mapno] = &s->s_map;
6486 			if (tTd(38, 4))
6487 				dprintf("\tmap_stack[%d] = %s:%s\n",
6488 					mapno, s->s_map.map_class->map_cname,
6489 					nbuf);
6490 		}
6491 	}
6492 	return TRUE;
6493 }
6494 
6495 
6496 /*
6497 **  SEQ_MAP_CLOSE -- close all underlying maps
6498 */
6499 
6500 void
6501 seq_map_close(map)
6502 	MAP *map;
6503 {
6504 	int mapno;
6505 
6506 	if (tTd(38, 9))
6507 		dprintf("seq_map_close(%s)\n", map->map_mname);
6508 
6509 	for (mapno = 0; mapno < MAXMAPSTACK; mapno++)
6510 	{
6511 		MAP *mm = map->map_stack[mapno];
6512 
6513 		if (mm == NULL || !bitset(MF_OPEN, mm->map_mflags))
6514 			continue;
6515 		mm->map_class->map_close(mm);
6516 		mm->map_mflags &= ~(MF_OPEN|MF_WRITABLE);
6517 	}
6518 }
6519 
6520 
6521 /*
6522 **  SEQ_MAP_LOOKUP -- sequenced map lookup
6523 */
6524 
6525 char *
6526 seq_map_lookup(map, key, args, pstat)
6527 	MAP *map;
6528 	char *key;
6529 	char **args;
6530 	int *pstat;
6531 {
6532 	int mapno;
6533 	int mapbit = 0x01;
6534 	bool tempfail = FALSE;
6535 
6536 	if (tTd(38, 20))
6537 		dprintf("seq_map_lookup(%s, %s)\n", map->map_mname, key);
6538 
6539 	for (mapno = 0; mapno < MAXMAPSTACK; mapbit <<= 1, mapno++)
6540 	{
6541 		MAP *mm = map->map_stack[mapno];
6542 		char *rv;
6543 
6544 		if (mm == NULL)
6545 			continue;
6546 		if (!bitset(MF_OPEN, mm->map_mflags) &&
6547 		    !openmap(mm))
6548 		{
6549 			if (bitset(mapbit, map->map_return[MA_UNAVAIL]))
6550 			{
6551 				*pstat = EX_UNAVAILABLE;
6552 				return NULL;
6553 			}
6554 			continue;
6555 		}
6556 		*pstat = EX_OK;
6557 		rv = mm->map_class->map_lookup(mm, key, args, pstat);
6558 		if (rv != NULL)
6559 			return rv;
6560 		if (*pstat == EX_TEMPFAIL)
6561 		{
6562 			if (bitset(mapbit, map->map_return[MA_TRYAGAIN]))
6563 				return NULL;
6564 			tempfail = TRUE;
6565 		}
6566 		else if (bitset(mapbit, map->map_return[MA_NOTFOUND]))
6567 			break;
6568 	}
6569 	if (tempfail)
6570 		*pstat = EX_TEMPFAIL;
6571 	else if (*pstat == EX_OK)
6572 		*pstat = EX_NOTFOUND;
6573 	return NULL;
6574 }
6575 
6576 
6577 /*
6578 **  SEQ_MAP_STORE -- sequenced map store
6579 */
6580 
6581 void
6582 seq_map_store(map, key, val)
6583 	MAP *map;
6584 	char *key;
6585 	char *val;
6586 {
6587 	int mapno;
6588 
6589 	if (tTd(38, 12))
6590 		dprintf("seq_map_store(%s, %s, %s)\n",
6591 			map->map_mname, key, val);
6592 
6593 	for (mapno = 0; mapno < MAXMAPSTACK; mapno++)
6594 	{
6595 		MAP *mm = map->map_stack[mapno];
6596 
6597 		if (mm == NULL || !bitset(MF_WRITABLE, mm->map_mflags))
6598 			continue;
6599 
6600 		mm->map_class->map_store(mm, key, val);
6601 		return;
6602 	}
6603 	syserr("seq_map_store(%s, %s, %s): no writable map",
6604 		map->map_mname, key, val);
6605 }
6606 /*
6607 **  NULL stubs
6608 */
6609 
6610 /* ARGSUSED */
6611 bool
6612 null_map_open(map, mode)
6613 	MAP *map;
6614 	int mode;
6615 {
6616 	return TRUE;
6617 }
6618 
6619 /* ARGSUSED */
6620 void
6621 null_map_close(map)
6622 	MAP *map;
6623 {
6624 	return;
6625 }
6626 
6627 char *
6628 null_map_lookup(map, key, args, pstat)
6629 	MAP *map;
6630 	char *key;
6631 	char **args;
6632 	int *pstat;
6633 {
6634 	*pstat = EX_NOTFOUND;
6635 	return NULL;
6636 }
6637 
6638 /* ARGSUSED */
6639 void
6640 null_map_store(map, key, val)
6641 	MAP *map;
6642 	char *key;
6643 	char *val;
6644 {
6645 	return;
6646 }
6647 
6648 
6649 /*
6650 **  BOGUS stubs
6651 */
6652 
6653 char *
6654 bogus_map_lookup(map, key, args, pstat)
6655 	MAP *map;
6656 	char *key;
6657 	char **args;
6658 	int *pstat;
6659 {
6660 	*pstat = EX_TEMPFAIL;
6661 	return NULL;
6662 }
6663 
6664 MAPCLASS	BogusMapClass =
6665 {
6666 	"bogus-map",		NULL,		0,
6667 	NULL,		bogus_map_lookup,	null_map_store,
6668 	null_map_open,	null_map_close,
6669 };
6670 /*
6671 **  MACRO modules
6672 */
6673 
6674 char *
6675 macro_map_lookup(map, name, av, statp)
6676 	MAP *map;
6677 	char *name;
6678 	char **av;
6679 	int *statp;
6680 {
6681 	int mid;
6682 
6683 	if (tTd(38, 20))
6684 		dprintf("macro_map_lookup(%s, %s)\n", map->map_mname,
6685 			name == NULL ? "NULL" : name);
6686 
6687 	if (name == NULL ||
6688 	    *name == '\0' ||
6689 	    (mid = macid(name, NULL)) == '\0')
6690 	{
6691 		*statp = EX_CONFIG;
6692 		return NULL;
6693 	}
6694 
6695 	if (av[1] == NULL)
6696 		define(mid, NULL, CurEnv);
6697 	else
6698 		define(mid, newstr(av[1]), CurEnv);
6699 
6700 	*statp = EX_OK;
6701 	return "";
6702 }
6703 /*
6704 **  REGEX modules
6705 */
6706 
6707 #ifdef MAP_REGEX
6708 
6709 # include <regex.h>
6710 
6711 # define DEFAULT_DELIM	CONDELSE
6712 
6713 # define END_OF_FIELDS	-1
6714 
6715 # define ERRBUF_SIZE	80
6716 # define MAX_MATCH	32
6717 
6718 # define xnalloc(s)	memset(xalloc(s), '\0', s);
6719 
6720 struct regex_map
6721 {
6722 	regex_t	regex_pattern_buf;	/* xalloc it */
6723 	int	*regex_subfields;	/* move to type MAP */
6724 	char	*regex_delim;		/* move to type MAP */
6725 };
6726 
6727 static int
6728 parse_fields(s, ibuf, blen, nr_substrings)
6729 	char *s;
6730 	int *ibuf;		/* array */
6731 	int blen;		/* number of elements in ibuf */
6732 	int nr_substrings;	/* number of substrings in the pattern */
6733 {
6734 	register char *cp;
6735 	int i = 0;
6736 	bool lastone = FALSE;
6737 
6738 	blen--;		/* for terminating END_OF_FIELDS */
6739 	cp = s;
6740 	do
6741 	{
6742 		for (;; cp++)
6743 		{
6744 			if (*cp == ',')
6745 			{
6746 				*cp = '\0';
6747 				break;
6748 			}
6749 			if (*cp == '\0')
6750 			{
6751 				lastone = TRUE;
6752 				break;
6753 			}
6754 		}
6755 		if (i < blen)
6756 		{
6757 			int val = atoi(s);
6758 
6759 			if (val < 0 || val >= nr_substrings)
6760 			{
6761 				syserr("field (%d) out of range, only %d substrings in pattern",
6762 				       val, nr_substrings);
6763 				return -1;
6764 			}
6765 			ibuf[i++] = val;
6766 		}
6767 		else
6768 		{
6769 			syserr("too many fields, %d max\n", blen);
6770 			return -1;
6771 		}
6772 		s = ++cp;
6773 	} while (!lastone);
6774 	ibuf[i] = END_OF_FIELDS;
6775 	return i;
6776 }
6777 
6778 bool
6779 regex_map_init(map, ap)
6780 	MAP *map;
6781 	char *ap;
6782 {
6783 	int regerr;
6784 	struct regex_map *map_p;
6785 	register char *p;
6786 	char *sub_param = NULL;
6787 	int pflags;
6788 	static char defdstr[] = { (char)DEFAULT_DELIM, '\0' };
6789 
6790 	if (tTd(38, 2))
6791 		dprintf("regex_map_init: mapname '%s', args '%s'\n",
6792 			map->map_mname, ap);
6793 
6794 	pflags = REG_ICASE | REG_EXTENDED | REG_NOSUB;
6795 
6796 	p = ap;
6797 
6798 	map_p = (struct regex_map *) xnalloc(sizeof *map_p);
6799 
6800 	for (;;)
6801 	{
6802 		while (isascii(*p) && isspace(*p))
6803 			p++;
6804 		if (*p != '-')
6805 			break;
6806 		switch (*++p)
6807 		{
6808 		  case 'n':	/* not */
6809 			map->map_mflags |= MF_REGEX_NOT;
6810 			break;
6811 
6812 		  case 'f':	/* case sensitive */
6813 			map->map_mflags |= MF_NOFOLDCASE;
6814 			pflags &= ~REG_ICASE;
6815 			break;
6816 
6817 		  case 'b':	/* basic regular expressions */
6818 			pflags &= ~REG_EXTENDED;
6819 			break;
6820 
6821 		  case 's':	/* substring match () syntax */
6822 			sub_param = ++p;
6823 			pflags &= ~REG_NOSUB;
6824 			break;
6825 
6826 		  case 'd':	/* delimiter */
6827 			map_p->regex_delim = ++p;
6828 			break;
6829 
6830 		  case 'a':	/* map append */
6831 			map->map_app = ++p;
6832 			break;
6833 
6834 		  case 'm':	/* matchonly */
6835 			map->map_mflags |= MF_MATCHONLY;
6836 			break;
6837 
6838 		  case 'S':
6839 			map->map_spacesub = *++p;
6840 			break;
6841 
6842 		  case 'D':
6843 			map->map_mflags |= MF_DEFER;
6844 			break;
6845 
6846 		}
6847 		while (*p != '\0' && !(isascii(*p) && isspace(*p)))
6848 			p++;
6849 		if (*p != '\0')
6850 			*p++ = '\0';
6851 	}
6852 	if (tTd(38, 3))
6853 		dprintf("regex_map_init: compile '%s' 0x%x\n", p, pflags);
6854 
6855 	if ((regerr = regcomp(&(map_p->regex_pattern_buf), p, pflags)) != 0)
6856 	{
6857 		/* Errorhandling */
6858 		char errbuf[ERRBUF_SIZE];
6859 
6860 		(void) regerror(regerr, &(map_p->regex_pattern_buf),
6861 			 errbuf, ERRBUF_SIZE);
6862 		syserr("pattern-compile-error: %s\n", errbuf);
6863 		free(map_p);
6864 		return FALSE;
6865 	}
6866 
6867 	if (map->map_app != NULL)
6868 		map->map_app = newstr(map->map_app);
6869 	if (map_p->regex_delim != NULL)
6870 		map_p->regex_delim = newstr(map_p->regex_delim);
6871 	else
6872 		map_p->regex_delim = defdstr;
6873 
6874 	if (!bitset(REG_NOSUB, pflags))
6875 	{
6876 		/* substring matching */
6877 		int substrings;
6878 		int *fields = (int *) xalloc(sizeof(int) * (MAX_MATCH + 1));
6879 
6880 		substrings = map_p->regex_pattern_buf.re_nsub + 1;
6881 
6882 		if (tTd(38, 3))
6883 			dprintf("regex_map_init: nr of substrings %d\n",
6884 				substrings);
6885 
6886 		if (substrings >= MAX_MATCH)
6887 		{
6888 			syserr("too many substrings, %d max\n", MAX_MATCH);
6889 			free(map_p);
6890 			return FALSE;
6891 		}
6892 		if (sub_param != NULL && sub_param[0] != '\0')
6893 		{
6894 			/* optional parameter -sfields */
6895 			if (parse_fields(sub_param, fields,
6896 					 MAX_MATCH + 1, substrings) == -1)
6897 				return FALSE;
6898 		}
6899 		else
6900 		{
6901 			/* set default fields */
6902 			int i;
6903 
6904 			for (i = 0; i < substrings; i++)
6905 				fields[i] = i;
6906 			fields[i] = END_OF_FIELDS;
6907 		}
6908 		map_p->regex_subfields = fields;
6909 		if (tTd(38, 3))
6910 		{
6911 			int *ip;
6912 
6913 			dprintf("regex_map_init: subfields");
6914 			for (ip = fields; *ip != END_OF_FIELDS; ip++)
6915 				dprintf(" %d", *ip);
6916 			dprintf("\n");
6917 		}
6918 	}
6919 	map->map_db1 = (ARBPTR_T)map_p;	/* dirty hack */
6920 
6921 	return TRUE;
6922 }
6923 
6924 static char *
6925 regex_map_rewrite(map, s, slen, av)
6926 	MAP *map;
6927 	const char *s;
6928 	size_t slen;
6929 	char **av;
6930 {
6931 	if (bitset(MF_MATCHONLY, map->map_mflags))
6932 		return map_rewrite(map, av[0], strlen(av[0]), NULL);
6933 	else
6934 		return map_rewrite(map, s, slen, NULL);
6935 }
6936 
6937 char *
6938 regex_map_lookup(map, name, av, statp)
6939 	MAP *map;
6940 	char *name;
6941 	char **av;
6942 	int *statp;
6943 {
6944 	int reg_res;
6945 	struct regex_map *map_p;
6946 	regmatch_t pmatch[MAX_MATCH];
6947 
6948 	if (tTd(38, 20))
6949 	{
6950 		char **cpp;
6951 
6952 		dprintf("regex_map_lookup: key '%s'\n", name);
6953 		for (cpp = av; cpp != NULL && *cpp != NULL; cpp++)
6954 			dprintf("regex_map_lookup: arg '%s'\n", *cpp);
6955 	}
6956 
6957 	map_p = (struct regex_map *)(map->map_db1);
6958 	reg_res = regexec(&(map_p->regex_pattern_buf),
6959 			  name, MAX_MATCH, pmatch, 0);
6960 
6961 	if (bitset(MF_REGEX_NOT, map->map_mflags))
6962 	{
6963 		/* option -n */
6964 		if (reg_res == REG_NOMATCH)
6965 			return regex_map_rewrite(map, "", (size_t)0, av);
6966 		else
6967 			return NULL;
6968 	}
6969 	if (reg_res == REG_NOMATCH)
6970 		return NULL;
6971 
6972 	if (map_p->regex_subfields != NULL)
6973 	{
6974 		/* option -s */
6975 		static char retbuf[MAXNAME];
6976 		int fields[MAX_MATCH + 1];
6977 		bool first = TRUE;
6978 		int anglecnt = 0, cmntcnt = 0, spacecnt = 0;
6979 		bool quotemode = FALSE, bslashmode = FALSE;
6980 		register char *dp, *sp;
6981 		char *endp, *ldp;
6982 		int *ip;
6983 
6984 		dp = retbuf;
6985 		ldp = retbuf + sizeof(retbuf) - 1;
6986 
6987 		if (av[1] != NULL)
6988 		{
6989 			if (parse_fields(av[1], fields, MAX_MATCH + 1,
6990 					 (int) map_p->regex_pattern_buf.re_nsub + 1) == -1)
6991 			{
6992 				*statp = EX_CONFIG;
6993 				return NULL;
6994 			}
6995 			ip = fields;
6996 		}
6997 		else
6998 			ip = map_p->regex_subfields;
6999 
7000 		for ( ; *ip != END_OF_FIELDS; ip++)
7001 		{
7002 			if (!first)
7003 			{
7004 				for (sp = map_p->regex_delim; *sp; sp++)
7005 				{
7006 					if (dp < ldp)
7007 						*dp++ = *sp;
7008 				}
7009 			}
7010 			else
7011 				first = FALSE;
7012 
7013 
7014 			if (pmatch[*ip].rm_so < 0 || pmatch[*ip].rm_eo < 0)
7015 				continue;
7016 
7017 			sp = name + pmatch[*ip].rm_so;
7018 			endp = name + pmatch[*ip].rm_eo;
7019 			for (; endp > sp; sp++)
7020 			{
7021 				if (dp < ldp)
7022 				{
7023 					if (bslashmode)
7024 					{
7025 						*dp++ = *sp;
7026 						bslashmode = FALSE;
7027 					}
7028 					else if (quotemode && *sp != '"' &&
7029 						*sp != '\\')
7030 					{
7031 						*dp++ = *sp;
7032 					}
7033 					else switch(*dp++ = *sp)
7034 					{
7035 						case '\\':
7036 						bslashmode = TRUE;
7037 						break;
7038 
7039 						case '(':
7040 						cmntcnt++;
7041 						break;
7042 
7043 						case ')':
7044 						cmntcnt--;
7045 						break;
7046 
7047 						case '<':
7048 						anglecnt++;
7049 						break;
7050 
7051 						case '>':
7052 						anglecnt--;
7053 						break;
7054 
7055 						case ' ':
7056 						spacecnt++;
7057 						break;
7058 
7059 						case '"':
7060 						quotemode = !quotemode;
7061 						break;
7062 					}
7063 				}
7064 			}
7065 		}
7066 		if (anglecnt != 0 || cmntcnt != 0 || quotemode ||
7067 		    bslashmode || spacecnt != 0)
7068 		{
7069 			sm_syslog(LOG_WARNING, NOQID,
7070 				  "Warning: regex may cause prescan() failure map=%s lookup=%s",
7071 				  map->map_mname, name);
7072 			return NULL;
7073 		}
7074 
7075 		*dp = '\0';
7076 
7077 		return regex_map_rewrite(map, retbuf, strlen(retbuf), av);
7078 	}
7079 	return regex_map_rewrite(map, "", (size_t)0, av);
7080 }
7081 #endif /* MAP_REGEX */
7082 /*
7083 **  NSD modules
7084 */
7085 #ifdef MAP_NSD
7086 
7087 # include <ndbm.h>
7088 # define _DATUM_DEFINED
7089 # include <ns_api.h>
7090 
7091 typedef struct ns_map_list
7092 {
7093 	ns_map_t *map;
7094 	char *mapname;
7095 	struct ns_map_list *next;
7096 } ns_map_list_t;
7097 
7098 static ns_map_t *
7099 ns_map_t_find(mapname)
7100 	char *mapname;
7101 {
7102 	static ns_map_list_t *ns_maps = NULL;
7103 	ns_map_list_t *ns_map;
7104 
7105 	/* walk the list of maps looking for the correctly named map */
7106 	for (ns_map = ns_maps; ns_map != NULL; ns_map = ns_map->next)
7107 	{
7108 		if (strcmp(ns_map->mapname, mapname) == 0)
7109 			break;
7110 	}
7111 
7112 	/* if we are looking at a NULL ns_map_list_t, then create a new one */
7113 	if (ns_map == NULL)
7114 	{
7115 		ns_map = (ns_map_list_t *) xalloc(sizeof *ns_map);
7116 		ns_map->mapname = newstr(mapname);
7117 		ns_map->map = (ns_map_t *) xalloc(sizeof *ns_map->map);
7118 		ns_map->next = ns_maps;
7119 		ns_maps = ns_map;
7120 	}
7121 	return ns_map->map;
7122 }
7123 
7124 char *
7125 nsd_map_lookup(map, name, av, statp)
7126 	MAP *map;
7127 	char *name;
7128 	char **av;
7129 	int *statp;
7130 {
7131 	int buflen;
7132 	char *p;
7133 	ns_map_t *ns_map;
7134 	char keybuf[MAXNAME + 1];
7135 	char buf[MAXLINE];
7136 
7137 	if (tTd(38, 20))
7138 		dprintf("nsd_map_lookup(%s, %s)\n", map->map_mname, name);
7139 
7140 	buflen = strlen(name);
7141 	if (buflen > sizeof keybuf - 1)
7142 		buflen = sizeof keybuf - 1;
7143 	memmove(keybuf, name, buflen);
7144 	keybuf[buflen] = '\0';
7145 	if (!bitset(MF_NOFOLDCASE, map->map_mflags))
7146 		makelower(keybuf);
7147 
7148 	ns_map = ns_map_t_find(map->map_file);
7149 	if (ns_map == NULL)
7150 	{
7151 		if (tTd(38, 20))
7152 			dprintf("nsd_map_t_find failed\n");
7153 		return NULL;
7154 	}
7155 
7156 	if (ns_lookup(ns_map, NULL, map->map_file,
7157 		      keybuf, NULL, buf, MAXLINE) == NULL)
7158 		return NULL;
7159 
7160 	/* Null out trailing \n */
7161 	if ((p = strchr(buf, '\n')) != NULL)
7162 		*p = '\0';
7163 
7164 	return map_rewrite(map, buf, strlen(buf), av);
7165 }
7166 #endif /* MAP_NSD */
7167 
7168 char *
7169 arith_map_lookup(map, name, av, statp)
7170 	MAP *map;
7171 	char *name;
7172 	char **av;
7173 	int *statp;
7174 {
7175 	long r;
7176 	long v[2];
7177 	bool res = FALSE;
7178 	bool boolres;
7179 	static char result[16];
7180 	char **cpp;
7181 
7182 	if (tTd(38, 2))
7183 	{
7184 		dprintf("arith_map_lookup: key '%s'\n", name);
7185 		for (cpp = av; cpp != NULL && *cpp != NULL; cpp++)
7186 			dprintf("arith_map_lookup: arg '%s'\n", *cpp);
7187 	}
7188 	r = 0;
7189 	boolres = FALSE;
7190 	cpp = av;
7191 	*statp = EX_OK;
7192 
7193 	/*
7194 	**  read arguments for arith map
7195 	**  - no check is made whether they are really numbers
7196 	**  - just ignores args after the second
7197 	*/
7198 	for (++cpp; cpp != NULL && *cpp != NULL && r < 2; cpp++)
7199 		v[r++] = strtol(*cpp, NULL, 0);
7200 
7201 	/* operator and (at least) two operands given? */
7202 	if (name != NULL && r == 2)
7203 	{
7204 		switch(*name)
7205 		{
7206 #if _FFR_ARITH
7207 		  case '|':
7208 			r = v[0] | v[1];
7209 			break;
7210 
7211 		  case '&':
7212 			r = v[0] & v[1];
7213 			break;
7214 
7215 		  case '%':
7216 			if (v[1] == 0)
7217 				return NULL;
7218 			r = v[0] % v[1];
7219 			break;
7220 #endif /* _FFR_ARITH */
7221 
7222 		  case '+':
7223 			r = v[0] + v[1];
7224 			break;
7225 
7226 		  case '-':
7227 			r = v[0] - v[1];
7228 			break;
7229 
7230 		  case '*':
7231 			r = v[0] * v[1];
7232 			break;
7233 
7234 		  case '/':
7235 			if (v[1] == 0)
7236 				return NULL;
7237 			r = v[0] / v[1];
7238 			break;
7239 
7240 		  case 'l':
7241 			res = v[0] < v[1];
7242 			boolres = TRUE;
7243 			break;
7244 
7245 		  case '=':
7246 			res = v[0] == v[1];
7247 			boolres = TRUE;
7248 			break;
7249 
7250 		  default:
7251 			/* XXX */
7252 			*statp = EX_CONFIG;
7253 			if (LogLevel > 10)
7254 				sm_syslog(LOG_WARNING, NOQID,
7255 					  "arith_map: unknown operator %c",
7256 					  isprint(*name) ? *name : '?');
7257 			return NULL;
7258 		}
7259 		if (boolres)
7260 			snprintf(result, sizeof result, res ? "TRUE" : "FALSE");
7261 		else
7262 			snprintf(result, sizeof result, "%ld", r);
7263 		return result;
7264 	}
7265 	*statp = EX_CONFIG;
7266 	return NULL;
7267 }
7268