More tls_sni support: outbound, logging.
authorPhil Pennock <pdp@exim.org>
Fri, 4 May 2012 15:27:09 +0000 (08:27 -0700)
committerPhil Pennock <pdp@exim.org>
Fri, 4 May 2012 15:27:09 +0000 (08:27 -0700)
tls_sni as SMTP transport option.
Use correct storage pool for copying tls_sni, so survives for life of process.
Add +tls_sni log-selector, for inbound tls_sni.
Update exipick to handle -tls_sni in spool files.

Also reset tls_bits at start of outbound connection (was missing).

16 files changed:
doc/doc-docbook/spec.xfpt
doc/doc-txt/ChangeLog
doc/doc-txt/NewStuff
doc/doc-txt/OptionLists.txt
src/src/exipick.src
src/src/functions.h
src/src/globals.c
src/src/local_scan.h
src/src/macros.h
src/src/receive.c
src/src/smtp_in.c
src/src/string.c
src/src/tls-gnu.c
src/src/tls-openssl.c
src/src/transports/smtp.c
src/src/transports/smtp.h

index 32e24ca..ea4e040 100644 (file)
@@ -11899,8 +11899,8 @@ If the variable appears in &%tls_certificate%& then this option and
 a different certificate to be presented (and optionally a different key to be
 used) to the client, based upon the value of the SNI extension.
 
-The value will be retained for the lifetime of the message, and not changed
-during outbound SMTP.
+The value will be retained for the lifetime of the message.  During outbound
+SMTP deliveries, it reflects the value of the tls_sni option on the transport.
 
 This is currently only available when using OpenSSL, built with support for
 SNI.
@@ -15627,6 +15627,12 @@ receiving incoming messages as a server. If you want to supply certificates for
 use when sending messages as a client, you must set the &%tls_certificate%&
 option in the relevant &(smtp)& transport.
 
+.new
+If the option contains &$tls_sni$& and Exim is built against OpenSSL, then
+if the OpenSSL build supports TLS extensions and the TLS client sends the
+Server Name Indication extension, then this option and &%tls_privatekey%&
+will be re-expanded.
+.wen
 
 .option tls_crl main string&!! unset
 .cindex "TLS" "server certificate revocation list"
@@ -15659,6 +15665,11 @@ the expansion is forced to fail, or the result is an empty string, the private
 key is assumed to be in the same file as the server's certificates. See chapter
 &<<CHAPTLS>>& for further details.
 
+.new
+See &%tls_certificate%& discussion of &$tls_sni$& for when this option may be
+re-expanded.
+.wen
+
 
 .option tls_remember_esmtp main boolean false
 .cindex "TLS" "esmtp state; remembering"
@@ -22371,6 +22382,20 @@ ciphers is a preference order.
 
 
 
+.new
+.option tls_sni smtp string&!! unset
+.cindex "TLS" "Server Name Indication"
+.vindex "&$tls_sni$&"
+If this option is set then it sets the $tls_sni variable and causes any
+TLS session to pass this value as the Server Name Indication extension to
+the remote side, which can be used by the remote side to select an appropriate
+certificate and private key for the session.
+
+OpenSSL only, also requiring a build of OpenSSL that supports TLS extensions.
+.wen
+
+
+
 .option tls_tempfail_tryclear smtp boolean true
 .cindex "4&'xx'& responses" "to STARTTLS"
 When the server host is not in &%hosts_require_tls%&, and there is a problem in
@@ -33155,6 +33180,7 @@ selection marked by asterisks:
 &` tls_certificate_verified   `&  certificate verification status
 &`*tls_cipher                 `&  TLS cipher suite on <= and => lines
 &` tls_peerdn                 `&  TLS peer DN on <= and => lines
+&` tls_sni                    `&  TLS SNI on <= lines
 &` unknown_in_list            `&  DNS lookup failed in list match
 
 &` all                        `&  all of the above
@@ -33450,6 +33476,12 @@ connection, the cipher suite used is added to the log line, preceded by X=.
 connection, and a certificate is supplied by the remote host, the peer DN is
 added to the log line, preceded by DN=.
 .next
+.cindex "log" "TLS SNI"
+.cindex "TLS" "logging SNI"
+&%tls_sni%&: When a message is received over an encrypted connection, and
+the remote host provided the Server Name Indication extension, the SNI is
+added to the log line, preceded by SNI=.
+.next
 .cindex "log" "DNS failure in list"
 &%unknown_in_list%&: This setting causes a log entry to be written when the
 result of a list match is failure because a DNS lookup failed.
index 4ad79c2..55cde6d 100644 (file)
@@ -75,6 +75,8 @@ PP/16 Removed "dont_insert_empty_fragments" fron "openssl_options".
 
 PP/17 OpenSSL: new expansion var $tls_sni, which if used in tls_certificate
       lets Exim select keys and certificates based upon TLS SNI from client.
+      Also option tls_sni on SMTP Transports.  Also clear $tls_bits correctly
+      before an outbound SMTP session.  New log_selector, +tls_sni.
 
 
 Exim version 4.77
index b788b45..2872d24 100644 (file)
@@ -47,6 +47,13 @@ Version 4.78
     sends the TLS Server Name Indication extension, to permit choosing a
     different certificate; tls_privatekey will also be re-expanded.  You must
     still set these options to expand to valid files when $tls_sni is not set.
+
+    The SMTP Transport has gained the option tls_sni, which will set a hostname
+    for outbound TLS sessions, and set $tls_sni too.
+
+    A new log_selector, +tls_sni, has been added, to log received SNI values
+    for Exim as a server.
+
     Currently OpenSSL only.
 
 
index b10f3f1..52a24b1 100644 (file)
@@ -554,6 +554,7 @@ tls_privatekey                       string*         unset         main
 tls_remember_emstp                   boolean         false         main              4.21
 tls_require_ciphers                  string*         unset         smtp              4.00 replaces tls_verify_ciphers
                                      string*         unset         main              4.33
+tls_sni                              string*         unset         main              4.78
 tls_tempfail_tryclear                boolean         true          smtp              4.05
 tls_try_verify_hosts                 host list       unset         main              4.00
 tls_verify_certificates              string*         unset         main              3.20
index 811092d..ed3b661 100644 (file)
@@ -955,6 +955,8 @@ sub _parse_header {
         $self->{_vars}{tls_cipher} = $arg;
       } elsif ($tag eq '-tls_peerdn') {
         $self->{_vars}{tls_peerdn} = $arg;
+      } elsif ($tag eq '-tls_sni') {
+        $self->{_vars}{tls_sni} = $arg;
       } elsif ($tag eq '-host_address') {
         $self->{_vars}{sender_host_port} = $self->_get_host_and_port(\$arg);
         $self->{_vars}{sender_host_address} = $arg;
@@ -1793,6 +1795,10 @@ The cipher suite that was negotiated for encrypted SMTP connections.
 
 The value of the Distinguished Name of the certificate if Exim is configured to request one
 
+=item S . $tls_sni
+
+The value of the Server Name Indication TLS extension sent by a client, if one was sent.
+
 =item N + $warning_count
 
 The number of delay warnings which have been sent for this message.
index f1af42e..2202352 100644 (file)
@@ -23,7 +23,7 @@ extern uschar *init_perl(uschar *);
 #ifdef SUPPORT_TLS
 extern int     tls_client_start(int, host_item *, address_item *, uschar *,
                  uschar *, uschar *, uschar *, uschar *, uschar *, uschar *,
-                 uschar *, uschar *, int);
+                 uschar *, uschar *, uschar *, int);
 extern void    tls_close(BOOL);
 extern int     tls_feof(void);
 extern int     tls_ferror(void);
index 7985cd3..f11c7c2 100644 (file)
@@ -697,7 +697,7 @@ uschar *log_file_path          = US LOG_FILE_PATH
 /* Those log options with L_xxx identifiers have values less than 0x800000 and
 are the ones that get put into log_write_selector. They can be used in calls to
 log_write() to test for the bit. The options with LX_xxx identifiers have
-values greater than 0x80000000 and are put int log_extra_selector (without the
+values greater than 0x80000000 and are put into log_extra_selector (without the
 top bit). They are never used in calls to log_write(), but are tested
 independently. This separation became necessary when the number of log
 selectors was getting close to filling a 32-bit word. */
@@ -746,6 +746,7 @@ bit_table log_options[]        = {
   { US"tls_certificate_verified",     LX_tls_certificate_verified },
   { US"tls_cipher",                   LX_tls_cipher },
   { US"tls_peerdn",                   LX_tls_peerdn },
+  { US"tls_sni",                      LX_tls_sni },
   { US"unknown_in_list",              LX_unknown_in_list }
 };
 
index 25b1944..aedfc9f 100644 (file)
@@ -186,7 +186,7 @@ extern uschar *rfc2047_decode(uschar *, BOOL, uschar *, int, int *, uschar **);
 extern int     smtp_fflush(void);
 extern void    smtp_printf(const char *, ...) PRINTF_FUNCTION(1,2);
 extern void    smtp_vprintf(const char *, va_list);
-extern uschar *string_copy(uschar *);
+extern uschar *string_copy(const uschar *);
 extern uschar *string_copyn(uschar *, int);
 extern uschar *string_sprintf(const char *, ...) PRINTF_FUNCTION(1,2);
 
index c1c4cc3..9b41226 100644 (file)
@@ -407,7 +407,8 @@ set all the bits in a multi-word selector. */
 #define LX_tls_certificate_verified    0x80100000
 #define LX_tls_cipher                  0x80200000
 #define LX_tls_peerdn                  0x80400000
-#define LX_unknown_in_list             0x80800000
+#define LX_tls_sni                     0x80800000
+#define LX_unknown_in_list             0x81000000
 
 #define L_default     (L_connection_reject        | \
                        L_delay_delivery           | \
index 7105265..aaaf64c 100644 (file)
@@ -3488,6 +3488,11 @@ if ((log_extra_selector & LX_tls_certificate_verified) != 0 &&
 if ((log_extra_selector & LX_tls_peerdn) != 0 && tls_peerdn != NULL)
   s = string_append(s, &size, &sptr, 3, US" DN=\"",
     string_printing(tls_peerdn), US"\"");
+#ifndef USE_GNUTLS
+if ((log_extra_selector & LX_tls_sni) != 0 && tls_sni != NULL)
+  s = string_append(s, &size, &sptr, 3, US" SNI=\"",
+    string_printing(tls_sni), US"\"");
+#endif
 #endif
 
 if (sender_host_authenticated != NULL)
index 23bc531..d1c10f0 100644 (file)
@@ -841,6 +841,11 @@ if ((log_extra_selector & LX_tls_certificate_verified) != 0 &&
 if ((log_extra_selector & LX_tls_peerdn) != 0 && tls_peerdn != NULL)
   s = string_append(s, &size, &ptr, 3, US" DN=\"",
     string_printing(tls_peerdn), US"\"");
+#ifndef USE_GNUTLS
+if ((log_extra_selector & LX_tls_sni) != 0 && tls_sni != NULL)
+  s = string_append(s, &size, &ptr, 3, US" SNI=\"",
+    string_printing(tls_sni), US"\"");
+#endif
 #endif
 
 sep = (smtp_connection_had[SMTP_HBUFF_SIZE-1] != SCH_NONE)?
index 0a321ee..3fea7c0 100644 (file)
@@ -415,7 +415,7 @@ Returns:  copy of string in new store
 */
 
 uschar *
-string_copy(uschar *s)
+string_copy(const uschar *s)
 {
 int len = Ustrlen(s) + 1;
 uschar *ss = store_get(len);
index 2f952e4..7e87dde 100644 (file)
@@ -1055,6 +1055,7 @@ Arguments:
   dhparam           DH parameter file
   certificate       certificate file
   privatekey        private key file
+  sni               TLS SNI to send to remote host
   verify_certs      file for certificate verify
   verify_crl        CRL for verify
   require_ciphers   list of allowed ciphers or NULL
@@ -1069,8 +1070,9 @@ Returns:            OK/DEFER/FAIL (because using common functions),
 
 int
 tls_client_start(int fd, host_item *host, address_item *addr, uschar *dhparam,
-  uschar *certificate, uschar *privatekey, uschar *verify_certs,
-  uschar *verify_crl, uschar *require_ciphers, uschar *require_mac,
+  uschar *certificate, uschar *privatekey, uschar *sni ARG_UNUSED,
+  uschar *verify_certs, uschar *verify_crl,
+  uschar *require_ciphers, uschar *require_mac,
   uschar *require_kx, uschar *require_proto, int timeout)
 {
 const gnutls_datum *server_certs;
index 8cc2457..e609670 100644 (file)
@@ -385,15 +385,18 @@ tls_servername_cb(SSL *s, int *ad ARG_UNUSED, void *arg)
 const char *servername = SSL_get_servername(s, TLSEXT_NAMETYPE_host_name);
 const tls_ext_ctx_cb *cbinfo = (tls_ext_ctx_cb *) arg;
 int rc;
+int old_pool = store_pool;
 
 if (!servername)
   return SSL_TLSEXT_ERR_OK;
 
-DEBUG(D_tls) debug_printf("TLS SNI: %s%s\n", servername,
+DEBUG(D_tls) debug_printf("Received TLS SNI \"%s\"%s\n", servername,
     reexpand_tls_files_for_sni ? "" : " (unused for certificate selection)");
 
 /* Make the extension value available for expansion */
-tls_sni = servername;
+store_pool = POOL_PERM;
+tls_sni = string_copy(US servername);
+store_pool = old_pool;
 
 if (!reexpand_tls_files_for_sni)
   return SSL_TLSEXT_ERR_OK;
@@ -550,10 +553,13 @@ if (rc != OK) return rc;
 
 /* If we need to handle SNI, do so */
 #if OPENSSL_VERSION_NUMBER >= 0x0090806fL && !defined(OPENSSL_NO_TLSEXT)
-/* We always do this, so that $tls_sni is available even if not used in
-tls_certificate */
-SSL_CTX_set_tlsext_servername_callback(ctx, tls_servername_cb);
-SSL_CTX_set_tlsext_servername_arg(ctx, cbinfo);
+if (host == NULL)
+  {
+  /* We always do this, so that $tls_sni is available even if not used in
+  tls_certificate */
+  SSL_CTX_set_tlsext_servername_callback(ctx, tls_servername_cb);
+  SSL_CTX_set_tlsext_servername_arg(ctx, cbinfo);
+  }
 #endif
 
 /* Set up the RSA callback */
@@ -944,6 +950,7 @@ Argument:
   dhparam          DH parameter file
   certificate      certificate file
   privatekey       private key file
+  sni              TLS SNI to send to remote host
   verify_certs     file for certificate verify
   crl              file containing CRL
   require_ciphers  list of allowed ciphers
@@ -961,7 +968,8 @@ Returns:           OK on success
 
 int
 tls_client_start(int fd, host_item *host, address_item *addr, uschar *dhparam,
-  uschar *certificate, uschar *privatekey, uschar *verify_certs, uschar *crl,
+  uschar *certificate, uschar *privatekey, uschar *sni,
+  uschar *verify_certs, uschar *crl,
   uschar *require_ciphers, uschar *require_mac, uschar *require_kx,
   uschar *require_proto, int timeout)
 {
@@ -1000,6 +1008,19 @@ SSL_set_session_id_context(ssl, sid_ctx, Ustrlen(sid_ctx));
 SSL_set_fd(ssl, fd);
 SSL_set_connect_state(ssl);
 
+if (sni)
+  {
+  if (!expand_check(sni, US"tls_sni", &tls_sni))
+    return FAIL;
+  if (!Ustrlen(tls_sni))
+    tls_sni = NULL;
+  else
+    {
+    DEBUG(D_tls) debug_printf("Setting TLS SNI \"%s\"\n", tls_sni);
+    SSL_set_tlsext_host_name(ssl, tls_sni);
+    }
+  }
+
 /* There doesn't seem to be a built-in timeout on connection. */
 
 DEBUG(D_tls) debug_printf("Calling SSL_connect\n");
@@ -1078,8 +1099,10 @@ if (ssl_xfer_buffer_lwm >= ssl_xfer_buffer_hwm)
     SSL_free(ssl);
     ssl = NULL;
     tls_active = -1;
+    tls_bits = 0;
     tls_cipher = NULL;
     tls_peerdn = NULL;
+    tls_sni = NULL;
 
     return smtp_getc();
     }
index c571d87..b1fedd2 100644 (file)
@@ -128,8 +128,10 @@ optionlist smtp_transport_options[] = {
       (void *)offsetof(smtp_transport_options_block, tls_crl) },
   { "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,
@@ -191,7 +193,8 @@ smtp_transport_options_block smtp_transport_option_defaults = {
   NULL,                /* gnutls_require_mac */
   NULL,                /* gnutls_require_proto */
   NULL,                /* tls_verify_certificates */
-  TRUE                 /* tls_tempfail_tryclear */
+  TRUE,                /* tls_tempfail_tryclear */
+  NULL                 /* tls_sni */
 #endif
 #ifndef DISABLE_DKIM
  ,NULL,                /* dkim_canon */
@@ -889,8 +892,10 @@ outblock.authenticating = FALSE;
 
 /* Reset the parameters of a TLS session. */
 
+tls_bits = 0;
 tls_cipher = NULL;
 tls_peerdn = NULL;
+tls_sni = NULL;
 
 /* 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
@@ -1122,6 +1127,7 @@ 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,
index a2ea4ff..605be48 100644 (file)
@@ -54,6 +54,7 @@ typedef struct {
   uschar *gnutls_require_proto;
   uschar *tls_verify_certificates;
   BOOL    tls_tempfail_tryclear;
+  uschar *tls_sni;
   #endif
   #ifndef DISABLE_DKIM
   uschar *dkim_domain;