From 061b7ebd7d69db7674f03025d552fa0bedd0fef8 Mon Sep 17 00:00:00 2001 From: Phil Pennock Date: Sat, 27 Aug 2011 14:43:09 -0700 Subject: [PATCH] Add protocol=smtps support to smtp transport. 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 | 10 +++++ doc/doc-txt/ChangeLog | 4 ++ doc/doc-txt/NewStuff | 3 ++ src/src/transports/smtp.c | 87 +++++++++++++++++++++++++++++++-------- 4 files changed, 87 insertions(+), 17 deletions(-) diff --git a/doc/doc-docbook/spec.xfpt b/doc/doc-docbook/spec.xfpt index e92013522..1092cab54 100644 --- a/doc/doc-docbook/spec.xfpt +++ b/doc/doc-docbook/spec.xfpt @@ -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 &<>&. +.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 diff --git a/doc/doc-txt/ChangeLog b/doc/doc-txt/ChangeLog index 0441c326e..9cef58475 100644 --- a/doc/doc-txt/ChangeLog +++ b/doc/doc-txt/ChangeLog @@ -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 ----------------- diff --git a/doc/doc-txt/NewStuff b/doc/doc-txt/NewStuff index 2d3f2b263..eb1e1397e 100644 --- a/doc/doc-txt/NewStuff +++ b/doc/doc-txt/NewStuff @@ -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 ------------ diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c index a79d8e927..53012eced 100644 --- a/src/src/transports/smtp.c +++ b/src/src/transports/smtp.c @@ -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 -- 2.30.2