xref: /netbsd-src/external/ibm-public/postfix/dist/src/smtp/smtp_addr.c (revision 6a493d6bc668897c91594964a732d38505b70cbb)
1 /*	$NetBSD: smtp_addr.c,v 1.1.1.2 2011/03/02 19:32:32 tron 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, 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 
105 static 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 
125 static 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 
237 static 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 
273 static 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 
318 static 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 
341 DNS_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 
479 DNS_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