625 lines
19 KiB
C
625 lines
19 KiB
C
/*++
|
|
/* NAME
|
|
/* smtp_proto 3
|
|
/* SUMMARY
|
|
/* client SMTP protocol
|
|
/* SYNOPSIS
|
|
/* #include "smtp.h"
|
|
/*
|
|
/* int smtp_helo(state)
|
|
/* SMTP_STATE *state;
|
|
/*
|
|
/* int smtp_xfer(state)
|
|
/* SMTP_STATE *state;
|
|
/* DESCRIPTION
|
|
/* This module implements the client side of the SMTP protocol.
|
|
/*
|
|
/* smtp_helo() performs the initial handshake with the SMTP server.
|
|
/*
|
|
/* smtp_xfer() sends message envelope information followed by the
|
|
/* message data, and finishes the SMTP conversation. These operations
|
|
/* are combined in one function, in order to implement SMTP pipelining.
|
|
/* Recipients are marked as "done" in the mail queue file when
|
|
/* bounced or delivered. The message delivery status is updated
|
|
/* accordingly.
|
|
/* DIAGNOSTICS
|
|
/* smtp_helo() and smtp_xfer() return 0 in case of success, -1 in case
|
|
/* of failure. For smtp_xfer(), success means the ability to perform
|
|
/* an SMTP conversation, not necessarily the ability to deliver mail.
|
|
/*
|
|
/* Warnings: corrupt message file. A corrupt message is marked
|
|
/* as "corrupt" by changing its queue file permissions.
|
|
/* BUGS
|
|
/* Some SMTP servers will abort when the number of recipients
|
|
/* for one message exceeds their capacity. This behavior violates
|
|
/* the SMTP protocol.
|
|
/* The only way around this is to limit the number of recipients
|
|
/* per transaction to an artificially-low value.
|
|
/* SEE ALSO
|
|
/* smtp(3h) internal data structures
|
|
/* smtp_chat(3) query/reply SMTP support
|
|
/* smtp_trouble(3) error handlers
|
|
/* LICENSE
|
|
/* .ad
|
|
/* .fi
|
|
/* The Secure Mailer license must be distributed with this software.
|
|
/* AUTHOR(S)
|
|
/* Wietse Venema
|
|
/* IBM T.J. Watson Research
|
|
/* P.O. Box 704
|
|
/* Yorktown Heights, NY 10598, USA
|
|
/*
|
|
/* Pipelining code in cooperation with:
|
|
/* Jon Ribbens
|
|
/* Oaktree Internet Solutions Ltd.,
|
|
/* Internet House,
|
|
/* Canal Basin,
|
|
/* Coventry,
|
|
/* CV1 4LY, United Kingdom.
|
|
/*
|
|
/*--*/
|
|
|
|
/* System library. */
|
|
|
|
#include <sys_defs.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/socket.h> /* shutdown(2) */
|
|
#include <setjmp.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <stdlib.h> /* 44BSD stdarg.h uses abort() */
|
|
#include <stdarg.h>
|
|
|
|
#ifdef STRCASECMP_IN_STRINGS_H
|
|
#include <strings.h>
|
|
#endif
|
|
|
|
/* Utility library. */
|
|
|
|
#include <msg.h>
|
|
#include <vstring.h>
|
|
#include <vstream.h>
|
|
#include <vstring_vstream.h>
|
|
#include <stringops.h>
|
|
#include <mymalloc.h>
|
|
|
|
/* Global library. */
|
|
|
|
#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>
|
|
|
|
/* Application-specific. */
|
|
|
|
#include "smtp.h"
|
|
#include "quote_821_local.h"
|
|
|
|
/*
|
|
* Sender and receiver state. A session does not necessarily go through a
|
|
* linear progression, but states are guaranteed to not jump backwards.
|
|
* Normal sessions go from MAIL->RCPT->DATA->DOT->QUIT->LAST. The states
|
|
* MAIL, RCPT, and DATA may also be followed by ABORT->QUIT->LAST.
|
|
*
|
|
* By default, the receiver skips the QUIT response. Some SMTP servers
|
|
* disconnect after responding to ".", and some SMTP servers wait before
|
|
* responding to QUIT.
|
|
*/
|
|
#define SMTP_STATE_MAIL 0
|
|
#define SMTP_STATE_RCPT 1
|
|
#define SMTP_STATE_DATA 2
|
|
#define SMTP_STATE_DOT 3
|
|
#define SMTP_STATE_ABORT 4
|
|
#define SMTP_STATE_QUIT 5
|
|
#define SMTP_STATE_LAST 6
|
|
|
|
int *xfer_timeouts[SMTP_STATE_LAST] = {
|
|
&var_smtp_mail_tmout,
|
|
&var_smtp_rcpt_tmout,
|
|
&var_smtp_data0_tmout,
|
|
&var_smtp_data2_tmout,
|
|
&var_smtp_quit_tmout,
|
|
&var_smtp_quit_tmout,
|
|
};
|
|
|
|
char *xfer_states[SMTP_STATE_LAST] = {
|
|
"sending MAIL FROM",
|
|
"sending RCPT TO",
|
|
"sending DATA command",
|
|
"sending end of data -- message may be sent more than once",
|
|
"sending final RSET",
|
|
"sending QUIT",
|
|
};
|
|
|
|
/* smtp_helo - perform initial handshake with SMTP server */
|
|
|
|
int smtp_helo(SMTP_STATE *state)
|
|
{
|
|
SMTP_SESSION *session = state->session;
|
|
DELIVER_REQUEST *request = state->request;
|
|
SMTP_RESP *resp;
|
|
int except;
|
|
char *lines;
|
|
char *words;
|
|
char *word;
|
|
int n;
|
|
|
|
/*
|
|
* Prepare for disaster.
|
|
*/
|
|
smtp_timeout_setup(state->session->stream, var_smtp_helo_tmout);
|
|
if ((except = setjmp(smtp_timeout_buf)) != 0)
|
|
return (smtp_stream_except(state, except, "sending HELO"));
|
|
|
|
/*
|
|
* Read and parse the server's SMTP greeting banner.
|
|
*/
|
|
if (((resp = smtp_chat_resp(state))->code / 100) != 2)
|
|
return (smtp_site_fail(state, resp->code,
|
|
"host %s refused to talk to me: %s",
|
|
session->namaddr, translit(resp->str, "\n", " ")));
|
|
|
|
/*
|
|
* See if we are talking to ourself. This should not be possible with the
|
|
* way we implement DNS lookups. However, people are known to sometimes
|
|
* screw up the naming service. And, mailer loops are still possible when
|
|
* our own mailer routing tables are mis-configured.
|
|
*/
|
|
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) {
|
|
msg_warn("host %s greeted me with my own hostname %s",
|
|
session->namaddr, var_myhostname);
|
|
} else if (strcasecmp(word, "ESMTP") == 0)
|
|
state->features |= SMTP_FEATURE_ESMTP;
|
|
}
|
|
|
|
/*
|
|
* Return the compliment. Fall back to SMTP if our ESMTP recognition
|
|
* heuristic failed.
|
|
*/
|
|
if (state->features & SMTP_FEATURE_ESMTP) {
|
|
smtp_chat_cmd(state, "EHLO %s", var_myhostname);
|
|
if ((resp = smtp_chat_resp(state))->code / 100 != 2)
|
|
state->features &= ~SMTP_FEATURE_ESMTP;
|
|
}
|
|
if ((state->features & SMTP_FEATURE_ESMTP) == 0) {
|
|
smtp_chat_cmd(state, "HELO %s", var_myhostname);
|
|
if ((resp = smtp_chat_resp(state))->code / 100 != 2)
|
|
return (smtp_site_fail(state, resp->code,
|
|
"host %s refused to talk to me: %s",
|
|
session->namaddr,
|
|
translit(resp->str, "\n", " ")));
|
|
}
|
|
|
|
/*
|
|
* Pick up some useful features offered by the SMTP server. XXX Until we
|
|
* have a portable routine to convert from string to off_t with proper
|
|
* overflow detection, ignore the message size limit advertised by the
|
|
* SMTP server. Otherwise, we might do the wrong thing when the server
|
|
* advertises a really huge message size limit.
|
|
*/
|
|
lines = resp->str;
|
|
while ((words = mystrtok(&lines, "\n")) != 0) {
|
|
if (mystrtok(&words, "- ") && (word = mystrtok(&words, " \t")) != 0) {
|
|
if (strcasecmp(word, "8BITMIME") == 0)
|
|
state->features |= SMTP_FEATURE_8BITMIME;
|
|
else if (strcasecmp(word, "PIPELINING") == 0)
|
|
state->features |= SMTP_FEATURE_PIPELINING;
|
|
else if (strcasecmp(word, "SIZE") == 0)
|
|
state->features |= SMTP_FEATURE_SIZE;
|
|
else if (strcasecmp(word, var_myhostname) == 0) {
|
|
msg_warn("host %s replied to HELO/EHLO with my own hostname %s",
|
|
session->namaddr, var_myhostname);
|
|
return (smtp_site_fail(state, session->best ? 550 : 450,
|
|
"mail for %s loops back to myself",
|
|
request->nexthop));
|
|
}
|
|
}
|
|
}
|
|
if (msg_verbose)
|
|
msg_info("server features: 0x%x", state->features);
|
|
return (0);
|
|
}
|
|
|
|
/* smtp_xfer - send a batch of envelope information and the message data */
|
|
|
|
int smtp_xfer(SMTP_STATE *state)
|
|
{
|
|
char *myname = "smtp_xfer";
|
|
DELIVER_REQUEST *request = state->request;
|
|
SMTP_SESSION *session = state->session;
|
|
SMTP_RESP *resp;
|
|
RECIPIENT *rcpt;
|
|
VSTRING *next_command = vstring_alloc(100);
|
|
int next_state;
|
|
int next_rcpt;
|
|
int send_state;
|
|
int recv_state;
|
|
int send_rcpt;
|
|
int recv_rcpt;
|
|
int nrcpt;
|
|
int except;
|
|
int rec_type;
|
|
int prev_type = 0;
|
|
int sndbufsize;
|
|
int sndbuffree;
|
|
SOCKOPT_SIZE optlen = sizeof(sndbufsize);
|
|
int mail_from_rejected;
|
|
|
|
/*
|
|
* Macros for readability.
|
|
*/
|
|
#define REWRITE_ADDRESS(addr) do { \
|
|
if (*(addr)) { \
|
|
quote_821_local(state->scratch, addr); \
|
|
smtp_unalias_addr(state->scratch2, vstring_str(state->scratch)); \
|
|
myfree(addr); \
|
|
addr = mystrdup(vstring_str(state->scratch2)); \
|
|
} \
|
|
} while (0)
|
|
|
|
#define RETURN(x) do { vstring_free(next_command); 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)
|
|
|
|
/*
|
|
* We use SMTP command pipelining if the server said it supported it.
|
|
* Since we use blocking I/O, RFC 2197 says that we should inspect the
|
|
* TCP window size and not send more than this amount of information.
|
|
* Unfortunately this information is not available using the sockets
|
|
* interface. However, we *can* get the TCP send buffer size on the local
|
|
* TCP/IP stack. We should be able to fill this buffer without being
|
|
* blocked, and then the kernel will effectively do non-blocking I/O for
|
|
* us by automatically writing out the contents of its send buffer while
|
|
* we are reading in the responses. In addition to TCP buffering we have
|
|
* to be aware of application-level buffering by the vstream module,
|
|
* which is limited to a couple kbytes.
|
|
*/
|
|
if (state->features & SMTP_FEATURE_PIPELINING) {
|
|
if (getsockopt(vstream_fileno(state->session->stream), SOL_SOCKET,
|
|
SO_SNDBUF, (char *) &sndbufsize, &optlen) < 0)
|
|
msg_fatal("%s: getsockopt: %m", myname);
|
|
if (msg_verbose)
|
|
msg_info("Using ESMTP PIPELINING, TCP send buffer size is %d",
|
|
sndbufsize);
|
|
} else {
|
|
sndbufsize = 0;
|
|
}
|
|
sndbuffree = sndbufsize;
|
|
|
|
/*
|
|
* Pipelining support requires two loops: one loop for sending and one
|
|
* for receiving. Each loop has its own independent state. Most of the
|
|
* time the sender can run ahead of the receiver by as much as the TCP
|
|
* send buffer permits. There are only two places where the sender must
|
|
* wait for status information from the receiver: once after sending DATA
|
|
* and once after sending QUIT.
|
|
*
|
|
* The sender state advances until the TCP send buffer would overflow, or
|
|
* until the sender needs status information from the receiver. At that
|
|
* point the receiver starts processing responses. Once the receiver has
|
|
* caught up with the sender, the sender resumes sending commands. If the
|
|
* receiver detects a serious problem (MAIL FROM rejected, all RCPT TO
|
|
* commands rejected, DATA rejected) it forces the sender to abort the
|
|
* SMTP dialog with RSET and QUIT.
|
|
*/
|
|
nrcpt = 0;
|
|
recv_state = send_state = SMTP_STATE_MAIL;
|
|
next_rcpt = send_rcpt = recv_rcpt = 0;
|
|
mail_from_rejected = 0;
|
|
|
|
while (recv_state != SMTP_STATE_LAST) {
|
|
|
|
/*
|
|
* Build the next command.
|
|
*/
|
|
switch (send_state) {
|
|
|
|
/*
|
|
* Sanity check.
|
|
*/
|
|
default:
|
|
msg_panic("%s: bad sender state %d", myname, send_state);
|
|
|
|
/*
|
|
* Build the MAIL FROM command.
|
|
*/
|
|
case SMTP_STATE_MAIL:
|
|
if (*request->sender)
|
|
if (var_disable_dns == 0)
|
|
REWRITE_ADDRESS(request->sender);
|
|
vstring_sprintf(next_command, "MAIL FROM:<%s>", request->sender);
|
|
if (state->features & SMTP_FEATURE_SIZE)
|
|
vstring_sprintf_append(next_command, " SIZE=%lu",
|
|
request->data_size);
|
|
next_state = SMTP_STATE_RCPT;
|
|
break;
|
|
|
|
/*
|
|
* Build one RCPT TO command before we have seen the MAIL FROM
|
|
* response.
|
|
*/
|
|
case SMTP_STATE_RCPT:
|
|
rcpt = request->rcpt_list.info + send_rcpt;
|
|
if (var_disable_dns == 0)
|
|
REWRITE_ADDRESS(rcpt->address);
|
|
vstring_sprintf(next_command, "RCPT TO:<%s>", rcpt->address);
|
|
if ((next_rcpt = send_rcpt + 1) == request->rcpt_list.len)
|
|
next_state = SMTP_STATE_DATA;
|
|
break;
|
|
|
|
/*
|
|
* Build the DATA command before we have seen all the RCPT TO
|
|
* responses.
|
|
*/
|
|
case SMTP_STATE_DATA:
|
|
vstring_strcpy(next_command, "DATA");
|
|
next_state = SMTP_STATE_DOT;
|
|
break;
|
|
|
|
/*
|
|
* Build the "." command before we have seen the DATA response.
|
|
*/
|
|
case SMTP_STATE_DOT:
|
|
vstring_strcpy(next_command, ".");
|
|
next_state = SMTP_STATE_QUIT;
|
|
break;
|
|
|
|
/*
|
|
* Can't happen. The SMTP_STATE_ABORT sender state is entered by
|
|
* the receiver and is left before the bottom of the main loop.
|
|
*/
|
|
case SMTP_STATE_ABORT:
|
|
msg_panic("%s: sender abort state", myname);
|
|
|
|
/*
|
|
* Build the QUIT command before we have seen the "." or RSET
|
|
* response.
|
|
*/
|
|
case SMTP_STATE_QUIT:
|
|
vstring_strcpy(next_command, "QUIT");
|
|
next_state = SMTP_STATE_LAST;
|
|
break;
|
|
|
|
/*
|
|
* The final sender state has no action associated with it.
|
|
*/
|
|
case SMTP_STATE_LAST:
|
|
break;
|
|
}
|
|
VSTRING_TERMINATE(next_command);
|
|
|
|
/*
|
|
* Process responses until the receiver has caught up. Vstreams
|
|
* automatically flush buffered output when reading new data.
|
|
*/
|
|
if (SENDER_IN_WAIT_STATE
|
|
|| (SENDER_IS_AHEAD && VSTRING_LEN(next_command) + 2 > sndbuffree)) {
|
|
while (SENDER_IS_AHEAD) {
|
|
|
|
/*
|
|
* Sanity check.
|
|
*/
|
|
if (recv_state < SMTP_STATE_MAIL
|
|
|| recv_state > SMTP_STATE_QUIT)
|
|
msg_panic("%s: bad receiver state %d (sender state %d)",
|
|
myname, recv_state, send_state);
|
|
|
|
/*
|
|
* Receive the next server response. Use the proper timeout,
|
|
* and log the proper client state in case of trouble.
|
|
*/
|
|
smtp_timeout_setup(state->session->stream,
|
|
*xfer_timeouts[recv_state]);
|
|
if ((except = setjmp(smtp_timeout_buf)) != 0)
|
|
RETURN(smtp_stream_except(state, except,
|
|
xfer_states[recv_state]));
|
|
resp = smtp_chat_resp(state);
|
|
|
|
/*
|
|
* Process the response.
|
|
*/
|
|
switch (recv_state) {
|
|
|
|
/*
|
|
* Process the MAIL FROM response. When the server
|
|
* rejects the sender, set the mail_from_rejected flag so
|
|
* that the receiver may apply a course correction.
|
|
*/
|
|
case SMTP_STATE_MAIL:
|
|
if (resp->code / 100 != 2) {
|
|
smtp_mesg_fail(state, resp->code,
|
|
"host %s said: %s", session->namaddr,
|
|
translit(resp->str, "\n", " "));
|
|
mail_from_rejected = 1;
|
|
}
|
|
recv_state = SMTP_STATE_RCPT;
|
|
break;
|
|
|
|
/*
|
|
* Process one RCPT TO response. If MAIL FROM was
|
|
* rejected, ignore RCPT TO responses: all recipients are
|
|
* dead already. When all recipients are rejected the
|
|
* receiver may apply a course correction.
|
|
*/
|
|
case SMTP_STATE_RCPT:
|
|
if (!mail_from_rejected) {
|
|
if (resp->code / 100 == 2) {
|
|
++nrcpt;
|
|
} else {
|
|
rcpt = request->rcpt_list.info + recv_rcpt;
|
|
smtp_rcpt_fail(state, resp->code, rcpt,
|
|
"host %s said: %s", session->namaddr,
|
|
translit(resp->str, "\n", " "));
|
|
rcpt->offset = 0; /* in case deferred */
|
|
}
|
|
}
|
|
if (++recv_rcpt == request->rcpt_list.len)
|
|
recv_state = SMTP_STATE_DATA;
|
|
break;
|
|
|
|
/*
|
|
* Process the DATA response. When the server rejects
|
|
* DATA, set nrcpt to a negative value so that the
|
|
* receiver can apply a course correction.
|
|
*/
|
|
case SMTP_STATE_DATA:
|
|
if (resp->code / 100 != 3) {
|
|
if (nrcpt > 0)
|
|
smtp_mesg_fail(state, resp->code,
|
|
"host %s said: %s", session->namaddr,
|
|
translit(resp->str, "\n", " "));
|
|
nrcpt = -1;
|
|
}
|
|
recv_state = SMTP_STATE_DOT;
|
|
break;
|
|
|
|
/*
|
|
* Process the end of message response. Ignore the
|
|
* response when no recipient was accepted: all
|
|
* recipients are dead already, and the next receiver
|
|
* state is SMTP_STATE_QUIT regardless. Otherwise, if the
|
|
* message transfer fails, bounce all remaining
|
|
* recipients, else cross off the recipients that were
|
|
* delivered.
|
|
*/
|
|
case SMTP_STATE_DOT:
|
|
if (nrcpt > 0) {
|
|
if (resp->code / 100 != 2) {
|
|
smtp_mesg_fail(state, resp->code,
|
|
"host %s said: %s",
|
|
session->namaddr,
|
|
translit(resp->str, "\n", " "));
|
|
} else {
|
|
for (nrcpt = 0; nrcpt < recv_rcpt; nrcpt++) {
|
|
rcpt = request->rcpt_list.info + nrcpt;
|
|
if (rcpt->offset) {
|
|
sent(request->queue_id, rcpt->address,
|
|
session->namaddr,
|
|
request->arrival_time, "%s",
|
|
resp->str);
|
|
deliver_completed(state->src, rcpt->offset);
|
|
rcpt->offset = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
recv_state = (var_skip_quit_resp ?
|
|
SMTP_STATE_LAST : SMTP_STATE_QUIT);
|
|
break;
|
|
|
|
/*
|
|
* Ignore the RSET response.
|
|
*/
|
|
case SMTP_STATE_ABORT:
|
|
recv_state = (var_skip_quit_resp ?
|
|
SMTP_STATE_LAST : SMTP_STATE_QUIT);
|
|
break;
|
|
|
|
/*
|
|
* Ignore the QUIT response.
|
|
*/
|
|
case SMTP_STATE_QUIT:
|
|
recv_state = SMTP_STATE_LAST;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* At this point, the sender and receiver are fully synchronized,
|
|
* so that the entire TCP send buffer becomes available again.
|
|
*/
|
|
sndbuffree = sndbufsize;
|
|
|
|
/*
|
|
* We know the server response to every command that was sent.
|
|
* Apply a course correction if necessary: the sender wants to
|
|
* send RCPT TO but MAIL FROM was rejected; the sender wants to
|
|
* send DATA but all recipients were rejected; the sender wants
|
|
* to deliver the message but DATA was rejected.
|
|
*/
|
|
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");
|
|
next_state = SMTP_STATE_QUIT;
|
|
next_rcpt = 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Make the next sender state the current sender state.
|
|
*/
|
|
if (send_state == SMTP_STATE_LAST)
|
|
continue;
|
|
|
|
/*
|
|
* Special case if the server accepted the DATA command. If the
|
|
* server accepted at least one recipient send the entire message.
|
|
* Otherwise, just send "." as per RFC 2197.
|
|
*/
|
|
if (send_state == SMTP_STATE_DOT && nrcpt > 0) {
|
|
smtp_timeout_setup(state->session->stream,
|
|
var_smtp_data1_tmout);
|
|
if ((except = setjmp(smtp_timeout_buf)) != 0)
|
|
RETURN(smtp_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) /* missing newline at end */
|
|
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));
|
|
}
|
|
|
|
/*
|
|
* Copy the next command to the buffer and update the sender state.
|
|
*/
|
|
if (sndbuffree > 0)
|
|
sndbuffree -= VSTRING_LEN(next_command) + 2;
|
|
smtp_chat_cmd(state, "%s", vstring_str(next_command));
|
|
send_state = next_state;
|
|
send_rcpt = next_rcpt;
|
|
}
|
|
|
|
RETURN(0);
|
|
}
|