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