OpenSSL: Fix memory leak during multi-message connections using STARTTLS
[exim.git] / src / src / tls-gnu.c
index 1fee6c10741bbdf06527352cb4a321272a840d14..e0ac6a546dc858561a73a51f82612a95b6adce23 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2017 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Copyright (c) Phil Pennock 2012 */
@@ -66,8 +66,17 @@ 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 >= 0x030000 && defined(EXPERIMENTAL_DANE)
-# define SUPPORT_DANE
+
+#ifdef SUPPORT_DANE
+# if GNUTLS_VERSION_NUMBER >= 0x030000
+#  define DANESSL_USAGE_DANE_TA 2
+#  define DANESSL_USAGE_DANE_EE 3
+# else
+#  error GnuTLS version too early for DANE
+# endif
+# if GNUTLS_VERSION_NUMBER < 0x999999
+#  define GNUTLS_BROKEN_DANE_VALIDATION
+# endif
 #endif
 
 #ifndef DISABLE_OCSP
@@ -1600,24 +1609,91 @@ else
 
     dane_state_t s;
     dane_query_t r;
-    const gnutls_datum_t * certlist;
     uint lsize;
+    const gnutls_datum_t * certlist =
+      gnutls_certificate_get_peers(state->session, &lsize);
+    int usage = tls_out.tlsa_usage;
+
+# 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). */
+
+    if (usage == ((1<<DANESSL_USAGE_DANE_TA) | (1<<DANESSL_USAGE_DANE_EE)))
+    {                                          /* a mixed-usage bundle */
+      int i, j, nrec;
+      const char ** dd;
+      int * ddl;
+
+      for(nrec = 0; state->dane_data_len[nrec]; ) nrec++;
+      nrec++;
+
+      dd = store_get(nrec * sizeof(uschar *));
+      ddl = store_get(nrec * sizeof(int));
+      nrec--;
+
+      if ((rc = dane_state_init(&s, 0)))
+       goto tlsa_prob;
+
+      for (usage = DANESSL_USAGE_DANE_EE;
+          usage >= DANESSL_USAGE_DANE_TA; usage--)
+       {                               /* take records with this usage */
+       for (j = i = 0; i < nrec; i++)
+         if (state->dane_data[i][0] == usage)
+           {
+           dd[j] = state->dane_data[i];
+           ddl[j++] = state->dane_data_len[i];
+           }
+       if (j)
+         {
+         dd[j] = NULL;
+         ddl[j] = 0;
+
+         if ((rc = dane_raw_tlsa(s, &r, (char * const *)dd, ddl, 1, 0)))
+           goto tlsa_prob;
+
+         if ((rc = dane_verify_crt_raw(s, certlist, lsize,
+                           gnutls_certificate_type_get(state->session),
+                           r, 0,
+                           usage == DANESSL_USAGE_DANE_EE
+                           ? DANE_VFLAG_ONLY_CHECK_EE_USAGE : 0,
+                           &verify)))
+           {
+           DEBUG(D_tls)
+             debug_printf("TLSA record problem: %s\n", dane_strerror(rc));
+           }
+         else if (verify == 0) /* verification passed */
+           {
+           usage = 1 << usage;
+           break;
+           }
+         }
+       }
 
-    certlist = gnutls_certificate_get_peers(state->session, &lsize);
-
-    if (  (rc = dane_state_init(&s, 0))
-       || (rc = dane_raw_tlsa(s, &r, state->dane_data, state->dane_data_len,
-                             1, 0))
-       || (rc = dane_verify_crt_raw(s, certlist, lsize,
-                             gnutls_certificate_type_get(state->session),
-                             r, 0, 0, &verify))
-       )
-
+       if (rc) goto tlsa_prob;
+      }
+    else
+# endif
       {
-      *errstr = string_sprintf("TLSA record problem: %s", dane_strerror(rc));
-      goto badcert;
+      if (  (rc = dane_state_init(&s, 0))
+        || (rc = dane_raw_tlsa(s, &r, state->dane_data, state->dane_data_len,
+                       1, 0))
+        || (rc = dane_verify_crt_raw(s, certlist, lsize,
+                       gnutls_certificate_type_get(state->session),
+                       r, 0, 
+# ifdef GNUTLS_BROKEN_DANE_VALIDATION
+                       usage == (1 << DANESSL_USAGE_DANE_EE)
+                       ? DANE_VFLAG_ONLY_CHECK_EE_USAGE : 0,
+# else
+                       0,
+# endif
+                       &verify))
+        )
+       goto tlsa_prob;
       }
-    if (verify != 0)
+
+    if (verify != 0)           /* verification failed */
       {
       gnutls_datum_t str;
       (void) dane_verification_status_print(verify, &str, 0);
@@ -1626,11 +1702,12 @@ else
       }
     state->peer_dane_verified = TRUE;
 
-    /* If there were only EE-mode TLSA records present, no checks on cert anchor
-    valididation or cert names are required.  For a TA record only, or a mixed
-    set, do them (we cannot tell if an EE record worked). */
+# 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. */
 
-    if (!(tls_out.tlsa_usage & (1 << 2)))
+    if (usage & (1 << DANESSL_USAGE_DANE_EE))
+# endif
       {
       state->peer_cert_verified = TRUE;
       goto goodcert;
@@ -1688,6 +1765,11 @@ goodcert:
   state->tlsp->peerdn = state->peerdn;
   return TRUE;
 
+#ifdef SUPPORT_DANE
+tlsa_prob:
+  *errstr = string_sprintf("TLSA record problem: %s", dane_strerror(rc));
+#endif
+
 badcert:
   gnutls_alert_send(state->session, GNUTLS_AL_FATAL, GNUTLS_A_BAD_CERTIFICATE);
   return FALSE;
@@ -2112,8 +2194,10 @@ for (rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS), i = 0;
   DEBUG(D_tls)
     debug_printf("TLSA: %d %d %d size %d\n", usage, sel, type, rr->size);
 
-  if (usage != 2 && usage != 3) continue;
-  if (sel != 0 && sel != 1) continue;
+  if (  (usage != DANESSL_USAGE_DANE_TA && usage != DANESSL_USAGE_DANE_EE)
+     || (sel != 0 && sel != 1)
+     )
+    continue;
   switch(type)
     {
     case 0:    /* Full: cannot check at present */
@@ -2169,7 +2253,7 @@ int
 tls_client_start(int fd, host_item *host,
     address_item *addr ARG_UNUSED,
     transport_instance * tb,
-#ifdef EXPERIMENTAL_DANE
+#ifdef SUPPORT_DANE
     dns_answer * tlsa_dnsa,
 #endif
     uschar ** errstr)
@@ -2284,10 +2368,8 @@ DEBUG(D_tls) debug_printf("about to gnutls_handshake\n");
 sigalrm_seen = FALSE;
 alarm(ob->command_timeout);
 do
-  {
   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)
@@ -2360,12 +2442,15 @@ return OK;
 daemon, to shut down the TLS library, without actually doing a shutdown (which
 would tamper with the TLS session in the parent process).
 
-Arguments:   TRUE if gnutls_bye is to be called
+Arguments:
+  shutdown     1 if TLS close-alert is to be sent,
+               2 if also response to be waited for
+
 Returns:     nothing
 */
 
 void
-tls_close(BOOL is_server, BOOL shutdown)
+tls_close(BOOL is_server, int shutdown)
 {
 exim_gnutls_state_st *state = is_server ? &state_server : &state_client;
 
@@ -2373,8 +2458,12 @@ if (!state->tlsp || state->tlsp->active < 0) return;  /* TLS was not active */
 
 if (shutdown)
   {
-  DEBUG(D_tls) debug_printf("tls_close(): shutting down TLS\n");
-  gnutls_bye(state->session, GNUTLS_SHUT_WR);
+  DEBUG(D_tls) debug_printf("tls_close(): shutting down TLS%s\n",
+    shutdown > 1 ? " (with response-wait)" : "");
+
+  alarm(2);
+  gnutls_bye(state->session, shutdown > 1 ? GNUTLS_SHUT_RDWR : GNUTLS_SHUT_WR);
+  alarm(0);
   }
 
 gnutls_deinit(state->session);
@@ -2382,9 +2471,10 @@ gnutls_certificate_free_credentials(state->x509_cred);
 
 
 state->tlsp->active = -1;
+if (state->xfer_buffer) store_free(state->xfer_buffer);
 memcpy(state, &exim_gnutls_state_init, sizeof(exim_gnutls_state_init));
 
-if ((state_server.session == NULL) && (state_client.session == NULL))
+if (!state_server.session && !state_client.session)
   {
   gnutls_global_deinit();
   exim_gnutls_base_init_done = FALSE;
@@ -2403,6 +2493,7 @@ ssize_t inbytes;
 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));