+while (rc == GNUTLS_E_AGAIN || rc == GNUTLS_E_INTERRUPTED && !sigalrm_seen);
+ALARM_CLR(0);
+
+if (rc != GNUTLS_E_SUCCESS)
+ {
+ DEBUG(D_tls) debug_printf(" error %d from gnutls_handshake: %s\n",
+ rc, gnutls_strerror(rc));
+
+ /* It seems that, except in the case of a timeout, we have to close the
+ connection right here; otherwise if the other end is running OpenSSL it hangs
+ until the server times out. */
+
+ if (sigalrm_seen)
+ {
+ tls_error(US"gnutls_handshake", US"timed out", NULL, errstr);
+#ifndef DISABLE_EVENT
+ (void) event_raise(event_action, US"tls:fail:connect", *errstr, NULL);
+#endif
+ gnutls_db_remove_session(state->session);
+ }
+ else
+ {
+ tls_error_gnu(state, US"gnutls_handshake", rc, errstr);
+#ifndef DISABLE_EVENT
+ (void) event_raise(event_action, US"tls:fail:connect", *errstr, NULL);
+#endif
+ (void) gnutls_alert_send_appropriate(state->session, rc);
+ gnutls_deinit(state->session);
+ millisleep(500);
+ shutdown(state->fd_out, SHUT_WR);
+ for (int i = 1024; fgetc(smtp_in) != EOF && i > 0; ) i--; /* drain skt */
+ (void)fclose(smtp_out);
+ (void)fclose(smtp_in);
+ smtp_out = smtp_in = NULL;
+ }
+
+ return FAIL;
+ }
+
+#ifdef GNUTLS_SFLAGS_EXT_MASTER_SECRET
+if (gnutls_session_get_flags(state->session) & GNUTLS_SFLAGS_EXT_MASTER_SECRET)
+ tls_in.ext_master_secret = TRUE;
+#endif
+
+#ifdef EXIM_HAVE_TLS_RESUME
+tls_server_resume_posthandshake(state);
+#endif
+
+DEBUG(D_tls) post_handshake_debug(state);
+
+#ifdef EXIM_HAVE_ALPN
+if (server_seen_alpn > 0)
+ {
+ 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_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 */
+
+if (!verify_certificate(state, errstr))
+ {
+ if (state->verify_requirement != VERIFY_OPTIONAL)
+ {
+ (void) tls_error(US"certificate verification failed", *errstr, NULL, errstr);
+ return FAIL;
+ }
+ DEBUG(D_tls)
+ debug_printf("TLS: continuing on only because verification was optional, after: %s\n",
+ *errstr);
+ }
+
+/* Sets various Exim expansion variables; always safe within server */
+
+extract_exim_vars_from_tls_state(state);
+
+/* TLS has been set up. Adjust the input functions to read via TLS,
+and initialize appropriately. */
+
+state->xfer_buffer = store_malloc(ssl_xfer_buffer_size);
+
+receive_getc = tls_getc;
+receive_getbuf = tls_getbuf;
+receive_get_cache = tls_get_cache;
+receive_hasc = tls_hasc;
+receive_ungetc = tls_ungetc;
+receive_feof = tls_feof;
+receive_ferror = tls_ferror;
+
+return OK;
+}
+
+
+
+
+static void
+tls_client_setup_hostname_checks(host_item * host, exim_gnutls_state_st * state,
+ smtp_transport_options_block * ob)
+{
+if (verify_check_given_host(CUSS &ob->tls_verify_cert_hostnames, host) == OK)
+ {
+ state->exp_tls_verify_cert_hostnames =
+#ifdef SUPPORT_I18N
+ string_domain_utf8_to_alabel(host->certname, NULL);
+#else
+ host->certname;
+#endif
+ DEBUG(D_tls)
+ debug_printf("TLS: server cert verification includes hostname: \"%s\"\n",
+ state->exp_tls_verify_cert_hostnames);
+ }
+}
+
+
+
+
+#ifdef SUPPORT_DANE
+/* Given our list of RRs from the TLSA lookup, build a lookup block in
+GnuTLS-DANE's preferred format. Hang it on the state str for later
+use in DANE verification.
+
+We point at the dnsa data not copy it, so it must remain valid until
+after verification is done.*/
+
+static BOOL
+dane_tlsa_load(exim_gnutls_state_st * state, dns_answer * dnsa)
+{
+dns_scan dnss;
+int i;
+const char ** dane_data;
+int * dane_data_len;
+
+i = 1;
+for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
+ rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)
+ ) if (rr->type == T_TLSA) i++;
+
+dane_data = store_get(i * sizeof(uschar *), GET_UNTAINTED);
+dane_data_len = store_get(i * sizeof(int), GET_UNTAINTED);
+
+i = 0;
+for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
+ rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)
+ ) if (rr->type == T_TLSA && rr->size > 3)
+ {
+ const uschar * p = rr->data;
+/*XXX need somehow to mark rr and its data as tainted. Doues this mean copying it? */
+ uint8_t usage = p[0], sel = p[1], type = p[2];
+
+ DEBUG(D_tls)
+ debug_printf("TLSA: %d %d %d size %d\n", usage, sel, type, rr->size);
+
+ if ( (usage != DANESSL_USAGE_DANE_TA && usage != DANESSL_USAGE_DANE_EE)
+ || (sel != 0 && sel != 1)
+ )
+ continue;
+ switch(type)
+ {
+ case 0: /* Full: cannot check at present */
+ break;
+ case 1: if (rr->size != 3 + 256/8) continue; /* sha2-256 */
+ break;
+ case 2: if (rr->size != 3 + 512/8) continue; /* sha2-512 */
+ break;
+ default: continue;
+ }
+
+ tls_out.tlsa_usage |= 1<<usage;
+ dane_data[i] = CS p;
+ dane_data_len[i++] = rr->size;
+ }
+
+if (!i) return FALSE;
+
+dane_data[i] = NULL;
+dane_data_len[i] = 0;
+
+state->dane_data = (char * const *)dane_data;
+state->dane_data_len = dane_data_len;
+return TRUE;
+}
+#endif
+
+
+
+#ifdef EXIM_HAVE_TLS_RESUME
+/* On the client, get any stashed session for the given IP from hints db
+and apply it to the ssl-connection for attempted resumption. Although
+there is a gnutls_session_ticket_enable_client() interface it is
+documented as unnecessary (as of 3.6.7) as "session tickets are emabled
+by deafult". There seems to be no way to disable them, so even hosts not
+enabled by the transport option will be sent a ticket request. We will
+however avoid storing and retrieving session information. */
+
+static void
+tls_retrieve_session(tls_support * tlsp, gnutls_session_t session,
+ smtp_connect_args * conn_args, smtp_transport_options_block * ob)
+{
+tlsp->resumption = RESUME_SUPPORTED;