1/*++
2/* NAME
3/*	smtp_addr 3
4/* SUMMARY
5/*	SMTP server address lookup
6/* SYNOPSIS
7/*	#include "smtp_addr.h"
8/*
9/*	DNS_RR *smtp_domain_addr(name, mxrr, misc_flags, why, found_myself)
10/*	char	*name;
11/*	DNS_RR  **mxrr;
12/*	int	misc_flags;
13/*	DSN_BUF	*why;
14/*	int	*found_myself;
15/*
16/*	DNS_RR *smtp_host_addr(name, misc_flags, why)
17/*	char	*name;
18/*	int	misc_flags;
19/*	DSN_BUF	*why;
20/* DESCRIPTION
21/*	This module implements Internet address lookups. By default,
22/*	lookups are done via the Internet domain name service (DNS).
23/*	A reasonable number of CNAME indirections is permitted. When
24/*	DNS lookups are disabled, host address lookup is done with
25/*	getnameinfo() or gethostbyname().
26/*
27/*	smtp_domain_addr() looks up the network addresses for mail
28/*	exchanger hosts listed for the named domain. Addresses are
29/*	returned in most-preferred first order. The result is truncated
30/*	so that it contains only hosts that are more preferred than the
31/*	local mail server itself. The found_myself result parameter
32/*	is updated when the local MTA is MX host for the specified
33/*	destination.  If MX records were found, the rname, qname,
34/*	and dnssec validation status of the MX RRset are returned
35/*	via mxrr, which the caller must free with dns_rr_free().
36/*
37/*	When no mail exchanger is listed in the DNS for \fIname\fR, the
38/*	request is passed to smtp_host_addr().
39/*
40/*	It is an error to call smtp_domain_addr() when DNS lookups are
41/*	disabled.
42/*
43/*	smtp_host_addr() looks up all addresses listed for the named
44/*	host.  The host can be specified as a numerical Internet network
45/*	address, or as a symbolic host name.
46/*
47/*	Results from smtp_domain_addr() or smtp_host_addr() are
48/*	destroyed by dns_rr_free(), including null lists.
49/* DIAGNOSTICS
50/*	Panics: interface violations. For example, calling smtp_domain_addr()
51/*	when DNS lookups are explicitly disabled.
52/*
53/*	All routines either return a DNS_RR pointer, or return a null
54/*	pointer and update the \fIwhy\fR argument accordingly.
55/* LICENSE
56/* .ad
57/* .fi
58/*	The Secure Mailer license must be distributed with this software.
59/* AUTHOR(S)
60/*	Wietse Venema
61/*	IBM T.J. Watson Research
62/*	P.O. Box 704
63/*	Yorktown Heights, NY 10598, USA
64/*--*/
65
66/* System library. */
67
68#include <sys_defs.h>
69#include <sys/socket.h>
70#include <netinet/in.h>
71#include <arpa/inet.h>
72#include <stdlib.h>
73#include <netdb.h>
74#include <ctype.h>
75#include <string.h>
76#include <unistd.h>
77#include <errno.h>
78
79/* Utility library. */
80
81#include <msg.h>
82#include <vstring.h>
83#include <mymalloc.h>
84#include <inet_addr_list.h>
85#include <stringops.h>
86#include <myaddrinfo.h>
87#include <inet_proto.h>
88
89/* Global library. */
90
91#include <mail_params.h>
92#include <own_inet_addr.h>
93#include <dsn_buf.h>
94
95/* DNS library. */
96
97#include <dns.h>
98
99/* Application-specific. */
100
101#include "smtp.h"
102#include "smtp_addr.h"
103
104/* smtp_print_addr - print address list */
105
106static void smtp_print_addr(const char *what, DNS_RR *addr_list)
107{
108    DNS_RR *addr;
109    MAI_HOSTADDR_STR hostaddr;
110
111    msg_info("begin %s address list", what);
112    for (addr = addr_list; addr; addr = addr->next) {
113	if (dns_rr_to_pa(addr, &hostaddr) == 0) {
114	    msg_warn("skipping record type %s: %m", dns_strtype(addr->type));
115	} else {
116	    msg_info("pref %4d host %s/%s",
117		     addr->pref, SMTP_HNAME(addr),
118		     hostaddr.buf);
119	}
120    }
121    msg_info("end %s address list", what);
122}
123
124/* smtp_addr_one - address lookup for one host name */
125
126static DNS_RR *smtp_addr_one(DNS_RR *addr_list, const char *host, int res_opt,
127			             unsigned pref, DSN_BUF *why)
128{
129    const char *myname = "smtp_addr_one";
130    DNS_RR *addr = 0;
131    DNS_RR *rr;
132    int     aierr;
133    struct addrinfo *res0;
134    struct addrinfo *res;
135    INET_PROTO_INFO *proto_info = inet_proto_info();
136    int     found;
137
138    if (msg_verbose)
139	msg_info("%s: host %s", myname, host);
140
141    /*
142     * Interpret a numerical name as an address.
143     */
144    if (hostaddr_to_sockaddr(host, (char *) 0, 0, &res0) == 0
145     && strchr((char *) proto_info->sa_family_list, res0->ai_family) != 0) {
146	if ((addr = dns_sa_to_rr(host, pref, res0->ai_addr)) == 0)
147	    msg_fatal("host %s: conversion error for address family %d: %m",
148		    host, ((struct sockaddr *) (res0->ai_addr))->sa_family);
149	addr_list = dns_rr_append(addr_list, addr);
150	freeaddrinfo(res0);
151	return (addr_list);
152    }
153
154    /*
155     * Use DNS lookup, but keep the option open to use native name service.
156     *
157     * XXX A soft error dominates past and future hard errors. Therefore we
158     * should not clobber a soft error text and status code.
159     */
160    if (smtp_host_lookup_mask & SMTP_HOST_FLAG_DNS) {
161	res_opt |= smtp_dns_res_opt;
162	switch (dns_lookup_v(host, res_opt, &addr, (VSTRING *) 0,
163			     why->reason, DNS_REQ_FLAG_NONE,
164			     proto_info->dns_atype_list)) {
165	case DNS_OK:
166	    for (rr = addr; rr; rr = rr->next)
167		rr->pref = pref;
168	    addr_list = dns_rr_append(addr_list, addr);
169	    return (addr_list);
170	default:
171	    dsb_status(why, "4.4.3");
172	    return (addr_list);
173	case DNS_FAIL:
174	    dsb_status(why, SMTP_HAS_SOFT_DSN(why) ? "4.4.3" : "5.4.3");
175	    return (addr_list);
176	case DNS_INVAL:
177	    dsb_status(why, SMTP_HAS_SOFT_DSN(why) ? "4.4.4" : "5.4.4");
178	    return (addr_list);
179	case DNS_NOTFOUND:
180	    dsb_status(why, SMTP_HAS_SOFT_DSN(why) ? "4.4.4" : "5.4.4");
181	    /* maybe native naming service will succeed */
182	    break;
183	}
184    }
185
186    /*
187     * Use the native name service which also looks in /etc/hosts.
188     *
189     * XXX A soft error dominates past and future hard errors. Therefore we
190     * should not clobber a soft error text and status code.
191     */
192#define RETRY_AI_ERROR(e) \
193        ((e) == EAI_AGAIN || (e) == EAI_MEMORY || (e) == EAI_SYSTEM)
194#ifdef EAI_NODATA
195#define DSN_NOHOST(e) \
196	((e) == EAI_AGAIN || (e) == EAI_NODATA || (e) == EAI_NONAME)
197#else
198#define DSN_NOHOST(e) \
199	((e) == EAI_AGAIN || (e) == EAI_NONAME)
200#endif
201
202    if (smtp_host_lookup_mask & SMTP_HOST_FLAG_NATIVE) {
203	if ((aierr = hostname_to_sockaddr(host, (char *) 0, 0, &res0)) != 0) {
204	    dsb_simple(why, (SMTP_HAS_SOFT_DSN(why) || RETRY_AI_ERROR(aierr)) ?
205		       (DSN_NOHOST(aierr) ? "4.4.4" : "4.3.0") :
206		       (DSN_NOHOST(aierr) ? "5.4.4" : "5.3.0"),
207		       "unable to look up host %s: %s",
208		       host, MAI_STRERROR(aierr));
209	} else {
210	    for (found = 0, res = res0; res != 0; res = res->ai_next) {
211		if (strchr((char *) proto_info->sa_family_list, res->ai_family) == 0) {
212		    msg_info("skipping address family %d for host %s",
213			     res->ai_family, host);
214		    continue;
215		}
216		found++;
217		if ((addr = dns_sa_to_rr(host, pref, res->ai_addr)) == 0)
218		    msg_fatal("host %s: conversion error for address family %d: %m",
219		    host, ((struct sockaddr *) (res0->ai_addr))->sa_family);
220		addr_list = dns_rr_append(addr_list, addr);
221	    }
222	    freeaddrinfo(res0);
223	    if (found == 0) {
224		dsb_simple(why, SMTP_HAS_SOFT_DSN(why) ? "4.4.4" : "5.4.4",
225			   "%s: host not found", host);
226	    }
227	    return (addr_list);
228	}
229    }
230
231    /*
232     * No further alternatives for host lookup.
233     */
234    return (addr_list);
235}
236
237/* smtp_addr_list - address lookup for a list of mail exchangers */
238
239static DNS_RR *smtp_addr_list(DNS_RR *mx_names, DSN_BUF *why)
240{
241    DNS_RR *addr_list = 0;
242    DNS_RR *rr;
243    int     res_opt = mx_names->dnssec_valid ? RES_USE_DNSSEC : 0;
244
245    /*
246     * As long as we are able to look up any host address, we ignore problems
247     * with DNS lookups (except if we're backup MX, and all the better MX
248     * hosts can't be found).
249     *
250     * XXX 2821: update the error status (0->FAIL upon unrecoverable lookup
251     * error, any->RETRY upon temporary lookup error) so that we can
252     * correctly handle the case of no resolvable MX host. Currently this is
253     * always treated as a soft error. RFC 2821 wants a more precise
254     * response.
255     *
256     * XXX dns_lookup() enables RES_DEFNAMES. This is wrong for names found in
257     * MX records - we should not append the local domain to dot-less names.
258     *
259     * XXX However, this is not the only problem. If we use the native name
260     * service for host lookup, then it will usually enable RES_DNSRCH which
261     * appends local domain information to all lookups. In particular,
262     * getaddrinfo() may invoke a resolver that runs in a different process
263     * (NIS server, nscd), so we can't even reliably turn this off by
264     * tweaking the in-process resolver flags.
265     */
266    for (rr = mx_names; rr; rr = rr->next) {
267	if (rr->type != T_MX)
268	    msg_panic("smtp_addr_list: bad resource type: %d", rr->type);
269	addr_list = smtp_addr_one(addr_list, (char *) rr->data, res_opt,
270				  rr->pref, why);
271    }
272    return (addr_list);
273}
274
275/* smtp_find_self - spot myself in a crowd of mail exchangers */
276
277static DNS_RR *smtp_find_self(DNS_RR *addr_list)
278{
279    const char *myname = "smtp_find_self";
280    INET_ADDR_LIST *self;
281    INET_ADDR_LIST *proxy;
282    DNS_RR *addr;
283    int     i;
284
285    self = own_inet_addr_list();
286    proxy = proxy_inet_addr_list();
287
288    for (addr = addr_list; addr; addr = addr->next) {
289
290	/*
291	 * Find out if this mail system is listening on this address.
292	 */
293	for (i = 0; i < self->used; i++)
294	    if (DNS_RR_EQ_SA(addr, (struct sockaddr *) (self->addrs + i))) {
295		if (msg_verbose)
296		    msg_info("%s: found self at pref %d", myname, addr->pref);
297		return (addr);
298	    }
299
300	/*
301	 * Find out if this mail system has a proxy listening on this
302	 * address.
303	 */
304	for (i = 0; i < proxy->used; i++)
305	    if (DNS_RR_EQ_SA(addr, (struct sockaddr *) (proxy->addrs + i))) {
306		if (msg_verbose)
307		    msg_info("%s: found proxy at pref %d", myname, addr->pref);
308		return (addr);
309	    }
310    }
311
312    /*
313     * Didn't find myself, or my proxy.
314     */
315    if (msg_verbose)
316	msg_info("%s: not found", myname);
317    return (0);
318}
319
320/* smtp_truncate_self - truncate address list at self and equivalents */
321
322static DNS_RR *smtp_truncate_self(DNS_RR *addr_list, unsigned pref)
323{
324    DNS_RR *addr;
325    DNS_RR *last;
326
327    for (last = 0, addr = addr_list; addr; last = addr, addr = addr->next) {
328	if (pref == addr->pref) {
329	    if (msg_verbose)
330		smtp_print_addr("truncated", addr);
331	    dns_rr_free(addr);
332	    if (last == 0) {
333		addr_list = 0;
334	    } else {
335		last->next = 0;
336	    }
337	    break;
338	}
339    }
340    return (addr_list);
341}
342
343/* smtp_domain_addr - mail exchanger address lookup */
344
345DNS_RR *smtp_domain_addr(char *name, DNS_RR **mxrr, int misc_flags,
346			         DSN_BUF *why, int *found_myself)
347{
348    DNS_RR *mx_names;
349    DNS_RR *addr_list = 0;
350    DNS_RR *self = 0;
351    unsigned best_pref;
352    unsigned best_found;
353    int     r = 0;			/* Resolver flags */
354
355    dsb_reset(why);				/* Paranoia */
356
357    /*
358     * Preferences from DNS use 0..32767, fall-backs use 32768+.
359     */
360#define IMPOSSIBLE_PREFERENCE	(~0)
361
362    /*
363     * Sanity check.
364     */
365    if (smtp_dns_support == SMTP_DNS_DISABLED)
366	msg_panic("smtp_domain_addr: DNS lookup is disabled");
367    if (smtp_dns_support == SMTP_DNS_DNSSEC)
368	r |= RES_USE_DNSSEC;
369
370    /*
371     * Look up the mail exchanger hosts listed for this name. Sort the
372     * results by preference. Look up the corresponding host addresses, and
373     * truncate the list so that it contains only hosts that are more
374     * preferred than myself. When no MX resource records exist, look up the
375     * addresses listed for this name.
376     *
377     * According to RFC 974: "It is possible that the list of MXs in the
378     * response to the query will be empty.  This is a special case.  If the
379     * list is empty, mailers should treat it as if it contained one RR, an
380     * MX RR with a preference value of 0, and a host name of REMOTE.  (I.e.,
381     * REMOTE is its only MX).  In addition, the mailer should do no further
382     * processing on the list, but should attempt to deliver the message to
383     * REMOTE."
384     *
385     * Normally it is OK if an MX host cannot be found in the DNS; we'll just
386     * use a backup one, and silently ignore the better MX host. However, if
387     * the best backup that we can find in the DNS is the local machine, then
388     * we must remember that the local machine is not the primary MX host, or
389     * else we will claim that mail loops back.
390     *
391     * XXX Optionally do A lookups even when the MX lookup didn't complete.
392     * Unfortunately with some DNS servers this is not a transient problem.
393     *
394     * XXX Ideally we would perform A lookups only as far as needed. But as long
395     * as we're looking up all the hosts, it would be better to look up the
396     * least preferred host first, so that DNS lookup error messages make
397     * more sense.
398     *
399     * XXX 2821: RFC 2821 says that the sender must shuffle equal-preference MX
400     * hosts, whereas multiple A records per hostname must be used in the
401     * order as received. They make the bogus assumption that a hostname with
402     * multiple A records corresponds to one machine with multiple network
403     * interfaces.
404     *
405     * XXX 2821: Postfix recognizes the local machine by looking for its own IP
406     * address in the list of mail exchangers. RFC 2821 says one has to look
407     * at the mail exchanger hostname as well, making the bogus assumption
408     * that an IP address is listed only under one hostname. However, looking
409     * at hostnames provides a partial solution for MX hosts behind a NAT
410     * gateway.
411     */
412    switch (dns_lookup(name, T_MX, r, &mx_names, (VSTRING *) 0, why->reason)) {
413    default:
414	dsb_status(why, "4.4.3");
415	if (var_ign_mx_lookup_err)
416	    addr_list = smtp_host_addr(name, misc_flags, why);
417	break;
418    case DNS_INVAL:
419	dsb_status(why, "5.4.4");
420	if (var_ign_mx_lookup_err)
421	    addr_list = smtp_host_addr(name, misc_flags, why);
422	break;
423    case DNS_FAIL:
424	dsb_status(why, "5.4.3");
425	if (var_ign_mx_lookup_err)
426	    addr_list = smtp_host_addr(name, misc_flags, why);
427	break;
428    case DNS_OK:
429	mx_names = dns_rr_sort(mx_names, dns_rr_compare_pref_any);
430	best_pref = (mx_names ? mx_names->pref : IMPOSSIBLE_PREFERENCE);
431	addr_list = smtp_addr_list(mx_names, why);
432	if (mxrr)
433	    *mxrr = dns_rr_copy(mx_names);	/* copies one record! */
434	dns_rr_free(mx_names);
435	if (addr_list == 0) {
436	    /* Text does not change. */
437	    if (var_smtp_defer_mxaddr) {
438		/* Don't clobber the null terminator. */
439		if (SMTP_HAS_HARD_DSN(why))
440		    SMTP_SET_SOFT_DSN(why);	/* XXX */
441		/* Require some error status. */
442		else if (!SMTP_HAS_SOFT_DSN(why))
443		    msg_panic("smtp_domain_addr: bad status");
444	    }
445	    msg_warn("no MX host for %s has a valid address record", name);
446	    break;
447	}
448	best_found = (addr_list ? addr_list->pref : IMPOSSIBLE_PREFERENCE);
449	if (msg_verbose)
450	    smtp_print_addr(name, addr_list);
451	if ((misc_flags & SMTP_MISC_FLAG_LOOP_DETECT)
452	    && (self = smtp_find_self(addr_list)) != 0) {
453	    addr_list = smtp_truncate_self(addr_list, self->pref);
454	    if (addr_list == 0) {
455		if (best_pref != best_found) {
456		    dsb_simple(why, "4.4.4",
457			       "unable to find primary relay for %s", name);
458		} else {
459		    dsb_simple(why, "5.4.6", "mail for %s loops back to myself",
460			       name);
461		}
462	    }
463	}
464#define SMTP_COMPARE_ADDR(flags) \
465	(((flags) & SMTP_MISC_FLAG_PREF_IPV6) ? dns_rr_compare_pref_ipv6 : \
466	 ((flags) & SMTP_MISC_FLAG_PREF_IPV4) ? dns_rr_compare_pref_ipv4 : \
467	 dns_rr_compare_pref_any)
468
469	if (addr_list && addr_list->next && var_smtp_rand_addr) {
470	    addr_list = dns_rr_shuffle(addr_list);
471	    addr_list = dns_rr_sort(addr_list, SMTP_COMPARE_ADDR(misc_flags));
472	}
473	break;
474    case DNS_NOTFOUND:
475	addr_list = smtp_host_addr(name, misc_flags, why);
476	break;
477    }
478
479    /*
480     * Clean up.
481     */
482    *found_myself |= (self != 0);
483    return (addr_list);
484}
485
486/* smtp_host_addr - direct host lookup */
487
488DNS_RR *smtp_host_addr(const char *host, int misc_flags, DSN_BUF *why)
489{
490    DNS_RR *addr_list;
491    int     res_opt = 0;
492
493    dsb_reset(why);				/* Paranoia */
494
495    if (smtp_dns_support == SMTP_DNS_DNSSEC)
496	res_opt |= RES_USE_DNSSEC;
497
498    /*
499     * If the host is specified by numerical address, just convert the
500     * address to internal form. Otherwise, the host is specified by name.
501     */
502#define PREF0	0
503    addr_list = smtp_addr_one((DNS_RR *) 0, host, res_opt, PREF0, why);
504    if (addr_list
505	&& (misc_flags & SMTP_MISC_FLAG_LOOP_DETECT)
506	&& smtp_find_self(addr_list) != 0) {
507	dns_rr_free(addr_list);
508	dsb_simple(why, "5.4.6", "mail for %s loops back to myself", host);
509	return (0);
510    }
511    if (addr_list && addr_list->next) {
512	if (var_smtp_rand_addr)
513	    addr_list = dns_rr_shuffle(addr_list);
514	/* The following changes the order of equal-preference hosts. */
515	if (inet_proto_info()->ai_family_list[1] != 0)
516	    addr_list = dns_rr_sort(addr_list, SMTP_COMPARE_ADDR(misc_flags));
517    }
518    if (msg_verbose)
519	smtp_print_addr(host, addr_list);
520    return (addr_list);
521}
522