+ /* GnuTLS overshoots!
+ * If we ask for 2236, we might get 2237 or more.
+ * But there's no way to ask GnuTLS how many bits there really are.
+ * We can ask how many bits were used in a TLS session, but that's it!
+ * The prime itself is hidden behind too much abstraction.
+ * So we ask for less, and proceed on a wing and a prayer.
+ * First attempt, subtracted 3 for 2233 and got 2240.
+ */
+ if (dh_bits >= EXIM_CLIENT_DH_MIN_BITS + 10)
+ {
+ dh_bits_gen = dh_bits - 10;
+ DEBUG(D_tls)
+ debug_printf("being paranoid about DH generation, make it '%d' bits'\n",
+ dh_bits_gen);
+ }
+
+ DEBUG(D_tls)
+ debug_printf("requesting generation of %d bit Diffie-Hellman prime ...\n",
+ dh_bits_gen);
+ rc = gnutls_dh_params_generate2(dh_server_params, dh_bits_gen);
+ exim_gnutls_err_check(US"gnutls_dh_params_generate2");
+
+ /* gnutls_dh_params_export_pkcs3() will tell us the exact size, every time,
+ and I confirmed that a NULL call to get the size first is how the GnuTLS
+ sample apps handle this. */
+
+ sz = 0;
+ m.data = NULL;
+ rc = gnutls_dh_params_export_pkcs3(dh_server_params, GNUTLS_X509_FMT_PEM,
+ m.data, &sz);
+ if (rc != GNUTLS_E_SHORT_MEMORY_BUFFER)
+ exim_gnutls_err_check(US"gnutls_dh_params_export_pkcs3(NULL) sizing");
+ m.size = sz;
+ m.data = malloc(m.size);
+ if (m.data == NULL)
+ return tls_error(US"memory allocation failed", strerror(errno), NULL);
+ /* this will return a size 1 less than the allocation size above */
+ rc = gnutls_dh_params_export_pkcs3(dh_server_params, GNUTLS_X509_FMT_PEM,
+ m.data, &sz);
+ if (rc != GNUTLS_E_SUCCESS)
+ {
+ free(m.data);
+ exim_gnutls_err_check(US"gnutls_dh_params_export_pkcs3() real");
+ }
+ m.size = sz; /* shrink by 1, probably */
+
+ sz = write_to_fd_buf(fd, m.data, (size_t) m.size);
+ if (sz != m.size)
+ {
+ free(m.data);
+ return tls_error(US"TLS cache write D-H params failed",
+ strerror(errno), NULL);
+ }
+ free(m.data);
+ sz = write_to_fd_buf(fd, US"\n", 1);
+ if (sz != 1)
+ return tls_error(US"TLS cache write D-H params final newline failed",
+ strerror(errno), NULL);
+
+ rc = close(fd);
+ if (rc)
+ return tls_error(US"TLS cache write close() failed",
+ strerror(errno), NULL);
+
+ if (Urename(temp_fn, filename) < 0)
+ return tls_error(string_sprintf("failed to rename \"%s\" as \"%s\"",
+ temp_fn, filename), strerror(errno), NULL);
+
+ DEBUG(D_tls) debug_printf("wrote D-H parameters to file \"%s\"\n", filename);
+ }
+
+DEBUG(D_tls) debug_printf("initialized server D-H parameters\n");
+return OK;
+}
+
+
+
+
+/*************************************************
+* Variables re-expanded post-SNI *
+*************************************************/
+
+/* Called from both server and client code, via tls_init(), and also from
+the SNI callback after receiving an SNI, if tls_certificate includes "tls_sni".
+
+We can tell the two apart by state->received_sni being non-NULL in callback.
+
+The callback should not call us unless state->trigger_sni_changes is true,
+which we are responsible for setting on the first pass through.
+
+Arguments:
+ state exim_gnutls_state_st *
+
+Returns: OK/DEFER/FAIL
+*/
+
+static int
+tls_expand_session_files(exim_gnutls_state_st *state)
+{
+struct stat statbuf;
+int rc;
+const host_item *host = state->host; /* macro should be reconsidered? */
+uschar *saved_tls_certificate = NULL;
+uschar *saved_tls_privatekey = NULL;
+uschar *saved_tls_verify_certificates = NULL;
+uschar *saved_tls_crl = NULL;
+int cert_count;
+
+/* We check for tls_sni *before* expansion. */
+if (!host) /* server */
+ {
+ if (!state->received_sni)
+ {
+ if (state->tls_certificate &&
+ (Ustrstr(state->tls_certificate, US"tls_sni") ||
+ Ustrstr(state->tls_certificate, US"tls_in_sni") ||
+ Ustrstr(state->tls_certificate, US"tls_out_sni")
+ ))
+ {
+ DEBUG(D_tls) debug_printf("We will re-expand TLS session files if we receive SNI.\n");
+ state->trigger_sni_changes = TRUE;
+ }
+ }
+ else
+ {
+ /* useful for debugging */
+ saved_tls_certificate = state->exp_tls_certificate;
+ saved_tls_privatekey = state->exp_tls_privatekey;
+ saved_tls_verify_certificates = state->exp_tls_verify_certificates;
+ saved_tls_crl = state->exp_tls_crl;
+ }
+ }
+
+rc = gnutls_certificate_allocate_credentials(&state->x509_cred);
+exim_gnutls_err_check(US"gnutls_certificate_allocate_credentials");
+
+/* remember: expand_check_tlsvar() is expand_check() but fiddling with
+state members, assuming consistent naming; and expand_check() returns
+false if expansion failed, unless expansion was forced to fail. */
+
+/* check if we at least have a certificate, before doing expensive
+D-H generation. */
+
+if (!expand_check_tlsvar(tls_certificate))
+ return DEFER;
+
+/* certificate is mandatory in server, optional in client */
+
+if ((state->exp_tls_certificate == NULL) ||
+ (*state->exp_tls_certificate == '\0'))
+ {
+ if (!host)
+ return tls_error(US"no TLS server certificate is specified", NULL, NULL);
+ else
+ DEBUG(D_tls) debug_printf("TLS: no client certificate specified; okay\n");
+ }
+
+if (state->tls_privatekey && !expand_check_tlsvar(tls_privatekey))
+ return DEFER;
+
+/* tls_privatekey is optional, defaulting to same file as certificate */
+
+if (state->tls_privatekey == NULL || *state->tls_privatekey == '\0')
+ {
+ state->tls_privatekey = state->tls_certificate;
+ state->exp_tls_privatekey = state->exp_tls_certificate;
+ }
+
+
+if (state->exp_tls_certificate && *state->exp_tls_certificate)
+ {
+ DEBUG(D_tls) debug_printf("certificate file = %s\nkey file = %s\n",
+ state->exp_tls_certificate, state->exp_tls_privatekey);
+
+ if (state->received_sni)
+ {
+ if ((Ustrcmp(state->exp_tls_certificate, saved_tls_certificate) == 0) &&
+ (Ustrcmp(state->exp_tls_privatekey, saved_tls_privatekey) == 0))
+ {
+ DEBUG(D_tls) debug_printf("TLS SNI: cert and key unchanged\n");
+ }
+ else
+ {
+ DEBUG(D_tls) debug_printf("TLS SNI: have a changed cert/key pair.\n");
+ }
+ }
+
+ rc = gnutls_certificate_set_x509_key_file(state->x509_cred,
+ CS state->exp_tls_certificate, CS state->exp_tls_privatekey,
+ GNUTLS_X509_FMT_PEM);
+ exim_gnutls_err_check(
+ string_sprintf("cert/key setup: cert=%s key=%s",
+ state->exp_tls_certificate, state->exp_tls_privatekey));
+ DEBUG(D_tls) debug_printf("TLS: cert/key registered\n");
+ } /* tls_certificate */
+