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