Fix cert-try-verify when denied by event action
[exim.git] / src / src / verify.c
index ea733b60596dae968d61efdd382853cff059ad10..82dc5cc722f322a8e933b6470600ba22d9ec1b7b 100644 (file)
@@ -426,6 +426,10 @@ else
     BOOL esmtp;
     BOOL suppress_tls = FALSE;
     uschar *interface = NULL;  /* Outgoing interface to use; NULL => any */
+#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_DANE)
+    BOOL dane = FALSE;
+    dns_answer tlsa_dnsa;
+#endif
     uschar inbuffer[4096];
     uschar outbuffer[1024];
     uschar responsebuffer[4096];
@@ -462,7 +466,9 @@ else
 
     deliver_host = host->name;
     deliver_host_address = host->address;
+    deliver_host_port = host->port;
     deliver_domain = addr->domain;
+    transport_name = addr->transport->name;
 
     if (!smtp_get_interface(tf->interface, host_af, addr, NULL, &interface,
             US"callout") ||
@@ -477,6 +483,37 @@ else
 
     HDEBUG(D_verify) debug_printf("interface=%s port=%d\n", interface, port);
 
+#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_DANE)
+      {
+      BOOL dane_required;
+      int rc;
+
+      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)
+       {
+       log_write(0, LOG_MAIN, "DANE error: %s lookup not DNSSEC", host->name);
+       return FAIL;
+       }
+
+      if (dane)
+       ob->tls_tempfail_tryclear = FALSE;
+      }
+#endif  /*DANE*/
+
     /* Set up the buffer for reading SMTP response packets. */
 
     inblock.buffer = inbuffer;
@@ -501,12 +538,18 @@ else
     tls_retry_connection:
 
     inblock.sock = outblock.sock =
-      smtp_connect(host, host_af, port, interface, callout_connect, TRUE, NULL);
+      smtp_connect(host, host_af, port, interface, callout_connect, TRUE, NULL
+#ifdef EXPERIMENTAL_EVENT
+    /*XXX event action? NULL for now. */
+                 , NULL
+#endif
+                 );
     /* reconsider DSCP here */
     if (inblock.sock < 0)
       {
       addr->message = string_sprintf("could not connect to %s [%s]: %s",
           host->name, host->address, strerror(errno));
+      transport_name = NULL;
       deliver_host = deliver_host_address = NULL;
       deliver_domain = save_deliver_domain;
       continue;
@@ -524,9 +567,6 @@ else
       else active_hostname = s;
       }
 
-    deliver_host = deliver_host_address = NULL;
-    deliver_domain = save_deliver_domain;
-
     /* Wait for initial response, and send HELO. The smtp_write_command()
     function leaves its command in big_buffer. This is used in error responses.
     Initialize it in case the connection is rejected. */
@@ -536,12 +576,23 @@ else
     /* Unless ssl-on-connect, wait for the initial greeting */
     smtps_redo_greeting:
 
-    #ifdef SUPPORT_TLS
+#ifdef SUPPORT_TLS
     if (!smtps || (smtps && tls_out.active >= 0))
-    #endif
+#endif
+      {
       if (!(done= smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer), '2', callout)))
         goto RESPONSE_FAILED;
 
+#ifdef EXPERIMENTAL_EVENT
+      if (event_raise(addr->transport->event_action,
+                           US"smtp:connect", responsebuffer))
+       {
+       /* Logging?  Debug? */
+       goto RESPONSE_FAILED;
+       }
+#endif
+      }
+
     /* Not worth checking greeting line for ESMTP support */
     if (!(esmtp = verify_check_this_host(&(ob->hosts_avoid_esmtp), NULL,
       host->name, host->address, NULL) != OK))
@@ -550,14 +601,14 @@ else
 
     tls_redo_helo:
 
-    #ifdef SUPPORT_TLS
+#ifdef SUPPORT_TLS
     if (smtps  &&  tls_out.active < 0) /* ssl-on-connect, first pass */
       {
       tls_offered = TRUE;
       ob->tls_tempfail_tryclear = FALSE;
       }
-      else                             /* all other cases */
-    #endif
+    else                               /* all other cases */
+#endif
 
       { esmtp_retry:
 
@@ -571,26 +622,26 @@ else
          done= FALSE;
          goto RESPONSE_FAILED;
          }
-        #ifdef SUPPORT_TLS
+#ifdef SUPPORT_TLS
         tls_offered = FALSE;
-        #endif
+#endif
         esmtp = FALSE;
         goto esmtp_retry;                      /* fallback to HELO */
         }
 
       /* Set tls_offered if the response to EHLO specifies support for STARTTLS. */
-      #ifdef SUPPORT_TLS
+#ifdef SUPPORT_TLS
       if (esmtp && !suppress_tls &&  tls_out.active < 0)
-        {
-          if (regex_STARTTLS == NULL) regex_STARTTLS =
-           regex_must_compile(US"\\n250[\\s\\-]STARTTLS(\\s|\\n|$)", FALSE, TRUE);
+       {
+       if (regex_STARTTLS == NULL) regex_STARTTLS =
+         regex_must_compile(US"\\n250[\\s\\-]STARTTLS(\\s|\\n|$)", FALSE, TRUE);
 
-          tls_offered = pcre_exec(regex_STARTTLS, NULL, CS responsebuffer,
-                       Ustrlen(responsebuffer), 0, PCRE_EOPT, NULL, 0) >= 0;
+       tls_offered = pcre_exec(regex_STARTTLS, NULL, CS responsebuffer,
+                     Ustrlen(responsebuffer), 0, PCRE_EOPT, NULL, 0) >= 0;
        }
       else
         tls_offered = FALSE;
-      #endif
+#endif
       }
 
     /* If TLS is available on this connection attempt to
@@ -601,7 +652,7 @@ else
     the client not be required to use TLS. If the response is bad, copy the buffer
     for error analysis. */
 
-    #ifdef SUPPORT_TLS
+#ifdef SUPPORT_TLS
     if (tls_offered &&
        verify_check_this_host(&(ob->hosts_avoid_tls), NULL, host->name,
          host->address, NULL) != OK &&
@@ -626,11 +677,11 @@ else
         {
         if (errno != 0 || buffer2[0] == 0 ||
                (buffer2[0] == '4' && !ob->tls_tempfail_tryclear))
-       {
-       Ustrncpy(responsebuffer, buffer2, sizeof(responsebuffer));
-       done= FALSE;
-       goto RESPONSE_FAILED;
-       }
+         {
+         Ustrncpy(responsebuffer, buffer2, sizeof(responsebuffer));
+         done= FALSE;
+         goto RESPONSE_FAILED;
+         }
         }
 
        /* STARTTLS accepted or ssl-on-connect: try to negotiate a TLS session. */
@@ -640,29 +691,40 @@ else
        int rc;
 
        ob->command_timeout = callout;
-        rc = tls_client_start(inblock.sock, host, addr, ob);
+        rc = tls_client_start(inblock.sock, host, addr, addr->transport
+#ifdef EXPERIMENTAL_DANE
+                           , dane ? &tlsa_dnsa : NULL
+#endif
+                           );
        ob->command_timeout = oldtimeout;
 
         /* TLS negotiation failed; give an error.  Try in clear on a new connection,
            if the options permit it for this host. */
         if (rc != OK)
           {
-       if (rc == DEFER && ob->tls_tempfail_tryclear && !smtps &&
-          verify_check_this_host(&(ob->hosts_require_tls), NULL, host->name,
-            host->address, NULL) != OK)
-         {
-            (void)close(inblock.sock);
-         log_write(0, LOG_MAIN, "TLS session failure: delivering unencrypted "
-           "to %s [%s] (not in hosts_require_tls)", host->name, host->address);
-         suppress_tls = TRUE;
-         goto tls_retry_connection;
-         }
-       /*save_errno = ERRNO_TLSFAILURE;*/
-       /*message = US"failure while setting up TLS session";*/
-       send_quit = FALSE;
-       done= FALSE;
-       goto TLS_FAILED;
-       }
+         if (  rc == DEFER
+            && ob->tls_tempfail_tryclear
+            && !smtps
+            && verify_check_this_host(&(ob->hosts_require_tls), NULL,
+              host->name, host->address, NULL) != OK
+            )
+           {
+           (void)close(inblock.sock);
+#ifdef EXPERIMENTAL_EVENT
+           (void) event_raise(addr->transport->event_action,
+                                   US"tcp:close", NULL);
+#endif
+           log_write(0, LOG_MAIN, "TLS session failure: delivering unencrypted "
+             "to %s [%s] (not in hosts_require_tls)", host->name, host->address);
+           suppress_tls = TRUE;
+           goto tls_retry_connection;
+           }
+         /*save_errno = ERRNO_TLSFAILURE;*/
+         /*message = US"failure while setting up TLS session";*/
+         send_quit = FALSE;
+         done= FALSE;
+         goto TLS_FAILED;
+         }
 
         /* TLS session is set up.  Copy info for logging. */
         addr->cipher = tls_out.cipher;
@@ -670,7 +732,7 @@ else
 
         /* For SMTPS we need to wait for the initial OK response, then do HELO. */
         if (smtps)
-        goto smtps_redo_greeting;
+         goto smtps_redo_greeting;
 
         /* For STARTTLS we need to redo EHLO */
         goto tls_redo_helo;
@@ -679,13 +741,20 @@ else
 
     /* If the host is required to use a secure channel, ensure that we have one. */
     if (tls_out.active < 0)
-      if (verify_check_this_host(&(ob->hosts_require_tls), NULL, host->name,
-       host->address, NULL) == OK)
+      if (
+#ifdef EXPERIMENTAL_DANE
+        dane ||
+#endif
+         verify_check_this_host(&(ob->hosts_require_tls), NULL, host->name,
+             host->address, NULL) == OK
+        )
         {
         /*save_errno = ERRNO_TLSREQUIRED;*/
-        log_write(0, LOG_MAIN, "a TLS session is required for %s [%s], but %s",
+        log_write(0, LOG_MAIN,
+         "H=%s [%s]: a TLS session is required for this host, but %s",
           host->name, host->address,
-       tls_offered? "an attempt to start TLS failed" : "the server did not offer TLS support");
+         tls_offered ? "an attempt to start TLS failed"
+                     : "the server did not offer TLS support");
         done= FALSE;
         goto TLS_FAILED;
         }
@@ -705,13 +774,13 @@ else
         cutthrough_delivery= FALSE;
         HDEBUG(D_acl|D_v) debug_printf("Cutthrough cancelled by presence of transport filter\n");
         }
-      #ifndef DISABLE_DKIM
+#ifndef DISABLE_DKIM
       if (ob->dkim_domain)
         {
         cutthrough_delivery= FALSE;
         HDEBUG(D_acl|D_v) debug_printf("Cutthrough cancelled by presence of DKIM signing\n");
         }
-      #endif
+#endif
       }
 
     SEND_FAILED:
@@ -720,7 +789,6 @@ else
     ;
     /* Clear down of the TLS, SMTP and TCP layers on error is handled below.  */
 
-
     /* Failure to accept HELO is cached; this blocks the whole domain for all
     senders. I/O errors and defer responses are not cached. */
 
@@ -758,6 +826,9 @@ else
       smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer),
         '2', callout);
 
+    deliver_host = deliver_host_address = NULL;
+    deliver_domain = save_deliver_domain;
+
     /* If the host does not accept MAIL FROM:<>, arrange to cache this
     information, but again, don't record anything for an I/O error or a defer. Do
     not cache rejections of MAIL when a non-empty sender has been used, because
@@ -810,9 +881,7 @@ else
         /* If accepted, we aren't going to do any further tests below. */
 
         if (random_ok)
-          {
           new_domain_record.random_result = ccache_accept;
-          }
 
         /* Otherwise, cache a real negative response, and get back to the right
         state to send RCPT. Unless there's some problem such as a dropped
@@ -977,9 +1046,7 @@ else
       cutthrough_addr = *addr;         /* Save the address_item for later logging */
       cutthrough_addr.next =     NULL;
       cutthrough_addr.host_used = store_get(sizeof(host_item));
-      cutthrough_addr.host_used->name =    host->name;
-      cutthrough_addr.host_used->address = host->address;
-      cutthrough_addr.host_used->port =    port;
+      *(cutthrough_addr.host_used) = *host;
       if (addr->parent)
         *(cutthrough_addr.parent = store_get(sizeof(address_item)))= *addr->parent;
       ctblock.buffer = ctbuffer;
@@ -995,10 +1062,14 @@ else
         cancel_cutthrough_connection("multiple verify calls");
       if (send_quit) (void)smtp_write_command(&outblock, FALSE, "QUIT\r\n");
 
-      #ifdef SUPPORT_TLS
+#ifdef SUPPORT_TLS
       tls_close(FALSE, TRUE);
-      #endif
+#endif
       (void)close(inblock.sock);
+#ifdef EXPERIMENTAL_EVENT
+      (void) event_raise(addr->transport->event_action,
+                             US"tcp:close", NULL);
+#endif
       }
 
     }    /* Loop through all hosts, while !done */
@@ -1781,8 +1852,10 @@ while (addr_new != NULL)
 #ifdef SUPPORT_TLS
          deliver_set_expansions(addr);
 #endif
+         verify_mode = is_recipient ? US"R" : US"S";
           rc = do_callout(addr, host_list, &tf, callout, callout_overall,
             callout_connect, options, se_mailfrom, pm_mailfrom);
+         verify_mode = NULL;
           }
         }
       else