#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 0xffffff
#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 <mail_params.h>
#include <own_inet_addr.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;
if (addr->data_len > sizeof(sin.sin_addr)) {
msg_warn("%s: skip address with length %d", myname, addr->data_len);
smtp_errno = SMTP_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 = 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_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_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 greeting",
addr->name, inet_ntoa(sin.sin_addr));
smtp_errno = SMTP_RETRY;
vstream_fclose(stream);
return (0);
}
vstream_ungetc(stream, ch);
if (ch == '4' && var_smtp_skip_4xx_greeting) {
vstring_sprintf(why, "connect to %s[%s]: server refused mail service",
addr->name, inet_ntoa(sin.sin_addr));
smtp_errno = SMTP_RETRY;
vstream_fclose(stream);
return (0);
}
if (ch == '5' && var_smtp_skip_5xx_greeting) {
vstring_sprintf(why, "connect to %s[%s]: server refused mail service",
addr->name, inet_ntoa(sin.sin_addr));
smtp_errno = SMTP_RETRY;
vstream_fclose(stream);
return (0);
}
return (smtp_session_alloc(dest, stream, addr->name, inet_ntoa(sin.sin_addr)));
}
SMTP_SESSION *smtp_connect_host(char *host, unsigned port, VSTRING *why)
{
SMTP_SESSION *session = 0;
DNS_RR *addr_list;
DNS_RR *addr;
addr_list = smtp_host_addr(host, why);
for (addr = addr_list; addr; addr = addr->next) {
if ((session = smtp_connect_addr(host, addr, port, why)) != 0) {
session->best = 1;
break;
}
msg_info("%s (port %d)", vstring_str(why), ntohs(port));
}
dns_rr_free(addr_list);
return (session);
}
SMTP_SESSION *smtp_connect_domain(char *name, unsigned port, VSTRING *why,
int *found_myself)
{
SMTP_SESSION *session = 0;
DNS_RR *addr_list;
DNS_RR *addr;
addr_list = smtp_domain_addr(name, why, found_myself);
for (addr = addr_list; addr; addr = addr->next) {
if ((session = smtp_connect_addr(name, addr, port, why)) != 0) {
session->best = (addr->pref == addr_list->pref);
break;
}
msg_info("%s (port %d)", vstring_str(why), ntohs(port));
}
dns_rr_free(addr_list);
return (session);
}
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 ((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);
}
SMTP_SESSION *smtp_connect(char *destination, VSTRING *why)
{
SMTP_SESSION *session = 0;
char *dest_buf = 0;
char *host;
unsigned port;
char *def_service = "smtp";
ARGV *sites;
char *dest;
char **cpp;
int found_myself = 0;
sites = argv_alloc(1);
argv_add(sites, destination, (char *) 0);
argv_split_append(sites, var_fallback_relay, ", \t\r\n");
for (cpp = sites->argv; (dest = *cpp) != 0; cpp++) {
dest_buf = smtp_parse_destination(dest, def_service, &host, &port);
if (msg_verbose)
msg_info("connecting to %s port %d", host, ntohs(port));
if (var_disable_dns || *dest == '[') {
session = smtp_connect_host(host, port, why);
} else {
session = smtp_connect_domain(host, port, why, &found_myself);
}
myfree(dest_buf);
if (session != 0 || smtp_errno == SMTP_OK || found_myself)
break;
}
if (session == 0 && dest_buf == 0)
msg_panic("null destination: \"%s\"", destination);
if (session == 0 && smtp_errno == SMTP_FAIL) {
if (strcmp(destination, var_relayhost) == 0) {
msg_warn("%s configuration problem: %s",
VAR_RELAYHOST, var_relayhost);
smtp_errno = SMTP_RETRY;
}
if (cpp > sites->argv && sites->argc > 1) {
msg_warn("%s problem: %s",
VAR_FALLBACK_RELAY, var_fallback_relay);
smtp_errno = SMTP_RETRY;
}
}
argv_free(sites);
return (session);
}