Move errno-protection into string_open_failed()
[users/heiko/exim.git] / src / src / tls-gnu.c
index 90c4cd024666d17d10e2794a080dfcebbc7f8df4..6308f10df4d00bb29ef55b0f88c8365a200dbe3d 100644 (file)
@@ -3,6 +3,7 @@
 *************************************************/
 
 /* 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 */
@@ -110,9 +111,11 @@ require current GnuTLS, then we'll drop support for the ancient libraries).
 # endif
 #endif
 
-#ifdef EXPERIMENTAL_TLS_RESUME
-# if GNUTLS_VERSION_NUMBER < 0x030603
-#  error GNUTLS version too early for session-resumption
+#ifndef DISABLE_TLS_RESUME
+# if GNUTLS_VERSION_NUMBER >= 0x030603
+#  define EXIM_HAVE_TLS_RESUME
+# else
+#  warning "GnuTLS library version too old; resumption unsupported"
 # endif
 #endif
 
@@ -130,7 +133,7 @@ require current GnuTLS, then we'll drop support for the ancient libraries).
 void
 options_tls(void)
 {
-# ifdef EXPERIMENTAL_TLS_RESUME
+# ifndef DISABLE_TLS_RESUME
 builtin_macro_create_var(US"_RESUME_DECODE", RESUME_DECODE_STRING );
 # endif
 # ifdef EXIM_HAVE_TLS1_3
@@ -265,7 +268,7 @@ static BOOL gnutls_buggy_ocsp = FALSE;
 static BOOL exim_testharness_disable_ocsp_validity_check = FALSE;
 #endif
 
-#ifdef EXPERIMENTAL_TLS_RESUME
+#ifdef EXIM_HAVE_TLS_RESUME
 static gnutls_datum_t server_sessticket_key;
 #endif
 
@@ -325,7 +328,7 @@ static void exim_gnutls_logger_cb(int level, const char *message);
 
 static int exim_sni_handling_cb(gnutls_session_t session);
 
-#ifdef EXPERIMENTAL_TLS_RESUME
+#ifdef EXIM_HAVE_TLS_RESUME
 static int
 tls_server_ticket_cb(gnutls_session_t sess, u_int htype, unsigned when,
   unsigned incoming, const gnutls_datum_t * msg);
@@ -336,7 +339,7 @@ tls_server_ticket_cb(gnutls_session_t sess, u_int htype, unsigned when,
 void
 tls_daemon_init(void)
 {
-#ifdef EXPERIMENTAL_TLS_RESUME
+#ifdef EXIM_HAVE_TLS_RESUME
 /* We are dependent on the GnuTLS implementation of the Session Ticket
 encryption; both the strength and the key rotation period.  We hope that
 the strength at least matches that of the ciphersuite (but GnuTLS does not
@@ -421,11 +424,16 @@ record_io_error(exim_gnutls_state_st *state, int rc, uschar *when, uschar *text)
 const uschar * msg;
 uschar * errstr;
 
-if (rc == GNUTLS_E_FATAL_ALERT_RECEIVED)
-  msg = string_sprintf("A TLS fatal alert has been received: %s",
-    US gnutls_alert_get_name(gnutls_alert_get(state->session)));
-else
-  msg = US gnutls_strerror(rc);
+msg = rc == GNUTLS_E_FATAL_ALERT_RECEIVED
+  ? string_sprintf("A TLS fatal alert has been received: %s",
+      US gnutls_alert_get_name(gnutls_alert_get(state->session)))
+#ifdef GNUTLS_E_PREMATURE_TERMINATION
+  : rc == GNUTLS_E_PREMATURE_TERMINATION && errno
+  ? errno == ECONNRESET                /* Outlook does this to us right after sending us QUIT */
+  ? string_sprintf("syscall: %s", strerror(errno))
+  : string_sprintf("%s: syscall: %s", US gnutls_strerror(rc), strerror(errno))
+#endif
+  : US gnutls_strerror(rc);
 
 (void) tls_error(when, msg, state->host, &errstr);
 
@@ -720,7 +728,7 @@ else if (errno == ENOENT)
     debug_printf("D-H parameter cache file \"%s\" does not exist\n", filename);
   }
 else
-  return tls_error(string_open_failed(errno, "\"%s\" for reading", filename),
+  return tls_error(string_open_failed("\"%s\" for reading", filename),
       NULL, NULL, errstr);
 
 /* If ret < 0, either the cache file does not exist, or the data it contains
@@ -999,10 +1007,10 @@ return gnutls_ext_raw_parse(NULL, tls_server_servercerts_ext, msg, 0);
  "Handshake Protocol: Certificate" record.
 So we need to spot the Certificate handshake message, parse it and spot any status_request extension(s)
 
-This is different to tls1.2 - where it is a separate record (wireshake term) / handshake message (gnutls term).
+This is different to tls1.2 - where it is a separate record (wireshark term) / handshake message (gnutls term).
 */
 
-#if defined(EXPERIMENTAL_TLS_RESUME) || defined(SUPPORT_GNUTLS_EXT_RAW_PARSE)
+#if defined(EXIM_HAVE_TLS_RESUME) || defined(SUPPORT_GNUTLS_EXT_RAW_PARSE)
 /* Callback for certificate-status, on server. We sent stapled OCSP. */
 static int
 tls_server_certstatus_cb(gnutls_session_t session, unsigned int htype,
@@ -1034,7 +1042,7 @@ switch (htype)
 # endif
   case GNUTLS_HANDSHAKE_CERTIFICATE_STATUS:
     return tls_server_certstatus_cb(sess, htype, when, incoming, msg);
-# ifdef EXPERIMENTAL_TLS_RESUME
+# ifdef EXIM_HAVE_TLS_RESUME
   case GNUTLS_HANDSHAKE_NEW_SESSION_TICKET:
     return tls_server_ticket_cb(sess, htype, when, incoming, msg);
 # endif
@@ -2327,7 +2335,7 @@ else
 }
 
 
-#ifdef EXPERIMENTAL_TLS_RESUME
+#ifdef EXIM_HAVE_TLS_RESUME
 static int
 tls_server_ticket_cb(gnutls_session_t sess, u_int htype, unsigned when,
   unsigned incoming, const gnutls_datum_t * msg)
@@ -2441,7 +2449,7 @@ DEBUG(D_tls) debug_printf("initialising GnuTLS as a server\n");
 #endif
   }
 
-#ifdef EXPERIMENTAL_TLS_RESUME
+#ifdef EXIM_HAVE_TLS_RESUME
 tls_server_resume_prehandshake(state);
 #endif
 
@@ -2513,7 +2521,7 @@ sigalrm_seen = FALSE;
 if (smtp_receive_timeout > 0) ALARM(smtp_receive_timeout);
 do
   rc = gnutls_handshake(state->session);
-while (rc == GNUTLS_E_INTERRUPTED && !sigalrm_seen);
+while (rc == GNUTLS_E_AGAIN ||  rc == GNUTLS_E_INTERRUPTED && !sigalrm_seen);
 ALARM_CLR(0);
 
 if (rc != GNUTLS_E_SUCCESS)
@@ -2549,7 +2557,7 @@ if (gnutls_session_get_flags(state->session) & GNUTLS_SFLAGS_EXT_MASTER_SECRET)
   tls_in.ext_master_secret = TRUE;
 #endif
 
-#ifdef EXPERIMENTAL_TLS_RESUME
+#ifdef EXIM_HAVE_TLS_RESUME
 tls_server_resume_posthandshake(state);
 #endif
 
@@ -2600,9 +2608,9 @@ if (verify_check_given_host(CUSS &ob->tls_verify_cert_hostnames, host) == OK)
   {
   state->exp_tls_verify_cert_hostnames =
 #ifdef SUPPORT_I18N
-    string_domain_utf8_to_alabel(host->name, NULL);
+    string_domain_utf8_to_alabel(host->certname, NULL);
 #else
-    host->name;
+    host->certname;
 #endif
   DEBUG(D_tls)
     debug_printf("TLS: server cert verification includes hostname: \"%s\".\n",
@@ -2682,7 +2690,7 @@ return TRUE;
 
 
 
-#ifdef EXPERIMENTAL_TLS_RESUME
+#ifdef EXIM_HAVE_TLS_RESUME
 /* On the client, get any stashed session for the given IP from hints db
 and apply it to the ssl-connection for attempted resumption.  Although
 there is a gnutls_session_ticket_enable_client() interface it is
@@ -2815,7 +2823,7 @@ if (gnutls_session_is_resumed(state->session))
 
 tls_save_session(tlsp, state->session, host);
 }
-#endif /* EXPERIMENTAL_TLS_RESUME */
+#endif /* !DISABLE_TLS_RESUME */
 
 
 /*************************************************
@@ -2969,7 +2977,7 @@ if (request_ocsp)
   }
 #endif
 
-#ifdef EXPERIMENTAL_TLS_RESUME
+#ifdef EXIM_HAVE_TLS_RESUME
 tls_client_resume_prehandshake(state, tlsp, host, ob);
 #endif
 
@@ -2993,7 +3001,7 @@ sigalrm_seen = FALSE;
 ALARM(ob->command_timeout);
 do
   rc = gnutls_handshake(state->session);
-while (rc == GNUTLS_E_INTERRUPTED && !sigalrm_seen);
+while (rc == GNUTLS_E_AGAIN || rc == GNUTLS_E_INTERRUPTED && !sigalrm_seen);
 ALARM_CLR(0);
 
 if (rc != GNUTLS_E_SUCCESS)
@@ -3069,7 +3077,7 @@ if (request_ocsp)
   }
 #endif
 
-#ifdef EXPERIMENTAL_TLS_RESUME
+#ifdef EXIM_HAVE_TLS_RESUME
 tls_client_resume_posthandshake(state, tlsp, host);
 #endif
 
@@ -3157,7 +3165,10 @@ DEBUG(D_tls) debug_printf("Calling gnutls_record_recv(session=%p, buffer=%p, buf
 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));
+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);
 
@@ -3314,7 +3325,9 @@ DEBUG(D_tls)
   debug_printf("Calling gnutls_record_recv(session=%p, buffer=%p, len=" 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)
@@ -3375,7 +3388,9 @@ while (left > 0)
   DEBUG(D_tls) debug_printf("gnutls_record_send(session=%p, buffer=%p, left=" SIZE_T_FMT ")\n",
       state->session, 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);
 
@@ -3407,12 +3422,21 @@ if (len > INT_MAX)
 if (!more && state->corked)
   {
   DEBUG(D_tls) debug_printf("gnutls_record_uncork(session=%p)\n", state->session);
-  outbytes = gnutls_record_uncork(state->session, 0);
+  do
+    /* We can't use GNUTLS_RECORD_WAIT here, as it retries on
+    GNUTLS_E_AGAIN || GNUTLS_E_INTR, which would break our timeout set by alarm().
+    The GNUTLS_E_AGAIN should not happen ever, as our sockets are blocking anyway.
+    But who knows. (That all relies on the fact that GNUTLS_E_INTR and GNUTLS_E_AGAIN
+    match the EINTR and EAGAIN errno values.) */
+    outbytes = gnutls_record_uncork(state->session, 0);
+  while (outbytes == GNUTLS_E_AGAIN);
+
   if (outbytes < 0)
     {
     record_io_error(state, len, US"uncork", NULL);
     return -1;
     }
+
   state->corked = FALSE;
   }
 #endif