#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>
#include <errno.h>
#ifndef INADDR_NONE
#define INADDR_NONE 0xffffffff
#endif
#ifdef NO_HERRNO
static int h_errno = TRY_AGAIN;
#define HSTRERROR(err) "Host not found"
#else
#define HSTRERROR(err) (\
err == TRY_AGAIN ? "Host not found, try again" : \
err == HOST_NOT_FOUND ? "Host not found" : \
err == NO_DATA ? "Host name has no address" : \
err == NO_RECOVERY ? "Name server failure" : \
strerror(errno) \
)
#endif
#include <msg.h>
#include <vstring.h>
#include <mymalloc.h>
#include <inet_addr_list.h>
#include <stringops.h>
#include <myrand.h>
#include <mail_params.h>
#include <own_inet_addr.h>
#include <dns.h>
#include "smtp.h"
#include "smtp_addr.h"
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);
}
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);
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))));
}
if (var_disable_dns) {
memset((char *) &fixed, 0, sizeof(fixed));
if ((hp = gethostbyname(host)) == 0) {
vstring_sprintf(why, "%s: %s", host, HSTRERROR(h_errno));
smtp_errno = (h_errno == TRY_AGAIN ? SMTP_RETRY : 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);
}
switch (dns_lookup(host, T_A, RES_DEFNAMES, &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);
}
static DNS_RR *smtp_addr_list(DNS_RR *mx_names, VSTRING *why)
{
DNS_RR *addr_list = 0;
DNS_RR *rr;
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);
}
static DNS_RR *smtp_find_self(DNS_RR *addr_list)
{
char *myname = "smtp_find_self";
INET_ADDR_LIST *self;
DNS_RR *addr;
int i;
#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);
}
}
self = proxy_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);
}
}
if (msg_verbose)
msg_info("%s: not found", myname);
return (0);
}
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);
}
static int smtp_compare_pref(DNS_RR *a, DNS_RR *b)
{
return (a->pref - b->pref);
}
DNS_RR *smtp_domain_addr(char *name, VSTRING *why, int *found_myself)
{
DNS_RR *mx_names;
DNS_RR *addr_list = 0;
DNS_RR *self = 0;
unsigned best_pref;
unsigned best_found;
#define IMPOSSIBLE_PREFERENCE (~0)
if (var_disable_dns)
msg_panic("smtp_domain_addr: DNS lookup is disabled");
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_pref);
best_pref = (mx_names ? mx_names->pref : IMPOSSIBLE_PREFERENCE);
addr_list = smtp_addr_list(mx_names, why);
dns_rr_free(mx_names);
if (addr_list == 0) {
smtp_errno = SMTP_RETRY;
msg_warn("no MX host for %s has a valid A record", name);
break;
}
best_found = (addr_list ? addr_list->pref : IMPOSSIBLE_PREFERENCE);
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) {
smtp_errno = SMTP_OK;
} else {
msg_warn("mailer loop: best MX host for %s is local",
name);
vstring_sprintf(why, "mail for %s loops back to myself",
name);
smtp_errno = SMTP_FAIL;
}
}
}
if (addr_list && addr_list->next && var_smtp_rand_addr) {
addr_list = dns_rr_shuffle(addr_list);
addr_list = dns_rr_sort(addr_list, smtp_compare_pref);
}
break;
case DNS_NOTFOUND:
addr_list = smtp_host_addr(name, why);
break;
}
*found_myself = (self != 0);
return (addr_list);
}
DNS_RR *smtp_host_addr(char *host, VSTRING *why)
{
DNS_RR *addr_list;
#define PREF0 0
addr_list = smtp_addr_one((DNS_RR *) 0, host, PREF0, why);
if (addr_list && addr_list->next && var_smtp_rand_addr)
addr_list = dns_rr_shuffle(addr_list);
if (msg_verbose)
smtp_print_addr(host, addr_list);
return (addr_list);
}