SPDX: license tags (mostly by guesswork)
[exim.git] / src / src / tls-gnu.c
index b9f8d2a51773afbd4aabbe401be1070dedf9395b..c9876020255db3d92cd861a18ac613cd32e0cdbb 100644 (file)
@@ -2,11 +2,11 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
+/* Copyright (c) The Exim Maintainers 2020 - 2022 */
 /* Copyright (c) University of Cambridge 1995 - 2018 */
-/* Copyright (c) The Exim Maintainers 2020 */
-/* 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. */
+/* SPDX-License-Identifier: GPL-2.0-only */
 
 /* This file provides TLS/SSL support for Exim using the GnuTLS library,
 one of the available supported implementations.  This file is #included into
@@ -90,9 +90,6 @@ require current GnuTLS, then we'll drop support for the ancient libraries).
 #if GNUTLS_VERSION_NUMBER >= 0x030506 && !defined(DISABLE_OCSP)
 # define SUPPORT_SRV_OCSP_STACK
 #endif
-#if GNUTLS_VERSION_NUMBER >= 0x030600
-# define GNUTLS_AUTO_DHPARAMS
-#endif
 #if GNUTLS_VERSION_NUMBER >= 0x030603
 # define EXIM_HAVE_TLS1_3
 # define SUPPORT_GNUTLS_EXT_RAW_PARSE
@@ -125,6 +122,10 @@ require current GnuTLS, then we'll drop support for the ancient libraries).
 # endif
 #endif
 
+#if GNUTLS_VERSION_NUMBER >= 0x030702
+# define HAVE_GNUTLS_EXPORTER
+#endif
+
 #ifndef DISABLE_OCSP
 # include <gnutls/ocsp.h>
 #endif
@@ -266,13 +267,11 @@ static exim_gnutls_state_st state_server = {
   .fd_out =            -1,
 };
 
-#ifndef GNUTLS_AUTO_DHPARAMS
 /* dh_params are initialised once within the lifetime of a process using TLS;
 if we used TLS in a long-lived daemon, we'd have to reconsider this.  But we
 don't want to repeat this. */
 
 static gnutls_dh_params_t dh_server_params = NULL;
-#endif
 
 static int ssl_session_timeout = 7200; /* Two hours */
 
@@ -339,6 +338,9 @@ before, for now. */
 # endif /* AVOID_GNUTLS_PKCS11 */
 #endif
 
+#if GNUTLS_VERSION_NUMBER >= 0x030404
+# define HAVE_GNUTLS_PRF_RFC5705
+#endif
 
 
 
@@ -630,11 +632,6 @@ Argument:
 static void
 extract_exim_vars_from_tls_state(exim_gnutls_state_st * state)
 {
-#ifdef HAVE_GNUTLS_SESSION_CHANNEL_BINDING
-int old_pool;
-int rc;
-gnutls_datum_t channel;
-#endif
 tls_support * tlsp = state->tlsp;
 
 tlsp->active.sock = state->fd_out;
@@ -652,21 +649,46 @@ only available for use for authenticators while this TLS session is running. */
 
 tlsp->channelbinding = NULL;
 #ifdef HAVE_GNUTLS_SESSION_CHANNEL_BINDING
-channel.data = NULL;
-channel.size = 0;
-if ((rc = gnutls_session_channel_binding(state->session, GNUTLS_CB_TLS_UNIQUE, &channel)))
-  { DEBUG(D_tls) debug_printf("Channel binding error: %s\n", gnutls_strerror(rc)); }
-else
   {
-  /* Declare the taintedness of the binding info.  On server, untainted; on
-  client, tainted - being the Finish msg from the server. */
+  gnutls_datum_t channel = {.data = NULL, .size = 0};
+  int rc;
 
-  old_pool = store_pool;
-  store_pool = POOL_PERM;
-  tlsp->channelbinding = b64encode_taint(CUS channel.data, (int)channel.size,
-                                         !!state->host);
-  store_pool = old_pool;
-  DEBUG(D_tls) debug_printf("Have channel bindings cached for possible auth usage\n");
+# ifdef HAVE_GNUTLS_EXPORTER
+  if (gnutls_protocol_get_version(state->session) >= GNUTLS_TLS1_3)
+    {
+    rc = gnutls_session_channel_binding(state->session, GNUTLS_CB_TLS_EXPORTER, &channel);
+    tlsp->channelbind_exporter = TRUE;
+    }
+  else
+# elif defined(HAVE_GNUTLS_PRF_RFC5705)
+  /* Older libraries may not have GNUTLS_TLS1_3 defined! */
+  if (gnutls_protocol_get_version(state->session) > GNUTLS_TLS1_2)
+    {
+    uschar * buf = store_get(32, state->host ? GET_TAINTED : GET_UNTAINTED);
+    rc = gnutls_prf_rfc5705(state->session,
+                               (size_t)24,  "EXPORTER-Channel-Binding", (size_t)0, "",
+                               32, CS buf);
+    channel.data = buf;
+    channel.size = 32;
+    }
+  else
+# endif
+    rc = gnutls_session_channel_binding(state->session, GNUTLS_CB_TLS_UNIQUE, &channel);
+
+  if (rc)
+    { DEBUG(D_tls) debug_printf("extracting channel binding: %s\n", gnutls_strerror(rc)); }
+  else
+    {
+    int old_pool = store_pool;
+    /* Declare the taintedness of the binding info.  On server, untainted; on
+    client, tainted if we used the Finish msg from the server. */
+
+    store_pool = POOL_PERM;
+    tlsp->channelbinding = b64encode_taint(CUS channel.data, (int)channel.size,
+               !tlsp->channelbind_exporter && state->host ? GET_TAINTED : GET_UNTAINTED);
+    store_pool = old_pool;
+    DEBUG(D_tls) debug_printf("Have channel bindings cached for possible auth usage\n");
+    }
   }
 #endif
 
@@ -689,7 +711,6 @@ if (!state->host)
 
 
 
-#ifndef GNUTLS_AUTO_DHPARAMS
 /*************************************************
 *            Setup up DH parameters              *
 *************************************************/
@@ -712,7 +733,7 @@ init_server_dh(uschar ** errstr)
 {
 int fd, rc;
 unsigned int dh_bits;
-gnutls_datum_t m = {.data = NULL, .size = 0};
+gnutls_datum_t m;
 uschar filename_buf[PATH_MAX];
 uschar *filename = NULL;
 size_t sz;
@@ -725,6 +746,9 @@ DEBUG(D_tls) debug_printf("Initialising GnuTLS server params\n");
 if ((rc = gnutls_dh_params_init(&dh_server_params)))
   return tls_error_gnu(NULL, US"gnutls_dh_params_init", rc, errstr);
 
+m.data = NULL;
+m.size = 0;
+
 if (!expand_check(tls_dhparam, US"tls_dhparam", &exp_tls_dhparam, errstr))
   return DEFER;
 
@@ -874,12 +898,14 @@ if (rc < 0)
     return tls_error_sys(US"Unable to open temp file", errno, NULL, errstr);
   (void)exim_chown(temp_fn, exim_uid, exim_gid);   /* Probably not necessary */
 
-  /* GnuTLS overshoots!  If we ask for 2236, we might get 2237 or more.  But
-  there's no way to ask GnuTLS how many bits there really are.  We can ask
-  how many bits were used in a TLS session, but that's it!  The prime itself
-  is hidden behind too much abstraction.  So we ask for less, and proceed on
-  a wing and a prayer.  First attempt, subtracted 3 for 2233 and got 2240.  */
-
+  /* GnuTLS overshoots!
+   * If we ask for 2236, we might get 2237 or more.
+   * But there's no way to ask GnuTLS how many bits there really are.
+   * We can ask how many bits were used in a TLS session, but that's it!
+   * The prime itself is hidden behind too much abstraction.
+   * So we ask for less, and proceed on a wing and a prayer.
+   * First attempt, subtracted 3 for 2233 and got 2240.
+   */
   if (dh_bits >= EXIM_CLIENT_DH_MIN_BITS + 10)
     {
     dh_bits_gen = dh_bits - 10;
@@ -942,7 +968,6 @@ if (rc < 0)
 DEBUG(D_tls) debug_printf("initialized server D-H parameters\n");
 return OK;
 }
-#endif
 
 
 
@@ -1079,7 +1104,8 @@ static int
 tls_server_clienthello_ext(void * ctx, unsigned tls_id,
   const uschar * data, unsigned size)
 {
-/* https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml */
+/* The values for tls_id are documented here:
+https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml */
 switch (tls_id)
   {
   case 5:      /* Status Request */
@@ -1117,8 +1143,9 @@ tls_server_clienthello_cb(gnutls_session_t session, unsigned int htype,
   unsigned when, unsigned int incoming, const gnutls_datum_t * msg)
 {
 /* Call fn for each extension seen.  3.6.3 onwards */
-return gnutls_ext_raw_parse(NULL, tls_server_clienthello_ext, msg,
+int rc = gnutls_ext_raw_parse(NULL, tls_server_clienthello_ext, msg,
                           GNUTLS_EXT_RAW_FLAG_TLS_CLIENT_HELLO);
+return rc == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE ? 0 : rc;
 }
 
 
@@ -1526,9 +1553,14 @@ else if (  !tls_certificate && !tls_privatekey
 else
   DEBUG(D_tls) debug_printf("TLS: not preloading server certs\n");
 
-/* If tls_verify_certificates is non-empty and has no $, load CAs */
+/* If tls_verify_certificates is non-empty and has no $, load CAs.
+If none was configured and we can't handle "system", treat as empty. */
 
-if (opt_set_and_noexpand(tls_verify_certificates))
+if (  opt_set_and_noexpand(tls_verify_certificates)
+#ifndef SUPPORT_SYSDEFAULT_CABUNDLE
+   && Ustrcmp(tls_verify_certificates, "system") != 0
+#endif
+   )
   {
   if (tls_set_watch(tls_verify_certificates, FALSE))
     {
@@ -1580,6 +1612,9 @@ return lifetime;
 /* Preload whatever creds are static, onto a transport.  The client can then
 just copy the pointer as it starts up. */
 
+/*XXX this is not called for a cmdline send. But one needing to use >1 conn would benefit,
+and there seems little downside. */
+
 static void
 tls_client_creds_init(transport_instance * t, BOOL watch)
 {
@@ -1632,7 +1667,14 @@ else
   DEBUG(D_tls)
     debug_printf("TLS: not preloading client certs, for transport '%s'\n", t->name);
 
-if (opt_set_and_noexpand(ob->tls_verify_certificates))
+/* If tls_verify_certificates is non-empty and has no $, load CAs.
+If none was configured and we can't handle "system", treat as empty. */
+
+if (  opt_set_and_noexpand(ob->tls_verify_certificates)
+#ifndef SUPPORT_SYSDEFAULT_CABUNDLE
+   && Ustrcmp(ob->tls_verify_certificates, "system") != 0
+#endif
+   )
   {
   if (!watch || tls_set_watch(ob->tls_verify_certificates, FALSE))
     {
@@ -1848,7 +1890,8 @@ else
 provided. Experiment shows that, if the certificate file is empty, an unhelpful
 error message is provided. However, if we just refrain from setting anything up
 in that case, certificate verification fails, which seems to be the correct
-behaviour. */
+behaviour.
+If none was configured and we can't handle "system", treat as empty. */
 
 if (!state->lib_state.cabundle)
   {
@@ -1939,7 +1982,6 @@ tls_set_remaining_x509(exim_gnutls_state_st *state, uschar ** errstr)
 int rc;
 const host_item *host = state->host;  /* macro should be reconsidered? */
 
-#ifndef GNUTLS_AUTO_DHPARAMS
 /* Create D-H parameters, or read them from the cache file. This function does
 its own SMTP error messaging. This only happens for the server, TLS D-H ignores
 client-side params. */
@@ -1949,10 +1991,10 @@ if (!state->host)
   if (!dh_server_params)
     if ((rc = init_server_dh(errstr)) != OK) return rc;
 
-  /* Unnecessary & discouraged with 3.6.0 or later */
+  /* Unnecessary & discouraged with 3.6.0 or later, according to docs.  But without it,
+  no DHE- ciphers are advertised. */
   gnutls_certificate_set_dh_params(state->lib_state.x509_cred, dh_server_params);
   }
-#endif
 
 /* Link the credentials to the session. */
 
@@ -2005,7 +2047,7 @@ if (host)
 
   int old_pool = store_pool;
   store_pool = POOL_PERM;
-  state = store_get(sizeof(exim_gnutls_state_st), FALSE);
+  state = store_get(sizeof(exim_gnutls_state_st), GET_UNTAINTED);
   store_pool = old_pool;
 
   memcpy(state, &exim_gnutls_state_init, sizeof(exim_gnutls_state_init));
@@ -2099,7 +2141,7 @@ if (!state->lib_state.pri_string)
   if ((rc = creds_load_pristring(state, p, &errpos)))
     return tls_error_gnu(state, string_sprintf(
                        "gnutls_priority_init(%s) failed at offset %ld, \"%.6s..\"",
-                       p, errpos - CS p, errpos),
+                       p, (long)(errpos - CS p), errpos),
                    rc, errstr);
   }
 else
@@ -2325,7 +2367,7 @@ if (rc != GNUTLS_E_SHORT_MEMORY_BUFFER)
   exim_gnutls_peer_err(US"getting size for cert DN failed");
   return FAIL; /* should not happen */
   }
-dn_buf = store_get_perm(sz, TRUE);     /* tainted */
+dn_buf = store_get_perm(sz, GET_TAINTED);
 rc = gnutls_x509_crt_get_dn(crt, CS dn_buf, &sz);
 exim_gnutls_peer_err(US"failed to extract certificate DN [gnutls_x509_crt_get_dn(cert 0)]");
 
@@ -2405,8 +2447,8 @@ else
       for (nrec = 0; state->dane_data_len[nrec]; ) nrec++;
       nrec++;
 
-      dd = store_get(nrec * sizeof(uschar *), FALSE);
-      ddl = store_get(nrec * sizeof(int), FALSE);
+      dd = store_get(nrec * sizeof(uschar *), GET_UNTAINTED);
+      ddl = store_get(nrec * sizeof(int), GET_UNTAINTED);
       nrec--;
 
       if ((rc = dane_state_init(&s, 0)))
@@ -2652,7 +2694,7 @@ if (sni_type != GNUTLS_NAME_DNS)
 /* We now have a UTF-8 string in sni_name */
 old_pool = store_pool;
 store_pool = POOL_PERM;
-state->received_sni = string_copy_taint(US sni_name, TRUE);
+state->received_sni = string_copy_taint(US sni_name, GET_TAINTED);
 store_pool = old_pool;
 
 /* We set this one now so that variable expansions below will work */
@@ -2711,7 +2753,7 @@ if ((cert_list = gnutls_certificate_get_peers(session, &cert_list_size)))
 
   state->tlsp->peercert = crt;
   if ((yield = event_raise(state->event_action,
-             US"tls:cert", string_sprintf("%d", cert_list_size))))
+             US"tls:cert", string_sprintf("%d", cert_list_size), &errno)))
     {
     log_write(0, LOG_MAIN,
              "SSL verify denied by event-action: depth=%d: %s",
@@ -2840,12 +2882,12 @@ NULL plist return for silent no-ALPN.
 */
 
 static BOOL
-tls_alpn_plist(const uschar * tls_alpn, const gnutls_datum_t ** plist, unsigned * plen,
+tls_alpn_plist(uschar ** tls_alpn, const gnutls_datum_t ** plist, unsigned * plen,
   uschar ** errstr)
 {
 uschar * exp_alpn;
 
-if (!expand_check(tls_alpn, US"tls_alpn", &exp_alpn, errstr))
+if (!expand_check(*tls_alpn, US"tls_alpn", &exp_alpn, errstr))
   return FALSE;
 
 if (!exp_alpn)
@@ -2863,7 +2905,7 @@ else
 
   while (string_nextinlist(&list, &sep, NULL, 0)) cnt++;
 
-  p = store_get(sizeof(gnutls_datum_t) * cnt, is_tainted(exp_alpn));
+  p = store_get(sizeof(gnutls_datum_t) * cnt, exp_alpn);
   list = exp_alpn;
   for (int i = 0; s = string_nextinlist(&list, &sep, NULL, 0); i++)
     { p[i].data = s; p[i].size = Ustrlen(s); }
@@ -2875,11 +2917,12 @@ return TRUE;
 static void
 tls_server_set_acceptable_alpns(exim_gnutls_state_st * state, uschar ** errstr)
 {
+uschar * local_alpn = string_copy(tls_alpn);
 int rc;
 const gnutls_datum_t * plist;
 unsigned plen;
 
-if (tls_alpn_plist(tls_alpn, &plist, &plen, errstr) && plist)
+if (tls_alpn_plist(&local_alpn, &plist, &plen, errstr) && plist)
   {
   /* This seems to be only mandatory if the client sends an ALPN extension;
   not trying ALPN is ok. Need to decide how to support server-side must-alpn. */
@@ -3033,6 +3076,9 @@ ALARM_CLR(0);
 
 if (rc != GNUTLS_E_SUCCESS)
   {
+  DEBUG(D_tls) debug_printf(" error %d from gnutls_handshake: %s\n",
+    rc, gnutls_strerror(rc));
+
   /* 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. */
@@ -3040,15 +3086,19 @@ if (rc != GNUTLS_E_SUCCESS)
   if (sigalrm_seen)
     {
     tls_error(US"gnutls_handshake", US"timed out", NULL, errstr);
+#ifndef DISABLE_EVENT
+    (void) event_raise(event_action, US"tls:fail:connect", *errstr, NULL);
+#endif
     gnutls_db_remove_session(state->session);
     }
   else
     {
     tls_error_gnu(state, US"gnutls_handshake", rc, errstr);
+#ifndef DISABLE_EVENT
+    (void) event_raise(event_action, US"tls:fail:connect", *errstr, NULL);
+#endif
     (void) gnutls_alert_send_appropriate(state->session, rc);
     gnutls_deinit(state->session);
-    gnutls_certificate_free_credentials(state->lib_state.x509_cred);
-    state->lib_state = null_tls_preload;
     millisleep(500);
     shutdown(state->fd_out, SHUT_WR);
     for (int i = 1024; fgetc(smtp_in) != EOF && i > 0; ) i--;  /* drain skt */
@@ -3124,10 +3174,10 @@ state->xfer_buffer = store_malloc(ssl_xfer_buffer_size);
 receive_getc = tls_getc;
 receive_getbuf = tls_getbuf;
 receive_get_cache = tls_get_cache;
+receive_hasc = tls_hasc;
 receive_ungetc = tls_ungetc;
 receive_feof = tls_feof;
 receive_ferror = tls_ferror;
-receive_smtp_buffered = tls_smtp_buffered;
 
 return OK;
 }
@@ -3177,8 +3227,8 @@ for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
      rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)
     ) if (rr->type == T_TLSA) i++;
 
-dane_data = store_get(i * sizeof(uschar *), FALSE);
-dane_data_len = store_get(i * sizeof(int), FALSE);
+dane_data = store_get(i * sizeof(uschar *), GET_UNTAINTED);
+dane_data_len = store_get(i * sizeof(int), GET_UNTAINTED);
 
 i = 0;
 for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
@@ -3236,25 +3286,28 @@ however avoid storing and retrieving session information. */
 
 static void
 tls_retrieve_session(tls_support * tlsp, gnutls_session_t session,
-  host_item * host, smtp_transport_options_block * ob)
+  smtp_connect_args * conn_args, smtp_transport_options_block * ob)
 {
 tlsp->resumption = RESUME_SUPPORTED;
-if (verify_check_given_host(CUSS &ob->tls_resumption_hosts, host) == OK)
+
+if (!conn_args->have_lbserver)
+  { DEBUG(D_tls) debug_printf("resumption not supported on continued-connection\n"); }
+else if (verify_check_given_host(CUSS &ob->tls_resumption_hosts, conn_args->host) == OK)
   {
   dbdata_tls_session * dt;
   int len, rc;
   open_db dbblock, * dbm_file;
 
-  DEBUG(D_tls)
-    debug_printf("check for resumable session for %s\n", host->address);
   tlsp->host_resumable = TRUE;
+  tls_client_resmption_key(tlsp, conn_args, ob);
+
   tlsp->resumption |= RESUME_CLIENT_REQUESTED;
   if ((dbm_file = dbfn_open(US"tls", O_RDONLY, &dbblock, FALSE, FALSE)))
     {
-    /* Key for the db is the IP.  We'd like to filter the retrieved session
-    for ticket advisory expiry, but 3.6.1 seems to give no access to that */
+    /* We'd like to filter the retrieved session for ticket advisory expiry,
+    but 3.6.1 seems to give no access to that */
 
-    if ((dt = dbfn_read_with_length(dbm_file, host->address, &len)))
+    if ((dt = dbfn_read_with_length(dbm_file, tlsp->resume_index, &len)))
       if (!(rc = gnutls_session_set_data(session,
                    CUS dt->session, (size_t)len - sizeof(dbdata_tls_session))))
        {
@@ -3291,7 +3344,7 @@ if (gnutls_session_get_flags(session) & GNUTLS_SFLAGS_SESSION_TICKET)
       {
       open_db dbblock, * dbm_file;
       int dlen = sizeof(dbdata_tls_session) + tkt.size;
-      dbdata_tls_session * dt = store_get(dlen, TRUE);
+      dbdata_tls_session * dt = store_get(dlen, GET_TAINTED);
 
       DEBUG(D_tls) debug_printf("session data size %u\n", (unsigned)tkt.size);
       memcpy(dt->session, tkt.data, tkt.size);
@@ -3300,8 +3353,7 @@ if (gnutls_session_get_flags(session) & GNUTLS_SFLAGS_SESSION_TICKET)
       if ((dbm_file = dbfn_open(US"tls", O_RDWR, &dbblock, FALSE, FALSE)))
        {
        /* key for the db is the IP */
-       dbfn_delete(dbm_file, host->address);
-       dbfn_write(dbm_file, host->address, dt, dlen);
+       dbfn_write(dbm_file, tlsp->resume_index, dt, dlen);
        dbfn_close(dbm_file);
 
        DEBUG(D_tls)
@@ -3336,14 +3388,14 @@ return 0;
 
 static void
 tls_client_resume_prehandshake(exim_gnutls_state_st * state,
-  tls_support * tlsp, host_item * host,
+  tls_support * tlsp, smtp_connect_args * conn_args,
   smtp_transport_options_block * ob)
 {
 gnutls_session_set_ptr(state->session, state);
 gnutls_handshake_set_hook_function(state->session,
   GNUTLS_HANDSHAKE_NEW_SESSION_TICKET, GNUTLS_HOOK_POST, tls_client_ticket_cb);
 
-tls_retrieve_session(tlsp, state->session, host, ob);
+tls_retrieve_session(tlsp, state->session, conn_args, ob);
 }
 
 static void
@@ -3441,7 +3493,7 @@ if (ob->tls_alpn)
   const gnutls_datum_t * plist;
   unsigned plen;
 
-  if (!tls_alpn_plist(ob->tls_alpn, &plist, &plen, errstr))
+  if (!tls_alpn_plist(&ob->tls_alpn, &plist, &plen, errstr))
     return FALSE;
   if (plist)
     if (gnutls_alpn_set_protocols(state->session, plist, plen, 0) != 0)
@@ -3533,7 +3585,7 @@ if (request_ocsp)
 #endif
 
 #ifdef EXIM_HAVE_TLS_RESUME
-tls_client_resume_prehandshake(state, tlsp, host, ob);
+tls_client_resume_prehandshake(state, tlsp, conn_args, ob);
 #endif
 
 #ifndef DISABLE_EVENT
@@ -3712,12 +3764,21 @@ if (!tlsp || tlsp->active.sock < 0) return;  /* TLS was not active */
 if (do_shutdown)
   {
   DEBUG(D_tls) debug_printf("tls_close(): shutting down TLS%s\n",
-    do_shutdown > 1 ? " (with response-wait)" : "");
+    do_shutdown > TLS_SHUTDOWN_NOWAIT ? " (with response-wait)" : "");
 
   tls_write(ct_ctx, NULL, 0, FALSE);   /* flush write buffer */
 
+#ifdef EXIM_TCP_CORK
+  if (do_shutdown == TLS_SHUTDOWN_WAIT)
+    (void) setsockopt(tlsp->active.sock, IPPROTO_TCP, EXIM_TCP_CORK, US &off, sizeof(off));
+#endif
+
+  /* The library seems to have no way to only wait for a peer's
+  shutdown, so handle the same as TLS_SHUTDOWN_WAIT */
+
   ALARM(2);
-  gnutls_bye(state->session, do_shutdown > 1 ? GNUTLS_SHUT_RDWR : GNUTLS_SHUT_WR);
+  gnutls_bye(state->session,
+      do_shutdown > TLS_SHUTDOWN_NOWAIT ? GNUTLS_SHUT_RDWR : GNUTLS_SHUT_WR);
   ALARM_CLR(0);
   }
 
@@ -3726,16 +3787,13 @@ if (!ct_ctx)    /* server */
   receive_getc =       smtp_getc;
   receive_getbuf =     smtp_getbuf;
   receive_get_cache =  smtp_get_cache;
+  receive_hasc =       smtp_hasc;
   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->lib_state.x509_cred);
-state->lib_state = null_tls_preload;
-
 tlsp->active.sock = -1;
 tlsp->active.tls_ctx = NULL;
 /* Leave bits, peercert, cipher, peerdn, certificate_verified set, for logging */
@@ -3840,6 +3898,13 @@ if (state->xfer_buffer_lwm >= state->xfer_buffer_hwm)
 return state->xfer_buffer[state->xfer_buffer_lwm++];
 }
 
+BOOL
+tls_hasc(void)
+{
+exim_gnutls_state_st * state = &state_server;
+return state->xfer_buffer_lwm < state->xfer_buffer_hwm;
+}
+
 uschar *
 tls_getbuf(unsigned * len)
 {
@@ -3864,12 +3929,15 @@ return buf;
 }
 
 
+/* Get up to the given number of bytes from any cached data, and feed to dkim. */
 void
-tls_get_cache(void)
+tls_get_cache(unsigned lim)
 {
 #ifndef DISABLE_DKIM
 exim_gnutls_state_st * state = &state_server;
 int n = state->xfer_buffer_hwm - state->xfer_buffer_lwm;
+if (n > lim)
+  n = lim;
 if (n > 0)
   dkim_exim_verify_feed(state->xfer_buffer+state->xfer_buffer_lwm, n);
 #endif
@@ -3877,7 +3945,7 @@ if (n > 0)
 
 
 BOOL
-tls_could_read(void)
+tls_could_getc(void)
 {
 return state_server.xfer_buffer_lwm < state_server.xfer_buffer_hwm
  || gnutls_record_check_pending(state_server.session) > 0;
@@ -4184,7 +4252,7 @@ DEBUG(D_tls)
 rc = gnutls_priority_init(&priority_cache, CS expciphers, &errpos);
 validate_check_rc(string_sprintf(
       "gnutls_priority_init(%s) failed at offset %ld, \"%.8s..\"",
-      expciphers, errpos - CS expciphers, errpos));
+      expciphers, (long)(errpos - CS expciphers), errpos));
 
 #undef return_deinit
 #undef validate_check_rc
@@ -4204,17 +4272,18 @@ return NULL;
 
 /* See a description in tls-openssl.c for an explanation of why this exists.
 
-Arguments:   a FILE* to print the results to
-Returns:     nothing
+Arguments:   string to append to
+Returns:     string
 */
 
-void
-tls_version_report(FILE *f)
+gstring *
+tls_version_report(gstring * g)
 {
-fprintf(f, "Library version: GnuTLS: Compile: %s\n"
-           "                         Runtime: %s\n",
-           LIBGNUTLS_VERSION,
-           gnutls_check_version(NULL));
+return string_fmt_append(g,
+    "Library version: GnuTLS: Compile: %s\n"
+    "                         Runtime: %s\n",
+            LIBGNUTLS_VERSION,
+            gnutls_check_version(NULL));
 }
 
 #endif /*!MACRO_PREDEF*/