summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJeremy Harris <jgh146exb@wizmail.org>2019-02-16 12:59:23 +0000
committerJeremy Harris <jgh146exb@wizmail.org>2019-02-16 12:59:23 +0000
commitfc243e944ec00b59b75f41d07494116f925d58b4 (patch)
treef297ea2e8ce5aa02c105c9b842d0d53523d8a62e /src
parent882bc1704d33aa34873e3a0f72e657b0cc2985e5 (diff)
GnuTLS: Fix client detection of server reject of client cert under TLS1.3
Diffstat (limited to 'src')
-rw-r--r--src/src/deliver.c2
-rw-r--r--src/src/smtp_out.c10
-rw-r--r--src/src/tls-gnu.c23
-rw-r--r--src/src/transports/lmtp.c3
-rw-r--r--src/src/transports/smtp.c81
5 files changed, 80 insertions, 39 deletions
diff --git a/src/src/deliver.c b/src/src/deliver.c
index ebb64778c..506470070 100644
--- a/src/src/deliver.c
+++ b/src/src/deliver.c
@@ -7401,7 +7401,7 @@ if (addr_senddsn)
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);
diff --git a/src/src/smtp_out.c b/src/src/smtp_out.c
index 8a4dace7e..b7a835479 100644
--- a/src/src/smtp_out.c
+++ b/src/src/smtp_out.c
@@ -690,20 +690,22 @@ Returns: TRUE if a valid, non-error response was received; else FALSE
/*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;
-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)
- 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");
+ buffer[0] = '\0';
+ if (rc == DEFER) errno = ERRNO_TLSFAILURE;
return FALSE;
}
#endif
diff --git a/src/src/tls-gnu.c b/src/src/tls-gnu.c
index faad38f76..1752bf3d8 100644
--- a/src/src/tls-gnu.c
+++ b/src/src/tls-gnu.c
@@ -238,7 +238,7 @@ static gnutls_dh_params_t dh_server_params = NULL;
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 */
@@ -1287,7 +1287,6 @@ int rc;
size_t sz;
const char *errpos;
uschar *p;
-BOOL want_default_priorities;
if (!exim_gnutls_base_init_done)
{
@@ -1396,32 +1395,24 @@ and replaces gnutls_require_kx, gnutls_require_mac & gnutls_require_protocols.
This was backwards incompatible, but means Exim no longer needs to track
all algorithms and provide string forms for them. */
-want_default_priorities = TRUE;
-
+p = NULL;
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;
+ DEBUG(D_tls) debug_printf("GnuTLS session cipher/priority \"%s\"\n", p);
}
}
-if (want_default_priorities)
+if (!p)
{
+ p = exim_default_gnutls_priority;
DEBUG(D_tls)
- 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..\"",
diff --git a/src/src/transports/lmtp.c b/src/src/transports/lmtp.c
index f007f4a97..b2bf5f0da 100644
--- a/src/src/transports/lmtp.c
+++ b/src/src/transports/lmtp.c
@@ -122,7 +122,8 @@ Arguments:
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 */
diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c
index 9ee1304bc..6ff993d57 100644
--- a/src/src/transports/smtp.c
+++ b/src/src/transports/smtp.c
@@ -593,6 +593,11 @@ switch(*errno_value)
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",
@@ -941,6 +946,7 @@ Arguments:
Return:
OK all well
+ DEFER error on first read of TLS'd conn
FAIL SMTP error in response
*/
int
@@ -948,6 +954,7 @@ smtp_reap_early_pipe(smtp_context * sx, int * countp)
{
BOOL pending_BANNER = sx->pending_BANNER;
BOOL pending_EHLO = sx->pending_EHLO;
+int rc = FAIL;
sx->pending_BANNER = FALSE; /* clear early to avoid recursion */
sx->pending_EHLO = FALSE;
@@ -959,6 +966,7 @@ if (pending_BANNER)
if (!smtp_reap_banner(sx))
{
DEBUG(D_transport) debug_printf("bad banner\n");
+ if (tls_out.active.sock >= 0) rc = DEFER;
goto fail;
}
}
@@ -973,6 +981,7 @@ if (pending_EHLO)
if (!smtp_reap_ehlo(sx))
{
DEBUG(D_transport) debug_printf("bad response for EHLO\n");
+ if (tls_out.active.sock >= 0) rc = DEFER;
goto fail;
}
@@ -1010,7 +1019,7 @@ return OK;
fail:
invalidate_ehlo_cache_entry(sx);
(void) smtp_discard_responses(sx, sx->conn_args.ob, *countp);
- return FAIL;
+ return rc;
}
#endif
@@ -1055,6 +1064,7 @@ Returns: 3 if at least one address had 2xx and one had 5xx
-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)
*/
static int
@@ -1063,10 +1073,11 @@ sync_responses(smtp_context * sx, int count, int pending_DATA)
address_item * addr = sx->sync_addr;
smtp_transport_options_block * ob = sx->conn_args.ob;
int yield = 0;
+int rc;
#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
@@ -1082,6 +1093,8 @@ if (sx->pending_MAIL)
{
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;
@@ -1140,6 +1153,11 @@ while (count-- > 0)
}
}
+ /* Error on first TLS read */
+
+ else if (errno == ERRNO_TLSFAILURE)
+ return -5;
+
/* Timeout while reading the response */
else if (errno == ETIMEDOUT)
@@ -1252,6 +1270,10 @@ if (pending_DATA != 0)
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')
@@ -1800,7 +1822,9 @@ Args:
tc_chunk_last add LAST option to SMTP BDAT command
tc_reap_prev reap response to previous SMTP commands
-Returns: OK or ERROR
+Returns:
+ OK or ERROR
+ DEFER TLS error on first read (EHLO-resp); errno set
*/
static int
@@ -1857,10 +1881,12 @@ if (flags & tc_reap_prev && prev_cmd_count > 0)
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
+ case -1: /* Timeout on RCPT */
default: return ERROR; /* I/O error, or any MAIL/DATA error */
}
cmd_count = 1;
@@ -1931,6 +1957,9 @@ BOOL pass_message = FALSE;
uschar * message = NULL;
int yield = OK;
int rc;
+#ifdef SUPPORT_TLS
+uschar * tls_errstr;
+#endif
sx->conn_args.ob = ob;
@@ -2471,27 +2500,27 @@ if ( smtp_peer_options & OPTION_TLS
else
TLS_NEGOTIATE:
{
- uschar * errstr;
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, &errstr);
+ &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. */
- 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",
- 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 */
@@ -2500,7 +2529,7 @@ if ( smtp_peer_options & OPTION_TLS
# 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;
}
@@ -2598,7 +2627,22 @@ if (tls_out.active.sock >= 0)
#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
goto RESPONSE_FAILED;
+#endif
smtp_peer_options = 0;
}
}
@@ -3257,6 +3301,7 @@ for (addr = sx->first_addr, address_count = 0;
#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 */
@@ -3582,11 +3627,12 @@ if ( !(sx.peer_offered & OPTION_CHUNKING)
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
+ case -5: /* TLS first-read error */
case -4: HDEBUG(D_transport)
debug_printf("failed reaping pipelined cmd responses\n");
#endif
@@ -3723,19 +3769,20 @@ else
{
case 3: sx.ok = TRUE; /* 2xx & 5xx => OK & progress made */
case 2: sx.completed_addr = TRUE; /* 5xx (only) => progress made */
- break;
+ break;
- 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 */
- 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
+ case -5: /* TLS first-read error */
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 */
}
}