Add protocol=smtps support to smtp transport.
authorPhil Pennock <pdp@exim.org>
Sat, 27 Aug 2011 21:43:09 +0000 (14:43 -0700)
committerPhil Pennock <pdp@exim.org>
Sat, 27 Aug 2011 21:43:09 +0000 (14:43 -0700)
Permits SSL-on-connect for outbound connections.

Heavily based on Simon Arlott's patch, but with enough modifications to
risk new bugs.

nb: am on a plane, change confirmed to compile on MacOS, nothing more

fixes bug 97

doc/doc-docbook/spec.xfpt
doc/doc-txt/ChangeLog
doc/doc-txt/NewStuff
src/src/transports/smtp.c

index e920135..1092cab 100644 (file)
@@ -22044,12 +22044,22 @@ is deferred.
 
 .option protocol smtp string smtp
 .cindex "LMTP" "over TCP/IP"
+.cindex "ssmtp protocol" "outbound"
+.cindex "TLS" "SSL-on-connect outbound"
+.vindex "&$port$&"
 If this option is set to &"lmtp"& instead of &"smtp"&, the default value for
 the &%port%& option changes to &"lmtp"&, and the transport operates the LMTP
 protocol (RFC 2033) instead of SMTP. This protocol is sometimes used for local
 deliveries into closed message stores. Exim also has support for running LMTP
 over a pipe to a local process &-- see chapter &<<CHAPLMTP>>&.
 
+.new
+If this option is set to &"smtps"&, the default vaule for the &%port%& option
+changes to &"smtps"&, and the transport initiates TLS immediately after
+connecting, as an outbound SSL-on-connect, instead of using STARTTLS to upgrade.
+The Internet standards bodies strongly discourage use of this mode.
+.wen
+
 
 .option retry_include_ip_address smtp boolean true
 Exim normally includes both the host name and the IP address in the key it
index 0441c32..9cef584 100644 (file)
@@ -91,6 +91,10 @@ TF/07 Automatically extract Exim's version number from tags in the git
 PP/02 Raise smtp_cmd_buffer_size to 16kB. Patch from Paul Fisher.
       Bugzilla 879.
 
+PP/03 Implement SSL-on-connect outbound with protocol=smtps on smtp transport.
+      Heavily based on revision 40f9a89a from Simon Arlott's tree.
+      Bugzilla 97.
+
 
 Exim version 4.76
 -----------------
index 2d3f2b2..eb1e139 100644 (file)
@@ -12,6 +12,9 @@ Version 4.77
  1. New options for the ratelimit ACL condition: /count= and /unique=.
     The /noupdate option has been replaced by a /readonly option.
 
+ 2. The SMTP transport's protocol option may now be set to "smtps", to
+    use SSL-on-connect outbound.
+
 
 Version 4.76
 ------------
index a79d8e9..53012ec 100644 (file)
@@ -302,7 +302,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. */
@@ -843,6 +844,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;
@@ -913,6 +915,14 @@ if (ob->authenticated_sender != NULL)
   else if (new[0] != 0) local_authenticated_sender = new;
   }
 
+#ifndef SUPPORT_TLS
+if (smtps)
+  {
+    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
 specially so they can be identified for retries. */
@@ -940,19 +950,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
@@ -993,6 +1006,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",
@@ -1087,6 +1114,7 @@ if (tls_offered && !suppress_tls &&
   /* STARTTLS accepted: try to negotiate a TLS session. */
 
   else
+  TLS_NEGOTIATE:
     {
     int rc = tls_client_start(inblock.sock,
       host,
@@ -1127,6 +1155,10 @@ if (tls_offered && !suppress_tls &&
     }
   }
 
+/* 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
@@ -1135,6 +1167,7 @@ start of the Exim process (in exim.c). */
 
 if (tls_active >= 0)
   {
+  char *greeting_cmd;
   if (helo_data == NULL)
     {
     helo_data = expand_string(ob->helo_data);
@@ -1148,8 +1181,25 @@ 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.
+  Also, it seems likely that a server not supporting STARTTLS is broken
+  enough to perhaps not support EHLO. */
+  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))
@@ -1990,9 +2040,12 @@ if (completed_address && ok && send_quit)
       if (tls_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);
+        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