int fd_out;
BOOL peer_cert_verified;
BOOL trigger_sni_changes;
+ BOOL have_set_peerdn;
const struct host_item *host;
uschar *peerdn;
+ uschar *ciphersuite;
uschar *received_sni;
const uschar *tls_certificate;
int xfer_buffer_hwm;
int xfer_eof;
int xfer_error;
-
- uschar cipherbuf[256];
} exim_gnutls_state_st;
static const exim_gnutls_state_st exim_gnutls_state_init = {
- NULL, NULL, NULL, VERIFY_NONE, -1, -1, FALSE, FALSE,
- NULL, NULL, NULL,
+ NULL, NULL, NULL, VERIFY_NONE, -1, -1, FALSE, FALSE, FALSE,
+ NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL,
NULL, 0, 0, 0, 0,
- ""
};
/* Not only do we have our own APIs which don't pass around state, assuming
/* 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)
static void
extract_exim_vars_from_tls_state(exim_gnutls_state_st *state)
{
-gnutls_protocol_t protocol;
gnutls_cipher_algorithm_t cipher;
-gnutls_kx_algorithm_t kx;
-gnutls_mac_algorithm_t mac;
-uschar *p;
#ifdef HAVE_GNUTLS_SESSION_CHANNEL_BINDING
int old_pool;
int rc;
/* returns size in "bytes" */
tls_bits = gnutls_cipher_get_key_size(cipher) * 8;
-if (!*state->cipherbuf)
- {
- protocol = gnutls_protocol_get_version(state->session);
- mac = gnutls_mac_get(state->session);
- kx = gnutls_kx_get(state->session);
-
- string_format(state->cipherbuf, sizeof(state->cipherbuf),
- "%s:%s:%u",
- gnutls_protocol_get_name(protocol),
- gnutls_cipher_suite_get_name(kx, cipher, mac),
- tls_bits);
-
- /* I don't see a way that spaces could occur, in the current GnuTLS
- code base, but it was a concern in the old code and perhaps older GnuTLS
- releases did return "TLS 1.0"; play it safe, just in case. */
- for (p = state->cipherbuf; *p != '\0'; ++p)
- if (isspace(*p))
- *p = '-';
- }
-tls_cipher = state->cipherbuf;
+tls_cipher = state->ciphersuite;
DEBUG(D_tls) debug_printf("cipher: %s\n", tls_cipher);
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)
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;
{
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;
}
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);
(Ustrcmp(state->exp_tls_privatekey, saved_tls_privatekey) == 0))
{
DEBUG(D_tls) debug_printf("TLS SNI: cert and key unchanged\n");
- setit = FALSE;
}
else
{
}
}
- 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));
- DEBUG(D_tls) debug_printf("TLS: cert/key registered\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 */
/* Set the trusted CAs file if one is provided, and then add the CRL if one is
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)
- {
- state->exp_tls_verify_certificates, state->exp_tls_verify_certificates,
- saved_tls_verify_certificates, saved_tls_verify_certificates);
- if (!(state->exp_tls_verify_certificates || saved_tls_verify_certificates))
- setit_vc = FALSE; /* never was set */
- else if (!state->exp_tls_verify_certificates || !saved_tls_verify_certificates)
- setit_vc = TRUE; /* changed whether set */
- else if (Ustrcmp(state->exp_tls_verify_certificates, saved_tls_verify_certificates) == 0)
- setit_vc = FALSE; /* not changed value */
-
- state->exp_tls_crl, state->exp_tls_crl,
- saved_tls_crl, saved_tls_crl);
- if (!(state->exp_tls_crl || saved_tls_crl))
- setit_crl = FALSE; /* never was set */
- else if (!state->exp_tls_crl || !saved_tls_crl)
- setit_crl = TRUE; /* changed whether set */
- else if (Ustrcmp(state->exp_tls_crl, saved_tls_crl) == 0)
- setit_crl = FALSE; /* not changed value */
- }
-
- /* nb: early exit; change if add more expansions to this function */
- if (!(setit_vc || setit_crl))
+ if (!(state->exp_tls_verify_certificates &&
+ *state->exp_tls_verify_certificates))
{
DEBUG(D_tls)
- debug_printf("TLS SNI: no change to tls_crl or tls_verify_certificates\n");
+ 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;
+ }
- 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",
+/* 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 a dir: \"%s\"\n",
state->exp_tls_verify_certificates);
- return DEFER;
- }
+ log_write(0, LOG_MAIN|LOG_PANIC,
+ "tls_verify_certificates \"%s\" is a directory",
+ 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 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. */
+if (statbuf.st_size == 0)
+ {
+ DEBUG(D_tls)
+ debug_printf("cert file empty, no certs, no verification, ignoring any CRL\n");
+ return OK;
+ }
- if (statbuf.st_size > 0)
- {
- 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);
- }
- else
- {
- DEBUG(D_tls) debug_printf("TLS SNI: tls_verify_certificates unchanged\n");
- }
+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");
- }
- }
- DEBUG(D_tls)
- if (!setit_crl) debug_printf("TLS SNI: tls_crl unchanged\n");
- } /* statbuf.st_size */
- } /* tls_verify_certificates */
+if (state->tls_crl && *state->tls_crl &&
+ state->exp_tls_crl && *state->exp_tls_crl)
+ {
+ DEBUG(D_tls) debug_printf("loading CRL file = %s\n", state->exp_tls_crl);
+ cert_count = gnutls_certificate_set_x509_crl_file(state->x509_cred,
+ CS state->exp_tls_crl, GNUTLS_X509_FMT_PEM);
+ if (cert_count < 0)
+ {
+ rc = cert_count;
+ exim_gnutls_err_check(US"gnutls_certificate_set_x509_crl_file");
+ }
+ DEBUG(D_tls) debug_printf("Processed %d CRLs.\n", cert_count);
+ }
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_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;
-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);
- }
-
-/* Link the credentials to the session. */
+/* These are all other parts of the x509_cred handling, since SNI in GnuTLS
+requires a new structure afterwards. */
-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;
*************************************************/
/* Called from both server and client code.
-Only this is allowed to set state->peerdn and we use that to detect double-calls.
+Only this is allowed to set state->peerdn and state->have_set_peerdn
+and we use that to detect double-calls.
+
+NOTE: the state blocks last while the TLS connection is up, which is fine
+for logging in the server side, but for the client side, we log after teardown
+in src/deliver.c. While the session is up, we can twist about states and
+repoint tls_* globals, but those variables used for logging or other variable
+expansion that happens _after_ delivery need to have a longer life-time.
+
+So for those, we get the data from POOL_PERM; the re-invoke guard keeps us from
+doing this more than once per generation of a state context. We set them in
+the state context, and repoint tls_* to them. After the state goes away, the
+tls_* copies of the pointers remain valid and client delivery logging is happy.
+
+tls_certificate_verified is a BOOL, so the tls_peerdn and tls_cipher issues
+don't apply.
Arguments:
state exim_gnutls_state_st *
static int
peer_status(exim_gnutls_state_st *state)
{
+uschar cipherbuf[256];
const gnutls_datum *cert_list;
-int rc;
+int old_pool, rc;
unsigned int cert_list_size = 0;
+gnutls_protocol_t protocol;
+gnutls_cipher_algorithm_t cipher;
+gnutls_kx_algorithm_t kx;
+gnutls_mac_algorithm_t mac;
gnutls_certificate_type_t ct;
gnutls_x509_crt_t crt;
-uschar *dn_buf;
+uschar *p, *dn_buf;
size_t sz;
-if (state->peerdn)
+if (state->have_set_peerdn)
return OK;
+state->have_set_peerdn = TRUE;
+
+state->peerdn = NULL;
-state->peerdn = US"unknown";
+/* tls_cipher */
+cipher = gnutls_cipher_get(state->session);
+protocol = gnutls_protocol_get_version(state->session);
+mac = gnutls_mac_get(state->session);
+kx = gnutls_kx_get(state->session);
+
+string_format(cipherbuf, sizeof(cipherbuf),
+ "%s:%s:%d",
+ gnutls_protocol_get_name(protocol),
+ gnutls_cipher_suite_get_name(kx, cipher, mac),
+ (int) gnutls_cipher_get_key_size(cipher) * 8);
+
+/* I don't see a way that spaces could occur, in the current GnuTLS
+code base, but it was a concern in the old code and perhaps older GnuTLS
+releases did return "TLS 1.0"; play it safe, just in case. */
+for (p = cipherbuf; *p != '\0'; ++p)
+ if (isspace(*p))
+ *p = '-';
+old_pool = store_pool;
+store_pool = POOL_PERM;
+state->ciphersuite = string_copy(cipherbuf);
+store_pool = old_pool;
+tls_cipher = state->ciphersuite;
+/* tls_peerdn */
cert_list = gnutls_certificate_get_peers(state->session, &cert_list_size);
if (cert_list == NULL || cert_list_size == 0)
{
- state->peerdn = US"unknown (no certificate)";
DEBUG(D_tls) debug_printf("TLS: no certificate from peer (%p & %d)\n",
cert_list, cert_list_size);
if (state->verify_requirement == VERIFY_REQUIRED)
if (ct != GNUTLS_CRT_X509)
{
const char *ctn = gnutls_certificate_type_get_name(ct);
- state->peerdn = string_sprintf("unknown (type %s)", ctn);
DEBUG(D_tls)
debug_printf("TLS: peer cert not X.509 but instead \"%s\"\n", ctn);
if (state->verify_requirement == VERIFY_REQUIRED)
DEBUG(D_tls)
debug_printf("TLS certificate verification failed (%s): peerdn=%s\n",
- *error, state->peerdn);
+ *error, state->peerdn ? state->peerdn : US"<unset>");
if (state->verify_requirement == VERIFY_REQUIRED)
{
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
{
state->peer_cert_verified = TRUE;
- DEBUG(D_tls) debug_printf("TLS certificate verified: peerdn=%s\n", state->peerdn);
+ DEBUG(D_tls) debug_printf("TLS certificate verified: peerdn=%s\n",
+ state->peerdn ? state->peerdn : US"<unset>");
}
tls_peerdn = state->peerdn;
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
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;
}
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)
+ return tls_error(US"gnutls_handshake",
+ sigalrm_seen ? "timed out" : gnutls_strerror(rc), state->host);
+
DEBUG(D_tls) debug_printf("gnutls_handshake was successful\n");
/* Verify late */
rc = peer_status(state);
if (rc != OK) return rc;
-/* Sets various Exim expansion variables; always safe within server */
+/* Sets various Exim expansion variables; may need to adjust for ACL callouts */
extract_exim_vars_from_tls_state(state);
+/*************************************************
+* 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. *
*************************************************/