I've not found an equivalent in OpenSSL of gnutls_record_cork() nor gnutls_record_check_pending() yet.
20 files changed:
AND make fixes to the calling application, such as using `--` to stop
processing options.
AND make fixes to the calling application, such as using `--` to stop
processing options.
+JH/13 Do pipelining under TLS, with GnuTLS. Previously, although safe, no
+ advantage was taken. Now take care to pack both (client) MAIL,RCPT,DATA,
+ and (server) responses to those, into a single TLS record each way (this
+ usually means a single packet). As a side issue, we can now detect
+ over-eager senders in non-pipelined mode.
+
Exim version 4.89
-----------------
Exim version 4.89
-----------------
-smtp_printf("334 %s\r\n", b64encode(challenge, challen));
+smtp_printf("334 %s\r\n", FALSE, b64encode(challenge, challen));
while ((c = receive_getc(GETC_BUFFER_UNLIMITED)) != '\n' && c != EOF)
{
if (p >= big_buffer_size - 1) return BAD64;
while ((c = receive_getc(GETC_BUFFER_UNLIMITED)) != '\n' && c != EOF)
{
if (p >= big_buffer_size - 1) return BAD64;
-smtp_printf("334 %s\r\n", challenge);
+smtp_printf("334 %s\r\n", FALSE, challenge);
while ((c = receive_getc(GETC_BUFFER_UNLIMITED)) != '\n' && c != EOF)
{
if (p >= big_buffer_size - 1) return BAD64;
while ((c = receive_getc(GETC_BUFFER_UNLIMITED)) != '\n' && c != EOF)
{
if (p >= big_buffer_size - 1) return BAD64;
uschar *emsg = (was_errno <= 0)? US"" :
string_sprintf(": %s", strerror(was_errno));
log_write(0, LOG_MAIN|LOG_PANIC, "%s%s", log_msg, emsg);
uschar *emsg = (was_errno <= 0)? US"" :
string_sprintf(": %s", strerror(was_errno));
log_write(0, LOG_MAIN|LOG_PANIC, "%s%s", log_msg, emsg);
-if (smtp_out != NULL) smtp_printf("421 %s\r\n", smtp_msg);
+if (smtp_out != NULL) smtp_printf("421 %s\r\n", FALSE, smtp_msg);
{
log_write(0, LOG_MAIN | ((errno == ECONNRESET)? 0 : LOG_PANIC),
"getsockname() failed: %s", strerror(errno));
{
log_write(0, LOG_MAIN | ((errno == ECONNRESET)? 0 : LOG_PANIC),
"getsockname() failed: %s", strerror(errno));
- smtp_printf("421 Local problem: getsockname() failed; please try again later\r\n");
+ smtp_printf("421 Local problem: getsockname() failed; please try again later\r\n", FALSE);
DEBUG(D_any) debug_printf("rejecting SMTP connection: count=%d max=%d\n",
smtp_accept_count, smtp_accept_max);
smtp_printf("421 Too many concurrent SMTP connections; "
DEBUG(D_any) debug_printf("rejecting SMTP connection: count=%d max=%d\n",
smtp_accept_count, smtp_accept_max);
smtp_printf("421 Too many concurrent SMTP connections; "
- "please try again later.\r\n");
+ "please try again later.\r\n", FALSE);
log_write(L_connection_reject,
LOG_MAIN, "Connection from %s refused: too many connections",
whofrom);
log_write(L_connection_reject,
LOG_MAIN, "Connection from %s refused: too many connections",
whofrom);
{
DEBUG(D_any) debug_printf("rejecting SMTP connection: load average = %.2f\n",
(double)load_average/1000.0);
{
DEBUG(D_any) debug_printf("rejecting SMTP connection: load average = %.2f\n",
(double)load_average/1000.0);
- smtp_printf("421 Too much load; please try again later.\r\n");
+ smtp_printf("421 Too much load; please try again later.\r\n", FALSE);
log_write(L_connection_reject,
LOG_MAIN, "Connection from %s refused: load average = %.2f",
whofrom, (double)load_average/1000.0);
log_write(L_connection_reject,
LOG_MAIN, "Connection from %s refused: load average = %.2f",
whofrom, (double)load_average/1000.0);
"IP address: count=%d max=%d\n",
host_accept_count, max_for_this_host);
smtp_printf("421 Too many concurrent SMTP connections "
"IP address: count=%d max=%d\n",
host_accept_count, max_for_this_host);
smtp_printf("421 Too many concurrent SMTP connections "
- "from this IP address; please try again later.\r\n");
+ "from this IP address; please try again later.\r\n", FALSE);
log_write(L_connection_reject,
LOG_MAIN, "Connection from %s refused: too many connections "
"from that IP address", whofrom);
log_write(L_connection_reject,
LOG_MAIN, "Connection from %s refused: too many connections "
"from that IP address", whofrom);
"(smtp_active_hostname): %s", raw_active_hostname,
expand_string_message);
smtp_printf("421 Local configuration error; "
"(smtp_active_hostname): %s", raw_active_hostname,
expand_string_message);
smtp_printf("421 Local configuration error; "
- "please try again later.\r\n");
+ "please try again later.\r\n", FALSE);
mac_smtp_fflush();
search_tidyup();
_exit(EXIT_FAILURE);
mac_smtp_fflush();
search_tidyup();
_exit(EXIT_FAILURE);
{
#ifdef SUPPORT_TLS
wwritten = tls_out.active == out_fd
{
#ifdef SUPPORT_TLS
wwritten = tls_out.active == out_fd
- ? tls_write(FALSE, p, sread)
+ ? tls_write(FALSE, p, sread, FALSE)
: write(out_fd, CS p, sread);
#else
wwritten = write(out_fd, CS p, sread);
: write(out_fd, CS p, sread);
#else
wwritten = write(out_fd, CS p, sread);
# endif
uschar **);
extern void tls_close(BOOL, BOOL);
# endif
uschar **);
extern void tls_close(BOOL, BOOL);
+extern BOOL tls_could_read(void);
extern int tls_export_cert(uschar *, size_t, void *);
extern int tls_feof(void);
extern int tls_ferror(void);
extern int tls_export_cert(uschar *, size_t, void *);
extern int tls_feof(void);
extern int tls_ferror(void);
extern int tls_server_start(const uschar *, uschar **);
extern BOOL tls_smtp_buffered(void);
extern int tls_ungetc(int);
extern int tls_server_start(const uschar *, uschar **);
extern BOOL tls_smtp_buffered(void);
extern int tls_ungetc(int);
-extern int tls_write(BOOL, const uschar *, size_t);
+extern int tls_write(BOOL, const uschar *, size_t, BOOL);
extern uschar *tls_validate_require_cipher(void);
extern void tls_version_report(FILE *);
# ifndef USE_GNUTLS
extern uschar *tls_validate_require_cipher(void);
extern void tls_version_report(FILE *);
# ifndef USE_GNUTLS
extern BOOL receive_remove_recipient(uschar *);
extern uschar *rfc2047_decode(uschar *, BOOL, uschar *, int, int *, uschar **);
extern int smtp_fflush(void);
extern BOOL receive_remove_recipient(uschar *);
extern uschar *rfc2047_decode(uschar *, BOOL, uschar *, int, int *, uschar **);
extern int smtp_fflush(void);
-extern void smtp_printf(const char *, ...) PRINTF_FUNCTION(1,2);
-extern void smtp_vprintf(const char *, va_list);
+extern void smtp_printf(const char *, BOOL, ...) PRINTF_FUNCTION(1,3);
+extern void smtp_vprintf(const char *, BOOL, va_list);
extern uschar *string_copy(const uschar *);
extern uschar *string_copyn(const uschar *, int);
extern uschar *string_sprintf(const char *, ...) ALMOST_PRINTF(1,2);
extern uschar *string_copy(const uschar *);
extern uschar *string_copyn(const uschar *, int);
extern uschar *string_sprintf(const char *, ...) ALMOST_PRINTF(1,2);
sender_address,
sender_fullhost ? " H=" : "", sender_fullhost ? sender_fullhost : US"",
sender_ident ? " U=" : "", sender_ident ? sender_ident : US"");
sender_address,
sender_fullhost ? " H=" : "", sender_fullhost ? sender_fullhost : US"",
sender_ident ? " U=" : "", sender_ident ? sender_ident : US"");
- smtp_printf("552 Message header not CRLF terminated\r\n");
+ smtp_printf("552 Message header not CRLF terminated\r\n", FALSE);
bdat_flush_data();
smtp_reply = US"";
goto TIDYUP; /* Skip to end of function */
bdat_flush_data();
smtp_reply = US"";
goto TIDYUP; /* Skip to end of function */
int all_pass = OK;
int all_fail = FAIL;
int all_pass = OK;
int all_fail = FAIL;
- smtp_printf("353 PRDR content analysis beginning\r\n");
+ smtp_printf("353 PRDR content analysis beginning\r\n", TRUE);
/* Loop through recipients, responses must be in same order received */
for (c = 0; recipients_count > c; c++)
{
/* Loop through recipients, responses must be in same order received */
for (c = 0; recipients_count > c; c++)
{
else if (chunking_state > CHUNKING_OFFERED)
{
else if (chunking_state > CHUNKING_OFFERED)
{
-/*XXX rethink for spool_wireformat */
- smtp_printf("250- %u byte chunk, total %d\r\n250 OK id=%s\r\n",
+ smtp_printf("250- %u byte chunk, total %d\r\n250 OK id=%s\r\n", FALSE,
chunking_datasize, message_size+message_linecount, message_id);
chunking_state = CHUNKING_OFFERED;
}
else
chunking_datasize, message_size+message_linecount, message_id);
chunking_state = CHUNKING_OFFERED;
}
else
- smtp_printf("250 OK id=%s\r\n", message_id);
+ smtp_printf("250 OK id=%s\r\n", FALSE, message_id);
if (host_checking)
fprintf(stdout,
if (host_checking)
fprintf(stdout,
smtp_respond((fake_response == DEFER)? US"450" : US"550", 3, TRUE,
fake_response_text);
else
smtp_respond((fake_response == DEFER)? US"450" : US"550", 3, TRUE,
fake_response_text);
else
- smtp_printf("%.1024s\r\n", smtp_reply);
+ smtp_printf("%.1024s\r\n", FALSE, smtp_reply);
switch (cutthrough_done)
{
switch (cutthrough_done)
{
*************************************************/
/* Synchronization checks can never be perfect because a packet may be on its
*************************************************/
/* Synchronization checks can never be perfect because a packet may be on its
-way but not arrived when the check is done. Such checks can in any case only be
-done when TLS is not in use. Normally, the checks happen when commands are
-read: Exim ensures that there is no more input in the input buffer. In normal
-cases, the response to the command will be fast, and there is no further check.
+way but not arrived when the check is done. Normally, the checks happen when
+commands are read: Exim ensures that there is no more input in the input buffer.
+In normal cases, the response to the command will be fast, and there is no
+further check.
However, for some commands an ACL is run, and that can include delays. In those
cases, it is useful to do another check on the input just before sending the
However, for some commands an ACL is run, and that can include delays. In those
cases, it is useful to do another check on the input just before sending the
+wouldblock_reading(void)
{
int fd, rc;
fd_set fds;
struct timeval tzero;
{
int fd, rc;
fd_set fds;
struct timeval tzero;
-if (!smtp_enforce_sync || sender_host_address == NULL ||
- sender_host_notsocket || tls_in.active >= 0)
- return TRUE;
+if (tls_in.active >= 0 && tls_could_read())
+ return FALSE;
if (smtp_inptr < smtp_inend)
return FALSE;
if (smtp_inptr < smtp_inend)
return FALSE;
+static BOOL
+check_sync(void)
+{
+if (!smtp_enforce_sync || sender_host_address == NULL || sender_host_notsocket)
+ return TRUE;
+
+return wouldblock_reading();
+}
+
+
+/* If there's input waiting (and we're doing pipelineing) then we can pipeline
+a reponse with the one following. */
+
+static BOOL
+pipeline_response(void)
+{
+if ( !smtp_enforce_sync || !sender_host_address
+ || sender_host_notsocket || !pipelining_advertised)
+ return FALSE;
+
+return !wouldblock_reading();
+}
+
/*************************************************
/*************************************************
- smtp_printf("250 %u byte chunk received\r\n", chunking_datasize);
+ smtp_printf("250 %u byte chunk received\r\n", FALSE, chunking_datasize);
chunking_state = CHUNKING_OFFERED;
DEBUG(D_receive) debug_printf("chunking state %d\n", (int)chunking_state);
chunking_state = CHUNKING_OFFERED;
DEBUG(D_receive) debug_printf("chunking state %d\n", (int)chunking_state);
case NOOP_CMD:
HAD(SCH_NOOP);
case NOOP_CMD:
HAD(SCH_NOOP);
- smtp_printf("250 OK\r\n");
+ smtp_printf("250 OK\r\n", FALSE);
goto next_cmd;
case BDAT_CMD:
goto next_cmd;
case BDAT_CMD:
Arguments:
format format string
Arguments:
format format string
+ more further data expected
... optional arguments
Returns: nothing
*/
void
... optional arguments
Returns: nothing
*/
void
-smtp_printf(const char *format, ...)
+smtp_printf(const char *format, BOOL more, ...)
-va_start(ap, format);
-smtp_vprintf(format, ap);
+va_start(ap, more);
+smtp_vprintf(format, more, ap);
call another vararg function, only a function which accepts a va_list. */
void
call another vararg function, only a function which accepts a va_list. */
void
-smtp_vprintf(const char *format, va_list ap)
+smtp_vprintf(const char *format, BOOL more, va_list ap)
#ifdef SUPPORT_TLS
if (tls_in.active >= 0)
{
#ifdef SUPPORT_TLS
if (tls_in.active >= 0)
{
- if (tls_write(TRUE, big_buffer, Ustrlen(big_buffer)) < 0)
+ if (tls_write(TRUE, big_buffer, Ustrlen(big_buffer), more) < 0)
smtp_write_error = -1;
}
else
smtp_write_error = -1;
}
else
{
if (smtp_in == NULL || smtp_batched_input) return;
receive_swallow_smtp();
{
if (smtp_in == NULL || smtp_batched_input) return;
receive_swallow_smtp();
-smtp_printf("421 %s\r\n", message);
+smtp_printf("421 %s\r\n", FALSE, message);
for (;;) switch(smtp_read_command(FALSE, GETC_BUFFER_UNLIMITED))
{
for (;;) switch(smtp_read_command(FALSE, GETC_BUFFER_UNLIMITED))
{
- smtp_printf("221 %s closing connection\r\n", smtp_active_hostname);
+ smtp_printf("221 %s closing connection\r\n", FALSE, smtp_active_hostname);
mac_smtp_fflush();
return;
case RSET_CMD:
mac_smtp_fflush();
return;
case RSET_CMD:
- smtp_printf("250 Reset OK\r\n");
+ smtp_printf("250 Reset OK\r\n", FALSE);
- smtp_printf("421 %s\r\n", message);
+ smtp_printf("421 %s\r\n", FALSE, message);
{
log_write(0, LOG_MAIN, "getsockopt() failed from %s: %s",
host_and_ident(FALSE), strerror(errno));
{
log_write(0, LOG_MAIN, "getsockopt() failed from %s: %s",
host_and_ident(FALSE), strerror(errno));
- smtp_printf("451 SMTP service not available\r\n");
+ smtp_printf("451 SMTP service not available\r\n", FALSE);
log_write(0, LOG_MAIN|LOG_REJECT,
"connection from %s refused (IP options)", host_and_ident(FALSE));
log_write(0, LOG_MAIN|LOG_REJECT,
"connection from %s refused (IP options)", host_and_ident(FALSE));
- smtp_printf("554 SMTP service not available\r\n");
+ smtp_printf("554 SMTP service not available\r\n", FALSE);
{
log_write(L_connection_reject, LOG_MAIN|LOG_REJECT, "refused connection "
"from %s (host_reject_connection)", host_and_ident(FALSE));
{
log_write(L_connection_reject, LOG_MAIN|LOG_REJECT, "refused connection "
"from %s (host_reject_connection)", host_and_ident(FALSE));
- smtp_printf("554 SMTP service not available\r\n");
+ smtp_printf("554 SMTP service not available\r\n", FALSE);
log_write(L_connection_reject,
LOG_MAIN|LOG_REJECT, "refused connection from %s "
"(tcp wrappers)", host_and_ident(FALSE));
log_write(L_connection_reject,
LOG_MAIN|LOG_REJECT, "refused connection from %s "
"(tcp wrappers)", host_and_ident(FALSE));
- smtp_printf("554 SMTP service not available\r\n");
+ smtp_printf("554 SMTP service not available\r\n", FALSE);
log_write(L_connection_reject,
LOG_MAIN|LOG_REJECT, "temporarily refused connection from %s "
"(tcp wrappers errno=%d)", host_and_ident(FALSE), save_errno);
log_write(L_connection_reject,
LOG_MAIN|LOG_REJECT, "temporarily refused connection from %s "
"(tcp wrappers errno=%d)", host_and_ident(FALSE), save_errno);
- smtp_printf("451 Temporary local problem - please try later\r\n");
+ smtp_printf("451 Temporary local problem - please try later\r\n", FALSE);
host_and_ident(FALSE), smtp_accept_count - 1, smtp_accept_max,
smtp_accept_reserve, (rc == DEFER)? " (lookup deferred)" : "");
smtp_printf("421 %s: Too many concurrent SMTP connections; "
host_and_ident(FALSE), smtp_accept_count - 1, smtp_accept_max,
smtp_accept_reserve, (rc == DEFER)? " (lookup deferred)" : "");
smtp_printf("421 %s: Too many concurrent SMTP connections; "
- "please try again later\r\n", smtp_active_hostname);
+ "please try again later\r\n", FALSE, smtp_active_hostname);
return FALSE;
}
reserved_host = TRUE;
return FALSE;
}
reserved_host = TRUE;
LOG_MAIN, "temporarily refused connection from %s: not in "
"reserve list and load average = %.2f", host_and_ident(FALSE),
(double)load_average/1000.0);
LOG_MAIN, "temporarily refused connection from %s: not in "
"reserve list and load average = %.2f", host_and_ident(FALSE),
(double)load_average/1000.0);
- smtp_printf("421 %s: Too much load; please try again later\r\n",
+ smtp_printf("421 %s: Too much load; please try again later\r\n", FALSE,
smtp_active_hostname);
return FALSE;
}
smtp_active_hostname);
return FALSE;
}
"synchronization error (input sent without waiting for greeting): "
"rejected connection from %s input=\"%s\"", host_and_ident(TRUE),
string_printing(string_copyn(smtp_inptr, n)));
"synchronization error (input sent without waiting for greeting): "
"rejected connection from %s input=\"%s\"", host_and_ident(TRUE),
string_printing(string_copyn(smtp_inptr, n)));
- smtp_printf("554 SMTP synchronization error\r\n");
+ smtp_printf("554 SMTP synchronization error\r\n", FALSE);
return FALSE;
}
/* Now output the banner */
return FALSE;
}
/* Now output the banner */
+smtp_printf("%s", FALSE, ss);
- smtp_printf("%d%c%s%s%s\r\n", code, (yield == 1)? '-' : ' ',
- (data == NULL)? US"" : data, (data == NULL)? US"" : US": ", errmess);
+ smtp_printf("%d%c%s%s%s\r\n", FALSE, code, yield == 1 ? '-' : ' ',
+ data ? data : US"", data ? US": " : US"", errmess);
- smtp_printf("%d Too many syntax or protocol errors\r\n", code);
+ smtp_printf("%d Too many syntax or protocol errors\r\n", FALSE, code);
rcpt_in_progress = FALSE;
}
rcpt_in_progress = FALSE;
}
-/* Not output the message, splitting it up into multiple lines if necessary. */
+/* Not output the message, splitting it up into multiple lines if necessary.
+We only handle pipelining these responses as far as nonfinal/final groups,
+not the whole MAIL/RCPT/DATA response set. */
for (;;)
{
uschar *nl = Ustrchr(msg, '\n');
if (nl == NULL)
{
for (;;)
{
uschar *nl = Ustrchr(msg, '\n');
if (nl == NULL)
{
- smtp_printf("%.3s%c%.*s%s\r\n", code, final? ' ':'-', esclen, esc, msg);
+ smtp_printf("%.3s%c%.*s%s\r\n", !final, code, final ? ' ':'-', esclen, esc, msg);
return;
}
else if (nl[1] == 0 || no_multiline_responses)
{
return;
}
else if (nl[1] == 0 || no_multiline_responses)
{
- smtp_printf("%.3s%c%.*s%.*s\r\n", code, final? ' ':'-', esclen, esc,
+ smtp_printf("%.3s%c%.*s%.*s\r\n", !final, code, final ? ' ':'-', esclen, esc,
(int)(nl - msg), msg);
return;
}
else
{
(int)(nl - msg), msg);
return;
}
else
{
- smtp_printf("%.3s-%.*s%.*s\r\n", code, esclen, esc, (int)(nl - msg), msg);
+ smtp_printf("%.3s-%.*s%.*s\r\n", TRUE, code, esclen, esc, (int)(nl - msg), msg);
msg = nl + 1;
while (isspace(*msg)) msg++;
}
msg = nl + 1;
while (isspace(*msg)) msg++;
}
va_start(ap, defaultrespond);
if (!string_vformat(buffer, sizeof(buffer), CS defaultrespond, ap))
log_write(0, LOG_MAIN|LOG_PANIC, "string too large in smtp_notquit_exit()");
va_start(ap, defaultrespond);
if (!string_vformat(buffer, sizeof(buffer), CS defaultrespond, ap))
log_write(0, LOG_MAIN|LOG_PANIC, "string too large in smtp_notquit_exit()");
- smtp_printf("%s %s\r\n", code, buffer);
+ smtp_printf("%s %s\r\n", FALSE, code, buffer);
va_end(ap);
}
mac_smtp_fflush();
va_end(ap);
}
mac_smtp_fflush();
*recipient = rewrite_address_qualify(*recipient, TRUE);
return rd;
}
*recipient = rewrite_address_qualify(*recipient, TRUE);
return rd;
}
-smtp_printf("501 %s: recipient address must contain a domain\r\n",
+smtp_printf("501 %s: recipient address must contain a domain\r\n", FALSE,
smtp_cmd_data);
log_write(L_smtp_syntax_error,
LOG_MAIN|LOG_REJECT, "unqualified %s rejected: <%s> %s%s",
smtp_cmd_data);
log_write(L_smtp_syntax_error,
LOG_MAIN|LOG_REJECT, "unqualified %s rejected: <%s> %s%s",
if (*user_msgp)
smtp_respond(US"221", 3, TRUE, *user_msgp);
else
if (*user_msgp)
smtp_respond(US"221", 3, TRUE, *user_msgp);
else
- smtp_printf("221 %s closing connection\r\n", smtp_active_hostname);
+ smtp_printf("221 %s closing connection\r\n", FALSE, smtp_active_hostname);
#ifdef SUPPORT_TLS
tls_close(TRUE, TRUE);
#ifdef SUPPORT_TLS
tls_close(TRUE, TRUE);
{
HAD(SCH_RSET);
incomplete_transaction_log(US"RSET");
{
HAD(SCH_RSET);
incomplete_transaction_log(US"RSET");
-smtp_printf("250 Reset OK\r\n");
+smtp_printf("250 Reset OK\r\n", FALSE);
cmd_list[CMD_LIST_RSET].is_mail_cmd = FALSE;
}
cmd_list[CMD_LIST_RSET].is_mail_cmd = FALSE;
}
{
c = smtp_in_auth(au, &s, &ss);
{
c = smtp_in_auth(au, &s, &ss);
- smtp_printf("%s\r\n", s);
+ smtp_printf("%s\r\n", FALSE, s);
if (c != OK)
log_write(0, LOG_MAIN|LOG_REJECT, "%s authenticator failed for %s: %s",
au->name, host_and_ident(FALSE), ss);
if (c != OK)
log_write(0, LOG_MAIN|LOG_REJECT, "%s authenticator failed for %s: %s",
au->name, host_and_ident(FALSE), ss);
if (!check_helo(smtp_cmd_data))
{
if (!check_helo(smtp_cmd_data))
{
- smtp_printf("501 Syntactically invalid %s argument(s)\r\n", hello);
+ smtp_printf("501 Syntactically invalid %s argument(s)\r\n", FALSE, hello);
log_write(0, LOG_MAIN|LOG_REJECT, "rejected %s from %s: syntactically "
"invalid argument(s): %s", hello, host_and_ident(FALSE),
log_write(0, LOG_MAIN|LOG_REJECT, "rejected %s from %s: syntactically "
"invalid argument(s): %s", hello, host_and_ident(FALSE),
- smtp_printf("%d %s argument does not match calling host\r\n",
+ smtp_printf("%d %s argument does not match calling host\r\n", FALSE,
tempfail? 451 : 550, hello);
log_write(0, LOG_MAIN|LOG_REJECT, "%srejected \"%s %s\" from %s",
tempfail? "temporarily " : "",
tempfail? 451 : 550, hello);
log_write(0, LOG_MAIN|LOG_REJECT, "%srejected \"%s %s\" from %s",
tempfail? "temporarily " : "",
s[ptr] = 0;
#ifdef SUPPORT_TLS
s[ptr] = 0;
#ifdef SUPPORT_TLS
- if (tls_in.active >= 0) (void)tls_write(TRUE, s, ptr); else
+ if (tls_in.active >= 0) (void)tls_write(TRUE, s, ptr, FALSE); else
if (helo_required && !helo_seen)
{
if (helo_required && !helo_seen)
{
- smtp_printf("503 HELO or EHLO required\r\n");
+ smtp_printf("503 HELO or EHLO required\r\n", FALSE);
log_write(0, LOG_MAIN|LOG_REJECT, "rejected MAIL from %s: no "
"HELO/EHLO given", host_and_ident(FALSE));
break;
log_write(0, LOG_MAIN|LOG_REJECT, "rejected MAIL from %s: no "
"HELO/EHLO given", host_and_ident(FALSE));
break;
if (smtp_accept_max_per_connection > 0 &&
smtp_mailcmd_count > smtp_accept_max_per_connection)
{
if (smtp_accept_max_per_connection > 0 &&
smtp_mailcmd_count > smtp_accept_max_per_connection)
{
- smtp_printf("421 too many messages in this connection\r\n");
+ smtp_printf("421 too many messages in this connection\r\n", FALSE);
log_write(0, LOG_MAIN|LOG_REJECT, "rejected MAIL command %s: too many "
"messages in one connection", host_and_ident(TRUE));
break;
log_write(0, LOG_MAIN|LOG_REJECT, "rejected MAIL command %s: too many "
"messages in one connection", host_and_ident(TRUE));
break;
if (thismessage_size_limit > 0 && message_size > thismessage_size_limit)
{
if (thismessage_size_limit > 0 && message_size > thismessage_size_limit)
{
- smtp_printf("552 Message size exceeds maximum permitted\r\n");
+ smtp_printf("552 Message size exceeds maximum permitted\r\n", FALSE);
log_write(L_size_reject,
LOG_MAIN|LOG_REJECT, "rejected MAIL FROM:<%s> %s: "
"message too big: size%s=%d max=%d",
log_write(L_size_reject,
LOG_MAIN|LOG_REJECT, "rejected MAIL FROM:<%s> %s: "
"message too big: size%s=%d max=%d",
(smtp_check_spool_space && message_size >= 0)?
message_size + 5000 : 0))
{
(smtp_check_spool_space && message_size >= 0)?
message_size + 5000 : 0))
{
- smtp_printf("452 Space shortage, please try later\r\n");
+ smtp_printf("452 Space shortage, please try later\r\n", FALSE);
sender_address = NULL;
break;
}
sender_address = NULL;
break;
}
- smtp_printf("501 %s: sender address must contain a domain\r\n",
+ smtp_printf("501 %s: sender address must contain a domain\r\n", FALSE,
smtp_cmd_data);
log_write(L_smtp_syntax_error,
LOG_MAIN|LOG_REJECT,
smtp_cmd_data);
log_write(L_smtp_syntax_error,
LOG_MAIN|LOG_REJECT,
if (rc == OK || rc == DISCARD)
{
if (rc == OK || rc == DISCARD)
{
+ BOOL more = pipeline_response();
+
- smtp_printf("%s%s%s", US"250 OK",
+ smtp_printf("%s%s%s", more, US"250 OK",
#ifndef DISABLE_PRDR
prdr_requested ? US", PRDR Requested" : US"",
#else
#ifndef DISABLE_PRDR
prdr_requested ? US", PRDR Requested" : US"",
#else
{
if (pipelining_advertised && last_was_rej_mail)
{
{
if (pipelining_advertised && last_was_rej_mail)
{
- smtp_printf("503 sender not yet given\r\n");
+ smtp_printf("503 sender not yet given\r\n", FALSE);
was_rej_mail = TRUE;
}
else
was_rej_mail = TRUE;
}
else
if (recipients_max_reject)
{
rcpt_fail_count++;
if (recipients_max_reject)
{
rcpt_fail_count++;
- smtp_printf("552 too many recipients\r\n");
+ smtp_printf("552 too many recipients\r\n", FALSE);
if (!toomany)
log_write(0, LOG_MAIN|LOG_REJECT, "too many recipients: message "
"rejected: sender=<%s> %s", sender_address, host_and_ident(TRUE));
if (!toomany)
log_write(0, LOG_MAIN|LOG_REJECT, "too many recipients: message "
"rejected: sender=<%s> %s", sender_address, host_and_ident(TRUE));
else
{
rcpt_defer_count++;
else
{
rcpt_defer_count++;
- smtp_printf("452 too many recipients\r\n");
+ smtp_printf("452 too many recipients\r\n", FALSE);
if (!toomany)
log_write(0, LOG_MAIN|LOG_REJECT, "too many recipients: excess "
"temporarily rejected: sender=<%s> %s", sender_address,
if (!toomany)
log_write(0, LOG_MAIN|LOG_REJECT, "too many recipients: excess "
"temporarily rejected: sender=<%s> %s", sender_address,
+ BOOL more = pipeline_response();
+
if (user_msg)
smtp_user_msg(US"250", user_msg);
else
if (user_msg)
smtp_user_msg(US"250", user_msg);
else
- smtp_printf("250 Accepted\r\n");
+ smtp_printf("250 Accepted\r\n", more);
receive_add_recipient(recipient, -1);
/* Set the dsn flags in the recipients_list */
receive_add_recipient(recipient, -1);
/* Set the dsn flags in the recipients_list */
if (user_msg)
smtp_user_msg(US"250", user_msg);
else
if (user_msg)
smtp_user_msg(US"250", user_msg);
else
- smtp_printf("250 Accepted\r\n");
+ smtp_printf("250 Accepted\r\n", FALSE);
rcpt_fail_count++;
discarded = TRUE;
log_write(0, LOG_MAIN|LOG_REJECT, "%s F=<%s> RCPT %s: "
rcpt_fail_count++;
discarded = TRUE;
log_write(0, LOG_MAIN|LOG_REJECT, "%s F=<%s> RCPT %s: "
smtp_respond(code, 3, FALSE, rcpt_smtp_response);
}
if (pipelining_advertised && last_was_rcpt)
smtp_respond(code, 3, FALSE, rcpt_smtp_response);
}
if (pipelining_advertised && last_was_rcpt)
- smtp_printf("503 Valid RCPT command must precede %s\r\n",
+ smtp_printf("503 Valid RCPT command must precede %s\r\n", FALSE,
smtp_names[smtp_connection_had[smtp_ch_index-1]]);
else
done = synprot_error(L_smtp_protocol_error, 503, NULL,
smtp_names[smtp_connection_had[smtp_ch_index-1]]);
else
done = synprot_error(L_smtp_protocol_error, 503, NULL,
{
sender_address = NULL; /* This will allow a new MAIL without RSET */
sender_address_unrewritten = NULL;
{
sender_address = NULL; /* This will allow a new MAIL without RSET */
sender_address_unrewritten = NULL;
- smtp_printf("554 Too many recipients\r\n");
+ smtp_printf("554 Too many recipients\r\n", FALSE);
smtp_user_msg(US"354", user_msg);
else
smtp_printf(
smtp_user_msg(US"354", user_msg);
else
smtp_printf(
- "354 Enter message, ending with \".\" on a line by itself\r\n");
+ "354 Enter message, ending with \".\" on a line by itself\r\n", FALSE);
if (!(address = parse_extract_address(smtp_cmd_data, &errmess,
&start, &end, &recipient_domain, FALSE)))
{
if (!(address = parse_extract_address(smtp_cmd_data, &errmess,
&start, &end, &recipient_domain, FALSE)))
{
- smtp_printf("501 %s\r\n", errmess);
+ smtp_printf("501 %s\r\n", FALSE, errmess);
- smtp_printf("%s\r\n", s);
+ smtp_printf("%s\r\n", FALSE, s);
- smtp_printf("454 TLS currently unavailable\r\n");
+ smtp_printf("454 TLS currently unavailable\r\n", FALSE);
if (user_msg)
smtp_respond(US"221", 3, TRUE, user_msg);
else
if (user_msg)
smtp_respond(US"221", 3, TRUE, user_msg);
else
- smtp_printf("221 %s closing connection\r\n", smtp_active_hostname);
+ smtp_printf("221 %s closing connection\r\n", FALSE, smtp_active_hostname);
log_write(L_smtp_connection, LOG_MAIN, "%s closed by QUIT",
smtp_get_connection_info());
done = 2;
break;
default:
log_write(L_smtp_connection, LOG_MAIN, "%s closed by QUIT",
smtp_get_connection_info());
done = 2;
break;
default:
- smtp_printf("554 Security failure\r\n");
+ smtp_printf("554 Security failure\r\n", FALSE);
break;
}
tls_close(TRUE, TRUE);
break;
}
tls_close(TRUE, TRUE);
case NOOP_CMD:
HAD(SCH_NOOP);
case NOOP_CMD:
HAD(SCH_NOOP);
- smtp_printf("250 OK\r\n");
+ smtp_printf("250 OK\r\n", FALSE);
case HELP_CMD:
HAD(SCH_HELP);
case HELP_CMD:
HAD(SCH_HELP);
- smtp_printf("214-Commands supported:\r\n");
+ smtp_printf("214-Commands supported:\r\n", TRUE);
{
uschar buffer[256];
buffer[0] = 0;
{
uschar buffer[256];
buffer[0] = 0;
if (acl_smtp_etrn != NULL) Ustrcat(buffer, " ETRN");
if (acl_smtp_expn != NULL) Ustrcat(buffer, " EXPN");
if (acl_smtp_vrfy != NULL) Ustrcat(buffer, " VRFY");
if (acl_smtp_etrn != NULL) Ustrcat(buffer, " ETRN");
if (acl_smtp_expn != NULL) Ustrcat(buffer, " EXPN");
if (acl_smtp_vrfy != NULL) Ustrcat(buffer, " VRFY");
- smtp_printf("214%s\r\n", buffer);
+ smtp_printf("214%s\r\n", FALSE, buffer);
{
log_write(0, LOG_MAIN|LOG_PANIC, "failed to set up ETRN command: %s",
error);
{
log_write(0, LOG_MAIN|LOG_PANIC, "failed to set up ETRN command: %s",
error);
- smtp_printf("458 Internal failure\r\n");
+ smtp_printf("458 Internal failure\r\n", FALSE);
debug_printf("ETRN command is: %s\n", etrn_command);
debug_printf("ETRN command execution skipped\n");
}
debug_printf("ETRN command is: %s\n", etrn_command);
debug_printf("ETRN command execution skipped\n");
}
- if (user_msg == NULL) smtp_printf("250 OK\r\n");
+ if (user_msg == NULL) smtp_printf("250 OK\r\n", FALSE);
else smtp_user_msg(US"250", user_msg);
break;
}
else smtp_user_msg(US"250", user_msg);
break;
}
if (smtp_etrn_serialize && !enq_start(etrn_serialize_key, 1))
{
if (smtp_etrn_serialize && !enq_start(etrn_serialize_key, 1))
{
- smtp_printf("458 Already processing %s\r\n", smtp_cmd_data);
+ smtp_printf("458 Already processing %s\r\n", FALSE, smtp_cmd_data);
{
log_write(0, LOG_MAIN|LOG_PANIC, "fork of process for ETRN failed: %s",
strerror(errno));
{
log_write(0, LOG_MAIN|LOG_PANIC, "fork of process for ETRN failed: %s",
strerror(errno));
- smtp_printf("458 Unable to fork process\r\n");
+ smtp_printf("458 Unable to fork process\r\n", FALSE);
if (smtp_etrn_serialize) enq_end(etrn_serialize_key);
}
else
{
if (smtp_etrn_serialize) enq_end(etrn_serialize_key);
}
else
{
- if (user_msg == NULL) smtp_printf("250 OK\r\n");
+ if (user_msg == NULL) smtp_printf("250 OK\r\n", FALSE);
else smtp_user_msg(US"250", user_msg);
}
else smtp_user_msg(US"250", user_msg);
}
case BADCHAR_CMD:
done = synprot_error(L_smtp_syntax_error, 0, NULL, /* Just logs */
US"NULL character(s) present (shown as '?')");
case BADCHAR_CMD:
done = synprot_error(L_smtp_syntax_error, 0, NULL, /* Just logs */
US"NULL character(s) present (shown as '?')");
- smtp_printf("501 NULL characters are not allowed in SMTP commands\r\n");
+ smtp_printf("501 NULL characters are not allowed in SMTP commands\r\n", FALSE);
#ifdef SUPPORT_PROXY
case PROXY_FAIL_IGNORE_CMD:
#ifdef SUPPORT_PROXY
case PROXY_FAIL_IGNORE_CMD:
- smtp_printf("503 Command refused, required Proxy negotiation failed\r\n");
+ smtp_printf("503 Command refused, required Proxy negotiation failed\r\n", FALSE);
Argument:
outblock the SMTP output block
Argument:
outblock the SMTP output block
- mode more-expected, or plain
+ mode further data expected, or plain
Returns: TRUE if OK, FALSE on error, with errno set
*/
Returns: TRUE if OK, FALSE on error, with errno set
*/
{
int rc;
int n = outblock->ptr - outblock->buffer;
{
int rc;
int n = outblock->ptr - outblock->buffer;
+BOOL more = mode == SCMD_MORE;
HDEBUG(D_transport|D_acl) debug_printf_indent("cmd buf flush %d bytes%s\n", n,
HDEBUG(D_transport|D_acl) debug_printf_indent("cmd buf flush %d bytes%s\n", n,
- mode == SCMD_MORE ? " (more expected)" : "");
+ more ? " (more expected)" : "");
#ifdef SUPPORT_TLS
if (tls_out.active == outblock->sock)
#ifdef SUPPORT_TLS
if (tls_out.active == outblock->sock)
- rc = tls_write(FALSE, outblock->buffer, n);
+ rc = tls_write(FALSE, outblock->buffer, n, more);
else
#endif
rc = send(outblock->sock, outblock->buffer, n,
#ifdef MSG_MORE
else
#endif
rc = send(outblock->sock, outblock->buffer, n,
#ifdef MSG_MORE
- mode == SCMD_MORE ? MSG_MORE : 0
#if GNUTLS_VERSION_NUMBER >= 0x030014
# define SUPPORT_SYSDEFAULT_CABUNDLE
#endif
#if GNUTLS_VERSION_NUMBER >= 0x030014
# define SUPPORT_SYSDEFAULT_CABUNDLE
#endif
+#if GNUTLS_VERSION_NUMBER >= 0x030109
+# define SUPPORT_CORK
+#endif
#ifndef DISABLE_OCSP
# include <gnutls/ocsp.h>
#ifndef DISABLE_OCSP
# include <gnutls/ocsp.h>
if (tls_in.active >= 0)
{
tls_error(US"STARTTLS received after TLS started", "", NULL, errstr);
if (tls_in.active >= 0)
{
tls_error(US"STARTTLS received after TLS started", "", NULL, errstr);
- smtp_printf("554 Already in TLS\r\n");
+ smtp_printf("554 Already in TLS\r\n", FALSE);
if (!state->tlsp->on_connect)
{
if (!state->tlsp->on_connect)
{
- smtp_printf("220 TLS go ahead\r\n");
+ smtp_printf("220 TLS go ahead\r\n", FALSE);
+BOOL
+tls_could_read(void)
+{
+return state_server.xfer_buffer_lwm < state_server.xfer_buffer_hwm
+ || gnutls_record_check_pending(state_server.session) > 0;
+}
+
+
/*************************************************
/*************************************************
is_server channel specifier
buff buffer of data
len number of bytes
is_server channel specifier
buff buffer of data
len number of bytes
+ more more data expected soon
Returns: the number of bytes after a successful write,
-1 after a failed write
*/
int
Returns: the number of bytes after a successful write,
-1 after a failed write
*/
int
-tls_write(BOOL is_server, const uschar *buff, size_t len)
+tls_write(BOOL is_server, const uschar *buff, size_t len, BOOL more)
{
ssize_t outbytes;
size_t left = len;
exim_gnutls_state_st *state = is_server ? &state_server : &state_client;
{
ssize_t outbytes;
size_t left = len;
exim_gnutls_state_st *state = is_server ? &state_server : &state_client;
+#ifdef SUPPORT_CORK
+static BOOL corked = FALSE;
+
+if (more && !corked) gnutls_record_cork(state->session);
+#endif
+
+DEBUG(D_tls) debug_printf("%s(%p, " SIZE_T_FMT "%s)\n", __FUNCTION__,
+ buff, left, more ? ", more" : "");
-DEBUG(D_tls) debug_printf("tls_do_write(%p, " SIZE_T_FMT ")\n", buff, left);
while (left > 0)
{
DEBUG(D_tls) debug_printf("gnutls_record_send(SSL, %p, " SIZE_T_FMT ")\n",
while (left > 0)
{
DEBUG(D_tls) debug_printf("gnutls_record_send(SSL, %p, " SIZE_T_FMT ")\n",
+#ifdef SUPPORT_CORK
+if (more != corked)
+ {
+ if (!more) (void) gnutls_record_uncork(state->session, 0);
+ corked = more;
+ }
+#endif
+
if (tls_in.active >= 0)
{
tls_error(US"STARTTLS received after TLS started", NULL, US"", errstr);
if (tls_in.active >= 0)
{
tls_error(US"STARTTLS received after TLS started", NULL, US"", errstr);
- smtp_printf("554 Already in TLS\r\n");
+ smtp_printf("554 Already in TLS\r\n", FALSE);
SSL_set_session_id_context(server_ssl, sid_ctx, Ustrlen(sid_ctx));
if (!tls_in.on_connect)
{
SSL_set_session_id_context(server_ssl, sid_ctx, Ustrlen(sid_ctx));
if (!tls_in.on_connect)
{
- smtp_printf("220 TLS go ahead\r\n");
+ smtp_printf("220 TLS go ahead\r\n", FALSE);
+BOOL
+tls_could_read(void)
+{
+/* XXX no actual inquiry into library; only our buffer */
+return ssl_xfer_buffer_lwm < ssl_xfer_buffer_hwm;
+}
+
/*************************************************
* Read bytes from TLS channel *
/*************************************************
* Read bytes from TLS channel *
is_server channel specifier
buff buffer of data
len number of bytes
is_server channel specifier
buff buffer of data
len number of bytes
+ more further data expected soon
Returns: the number of bytes after a successful write,
-1 after a failed write
Returns: the number of bytes after a successful write,
-1 after a failed write
-tls_write(BOOL is_server, const uschar *buff, size_t len)
+tls_write(BOOL is_server, const uschar *buff, size_t len, BOOL more)
{
int outbytes;
int error;
int left = len;
SSL *ssl = is_server ? server_ssl : client_ssl;
{
int outbytes;
int error;
int left = len;
SSL *ssl = is_server ? server_ssl : client_ssl;
-DEBUG(D_tls) debug_printf("tls_do_write(%p, %d)\n", buff, left);
+DEBUG(D_tls) debug_printf("%s(%p, %d)\n", __FUNCTION__, buff, left);
while (left > 0)
{
DEBUG(D_tls) debug_printf("SSL_write(SSL, %p, %d)\n", buff, left);
while (left > 0)
{
DEBUG(D_tls) debug_printf("SSL_write(SSL, %p, %d)\n", buff, left);
tctx transport context: file descriptor or string to write to
block block of bytes to write
len number of bytes to write
tctx transport context: file descriptor or string to write to
block block of bytes to write
len number of bytes to write
+ more further data expected soon
Returns: TRUE on success, FALSE on failure (with errno preserved);
transport_count is incremented by the number of bytes written
Returns: TRUE on success, FALSE on failure (with errno preserved);
transport_count is incremented by the number of bytes written
{
rc =
#ifdef SUPPORT_TLS
{
rc =
#ifdef SUPPORT_TLS
- (tls_out.active == fd) ? tls_write(FALSE, block, len) :
+ tls_out.active == fd ? tls_write(FALSE, block, len, more) :
#endif
#ifdef MSG_MORE
more ? send(fd, block, len, MSG_MORE) :
#endif
#ifdef MSG_MORE
more ? send(fd, block, len, MSG_MORE) :
- (tls_out.active == fd) ? tls_write(FALSE, block, len) :
+ tls_out.active == fd ? tls_write(FALSE, block, len, more) :
#endif
#ifdef MSG_MORE
more ? send(fd, block, len, MSG_MORE) :
#endif
#ifdef MSG_MORE
more ? send(fd, block, len, MSG_MORE) :
else
{
for (nbytes = 0; rc - nbytes > 0; nbytes += i)
else
{
for (nbytes = 0; rc - nbytes > 0; nbytes += i)
- if ((i = tls_write(FALSE, buf + nbytes, rc - nbytes)) < 0) return;
+ if ((i = tls_write(FALSE, buf + nbytes, rc - nbytes, FALSE)) < 0)
+ return;
}
else if (fd_bits & 2)
FD_SET(proxy_fd, &fds);
}
else if (fd_bits & 2)
FD_SET(proxy_fd, &fds);
- (tls_out.active == cutthrough.fd) ? tls_write(FALSE, ctblock.buffer, n) :
+ tls_out.active == cutthrough.fd ? tls_write(FALSE, ctblock.buffer, n, FALSE) :
#endif
send(cutthrough.fd, ctblock.buffer, n, 0) > 0
)
#endif
send(cutthrough.fd, ctblock.buffer, n, 0) > 0
)
va_start(ap, format);
if (smtp_out && (f == smtp_out))
va_start(ap, format);
if (smtp_out && (f == smtp_out))
- smtp_vprintf(format, ap);
+ smtp_vprintf(format, FALSE, ap);
else
vfprintf(f, format, ap);
va_end(ap);
else
vfprintf(f, format, ap);
va_end(ap);
queue_run_in_order
tls_advertise_hosts = *
queue_run_in_order
tls_advertise_hosts = *
+tls_require_ciphers = NORMAL:!DHE-RSA:!DHE-DSS:!ECDHE-RSA:!ECDHE-ECDSA:!ECDHE-PSK
# Set certificate only if server
# Set certificate only if server
??? 250-PIPELINING
??? 250-CHUNKING
??? 250 HELP
??? 250-PIPELINING
??? 250-CHUNKING
??? 250 HELP
-MAIL FROM:<someone@some.domain>
-RCPT TO:<CALLER@test.ex>
-BDAT 88 LAST
-To: Susan@random.com
-From: Sam@random.com
-Subject: This is a bodyless test message
-
+MAIL FROM:<someone@some.domain>\r\nRCPT TO:<CALLER@test.ex>\r\nBDAT 88 LAST\r\nTo: Susan@random.com\r\nFrom: Sam@random.com\r\nSubject: This is a bodyless test message\r\n
??? 250-PIPELINING
??? 250-CHUNKING
??? 250 HELP
??? 250-PIPELINING
??? 250-CHUNKING
??? 250 HELP
-MAIL FROM:<someone@some.domain>
-RCPT TO:<CALLER@test.ex>
-BDAT 88 LAST
-To: Susan@random.com
-From: Sam@random.com
-Subject: This is a bodyless test message
-
+MAIL FROM:<someone@some.domain>\r\nRCPT TO:<CALLER@test.ex>\r\nBDAT 88 LAST\r\nTo: Susan@random.com\r\nFrom: Sam@random.com\r\nSubject: This is a bodyless test message\r\n
<<< 250-CHUNKING
??? 250 HELP
<<< 250 HELP
<<< 250-CHUNKING
??? 250 HELP
<<< 250 HELP
->>> MAIL FROM:<someone@some.domain>
->>> RCPT TO:<CALLER@test.ex>
->>> BDAT 88 LAST
->>> To: Susan@random.com
->>> From: Sam@random.com
->>> Subject: This is a bodyless test message
->>>
+>>> MAIL FROM:<someone@some.domain>\r\nRCPT TO:<CALLER@test.ex>\r\nBDAT 88 LAST\r\nTo: Susan@random.com\r\nFrom: Sam@random.com\r\nSubject: This is a bodyless test message\r\n
??? 250
<<< 250 OK
??? 250
??? 250
<<< 250 OK
??? 250
<<< 250-CHUNKING
??? 250 HELP
<<< 250 HELP
<<< 250-CHUNKING
??? 250 HELP
<<< 250 HELP
->>> MAIL FROM:<someone@some.domain>
->>> RCPT TO:<CALLER@test.ex>
->>> BDAT 88 LAST
->>> To: Susan@random.com
->>> From: Sam@random.com
->>> Subject: This is a bodyless test message
->>>
+>>> MAIL FROM:<someone@some.domain>\r\nRCPT TO:<CALLER@test.ex>\r\nBDAT 88 LAST\r\nTo: Susan@random.com\r\nFrom: Sam@random.com\r\nSubject: This is a bodyless test message\r\n
??? 250
<<< 250 OK
??? 250
??? 250
<<< 250 OK
??? 250