GnuTLS: repeat lowlevel read and write operations while they request retry
[exim.git] / src / src / tls-gnu.c
index dfe09200b57f07ba7fb0be38c443b6f522bd9ee3..c404dc29a0a3b1739b5702c313382a29f09b92a3 100644 (file)
@@ -39,6 +39,7 @@ require current GnuTLS, then we'll drop support for the ancient libraries).
 #include <gnutls/x509.h>
 /* man-page is incorrect, gnutls_rnd() is not in gnutls.h: */
 #include <gnutls/crypto.h>
+
 /* needed to disable PKCS11 autoload unless requested */
 #if GNUTLS_VERSION_NUMBER >= 0x020c00
 # include <gnutls/pkcs11.h>
@@ -60,6 +61,9 @@ require current GnuTLS, then we'll drop support for the ancient libraries).
 #if GNUTLS_VERSION_NUMBER >= 0x030014
 # define SUPPORT_SYSDEFAULT_CABUNDLE
 #endif
+#if GNUTLS_VERSION_NUMBER >= 0x030104
+# define GNUTLS_CERT_VFY_STATUS_PRINT
+#endif
 #if GNUTLS_VERSION_NUMBER >= 0x030109
 # define SUPPORT_CORK
 #endif
@@ -124,7 +128,7 @@ typedef struct exim_gnutls_state {
   BOOL                 peer_dane_verified;
   BOOL                 trigger_sni_changes;
   BOOL                 have_set_peerdn;
-  const struct host_item *host;
+  const struct host_item *host;                /* NULL if server */
   gnutls_x509_crt_t    peercert;
   uschar               *peerdn;
   uschar               *ciphersuite;
@@ -262,7 +266,7 @@ before, for now. */
 
 #define exim_gnutls_err_check(rc, Label) do { \
   if ((rc) != GNUTLS_E_SUCCESS) \
-    return tls_error((Label), gnutls_strerror(rc), host, errstr); \
+    return tls_error((Label), US gnutls_strerror(rc), host, errstr); \
   } while (0)
 
 #define expand_check_tlsvar(Varname, errstr) \
@@ -328,11 +332,11 @@ Returns:    OK/DEFER/FAIL
 */
 
 static int
-tls_error(const uschar *prefix, const char *msg, const host_item *host,
+tls_error(const uschar *prefix, const uschar *msg, const host_item *host,
   uschar ** errstr)
 {
 if (errstr)
-  *errstr = string_sprintf("(%s)%s%s", prefix, msg ? ": " : "", msg ? msg : "");
+  *errstr = string_sprintf("(%s)%s%s", prefix, msg ? ": " : "", msg ? msg : US"");
 return host ? FAIL : DEFER;
 }
 
@@ -357,14 +361,14 @@ Returns:   nothing
 static void
 record_io_error(exim_gnutls_state_st *state, int rc, uschar *when, uschar *text)
 {
-const char * msg;
+const uschar * msg;
 uschar * errstr;
 
 if (rc == GNUTLS_E_FATAL_ALERT_RECEIVED)
-  msg = CS string_sprintf("%s: %s", US gnutls_strerror(rc),
+  msg = string_sprintf("%s: %s", US gnutls_strerror(rc),
     US gnutls_alert_get_name(gnutls_alert_get(state->session)));
 else
-  msg = gnutls_strerror(rc);
+  msg = US gnutls_strerror(rc);
 
 (void) tls_error(when, msg, state->host, &errstr);
 
@@ -557,7 +561,7 @@ else if (Ustrcmp(exp_tls_dhparam, "none") == 0)
 else if (exp_tls_dhparam[0] != '/')
   {
   if (!(m.data = US std_dh_prime_named(exp_tls_dhparam)))
-    return tls_error(US"No standard prime named", CS exp_tls_dhparam, NULL, errstr);
+    return tls_error(US"No standard prime named", exp_tls_dhparam, NULL, errstr);
   m.size = Ustrlen(m.data);
   }
 else
@@ -620,7 +624,7 @@ if ((fd = Uopen(filename, O_RDONLY, 0)) >= 0)
     {
     saved_errno = errno;
     (void)close(fd);
-    return tls_error(US"TLS cache stat failed", strerror(saved_errno), NULL, errstr);
+    return tls_error(US"TLS cache stat failed", US strerror(saved_errno), NULL, errstr);
     }
   if (!S_ISREG(statbuf.st_mode))
     {
@@ -632,21 +636,21 @@ if ((fd = Uopen(filename, O_RDONLY, 0)) >= 0)
     saved_errno = errno;
     (void)close(fd);
     return tls_error(US"fdopen(TLS cache stat fd) failed",
-        strerror(saved_errno), NULL, errstr);
+        US strerror(saved_errno), NULL, errstr);
     }
 
   m.size = statbuf.st_size;
   if (!(m.data = malloc(m.size)))
     {
     fclose(fp);
-    return tls_error(US"malloc failed", strerror(errno), NULL, errstr);
+    return tls_error(US"malloc failed", US strerror(errno), NULL, errstr);
     }
   if (!(sz = fread(m.data, m.size, 1, fp)))
     {
     saved_errno = errno;
     fclose(fp);
     free(m.data);
-    return tls_error(US"fread failed", strerror(saved_errno), NULL, errstr);
+    return tls_error(US"fread failed", US strerror(saved_errno), NULL, errstr);
     }
   fclose(fp);
 
@@ -682,11 +686,11 @@ if (rc < 0)
 
   if ((PATH_MAX - Ustrlen(filename)) < 10)
     return tls_error(US"Filename too long to generate replacement",
-        CS filename, NULL, errstr);
+        filename, NULL, errstr);
 
-  temp_fn = string_copy(US "%s.XXXXXXX");
+  temp_fn = string_copy(US"%s.XXXXXXX");
   if ((fd = mkstemp(CS temp_fn)) < 0)  /* modifies temp_fn */
-    return tls_error(US"Unable to open temp file", strerror(errno), NULL, errstr);
+    return tls_error(US"Unable to open temp file", US strerror(errno), NULL, errstr);
   (void)fchown(fd, exim_uid, exim_gid);   /* Probably not necessary */
 
   /* GnuTLS overshoots!
@@ -723,7 +727,7 @@ if (rc < 0)
     exim_gnutls_err_check(rc, US"gnutls_dh_params_export_pkcs3(NULL) sizing");
   m.size = sz;
   if (!(m.data = malloc(m.size)))
-    return tls_error(US"memory allocation failed", strerror(errno), NULL, errstr);
+    return tls_error(US"memory allocation failed", US strerror(errno), NULL, errstr);
 
   /* 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,
@@ -739,19 +743,19 @@ if (rc < 0)
     {
     free(m.data);
     return tls_error(US"TLS cache write D-H params failed",
-        strerror(errno), NULL, errstr);
+        US strerror(errno), NULL, errstr);
     }
   free(m.data);
   if ((sz = write_to_fd_buf(fd, US"\n", 1)) != 1)
     return tls_error(US"TLS cache write D-H params final newline failed",
-        strerror(errno), NULL, errstr);
+        US strerror(errno), NULL, errstr);
 
   if ((rc = close(fd)))
-    return tls_error(US"TLS cache write close() failed", strerror(errno), NULL, errstr);
+    return tls_error(US"TLS cache write close() failed", US strerror(errno), NULL, errstr);
 
   if (Urename(temp_fn, filename) < 0)
     return tls_error(string_sprintf("failed to rename \"%s\" as \"%s\"",
-          temp_fn, filename), strerror(errno), NULL, errstr);
+          temp_fn, filename), US strerror(errno), NULL, errstr);
 
   DEBUG(D_tls) debug_printf("wrote D-H parameters to file \"%s\"\n", filename);
   }
@@ -783,9 +787,12 @@ 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),
+# ifndef GNUTLS_SEC_PARAM_MEDIUM
+#  define GNUTLS_SEC_PARAM_MEDIUM GNUTLS_SEC_PARAM_HIGH
+# endif
+           gnutls_sec_param_to_pk_bits(GNUTLS_PK_RSA, GNUTLS_SEC_PARAM_MEDIUM),
 #else
-           1024,
+           2048,
 #endif
            0)))
   goto err;
@@ -824,7 +831,7 @@ out:
   return rc;
 
 err:
-  rc = tls_error(where, gnutls_strerror(rc), NULL, errstr);
+  rc = tls_error(where, US gnutls_strerror(rc), NULL, errstr);
   goto out;
 }
 
@@ -847,7 +854,7 @@ int rc = gnutls_certificate_set_x509_key_file(state->x509_cred,
 if (rc < 0)
   return tls_error(
     string_sprintf("cert/key setup: cert=%s key=%s", certfile, keyfile),
-    gnutls_strerror(rc), host, errstr);
+    US gnutls_strerror(rc), host, errstr);
 return -rc;
 }
 
@@ -1297,7 +1304,7 @@ if (!exim_gnutls_base_init_done)
   DEBUG(D_tls)
     {
     gnutls_global_set_log_function(exim_gnutls_logger_cb);
-    /* arbitrarily chosen level; bump upto 9 for more */
+    /* arbitrarily chosen level; bump up to 9 for more */
     gnutls_global_set_log_level(EXIM_GNUTLS_LIBRARY_LOG_LEVEL);
     }
 #endif
@@ -1518,14 +1525,14 @@ if (cert_list == NULL || cert_list_size == 0)
       cert_list, cert_list_size);
   if (state->verify_requirement >= VERIFY_REQUIRED)
     return tls_error(US"certificate verification failed",
-        "no certificate received from peer", state->host, errstr);
+        US"no certificate received from peer", state->host, errstr);
   return OK;
   }
 
 ct = gnutls_certificate_type_get(state->session);
 if (ct != GNUTLS_CRT_X509)
   {
-  const char *ctn = gnutls_certificate_type_get_name(ct);
+  const uschar *ctn = US gnutls_certificate_type_get_name(ct);
   DEBUG(D_tls)
     debug_printf("TLS: peer cert not X.509 but instead \"%s\"\n", ctn);
   if (state->verify_requirement >= VERIFY_REQUIRED)
@@ -1541,7 +1548,7 @@ if (ct != GNUTLS_CRT_X509)
       DEBUG(D_tls) debug_printf("TLS: peer cert problem: %s: %s\n", \
        (Label), gnutls_strerror(rc)); \
       if (state->verify_requirement >= VERIFY_REQUIRED) \
-       return tls_error((Label), gnutls_strerror(rc), state->host, errstr); \
+       return tls_error((Label), US gnutls_strerror(rc), state->host, errstr); \
       return OK; \
       } \
     } while (0)
@@ -1598,6 +1605,7 @@ uint verify;
 if (state->verify_requirement == VERIFY_NONE)
   return TRUE;
 
+DEBUG(D_tls) debug_printf("TLS: checking peer certificate\n");
 *errstr = NULL;
 
 if ((rc = peer_status(state, errstr)) != OK)
@@ -1625,8 +1633,7 @@ else
 # ifdef GNUTLS_BROKEN_DANE_VALIDATION
     /* Split the TLSA records into two sets, TA and EE selectors.  Run the
     dane-verification separately so that we know which selector verified;
-    then we know whether to do CA-chain-verification and name-verification
-    (needed for TA but not EE). */
+    then we know whether to do name-verification (needed for TA but not EE). */
 
     if (usage == ((1<<DANESSL_USAGE_DANE_TA) | (1<<DANESSL_USAGE_DANE_EE)))
       {                                                /* a mixed-usage bundle */
@@ -1708,20 +1715,31 @@ else
       *errstr = US str.data;   /* don't bother to free */
       goto badcert;
       }
-    state->peer_dane_verified = TRUE;
 
 # ifdef GNUTLS_BROKEN_DANE_VALIDATION
     /* If a TA-mode TLSA record was used for verification we must additionally
-    verify the CA chain and the cert name.  For EE-mode, skip it. */
+    verify the cert name (but not the CA chain).  For EE-mode, skip it. */
 
     if (usage & (1 << DANESSL_USAGE_DANE_EE))
 # endif
       {
-      state->peer_cert_verified = TRUE;
+      state->peer_dane_verified = state->peer_cert_verified = TRUE;
       goto goodcert;
       }
+# ifdef GNUTLS_BROKEN_DANE_VALIDATION
+    /* Assume that the name on the A-record is the one that should be matching
+    the cert.  An alternate view is that the domain part of the email address
+    is also permissible. */
+
+    if (gnutls_x509_crt_check_hostname(state->tlsp->peercert,
+         CS state->host->name))
+      {
+      state->peer_dane_verified = state->peer_cert_verified = TRUE;
+      goto goodcert;
+      }
+# endif
     }
-#endif
+#endif /*SUPPORT_DANE*/
 
   rc = gnutls_certificate_verify_peers2(state->session, &verify);
   }
@@ -1732,8 +1750,24 @@ if (rc < 0 || verify & (GNUTLS_CERT_INVALID|GNUTLS_CERT_REVOKED))
   {
   state->peer_cert_verified = FALSE;
   if (!*errstr)
+    {
+#ifdef GNUTLS_CERT_VFY_STATUS_PRINT
+    DEBUG(D_tls)
+      {
+      gnutls_datum_t txt;
+
+      if (gnutls_certificate_verification_status_print(verify,
+           gnutls_certificate_type_get(state->session), &txt, 0)
+         == GNUTLS_E_SUCCESS)
+       {
+       debug_printf("%s\n", txt.data);
+       gnutls_free(txt.data);
+       }
+      }
+#endif
     *errstr = verify & GNUTLS_CERT_REVOKED
       ? US"certificate revoked" : US"certificate invalid";
+    }
 
   DEBUG(D_tls)
     debug_printf("TLS certificate verification failed (%s): peerdn=\"%s\"\n",
@@ -1747,23 +1781,23 @@ if (rc < 0 || verify & (GNUTLS_CERT_INVALID|GNUTLS_CERT_REVOKED))
 
 else
   {
-  if (state->exp_tls_verify_cert_hostnames)
+  /* Client side, check the server's certificate name versus the name on the
+  A-record for the connection we made.  What to do for server side - what name
+  to use for client?  We document that there is no such checking for server
+  side. */
+
+  if (  state->exp_tls_verify_cert_hostnames
+     && !gnutls_x509_crt_check_hostname(state->tlsp->peercert,
+               CS state->exp_tls_verify_cert_hostnames)
+     )
     {
-    int sep = 0;
-    const uschar * list = state->exp_tls_verify_cert_hostnames;
-    uschar * name;
-    while ((name = string_nextinlist(&list, &sep, NULL, 0)))
-      if (gnutls_x509_crt_check_hostname(state->tlsp->peercert, CS name))
-       break;
-    if (!name)
-      {
-      DEBUG(D_tls)
-       debug_printf("TLS certificate verification failed: cert name mismatch\n");
-      if (state->verify_requirement >= VERIFY_REQUIRED)
-       goto badcert;
-      return TRUE;
-      }
+    DEBUG(D_tls)
+      debug_printf("TLS certificate verification failed: cert name mismatch\n");
+    if (state->verify_requirement >= VERIFY_REQUIRED)
+      goto badcert;
+    return TRUE;
     }
+
   state->peer_cert_verified = TRUE;
   DEBUG(D_tls) debug_printf("TLS certificate verified: peerdn=\"%s\"\n",
       state->peerdn ? state->peerdn : US"<unset>");
@@ -1775,7 +1809,8 @@ goodcert:
 
 #ifdef SUPPORT_DANE
 tlsa_prob:
-  *errstr = string_sprintf("TLSA record problem: %s", dane_strerror(rc));
+  *errstr = string_sprintf("TLSA record problem: %s",
+    rc == DANE_E_REQUESTED_DATA_NOT_AVAILABLE ? "none usable" : dane_strerror(rc));
 #endif
 
 badcert:
@@ -1990,7 +2025,7 @@ exim_gnutls_state_st * state = NULL;
 /* Check for previous activation */
 if (tls_in.active.sock >= 0)
   {
-  tls_error(US"STARTTLS received after TLS started", "", NULL, errstr);
+  tls_error(US"STARTTLS received after TLS started", US "", NULL, errstr);
   smtp_printf("554 Already in TLS\r\n", FALSE);
   return FAIL;
   }
@@ -2057,7 +2092,10 @@ if (!state->tlsp->on_connect)
   }
 
 /* Now negotiate the TLS session. We put our own timer on it, since it seems
-that the GnuTLS library doesn't. */
+that the GnuTLS library doesn't.
+From 3.1.0 there is gnutls_handshake_set_timeout() - but it requires you
+to set (and clear down afterwards) up a pull-timeout callback function that does
+a select, so we're no better off unless avoiding signals becomes an issue. */
 
 gnutls_transport_set_ptr2(state->session,
     (gnutls_transport_ptr_t)(long) fileno(smtp_in),
@@ -2066,11 +2104,11 @@ state->fd_in = fileno(smtp_in);
 state->fd_out = fileno(smtp_out);
 
 sigalrm_seen = FALSE;
-if (smtp_receive_timeout > 0) alarm(smtp_receive_timeout);
+if (smtp_receive_timeout > 0) ALARM(smtp_receive_timeout);
 do
   rc = gnutls_handshake(state->session);
 while (rc == GNUTLS_E_AGAIN ||  rc == GNUTLS_E_INTERRUPTED && !sigalrm_seen);
-alarm(0);
+ALARM_CLR(0);
 
 if (rc != GNUTLS_E_SUCCESS)
   {
@@ -2080,12 +2118,12 @@ if (rc != GNUTLS_E_SUCCESS)
 
   if (sigalrm_seen)
     {
-    tls_error(US"gnutls_handshake", "timed out", NULL, errstr);
+    tls_error(US"gnutls_handshake", US"timed out", NULL, errstr);
     gnutls_db_remove_session(state->session);
     }
   else
     {
-    tls_error(US"gnutls_handshake", gnutls_strerror(rc), NULL, errstr);
+    tls_error(US"gnutls_handshake", US gnutls_strerror(rc), NULL, errstr);
     (void) gnutls_alert_send_appropriate(state->session, rc);
     gnutls_deinit(state->session);
     gnutls_certificate_free_credentials(state->x509_cred);
@@ -2147,7 +2185,7 @@ static void
 tls_client_setup_hostname_checks(host_item * host, exim_gnutls_state_st * state,
   smtp_transport_options_block * ob)
 {
-if (verify_check_given_host(&ob->tls_verify_cert_hostnames, host) == OK)
+if (verify_check_given_host(CUSS &ob->tls_verify_cert_hostnames, host) == OK)
   {
   state->exp_tls_verify_cert_hostnames =
 #ifdef SUPPORT_I18N
@@ -2192,7 +2230,7 @@ dane_data_len = store_get(i * sizeof(int));
 for (rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS), i = 0;
      rr;
      rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)
-    ) if (rr->type == T_TLSA)
+    ) if (rr->type == T_TLSA && rr->size > 3)
   {
   const uschar * p = rr->data;
   uint8_t usage = p[0], sel = p[1], type = p[2];
@@ -2216,7 +2254,7 @@ for (rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS), i = 0;
     }
 
   tls_out.tlsa_usage |= 1<<usage;
-  dane_data[i] = p;
+  dane_data[i] = CS p;
   dane_data_len[i++] = rr->size;
   }
 
@@ -2273,9 +2311,9 @@ uschar *cipher_list = NULL;
 
 #ifndef DISABLE_OCSP
 BOOL require_ocsp =
-  verify_check_given_host(&ob->hosts_require_ocsp, host) == OK;
+  verify_check_given_host(CUSS &ob->hosts_require_ocsp, host) == OK;
 BOOL request_ocsp = require_ocsp ? TRUE
-  : verify_check_given_host(&ob->hosts_request_ocsp, host) == OK;
+  : verify_check_given_host(CUSS &ob->hosts_request_ocsp, host) == OK;
 #endif
 
 DEBUG(D_tls) debug_printf("initialising GnuTLS as a client on fd %d\n", fd);
@@ -2335,7 +2373,7 @@ else
          && !ob->tls_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(CUSS &ob->tls_verify_hosts, host) == OK
        )
   {
   tls_client_setup_hostname_checks(host, state, ob);
@@ -2344,7 +2382,7 @@ else
   state->verify_requirement = VERIFY_REQUIRED;
   gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_REQUIRE);
   }
-else if (verify_check_given_host(&ob->tls_try_verify_hosts, host) == OK)
+else if (verify_check_given_host(CUSS &ob->tls_try_verify_hosts, host) == OK)
   {
   tls_client_setup_hostname_checks(host, state, ob);
   DEBUG(D_tls)
@@ -2368,7 +2406,7 @@ if (request_ocsp)
   if ((rc = gnutls_ocsp_status_request_enable_client(state->session,
                    NULL, 0, NULL)) != OK)
     {
-    tls_error(US"cert-status-req", gnutls_strerror(rc), state->host, errstr);
+    tls_error(US"cert-status-req", US gnutls_strerror(rc), state->host, errstr);
     return NULL;
     }
   tlsp->ocsp = OCSP_NOT_RESP;
@@ -2392,21 +2430,21 @@ DEBUG(D_tls) debug_printf("about to gnutls_handshake\n");
 /* There doesn't seem to be a built-in timeout on connection. */
 
 sigalrm_seen = FALSE;
-alarm(ob->command_timeout);
+ALARM(ob->command_timeout);
 do
   rc = gnutls_handshake(state->session);
 while (rc == GNUTLS_E_AGAIN || rc == GNUTLS_E_INTERRUPTED && !sigalrm_seen);
-alarm(0);
+ALARM_CLR(0);
 
 if (rc != GNUTLS_E_SUCCESS)
   {
   if (sigalrm_seen)
     {
     gnutls_alert_send(state->session, GNUTLS_AL_FATAL, GNUTLS_A_USER_CANCELED);
-    tls_error(US"gnutls_handshake", "timed out", state->host, errstr);
+    tls_error(US"gnutls_handshake", US"timed out", state->host, errstr);
     }
   else
-    tls_error(US"gnutls_handshake", gnutls_strerror(rc), state->host, errstr);
+    tls_error(US"gnutls_handshake", US gnutls_strerror(rc), state->host, errstr);
   return NULL;
   }
 
@@ -2438,7 +2476,7 @@ if (require_ocsp)
       gnutls_free(printed.data);
       }
     else
-      (void) tls_error(US"ocsp decode", gnutls_strerror(rc), state->host, errstr);
+      (void) tls_error(US"ocsp decode", US gnutls_strerror(rc), state->host, errstr);
     }
 
   if (gnutls_ocsp_status_request_is_checked(state->session, 0) == 0)
@@ -2495,9 +2533,9 @@ if (shutdown)
   DEBUG(D_tls) debug_printf("tls_close(): shutting down TLS%s\n",
     shutdown > 1 ? " (with response-wait)" : "");
 
-  alarm(2);
+  ALARM(2);
   gnutls_bye(state->session, shutdown > 1 ? GNUTLS_SHUT_RDWR : GNUTLS_SHUT_WR);
-  alarm(0);
+  ALARM_CLR(0);
   }
 
 gnutls_deinit(state->session);
@@ -2523,10 +2561,14 @@ DEBUG(D_tls) debug_printf("Calling gnutls_record_recv(%p, %p, %u)\n",
   state->session, state->xfer_buffer, ssl_xfer_buffer_size);
 
 sigalrm_seen = FALSE;
-if (smtp_receive_timeout > 0) alarm(smtp_receive_timeout);
-inbytes = gnutls_record_recv(state->session, state->xfer_buffer,
-  MIN(ssl_xfer_buffer_size, lim));
-if (smtp_receive_timeout > 0) alarm(0);
+if (smtp_receive_timeout > 0) ALARM(smtp_receive_timeout);
+
+do
+  inbytes = gnutls_record_recv(state->session, state->xfer_buffer,
+    MIN(ssl_xfer_buffer_size, lim));
+while (inbytes == GNUTLS_E_AGAIN);
+
+if (smtp_receive_timeout > 0) ALARM_CLR(0);
 
 if (had_command_timeout)               /* set by signal handler */
   smtp_command_timeout_exit();         /* does not return */
@@ -2580,6 +2622,7 @@ else if (inbytes == 0)
 
 else if (inbytes < 0)
   {
+  DEBUG(D_tls) debug_printf("%s: err from gnutls_record_recv(\n", __FUNCTION__);
   record_io_error(state, (int) inbytes, US"recv", NULL);
   state->xfer_error = TRUE;
   return FALSE;
@@ -2602,7 +2645,7 @@ Only used by the server-side TLS.
 
 This feeds DKIM and should be used for all message-body reads.
 
-Arguments:  lim                Maximum amount to read/bufffer
+Arguments:  lim                Maximum amount to read/buffer
 Returns:    the next character or EOF
 */
 
@@ -2701,13 +2744,20 @@ DEBUG(D_tls)
   debug_printf("Calling gnutls_record_recv(%p, %p, " SIZE_T_FMT ")\n",
       state->session, buff, len);
 
-inbytes = gnutls_record_recv(state->session, buff, len);
+do
+  inbytes = gnutls_record_recv(state->session, buff, len);
+while (inbytes == GNUTLS_E_AGAIN);
+
 if (inbytes > 0) return inbytes;
 if (inbytes == 0)
   {
   DEBUG(D_tls) debug_printf("Got TLS_EOF\n");
   }
-else record_io_error(state, (int)inbytes, US"recv", NULL);
+else
+  {
+  DEBUG(D_tls) debug_printf("%s: err from gnutls_record_recv(\n", __FUNCTION__);
+  record_io_error(state, (int)inbytes, US"recv", NULL);
+  }
 
 return -1;
 }
@@ -2749,11 +2799,15 @@ while (left > 0)
   {
   DEBUG(D_tls) debug_printf("gnutls_record_send(SSL, %p, " SIZE_T_FMT ")\n",
       buff, left);
-  outbytes = gnutls_record_send(state->session, buff, left);
+
+  do
+    outbytes = gnutls_record_send(state->session, buff, left);
+  while (outbytes == GNUTLS_E_AGAIN);
 
   DEBUG(D_tls) debug_printf("outbytes=" SSIZE_T_FMT "\n", outbytes);
   if (outbytes < 0)
     {
+    DEBUG(D_tls) debug_printf("%s: gnutls_record_send err\n", __FUNCTION__);
     record_io_error(state, outbytes, US"send", NULL);
     return -1;
     }