#include <sys_defs.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <netdb.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <ctype.h>
#ifndef IPPORT_SMTP
#define IPPORT_SMTP 25
#endif
#include <msg.h>
#include <vstream.h>
#include <vstring.h>
#include <split_at.h>
#include <mymalloc.h>
#include <inet_addr_list.h>
#include <iostuff.h>
#include <timed_connect.h>
#include <stringops.h>
#include <host_port.h>
#include <sane_connect.h>
#include <myaddrinfo.h>
#include <sock_addr.h>
#include <inet_proto.h>
#include <mail_params.h>
#include <own_inet_addr.h>
#include <deliver_pass.h>
#include <mail_error.h>
#include <dsn_buf.h>
#include <mail_addr.h>
#include <dns.h>
#include <smtp.h>
#include <smtp_addr.h>
#include <smtp_reuse.h>
static SMTP_SESSION *smtp_connect_sock(int, struct sockaddr *, int,
const char *, const char *,
unsigned,
const char *, DSN_BUF *,
int);
static SMTP_SESSION *smtp_connect_unix(const char *addr,
DSN_BUF *why,
int sess_flags)
{
const char *myname = "smtp_connect_unix";
struct sockaddr_un sock_un;
int len = strlen(addr);
int sock;
dsb_reset(why);
if (len >= (int) sizeof(sock_un.sun_path)) {
msg_warn("unix-domain name too long: %s", addr);
dsb_simple(why, "4.3.5", "Server configuration error");
return (0);
}
memset((char *) &sock_un, 0, sizeof(sock_un));
sock_un.sun_family = AF_UNIX;
#ifdef HAS_SUN_LEN
sock_un.sun_len = len + 1;
#endif
memcpy(sock_un.sun_path, addr, len + 1);
if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
msg_fatal("%s: socket: %m", myname);
if (msg_verbose)
msg_info("%s: trying: %s...", myname, addr);
return (smtp_connect_sock(sock, (struct sockaddr *) & sock_un,
sizeof(sock_un), var_myhostname, addr,
0, addr, why, sess_flags));
}
static SMTP_SESSION *smtp_connect_addr(const char *destination, DNS_RR *addr,
unsigned port, DSN_BUF *why,
int sess_flags)
{
const char *myname = "smtp_connect_addr";
struct sockaddr_storage ss;
struct sockaddr *sa = (struct sockaddr *) & ss;
SOCKADDR_SIZE salen = sizeof(ss);
MAI_HOSTADDR_STR hostaddr;
int sock;
char *bind_addr;
char *bind_var;
dsb_reset(why);
if (dns_rr_to_sa(addr, port, sa, &salen) != 0) {
msg_warn("%s: skip address type %s: %m",
myname, dns_strtype(addr->type));
dsb_simple(why, "4.4.0", "network address conversion failed: %m");
return (0);
}
if ((sock = socket(sa->sa_family, SOCK_STREAM, 0)) < 0)
msg_fatal("%s: socket: %m", myname);
if (inet_windowsize > 0)
set_inet_windowsize(sock, inet_windowsize);
#ifdef HAS_IPV6
if (sa->sa_family == AF_INET6) {
bind_addr = var_smtp_bind_addr6;
bind_var = VAR_SMTP_BIND_ADDR6;
} else
#endif
if (sa->sa_family == AF_INET) {
bind_addr = var_smtp_bind_addr;
bind_var = VAR_SMTP_BIND_ADDR;
} else
bind_var = bind_addr = "";
if (*bind_addr) {
int aierr;
struct addrinfo *res0;
if ((aierr = hostaddr_to_sockaddr(bind_addr, (char *) 0, 0, &res0)) != 0)
msg_fatal("%s: bad %s parameter: %s: %s",
myname, bind_var, bind_addr, MAI_STRERROR(aierr));
if (bind(sock, res0->ai_addr, res0->ai_addrlen) < 0)
msg_warn("%s: bind %s: %m", myname, bind_addr);
else if (msg_verbose)
msg_info("%s: bind %s", myname, bind_addr);
freeaddrinfo(res0);
}
else {
int count = 0;
struct sockaddr *own_addr = 0;
INET_ADDR_LIST *addr_list = own_inet_addr_list();
struct sockaddr_storage *s;
for (s = addr_list->addrs; s < addr_list->addrs + addr_list->used; s++) {
if (SOCK_ADDR_FAMILY(s) == sa->sa_family) {
if (count++ > 0)
break;
own_addr = SOCK_ADDR_PTR(s);
}
}
if (count == 1 && !sock_addr_in_loopback(own_addr)) {
if (bind(sock, own_addr, SOCK_ADDR_LEN(own_addr)) < 0) {
SOCKADDR_TO_HOSTADDR(own_addr, SOCK_ADDR_LEN(own_addr),
&hostaddr, (MAI_SERVPORT_STR *) 0, 0);
msg_warn("%s: bind %s: %m", myname, hostaddr.buf);
} else if (msg_verbose) {
SOCKADDR_TO_HOSTADDR(own_addr, SOCK_ADDR_LEN(own_addr),
&hostaddr, (MAI_SERVPORT_STR *) 0, 0);
msg_info("%s: bind %s", myname, hostaddr.buf);
}
}
}
SOCKADDR_TO_HOSTADDR(sa, salen, &hostaddr, (MAI_SERVPORT_STR *) 0, 0);
if (msg_verbose)
msg_info("%s: trying: %s[%s] port %d...",
myname, SMTP_HNAME(addr), hostaddr.buf, ntohs(port));
return (smtp_connect_sock(sock, sa, salen, SMTP_HNAME(addr), hostaddr.buf,
port, destination, why, sess_flags));
}
static SMTP_SESSION *smtp_connect_sock(int sock, struct sockaddr * sa,
int salen, const char *name,
const char *addr,
unsigned port,
const char *destination,
DSN_BUF *why,
int sess_flags)
{
int conn_stat;
int saved_errno;
VSTREAM *stream;
time_t start_time;
start_time = time((time_t *) 0);
if (var_smtp_conn_tmout > 0) {
non_blocking(sock, NON_BLOCKING);
conn_stat = timed_connect(sock, sa, salen, var_smtp_conn_tmout);
saved_errno = errno;
non_blocking(sock, BLOCKING);
errno = saved_errno;
} else {
conn_stat = sane_connect(sock, sa, salen);
}
if (conn_stat < 0) {
if (port)
dsb_simple(why, "4.4.1", "connect to %s[%s]:%d: %m",
name, addr, ntohs(port));
else
dsb_simple(why, "4.4.1", "connect to %s[%s]: %m", name, addr);
close(sock);
return (0);
}
stream = vstream_fdopen(sock, O_RDWR);
if (sa->sa_family == AF_INET
#ifdef AF_INET6
|| sa->sa_family == AF_INET6
#endif
)
vstream_tweak_tcp(stream);
return (smtp_session_alloc(stream, destination, name, addr,
port, start_time, sess_flags));
}
static char *smtp_parse_destination(char *destination, char *def_service,
char **hostp, unsigned *portp)
{
char *buf = mystrdup(destination);
char *service;
struct servent *sp;
char *protocol = "tcp";
unsigned port;
const char *err;
if (msg_verbose)
msg_info("smtp_parse_destination: %s %s", destination, def_service);
if ((err = host_port(buf, hostp, (char *) 0, &service, def_service)) != 0)
msg_fatal("%s in server description: %s", err, destination);
if (alldig(service)) {
if ((port = atoi(service)) >= 65536 || port == 0)
msg_fatal("bad network port in destination: %s", destination);
*portp = htons(port);
} else {
if ((sp = getservbyname(service, protocol)) == 0)
msg_fatal("unknown service: %s/%s", service, protocol);
*portp = sp->s_port;
}
return (buf);
}
static void smtp_cleanup_session(SMTP_STATE *state)
{
DELIVER_REQUEST *request = state->request;
SMTP_SESSION *session = state->session;
int bad_session;
#define POSSIBLE_NOTIFICATION(sender) \
(*sender == 0 || strcmp(sender, mail_addr_double_bounce()) == 0)
if (session->history != 0
&& (session->error_mask & name_mask(VAR_NOTIFY_CLASSES,
mail_error_masks,
var_notify_classes)) != 0
&& POSSIBLE_NOTIFICATION(request->sender) == 0)
smtp_chat_notify(session);
bad_session = THIS_SESSION_IS_BAD;
if (THIS_SESSION_IS_EXPIRED)
smtp_quit(state);
if (THIS_SESSION_IS_CACHED
&& vstream_ferror(session->stream) == 0
&& vstream_feof(session->stream) == 0) {
smtp_save_session(state);
} else {
smtp_session_free(session);
}
state->session = 0;
if (HAVE_NEXTHOP_STATE(state) && !bad_session)
FREE_NEXTHOP_STATE(state);
smtp_rcpt_cleanup(state);
memset((char *) &request->msg_stats.conn_setup_done, 0,
sizeof(request->msg_stats.conn_setup_done));
memset((char *) &request->msg_stats.deliver_done, 0,
sizeof(request->msg_stats.deliver_done));
request->msg_stats.reuse_count = 0;
}
static void smtp_cache_policy(SMTP_STATE *state, const char *dest)
{
DELIVER_REQUEST *request = state->request;
state->misc_flags &= ~SMTP_MISC_FLAG_CONN_CACHE_MASK;
if (var_smtp_sender_auth)
return;
if (smtp_cache_dest && string_list_match(smtp_cache_dest, dest)) {
state->misc_flags |= SMTP_MISC_FLAG_CONN_CACHE_MASK;
} else if (var_smtp_cache_demand) {
if (request->flags & DEL_REQ_FLAG_CONN_LOAD)
state->misc_flags |= SMTP_MISC_FLAG_CONN_LOAD;
if (request->flags & DEL_REQ_FLAG_CONN_STORE)
state->misc_flags |= SMTP_MISC_FLAG_CONN_STORE;
}
}
static void smtp_connect_local(SMTP_STATE *state, const char *path)
{
const char *myname = "smtp_connect_local";
SMTP_SESSION *session;
DSN_BUF *why = state->why;
smtp_cache_policy(state, path);
#define NO_PORT 0
if ((state->misc_flags & SMTP_MISC_FLAG_CONN_LOAD) == 0
|| (session = smtp_reuse_addr(state, path, NO_PORT)) == 0)
session = smtp_connect_unix(path, why, state->misc_flags);
if ((state->session = session) != 0) {
session->state = state;
#ifdef USE_TLS
session->tls_nexthop = var_myhostname;
if (session->tls_level == TLS_LEV_MAY) {
msg_warn("%s: opportunistic TLS encryption is not appropriate "
"for unix-domain destinations.", myname);
session->tls_level = TLS_LEV_NONE;
}
#endif
state->misc_flags |= SMTP_MISC_FLAG_FINAL_SERVER;
if ((session->features & SMTP_FEATURE_FROM_CACHE) == 0
&& smtp_helo(state) != 0) {
if (!THIS_SESSION_IS_DEAD
&& vstream_ferror(session->stream) == 0
&& vstream_feof(session->stream) == 0)
smtp_quit(state);
} else {
smtp_xfer(state);
}
if ((state->misc_flags & SMTP_MISC_FLAG_FINAL_SERVER) == 0)
msg_panic("%s: unix-domain destination not final!", myname);
smtp_cleanup_session(state);
}
}
static void smtp_scrub_addr_list(HTABLE *cached_addr, DNS_RR **addr_list)
{
MAI_HOSTADDR_STR hostaddr;
DNS_RR *addr;
DNS_RR *next;
for (addr = *addr_list; addr; addr = next) {
next = addr->next;
if (dns_rr_to_pa(addr, &hostaddr) == 0) {
msg_warn("cannot convert type %s resource record to socket address",
dns_strtype(addr->type));
continue;
}
if (htable_locate(cached_addr, hostaddr.buf))
*addr_list = dns_rr_remove(*addr_list, addr);
}
}
static void smtp_update_addr_list(DNS_RR **addr_list, const char *server_addr,
int session_count)
{
DNS_RR *addr;
DNS_RR *next;
int aierr;
struct addrinfo *res0;
if (*addr_list == 0)
return;
if (session_count == var_smtp_mxsess_limit
|| session_count == var_smtp_mxaddr_limit) {
dns_rr_free(*addr_list);
*addr_list = 0;
return;
}
if ((aierr = hostaddr_to_sockaddr(server_addr, (char *) 0, 0, &res0)) != 0) {
msg_warn("hostaddr_to_sockaddr %s: %s",
server_addr, MAI_STRERROR(aierr));
} else {
for (addr = *addr_list; addr; addr = next) {
next = addr->next;
if (DNS_RR_EQ_SA(addr, (struct sockaddr *) res0->ai_addr)) {
*addr_list = dns_rr_remove(*addr_list, addr);
break;
}
}
freeaddrinfo(res0);
}
}
static int smtp_reuse_session(SMTP_STATE *state, int lookup_mx,
const char *domain, unsigned port,
DNS_RR **addr_list, int domain_best_pref)
{
int session_count = 0;
DNS_RR *addr;
DNS_RR *next;
MAI_HOSTADDR_STR hostaddr;
SMTP_SESSION *session;
if (*addr_list && SMTP_RCPT_LEFT(state) > 0
&& (session = smtp_reuse_domain(state, lookup_mx, domain, port)) != 0) {
session_count = 1;
smtp_update_addr_list(addr_list, session->addr, session_count);
if ((state->misc_flags & SMTP_MISC_FLAG_FINAL_NEXTHOP)
&& *addr_list == 0)
state->misc_flags |= SMTP_MISC_FLAG_FINAL_SERVER;
smtp_xfer(state);
smtp_cleanup_session(state);
}
for (addr = *addr_list; SMTP_RCPT_LEFT(state) > 0 && addr; addr = next) {
if (addr->pref != domain_best_pref)
break;
next = addr->next;
if (dns_rr_to_pa(addr, &hostaddr) != 0
&& (session = smtp_reuse_addr(state, hostaddr.buf, port)) != 0) {
session->features |= SMTP_FEATURE_BEST_MX;
session_count += 1;
smtp_update_addr_list(addr_list, session->addr, session_count);
if (*addr_list == 0)
next = 0;
if ((state->misc_flags & SMTP_MISC_FLAG_FINAL_NEXTHOP)
&& next == 0)
state->misc_flags |= SMTP_MISC_FLAG_FINAL_SERVER;
smtp_xfer(state);
smtp_cleanup_session(state);
}
}
return (session_count);
}
static void smtp_connect_inet(SMTP_STATE *state, const char *nexthop,
char *def_service)
{
DELIVER_REQUEST *request = state->request;
ARGV *sites;
char *dest;
char **cpp;
int non_fallback_sites;
int retry_plain = 0;
DSN_BUF *why = state->why;
if (inet_proto_info()->ai_family_list[0] == 0) {
dsb_simple(why, "4.4.4", "all network protocols are disabled");
return;
}
sites = argv_alloc(1);
argv_add(sites, nexthop, (char *) 0);
if (sites->argc == 0)
msg_panic("null destination: \"%s\"", nexthop);
non_fallback_sites = sites->argc;
if ((state->misc_flags & SMTP_MISC_FLAG_USE_LMTP) == 0)
argv_split_append(sites, var_fallback_relay, ", \t\r\n");
#define IS_FALLBACK_RELAY(cpp, sites, non_fallback_sites) \
(*(cpp) && (cpp) >= (sites)->argv + (non_fallback_sites))
for (cpp = sites->argv, (state->misc_flags |= SMTP_MISC_FLAG_FIRST_NEXTHOP);
SMTP_RCPT_LEFT(state) > 0 && (dest = *cpp) != 0;
cpp++, (state->misc_flags &= ~SMTP_MISC_FLAG_FIRST_NEXTHOP)) {
char *dest_buf;
char *domain;
unsigned port;
DNS_RR *addr_list;
DNS_RR *addr;
DNS_RR *next;
int addr_count;
int sess_count;
SMTP_SESSION *session;
int lookup_mx;
unsigned domain_best_pref;
MAI_HOSTADDR_STR hostaddr;
if (cpp[1] == 0)
state->misc_flags |= SMTP_MISC_FLAG_FINAL_NEXTHOP;
dest_buf = smtp_parse_destination(dest, def_service, &domain, &port);
if (var_helpful_warnings && ntohs(port) == 465) {
msg_info("CLIENT wrappermode (port smtps/465) is unimplemented");
msg_info("instead, send to (port submission/587) with STARTTLS");
}
if (msg_verbose)
msg_info("connecting to %s port %d", domain, ntohs(port));
if ((state->misc_flags & SMTP_MISC_FLAG_USE_LMTP) == 0) {
if (ntohs(port) == IPPORT_SMTP)
state->misc_flags |= SMTP_MISC_FLAG_LOOP_DETECT;
else
state->misc_flags &= ~SMTP_MISC_FLAG_LOOP_DETECT;
lookup_mx = (var_disable_dns == 0 && *dest != '[');
} else
lookup_mx = 0;
if (!lookup_mx) {
addr_list = smtp_host_addr(domain, state->misc_flags, why);
} else {
int i_am_mx = 0;
addr_list = smtp_domain_addr(domain, state->misc_flags,
why, &i_am_mx);
if (i_am_mx)
state->misc_flags |= SMTP_MISC_FLAG_FINAL_NEXTHOP;
}
if (addr_list == 0 && SMTP_HAS_LOOP_DSN(why))
state->misc_flags |= SMTP_MISC_FLAG_FINAL_NEXTHOP;
if (addr_list)
domain_best_pref = addr_list->pref;
if (addr_list && (state->misc_flags & SMTP_MISC_FLAG_FIRST_NEXTHOP)) {
smtp_cache_policy(state, domain);
if (state->misc_flags & SMTP_MISC_FLAG_CONN_STORE)
SET_NEXTHOP_STATE(state, lookup_mx, domain, port);
}
if (addr_list && (state->misc_flags & SMTP_MISC_FLAG_CONN_LOAD)) {
if (state->cache_used->used > 0)
smtp_scrub_addr_list(state->cache_used, &addr_list);
sess_count = addr_count =
smtp_reuse_session(state, lookup_mx, domain, port,
&addr_list, domain_best_pref);
} else
sess_count = addr_count = 0;
for (addr = addr_list; SMTP_RCPT_LEFT(state) > 0 && addr; addr = next) {
next = addr->next;
if (++addr_count == var_smtp_mxaddr_limit)
next = 0;
if ((state->misc_flags & SMTP_MISC_FLAG_CONN_LOAD) == 0
|| addr->pref == domain_best_pref
|| dns_rr_to_pa(addr, &hostaddr) == 0
|| !(session = smtp_reuse_addr(state, hostaddr.buf, port)))
session = smtp_connect_addr(dest, addr, port, why,
state->misc_flags);
if ((state->session = session) != 0) {
session->state = state;
if (addr->pref == domain_best_pref)
session->features |= SMTP_FEATURE_BEST_MX;
if ((state->misc_flags & SMTP_MISC_FLAG_FINAL_NEXTHOP)
&& next == 0)
state->misc_flags |= SMTP_MISC_FLAG_FINAL_SERVER;
#ifdef USE_TLS
if (retry_plain) {
if (session->tls_level >= TLS_LEV_ENCRYPT)
msg_panic("Plain-text retry wrong for mandatory TLS");
session->tls_level = TLS_LEV_NONE;
retry_plain = 0;
}
session->tls_nexthop = domain;
#endif
if ((session->features & SMTP_FEATURE_FROM_CACHE) == 0
&& smtp_helo(state) != 0) {
#ifdef USE_TLS
if ((retry_plain = session->tls_retry_plain) != 0) {
--addr_count;
next = addr;
}
#endif
if (!THIS_SESSION_IS_DEAD
&& vstream_ferror(session->stream) == 0
&& vstream_feof(session->stream) == 0)
smtp_quit(state);
} else {
if (++sess_count == var_smtp_mxsess_limit)
next = 0;
if ((state->misc_flags & SMTP_MISC_FLAG_FINAL_NEXTHOP)
&& next == 0)
state->misc_flags |= SMTP_MISC_FLAG_FINAL_SERVER;
smtp_xfer(state);
}
smtp_cleanup_session(state);
} else {
msg_info("%s", STR(why->reason));
}
}
dns_rr_free(addr_list);
myfree(dest_buf);
if (state->misc_flags & SMTP_MISC_FLAG_FINAL_NEXTHOP)
break;
}
if (SMTP_RCPT_LEFT(state) > 0) {
if (!SMTP_HAS_DSN(why)) {
dsb_simple(why, "4.3.0",
"server unavailable or unable to receive mail");
}
else if (!SMTP_HAS_SOFT_DSN(why)
&& (state->misc_flags & SMTP_MISC_FLAG_USE_LMTP) == 0) {
if (IS_FALLBACK_RELAY(cpp, sites, non_fallback_sites)) {
msg_warn("%s configuration problem", VAR_SMTP_FALLBACK);
vstring_strcpy(why->status, "4.3.5");
}
else if (strcmp(sites->argv[0], var_relayhost) == 0) {
msg_warn("%s configuration problem", VAR_RELAYHOST);
vstring_strcpy(why->status, "4.3.5");
}
else if (SMTP_HAS_LOOP_DSN(why) && *var_bestmx_transp) {
dsb_reset(why);
state->status = deliver_pass_all(MAIL_CLASS_PRIVATE,
var_bestmx_transp,
request);
SMTP_RCPT_LEFT(state) = 0;
}
}
}
if (HAVE_NEXTHOP_STATE(state))
FREE_NEXTHOP_STATE(state);
argv_free(sites);
}
int smtp_connect(SMTP_STATE *state)
{
DELIVER_REQUEST *request = state->request;
char *destination = request->nexthop;
#define DEF_LMTP_SERVICE var_lmtp_tcp_port
#define DEF_SMTP_SERVICE "smtp"
if (state->misc_flags & SMTP_MISC_FLAG_USE_LMTP) {
if (strncmp(destination, "unix:", 5) == 0) {
smtp_connect_local(state, destination + 5);
} else {
if (strncmp(destination, "inet:", 5) == 0)
destination += 5;
smtp_connect_inet(state, destination, DEF_LMTP_SERVICE);
}
}
else {
smtp_connect_inet(state, destination, DEF_SMTP_SERVICE);
}
if (SMTP_RCPT_LEFT(state) > 0) {
state->misc_flags |= SMTP_MISC_FLAG_FINAL_SERVER;
smtp_sess_fail(state);
smtp_rcpt_cleanup(state);
if (SMTP_RCPT_LEFT(state) > 0)
msg_panic("smtp_connect: left-over recipients");
}
return (state->status);
}