static BOOL exim_gnutls_base_init_done = FALSE;
-/* ------------------------------------------------------------------------ */
-/* Callback declarations */
-
-static void exim_gnutls_logger_cb(int level, const char *message);
-static int exim_sni_handling_cb(gnutls_session_t session);
-
/* ------------------------------------------------------------------------ */
/* macros */
/* Set this to control gnutls_global_set_log_level(); values 0 to 9 will setup
the library logging; a value less than 0 disables the calls to set up logging
callbacks. */
+#ifndef EXIM_GNUTLS_LIBRARY_LOG_LEVEL
#define EXIM_GNUTLS_LIBRARY_LOG_LEVEL -1
+#endif
+#ifndef EXIM_CLIENT_DH_MIN_BITS
#define EXIM_CLIENT_DH_MIN_BITS 1024
+#endif
+
+/* With GnuTLS 2.12.x+ we have gnutls_sec_param_to_pk_bits() with which we
+can ask for a bit-strength. Without that, we stick to the constant we had
+before, for now. */
+#ifndef EXIM_SERVER_DH_BITS_PRE2_12
+#define EXIM_SERVER_DH_BITS_PRE2_12 1024
+#endif
#define exim_gnutls_err_check(Label) do { \
if (rc != GNUTLS_E_SUCCESS) { return tls_error((Label), gnutls_strerror(rc), host); } } while (0)
-#define exim_gnutls_err_debugreturn0(Label) do { \
- if (rc != GNUTLS_E_SUCCESS) { \
- DEBUG(D_tls) debug_printf("TLS failure: %s: %s", (Label), gnutls_strerror(rc)); \
- return 0; } } while (0)
-
#define expand_check_tlsvar(Varname) expand_check(state->Varname, US #Varname, &state->exp_##Varname)
#if GNUTLS_VERSION_NUMBER >= 0x020c00
#define HAVE_GNUTLS_SESSION_CHANNEL_BINDING
+#define HAVE_GNUTLS_SEC_PARAM_CONSTANTS
+#define HAVE_GNUTLS_RND
+#endif
+
+
+
+
+/* ------------------------------------------------------------------------ */
+/* Callback declarations */
+
+#if EXIM_GNUTLS_LIBRARY_LOG_LEVEL >= 0
+static void exim_gnutls_logger_cb(int level, const char *message);
#endif
+static int exim_sni_handling_cb(gnutls_session_t session);
+
+
+
+
/* ------------------------------------------------------------------------ */
/* Static functions */
uschar filename[PATH_MAX];
size_t sz;
host_item *host = NULL; /* dummy for macros */
-const char * const dh_param_fn_ext = "normal"; /* change as dh_bits changes */
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");
-/* If you change this, also change dh_param_fn_ext so that we can use a
+#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 = gnutls_sec_param_to_pk_bits(GNUTLS_PK_DH, GNUTLS_SEC_PARAM_NORMAL);
if (!dh_bits)
return tls_error(US"gnutls_sec_param_to_pk_bits() failed", NULL, NULL);
+DEBUG(D_tls)
+ debug_printf("GnuTLS tells us that for D-H PK, NORMAL is %d bits.\n",
+ dh_bits);
+#else
+dh_bits = EXIM_SERVER_DH_BITS_PRE2_12;
+DEBUG(D_tls)
+ debug_printf("GnuTLS lacks gnutls_sec_param_to_pk_bits(), using %d bits.\n",
+ dh_bits);
+#endif
if (!string_format(filename, sizeof(filename),
- "%s/gnutls-params-%s", spool_directory, dh_param_fn_ext))
+ "%s/gnutls-params-%d", spool_directory, dh_bits))
return tls_error(US"overlong filename", NULL, NULL);
/* Open the cache file for reading and if successful, read it and set up the
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;
}
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;
}
}
+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. */
if (state->exp_tls_certificate && *state->exp_tls_certificate)
{
- BOOL setit = TRUE;
DEBUG(D_tls) debug_printf("certificate file = %s\nkey file = %s\n",
state->exp_tls_certificate, state->exp_tls_privatekey);
if ((Ustrcmp(state->exp_tls_certificate, saved_tls_certificate) == 0) &&
(Ustrcmp(state->exp_tls_privatekey, saved_tls_privatekey) == 0))
{
- DEBUG(D_tls) debug_printf("cert and key unchanged with SNI.\n");
- setit = FALSE;
+ DEBUG(D_tls) debug_printf("TLS SNI: cert and key unchanged\n");
}
else
{
- DEBUG(D_tls) debug_printf("SNI changed cert/key pair.\n");
+ DEBUG(D_tls) debug_printf("TLS SNI: have a changed cert/key pair.\n");
}
}
- if (setit)
- {
- 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));
- }
- }
+ 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 */
/* Set the trusted CAs file if one is provided, and then add the CRL if one is
provided. Experiment shows that, if the certificate file is empty, an unhelpful
if (state->tls_verify_certificates && *state->tls_verify_certificates)
{
- struct stat statbuf;
- BOOL setit_vc = TRUE, setit_crl = TRUE;
-
if (!expand_check_tlsvar(tls_verify_certificates))
return DEFER;
if (state->tls_crl && *state->tls_crl)
if (!expand_check_tlsvar(tls_crl))
return DEFER;
- if (state->received_sni)
+ if (!(state->exp_tls_verify_certificates &&
+ *state->exp_tls_verify_certificates))
{
- if (Ustrcmp(state->exp_tls_verify_certificates, saved_tls_verify_certificates) == 0)
- setit_vc = FALSE;
- if (Ustrcmp(state->exp_tls_crl, saved_tls_crl) == 0)
- setit_crl = FALSE;
+ DEBUG(D_tls)
+ debug_printf("TLS: tls_verify_certificates expanded empty, ignoring\n");
+ /* With no tls_verify_certificates, we ignore tls_crl too */
+ return OK;
}
+ }
+else
+ {
+ DEBUG(D_tls)
+ debug_printf("TLS: tls_verify_certificates not set or empty, ignoring\n");
+ return OK;
+ }
- /* nb: early exit; change if add more expansions to this function */
- if (!(setit_vc || setit_crl))
- return OK;
+if (Ustat(state->exp_tls_verify_certificates, &statbuf) < 0)
+ {
+ log_write(0, LOG_MAIN|LOG_PANIC, "could not stat %s "
+ "(tls_verify_certificates): %s", state->exp_tls_verify_certificates,
+ strerror(errno));
+ return DEFER;
+ }
- if (Ustat(state->exp_tls_verify_certificates, &statbuf) < 0)
- {
- log_write(0, LOG_MAIN|LOG_PANIC, "could not stat %s "
- "(tls_verify_certificates): %s", state->exp_tls_verify_certificates,
- strerror(errno));
- return DEFER;
- }
+if (!S_ISREG(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)");
+ log_write(0, LOG_MAIN|LOG_PANIC,
+ "tls_verify_certificates \"%s\" is not a file",
+ state->exp_tls_verify_certificates);
+ return DEFER;
+ }
- if (!S_ISREG(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)");
- log_write(0, LOG_MAIN|LOG_PANIC,
- "tls_verify_certificates \"%s\" is not a file",
- state->exp_tls_verify_certificates);
- return DEFER;
- }
+DEBUG(D_tls) debug_printf("verify certificates = %s size=" OFF_T_FMT "\n",
+ state->exp_tls_verify_certificates, statbuf.st_size);
- DEBUG(D_tls) debug_printf("verify certificates = %s size=" OFF_T_FMT "\n",
- state->exp_tls_verify_certificates, statbuf.st_size);
+if (statbuf.st_size == 0)
+ {
+ DEBUG(D_tls)
+ debug_printf("cert file empty, no certs, no verification, ignoring any CRL\n");
+ return OK;
+ }
- /* If the CA cert file is empty, there's no point in loading the CRL file,
- as we aren't verifying, so checking for revocation is pointless. */
+cert_count = gnutls_certificate_set_x509_trust_file(state->x509_cred,
+ CS state->exp_tls_verify_certificates, GNUTLS_X509_FMT_PEM);
+if (cert_count < 0)
+ {
+ rc = cert_count;
+ exim_gnutls_err_check(US"gnutls_certificate_set_x509_trust_file");
+ }
+DEBUG(D_tls) debug_printf("Added %d certificate authorities.\n", cert_count);
- if (statbuf.st_size > 0)
+if (state->tls_crl && *state->tls_crl)
+ {
+ if (state->exp_tls_crl && *state->exp_tls_crl)
{
- if (setit_vc)
- {
- cert_count = gnutls_certificate_set_x509_trust_file(state->x509_cred,
- CS state->exp_tls_verify_certificates, GNUTLS_X509_FMT_PEM);
- if (cert_count < 0)
- {
- rc = cert_count;
- exim_gnutls_err_check(US"gnutls_certificate_set_x509_trust_file");
- }
- DEBUG(D_tls) debug_printf("Added %d certificate authorities.\n", cert_count);
- }
-
- if (setit_crl && state->tls_crl && *state->tls_crl)
- {
- if (state->exp_tls_crl && *state->exp_tls_crl)
- {
- DEBUG(D_tls) debug_printf("loading CRL file = %s\n", state->exp_tls_crl);
- rc = gnutls_certificate_set_x509_crl_file(state->x509_cred,
- CS state->exp_tls_crl, GNUTLS_X509_FMT_PEM);
- exim_gnutls_err_check(US"gnutls_certificate_set_x509_crl_file");
- }
- }
- } /* statbuf.st_size */
- } /* tls_verify_certificates */
+ DEBUG(D_tls) debug_printf("loading CRL file = %s\n", state->exp_tls_crl);
+ rc = gnutls_certificate_set_x509_crl_file(state->x509_cred,
+ CS state->exp_tls_crl, GNUTLS_X509_FMT_PEM);
+ exim_gnutls_err_check(US"gnutls_certificate_set_x509_crl_file");
+ }
+ }
return OK;
-/* also above, during verify_certificates/crl, during SNI, if unchanged */
}
+/*************************************************
+* Set X.509 state variables *
+*************************************************/
+
+/* In GnuTLS, the registered cert/key are not replaced by a later
+set of a cert/key, so for SNI support we need a whole new x509_cred
+structure. Which means various other non-re-expanded pieces of state
+need to be re-set in the new struct, so the setting logic is pulled
+out to this.
+
+Arguments:
+ state exim_gnutls_state_st *
+
+Returns: OK/DEFER/FAIL
+*/
+
+static int
+tls_set_remaining_x509(exim_gnutls_state_st *state)
+{
+int rc;
+const host_item *host = state->host; /* macro should be reconsidered? */
+
+/* Create D-H parameters, or read them from the cache file. This function does
+its own SMTP error messaging. This only happens for the server, TLS D-H ignores
+client-side params. */
+
+if (!state->host)
+ {
+ if (!dh_server_params)
+ {
+ rc = init_server_dh();
+ if (rc != OK) return rc;
+ }
+ gnutls_certificate_set_dh_params(state->x509_cred, dh_server_params);
+ }
+
+/* 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 *
*************************************************/
state->tls_verify_certificates = cas;
state->tls_crl = crl;
-rc = gnutls_certificate_allocate_credentials(&state->x509_cred);
-exim_gnutls_err_check(US"gnutls_certificate_allocate_credentials");
-
/* This handles the variables that might get re-expanded after TLS SNI;
that's tls_certificate, tls_privatekey, tls_verify_certificates, tls_crl */
rc = tls_expand_session_files(state);
if (rc != OK) return rc;
-/* Create D-H parameters, or read them from the cache file. This function does
-its own SMTP error messaging. This only happens for the server, TLS D-H ignores
-client-side params. */
-
-if (!host)
- {
- rc = init_server_dh();
- if (rc != OK) return rc;
- gnutls_certificate_set_dh_params(state->x509_cred, dh_server_params);
- }
+/* These are all other parts of the x509_cred handling, since SNI in GnuTLS
+requires a new structure afterwards. */
-/* 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");
+rc = tls_set_remaining_x509(state);
+if (rc != OK) return rc;
/* set SNI in client, only */
if (host)
}
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;
* 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)
{
DEBUG(D_tls) debug_printf("GnuTLS<%d>: %s\n", level, message);
}
+#endif
/* Called after client hello, should handle SNI work.
int rc, old_pool;
rc = gnutls_server_name_get(session, sni_name, &data_len, &sni_type, 0);
-exim_gnutls_err_debugreturn0("gnutls_server_name_get()");
+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 GNUTLS_E_APPLICATION_ERROR_MIN;
}
-rc = gnutls_credentials_set(state->session, GNUTLS_CRD_CERTIFICATE, state->x509_cred);
-return (rc == GNUTLS_E_SUCCESS) ? 0 : rc;
+rc = tls_set_remaining_x509(state);
+if (rc != OK) return GNUTLS_E_APPLICATION_ERROR_MIN;
+
+return 0;
}
Returns a random number in range [0, max-1]
*/
+#ifdef HAVE_GNUTLS_RND
int
vaguely_random_number(int max)
{
* smooth distribution and cares enough then they should submit a patch then. */
return r % max;
}
+#else /* HAVE_GNUTLS_RND */
+int
+vaguely_random_number(int max)
+{
+ return vaguely_random_number_fallback(max);
+}
+#endif /* HAVE_GNUTLS_RND */