Fix cert-try-verify when denied by event action
[exim.git] / src / src / tls-openssl.c
index 2e95a467af3f7959a4adbbc2944cc449351babda..fe1b208ac5b2e4d708b913e2f466e11733ad0450 100644 (file)
@@ -38,6 +38,13 @@ functions from the OpenSSL library. */
 #if OPENSSL_VERSION_NUMBER >= 0x0090806fL && !defined(OPENSSL_NO_TLSEXT)
 # define EXIM_HAVE_OPENSSL_TLSEXT
 #endif
+#if OPENSSL_VERSION_NUMBER >= 0x010100000L
+# define EXIM_HAVE_OPENSSL_CHECKHOST
+#endif
+#if OPENSSL_VERSION_NUMBER >= 0x010000000L \
+    && (OPENSSL_VERSION_NUMBER & 0x0000ff000L) >= 0x000002000L
+# define EXIM_HAVE_OPENSSL_CHECKHOST
+#endif
 
 #if !defined(EXIM_HAVE_OPENSSL_TLSEXT) && !defined(DISABLE_OCSP)
 # warning "OpenSSL library version too old; define DISABLE_OCSP in Makefile"
@@ -120,7 +127,7 @@ typedef struct tls_ext_ctx_cb {
 #ifdef EXPERIMENTAL_CERTNAMES
   uschar * verify_cert_hostnames;
 #endif
-#ifdef EXPERIMENTAL_TPDA
+#ifdef EXPERIMENTAL_EVENT
   uschar * event_action;
 #endif
 } tls_ext_ctx_cb;
@@ -167,27 +174,28 @@ Returns:    OK/DEFER/FAIL
 static int
 tls_error(uschar *prefix, host_item *host, uschar *msg)
 {
-if (msg == NULL)
+if (!msg)
   {
   ERR_error_string(ERR_get_error(), ssl_errstring);
   msg = (uschar *)ssl_errstring;
   }
 
-if (host == NULL)
+if (host)
+  {
+  log_write(0, LOG_MAIN, "H=%s [%s] TLS error on connection (%s): %s",
+    host->name, host->address, prefix, msg);
+  return FAIL;
+  }
+else
   {
   uschar *conn_info = smtp_get_connection_info();
   if (Ustrncmp(conn_info, US"SMTP ", 5) == 0)
     conn_info += 5;
+  /* I'd like to get separated H= here, but too hard for now */
   log_write(0, LOG_MAIN, "TLS error on %s (%s): %s",
     conn_info, prefix, msg);
   return DEFER;
   }
-else
-  {
-  log_write(0, LOG_MAIN, "TLS error on connection to %s [%s] (%s): %s",
-    host->name, host->address, prefix, msg);
-  return FAIL;
-  }
 }
 
 
@@ -287,6 +295,10 @@ verify_callback(int state, X509_STORE_CTX *x509ctx,
 X509 * cert = X509_STORE_CTX_get_current_cert(x509ctx);
 int depth = X509_STORE_CTX_get_error_depth(x509ctx);
 static uschar txt[256];
+#ifdef EXPERIMENTAL_EVENT
+uschar * ev;
+uschar * yield;
+#endif
 
 X509_NAME_oneline(X509_get_subject_name(cert), CS txt, sizeof(txt));
 
@@ -296,7 +308,6 @@ if (state == 0)
     depth,
     X509_verify_cert_error_string(X509_STORE_CTX_get_error(x509ctx)),
     txt);
-  tlsp->certificate_verified = FALSE;
   *calledp = TRUE;
   if (!*optionalp)
     {
@@ -321,18 +332,20 @@ else if (depth != 0)
       ERR_clear_error();
     }
 #endif
-#ifdef EXPERIMENTAL_TPDA
-  if (tlsp == &tls_out && client_static_cbinfo->event_action)
+#ifdef EXPERIMENTAL_EVENT
+  ev = tlsp == &tls_out ? client_static_cbinfo->event_action : event_action;
+  if (ev)
     {
     tlsp->peercert = X509_dup(cert);
-    if (tpda_raise_event(client_static_cbinfo->event_action,
-                   US"tls:cert", string_sprintf("%d", depth)) == DEFER)
+    if ((yield = event_raise(ev, US"tls:cert", string_sprintf("%d", depth))))
       {
       log_write(0, LOG_MAIN, "SSL verify denied by event-action: "
-                             "depth=%d cert=%s", depth, txt);
-      tlsp->certificate_verified = FALSE;
+                             "depth=%d cert=%s: %s", depth, txt, yield);
       *calledp = TRUE;
-      return 0;                            /* reject */
+      if (!*optionalp)
+       return 0;                           /* reject */
+      DEBUG(D_tls) debug_printf("Event-action verify failure overridden "
+       "(host in tls_try_verify_hosts)\n");
       }
     X509_free(tlsp->peercert);
     tlsp->peercert = NULL;
@@ -353,7 +366,7 @@ else
      && ((verify_cert_hostnames = client_static_cbinfo->verify_cert_hostnames)))
        /* client, wanting hostname check */
 
-# if OPENSSL_VERSION_NUMBER >= 0x010100000L || OPENSSL_VERSION_NUMBER >= 0x010002000L
+# if EXIM_HAVE_OPENSSL_CHECKHOST
 #  ifndef X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS
 #   define X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS 0
 #  endif
@@ -377,7 +390,11 @@ else
       {
       log_write(0, LOG_MAIN,
        "SSL verify error: certificate name mismatch: \"%s\"\n", txt);
-      return 0;                                /* reject */
+      *calledp = TRUE;
+      if (!*optionalp)
+       return 0;                           /* reject */
+      DEBUG(D_tls) debug_printf("SSL verify failure overridden (host in "
+       "tls_try_verify_hosts)\n");
       }
     }
 # else
@@ -385,24 +402,28 @@ else
       {
       log_write(0, LOG_MAIN,
        "SSL verify error: certificate name mismatch: \"%s\"\n", txt);
-      return 0;                                /* reject */
+      *calledp = TRUE;
+      if (!*optionalp)
+       return 0;                           /* reject */
+      DEBUG(D_tls) debug_printf("SSL verify failure overridden (host in "
+       "tls_try_verify_hosts)\n");
       }
 # endif
 #endif /*EXPERIMENTAL_CERTNAMES*/
 
-#ifdef EXPERIMENTAL_TPDA
-  if (tlsp == &tls_out)
-    {
-    if (tpda_raise_event(client_static_cbinfo->event_action,
-                   US"tls:cert", US"0") == DEFER)
+#ifdef EXPERIMENTAL_EVENT
+  ev = tlsp == &tls_out ? client_static_cbinfo->event_action : event_action;
+  if (ev)
+    if ((yield = event_raise(ev, US"tls:cert", US"0")))
       {
       log_write(0, LOG_MAIN, "SSL verify denied by event-action: "
-                             "depth=0 cert=%s", txt);
-      tlsp->certificate_verified = FALSE;
+                             "depth=0 cert=%s: %s", txt, yield);
       *calledp = TRUE;
-      return 0;                            /* reject */
+      if (!*optionalp)
+       return 0;                           /* reject */
+      DEBUG(D_tls) debug_printf("Event-action verify failure overridden "
+       "(host in tls_try_verify_hosts)\n");
       }
-    }
 #endif
 
   DEBUG(D_tls) debug_printf("SSL%s verify ok: depth=0 SN=%s\n",
@@ -437,8 +458,9 @@ verify_callback_client_dane(int state, X509_STORE_CTX * x509ctx)
 {
 X509 * cert = X509_STORE_CTX_get_current_cert(x509ctx);
 static uschar txt[256];
-#ifdef EXPERIMENTAL_TPDA
+#ifdef EXPERIMENTAL_EVENT
 int depth = X509_STORE_CTX_get_error_depth(x509ctx);
+uschar * yield;
 #endif
 
 X509_NAME_oneline(X509_get_subject_name(cert), CS txt, sizeof(txt));
@@ -447,14 +469,14 @@ DEBUG(D_tls) debug_printf("verify_callback_client_dane: %s\n", txt);
 tls_out.peerdn = txt;
 tls_out.peercert = X509_dup(cert);
 
-#ifdef EXPERIMENTAL_TPDA
+#ifdef EXPERIMENTAL_EVENT
   if (client_static_cbinfo->event_action)
     {
-    if (tpda_raise_event(client_static_cbinfo->event_action,
-                   US"tls:cert", string_sprintf("%d", depth)) == DEFER)
+    if ((yield = event_raise(client_static_cbinfo->event_action,
+                   US"tls:cert", string_sprintf("%d", depth))))
       {
       log_write(0, LOG_MAIN, "DANE verify denied by event-action: "
-                             "depth=%d cert=%s", depth, txt);
+                             "depth=%d cert=%s: %s", depth, txt, yield);
       tls_out.certificate_verified = FALSE;
       return 0;                            /* reject */
       }
@@ -1139,7 +1161,7 @@ else
 cbinfo->dhparam = dhparam;
 cbinfo->server_cipher_list = NULL;
 cbinfo->host = host;
-#ifdef EXPERIMENTAL_TPDA
+#ifdef EXPERIMENTAL_EVENT
 cbinfo->event_action = NULL;
 #endif
 
@@ -1376,9 +1398,23 @@ if (expcerts != NULL && *expcerts != '\0')
           !SSL_CTX_load_verify_locations(sctx, CS file, CS dir))
       return tls_error(US"SSL_CTX_load_verify_locations", host, NULL);
 
+    /* Load the list of CAs for which we will accept certs, for sending
+    to the client.  This is only for the one-file tls_verify_certificates
+    variant.
+    If a list isn't loaded into the server, but
+    some verify locations are set, the server end appears to make
+    a wildcard reqest for client certs.
+    Meanwhile, the client library as deafult behaviour *ignores* the list
+    we send over the wire - see man SSL_CTX_set_client_cert_cb.
+    Because of this, and that the dir variant is likely only used for
+    the public-CA bundle (not for a private CA), not worth fixing.
+    */
     if (file != NULL)
       {
-      SSL_CTX_set_client_CA_list(sctx, SSL_load_client_CA_file(CS file));
+      STACK_OF(X509_NAME) * names = SSL_load_client_CA_file(CS file);
+DEBUG(D_tls) debug_printf("Added %d certificate authorities.\n",
+                                 sk_X509_NAME_num(names));
+      SSL_CTX_set_client_CA_list(sctx, names);
       }
     }
 
@@ -1677,44 +1713,6 @@ return OK;
 
 
 #ifdef EXPERIMENTAL_DANE
-static int
-tlsa_lookup(host_item * host, dns_answer * dnsa,
-  BOOL dane_required, BOOL * dane)
-{
-/* move this out to host.c given the similarity to dns_lookup() ? */
-uschar buffer[300];
-uschar * fullname = buffer;
-
-/* TLSA lookup string */
-(void)sprintf(CS buffer, "_%d._tcp.%.256s", host->port, host->name);
-
-switch (dns_lookup(dnsa, buffer, T_TLSA, &fullname))
-  {
-  case DNS_AGAIN:
-    return DEFER; /* just defer this TLS'd conn */
-
-  default:
-  case DNS_FAIL:
-    if (dane_required)
-      {
-      log_write(0, LOG_MAIN, "DANE error: TLSA lookup failed");
-      return FAIL;
-      }
-    break;
-
-  case DNS_SUCCEED:
-    if (!dns_is_secure(dnsa))
-      {
-      log_write(0, LOG_MAIN, "DANE error: TLSA lookup not DNSSEC");
-      return DEFER;
-      }
-    *dane = TRUE;
-    break;
-  }
-return OK;
-}
-
-
 static int
 dane_tlsa_load(SSL * ssl, host_item * host, dns_answer * dnsa)
 {
@@ -1735,22 +1733,23 @@ for (rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS);
   uint8_t usage, selector, mtype;
   const char * mdname;
 
-  found++;
   usage = *p++;
+
+  /* Only DANE-TA(2) and DANE-EE(3) are supported */
+  if (usage != 2 && usage != 3) continue;
+
   selector = *p++;
   mtype = *p++;
 
   switch (mtype)
     {
-    default:
-      log_write(0, LOG_MAIN,
-               "DANE error: TLSA record w/bad mtype 0x%x", mtype);
-      return FAIL;
-    case 0:    mdname = NULL; break;
-    case 1:    mdname = "sha256"; break;
-    case 2:    mdname = "sha512"; break;
+    default: continue; /* Only match-types 0, 1, 2 are supported */
+    case 0:  mdname = NULL; break;
+    case 1:  mdname = "sha256"; break;
+    case 2:  mdname = "sha512"; break;
     }
 
+  found++;
   switch (DANESSL_add_tlsa(ssl, usage, selector, mdname, p, rr->size - 3))
     {
     default:
@@ -1765,7 +1764,7 @@ for (rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS);
 if (found)
   return OK;
 
-log_write(0, LOG_MAIN, "DANE error: No TLSA records");
+log_write(0, LOG_MAIN, "DANE error: No usable TLSA records");
 return FAIL;
 }
 #endif /*EXPERIMENTAL_DANE*/
@@ -1783,6 +1782,7 @@ Argument:
   host             connected host (for messages)
   addr             the first address
   tb               transport (always smtp)
+  tlsa_dnsa        tlsa lookup, if DANE, else null
 
 Returns:           OK on success
                    FAIL otherwise - note that tls_error() will not give DEFER
@@ -1791,7 +1791,11 @@ Returns:           OK on success
 
 int
 tls_client_start(int fd, host_item *host, address_item *addr,
-  transport_instance *tb)
+  transport_instance *tb
+#ifdef EXPERIMENTAL_DANE
+  , dns_answer * tlsa_dnsa
+#endif
+  )
 {
 smtp_transport_options_block * ob =
   (smtp_transport_options_block *)tb->options_block;
@@ -1805,60 +1809,36 @@ static uschar cipherbuf[256];
 BOOL request_ocsp = FALSE;
 BOOL require_ocsp = FALSE;
 #endif
-#ifdef EXPERIMENTAL_DANE
-dns_answer tlsa_dnsa;
-BOOL dane = FALSE;
-BOOL dane_required;
-#endif
 
 #ifdef EXPERIMENTAL_DANE
-tls_out.dane_verified = FALSE;
 tls_out.tlsa_usage = 0;
-dane_required = verify_check_this_host(&ob->hosts_require_dane, NULL,
-                         host->name, host->address, NULL) == OK;
-
-if (host->dnssec == DS_YES)
-  {
-  if(  dane_required
-    || verify_check_this_host(&ob->hosts_try_dane, NULL,
-                         host->name, host->address, NULL) == OK
-    )
-    if ((rc = tlsa_lookup(host, &tlsa_dnsa, dane_required, &dane)) != OK)
-      return rc;
-  }
-else if (dane_required)
-  {
-  /*XXX a shame we only find this after making tcp & smtp connection */
-  /* move the test earlier? */
-  log_write(0, LOG_MAIN, "DANE error: previous lookup not DNSSEC");
-  return FAIL;
-  }
 #endif
 
 #ifndef DISABLE_OCSP
   {
+# ifdef EXPERIMENTAL_DANE
+  if (  tlsa_dnsa
+     && ob->hosts_request_ocsp[0] == '*'
+     && ob->hosts_request_ocsp[1] == '\0'
+     )
+    {
+    /* Unchanged from default.  Use a safer one under DANE */
+    request_ocsp = TRUE;
+    ob->hosts_request_ocsp = US"${if or { {= {0}{$tls_out_tlsa_usage}} "
+                                     "   {= {4}{$tls_out_tlsa_usage}} } "
+                                " {*}{}}";
+    }
+# endif
+
   if ((require_ocsp = verify_check_this_host(&ob->hosts_require_ocsp,
     NULL, host->name, host->address, NULL) == OK))
     request_ocsp = TRUE;
   else
-    {
 # ifdef EXPERIMENTAL_DANE
-    if (  dane
-       && ob->hosts_request_ocsp[0] == '*'
-       && ob->hosts_request_ocsp[1] == '\0'
-       )
-      {
-      /* Unchanged from default.  Use a safer one under DANE */
-      request_ocsp = TRUE;
-      ob->hosts_request_ocsp = US"${if or { {= {0}{$tls_out_tlsa_usage}} "
-                                       "   {= {4}{$tls_out_tlsa_usage}} } "
-                                  " {*}{}}";
-      }
-    else
+    if (!request_ocsp)
 # endif
       request_ocsp = verify_check_this_host(&ob->hosts_request_ocsp,
          NULL, host->name, host->address, NULL) == OK;
-    }
   }
 #endif
 
@@ -1891,7 +1871,7 @@ if (expciphers != NULL)
   }
 
 #ifdef EXPERIMENTAL_DANE
-if (dane)
+if (tlsa_dnsa)
   {
   SSL_CTX_set_verify(client_ctx, SSL_VERIFY_PEER, verify_callback_client_dane);
 
@@ -1941,8 +1921,8 @@ if (ob->tls_sni)
   }
 
 #ifdef EXPERIMENTAL_DANE
-if (dane)
-  if ((rc = dane_tlsa_load(client_ssl, host, &tlsa_dnsa)) != OK)
+if (tlsa_dnsa)
+  if ((rc = dane_tlsa_load(client_ssl, host, tlsa_dnsa)) != OK)
     return rc;
 #endif
 
@@ -1953,8 +1933,8 @@ does OCSP stapling we will get the callback (set in tls_init()) */
 if (request_ocsp)
   {
   const uschar * s;
-  if (  (s = ob->hosts_require_ocsp) && Ustrstr(s, US"tls_out_tlsa_usage")
-     || (s = ob->hosts_request_ocsp) && Ustrstr(s, US"tls_out_tlsa_usage")
+  if (  ((s = ob->hosts_require_ocsp) && Ustrstr(s, US"tls_out_tlsa_usage"))
+     || ((s = ob->hosts_request_ocsp) && Ustrstr(s, US"tls_out_tlsa_usage"))
      )
     {  /* Re-eval now $tls_out_tlsa_usage is populated.  If
        this means we avoid the OCSP request, we wasted the setup
@@ -1976,12 +1956,8 @@ if (request_ocsp)
   }
 #endif
 
-#ifdef EXPERIMENTAL_TPDA
-client_static_cbinfo->event_action = tb->tpda_event_action;
-#endif
-
-#ifdef EXPERIMENTAL_TPDA
-client_static_cbinfo->event_action = tb->tpda_event_action;
+#ifdef EXPERIMENTAL_EVENT
+client_static_cbinfo->event_action = tb->event_action;
 #endif
 
 /* There doesn't seem to be a built-in timeout on connection. */
@@ -1993,7 +1969,7 @@ rc = SSL_connect(client_ssl);
 alarm(0);
 
 #ifdef EXPERIMENTAL_DANE
-if (dane)
+if (tlsa_dnsa)
   DANESSL_cleanup(client_ssl);
 #endif