From 925ac8e4f1c5d365ddea2f7aee460cd0a3cd409d Mon Sep 17 00:00:00 2001 From: Jeremy Harris Date: Wed, 17 May 2017 17:32:30 +0100 Subject: [PATCH] TLS: do decent PIPELINING under TLS, at least with GnuTLS I've not found an equivalent in OpenSSL of gnutls_record_cork() nor gnutls_record_check_pending() yet. --- doc/doc-txt/ChangeLog | 6 ++ src/src/auths/get_data.c | 2 +- src/src/auths/get_no64_data.c | 2 +- src/src/daemon.c | 12 +-- src/src/dkim_transport.c | 2 +- src/src/functions.h | 3 +- src/src/local_scan.h | 4 +- src/src/receive.c | 11 +-- src/src/smtp_in.c | 173 +++++++++++++++++++-------------- src/src/smtp_out.c | 9 +- src/src/tls-gnu.c | 35 ++++++- src/src/tls-openssl.c | 16 ++- src/src/transport.c | 5 +- src/src/transports/smtp.c | 3 +- src/src/verify.c | 4 +- test/confs/2007 | 1 + test/scripts/2000-GnuTLS/2090 | 8 +- test/scripts/2100-OpenSSL/2190 | 8 +- test/stdout/2090 | 8 +- test/stdout/2190 | 8 +- 20 files changed, 185 insertions(+), 135 deletions(-) diff --git a/doc/doc-txt/ChangeLog b/doc/doc-txt/ChangeLog index 4bc5235ec..12c1ff35b 100644 --- a/doc/doc-txt/ChangeLog +++ b/doc/doc-txt/ChangeLog @@ -86,6 +86,12 @@ PP/04 Add commandline_checks_require_admin option. 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 ----------------- diff --git a/src/src/auths/get_data.c b/src/src/auths/get_data.c index 11bc581b9..3857e0738 100644 --- a/src/src/auths/get_data.c +++ b/src/src/auths/get_data.c @@ -30,7 +30,7 @@ auth_get_data(uschar **aptr, uschar *challenge, int challen) { int c; int p = 0; -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; diff --git a/src/src/auths/get_no64_data.c b/src/src/auths/get_no64_data.c index 71e71394c..1bb611c1c 100644 --- a/src/src/auths/get_no64_data.c +++ b/src/src/auths/get_no64_data.c @@ -31,7 +31,7 @@ auth_get_no64_data(uschar **aptr, uschar *challenge) { int c; int p = 0; -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; diff --git a/src/src/daemon.c b/src/src/daemon.c index af5015454..06c2b258c 100644 --- a/src/src/daemon.c +++ b/src/src/daemon.c @@ -109,7 +109,7 @@ never_error(uschar *log_msg, uschar *smtp_msg, int was_errno) 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); } @@ -189,7 +189,7 @@ if (getsockname(accept_socket, (struct sockaddr *)(&interface_sockaddr), { 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); goto ERROR_RETURN; } @@ -222,7 +222,7 @@ if (smtp_accept_max > 0 && smtp_accept_count >= smtp_accept_max) 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); @@ -241,7 +241,7 @@ if (smtp_load_reserve >= 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); @@ -312,7 +312,7 @@ if ((max_for_this_host > 0) && "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); @@ -396,7 +396,7 @@ if (pid == 0) "(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); diff --git a/src/src/dkim_transport.c b/src/src/dkim_transport.c index 95e750e5e..4d26f9291 100644 --- a/src/src/dkim_transport.c +++ b/src/src/dkim_transport.c @@ -77,7 +77,7 @@ else { #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); diff --git a/src/src/functions.h b/src/src/functions.h index 963f48ed4..4be375d77 100644 --- a/src/src/functions.h +++ b/src/src/functions.h @@ -51,6 +51,7 @@ extern int tls_client_start(int, host_item *, address_item *, # 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); @@ -63,7 +64,7 @@ extern int tls_read(BOOL, uschar *, size_t); 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 diff --git a/src/src/local_scan.h b/src/src/local_scan.h index bc4fc8e25..c03d87032 100644 --- a/src/src/local_scan.h +++ b/src/src/local_scan.h @@ -186,8 +186,8 @@ extern void receive_add_recipient(uschar *, int); 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); diff --git a/src/src/receive.c b/src/src/receive.c index 6316ff961..9561a4baf 100644 --- a/src/src/receive.c +++ b/src/src/receive.c @@ -2194,7 +2194,7 @@ for (;;) 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 */ @@ -3501,7 +3501,7 @@ else 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++) { @@ -4302,13 +4302,12 @@ if (smtp_input) 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 - 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, @@ -4322,7 +4321,7 @@ if (smtp_input) 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) { diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c index e5885e477..5ff1b7f32 100644 --- a/src/src/smtp_in.c +++ b/src/src/smtp_in.c @@ -314,10 +314,10 @@ static void smtp_rset_handler(void); *************************************************/ /* 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 @@ -333,15 +333,14 @@ Returns: TRUE if all is well; FALSE if there is input pending */ static BOOL -check_sync(void) +wouldblock_reading(void) { 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; @@ -364,6 +363,29 @@ smtp_inptr[rc] = 0; 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(); +} + /************************************************* @@ -560,7 +582,7 @@ for(;;) return EOD; } - 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); @@ -598,7 +620,7 @@ next_cmd: 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: @@ -774,18 +796,19 @@ they are also picked up later by smtp_fflush(). Arguments: format format string + more further data expected ... optional arguments Returns: nothing */ void -smtp_printf(const char *format, ...) +smtp_printf(const char *format, BOOL more, ...) { va_list ap; -va_start(ap, format); -smtp_vprintf(format, ap); +va_start(ap, more); +smtp_vprintf(format, more, ap); va_end(ap); } @@ -794,7 +817,7 @@ smtp_printf(), bearing in mind that in C a vararg function can't directly 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) { BOOL yield; @@ -840,7 +863,7 @@ if (rcpt_in_progress) #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 @@ -1619,7 +1642,7 @@ smtp_closedown(uschar *message) { 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)) { @@ -1627,16 +1650,16 @@ for (;;) switch(smtp_read_command(FALSE, GETC_BUFFER_UNLIMITED)) return; case QUIT_CMD: - 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: - smtp_printf("250 Reset OK\r\n"); + smtp_printf("250 Reset OK\r\n", FALSE); break; default: - smtp_printf("421 %s\r\n", message); + smtp_printf("421 %s\r\n", FALSE, message); break; } } @@ -2490,7 +2513,7 @@ if (!sender_host_unknown) { 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); return FALSE; } } @@ -2595,7 +2618,7 @@ if (!sender_host_unknown) 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); return FALSE; } @@ -2647,7 +2670,7 @@ if (!sender_host_unknown) { 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); return FALSE; } @@ -2678,7 +2701,7 @@ if (!sender_host_unknown) 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); } else { @@ -2688,7 +2711,7 @@ if (!sender_host_unknown) 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); } return FALSE; } @@ -2708,7 +2731,7 @@ if (!sender_host_unknown) 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; @@ -2729,7 +2752,7 @@ if (!sender_host_unknown) 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; } @@ -2876,13 +2899,13 @@ if (!check_sync()) "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 */ -smtp_printf("%s", ss); +smtp_printf("%s", FALSE, ss); return TRUE; } @@ -2929,10 +2952,10 @@ if (++synprot_error_count > smtp_max_synprot_errors) if (code > 0) { - 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); if (yield == 1) - 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); } return yield; @@ -2988,25 +3011,27 @@ if (rcpt_in_progress) 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) { - 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) { - 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 { - 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++; } @@ -3350,7 +3375,7 @@ if (code && defaultrespond) 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(); @@ -3642,7 +3667,7 @@ if (allow_unqualified_recipient || strcmpic(*recipient, US"postmaster") == 0) *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", @@ -3668,7 +3693,7 @@ if (acl_smtp_quit) 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); @@ -3684,7 +3709,7 @@ smtp_rset_handler(void) { 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; } @@ -3904,7 +3929,7 @@ while (done <= 0) { 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); @@ -3951,7 +3976,7 @@ while (done <= 0) 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), @@ -4015,7 +4040,7 @@ while (done <= 0) { if (helo_required) { - 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 " : "", @@ -4285,7 +4310,7 @@ while (done <= 0) 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 #endif { @@ -4328,7 +4353,7 @@ while (done <= 0) 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; @@ -4354,7 +4379,7 @@ while (done <= 0) 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; @@ -4619,7 +4644,7 @@ while (done <= 0) 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", @@ -4644,7 +4669,7 @@ while (done <= 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; } @@ -4666,7 +4691,7 @@ while (done <= 0) } else { - 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, @@ -4694,8 +4719,10 @@ while (done <= 0) if (rc == OK || rc == DISCARD) { + BOOL more = pipeline_response(); + if (!user_msg) - 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 @@ -4741,7 +4768,7 @@ while (done <= 0) { 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 @@ -4889,7 +4916,7 @@ while (done <= 0) 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)); @@ -4897,7 +4924,7 @@ while (done <= 0) 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, @@ -4939,10 +4966,12 @@ while (done <= 0) if (rc == OK) { + BOOL more = pipeline_response(); + 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 */ @@ -4961,7 +4990,7 @@ while (done <= 0) 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: " @@ -5054,7 +5083,7 @@ while (done <= 0) 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, @@ -5071,7 +5100,7 @@ while (done <= 0) { 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); break; } @@ -5106,7 +5135,7 @@ while (done <= 0) 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); } #ifdef TCP_QUICKACK @@ -5129,7 +5158,7 @@ while (done <= 0) 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); break; } @@ -5168,7 +5197,7 @@ while (done <= 0) break; } - smtp_printf("%s\r\n", s); + smtp_printf("%s\r\n", FALSE, s); } break; } @@ -5297,7 +5326,7 @@ while (done <= 0) if (rc == DEFER) { - smtp_printf("454 TLS currently unavailable\r\n"); + smtp_printf("454 TLS currently unavailable\r\n", FALSE); break; } @@ -5330,14 +5359,14 @@ while (done <= 0) 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: - smtp_printf("554 Security failure\r\n"); + smtp_printf("554 Security failure\r\n", FALSE); break; } tls_close(TRUE, TRUE); @@ -5365,7 +5394,7 @@ while (done <= 0) case NOOP_CMD: HAD(SCH_NOOP); - smtp_printf("250 OK\r\n"); + smtp_printf("250 OK\r\n", FALSE); break; @@ -5376,7 +5405,7 @@ while (done <= 0) 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; @@ -5391,7 +5420,7 @@ while (done <= 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"); - smtp_printf("214%s\r\n", buffer); + smtp_printf("214%s\r\n", FALSE, buffer); } break; @@ -5460,7 +5489,7 @@ while (done <= 0) { 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); break; } } @@ -5491,7 +5520,7 @@ while (done <= 0) 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; } @@ -5502,7 +5531,7 @@ while (done <= 0) 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); break; } @@ -5565,12 +5594,12 @@ while (done <= 0) { 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 (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); } @@ -5589,7 +5618,7 @@ while (done <= 0) 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); break; @@ -5626,7 +5655,7 @@ while (done <= 0) #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); break; #endif diff --git a/src/src/smtp_out.c b/src/src/smtp_out.c index 4328cb342..253d7670d 100644 --- a/src/src/smtp_out.c +++ b/src/src/smtp_out.c @@ -323,7 +323,7 @@ pipelining. 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 */ @@ -333,18 +333,19 @@ flush_buffer(smtp_outblock * outblock, int mode) { 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, - mode == SCMD_MORE ? " (more expected)" : ""); + more ? " (more expected)" : ""); #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 - mode == SCMD_MORE ? MSG_MORE : 0 + more ? MSG_MORE : 0 #else 0 #endif diff --git a/src/src/tls-gnu.c b/src/src/tls-gnu.c index d623d8e4a..c68d11d72 100644 --- a/src/src/tls-gnu.c +++ b/src/src/tls-gnu.c @@ -60,6 +60,9 @@ require current GnuTLS, then we'll drop support for the ancient libraries). #if GNUTLS_VERSION_NUMBER >= 0x030014 # define SUPPORT_SYSDEFAULT_CABUNDLE #endif +#if GNUTLS_VERSION_NUMBER >= 0x030109 +# define SUPPORT_CORK +#endif #ifndef DISABLE_OCSP # include @@ -1745,7 +1748,7 @@ exim_gnutls_state_st * state = NULL; 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); return FAIL; } @@ -1806,7 +1809,7 @@ mode, the fflush() happens when smtp_getc() is called. */ if (!state->tlsp->on_connect) { - smtp_printf("220 TLS go ahead\r\n"); + smtp_printf("220 TLS go ahead\r\n", FALSE); fflush(smtp_out); } @@ -2288,6 +2291,14 @@ if (n > 0) } +BOOL +tls_could_read(void) +{ +return state_server.xfer_buffer_lwm < state_server.xfer_buffer_hwm + || gnutls_record_check_pending(state_server.session) > 0; +} + + /************************************************* @@ -2347,19 +2358,27 @@ Arguments: 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 -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; +#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", @@ -2390,6 +2409,14 @@ if (len > INT_MAX) len = INT_MAX; } +#ifdef SUPPORT_CORK +if (more != corked) + { + if (!more) (void) gnutls_record_uncork(state->session, 0); + corked = more; + } +#endif + return (int) len; } diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c index f17d94b5f..7f41c106e 100644 --- a/src/src/tls-openssl.c +++ b/src/src/tls-openssl.c @@ -1875,7 +1875,7 @@ static uschar cipherbuf[256]; 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); return FAIL; } @@ -1959,7 +1959,7 @@ mode, the fflush() happens when smtp_getc() is called. */ 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); fflush(smtp_out); } @@ -2480,6 +2480,13 @@ if (n > 0) } +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 * @@ -2533,6 +2540,7 @@ Arguments: 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 @@ -2541,14 +2549,14 @@ Used by both server-side and client-side TLS. */ 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) { 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); diff --git a/src/src/transport.c b/src/src/transport.c index 5bcf8c1ae..04b67f9e9 100644 --- a/src/src/transport.c +++ b/src/src/transport.c @@ -203,6 +203,7 @@ Arguments: 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 @@ -233,7 +234,7 @@ for (i = 0; i < 100; i++) { 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) : @@ -250,7 +251,7 @@ for (i = 0; i < 100; i++) 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) : diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c index 8f1e0bff8..454c0f74d 100644 --- a/src/src/transports/smtp.c +++ b/src/src/transports/smtp.c @@ -2618,7 +2618,8 @@ for (fd_bits = 3; fd_bits; ) 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); diff --git a/src/src/verify.c b/src/src/verify.c index 6a50af506..3a8f9b6bc 100644 --- a/src/src/verify.c +++ b/src/src/verify.c @@ -1177,7 +1177,7 @@ if(cutthrough.fd < 0) if( #ifdef SUPPORT_TLS - (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 ) @@ -1517,7 +1517,7 @@ va_list ap; 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); diff --git a/test/confs/2007 b/test/confs/2007 index 7b58b73ee..a16b9a57e 100644 --- a/test/confs/2007 +++ b/test/confs/2007 @@ -16,6 +16,7 @@ queue_only 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 diff --git a/test/scripts/2000-GnuTLS/2090 b/test/scripts/2000-GnuTLS/2090 index 278f03429..91227e8ca 100644 --- a/test/scripts/2000-GnuTLS/2090 +++ b/test/scripts/2000-GnuTLS/2090 @@ -58,13 +58,7 @@ EHLO rhu.barb ??? 250-PIPELINING ??? 250-CHUNKING ??? 250 HELP -MAIL FROM: -RCPT TO: -BDAT 88 LAST -To: Susan@random.com -From: Sam@random.com -Subject: This is a bodyless test message - +MAIL FROM:\r\nRCPT TO:\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 ??? 250- diff --git a/test/scripts/2100-OpenSSL/2190 b/test/scripts/2100-OpenSSL/2190 index 54095d49a..e004e5e02 100644 --- a/test/scripts/2100-OpenSSL/2190 +++ b/test/scripts/2100-OpenSSL/2190 @@ -57,13 +57,7 @@ EHLO rhu.barb ??? 250-PIPELINING ??? 250-CHUNKING ??? 250 HELP -MAIL FROM: -RCPT TO: -BDAT 88 LAST -To: Susan@random.com -From: Sam@random.com -Subject: This is a bodyless test message - +MAIL FROM:\r\nRCPT TO:\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 ??? 250- diff --git a/test/stdout/2090 b/test/stdout/2090 index 7bf70310a..f08abd10b 100644 --- a/test/stdout/2090 +++ b/test/stdout/2090 @@ -89,13 +89,7 @@ Succeeded in starting TLS <<< 250-CHUNKING ??? 250 HELP <<< 250 HELP ->>> MAIL FROM: ->>> RCPT TO: ->>> BDAT 88 LAST ->>> To: Susan@random.com ->>> From: Sam@random.com ->>> Subject: This is a bodyless test message ->>> +>>> MAIL FROM:\r\nRCPT TO:\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 diff --git a/test/stdout/2190 b/test/stdout/2190 index 9d386bdf7..e09556e69 100644 --- a/test/stdout/2190 +++ b/test/stdout/2190 @@ -91,13 +91,7 @@ Succeeded in starting TLS <<< 250-CHUNKING ??? 250 HELP <<< 250 HELP ->>> MAIL FROM: ->>> RCPT TO: ->>> BDAT 88 LAST ->>> To: Susan@random.com ->>> From: Sam@random.com ->>> Subject: This is a bodyless test message ->>> +>>> MAIL FROM:\r\nRCPT TO:\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 -- 2.30.2