SPDX: license tags (mostly by guesswork)
[exim.git] / src / src / tls-gnu.c
index 618caf6a225f19b35fb4780aff6d7b6df5b34231..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;
 }
 
 
@@ -1585,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)
 {
@@ -1952,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. */
@@ -1962,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. */
 
@@ -2018,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));
@@ -2338,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)]");
 
@@ -2418,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)))
@@ -2665,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 */
@@ -2724,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",
@@ -2853,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)
@@ -2876,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); }
@@ -2888,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. */
@@ -3046,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. */
@@ -3053,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 */
@@ -3137,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;
 }
@@ -3190,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;
@@ -3249,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))))
        {
@@ -3304,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);
@@ -3313,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)
@@ -3349,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
@@ -3454,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)
@@ -3546,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
@@ -3725,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);
   }
 
@@ -3739,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 */
@@ -3853,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)
 {
@@ -3877,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
@@ -3890,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;
@@ -4217,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*/