#include <sys_defs.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdarg.h>
#include <time.h>
#ifdef STRCASECMP_IN_STRINGS_H
#include <strings.h>
#endif
#include <msg.h>
#include <vstring.h>
#include <vstream.h>
#include <vstring_vstream.h>
#include <stringops.h>
#include <mymalloc.h>
#include <iostuff.h>
#include <split_at.h>
#include <name_code.h>
#include <name_mask.h>
#include <mail_params.h>
#include <smtp_stream.h>
#include <mail_queue.h>
#include <recipient_list.h>
#include <deliver_request.h>
#include <defer.h>
#include <bounce.h>
#include <record.h>
#include <rec_type.h>
#include <off_cvt.h>
#include <mark_corrupt.h>
#include <quote_821_local.h>
#include <quote_822_local.h>
#include <mail_proto.h>
#include <mime_state.h>
#include <ehlo_mask.h>
#include <maps.h>
#include <tok822.h>
#include <mail_addr_map.h>
#include <ext_prop.h>
#include <lex_822.h>
#include <dsn_mask.h>
#include <xtext.h>
#include "smtp.h"
#include "smtp_sasl.h"
#define SMTP_STATE_XFORWARD_NAME_ADDR 0
#define SMTP_STATE_XFORWARD_PROTO_HELO 1
#define SMTP_STATE_MAIL 2
#define SMTP_STATE_RCPT 3
#define SMTP_STATE_DATA 4
#define SMTP_STATE_DOT 5
#define SMTP_STATE_ABORT 6
#define SMTP_STATE_RSET 7
#define SMTP_STATE_QUIT 8
#define SMTP_STATE_LAST 9
int *xfer_timeouts[SMTP_STATE_LAST] = {
&var_smtp_xfwd_tmout,
&var_smtp_xfwd_tmout,
&var_smtp_mail_tmout,
&var_smtp_rcpt_tmout,
&var_smtp_data0_tmout,
&var_smtp_data2_tmout,
&var_smtp_rset_tmout,
&var_smtp_rset_tmout,
&var_smtp_quit_tmout,
};
char *xfer_states[SMTP_STATE_LAST] = {
"sending XFORWARD name/address",
"sending XFORWARD protocol/helo_name",
"sending MAIL FROM",
"sending RCPT TO",
"sending DATA command",
"sending end of data -- message may be sent more than once",
"sending final RSET",
"sending RSET probe",
"sending QUIT",
};
char *xfer_request[SMTP_STATE_LAST] = {
"XFORWARD name/address command",
"XFORWARD helo/protocol command",
"MAIL FROM command",
"RCPT TO command",
"DATA command",
"end of DATA command",
"final RSET command",
"RSET probe",
"QUIT command",
};
#define SMTP_MIME_DOWNGRADE(session, request) \
(var_disable_mime_oconv == 0 \
&& (session->features & SMTP_FEATURE_8BITMIME) == 0 \
&& strcmp(request->encoding, MAIL_ATTR_ENC_7BIT) != 0)
static int smtp_start_tls(SMTP_STATE *);
int smtp_helo(SMTP_STATE *state)
{
const char *myname = "smtp_helo";
SMTP_SESSION *session = state->session;
DELIVER_REQUEST *request = state->request;
SMTP_RESP *resp;
SMTP_RESP fake;
int except;
char *lines;
char *words;
char *word;
int n;
static NAME_CODE xforward_features[] = {
XFORWARD_NAME, SMTP_FEATURE_XFORWARD_NAME,
XFORWARD_ADDR, SMTP_FEATURE_XFORWARD_ADDR,
XFORWARD_PROTO, SMTP_FEATURE_XFORWARD_PROTO,
XFORWARD_HELO, SMTP_FEATURE_XFORWARD_HELO,
XFORWARD_DOMAIN, SMTP_FEATURE_XFORWARD_DOMAIN,
0, 0,
};
SOCKOPT_SIZE optlen;
const char *ehlo_words;
int discard_mask;
static NAME_MASK pix_bug_table[] = {
PIX_BUG_DISABLE_ESMTP, SMTP_FEATURE_PIX_NO_ESMTP,
PIX_BUG_DELAY_DOTCRLF, SMTP_FEATURE_PIX_DELAY_DOTCRLF,
0,
};
const char *pix_bug_words;
const char *pix_bug_source;
int pix_bug_mask;
#ifdef USE_TLS
int saved_features = session->features;
int tls_helo_status;
#endif
const char *NOCLOBBER where;
smtp_timeout_setup(state->session->stream, var_smtp_helo_tmout);
if ((except = vstream_setjmp(state->session->stream)) != 0)
return (smtp_stream_except(state, except, where));
if ((state->misc_flags & SMTP_MISC_FLAG_IN_STARTTLS) == 0) {
where = "receiving the initial server greeting";
switch ((resp = smtp_chat_resp(session))->code / 100) {
case 2:
break;
case 5:
if (var_smtp_skip_5xx_greeting)
STR(resp->dsn_buf)[0] = '4';
default:
return (smtp_site_fail(state, session->host, resp,
"host %s refused to talk to me: %s",
session->namaddr,
translit(resp->str, "\n", " ")));
}
if (resp->str[strspn(resp->str, "20 *\t\n")] == 0) {
if (smtp_pix_bug_maps != 0
&& (pix_bug_words =
maps_find(smtp_pix_bug_maps,
state->session->addr, 0)) != 0) {
pix_bug_source = VAR_SMTP_PIX_BUG_MAPS;
} else {
pix_bug_words = var_smtp_pix_bug_words;
pix_bug_source = VAR_SMTP_PIX_BUG_WORDS;
}
if (*pix_bug_words) {
pix_bug_mask = name_mask_opt(pix_bug_source, pix_bug_table,
pix_bug_words, NAME_MASK_ANY_CASE);
msg_info("%s: enabling PIX workarounds: %s for %s",
request->queue_id,
str_name_mask("pix workaround bitmask",
pix_bug_table, pix_bug_mask),
session->namaddrport);
session->features |= pix_bug_mask;
}
}
words = resp->str;
(void) mystrtok(&words, "- \t\n");
for (n = 0; (word = mystrtok(&words, " \t\n")) != 0; n++) {
if (n == 0 && strcasecmp(word, var_myhostname) == 0) {
if (state->misc_flags & SMTP_MISC_FLAG_LOOP_DETECT)
msg_warn("host %s greeted me with my own hostname %s",
session->namaddrport, var_myhostname);
} else if (strcasecmp(word, "ESMTP") == 0)
session->features |= SMTP_FEATURE_ESMTP;
}
if ((state->misc_flags & SMTP_MISC_FLAG_USE_LMTP) == 0) {
if (var_smtp_always_ehlo
&& (session->features & SMTP_FEATURE_PIX_NO_ESMTP) == 0)
session->features |= SMTP_FEATURE_ESMTP;
if (var_smtp_never_ehlo
|| (session->features & SMTP_FEATURE_PIX_NO_ESMTP) != 0)
session->features &= ~SMTP_FEATURE_ESMTP;
} else {
session->features |= SMTP_FEATURE_ESMTP;
}
}
else {
session->features |= SMTP_FEATURE_ESMTP;
}
if ((state->misc_flags & SMTP_MISC_FLAG_USE_LMTP) == 0) {
where = "performing the EHLO handshake";
if (session->features & SMTP_FEATURE_ESMTP) {
smtp_chat_cmd(session, "EHLO %s", var_smtp_helo_name);
if ((resp = smtp_chat_resp(session))->code / 100 != 2)
session->features &= ~SMTP_FEATURE_ESMTP;
}
if ((session->features & SMTP_FEATURE_ESMTP) == 0) {
where = "performing the HELO handshake";
smtp_chat_cmd(session, "HELO %s", var_smtp_helo_name);
if ((resp = smtp_chat_resp(session))->code / 100 != 2)
return (smtp_site_fail(state, session->host, resp,
"host %s refused to talk to me: %s",
session->namaddr,
translit(resp->str, "\n", " ")));
}
} else {
where = "performing the LHLO handshake";
smtp_chat_cmd(session, "LHLO %s", var_smtp_helo_name);
if ((resp = smtp_chat_resp(session))->code / 100 != 2)
return (smtp_site_fail(state, session->host, resp,
"host %s refused to talk to me: %s",
session->namaddr,
translit(resp->str, "\n", " ")));
}
if (session->features & SMTP_FEATURE_ESMTP) {
if (smtp_ehlo_dis_maps == 0
|| (ehlo_words = maps_find(smtp_ehlo_dis_maps,
state->session->addr, 0)) == 0)
ehlo_words = var_smtp_ehlo_dis_words;
discard_mask = ehlo_mask(ehlo_words);
if (discard_mask && !(discard_mask & EHLO_MASK_SILENT))
msg_info("discarding EHLO keywords: %s",
str_ehlo_mask(discard_mask));
lines = resp->str;
for (n = 0; (words = mystrtok(&lines, "\n")) != 0; ) {
if (mystrtok(&words, "- ")
&& (word = mystrtok(&words, " \t=")) != 0) {
if (n == 0) {
if (session->helo != 0)
myfree(session->helo);
session->helo = mystrdup(word);
if (strcasecmp(word, var_myhostname) == 0
&& (state->misc_flags & SMTP_MISC_FLAG_LOOP_DETECT) != 0) {
msg_warn("host %s replied to HELO/EHLO"
" with my own hostname %s",
session->namaddrport, var_myhostname);
if (session->features & SMTP_FEATURE_BEST_MX)
return (smtp_site_fail(state, DSN_BY_LOCAL_MTA,
SMTP_RESP_FAKE(&fake, "5.4.6"),
"mail for %s loops back to myself",
request->nexthop));
else
return (smtp_site_fail(state, DSN_BY_LOCAL_MTA,
SMTP_RESP_FAKE(&fake, "4.4.6"),
"mail for %s loops back to myself",
request->nexthop));
}
} else if (strcasecmp(word, "8BITMIME") == 0) {
if ((discard_mask & EHLO_MASK_8BITMIME) == 0)
session->features |= SMTP_FEATURE_8BITMIME;
} else if (strcasecmp(word, "PIPELINING") == 0) {
if ((discard_mask & EHLO_MASK_PIPELINING) == 0)
session->features |= SMTP_FEATURE_PIPELINING;
} else if (strcasecmp(word, "XFORWARD") == 0) {
if ((discard_mask & EHLO_MASK_XFORWARD) == 0)
while ((word = mystrtok(&words, " \t")) != 0)
session->features |=
name_code(xforward_features,
NAME_CODE_FLAG_NONE, word);
} else if (strcasecmp(word, "SIZE") == 0) {
if ((discard_mask & EHLO_MASK_SIZE) == 0) {
session->features |= SMTP_FEATURE_SIZE;
if ((word = mystrtok(&words, " \t")) != 0) {
if (!alldig(word))
msg_warn("bad EHLO SIZE limit \"%s\" from %s",
word, session->namaddrport);
else
session->size_limit = off_cvt_string(word);
}
}
#ifdef USE_TLS
} else if (strcasecmp(word, "STARTTLS") == 0) {
if ((discard_mask & EHLO_MASK_STARTTLS) == 0)
session->features |= SMTP_FEATURE_STARTTLS;
#endif
#ifdef USE_SASL_AUTH
} else if (var_smtp_sasl_enable
&& strcasecmp(word, "AUTH") == 0) {
if ((discard_mask & EHLO_MASK_AUTH) == 0)
smtp_sasl_helo_auth(session, words);
#endif
} else if (strcasecmp(word, "DSN") == 0) {
if ((discard_mask & EHLO_MASK_DSN) == 0)
session->features |= SMTP_FEATURE_DSN;
}
n++;
}
}
}
if (msg_verbose)
msg_info("server features: 0x%x size %.0f",
session->features, (double) session->size_limit);
if (session->features & SMTP_FEATURE_PIPELINING) {
optlen = sizeof(session->sndbufsize);
if (getsockopt(vstream_fileno(session->stream), SOL_SOCKET,
SO_SNDBUF, (char *) &session->sndbufsize, &optlen) < 0)
msg_fatal("%s: getsockopt: %m", myname);
if (session->sndbufsize > VSTREAM_BUFSIZE)
session->sndbufsize = VSTREAM_BUFSIZE;
if (session->sndbufsize == 0) {
session->sndbufsize = VSTREAM_BUFSIZE;
if (setsockopt(vstream_fileno(session->stream), SOL_SOCKET,
SO_SNDBUF, (char *) &session->sndbufsize, optlen) < 0)
msg_fatal("%s: setsockopt: %m", myname);
}
if (msg_verbose)
msg_info("Using %s PIPELINING, TCP send buffer size is %d",
(state->misc_flags &
SMTP_MISC_FLAG_USE_LMTP) ? "LMTP" : "ESMTP",
session->sndbufsize);
} else {
session->sndbufsize = 0;
}
#ifdef USE_TLS
if ((state->misc_flags & SMTP_MISC_FLAG_IN_STARTTLS) == 0) {
if ((session->features & SMTP_FEATURE_STARTTLS) &&
var_smtp_tls_note_starttls_offer &&
session->tls_level <= TLS_LEV_NONE)
msg_info("Host offered STARTTLS: [%s]", session->host);
if ((session->features & SMTP_FEATURE_STARTTLS) != 0
&& smtp_tls_ctx != 0 && session->tls_level >= TLS_LEV_MAY) {
smtp_timeout_setup(state->session->stream, var_smtp_starttls_tmout);
if ((except = vstream_setjmp(state->session->stream)) != 0)
return (smtp_stream_except(state, except,
"receiving the STARTTLS response"));
smtp_chat_cmd(session, "STARTTLS");
if ((resp = smtp_chat_resp(session))->code / 100 == 2) {
#ifdef USE_SASL_AUTH
if (session->features & SMTP_FEATURE_AUTH)
smtp_sasl_cleanup(session);
#endif
session->features = saved_features;
state->misc_flags |= SMTP_MISC_FLAG_IN_STARTTLS;
tls_helo_status = smtp_start_tls(state);
state->misc_flags &= ~SMTP_MISC_FLAG_IN_STARTTLS;
return (tls_helo_status);
}
session->features &= ~SMTP_FEATURE_STARTTLS;
if (session->tls_level >= TLS_LEV_ENCRYPT)
return (smtp_site_fail(state, session->host, resp,
"TLS is required, but host %s refused to start TLS: %s",
session->namaddr,
translit(resp->str, "\n", " ")));
}
if (session->tls_level >= TLS_LEV_ENCRYPT) {
if (!(session->features & SMTP_FEATURE_STARTTLS)) {
return (smtp_site_fail(state, DSN_BY_LOCAL_MTA,
SMTP_RESP_FAKE(&fake, "4.7.4"),
"TLS is required, but was not offered by host %s",
session->namaddr));
} else if (smtp_tls_ctx == 0) {
return (smtp_site_fail(state, DSN_BY_LOCAL_MTA,
SMTP_RESP_FAKE(&fake, "4.7.5"),
"TLS is required, but our TLS engine is unavailable"));
} else {
msg_warn("%s: TLS is required but unavailable, don't know why",
myname);
return (smtp_site_fail(state, DSN_BY_LOCAL_MTA,
SMTP_RESP_FAKE(&fake, "4.7.0"),
"TLS is required, but unavailable"));
}
}
}
#endif
#ifdef USE_SASL_AUTH
if (var_smtp_sasl_enable && (session->features & SMTP_FEATURE_AUTH))
return (smtp_sasl_helo_login(state));
#endif
return (0);
}
#ifdef USE_TLS
static int smtp_start_tls(SMTP_STATE *state)
{
SMTP_SESSION *session = state->session;
tls_client_start_props tls_props;
VSTRING *serverid;
SMTP_RESP fake;
DONT_CACHE_THIS_SESSION;
serverid = vstring_alloc(10);
vstring_sprintf(serverid, "%s:%s:%u:%s", state->service, session->addr,
ntohs(session->port), session->helo ? session->helo : "");
if (session->tls_level >= TLS_LEV_ENCRYPT
&& session->tls_protocols != 0
&& session->tls_protocols != TLS_ALL_PROTOCOLS)
vstring_sprintf_append(serverid, "&p=%s",
tls_protocol_names(VAR_SMTP_TLS_MAND_PROTO,
session->tls_protocols));
if (session->tls_level >= TLS_LEV_ENCRYPT)
vstring_sprintf_append(serverid, "&c=%s", session->tls_cipherlist);
tls_props.ctx = smtp_tls_ctx;
tls_props.stream = session->stream;
tls_props.log_level = var_smtp_tls_loglevel;
tls_props.timeout = var_smtp_starttls_tmout;
tls_props.tls_level = session->tls_level;
tls_props.nexthop = session->tls_nexthop;
tls_props.host = session->host;
tls_props.serverid = vstring_str(serverid);
tls_props.protocols = session->tls_protocols;
tls_props.cipherlist = session->tls_cipherlist;
tls_props.certmatch = session->tls_certmatch;
session->tls_context = tls_client_start(&tls_props);
vstring_free(serverid);
if (session->tls_context == 0) {
(void) vstream_fpurge(session->stream, VSTREAM_PURGE_BOTH);
DONT_USE_DEAD_SESSION;
if (session->tls_level == TLS_LEV_MAY
#ifdef USE_SASL_AUTH
&& !(var_smtp_sasl_enable
&& *var_smtp_sasl_passwd
&& smtp_sasl_passwd_lookup(session))
#endif
)
RETRY_AS_PLAINTEXT;
return (smtp_site_fail(state, DSN_BY_LOCAL_MTA,
SMTP_RESP_FAKE(&fake, "4.7.5"),
"Cannot start TLS: handshake failure"));
}
return (smtp_helo(state));
}
#endif
static void smtp_text_out(void *context, int rec_type,
const char *text, ssize_t len,
off_t unused_offset)
{
SMTP_STATE *state = (SMTP_STATE *) context;
SMTP_SESSION *session = state->session;
ssize_t data_left;
const char *data_start;
data_left = len;
data_start = text;
do {
if (state->space_left == var_smtp_line_limit
&& data_left > 0 && *data_start == '.')
smtp_fputc('.', session->stream);
if (var_smtp_line_limit > 0 && data_left >= state->space_left) {
smtp_fputs(data_start, state->space_left, session->stream);
data_start += state->space_left;
data_left -= state->space_left;
state->space_left = var_smtp_line_limit;
if (data_left > 0 || rec_type == REC_TYPE_CONT) {
smtp_fputc(' ', session->stream);
state->space_left -= 1;
}
} else {
if (rec_type == REC_TYPE_CONT) {
smtp_fwrite(data_start, data_left, session->stream);
state->space_left -= data_left;
} else {
smtp_fputs(data_start, data_left, session->stream);
state->space_left = var_smtp_line_limit;
}
break;
}
} while (data_left > 0);
}
static void PRINTFLIKE(3, 4) smtp_format_out(void *, int, const char *,...);
static void smtp_format_out(void *context, int rec_type, const char *fmt,...)
{
static VSTRING *vp;
va_list ap;
if (vp == 0)
vp = vstring_alloc(100);
va_start(ap, fmt);
vstring_vsprintf(vp, fmt, ap);
va_end(ap);
smtp_text_out(context, rec_type, vstring_str(vp), VSTRING_LEN(vp), 0);
}
static void smtp_header_out(void *context, int unused_header_class,
HEADER_OPTS *unused_info, VSTRING *buf,
off_t offset)
{
char *start = vstring_str(buf);
char *line;
char *next_line;
for (line = start; line; line = next_line) {
next_line = split_at(line, '\n');
smtp_text_out(context, REC_TYPE_NORM, line, next_line ?
next_line - line - 1 : strlen(line), offset);
}
}
static void smtp_header_rewrite(void *context, int header_class,
HEADER_OPTS *header_info, VSTRING *buf,
off_t offset)
{
SMTP_STATE *state = (SMTP_STATE *) context;
int did_rewrite = 0;
char *line;
char *start;
char *next_line;
char *end_line;
if (header_info && header_class == MIME_HDR_PRIMARY
&& (header_info->flags & (HDR_OPT_SENDER | HDR_OPT_RECIP)) != 0) {
TOK822 *tree;
TOK822 **addr_list;
TOK822 **tpp;
tree = tok822_parse(vstring_str(buf)
+ strlen(header_info->name) + 1);
addr_list = tok822_grep(tree, TOK822_ADDR);
for (tpp = addr_list; *tpp; tpp++)
did_rewrite |= smtp_map11_tree(tpp[0], smtp_generic_maps,
smtp_ext_prop_mask & EXT_PROP_GENERIC);
if (did_rewrite) {
vstring_truncate(buf, strlen(header_info->name));
vstring_strcat(buf, ": ");
tok822_externalize(buf, tree, TOK822_STR_HEAD);
}
myfree((char *) addr_list);
tok822_free_tree(tree);
}
if (did_rewrite == 0) {
smtp_header_out(context, header_class, header_info, buf, offset);
return;
}
for (line = start = vstring_str(buf); line != 0; line = next_line) {
end_line = line + strcspn(line, "\n");
if (line > start) {
if (end_line - start < 70) {
line[-1] = ' ';
} else {
start = line;
}
}
next_line = *end_line ? end_line + 1 : 0;
}
for (line = start = vstring_str(buf); line != 0; line = next_line) {
next_line = split_at(line, '\n');
if (line == start || IS_SPACE_TAB(*line)) {
smtp_text_out(state, REC_TYPE_NORM, line, next_line ?
next_line - line - 1 : strlen(line), offset);
} else {
smtp_format_out(state, REC_TYPE_NORM, "\t%s", line);
}
}
}
static void smtp_mime_fail(SMTP_STATE *state, int mime_errs)
{
MIME_STATE_DETAIL *detail;
SMTP_RESP fake;
detail = mime_state_detail(mime_errs);
smtp_mesg_fail(state, DSN_BY_LOCAL_MTA,
SMTP_RESP_FAKE(&fake, detail->dsn),
"%s", detail->text);
}
static int smtp_loop(SMTP_STATE *state, NOCLOBBER int send_state,
NOCLOBBER int recv_state)
{
const char *myname = "smtp_loop";
DELIVER_REQUEST *request = state->request;
SMTP_SESSION *session = state->session;
SMTP_RESP *resp;
RECIPIENT *rcpt;
VSTRING *next_command = vstring_alloc(100);
int *NOCLOBBER survivors = 0;
NOCLOBBER int next_state;
NOCLOBBER int next_rcpt;
NOCLOBBER int send_rcpt;
NOCLOBBER int recv_rcpt;
NOCLOBBER int nrcpt;
NOCLOBBER int recv_done;
int except;
int rec_type;
NOCLOBBER int prev_type = 0;
NOCLOBBER int sndbuffree;
NOCLOBBER int mail_from_rejected;
NOCLOBBER int downgrading;
int mime_errs;
SMTP_RESP fake;
int fail_status;
#define REWRITE_ADDRESS(dst, src) do { \
vstring_strcpy(dst, src); \
if (*(src) && smtp_generic_maps) \
smtp_map11_internal(dst, smtp_generic_maps, \
smtp_ext_prop_mask & EXT_PROP_GENERIC); \
} while (0)
#define QUOTE_ADDRESS(dst, src) do { \
if (*(src) && var_smtp_quote_821_env) { \
quote_821_local(dst, src); \
} else { \
vstring_strcpy(dst, src); \
} \
} while (0)
#define RETURN(x) do { \
if (recv_state != SMTP_STATE_LAST) \
DONT_CACHE_THIS_SESSION; \
vstring_free(next_command); \
if (survivors) \
myfree((char *) survivors); \
if (session->mime_state) \
session->mime_state = mime_state_free(session->mime_state); \
return (x); \
} while (0)
#define SENDER_IS_AHEAD \
(recv_state < send_state || recv_rcpt != send_rcpt)
#define SENDER_IN_WAIT_STATE \
(send_state == SMTP_STATE_DOT || send_state == SMTP_STATE_LAST)
#define SENDING_MAIL \
(recv_state <= SMTP_STATE_DOT)
#define CANT_RSET_THIS_SESSION \
(session->features |= SMTP_FEATURE_RSET_REJECTED)
if (session->sndbufsize > VSTREAM_BUFSIZE)
msg_panic("bad sndbufsize %d > VSTREAM_BUFSIZE %d",
session->sndbufsize, VSTREAM_BUFSIZE);
sndbuffree = session->sndbufsize;
nrcpt = 0;
next_rcpt = send_rcpt = recv_rcpt = recv_done = 0;
mail_from_rejected = 0;
if (send_state < SMTP_STATE_XFORWARD_NAME_ADDR
|| send_state > SMTP_STATE_QUIT)
msg_panic("%s: bad sender state %d (receiver state %d)",
myname, send_state, recv_state);
smtp_timeout_setup(session->stream,
*xfer_timeouts[send_state]);
if ((except = vstream_setjmp(session->stream)) != 0) {
msg_warn("smtp_proto: spurious flush before read in send state %d",
send_state);
RETURN(SENDING_MAIL ? smtp_stream_except(state, except,
xfer_states[send_state]) : -1);
}
do {
switch (send_state) {
default:
msg_panic("%s: bad sender state %d", myname, send_state);
case SMTP_STATE_XFORWARD_NAME_ADDR:
vstring_strcpy(next_command, XFORWARD_CMD);
if (session->features & SMTP_FEATURE_XFORWARD_NAME) {
vstring_strcat(next_command, " " XFORWARD_NAME "=");
xtext_quote_append(next_command,
DEL_REQ_ATTR_AVAIL(request->client_name) ?
request->client_name : XFORWARD_UNAVAILABLE, "");
}
if (session->features & SMTP_FEATURE_XFORWARD_ADDR) {
vstring_strcat(next_command, " " XFORWARD_ADDR "=");
xtext_quote_append(next_command,
DEL_REQ_ATTR_AVAIL(request->client_addr) ?
request->client_addr : XFORWARD_UNAVAILABLE, "");
}
if (session->send_proto_helo)
next_state = SMTP_STATE_XFORWARD_PROTO_HELO;
else
next_state = SMTP_STATE_MAIL;
break;
case SMTP_STATE_XFORWARD_PROTO_HELO:
vstring_strcpy(next_command, XFORWARD_CMD);
if (session->features & SMTP_FEATURE_XFORWARD_PROTO) {
vstring_strcat(next_command, " " XFORWARD_PROTO "=");
xtext_quote_append(next_command,
DEL_REQ_ATTR_AVAIL(request->client_proto) ?
request->client_proto : XFORWARD_UNAVAILABLE, "");
}
if (session->features & SMTP_FEATURE_XFORWARD_HELO) {
vstring_strcat(next_command, " " XFORWARD_HELO "=");
xtext_quote_append(next_command,
DEL_REQ_ATTR_AVAIL(request->client_helo) ?
request->client_helo : XFORWARD_UNAVAILABLE, "");
}
if (session->features & SMTP_FEATURE_XFORWARD_DOMAIN) {
vstring_strcat(next_command, " " XFORWARD_DOMAIN "=");
xtext_quote_append(next_command,
DEL_REQ_ATTR_AVAIL(request->rewrite_context) == 0 ?
XFORWARD_UNAVAILABLE :
strcmp(request->rewrite_context, MAIL_ATTR_RWR_LOCAL) ?
XFORWARD_DOM_REMOTE : XFORWARD_DOM_LOCAL, "");
}
next_state = SMTP_STATE_MAIL;
break;
case SMTP_STATE_MAIL:
request->msg_stats.reuse_count = session->reuse_count;
GETTIMEOFDAY(&request->msg_stats.conn_setup_done);
REWRITE_ADDRESS(session->scratch2, request->sender);
QUOTE_ADDRESS(session->scratch, vstring_str(session->scratch2));
vstring_sprintf(next_command, "MAIL FROM:<%s>",
vstring_str(session->scratch));
if (session->features & SMTP_FEATURE_SIZE
&& !SMTP_MIME_DOWNGRADE(session, request))
vstring_sprintf_append(next_command, " SIZE=%lu",
request->data_size);
if (session->features & SMTP_FEATURE_8BITMIME) {
if (strcmp(request->encoding, MAIL_ATTR_ENC_8BIT) == 0)
vstring_strcat(next_command, " BODY=8BITMIME");
else if (strcmp(request->encoding, MAIL_ATTR_ENC_7BIT) == 0)
vstring_strcat(next_command, " BODY=7BIT");
else if (strcmp(request->encoding, MAIL_ATTR_ENC_NONE) != 0)
msg_warn("%s: unknown content encoding: %s",
request->queue_id, request->encoding);
}
if (session->features & SMTP_FEATURE_DSN) {
if (request->dsn_envid[0]) {
vstring_sprintf_append(next_command, " ENVID=");
xtext_quote_append(next_command, request->dsn_envid, "+=");
}
if (request->dsn_ret)
vstring_sprintf_append(next_command, " RET=%s",
dsn_ret_str(request->dsn_ret));
}
#ifdef USE_SASL_AUTH
if (var_smtp_sasl_enable
&& (session->features & SMTP_FEATURE_AUTH))
vstring_strcat(next_command, " AUTH=<>");
#endif
next_state = SMTP_STATE_RCPT;
break;
case SMTP_STATE_RCPT:
rcpt = request->rcpt_list.info + send_rcpt;
REWRITE_ADDRESS(session->scratch2, rcpt->address);
QUOTE_ADDRESS(session->scratch, vstring_str(session->scratch2));
vstring_sprintf(next_command, "RCPT TO:<%s>",
vstring_str(session->scratch));
if (session->features & SMTP_FEATURE_DSN) {
if (rcpt->dsn_orcpt[0]) {
xtext_quote(session->scratch, rcpt->dsn_orcpt, "+=");
vstring_sprintf_append(next_command, " ORCPT=%s",
vstring_str(session->scratch));
} else if (rcpt->orig_addr[0]) {
quote_822_local(session->scratch, rcpt->orig_addr);
vstring_sprintf(session->scratch2, "rfc822;%s",
vstring_str(session->scratch));
xtext_quote(session->scratch, vstring_str(session->scratch2), "+=");
vstring_sprintf_append(next_command, " ORCPT=%s",
vstring_str(session->scratch));
}
if (rcpt->dsn_notify)
vstring_sprintf_append(next_command, " NOTIFY=%s",
dsn_notify_str(rcpt->dsn_notify));
}
if ((next_rcpt = send_rcpt + 1) == SMTP_RCPT_LEFT(state))
next_state = DEL_REQ_TRACE_ONLY(request->flags) ?
SMTP_STATE_ABORT : SMTP_STATE_DATA;
break;
case SMTP_STATE_DATA:
vstring_strcpy(next_command, "DATA");
next_state = SMTP_STATE_DOT;
break;
case SMTP_STATE_DOT:
vstring_strcpy(next_command, ".");
if (THIS_SESSION_IS_EXPIRED)
DONT_CACHE_THIS_SESSION;
next_state = THIS_SESSION_IS_CACHED ?
SMTP_STATE_LAST : SMTP_STATE_QUIT;
break;
case SMTP_STATE_ABORT:
vstring_strcpy(next_command, "RSET");
if (THIS_SESSION_IS_EXPIRED)
DONT_CACHE_THIS_SESSION;
next_state = THIS_SESSION_IS_CACHED ?
SMTP_STATE_LAST : SMTP_STATE_QUIT;
break;
case SMTP_STATE_RSET:
vstring_strcpy(next_command, "RSET");
next_state = SMTP_STATE_LAST;
break;
case SMTP_STATE_QUIT:
vstring_strcpy(next_command, "QUIT");
next_state = SMTP_STATE_LAST;
if (THIS_SESSION_IS_CACHED)
DONT_CACHE_THIS_SESSION;
break;
case SMTP_STATE_LAST:
VSTRING_RESET(next_command);
break;
}
VSTRING_TERMINATE(next_command);
if (SENDER_IN_WAIT_STATE
|| (SENDER_IS_AHEAD
&& (VSTRING_LEN(next_command) + 2 > sndbuffree
|| time((time_t *) 0) - vstream_ftime(session->stream) > 10))) {
while (SENDER_IS_AHEAD) {
if (recv_state < SMTP_STATE_XFORWARD_NAME_ADDR
|| recv_state > SMTP_STATE_QUIT)
msg_panic("%s: bad receiver state %d (sender state %d)",
myname, recv_state, send_state);
#define LOST_CONNECTION_INSIDE_DATA (except == SMTP_ERR_EOF)
smtp_timeout_setup(session->stream,
*xfer_timeouts[recv_state]);
if (LOST_CONNECTION_INSIDE_DATA) {
if (vstream_setjmp(session->stream) != 0)
RETURN(smtp_stream_except(state, SMTP_ERR_EOF,
"sending message body"));
} else {
if ((except = vstream_setjmp(session->stream)) != 0)
RETURN(SENDING_MAIL ? smtp_stream_except(state, except,
xfer_states[recv_state]) : -1);
}
resp = smtp_chat_resp(session);
switch (recv_state) {
case SMTP_STATE_XFORWARD_NAME_ADDR:
if (resp->code / 100 != 2)
msg_warn("host %s said: %s (in reply to %s)",
session->namaddrport,
translit(resp->str, "\n", " "),
xfer_request[SMTP_STATE_XFORWARD_NAME_ADDR]);
if (session->send_proto_helo)
recv_state = SMTP_STATE_XFORWARD_PROTO_HELO;
else
recv_state = SMTP_STATE_MAIL;
break;
case SMTP_STATE_XFORWARD_PROTO_HELO:
if (resp->code / 100 != 2)
msg_warn("host %s said: %s (in reply to %s)",
session->namaddrport,
translit(resp->str, "\n", " "),
xfer_request[SMTP_STATE_XFORWARD_PROTO_HELO]);
recv_state = SMTP_STATE_MAIL;
break;
case SMTP_STATE_MAIL:
if (resp->code / 100 != 2) {
smtp_mesg_fail(state, session->host, resp,
"host %s said: %s (in reply to %s)",
session->namaddr,
translit(resp->str, "\n", " "),
xfer_request[SMTP_STATE_MAIL]);
mail_from_rejected = 1;
}
recv_state = SMTP_STATE_RCPT;
break;
case SMTP_STATE_RCPT:
if (!mail_from_rejected) {
#ifdef notdef
if (resp->code == 552) {
resp->code = 452;
resp->dsn[0] = '4';
}
#endif
rcpt = request->rcpt_list.info + recv_rcpt;
if (resp->code / 100 == 2) {
if (state->misc_flags & SMTP_MISC_FLAG_USE_LMTP) {
if (survivors == 0)
survivors = (int *)
mymalloc(request->rcpt_list.len
* sizeof(int));
survivors[nrcpt] = recv_rcpt;
}
++nrcpt;
if (DEL_REQ_TRACE_ONLY(request->flags)) {
translit(resp->str, "\n", " ");
smtp_rcpt_done(state, resp, rcpt);
}
} else {
smtp_rcpt_fail(state, rcpt, session->host, resp,
"host %s said: %s (in reply to %s)",
session->namaddr,
translit(resp->str, "\n", " "),
xfer_request[SMTP_STATE_RCPT]);
}
}
if (++recv_rcpt == SMTP_RCPT_LEFT(state))
recv_state = DEL_REQ_TRACE_ONLY(request->flags) ?
SMTP_STATE_ABORT : SMTP_STATE_DATA;
break;
case SMTP_STATE_DATA:
if (resp->code / 100 != 3) {
if (nrcpt > 0)
smtp_mesg_fail(state, session->host, resp,
"host %s said: %s (in reply to %s)",
session->namaddr,
translit(resp->str, "\n", " "),
xfer_request[SMTP_STATE_DATA]);
nrcpt = -1;
}
recv_state = SMTP_STATE_DOT;
break;
case SMTP_STATE_DOT:
GETTIMEOFDAY(&request->msg_stats.deliver_done);
if ((state->misc_flags & SMTP_MISC_FLAG_USE_LMTP) == 0) {
if (nrcpt > 0) {
if (resp->code / 100 != 2) {
smtp_mesg_fail(state, session->host, resp,
"host %s said: %s (in reply to %s)",
session->namaddr,
translit(resp->str, "\n", " "),
xfer_request[SMTP_STATE_DOT]);
} else {
for (nrcpt = 0; nrcpt < recv_rcpt; nrcpt++) {
rcpt = request->rcpt_list.info + nrcpt;
if (!SMTP_RCPT_ISMARKED(rcpt)) {
translit(resp->str, "\n", " ");
smtp_rcpt_done(state, resp, rcpt);
}
}
}
}
}
else {
if (nrcpt > 0) {
rcpt = request->rcpt_list.info
+ survivors[recv_done++];
if (resp->code / 100 != 2) {
smtp_rcpt_fail(state, rcpt, session->host, resp,
"host %s said: %s (in reply to %s)",
session->namaddr,
translit(resp->str, "\n", " "),
xfer_request[SMTP_STATE_DOT]);
} else {
translit(resp->str, "\n", " ");
smtp_rcpt_done(state, resp, rcpt);
}
}
if (msg_verbose)
msg_info("%s: got %d of %d end-of-data replies",
myname, recv_done, nrcpt);
if (recv_done < nrcpt)
break;
}
if (var_skip_quit_resp || THIS_SESSION_IS_CACHED
|| LOST_CONNECTION_INSIDE_DATA)
recv_state = SMTP_STATE_LAST;
else
recv_state = SMTP_STATE_QUIT;
break;
case SMTP_STATE_ABORT:
recv_state = (var_skip_quit_resp || THIS_SESSION_IS_CACHED ?
SMTP_STATE_LAST : SMTP_STATE_QUIT);
break;
case SMTP_STATE_RSET:
if (resp->code / 100 != 2)
CANT_RSET_THIS_SESSION;
recv_state = SMTP_STATE_LAST;
break;
case SMTP_STATE_QUIT:
recv_state = SMTP_STATE_LAST;
break;
}
}
sndbuffree = session->sndbufsize;
if ((send_state == SMTP_STATE_RCPT && mail_from_rejected)
|| (send_state == SMTP_STATE_DATA && nrcpt == 0)
|| (send_state == SMTP_STATE_DOT && nrcpt < 0)) {
send_state = recv_state = SMTP_STATE_ABORT;
send_rcpt = recv_rcpt = 0;
vstring_strcpy(next_command, "RSET");
if (THIS_SESSION_IS_EXPIRED)
DONT_CACHE_THIS_SESSION;
next_state = THIS_SESSION_IS_CACHED ?
SMTP_STATE_LAST : SMTP_STATE_QUIT;
next_rcpt = 0;
}
}
if (send_state == SMTP_STATE_LAST)
continue;
if (send_state == SMTP_STATE_DOT && nrcpt > 0) {
smtp_timeout_setup(session->stream,
var_smtp_data1_tmout);
if ((except = vstream_setjmp(session->stream)) == 0) {
if (vstream_fseek(state->src, request->data_offset, SEEK_SET) < 0)
msg_fatal("seek queue file: %m");
downgrading = SMTP_MIME_DOWNGRADE(session, request);
if (downgrading || smtp_generic_maps)
session->mime_state = mime_state_alloc(downgrading ?
MIME_OPT_DOWNGRADE
| MIME_OPT_REPORT_NESTING :
MIME_OPT_DISABLE_MIME,
smtp_generic_maps ?
smtp_header_rewrite :
smtp_header_out,
(MIME_STATE_ANY_END) 0,
smtp_text_out,
(MIME_STATE_ANY_END) 0,
(MIME_STATE_ERR_PRINT) 0,
(void *) state);
state->space_left = var_smtp_line_limit;
while ((rec_type = rec_get(state->src, session->scratch, 0)) > 0) {
if (rec_type != REC_TYPE_NORM && rec_type != REC_TYPE_CONT)
break;
if (session->mime_state == 0) {
smtp_text_out((void *) state, rec_type,
vstring_str(session->scratch),
VSTRING_LEN(session->scratch),
(off_t) 0);
} else {
mime_errs =
mime_state_update(session->mime_state, rec_type,
vstring_str(session->scratch),
VSTRING_LEN(session->scratch));
if (mime_errs) {
smtp_mime_fail(state, mime_errs);
RETURN(0);
}
}
prev_type = rec_type;
}
if (session->mime_state) {
mime_errs =
mime_state_update(session->mime_state, rec_type, "", 0);
if (mime_errs) {
smtp_mime_fail(state, mime_errs);
RETURN(0);
}
} else if (prev_type == REC_TYPE_CONT)
smtp_fputs("", 0, session->stream);
if ((session->features & SMTP_FEATURE_PIX_DELAY_DOTCRLF) != 0
&& request->msg_stats.incoming_arrival.tv_sec
<= vstream_ftime(session->stream) - var_smtp_pix_thresh) {
smtp_flush(session->stream);
sleep(var_smtp_pix_delay);
}
if (vstream_ferror(state->src))
msg_fatal("queue file read error");
if (rec_type != REC_TYPE_XTRA) {
msg_warn("%s: bad record type: %d in message content",
request->queue_id, rec_type);
fail_status = smtp_mesg_fail(state, DSN_BY_LOCAL_MTA,
SMTP_RESP_FAKE(&fake, "5.3.0"),
"unreadable mail queue entry");
if (fail_status == 0)
(void) mark_corrupt(state->src);
RETURN(fail_status);
}
} else {
if (!LOST_CONNECTION_INSIDE_DATA)
RETURN(smtp_stream_except(state, except,
"sending message body"));
(void) vstream_fpurge(session->stream, VSTREAM_PURGE_WRITE);
next_state = SMTP_STATE_LAST;
}
}
if (except == 0) {
if (sndbuffree > 0)
sndbuffree -= VSTRING_LEN(next_command) + 2;
smtp_chat_cmd(session, "%s", vstring_str(next_command));
} else {
DONT_CACHE_THIS_SESSION;
}
send_state = next_state;
send_rcpt = next_rcpt;
} while (recv_state != SMTP_STATE_LAST);
RETURN(0);
}
int smtp_xfer(SMTP_STATE *state)
{
DELIVER_REQUEST *request = state->request;
SMTP_SESSION *session = state->session;
SMTP_RESP fake;
int send_state;
int recv_state;
int send_name_addr;
if (SMTP_RCPT_LEFT(state) <= 0)
msg_panic("smtp_xfer: bad recipient count: %d",
SMTP_RCPT_LEFT(state));
if (SMTP_RCPT_ISMARKED(request->rcpt_list.info))
msg_panic("smtp_xfer: bad recipient status: %d",
request->rcpt_list.info->u.status);
if (session->size_limit > 0 && session->size_limit < request->data_size) {
smtp_mesg_fail(state, DSN_BY_LOCAL_MTA,
SMTP_RESP_FAKE(&fake, "5.3.4"),
"message size %lu exceeds size limit %.0f of server %s",
request->data_size, (double) session->size_limit,
session->namaddr);
return (0);
}
send_name_addr =
var_smtp_send_xforward
&& (((session->features & SMTP_FEATURE_XFORWARD_NAME)
&& DEL_REQ_ATTR_AVAIL(request->client_name))
|| ((session->features & SMTP_FEATURE_XFORWARD_ADDR)
&& DEL_REQ_ATTR_AVAIL(request->client_addr)));
session->send_proto_helo =
var_smtp_send_xforward
&& (((session->features & SMTP_FEATURE_XFORWARD_PROTO)
&& DEL_REQ_ATTR_AVAIL(request->client_proto))
|| ((session->features & SMTP_FEATURE_XFORWARD_HELO)
&& DEL_REQ_ATTR_AVAIL(request->client_helo)));
if (send_name_addr)
recv_state = send_state = SMTP_STATE_XFORWARD_NAME_ADDR;
else if (session->send_proto_helo)
recv_state = send_state = SMTP_STATE_XFORWARD_PROTO_HELO;
else
recv_state = send_state = SMTP_STATE_MAIL;
return (smtp_loop(state, send_state, recv_state));
}
int smtp_rset(SMTP_STATE *state)
{
return (smtp_loop(state, SMTP_STATE_RSET, SMTP_STATE_RSET));
}
int smtp_quit(SMTP_STATE *state)
{
return (smtp_loop(state, SMTP_STATE_QUIT, var_skip_quit_resp ?
SMTP_STATE_LAST : SMTP_STATE_QUIT));
}