Add optional authenticated_sender info to A= elements of log lines; bug 1314.
[exim.git] / src / src / transports / smtp.c
index e06631f2fa50b4656cc046e5ce2a21dc01d6dd7c..b4ef7cf4d77c72af3ea5a8f742430e99cab9c09b 100644 (file)
@@ -1,10 +1,8 @@
-/* $Cambridge: exim/src/src/transports/smtp.c,v 1.44 2009/11/16 19:56:54 nm4 Exp $ */
-
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2012 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 #include "../exim.h"
@@ -57,6 +55,8 @@ optionlist smtp_transport_options[] = {
       (void *)offsetof(smtp_transport_options_block, dns_qualify_single) },
   { "dns_search_parents",   opt_bool,
       (void *)offsetof(smtp_transport_options_block, dns_search_parents) },
+  { "dscp",                 opt_stringptr,
+      (void *)offsetof(smtp_transport_options_block, dscp) },
   { "fallback_hosts",       opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, fallback_hosts) },
   { "final_timeout",        opt_time,
@@ -64,6 +64,9 @@ optionlist smtp_transport_options[] = {
   { "gethostbyname",        opt_bool,
       (void *)offsetof(smtp_transport_options_block, gethostbyname) },
 #ifdef SUPPORT_TLS
+  /* These are no longer honoured, as of Exim 4.80; for now, we silently
+  ignore; a later release will warn, and a later-still release will remove
+  these options, so that using them becomes an error. */
   { "gnutls_require_kx",    opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, gnutls_require_kx) },
   { "gnutls_require_mac",   opt_stringptr,
@@ -103,6 +106,10 @@ optionlist smtp_transport_options[] = {
 #endif
   { "hosts_try_auth",       opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, hosts_try_auth) },
+#ifdef SUPPORT_TLS
+  { "hosts_verify_avoid_tls", opt_stringptr,
+      (void *)offsetof(smtp_transport_options_block, hosts_verify_avoid_tls) },
+#endif
   { "interface",            opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, interface) },
   { "keepalive",            opt_bool,
@@ -128,10 +135,14 @@ optionlist smtp_transport_options[] = {
       (void *)offsetof(smtp_transport_options_block, tls_certificate) },
   { "tls_crl",              opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, tls_crl) },
+  { "tls_dh_min_bits",      opt_int,
+      (void *)offsetof(smtp_transport_options_block, tls_dh_min_bits) },
   { "tls_privatekey",       opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, tls_privatekey) },
-  { "tls_require_ciphers",   opt_stringptr,
+  { "tls_require_ciphers",  opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, tls_require_ciphers) },
+  { "tls_sni",              opt_stringptr,
+      (void *)offsetof(smtp_transport_options_block, tls_sni) },
   { "tls_tempfail_tryclear", opt_bool,
       (void *)offsetof(smtp_transport_options_block, tls_tempfail_tryclear) },
   { "tls_verify_certificates", opt_stringptr,
@@ -157,11 +168,13 @@ smtp_transport_options_block smtp_transport_option_defaults = {
   NULL,                /* interface */
   NULL,                /* port */
   US"smtp",            /* protocol */
+  NULL,                /* DSCP */
   NULL,                /* serialize_hosts */
   NULL,                /* hosts_try_auth */
   NULL,                /* hosts_require_auth */
   NULL,                /* hosts_require_tls */
   NULL,                /* hosts_avoid_tls */
+  US"*",               /* hosts_verify_avoid_tls */
   NULL,                /* hosts_avoid_pipelining */
   NULL,                /* hosts_avoid_esmtp */
   NULL,                /* hosts_nopass_tls */
@@ -192,7 +205,10 @@ smtp_transport_options_block smtp_transport_option_defaults = {
   NULL,                /* gnutls_require_kx */
   NULL,                /* gnutls_require_mac */
   NULL,                /* gnutls_require_proto */
+  NULL,                /* tls_sni */
   NULL,                /* tls_verify_certificates */
+  EXIM_CLIENT_DH_DEFAULT_MIN_BITS,
+                       /* tls_dh_min_bits */
   TRUE                 /* tls_tempfail_tryclear */
 #endif
 #ifndef DISABLE_DKIM
@@ -304,7 +320,8 @@ if (tblock->retry_use_local_part == TRUE_UNSET)
 /* Set the default port according to the protocol */
 
 if (ob->port == NULL)
-  ob->port = (strcmpic(ob->protocol, US"lmtp") == 0)? US"lmtp" : US"smtp";
+  ob->port = (strcmpic(ob->protocol, US"lmtp") == 0)? US"lmtp" :
+    (strcmpic(ob->protocol, US"smtps") == 0)? US"smtps" : US"smtp";
 
 /* Set up the setup entry point, to be called before subprocesses for this
 transport. */
@@ -845,6 +862,7 @@ time_t start_delivery_time = time(NULL);
 smtp_transport_options_block *ob =
   (smtp_transport_options_block *)(tblock->options_block);
 BOOL lmtp = strcmpic(ob->protocol, US"lmtp") == 0;
+BOOL smtps = strcmpic(ob->protocol, US"smtps") == 0;
 BOOL ok = FALSE;
 BOOL send_rset = TRUE;
 BOOL send_quit = TRUE;
@@ -889,31 +907,27 @@ outblock.authenticating = FALSE;
 
 /* Reset the parameters of a TLS session. */
 
-tls_cipher = NULL;
-tls_peerdn = NULL;
+tls_in.bits = 0;
+tls_in.cipher = NULL;  /* for back-compatible behaviour */
+tls_in.peerdn = NULL;
+#if defined(SUPPORT_TLS) && !defined(USE_GNUTLS)
+tls_in.sni = NULL;
+#endif
 
-/* If an authenticated_sender override has been specified for this transport
-instance, expand it. If the expansion is forced to fail, and there was already
-an authenticated_sender for this message, the original value will be used.
-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. */
+tls_out.bits = 0;
+tls_out.cipher = NULL; /* the one we may use for this transport */
+tls_out.peerdn = NULL;
+#if defined(SUPPORT_TLS) && !defined(USE_GNUTLS)
+tls_out.sni = NULL;
+#endif
 
-if (ob->authenticated_sender != NULL)
+#ifndef SUPPORT_TLS
+if (smtps)
   {
-  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;
+    set_errno(addrlist, 0, US"TLS support not available", DEFER, FALSE);
+    return ERROR;
   }
+#endif
 
 /* Make a connection to the host if this isn't a continued delivery, and handle
 the initial interaction and HELO/EHLO/LHLO. Connect timeout errors are handled
@@ -923,7 +937,7 @@ if (continue_hostname == NULL)
   {
   inblock.sock = outblock.sock =
     smtp_connect(host, host_af, port, interface, ob->connect_timeout,
-      ob->keepalive);   /* This puts port into host->port */
+      ob->keepalive, ob->dscp);   /* This puts port into host->port */
 
   if (inblock.sock < 0)
     {
@@ -942,19 +956,22 @@ if (continue_hostname == NULL)
   is nevertheless a reasonably clean way of programming this kind of logic,
   where you want to escape on any error. */
 
-  if (!smtp_read_response(&inblock, buffer, sizeof(buffer), '2',
-    ob->command_timeout)) goto RESPONSE_FAILED;
+  if (!smtps)
+    {
+    if (!smtp_read_response(&inblock, buffer, sizeof(buffer), '2',
+      ob->command_timeout)) goto RESPONSE_FAILED;
 
-  /* Now check if the helo_data expansion went well, and sign off cleanly if it
-  didn't. */
+    /* Now check if the helo_data expansion went well, and sign off cleanly if
+    it didn't. */
 
-  if (helo_data == NULL)
-    {
-    uschar *message = string_sprintf("failed to expand helo_data: %s",
-      expand_string_message);
-    set_errno(addrlist, 0, message, DEFER, FALSE);
-    yield = DEFER;
-    goto SEND_QUIT;
+    if (helo_data == NULL)
+      {
+      uschar *message = string_sprintf("failed to expand helo_data: %s",
+        expand_string_message);
+      set_errno(addrlist, 0, message, DEFER, FALSE);
+      yield = DEFER;
+      goto SEND_QUIT;
+      }
     }
 
 /** Debugging without sending a message
@@ -995,6 +1012,20 @@ goto SEND_QUIT;
   esmtp = verify_check_this_host(&(ob->hosts_avoid_esmtp), NULL,
      host->name, host->address, NULL) != OK;
 
+  /* Alas; be careful, since this goto is not an error-out, so conceivably
+  we might set data between here and the target which we assume to exist
+  and be usable.  I can see this coming back to bite us. */
+  #ifdef SUPPORT_TLS
+  if (smtps)
+    {
+    tls_offered = TRUE;
+    suppress_tls = FALSE;
+    ob->tls_tempfail_tryclear = FALSE;
+    smtp_command = US"SSL-on-connect";
+    goto TLS_NEGOTIATE;
+    }
+  #endif
+
   if (esmtp)
     {
     if (smtp_write_command(&outblock, FALSE, "%s %s\r\n",
@@ -1078,15 +1109,18 @@ if (tls_offered && !suppress_tls &&
   if (!smtp_read_response(&inblock, buffer2, sizeof(buffer2), '2',
       ob->command_timeout))
     {
-    Ustrncpy(buffer, buffer2, sizeof(buffer));
     if (errno != 0 || buffer2[0] == 0 ||
          (buffer2[0] == '4' && !ob->tls_tempfail_tryclear))
+      {
+      Ustrncpy(buffer, buffer2, sizeof(buffer));
       goto RESPONSE_FAILED;
+      }
     }
 
   /* STARTTLS accepted: try to negotiate a TLS session. */
 
   else
+  TLS_NEGOTIATE:
     {
     int rc = tls_client_start(inblock.sock,
       host,
@@ -1094,12 +1128,11 @@ if (tls_offered && !suppress_tls &&
       NULL,                    /* No DH param */
       ob->tls_certificate,
       ob->tls_privatekey,
+      ob->tls_sni,
       ob->tls_verify_certificates,
       ob->tls_crl,
       ob->tls_require_ciphers,
-      ob->gnutls_require_mac,
-      ob->gnutls_require_kx,
-      ob->gnutls_require_proto,
+      ob->tls_dh_min_bits,
       ob->command_timeout);
 
     /* TLS negotiation failed; give an error. From outside, this function may
@@ -1120,21 +1153,26 @@ if (tls_offered && !suppress_tls &&
       {
       if (addr->transport_return == PENDING_DEFER)
         {
-        addr->cipher = tls_cipher;
-        addr->peerdn = tls_peerdn;
+        addr->cipher = tls_out.cipher;
+        addr->peerdn = tls_out.peerdn;
         }
       }
     }
   }
 
+/* if smtps, we'll have smtp_command set to something else; always safe to
+reset it here. */
+smtp_command = big_buffer;
+
 /* If we started TLS, redo the EHLO/LHLO exchange over the secure channel. If
 helo_data is null, we are dealing with a connection that was passed from
 another process, and so we won't have expanded helo_data above. We have to
 expand it here. $sending_ip_address and $sending_port are set up right at the
 start of the Exim process (in exim.c). */
 
-if (tls_active >= 0)
+if (tls_out.active >= 0)
   {
+  char *greeting_cmd;
   if (helo_data == NULL)
     {
     helo_data = expand_string(ob->helo_data);
@@ -1148,8 +1186,24 @@ if (tls_active >= 0)
       }
     }
 
-  if (smtp_write_command(&outblock, FALSE, "%s %s\r\n", lmtp? "LHLO" : "EHLO",
-        helo_data) < 0)
+  /* For SMTPS we need to wait for the initial OK response. */
+  if (smtps)
+    {
+    if (!smtp_read_response(&inblock, buffer, sizeof(buffer), '2',
+      ob->command_timeout)) goto RESPONSE_FAILED;
+    }
+
+  if (esmtp)
+    greeting_cmd = "EHLO";
+  else
+    {
+    greeting_cmd = "HELO";
+    DEBUG(D_transport)
+      debug_printf("not sending EHLO (host matches hosts_avoid_esmtp)\n");
+    }
+
+  if (smtp_write_command(&outblock, FALSE, "%s %s\r\n",
+        lmtp? "LHLO" : greeting_cmd, helo_data) < 0)
     goto SEND_FAILED;
   if (!smtp_read_response(&inblock, buffer, sizeof(buffer), '2',
        ob->command_timeout))
@@ -1178,7 +1232,7 @@ we skip this. */
 
 if (continue_hostname == NULL
     #ifdef SUPPORT_TLS
-    || tls_active >= 0
+    || tls_out.active >= 0
     #endif
     )
   {
@@ -1295,6 +1349,9 @@ if (continue_hostname == NULL
             {
             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 */
@@ -1411,6 +1468,29 @@ if (smtp_use_size)
   while (*p) p++;
   }
 
+/* If an authenticated_sender override has been specified for this transport
+instance, expand it. If the expansion is forced to fail, and there was already
+an authenticated_sender for this message, the original value will be used.
+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) &&
@@ -1419,7 +1499,10 @@ if ((smtp_authenticated || ob->authenticated_sender_force) &&
   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);
   }
+else
+  client_authenticated_sender = NULL;
 
 /* 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
@@ -1938,7 +2021,7 @@ if (completed_address && ok && send_quit)
   BOOL more;
   if (first_addr != NULL || continue_more ||
         (
-           (tls_active < 0 ||
+           (tls_out.active < 0 ||
            verify_check_this_host(&(ob->hosts_nopass_tls), NULL, host->name,
              host->address, NULL) != OK)
         &&
@@ -1987,12 +2070,15 @@ if (completed_address && ok && send_quit)
       don't get a good response, we don't attempt to pass the socket on. */
 
       #ifdef SUPPORT_TLS
-      if (tls_active >= 0)
+      if (tls_out.active >= 0)
         {
-        tls_close(TRUE);
-        ok = smtp_write_command(&outblock,FALSE,"EHLO %s\r\n",helo_data) >= 0 &&
-             smtp_read_response(&inblock, buffer, sizeof(buffer), '2',
-               ob->command_timeout);
+        tls_close(FALSE, TRUE);
+        if (smtps)
+          ok = FALSE;
+        else
+          ok = smtp_write_command(&outblock,FALSE,"EHLO %s\r\n",helo_data) >= 0 &&
+               smtp_read_response(&inblock, buffer, sizeof(buffer), '2',
+                 ob->command_timeout);
         }
       #endif
 
@@ -2036,7 +2122,7 @@ if (send_quit) (void)smtp_write_command(&outblock, FALSE, "QUIT\r\n");
 END_OFF:
 
 #ifdef SUPPORT_TLS
-tls_close(TRUE);
+tls_close(FALSE, TRUE);
 #endif
 
 /* Close the socket, and return the appropriate value, first setting
@@ -3034,9 +3120,12 @@ for (addr = addrlist; addr != NULL; addr = addr->next)
 /* Update the database which keeps information about which messages are waiting
 for which hosts to become available. For some message-specific errors, the
 update_waiting flag is turned off because we don't want follow-on deliveries in
-those cases. */
+those cases.  If this transport instance is explicitly limited to one message
+per connection then follow-on deliveries are not possible and there's no need
+to create/update the per-transport wait-<transport_name> database. */
 
-if (update_waiting) transport_update_waiting(hostlist, tblock->name);
+if (update_waiting && tblock->connection_max_messages != 1)
+  transport_update_waiting(hostlist, tblock->name);
 
 END_TRANSPORT: