/*++ /* NAME /* smtp_addr 3 /* SUMMARY /* SMTP server address lookup /* SYNOPSIS /* #include "smtp_addr.h" /* /* DNS_RR *smtp_domain_addr(name, why) /* char *name; /* VSTRING *why; /* /* DNS_RR *smtp_host_addr(name, why) /* char *name; /* VSTRING *why; /* DESCRIPTION /* This module implements Internet address lookups. By default, /* lookups are done via the Internet domain name service (DNS). /* A reasonable number of CNAME indirections is permitted. When /* DNS lookups are disabled, host address lookup is done with /* gethostbyname(). /* /* smtp_domain_addr() looks up the network addresses for mail /* exchanger hosts listed for the named domain. Addresses are /* returned in most-preferred first order. The result is truncated /* so that it contains only hosts that are more preferred than the /* local mail server itself. When the "best MX is local" feature /* is enabled, the local system is allowed to be the best mail /* exchanger, and the result is a null list pointer. Otherwise, /* mailer loops are treated as an error. /* /* When no mail exchanger is listed in the DNS for \fIname\fR, the /* request is passed to smtp_host_addr(). /* /* It is an error to call smtp_domain_addr() when DNS lookups are /* disabled. /* /* smtp_host_addr() looks up all addresses listed for the named /* host. The host can be specified as a numerical Internet network /* address, or as a symbolic host name. /* /* Results from smtp_domain_addr() or smtp_host_addr() are /* destroyed by dns_rr_free(), including null lists. /* DIAGNOSTICS /* Panics: interface violations. For example, calling smtp_domain_addr() /* when DNS lookups are explicitly disabled. /* /* All routines either return a DNS_RR pointer, or return a null /* pointer and set the \fIsmtp_errno\fR global variable accordingly: /* .IP SMTP_RETRY /* The request failed due to a soft error, and should be retried later. /* .IP SMTP_FAIL /* The request attempt failed due to a hard error. /* .PP /* In addition, a textual description of the problem is made available /* via the \fIwhy\fR argument. /* LICENSE /* .ad /* .fi /* The Secure Mailer license must be distributed with this software. /* AUTHOR(S) /* Wietse Venema /* IBM T.J. Watson Research /* P.O. Box 704 /* Yorktown Heights, NY 10598, USA /*--*/ /* System library. */ #include #include #include #include #include #include #include #include #include #ifndef INADDR_NONE #define INADDR_NONE 0xffffffff #endif /* Utility library. */ #include #include #include #include #include /* Global library. */ #include #include /* DNS library. */ #include /* Application-specific. */ #include "smtp.h" #include "smtp_addr.h" /* smtp_print_addr - print address list */ static void smtp_print_addr(char *what, DNS_RR *addr_list) { DNS_RR *addr; struct in_addr in_addr; msg_info("begin %s address list", what); for (addr = addr_list; addr; addr = addr->next) { if (addr->data_len > sizeof(addr)) { msg_warn("skipping address length %d", addr->data_len); } else { memcpy((char *) &in_addr, addr->data, sizeof(in_addr)); msg_info("pref %4d host %s/%s", addr->pref, addr->name, inet_ntoa(in_addr)); } } msg_info("end %s address list", what); } /* smtp_addr_one - address lookup for one host name */ static DNS_RR *smtp_addr_one(DNS_RR *addr_list, char *host, unsigned pref, VSTRING *why) { char *myname = "smtp_addr_one"; struct in_addr inaddr; DNS_FIXED fixed; DNS_RR *addr = 0; DNS_RR *rr; struct hostent *hp; if (msg_verbose) msg_info("%s: host %s", myname, host); /* * Interpret a numerical name as an address. */ if (ISDIGIT(host[0]) && (inaddr.s_addr = inet_addr(host)) != INADDR_NONE) { memset((char *) &fixed, 0, sizeof(fixed)); return (dns_rr_append(addr_list, dns_rr_create(host, &fixed, pref, (char *) &inaddr, sizeof(inaddr)))); } /* * Use gethostbyname() when DNS is disabled. */ if (var_disable_dns) { memset((char *) &fixed, 0, sizeof(fixed)); if ((hp = gethostbyname(host)) == 0) { vstring_sprintf(why, "%s: host not found", host); smtp_errno = SMTP_FAIL; } else if (hp->h_addrtype != AF_INET) { vstring_sprintf(why, "%s: host not found", host); msg_warn("%s: unknown address family %d for %s", myname, hp->h_addrtype, host); smtp_errno = SMTP_FAIL; } else { while (hp->h_addr_list[0]) { addr_list = dns_rr_append(addr_list, dns_rr_create(host, &fixed, pref, hp->h_addr_list[0], sizeof(inaddr))); hp->h_addr_list++; } } return (addr_list); } /* * Append the addresses for this host to the address list. */ switch (dns_lookup(host, T_A, 0, &addr, (VSTRING *) 0, why)) { case DNS_OK: for (rr = addr; rr; rr = rr->next) rr->pref = pref; addr_list = dns_rr_append(addr_list, addr); break; default: smtp_errno = SMTP_RETRY; break; case DNS_NOTFOUND: case DNS_FAIL: smtp_errno = SMTP_FAIL; break; } return (addr_list); } /* smtp_addr_list - address lookup for a list of mail exchangers */ static DNS_RR *smtp_addr_list(DNS_RR *mx_names, VSTRING *why) { DNS_RR *addr_list = 0; DNS_RR *rr; /* * As long as we are able to look up any host address, we ignore problems * with DNS lookups. */ for (rr = mx_names; rr; rr = rr->next) { if (rr->type != T_MX) msg_panic("smtp_addr_list: bad resource type: %d", rr->type); addr_list = smtp_addr_one(addr_list, (char *) rr->data, rr->pref, why); } return (addr_list); } /* smtp_addr_fallback - add list of fallback addresses */ static DNS_RR *smtp_addr_fallback(DNS_RR *addr_list) { static DNS_RR *fallback_list = 0; DNS_RR *mx_names; DNS_RR *mx_addr_list; DNS_RR *addr; char *saved_fallback_relay; char *cp; char *relay; int saved_smtp_errno = smtp_errno; VSTRING *why; DNS_RR *rr; unsigned int pref; /* * Build a cached list of fall-back host addresses. Issue a warning when * a fall-back host or domain is not found. This is most likely a local * configuration problem. * * XXX For the sake of admin-friendliness we want to support MX lookups for * fall-back relays. This comes at a price: the fallback relay lookup * routine almost entirely duplicates the smtp_domain_addr() routine. * * Fall-back hosts are given a preference that is outside the range of valid * DNS preferences (unsigned 16-bit integer). */ #define FB_PREF (0xffff + 1) if (fallback_list == 0) { why = vstring_alloc(1); cp = saved_fallback_relay = mystrdup(var_fallback_relay); for (pref = FB_PREF; (relay = mystrtok(&cp, " \t\r\n,")) != 0; pref++) { smtp_errno = 0; switch (dns_lookup(relay, T_MX, 0, &mx_names, (VSTRING *) 0, why)) { default: smtp_errno = SMTP_RETRY; break; case DNS_FAIL: smtp_errno = SMTP_FAIL; break; case DNS_OK: mx_addr_list = smtp_addr_list(mx_names, why); dns_rr_free(mx_names); for (addr = mx_addr_list; addr; addr = addr->next) addr->pref = pref; fallback_list = dns_rr_append(fallback_list, mx_addr_list); break; case DNS_NOTFOUND: fallback_list = smtp_addr_one(fallback_list, relay, pref, why); break; } if (smtp_errno != SMTP_OK) msg_warn("look up fall-back relay %s: %s", relay, vstring_str(why)); } vstring_free(why); myfree(saved_fallback_relay); } /* * Append a copy of the fall-back address list to the mail exchanger * address list - which may be an empty list if no mail exchanger was * found. */ for (rr = fallback_list; rr; rr = rr->next) addr_list = dns_rr_append(addr_list, dns_rr_copy(rr)); /* * Clean up. */ smtp_errno = saved_smtp_errno; return (addr_list); } /* smtp_find_self - spot myself in a crowd of mail exchangers */ static DNS_RR *smtp_find_self(DNS_RR *addr_list) { char *myname = "smtp_find_self"; INET_ADDR_LIST *self; DNS_RR *addr; int i; /* * Find the first address that lists any address that this mail system is * supposed to be listening on. */ #define INADDRP(x) ((struct in_addr *) (x)) self = own_inet_addr_list(); for (addr = addr_list; addr; addr = addr->next) { for (i = 0; i < self->used; i++) if (INADDRP(addr->data)->s_addr == self->addrs[i].s_addr) { if (msg_verbose) msg_info("%s: found at pref %d", myname, addr->pref); return (addr); } } /* * Didn't find myself. */ if (msg_verbose) msg_info("%s: not found", myname); return (0); } /* smtp_truncate_self - truncate address list at self and equivalents */ static DNS_RR *smtp_truncate_self(DNS_RR *addr_list, unsigned pref) { DNS_RR *addr; DNS_RR *last; for (last = 0, addr = addr_list; addr; last = addr, addr = addr->next) { if (pref == addr->pref) { if (msg_verbose) smtp_print_addr("truncated", addr); dns_rr_free(addr); if (last == 0) { addr_list = 0; } else { last->next = 0; } break; } } return (addr_list); } /* smtp_compare_mx - compare resource records by preference */ static int smtp_compare_mx(DNS_RR *a, DNS_RR *b) { return (a->pref - b->pref); } /* smtp_domain_addr - mail exchanger address lookup */ DNS_RR *smtp_domain_addr(char *name, VSTRING *why) { DNS_RR *mx_names; DNS_RR *addr_list = 0; DNS_RR *self; unsigned best_pref; unsigned best_found; /* * Preferences from DNS use 0..32767, fall-backs use 32768+. */ #define IMPOSSIBLE_PREFERENCE (~0) /* * Sanity check. */ if (var_disable_dns) msg_panic("smtp_domain_addr: DNS lookup is disabled"); /* * Look up the mail exchanger hosts listed for this name. Sort the * results by preference. Look up the corresponding host addresses, and * truncate the list so that it contains only hosts that are more * preferred than myself. When no MX resource records exist, look up the * addresses listed for this name. * * Normally it is OK if an MX host cannot be found in the DNS; we'll just * use a backup one, and silently ignore the better MX host. However, if * the best backup that we can find in the DNS is the local machine, then * we must remember that the local machine is not the primary MX host, or * else we will claim that mail loops back. * * XXX Optionally do A lookups even when the MX lookup didn't complete. * Unfortunately with some DNS servers this is not a transient problem. * * XXX Ideally we would perform A lookups only as far as needed. But as long * as we're looking up all the hosts, it would be better to look up the * least preferred host first, so that DNS lookup error messages make * more sense. */ switch (dns_lookup(name, T_MX, 0, &mx_names, (VSTRING *) 0, why)) { default: smtp_errno = SMTP_RETRY; if (var_ign_mx_lookup_err) addr_list = smtp_host_addr(name, why); break; case DNS_FAIL: smtp_errno = SMTP_FAIL; if (var_ign_mx_lookup_err) addr_list = smtp_host_addr(name, why); break; case DNS_OK: mx_names = dns_rr_sort(mx_names, smtp_compare_mx); best_pref = (mx_names ? mx_names->pref : IMPOSSIBLE_PREFERENCE); addr_list = smtp_addr_list(mx_names, why); dns_rr_free(mx_names); best_found = (addr_list ? addr_list->pref : IMPOSSIBLE_PREFERENCE); if (*var_fallback_relay) addr_list = smtp_addr_fallback(addr_list); if (msg_verbose) smtp_print_addr(name, addr_list); if ((self = smtp_find_self(addr_list)) != 0) { addr_list = smtp_truncate_self(addr_list, self->pref); if (addr_list == 0) { if (best_pref != best_found) { vstring_sprintf(why, "unable to find primary relay for %s", name); smtp_errno = SMTP_RETRY; } else if (*var_bestmx_transp != 0) { /* we're best MX */ smtp_errno = SMTP_OK; } else { vstring_sprintf(why, "mail for %s loops back to myself", name); smtp_errno = SMTP_FAIL; } } } break; case DNS_NOTFOUND: addr_list = smtp_host_addr(name, why); break; } /* * Clean up. */ return (addr_list); } /* smtp_host_addr - direct host lookup */ DNS_RR *smtp_host_addr(char *host, VSTRING *why) { DNS_RR *addr_list; /* * If the host is specified by numerical address, just convert the * address to internal form. Otherwise, the host is specified by name. */ #define PREF0 0 addr_list = smtp_addr_one((DNS_RR *) 0, host, PREF0, why); if (*var_fallback_relay) addr_list = smtp_addr_fallback(addr_list); if (msg_verbose) smtp_print_addr(host, addr_list); return (addr_list); }