#include <sys_defs.h>
#include <sys/socket.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>
#ifdef STRCASECMP_IN_STRINGS_H
#include <strings.h>
#endif
#ifndef INADDR_NONE
#define INADDR_NONE 0xffffffff
#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 <mail_params.h>
#include <own_inet_addr.h>
#include <debug_peer.h>
#include <deliver_pass.h>
#include <mail_error.h>
#include <pfixtls.h>
#include <dns.h>
#include "smtp.h"
#include "smtp_addr.h"
static SMTP_SESSION *smtp_connect_addr(char *dest, DNS_RR *addr, unsigned port,
VSTRING *why)
{
char *myname = "smtp_connect_addr";
struct sockaddr_in sin;
int sock;
INET_ADDR_LIST *addr_list;
int conn_stat;
int saved_errno;
VSTREAM *stream;
int ch;
unsigned long inaddr;
smtp_errno = SMTP_ERR_NONE;
if (addr->data_len > sizeof(sin.sin_addr)) {
msg_warn("%s: skip address with length %d", myname, addr->data_len);
smtp_errno = SMTP_ERR_RETRY;
return (0);
}
memset((char *) &sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
if ((sock = socket(sin.sin_family, SOCK_STREAM, 0)) < 0)
msg_fatal("%s: socket: %m", myname);
if (*var_smtp_bind_addr) {
sin.sin_addr.s_addr = inet_addr(var_smtp_bind_addr);
if (sin.sin_addr.s_addr == INADDR_NONE)
msg_fatal("%s: bad %s parameter: %s",
myname, VAR_SMTP_BIND_ADDR, var_smtp_bind_addr);
if (bind(sock, (struct sockaddr *) & sin, sizeof(sin)) < 0)
msg_warn("%s: bind %s: %m", myname, inet_ntoa(sin.sin_addr));
if (msg_verbose)
msg_info("%s: bind %s", myname, inet_ntoa(sin.sin_addr));
}
else if ((addr_list = own_inet_addr_list())->used == 1) {
memcpy((char *) &sin.sin_addr, addr_list->addrs, sizeof(sin.sin_addr));
inaddr = ntohl(sin.sin_addr.s_addr);
if (!IN_CLASSA(inaddr)
|| !(((inaddr & IN_CLASSA_NET) >> IN_CLASSA_NSHIFT) == IN_LOOPBACKNET)) {
if (bind(sock, (struct sockaddr *) & sin, sizeof(sin)) < 0)
msg_warn("%s: bind %s: %m", myname, inet_ntoa(sin.sin_addr));
if (msg_verbose)
msg_info("%s: bind %s", myname, inet_ntoa(sin.sin_addr));
}
}
sin.sin_port = port;
memcpy((char *) &sin.sin_addr, addr->data, sizeof(sin.sin_addr));
if (msg_verbose)
msg_info("%s: trying: %s[%s] port %d...",
myname, addr->name, inet_ntoa(sin.sin_addr), ntohs(port));
if (var_smtp_conn_tmout > 0) {
non_blocking(sock, NON_BLOCKING);
conn_stat = timed_connect(sock, (struct sockaddr *) & sin,
sizeof(sin), var_smtp_conn_tmout);
saved_errno = errno;
non_blocking(sock, BLOCKING);
errno = saved_errno;
} else {
conn_stat = sane_connect(sock, (struct sockaddr *) & sin, sizeof(sin));
}
if (conn_stat < 0) {
vstring_sprintf(why, "connect to %s[%s]: %m",
addr->name, inet_ntoa(sin.sin_addr));
smtp_errno = SMTP_ERR_RETRY;
close(sock);
return (0);
}
if (read_wait(sock, var_smtp_helo_tmout) < 0) {
vstring_sprintf(why, "connect to %s[%s]: read timeout",
addr->name, inet_ntoa(sin.sin_addr));
smtp_errno = SMTP_ERR_RETRY;
close(sock);
return (0);
}
stream = vstream_fdopen(sock, O_RDWR);
if ((ch = VSTREAM_GETC(stream)) == VSTREAM_EOF) {
vstring_sprintf(why, "connect to %s[%s]: server dropped connection without sending the initial SMTP greeting",
addr->name, inet_ntoa(sin.sin_addr));
smtp_errno = SMTP_ERR_RETRY;
vstream_fclose(stream);
return (0);
}
vstream_ungetc(stream, ch);
return (smtp_session_alloc(dest, stream, addr->name, inet_ntoa(sin.sin_addr)));
}
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, &service, def_service)) != 0)
msg_fatal("%s in SMTP server description: %s", err, destination);
if (alldig(service) && (port = atoi(service)) != 0) {
*portp = htons(port);
} else {
if ((sp = getservbyname(service, protocol)) == 0)
msg_fatal("unknown service: %s/%s", service, protocol);
*portp = sp->s_port;
}
return (buf);
}
int smtp_connect(SMTP_STATE *state)
{
DELIVER_REQUEST *request = state->request;
VSTRING *why = vstring_alloc(10);
char *dest_buf;
char *host;
unsigned port;
char *def_service = "smtp";
ARGV *sites;
char *dest;
char **cpp;
DNS_RR *addr_list;
DNS_RR *addr;
DNS_RR *next;
int addr_count;
int sess_count;
int misc_flags = SMTP_MISC_FLAG_DEFAULT;
sites = argv_alloc(1);
argv_add(sites, request->nexthop, (char *) 0);
if (sites->argc == 0)
msg_panic("null destination: \"%s\"", request->nexthop);
argv_split_append(sites, var_fallback_relay, ", \t\r\n");
for (cpp = sites->argv; SMTP_RCPT_LEFT(state) > 0 && (dest = *cpp) != 0; cpp++) {
state->final_server = (cpp[1] == 0);
dest_buf = smtp_parse_destination(dest, def_service, &host, &port);
if (msg_verbose)
msg_info("connecting to %s port %d", host, ntohs(port));
if (ntohs(port) != 25)
misc_flags &= ~SMTP_MISC_FLAG_LOOP_DETECT;
else
misc_flags |= SMTP_MISC_FLAG_LOOP_DETECT;
if (var_disable_dns || *dest == '[') {
addr_list = smtp_host_addr(host, misc_flags, why);
} else {
addr_list = smtp_domain_addr(host, misc_flags, why);
}
myfree(dest_buf);
if (addr_list == 0 && smtp_errno == SMTP_ERR_LOOP)
break;
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->session = smtp_connect_addr(host, addr, port, why)) != 0) {
state->features = 0;
if (++sess_count == var_smtp_mxsess_limit)
next = 0;
state->final_server = (cpp[1] == 0 && next == 0);
state->session->best = (addr->pref == addr_list->pref);
debug_peer_check(state->session->host, state->session->addr);
if (smtp_helo(state, misc_flags) == 0)
smtp_xfer(state);
if (state->history != 0) {
if (state->error_mask & name_mask(VAR_NOTIFY_CLASSES,
mail_error_masks, var_notify_classes))
smtp_chat_notify(state);
smtp_chat_reset(state);
}
state->error_mask = 0;
state->size_limit = 0;
smtp_session_free(state->session);
state->session = 0;
#ifdef USE_SASL_AUTH
smtp_sasl_cleanup(state);
#endif
debug_peer_restore();
smtp_rcpt_cleanup(state);
} else {
msg_info("%s (port %d)", vstring_str(why), ntohs(port));
}
}
dns_rr_free(addr_list);
}
if (SMTP_RCPT_LEFT(state) > 0) {
switch (smtp_errno) {
default:
msg_panic("smtp_connect: bad error indication %d", smtp_errno);
case SMTP_ERR_LOOP:
case SMTP_ERR_FAIL:
if (sites->argc > 1 && cpp > sites->argv) {
msg_warn("%s configuration problem", VAR_FALLBACK_RELAY);
smtp_errno = SMTP_ERR_RETRY;
}
else if (strcmp(sites->argv[0], var_relayhost) == 0) {
msg_warn("%s configuration problem", VAR_RELAYHOST);
smtp_errno = SMTP_ERR_RETRY;
}
else if (smtp_errno == SMTP_ERR_LOOP && *var_bestmx_transp) {
state->status = deliver_pass_all(MAIL_CLASS_PRIVATE,
var_bestmx_transp,
request);
SMTP_RCPT_LEFT(state) = 0;
break;
}
case SMTP_ERR_RETRY:
state->final_server = 1;
smtp_site_fail(state, smtp_errno == SMTP_ERR_RETRY ? 450 : 550,
"%s", vstring_str(why));
smtp_rcpt_cleanup(state);
if (SMTP_RCPT_LEFT(state) > 0)
msg_panic("smtp_connect: left-over recipients");
}
}
argv_free(sites);
vstring_free(why);
return (state->status);
}