+ errstr pointer to error string
+
+Returns: OK/DEFER/FAIL
+*/
+
+static int
+peer_status(exim_gnutls_state_st * state, uschar ** errstr)
+{
+gnutls_session_t session = state->session;
+const gnutls_datum_t * cert_list;
+int old_pool, rc;
+unsigned int cert_list_size = 0;
+gnutls_protocol_t protocol;
+gnutls_cipher_algorithm_t cipher;
+gnutls_kx_algorithm_t kx;
+gnutls_mac_algorithm_t mac;
+gnutls_certificate_type_t ct;
+gnutls_x509_crt_t crt;
+uschar * dn_buf;
+size_t sz;
+
+if (state->have_set_peerdn)
+ return OK;
+state->have_set_peerdn = TRUE;
+
+state->peerdn = NULL;
+
+/* tls_cipher */
+cipher = gnutls_cipher_get(session);
+protocol = gnutls_protocol_get_version(session);
+mac = gnutls_mac_get(session);
+kx =
+#ifdef GNUTLS_TLS1_3
+ protocol >= GNUTLS_TLS1_3 ? 0 :
+#endif
+ gnutls_kx_get(session);
+
+old_pool = store_pool;
+ {
+ tls_support * tlsp = state->tlsp;
+ store_pool = POOL_PERM;
+
+#ifdef SUPPORT_GNUTLS_SESS_DESC
+ {
+ gstring * g = NULL;
+ uschar * s = US gnutls_session_get_desc(session), c;
+
+ /* Nikos M suggests we use this by preference. It returns like:
+ (TLS1.3)-(ECDHE-SECP256R1)-(RSA-PSS-RSAE-SHA256)-(AES-256-GCM)
+
+ For partial back-compat, put a colon after the TLS version, replace the
+ )-( grouping with __, replace in-group - with _ and append the :keysize. */
+
+ /* debug_printf("peer_status: gnutls_session_get_desc %s\n", s); */
+
+ for (s++; (c = *s) && c != ')'; s++) g = string_catn(g, s, 1);
+
+ tlsp->ver = string_copyn(g->s, g->ptr);
+ for (uschar * p = US tlsp->ver; *p; p++)
+ if (*p == '-') { *p = '\0'; break; } /* TLS1.0-PKIX -> TLS1.0 */
+
+ g = string_catn(g, US":", 1);
+ if (*s) s++; /* now on _ between groups */
+ while ((c = *s))
+ {
+ for (*++s && ++s; (c = *s) && c != ')'; s++)
+ g = string_catn(g, c == '-' ? US"_" : s, 1);
+ /* now on ) closing group */
+ if ((c = *s) && *++s == '-') g = string_catn(g, US"__", 2);
+ /* now on _ between groups */
+ }
+ g = string_catn(g, US":", 1);
+ g = string_cat(g, string_sprintf("%d", (int) gnutls_cipher_get_key_size(cipher) * 8));
+ state->ciphersuite = string_from_gstring(g);
+ }
+#else
+ state->ciphersuite = string_sprintf("%s:%s:%d",
+ gnutls_protocol_get_name(protocol),
+ gnutls_cipher_suite_get_name(kx, cipher, mac),
+ (int) gnutls_cipher_get_key_size(cipher) * 8);
+
+ /* I don't see a way that spaces could occur, in the current GnuTLS
+ code base, but it was a concern in the old code and perhaps older GnuTLS
+ releases did return "TLS 1.0"; play it safe, just in case. */
+
+ for (uschar * p = state->ciphersuite; *p; p++) if (isspace(*p)) *p = '-';
+ tlsp->ver = string_copyn(state->ciphersuite,
+ Ustrchr(state->ciphersuite, ':') - state->ciphersuite);
+#endif
+
+/* debug_printf("peer_status: ciphersuite %s\n", state->ciphersuite); */
+
+ tlsp->cipher = state->ciphersuite;
+ tlsp->bits = gnutls_cipher_get_key_size(cipher) * 8;
+
+ tlsp->cipher_stdname = cipher_stdname_kcm(kx, cipher, mac);
+ }
+store_pool = old_pool;
+
+/* tls_peerdn */
+cert_list = gnutls_certificate_get_peers(session, &cert_list_size);
+
+if (!cert_list || cert_list_size == 0)
+ {
+ DEBUG(D_tls) debug_printf("TLS: no certificate from peer (%p & %d)\n",
+ cert_list, cert_list_size);
+ if (state->verify_requirement >= VERIFY_REQUIRED)
+ return tls_error(US"certificate verification failed",
+ US"no certificate received from peer", state->host, errstr);
+ return OK;
+ }
+
+if ((ct = gnutls_certificate_type_get(session)) != GNUTLS_CRT_X509)
+ {
+ const uschar * ctn = US gnutls_certificate_type_get_name(ct);
+ DEBUG(D_tls)
+ debug_printf("TLS: peer cert not X.509 but instead \"%s\"\n", ctn);
+ if (state->verify_requirement >= VERIFY_REQUIRED)
+ return tls_error(US"certificate verification not possible, unhandled type",
+ ctn, state->host, errstr);
+ return OK;
+ }
+
+#define exim_gnutls_peer_err(Label) \
+ do { \
+ if (rc != GNUTLS_E_SUCCESS) \
+ { \
+ 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(state, (Label), rc, errstr); \
+ return OK; \
+ } \
+ } while (0)
+
+rc = import_cert(&cert_list[0], &crt);
+exim_gnutls_peer_err(US"cert 0");
+
+state->tlsp->peercert = state->peercert = crt;
+
+sz = 0;
+rc = gnutls_x509_crt_get_dn(crt, NULL, &sz);
+if (rc != GNUTLS_E_SHORT_MEMORY_BUFFER)
+ {
+ exim_gnutls_peer_err(US"getting size for cert DN failed");
+ return FAIL; /* should not happen */
+ }
+dn_buf = store_get_perm(sz, GET_TAINTED);
+rc = gnutls_x509_crt_get_dn(crt, CS dn_buf, &sz);
+exim_gnutls_peer_err(US"failed to extract certificate DN [gnutls_x509_crt_get_dn(cert 0)]");
+
+state->peerdn = dn_buf;
+
+return OK;
+#undef exim_gnutls_peer_err
+}
+
+
+
+
+/*************************************************
+* Verify peer certificate *
+*************************************************/
+
+/* Called from both server and client code.
+*Should* be using a callback registered with
+gnutls_certificate_set_verify_function() to fail the handshake if we dislike
+the peer information, but that's too new for some OSes.
+
+Arguments:
+ state exim_gnutls_state_st *
+ errstr where to put an error message
+
+Returns:
+ FALSE if the session should be rejected
+ TRUE if the cert is okay or we just don't care
+*/
+
+static BOOL
+verify_certificate(exim_gnutls_state_st * state, uschar ** errstr)
+{
+int rc;
+uint verify;
+
+DEBUG(D_tls) debug_printf("TLS: checking peer certificate\n");
+*errstr = NULL;
+rc = peer_status(state, errstr);
+
+if (state->verify_requirement == VERIFY_NONE)
+ return TRUE;
+
+if (rc != OK || !state->peerdn)
+ {
+ verify = GNUTLS_CERT_INVALID;
+ *errstr = US"certificate not supplied";
+ }
+else
+
+ {
+#ifdef SUPPORT_DANE
+ if (state->verify_requirement == VERIFY_DANE && state->host)
+ {
+ /* Using dane_verify_session_crt() would be easy, as it does it all for us
+ including talking to a DNS resolver. But we want to do that bit ourselves
+ as the testsuite intercepts and fakes its own DNS environment. */
+
+ dane_state_t s;
+ dane_query_t r;
+ uint lsize;
+ const gnutls_datum_t * certlist =
+ gnutls_certificate_get_peers(state->session, &lsize);
+ int usage = tls_out.tlsa_usage;
+
+# ifdef GNUTLS_BROKEN_DANE_VALIDATION
+ /* Split the TLSA records into two sets, TA and EE selectors. Run the
+ dane-verification separately so that we know which selector verified;
+ then we know whether to do name-verification (needed for TA but not EE). */
+
+ if (usage == ((1<<DANESSL_USAGE_DANE_TA) | (1<<DANESSL_USAGE_DANE_EE)))
+ { /* a mixed-usage bundle */
+ int i, j, nrec;
+ const char ** dd;
+ int * ddl;
+
+ for (nrec = 0; state->dane_data_len[nrec]; ) nrec++;
+ nrec++;
+
+ dd = store_get(nrec * sizeof(uschar *), GET_UNTAINTED);
+ ddl = store_get(nrec * sizeof(int), GET_UNTAINTED);
+ nrec--;
+
+ if ((rc = dane_state_init(&s, 0)))
+ goto tlsa_prob;
+
+ for (usage = DANESSL_USAGE_DANE_EE;
+ usage >= DANESSL_USAGE_DANE_TA; usage--)
+ { /* take records with this usage */
+ for (j = i = 0; i < nrec; i++)
+ if (state->dane_data[i][0] == usage)
+ {
+ dd[j] = state->dane_data[i];
+ ddl[j++] = state->dane_data_len[i];
+ }
+ if (j)
+ {
+ dd[j] = NULL;
+ ddl[j] = 0;
+
+ if ((rc = dane_raw_tlsa(s, &r, (char * const *)dd, ddl, 1, 0)))
+ goto tlsa_prob;
+
+ if ((rc = dane_verify_crt_raw(s, certlist, lsize,
+ gnutls_certificate_type_get(state->session),
+ r, 0,
+ usage == DANESSL_USAGE_DANE_EE
+ ? DANE_VFLAG_ONLY_CHECK_EE_USAGE : 0,
+ &verify)))
+ {
+ DEBUG(D_tls)
+ debug_printf("TLSA record problem: %s\n", dane_strerror(rc));
+ }
+ else if (verify == 0) /* verification passed */
+ {
+ usage = 1 << usage;
+ break;
+ }
+ }
+ }
+
+ if (rc) goto tlsa_prob;
+ }
+ else
+# endif
+ {
+ if ( (rc = dane_state_init(&s, 0))
+ || (rc = dane_raw_tlsa(s, &r, state->dane_data, state->dane_data_len,
+ 1, 0))
+ || (rc = dane_verify_crt_raw(s, certlist, lsize,
+ gnutls_certificate_type_get(state->session),
+ r, 0,
+# ifdef GNUTLS_BROKEN_DANE_VALIDATION
+ usage == (1 << DANESSL_USAGE_DANE_EE)
+ ? DANE_VFLAG_ONLY_CHECK_EE_USAGE : 0,
+# else
+ 0,
+# endif
+ &verify))
+ )
+ goto tlsa_prob;
+ }
+
+ if (verify != 0) /* verification failed */
+ {
+ gnutls_datum_t str;
+ (void) dane_verification_status_print(verify, &str, 0);
+ *errstr = US str.data; /* don't bother to free */
+ goto badcert;
+ }
+
+# ifdef GNUTLS_BROKEN_DANE_VALIDATION
+ /* If a TA-mode TLSA record was used for verification we must additionally
+ verify the cert name (but not the CA chain). For EE-mode, skip it. */
+
+ if (usage & (1 << DANESSL_USAGE_DANE_EE))
+# endif
+ {
+ state->peer_dane_verified = state->peer_cert_verified = TRUE;
+ goto goodcert;
+ }
+# ifdef GNUTLS_BROKEN_DANE_VALIDATION
+ /* Assume that the name on the A-record is the one that should be matching
+ the cert. An alternate view is that the domain part of the email address
+ is also permissible. */
+
+ if (gnutls_x509_crt_check_hostname(state->tlsp->peercert,
+ CS state->host->name))
+ {
+ state->peer_dane_verified = state->peer_cert_verified = TRUE;
+ goto goodcert;
+ }
+# endif
+ }
+#endif /*SUPPORT_DANE*/
+
+ rc = gnutls_certificate_verify_peers2(state->session, &verify);
+ }
+
+/* Handle the result of verification. INVALID is set if any others are. */
+
+if (rc < 0 || verify & (GNUTLS_CERT_INVALID|GNUTLS_CERT_REVOKED))
+ {
+ state->peer_cert_verified = FALSE;
+ if (!*errstr)
+ {
+#ifdef GNUTLS_CERT_VFY_STATUS_PRINT
+ DEBUG(D_tls)
+ {
+ gnutls_datum_t txt;
+
+ if (gnutls_certificate_verification_status_print(verify,
+ gnutls_certificate_type_get(state->session), &txt, 0)
+ == GNUTLS_E_SUCCESS)
+ {
+ debug_printf("%s\n", txt.data);
+ gnutls_free(txt.data);
+ }
+ }
+#endif
+ *errstr = verify & GNUTLS_CERT_REVOKED
+ ? US"certificate revoked" : US"certificate invalid";
+ }
+
+ DEBUG(D_tls)
+ debug_printf("TLS certificate verification failed (%s): peerdn=\"%s\"\n",
+ *errstr, state->peerdn ? state->peerdn : US"<unset>");
+
+ if (state->verify_requirement >= VERIFY_REQUIRED)
+ goto badcert;
+ DEBUG(D_tls)
+ debug_printf("TLS verify failure overridden (host in tls_try_verify_hosts)\n");
+ }
+
+else
+ {
+ /* Client side, check the server's certificate name versus the name on the
+ A-record for the connection we made. What to do for server side - what name
+ to use for client? We document that there is no such checking for server
+ side. */
+
+ if ( state->exp_tls_verify_cert_hostnames
+ && !gnutls_x509_crt_check_hostname(state->tlsp->peercert,
+ CS state->exp_tls_verify_cert_hostnames)
+ )
+ {
+ DEBUG(D_tls)
+ debug_printf("TLS certificate verification failed: cert name mismatch\n");
+ if (state->verify_requirement >= VERIFY_REQUIRED)
+ goto badcert;
+ return TRUE;
+ }
+
+ state->peer_cert_verified = TRUE;
+ DEBUG(D_tls) debug_printf("TLS certificate verified: peerdn=\"%s\"\n",
+ state->peerdn ? state->peerdn : US"<unset>");
+ }
+
+goodcert:
+ state->tlsp->peerdn = state->peerdn;
+ return TRUE;
+
+#ifdef SUPPORT_DANE
+tlsa_prob:
+ *errstr = string_sprintf("TLSA record problem: %s",
+ rc == DANE_E_REQUESTED_DATA_NOT_AVAILABLE ? "none usable" : dane_strerror(rc));
+#endif
+
+badcert:
+ gnutls_alert_send(state->session, GNUTLS_AL_FATAL, GNUTLS_A_BAD_CERTIFICATE);
+ return FALSE;
+}
+
+
+
+
+/* ------------------------------------------------------------------------ */
+/* Callbacks */
+
+/* Logging function which can be registered with
+ * gnutls_global_set_log_function()
+ * gnutls_global_set_log_level() 0..9
+ */
+#if EXIM_GNUTLS_LIBRARY_LOG_LEVEL >= 0
+static void
+exim_gnutls_logger_cb(int level, const char *message)
+{
+ size_t len = strlen(message);
+ if (len < 1)
+ {
+ DEBUG(D_tls) debug_printf("GnuTLS<%d> empty debug message\n", level);
+ return;
+ }
+ DEBUG(D_tls) debug_printf("GnuTLS<%d>: %s%s", level, message,
+ message[len-1] == '\n' ? "" : "\n");
+}
+#endif
+
+
+/* Called after client hello, should handle SNI work.
+This will always set tls_sni (state->received_sni) if available,
+and may trigger presenting different certificates,
+if state->trigger_sni_changes is TRUE.
+
+Should be registered with
+ gnutls_handshake_set_post_client_hello_function()
+
+"This callback must return 0 on success or a gnutls error code to terminate the
+handshake.".
+
+For inability to get SNI information, we return 0.
+We only return non-zero if re-setup failed.
+Only used for server-side TLS.
+*/
+
+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_copy_taint(US sni_name, GET_TAINTED);
+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_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), &errno)))
+ {
+ 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 EXIM_HAVE_TLS1_3
+if (gnutls_protocol_get_version(state->session) < GNUTLS_TLS1_3)
+# else
+if (TRUE)
+# endif
+ {
+ gnutls_datum_t c, s;
+ gstring * gc, * gs;
+ /* For TLS1.2 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 relative to the spool directory,\n"
+ " and make sure it is writable by the Exim runtime user.\n"
+ " Add SSLKEYLOGFILE to keep_environment in the exim config.\n"
+ " Start Exim as root.\n"
+ " If using sudo, add SSLKEYLOGFILE to env_keep in /etc/sudoers\n"
+ " (works for TLS1.2 also, and saves cut-paste into file).\n"
+ " Trying to use add_environment for this will not work\n");
+#endif
+}
+
+
+#ifdef EXIM_HAVE_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_ANY, GNUTLS_HOOK_POST, tls_server_hook_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 /* 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(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, 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, uschar ** errstr)
+{
+uschar * local_alpn = string_copy(tls_alpn);
+int rc;
+const gnutls_datum_t * plist;
+unsigned plen;
+
+if (tls_alpn_plist(&local_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 /* EXIM_HAVE_ALPN */
+
+/* ------------------------------------------------------------------------ */
+/* 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.
+
+Arguments:
+ errstr pointer to error string
+
+Returns: OK on success
+ DEFER for errors before the start of the negotiation
+ FAIL for errors during the negotiation; the server can't
+ continue running.
+*/
+
+int
+tls_server_start(uschar ** errstr)
+{
+int rc;
+exim_gnutls_state_st * state = NULL;
+
+/* Check for previous activation */
+if (tls_in.active.sock >= 0)
+ {
+ tls_error(US"STARTTLS received after TLS started", US "", NULL, errstr);
+ smtp_printf("554 Already in TLS\r\n", FALSE);
+ return FAIL;
+ }
+
+/* Initialize the library. If it fails, it will already have logged the error
+and sent an SMTP response. */
+
+DEBUG(D_tls) debug_printf("initialising GnuTLS as a server\n");
+
+ {
+#ifdef MEASURE_TIMING
+ struct timeval t0;
+ gettimeofday(&t0, NULL);
+#endif
+
+ if ((rc = tls_init(NULL, NULL,
+ tls_require_ciphers, &state, &tls_in, errstr)) != OK) return rc;
+
+#ifdef MEASURE_TIMING
+ report_time_since(&t0, US"server tls_init (delta)");
+#endif
+ }
+
+#ifdef EXIM_HAVE_ALPN
+tls_server_set_acceptable_alpns(state, errstr);
+#endif
+
+#ifdef EXIM_HAVE_TLS_RESUME
+tls_server_resume_prehandshake(state);
+#endif
+
+/* If this is a host for which certificate verification is mandatory or
+optional, set up appropriately. */
+
+if (verify_check_host(&tls_verify_hosts) == OK)
+ {
+ DEBUG(D_tls)
+ debug_printf("TLS: a client certificate will be required\n");
+ state->verify_requirement = VERIFY_REQUIRED;
+ gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_REQUIRE);
+ }
+else if (verify_check_host(&tls_try_verify_hosts) == OK)
+ {
+ DEBUG(D_tls)
+ debug_printf("TLS: a client certificate will be requested but not required\n");
+ state->verify_requirement = VERIFY_OPTIONAL;
+ gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_REQUEST);
+ }
+else
+ {
+ DEBUG(D_tls)
+ debug_printf("TLS: a client certificate will not be requested\n");
+ state->verify_requirement = VERIFY_NONE;
+ gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_IGNORE);
+ }
+
+#ifndef DISABLE_EVENT
+if (event_action)
+ {
+ state->event_action = event_action;
+ gnutls_session_set_ptr(state->session, state);
+ gnutls_certificate_set_verify_function(state->lib_state.x509_cred, verify_cb);
+ }
+#endif
+
+/* Register SNI handling; always, even if not in tls_certificate, so that the
+expansion variable $tls_sni is always available. */
+
+gnutls_handshake_set_post_client_hello_function(state->session,
+ exim_sni_handling_cb);