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