NetBSD/gnu/dist/postfix/smtp/smtp_addr.c

464 lines
13 KiB
C

/*++
/* 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 <sys_defs.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <netdb.h>
#include <ctype.h>
#include <string.h>
#include <unistd.h>
#ifndef INADDR_NONE
#define INADDR_NONE 0xffffffff
#endif
/* Utility library. */
#include <msg.h>
#include <vstring.h>
#include <mymalloc.h>
#include <inet_addr_list.h>
#include <stringops.h>
/* Global library. */
#include <mail_params.h>
#include <own_inet_addr.h>
/* DNS library. */
#include <dns.h>
/* 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);
}