Add cmdline option to append a log message. Bug 418
[exim.git] / src / src / tls-openssl.c
index 43ea8a0d2ec31a42b550b538ea8d574fa461a6af..ce6b47832843b06b17c4b804f11029b3f954be8e 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"
@@ -116,11 +123,8 @@ typedef struct tls_ext_ctx_cb {
   uschar *server_cipher_list;
   /* only passed down to tls_error: */
   host_item *host;
-
-#ifdef EXPERIMENTAL_CERTNAMES
   uschar * verify_cert_hostnames;
-#endif
-#ifdef EXPERIMENTAL_TPDA
+#ifdef EXPERIMENTAL_EVENT
   uschar * event_action;
 #endif
 } tls_ext_ctx_cb;
@@ -165,7 +169,7 @@ Returns:    OK/DEFER/FAIL
 */
 
 static int
-tls_error(uschar *prefix, host_item *host, uschar *msg)
+tls_error(uschar * prefix, const host_item * host, uschar *  msg)
 {
 if (!msg)
   {
@@ -288,6 +292,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));
 
@@ -297,7 +305,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)
     {
@@ -322,18 +329,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;
@@ -342,21 +351,21 @@ else if (depth != 0)
   }
 else
   {
-#ifdef EXPERIMENTAL_CERTNAMES
   uschar * verify_cert_hostnames;
-#endif
 
   tlsp->peerdn = txt;
   tlsp->peercert = X509_dup(cert);
 
-#ifdef EXPERIMENTAL_CERTNAMES
   if (  tlsp == &tls_out
      && ((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
+#  ifndef X509_CHECK_FLAG_SINGLE_LABEL_SUBDOMAINS
+#   define X509_CHECK_FLAG_SINGLE_LABEL_SUBDOMAINS 0
 #  endif
     {
     int sep = 0;
@@ -365,7 +374,8 @@ else
     int rc;
     while ((name = string_nextinlist(&list, &sep, NULL, 0)))
       if ((rc = X509_check_host(cert, name, 0,
-                 X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS)))
+                 X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS
+                 | X509_CHECK_FLAG_SINGLE_LABEL_SUBDOMAINS)))
        {
        if (rc < 0)
          {
@@ -378,7 +388,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
@@ -386,24 +400,27 @@ 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",
@@ -438,8 +455,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));
@@ -448,14 +466,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 */
       }
@@ -516,7 +534,7 @@ Returns:    TRUE if OK (nothing to set up, or setup worked)
 */
 
 static BOOL
-init_dh(SSL_CTX *sctx, uschar *dhparam, host_item *host)
+init_dh(SSL_CTX *sctx, uschar *dhparam, const host_item *host)
 {
 BIO *bio;
 DH *dh;
@@ -1140,7 +1158,7 @@ else
 cbinfo->dhparam = dhparam;
 cbinfo->server_cipher_list = NULL;
 cbinfo->host = host;
-#ifdef EXPERIMENTAL_TPDA
+#ifdef EXPERIMENTAL_EVENT
 cbinfo->event_action = NULL;
 #endif
 
@@ -1264,9 +1282,7 @@ else                      /* client */
 # endif
 #endif
 
-#ifdef EXPERIMENTAL_CERTNAMES
 cbinfo->verify_cert_hostnames = NULL;
-#endif
 
 /* Set up the RSA callback */
 
@@ -1350,50 +1366,65 @@ if (!expand_check(certs, US"tls_verify_certificates", &expcerts))
 
 if (expcerts != NULL && *expcerts != '\0')
   {
-  struct stat statbuf;
-  if (!SSL_CTX_set_default_verify_paths(sctx))
-    return tls_error(US"SSL_CTX_set_default_verify_paths", host, NULL);
-
-  if (Ustat(expcerts, &statbuf) < 0)
+  if (Ustrcmp(expcerts, "system") == 0)
     {
-    log_write(0, LOG_MAIN|LOG_PANIC,
-      "failed to stat %s for certificates", expcerts);
-    return DEFER;
+    /* Tell the library to use its compiled-in location for the system default
+    CA bundle, only */
+
+    if (!SSL_CTX_set_default_verify_paths(sctx))
+      return tls_error(US"SSL_CTX_set_default_verify_paths", host, NULL);
     }
   else
     {
-    uschar *file, *dir;
-    if ((statbuf.st_mode & S_IFMT) == S_IFDIR)
-      { file = NULL; dir = expcerts; }
+    struct stat statbuf;
+
+    /* Tell the library to use its compiled-in location for the system default
+    CA bundle. Those given by the exim config are additional to these */
+
+    if (!SSL_CTX_set_default_verify_paths(sctx))
+      return tls_error(US"SSL_CTX_set_default_verify_paths", host, NULL);
+
+    if (Ustat(expcerts, &statbuf) < 0)
+      {
+      log_write(0, LOG_MAIN|LOG_PANIC,
+       "failed to stat %s for certificates", expcerts);
+      return DEFER;
+      }
     else
-      { file = expcerts; dir = NULL; }
-
-    /* If a certificate file is empty, the next function fails with an
-    unhelpful error message. If we skip it, we get the correct behaviour (no
-    certificates are recognized, but the error message is still misleading (it
-    says no certificate was supplied.) But this is better. */
-
-    if ((file == NULL || statbuf.st_size > 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)
       {
-      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);
+      uschar *file, *dir;
+      if ((statbuf.st_mode & S_IFMT) == S_IFDIR)
+       { file = NULL; dir = expcerts; }
+      else
+       { file = expcerts; dir = NULL; }
+
+      /* If a certificate file is empty, the next function fails with an
+      unhelpful error message. If we skip it, we get the correct behaviour (no
+      certificates are recognized, but the error message is still misleading (it
+      says no certificate was supplied.) But this is better. */
+
+      if ((file == NULL || statbuf.st_size > 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)
+       {
+       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);
+       }
       }
     }
 
@@ -1647,10 +1678,7 @@ return OK;
 
 static int
 tls_client_basic_ctx_init(SSL_CTX * ctx,
-    host_item * host, smtp_transport_options_block * ob
-#ifdef EXPERIMENTAL_CERTNAMES
-    , tls_ext_ctx_cb * cbinfo
-#endif
+    host_item * host, smtp_transport_options_block * ob, tls_ext_ctx_cb * cbinfo
                          )
 {
 int rc;
@@ -1658,35 +1686,27 @@ int rc;
    set but both tls_verify_hosts and tls_try_verify_hosts is not set. Check only
    the specified host patterns if one of them is defined */
 
-if ((!ob->tls_verify_hosts && !ob->tls_try_verify_hosts) ||
-    (verify_check_host(&ob->tls_verify_hosts) == OK))
-  {
-  if ((rc = setup_certs(ctx, ob->tls_verify_certificates,
-       ob->tls_crl, host, FALSE, verify_callback_client)) != OK)
-    return rc;
+if (  (  !ob->tls_verify_hosts
+      && (!ob->tls_try_verify_hosts || !*ob->tls_try_verify_hosts)
+      )
+   || (verify_check_given_host(&ob->tls_verify_hosts, host) == OK)
+   )
   client_verify_optional = FALSE;
+else if (verify_check_given_host(&ob->tls_try_verify_hosts, host) == OK)
+  client_verify_optional = TRUE;
+else
+  return OK;
 
-#ifdef EXPERIMENTAL_CERTNAMES
-  if (ob->tls_verify_cert_hostnames)
-    {
-    if (!expand_check(ob->tls_verify_cert_hostnames,
-                     US"tls_verify_cert_hostnames",
-                     &cbinfo->verify_cert_hostnames))
-      return FAIL;
-    if (cbinfo->verify_cert_hostnames)
-      DEBUG(D_tls) debug_printf("Cert hostname to check: \"%s\"\n",
-                     cbinfo->verify_cert_hostnames);
-    }
-#endif
-  }
-else if (verify_check_host(&ob->tls_try_verify_hosts) == OK)
+if ((rc = setup_certs(ctx, ob->tls_verify_certificates,
+      ob->tls_crl, host, client_verify_optional, verify_callback_client)) != OK)
+  return rc;
+
+if (verify_check_given_host(&ob->tls_verify_cert_hostnames, host) == OK)
   {
-  if ((rc = setup_certs(ctx, ob->tls_verify_certificates,
-       ob->tls_crl, host, TRUE, verify_callback_client)) != OK)
-    return rc;
-  client_verify_optional = TRUE;
+  cbinfo->verify_cert_hostnames = host->name;
+  DEBUG(D_tls) debug_printf("Cert hostname to check: \"%s\"\n",
+                   cbinfo->verify_cert_hostnames);
   }
-
 return OK;
 }
 
@@ -1809,15 +1829,15 @@ tls_out.tlsa_usage = 0;
     }
 # endif
 
-  if ((require_ocsp = verify_check_this_host(&ob->hosts_require_ocsp,
-    NULL, host->name, host->address, NULL) == OK))
+  if ((require_ocsp =
+       verify_check_given_host(&ob->hosts_require_ocsp, host) == OK))
     request_ocsp = TRUE;
   else
 # ifdef EXPERIMENTAL_DANE
     if (!request_ocsp)
 # endif
-      request_ocsp = verify_check_this_host(&ob->hosts_request_ocsp,
-         NULL, host->name, host->address, NULL) == OK;
+      request_ocsp =
+       verify_check_given_host(&ob->hosts_request_ocsp, host) == OK;
   }
 #endif
 
@@ -1852,7 +1872,9 @@ if (expciphers != NULL)
 #ifdef EXPERIMENTAL_DANE
 if (tlsa_dnsa)
   {
-  SSL_CTX_set_verify(client_ctx, SSL_VERIFY_PEER, verify_callback_client_dane);
+  SSL_CTX_set_verify(client_ctx,
+    SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
+    verify_callback_client_dane);
 
   if (!DANESSL_library_init())
     return tls_error(US"library init", host, NULL);
@@ -1863,11 +1885,8 @@ else
 
 #endif
 
-  if ((rc = tls_client_basic_ctx_init(client_ctx, host, ob
-#ifdef EXPERIMENTAL_CERTNAMES
-                               , client_static_cbinfo
-#endif
-                               )) != OK)
+  if ((rc = tls_client_basic_ctx_init(client_ctx, host, ob, client_static_cbinfo))
+      != OK)
     return rc;
 
 if ((client_ssl = SSL_new(client_ctx)) == NULL)
@@ -1918,11 +1937,9 @@ if (request_ocsp)
     {  /* Re-eval now $tls_out_tlsa_usage is populated.  If
        this means we avoid the OCSP request, we wasted the setup
        cost in tls_init(). */
-    require_ocsp = verify_check_this_host(&ob->hosts_require_ocsp,
-      NULL, host->name, host->address, NULL) == OK;
-    request_ocsp = require_ocsp ? TRUE
-      : verify_check_this_host(&ob->hosts_request_ocsp,
-         NULL, host->name, host->address, NULL) == OK;
+    require_ocsp = verify_check_given_host(&ob->hosts_require_ocsp, host) == OK;
+    request_ocsp = require_ocsp
+      || verify_check_given_host(&ob->hosts_request_ocsp, host) == OK;
     }
   }
 # endif
@@ -1935,8 +1952,8 @@ if (request_ocsp)
   }
 #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. */