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