xref: /illumos-gate/usr/src/cmd/sendmail/src/domain.c (revision 3ee0e492)
1 /*
2  * Copyright (c) 1998-2004, 2006 Sendmail, Inc. and its suppliers.
3  *	All rights reserved.
4  * Copyright (c) 1986, 1995-1997 Eric P. Allman.  All rights reserved.
5  * Copyright (c) 1988, 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 #pragma ident	"%Z%%M%	%I%	%E% SMI"
15 
16 #include <sendmail.h>
17 
18 #if NAMED_BIND
19 SM_RCSID("@(#)$Id: domain.c,v 8.199 2006/04/18 00:00:34 ca Exp $ (with name server)")
20 #else /* NAMED_BIND */
21 SM_RCSID("@(#)$Id: domain.c,v 8.199 2006/04/18 00:00:34 ca Exp $ (without name server)")
22 #endif /* NAMED_BIND */
23 
24 #if NAMED_BIND
25 
26 # include <arpa/inet.h>
27 
28 
29 /*
30 **  The standard udp packet size PACKETSZ (512) is not sufficient for some
31 **  nameserver answers containing very many resource records. The resolver
32 **  may switch to tcp and retry if it detects udp packet overflow.
33 **  Also note that the resolver routines res_query and res_search return
34 **  the size of the *un*truncated answer in case the supplied answer buffer
35 **  it not big enough to accommodate the entire answer.
36 */
37 
38 # ifndef MAXPACKET
39 #  define MAXPACKET 8192	/* max packet size used internally by BIND */
40 # endif /* ! MAXPACKET */
41 
42 typedef union
43 {
44 	HEADER		qb1;
45 	unsigned char	qb2[MAXPACKET];
46 } querybuf;
47 
48 # ifndef MXHOSTBUFSIZE
49 #  define MXHOSTBUFSIZE	(128 * MAXMXHOSTS)
50 # endif /* ! MXHOSTBUFSIZE */
51 
52 static char	MXHostBuf[MXHOSTBUFSIZE];
53 #if (MXHOSTBUFSIZE < 2) || (MXHOSTBUFSIZE >= INT_MAX/2)
54 	ERROR: _MXHOSTBUFSIZE is out of range
55 #endif /* (MXHOSTBUFSIZE < 2) || (MXHOSTBUFSIZE >= INT_MAX/2) */
56 
57 # ifndef MAXDNSRCH
58 #  define MAXDNSRCH	6	/* number of possible domains to search */
59 # endif /* ! MAXDNSRCH */
60 
61 # ifndef RES_DNSRCH_VARIABLE
62 #  define RES_DNSRCH_VARIABLE	_res.dnsrch
63 # endif /* ! RES_DNSRCH_VARIABLE */
64 
65 # ifndef NO_DATA
66 #  define NO_DATA	NO_ADDRESS
67 # endif /* ! NO_DATA */
68 
69 # ifndef HFIXEDSZ
70 #  define HFIXEDSZ	12	/* sizeof(HEADER) */
71 # endif /* ! HFIXEDSZ */
72 
73 # define MAXCNAMEDEPTH	10	/* maximum depth of CNAME recursion */
74 
75 # if defined(__RES) && (__RES >= 19940415)
76 #  define RES_UNC_T	char *
77 # else /* defined(__RES) && (__RES >= 19940415) */
78 #  define RES_UNC_T	unsigned char *
79 # endif /* defined(__RES) && (__RES >= 19940415) */
80 
81 static int	mxrand __P((char *));
82 static int	fallbackmxrr __P((int, unsigned short *, char **));
83 
84 /*
85 **  GETFALLBACKMXRR -- get MX resource records for fallback MX host.
86 **
87 **	We have to initialize this once before doing anything else.
88 **	Moreover, we have to repeat this from time to time to avoid
89 **	stale data, e.g., in persistent queue runners.
90 **	This should be done in a parent process so the child
91 **	processes have the right data.
92 **
93 **	Parameters:
94 **		host -- the name of the fallback MX host.
95 **
96 **	Returns:
97 **		number of MX records.
98 **
99 **	Side Effects:
100 **		Populates NumFallbackMXHosts and fbhosts.
101 **		Sets renewal time (based on TTL).
102 */
103 
104 int NumFallbackMXHosts = 0;	/* Number of fallback MX hosts (after MX expansion) */
105 static char *fbhosts[MAXMXHOSTS + 1];
106 
107 int
108 getfallbackmxrr(host)
109 	char *host;
110 {
111 	int i, rcode;
112 	int ttl;
113 	static time_t renew = 0;
114 
115 #if 0
116 	/* This is currently done before this function is called. */
117 	if (host == NULL || *host == '\0')
118 		return 0;
119 #endif /* 0 */
120 	if (NumFallbackMXHosts > 0 && renew > curtime())
121 		return NumFallbackMXHosts;
122 	if (host[0] == '[')
123 	{
124 		fbhosts[0] = host;
125 		NumFallbackMXHosts = 1;
126 	}
127 	else
128 	{
129 		/* free old data */
130 		for (i = 0; i < NumFallbackMXHosts; i++)
131 			sm_free(fbhosts[i]);
132 
133 		/* get new data */
134 		NumFallbackMXHosts = getmxrr(host, fbhosts, NULL, false,
135 					     &rcode, false, &ttl);
136 		renew = curtime() + ttl;
137 		for (i = 0; i < NumFallbackMXHosts; i++)
138 			fbhosts[i] = newstr(fbhosts[i]);
139 	}
140 	return NumFallbackMXHosts;
141 }
142 
143 /*
144 **  FALLBACKMXRR -- add MX resource records for fallback MX host to list.
145 **
146 **	Parameters:
147 **		nmx -- current number of MX records.
148 **		prefs -- array of preferences.
149 **		mxhosts -- array of MX hosts (maximum size: MAXMXHOSTS)
150 **
151 **	Returns:
152 **		new number of MX records.
153 **
154 **	Side Effects:
155 **		If FallbackMX was set, it appends the MX records for
156 **		that host to mxhosts (and modifies prefs accordingly).
157 */
158 
159 static int
160 fallbackmxrr(nmx, prefs, mxhosts)
161 	int nmx;
162 	unsigned short *prefs;
163 	char **mxhosts;
164 {
165 	int i;
166 
167 	for (i = 0; i < NumFallbackMXHosts && nmx < MAXMXHOSTS; i++)
168 	{
169 		if (nmx > 0)
170 			prefs[nmx] = prefs[nmx - 1] + 1;
171 		else
172 			prefs[nmx] = 0;
173 		mxhosts[nmx++] = fbhosts[i];
174 	}
175 	return nmx;
176 }
177 
178 /*
179 **  GETMXRR -- get MX resource records for a domain
180 **
181 **	Parameters:
182 **		host -- the name of the host to MX.
183 **		mxhosts -- a pointer to a return buffer of MX records.
184 **		mxprefs -- a pointer to a return buffer of MX preferences.
185 **			If NULL, don't try to populate.
186 **		droplocalhost -- If true, all MX records less preferred
187 **			than the local host (as determined by $=w) will
188 **			be discarded.
189 **		rcode -- a pointer to an EX_ status code.
190 **		tryfallback -- add also fallback MX host?
191 **		pttl -- pointer to return TTL (can be NULL).
192 **
193 **	Returns:
194 **		The number of MX records found.
195 **		-1 if there is an internal failure.
196 **		If no MX records are found, mxhosts[0] is set to host
197 **			and 1 is returned.
198 **
199 **	Side Effects:
200 **		The entries made for mxhosts point to a static array
201 **		MXHostBuf[MXHOSTBUFSIZE], so the data needs to be copied,
202 **		if it must be preserved across calls to this function.
203 */
204 
205 int
206 getmxrr(host, mxhosts, mxprefs, droplocalhost, rcode, tryfallback, pttl)
207 	char *host;
208 	char **mxhosts;
209 	unsigned short *mxprefs;
210 	bool droplocalhost;
211 	int *rcode;
212 	bool tryfallback;
213 	int *pttl;
214 {
215 	register unsigned char *eom, *cp;
216 	register int i, j, n;
217 	int nmx = 0;
218 	register char *bp;
219 	HEADER *hp;
220 	querybuf answer;
221 	int ancount, qdcount, buflen;
222 	bool seenlocal = false;
223 	unsigned short pref, type;
224 	unsigned short localpref = 256;
225 	char *fallbackMX = FallbackMX;
226 	bool trycanon = false;
227 	unsigned short *prefs;
228 	int (*resfunc) __P((const char *, int, int, u_char *, int));
229 	unsigned short prefer[MAXMXHOSTS];
230 	int weight[MAXMXHOSTS];
231 	int ttl = 0;
232 	extern int res_query(), res_search();
233 
234 	if (tTd(8, 2))
235 		sm_dprintf("getmxrr(%s, droplocalhost=%d)\n",
236 			   host, droplocalhost);
237 	*rcode = EX_OK;
238 	if (pttl != NULL)
239 		*pttl = SM_DEFAULT_TTL;
240 	if (*host == '\0')
241 		return 0;
242 
243 	if ((fallbackMX != NULL && droplocalhost &&
244 	     wordinclass(fallbackMX, 'w')) || !tryfallback)
245 	{
246 		/* don't use fallback for this pass */
247 		fallbackMX = NULL;
248 	}
249 
250 	if (mxprefs != NULL)
251 		prefs = mxprefs;
252 	else
253 		prefs = prefer;
254 
255 	/* efficiency hack -- numeric or non-MX lookups */
256 	if (host[0] == '[')
257 		goto punt;
258 
259 	/*
260 	**  If we don't have MX records in our host switch, don't
261 	**  try for MX records.  Note that this really isn't "right",
262 	**  since we might be set up to try NIS first and then DNS;
263 	**  if the host is found in NIS we really shouldn't be doing
264 	**  MX lookups.  However, that should be a degenerate case.
265 	*/
266 
267 	if (!UseNameServer)
268 		goto punt;
269 	if (HasWildcardMX && ConfigLevel >= 6)
270 		resfunc = res_query;
271 	else
272 		resfunc = res_search;
273 
274 	errno = 0;
275 	n = (*resfunc)(host, C_IN, T_MX, (unsigned char *) &answer,
276 		       sizeof(answer));
277 	if (n < 0)
278 	{
279 		if (tTd(8, 1))
280 			sm_dprintf("getmxrr: res_search(%s) failed (errno=%d, h_errno=%d)\n",
281 				host == NULL ? "<NULL>" : host, errno, h_errno);
282 		switch (h_errno)
283 		{
284 		  case NO_DATA:
285 			trycanon = true;
286 			/* FALLTHROUGH */
287 
288 		  case NO_RECOVERY:
289 			/* no MX data on this host */
290 			goto punt;
291 
292 		  case HOST_NOT_FOUND:
293 # if BROKEN_RES_SEARCH
294 		  case 0:	/* Ultrix resolver retns failure w/ h_errno=0 */
295 # endif /* BROKEN_RES_SEARCH */
296 			/* host doesn't exist in DNS; might be in /etc/hosts */
297 			trycanon = true;
298 			*rcode = EX_NOHOST;
299 			goto punt;
300 
301 		  case TRY_AGAIN:
302 		  case -1:
303 			/* couldn't connect to the name server */
304 			if (fallbackMX != NULL)
305 			{
306 				/* name server is hosed -- push to fallback */
307 				return fallbackmxrr(nmx, prefs, mxhosts);
308 			}
309 			/* it might come up later; better queue it up */
310 			*rcode = EX_TEMPFAIL;
311 			break;
312 
313 		  default:
314 			syserr("getmxrr: res_search (%s) failed with impossible h_errno (%d)",
315 				host, h_errno);
316 			*rcode = EX_OSERR;
317 			break;
318 		}
319 
320 		/* irreconcilable differences */
321 		return -1;
322 	}
323 
324 	/* avoid problems after truncation in tcp packets */
325 	if (n > sizeof(answer))
326 		n = sizeof(answer);
327 
328 	/* find first satisfactory answer */
329 	hp = (HEADER *)&answer;
330 	cp = (unsigned char *)&answer + HFIXEDSZ;
331 	eom = (unsigned char *)&answer + n;
332 	for (qdcount = ntohs((unsigned short) hp->qdcount);
333 	     qdcount--;
334 	     cp += n + QFIXEDSZ)
335 	{
336 		if ((n = dn_skipname(cp, eom)) < 0)
337 			goto punt;
338 	}
339 
340 	/* NOTE: see definition of MXHostBuf! */
341 	buflen = sizeof(MXHostBuf) - 1;
342 	SM_ASSERT(buflen > 0);
343 	bp = MXHostBuf;
344 	ancount = ntohs((unsigned short) hp->ancount);
345 
346 	/* See RFC 1035 for layout of RRs. */
347 	/* XXX leave room for FallbackMX ? */
348 	while (--ancount >= 0 && cp < eom && nmx < MAXMXHOSTS - 1)
349 	{
350 		if ((n = dn_expand((unsigned char *)&answer, eom, cp,
351 				   (RES_UNC_T) bp, buflen)) < 0)
352 			break;
353 		cp += n;
354 		GETSHORT(type, cp);
355 		cp += INT16SZ;		/* skip over class */
356 		GETLONG(ttl, cp);
357 		GETSHORT(n, cp);	/* rdlength */
358 		if (type != T_MX)
359 		{
360 			if (tTd(8, 8) || _res.options & RES_DEBUG)
361 				sm_dprintf("unexpected answer type %d, size %d\n",
362 					type, n);
363 			cp += n;
364 			continue;
365 		}
366 		GETSHORT(pref, cp);
367 		if ((n = dn_expand((unsigned char *)&answer, eom, cp,
368 				   (RES_UNC_T) bp, buflen)) < 0)
369 			break;
370 		cp += n;
371 		n = strlen(bp);
372 # if 0
373 		/* Can this happen? */
374 		if (n == 0)
375 		{
376 			if (LogLevel > 4)
377 				sm_syslog(LOG_ERR, NOQID,
378 					  "MX records for %s contain empty string",
379 					  host);
380 			continue;
381 		}
382 # endif /* 0 */
383 		if (wordinclass(bp, 'w'))
384 		{
385 			if (tTd(8, 3))
386 				sm_dprintf("found localhost (%s) in MX list, pref=%d\n",
387 					bp, pref);
388 			if (droplocalhost)
389 			{
390 				if (!seenlocal || pref < localpref)
391 					localpref = pref;
392 				seenlocal = true;
393 				continue;
394 			}
395 			weight[nmx] = 0;
396 		}
397 		else
398 			weight[nmx] = mxrand(bp);
399 		prefs[nmx] = pref;
400 		mxhosts[nmx++] = bp;
401 		bp += n;
402 		if (bp[-1] != '.')
403 		{
404 			*bp++ = '.';
405 			n++;
406 		}
407 		*bp++ = '\0';
408 		if (buflen < n + 1)
409 		{
410 			/* don't want to wrap buflen */
411 			break;
412 		}
413 		buflen -= n + 1;
414 	}
415 
416 	/* return only one TTL entry, that should be sufficient */
417 	if (ttl > 0 && pttl != NULL)
418 		*pttl = ttl;
419 
420 	/* sort the records */
421 	for (i = 0; i < nmx; i++)
422 	{
423 		for (j = i + 1; j < nmx; j++)
424 		{
425 			if (prefs[i] > prefs[j] ||
426 			    (prefs[i] == prefs[j] && weight[i] > weight[j]))
427 			{
428 				register int temp;
429 				register char *temp1;
430 
431 				temp = prefs[i];
432 				prefs[i] = prefs[j];
433 				prefs[j] = temp;
434 				temp1 = mxhosts[i];
435 				mxhosts[i] = mxhosts[j];
436 				mxhosts[j] = temp1;
437 				temp = weight[i];
438 				weight[i] = weight[j];
439 				weight[j] = temp;
440 			}
441 		}
442 		if (seenlocal && prefs[i] >= localpref)
443 		{
444 			/* truncate higher preference part of list */
445 			nmx = i;
446 		}
447 	}
448 
449 	/* delete duplicates from list (yes, some bozos have duplicates) */
450 	for (i = 0; i < nmx - 1; )
451 	{
452 		if (sm_strcasecmp(mxhosts[i], mxhosts[i + 1]) != 0)
453 			i++;
454 		else
455 		{
456 			/* compress out duplicate */
457 			for (j = i + 1; j < nmx; j++)
458 			{
459 				mxhosts[j] = mxhosts[j + 1];
460 				prefs[j] = prefs[j + 1];
461 			}
462 			nmx--;
463 		}
464 	}
465 
466 	if (nmx == 0)
467 	{
468 punt:
469 		if (seenlocal)
470 		{
471 			struct hostent *h = NULL;
472 
473 			/*
474 			**  If we have deleted all MX entries, this is
475 			**  an error -- we should NEVER send to a host that
476 			**  has an MX, and this should have been caught
477 			**  earlier in the config file.
478 			**
479 			**  Some sites prefer to go ahead and try the
480 			**  A record anyway; that case is handled by
481 			**  setting TryNullMXList.  I believe this is a
482 			**  bad idea, but it's up to you....
483 			*/
484 
485 			if (TryNullMXList)
486 			{
487 				SM_SET_H_ERRNO(0);
488 				errno = 0;
489 				h = sm_gethostbyname(host, AF_INET);
490 				if (h == NULL)
491 				{
492 					if (errno == ETIMEDOUT ||
493 					    h_errno == TRY_AGAIN ||
494 					    (errno == ECONNREFUSED &&
495 					     UseNameServer))
496 					{
497 						*rcode = EX_TEMPFAIL;
498 						return -1;
499 					}
500 # if NETINET6
501 					SM_SET_H_ERRNO(0);
502 					errno = 0;
503 					h = sm_gethostbyname(host, AF_INET6);
504 					if (h == NULL &&
505 					    (errno == ETIMEDOUT ||
506 					     h_errno == TRY_AGAIN ||
507 					     (errno == ECONNREFUSED &&
508 					      UseNameServer)))
509 					{
510 						*rcode = EX_TEMPFAIL;
511 						return -1;
512 					}
513 # endif /* NETINET6 */
514 				}
515 			}
516 
517 			if (h == NULL)
518 			{
519 				*rcode = EX_CONFIG;
520 				syserr("MX list for %s points back to %s",
521 				       host, MyHostName);
522 				return -1;
523 			}
524 # if NETINET6
525 			freehostent(h);
526 			h = NULL;
527 # endif /* NETINET6 */
528 		}
529 		if (strlen(host) >= sizeof MXHostBuf)
530 		{
531 			*rcode = EX_CONFIG;
532 			syserr("Host name %s too long",
533 			       shortenstring(host, MAXSHORTSTR));
534 			return -1;
535 		}
536 		(void) sm_strlcpy(MXHostBuf, host, sizeof MXHostBuf);
537 		mxhosts[0] = MXHostBuf;
538 		prefs[0] = 0;
539 		if (host[0] == '[')
540 		{
541 			register char *p;
542 # if NETINET6
543 			struct sockaddr_in6 tmp6;
544 # endif /* NETINET6 */
545 
546 			/* this may be an MX suppression-style address */
547 			p = strchr(MXHostBuf, ']');
548 			if (p != NULL)
549 			{
550 				*p = '\0';
551 
552 				if (inet_addr(&MXHostBuf[1]) != INADDR_NONE)
553 				{
554 					nmx++;
555 					*p = ']';
556 				}
557 # if NETINET6
558 				else if (anynet_pton(AF_INET6, &MXHostBuf[1],
559 						     &tmp6.sin6_addr) == 1)
560 				{
561 					nmx++;
562 					*p = ']';
563 				}
564 # endif /* NETINET6 */
565 				else
566 				{
567 					trycanon = true;
568 					mxhosts[0]++;
569 				}
570 			}
571 		}
572 		if (trycanon &&
573 		    getcanonname(mxhosts[0], sizeof MXHostBuf - 2, false, pttl))
574 		{
575 			/* XXX MXHostBuf == "" ?  is that possible? */
576 			bp = &MXHostBuf[strlen(MXHostBuf)];
577 			if (bp[-1] != '.')
578 			{
579 				*bp++ = '.';
580 				*bp = '\0';
581 			}
582 			nmx = 1;
583 		}
584 	}
585 
586 	/* if we have a default lowest preference, include that */
587 	if (fallbackMX != NULL && !seenlocal)
588 	{
589 		nmx = fallbackmxrr(nmx, prefs, mxhosts);
590 	}
591 	return nmx;
592 }
593 /*
594 **  MXRAND -- create a randomizer for equal MX preferences
595 **
596 **	If two MX hosts have equal preferences we want to randomize
597 **	the selection.  But in order for signatures to be the same,
598 **	we need to randomize the same way each time.  This function
599 **	computes a pseudo-random hash function from the host name.
600 **
601 **	Parameters:
602 **		host -- the name of the host.
603 **
604 **	Returns:
605 **		A random but repeatable value based on the host name.
606 */
607 
608 static int
609 mxrand(host)
610 	register char *host;
611 {
612 	int hfunc;
613 	static unsigned int seed;
614 
615 	if (seed == 0)
616 	{
617 		seed = (int) curtime() & 0xffff;
618 		if (seed == 0)
619 			seed++;
620 	}
621 
622 	if (tTd(17, 9))
623 		sm_dprintf("mxrand(%s)", host);
624 
625 	hfunc = seed;
626 	while (*host != '\0')
627 	{
628 		int c = *host++;
629 
630 		if (isascii(c) && isupper(c))
631 			c = tolower(c);
632 		hfunc = ((hfunc << 1) ^ c) % 2003;
633 	}
634 
635 	hfunc &= 0xff;
636 	hfunc++;
637 
638 	if (tTd(17, 9))
639 		sm_dprintf(" = %d\n", hfunc);
640 	return hfunc;
641 }
642 /*
643 **  BESTMX -- find the best MX for a name
644 **
645 **	This is really a hack, but I don't see any obvious way
646 **	to generalize it at the moment.
647 */
648 
649 /* ARGSUSED3 */
650 char *
651 bestmx_map_lookup(map, name, av, statp)
652 	MAP *map;
653 	char *name;
654 	char **av;
655 	int *statp;
656 {
657 	int nmx;
658 	int saveopts = _res.options;
659 	int i;
660 	ssize_t len = 0;
661 	char *result;
662 	char *mxhosts[MAXMXHOSTS + 1];
663 #if _FFR_BESTMX_BETTER_TRUNCATION
664 	char *buf;
665 #else /* _FFR_BESTMX_BETTER_TRUNCATION */
666 	char *p;
667 	char buf[PSBUFSIZE / 2];
668 #endif /* _FFR_BESTMX_BETTER_TRUNCATION */
669 
670 	_res.options &= ~(RES_DNSRCH|RES_DEFNAMES);
671 	nmx = getmxrr(name, mxhosts, NULL, false, statp, false, NULL);
672 	_res.options = saveopts;
673 	if (nmx <= 0)
674 		return NULL;
675 	if (bitset(MF_MATCHONLY, map->map_mflags))
676 		return map_rewrite(map, name, strlen(name), NULL);
677 	if ((map->map_coldelim == '\0') || (nmx == 1))
678 		return map_rewrite(map, mxhosts[0], strlen(mxhosts[0]), av);
679 
680 	/*
681 	**  We were given a -z flag (return all MXs) and there are multiple
682 	**  ones.  We need to build them all into a list.
683 	*/
684 
685 #if _FFR_BESTMX_BETTER_TRUNCATION
686 	for (i = 0; i < nmx; i++)
687 	{
688 		if (strchr(mxhosts[i], map->map_coldelim) != NULL)
689 		{
690 			syserr("bestmx_map_lookup: MX host %.64s includes map delimiter character 0x%02X",
691 			       mxhosts[i], map->map_coldelim);
692 			return NULL;
693 		}
694 		len += strlen(mxhosts[i]) + 1;
695 		if (len < 0)
696 		{
697 			len -= strlen(mxhosts[i]) + 1;
698 			break;
699 		}
700 	}
701 	buf = (char *) sm_malloc(len);
702 	if (buf == NULL)
703 	{
704 		*statp = EX_UNAVAILABLE;
705 		return NULL;
706 	}
707 	*buf = '\0';
708 	for (i = 0; i < nmx; i++)
709 	{
710 		int end;
711 
712 		end = sm_strlcat(buf, mxhosts[i], len);
713 		if (i != nmx && end + 1 < len)
714 		{
715 			buf[end] = map->map_coldelim;
716 			buf[end + 1] = '\0';
717 		}
718 	}
719 
720 	/* Cleanly truncate for rulesets */
721 	truncate_at_delim(buf, PSBUFSIZE / 2, map->map_coldelim);
722 #else /* _FFR_BESTMX_BETTER_TRUNCATION */
723 	p = buf;
724 	for (i = 0; i < nmx; i++)
725 	{
726 		size_t slen;
727 
728 		if (strchr(mxhosts[i], map->map_coldelim) != NULL)
729 		{
730 			syserr("bestmx_map_lookup: MX host %.64s includes map delimiter character 0x%02X",
731 			       mxhosts[i], map->map_coldelim);
732 			return NULL;
733 		}
734 		slen = strlen(mxhosts[i]);
735 		if (len + slen + 2 > sizeof buf)
736 			break;
737 		if (i > 0)
738 		{
739 			*p++ = map->map_coldelim;
740 			len++;
741 		}
742 		(void) sm_strlcpy(p, mxhosts[i], sizeof buf - len);
743 		p += slen;
744 		len += slen;
745 	}
746 #endif /* _FFR_BESTMX_BETTER_TRUNCATION */
747 
748 	result = map_rewrite(map, buf, len, av);
749 #if _FFR_BESTMX_BETTER_TRUNCATION
750 	sm_free(buf);
751 #endif /* _FFR_BESTMX_BETTER_TRUNCATION */
752 	return result;
753 }
754 /*
755 **  DNS_GETCANONNAME -- get the canonical name for named host using DNS
756 **
757 **	This algorithm tries to be smart about wildcard MX records.
758 **	This is hard to do because DNS doesn't tell is if we matched
759 **	against a wildcard or a specific MX.
760 **
761 **	We always prefer A & CNAME records, since these are presumed
762 **	to be specific.
763 **
764 **	If we match an MX in one pass and lose it in the next, we use
765 **	the old one.  For example, consider an MX matching *.FOO.BAR.COM.
766 **	A hostname bletch.foo.bar.com will match against this MX, but
767 **	will stop matching when we try bletch.bar.com -- so we know
768 **	that bletch.foo.bar.com must have been right.  This fails if
769 **	there was also an MX record matching *.BAR.COM, but there are
770 **	some things that just can't be fixed.
771 **
772 **	Parameters:
773 **		host -- a buffer containing the name of the host.
774 **			This is a value-result parameter.
775 **		hbsize -- the size of the host buffer.
776 **		trymx -- if set, try MX records as well as A and CNAME.
777 **		statp -- pointer to place to store status.
778 **		pttl -- pointer to return TTL (can be NULL).
779 **
780 **	Returns:
781 **		true -- if the host matched.
782 **		false -- otherwise.
783 */
784 
785 bool
786 dns_getcanonname(host, hbsize, trymx, statp, pttl)
787 	char *host;
788 	int hbsize;
789 	bool trymx;
790 	int *statp;
791 	int *pttl;
792 {
793 	register unsigned char *eom, *ap;
794 	register char *cp;
795 	register int n;
796 	HEADER *hp;
797 	querybuf answer;
798 	int ancount, qdcount;
799 	int ret;
800 	char **domain;
801 	int type;
802 	int ttl = 0;
803 	char **dp;
804 	char *mxmatch;
805 	bool amatch;
806 	bool gotmx = false;
807 	int qtype;
808 	int initial;
809 	int loopcnt;
810 	char nbuf[SM_MAX(MAXPACKET, MAXDNAME*2+2)];
811 	char *searchlist[MAXDNSRCH + 2];
812 
813 	if (tTd(8, 2))
814 		sm_dprintf("dns_getcanonname(%s, trymx=%d)\n", host, trymx);
815 
816 	if ((_res.options & RES_INIT) == 0 && res_init() == -1)
817 	{
818 		*statp = EX_UNAVAILABLE;
819 		return false;
820 	}
821 
822 	*statp = EX_OK;
823 
824 	/*
825 	**  Initialize domain search list.  If there is at least one
826 	**  dot in the name, search the unmodified name first so we
827 	**  find "vse.CS" in Czechoslovakia instead of in the local
828 	**  domain (e.g., vse.CS.Berkeley.EDU).  Note that there is no
829 	**  longer a country named Czechoslovakia but this type of problem
830 	**  is still present.
831 	**
832 	**  Older versions of the resolver could create this
833 	**  list by tearing apart the host name.
834 	*/
835 
836 	loopcnt = 0;
837 cnameloop:
838 	/* Check for dots in the name */
839 	for (cp = host, n = 0; *cp != '\0'; cp++)
840 		if (*cp == '.')
841 			n++;
842 
843 	/*
844 	**  Build the search list.
845 	**	If there is at least one dot in name, start with a null
846 	**	domain to search the unmodified name first.
847 	**	If name does not end with a dot and search up local domain
848 	**	tree desired, append each local domain component to the
849 	**	search list; if name contains no dots and default domain
850 	**	name is desired, append default domain name to search list;
851 	**	else if name ends in a dot, remove that dot.
852 	*/
853 
854 	dp = searchlist;
855 	if (n > 0)
856 		*dp++ = "";
857 	if (n >= 0 && *--cp != '.' && bitset(RES_DNSRCH, _res.options))
858 	{
859 		/* make sure there are less than MAXDNSRCH domains */
860 		for (domain = RES_DNSRCH_VARIABLE, ret = 0;
861 		     *domain != NULL && ret < MAXDNSRCH;
862 		     ret++)
863 			*dp++ = *domain++;
864 	}
865 	else if (n == 0 && bitset(RES_DEFNAMES, _res.options))
866 	{
867 		*dp++ = _res.defdname;
868 	}
869 	else if (*cp == '.')
870 	{
871 		*cp = '\0';
872 	}
873 	*dp = NULL;
874 
875 	/*
876 	**  Now loop through the search list, appending each domain in turn
877 	**  name and searching for a match.
878 	*/
879 
880 	mxmatch = NULL;
881 	initial = T_A;
882 # if NETINET6
883 	if (InetMode == AF_INET6)
884 		initial = T_AAAA;
885 # endif /* NETINET6 */
886 	qtype = initial;
887 
888 	for (dp = searchlist; *dp != NULL; )
889 	{
890 		if (qtype == initial)
891 			gotmx = false;
892 		if (tTd(8, 5))
893 			sm_dprintf("dns_getcanonname: trying %s.%s (%s)\n",
894 				host, *dp,
895 # if NETINET6
896 				qtype == T_AAAA ? "AAAA" :
897 # endif /* NETINET6 */
898 				qtype == T_A ? "A" :
899 				qtype == T_MX ? "MX" :
900 				"???");
901 		errno = 0;
902 		ret = res_querydomain(host, *dp, C_IN, qtype,
903 				      answer.qb2, sizeof(answer.qb2));
904 		if (ret <= 0)
905 		{
906 			int save_errno = errno;
907 
908 			if (tTd(8, 7))
909 				sm_dprintf("\tNO: errno=%d, h_errno=%d\n",
910 					   save_errno, h_errno);
911 
912 			if (save_errno == ECONNREFUSED || h_errno == TRY_AGAIN)
913 			{
914 				/*
915 				**  the name server seems to be down or broken.
916 				*/
917 
918 				SM_SET_H_ERRNO(TRY_AGAIN);
919 				if (**dp == '\0')
920 				{
921 					if (*statp == EX_OK)
922 						*statp = EX_TEMPFAIL;
923 					goto nexttype;
924 				}
925 				*statp = EX_TEMPFAIL;
926 
927 				if (WorkAroundBrokenAAAA)
928 				{
929 					/*
930 					**  Only return if not TRY_AGAIN as an
931 					**  attempt with a different qtype may
932 					**  succeed (res_querydomain() calls
933 					**  res_query() calls res_send() which
934 					**  sets errno to ETIMEDOUT if the
935 					**  nameservers could be contacted but
936 					**  didn't give an answer).
937 					*/
938 
939 					if (save_errno != ETIMEDOUT)
940 						return false;
941 				}
942 				else
943 					return false;
944 			}
945 
946 nexttype:
947 			if (h_errno != HOST_NOT_FOUND)
948 			{
949 				/* might have another type of interest */
950 # if NETINET6
951 				if (qtype == T_AAAA)
952 				{
953 					qtype = T_A;
954 					continue;
955 				}
956 				else
957 # endif /* NETINET6 */
958 				if (qtype == T_A && !gotmx &&
959 				    (trymx || **dp == '\0'))
960 				{
961 					qtype = T_MX;
962 					continue;
963 				}
964 			}
965 
966 			/* definite no -- try the next domain */
967 			dp++;
968 			qtype = initial;
969 			continue;
970 		}
971 		else if (tTd(8, 7))
972 			sm_dprintf("\tYES\n");
973 
974 		/* avoid problems after truncation in tcp packets */
975 		if (ret > sizeof(answer))
976 			ret = sizeof(answer);
977 		SM_ASSERT(ret >= 0);
978 
979 		/*
980 		**  Appear to have a match.  Confirm it by searching for A or
981 		**  CNAME records.  If we don't have a local domain
982 		**  wild card MX record, we will accept MX as well.
983 		*/
984 
985 		hp = (HEADER *) &answer;
986 		ap = (unsigned char *) &answer + HFIXEDSZ;
987 		eom = (unsigned char *) &answer + ret;
988 
989 		/* skip question part of response -- we know what we asked */
990 		for (qdcount = ntohs((unsigned short) hp->qdcount);
991 		     qdcount--;
992 		     ap += ret + QFIXEDSZ)
993 		{
994 			if ((ret = dn_skipname(ap, eom)) < 0)
995 			{
996 				if (tTd(8, 20))
997 					sm_dprintf("qdcount failure (%d)\n",
998 						ntohs((unsigned short) hp->qdcount));
999 				*statp = EX_SOFTWARE;
1000 				return false;		/* ???XXX??? */
1001 			}
1002 		}
1003 
1004 		amatch = false;
1005 		for (ancount = ntohs((unsigned short) hp->ancount);
1006 		     --ancount >= 0 && ap < eom;
1007 		     ap += n)
1008 		{
1009 			n = dn_expand((unsigned char *) &answer, eom, ap,
1010 				      (RES_UNC_T) nbuf, sizeof nbuf);
1011 			if (n < 0)
1012 				break;
1013 			ap += n;
1014 			GETSHORT(type, ap);
1015 			ap += INT16SZ;		/* skip over class */
1016 			GETLONG(ttl, ap);
1017 			GETSHORT(n, ap);	/* rdlength */
1018 			switch (type)
1019 			{
1020 			  case T_MX:
1021 				gotmx = true;
1022 				if (**dp != '\0' && HasWildcardMX)
1023 				{
1024 					/*
1025 					**  If we are using MX matches and have
1026 					**  not yet gotten one, save this one
1027 					**  but keep searching for an A or
1028 					**  CNAME match.
1029 					*/
1030 
1031 					if (trymx && mxmatch == NULL)
1032 						mxmatch = *dp;
1033 					continue;
1034 				}
1035 
1036 				/*
1037 				**  If we did not append a domain name, this
1038 				**  must have been a canonical name to start
1039 				**  with.  Even if we did append a domain name,
1040 				**  in the absence of a wildcard MX this must
1041 				**  still be a real MX match.
1042 				**  Such MX matches are as good as an A match,
1043 				**  fall through.
1044 				*/
1045 				/* FALLTHROUGH */
1046 
1047 # if NETINET6
1048 			  case T_AAAA:
1049 # endif /* NETINET6 */
1050 			  case T_A:
1051 				/* Flag that a good match was found */
1052 				amatch = true;
1053 
1054 				/* continue in case a CNAME also exists */
1055 				continue;
1056 
1057 			  case T_CNAME:
1058 				if (DontExpandCnames)
1059 				{
1060 					/* got CNAME -- guaranteed canonical */
1061 					amatch = true;
1062 					break;
1063 				}
1064 
1065 				if (loopcnt++ > MAXCNAMEDEPTH)
1066 				{
1067 					/*XXX should notify postmaster XXX*/
1068 					message("DNS failure: CNAME loop for %s",
1069 						host);
1070 					if (CurEnv->e_message == NULL)
1071 					{
1072 						char ebuf[MAXLINE];
1073 
1074 						(void) sm_snprintf(ebuf,
1075 							sizeof ebuf,
1076 							"Deferred: DNS failure: CNAME loop for %.100s",
1077 							host);
1078 						CurEnv->e_message =
1079 						    sm_rpool_strdup_x(
1080 							CurEnv->e_rpool, ebuf);
1081 					}
1082 					SM_SET_H_ERRNO(NO_RECOVERY);
1083 					*statp = EX_CONFIG;
1084 					return false;
1085 				}
1086 
1087 				/* value points at name */
1088 				if ((ret = dn_expand((unsigned char *)&answer,
1089 						     eom, ap, (RES_UNC_T) nbuf,
1090 						     sizeof(nbuf))) < 0)
1091 					break;
1092 				(void) sm_strlcpy(host, nbuf, hbsize);
1093 
1094 				/*
1095 				**  RFC 1034 section 3.6 specifies that CNAME
1096 				**  should point at the canonical name -- but
1097 				**  urges software to try again anyway.
1098 				*/
1099 
1100 				goto cnameloop;
1101 
1102 			  default:
1103 				/* not a record of interest */
1104 				continue;
1105 			}
1106 		}
1107 
1108 		if (amatch)
1109 		{
1110 			/*
1111 			**  Got a good match -- either an A, CNAME, or an
1112 			**  exact MX record.  Save it and get out of here.
1113 			*/
1114 
1115 			mxmatch = *dp;
1116 			break;
1117 		}
1118 
1119 		/*
1120 		**  Nothing definitive yet.
1121 		**	If this was a T_A query and we haven't yet found a MX
1122 		**		match, try T_MX if allowed to do so.
1123 		**	Otherwise, try the next domain.
1124 		*/
1125 
1126 # if NETINET6
1127 		if (qtype == T_AAAA)
1128 			qtype = T_A;
1129 		else
1130 # endif /* NETINET6 */
1131 		if (qtype == T_A && !gotmx && (trymx || **dp == '\0'))
1132 			qtype = T_MX;
1133 		else
1134 		{
1135 			qtype = initial;
1136 			dp++;
1137 		}
1138 	}
1139 
1140 	/* if nothing was found, we are done */
1141 	if (mxmatch == NULL)
1142 	{
1143 		if (*statp == EX_OK)
1144 			*statp = EX_NOHOST;
1145 		return false;
1146 	}
1147 
1148 	/*
1149 	**  Create canonical name and return.
1150 	**  If saved domain name is null, name was already canonical.
1151 	**  Otherwise append the saved domain name.
1152 	*/
1153 
1154 	(void) sm_snprintf(nbuf, sizeof nbuf, "%.*s%s%.*s", MAXDNAME, host,
1155 			   *mxmatch == '\0' ? "" : ".",
1156 			   MAXDNAME, mxmatch);
1157 	(void) sm_strlcpy(host, nbuf, hbsize);
1158 	if (tTd(8, 5))
1159 		sm_dprintf("dns_getcanonname: %s\n", host);
1160 	*statp = EX_OK;
1161 
1162 	/* return only one TTL entry, that should be sufficient */
1163 	if (ttl > 0 && pttl != NULL)
1164 		*pttl = ttl;
1165 	return true;
1166 }
1167 #endif /* NAMED_BIND */
1168