#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>
#include <msg.h>
#include <vstring.h>
#include <mymalloc.h>
#include <inet_addr_list.h>
#include <stringops.h>
#include <myaddrinfo.h>
#include <inet_proto.h>
#include <mail_params.h>
#include <own_inet_addr.h>
#include <dsn_buf.h>
#include <dns.h>
#include "smtp.h"
#include "smtp_addr.h"
static void smtp_print_addr(const char *what, DNS_RR *addr_list)
{
DNS_RR *addr;
MAI_HOSTADDR_STR hostaddr;
msg_info("begin %s address list", what);
for (addr = addr_list; addr; addr = addr->next) {
if (dns_rr_to_pa(addr, &hostaddr) == 0) {
msg_warn("skipping record type %s: %m", dns_strtype(addr->type));
} else {
msg_info("pref %4d host %s/%s",
addr->pref, SMTP_HNAME(addr),
hostaddr.buf);
}
}
msg_info("end %s address list", what);
}
static DNS_RR *smtp_addr_one(DNS_RR *addr_list, const char *host,
unsigned pref, DSN_BUF *why)
{
const char *myname = "smtp_addr_one";
DNS_RR *addr = 0;
DNS_RR *rr;
int aierr;
struct addrinfo *res0;
struct addrinfo *res;
INET_PROTO_INFO *proto_info = inet_proto_info();
int found;
if (msg_verbose)
msg_info("%s: host %s", myname, host);
if (hostaddr_to_sockaddr(host, (char *) 0, 0, &res0) == 0
&& strchr((char *) proto_info->sa_family_list, res0->ai_family) != 0) {
if ((addr = dns_sa_to_rr(host, pref, res0->ai_addr)) == 0)
msg_fatal("host %s: conversion error for address family %d: %m",
host, ((struct sockaddr *) (res0->ai_addr))->sa_family);
addr_list = dns_rr_append(addr_list, addr);
freeaddrinfo(res0);
return (addr_list);
}
if (smtp_host_lookup_mask & SMTP_HOST_FLAG_DNS) {
switch (dns_lookup_v(host, RES_DEFNAMES, &addr, (VSTRING *) 0,
why->reason, DNS_REQ_FLAG_NONE,
proto_info->dns_atype_list)) {
case DNS_OK:
for (rr = addr; rr; rr = rr->next)
rr->pref = pref;
addr_list = dns_rr_append(addr_list, addr);
return (addr_list);
default:
dsb_status(why, "4.4.3");
return (addr_list);
case DNS_FAIL:
dsb_status(why, SMTP_HAS_SOFT_DSN(why) ? "4.4.3" : "5.4.3");
return (addr_list);
case DNS_INVAL:
dsb_status(why, SMTP_HAS_SOFT_DSN(why) ? "4.4.4" : "5.4.4");
return (addr_list);
case DNS_NOTFOUND:
dsb_status(why, SMTP_HAS_SOFT_DSN(why) ? "4.4.4" : "5.4.4");
break;
}
}
#define RETRY_AI_ERROR(e) \
((e) == EAI_AGAIN || (e) == EAI_MEMORY || (e) == EAI_SYSTEM)
#ifdef EAI_NODATA
#define DSN_NOHOST(e) \
((e) == EAI_AGAIN || (e) == EAI_NODATA || (e) == EAI_NONAME)
#else
#define DSN_NOHOST(e) \
((e) == EAI_AGAIN || (e) == EAI_NONAME)
#endif
if (smtp_host_lookup_mask & SMTP_HOST_FLAG_NATIVE) {
if ((aierr = hostname_to_sockaddr(host, (char *) 0, 0, &res0)) != 0) {
dsb_simple(why, (SMTP_HAS_SOFT_DSN(why) || RETRY_AI_ERROR(aierr)) ?
(DSN_NOHOST(aierr) ? "4.4.4" : "4.3.0") :
(DSN_NOHOST(aierr) ? "5.4.4" : "5.3.0"),
"unable to look up host %s: %s",
host, MAI_STRERROR(aierr));
} else {
for (found = 0, res = res0; res != 0; res = res->ai_next) {
if (strchr((char *) proto_info->sa_family_list, res->ai_family) == 0) {
msg_info("skipping address family %d for host %s",
res->ai_family, host);
continue;
}
found++;
if ((addr = dns_sa_to_rr(host, pref, res->ai_addr)) == 0)
msg_fatal("host %s: conversion error for address family %d: %m",
host, ((struct sockaddr *) (res0->ai_addr))->sa_family);
addr_list = dns_rr_append(addr_list, addr);
}
freeaddrinfo(res0);
if (found == 0) {
dsb_simple(why, SMTP_HAS_SOFT_DSN(why) ? "4.4.4" : "5.4.4",
"%s: host not found", host);
}
return (addr_list);
}
}
return (addr_list);
}
static DNS_RR *smtp_addr_list(DNS_RR *mx_names, DSN_BUF *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)
{
const char *myname = "smtp_find_self";
INET_ADDR_LIST *self;
INET_ADDR_LIST *proxy;
DNS_RR *addr;
int i;
self = own_inet_addr_list();
proxy = proxy_inet_addr_list();
for (addr = addr_list; addr; addr = addr->next) {
for (i = 0; i < self->used; i++)
if (DNS_RR_EQ_SA(addr, (struct sockaddr *) (self->addrs + i))) {
if (msg_verbose)
msg_info("%s: found self at pref %d", myname, addr->pref);
return (addr);
}
for (i = 0; i < proxy->used; i++)
if (DNS_RR_EQ_SA(addr, (struct sockaddr *) (proxy->addrs + i))) {
if (msg_verbose)
msg_info("%s: found proxy 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);
}
DNS_RR *smtp_domain_addr(char *name, int misc_flags, DSN_BUF *why,
int *found_myself)
{
DNS_RR *mx_names;
DNS_RR *addr_list = 0;
DNS_RR *self = 0;
unsigned best_pref;
unsigned best_found;
dsb_reset(why);
#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->reason)) {
default:
dsb_status(why, "4.4.3");
if (var_ign_mx_lookup_err)
addr_list = smtp_host_addr(name, misc_flags, why);
break;
case DNS_INVAL:
dsb_status(why, "5.4.4");
if (var_ign_mx_lookup_err)
addr_list = smtp_host_addr(name, misc_flags, why);
break;
case DNS_FAIL:
dsb_status(why, "5.4.3");
if (var_ign_mx_lookup_err)
addr_list = smtp_host_addr(name, misc_flags, why);
break;
case DNS_OK:
mx_names = dns_rr_sort(mx_names, dns_rr_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) {
if (var_smtp_defer_mxaddr) {
if (SMTP_HAS_HARD_DSN(why))
SMTP_SET_SOFT_DSN(why);
else if (!SMTP_HAS_SOFT_DSN(why))
msg_panic("smtp_domain_addr: bad status");
}
msg_warn("no MX host for %s has a valid address record", name);
break;
}
best_found = (addr_list ? addr_list->pref : IMPOSSIBLE_PREFERENCE);
if (msg_verbose)
smtp_print_addr(name, addr_list);
if ((misc_flags & SMTP_MISC_FLAG_LOOP_DETECT)
&& (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) {
dsb_simple(why, "4.4.4",
"unable to find primary relay for %s", name);
} else {
dsb_simple(why, "5.4.6", "mail for %s loops back to myself",
name);
}
}
}
if (addr_list && addr_list->next && var_smtp_rand_addr) {
addr_list = dns_rr_shuffle(addr_list);
addr_list = dns_rr_sort(addr_list, dns_rr_compare_pref);
}
break;
case DNS_NOTFOUND:
addr_list = smtp_host_addr(name, misc_flags, why);
break;
}
*found_myself |= (self != 0);
return (addr_list);
}
DNS_RR *smtp_host_addr(const char *host, int misc_flags, DSN_BUF *why)
{
DNS_RR *addr_list;
dsb_reset(why);
#define PREF0 0
addr_list = smtp_addr_one((DNS_RR *) 0, host, PREF0, why);
if (addr_list
&& (misc_flags & SMTP_MISC_FLAG_LOOP_DETECT)
&& smtp_find_self(addr_list) != 0) {
dns_rr_free(addr_list);
dsb_simple(why, "5.4.6", "mail for %s loops back to myself", host);
return (0);
}
if (addr_list && addr_list->next) {
if (var_smtp_rand_addr)
addr_list = dns_rr_shuffle(addr_list);
if (inet_proto_info()->ai_family_list[1] != 0)
addr_list = dns_rr_sort(addr_list, dns_rr_compare_pref);
}
if (msg_verbose)
smtp_print_addr(host, addr_list);
return (addr_list);
}