waste a bit of effort, but it doesn't seem worth messing around with locking to
prevent this.
-Argument:
- host NULL for server, server for client (for error handling)
-
Returns: OK/DEFER/FAIL
*/
int fd, rc;
unsigned int dh_bits;
gnutls_datum m;
-uschar filename[PATH_MAX];
+uschar filename_buf[PATH_MAX];
+uschar *filename = NULL;
size_t sz;
+uschar *exp_tls_dhparam;
+BOOL use_file_in_spool = FALSE;
+BOOL use_fixed_file = FALSE;
host_item *host = NULL; /* dummy for macros */
DEBUG(D_tls) debug_printf("Initialising GnuTLS server params.\n");
rc = gnutls_dh_params_init(&dh_server_params);
exim_gnutls_err_check(US"gnutls_dh_params_init");
+m.data = NULL;
+m.size = 0;
+
+if (!expand_check(tls_dhparam, US"tls_dhparam", &exp_tls_dhparam))
+ return DEFER;
+
+if (!exp_tls_dhparam)
+ {
+ DEBUG(D_tls) debug_printf("Loading default hard-coded DH params\n");
+ m.data = US std_dh_prime_default();
+ m.size = Ustrlen(m.data);
+ }
+else if (Ustrcmp(exp_tls_dhparam, "historic") == 0)
+ use_file_in_spool = TRUE;
+else if (Ustrcmp(exp_tls_dhparam, "none") == 0)
+ {
+ DEBUG(D_tls) debug_printf("Requested no DH parameters.\n");
+ return OK;
+ }
+else if (exp_tls_dhparam[0] != '/')
+ {
+ m.data = US std_dh_prime_named(exp_tls_dhparam);
+ if (m.data == NULL)
+ return tls_error(US"No standard prime named", CS exp_tls_dhparam, NULL);
+ m.size = Ustrlen(m.data);
+ }
+else
+ {
+ use_fixed_file = TRUE;
+ filename = exp_tls_dhparam;
+ }
+
+if (m.data)
+ {
+ rc = gnutls_dh_params_import_pkcs3(dh_server_params, &m, GNUTLS_X509_FMT_PEM);
+ exim_gnutls_err_check(US"gnutls_dh_params_import_pkcs3");
+ DEBUG(D_tls) debug_printf("Loaded fixed standard D-H parameters\n");
+ return OK;
+ }
+
#ifdef HAVE_GNUTLS_SEC_PARAM_CONSTANTS
/* If you change this constant, also change dh_param_fn_ext so that we can use a
different filename and ensure we have sufficient bits. */
dh_bits);
#endif
-if (!string_format(filename, sizeof(filename),
- "%s/gnutls-params-%d", spool_directory, dh_bits))
- return tls_error(US"overlong filename", NULL, NULL);
+/* Some clients have hard-coded limits. */
+if (dh_bits > tls_dh_max_bits)
+ {
+ DEBUG(D_tls)
+ debug_printf("tls_dh_max_bits clamping override, using %d bits instead.\n",
+ tls_dh_max_bits);
+ dh_bits = tls_dh_max_bits;
+ }
+
+if (use_file_in_spool)
+ {
+ if (!string_format(filename_buf, sizeof(filename_buf),
+ "%s/gnutls-params-%d", spool_directory, dh_bits))
+ return tls_error(US"overlong filename", NULL, NULL);
+ filename = filename_buf;
+ }
/* Open the cache file for reading and if successful, read it and set up the
parameters. */
if (rc < 0)
{
uschar *temp_fn;
+ unsigned int dh_bits_gen = dh_bits;
if ((PATH_MAX - Ustrlen(filename)) < 10)
return tls_error(US"Filename too long to generate replacement",
return tls_error(US"Unable to open temp file", strerror(errno), NULL);
(void)fchown(fd, exim_uid, exim_gid); /* Probably not necessary */
- DEBUG(D_tls) debug_printf("generating %d bits Diffie-Hellman key ...\n", dh_bits);
- rc = gnutls_dh_params_generate2(dh_server_params, dh_bits);
+ /* 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,
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)
{
if (!state->received_sni)
{
- if (Ustrstr(state->tls_certificate, US"tls_sni"))
+ if (state->tls_certificate && Ustrstr(state->tls_certificate, US"tls_sni"))
{
DEBUG(D_tls) debug_printf("We will re-expand TLS session files if we receive SNI.\n");
state->trigger_sni_changes = TRUE;
return DEFER;
}
-if (!S_ISREG(statbuf.st_mode))
+/* The test suite passes in /dev/null; we could check for that path explicitly,
+but who knows if someone has some weird FIFO which always dumps some certs, or
+other weirdness. The thing we really want to check is that it's not a
+directory, since while OpenSSL supports that, GnuTLS does not.
+So s/!S_ISREG/S_ISDIR/ and change some messsaging ... */
+if (S_ISDIR(statbuf.st_mode))
{
DEBUG(D_tls)
- debug_printf("verify certificates path is not a file: \"%s\"\n%s\n",
- state->exp_tls_verify_certificates,
- S_ISDIR(statbuf.st_mode)
- ? " it's a directory, that's OpenSSL, this is GnuTLS"
- : " (not a directory either)");
+ debug_printf("verify certificates path is a dir: \"%s\"\n",
+ state->exp_tls_verify_certificates);
log_write(0, LOG_MAIN|LOG_PANIC,
- "tls_verify_certificates \"%s\" is not a file",
+ "tls_verify_certificates \"%s\" is a directory",
state->exp_tls_verify_certificates);
return DEFER;
}
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;
return FALSE;
}
DEBUG(D_tls)
- debug_printf("TLS verify failure overriden (host in tls_try_verify_hosts)\n");
+ debug_printf("TLS verify failure overridden (host in tls_try_verify_hosts)\n");
}
else
{
static void
exim_gnutls_logger_cb(int level, const char *message)
{
- DEBUG(D_tls) debug_printf("GnuTLS<%d>: %s\n", level, 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
do
{
rc = gnutls_handshake(state->session);
- } while ((rc == GNUTLS_E_AGAIN) || (rc == GNUTLS_E_INTERRUPTED));
+ } while ((rc == GNUTLS_E_AGAIN) ||
+ (rc == GNUTLS_E_INTERRUPTED && !sigalrm_seen));
alarm(0);
if (rc != GNUTLS_E_SUCCESS)
do
{
rc = gnutls_handshake(state->session);
- } while ((rc == GNUTLS_E_AGAIN) || (rc == GNUTLS_E_INTERRUPTED));
+ } while ((rc == GNUTLS_E_AGAIN) ||
+ (rc == GNUTLS_E_INTERRUPTED && !sigalrm_seen));
alarm(0);
if (rc != GNUTLS_E_SUCCESS)
+/*************************************************
+* Let tls_require_ciphers be checked at startup *
+*************************************************/
+
+/* The tls_require_ciphers option, if set, must be something which the
+library can parse.
+
+Returns: NULL on success, or error message
+*/
+
+uschar *
+tls_validate_require_cipher(void)
+{
+int rc;
+uschar *expciphers = NULL;
+gnutls_priority_t priority_cache;
+const char *errpos;
+
+#define validate_check_rc(Label) do { \
+ if (rc != GNUTLS_E_SUCCESS) { if (exim_gnutls_base_init_done) gnutls_global_deinit(); \
+ return string_sprintf("%s failed: %s", (Label), gnutls_strerror(rc)); } } while (0)
+#define return_deinit(Label) do { gnutls_global_deinit(); return (Label); } while (0)
+
+if (exim_gnutls_base_init_done)
+ log_write(0, LOG_MAIN|LOG_PANIC,
+ "already initialised GnuTLS, Exim developer bug");
+
+rc = gnutls_global_init();
+validate_check_rc(US"gnutls_global_init()");
+exim_gnutls_base_init_done = TRUE;
+
+if (!(tls_require_ciphers && *tls_require_ciphers))
+ return_deinit(NULL);
+
+if (!expand_check(tls_require_ciphers, US"tls_require_ciphers", &expciphers))
+ return_deinit(US"failed to expand tls_require_ciphers");
+
+if (!(expciphers && *expciphers))
+ return_deinit(NULL);
+
+DEBUG(D_tls)
+ debug_printf("tls_require_ciphers expands to \"%s\"\n", expciphers);
+
+rc = gnutls_priority_init(&priority_cache, CS expciphers, &errpos);
+validate_check_rc(string_sprintf(
+ "gnutls_priority_init(%s) failed at offset %ld, \"%.8s..\"",
+ expciphers, errpos - CS expciphers, errpos));
+
+#undef return_deinit
+#undef validate_check_rc
+gnutls_global_deinit();
+
+return NULL;
+}
+
+
+
+
/*************************************************
* Report the library versions. *
*************************************************/