+
+/* Link the credentials to the session. */
+
+rc = gnutls_credentials_set(state->session, GNUTLS_CRD_CERTIFICATE, state->x509_cred);
+exim_gnutls_err_check(US"gnutls_credentials_set");
+
+return OK;
+}
+
+/*************************************************
+* Initialize for GnuTLS *
+*************************************************/
+
+/* Called from both server and client code. In the case of a server, errors
+before actual TLS negotiation return DEFER.
+
+Arguments:
+ host connected host, if client; NULL if server
+ certificate certificate file
+ privatekey private key file
+ sni TLS SNI to send, sometimes when client; else NULL
+ cas CA certs file
+ crl CRL file
+ require_ciphers tls_require_ciphers setting
+ caller_state returned state-info structure
+
+Returns: OK/DEFER/FAIL
+*/
+
+static int
+tls_init(
+ const host_item *host,
+ const uschar *certificate,
+ const uschar *privatekey,
+ const uschar *sni,
+ const uschar *cas,
+ const uschar *crl,
+ const uschar *require_ciphers,
+ exim_gnutls_state_st **caller_state)
+{
+exim_gnutls_state_st *state;
+int rc;
+size_t sz;
+const char *errpos;
+uschar *p;
+BOOL want_default_priorities;
+
+if (!exim_gnutls_base_init_done)
+ {
+ DEBUG(D_tls) debug_printf("GnuTLS global init required.\n");
+
+#ifdef HAVE_GNUTLS_PKCS11
+ /* By default, gnutls_global_init will init PKCS11 support in auto mode,
+ which loads modules from a config file, which sounds good and may be wanted
+ by some sysadmin, but also means in common configurations that GNOME keyring
+ environment variables are used and so breaks for users calling mailq.
+ To prevent this, we init PKCS11 first, which is the documented approach. */
+ if (!gnutls_allow_auto_pkcs11)
+ {
+ rc = gnutls_pkcs11_init(GNUTLS_PKCS11_FLAG_MANUAL, NULL);
+ exim_gnutls_err_check(US"gnutls_pkcs11_init");
+ }
+#endif
+
+ rc = gnutls_global_init();
+ exim_gnutls_err_check(US"gnutls_global_init");
+
+#if EXIM_GNUTLS_LIBRARY_LOG_LEVEL >= 0
+ DEBUG(D_tls)
+ {
+ gnutls_global_set_log_function(exim_gnutls_logger_cb);
+ /* arbitrarily chosen level; bump upto 9 for more */
+ gnutls_global_set_log_level(EXIM_GNUTLS_LIBRARY_LOG_LEVEL);
+ }
+#endif
+
+ exim_gnutls_base_init_done = TRUE;
+ }
+
+if (host)
+ {
+ state = &state_client;
+ memcpy(state, &exim_gnutls_state_init, sizeof(exim_gnutls_state_init));
+ state->tlsp = &tls_out;
+ DEBUG(D_tls) debug_printf("initialising GnuTLS client session\n");
+ rc = gnutls_init(&state->session, GNUTLS_CLIENT);
+ }
+else
+ {
+ state = &state_server;
+ memcpy(state, &exim_gnutls_state_init, sizeof(exim_gnutls_state_init));
+ state->tlsp = &tls_in;
+ DEBUG(D_tls) debug_printf("initialising GnuTLS server session\n");
+ rc = gnutls_init(&state->session, GNUTLS_SERVER);
+ }
+exim_gnutls_err_check(US"gnutls_init");
+
+state->host = host;
+
+state->tls_certificate = certificate;
+state->tls_privatekey = privatekey;
+state->tls_require_ciphers = require_ciphers;
+state->tls_sni = sni;
+state->tls_verify_certificates = cas;
+state->tls_crl = crl;
+
+/* This handles the variables that might get re-expanded after TLS SNI;
+that's tls_certificate, tls_privatekey, tls_verify_certificates, tls_crl */
+
+DEBUG(D_tls)
+ debug_printf("Expanding various TLS configuration options for session credentials.\n");
+rc = tls_expand_session_files(state);
+if (rc != OK) return rc;
+
+/* These are all other parts of the x509_cred handling, since SNI in GnuTLS
+requires a new structure afterwards. */
+
+rc = tls_set_remaining_x509(state);
+if (rc != OK) return rc;
+
+/* set SNI in client, only */
+if (host)
+ {
+ if (!expand_check(sni, US"tls_out_sni", &state->tlsp->sni))
+ return DEFER;
+ if (state->tlsp->sni && *state->tlsp->sni)
+ {
+ DEBUG(D_tls)
+ debug_printf("Setting TLS client SNI to \"%s\"\n", state->tlsp->sni);
+ sz = Ustrlen(state->tlsp->sni);
+ rc = gnutls_server_name_set(state->session,
+ GNUTLS_NAME_DNS, state->tlsp->sni, sz);
+ exim_gnutls_err_check(US"gnutls_server_name_set");
+ }
+ }
+else if (state->tls_sni)
+ DEBUG(D_tls) debug_printf("*** PROBABLY A BUG *** " \
+ "have an SNI set for a client [%s]\n", state->tls_sni);
+
+/* This is the priority string support,
+http://www.gnutls.org/manual/html_node/Priority-Strings.html
+and replaces gnutls_require_kx, gnutls_require_mac & gnutls_require_protocols.
+This was backwards incompatible, but means Exim no longer needs to track
+all algorithms and provide string forms for them. */
+
+want_default_priorities = TRUE;
+
+if (state->tls_require_ciphers && *state->tls_require_ciphers)
+ {
+ if (!expand_check_tlsvar(tls_require_ciphers))
+ return DEFER;
+ if (state->exp_tls_require_ciphers && *state->exp_tls_require_ciphers)
+ {
+ DEBUG(D_tls) debug_printf("GnuTLS session cipher/priority \"%s\"\n",
+ state->exp_tls_require_ciphers);
+
+ rc = gnutls_priority_init(&state->priority_cache,
+ CS state->exp_tls_require_ciphers, &errpos);
+ want_default_priorities = FALSE;
+ p = state->exp_tls_require_ciphers;
+ }
+ }
+if (want_default_priorities)
+ {
+ DEBUG(D_tls)
+ debug_printf("GnuTLS using default session cipher/priority \"%s\"\n",
+ exim_default_gnutls_priority);
+ rc = gnutls_priority_init(&state->priority_cache,
+ exim_default_gnutls_priority, &errpos);
+ p = US exim_default_gnutls_priority;
+ }
+
+exim_gnutls_err_check(string_sprintf(
+ "gnutls_priority_init(%s) failed at offset %ld, \"%.6s..\"",
+ p, errpos - CS p, errpos));
+
+rc = gnutls_priority_set(state->session, state->priority_cache);
+exim_gnutls_err_check(US"gnutls_priority_set");
+
+gnutls_db_set_cache_expiration(state->session, ssl_session_timeout);
+
+/* Reduce security in favour of increased compatibility, if the admin
+decides to make that trade-off. */
+if (gnutls_compat_mode)
+ {
+#if LIBGNUTLS_VERSION_NUMBER >= 0x020104
+ DEBUG(D_tls) debug_printf("lowering GnuTLS security, compatibility mode\n");
+ gnutls_session_enable_compatibility_mode(state->session);
+#else
+ DEBUG(D_tls) debug_printf("Unable to set gnutls_compat_mode - GnuTLS version too old\n");
+#endif
+ }
+
+*caller_state = state;
+return OK;