Make router_name and transport_name usable for the expansions
[users/jgh/exim.git] / src / src / transports / smtp.c
index ee260a129b3e2d62d0b073b97236857d10e4fc84..ac24f5f09ab3a4a0e4b82ec49855f3551cd455b4 100644 (file)
@@ -35,6 +35,10 @@ optionlist smtp_transport_options[] = {
       (void *)offsetof(transport_instance, connection_max_messages) },
   { "data_timeout",         opt_time,
       (void *)offsetof(smtp_transport_options_block, data_timeout) },
+#ifdef EXPERIMENTAL_DBL
+  { "dbl_host_defer_query",      opt_stringptr,
+         (void *)offsetof(smtp_transport_options_block, dbl_host_defer_query) },
+#endif
   { "delay_after_cutoff", opt_bool,
       (void *)offsetof(smtp_transport_options_block, delay_after_cutoff) },
 #ifndef DISABLE_DKIM
@@ -101,6 +105,10 @@ optionlist smtp_transport_options[] = {
   { "hosts_require_auth",   opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, hosts_require_auth) },
 #ifdef SUPPORT_TLS
+# if defined EXPERIMENTAL_OCSP
+  { "hosts_require_ocsp",   opt_stringptr,
+      (void *)offsetof(smtp_transport_options_block, hosts_require_ocsp) },
+# endif
   { "hosts_require_tls",    opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, hosts_require_tls) },
 #endif
@@ -178,6 +186,9 @@ smtp_transport_options_block smtp_transport_option_defaults = {
   NULL,                /* hosts_require_auth */
 #ifdef EXPERIMENTAL_PRDR
   NULL,                /* hosts_try_prdr */
+#endif
+#ifdef EXPERIMENTAL_OCSP
+  NULL,                /* hosts_require_ocsp */
 #endif
   NULL,                /* hosts_require_tls */
   NULL,                /* hosts_avoid_tls */
@@ -226,6 +237,9 @@ smtp_transport_options_block smtp_transport_option_defaults = {
   NULL,                /* dkim_sign_headers */
   NULL                 /* dkim_strict */
 #endif
+#ifdef EXPERIMENTAL_DBL
+ ,NULL                 /* dbl_host_defer_query */
+#endif
 };
 
 
@@ -570,6 +584,56 @@ else
 
 
 
+#ifdef EXPERIMENTAL_DBL
+/*************************************************
+*          Write error message to database log   *
+*************************************************/
+
+/* This expands an arbitrary per-transport string.
+   It might, for example, be used to write to the database log.
+
+Arguments:
+  dbl_host_defer_query  dbl_host_defer_query from the transport options block
+  addr                  the address item containing error information
+  host                  the current host
+
+Returns:   nothing
+*/
+
+static void
+dbl_write_defer_log(uschar *dbl_host_defer_query, address_item *addr, host_item *host)
+{
+if (dbl_host_defer_query == NULL)
+       return;
+
+dbl_delivery_ip = string_copy(host->address);
+dbl_delivery_port = (host->port == PORT_NONE)? 25 : host->port;
+dbl_delivery_fqdn = string_copy(host->name);
+dbl_delivery_local_part = string_copy(addr->local_part);
+dbl_delivery_domain = string_copy(addr->domain);
+dbl_defer_errno = addr->basic_errno;
+
+dbl_defer_errstr = (addr->message != NULL)
+  ? (addr->basic_errno > 0)
+  ? string_sprintf("%s: %s", addr->message, strerror(addr->basic_errno))
+  : string_copy(addr->message)
+  : (addr->basic_errno > 0)
+  ? string_copy(strerror(addr->basic_errno))
+  : NULL;
+
+DEBUG(D_transport) {
+       debug_printf("  DBL(host defer): dbl_host_defer_query=|%s| dbl_delivery_IP=%s\n", dbl_host_defer_query, dbl_delivery_ip);
+}
+
+router_name =    addr->router->name;
+transport_name = addr->transport->name;
+expand_string(dbl_host_defer_query);
+router_name = transport_name = NULL;
+}
+#endif
+
+
+
 /*************************************************
 *           Synchronize SMTP responses           *
 *************************************************/
@@ -807,6 +871,229 @@ return yield;
 
 
 
+/* Do the client side of smtp-level authentication */
+/*
+Arguments:
+  buffer       EHLO response from server (gets overwritten)
+  addrlist      chain of potential addresses to deliver
+  host          host to deliver to
+  ob           transport options
+  ibp, obp     comms channel control blocks
+
+Returns:
+  OK                   Success, or failed (but not required): global "smtp_authenticated" set
+  DEFER                        Failed authentication (and was required)
+  ERROR                        Internal problem
+
+  FAIL_SEND            Failed communications - transmit
+  FAIL                 - response
+*/
+
+int
+smtp_auth(uschar *buffer, unsigned bufsize, address_item *addrlist, host_item *host,
+    smtp_transport_options_block *ob, BOOL is_esmtp,
+    smtp_inblock *ibp, smtp_outblock *obp)
+{
+  int require_auth;
+  uschar *fail_reason = US"server did not advertise AUTH support";
+
+  smtp_authenticated = FALSE;
+  client_authenticator = client_authenticated_id = client_authenticated_sender = NULL;
+  require_auth = verify_check_this_host(&(ob->hosts_require_auth), NULL,
+    host->name, host->address, NULL);
+
+  if (is_esmtp && !regex_AUTH) regex_AUTH =
+      regex_must_compile(US"\\n250[\\s\\-]AUTH\\s+([\\-\\w\\s]+)(?:\\n|$)",
+            FALSE, TRUE);
+
+  if (is_esmtp && regex_match_and_setup(regex_AUTH, buffer, 0, -1))
+    {
+    uschar *names = string_copyn(expand_nstring[1], expand_nlength[1]);
+    expand_nmax = -1;                          /* reset */
+
+    /* Must not do this check until after we have saved the result of the
+    regex match above. */
+
+    if (require_auth == OK ||
+        verify_check_this_host(&(ob->hosts_try_auth), NULL, host->name,
+          host->address, NULL) == OK)
+      {
+      auth_instance *au;
+      fail_reason = US"no common mechanisms were found";
+
+      DEBUG(D_transport) debug_printf("scanning authentication mechanisms\n");
+
+      /* Scan the configured authenticators looking for one which is configured
+      for use as a client, which is not suppressed by client_condition, and
+      whose name matches an authentication mechanism supported by the server.
+      If one is found, attempt to authenticate by calling its client function.
+      */
+
+      for (au = auths; !smtp_authenticated && au != NULL; au = au->next)
+        {
+        uschar *p = names;
+        if (!au->client ||
+            (au->client_condition != NULL &&
+             !expand_check_condition(au->client_condition, au->name,
+               US"client authenticator")))
+          {
+          DEBUG(D_transport) debug_printf("skipping %s authenticator: %s\n",
+            au->name,
+            (au->client)? "client_condition is false" :
+                          "not configured as a client");
+          continue;
+          }
+
+        /* Loop to scan supported server mechanisms */
+
+        while (*p != 0)
+          {
+          int rc;
+          int len = Ustrlen(au->public_name);
+          while (isspace(*p)) p++;
+
+          if (strncmpic(au->public_name, p, len) != 0 ||
+              (p[len] != 0 && !isspace(p[len])))
+            {
+            while (*p != 0 && !isspace(*p)) p++;
+            continue;
+            }
+
+          /* Found data for a listed mechanism. Call its client entry. Set
+          a flag in the outblock so that data is overwritten after sending so
+          that reflections don't show it. */
+
+          fail_reason = US"authentication attempt(s) failed";
+          obp->authenticating = TRUE;
+          rc = (au->info->clientcode)(au, ibp, obp,
+            ob->command_timeout, buffer, bufsize);
+          obp->authenticating = FALSE;
+          DEBUG(D_transport) debug_printf("%s authenticator yielded %d\n",
+            au->name, rc);
+
+          /* A temporary authentication failure must hold up delivery to
+          this host. After a permanent authentication failure, we carry on
+          to try other authentication methods. If all fail hard, try to
+          deliver the message unauthenticated unless require_auth was set. */
+
+          switch(rc)
+            {
+            case OK:
+            smtp_authenticated = TRUE;   /* stops the outer loop */
+           client_authenticator = au->name;
+           if (au->set_client_id != NULL)
+             client_authenticated_id = expand_string(au->set_client_id);
+            break;
+
+            /* Failure after writing a command */
+
+            case FAIL_SEND:
+            return FAIL_SEND;
+
+            /* Failure after reading a response */
+
+            case FAIL:
+            if (errno != 0 || buffer[0] != '5') return FAIL;
+            log_write(0, LOG_MAIN, "%s authenticator failed H=%s [%s] %s",
+              au->name, host->name, host->address, buffer);
+            break;
+
+            /* Failure by some other means. In effect, the authenticator
+            decided it wasn't prepared to handle this case. Typically this
+            is the result of "fail" in an expansion string. Do we need to
+            log anything here? Feb 2006: a message is now put in the buffer
+            if logging is required. */
+
+            case CANCELLED:
+            if (*buffer != 0)
+              log_write(0, LOG_MAIN, "%s authenticator cancelled "
+                "authentication H=%s [%s] %s", au->name, host->name,
+                host->address, buffer);
+            break;
+
+            /* Internal problem, message in buffer. */
+
+            case ERROR:
+            set_errno(addrlist, 0, string_copy(buffer), DEFER, FALSE);
+            return ERROR;
+            }
+
+          break;  /* If not authenticated, try next authenticator */
+          }       /* Loop for scanning supported server mechanisms */
+        }         /* Loop for further authenticators */
+      }
+    }
+
+  /* If we haven't authenticated, but are required to, give up. */
+
+  if (require_auth == OK && !smtp_authenticated)
+    {
+    set_errno(addrlist, ERRNO_AUTHFAIL,
+      string_sprintf("authentication required but %s", fail_reason), DEFER,
+      FALSE);
+    return DEFER;
+    }
+  
+  return OK;
+}
+
+
+/* Construct AUTH appendix string for MAIL TO */
+/*
+Arguments
+  buffer       to build string
+  addrlist      chain of potential addresses to deliver
+  ob           transport options
+
+Globals                smtp_authenticated
+               client_authenticated_sender
+Return True on error, otherwise buffer has (possibly empty) terminated string
+*/
+
+BOOL
+smtp_mail_auth_str(uschar *buffer, unsigned bufsize, address_item *addrlist,
+                   smtp_transport_options_block *ob)
+{
+uschar *local_authenticated_sender = authenticated_sender;
+
+#ifdef notdef
+  debug_printf("smtp_mail_auth_str: as<%s> os<%s> SA<%s>\n", authenticated_sender, ob->authenticated_sender, smtp_authenticated?"Y":"N");
+#endif
+
+if (ob->authenticated_sender != NULL)
+  {
+  uschar *new = expand_string(ob->authenticated_sender);
+  if (new == NULL)
+    {
+    if (!expand_string_forcedfail)
+      {
+      uschar *message = string_sprintf("failed to expand "
+        "authenticated_sender: %s", expand_string_message);
+      set_errno(addrlist, 0, message, DEFER, FALSE);
+      return TRUE;
+      }
+    }
+  else if (new[0] != 0) local_authenticated_sender = new;
+  }
+
+/* Add the authenticated sender address if present */
+
+if ((smtp_authenticated || ob->authenticated_sender_force) &&
+    local_authenticated_sender != NULL)
+  {
+  string_format(buffer, bufsize, " AUTH=%s",
+    auth_xtextencode(local_authenticated_sender,
+    Ustrlen(local_authenticated_sender)));
+  client_authenticated_sender = string_copy(local_authenticated_sender);
+  }
+else
+  *buffer= 0;
+
+return FALSE;
+}
+
+
+
 /*************************************************
 *       Deliver address list to given host       *
 *************************************************/
@@ -886,7 +1173,6 @@ smtp_inblock inblock;
 smtp_outblock outblock;
 int max_rcpt = tblock->max_addresses;
 uschar *igquotstr = US"";
-uschar *local_authenticated_sender = authenticated_sender;
 uschar *helo_data = NULL;
 uschar *message = NULL;
 uschar new_message_id[MESSAGE_ID_LENGTH + 1];
@@ -1147,13 +1433,15 @@ if (tls_offered && !suppress_tls &&
     int rc = tls_client_start(inblock.sock,
       host,
       addrlist,
-      NULL,                    /* No DH param */
       ob->tls_certificate,
       ob->tls_privatekey,
       ob->tls_sni,
       ob->tls_verify_certificates,
       ob->tls_crl,
       ob->tls_require_ciphers,
+#ifdef EXPERIMENTAL_OCSP
+      ob->hosts_require_ocsp,
+#endif
       ob->tls_dh_min_bits,
       ob->command_timeout);
 
@@ -1258,9 +1546,6 @@ if (continue_hostname == NULL
     #endif
     )
   {
-  int require_auth;
-  uschar *fail_reason = US"server did not advertise AUTH support";
-
   /* Set for IGNOREQUOTA if the response to LHLO specifies support and the
   lmtp_ignore_quota option was set. */
 
@@ -1304,139 +1589,13 @@ if (continue_hostname == NULL
   the business. The host name and address must be available when the
   authenticator's client driver is running. */
 
-  smtp_authenticated = FALSE;
-  client_authenticator = client_authenticated_id = client_authenticated_sender = NULL;
-  require_auth = verify_check_this_host(&(ob->hosts_require_auth), NULL,
-    host->name, host->address, NULL);
-
-  if (esmtp && regex_match_and_setup(regex_AUTH, buffer, 0, -1))
-    {
-    uschar *names = string_copyn(expand_nstring[1], expand_nlength[1]);
-    expand_nmax = -1;                          /* reset */
-
-    /* Must not do this check until after we have saved the result of the
-    regex match above. */
-
-    if (require_auth == OK ||
-        verify_check_this_host(&(ob->hosts_try_auth), NULL, host->name,
-          host->address, NULL) == OK)
-      {
-      auth_instance *au;
-      fail_reason = US"no common mechanisms were found";
-
-      DEBUG(D_transport) debug_printf("scanning authentication mechanisms\n");
-
-      /* Scan the configured authenticators looking for one which is configured
-      for use as a client, which is not suppressed by client_condition, and
-      whose name matches an authentication mechanism supported by the server.
-      If one is found, attempt to authenticate by calling its client function.
-      */
-
-      for (au = auths; !smtp_authenticated && au != NULL; au = au->next)
-        {
-        uschar *p = names;
-        if (!au->client ||
-            (au->client_condition != NULL &&
-             !expand_check_condition(au->client_condition, au->name,
-               US"client authenticator")))
-          {
-          DEBUG(D_transport) debug_printf("skipping %s authenticator: %s\n",
-            au->name,
-            (au->client)? "client_condition is false" :
-                          "not configured as a client");
-          continue;
-          }
-
-        /* Loop to scan supported server mechanisms */
-
-        while (*p != 0)
-          {
-          int rc;
-          int len = Ustrlen(au->public_name);
-          while (isspace(*p)) p++;
-
-          if (strncmpic(au->public_name, p, len) != 0 ||
-              (p[len] != 0 && !isspace(p[len])))
-            {
-            while (*p != 0 && !isspace(*p)) p++;
-            continue;
-            }
-
-          /* Found data for a listed mechanism. Call its client entry. Set
-          a flag in the outblock so that data is overwritten after sending so
-          that reflections don't show it. */
-
-          fail_reason = US"authentication attempt(s) failed";
-          outblock.authenticating = TRUE;
-          rc = (au->info->clientcode)(au, &inblock, &outblock,
-            ob->command_timeout, buffer, sizeof(buffer));
-          outblock.authenticating = FALSE;
-          DEBUG(D_transport) debug_printf("%s authenticator yielded %d\n",
-            au->name, rc);
-
-          /* A temporary authentication failure must hold up delivery to
-          this host. After a permanent authentication failure, we carry on
-          to try other authentication methods. If all fail hard, try to
-          deliver the message unauthenticated unless require_auth was set. */
-
-          switch(rc)
-            {
-            case OK:
-            smtp_authenticated = TRUE;   /* stops the outer loop */
-           client_authenticator = au->name;
-           if (au->set_client_id != NULL)
-             client_authenticated_id = expand_string(au->set_client_id);
-            break;
-
-            /* Failure after writing a command */
-
-            case FAIL_SEND:
-            goto SEND_FAILED;
-
-            /* Failure after reading a response */
-
-            case FAIL:
-            if (errno != 0 || buffer[0] != '5') goto RESPONSE_FAILED;
-            log_write(0, LOG_MAIN, "%s authenticator failed H=%s [%s] %s",
-              au->name, host->name, host->address, buffer);
-            break;
-
-            /* Failure by some other means. In effect, the authenticator
-            decided it wasn't prepared to handle this case. Typically this
-            is the result of "fail" in an expansion string. Do we need to
-            log anything here? Feb 2006: a message is now put in the buffer
-            if logging is required. */
-
-            case CANCELLED:
-            if (*buffer != 0)
-              log_write(0, LOG_MAIN, "%s authenticator cancelled "
-                "authentication H=%s [%s] %s", au->name, host->name,
-                host->address, buffer);
-            break;
-
-            /* Internal problem, message in buffer. */
-
-            case ERROR:
-            yield = ERROR;
-            set_errno(addrlist, 0, string_copy(buffer), DEFER, FALSE);
-            goto SEND_QUIT;
-            }
-
-          break;  /* If not authenticated, try next authenticator */
-          }       /* Loop for scanning supported server mechanisms */
-        }         /* Loop for further authenticators */
-      }
-    }
-
-  /* If we haven't authenticated, but are required to, give up. */
-
-  if (require_auth == OK && !smtp_authenticated)
+  switch (yield = smtp_auth(buffer, sizeof(buffer), addrlist, host,
+                           ob, esmtp, &inblock, &outblock))
     {
-    yield = DEFER;
-    set_errno(addrlist, ERRNO_AUTHFAIL,
-      string_sprintf("authentication required but %s", fail_reason), DEFER,
-      FALSE);
-    goto SEND_QUIT;
+    default:           goto SEND_QUIT;
+    case OK:           break;
+    case FAIL_SEND:    goto SEND_FAILED;
+    case FAIL:         goto RESPONSE_FAILED;
     }
   }
 
@@ -1529,32 +1688,8 @@ Other expansion failures are serious. An empty result is ignored, but there is
 otherwise no check - this feature is expected to be used with LMTP and other
 cases where non-standard addresses (e.g. without domains) might be required. */
 
-if (ob->authenticated_sender != NULL)
-  {
-  uschar *new = expand_string(ob->authenticated_sender);
-  if (new == NULL)
-    {
-    if (!expand_string_forcedfail)
-      {
-      uschar *message = string_sprintf("failed to expand "
-        "authenticated_sender: %s", expand_string_message);
-      set_errno(addrlist, 0, message, DEFER, FALSE);
-      return ERROR;
-      }
-    }
-  else if (new[0] != 0) local_authenticated_sender = new;
-  }
-
-/* Add the authenticated sender address if present */
-
-if ((smtp_authenticated || ob->authenticated_sender_force) &&
-    local_authenticated_sender != NULL)
-  {
-  string_format(p, sizeof(buffer) - (p-buffer), " AUTH=%s",
-    auth_xtextencode(local_authenticated_sender,
-    Ustrlen(local_authenticated_sender)));
-  client_authenticated_sender = string_copy(local_authenticated_sender);
-  }
+if (smtp_mail_auth_str(p, sizeof(buffer) - (p-buffer), addrlist, ob))
+    return ERROR;
 
 /* From here until we send the DATA command, we can make use of PIPELINING
 if the server host supports it. The code has to be able to check the responses
@@ -1834,7 +1969,12 @@ if (!ok) ok = TRUE; else
 
     /* Set up confirmation if needed - applies only to SMTP */
 
-    if ((log_extra_selector & LX_smtp_confirmation) != 0 && !lmtp)
+    if (
+               #ifndef EXPERIMENTAL_DBL
+                       (log_extra_selector & LX_smtp_confirmation) != 0 &&
+               #endif
+                       !lmtp
+        )
       {
       uschar *s = string_printing(buffer);
       conf = (s == buffer)? (uschar *)string_copy(s) : s;
@@ -2973,6 +3113,11 @@ for (cutoff_retry = 0; expired &&
                          first_addr->basic_errno != ERRNO_TLSFAILURE)
         write_logs(first_addr, host);
 
+      #ifdef EXPERIMENTAL_DBL
+      if (rc == DEFER)
+        dbl_write_defer_log(ob->dbl_host_defer_query, first_addr, host);
+      #endif
+
       /* If STARTTLS was accepted, but there was a failure in setting up the
       TLS session (usually a certificate screwup), and the host is not in
       hosts_require_tls, and tls_tempfail_tryclear is true, try again, with
@@ -2995,6 +3140,10 @@ for (cutoff_retry = 0; expired &&
           expanded_hosts != NULL, &message_defer, TRUE);
         if (rc == DEFER && first_addr->basic_errno != ERRNO_AUTHFAIL)
           write_logs(first_addr, host);
+        #ifdef EXPERIMENTAL_DBL
+        if (rc == DEFER)
+          dbl_write_defer_log(ob->dbl_host_defer_query, first_addr, host);
+        #endif
         }
       #endif
       }