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