# endif
#endif
+#if GNUTLS_VERSION_NUMBER >= 0x030200
+# define EXIM_HAVE_ALPN
+#endif
+
#ifndef DISABLE_OCSP
# include <gnutls/ocsp.h>
#endif
static BOOL exim_testharness_disable_ocsp_validity_check = FALSE;
#endif
+#ifdef EXIM_HAVE_ALPN
+static BOOL server_seen_alpn = FALSE;
+#endif
#ifdef EXIM_HAVE_TLS_RESUME
static gnutls_datum_t server_sessticket_key;
#endif
+
/* ------------------------------------------------------------------------ */
/* macros */
const unsigned char *data, unsigned size)
{
/* https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml */
-if (tls_id == 5) /* status_request */
+switch (tls_id)
{
- DEBUG(D_tls) debug_printf("Seen status_request extension from client\n");
- tls_in.ocsp = OCSP_NOT_RESP;
+ 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;
+ break;
+#endif
}
return 0;
}
}
}
#endif
+
+
+#ifdef EXIM_HAVE_ALPN
+static void
+tls_server_set_acceptable_alpns(exim_gnutls_state_st * state)
+{
+int rc;
+gnutls_datum_t protocols[2] = {[0] = {.data = US"smtp", .size = 4},
+ [1] = {.data = US"esmtp", .size = 5}};
+
+/* 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));
+}
+#endif
+
/* ------------------------------------------------------------------------ */
/* Exported functions */
#endif
}
+#ifdef EXIM_HAVE_ALPN
+tls_server_set_acceptable_alpns(state);
+#endif
+
#ifdef EXIM_HAVE_TLS_RESUME
tls_server_resume_prehandshake(state);
#endif
DEBUG(D_tls) post_handshake_debug(state);
+#ifdef EXIM_HAVE_ALPN
+if (server_seen_alpn)
+ {
+ /* 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_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));
+
+ }
+#endif
+
/* Verify after the fact */
if (!verify_certificate(state, errstr))
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
# define EXIM_HAVE_OCSP_RESP_COUNT
# define OPENSSL_AUTO_SHA256
+# define EXIM_HAVE_ALPN
#else
# define EXIM_HAVE_EPHEM_RSA_KEX
# define EXIM_HAVE_RAND_PSEUDO
uschar ** errstr );
/* Callbacks */
-#ifdef EXIM_HAVE_OPENSSL_TLSEXT
-static int tls_servername_cb(SSL *s, int *ad ARG_UNUSED, void *arg);
-#endif
#ifndef DISABLE_OCSP
static int tls_server_stapling_cb(SSL *s, void *arg);
#endif
+#ifdef EXIM_HAVE_ALPN
+/*************************************************
+* 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 */
+
+static int
+tls_server_alpn_cb(SSL *ssl, const uschar ** out, uschar * outlen,
+ const uschar * in, unsigned int inlen, void * arg)
+{
+const exim_openssl_state_st * state = arg;
+
+DEBUG(D_tls)
+ {
+ debug_printf("Received TLS ALPN offer:");
+ for (int pos = 0, siz; pos < inlen; pos += siz+1)
+ {
+ siz = in[pos];
+ if (pos + 1 + siz > inlen) siz = inlen - pos - 1;
+ debug_printf(" '%.*s'", siz, in + pos + 1);
+ }
+ debug_printf("\n");
+ }
+
+/* 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 */
+ }
+
+/* Reject unacceptable ALPN */
+/* This will be fatal to the TLS conn; would be nice to kill TCP also */
+return SSL_TLSEXT_ERR_ALERT_FATAL;
+}
+#endif /* EXIM_HAVE_ALPN */
+
+
+
#ifndef DISABLE_OCSP
/*************************************************
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);
+# endif
}
# ifndef DISABLE_OCSP
else /* client */