xref: /netbsd-src/external/ibm-public/postfix/dist/src/smtp/smtp_addr.c (revision c48c605c14fd8622b523d1d6a3f0c0bad133ea89)
1 /*	$NetBSD: smtp_addr.c,v 1.5 2023/12/23 20:30:45 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 
smtp_print_addr(const char * what,DNS_RR * addr_list)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 
smtp_addr_one(DNS_RR * addr_list,const char * host,int res_opt,unsigned pref,unsigned port,DSN_BUF * why)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_list = dns_rr_append(addr_list, addr);
185 	    addr->pref = pref;
186 	    addr->port = port;
187 	    if (msg_verbose)
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 (msg_verbose) {
268 		    MAI_HOSTADDR_STR hostaddr_str;
269 
270 		    SOCKADDR_TO_HOSTADDR(res->ai_addr, res->ai_addrlen,
271 				  &hostaddr_str, (MAI_SERVPORT_STR *) 0, 0);
272 		    msg_info("%s: native lookup result: %s",
273 			     myname, hostaddr_str.buf);
274 		}
275 	    }
276 	    freeaddrinfo(res0);
277 	    if (found == 0) {
278 		dsb_simple(why, SMTP_HAS_SOFT_DSN(why) ? "4.4.4" : "5.4.4",
279 			   "%s: host not found", host);
280 	    }
281 	    return (addr_list);
282 	}
283     }
284 
285     /*
286      * No further alternatives for host lookup.
287      */
288     return (addr_list);
289 }
290 
291 /* smtp_addr_list - address lookup for a list of mail exchangers */
292 
smtp_addr_list(DNS_RR * mx_names,DSN_BUF * why)293 static DNS_RR *smtp_addr_list(DNS_RR *mx_names, DSN_BUF *why)
294 {
295     DNS_RR *addr_list = 0;
296     DNS_RR *rr;
297     int     res_opt = 0;
298 
299     if (mx_names->dnssec_valid)
300 	res_opt = RES_USE_DNSSEC;
301 #ifdef USE_TLS
302     else if (smtp_tls_insecure_mx_policy > TLS_LEV_MAY)
303 	res_opt = RES_USE_DNSSEC;
304 #endif
305 
306     /*
307      * As long as we are able to look up any host address, we ignore problems
308      * with DNS lookups (except if we're backup MX, and all the better MX
309      * hosts can't be found).
310      *
311      * XXX 2821: update the error status (0->FAIL upon unrecoverable lookup
312      * error, any->RETRY upon temporary lookup error) so that we can
313      * correctly handle the case of no resolvable MX host. Currently this is
314      * always treated as a soft error. RFC 2821 wants a more precise
315      * response.
316      *
317      * XXX dns_lookup() enables RES_DEFNAMES. This is wrong for names found in
318      * MX records - we should not append the local domain to dot-less names.
319      *
320      * XXX However, this is not the only problem. If we use the native name
321      * service for host lookup, then it will usually enable RES_DNSRCH which
322      * appends local domain information to all lookups. In particular,
323      * getaddrinfo() may invoke a resolver that runs in a different process
324      * (NIS server, nscd), so we can't even reliably turn this off by
325      * tweaking the in-process resolver flags.
326      */
327     for (rr = mx_names; rr; rr = rr->next) {
328 	if (rr->type != T_MX && rr->type != T_SRV)
329 	    msg_panic("smtp_addr_list: bad resource type: %d", rr->type);
330 	addr_list = smtp_addr_one(addr_list, (char *) rr->data, res_opt,
331 				  rr->pref, rr->port, why);
332     }
333     return (addr_list);
334 }
335 
336 /* smtp_find_self - spot myself in a crowd of mail exchangers */
337 
smtp_find_self(DNS_RR * addr_list)338 static DNS_RR *smtp_find_self(DNS_RR *addr_list)
339 {
340     const char *myname = "smtp_find_self";
341     INET_ADDR_LIST *self;
342     INET_ADDR_LIST *proxy;
343     DNS_RR *addr;
344     int     i;
345 
346     self = own_inet_addr_list();
347     proxy = proxy_inet_addr_list();
348 
349     for (addr = addr_list; addr; addr = addr->next) {
350 
351 	/*
352 	 * Find out if this mail system is listening on this address.
353 	 */
354 	for (i = 0; i < self->used; i++)
355 	    if (DNS_RR_EQ_SA(addr, (struct sockaddr *) (self->addrs + i))) {
356 		if (msg_verbose)
357 		    msg_info("%s: found self at pref %d", myname, addr->pref);
358 		return (addr);
359 	    }
360 
361 	/*
362 	 * Find out if this mail system has a proxy listening on this
363 	 * address.
364 	 */
365 	for (i = 0; i < proxy->used; i++)
366 	    if (DNS_RR_EQ_SA(addr, (struct sockaddr *) (proxy->addrs + i))) {
367 		if (msg_verbose)
368 		    msg_info("%s: found proxy at pref %d", myname, addr->pref);
369 		return (addr);
370 	    }
371     }
372 
373     /*
374      * Didn't find myself, or my proxy.
375      */
376     if (msg_verbose)
377 	msg_info("%s: not found", myname);
378     return (0);
379 }
380 
381 /* smtp_truncate_self - truncate address list at self and equivalents */
382 
smtp_truncate_self(DNS_RR * addr_list,unsigned pref)383 static DNS_RR *smtp_truncate_self(DNS_RR *addr_list, unsigned pref)
384 {
385     DNS_RR *addr;
386     DNS_RR *last;
387 
388     for (last = 0, addr = addr_list; addr; last = addr, addr = addr->next) {
389 	if (pref == addr->pref) {
390 	    if (msg_verbose)
391 		smtp_print_addr("truncated", addr);
392 	    dns_rr_free(addr);
393 	    if (last == 0) {
394 		addr_list = 0;
395 	    } else {
396 		last->next = 0;
397 	    }
398 	    break;
399 	}
400     }
401     return (addr_list);
402 }
403 
404 /* smtp_balance_inet_proto - balance IPv4/6 protocols within address limit */
405 
smtp_balance_inet_proto(DNS_RR * addr_list,int misc_flags,int addr_limit)406 static DNS_RR *smtp_balance_inet_proto(DNS_RR *addr_list, int misc_flags,
407 				               int addr_limit)
408 {
409     const char myname[] = "smtp_balance_inet_proto";
410     DNS_RR *rr;
411     DNS_RR *stripped_list;
412     DNS_RR *next;
413     int     v6_count;
414     int     v4_count;
415     int     v6_target, v4_target;
416     int    *p;
417 
418     /*
419      * Precondition: the input is sorted by MX preference (not necessarily IP
420      * address family preference), and addresses with the same or worse
421      * preference than 'myself' have been eliminated. Postcondition: the
422      * relative list order is unchanged, but some elements are removed.
423      */
424 
425     /*
426      * Count the number of IPv6 and IPv4 addresses.
427      */
428     for (v4_count = v6_count = 0, rr = addr_list; rr != 0; rr = rr->next) {
429 	if (rr->type == T_A) {
430 	    v4_count++;
431 	} else if (rr->type == T_AAAA) {
432 	    v6_count++;
433 	} else {
434 	    msg_panic("%s: unexpected record type: %s",
435 		      myname, dns_strtype(rr->type));
436 	}
437     }
438 
439     /*
440      * Ensure that one address type will not out-crowd the other, while
441      * enforcing the address count limit. This works around a current problem
442      * where some destination announces primarily IPv6 MX addresses, the
443      * smtp_address_limit eliminates most or all IPv4 addresses, and the
444      * destination is not reachable over IPv6.
445      *
446      * Maybe: do all smtp_mx_address_limit enforcement here, and remove
447      * pre-existing enforcement elsewhere. That would obsolete the
448      * smtp_balance_inet_protocols configuration parameter.
449      */
450     if (v4_count > 0 && v6_count > 0 && v4_count + v6_count > addr_limit) {
451 
452 	/*-
453          * Decide how many IPv6 and IPv4 addresses to keep. The code below
454          * has three branches, corresponding to the regions R1, R2 and R3
455          * in the figure.
456          *
457          *  L = addr_limit
458          *  X = excluded by condition (v4_count + v6_count > addr_limit)
459          *
460          * v4_count
461          *     ^
462          *     |
463          *  L  \  R1
464          *     |X\     |
465          *     |XXX\   |
466          *     |XXXXX\ | R2
467          * L/2 +-------\-------
468          *     |XXXXXXX|X\
469          *     |XXXXXXX|XXX\  R3
470          *     |XXXXXXX|XXXXX\
471          *   0 +-------+-------\--> v6_count
472          *     0      L/2      L
473          */
474 	if (v6_count <= addr_limit / 2) {	/* Region R1 */
475 	    v6_target = v6_count;
476 	    v4_target = addr_limit - v6_target;
477 	} else if (v4_count <= addr_limit / 2) {/* Region R3 */
478 	    v4_target = v4_count;
479 	    v6_target = addr_limit - v4_target;
480 	} else {				/* Region R2 */
481 	    /* v4_count > addr_limit / 2 && v6_count > addr_limit / 2 */
482 	    v4_target = (addr_limit + (addr_list->type == T_A)) / 2;
483 	    v6_target = addr_limit - v4_target;
484 	}
485 	if (msg_verbose)
486 	    msg_info("v6_target=%d, v4_target=%d", v6_target, v4_target);
487 
488 	/* Enforce the address count targets. */
489 	stripped_list = 0;
490 	for (rr = addr_list; rr != 0; rr = next) {
491 	    next = rr->next;
492 	    rr->next = 0;
493 	    if (rr->type == T_A) {
494 		p = &v4_target;
495 	    } else if (rr->type == T_AAAA) {
496 		p = &v6_target;
497 	    } else {
498 		msg_panic("%s: unexpected record type: %s",
499 			  myname, dns_strtype(rr->type));
500 	    }
501 	    if (*p > 0) {
502 		stripped_list = dns_rr_append(stripped_list, rr);
503 		*p -= 1;
504 	    } else {
505 		dns_rr_free(rr);
506 	    }
507 	}
508 	if (v4_target > 0 || v6_target > 0)
509 	    msg_panic("%s: bad target count: v4_target=%d, v6_target=%d",
510 		      myname, v4_target, v6_target);
511 	if (msg_verbose)
512 	    smtp_print_addr("smtp_balance_inet_proto result", stripped_list);
513 	return (stripped_list);
514     } else {
515 	return (addr_list);
516     }
517 }
518 
519 /* smtp_domain_addr - mail exchanger address lookup */
520 
smtp_domain_addr(const char * name,DNS_RR ** mxrr,int misc_flags,DSN_BUF * why,int * found_myself)521 DNS_RR *smtp_domain_addr(const char *name, DNS_RR **mxrr, int misc_flags,
522 			         DSN_BUF *why, int *found_myself)
523 {
524     DNS_RR *mx_names;
525     DNS_RR *addr_list = 0;
526     DNS_RR *self = 0;
527     unsigned best_pref;
528     unsigned best_found;
529     int     r = 0;			/* Resolver flags */
530     const char *aname;
531 
532     dsb_reset(why);				/* Paranoia */
533 
534     /*
535      * Preferences from DNS use 0..32767, fall-backs use 32768+.
536      */
537 #define IMPOSSIBLE_PREFERENCE	(~0)
538 
539     /*
540      * Sanity check.
541      */
542     if (smtp_dns_support == SMTP_DNS_DISABLED)
543 	msg_panic("smtp_domain_addr: DNS lookup is disabled");
544     if (smtp_dns_support == SMTP_DNS_DNSSEC)
545 	r |= RES_USE_DNSSEC;
546 
547     /*
548      * IDNA support.
549      */
550 #ifndef NO_EAI
551     if (!allascii(name) && (aname = midna_domain_to_ascii(name)) != 0) {
552 	if (msg_verbose)
553 	    msg_info("%s asciified to %s", name, aname);
554     } else
555 #endif
556 	aname = name;
557 
558     /*
559      * Look up the mail exchanger hosts listed for this name. Sort the
560      * results by preference. Look up the corresponding host addresses, and
561      * truncate the list so that it contains only hosts that are more
562      * preferred than myself. When no MX resource records exist, look up the
563      * addresses listed for this name.
564      *
565      * According to RFC 974: "It is possible that the list of MXs in the
566      * response to the query will be empty.  This is a special case.  If the
567      * list is empty, mailers should treat it as if it contained one RR, an
568      * MX RR with a preference value of 0, and a host name of REMOTE.  (I.e.,
569      * REMOTE is its only MX).  In addition, the mailer should do no further
570      * processing on the list, but should attempt to deliver the message to
571      * REMOTE."
572      *
573      * Normally it is OK if an MX host cannot be found in the DNS; we'll just
574      * use a backup one, and silently ignore the better MX host. However, if
575      * the best backup that we can find in the DNS is the local machine, then
576      * we must remember that the local machine is not the primary MX host, or
577      * else we will claim that mail loops back.
578      *
579      * XXX Optionally do A lookups even when the MX lookup didn't complete.
580      * Unfortunately with some DNS servers this is not a transient problem.
581      *
582      * XXX Ideally we would perform A lookups only as far as needed. But as long
583      * as we're looking up all the hosts, it would be better to look up the
584      * least preferred host first, so that DNS lookup error messages make
585      * more sense.
586      *
587      * XXX 2821: RFC 2821 says that the sender must shuffle equal-preference MX
588      * hosts, whereas multiple A records per hostname must be used in the
589      * order as received. They make the bogus assumption that a hostname with
590      * multiple A records corresponds to one machine with multiple network
591      * interfaces.
592      *
593      * XXX 2821: Postfix recognizes the local machine by looking for its own IP
594      * address in the list of mail exchangers. RFC 2821 says one has to look
595      * at the mail exchanger hostname as well, making the bogus assumption
596      * that an IP address is listed only under one hostname. However, looking
597      * at hostnames provides a partial solution for MX hosts behind a NAT
598      * gateway.
599      */
600     switch (dns_lookup(aname, T_MX, r, &mx_names, (VSTRING *) 0, why->reason)) {
601     default:
602 	dsb_status(why, "4.4.3");
603 	if (var_ign_mx_lookup_err)
604 	    addr_list = smtp_host_addr(aname, misc_flags, why);
605 	break;
606     case DNS_INVAL:
607 	dsb_status(why, "5.4.4");
608 	if (var_ign_mx_lookup_err)
609 	    addr_list = smtp_host_addr(aname, misc_flags, why);
610 	break;
611     case DNS_NULLMX:
612 	dsb_status(why, "5.1.0");
613 	break;
614     case DNS_POLICY:
615 	dsb_status(why, "4.7.0");
616 	break;
617     case DNS_FAIL:
618 	dsb_status(why, "5.4.3");
619 	if (var_ign_mx_lookup_err)
620 	    addr_list = smtp_host_addr(aname, misc_flags, why);
621 	break;
622     case DNS_OK:
623 	mx_names = dns_rr_sort(mx_names, dns_rr_compare_pref_any);
624 	best_pref = (mx_names ? mx_names->pref : IMPOSSIBLE_PREFERENCE);
625 	addr_list = smtp_addr_list(mx_names, why);
626 	if (mxrr)
627 	    *mxrr = dns_rr_copy(mx_names);	/* copies one record! */
628 	dns_rr_free(mx_names);
629 	if (addr_list == 0) {
630 	    /* Text does not change. */
631 	    if (var_smtp_defer_mxaddr) {
632 		/* Don't clobber the null terminator. */
633 		if (SMTP_HAS_HARD_DSN(why))
634 		    SMTP_SET_SOFT_DSN(why);	/* XXX */
635 		/* Require some error status. */
636 		else if (!SMTP_HAS_SOFT_DSN(why))
637 		    msg_panic("smtp_domain_addr: bad status");
638 	    }
639 	    msg_warn("no MX host for %s has a valid address record", name);
640 	    break;
641 	}
642 	best_found = (addr_list ? addr_list->pref : IMPOSSIBLE_PREFERENCE);
643 	if (msg_verbose)
644 	    smtp_print_addr(name, addr_list);
645 	if ((misc_flags & SMTP_MISC_FLAG_LOOP_DETECT)
646 	    && (self = smtp_find_self(addr_list)) != 0) {
647 	    addr_list = smtp_truncate_self(addr_list, self->pref);
648 	    if (addr_list == 0) {
649 		if (best_pref != best_found) {
650 		    dsb_simple(why, "4.4.4",
651 			       "unable to find primary relay for %s", name);
652 		} else {
653 		    dsb_simple(why, "5.4.6", "mail for %s loops back to myself",
654 			       name);
655 		}
656 	    }
657 	}
658 #define SMTP_COMPARE_ADDR(flags) \
659 	(((flags) & SMTP_MISC_FLAG_PREF_IPV6) ? dns_rr_compare_pref_ipv6 : \
660 	 ((flags) & SMTP_MISC_FLAG_PREF_IPV4) ? dns_rr_compare_pref_ipv4 : \
661 	 dns_rr_compare_pref_any)
662 
663 	if (addr_list && addr_list->next) {
664 	    if (var_smtp_rand_addr)
665 		addr_list = dns_rr_shuffle(addr_list);
666 	    addr_list = dns_rr_sort(addr_list, SMTP_COMPARE_ADDR(misc_flags));
667 	    if (var_smtp_mxaddr_limit > 0 && var_smtp_balance_inet_proto)
668 		addr_list = smtp_balance_inet_proto(addr_list, misc_flags,
669 						    var_smtp_mxaddr_limit);
670 	}
671 	break;
672     case DNS_NOTFOUND:
673 	addr_list = smtp_host_addr(aname, misc_flags, why);
674 	break;
675     }
676 
677     /*
678      * Clean up.
679      */
680     *found_myself |= (self != 0);
681     return (addr_list);
682 }
683 
684 /* smtp_host_addr - direct host lookup */
685 
smtp_host_addr(const char * host,int misc_flags,DSN_BUF * why)686 DNS_RR *smtp_host_addr(const char *host, int misc_flags, DSN_BUF *why)
687 {
688     DNS_RR *addr_list;
689     int     res_opt = 0;
690     const char *ahost;
691 
692     dsb_reset(why);				/* Paranoia */
693 
694     if (smtp_dns_support == SMTP_DNS_DNSSEC)
695 	res_opt |= RES_USE_DNSSEC;
696 
697     /*
698      * IDNA support.
699      */
700 #ifndef NO_EAI
701     if (!allascii(host) && (ahost = midna_domain_to_ascii(host)) != 0) {
702 	if (msg_verbose)
703 	    msg_info("%s asciified to %s", host, ahost);
704     } else
705 #endif
706 	ahost = host;
707 
708     /*
709      * If the host is specified by numerical address, just convert the
710      * address to internal form. Otherwise, the host is specified by name.
711      */
712 #define PREF0	0
713     addr_list = smtp_addr_one((DNS_RR *) 0, ahost, res_opt, PREF0, 0, why);
714     if (addr_list
715 	&& (misc_flags & SMTP_MISC_FLAG_LOOP_DETECT)
716 	&& smtp_find_self(addr_list) != 0) {
717 	dns_rr_free(addr_list);
718 	dsb_simple(why, "5.4.6", "mail for %s loops back to myself", host);
719 	return (0);
720     }
721     if (addr_list && addr_list->next) {
722 	if (var_smtp_rand_addr)
723 	    addr_list = dns_rr_shuffle(addr_list);
724 	/* The following changes the order of equal-preference hosts. */
725 	if (inet_proto_info()->ai_family_list[1] != 0)
726 	    addr_list = dns_rr_sort(addr_list, SMTP_COMPARE_ADDR(misc_flags));
727 	if (var_smtp_mxaddr_limit > 0 && var_smtp_balance_inet_proto)
728 	    addr_list = smtp_balance_inet_proto(addr_list, misc_flags,
729 						var_smtp_mxaddr_limit);
730     }
731     if (msg_verbose)
732 	smtp_print_addr(host, addr_list);
733     return (addr_list);
734 }
735 
736 /* smtp_service_addr - service address lookup */
737 
smtp_service_addr(const char * name,const char * service,DNS_RR ** mxrr,int misc_flags,DSN_BUF * why,int * found_myself)738 DNS_RR *smtp_service_addr(const char *name, const char *service, DNS_RR **mxrr,
739 			          int misc_flags, DSN_BUF *why,
740 			          int *found_myself)
741 {
742     static VSTRING *srv_qname = 0;
743     const char *str_srv_qname;
744     DNS_RR *srv_names = 0;
745     DNS_RR *addr_list = 0;
746     DNS_RR *self = 0;
747     unsigned best_pref;
748     unsigned best_found;
749     int     r = 0;
750     const char *aname;
751     int     allow_non_srv_fallback = var_allow_srv_fallback;
752 
753     dsb_reset(why);
754 
755     /*
756      * Sanity check.
757      */
758     if (smtp_dns_support == SMTP_DNS_DISABLED)
759 	msg_panic("smtp_service_addr: DNS lookup is disabled");
760 
761     if (smtp_dns_support == SMTP_DNS_DNSSEC) {
762 	r |= RES_USE_DNSSEC;
763     }
764     if (srv_qname == 0)
765 	srv_qname = vstring_alloc(100);
766     vstring_sprintf(srv_qname, "_%s._tcp.%s", service, name);
767     str_srv_qname = STR(srv_qname);
768 
769     /*
770      * IDNA support.
771      */
772 #ifndef NO_EAI
773     if (!allascii(str_srv_qname)
774 	&& (aname = midna_domain_to_ascii(str_srv_qname)) != 0) {
775 	if (msg_verbose)
776 	    msg_info("%s asciified to %s", str_srv_qname, aname);
777     } else
778 #endif
779 	aname = str_srv_qname;
780 
781     switch (dns_lookup(aname, T_SRV, r, &srv_names, (VSTRING *) 0,
782 		       why->reason)) {
783     default:
784 	dsb_status(why, "4.4.3");
785 	allow_non_srv_fallback |= var_ign_srv_lookup_err;
786 	break;
787     case DNS_INVAL:
788 	dsb_status(why, "5.4.4");
789 	allow_non_srv_fallback |= var_ign_srv_lookup_err;
790 	break;
791     case DNS_POLICY:
792 	dsb_status(why, "4.7.0");
793 	break;
794     case DNS_FAIL:
795 	dsb_status(why, "5.4.3");
796 	allow_non_srv_fallback |= var_ign_srv_lookup_err;
797 	break;
798     case DNS_NULLSRV:
799 	dsb_status(why, "5.1.0");
800 	break;
801     case DNS_OK:
802 	/* Shuffle then sort the SRV rr records by priority and weight. */
803 	srv_names = dns_srv_rr_sort(srv_names);
804 	best_pref = (srv_names ? srv_names->pref : IMPOSSIBLE_PREFERENCE);
805 	addr_list = smtp_addr_list(srv_names, why);
806 	if (mxrr)
807 	    *mxrr = dns_rr_copy(srv_names);	/* copies one record! */
808 	dns_rr_free(srv_names);
809 	if (addr_list == 0) {
810 	    msg_warn("no SRV host for %s has a valid address record",
811 		     str_srv_qname);
812 	    break;
813 	}
814 	/* Optional loop prevention, similar to smtp_domain_addr(). */
815 	best_found = (addr_list ? addr_list->pref : IMPOSSIBLE_PREFERENCE);
816 	if (msg_verbose)
817 	    smtp_print_addr(aname, addr_list);
818 	if ((misc_flags & SMTP_MISC_FLAG_LOOP_DETECT)
819 	    && (self = smtp_find_self(addr_list)) != 0) {
820 	    addr_list = smtp_truncate_self(addr_list, self->pref);
821 	    if (addr_list == 0) {
822 		if (best_pref != best_found) {
823 		    dsb_simple(why, "4.4.4",
824 			       "unable to find primary relay for %s",
825 			       str_srv_qname);
826 		} else {
827 		    dsb_simple(why, "5.4.6", "mail for %s loops back to myself",
828 			       str_srv_qname);
829 		}
830 	    }
831 	}
832 	/* TODO: sort by priority, weight, and address family preference. */
833 
834 	/* Optional address family balancing, as in smtp_domain_addr(). */
835 	if (addr_list && addr_list->next) {
836 	    if (var_smtp_mxaddr_limit > 0 && var_smtp_balance_inet_proto)
837 		addr_list = smtp_balance_inet_proto(addr_list, misc_flags,
838 						    var_smtp_mxaddr_limit);
839 	}
840 	break;
841     case DNS_NOTFOUND:
842 	dsb_status(why, "5.4.4");
843 	break;
844     }
845 
846     /*
847      * If permitted, fall back to non-SRV record lookups.
848      */
849     if (addr_list == 0 && allow_non_srv_fallback) {
850 	msg_info("skipping SRV lookup for %s: %s",
851 		 str_srv_qname, STR(why->reason));
852 	if (misc_flags & SMTP_MISC_FLAG_FALLBACK_SRV_TO_MX)
853 	    addr_list = smtp_domain_addr(name, mxrr, misc_flags, why,
854 					 found_myself);
855 	else
856 	    addr_list = smtp_host_addr(name, misc_flags, why);
857     }
858 
859     /*
860      * Only if we're not falling back.
861      */
862     else {
863 	*found_myself |= (self != 0);
864     }
865     return (addr_list);
866 }
867