* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2015 */
+/* Copyright (c) University of Cambridge 1995 - 2017 */
/* See the file NOTICE for conditions of use and distribution. */
/* Copyright (c) Phil Pennock 2012 */
#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>
} exim_gnutls_state_st;
static const exim_gnutls_state_st exim_gnutls_state_init = {
- NULL, NULL, NULL, VERIFY_NONE, -1, -1, FALSE, FALSE, FALSE,
- NULL, NULL, NULL, NULL,
- NULL, NULL, NULL, NULL, NULL, NULL,
- NULL, NULL, NULL, NULL, NULL, NULL, NULL,
- NULL,
+ .session = NULL,
+ .x509_cred = NULL,
+ .priority_cache = NULL,
+ .verify_requirement = VERIFY_NONE,
+ .fd_in = -1,
+ .fd_out = -1,
+ .peer_cert_verified = FALSE,
+ .trigger_sni_changes =FALSE,
+ .have_set_peerdn = FALSE,
+ .host = NULL,
+ .peercert = NULL,
+ .peerdn = NULL,
+ .ciphersuite = NULL,
+ .received_sni = NULL,
+
+ .tls_certificate = NULL,
+ .tls_privatekey = NULL,
+ .tls_sni = NULL,
+ .tls_verify_certificates = NULL,
+ .tls_crl = NULL,
+ .tls_require_ciphers =NULL,
+
+ .exp_tls_certificate = NULL,
+ .exp_tls_privatekey = NULL,
+ .exp_tls_verify_certificates = NULL,
+ .exp_tls_crl = NULL,
+ .exp_tls_require_ciphers = NULL,
+ .exp_tls_ocsp_file = NULL,
+ .exp_tls_verify_cert_hostnames = NULL,
#ifndef DISABLE_EVENT
- NULL,
+ .event_action = NULL,
#endif
- NULL,
- NULL, 0, 0, 0, 0,
+ .tlsp = NULL,
+
+ .xfer_buffer = NULL,
+ .xfer_buffer_lwm = 0,
+ .xfer_buffer_hwm = 0,
+ .xfer_eof = 0,
+ .xfer_error = 0,
};
/* Not only do we have our own APIs which don't pass around state, assuming
/* Set this to control gnutls_global_set_log_level(); values 0 to 9 will setup
the library logging; a value less than 0 disables the calls to set up logging
-callbacks. */
+callbacks. Possibly GNuTLS also looks for an environment variable
+"GNUTLS_DEBUG_LEVEL". */
#ifndef EXIM_GNUTLS_LIBRARY_LOG_LEVEL
# define EXIM_GNUTLS_LIBRARY_LOG_LEVEL -1
#endif
+static int
+tls_add_certfile(exim_gnutls_state_st * state, const host_item * host,
+ uschar * certfile, uschar * keyfile, uschar ** errstr)
+{
+int rc = gnutls_certificate_set_x509_key_file(state->x509_cred,
+ CS certfile, CS keyfile, GNUTLS_X509_FMT_PEM);
+exim_gnutls_err_check(
+ string_sprintf("cert/key setup: cert=%s key=%s", certfile, keyfile));
+return OK;
+}
+
+
/*************************************************
* Variables re-expanded post-SNI *
*************************************************/
*/
static int
-tls_expand_session_files(exim_gnutls_state_st *state, uschar ** errstr)
+tls_expand_session_files(exim_gnutls_state_st * state, uschar ** errstr)
{
struct stat statbuf;
int rc;
if (!host) /* server */
if (!state->received_sni)
{
- if (state->tls_certificate &&
- (Ustrstr(state->tls_certificate, US"tls_sni") ||
- Ustrstr(state->tls_certificate, US"tls_in_sni") ||
- Ustrstr(state->tls_certificate, US"tls_out_sni")
- ))
+ if ( state->tls_certificate
+ && ( Ustrstr(state->tls_certificate, US"tls_sni")
+ || Ustrstr(state->tls_certificate, US"tls_in_sni")
+ || Ustrstr(state->tls_certificate, US"tls_out_sni")
+ ) )
{
DEBUG(D_tls) debug_printf("We will re-expand TLS session files if we receive SNI.\n");
state->trigger_sni_changes = TRUE;
DEBUG(D_tls) debug_printf("TLS SNI: have a changed cert/key pair.\n");
}
- rc = gnutls_certificate_set_x509_key_file(state->x509_cred,
- CS state->exp_tls_certificate, CS state->exp_tls_privatekey,
- GNUTLS_X509_FMT_PEM);
- exim_gnutls_err_check(
- string_sprintf("cert/key setup: cert=%s key=%s",
- state->exp_tls_certificate, state->exp_tls_privatekey));
- DEBUG(D_tls) debug_printf("TLS: cert/key registered\n");
+ if (!host) /* server */
+ {
+ const uschar * clist = state->exp_tls_certificate;
+ const uschar * klist = state->exp_tls_privatekey;
+ int csep = 0, ksep = 0;
+ uschar * cfile, * kfile;
+
+ while (cfile = string_nextinlist(&clist, &csep, NULL, 0))
+ if (!(kfile = string_nextinlist(&klist, &ksep, NULL, 0)))
+ return tls_error(US"cert/key setup: out of keys", NULL, host, errstr);
+ else if ((rc = tls_add_certfile(state, host, cfile, kfile, errstr)))
+ return rc;
+ else
+ DEBUG(D_tls) debug_printf("TLS: cert/key %s registered\n", cfile);
+ }
+ else
+ {
+ if ((rc = tls_add_certfile(state, host,
+ state->exp_tls_certificate, state->exp_tls_privatekey, errstr)))
+ return rc;
+ DEBUG(D_tls) debug_printf("TLS: cert/key registered\n");
+ }
+
} /* tls_certificate */
}
else if (state->tls_sni)
DEBUG(D_tls) debug_printf("*** PROBABLY A BUG *** " \
- "have an SNI set for a client [%s]\n", state->tls_sni);
+ "have an SNI set for a server [%s]\n", state->tls_sni);
/* This is the priority string support,
http://www.gnutls.org/manual/html_node/Priority-Strings.html
if ((ret = gnutls_load_file(ptr, ocsp_response)) < 0)
{
DEBUG(D_tls) debug_printf("Failed to load ocsp stapling file %s\n",
- (char *)ptr);
+ CS ptr);
tls_in.ocsp = OCSP_NOT_RESP;
return GNUTLS_E_NO_CERTIFICATE_STATUS;
}
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;
}
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);
}
state->xfer_buffer = store_malloc(ssl_xfer_buffer_size);
receive_getc = tls_getc;
+receive_getbuf = tls_getbuf;
receive_get_cache = tls_get_cache;
receive_ungetc = tls_ungetc;
receive_feof = tls_feof;
+static BOOL
+tls_refill(unsigned lim)
+{
+exim_gnutls_state_st * state = &state_server;
+ssize_t inbytes;
+
+DEBUG(D_tls) debug_printf("Calling gnutls_record_recv(%p, %p, %u)\n",
+ state->session, state->xfer_buffer, ssl_xfer_buffer_size);
+
+if (smtp_receive_timeout > 0) alarm(smtp_receive_timeout);
+inbytes = gnutls_record_recv(state->session, state->xfer_buffer,
+ MIN(ssl_xfer_buffer_size, lim));
+alarm(0);
+
+/* Timeouts do not get this far; see command_timeout_handler().
+ A zero-byte return appears to mean that the TLS session has been
+ closed down, not that the socket itself has been closed down. Revert to
+ non-TLS handling. */
+
+if (sigalrm_seen)
+ {
+ DEBUG(D_tls) debug_printf("Got tls read timeout\n");
+ state->xfer_error = 1;
+ return FALSE;
+ }
+
+else if (inbytes == 0)
+ {
+ DEBUG(D_tls) debug_printf("Got TLS_EOF\n");
+
+ receive_getc = smtp_getc;
+ receive_getbuf = smtp_getbuf;
+ receive_get_cache = smtp_get_cache;
+ receive_ungetc = smtp_ungetc;
+ receive_feof = smtp_feof;
+ receive_ferror = smtp_ferror;
+ receive_smtp_buffered = smtp_buffered;
+
+ gnutls_deinit(state->session);
+ gnutls_certificate_free_credentials(state->x509_cred);
+
+ state->session = NULL;
+ state->tlsp->active = -1;
+ state->tlsp->bits = 0;
+ state->tlsp->certificate_verified = FALSE;
+ tls_channelbinding_b64 = NULL;
+ state->tlsp->cipher = NULL;
+ state->tlsp->peercert = NULL;
+ state->tlsp->peerdn = NULL;
+
+ return FALSE;
+ }
+
+/* Handle genuine errors */
+
+else if (inbytes < 0)
+ {
+ record_io_error(state, (int) inbytes, US"recv", NULL);
+ state->xfer_error = 1;
+ return FALSE;
+ }
+#ifndef DISABLE_DKIM
+dkim_exim_verify_feed(state->xfer_buffer, inbytes);
+#endif
+state->xfer_buffer_hwm = (int) inbytes;
+state->xfer_buffer_lwm = 0;
+return TRUE;
+}
+
/*************************************************
* TLS version of getc *
*************************************************/
int
tls_getc(unsigned lim)
{
-exim_gnutls_state_st *state = &state_server;
-if (state->xfer_buffer_lwm >= state->xfer_buffer_hwm)
- {
- ssize_t inbytes;
-
- DEBUG(D_tls) debug_printf("Calling gnutls_record_recv(%p, %p, %u)\n",
- state->session, state->xfer_buffer, ssl_xfer_buffer_size);
-
- if (smtp_receive_timeout > 0) alarm(smtp_receive_timeout);
- inbytes = gnutls_record_recv(state->session, state->xfer_buffer,
- MIN(ssl_xfer_buffer_size, lim));
- alarm(0);
-
- /* Timeouts do not get this far; see command_timeout_handler().
- A zero-byte return appears to mean that the TLS session has been
- closed down, not that the socket itself has been closed down. Revert to
- non-TLS handling. */
-
- if (sigalrm_seen)
- {
- DEBUG(D_tls) debug_printf("Got tls read timeout\n");
- state->xfer_error = 1;
- return EOF;
- }
-
- else if (inbytes == 0)
- {
- DEBUG(D_tls) debug_printf("Got TLS_EOF\n");
-
- receive_getc = smtp_getc;
- receive_get_cache = smtp_get_cache;
- receive_ungetc = smtp_ungetc;
- receive_feof = smtp_feof;
- receive_ferror = smtp_ferror;
- receive_smtp_buffered = smtp_buffered;
+exim_gnutls_state_st * state = &state_server;
- gnutls_deinit(state->session);
- gnutls_certificate_free_credentials(state->x509_cred);
+if (state->xfer_buffer_lwm >= state->xfer_buffer_hwm)
+ if (!tls_refill(lim))
+ return state->xfer_error ? EOF : smtp_getc(lim);
- state->session = NULL;
- state->tlsp->active = -1;
- state->tlsp->bits = 0;
- state->tlsp->certificate_verified = FALSE;
- tls_channelbinding_b64 = NULL;
- state->tlsp->cipher = NULL;
- state->tlsp->peercert = NULL;
- state->tlsp->peerdn = NULL;
+/* Something in the buffer; return next uschar */
- return smtp_getc(lim);
- }
+return state->xfer_buffer[state->xfer_buffer_lwm++];
+}
- /* Handle genuine errors */
+uschar *
+tls_getbuf(unsigned * len)
+{
+exim_gnutls_state_st * state = &state_server;
+unsigned size;
+uschar * buf;
- else if (inbytes < 0)
+if (state->xfer_buffer_lwm >= state->xfer_buffer_hwm)
+ if (!tls_refill(*len))
{
- record_io_error(state, (int) inbytes, US"recv", NULL);
- state->xfer_error = 1;
- return EOF;
+ if (!state->xfer_error) return smtp_getbuf(len);
+ *len = 0;
+ return NULL;
}
-#ifndef DISABLE_DKIM
- dkim_exim_verify_feed(state->xfer_buffer, inbytes);
-#endif
- state->xfer_buffer_hwm = (int) inbytes;
- state->xfer_buffer_lwm = 0;
- }
-/* Something in the buffer; return next uschar */
-
-return state->xfer_buffer[state->xfer_buffer_lwm++];
+if ((size = state->xfer_buffer_hwm - state->xfer_buffer_lwm) > *len)
+ size = *len;
+buf = &state->xfer_buffer[state->xfer_buffer_lwm];
+state->xfer_buffer_lwm += size;
+*len = size;
+return buf;
}
+
void
tls_get_cache()
{
}
+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
+ 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",
len = INT_MAX;
}
+#ifdef SUPPORT_CORK
+if (more != corked)
+ {
+ if (!more) (void) gnutls_record_uncork(state->session, 0);
+ corked = more;
+ }
+#endif
+
return (int) len;
}