Home | History | Annotate | Line # | Download | only in smtp
      1 /*	$NetBSD: smtp_addr.c,v 1.6 2025/02/25 19:15:49 christos Exp $	*/
      2 
      3 /*++
      4 /* NAME
      5 /*	smtp_addr 3
      6 /* SUMMARY
      7 /*	SMTP server address lookup
      8 /* SYNOPSIS
      9 /*	#include "smtp_addr.h"
     10 /*
     11 /*	DNS_RR *smtp_domain_addr(name, mxrr, misc_flags, why, found_myself)
     12 /*	char	*name;
     13 /*	DNS_RR  **mxrr;
     14 /*	int	misc_flags;
     15 /*	DSN_BUF	*why;
     16 /*	int	*found_myself;
     17 /*
     18 /*	DNS_RR *smtp_host_addr(name, misc_flags, why)
     19 /*	char	*name;
     20 /*	int	misc_flags;
     21 /*	DSN_BUF	*why;
     22 /*
     23 /*	DNS_RR	*smtp_service_addr(name, service, mxrr, misc_flags, why,
     24 /*					found_myself)
     25 /*	const char *name;
     26 /*	const char *service;
     27 /*	DNS_RR  **mxrr;
     28 /*	int	misc_flags;
     29 /*	DSN_BUF	*why;
     30 /*	int	*found_myself;
     31 /* DESCRIPTION
     32 /*	This module implements Internet address lookups. By default,
     33 /*	lookups are done via the Internet domain name service (DNS).
     34 /*	A reasonable number of CNAME indirections is permitted. When
     35 /*	DNS lookups are disabled, host address lookup is done with
     36 /*	getnameinfo() or gethostbyname().
     37 /*
     38 /*	smtp_domain_addr() looks up the network addresses for mail
     39 /*	exchanger hosts listed for the named domain. Addresses are
     40 /*	returned in most-preferred first order. The result is truncated
     41 /*	so that it contains only hosts that are more preferred than the
     42 /*	local mail server itself. The found_myself result parameter
     43 /*	is updated when the local MTA is MX host for the specified
     44 /*	destination.  If MX records were found, the rname, qname,
     45 /*	and dnssec validation status of the MX RRset are returned
     46 /*	via mxrr, which the caller must free with dns_rr_free().
     47 /*	Fallback from MX to address lookups is governed by RFC 2821,
     48 /*	and by local policy (var_ign_mx_lookup_err).
     49 /*
     50 /*	When no mail exchanger is listed in the DNS for \fIname\fR, the
     51 /*	request is passed to smtp_host_addr().
     52 /*
     53 /*	It is an error to call smtp_domain_addr() when DNS lookups are
     54 /*	disabled.
     55 /*
     56 /*	smtp_host_addr() looks up all addresses listed for the named
     57 /*	host.  The host can be specified as a numerical Internet network
     58 /*	address, or as a symbolic host name.
     59 /*
     60 /*	smtp_service_addr() looks up addresses for hosts specified
     61 /*	in SRV records for the specified domain and service. This
     62 /*	supports the features of smtp_domain_addr() except that
     63 /*	the order of SRV records is determined by RFC 2782, and
     64 /*	that address records are not sorted by IP address family
     65 /*	preference.  Fallback from SRV to MX or address lookups is
     66 /*	governed by local policy (var_ign_mx_lookup_err and
     67 /*	var_allow_srv_fallback).
     68 /*
     69 /*	Results from smtp_domain_addr(), smtp_host_addr(), and
     70 /*	smtp_service_addr() are destroyed by dns_rr_free(), including
     71 /*	null lists.
     72 /* DIAGNOSTICS
     73 /*	Panics: interface violations. For example, calling smtp_domain_addr()
     74 /*	when DNS lookups are explicitly disabled.
     75 /*
     76 /*	All routines either return a DNS_RR pointer, or return a null
     77 /*	pointer and update the \fIwhy\fR argument accordingly.
     78 /* LICENSE
     79 /* .ad
     80 /* .fi
     81 /*	The Secure Mailer license must be distributed with this software.
     82 /* AUTHOR(S)
     83 /*	Wietse Venema
     84 /*	IBM T.J. Watson Research
     85 /*	P.O. Box 704
     86 /*	Yorktown Heights, NY 10598, USA
     87 /*
     88 /*	Wietse Venema
     89 /*	Google, Inc.
     90 /*	111 8th Avenue
     91 /*	New York, NY 10011, USA
     92 /*
     93 /*	SRV Support by
     94 /*	Tomas Korbar
     95 /*	Red Hat, Inc.
     96 /*--*/
     97 
     98 /* System library. */
     99 
    100 #include <sys_defs.h>
    101 #include <sys/socket.h>
    102 #include <netinet/in.h>
    103 #include <arpa/inet.h>
    104 #include <stdlib.h>
    105 #include <netdb.h>
    106 #include <ctype.h>
    107 #include <string.h>
    108 #include <unistd.h>
    109 #include <errno.h>
    110 
    111 /* Utility library. */
    112 
    113 #include <msg.h>
    114 #include <vstring.h>
    115 #include <mymalloc.h>
    116 #include <inet_addr_list.h>
    117 #include <stringops.h>
    118 #include <myaddrinfo.h>
    119 #include <inet_proto.h>
    120 #include <midna_domain.h>
    121 
    122 /* Global library. */
    123 
    124 #include <mail_params.h>
    125 #include <own_inet_addr.h>
    126 #include <dsn_buf.h>
    127 
    128 /* DNS library. */
    129 
    130 #include <dns.h>
    131 
    132 /* Application-specific. */
    133 
    134 #include "smtp.h"
    135 #include "smtp_addr.h"
    136 
    137 /* smtp_print_addr - print address list */
    138 
    139 static void smtp_print_addr(const char *what, DNS_RR *addr_list)
    140 {
    141     DNS_RR *addr;
    142     MAI_HOSTADDR_STR hostaddr;
    143 
    144     msg_info("begin %s address list", what);
    145     for (addr = addr_list; addr; addr = addr->next) {
    146 	if (dns_rr_to_pa(addr, &hostaddr) == 0) {
    147 	    msg_warn("skipping record type %s: %m", dns_strtype(addr->type));
    148 	} else {
    149 	    msg_info("pref %4d host %s/%s",
    150 		     addr->pref, SMTP_HNAME(addr),
    151 		     hostaddr.buf);
    152 	}
    153     }
    154     msg_info("end %s address list", what);
    155 }
    156 
    157 /* smtp_addr_one - address lookup for one host name */
    158 
    159 static DNS_RR *smtp_addr_one(DNS_RR *addr_list, const char *host, int res_opt,
    160 			             unsigned pref, unsigned port,
    161 			             DSN_BUF *why)
    162 {
    163     const char *myname = "smtp_addr_one";
    164     DNS_RR *addr = 0;
    165     DNS_RR *rr;
    166     int     aierr;
    167     struct addrinfo *res0;
    168     struct addrinfo *res;
    169     const INET_PROTO_INFO *proto_info = inet_proto_info();
    170     unsigned char *proto_family_list = proto_info->sa_family_list;
    171     int     found;
    172 
    173     if (msg_verbose)
    174 	msg_info("%s: host %s", myname, host);
    175 
    176     /*
    177      * Interpret a numerical name as an address.
    178      */
    179     if (hostaddr_to_sockaddr(host, (char *) 0, 0, &res0) == 0) {
    180 	if (strchr((char *) proto_family_list, res0->ai_family) != 0) {
    181 	    if ((addr = dns_sa_to_rr(host, pref, res0->ai_addr)) == 0)
    182 		msg_fatal("host %s: conversion error for address family "
    183 			  "%d: %m", host, res0->ai_addr->sa_family);
    184 	    addr->pref = pref;
    185 	    addr->port = port;
    186 	    addr_list = dns_rr_append(addr_list, addr);
    187 	    if (msg_verbose && !DNS_RR_IS_TRUNCATED(addr_list))
    188 		msg_info("%s: using numerical host %s", myname, host);
    189 	    freeaddrinfo(res0);
    190 	    return (addr_list);
    191 	}
    192 	freeaddrinfo(res0);
    193     }
    194 
    195     /*
    196      * Use DNS lookup, but keep the option open to use native name service.
    197      *
    198      * XXX A soft error dominates past and future hard errors. Therefore we
    199      * should not clobber a soft error text and status code.
    200      */
    201     if (smtp_host_lookup_mask & SMTP_HOST_FLAG_DNS) {
    202 	res_opt |= smtp_dns_res_opt;
    203 	switch (dns_lookup_v(host, res_opt, &addr, (VSTRING *) 0,
    204 			     why->reason, DNS_REQ_FLAG_NONE,
    205 			     proto_info->dns_atype_list)) {
    206 	case DNS_OK:
    207 	    for (rr = addr; rr; rr = rr->next) {
    208 		rr->pref = pref;
    209 		rr->port = port;
    210 	    }
    211 	    addr_list = dns_rr_append(addr_list, addr);
    212 	    return (addr_list);
    213 	default:
    214 	    dsb_status(why, "4.4.3");
    215 	    return (addr_list);
    216 	case DNS_FAIL:
    217 	    dsb_status(why, SMTP_HAS_SOFT_DSN(why) ? "4.4.3" : "5.4.3");
    218 	    return (addr_list);
    219 	case DNS_INVAL:
    220 	    dsb_status(why, SMTP_HAS_SOFT_DSN(why) ? "4.4.4" : "5.4.4");
    221 	    return (addr_list);
    222 	case DNS_POLICY:
    223 	    dsb_status(why, "4.7.0");
    224 	    return (addr_list);
    225 	case DNS_NOTFOUND:
    226 	    dsb_status(why, SMTP_HAS_SOFT_DSN(why) ? "4.4.4" : "5.4.4");
    227 	    /* maybe native naming service will succeed */
    228 	    break;
    229 	}
    230     }
    231 
    232     /*
    233      * Use the native name service which also looks in /etc/hosts.
    234      *
    235      * XXX A soft error dominates past and future hard errors. Therefore we
    236      * should not clobber a soft error text and status code.
    237      */
    238 #define RETRY_AI_ERROR(e) \
    239         ((e) == EAI_AGAIN || (e) == EAI_MEMORY || (e) == EAI_SYSTEM)
    240 #ifdef EAI_NODATA
    241 #define DSN_NOHOST(e) \
    242 	((e) == EAI_AGAIN || (e) == EAI_NODATA || (e) == EAI_NONAME)
    243 #else
    244 #define DSN_NOHOST(e) \
    245 	((e) == EAI_AGAIN || (e) == EAI_NONAME)
    246 #endif
    247 
    248     if (smtp_host_lookup_mask & SMTP_HOST_FLAG_NATIVE) {
    249 	if ((aierr = hostname_to_sockaddr(host, (char *) 0, 0, &res0)) != 0) {
    250 	    dsb_simple(why, (SMTP_HAS_SOFT_DSN(why) || RETRY_AI_ERROR(aierr)) ?
    251 		       (DSN_NOHOST(aierr) ? "4.4.4" : "4.3.0") :
    252 		       (DSN_NOHOST(aierr) ? "5.4.4" : "5.3.0"),
    253 		       "unable to look up host %s: %s",
    254 		       host, MAI_STRERROR(aierr));
    255 	} else {
    256 	    for (found = 0, res = res0; res != 0; res = res->ai_next) {
    257 		if (strchr((char *) proto_family_list, res->ai_family) == 0) {
    258 		    msg_info("skipping address family %d for host %s",
    259 			     res->ai_family, host);
    260 		    continue;
    261 		}
    262 		found++;
    263 		if ((addr = dns_sa_to_rr(host, pref, res->ai_addr)) == 0)
    264 		    msg_fatal("host %s: conversion error for address family "
    265 			      "%d: %m", host, res0->ai_addr->sa_family);
    266 		addr_list = dns_rr_append(addr_list, addr);
    267 		if (DNS_RR_IS_TRUNCATED(addr_list))
    268 		    break;
    269 		if (msg_verbose) {
    270 		    MAI_HOSTADDR_STR hostaddr_str;
    271 
    272 		    SOCKADDR_TO_HOSTADDR(res->ai_addr, res->ai_addrlen,
    273 				  &hostaddr_str, (MAI_SERVPORT_STR *) 0, 0);
    274 		    msg_info("%s: native lookup result: %s",
    275 			     myname, hostaddr_str.buf);
    276 		}
    277 	    }
    278 	    freeaddrinfo(res0);
    279 	    if (found == 0) {
    280 		dsb_simple(why, SMTP_HAS_SOFT_DSN(why) ? "4.4.4" : "5.4.4",
    281 			   "%s: host not found", host);
    282 	    }
    283 	    return (addr_list);
    284 	}
    285     }
    286 
    287     /*
    288      * No further alternatives for host lookup.
    289      */
    290     return (addr_list);
    291 }
    292 
    293 /* smtp_addr_list - address lookup for a list of mail exchangers */
    294 
    295 static DNS_RR *smtp_addr_list(DNS_RR *mx_names, DSN_BUF *why)
    296 {
    297     DNS_RR *addr_list = 0;
    298     DNS_RR *rr;
    299     int     res_opt = 0;
    300 
    301     if (mx_names->dnssec_valid)
    302 	res_opt = RES_USE_DNSSEC;
    303 #ifdef USE_TLS
    304     else if (smtp_tls_insecure_mx_policy > TLS_LEV_MAY
    305 	     && smtp_dns_support == SMTP_DNS_DNSSEC)
    306 	res_opt = RES_USE_DNSSEC;
    307 #endif
    308 
    309     /*
    310      * As long as we are able to look up any host address, we ignore problems
    311      * with DNS lookups (except if we're backup MX, and all the better MX
    312      * hosts can't be found).
    313      *
    314      * XXX 2821: update the error status (0->FAIL upon unrecoverable lookup
    315      * error, any->RETRY upon temporary lookup error) so that we can
    316      * correctly handle the case of no resolvable MX host. Currently this is
    317      * always treated as a soft error. RFC 2821 wants a more precise
    318      * response.
    319      *
    320      * XXX dns_lookup() enables RES_DEFNAMES. This is wrong for names found in
    321      * MX records - we should not append the local domain to dot-less names.
    322      *
    323      * XXX However, this is not the only problem. If we use the native name
    324      * service for host lookup, then it will usually enable RES_DNSRCH which
    325      * appends local domain information to all lookups. In particular,
    326      * getaddrinfo() may invoke a resolver that runs in a different process
    327      * (NIS server, nscd), so we can't even reliably turn this off by
    328      * tweaking the in-process resolver flags.
    329      */
    330     for (rr = mx_names; rr; rr = rr->next) {
    331 	if (rr->type != T_MX && rr->type != T_SRV)
    332 	    msg_panic("smtp_addr_list: bad resource type: %d", rr->type);
    333 	addr_list = smtp_addr_one(addr_list, (char *) rr->data, res_opt,
    334 				  rr->pref, rr->port, why);
    335 	if (addr_list && DNS_RR_IS_TRUNCATED(addr_list))
    336 	    break;
    337     }
    338     return (addr_list);
    339 }
    340 
    341 /* smtp_find_self - spot myself in a crowd of mail exchangers */
    342 
    343 static DNS_RR *smtp_find_self(DNS_RR *addr_list)
    344 {
    345     const char *myname = "smtp_find_self";
    346     INET_ADDR_LIST *self;
    347     INET_ADDR_LIST *proxy;
    348     DNS_RR *addr;
    349     int     i;
    350 
    351     self = own_inet_addr_list();
    352     proxy = proxy_inet_addr_list();
    353 
    354     for (addr = addr_list; addr; addr = addr->next) {
    355 
    356 	/*
    357 	 * Find out if this mail system is listening on this address.
    358 	 */
    359 	for (i = 0; i < self->used; i++)
    360 	    if (DNS_RR_EQ_SA(addr, (struct sockaddr *) (self->addrs + i))) {
    361 		if (msg_verbose)
    362 		    msg_info("%s: found self at pref %d", myname, addr->pref);
    363 		return (addr);
    364 	    }
    365 
    366 	/*
    367 	 * Find out if this mail system has a proxy listening on this
    368 	 * address.
    369 	 */
    370 	for (i = 0; i < proxy->used; i++)
    371 	    if (DNS_RR_EQ_SA(addr, (struct sockaddr *) (proxy->addrs + i))) {
    372 		if (msg_verbose)
    373 		    msg_info("%s: found proxy at pref %d", myname, addr->pref);
    374 		return (addr);
    375 	    }
    376     }
    377 
    378     /*
    379      * Didn't find myself, or my proxy.
    380      */
    381     if (msg_verbose)
    382 	msg_info("%s: not found", myname);
    383     return (0);
    384 }
    385 
    386 /* smtp_truncate_self - truncate address list at self and equivalents */
    387 
    388 static DNS_RR *smtp_truncate_self(DNS_RR *addr_list, unsigned pref)
    389 {
    390     DNS_RR *addr;
    391     DNS_RR *last;
    392 
    393     for (last = 0, addr = addr_list; addr; last = addr, addr = addr->next) {
    394 	if (pref == addr->pref) {
    395 	    if (msg_verbose)
    396 		smtp_print_addr("truncated", addr);
    397 	    dns_rr_free(addr);
    398 	    if (last == 0) {
    399 		addr_list = 0;
    400 	    } else {
    401 		last->next = 0;
    402 	    }
    403 	    break;
    404 	}
    405     }
    406     return (addr_list);
    407 }
    408 
    409 /* smtp_balance_inet_proto - balance IPv4/6 protocols within address limit */
    410 
    411 static DNS_RR *smtp_balance_inet_proto(DNS_RR *addr_list, int misc_flags,
    412 				               int addr_limit)
    413 {
    414     const char myname[] = "smtp_balance_inet_proto";
    415     DNS_RR *rr;
    416     DNS_RR *stripped_list;
    417     DNS_RR *next;
    418     int     v6_count;
    419     int     v4_count;
    420     int     v6_target, v4_target;
    421     int    *p;
    422 
    423     /*
    424      * Precondition: the input is sorted by MX preference (not necessarily IP
    425      * address family preference), and addresses with the same or worse
    426      * preference than 'myself' have been eliminated. Postcondition: the
    427      * relative list order is unchanged, but some elements are removed.
    428      */
    429 
    430     /*
    431      * Ensure that dns_rr_append() won't interfere with the protocol
    432      * balancing goals.
    433      */
    434     if (addr_limit > var_dns_rr_list_limit)
    435 	addr_limit = var_dns_rr_list_limit;
    436 
    437     /*
    438      * Count the number of IPv6 and IPv4 addresses.
    439      */
    440     for (v4_count = v6_count = 0, rr = addr_list; rr != 0; rr = rr->next) {
    441 	if (rr->type == T_A) {
    442 	    v4_count++;
    443 	} else if (rr->type == T_AAAA) {
    444 	    v6_count++;
    445 	} else {
    446 	    msg_panic("%s: unexpected record type: %s",
    447 		      myname, dns_strtype(rr->type));
    448 	}
    449     }
    450 
    451     /*
    452      * Ensure that one address type will not out-crowd the other, while
    453      * enforcing the address count limit. This works around a current problem
    454      * where some destination announces primarily IPv6 MX addresses, the
    455      * smtp_address_limit eliminates most or all IPv4 addresses, and the
    456      * destination is not reachable over IPv6.
    457      *
    458      * Maybe: do all smtp_mx_address_limit enforcement here, and remove
    459      * pre-existing enforcement elsewhere. That would obsolete the
    460      * smtp_balance_inet_protocols configuration parameter.
    461      */
    462     if (v4_count > 0 && v6_count > 0 && v4_count + v6_count > addr_limit) {
    463 
    464 	/*-
    465          * Decide how many IPv6 and IPv4 addresses to keep. The code below
    466          * has three branches, corresponding to the regions R1, R2 and R3
    467          * in the figure.
    468          *
    469          *  L = addr_limit
    470          *  X = excluded by condition (v4_count + v6_count > addr_limit)
    471          *
    472          * v4_count
    473          *     ^
    474          *     |
    475          *  L  \  R1
    476          *     |X\     |
    477          *     |XXX\   |
    478          *     |XXXXX\ | R2
    479          * L/2 +-------\-------
    480          *     |XXXXXXX|X\
    481          *     |XXXXXXX|XXX\  R3
    482          *     |XXXXXXX|XXXXX\
    483          *   0 +-------+-------\--> v6_count
    484          *     0      L/2      L
    485          */
    486 	if (v6_count <= addr_limit / 2) {	/* Region R1 */
    487 	    v6_target = v6_count;
    488 	    v4_target = addr_limit - v6_target;
    489 	} else if (v4_count <= addr_limit / 2) {/* Region R3 */
    490 	    v4_target = v4_count;
    491 	    v6_target = addr_limit - v4_target;
    492 	} else {				/* Region R2 */
    493 	    /* v4_count > addr_limit / 2 && v6_count > addr_limit / 2 */
    494 	    v4_target = (addr_limit + (addr_list->type == T_A)) / 2;
    495 	    v6_target = addr_limit - v4_target;
    496 	}
    497 	if (msg_verbose)
    498 	    msg_info("v6_target=%d, v4_target=%d", v6_target, v4_target);
    499 
    500 	/* Enforce the address count targets. */
    501 	stripped_list = 0;
    502 	for (rr = addr_list; rr != 0; rr = next) {
    503 	    next = rr->next;
    504 	    rr->next = 0;
    505 	    if (rr->type == T_A) {
    506 		p = &v4_target;
    507 	    } else if (rr->type == T_AAAA) {
    508 		p = &v6_target;
    509 	    } else {
    510 		msg_panic("%s: unexpected record type: %s",
    511 			  myname, dns_strtype(rr->type));
    512 	    }
    513 	    if (*p > 0) {
    514 		stripped_list = dns_rr_append(stripped_list, rr);
    515 		*p -= 1;
    516 	    } else {
    517 		dns_rr_free(rr);
    518 	    }
    519 	}
    520 	if (v4_target > 0 || v6_target > 0)
    521 	    msg_panic("%s: bad target count: v4_target=%d, v6_target=%d",
    522 		      myname, v4_target, v6_target);
    523 	if (msg_verbose)
    524 	    smtp_print_addr("smtp_balance_inet_proto result", stripped_list);
    525 	return (stripped_list);
    526     } else {
    527 	return (addr_list);
    528     }
    529 }
    530 
    531 /* smtp_domain_addr - mail exchanger address lookup */
    532 
    533 DNS_RR *smtp_domain_addr(const char *name, DNS_RR **mxrr, int misc_flags,
    534 			         DSN_BUF *why, int *found_myself)
    535 {
    536     DNS_RR *mx_names;
    537     DNS_RR *addr_list = 0;
    538     DNS_RR *self = 0;
    539     unsigned best_pref;
    540     unsigned best_found;
    541     int     r = 0;			/* Resolver flags */
    542     const char *aname;
    543 
    544     dsb_reset(why);				/* Paranoia */
    545 
    546     /*
    547      * Preferences from DNS use 0..32767, fall-backs use 32768+.
    548      */
    549 #define IMPOSSIBLE_PREFERENCE	(~0)
    550 
    551     /*
    552      * Sanity check.
    553      */
    554     if (smtp_dns_support == SMTP_DNS_DISABLED)
    555 	msg_panic("smtp_domain_addr: DNS lookup is disabled");
    556     if (smtp_dns_support == SMTP_DNS_DNSSEC)
    557 	r |= RES_USE_DNSSEC;
    558 
    559     /*
    560      * IDNA support.
    561      */
    562 #ifndef NO_EAI
    563     if (!allascii(name) && (aname = midna_domain_to_ascii(name)) != 0) {
    564 	if (msg_verbose)
    565 	    msg_info("%s asciified to %s", name, aname);
    566     } else
    567 #endif
    568 	aname = name;
    569 
    570     /*
    571      * Look up the mail exchanger hosts listed for this name. Sort the
    572      * results by preference. Look up the corresponding host addresses, and
    573      * truncate the list so that it contains only hosts that are more
    574      * preferred than myself. When no MX resource records exist, look up the
    575      * addresses listed for this name.
    576      *
    577      * According to RFC 974: "It is possible that the list of MXs in the
    578      * response to the query will be empty.  This is a special case.  If the
    579      * list is empty, mailers should treat it as if it contained one RR, an
    580      * MX RR with a preference value of 0, and a host name of REMOTE.  (I.e.,
    581      * REMOTE is its only MX).  In addition, the mailer should do no further
    582      * processing on the list, but should attempt to deliver the message to
    583      * REMOTE."
    584      *
    585      * Normally it is OK if an MX host cannot be found in the DNS; we'll just
    586      * use a backup one, and silently ignore the better MX host. However, if
    587      * the best backup that we can find in the DNS is the local machine, then
    588      * we must remember that the local machine is not the primary MX host, or
    589      * else we will claim that mail loops back.
    590      *
    591      * XXX Optionally do A lookups even when the MX lookup didn't complete.
    592      * Unfortunately with some DNS servers this is not a transient problem.
    593      *
    594      * XXX Ideally we would perform A lookups only as far as needed. But as long
    595      * as we're looking up all the hosts, it would be better to look up the
    596      * least preferred host first, so that DNS lookup error messages make
    597      * more sense.
    598      *
    599      * XXX 2821: RFC 2821 says that the sender must shuffle equal-preference MX
    600      * hosts, whereas multiple A records per hostname must be used in the
    601      * order as received. They make the bogus assumption that a hostname with
    602      * multiple A records corresponds to one machine with multiple network
    603      * interfaces.
    604      *
    605      * XXX 2821: Postfix recognizes the local machine by looking for its own IP
    606      * address in the list of mail exchangers. RFC 2821 says one has to look
    607      * at the mail exchanger hostname as well, making the bogus assumption
    608      * that an IP address is listed only under one hostname. However, looking
    609      * at hostnames provides a partial solution for MX hosts behind a NAT
    610      * gateway.
    611      */
    612     switch (dns_lookup(aname, T_MX, r, &mx_names, (VSTRING *) 0, why->reason)) {
    613     default:
    614 	dsb_status(why, "4.4.3");
    615 	if (var_ign_mx_lookup_err)
    616 	    addr_list = smtp_host_addr(aname, misc_flags, why);
    617 	break;
    618     case DNS_INVAL:
    619 	dsb_status(why, "5.4.4");
    620 	if (var_ign_mx_lookup_err)
    621 	    addr_list = smtp_host_addr(aname, misc_flags, why);
    622 	break;
    623     case DNS_NULLMX:
    624 	dsb_status(why, "5.1.0");
    625 	break;
    626     case DNS_POLICY:
    627 	dsb_status(why, "4.7.0");
    628 	break;
    629     case DNS_FAIL:
    630 	dsb_status(why, "5.4.3");
    631 	if (var_ign_mx_lookup_err)
    632 	    addr_list = smtp_host_addr(aname, misc_flags, why);
    633 	break;
    634     case DNS_OK:
    635 	mx_names = dns_rr_sort(mx_names, dns_rr_compare_pref_any);
    636 	best_pref = (mx_names ? mx_names->pref : IMPOSSIBLE_PREFERENCE);
    637 	addr_list = smtp_addr_list(mx_names, why);
    638 	if (mxrr)
    639 	    *mxrr = dns_rr_copy(mx_names);	/* copies one record! */
    640 	dns_rr_free(mx_names);
    641 	if (addr_list == 0) {
    642 	    /* Text does not change. */
    643 	    if (var_smtp_defer_mxaddr) {
    644 		/* Don't clobber the null terminator. */
    645 		if (SMTP_HAS_HARD_DSN(why))
    646 		    SMTP_SET_SOFT_DSN(why);	/* XXX */
    647 		/* Require some error status. */
    648 		else if (!SMTP_HAS_SOFT_DSN(why))
    649 		    msg_panic("smtp_domain_addr: bad status");
    650 	    }
    651 	    msg_warn("no MX host for %s has a valid address record", name);
    652 	    break;
    653 	}
    654 	best_found = (addr_list ? addr_list->pref : IMPOSSIBLE_PREFERENCE);
    655 	if (msg_verbose)
    656 	    smtp_print_addr(name, addr_list);
    657 	if ((misc_flags & SMTP_MISC_FLAG_LOOP_DETECT)
    658 	    && (self = smtp_find_self(addr_list)) != 0) {
    659 	    addr_list = smtp_truncate_self(addr_list, self->pref);
    660 	    if (addr_list == 0) {
    661 		if (best_pref != best_found) {
    662 		    dsb_simple(why, "4.4.4",
    663 			       "unable to find primary relay for %s", name);
    664 		} else {
    665 		    dsb_simple(why, "5.4.6", "mail for %s loops back to myself",
    666 			       name);
    667 		}
    668 	    }
    669 	}
    670 #define SMTP_COMPARE_ADDR(flags) \
    671 	(((flags) & SMTP_MISC_FLAG_PREF_IPV6) ? dns_rr_compare_pref_ipv6 : \
    672 	 ((flags) & SMTP_MISC_FLAG_PREF_IPV4) ? dns_rr_compare_pref_ipv4 : \
    673 	 dns_rr_compare_pref_any)
    674 
    675 	if (addr_list && addr_list->next) {
    676 	    if (var_smtp_rand_addr)
    677 		addr_list = dns_rr_shuffle(addr_list);
    678 	    addr_list = dns_rr_sort(addr_list, SMTP_COMPARE_ADDR(misc_flags));
    679 	    if (var_smtp_mxaddr_limit > 0 && var_smtp_balance_inet_proto)
    680 		addr_list = smtp_balance_inet_proto(addr_list, misc_flags,
    681 						    var_smtp_mxaddr_limit);
    682 	}
    683 	break;
    684     case DNS_NOTFOUND:
    685 	addr_list = smtp_host_addr(aname, misc_flags, why);
    686 	break;
    687     }
    688 
    689     /*
    690      * Clean up.
    691      */
    692     *found_myself |= (self != 0);
    693     return (addr_list);
    694 }
    695 
    696 /* smtp_host_addr - direct host lookup */
    697 
    698 DNS_RR *smtp_host_addr(const char *host, int misc_flags, DSN_BUF *why)
    699 {
    700     DNS_RR *addr_list;
    701     int     res_opt = 0;
    702     const char *ahost;
    703 
    704     dsb_reset(why);				/* Paranoia */
    705 
    706     if (smtp_dns_support == SMTP_DNS_DNSSEC)
    707 	res_opt |= RES_USE_DNSSEC;
    708 
    709     /*
    710      * IDNA support.
    711      */
    712 #ifndef NO_EAI
    713     if (!allascii(host) && (ahost = midna_domain_to_ascii(host)) != 0) {
    714 	if (msg_verbose)
    715 	    msg_info("%s asciified to %s", host, ahost);
    716     } else
    717 #endif
    718 	ahost = host;
    719 
    720     /*
    721      * If the host is specified by numerical address, just convert the
    722      * address to internal form. Otherwise, the host is specified by name.
    723      */
    724 #define PREF0	0
    725     addr_list = smtp_addr_one((DNS_RR *) 0, ahost, res_opt, PREF0, 0, why);
    726     if (addr_list
    727 	&& (misc_flags & SMTP_MISC_FLAG_LOOP_DETECT)
    728 	&& smtp_find_self(addr_list) != 0) {
    729 	dns_rr_free(addr_list);
    730 	dsb_simple(why, "5.4.6", "mail for %s loops back to myself", host);
    731 	return (0);
    732     }
    733     if (addr_list && addr_list->next) {
    734 	if (var_smtp_rand_addr)
    735 	    addr_list = dns_rr_shuffle(addr_list);
    736 	/* The following changes the order of equal-preference hosts. */
    737 	if (inet_proto_info()->ai_family_list[1] != 0)
    738 	    addr_list = dns_rr_sort(addr_list, SMTP_COMPARE_ADDR(misc_flags));
    739 	if (var_smtp_mxaddr_limit > 0 && var_smtp_balance_inet_proto)
    740 	    addr_list = smtp_balance_inet_proto(addr_list, misc_flags,
    741 						var_smtp_mxaddr_limit);
    742     }
    743     if (msg_verbose)
    744 	smtp_print_addr(host, addr_list);
    745     return (addr_list);
    746 }
    747 
    748 /* smtp_service_addr - service address lookup */
    749 
    750 DNS_RR *smtp_service_addr(const char *name, const char *service, DNS_RR **mxrr,
    751 			          int misc_flags, DSN_BUF *why,
    752 			          int *found_myself)
    753 {
    754     static VSTRING *srv_qname = 0;
    755     const char *str_srv_qname;
    756     DNS_RR *srv_names = 0;
    757     DNS_RR *addr_list = 0;
    758     DNS_RR *self = 0;
    759     unsigned best_pref;
    760     unsigned best_found;
    761     int     r = 0;
    762     const char *aname;
    763     int     allow_non_srv_fallback = var_allow_srv_fallback;
    764 
    765     dsb_reset(why);
    766 
    767     /*
    768      * Sanity check.
    769      */
    770     if (smtp_dns_support == SMTP_DNS_DISABLED)
    771 	msg_panic("smtp_service_addr: DNS lookup is disabled");
    772 
    773     if (smtp_dns_support == SMTP_DNS_DNSSEC) {
    774 	r |= RES_USE_DNSSEC;
    775     }
    776     if (srv_qname == 0)
    777 	srv_qname = vstring_alloc(100);
    778     vstring_sprintf(srv_qname, "_%s._tcp.%s", service, name);
    779     str_srv_qname = STR(srv_qname);
    780 
    781     /*
    782      * IDNA support.
    783      */
    784 #ifndef NO_EAI
    785     if (!allascii(str_srv_qname)
    786 	&& (aname = midna_domain_to_ascii(str_srv_qname)) != 0) {
    787 	if (msg_verbose)
    788 	    msg_info("%s asciified to %s", str_srv_qname, aname);
    789     } else
    790 #endif
    791 	aname = str_srv_qname;
    792 
    793     switch (dns_lookup(aname, T_SRV, r, &srv_names, (VSTRING *) 0,
    794 		       why->reason)) {
    795     default:
    796 	dsb_status(why, "4.4.3");
    797 	allow_non_srv_fallback |= var_ign_srv_lookup_err;
    798 	break;
    799     case DNS_INVAL:
    800 	dsb_status(why, "5.4.4");
    801 	allow_non_srv_fallback |= var_ign_srv_lookup_err;
    802 	break;
    803     case DNS_POLICY:
    804 	dsb_status(why, "4.7.0");
    805 	break;
    806     case DNS_FAIL:
    807 	dsb_status(why, "5.4.3");
    808 	allow_non_srv_fallback |= var_ign_srv_lookup_err;
    809 	break;
    810     case DNS_NULLSRV:
    811 	dsb_status(why, "5.1.0");
    812 	break;
    813     case DNS_OK:
    814 	/* Shuffle then sort the SRV rr records by priority and weight. */
    815 	srv_names = dns_srv_rr_sort(srv_names);
    816 	best_pref = (srv_names ? srv_names->pref : IMPOSSIBLE_PREFERENCE);
    817 	addr_list = smtp_addr_list(srv_names, why);
    818 	if (mxrr)
    819 	    *mxrr = dns_rr_copy(srv_names);	/* copies one record! */
    820 	dns_rr_free(srv_names);
    821 	if (addr_list == 0) {
    822 	    msg_warn("no SRV host for %s has a valid address record",
    823 		     str_srv_qname);
    824 	    break;
    825 	}
    826 	/* Optional loop prevention, similar to smtp_domain_addr(). */
    827 	best_found = (addr_list ? addr_list->pref : IMPOSSIBLE_PREFERENCE);
    828 	if (msg_verbose)
    829 	    smtp_print_addr(aname, addr_list);
    830 	if ((misc_flags & SMTP_MISC_FLAG_LOOP_DETECT)
    831 	    && (self = smtp_find_self(addr_list)) != 0) {
    832 	    addr_list = smtp_truncate_self(addr_list, self->pref);
    833 	    if (addr_list == 0) {
    834 		if (best_pref != best_found) {
    835 		    dsb_simple(why, "4.4.4",
    836 			       "unable to find primary relay for %s",
    837 			       str_srv_qname);
    838 		} else {
    839 		    dsb_simple(why, "5.4.6", "mail for %s loops back to myself",
    840 			       str_srv_qname);
    841 		}
    842 	    }
    843 	}
    844 	/* TODO: sort by priority, weight, and address family preference. */
    845 
    846 	/* Optional address family balancing, as in smtp_domain_addr(). */
    847 	if (addr_list && addr_list->next) {
    848 	    if (var_smtp_mxaddr_limit > 0 && var_smtp_balance_inet_proto)
    849 		addr_list = smtp_balance_inet_proto(addr_list, misc_flags,
    850 						    var_smtp_mxaddr_limit);
    851 	}
    852 	break;
    853     case DNS_NOTFOUND:
    854 	dsb_status(why, "5.4.4");
    855 	break;
    856     }
    857 
    858     /*
    859      * If permitted, fall back to non-SRV record lookups.
    860      */
    861     if (addr_list == 0 && allow_non_srv_fallback) {
    862 	msg_info("skipping SRV lookup for %s: %s",
    863 		 str_srv_qname, STR(why->reason));
    864 	if (misc_flags & SMTP_MISC_FLAG_FALLBACK_SRV_TO_MX)
    865 	    addr_list = smtp_domain_addr(name, mxrr, misc_flags, why,
    866 					 found_myself);
    867 	else
    868 	    addr_list = smtp_host_addr(name, misc_flags, why);
    869     }
    870 
    871     /*
    872      * Only if we're not falling back.
    873      */
    874     else {
    875 	*found_myself |= (self != 0);
    876     }
    877     return (addr_list);
    878 }
    879