TLS: ALPN options
authorJeremy Harris <jgh146exb@wizmail.org>
Sat, 17 Jul 2021 23:15:01 +0000 (00:15 +0100)
committerJeremy Harris <jgh146exb@wizmail.org>
Sun, 18 Jul 2021 12:23:11 +0000 (13:23 +0100)
22 files changed:
doc/doc-docbook/spec.xfpt
doc/doc-txt/NewStuff
doc/doc-txt/OptionLists.txt
src/src/globals.c
src/src/globals.h
src/src/readconf.c
src/src/tls-gnu.c
src/src/tls-openssl.c
src/src/tls.c
src/src/transports/smtp.c
src/src/transports/smtp.h
test/confs/1115 [new file with mode: 0644]
test/confs/2038 [new symlink]
test/confs/2138 [new symlink]
test/log/1115 [new file with mode: 0644]
test/log/2038 [new file with mode: 0644]
test/log/2138 [new file with mode: 0644]
test/runtest
test/scripts/1100-Basic-TLS/1115 [new file with mode: 0644]
test/scripts/2000-GnuTLS/2038 [new file with mode: 0644]
test/scripts/2100-OpenSSL/2138 [new file with mode: 0644]
test/stdout/0572

index 3384d60d940c92827e2832801d3ba0361adecd9c..2687f60481431705e2e30fe95d37f059c19f072f 100644 (file)
@@ -14829,8 +14829,10 @@ listed in more than one group.
 .table2
 .row &%gnutls_compat_mode%&          "use GnuTLS compatibility mode"
 .row &%gnutls_allow_auto_pkcs11%&    "allow GnuTLS to autoload PKCS11 modules"
+.row &%hosts_require_alpn%&          "mandatory ALPN"
 .row &%openssl_options%&             "adjust OpenSSL compatibility options"
 .row &%tls_advertise_hosts%&         "advertise TLS to these hosts"
+.row &%tls_alpn%&                   "acceptable protocol names"
 .row &%tls_certificate%&             "location of server certificate"
 .row &%tls_crl%&                     "certificate revocation list"
 .row &%tls_dh_max_bits%&             "clamp D-H bit count suggestion"
@@ -16331,6 +16333,20 @@ hosts_connection_nolog = :
 If the &%smtp_connection%& log selector is not set, this option has no effect.
 
 
+.new
+.option hosts_require_alpn main "host list&!!" unset
+.cindex ALPN "require negotiation in server"
+.cindex TLS ALPN
+.cindex TLS "Application Layer Protocol Names"
+If the TLS library supports ALPN
+then a successful negotiation of ALPN will be required for any client
+matching the list, for TLS to be used.
+See also the &%tls_alpn%& option.
+
+&*Note*&: prevention of fallback to in-clear connection is not
+managed by this option, and should be done separately.
+.wen
+
 
 .option hosts_proxy main "host list&!!" unset
 .cindex proxy "proxy protocol"
@@ -18357,6 +18373,19 @@ using the &%tls_certificate%& option.  If TLS support for incoming connections
 is not required the &%tls_advertise_hosts%& option should be set empty.
 
 
+.new
+.option tls_alpn main "string list&!!" "smtp : esmtp"
+.cindex TLS "Application Layer Protocol Names"
+.cindex TLS ALPN
+.cindex ALPN "set acceptable names for server"
+If this option is set,
+the TLS library supports ALPN,
+and the client offers either more than
+ALPN name or a name which does not match the list,
+the TLS connection is declined.
+.wen
+
+
 .option tls_certificate main "string list&!!" unset
 .cindex "TLS" "server certificate; location of"
 .cindex "certificate" "server, location of"
@@ -25657,6 +25686,20 @@ Exim will request a Certificate Status on a
 TLS session for any host that matches this list.
 &%tls_verify_certificates%& should also be set for the transport.
 
+.new
+.option hosts_require_alpn smtp "host list&!!" unset
+.cindex ALPN "require negotiation in client"
+.cindex TLS ALPN
+.cindex TLS "Application Layer Protocol Names"
+If the TLS library supports ALPN
+then a successful negotiation of ALPN will be required for any host
+matching the list, for TLS to be used.
+See also the &%tls_alpn%& option.
+
+&*Note*&: prevention of fallback to in-clear connection is not
+managed by this option; see &%hosts_require_tls%&.
+.wen
+
 .option hosts_require_dane smtp "host list&!!" unset
 .cindex DANE "transport options"
 .cindex DANE "requiring for certain servers"
@@ -25940,6 +25983,21 @@ This option enables use of SOCKS proxies for connections made by the
 transport.  For details see section &<<SECTproxySOCKS>>&.
 
 
+.new
+.option tls_alpn smtp string&!! unset
+.cindex TLS "Application Layer Protocol Names"
+.cindex TLS ALPN
+.cindex ALPN "set name in client"
+If this option is set
+and the TLS library supports ALPN,
+the value given is used.
+
+As of writing no value has been standardised for email use.
+The authors suggest using &"smtp"&.
+.wen
+
+
+
 .option tls_certificate smtp string&!! unset
 .cindex "TLS" "client certificate, location of"
 .cindex "certificate" "client, location of"
@@ -29870,6 +29928,34 @@ When Exim is built against GnuTLS, SNI support is available as of GnuTLS
 0.5.10.  (Its presence predates the current API which Exim uses, so if Exim
 built, then you have SNI support).
 
+.new
+.cindex TLS ALPN
+.cindex ALPN "general information"
+.cindex TLS "Application Layer Protocol Names"
+There is a TLS feature related to SNI
+called Application Layer Protocol Name (ALPN).
+This is intended to declare, or select, what protocol layer will be using a TLS
+connection.
+The client for the connection proposes a set of protocol names, and
+the server responds with a selected one.
+It is not, as of 2021, commonly used for SMTP connections.
+However, to guard against misirected or malicious use of web clients
+(which often do use ALPN) against MTA ports, Exim by default check that
+there is no incompatible ALPN specified by a client for a TLS connection.
+If there is, the connection is rejected.
+
+As a client Exim does not supply ALPN by default.
+The behaviour of both client and server can be configured using the options
+&%tls_alpn%& and &%hosts_require_alpn%&.
+There are no variables providing observability.
+Some feature-specific logging may appear on denied connections, but this
+depends on the behavious of the peer
+(not all peers can send a feature-specific TLS Alert).
+
+This feature is available when Exim is built with
+OpenSSL 1.1.0 or later or GnuTLS 3.2.0 or later.
+.wen
+
 
 
 .section "Multiple messages on the same encrypted TCP/IP connection" &&&
index 60bb3762e3be09c862bebb5e78488d4c7f603a53..6f3d4b31c3380c4ef208ecc702d840f51a1ca5cc 100644 (file)
@@ -57,7 +57,9 @@ Version 4.95
 16. Main option "hosts_require_helo", requiring HELO or EHLO before MAIL.
 
 17. A main config option "allow_insecure_tainted_data" allows to turn
-    taint errors into warnings.
+
+18. TLS ALPN handling.  By default, refuse TLS connections that try to specify
+    a non-smtp (eg. http) use.  Options for customising.
 
 
 Version 4.94
index 366a74b18bb9be4f4659a995736d2ea30292244e..4314c534b2ec7625f4c5645db8e43dc76a2b4ef1 100644 (file)
@@ -314,6 +314,8 @@ hosts_pipe_connect               host_list       unset         smtp              4.93 if experimental
 hosts_randomize                      boolean         false         manualroute       4.00
                                                      false         smtp              3.14
 hosts_require_auth                   host list       unset         smtp              4.00
+hosts_require_alpn                   host list       unset         main              4.95
+                                                                  smtp              4.95
 hosts_require_dane                   host list       unset         smtp              4.91 (4.85 experimental)
 hosts_require_helo                  host list       "*"           main              4.95
 hosts_require_ocsp                   host list       unset         smtp              4.82 if experimental_ocsp
@@ -598,7 +600,8 @@ timeout_defer                        boolean         false         pipe
 timeout_frozen_after                 time            0s            main              3.20
 timezone                             string          +             main              3.15
 tls_advertise_hosts                  host list       *             main              3.20
-tls_advertise_requiretls             host list       *             main              4.92 if experimental_requiretls
+tls_alpn                            string*         unset         main              4.95
+                                                                  smtp              4.95
 tls_certificate                      string*         unset         main              3.20
                                                      unset         smtp              3.20
 tls_dh_max_bits                      integer         2236          main              4.80
index 9e68aaca81a065f0a178e9c63f5aad380fb894f2..1e12bcb92f709681696e1b8ef15bec7428c583ff 100644 (file)
@@ -124,9 +124,11 @@ uschar *dsn_advertise_hosts    = NULL;
 #ifndef DISABLE_TLS
 BOOL    gnutls_compat_mode     = FALSE;
 BOOL    gnutls_allow_auto_pkcs11 = FALSE;
+uschar *hosts_require_alpn     = NULL;
 uschar *openssl_options        = NULL;
 const pcre *regex_STARTTLS     = NULL;
 uschar *tls_advertise_hosts    = US"*";
+uschar *tls_alpn              = US"smtp:esmtp";
 uschar *tls_certificate        = NULL;
 uschar *tls_crl                = NULL;
 /* This default matches NSS DH_MAX_P_BITS value at current time (2012), because
index 657e6c70631381cde5f14654db2598a1143304f8..d5d93148fc06385d520cd28301a67440f9d9e3c2 100644 (file)
@@ -125,8 +125,10 @@ extern tls_support tls_out;
 #ifndef DISABLE_TLS
 extern BOOL    gnutls_compat_mode;     /* Less security, more compatibility */
 extern BOOL    gnutls_allow_auto_pkcs11; /* Let GnuTLS autoload PKCS11 modules */
+extern uschar *hosts_require_alpn;     /* Mandatory ALPN successful nogitiation */
 extern uschar *openssl_options;        /* OpenSSL compatibility options */
 extern const pcre *regex_STARTTLS;     /* For recognizing STARTTLS settings */
+extern uschar *tls_alpn;              /* ALPN names acceptable */
 extern uschar *tls_certificate;        /* Certificate file */
 extern uschar *tls_crl;                /* CRL File */
 extern int     tls_dh_max_bits;        /* don't accept higher lib suggestions */
index 01e85a329f9a1b2265c2b22f35e394bf6c7a90e8..a1eafb2ecccf93353c2fcd1719f0275dcb27ef76 100644 (file)
@@ -184,6 +184,7 @@ static optionlist optionlist_config[] = {
 #ifdef SUPPORT_PROXY
   { "hosts_proxy",              opt_stringptr,   {&hosts_proxy} },
 #endif
+  { "hosts_require_alpn",       opt_stringptr,   {&hosts_require_alpn} },
   { "hosts_require_helo",       opt_stringptr,   {&hosts_require_helo} },
   { "hosts_treat_as_local",     opt_stringptr,   {&hosts_treat_as_local} },
 #ifdef LOOKUP_IBASE
@@ -378,6 +379,7 @@ static optionlist optionlist_config[] = {
   { "timezone",                 opt_stringptr,   {&timezone_string} },
   { "tls_advertise_hosts",      opt_stringptr,   {&tls_advertise_hosts} },
 #ifndef DISABLE_TLS
+  { "tls_alpn",                        opt_stringptr,   {&tls_alpn} },
   { "tls_certificate",          opt_stringptr,   {&tls_certificate} },
   { "tls_crl",                  opt_stringptr,   {&tls_crl} },
   { "tls_dh_max_bits",          opt_int,         {&tls_dh_max_bits} },
index 2d7041f3e4077e41a4cee7e8f0061c2b3266f0cb..bc16ec03e5b9d88f22af2e7ec1370a8a38939e80 100644 (file)
@@ -285,7 +285,7 @@ static BOOL exim_testharness_disable_ocsp_validity_check = FALSE;
 #endif
 
 #ifdef EXIM_HAVE_ALPN
-static BOOL server_seen_alpn = FALSE;
+static int server_seen_alpn = -1;      /* count of names */
 #endif
 #ifdef EXIM_HAVE_TLS_RESUME
 static gnutls_datum_t server_sessticket_key;
@@ -388,10 +388,15 @@ return host ? FAIL : DEFER;
 
 
 static int
-tls_error_gnu(const uschar *prefix, int err, const host_item *host,
+tls_error_gnu(exim_gnutls_state_st * state, const uschar *prefix, int err,
   uschar ** errstr)
 {
-return tls_error(prefix, US gnutls_strerror(err), host, errstr);
+return tls_error(prefix,
+  state && err == GNUTLS_E_FATAL_ALERT_RECEIVED
+  ? US gnutls_alert_get_name(gnutls_alert_get(state->session))
+  : US gnutls_strerror(err),
+  state ? state->host : NULL,
+  errstr);
 }
 
 static int
@@ -451,12 +456,12 @@ To prevent this, we init PKCS11 first, which is the documented approach. */
 
 if (!gnutls_allow_auto_pkcs11)
   if ((rc = gnutls_pkcs11_init(GNUTLS_PKCS11_FLAG_MANUAL, NULL)))
-    return tls_error_gnu(US"gnutls_pkcs11_init", rc, NULL, errstr);
+    return tls_error_gnu(NULL, US"gnutls_pkcs11_init", rc, errstr);
 #endif
 
 #ifndef GNUTLS_AUTO_GLOBAL_INIT
 if ((rc = gnutls_global_init()))
-  return tls_error_gnu(US"gnutls_global_init", rc, NULL, errstr);
+  return tls_error_gnu(UNULL, S"gnutls_global_init", rc, errstr);
 #endif
 
 #if EXIM_GNUTLS_LIBRARY_LOG_LEVEL >= 0
@@ -715,7 +720,7 @@ host_item *host = NULL; /* dummy for macros */
 DEBUG(D_tls) debug_printf("Initialising GnuTLS server params\n");
 
 if ((rc = gnutls_dh_params_init(&dh_server_params)))
-  return tls_error_gnu(US"gnutls_dh_params_init", rc, host, errstr);
+  return tls_error_gnu(NULL, US"gnutls_dh_params_init", rc, errstr);
 
 if (!expand_check(tls_dhparam, US"tls_dhparam", &exp_tls_dhparam, errstr))
   return DEFER;
@@ -745,7 +750,7 @@ else
 if (m.data)
   {
   if ((rc = gnutls_dh_params_import_pkcs3(dh_server_params, &m, GNUTLS_X509_FMT_PEM)))
-    return tls_error_gnu(US"gnutls_dh_params_import_pkcs3", rc, host, errstr);
+    return tls_error_gnu(NULL, US"gnutls_dh_params_import_pkcs3", rc, errstr);
   DEBUG(D_tls) debug_printf("Loaded fixed standard D-H parameters\n");
   return OK;
   }
@@ -829,7 +834,7 @@ if ((fd = Uopen(filename, O_RDONLY, 0)) >= 0)
   rc = gnutls_dh_params_import_pkcs3(dh_server_params, &m, GNUTLS_X509_FMT_PEM);
   store_free(m.data);
   if (rc)
-    return tls_error_gnu(US"gnutls_dh_params_import_pkcs3", rc, host, errstr);
+    return tls_error_gnu(NULL, US"gnutls_dh_params_import_pkcs3", rc, errstr);
   DEBUG(D_tls) debug_printf("read D-H parameters from file \"%s\"\n", filename);
   }
 
@@ -884,7 +889,7 @@ if (rc < 0)
     debug_printf("requesting generation of %d bit Diffie-Hellman prime ...\n",
         dh_bits_gen);
   if ((rc = gnutls_dh_params_generate2(dh_server_params, dh_bits_gen)))
-    return tls_error_gnu(US"gnutls_dh_params_generate2", rc, host, errstr);
+    return tls_error_gnu(NULL, US"gnutls_dh_params_generate2", rc, errstr);
 
   /* gnutls_dh_params_export_pkcs3() will tell us the exact size, every time,
   and I confirmed that a NULL call to get the size first is how the GnuTLS
@@ -895,8 +900,8 @@ if (rc < 0)
   if (  (rc = gnutls_dh_params_export_pkcs3(dh_server_params,
                GNUTLS_X509_FMT_PEM, m.data, &sz))
      && rc != GNUTLS_E_SHORT_MEMORY_BUFFER)
-    return tls_error_gnu(US"gnutls_dh_params_export_pkcs3(NULL) sizing",
-             rc, host, errstr);
+    return tls_error_gnu(NULL, US"gnutls_dh_params_export_pkcs3(NULL) sizing",
+             rc, errstr);
   m.size = sz;
   if (!(m.data = store_malloc(m.size)))
     return tls_error_sys(US"memory allocation failed", errno, NULL, errstr);
@@ -906,7 +911,7 @@ if (rc < 0)
       m.data, &sz)))
     {
     store_free(m.data);
-    return tls_error_gnu(US"gnutls_dh_params_export_pkcs3() real", rc, host, errstr);
+    return tls_error_gnu(NULL, US"gnutls_dh_params_export_pkcs3() real", rc, errstr);
     }
   m.size = sz; /* shrink by 1, probably */
 
@@ -1011,7 +1016,7 @@ out:
   return rc;
 
 err:
-  rc = tls_error_gnu(where, rc, NULL, errstr);
+  rc = tls_error_gnu(state, where, rc, errstr);
   goto out;
 }
 
@@ -1032,9 +1037,9 @@ tls_add_certfile(exim_gnutls_state_st * state, const host_item * host,
 int rc = gnutls_certificate_set_x509_key_file(state->lib_state.x509_cred,
     CCS certfile, CCS keyfile, GNUTLS_X509_FMT_PEM);
 if (rc < 0)
-  return tls_error_gnu(
+  return tls_error_gnu(state,
     string_sprintf("cert/key setup: cert=%s key=%s", certfile, keyfile),
-    rc, host, errstr);
+    rc, errstr);
 return -rc;
 }
 
@@ -1069,19 +1074,34 @@ return 0;
 /* Make a note that we saw a status-request */
 static int
 tls_server_clienthello_ext(void * ctx, unsigned tls_id,
-  const unsigned char *data, unsigned size)
+  const uschar * data, unsigned size)
 {
 /* https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml */
 switch (tls_id)
   {
-  case 5:      /* status_request */
+  case 5:      /* Status Request */
     DEBUG(D_tls) debug_printf("Seen status_request extension from client\n");
     tls_in.ocsp = OCSP_NOT_RESP;
     break;
 #ifdef EXIM_HAVE_ALPN
   case 16:     /* Application Layer Protocol Notification */
-    DEBUG(D_tls) debug_printf("Seen ALPN extension from client\n");
-    server_seen_alpn = TRUE;
+    /* The format of "data" here doesn't seem to be documented, but appears
+    to be a 2-byte field with a (redundant, given the "size" arg) total length
+    then a sequence of one-byte size then string (not nul-term) names.  The
+    latter is as described in OpenSSL documentation. */
+
+    DEBUG(D_tls) debug_printf("Seen ALPN extension from client (s=%u):", size);
+    for (const uschar * s = data+2; s-data < size-1; s += *s + 1)
+      {
+      server_seen_alpn++;
+      DEBUG(D_tls) debug_printf(" '%.*s'", (int)*s, s+1);
+      }
+    DEBUG(D_tls) debug_printf("\n");
+    if (server_seen_alpn > 1)
+      {
+      DEBUG(D_tls) debug_printf("TLS: too many ALPNs presented in handshake\n");
+      return GNUTLS_E_NO_APPLICATION_PROTOCOL;
+      }
     break;
 #endif
   }
@@ -1279,9 +1299,9 @@ while (cfile = string_nextinlist(&clist, &csep, NULL, 0))
        if  ((rc = gnutls_certificate_set_ocsp_status_request_file2(
                  state->lib_state.x509_cred, CCS ofile, gnutls_cert_index,
                  ocsp_fmt)) < 0)
-         return tls_error_gnu(
+         return tls_error_gnu(state,
                  US"gnutls_certificate_set_ocsp_status_request_file2",
-                 rc, NULL, errstr);
+                 rc, errstr);
        DEBUG(D_tls)
          debug_printf(" %d response%s loaded\n", rc, rc>1 ? "s":"");
 
@@ -1299,9 +1319,9 @@ while (cfile = string_nextinlist(&clist, &csep, NULL, 0))
        if ((rc = gnutls_certificate_set_ocsp_status_request_function2(
                     state->lib_state.x509_cred, gnutls_cert_index,
                     server_ocsp_stapling_cb, ofile)))
-           return tls_error_gnu(
+           return tls_error_gnu(state,
                  US"gnutls_certificate_set_ocsp_status_request_function2",
-                 rc, NULL, errstr);
+                 rc, errstr);
        else
 #  endif
          {
@@ -1403,7 +1423,7 @@ else
   }
 
 if (cert_count < 0)
-  return tls_error_gnu(US"setting certificate trust", cert_count, host, errstr);
+  return tls_error_gnu(state, US"setting certificate trust", cert_count, errstr);
 DEBUG(D_tls)
   debug_printf("Added %d certificate authorities\n", cert_count);
 
@@ -1418,8 +1438,8 @@ int cert_count;
 DEBUG(D_tls) debug_printf("loading CRL file = %s\n", crl);
 if ((cert_count = gnutls_certificate_set_x509_crl_file(state->lib_state.x509_cred,
     CS crl, GNUTLS_X509_FMT_PEM)) < 0)
-  return tls_error_gnu(US"gnutls_certificate_set_x509_crl_file",
-           cert_count, state->host, errstr);
+  return tls_error_gnu(state, US"gnutls_certificate_set_x509_crl_file",
+           cert_count, errstr);
 
 DEBUG(D_tls) debug_printf("Processed %d CRLs\n", cert_count);
 return OK;
@@ -1730,8 +1750,8 @@ if (!state->lib_state.x509_cred)
   {
   if ((rc = gnutls_certificate_allocate_credentials(
        (gnutls_certificate_credentials_t *) &state->lib_state.x509_cred)))
-    return tls_error_gnu(US"gnutls_certificate_allocate_credentials",
-           rc, host, errstr);
+    return tls_error_gnu(state, US"gnutls_certificate_allocate_credentials",
+           rc, errstr);
   creds_basic_init(state->lib_state.x509_cred, !host);
   }
 
@@ -1935,7 +1955,7 @@ if (!state->host)
 
 if ((rc = gnutls_credentials_set(state->session,
            GNUTLS_CRD_CERTIFICATE, state->lib_state.x509_cred)))
-  return tls_error_gnu(US"gnutls_credentials_set", rc, host, errstr);
+  return tls_error_gnu(state, US"gnutls_credentials_set", rc, errstr);
 
 return OK;
 }
@@ -2015,7 +2035,7 @@ else
   state->tls_crl =             tls_crl;
   }
 if (rc)
-  return tls_error_gnu(US"gnutls_init", rc, host, errstr);
+  return tls_error_gnu(state, US"gnutls_init", rc, errstr);
 
 state->tls_require_ciphers =   require_ciphers;
 state->host = host;
@@ -2044,7 +2064,7 @@ if (host)
     sz = Ustrlen(state->tlsp->sni);
     if ((rc = gnutls_server_name_set(state->session,
          GNUTLS_NAME_DNS, state->tlsp->sni, sz)))
-      return tls_error_gnu(US"gnutls_server_name_set", rc, host, errstr);
+      return tls_error_gnu(state, US"gnutls_server_name_set", rc, errstr);
     }
   }
 else if (state->tls_sni)
@@ -2074,10 +2094,10 @@ if (!state->lib_state.pri_string)
     }
 
   if ((rc = creds_load_pristring(state, p, &errpos)))
-    return tls_error_gnu(string_sprintf(
+    return tls_error_gnu(state, string_sprintf(
                        "gnutls_priority_init(%s) failed at offset %ld, \"%.6s..\"",
                        p, errpos - CS p, errpos),
-                   rc, host, errstr);
+                   rc, errstr);
   }
 else
   {
@@ -2087,7 +2107,7 @@ else
 
 
 if ((rc = gnutls_priority_set(state->session, state->lib_state.pri_cache)))
-  return tls_error_gnu(US"gnutls_priority_set", rc, host, errstr);
+  return tls_error_gnu(state, US"gnutls_priority_set", rc, errstr);
 
 /* This also sets the server ticket expiration time to the same, and
 the STEK rotation time to 3x. */
@@ -2285,7 +2305,7 @@ if ((ct = gnutls_certificate_type_get(session)) != GNUTLS_CRT_X509)
       DEBUG(D_tls) debug_printf("TLS: peer cert problem: %s: %s\n", \
        (Label), gnutls_strerror(rc)); \
       if (state->verify_requirement >= VERIFY_REQUIRED) \
-       return tls_error_gnu((Label), rc, state->host, errstr); \
+       return tls_error_gnu(state, (Label), rc, errstr); \
       return OK; \
       } \
     } while (0)
@@ -2808,26 +2828,70 @@ if (gnutls_session_is_resumed(state->session))
   DEBUG(D_tls) debug_printf("Session resumed\n");
   }
 }
-#endif
+#endif /* EXIM_HAVE_TLS_RESUME */
 
 
 #ifdef EXIM_HAVE_ALPN
+/* Expand and convert an Exim list to a gnutls_datum list.  False return for fail.
+NULL plist return for silent no-ALPN.
+*/
+
+static BOOL
+tls_alpn_plist(const uschar * tls_alpn, const gnutls_datum_t ** plist, unsigned * plen,
+  uschar ** errstr)
+{
+uschar * exp_alpn;
+
+if (!expand_check(tls_alpn, US"tls_alpn", &exp_alpn, errstr))
+  return FALSE;
+
+if (!exp_alpn)
+  {
+  DEBUG(D_tls) debug_printf("Setting TLS ALPN forced to fail, not sending\n");
+  *plist = NULL;
+  }
+else
+  {
+  const uschar * list = exp_alpn;
+  int sep = 0;
+  unsigned cnt = 0;
+  gnutls_datum_t * p;
+  uschar * s;
+
+  while (string_nextinlist(&list, &sep, NULL, 0)) cnt++;
+
+  p = store_get(sizeof(gnutls_datum_t) * cnt, is_tainted(exp_alpn));
+  list = exp_alpn;
+  for (int i = 0; s = string_nextinlist(&list, &sep, NULL, 0); i++)
+    { p[i].data = s; p[i].size = Ustrlen(s); }
+  *plist = (*plen = cnt) ? p : NULL;
+  }
+return TRUE;
+}
+
 static void
-tls_server_set_acceptable_alpns(exim_gnutls_state_st * state)
+tls_server_set_acceptable_alpns(exim_gnutls_state_st * state, uschar ** errstr)
 {
 int rc;
-gnutls_datum_t protocols[2] = {[0] = {.data = US"smtp", .size = 4},
-                              [1] = {.data = US"esmtp", .size = 5}};
+const gnutls_datum_t * plist;
+unsigned plen;
 
-/* Set non-mandatory set of protocol names */
-if (!(rc = gnutls_alpn_set_protocols(state->session, protocols, 2, 0)))
-  gnutls_handshake_set_hook_function(state->session,
-    GNUTLS_HANDSHAKE_ANY, GNUTLS_HOOK_POST, tls_server_hook_cb);
-else
-  DEBUG(D_tls)
-    debug_printf("setting alpn protocols: %s\n", US gnutls_strerror(rc));
+if (tls_alpn_plist(tls_alpn, &plist, &plen, errstr) && plist)
+  {
+  /* This seems to be only mandatory if the client sends an ALPN extension;
+  not trying ALPN is ok. Need to decide how to support server-side must-alpn. */
+
+  server_seen_alpn = 0;
+  if (!(rc = gnutls_alpn_set_protocols(state->session, plist, plen,
+                                       GNUTLS_ALPN_MANDATORY)))
+    gnutls_handshake_set_hook_function(state->session,
+      GNUTLS_HANDSHAKE_ANY, GNUTLS_HOOK_POST, tls_server_hook_cb);
+  else
+    DEBUG(D_tls)
+      debug_printf("setting alpn protocols: %s\n", US gnutls_strerror(rc));
+  }
 }
-#endif
+#endif /* EXIM_HAVE_ALPN */
 
 /* ------------------------------------------------------------------------ */
 /* Exported functions */
@@ -2886,7 +2950,7 @@ DEBUG(D_tls) debug_printf("initialising GnuTLS as a server\n");
   }
 
 #ifdef EXIM_HAVE_ALPN
-tls_server_set_acceptable_alpns(state);
+tls_server_set_acceptable_alpns(state, errstr);
 #endif
 
 #ifdef EXIM_HAVE_TLS_RESUME
@@ -2977,7 +3041,7 @@ if (rc != GNUTLS_E_SUCCESS)
     }
   else
     {
-    tls_error_gnu(US"gnutls_handshake", rc, NULL, errstr);
+    tls_error_gnu(state, US"gnutls_handshake", rc, errstr);
     (void) gnutls_alert_send_appropriate(state->session, rc);
     gnutls_deinit(state->session);
     gnutls_certificate_free_credentials(state->lib_state.x509_cred);
@@ -3005,29 +3069,30 @@ tls_server_resume_posthandshake(state);
 DEBUG(D_tls) post_handshake_debug(state);
 
 #ifdef EXIM_HAVE_ALPN
-if (server_seen_alpn)
+if (server_seen_alpn > 0)
   {
-  /* The client offered ALPN.  We were set up with a nonmandatory list;
-  see what was negotiated.  We require a match now, given that something
-  was offered. */
-  gnutls_datum_t p = {.size = 0};
-  int rc = gnutls_alpn_get_selected_protocol(state->session, &p);
-  if (!rc || rc == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE)
-    {
-    if (p.size == 0)
-      {
-      *errstr = US"ALPN rejected";
-      return FAIL;
-      }
-    else
-      DEBUG(D_tls)
+  DEBUG(D_tls)
+    {          /* The client offered ALPN.  See what was negotiated. */
+    gnutls_datum_t p = {.size = 0};
+    int rc = gnutls_alpn_get_selected_protocol(state->session, &p);
+    if (!rc)
        debug_printf("ALPN negotiated: %.*s\n", (int)p.size, p.data);
-    }
-  else
-    DEBUG(D_tls)
-      debug_printf("getting alpn protocol: %s\n", US gnutls_strerror(rc));
+    else
+       debug_printf("getting alpn protocol: %s\n", US gnutls_strerror(rc));
 
+    }
   }
+else if (server_seen_alpn == 0)
+  if (verify_check_host(&hosts_require_alpn) == OK)
+    {
+    gnutls_alert_send(state->session, GNUTLS_AL_FATAL, GNUTLS_A_NO_APPLICATION_PROTOCOL);
+    tls_error(US"handshake", US"ALPN required but not negotiated", NULL, errstr);
+    return FAIL;
+    }
+  else
+    DEBUG(D_tls) debug_printf("TLS: no ALPN presented in handshake\n");
+else
+  DEBUG(D_tls) debug_printf("TLS: was not watching for ALPN\n");
 #endif
 
 /* Verify after the fact */
@@ -3367,6 +3432,28 @@ if (!cipher_list)
 #endif
   }
 
+if (ob->tls_alpn)
+#ifdef EXIM_HAVE_ALPN
+  {
+  const gnutls_datum_t * plist;
+  unsigned plen;
+
+  if (!tls_alpn_plist(ob->tls_alpn, &plist, &plen, errstr))
+    return FALSE;
+  if (plist)
+    if (gnutls_alpn_set_protocols(state->session, plist, plen, 0) != 0)
+      {
+      tls_error(US"alpn init", NULL, state->host, errstr);
+      return FALSE;
+      }
+    else
+      DEBUG(D_tls) debug_printf("Setting TLS ALPN '%s'\n", ob->tls_alpn);
+  }
+#else
+  log_write(0, LOG_MAIN, "ALPN unusable with this GnuTLS library version; ignoring \"%s\"\n",
+          ob->tls_alpn);
+#endif
+
   {
   int dh_min_bits = ob->tls_dh_min_bits;
   if (dh_min_bits < EXIM_CLIENT_DH_MIN_MIN_BITS)
@@ -3435,7 +3522,7 @@ if (request_ocsp)
   if ((rc = gnutls_ocsp_status_request_enable_client(state->session,
                    NULL, 0, NULL)) != OK)
     {
-    tls_error_gnu(US"cert-status-req", rc, state->host, errstr);
+    tls_error_gnu(state, US"cert-status-req", rc, errstr);
     return FALSE;
     }
   tlsp->ocsp = OCSP_NOT_RESP;
@@ -3477,7 +3564,7 @@ if (rc != GNUTLS_E_SUCCESS)
     tls_error(US"gnutls_handshake", US"timed out", state->host, errstr);
     }
   else
-    tls_error_gnu(US"gnutls_handshake", rc, state->host, errstr);
+    tls_error_gnu(state, US"gnutls_handshake", rc, errstr);
   return FALSE;
   }
 
@@ -3522,9 +3609,9 @@ if (request_ocsp)
        gnutls_free(printed.data);
        }
       else
-       (void) tls_error_gnu(US"ocsp decode", rc, state->host, errstr);
+       (void) tls_error_gnu(state, US"ocsp decode", rc, errstr);
     if (idx == 0 && rc)
-      (void) tls_error_gnu(US"ocsp decode", rc, state->host, errstr);
+      (void) tls_error_gnu(state, US"ocsp decode", rc, errstr);
     }
 
   if (gnutls_ocsp_status_request_is_checked(state->session, 0) == 0)
@@ -3546,6 +3633,24 @@ if (request_ocsp)
 tls_client_resume_posthandshake(state, tlsp, host);
 #endif
 
+#ifdef EXIM_HAVE_ALPN
+if (ob->tls_alpn)      /* We requested. See what was negotiated. */
+  {
+  gnutls_datum_t p = {.size = 0};
+
+  if (gnutls_alpn_get_selected_protocol(state->session, &p) == 0)
+    { DEBUG(D_tls) debug_printf("ALPN negotiated: '%.*s'\n", (int)p.size, p.data); }
+  else if (verify_check_given_host(CUSS &ob->hosts_require_alpn, host) == OK)
+    {
+    gnutls_alert_send(state->session, GNUTLS_AL_FATAL, GNUTLS_A_NO_APPLICATION_PROTOCOL);
+    tls_error(US"handshake", US"ALPN required but not negotiated", state->host, errstr);
+    return FALSE;
+    }
+  else
+    DEBUG(D_tls) debug_printf("No ALPN negotiated");
+  }
+#endif
+
 /* Sets various Exim expansion variables; may need to adjust for ACL callouts */
 
 extract_exim_vars_from_tls_state(state);
index d99de53d6fdf7f7c33424558426484ffabcacbbd..678288ca380a9a3163fabcf11fdda7ca2f01474d 100644 (file)
@@ -359,6 +359,9 @@ typedef struct {
 #ifdef EXIM_HAVE_OPENSSL_TLSEXT
 static SSL_CTX *server_sni = NULL;
 #endif
+#ifdef EXIM_HAVE_ALPN
+static BOOL server_seen_alpn = FALSE;
+#endif
 
 static char ssl_errstring[256];
 
@@ -2140,9 +2143,9 @@ bad: return SSL_TLSEXT_ERR_ALERT_FATAL;
 *        Callback to handle ALPN                 *
 *************************************************/
 
-/* SSL_CTX_set_alpn_select_cb() */
-/* Called on server when client offers ALPN, after the SNI callback.
-If set and not e?smtp then we dump the connection */
+/* Called on server if tls_alpn nonblank after expansion,
+when client offers ALPN, after the SNI callback.
+If set and not matching the list then we dump the connection */
 
 static int
 tls_server_alpn_cb(SSL *ssl, const uschar ** out, uschar * outlen,
@@ -2150,6 +2153,7 @@ tls_server_alpn_cb(SSL *ssl, const uschar ** out, uschar * outlen,
 {
 const exim_openssl_state_st * state = arg;
 
+server_seen_alpn = TRUE;
 DEBUG(D_tls)
   {
   debug_printf("Received TLS ALPN offer:");
@@ -2159,23 +2163,32 @@ DEBUG(D_tls)
     if (pos + 1 + siz > inlen) siz = inlen - pos - 1;
     debug_printf(" '%.*s'", siz, in + pos + 1);
     }
-  debug_printf("\n");
+  debug_printf(".  Our list: '%s'\n", tls_alpn);
   }
 
 /* Look for an acceptable ALPN */
+
 if (  inlen > 1                /* at least one name */
    && in[0]+1 == inlen /* filling the vector, so exactly one name */
-   && (  Ustrncmp(in+1, "smtp", in[0]) == 0
-      || Ustrncmp(in+1, "esmtp", in[0]) == 0
-   )  )
+   )
   {
-  *out = in;                   /* we checked for exactly one, so can just point to it */
-  *outlen = inlen;
-  return SSL_TLSEXT_ERR_OK;    /* use ALPN */
+  const uschar * list = tls_alpn;
+  int sep = 0;
+  for (uschar * name; name = string_nextinlist(&list, &sep, NULL, 0); )
+    if (Ustrncmp(in+1, name, in[0]) == 0)
+      {
+      *out = in;                       /* we checked for exactly one, so can just point to it */
+      *outlen = inlen;
+      return SSL_TLSEXT_ERR_OK;                /* use ALPN */
+      }
   }
 
-/* Reject unacceptable ALPN */
-/* This will be fatal to the TLS conn; would be nice to kill TCP also */
+/* More than one name from clilent, or name did not match our list. */
+
+/* This will be fatal to the TLS conn; would be nice to kill TCP also.
+Maybe as an option in future; for now leave control to the config (must-tls). */
+
+DEBUG(D_tls) debug_printf("TLS ALPN rejected\n");
 return SSL_TLSEXT_ERR_ALERT_FATAL;
 }
 #endif /* EXIM_HAVE_ALPN */
@@ -2649,8 +2662,20 @@ if (!host)               /* server */
   tls_certificate */
   SSL_CTX_set_tlsext_servername_callback(ctx, tls_servername_cb);
   SSL_CTX_set_tlsext_servername_arg(ctx, state);
+
 # ifdef EXIM_HAVE_ALPN
-  SSL_CTX_set_alpn_select_cb(ctx, tls_server_alpn_cb, state);
+  if (tls_alpn && *tls_alpn)
+    {
+    uschar * exp_alpn;
+    if (  expand_check(tls_alpn, US"tls_alpn", &exp_alpn, errstr)
+       && *exp_alpn && !isblank(*exp_alpn))
+      {
+      tls_alpn = exp_alpn;     /* subprocess so ok to overwrite */
+      SSL_CTX_set_alpn_select_cb(ctx, tls_server_alpn_cb, state);
+      }
+    else
+      tls_alpn = NULL;
+    }
 # endif
   }
 # ifndef DISABLE_OCSP
@@ -3226,6 +3251,30 @@ if (SSL_session_reused(ssl))
   }
 #endif
 
+#ifdef EXIM_HAVE_ALPN
+/* If require-alpn, check server_seen_alpn here.  Else abort TLS */
+if (!tls_alpn || !*tls_alpn)
+  { DEBUG(D_tls) debug_printf("TLS: was not watching for ALPN\n"); }
+else if (!server_seen_alpn)
+  if (verify_check_host(&hosts_require_alpn) == OK)
+    {
+    /* We'd like to send a definitive Alert but OpenSSL provides no facility */
+    SSL_shutdown(ssl);
+    tls_error(US"handshake", NULL, US"ALPN required but not negotiated", errstr);
+    return FAIL;
+    }
+  else
+    { DEBUG(D_tls) debug_printf("TLS: no ALPN presented in handshake\n"); }
+else DEBUG(D_tls)
+  {
+  const uschar * name;
+  unsigned len;
+  SSL_get0_alpn_selected(ssl, &name, &len);
+  debug_printf("ALPN negotiated: '%.*s'\n", (int)*name, name+1);
+  }
+#endif
+
+
 /* TLS has been set up. Record data for the connection,
 adjust the input functions to read via TLS, and initialize things. */
 
@@ -3593,6 +3642,47 @@ if (SSL_session_reused(exim_client_ctx->ssl))
 #endif /* !DISABLE_TLS_RESUME */
 
 
+#ifdef EXIM_HAVE_ALPN
+/* Expand and convert an Exim list to an ALPN list.  False return for fail.
+NULL plist return for silent no-ALPN.
+*/
+
+static BOOL
+tls_alpn_plist(const uschar * tls_alpn, const uschar ** plist, unsigned * plen,
+  uschar ** errstr)
+{
+uschar * exp_alpn;
+
+if (!expand_check(tls_alpn, US"tls_alpn", &exp_alpn, errstr))
+  return FALSE;
+
+if (!exp_alpn)
+  {
+  DEBUG(D_tls) debug_printf("Setting TLS ALPN forced to fail, not sending\n");
+  *plist = NULL;
+  }
+else
+  {
+  /* The server implementation only accepts exactly one protocol name
+  but it's little extra code complexity in the client. */
+
+  const uschar * list = exp_alpn;
+  uschar * p = store_get(Ustrlen(exp_alpn), is_tainted(exp_alpn)), * s, * t;
+  int sep = 0;
+  uschar len;
+
+  for (t = p; s = string_nextinlist(&list, &sep, NULL, 0); t += len)
+    {
+    *t++ = len = (uschar) Ustrlen(s);
+    memcpy(t, s, len);
+    }
+  *plist = (*plen = t - p) ? p : NULL;
+  }
+return TRUE;
+}
+#endif /* EXIM_HAVE_ALPN */
+
+
 /*************************************************
 *    Start a TLS session in a client             *
 *************************************************/
@@ -3778,6 +3868,28 @@ if (ob->tls_sni)
     }
   }
 
+if (ob->tls_alpn)
+#ifdef EXIM_HAVE_ALPN
+  {
+  const uschar * plist;
+  unsigned plen;
+
+  if (!tls_alpn_plist(ob->tls_alpn, &plist, &plen, errstr))
+    return FALSE;
+  if (plist)
+    if (SSL_set_alpn_protos(exim_client_ctx->ssl, plist, plen) != 0)
+      {
+      tls_error(US"alpn init", host, NULL, errstr);
+      return FALSE;
+      }
+    else
+      DEBUG(D_tls) debug_printf("Setting TLS ALPN '%s'\n", ob->tls_alpn);
+  }
+#else
+  log_write(0, LOG_MAIN, "ALPN unusable with this OpenSSL library version; ignoring \"%s\"\n",
+          ob->alpn);
+#endif
+
 #ifdef SUPPORT_DANE
 if (conn_args->dane)
   if (dane_tlsa_load(exim_client_ctx->ssl, host, &conn_args->tlsa_dnsa, errstr) != OK)
@@ -3857,6 +3969,24 @@ DEBUG(D_tls)
 tls_client_resume_posthandshake(exim_client_ctx, tlsp);
 #endif
 
+#ifdef EXIM_HAVE_ALPN
+if (ob->tls_alpn)      /* We requested. See what was negotiated. */
+  {
+  const uschar * name;
+  unsigned len;
+
+  SSL_get0_alpn_selected(exim_client_ctx->ssl, &name, &len);
+  if (len > 0)
+    { DEBUG(D_tls) debug_printf("ALPN negotiated %u: '%.*s'\n", len, (int)*name, name+1); }
+  else if (verify_check_given_host(CUSS &ob->hosts_require_alpn, host) == OK)
+    {
+    /* Would like to send a relevant fatal Alert, but OpenSSL has no API */
+    tls_error(US"handshake", host, US"ALPN required but not negotiated", errstr);
+    return FALSE;
+    }
+  }
+#endif
+
 #ifdef SSL_get_extms_support
 tlsp->ext_master_secret = SSL_get_extms_support(exim_client_ctx->ssl) == 1;
 #endif
index 0df99845c58a1169691fb4e2400d10f096ddf012..c497ac307c1782c4155cd6135ea219331bea6e75 100644 (file)
@@ -685,7 +685,6 @@ else if ((subjdn = tls_cert_subject(cert, NULL)))
 return FALSE;
 }
 
-
 /* Environment cleanup: The GnuTLS library uses SSLKEYLOGFILE in the environment
 and writes a file by that name.  Our OpenSSL code does the same, using keying
 info from the library API.
index 3210e596c4f68a9e0a5261d5151acb3043ffbbbb..c62de724d8efa0a1bd7f759578468ea881a5002e 100644 (file)
@@ -84,6 +84,7 @@ optionlist smtp_transport_options[] = {
 #if !defined(DISABLE_TLS) && !defined(DISABLE_OCSP)
   { "hosts_request_ocsp",   opt_stringptr, LOFF(hosts_request_ocsp) },
 #endif
+  { "hosts_require_alpn",   opt_stringptr, LOFF(hosts_require_alpn) },
   { "hosts_require_auth",   opt_stringptr, LOFF(hosts_require_auth) },
 #ifndef DISABLE_TLS
 # ifdef SUPPORT_DANE
@@ -123,6 +124,7 @@ optionlist smtp_transport_options[] = {
   { "socks_proxy",          opt_stringptr, LOFF(socks_proxy) },
 #endif
 #ifndef DISABLE_TLS
+  { "tls_alpn",             opt_stringptr, LOFF(tls_alpn) },
   { "tls_certificate",      opt_stringptr, LOFF(tls_certificate) },
   { "tls_crl",              opt_stringptr, LOFF(tls_crl) },
   { "tls_dh_min_bits",      opt_int,      LOFF(tls_dh_min_bits) },
index aff3f545288a072199a15c12fd3f6cfa210a30ca..2ce8b11c5fbf72357b60f277ade336605c4d6ac9 100644 (file)
@@ -48,6 +48,7 @@ typedef struct {
   uschar       *dscp;
   uschar       *serialize_hosts;
   uschar       *hosts_try_auth;
+  uschar       *hosts_require_alpn;
   uschar       *hosts_require_auth;
   uschar       *hosts_try_chunking;
 #ifdef SUPPORT_DANE
@@ -101,6 +102,7 @@ typedef struct {
   uschar       *socks_proxy;
 #endif
 #ifndef DISABLE_TLS
+  uschar       *tls_alpn;
   uschar       *tls_certificate;
   uschar       *tls_crl;
   uschar       *tls_privatekey;
diff --git a/test/confs/1115 b/test/confs/1115
new file mode 100644 (file)
index 0000000..c1d5718
--- /dev/null
@@ -0,0 +1,73 @@
+# Exim test configuration 1115
+# ALPN
+
+SERVER =
+CONTROL =
+
+.include DIR/aux-var/tls_conf_prefix
+
+primary_hostname = myhost.test.ex
+
+# ----- Main settings -----
+
+domainlist local_domains = test.ex : *.test.ex
+
+acl_smtp_rcpt = accept
+
+tls_advertise_hosts = *
+tls_certificate = DIR/aux-fixed/cert1
+
+.ifdef STRICT
+tls_alpn = STRICT
+.endif
+.ifdef REQUIRE
+hosts_require_alpn = *
+.endif
+
+
+# ------ ACL ------
+
+begin acl
+
+# ----- Routers -----
+
+begin routers
+
+client:
+  driver = accept
+  condition = ${if eq {SERVER}{server} {no}{yes}}
+  transport = send_to_server
+
+server:
+  driver = redirect
+  data = :blackhole:
+
+
+# ----- Transports -----
+
+begin transports
+
+send_to_server:
+  driver =     smtp
+  allow_localhost
+  hosts =      HOSTIPV4
+  port =       PORT_D
+  tls_verify_certificates = DIR/aux-fixed/cert1
+  tls_verify_cert_hostnames = :
+
+  hosts_require_tls = *
+  hosts_try_fastopen = :
+  tls_alpn =   CONTROL
+.ifdef REQUIRE
+  hosts_require_alpn = *
+.endif
+
+# ----- Retry -----
+
+
+begin retry
+
+* * F,5d,10s
+
+
+# End
diff --git a/test/confs/2038 b/test/confs/2038
new file mode 120000 (symlink)
index 0000000..03a524d
--- /dev/null
@@ -0,0 +1 @@
+1115
\ No newline at end of file
diff --git a/test/confs/2138 b/test/confs/2138
new file mode 120000 (symlink)
index 0000000..03a524d
--- /dev/null
@@ -0,0 +1 @@
+1115
\ No newline at end of file
diff --git a/test/log/1115 b/test/log/1115
new file mode 100644 (file)
index 0000000..af3f1df
--- /dev/null
@@ -0,0 +1,45 @@
+1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local S=sss
+1999-03-02 09:44:33 10HmaX-0005vi-00 => a@test.ex R=client T=send_to_server H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4] X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx CV=yes C="250 OK id=10HmaY-0005vi-00"
+1999-03-02 09:44:33 10HmaX-0005vi-00 Completed
+1999-03-02 09:44:33 10HmaZ-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local S=sss
+1999-03-02 09:44:33 10HmaZ-0005vi-00 == b@test.ex R=client T=send_to_server defer (-37) H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4]: TLS session: (SSL_connect): error: <<detail omitted>>
+1999-03-02 09:44:33 10HmaZ-0005vi-00 removed by CALLER
+1999-03-02 09:44:33 10HmaZ-0005vi-00 Completed
+1999-03-02 09:44:33 10HmbA-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local S=sss
+1999-03-02 09:44:33 10HmbA-0005vi-00 == c@test.ex R=client T=send_to_server defer (-37) H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4]: TLS session: (SSL_connect): error: <<detail omitted>>
+1999-03-02 09:44:33 10HmbA-0005vi-00 removed by CALLER
+1999-03-02 09:44:33 10HmbA-0005vi-00 Completed
+1999-03-02 09:44:33 10HmbB-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local S=sss
+1999-03-02 09:44:33 10HmbB-0005vi-00 => d@test.ex R=client T=send_to_server H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4] X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx CV=yes C="250 OK id=10HmbC-0005vi-00"
+1999-03-02 09:44:33 10HmbB-0005vi-00 Completed
+1999-03-02 09:44:33 10HmbD-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local S=sss
+1999-03-02 09:44:33 10HmbD-0005vi-00 => e@test.ex R=client T=send_to_server H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4] X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx CV=yes C="250 OK id=10HmbE-0005vi-00"
+1999-03-02 09:44:33 10HmbD-0005vi-00 Completed
+1999-03-02 09:44:33 10HmbF-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local S=sss
+1999-03-02 09:44:33 10HmbF-0005vi-00 => f@test.ex R=client T=send_to_server H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4] X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx CV=yes C="250 OK id=10HmbG-0005vi-00"
+1999-03-02 09:44:33 10HmbF-0005vi-00 Completed
+1999-03-02 09:44:33 10HmbH-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local S=sss
+1999-03-02 09:44:33 10HmbH-0005vi-00 => g@test.ex R=client T=send_to_server H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4] X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx CV=yes C="250 OK id=10HmbI-0005vi-00"
+1999-03-02 09:44:33 10HmbH-0005vi-00 Completed
+
+******** SERVER ********
+1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port PORT_D
+1999-03-02 09:44:33 10HmaY-0005vi-00 <= CALLER@myhost.test.ex H=the.local.host.name (myhost.test.ex) [ip4.ip4.ip4.ip4] P=esmtps X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx CV=no S=sss id=E10HmaX-0005vi-00@myhost.test.ex
+1999-03-02 09:44:33 10HmaY-0005vi-00 => :blackhole: <a@test.ex> R=server
+1999-03-02 09:44:33 10HmaY-0005vi-00 Completed
+1999-03-02 09:44:33 TLS error on connection from the.local.host.name (myhost.test.ex) [ip4.ip4.ip4.ip4] (SSL_accept): error: <<detail omitted>>
+1999-03-02 09:44:33 TLS error on connection from the.local.host.name (myhost.test.ex) [ip4.ip4.ip4.ip4] (SSL_accept): error: <<detail omitted>>
+1999-03-02 09:44:33 10HmbC-0005vi-00 <= CALLER@myhost.test.ex H=the.local.host.name (myhost.test.ex) [ip4.ip4.ip4.ip4] P=esmtps X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx CV=no S=sss id=E10HmbB-0005vi-00@myhost.test.ex
+1999-03-02 09:44:33 10HmbC-0005vi-00 => :blackhole: <d@test.ex> R=server
+1999-03-02 09:44:33 10HmbC-0005vi-00 Completed
+1999-03-02 09:44:33 10HmbE-0005vi-00 <= CALLER@myhost.test.ex H=the.local.host.name (myhost.test.ex) [ip4.ip4.ip4.ip4] P=esmtps X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx CV=no S=sss id=E10HmbD-0005vi-00@myhost.test.ex
+1999-03-02 09:44:33 10HmbE-0005vi-00 => :blackhole: <e@test.ex> R=server
+1999-03-02 09:44:33 10HmbE-0005vi-00 Completed
+1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port PORT_D
+1999-03-02 09:44:33 10HmbG-0005vi-00 <= CALLER@myhost.test.ex H=the.local.host.name (myhost.test.ex) [ip4.ip4.ip4.ip4] P=esmtps X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx CV=no S=sss id=E10HmbF-0005vi-00@myhost.test.ex
+1999-03-02 09:44:33 10HmbG-0005vi-00 => :blackhole: <f@test.ex> R=server
+1999-03-02 09:44:33 10HmbG-0005vi-00 Completed
+1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port PORT_D
+1999-03-02 09:44:33 10HmbI-0005vi-00 <= CALLER@myhost.test.ex H=the.local.host.name (myhost.test.ex) [ip4.ip4.ip4.ip4] P=esmtps X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx CV=no S=sss id=E10HmbH-0005vi-00@myhost.test.ex
+1999-03-02 09:44:33 10HmbI-0005vi-00 => :blackhole: <g@test.ex> R=server
+1999-03-02 09:44:33 10HmbI-0005vi-00 Completed
diff --git a/test/log/2038 b/test/log/2038
new file mode 100644 (file)
index 0000000..a93f4d3
--- /dev/null
@@ -0,0 +1,26 @@
+1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local S=sss
+1999-03-02 09:44:33 10HmaX-0005vi-00 => client_require@test.ex R=client T=send_to_server H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4] X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx CV=yes C="250 OK id=10HmaY-0005vi-00"
+1999-03-02 09:44:33 10HmaX-0005vi-00 Completed
+1999-03-02 09:44:33 10HmaZ-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local S=sss
+1999-03-02 09:44:33 10HmaZ-0005vi-00 == client_require_fail@test.ex R=client T=send_to_server defer (-37) H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4]: TLS session: (handshake): ALPN required but not negotiated
+1999-03-02 09:44:33 10HmaZ-0005vi-00 removed by CALLER
+1999-03-02 09:44:33 10HmaZ-0005vi-00 Completed
+1999-03-02 09:44:33 10HmbA-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local S=sss
+1999-03-02 09:44:33 10HmbA-0005vi-00 => server_require_good@test.ex R=client T=send_to_server H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4] X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx CV=yes C="250 OK id=10HmbB-0005vi-00"
+1999-03-02 09:44:33 10HmbA-0005vi-00 Completed
+1999-03-02 09:44:33 10HmbC-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local S=sss
+1999-03-02 09:44:33 10HmbC-0005vi-00 H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4] TLS error on connection (recv): A TLS fatal alert has been received: No supported application protocol could be negotiated
+1999-03-02 09:44:33 10HmbC-0005vi-00 == server_require_bad@test.ex R=client T=send_to_server defer (-37) H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4]: TLS session: error on first read
+
+******** SERVER ********
+1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port PORT_D
+1999-03-02 09:44:33 10HmaY-0005vi-00 <= CALLER@myhost.test.ex H=the.local.host.name (myhost.test.ex) [ip4.ip4.ip4.ip4] P=esmtps X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx CV=no S=sss id=E10HmaX-0005vi-00@myhost.test.ex
+1999-03-02 09:44:33 10HmaY-0005vi-00 => :blackhole: <client_require@test.ex> R=server
+1999-03-02 09:44:33 10HmaY-0005vi-00 Completed
+1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port PORT_D
+1999-03-02 09:44:33 TLS error on connection from the.local.host.name [ip4.ip4.ip4.ip4] (recv): A TLS fatal alert has been received: No supported application protocol could be negotiated
+1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port PORT_D
+1999-03-02 09:44:33 10HmbB-0005vi-00 <= CALLER@myhost.test.ex H=the.local.host.name (myhost.test.ex) [ip4.ip4.ip4.ip4] P=esmtps X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx CV=no S=sss id=E10HmbA-0005vi-00@myhost.test.ex
+1999-03-02 09:44:33 10HmbB-0005vi-00 => :blackhole: <server_require_good@test.ex> R=server
+1999-03-02 09:44:33 10HmbB-0005vi-00 Completed
+1999-03-02 09:44:33 TLS error on connection from the.local.host.name (myhost.test.ex) [ip4.ip4.ip4.ip4] (handshake): ALPN required but not negotiated
diff --git a/test/log/2138 b/test/log/2138
new file mode 100644 (file)
index 0000000..bbc80a4
--- /dev/null
@@ -0,0 +1,25 @@
+1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local S=sss
+1999-03-02 09:44:33 10HmaX-0005vi-00 => client_require@test.ex R=client T=send_to_server H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4] X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx CV=yes C="250 OK id=10HmaY-0005vi-00"
+1999-03-02 09:44:33 10HmaX-0005vi-00 Completed
+1999-03-02 09:44:33 10HmaZ-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local S=sss
+1999-03-02 09:44:33 10HmaZ-0005vi-00 == client_require_fail@test.ex R=client T=send_to_server defer (-37) H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4]: TLS session: (handshake): ALPN required but not negotiated
+1999-03-02 09:44:33 10HmaZ-0005vi-00 removed by CALLER
+1999-03-02 09:44:33 10HmaZ-0005vi-00 Completed
+1999-03-02 09:44:33 10HmbA-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local S=sss
+1999-03-02 09:44:33 10HmbA-0005vi-00 => server_require_good@test.ex R=client T=send_to_server H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4] X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx CV=yes C="250 OK id=10HmbB-0005vi-00"
+1999-03-02 09:44:33 10HmbA-0005vi-00 Completed
+1999-03-02 09:44:33 10HmbC-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local S=sss
+1999-03-02 09:44:33 10HmbC-0005vi-00 H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4]: Remote host closed connection in response to EHLO myhost.test.ex
+1999-03-02 09:44:33 10HmbC-0005vi-00 == server_require_bad@test.ex R=client T=send_to_server defer (-18) H=ip4.ip4.ip4.ip4 [ip4.ip4.ip4.ip4]: Remote host closed connection in response to EHLO myhost.test.ex
+
+******** SERVER ********
+1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port PORT_D
+1999-03-02 09:44:33 10HmaY-0005vi-00 <= CALLER@myhost.test.ex H=the.local.host.name (myhost.test.ex) [ip4.ip4.ip4.ip4] P=esmtps X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx CV=no S=sss id=E10HmaX-0005vi-00@myhost.test.ex
+1999-03-02 09:44:33 10HmaY-0005vi-00 => :blackhole: <client_require@test.ex> R=server
+1999-03-02 09:44:33 10HmaY-0005vi-00 Completed
+1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port PORT_D
+1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port PORT_D
+1999-03-02 09:44:33 10HmbB-0005vi-00 <= CALLER@myhost.test.ex H=the.local.host.name (myhost.test.ex) [ip4.ip4.ip4.ip4] P=esmtps X=TLS1.x:ke-RSA-AES256-SHAnnn:xxx CV=no S=sss id=E10HmbA-0005vi-00@myhost.test.ex
+1999-03-02 09:44:33 10HmbB-0005vi-00 => :blackhole: <server_require_good@test.ex> R=server
+1999-03-02 09:44:33 10HmbB-0005vi-00 Completed
+1999-03-02 09:44:33 TLS error on connection from the.local.host.name (myhost.test.ex) [ip4.ip4.ip4.ip4] (handshake): ALPN required but not negotiated
index 670672a70263264a6da875651b42cbcf03f3d723..6f142c5406d78421516fa1e599a6614ea8e7978f 100755 (executable)
@@ -1507,6 +1507,8 @@ RESET_AFTER_EXTRA_LINE_READ:
     s/session: \K\((SSL_connect|gnutls_handshake)\): timed out/(tls lib connect fn): timed out/;
     s/TLS error on connection from .*\K\((SSL_accept|gnutls_handshake)\): timed out/(tls lib accept fn): timed out/;
     s/TLS error on connection from .*\K(SSL_accept: TCP connection closed by peer|\(gnutls_handshake\): The TLS connection was non-properly terminated.)/(tls lib accept fn): TCP connection closed by peer/;
+    s/TLS session: \K\(gnutls_handshake\): No supported application protocol could be negotiated/(SSL_connect): error: <<detail omitted>>/;
+    s/\(gnutls_handshake\): No common application protocol could be negotiated./(SSL_accept): error: <<detail omitted>>/;
     }
 
   # ======== mail ========
diff --git a/test/scripts/1100-Basic-TLS/1115 b/test/scripts/1100-Basic-TLS/1115
new file mode 100644 (file)
index 0000000..d74caca
--- /dev/null
@@ -0,0 +1,50 @@
+# TLS: ALPN
+gnutls
+exim -DSERVER=server -bd -oX PORT_D
+****
+#
+# Basic: is good ALPN set on tpt acceptable to server
+exim -DCONTROL=smtp -odf a@test.ex
+Test message.
+****
+#
+# Bad ALPN rejected
+exim -DCONTROL=http -odf b@test.ex
+****
+exim -Mrm $msg1
+****
+sudo rm -f DIR/spool/db/retry
+#
+# Multiple ALPN rejected
+exim -DCONTROL=smtp:smtp -odf c@test.ex
+****
+exim -Mrm $msg1
+****
+sudo rm -f DIR/spool/db/retry
+#
+# Empty client option is ok
+exim -DCONTROL="" -odf d@test.ex
+****
+# Content-free client option is ok
+exim -DCONTROL=" " -odf e@test.ex
+****
+killdaemon
+#
+# Server can be told to ignore (bad) ALPN from client
+exim -DSERVER=server -DSTRICT="" -bd -oX PORT_D
+****
+exim -DCONTROL=http -odf f@test.ex
+****
+killdaemon
+#
+# Server can be told custom names list
+exim -DSERVER=server -DSTRICT='${if eq {$sender_host_address}{HOSTIPV4} {smtp:weird} {smtp}}' -bd -oX PORT_D
+****
+exim -DCONTROL=weird -odf g@test.ex
+****
+killdaemon
+#
+#
+no_msglog_check
+no_stdout_check
+millisleep 500
diff --git a/test/scripts/2000-GnuTLS/2038 b/test/scripts/2000-GnuTLS/2038
new file mode 100644 (file)
index 0000000..227af84
--- /dev/null
@@ -0,0 +1,36 @@
+# TLS: ALPN: mandatory
+# Separated from the OpenSSL equivalent as we deliberately send a Fatal Alert, and that gets logged by the receiver
+# OpenSSL does not provides the facility.
+gnutls
+exim -DSERVER=server -bd -oX PORT_D
+****
+# Client requires ALPN (success)
+exim -DCONTROL=smtp -DREQUIRE=y -odf client_require@test.ex
+****
+killdaemon
+#
+# Server can be told to ignore (bad) ALPN from client
+exim -DSERVER=server -DSTRICT="" -bd -oX PORT_D
+****
+# Client requires ALPN (fail)
+exim -DCONTROL=http -DREQUIRE=y -odf client_require_fail@test.ex
+****
+exim -Mrm $msg1
+****
+sudo rm -f DIR/spool/db/retry
+killdaemon
+#
+#
+# Server can be told ALPN mandatory
+exim -DSERVER=server -DREQUIRE=y -bd -oX PORT_D
+****
+# Client supplies ALPN, good
+exim -DCONTROL=smtp -odf server_require_good@test.ex
+****
+# Client does not supply ALPN, fails
+exim -odf server_require_bad@test.ex
+****
+killdaemon
+#
+no_msglog_check
+no_stdout_check
diff --git a/test/scripts/2100-OpenSSL/2138 b/test/scripts/2100-OpenSSL/2138
new file mode 100644 (file)
index 0000000..7dadb30
--- /dev/null
@@ -0,0 +1,35 @@
+# TLS: ALPN: mandatory
+#
+# Plain server
+exim -DSERVER=server -bd -oX PORT_D
+****
+# Client requires ALPN (success)
+exim -DCONTROL=smtp -DREQUIRE=y -odf client_require@test.ex
+****
+killdaemon
+#
+# Server can be told to ignore (bad) ALPN from client
+exim -DSERVER=server -DSTRICT="" -bd -oX PORT_D
+****
+# Client requires ALPN (fail)
+exim -DCONTROL=http -DREQUIRE=y -odf client_require_fail@test.ex
+****
+exim -Mrm $msg1
+****
+sudo rm -f DIR/spool/db/retry
+killdaemon
+#
+#
+# Server can be told ALPN mandatory
+exim -DSERVER=server -DREQUIRE=y -bd -oX PORT_D
+****
+# Client supplies ALPN, good
+exim -DCONTROL=smtp -odf server_require_good@test.ex
+****
+# Client does not supply ALPN, fails
+exim -odf server_require_bad@test.ex
+****
+killdaemon
+#
+no_msglog_check
+no_stdout_check
index fd77c72b7be163dad67999c7ad79d0ff797b62cb..0ba712dcf1452a98a458c77b7b566e813f446fad 100644 (file)
@@ -55,6 +55,7 @@ hosts_max_try = 5
 hosts_max_try_hardlimit = 50
 no_hosts_override
 no_hosts_randomize
+hosts_require_alpn = 
 hosts_require_auth = 
 hosts_try_auth = 
 hosts_try_chunking = *