#include <sys_defs.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <errno.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 <name_code.h>
#include <mail_params.h>
#include <smtp_stream.h>
#include <mail_queue.h>
#include <recipient_list.h>
#include <deliver_request.h>
#include <deliver_completed.h>
#include <defer.h>
#include <bounce.h>
#include <sent.h>
#include <record.h>
#include <rec_type.h>
#include <off_cvt.h>
#include <mark_corrupt.h>
#include <quote_821_local.h>
#include <mail_proto.h>
#include "lmtp.h"
#include "lmtp_sasl.h"
#define LMTP_STATE_XFORWARD_NAME_ADDR 0
#define LMTP_STATE_XFORWARD_PROTO_HELO 1
#define LMTP_STATE_MAIL 2
#define LMTP_STATE_RCPT 3
#define LMTP_STATE_DATA 4
#define LMTP_STATE_DOT 5
#define LMTP_STATE_ABORT 6
#define LMTP_STATE_RSET 7
#define LMTP_STATE_QUIT 8
#define LMTP_STATE_LAST 9
int *xfer_timeouts[LMTP_STATE_LAST] = {
&var_lmtp_xfwd_tmout,
&var_lmtp_xfwd_tmout,
&var_lmtp_mail_tmout,
&var_lmtp_rcpt_tmout,
&var_lmtp_data0_tmout,
&var_lmtp_data2_tmout,
&var_lmtp_rset_tmout,
&var_lmtp_rset_tmout,
&var_lmtp_quit_tmout,
};
char *xfer_states[LMTP_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 RSET",
"sending RSET",
"sending QUIT",
};
char *xfer_request[LMTP_STATE_LAST] = {
"XFORWARD name/address command",
"XFORWARD helo/protocol command",
"MAIL FROM command",
"RCPT TO command",
"DATA command",
"end of DATA command",
"RSET command",
"RSET command",
"QUIT command",
};
static int lmtp_send_proto_helo;
int lmtp_lhlo(LMTP_STATE *state)
{
LMTP_SESSION *session = state->session;
LMTP_RESP *resp;
int except;
char *lines;
char *words;
char *word;
static NAME_CODE xforward_features[] = {
XFORWARD_NAME, LMTP_FEATURE_XFORWARD_NAME,
XFORWARD_ADDR, LMTP_FEATURE_XFORWARD_ADDR,
XFORWARD_PROTO, LMTP_FEATURE_XFORWARD_PROTO,
XFORWARD_HELO, LMTP_FEATURE_XFORWARD_HELO,
0, 0,
};
smtp_timeout_setup(state->session->stream, var_lmtp_lhlo_tmout);
if ((except = vstream_setjmp(state->session->stream)) != 0)
return (lmtp_stream_except(state, except, "sending LHLO"));
if (((resp = lmtp_chat_resp(state))->code / 100) != 2)
return (lmtp_site_fail(state, resp->code,
"host %s refused to talk to me: %s",
session->namaddr, translit(resp->str, "\n", " ")));
lmtp_chat_cmd(state, "LHLO %s", var_myhostname);
if ((resp = lmtp_chat_resp(state))->code / 100 != 2)
return (lmtp_site_fail(state, resp->code,
"host %s refused to talk to me: %s",
session->namaddr,
translit(resp->str, "\n", " ")));
state->features = 0;
lines = resp->str;
(void) mystrtok(&lines, "\n");
while ((words = mystrtok(&lines, "\n")) != 0) {
if (mystrtok(&words, "- ") && (word = mystrtok(&words, " \t=")) != 0) {
if (strcasecmp(word, "8BITMIME") == 0)
state->features |= LMTP_FEATURE_8BITMIME;
else if (strcasecmp(word, "PIPELINING") == 0)
state->features |= LMTP_FEATURE_PIPELINING;
else if (strcasecmp(word, "XFORWARD") == 0)
while ((word = mystrtok(&words, " \t")) != 0)
state->features |= name_code(xforward_features,
NAME_CODE_FLAG_NONE, word);
else if (strcasecmp(word, "SIZE") == 0)
state->features |= LMTP_FEATURE_SIZE;
#ifdef USE_SASL_AUTH
else if (var_lmtp_sasl_enable && strcasecmp(word, "AUTH") == 0)
lmtp_sasl_helo_auth(state, words);
#endif
}
}
if (msg_verbose)
msg_info("server features: 0x%x", state->features);
if (state->features & LMTP_FEATURE_PIPELINING) {
state->sndbufsize = 4 * 1024;
if (msg_verbose)
msg_info("Using LMTP PIPELINING, send buffer size is %d",
state->sndbufsize);
} else
state->sndbufsize = 0;
#ifdef USE_SASL_AUTH
if (var_lmtp_sasl_enable && (state->features & LMTP_FEATURE_AUTH))
return (lmtp_sasl_helo_login(state));
#endif
return (0);
}
static int lmtp_loop(LMTP_STATE *state, NOCLOBBER int send_state,
NOCLOBBER int recv_state)
{
char *myname = "lmtp_loop";
DELIVER_REQUEST *request = state->request;
LMTP_SESSION *session = state->session;
LMTP_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;
int except;
int rec_type;
NOCLOBBER int prev_type = 0;
NOCLOBBER int sndbuffree;
NOCLOBBER int mail_from_rejected;
NOCLOBBER int recv_dot;
#define REWRITE_ADDRESS(dst, src) do { \
if (*(src)) { \
quote_821_local(dst, src); \
} else { \
vstring_strcpy(dst, src); \
} \
} while (0)
#define RETURN(x) do { \
vstring_free(next_command); \
if (survivors) \
myfree((char *) survivors); \
return (x); \
} while (0)
#define SENDER_IS_AHEAD \
(recv_state < send_state || recv_rcpt != send_rcpt)
#define SENDER_IN_WAIT_STATE \
(send_state == LMTP_STATE_DOT || send_state == LMTP_STATE_LAST)
#define SENDING_MAIL \
(recv_state <= LMTP_STATE_DOT)
nrcpt = 0;
next_rcpt = send_rcpt = recv_rcpt = recv_dot = 0;
mail_from_rejected = 0;
sndbuffree = state->sndbufsize;
while (recv_state != LMTP_STATE_LAST) {
switch (send_state) {
default:
msg_panic("%s: bad sender state %d", myname, send_state);
case LMTP_STATE_XFORWARD_NAME_ADDR:
vstring_strcpy(next_command, XFORWARD_CMD);
if (state->features & LMTP_FEATURE_XFORWARD_NAME)
vstring_sprintf_append(next_command, " %s=%s",
XFORWARD_NAME, DEL_REQ_ATTR_AVAIL(request->client_name) ?
request->client_name : XFORWARD_UNAVAILABLE);
if (state->features & LMTP_FEATURE_XFORWARD_ADDR)
vstring_sprintf_append(next_command, " %s=%s",
XFORWARD_ADDR, DEL_REQ_ATTR_AVAIL(request->client_addr) ?
request->client_addr : XFORWARD_UNAVAILABLE);
if (lmtp_send_proto_helo)
next_state = LMTP_STATE_XFORWARD_PROTO_HELO;
else
next_state = LMTP_STATE_MAIL;
break;
case LMTP_STATE_XFORWARD_PROTO_HELO:
vstring_strcpy(next_command, XFORWARD_CMD);
if (state->features & LMTP_FEATURE_XFORWARD_PROTO)
vstring_sprintf_append(next_command, " %s=%s",
XFORWARD_PROTO, DEL_REQ_ATTR_AVAIL(request->client_proto) ?
request->client_proto : XFORWARD_UNAVAILABLE);
if (state->features & LMTP_FEATURE_XFORWARD_HELO)
vstring_sprintf_append(next_command, " %s=%s",
XFORWARD_HELO, DEL_REQ_ATTR_AVAIL(request->client_helo) ?
request->client_helo : XFORWARD_UNAVAILABLE);
next_state = LMTP_STATE_MAIL;
break;
case LMTP_STATE_MAIL:
REWRITE_ADDRESS(state->scratch, request->sender);
vstring_sprintf(next_command, "MAIL FROM:<%s>",
vstring_str(state->scratch));
if (state->features & LMTP_FEATURE_SIZE)
vstring_sprintf_append(next_command, " SIZE=%lu",
request->data_size);
if (state->features & LMTP_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);
}
#ifdef USE_SASL_AUTH
if (var_lmtp_sasl_enable
&& (state->features & LMTP_FEATURE_AUTH)
&& state->sasl_passwd)
vstring_strcat(next_command, " AUTH=<>");
#endif
next_state = LMTP_STATE_RCPT;
break;
case LMTP_STATE_RCPT:
rcpt = request->rcpt_list.info + send_rcpt;
REWRITE_ADDRESS(state->scratch, rcpt->address);
vstring_sprintf(next_command, "RCPT TO:<%s>",
vstring_str(state->scratch));
if ((next_rcpt = send_rcpt + 1) == request->rcpt_list.len)
next_state = DEL_REQ_TRACE_ONLY(request->flags) ?
LMTP_STATE_RSET : LMTP_STATE_DATA;
break;
case LMTP_STATE_DATA:
vstring_strcpy(next_command, "DATA");
next_state = LMTP_STATE_DOT;
break;
case LMTP_STATE_DOT:
vstring_strcpy(next_command, ".");
next_state = var_lmtp_cache_conn ?
LMTP_STATE_LAST : LMTP_STATE_QUIT;
break;
case LMTP_STATE_ABORT:
msg_panic("%s: sender abort state", myname);
case LMTP_STATE_RSET:
vstring_strcpy(next_command, "RSET");
next_state = LMTP_STATE_LAST;
break;
case LMTP_STATE_QUIT:
vstring_strcpy(next_command, "QUIT");
next_state = LMTP_STATE_LAST;
break;
case LMTP_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 < LMTP_STATE_XFORWARD_NAME_ADDR
|| recv_state > LMTP_STATE_QUIT)
msg_panic("%s: bad receiver state %d (sender state %d)",
myname, recv_state, send_state);
smtp_timeout_setup(state->session->stream,
*xfer_timeouts[recv_state]);
if ((except = vstream_setjmp(state->session->stream)) != 0)
RETURN(SENDING_MAIL ? lmtp_stream_except(state, except,
xfer_states[recv_state]) : -1);
resp = lmtp_chat_resp(state);
switch (recv_state) {
case LMTP_STATE_XFORWARD_NAME_ADDR:
if (resp->code / 100 != 2)
msg_warn("host %s said: %s (in reply to %s)",
session->namaddr,
translit(resp->str, "\n", " "),
xfer_request[LMTP_STATE_XFORWARD_NAME_ADDR]);
if (lmtp_send_proto_helo)
recv_state = LMTP_STATE_XFORWARD_PROTO_HELO;
else
recv_state = LMTP_STATE_MAIL;
break;
case LMTP_STATE_XFORWARD_PROTO_HELO:
if (resp->code / 100 != 2)
msg_warn("host %s said: %s (in reply to %s)",
session->namaddr,
translit(resp->str, "\n", " "),
xfer_request[LMTP_STATE_XFORWARD_PROTO_HELO]);
recv_state = LMTP_STATE_MAIL;
break;
case LMTP_STATE_MAIL:
if (resp->code / 100 != 2) {
lmtp_mesg_fail(state, resp->code,
"host %s said: %s (in reply to %s)",
session->namaddr,
translit(resp->str, "\n", " "),
xfer_request[LMTP_STATE_MAIL]);
mail_from_rejected = 1;
}
recv_state = LMTP_STATE_RCPT;
break;
case LMTP_STATE_RCPT:
if (!mail_from_rejected) {
#ifdef notdef
if (resp->code == 552)
resp->code = 452;
#endif
rcpt = request->rcpt_list.info + recv_rcpt;
if (resp->code / 100 == 2) {
if (survivors == 0)
survivors = (int *)
mymalloc(request->rcpt_list.len
* sizeof(int));
survivors[nrcpt++] = recv_rcpt;
if (DEL_REQ_TRACE_ONLY(request->flags)
&& sent(DEL_REQ_TRACE_FLAGS(request->flags),
request->queue_id, rcpt->orig_addr,
rcpt->address, rcpt->offset,
session->namaddr, request->arrival_time,
"%s",
translit(resp->str, "\n", " ")) == 0) {
if (request->flags & DEL_REQ_FLAG_SUCCESS)
deliver_completed(state->src, rcpt->offset);
rcpt->offset = 0;
}
} else {
lmtp_rcpt_fail(state, resp->code, rcpt,
"host %s said: %s (in reply to %s)",
session->namaddr,
translit(resp->str, "\n", " "),
xfer_request[LMTP_STATE_RCPT]);
rcpt->offset = 0;
}
}
if (++recv_rcpt == request->rcpt_list.len)
recv_state = DEL_REQ_TRACE_ONLY(request->flags) ?
LMTP_STATE_ABORT : LMTP_STATE_DATA;
break;
case LMTP_STATE_DATA:
if (resp->code / 100 != 3) {
if (nrcpt > 0)
lmtp_mesg_fail(state, resp->code,
"host %s said: %s (in reply to %s)",
session->namaddr,
translit(resp->str, "\n", " "),
xfer_request[LMTP_STATE_DATA]);
nrcpt = -1;
}
recv_state = LMTP_STATE_DOT;
break;
case LMTP_STATE_DOT:
if (nrcpt > 0) {
rcpt = request->rcpt_list.info + survivors[recv_dot];
if (resp->code / 100 == 2) {
if (rcpt->offset) {
if (sent(DEL_REQ_TRACE_FLAGS(request->flags),
request->queue_id, rcpt->orig_addr,
rcpt->address, rcpt->offset,
session->namaddr,
request->arrival_time,
"%s", resp->str) == 0) {
if (request->flags & DEL_REQ_FLAG_SUCCESS)
deliver_completed(state->src, rcpt->offset);
rcpt->offset = 0;
}
}
} else {
lmtp_rcpt_fail(state, resp->code, rcpt,
"host %s said: %s (in reply to %s)",
session->namaddr,
translit(resp->str, "\n", " "),
xfer_request[LMTP_STATE_DOT]);
rcpt->offset = 0;
}
}
if (msg_verbose)
msg_info("%s: recv_dot = %d", myname, recv_dot);
if (++recv_dot >= nrcpt) {
if (msg_verbose)
msg_info("%s: finished . command", myname);
recv_state = var_lmtp_skip_quit_resp || var_lmtp_cache_conn ?
LMTP_STATE_LAST : LMTP_STATE_QUIT;
}
break;
case LMTP_STATE_ABORT:
recv_state = var_lmtp_skip_quit_resp || var_lmtp_cache_conn ?
LMTP_STATE_LAST : LMTP_STATE_QUIT;
break;
case LMTP_STATE_RSET:
recv_state = LMTP_STATE_LAST;
break;
case LMTP_STATE_QUIT:
recv_state = LMTP_STATE_LAST;
break;
}
}
sndbuffree = state->sndbufsize;
if ((send_state == LMTP_STATE_RCPT && mail_from_rejected)
|| (send_state == LMTP_STATE_DATA && nrcpt == 0)
|| (send_state == LMTP_STATE_DOT && nrcpt < 0)) {
send_state = recv_state = LMTP_STATE_ABORT;
send_rcpt = recv_rcpt = 0;
vstring_strcpy(next_command, "RSET");
next_state = var_lmtp_cache_conn ?
LMTP_STATE_LAST : LMTP_STATE_QUIT;
next_rcpt = 0;
}
}
if (send_state == LMTP_STATE_LAST)
continue;
if (send_state == LMTP_STATE_DOT && nrcpt > 0) {
smtp_timeout_setup(state->session->stream,
var_lmtp_data1_tmout);
if ((except = vstream_setjmp(state->session->stream)) != 0)
RETURN(lmtp_stream_except(state, except,
"sending message body"));
if (vstream_fseek(state->src, request->data_offset, SEEK_SET) < 0)
msg_fatal("seek queue file: %m");
while ((rec_type = rec_get(state->src, state->scratch, 0)) > 0) {
if (rec_type != REC_TYPE_NORM && rec_type != REC_TYPE_CONT)
break;
if (prev_type != REC_TYPE_CONT)
if (vstring_str(state->scratch)[0] == '.')
smtp_fputc('.', session->stream);
if (rec_type == REC_TYPE_CONT)
smtp_fwrite(vstring_str(state->scratch),
VSTRING_LEN(state->scratch),
session->stream);
else
smtp_fputs(vstring_str(state->scratch),
VSTRING_LEN(state->scratch),
session->stream);
prev_type = rec_type;
}
if (prev_type == REC_TYPE_CONT)
smtp_fputs("", 0, session->stream);
if (vstream_ferror(state->src))
msg_fatal("queue file read error");
if (rec_type != REC_TYPE_XTRA)
RETURN(mark_corrupt(state->src));
}
if (sndbuffree > 0)
sndbuffree -= VSTRING_LEN(next_command) + 2;
lmtp_chat_cmd(state, "%s", vstring_str(next_command));
send_state = next_state;
send_rcpt = next_rcpt;
}
RETURN(0);
}
int lmtp_xfer(LMTP_STATE *state)
{
DELIVER_REQUEST *request = state->request;
int start;
int send_name_addr;
send_name_addr =
var_lmtp_send_xforward
&& (((state->features & LMTP_FEATURE_XFORWARD_NAME)
&& DEL_REQ_ATTR_AVAIL(request->client_name))
|| ((state->features & LMTP_FEATURE_XFORWARD_ADDR)
&& DEL_REQ_ATTR_AVAIL(request->client_addr)));
lmtp_send_proto_helo =
var_lmtp_send_xforward
&& (((state->features & LMTP_FEATURE_XFORWARD_PROTO)
&& DEL_REQ_ATTR_AVAIL(request->client_proto))
|| ((state->features & LMTP_FEATURE_XFORWARD_HELO)
&& DEL_REQ_ATTR_AVAIL(request->client_helo)));
if (send_name_addr)
start = LMTP_STATE_XFORWARD_NAME_ADDR;
else if (lmtp_send_proto_helo)
start = LMTP_STATE_XFORWARD_PROTO_HELO;
else
start = LMTP_STATE_MAIL;
return (lmtp_loop(state, start, start));
}
int lmtp_rset(LMTP_STATE *state)
{
return (lmtp_loop(state, LMTP_STATE_RSET, LMTP_STATE_RSET));
}
int lmtp_quit(LMTP_STATE *state)
{
return (lmtp_loop(state, LMTP_STATE_QUIT, var_lmtp_skip_quit_resp ?
LMTP_STATE_LAST : LMTP_STATE_QUIT));
}