12 files changed:
JH/06 Fix buggy handling of autoreply bounce_return_size_limit, and a possible
buffer overrun for (non-chunking) other transports.
JH/06 Fix buggy handling of autoreply bounce_return_size_limit, and a possible
buffer overrun for (non-chunking) other transports.
+JH/07 GnuTLS: Our use of late (post-handshake) certificate verification, under
+ TLS1.3, means that a server rejecting a client certificate is not visible
+ to the client until the first read of encrypted data (typically the
+ response to EHLO). Add detection for that case and treat it as a failed
+ TLS connection attempt, so that the normal retry-in-clear can work (if
+ suitably configured).
+
Exim version 4.92
-----------------
Exim version 4.92
-----------------
tctx.u.fd = fd;
tctx.options = topt_add_return_path | topt_no_body;
tctx.u.fd = fd;
tctx.options = topt_add_return_path | topt_no_body;
- /*XXX hmm, retval ignored.
+ /*XXX hmm, FALSE(fail) retval ignored.
Could error for any number of reasons, and they are not handled. */
transport_write_message(&tctx, 0);
fflush(f);
Could error for any number of reasons, and they are not handled. */
transport_write_message(&tctx, 0);
fflush(f);
/*XXX could move to smtp transport; no other users */
BOOL
/*XXX could move to smtp transport; no other users */
BOOL
-smtp_read_response(void * sx0, uschar *buffer, int size, int okdigit,
+smtp_read_response(void * sx0, uschar * buffer, int size, int okdigit,
int timeout)
{
smtp_context * sx = sx0;
int timeout)
{
smtp_context * sx = sx0;
-uschar *ptr = buffer;
-int count = 0;
+uschar * ptr = buffer;
+int count = 0, rc;
errno = 0; /* Ensure errno starts out zero */
#ifdef EXPERIMENTAL_PIPE_CONNECT
if (sx->pending_BANNER || sx->pending_EHLO)
errno = 0; /* Ensure errno starts out zero */
#ifdef EXPERIMENTAL_PIPE_CONNECT
if (sx->pending_BANNER || sx->pending_EHLO)
- if (smtp_reap_early_pipe(sx, &count) != OK)
+ if ((rc = smtp_reap_early_pipe(sx, &count)) != OK)
{
DEBUG(D_transport) debug_printf("failed reaping pipelined cmd responsess\n");
{
DEBUG(D_transport) debug_printf("failed reaping pipelined cmd responsess\n");
+ buffer[0] = '\0';
+ if (rc == DEFER) errno = ERRNO_TLSFAILURE;
static const int ssl_session_timeout = 200;
static const int ssl_session_timeout = 200;
-static const char * const exim_default_gnutls_priority = "NORMAL";
+static const uschar * const exim_default_gnutls_priority = US"NORMAL";
/* Guard library core initialisation */
/* Guard library core initialisation */
size_t sz;
const char *errpos;
uschar *p;
size_t sz;
const char *errpos;
uschar *p;
-BOOL want_default_priorities;
if (!exim_gnutls_base_init_done)
{
if (!exim_gnutls_base_init_done)
{
This was backwards incompatible, but means Exim no longer needs to track
all algorithms and provide string forms for them. */
This was backwards incompatible, but means Exim no longer needs to track
all algorithms and provide string forms for them. */
-want_default_priorities = TRUE;
-
if (state->tls_require_ciphers && *state->tls_require_ciphers)
{
if (!expand_check_tlsvar(tls_require_ciphers, errstr))
return DEFER;
if (state->exp_tls_require_ciphers && *state->exp_tls_require_ciphers)
{
if (state->tls_require_ciphers && *state->tls_require_ciphers)
{
if (!expand_check_tlsvar(tls_require_ciphers, errstr))
return DEFER;
if (state->exp_tls_require_ciphers && *state->exp_tls_require_ciphers)
{
- DEBUG(D_tls) debug_printf("GnuTLS session cipher/priority \"%s\"\n",
- state->exp_tls_require_ciphers);
-
- rc = gnutls_priority_init(&state->priority_cache,
- CS state->exp_tls_require_ciphers, &errpos);
- want_default_priorities = FALSE;
p = state->exp_tls_require_ciphers;
p = state->exp_tls_require_ciphers;
+ DEBUG(D_tls) debug_printf("GnuTLS session cipher/priority \"%s\"\n", p);
-if (want_default_priorities)
+ p = exim_default_gnutls_priority;
- debug_printf("GnuTLS using default session cipher/priority \"%s\"\n",
- exim_default_gnutls_priority);
- rc = gnutls_priority_init(&state->priority_cache,
- exim_default_gnutls_priority, &errpos);
- p = US exim_default_gnutls_priority;
+ debug_printf("GnuTLS using default session cipher/priority \"%s\"\n", p);
+rc = gnutls_priority_init(&state->priority_cache, CCS p, &errpos);
exim_gnutls_err_check(rc, string_sprintf(
"gnutls_priority_init(%s) failed at offset %ld, \"%.6s..\"",
exim_gnutls_err_check(rc, string_sprintf(
"gnutls_priority_init(%s) failed at offset %ld, \"%.6s..\"",
Returns: TRUE if a "QUIT" command should be sent, else FALSE
*/
Returns: TRUE if a "QUIT" command should be sent, else FALSE
*/
-static BOOL check_response(int *errno_value, int more_errno, uschar *buffer,
+static BOOL
+check_response(int *errno_value, int more_errno, uschar *buffer,
int *yield, uschar **message)
{
*yield = '4'; /* Default setting is to give a temporary error */
int *yield, uschar **message)
{
*yield = '4'; /* Default setting is to give a temporary error */
pl, smtp_command, s);
return FALSE;
pl, smtp_command, s);
return FALSE;
+ case ERRNO_TLSFAILURE: /* Handle bad first read; can happen with
+ GnuTLS and TLS1.3 */
+ *message = US"bad first read from TLS conn";
+ return TRUE;
+
case ERRNO_FILTER_FAIL: /* Handle a failed filter process error;
can't send QUIT as we mustn't end the DATA. */
*message = string_sprintf("transport filter process failed (%d)%s",
case ERRNO_FILTER_FAIL: /* Handle a failed filter process error;
can't send QUIT as we mustn't end the DATA. */
*message = string_sprintf("transport filter process failed (%d)%s",
+ DEFER error on first read of TLS'd conn
FAIL SMTP error in response
*/
int
FAIL SMTP error in response
*/
int
{
BOOL pending_BANNER = sx->pending_BANNER;
BOOL pending_EHLO = sx->pending_EHLO;
{
BOOL pending_BANNER = sx->pending_BANNER;
BOOL pending_EHLO = sx->pending_EHLO;
sx->pending_BANNER = FALSE; /* clear early to avoid recursion */
sx->pending_EHLO = FALSE;
sx->pending_BANNER = FALSE; /* clear early to avoid recursion */
sx->pending_EHLO = FALSE;
if (!smtp_reap_banner(sx))
{
DEBUG(D_transport) debug_printf("bad banner\n");
if (!smtp_reap_banner(sx))
{
DEBUG(D_transport) debug_printf("bad banner\n");
+ if (tls_out.active.sock >= 0) rc = DEFER;
if (!smtp_reap_ehlo(sx))
{
DEBUG(D_transport) debug_printf("bad response for EHLO\n");
if (!smtp_reap_ehlo(sx))
{
DEBUG(D_transport) debug_printf("bad response for EHLO\n");
+ if (tls_out.active.sock >= 0) rc = DEFER;
fail:
invalidate_ehlo_cache_entry(sx);
(void) smtp_discard_responses(sx, sx->conn_args.ob, *countp);
fail:
invalidate_ehlo_cache_entry(sx);
(void) smtp_discard_responses(sx, sx->conn_args.ob, *countp);
-2 I/O or other non-response error for RCPT
-3 DATA or MAIL failed - errno and buffer set
-4 banner or EHLO failed (early-pipelining)
-2 I/O or other non-response error for RCPT
-3 DATA or MAIL failed - errno and buffer set
-4 banner or EHLO failed (early-pipelining)
+ -5 banner or EHLO failed (early-pipelining, TLS)
address_item * addr = sx->sync_addr;
smtp_transport_options_block * ob = sx->conn_args.ob;
int yield = 0;
address_item * addr = sx->sync_addr;
smtp_transport_options_block * ob = sx->conn_args.ob;
int yield = 0;
#ifdef EXPERIMENTAL_PIPE_CONNECT
#ifdef EXPERIMENTAL_PIPE_CONNECT
-if (smtp_reap_early_pipe(sx, &count) != OK)
- return -4;
+if ((rc = smtp_reap_early_pipe(sx, &count)) != OK)
+ return rc == FAIL ? -4 : -5;
#endif
/* Handle the response for a MAIL command. On error, reinstate the original
#endif
/* Handle the response for a MAIL command. On error, reinstate the original
{
DEBUG(D_transport) debug_printf("bad response for MAIL\n");
Ustrcpy(big_buffer, mail_command); /* Fits, because it came from there! */
{
DEBUG(D_transport) debug_printf("bad response for MAIL\n");
Ustrcpy(big_buffer, mail_command); /* Fits, because it came from there! */
+ if (errno == ERRNO_TLSFAILURE)
+ return -5;
if (errno == 0 && sx->buffer[0] != 0)
{
int save_errno = 0;
if (errno == 0 && sx->buffer[0] != 0)
{
int save_errno = 0;
+ /* Error on first TLS read */
+
+ else if (errno == ERRNO_TLSFAILURE)
+ return -5;
+
/* Timeout while reading the response */
else if (errno == ETIMEDOUT)
/* Timeout while reading the response */
else if (errno == ETIMEDOUT)
int code;
uschar *msg;
BOOL pass_message;
int code;
uschar *msg;
BOOL pass_message;
+
+ if (errno == ERRNO_TLSFAILURE) /* Error on first TLS read */
+ return -5;
+
if (pending_DATA > 0 || (yield & 1) != 0)
{
if (errno == 0 && sx->buffer[0] == '4')
if (pending_DATA > 0 || (yield & 1) != 0)
{
if (errno == 0 && sx->buffer[0] == '4')
tc_chunk_last add LAST option to SMTP BDAT command
tc_reap_prev reap response to previous SMTP commands
tc_chunk_last add LAST option to SMTP BDAT command
tc_reap_prev reap response to previous SMTP commands
+Returns:
+ OK or ERROR
+ DEFER TLS error on first read (EHLO-resp); errno set
case 2: sx->completed_addr = TRUE; /* 5xx (only) => progress made */
case 0: break; /* No 2xx or 5xx, but no probs */
case 2: sx->completed_addr = TRUE; /* 5xx (only) => progress made */
case 0: break; /* No 2xx or 5xx, but no probs */
- case -1: /* Timeout on RCPT */
+ case -5: errno = ERRNO_TLSFAILURE;
+ return DEFER;
#ifdef EXPERIMENTAL_PIPE_CONNECT
case -4: /* non-2xx for pipelined banner or EHLO */
#endif
#ifdef EXPERIMENTAL_PIPE_CONNECT
case -4: /* non-2xx for pipelined banner or EHLO */
#endif
+ case -1: /* Timeout on RCPT */
default: return ERROR; /* I/O error, or any MAIL/DATA error */
}
cmd_count = 1;
default: return ERROR; /* I/O error, or any MAIL/DATA error */
}
cmd_count = 1;
uschar * message = NULL;
int yield = OK;
int rc;
uschar * message = NULL;
int yield = OK;
int rc;
+#ifdef SUPPORT_TLS
+uschar * tls_errstr;
+#endif
TLS_NEGOTIATE:
{
address_item * addr;
TLS_NEGOTIATE:
{
address_item * addr;
sx->cctx.tls_ctx = tls_client_start(sx->cctx.sock, sx->conn_args.host,
sx->addrlist, sx->conn_args.tblock,
# ifdef SUPPORT_DANE
sx->dane ? &tlsa_dnsa : NULL,
# endif
sx->cctx.tls_ctx = tls_client_start(sx->cctx.sock, sx->conn_args.host,
sx->addrlist, sx->conn_args.tblock,
# ifdef SUPPORT_DANE
sx->dane ? &tlsa_dnsa : NULL,
# endif
+ &tls_out, &tls_errstr);
if (!sx->cctx.tls_ctx)
{
/* TLS negotiation failed; give an error. From outside, this function may
be called again to try in clear on a new connection, if the options permit
it for this host. */
if (!sx->cctx.tls_ctx)
{
/* TLS negotiation failed; give an error. From outside, this function may
be called again to try in clear on a new connection, if the options permit
it for this host. */
- DEBUG(D_tls) debug_printf("TLS session fail: %s\n", errstr);
+GNUTLS_CONN_FAILED:
+ DEBUG(D_tls) debug_printf("TLS session fail: %s\n", tls_errstr);
# ifdef SUPPORT_DANE
if (sx->dane)
{
log_write(0, LOG_MAIN,
"DANE attempt failed; TLS connection to %s [%s]: %s",
# ifdef SUPPORT_DANE
if (sx->dane)
{
log_write(0, LOG_MAIN,
"DANE attempt failed; TLS connection to %s [%s]: %s",
- sx->conn_args.host->name, sx->conn_args.host->address, errstr);
+ sx->conn_args.host->name, sx->conn_args.host->address, tls_errstr);
# ifndef DISABLE_EVENT
(void) event_raise(sx->conn_args.tblock->event_action,
US"dane:fail", US"validation-failure"); /* could do with better detail */
# ifndef DISABLE_EVENT
(void) event_raise(sx->conn_args.tblock->event_action,
US"dane:fail", US"validation-failure"); /* could do with better detail */
# endif
errno = ERRNO_TLSFAILURE;
# endif
errno = ERRNO_TLSFAILURE;
- message = string_sprintf("TLS session: %s", errstr);
+ message = string_sprintf("TLS session: %s", tls_errstr);
sx->send_quit = FALSE;
goto TLS_FAILED;
}
sx->send_quit = FALSE;
goto TLS_FAILED;
}
#endif
{
if (!smtp_reap_ehlo(sx))
#endif
{
if (!smtp_reap_ehlo(sx))
+#ifdef USE_GNUTLS
+ {
+ /* The GnuTLS layer in Exim only spots a server-rejection of a client
+ cert late, under TLS1.3 - which means here; the first time we try to
+ receive crypted data. Treat it as if it was a connect-time failure.
+ See also the early-pipe equivalent... which will be hard; every call
+ to sync_responses will need to check the result.
+ It would be nicer to have GnuTLS check the cert during the handshake.
+ Can it do that, with all the flexibility we need? */
+
+ tls_errstr = US"error on first read";
+ goto GNUTLS_CONN_FAILED;
+ }
+#else
smtp_peer_options = 0;
}
}
smtp_peer_options = 0;
}
}
#ifdef EXPERIMENTAL_PIPE_CONNECT
case -4: return -1; /* non-2xx for pipelined banner or EHLO */
#ifdef EXPERIMENTAL_PIPE_CONNECT
case -4: return -1; /* non-2xx for pipelined banner or EHLO */
+ case -5: return -1; /* TLS first-read error */
#endif
}
sx->pending_MAIL = FALSE; /* Dealt with MAIL */
#endif
}
sx->pending_MAIL = FALSE; /* Dealt with MAIL */
case 1: sx.ok = TRUE; /* 2xx (only) => OK, but if LMTP, */
if (!sx.lmtp) sx.completed_addr = TRUE; /* can't tell about progress yet */
case 1: sx.ok = TRUE; /* 2xx (only) => OK, but if LMTP, */
if (!sx.lmtp) sx.completed_addr = TRUE; /* can't tell about progress yet */
- case 0: break; /* No 2xx or 5xx, but no probs */
+ case 0: break; /* No 2xx or 5xx, but no probs */
- case -1: goto END_OFF; /* Timeout on RCPT */
+ case -1: goto END_OFF; /* Timeout on RCPT */
#ifdef EXPERIMENTAL_PIPE_CONNECT
#ifdef EXPERIMENTAL_PIPE_CONNECT
+ case -5: /* TLS first-read error */
case -4: HDEBUG(D_transport)
debug_printf("failed reaping pipelined cmd responses\n");
#endif
case -4: HDEBUG(D_transport)
debug_printf("failed reaping pipelined cmd responses\n");
#endif
{
case 3: sx.ok = TRUE; /* 2xx & 5xx => OK & progress made */
case 2: sx.completed_addr = TRUE; /* 5xx (only) => progress made */
{
case 3: sx.ok = TRUE; /* 2xx & 5xx => OK & progress made */
case 2: sx.completed_addr = TRUE; /* 5xx (only) => progress made */
- case 1: sx.ok = TRUE; /* 2xx (only) => OK, but if LMTP, */
+ case 1: sx.ok = TRUE; /* 2xx (only) => OK, but if LMTP, */
if (!sx.lmtp) sx.completed_addr = TRUE; /* can't tell about progress yet */
if (!sx.lmtp) sx.completed_addr = TRUE; /* can't tell about progress yet */
- case 0: break; /* No 2xx or 5xx, but no probs */
+ case 0: break; /* No 2xx or 5xx, but no probs */
- case -1: goto END_OFF; /* Timeout on RCPT */
+ case -1: goto END_OFF; /* Timeout on RCPT */
#ifdef EXPERIMENTAL_PIPE_CONNECT
#ifdef EXPERIMENTAL_PIPE_CONNECT
+ case -5: /* TLS first-read error */
case -4: HDEBUG(D_transport)
debug_printf("failed reaping pipelined cmd responses\n");
#endif
case -4: HDEBUG(D_transport)
debug_printf("failed reaping pipelined cmd responses\n");
#endif
- default: goto RESPONSE_FAILED; /* I/O error, or any MAIL/DATA error */
+ default: goto RESPONSE_FAILED; /* I/O error, or any MAIL/DATA error */
user = CALLER
send_to_server:
user = CALLER
send_to_server:
- hosts = ${if eq{$local_part}{userx}{127.0.0.1}{HOSTIPV4}}
- port = PORT_D
+ hosts = ${if eq{$local_part}{userx}{127.0.0.1}{HOSTIPV4}}
+ port = PORT_D
+ tls_verify_certificates = DIR/aux-fixed/cert1
+ tls_verify_cert_hostnames = :
: DECDSA/server1.example_ec.com/server1.example_ec.com.ocsp.good.resp
: DECDSA/server1.example_ec.com/server1.example_ec.com.ocsp.good.resp
+tls_require_ciphers = NORMAL:!VERS-TLS1.3
tls_privatekey = ${if eq {SERVER}{server} {CDIR2/server1.example.com.unlocked.key}fail}
# Permit two specific ciphers
tls_privatekey = ${if eq {SERVER}{server} {CDIR2/server1.example.com.unlocked.key}fail}
# Permit two specific ciphers
-tls_require_ciphers = NORMAL:-KX-ALL:+RSA:-CIPHER-ALL:+AES-128-CBC:+CAMELLIA-256-GCM
+tls_require_ciphers = NORMAL:-VERS-TLS1.3:-KX-ALL:+RSA:-CIPHER-ALL:+AES-128-CBC:+CAMELLIA-256-GCM
# ----- Routers -----
begin routers
# ----- Routers -----
begin routers
1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local S=sss
1999-03-02 09:44:33 10HmaY-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local S=sss
1999-03-02 09:44:33 Start queue run: pid=pppp -qf
1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local S=sss
1999-03-02 09:44:33 10HmaY-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local S=sss
1999-03-02 09:44:33 Start queue run: pid=pppp -qf
-1999-03-02 09:44:33 10HmaX-0005vi-00 => userx@test.ex R=client T=send_to_server H=127.0.0.1 [127.0.0.1] X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx CV=no C="250 OK id=10HmaZ-0005vi-00"
+1999-03-02 09:44:33 10HmaX-0005vi-00 => userx@test.ex R=client T=send_to_server H=127.0.0.1 [127.0.0.1] X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx CV=yes C="250 OK id=10HmaZ-0005vi-00"
1999-03-02 09:44:33 10HmaX-0005vi-00 Completed
1999-03-02 09:44:33 10HmaY-0005vi-00 TLS session: (gnutls_handshake): A TLS fatal alert has been received.: delivering unencrypted to H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4] (not in hosts_require_tls)
1999-03-02 09:44:33 10HmaY-0005vi-00 => usery@test.ex R=client T=send_to_server H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4] C="250 OK id=10HmbA-0005vi-00"
1999-03-02 09:44:33 10HmaX-0005vi-00 Completed
1999-03-02 09:44:33 10HmaY-0005vi-00 TLS session: (gnutls_handshake): A TLS fatal alert has been received.: delivering unencrypted to H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4] (not in hosts_require_tls)
1999-03-02 09:44:33 10HmaY-0005vi-00 => usery@test.ex R=client T=send_to_server H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4] C="250 OK id=10HmbA-0005vi-00"
s/(DANE attempt failed.*error:)[0-9A-F]{8}(:SSL routines:)(ssl3_get_server_certificate|tls_process_server_certificate|CONNECT_CR_CERT)(?=:certificate verify failed$)/$1xxxxxxxx$2ssl3_get_server_certificate/;
s/(DKIM: validation error: )error:[0-9A-F]{8}:rsa routines:(?:(?i)int_rsa_verify|CRYPTO_internal):(?:bad signature|algorithm mismatch)$/$1Public key signature verification has failed./;
s/(DANE attempt failed.*error:)[0-9A-F]{8}(:SSL routines:)(ssl3_get_server_certificate|tls_process_server_certificate|CONNECT_CR_CERT)(?=:certificate verify failed$)/$1xxxxxxxx$2ssl3_get_server_certificate/;
s/(DKIM: validation error: )error:[0-9A-F]{8}:rsa routines:(?:(?i)int_rsa_verify|CRYPTO_internal):(?:bad signature|algorithm mismatch)$/$1Public key signature verification has failed./;
+ # gnutls version variances
+ if (/TLS error on connection \(recv\): .* Decode error/)
+ {
+ my $prev = $_;
+ $_ = <IN>;
+ if (/error on first read/)
+ {
+ s/TLS session: \Kerror on first read:/(gnutls_handshake): A TLS fatal alert has been received.:/;
+ goto RESET_AFTER_EXTRA_LINE_READ;
+ }
+ else
+ { $_ = $prev; }
+ }
+
# DKIM timestamps
if ( /(DKIM: d=.*) t=([0-9]*) x=([0-9]*) / )
{
# DKIM timestamps
if ( /(DKIM: d=.*) t=([0-9]*) x=([0-9]*) / )
{
munge gnutls_handshake
exim -DSERVER=server -bd -oX PORT_D
****
munge gnutls_handshake
exim -DSERVER=server -bd -oX PORT_D
****
+# will send to 127.0.0.1 and the server requests a client-cert
exim userx@test.ex
Test message
****
exim userx@test.ex
Test message
****
+# will send to HOSTIPV4 and the server requests&requires
exim usery@test.ex
Test message
****
exim usery@test.ex
Test message
****