+static int
+exim_sni_handling_cb(gnutls_session_t session)
+{
+char sni_name[MAX_HOST_LEN];
+size_t data_len = MAX_HOST_LEN;
+exim_gnutls_state_st *state = &state_server;
+unsigned int sni_type;
+int rc, old_pool;
+uschar * dummy_errstr;
+
+rc = gnutls_server_name_get(session, sni_name, &data_len, &sni_type, 0);
+if (rc != GNUTLS_E_SUCCESS)
+ {
+ DEBUG(D_tls) {
+ if (rc == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE)
+ debug_printf("TLS: no SNI presented in handshake.\n");
+ else
+ debug_printf("TLS failure: gnutls_server_name_get(): %s [%d]\n",
+ gnutls_strerror(rc), rc);
+ }
+ return 0;
+ }
+
+if (sni_type != GNUTLS_NAME_DNS)
+ {
+ DEBUG(D_tls) debug_printf("TLS: ignoring SNI of unhandled type %u\n", sni_type);
+ return 0;
+ }
+
+/* We now have a UTF-8 string in sni_name */
+old_pool = store_pool;
+store_pool = POOL_PERM;
+state->received_sni = string_copyn(US sni_name, data_len);
+store_pool = old_pool;
+
+/* We set this one now so that variable expansions below will work */
+state->tlsp->sni = state->received_sni;
+
+DEBUG(D_tls) debug_printf("Received TLS SNI \"%s\"%s\n", sni_name,
+ state->trigger_sni_changes ? "" : " (unused for certificate selection)");
+
+if (!state->trigger_sni_changes)
+ return 0;
+
+if ((rc = tls_expand_session_files(state, &dummy_errstr)) != OK)
+ {
+ /* If the setup of certs/etc failed before handshake, TLS would not have
+ been offered. The best we can do now is abort. */
+ return GNUTLS_E_APPLICATION_ERROR_MIN;
+ }
+
+rc = tls_set_remaining_x509(state, &dummy_errstr);
+if (rc != OK) return GNUTLS_E_APPLICATION_ERROR_MIN;
+
+return 0;
+}
+
+
+
+#ifndef DISABLE_OCSP
+
+static int
+server_ocsp_stapling_cb(gnutls_session_t session, void * ptr,
+ gnutls_datum_t * ocsp_response)
+{
+int ret;
+DEBUG(D_tls) debug_printf("OCSP stapling callback: %s\n", US ptr);
+
+if ((ret = gnutls_load_file(ptr, ocsp_response)) < 0)
+ {
+ DEBUG(D_tls) debug_printf("Failed to load ocsp stapling file %s\n",
+ CS ptr);
+ tls_in.ocsp = OCSP_NOT_RESP;
+ return GNUTLS_E_NO_CERTIFICATE_STATUS;
+ }
+
+tls_in.ocsp = OCSP_VFY_NOT_TRIED;
+return 0;
+}
+
+#endif
+
+
+#ifndef DISABLE_EVENT
+/*
+We use this callback to get observability and detail-level control
+for an exim TLS connection (either direction), raising a tls:cert event
+for each cert in the chain presented by the peer. Any event
+can deny verification.
+
+Return 0 for the handshake to continue or non-zero to terminate.
+*/
+
+static int
+verify_cb(gnutls_session_t session)
+{
+const gnutls_datum_t * cert_list;
+unsigned int cert_list_size = 0;
+gnutls_x509_crt_t crt;
+int rc;
+uschar * yield;
+exim_gnutls_state_st * state = gnutls_session_get_ptr(session);
+
+if ((cert_list = gnutls_certificate_get_peers(session, &cert_list_size)))
+ while (cert_list_size--)
+ {
+ if ((rc = import_cert(&cert_list[cert_list_size], &crt)) != GNUTLS_E_SUCCESS)
+ {
+ DEBUG(D_tls) debug_printf("TLS: peer cert problem: depth %d: %s\n",
+ cert_list_size, gnutls_strerror(rc));
+ break;
+ }
+
+ state->tlsp->peercert = crt;
+ if ((yield = event_raise(state->event_action,
+ US"tls:cert", string_sprintf("%d", cert_list_size))))
+ {
+ log_write(0, LOG_MAIN,
+ "SSL verify denied by event-action: depth=%d: %s",
+ cert_list_size, yield);
+ return 1; /* reject */
+ }
+ state->tlsp->peercert = NULL;
+ }
+
+return 0;
+}
+
+#endif
+
+
+static gstring *
+ddump(gnutls_datum_t * d)
+{
+gstring * g = string_get((d->size+1) * 2);
+uschar * s = d->data;
+for (unsigned i = d->size; i > 0; i--, s++)
+ {
+ g = string_catn(g, US "0123456789abcdef" + (*s >> 4), 1);
+ g = string_catn(g, US "0123456789abcdef" + (*s & 0xf), 1);
+ }
+return g;
+}
+
+static void
+post_handshake_debug(exim_gnutls_state_st * state)
+{
+#ifdef SUPPORT_GNUTLS_SESS_DESC
+debug_printf("%s\n", gnutls_session_get_desc(state->session));
+#endif
+#ifdef SUPPORT_GNUTLS_KEYLOG
+# ifdef GNUTLS_TLS1_3
+if (gnutls_protocol_get_version(state->session) < GNUTLS_TLS1_3)
+#else
+if (TRUE)
+#endif
+ {
+ gnutls_datum_t c, s;
+ gstring * gc, * gs;
+ /* we only want the client random and the master secret */
+ gnutls_session_get_random(state->session, &c, &s);
+ gnutls_session_get_master_secret(state->session, &s);
+ gc = ddump(&c);
+ gs = ddump(&s);
+ debug_printf("CLIENT_RANDOM %.*s %.*s\n", (int)gc->ptr, gc->s, (int)gs->ptr, gs->s);
+ }
+else
+ debug_printf("To get keying info for TLS1.3 is hard:\n"
+ " set environment variable SSLKEYLOGFILE to a filename writable by uid exim\n"
+ " add SSLKEYLOGFILE to keep_environment in the exim config\n"
+ " run exim as root\n"
+ " if using sudo, add SSLKEYLOGFILE to env_keep in /etc/sudoers\n");
+#endif
+}
+
+
+#ifdef EXPERIMENTAL_TLS_RESUME
+static int
+tls_server_ticket_cb(gnutls_session_t sess, u_int htype, unsigned when,
+ unsigned incoming, const gnutls_datum_t * msg)
+{
+DEBUG(D_tls) debug_printf("newticket cb\n");
+tls_in.resumption |= RESUME_CLIENT_REQUESTED;
+return 0;
+}
+
+static void
+tls_server_resume_prehandshake(exim_gnutls_state_st * state)
+{
+/* Should the server offer session resumption? */
+tls_in.resumption = RESUME_SUPPORTED;
+if (verify_check_host(&tls_resumption_hosts) == OK)
+ {
+ int rc;
+ /* GnuTLS appears to not do ticket overlap, but does emit a fresh ticket when
+ an offered resumption is unacceptable. We lose one resumption per ticket
+ lifetime, and sessions cannot be indefinitely re-used. There seems to be no
+ way (3.6.7) of changing the default number of 2 TLS1.3 tickets issued, but at
+ least they go out in a single packet. */
+
+ if (!(rc = gnutls_session_ticket_enable_server(state->session,
+ &server_sessticket_key)))
+ tls_in.resumption |= RESUME_SERVER_TICKET;
+ else
+ DEBUG(D_tls)
+ debug_printf("enabling session tickets: %s\n", US gnutls_strerror(rc));
+
+ /* Try to tell if we see a ticket request */
+ gnutls_handshake_set_hook_function(state->session,
+ GNUTLS_HANDSHAKE_NEW_SESSION_TICKET, GNUTLS_HOOK_POST, tls_server_ticket_cb);
+ }
+}
+
+static void
+tls_server_resume_posthandshake(exim_gnutls_state_st * state)
+{
+if (gnutls_session_resumption_requested(state->session))
+ {
+ /* This tells us the client sent a full ticket. We use a
+ callback on session-ticket request, elsewhere, to tell
+ if a client asked for a ticket. */
+
+ tls_in.resumption |= RESUME_CLIENT_SUGGESTED;
+ DEBUG(D_tls) debug_printf("client requested resumption\n");
+ }
+if (gnutls_session_is_resumed(state->session))
+ {
+ tls_in.resumption |= RESUME_USED;
+ DEBUG(D_tls) debug_printf("Session resumed\n");
+ }
+}
+#endif
+/* ------------------------------------------------------------------------ */
+/* Exported functions */
+
+
+
+
+/*************************************************
+* Start a TLS session in a server *
+*************************************************/
+
+/* This is called when Exim is running as a server, after having received
+the STARTTLS command. It must respond to that command, and then negotiate
+a TLS session.