#include <sys_defs.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#ifdef STRCASECMP_IN_STRINGS_H
#include <strings.h>
#endif
#include <msg.h>
#include <msg_vstream.h>
#include <vstring.h>
#include <vstream.h>
#include <vstring_vstream.h>
#include <mymalloc.h>
#include <stringops.h>
#include <argv.h>
#include <name_mask.h>
#include <name_code.h>
#include <chroot_uid.h>
#include <host_port.h>
#include <inet_proto.h>
#include <iostuff.h>
#include <timed_connect.h>
#include <sane_connect.h>
#include <myaddrinfo.h>
#include <sock_addr.h>
#include <midna_domain.h>
#include <clean_env.h>
#define STR(x) vstring_str(x)
#include <mail_params.h>
#include <mail_conf.h>
#include <smtp_stream.h>
#include <dsn_buf.h>
#include <mail_parm_split.h>
#include <dns.h>
#include <mail_server.h>
#define TLS_INTERNAL
#include <tls.h>
#ifdef USE_TLS
#include <openssl/engine.h>
#endif
#include "tlsmgrmem.h"
static int conn_tmout = 30;
static int smtp_tmout = 30;
#define HOST_FLAG_DNS (1<<0)
#define HOST_FLAG_NATIVE (1<<1)
#define MISC_FLAG_PREF_IPV6 (1<<0)
#define MISC_FLAG_PREF_IPV4 (1<<1)
static const NAME_MASK lookup_masks[] = {
"dns", HOST_FLAG_DNS,
"native", HOST_FLAG_NATIVE,
0,
};
static const NAME_CODE addr_pref_map[] = {
INET_PROTO_NAME_IPV6, MISC_FLAG_PREF_IPV6,
INET_PROTO_NAME_IPV4, MISC_FLAG_PREF_IPV4,
INET_PROTO_NAME_ANY, 0,
0, -1,
};
typedef struct OPTIONS {
char *logopts;
char *level;
ARGV *tas;
char *host_lookup;
char *addr_pref;
} OPTIONS;
typedef struct STATE {
int smtp;
int host_lookup;
int addr_pref;
int log_mask;
int reconnect;
int max_reconnect;
int force_tlsa;
unsigned port;
char *dest;
char *addrport;
char *namaddrport;
char *nexthop;
char *hostname;
DNS_RR *addr;
DNS_RR *mx;
int pass;
int nochat;
char *helo;
DSN_BUF *why;
VSTRING *buffer;
VSTREAM *stream;
int level;
int wrapper_mode;
#ifdef USE_TLS
char *mdalg;
char *CAfile;
char *CApath;
char *certfile;
char *keyfile;
ARGV *match;
int print_trust;
BIO *tls_bio;
TLS_APPL_STATE *tls_ctx;
TLS_SESS_STATE *tls_context;
TLS_DANE *dane;
TLS_DANE *ddane;
char *grade;
char *protocols;
int mxinsec_level;
#endif
OPTIONS options;
} STATE;
static DNS_RR *host_addr(STATE *, const char *);
#define HNAME(addr) (addr->qname)
typedef struct {
int code;
char *str;
VSTRING *buf;
} RESPONSE;
static void PRINTFLIKE(3, 4) command(STATE *state, int verbose, char *fmt,...)
{
VSTREAM *stream = state->stream;
VSTRING *buf;
va_list ap;
char *line;
buf = vstring_alloc(100);
va_start(ap, fmt);
vstring_vsprintf(buf, fmt, ap);
va_end(ap);
line = vstring_str(buf);
while (line && *line) {
char *nextline = strchr(line, '\n');
if (nextline)
*nextline++ = '\0';
if (verbose && !state->nochat)
msg_info("> %s", line);
smtp_printf(stream, "%s", line);
line = nextline;
}
vstring_free(buf);
}
static RESPONSE *response(STATE *state, int verbose)
{
VSTREAM *stream = state->stream;
VSTRING *buf = state->buffer;
static RESPONSE rdata;
int more;
char *cp;
if (rdata.buf == 0) {
rdata.buf = vstring_alloc(100);
vstring_ctl(rdata.buf, CA_VSTRING_CTL_MAXLEN(var_line_limit), 0);
}
#define BUF ((char *) vstring_str(buf))
VSTRING_RESET(rdata.buf);
for (;;) {
smtp_get(buf, stream, var_line_limit, SMTP_GET_FLAG_SKIP);
for (cp = BUF; *cp != 0; cp++)
if (!ISPRINT(*cp) && !ISSPACE(*cp))
*cp = '?';
cp = BUF;
if (verbose && !state->nochat)
msg_info("< %s", cp);
while (ISDIGIT(*cp))
cp++;
rdata.code = (cp - BUF == 3 ? atoi(BUF) : 0);
if ((more = (*cp == '-')) != 0)
cp++;
while (ISSPACE(*cp))
cp++;
vstring_strcat(rdata.buf, cp);
if (more == 0)
break;
VSTRING_ADDCH(rdata.buf, '\n');
}
VSTRING_TERMINATE(rdata.buf);
rdata.str = vstring_str(rdata.buf);
return (&rdata);
}
static char *exception_text(int except)
{
switch (except) {
case SMTP_ERR_EOF:
return ("lost connection");
case SMTP_ERR_TIME:
return ("timeout");
default:
msg_panic("exception_text: unknown exception %d", except);
}
}
static int greeting(STATE *state)
{
VSTREAM *stream = state->stream;
int except;
RESPONSE *resp;
smtp_stream_setup(stream, conn_tmout, 1);
if ((except = vstream_setjmp(stream)) != 0) {
msg_info("%s while reading server greeting", exception_text(except));
return (1);
}
if (((resp = response(state, 1))->code / 100) != 2) {
msg_info("SMTP service not available: %d %s", resp->code, resp->str);
return (1);
}
return (0);
}
static RESPONSE *ehlo(STATE *state)
{
int except;
int verbose;
volatile char *ehlo = state->smtp ? "EHLO" : "LHLO";
VSTREAM *stream = state->stream;
RESPONSE *resp;
#ifdef USE_TLS
verbose = (state->pass == 1 && state->nochat == 0);
#else
verbose = 1;
#endif
if ((except = vstream_setjmp(stream)) != 0) {
msg_info("%s while sending %s", exception_text(except), ehlo);
return (0);
}
command(state, verbose, "%s %s", ehlo, var_myhostname);
resp = response(state, verbose);
if (resp->code / 100 != 2) {
msg_info("%s rejected: %d %s", ehlo, resp->code, resp->str);
return (0);
}
return resp;
}
#ifdef USE_TLS
static void print_stack(STATE *state, x509_stack_t *sk, int trustout)
{
int i;
for (i = 0; i < sk_X509_num(sk); i++) {
X509 *cert = sk_X509_value(sk, i);
char buf[CCERT_BUFSIZ];
X509_NAME *xn;
char *digest;
if ((xn = X509_get_subject_name(cert)) != 0) {
X509_NAME_oneline(xn, buf, sizeof buf);
BIO_printf(state->tls_bio, "%2d subject: %s\n", i, buf);
}
if ((xn = X509_get_issuer_name(cert)) != 0) {
X509_NAME_oneline(xn, buf, sizeof buf);
BIO_printf(state->tls_bio, " issuer: %s\n", buf);
}
digest = tls_cert_fprint(cert, state->mdalg);
BIO_printf(state->tls_bio, " cert digest=%s\n", digest);
myfree(digest);
digest = tls_pkey_fprint(cert, state->mdalg);
BIO_printf(state->tls_bio, " pkey digest=%s\n", digest);
myfree(digest);
if (trustout)
PEM_write_bio_X509_AUX(state->tls_bio, cert);
else
PEM_write_bio_X509(state->tls_bio, cert);
}
}
static void print_trust_info(STATE *state)
{
x509_stack_t *sk = SSL_get_peer_cert_chain(state->tls_context->con);
if (sk != 0) {
BIO_printf(state->tls_bio, "\n---\nCertificate chain\n");
print_stack(state, sk, 0);
}
#ifdef dane_verify_debug
if ((sk = state->tls_context->untrusted) != 0) {
BIO_printf(state->tls_bio, "\n---\nUntrusted chain\n");
print_stack(state, sk, 0);
}
if ((sk = state->tls_context->trusted) != 0) {
BIO_printf(state->tls_bio, "\n---\nTrusted chain\n");
print_stack(state, sk, 1);
}
#endif
}
static int starttls(STATE *state)
{
VSTRING *cipher_exclusions;
int except;
RESPONSE *resp;
VSTREAM *stream = state->stream;
TLS_CLIENT_START_PROPS tls_props;
if (state->wrapper_mode == 0) {
smtp_stream_setup(stream, smtp_tmout, 1);
if ((except = vstream_setjmp(stream)) != 0) {
msg_fatal("%s while sending STARTTLS", exception_text(except));
return (1);
}
command(state, state->pass == 1, "STARTTLS");
resp = response(state, state->pass == 1);
if (resp->code / 100 != 2) {
msg_info("STARTTLS rejected: %d %s", resp->code, resp->str);
return (1);
}
vstream_fpurge(stream, VSTREAM_PURGE_READ);
}
#define ADD_EXCLUDE(vstr, str) \
do { \
if (*(str)) \
vstring_sprintf_append((vstr), "%s%s", \
VSTRING_LEN(vstr) ? " " : "", (str)); \
} while (0)
cipher_exclusions = vstring_alloc(10);
ADD_EXCLUDE(cipher_exclusions, DEF_SMTP_TLS_EXCL_CIPH);
if (TLS_REQUIRED(state->level))
ADD_EXCLUDE(cipher_exclusions, DEF_SMTP_TLS_MAND_EXCL);
if (TLS_MUST_MATCH(state->level))
ADD_EXCLUDE(cipher_exclusions, "aNULL");
else
ADD_EXCLUDE(cipher_exclusions, "eNULL");
state->tls_context =
TLS_CLIENT_START(&tls_props,
ctx = state->tls_ctx,
stream = stream,
timeout = smtp_tmout,
tls_level = state->level,
nexthop = state->nexthop,
host = state->hostname,
namaddr = state->namaddrport,
serverid = state->addrport,
helo = state->helo ? state->helo : "",
protocols = state->protocols,
cipher_grade = state->grade,
cipher_exclusions
= vstring_str(cipher_exclusions),
matchargv = state->match,
mdalg = state->mdalg,
dane = state->ddane ? state->ddane : state->dane);
vstring_free(cipher_exclusions);
if (state->helo) {
myfree(state->helo);
state->helo = 0;
}
if (state->tls_context == 0) {
(void) vstream_fpurge(stream, VSTREAM_PURGE_BOTH);
(void) vstream_fclose(stream);
state->stream = 0;
return (1);
}
if (state->wrapper_mode && greeting(state) != 0)
return (1);
if (state->pass == 1) {
ehlo(state);
if (!TLS_CERT_IS_PRESENT(state->tls_context))
msg_info("Server is anonymous");
else if (state->print_trust)
print_trust_info(state);
state->log_mask &= ~(TLS_LOG_CERTMATCH | TLS_LOG_PEERCERT |
TLS_LOG_VERBOSE | TLS_LOG_UNTRUSTED);
state->log_mask |= TLS_LOG_CACHE | TLS_LOG_SUMMARY;
tls_update_app_logmask(state->tls_ctx, state->log_mask);
}
return (0);
}
#endif
static int doproto(STATE *state)
{
VSTREAM *stream = state->stream;
RESPONSE *resp;
int except;
int n;
char *lines;
char *words = 0;
char *word;
if (!state->wrapper_mode) {
if (greeting(state) != 0)
return (1);
if ((resp = ehlo(state)) == 0)
return (1);
lines = resp->str;
for (n = 0; (words = mystrtok(&lines, "\n")) != 0; ++n) {
if ((word = mystrtok(&words, " \t=")) != 0) {
if (n == 0)
state->helo = mystrdup(word);
if (strcasecmp(word, "STARTTLS") == 0)
break;
}
}
}
#ifdef USE_TLS
if ((state->wrapper_mode || words) && state->tls_ctx)
if (starttls(state))
return (1);
#endif
smtp_stream_setup(stream, smtp_tmout, 1);
if ((except = vstream_setjmp(stream)) != 0) {
msg_warn("%s while sending QUIT command", exception_text(except));
return (0);
}
command(state, 1, "QUIT");
(void) response(state, 1);
return (0);
}
static VSTREAM *connect_sock(int sock, struct sockaddr *sa, int salen,
const char *name, const char *addr, STATE *state)
{
DSN_BUF *why = state->why;
int conn_stat;
int saved_errno;
VSTREAM *stream;
if (conn_tmout > 0) {
non_blocking(sock, NON_BLOCKING);
conn_stat = timed_connect(sock, sa, salen, 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 (state->port)
dsb_simple(why, "4.4.1", "connect to %s[%s]:%d: %m",
name, addr, ntohs(state->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);
state->namaddrport =
vstring_export(state->port == 0 ?
vstring_sprintf(vstring_alloc(10), "%s[%s]", name, addr) :
vstring_sprintf(vstring_alloc(10), "%s[%s]:%u",
name, addr, ntohs(state->port)));
state->addrport =
vstring_export(state->port == 0 ?
vstring_sprintf(vstring_alloc(10), "%s", addr) :
vstring_sprintf(vstring_alloc(10), "[%s]:%u",
addr, ntohs(state->port)));
if (sa->sa_family == AF_INET
#ifdef AF_INET6
|| sa->sa_family == AF_INET6
#endif
)
vstream_tweak_tcp(stream);
return (stream);
}
static VSTREAM *connect_unix(STATE *state, const char *path)
{
static const char *myname = "connect_unix";
DSN_BUF *why = state->why;
struct sockaddr_un sock_un;
int len = strlen(path);
int sock;
if (!state->nexthop)
state->nexthop = mystrdup(var_myhostname);
state->hostname = mystrdup(var_myhostname);
dsb_reset(why);
if (len >= (int) sizeof(sock_un.sun_path)) {
dsb_simple(why, "4.3.5", "unix-domain name too long: %s", path);
return (0);
}
memset((void *) &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, path, 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, path);
return (connect_sock(sock, (struct sockaddr *) &sock_un, sizeof(sock_un),
var_myhostname, path, state));
}
static VSTREAM *connect_addr(STATE *state, DNS_RR *addr)
{
static const char *myname = "connect_addr";
DSN_BUF *why = state->why;
struct sockaddr_storage ss;
struct sockaddr *sa = (struct sockaddr *) &ss;
SOCKADDR_SIZE salen = sizeof(ss);
MAI_HOSTADDR_STR hostaddr;
int sock;
dsb_reset(why);
if (dns_rr_to_sa(addr, state->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);
SOCKADDR_TO_HOSTADDR(sa, salen, &hostaddr, (MAI_SERVPORT_STR *) 0, 0);
if (msg_verbose)
msg_info("%s: trying: %s[%s] port %d...",
myname, HNAME(addr), hostaddr.buf, ntohs(state->port));
return (connect_sock(sock, sa, salen, HNAME(addr), hostaddr.buf, state));
}
#define HAS_DSN(why) (STR((why)->status)[0] != 0)
#define HAS_SOFT_DSN(why) (STR((why)->status)[0] == '4')
#define HAS_HARD_DSN(why) (STR((why)->status)[0] == '5')
#define HAS_LOOP_DSN(why) \
(HAS_DSN(why) && strcmp(STR((why)->status) + 1, ".4.6") == 0)
#define SET_SOFT_DSN(why) (STR((why)->status)[0] = '4')
#define SET_HARD_DSN(why) (STR((why)->status)[0] = '5')
static DNS_RR *addr_one(STATE *state, DNS_RR *addr_list, const char *host,
int res_opt, unsigned pref)
{
static const char *myname = "addr_one";
DSN_BUF *why = state->why;
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 (state->host_lookup & HOST_FLAG_DNS) {
switch (dns_lookup_v(host, res_opt, &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, HAS_SOFT_DSN(why) ? "4.4.3" : "5.4.3");
return (addr_list);
case DNS_INVAL:
dsb_status(why, HAS_SOFT_DSN(why) ? "4.4.4" : "5.4.4");
return (addr_list);
case DNS_NOTFOUND:
dsb_status(why, 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 (state->host_lookup & HOST_FLAG_NATIVE) {
if ((aierr = hostname_to_sockaddr(host, (char *) 0, 0, &res0)) != 0) {
dsb_simple(why, (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, HAS_SOFT_DSN(why) ? "4.4.4" : "5.4.4",
"%s: host not found", host);
}
return (addr_list);
}
}
return (addr_list);
}
static DNS_RR *mx_addr_list(STATE *state, DNS_RR *mx_names)
{
static const char *myname = "mx_addr_list";
DNS_RR *addr_list = 0;
DNS_RR *rr;
int res_opt = 0;
if (mx_names->dnssec_valid)
res_opt = RES_USE_DNSSEC;
#ifdef USE_TLS
else if (state->mxinsec_level > TLS_LEV_MAY)
res_opt = RES_USE_DNSSEC;
#endif
for (rr = mx_names; rr; rr = rr->next) {
if (rr->type != T_MX)
msg_panic("%s: bad resource type: %d", myname, rr->type);
addr_list = addr_one(state, addr_list, (char *) rr->data, res_opt,
rr->pref);
}
return (addr_list);
}
static DNS_RR *domain_addr(STATE *state, char *domain)
{
DNS_RR *mx_names;
DNS_RR *addr_list = 0;
int r = 0;
const char *aname;
dsb_reset(state->why);
#if (RES_USE_DNSSEC != 0) && (RES_USE_EDNS0 != 0)
r |= RES_USE_DNSSEC;
#endif
#ifndef NO_EAI
if (!allascii(domain) && (aname = midna_domain_to_ascii(domain)) != 0) {
msg_info("%s asciified to %s", domain, aname);
} else
#endif
aname = domain;
switch (dns_lookup(aname, T_MX, r, &mx_names, (VSTRING *) 0,
state->why->reason)) {
default:
dsb_status(state->why, "4.4.3");
break;
case DNS_INVAL:
dsb_status(state->why, "5.4.4");
break;
case DNS_NULLMX:
dsb_status(state->why, "5.1.0");
break;
case DNS_FAIL:
dsb_status(state->why, "5.4.3");
break;
case DNS_OK:
mx_names = dns_rr_sort(mx_names, dns_rr_compare_pref_any);
addr_list = mx_addr_list(state, mx_names);
state->mx = dns_rr_copy(mx_names);
dns_rr_free(mx_names);
if (addr_list == 0) {
msg_warn("no MX host for %s has a valid address record", domain);
break;
}
#define COMPARE_ADDR(flags) \
((flags & MISC_FLAG_PREF_IPV6) ? dns_rr_compare_pref_ipv6 : \
(flags & MISC_FLAG_PREF_IPV4) ? dns_rr_compare_pref_ipv4 : \
dns_rr_compare_pref_any)
if (addr_list && addr_list->next) {
addr_list = dns_rr_shuffle(addr_list);
addr_list = dns_rr_sort(addr_list, COMPARE_ADDR(state->addr_pref));
}
break;
case DNS_NOTFOUND:
addr_list = host_addr(state, domain);
break;
}
return (addr_list);
}
static DNS_RR *host_addr(STATE *state, const char *host)
{
DSN_BUF *why = state->why;
DNS_RR *addr_list;
int res_opt = 0;
const char *ahost;
dsb_reset(why);
#if (RES_USE_DNSSEC != 0) && (RES_USE_EDNS0 != 0)
res_opt |= RES_USE_DNSSEC;
#endif
#ifndef NO_EAI
if (!allascii(host) && (ahost = midna_domain_to_ascii(host)) != 0) {
msg_info("%s asciified to %s", host, ahost);
} else
#endif
ahost = host;
#define PREF0 0
addr_list = addr_one(state, (DNS_RR *) 0, ahost, res_opt, PREF0);
if (addr_list && addr_list->next) {
addr_list = dns_rr_shuffle(addr_list);
if (inet_proto_info()->ai_family_list[1] != 0)
addr_list = dns_rr_sort(addr_list, COMPARE_ADDR(state->addr_pref));
}
return (addr_list);
}
static int dane_host_level(STATE *state, DNS_RR *addr)
{
int level = state->level;
#ifdef USE_TLS
if (TLS_DANE_BASED(level)) {
if (state->mx == 0 || state->mx->dnssec_valid ||
state->mxinsec_level > TLS_LEV_MAY) {
if (state->log_mask & (TLS_LOG_CERTMATCH | TLS_LOG_VERBOSE))
tls_dane_verbose(1);
else
tls_dane_verbose(0);
if (state->ddane)
tls_dane_free(state->ddane);
state->ddane = tls_dane_resolve(state->port, "tcp", addr,
state->force_tlsa);
if (!state->ddane) {
dsb_simple(state->why, "4.7.5",
"TLSA lookup error for %s:%u",
HNAME(addr), ntohs(state->port));
level = TLS_LEV_INVALID;
} else if (tls_dane_notfound(state->ddane)
|| tls_dane_unusable(state->ddane)) {
if (msg_verbose || level == TLS_LEV_DANE_ONLY)
msg_info("no %sTLSA records found, "
"resorting to \"secure\"",
tls_dane_unusable(state->ddane) ?
"usable " : "");
level = TLS_LEV_SECURE;
} else if (!TLS_DANE_HASTA(state->ddane)
&& !TLS_DANE_HASEE(state->ddane)) {
msg_panic("DANE activated with no TLSA records to match");
} else if (state->mx && !state->mx->dnssec_valid &&
state->mxinsec_level == TLS_LEV_ENCRYPT) {
msg_info("TLSA RRs found, MX RRset insecure: just encrypt");
tls_dane_free(state->ddane);
state->ddane = 0;
level = TLS_LEV_ENCRYPT;
} else {
if (state->match)
argv_free(state->match);
argv_add(state->match = argv_alloc(2),
state->ddane->base_domain, ARGV_END);
if (state->mx) {
if (!state->mx->dnssec_valid) {
msg_info("MX RRset insecure: log verified as trusted");
level = TLS_LEV_HALF_DANE;
}
if (strcmp(state->mx->qname, state->mx->rname) == 0)
argv_add(state->match, state->mx->qname, ARGV_END);
else
argv_add(state->match, state->mx->rname,
state->mx->qname, ARGV_END);
}
}
} else if (state->mx && !state->mx->dnssec_valid &&
state->mxinsec_level == TLS_LEV_MAY) {
msg_info("MX RRset is insecure: try to encrypt");
level = TLS_LEV_MAY;
} else {
level = TLS_LEV_SECURE;
}
}
#endif
return (level);
}
static char *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("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)
*portp = sp->s_port;
else if (strcmp(service, "smtp") == 0)
*portp = htons(25);
else
msg_fatal("unknown service: %s/%s", service, protocol);
}
return (buf);
}
static void connect_remote(STATE *state, char *dest)
{
DNS_RR *addr;
char *buf;
char *domain;
if (state->addr == 0) {
buf = parse_destination(dest, state->smtp ? "smtp" : "24",
&domain, &state->port);
if (!state->nexthop)
state->nexthop = mystrdup(domain);
if (state->smtp == 0 || *dest == '[')
state->addr = host_addr(state, domain);
else
state->addr = domain_addr(state, domain);
myfree(buf);
if (state->addr == 0) {
msg_info("Destination address lookup failed: %s",
vstring_str(state->why->reason));
return;
}
}
for (addr = state->addr; addr; addr = addr->next) {
int level = dane_host_level(state, addr);
if (level == TLS_LEV_INVALID
|| (state->stream = connect_addr(state, addr)) == 0) {
msg_info("Failed to establish session to %s via %s: %s",
dest, HNAME(addr), vstring_str(state->why->reason));
continue;
}
state->level = level;
state->hostname = mystrdup(HNAME(addr));
addr = dns_rr_copy(addr);
dns_rr_free(state->addr);
state->addr = addr;
break;
}
}
static int connect_dest(STATE *state)
{
char *dest = state->dest;
if (state->smtp == 0) {
if (strncmp(dest, "unix:", 5) == 0) {
connect_unix(state, dest + 5);
if (!state->stream)
msg_info("Failed to establish session to %s: %s",
dest, vstring_str(state->why->reason));
return (1);
}
if (strncmp(dest, "inet:", 5) == 0)
dest += 5;
}
connect_remote(state, dest);
return (state->stream == 0);
}
static void disconnect_dest(STATE *state)
{
#ifdef USE_TLS
if (state->tls_context)
tls_client_stop(state->tls_ctx, state->stream,
smtp_tmout, 0, state->tls_context);
state->tls_context = 0;
if (state->ddane)
tls_dane_free(state->ddane);
state->ddane = 0;
#endif
if (state->stream)
vstream_fclose(state->stream);
state->stream = 0;
if (state->namaddrport)
myfree(state->namaddrport);
state->namaddrport = 0;
if (state->addrport)
myfree(state->addrport);
state->addrport = 0;
if (state->reconnect <= 0) {
if (state->addr)
dns_rr_free(state->addr);
state->addr = 0;
if (state->mx)
dns_rr_free(state->mx);
state->mx = 0;
if (state->nexthop)
myfree(state->nexthop);
state->nexthop = 0;
}
if (state->hostname)
myfree(state->hostname);
state->hostname = 0;
dsb_free(state->why);
vstring_free(state->buffer);
}
static int finger(STATE *state)
{
int err;
state->buffer = vstring_alloc(100);
vstring_ctl(state->buffer, CA_VSTRING_CTL_MAXLEN(var_line_limit), 0);
state->why = dsb_create();
if (!(err = connect_dest(state))) {
if (state->pass == 1 && !state->nochat)
msg_info("Connected to %s", state->namaddrport);
err = doproto(state);
}
disconnect_dest(state);
if (err != 0)
return (1);
#ifdef USE_TLS
if (state->reconnect > 0) {
int cache_enabled;
int cache_count;
int cache_hits;
tlsmgrmem_status(&cache_enabled, &cache_count, &cache_hits);
if (cache_enabled && cache_count == 0) {
msg_info("Server declined session caching. Done reconnecting.");
state->reconnect = 0;
} else if (cache_hits > 0 && (state->log_mask & TLS_LOG_CACHE) != 0) {
msg_info("Found a previously used server. Done reconnecting.");
state->reconnect = 0;
} else if (state->max_reconnect-- <= 0) {
msg_info("Maximum reconnect count reached.");
state->reconnect = 0;
}
}
#endif
return (0);
}
#if defined(USE_TLS) && OPENSSL_VERSION_NUMBER < 0x10100000L
static void ssl_cleanup(void)
{
#if OPENSSL_VERSION_NUMBER >= 0x10000000L
ERR_remove_thread_state(0);
#else
ERR_remove_state(0);
#endif
ENGINE_cleanup();
CONF_modules_unload(1);
ERR_free_strings();
EVP_cleanup();
CRYPTO_cleanup_all_ex_data();
}
#endif
static int run(STATE *state)
{
while (1) {
if (finger(state) != 0)
break;
if (state->reconnect <= 0)
break;
msg_info("Reconnecting after %d seconds", state->reconnect);
++state->pass;
sleep(state->reconnect);
}
return (0);
}
static void cleanup(STATE *state)
{
#ifdef USE_TLS
if (state->tls_ctx != 0)
tls_free_app_context(state->tls_ctx);
if (state->tls_bio)
(void) BIO_free(state->tls_bio);
state->tls_bio = 0;
myfree(state->mdalg);
myfree(state->CApath);
myfree(state->CAfile);
myfree(state->certfile);
myfree(state->keyfile);
if (state->options.level)
myfree(state->options.level);
myfree(state->options.logopts);
if (state->match)
argv_free(state->match);
if (state->options.tas)
argv_free(state->options.tas);
if (state->dane)
tls_dane_free(state->dane);
tls_dane_flush();
tlsmgrmem_flush();
myfree(state->grade);
myfree(state->protocols);
#endif
myfree(state->options.host_lookup);
myfree(state->dest);
mail_conf_flush();
}
static void usage(void)
{
#ifdef USE_TLS
fprintf(stderr, "usage: %s %s \\\n\t%s \\\n\t%s \\\n\t%s"
" destination [match ...]\n", var_procname,
"[-acCfSvw] [-t conn_tmout] [-T cmd_tmout] [-L logopts]",
"[-h host_lookup] [-l level] [-d mdalg] [-g grade] [-p protocols]",
"[-A tafile] [-F CAfile.pem] [-P CApath/] "
"[-k certfile [-K keyfile]] [-m count] [-r delay]",
"[-o name=value]");
#else
fprintf(stderr, "usage: %s [-acStTv] [-h host_lookup] [-o name=value] destination\n",
var_procname);
#endif
exit(1);
}
static void tls_init(STATE *state)
{
#ifdef USE_TLS
TLS_CLIENT_INIT_PROPS props;
if (state->level <= TLS_LEV_NONE)
return;
state->tls_ctx =
TLS_CLIENT_INIT(&props,
log_param = "-L option",
log_level = state->options.logopts,
verifydepth = DEF_SMTP_TLS_SCERT_VD,
cache_type = "memory",
cert_file = state->certfile,
key_file = state->keyfile,
dcert_file = "",
dkey_file = "",
eccert_file = "",
eckey_file = "",
CAfile = state->CAfile,
CApath = state->CApath,
mdalg = state->mdalg);
#endif
}
static void override(const char *nameval)
{
char *param_name;
char *param_value;
char *save = mystrdup(nameval);
if (split_nameval(save, ¶m_name, ¶m_value) != 0)
usage();
mail_conf_update(param_name, param_value);
myfree(save);
}
static void parse_options(STATE *state, int argc, char *argv[])
{
int c;
state->smtp = 1;
state->pass = 1;
state->reconnect = -1;
state->max_reconnect = 5;
state->wrapper_mode = 0;
#ifdef USE_TLS
state->protocols = mystrdup("!SSLv2");
state->grade = mystrdup("medium");
#endif
memset((void *) &state->options, 0, sizeof(state->options));
state->options.host_lookup = mystrdup("dns");
#define OPTS "a:ch:o:St:T:v"
#ifdef USE_TLS
#define TLSOPTS "A:Cd:fF:g:k:K:l:L:m:M:p:P:r:w"
state->mdalg = mystrdup("sha1");
state->CApath = mystrdup("");
state->CAfile = mystrdup("");
state->certfile = mystrdup("");
state->keyfile = mystrdup("");
state->options.tas = argv_alloc(1);
state->options.logopts = 0;
state->level = TLS_LEV_DANE;
state->mxinsec_level = TLS_LEV_DANE;
#else
#define TLSOPTS ""
state->level = TLS_LEV_NONE;
#endif
while ((c = GETOPT(argc, argv, OPTS TLSOPTS)) > 0) {
switch (c) {
default:
usage();
break;
case 'a':
state->options.addr_pref = mystrdup(optarg);
break;
case 'c':
state->nochat = 1;
break;
case 'h':
myfree(state->options.host_lookup);
state->options.host_lookup = mystrdup(optarg);
break;
case 'o':
override(optarg);
break;
case 'S':
state->smtp = 0;
break;
case 't':
conn_tmout = atoi(optarg);
break;
case 'T':
smtp_tmout = atoi(optarg);
break;
case 'v':
msg_verbose++;
break;
#ifdef USE_TLS
case 'A':
argv_add(state->options.tas, optarg, ARGV_END);
break;
case 'C':
state->print_trust = 1;
break;
case 'd':
myfree(state->mdalg);
state->mdalg = mystrdup(optarg);
break;
case 'f':
state->force_tlsa = 1;
break;
case 'F':
myfree(state->CAfile);
state->CAfile = mystrdup(optarg);
break;
case 'g':
myfree(state->grade);
state->grade = mystrdup(optarg);
break;
case 'k':
myfree(state->certfile);
state->certfile = mystrdup(optarg);
if (!*state->keyfile) {
myfree(state->keyfile);
state->keyfile = mystrdup(optarg);
}
break;
case 'K':
myfree(state->keyfile);
state->keyfile = mystrdup(optarg);
if (!*state->certfile) {
myfree(state->certfile);
state->certfile = mystrdup(optarg);
}
break;
case 'l':
if (state->options.level)
myfree(state->options.level);
state->options.level = mystrdup(optarg);
break;
case 'L':
if (state->options.logopts)
myfree(state->options.logopts);
state->options.logopts = mystrdup(optarg);
break;
case 'm':
state->max_reconnect = atoi(optarg);
break;
case 'M':
switch (state->mxinsec_level = tls_level_lookup(optarg)) {
case TLS_LEV_MAY:
case TLS_LEV_ENCRYPT:
case TLS_LEV_DANE:
break;
default:
msg_fatal("bad '-M' option value: %s", optarg);
}
break;
case 'p':
myfree(state->protocols);
state->protocols = mystrdup(optarg);
break;
case 'P':
myfree(state->CApath);
state->CApath = mystrdup(optarg);
break;
case 'r':
state->reconnect = atoi(optarg);
break;
case 'w':
state->wrapper_mode = 1;
break;
#endif
}
}
state->addr_pref =
name_code(addr_pref_map, NAME_CODE_FLAG_NONE, state->options.addr_pref ?
state->options.addr_pref : "any");
if (state->addr_pref < 0)
msg_fatal("bad '-a' option value: %s", state->options.addr_pref);
state->host_lookup =
name_mask("-h option", lookup_masks, state->options.host_lookup ?
state->options.host_lookup : "dns");
#ifdef USE_TLS
if (state->reconnect < 0)
tlsmgrmem_disable();
if (state->options.logopts == 0)
state->options.logopts = mystrdup("routine,certmatch");
state->log_mask = tls_log_mask("-L option", state->options.logopts);
if (state->options.level) {
state->level = tls_level_lookup(state->options.level);
switch (state->level) {
case TLS_LEV_NONE:
if (state->wrapper_mode)
msg_fatal("SSL wrapper mode requires that TLS not be disabled");
return;
case TLS_LEV_INVALID:
msg_fatal("Invalid TLS level \"%s\"", state->options.level);
}
}
tls_init(state);
if (TLS_DANE_BASED(state->level) && !tls_dane_avail()) {
msg_warn("DANE TLS support is not available, resorting to \"secure\"");
state->level = TLS_LEV_SECURE;
}
state->tls_bio = 0;
if (state->print_trust)
state->tls_bio = BIO_new_fp(stdout, BIO_NOCLOSE);
#endif
}
static void parse_match(STATE *state, int argc, char *argv[])
{
#ifdef USE_TLS
switch (state->level) {
case TLS_LEV_SECURE:
state->match = argv_alloc(2);
while (*argv)
argv_split_append(state->match, *argv++, "");
if (state->match->argc == 0)
argv_add(state->match, "nexthop", "dot-nexthop", ARGV_END);
break;
case TLS_LEV_VERIFY:
state->match = argv_alloc(1);
while (*argv)
argv_split_append(state->match, *argv++, "");
if (state->match->argc == 0)
argv_add(state->match, "hostname", ARGV_END);
break;
case TLS_LEV_FPRINT:
state->dane = tls_dane_alloc();
while (*argv)
tls_dane_add_ee_digests((TLS_DANE *) state->dane,
state->mdalg, *argv++, "");
break;
case TLS_LEV_DANE:
case TLS_LEV_DANE_ONLY:
state->match = argv_alloc(2);
argv_add(state->match, "nexthop", "hostname", ARGV_END);
break;
}
#endif
}
static void parse_tas(STATE *state)
{
#ifdef USE_TLS
char **file;
if (!state->options.tas->argc)
return;
switch (state->level) {
default:
return;
case TLS_LEV_SECURE:
case TLS_LEV_VERIFY:
state->dane = tls_dane_alloc();
for (file = state->options.tas->argv; *file; ++file) {
if (!tls_dane_load_trustfile((TLS_DANE *) state->dane, *file))
break;
}
if (*file)
msg_fatal("Failed to load trust anchor file: %s", *file);
break;
}
#endif
}
int main(int argc, char *argv[])
{
static STATE state;
char *loopenv = getenv("VALGRINDLOOP");
int loop = loopenv ? atoi(loopenv) : 1;
ARGV *import_env;
signal(SIGPIPE, SIG_IGN);
var_procname = mystrdup(basename(argv[0]));
set_mail_conf_str(VAR_PROCNAME, var_procname);
msg_vstream_init(var_procname, VSTREAM_OUT);
mail_conf_suck();
parse_options(&state, argc, argv);
mail_params_init();
parse_tas(&state);
import_env = mail_parm_split(VAR_IMPORT_ENVIRON, var_import_environ);
update_env(import_env->argv);
argv_free(import_env);
argc -= optind;
argv += optind;
if (!argc)
usage();
state.dest = mystrdup(argv[0]);
parse_match(&state, --argc, ++argv);
if (!geteuid())
chroot_uid(0, var_mail_owner);
while (loop-- > 0)
run(&state);
cleanup(&state);
#if defined(USE_TLS) && OPENSSL_VERSION_NUMBER < 0x10100000L
ssl_cleanup();
#endif
return (0);
}