From 3f0945ffae8acee547d11ae53d38fbdf9a2cc81f Mon Sep 17 00:00:00 2001 From: Phil Pennock Date: Fri, 4 May 2012 08:27:09 -0700 Subject: [PATCH] More tls_sni support: outbound, logging. 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). --- doc/doc-docbook/spec.xfpt | 36 ++++++++++++++++++++++++++++++++++-- doc/doc-txt/ChangeLog | 2 ++ doc/doc-txt/NewStuff | 7 +++++++ doc/doc-txt/OptionLists.txt | 1 + src/src/exipick.src | 6 ++++++ src/src/functions.h | 2 +- src/src/globals.c | 3 ++- src/src/local_scan.h | 2 +- src/src/macros.h | 3 ++- src/src/receive.c | 5 +++++ src/src/smtp_in.c | 5 +++++ src/src/string.c | 2 +- src/src/tls-gnu.c | 6 ++++-- src/src/tls-openssl.c | 37 ++++++++++++++++++++++++++++++------- src/src/transports/smtp.c | 10 ++++++++-- src/src/transports/smtp.h | 1 + 16 files changed, 110 insertions(+), 18 deletions(-) diff --git a/doc/doc-docbook/spec.xfpt b/doc/doc-docbook/spec.xfpt index 32e24ca80..ea4e040e1 100644 --- a/doc/doc-docbook/spec.xfpt +++ b/doc/doc-docbook/spec.xfpt @@ -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 &<>& 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. diff --git a/doc/doc-txt/ChangeLog b/doc/doc-txt/ChangeLog index 4ad79c28e..55cde6dcf 100644 --- a/doc/doc-txt/ChangeLog +++ b/doc/doc-txt/ChangeLog @@ -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 diff --git a/doc/doc-txt/NewStuff b/doc/doc-txt/NewStuff index b788b45dc..2872d241f 100644 --- a/doc/doc-txt/NewStuff +++ b/doc/doc-txt/NewStuff @@ -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. diff --git a/doc/doc-txt/OptionLists.txt b/doc/doc-txt/OptionLists.txt index b10f3f1aa..52a24b198 100644 --- a/doc/doc-txt/OptionLists.txt +++ b/doc/doc-txt/OptionLists.txt @@ -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 diff --git a/src/src/exipick.src b/src/src/exipick.src index 811092dc1..ed3b66154 100644 --- a/src/src/exipick.src +++ b/src/src/exipick.src @@ -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. diff --git a/src/src/functions.h b/src/src/functions.h index f1af42ee5..220235235 100644 --- a/src/src/functions.h +++ b/src/src/functions.h @@ -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); diff --git a/src/src/globals.c b/src/src/globals.c index 7985cd3f0..f11c7c2db 100644 --- a/src/src/globals.c +++ b/src/src/globals.c @@ -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 } }; diff --git a/src/src/local_scan.h b/src/src/local_scan.h index 25b194407..aedfc9f92 100644 --- a/src/src/local_scan.h +++ b/src/src/local_scan.h @@ -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); diff --git a/src/src/macros.h b/src/src/macros.h index c1c4cc33f..9b41226e5 100644 --- a/src/src/macros.h +++ b/src/src/macros.h @@ -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 | \ diff --git a/src/src/receive.c b/src/src/receive.c index 71052657c..aaaf64ce6 100644 --- a/src/src/receive.c +++ b/src/src/receive.c @@ -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) diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c index 23bc5315e..d1c10f00f 100644 --- a/src/src/smtp_in.c +++ b/src/src/smtp_in.c @@ -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)? diff --git a/src/src/string.c b/src/src/string.c index 0a321ee1f..3fea7c048 100644 --- a/src/src/string.c +++ b/src/src/string.c @@ -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); diff --git a/src/src/tls-gnu.c b/src/src/tls-gnu.c index 2f952e47b..7e87dded0 100644 --- a/src/src/tls-gnu.c +++ b/src/src/tls-gnu.c @@ -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; diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c index 8cc2457e5..e609670ee 100644 --- a/src/src/tls-openssl.c +++ b/src/src/tls-openssl.c @@ -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(); } diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c index c571d874c..b1fedd2d4 100644 --- a/src/src/transports/smtp.c +++ b/src/src/transports/smtp.c @@ -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, diff --git a/src/src/transports/smtp.h b/src/src/transports/smtp.h index a2ea4ff0a..605be4800 100644 --- a/src/src/transports/smtp.h +++ b/src/src/transports/smtp.h @@ -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; -- 2.30.2