Drain socket to get clean TCP FINs
[exim.git] / src / src / tls-gnu.c
index 4943f48b7d2e3318bb46c683d32c44e65b629185..383a00f4ededfc6b0aee181d9815f15f76259099 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Copyright (c) Phil Pennock 2012 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Copyright (c) Phil Pennock 2012 */
@@ -42,21 +42,22 @@ require current GnuTLS, then we'll drop support for the ancient libraries).
 /* needed to disable PKCS11 autoload unless requested */
 #if GNUTLS_VERSION_NUMBER >= 0x020c00
 # include <gnutls/pkcs11.h>
 /* needed to disable PKCS11 autoload unless requested */
 #if GNUTLS_VERSION_NUMBER >= 0x020c00
 # include <gnutls/pkcs11.h>
+# define SUPPORT_PARAM_TO_PK_BITS
 #endif
 #if GNUTLS_VERSION_NUMBER < 0x030103 && !defined(DISABLE_OCSP)
 # warning "GnuTLS library version too old; define DISABLE_OCSP in Makefile"
 # define DISABLE_OCSP
 #endif
 #endif
 #if GNUTLS_VERSION_NUMBER < 0x030103 && !defined(DISABLE_OCSP)
 # warning "GnuTLS library version too old; define DISABLE_OCSP in Makefile"
 # define DISABLE_OCSP
 #endif
-#if GNUTLS_VERSION_NUMBER < 0x020a00 && defined(EXPERIMENTAL_EVENT)
+#if GNUTLS_VERSION_NUMBER < 0x020a00 && !defined(DISABLE_EVENT)
 # warning "GnuTLS library version too old; tls:cert event unsupported"
 # warning "GnuTLS library version too old; tls:cert event unsupported"
-# undef EXPERIMENTAL_EVENT
+# define DISABLE_EVENT
 #endif
 #if GNUTLS_VERSION_NUMBER >= 0x030306
 # define SUPPORT_CA_DIR
 #else
 # undef  SUPPORT_CA_DIR
 #endif
 #endif
 #if GNUTLS_VERSION_NUMBER >= 0x030306
 # define SUPPORT_CA_DIR
 #else
 # undef  SUPPORT_CA_DIR
 #endif
-#if GNUTLS_VERSION_NUMBER >= 0x030314
+#if GNUTLS_VERSION_NUMBER >= 0x030014
 # define SUPPORT_SYSDEFAULT_CABUNDLE
 #endif
 
 # define SUPPORT_SYSDEFAULT_CABUNDLE
 #endif
 
@@ -120,8 +121,8 @@ typedef struct exim_gnutls_state {
   uschar *exp_tls_crl;
   uschar *exp_tls_require_ciphers;
   uschar *exp_tls_ocsp_file;
   uschar *exp_tls_crl;
   uschar *exp_tls_require_ciphers;
   uschar *exp_tls_ocsp_file;
-  uschar *exp_tls_verify_cert_hostnames;
-#ifdef EXPERIMENTAL_EVENT
+  const uschar *exp_tls_verify_cert_hostnames;
+#ifndef DISABLE_EVENT
   uschar *event_action;
 #endif
 
   uschar *event_action;
 #endif
 
@@ -140,7 +141,7 @@ static const exim_gnutls_state_st exim_gnutls_state_init = {
   NULL, NULL, NULL, NULL, NULL, NULL,
   NULL, NULL, NULL, NULL, NULL, NULL, NULL,
   NULL,
   NULL, NULL, NULL, NULL, NULL, NULL,
   NULL, NULL, NULL, NULL, NULL, NULL, NULL,
   NULL,
-#ifdef EXPERIMENTAL_EVENT
+#ifndef DISABLE_EVENT
                                             NULL,
 #endif
   NULL,
                                             NULL,
 #endif
   NULL,
@@ -176,6 +177,10 @@ static const char * const exim_default_gnutls_priority = "NORMAL";
 
 static BOOL exim_gnutls_base_init_done = FALSE;
 
 
 static BOOL exim_gnutls_base_init_done = FALSE;
 
+#ifndef DISABLE_OCSP
+static BOOL gnutls_buggy_ocsp = FALSE;
+#endif
+
 
 /* ------------------------------------------------------------------------ */
 /* macros */
 
 /* ------------------------------------------------------------------------ */
 /* macros */
@@ -335,7 +340,7 @@ tls_error(when, msg, state->host);
     } while (0)
 
 static int
     } while (0)
 
 static int
-import_cert(const gnutls_datum * cert, gnutls_x509_crt_t * crtp)
+import_cert(const gnutls_datum_t * cert, gnutls_x509_crt_t * crtp)
 {
 int rc;
 
 {
 int rc;
 
@@ -409,7 +414,7 @@ if (rc) {
 } else {
   old_pool = store_pool;
   store_pool = POOL_PERM;
 } else {
   old_pool = store_pool;
   store_pool = POOL_PERM;
-  tls_channelbinding_b64 = auth_b64encode(channel.data, (int)channel.size);
+  tls_channelbinding_b64 = b64encode(channel.data, (int)channel.size);
   store_pool = old_pool;
   DEBUG(D_tls) debug_printf("Have channel bindings cached for possible auth usage.\n");
 }
   store_pool = old_pool;
   DEBUG(D_tls) debug_printf("Have channel bindings cached for possible auth usage.\n");
 }
@@ -421,7 +426,7 @@ tlsp->sni =    state->received_sni;
 
 /* record our certificate */
   {
 
 /* record our certificate */
   {
-  const gnutls_datum * cert = gnutls_certificate_get_ours(state->session);
+  const gnutls_datum_t * cert = gnutls_certificate_get_ours(state->session);
   gnutls_x509_crt_t crt;
 
   tlsp->ourcert = cert && import_cert(cert, &crt)==0 ? crt : NULL;
   gnutls_x509_crt_t crt;
 
   tlsp->ourcert = cert && import_cert(cert, &crt)==0 ? crt : NULL;
@@ -453,7 +458,7 @@ init_server_dh(void)
 {
 int fd, rc;
 unsigned int dh_bits;
 {
 int fd, rc;
 unsigned int dh_bits;
-gnutls_datum m;
+gnutls_datum_t m;
 uschar filename_buf[PATH_MAX];
 uschar *filename = NULL;
 size_t sz;
 uschar filename_buf[PATH_MAX];
 uschar *filename = NULL;
 size_t sz;
@@ -561,8 +566,7 @@ if (fd >= 0)
     (void)close(fd);
     return tls_error(US"TLS cache not a file", NULL, NULL);
     }
     (void)close(fd);
     return tls_error(US"TLS cache not a file", NULL, NULL);
     }
-  fp = fdopen(fd, "rb");
-  if (!fp)
+  if (!(fp = fdopen(fd, "rb")))
     {
     saved_errno = errno;
     (void)close(fd);
     {
     saved_errno = errno;
     (void)close(fd);
@@ -571,14 +575,12 @@ if (fd >= 0)
     }
 
   m.size = statbuf.st_size;
     }
 
   m.size = statbuf.st_size;
-  m.data = malloc(m.size);
-  if (m.data == NULL)
+  if (!(m.data = malloc(m.size)))
     {
     fclose(fp);
     return tls_error(US"malloc failed", strerror(errno), NULL);
     }
     {
     fclose(fp);
     return tls_error(US"malloc failed", strerror(errno), NULL);
     }
-  sz = fread(m.data, m.size, 1, fp);
-  if (!sz)
+  if (!(sz = fread(m.data, m.size, 1, fp)))
     {
     saved_errno = errno;
     fclose(fp);
     {
     saved_errno = errno;
     fclose(fp);
@@ -660,9 +662,9 @@ if (rc < 0)
   if (rc != GNUTLS_E_SHORT_MEMORY_BUFFER)
     exim_gnutls_err_check(US"gnutls_dh_params_export_pkcs3(NULL) sizing");
   m.size = sz;
   if (rc != GNUTLS_E_SHORT_MEMORY_BUFFER)
     exim_gnutls_err_check(US"gnutls_dh_params_export_pkcs3(NULL) sizing");
   m.size = sz;
-  m.data = malloc(m.size);
-  if (m.data == NULL)
+  if (!(m.data = malloc(m.size)))
     return tls_error(US"memory allocation failed", strerror(errno), NULL);
     return tls_error(US"memory allocation failed", strerror(errno), NULL);
+
   /* this will return a size 1 less than the allocation size above */
   rc = gnutls_dh_params_export_pkcs3(dh_server_params, GNUTLS_X509_FMT_PEM,
       m.data, &sz);
   /* this will return a size 1 less than the allocation size above */
   rc = gnutls_dh_params_export_pkcs3(dh_server_params, GNUTLS_X509_FMT_PEM,
       m.data, &sz);
@@ -705,6 +707,74 @@ return OK;
 
 
 
 
 
 
+/* Create and install a selfsigned certificate, for use in server mode */
+
+static int
+tls_install_selfsign(exim_gnutls_state_st * state)
+{
+gnutls_x509_crt_t cert = NULL;
+time_t now;
+gnutls_x509_privkey_t pkey = NULL;
+const uschar * where;
+int rc;
+
+where = US"initialising pkey";
+if ((rc = gnutls_x509_privkey_init(&pkey))) goto err;
+
+where = US"initialising cert";
+if ((rc = gnutls_x509_crt_init(&cert))) goto err;
+
+where = US"generating pkey";
+if ((rc = gnutls_x509_privkey_generate(pkey, GNUTLS_PK_RSA,
+#ifdef SUPPORT_PARAM_TO_PK_BITS
+           gnutls_sec_param_to_pk_bits(GNUTLS_PK_RSA, GNUTLS_SEC_PARAM_LOW),
+#else
+           1024,
+#endif
+           0)))
+  goto err;
+
+where = US"configuring cert";
+now = 0;
+if (  (rc = gnutls_x509_crt_set_version(cert, 3))
+   || (rc = gnutls_x509_crt_set_serial(cert, &now, sizeof(now)))
+   || (rc = gnutls_x509_crt_set_activation_time(cert, now = time(NULL)))
+   || (rc = gnutls_x509_crt_set_expiration_time(cert, now + 60 * 60)) /* 1 hr */
+   || (rc = gnutls_x509_crt_set_key(cert, pkey))
+
+   || (rc = gnutls_x509_crt_set_dn_by_oid(cert,
+             GNUTLS_OID_X520_COUNTRY_NAME, 0, "UK", 2))
+   || (rc = gnutls_x509_crt_set_dn_by_oid(cert,
+             GNUTLS_OID_X520_ORGANIZATION_NAME, 0, "Exim Developers", 15))
+   || (rc = gnutls_x509_crt_set_dn_by_oid(cert,
+             GNUTLS_OID_X520_COMMON_NAME, 0,
+             smtp_active_hostname, Ustrlen(smtp_active_hostname)))
+   )
+  goto err;
+
+where = US"signing cert";
+if ((rc = gnutls_x509_crt_sign(cert, cert, pkey))) goto err;
+
+where = US"installing selfsign cert";
+                                       /* Since: 2.4.0 */
+if ((rc = gnutls_certificate_set_x509_key(state->x509_cred, &cert, 1, pkey)))
+  goto err;
+
+rc = OK;
+
+out:
+  if (cert) gnutls_x509_crt_deinit(cert);
+  if (pkey) gnutls_x509_privkey_deinit(pkey);
+  return rc;
+
+err:
+  rc = tls_error(where, gnutls_strerror(rc), NULL);
+  goto out;
+}
+
+
+
+
 /*************************************************
 *       Variables re-expanded post-SNI           *
 *************************************************/
 /*************************************************
 *       Variables re-expanded post-SNI           *
 *************************************************/
@@ -737,7 +807,6 @@ int cert_count;
 
 /* We check for tls_sni *before* expansion. */
 if (!host)     /* server */
 
 /* We check for tls_sni *before* expansion. */
 if (!host)     /* server */
-  {
   if (!state->received_sni)
     {
     if (state->tls_certificate &&
   if (!state->received_sni)
     {
     if (state->tls_certificate &&
@@ -758,7 +827,6 @@ if (!host)  /* server */
     saved_tls_verify_certificates = state->exp_tls_verify_certificates;
     saved_tls_crl = state->exp_tls_crl;
     }
     saved_tls_verify_certificates = state->exp_tls_verify_certificates;
     saved_tls_crl = state->exp_tls_crl;
     }
-  }
 
 rc = gnutls_certificate_allocate_credentials(&state->x509_cred);
 exim_gnutls_err_check(US"gnutls_certificate_allocate_credentials");
 
 rc = gnutls_certificate_allocate_credentials(&state->x509_cred);
 exim_gnutls_err_check(US"gnutls_certificate_allocate_credentials");
@@ -775,14 +843,13 @@ if (!expand_check_tlsvar(tls_certificate))
 
 /* certificate is mandatory in server, optional in client */
 
 
 /* certificate is mandatory in server, optional in client */
 
-if ((state->exp_tls_certificate == NULL) ||
-    (*state->exp_tls_certificate == '\0'))
-  {
+if (  !state->exp_tls_certificate
+   || !*state->exp_tls_certificate
+   )
   if (!host)
   if (!host)
-    return tls_error(US"no TLS server certificate is specified", NULL, NULL);
+    return tls_install_selfsign(state);
   else
     DEBUG(D_tls) debug_printf("TLS: no client certificate specified; okay\n");
   else
     DEBUG(D_tls) debug_printf("TLS: no client certificate specified; okay\n");
-  }
 
 if (state->tls_privatekey && !expand_check_tlsvar(tls_privatekey))
   return DEFER;
 
 if (state->tls_privatekey && !expand_check_tlsvar(tls_privatekey))
   return DEFER;
@@ -802,9 +869,9 @@ if (state->exp_tls_certificate && *state->exp_tls_certificate)
       state->exp_tls_certificate, state->exp_tls_privatekey);
 
   if (state->received_sni)
       state->exp_tls_certificate, state->exp_tls_privatekey);
 
   if (state->received_sni)
-    {
-    if ((Ustrcmp(state->exp_tls_certificate, saved_tls_certificate) == 0) &&
-        (Ustrcmp(state->exp_tls_privatekey, saved_tls_privatekey) == 0))
+    if (  Ustrcmp(state->exp_tls_certificate, saved_tls_certificate) == 0
+       && Ustrcmp(state->exp_tls_privatekey,  saved_tls_privatekey)  == 0
+       )
       {
       DEBUG(D_tls) debug_printf("TLS SNI: cert and key unchanged\n");
       }
       {
       DEBUG(D_tls) debug_printf("TLS SNI: cert and key unchanged\n");
       }
@@ -812,7 +879,6 @@ if (state->exp_tls_certificate && *state->exp_tls_certificate)
       {
       DEBUG(D_tls) debug_printf("TLS SNI: have a changed cert/key pair.\n");
       }
       {
       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,
 
   rc = gnutls_certificate_set_x509_key_file(state->x509_cred,
       CS state->exp_tls_certificate, CS state->exp_tls_privatekey,
@@ -831,18 +897,25 @@ if (  !host       /* server */
    && tls_ocsp_file
    )
   {
    && tls_ocsp_file
    )
   {
-  if (!expand_check(tls_ocsp_file, US"tls_ocsp_file",
-       &state->exp_tls_ocsp_file))
-    return DEFER;
+  if (gnutls_buggy_ocsp)
+    {
+    DEBUG(D_tls) debug_printf("GnuTLS library is buggy for OCSP; avoiding\n");
+    }
+  else
+    {
+    if (!expand_check(tls_ocsp_file, US"tls_ocsp_file",
+         &state->exp_tls_ocsp_file))
+      return DEFER;
 
 
-  /* Use the full callback method for stapling just to get observability.
-  More efficient would be to read the file once only, if it never changed
-  (due to SNI). Would need restart on file update, or watch datestamp.  */
+    /* Use the full callback method for stapling just to get observability.
+    More efficient would be to read the file once only, if it never changed
+    (due to SNI). Would need restart on file update, or watch datestamp.  */
 
 
-  gnutls_certificate_set_ocsp_status_request_function(state->x509_cred,
-    server_ocsp_stapling_cb, state->exp_tls_ocsp_file);
+    gnutls_certificate_set_ocsp_status_request_function(state->x509_cred,
+      server_ocsp_stapling_cb, state->exp_tls_ocsp_file);
 
 
-  DEBUG(D_tls) debug_printf("Set OCSP response file %s\n", &state->exp_tls_ocsp_file);
+    DEBUG(D_tls) debug_printf("OCSP response file = %s\n", state->exp_tls_ocsp_file);
+    }
   }
 #endif
 
   }
 #endif
 
@@ -857,6 +930,10 @@ if (state->tls_verify_certificates && *state->tls_verify_certificates)
   {
   if (!expand_check_tlsvar(tls_verify_certificates))
     return DEFER;
   {
   if (!expand_check_tlsvar(tls_verify_certificates))
     return DEFER;
+#ifndef SUPPORT_SYSDEFAULT_CABUNDLE
+  if (Ustrcmp(state->exp_tls_verify_certificates, "system") == 0)
+    state->exp_tls_verify_certificates = NULL;
+#endif
   if (state->tls_crl && *state->tls_crl)
     if (!expand_check_tlsvar(tls_crl))
       return DEFER;
   if (state->tls_crl && *state->tls_crl)
     if (!expand_check_tlsvar(tls_crl))
       return DEFER;
@@ -896,7 +973,7 @@ else
   but who knows if someone has some weird FIFO which always dumps some certs, or
   other weirdness.  The thing we really want to check is that it's not a
   directory, since while OpenSSL supports that, GnuTLS does not.
   but who knows if someone has some weird FIFO which always dumps some certs, or
   other weirdness.  The thing we really want to check is that it's not a
   directory, since while OpenSSL supports that, GnuTLS does not.
-  So s/!S_ISREG/S_ISDIR/ and change some messsaging ... */
+  So s/!S_ISREG/S_ISDIR/ and change some messaging ... */
   if (S_ISDIR(statbuf.st_mode))
     {
     DEBUG(D_tls)
   if (S_ISDIR(statbuf.st_mode))
     {
     DEBUG(D_tls)
@@ -1007,6 +1084,38 @@ return OK;
 *            Initialize for GnuTLS               *
 *************************************************/
 
 *            Initialize for GnuTLS               *
 *************************************************/
 
+
+#ifndef DISABLE_OCSP
+
+static BOOL
+tls_is_buggy_ocsp(void)
+{
+const uschar * s;
+uschar maj, mid, mic;
+
+s = CUS gnutls_check_version(NULL);
+maj = atoi(CCS s);
+if (maj == 3)
+  {
+  while (*s && *s != '.') s++;
+  mid = atoi(CCS ++s);
+  if (mid <= 2)
+    return TRUE;
+  else if (mid >= 5)
+    return FALSE;
+  else
+    {
+    while (*s && *s != '.') s++;
+    mic = atoi(CCS ++s);
+    return mic <= (mid == 3 ? 16 : 3);
+    }
+  }
+return FALSE;
+}
+
+#endif
+
+
 /* Called from both server and client code. In the case of a server, errors
 before actual TLS negotiation return DEFER.
 
 /* Called from both server and client code. In the case of a server, errors
 before actual TLS negotiation return DEFER.
 
@@ -1070,6 +1179,11 @@ if (!exim_gnutls_base_init_done)
     }
 #endif
 
     }
 #endif
 
+#ifndef DISABLE_OCSP
+  if (tls_ocsp_file && (gnutls_buggy_ocsp = tls_is_buggy_ocsp()))
+    log_write(0, LOG_MAIN, "OCSP unusable with this GnuTLS library version");
+#endif
+
   exim_gnutls_base_init_done = TRUE;
   }
 
   exim_gnutls_base_init_done = TRUE;
   }
 
@@ -1225,7 +1339,7 @@ static int
 peer_status(exim_gnutls_state_st *state)
 {
 uschar cipherbuf[256];
 peer_status(exim_gnutls_state_st *state)
 {
 uschar cipherbuf[256];
-const gnutls_datum *cert_list;
+const gnutls_datum_t *cert_list;
 int old_pool, rc;
 unsigned int cert_list_size = 0;
 gnutls_protocol_t protocol;
 int old_pool, rc;
 unsigned int cert_list_size = 0;
 gnutls_protocol_t protocol;
@@ -1394,9 +1508,9 @@ else
   if (state->exp_tls_verify_cert_hostnames)
     {
     int sep = 0;
   if (state->exp_tls_verify_cert_hostnames)
     {
     int sep = 0;
-    uschar * list = state->exp_tls_verify_cert_hostnames;
+    const uschar * list = state->exp_tls_verify_cert_hostnames;
     uschar * name;
     uschar * name;
-    while (name = string_nextinlist(&list, &sep, NULL, 0))
+    while ((name = string_nextinlist(&list, &sep, NULL, 0)))
       if (gnutls_x509_crt_check_hostname(state->tlsp->peercert, CS name))
        break;
     if (!name)
       if (gnutls_x509_crt_check_hostname(state->tlsp->peercert, CS name))
        break;
     if (!name)
@@ -1546,7 +1660,7 @@ return 0;
 #endif
 
 
 #endif
 
 
-#ifdef EXPERIMENTAL_EVENT
+#ifndef DISABLE_EVENT
 /*
 We use this callback to get observability and detail-level control
 for an exim TLS connection (either direction), raising a tls:cert event
 /*
 We use this callback to get observability and detail-level control
 for an exim TLS connection (either direction), raising a tls:cert event
@@ -1559,7 +1673,7 @@ Return 0 for the handshake to continue or non-zero to terminate.
 static int
 verify_cb(gnutls_session_t session)
 {
 static int
 verify_cb(gnutls_session_t session)
 {
-const gnutls_datum * cert_list;
+const gnutls_datum_t * cert_list;
 unsigned int cert_list_size = 0;
 gnutls_x509_crt_t crt;
 int rc;
 unsigned int cert_list_size = 0;
 gnutls_x509_crt_t crt;
 int rc;
@@ -1670,7 +1784,7 @@ else
   gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_IGNORE);
   }
 
   gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_IGNORE);
   }
 
-#ifdef EXPERIMENTAL_EVENT
+#ifndef DISABLE_EVENT
 if (event_action)
   {
   state->event_action = event_action;
 if (event_action)
   {
   state->event_action = event_action;
@@ -1701,32 +1815,36 @@ if (!state->tlsp->on_connect)
 that the GnuTLS library doesn't. */
 
 gnutls_transport_set_ptr2(state->session,
 that the GnuTLS library doesn't. */
 
 gnutls_transport_set_ptr2(state->session,
-    (gnutls_transport_ptr)(long) fileno(smtp_in),
-    (gnutls_transport_ptr)(long) fileno(smtp_out));
+    (gnutls_transport_ptr_t)(long) fileno(smtp_in),
+    (gnutls_transport_ptr_t)(long) fileno(smtp_out));
 state->fd_in = fileno(smtp_in);
 state->fd_out = fileno(smtp_out);
 
 sigalrm_seen = FALSE;
 if (smtp_receive_timeout > 0) alarm(smtp_receive_timeout);
 do
 state->fd_in = fileno(smtp_in);
 state->fd_out = fileno(smtp_out);
 
 sigalrm_seen = FALSE;
 if (smtp_receive_timeout > 0) alarm(smtp_receive_timeout);
 do
-  {
   rc = gnutls_handshake(state->session);
   rc = gnutls_handshake(state->session);
-  } while ((rc == GNUTLS_E_AGAIN) ||
-      (rc == GNUTLS_E_INTERRUPTED && !sigalrm_seen));
+while (rc == GNUTLS_E_AGAIN ||  rc == GNUTLS_E_INTERRUPTED && !sigalrm_seen);
 alarm(0);
 
 if (rc != GNUTLS_E_SUCCESS)
   {
 alarm(0);
 
 if (rc != GNUTLS_E_SUCCESS)
   {
-  tls_error(US"gnutls_handshake",
-      sigalrm_seen ? "timed out" : gnutls_strerror(rc), NULL);
   /* It seems that, except in the case of a timeout, we have to close the
   connection right here; otherwise if the other end is running OpenSSL it hangs
   until the server times out. */
 
   /* It seems that, except in the case of a timeout, we have to close the
   connection right here; otherwise if the other end is running OpenSSL it hangs
   until the server times out. */
 
-  if (!sigalrm_seen)
+  if (sigalrm_seen)
+    tls_error(US"gnutls_handshake", "timed out", NULL);
+  else
     {
     {
+    tls_error(US"gnutls_handshake", gnutls_strerror(rc), NULL);
+    gnutls_alert_send_appropriate(state->session, rc);
+    millisleep(500);
+    shutdown(fileno(smtp_out), SHUT_WR);
+    for (rc = 1024; fgetc(smtp_in) != EOF && rc > 0; ) rc--;   /* drain skt */
     (void)fclose(smtp_out);
     (void)fclose(smtp_in);
     (void)fclose(smtp_out);
     (void)fclose(smtp_in);
+    smtp_out = smtp_in = NULL;
     }
 
   return FAIL;
     }
 
   return FAIL;
@@ -1751,8 +1869,7 @@ if (  state->verify_requirement != VERIFY_NONE
 
 /* Figure out peer DN, and if authenticated, etc. */
 
 
 /* Figure out peer DN, and if authenticated, etc. */
 
-rc = peer_status(state);
-if (rc != OK) return rc;
+if ((rc = peer_status(state)) != OK) return rc;
 
 /* Sets various Exim expansion variables; always safe within server */
 
 
 /* Sets various Exim expansion variables; always safe within server */
 
@@ -1764,6 +1881,7 @@ and initialize appropriately. */
 state->xfer_buffer = store_malloc(ssl_xfer_buffer_size);
 
 receive_getc = tls_getc;
 state->xfer_buffer = store_malloc(ssl_xfer_buffer_size);
 
 receive_getc = tls_getc;
+receive_get_cache = tls_get_cache;
 receive_ungetc = tls_ungetc;
 receive_feof = tls_feof;
 receive_ferror = tls_ferror;
 receive_ungetc = tls_ungetc;
 receive_feof = tls_feof;
 receive_ferror = tls_ferror;
@@ -1781,7 +1899,12 @@ tls_client_setup_hostname_checks(host_item * host, exim_gnutls_state_st * state,
 {
 if (verify_check_given_host(&ob->tls_verify_cert_hostnames, host) == OK)
   {
 {
 if (verify_check_given_host(&ob->tls_verify_cert_hostnames, host) == OK)
   {
-  state->exp_tls_verify_cert_hostnames = host->name;
+  state->exp_tls_verify_cert_hostnames =
+#ifdef SUPPORT_I18N
+    string_domain_utf8_to_alabel(host->name, NULL);
+#else
+    host->name;
+#endif
   DEBUG(D_tls)
     debug_printf("TLS: server cert verification includes hostname: \"%s\".\n",
                    state->exp_tls_verify_cert_hostnames);
   DEBUG(D_tls)
     debug_printf("TLS: server cert verification includes hostname: \"%s\".\n",
                    state->exp_tls_verify_cert_hostnames);
@@ -1810,7 +1933,7 @@ tls_client_start(int fd, host_item *host,
     address_item *addr ARG_UNUSED,
     transport_instance *tb
 #ifdef EXPERIMENTAL_DANE
     address_item *addr ARG_UNUSED,
     transport_instance *tb
 #ifdef EXPERIMENTAL_DANE
-    , dne_answer * unused_tlsa_dnsa
+    , dns_answer * unused_tlsa_dnsa
 #endif
     )
 {
 #endif
     )
 {
@@ -1850,13 +1973,13 @@ if ((rc = tls_init(host, ob->tls_certificate, ob->tls_privatekey,
   gnutls_dh_set_prime_bits(state->session, dh_min_bits);
   }
 
   gnutls_dh_set_prime_bits(state->session, dh_min_bits);
   }
 
-/* Stick to the old behaviour for compatibility if tls_verify_certificates is 
+/* Stick to the old behaviour for compatibility if tls_verify_certificates is
 set but both tls_verify_hosts and tls_try_verify_hosts are unset. Check only
 the specified host patterns if one of them is defined */
 
 if (  (  state->exp_tls_verify_certificates
       && !ob->tls_verify_hosts
 set but both tls_verify_hosts and tls_try_verify_hosts are unset. Check only
 the specified host patterns if one of them is defined */
 
 if (  (  state->exp_tls_verify_certificates
       && !ob->tls_verify_hosts
-      && !ob->tls_try_verify_hosts
+      && (!ob->tls_try_verify_hosts || !*ob->tls_try_verify_hosts)
       )
     || verify_check_given_host(&ob->tls_verify_hosts, host) == OK
    )
       )
     || verify_check_given_host(&ob->tls_verify_hosts, host) == OK
    )
@@ -1896,7 +2019,7 @@ if (request_ocsp)
   }
 #endif
 
   }
 #endif
 
-#ifdef EXPERIMENTAL_EVENT
+#ifndef DISABLE_EVENT
 if (tb->event_action)
   {
   state->event_action = tb->event_action;
 if (tb->event_action)
   {
   state->event_action = tb->event_action;
@@ -1905,7 +2028,7 @@ if (tb->event_action)
   }
 #endif
 
   }
 #endif
 
-gnutls_transport_set_ptr(state->session, (gnutls_transport_ptr)(long) fd);
+gnutls_transport_set_ptr(state->session, (gnutls_transport_ptr_t)(long) fd);
 state->fd_in = fd;
 state->fd_out = fd;
 
 state->fd_in = fd;
 state->fd_out = fd;
 
@@ -1922,8 +2045,13 @@ do
 alarm(0);
 
 if (rc != GNUTLS_E_SUCCESS)
 alarm(0);
 
 if (rc != GNUTLS_E_SUCCESS)
-  return tls_error(US"gnutls_handshake",
-      sigalrm_seen ? "timed out" : gnutls_strerror(rc), state->host);
+  if (sigalrm_seen)
+    {
+    gnutls_alert_send(state->session, GNUTLS_AL_FATAL, GNUTLS_A_USER_CANCELED);
+    return tls_error(US"gnutls_handshake", "timed out", state->host);
+    }
+  else
+    return tls_error(US"gnutls_handshake", gnutls_strerror(rc), state->host);
 
 DEBUG(D_tls) debug_printf("gnutls_handshake was successful\n");
 
 
 DEBUG(D_tls) debug_printf("gnutls_handshake was successful\n");
 
@@ -2000,7 +2128,7 @@ if (!state->tlsp || state->tlsp->active < 0) return;  /* TLS was not active */
 
 if (shutdown)
   {
 
 if (shutdown)
   {
-  DEBUG(D_tls) debug_printf("tls_close(): shutting down TLS\n");
+  DEBUG(D_tls) debug_printf("tls_close() from '%s': shutting down TLS\n");
   gnutls_bye(state->session, GNUTLS_SHUT_WR);
   }
 
   gnutls_bye(state->session, GNUTLS_SHUT_WR);
   }
 
@@ -2050,15 +2178,24 @@ if (state->xfer_buffer_lwm >= state->xfer_buffer_hwm)
     ssl_xfer_buffer_size);
   alarm(0);
 
     ssl_xfer_buffer_size);
   alarm(0);
 
-  /* A zero-byte return appears to mean that the TLS session has been
+  /* 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. */
 
      closed down, not that the socket itself has been closed down. Revert to
      non-TLS handling. */
 
-  if (inbytes == 0)
+  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;
     {
     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_ungetc = smtp_ungetc;
     receive_feof = smtp_feof;
     receive_ferror = smtp_ferror;
@@ -2097,6 +2234,17 @@ if (state->xfer_buffer_lwm >= state->xfer_buffer_hwm)
 return state->xfer_buffer[state->xfer_buffer_lwm++];
 }
 
 return state->xfer_buffer[state->xfer_buffer_lwm++];
 }
 
+void
+tls_get_cache()
+{
+#ifndef DISABLE_DKIM
+exim_gnutls_state_st * state = &state_server;
+int n = state->xfer_buffer_hwm - state->xfer_buffer_lwm;
+if (n > 0)
+  dkim_exim_verify_feed(state->xfer_buffer+state->xfer_buffer_lwm, n);
+#endif
+}
+