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