* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2017 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* Copyright (c) Phil Pennock 2012 */
#include <gnutls/x509.h>
/* man-page is incorrect, gnutls_rnd() is not in gnutls.h: */
#include <gnutls/crypto.h>
+
/* needed to disable PKCS11 autoload unless requested */
#if GNUTLS_VERSION_NUMBER >= 0x020c00
# include <gnutls/pkcs11.h>
#if GNUTLS_VERSION_NUMBER >= 0x030014
# define SUPPORT_SYSDEFAULT_CABUNDLE
#endif
+#if GNUTLS_VERSION_NUMBER >= 0x030104
+# define GNUTLS_CERT_VFY_STATUS_PRINT
+#endif
#if GNUTLS_VERSION_NUMBER >= 0x030109
# define SUPPORT_CORK
#endif
+#if GNUTLS_VERSION_NUMBER >= 0x03010a
+# define SUPPORT_GNUTLS_SESS_DESC
+#endif
+#if GNUTLS_VERSION_NUMBER >= 0x030500
+# define SUPPORT_GNUTLS_KEYLOG
+#endif
#if GNUTLS_VERSION_NUMBER >= 0x030506 && !defined(DISABLE_OCSP)
# define SUPPORT_SRV_OCSP_STACK
#endif
-#if GNUTLS_VERSION_NUMBER >= 0x030000 && defined(EXPERIMENTAL_DANE)
-# define SUPPORT_DANE
-# define DANESSL_USAGE_DANE_TA 2
-# define DANESSL_USAGE_DANE_EE 3
-#endif
-#if GNUTLS_VERSION_NUMBER < 0x999999 && defined(EXPERIMENTAL_DANE)
-# define GNUTLS_BROKEN_DANE_VALIDATION
+
+#ifdef SUPPORT_DANE
+# if GNUTLS_VERSION_NUMBER >= 0x030000
+# define DANESSL_USAGE_DANE_TA 2
+# define DANESSL_USAGE_DANE_EE 3
+# else
+# error GnuTLS version too early for DANE
+# endif
+# if GNUTLS_VERSION_NUMBER < 0x999999
+# define GNUTLS_BROKEN_DANE_VALIDATION
+# endif
#endif
#ifndef DISABLE_OCSP
# include <gnutls/dane.h>
#endif
+#include "tls-cipher-stdname.c"
+
+
/* GnuTLS 2 vs 3
GnuTLS 3 only:
BOOL peer_dane_verified;
BOOL trigger_sni_changes;
BOOL have_set_peerdn;
- const struct host_item *host;
- gnutls_x509_crt_t peercert;
+ const struct host_item *host; /* NULL if server */
+ gnutls_x509_crt_t peercert;
uschar *peerdn;
uschar *ciphersuite;
uschar *received_sni;
uschar *xfer_buffer;
int xfer_buffer_lwm;
int xfer_buffer_hwm;
- int xfer_eof;
- int xfer_error;
+ BOOL xfer_eof; /*XXX never gets set! */
+ BOOL xfer_error;
} exim_gnutls_state_st;
static const exim_gnutls_state_st exim_gnutls_state_init = {
.xfer_buffer = NULL,
.xfer_buffer_lwm = 0,
.xfer_buffer_hwm = 0,
- .xfer_eof = 0,
- .xfer_error = 0,
+ .xfer_eof = FALSE,
+ .xfer_error = FALSE,
};
/* Not only do we have our own APIs which don't pass around state, assuming
XXX But see gnutls_session_get_ptr()
*/
-static exim_gnutls_state_st state_server, state_client;
+static exim_gnutls_state_st state_server;
/* dh_params are initialised once within the lifetime of a process using TLS;
if we used TLS in a long-lived daemon, we'd have to reconsider this. But we
static const int ssl_session_timeout = 200;
-static const char * const exim_default_gnutls_priority = "NORMAL";
+static const uschar * const exim_default_gnutls_priority = US"NORMAL";
/* Guard library core initialisation */
# define EXIM_SERVER_DH_BITS_PRE2_12 1024
#endif
-#define exim_gnutls_err_check(rc, Label) do { \
- if ((rc) != GNUTLS_E_SUCCESS) \
- return tls_error((Label), gnutls_strerror(rc), host, errstr); \
- } while (0)
-
#define expand_check_tlsvar(Varname, errstr) \
expand_check(state->Varname, US #Varname, &state->exp_##Varname, errstr)
*/
static int
-tls_error(const uschar *prefix, const char *msg, const host_item *host,
+tls_error(const uschar *prefix, const uschar *msg, const host_item *host,
uschar ** errstr)
{
if (errstr)
- *errstr = string_sprintf("(%s)%s%s", prefix, msg ? ": " : "", msg ? msg : "");
+ *errstr = string_sprintf("(%s)%s%s", prefix, msg ? ": " : "", msg ? msg : US"");
return host ? FAIL : DEFER;
}
+static int
+tls_error_gnu(const uschar *prefix, int err, const host_item *host,
+ uschar ** errstr)
+{
+return tls_error(prefix, US gnutls_strerror(err), host, errstr);
+}
+
+static int
+tls_error_sys(const uschar *prefix, int err, const host_item *host,
+ uschar ** errstr)
+{
+return tls_error(prefix, US strerror(err), host, errstr);
+}
/*************************************************
state the current GnuTLS exim state container
rc the GnuTLS error code, or 0 if it's a local error
when text identifying read or write
- text local error text when ec is 0
+ text local error text when rc is 0
Returns: nothing
*/
static void
record_io_error(exim_gnutls_state_st *state, int rc, uschar *when, uschar *text)
{
-const char * msg;
+const uschar * msg;
uschar * errstr;
if (rc == GNUTLS_E_FATAL_ALERT_RECEIVED)
- msg = CS string_sprintf("%s: %s", US gnutls_strerror(rc),
+ msg = string_sprintf("A TLS fatal alert has been received: %s",
US gnutls_alert_get_name(gnutls_alert_get(state->session)));
else
- msg = gnutls_strerror(rc);
+ msg = US gnutls_strerror(rc);
(void) tls_error(when, msg, state->host, &errstr);
#endif
tls_support * tlsp = state->tlsp;
-tlsp->active = state->fd_out;
+tlsp->active.sock = state->fd_out;
+tlsp->active.tls_ctx = state;
cipher = gnutls_cipher_get(state->session);
/* returns size in "bytes" */
#ifdef HAVE_GNUTLS_SESSION_CHANNEL_BINDING
channel.data = NULL;
channel.size = 0;
-rc = gnutls_session_channel_binding(state->session, GNUTLS_CB_TLS_UNIQUE, &channel);
-if (rc) {
- DEBUG(D_tls) debug_printf("Channel binding error: %s\n", gnutls_strerror(rc));
-} else {
+if ((rc = gnutls_session_channel_binding(state->session, GNUTLS_CB_TLS_UNIQUE, &channel)))
+ { DEBUG(D_tls) debug_printf("Channel binding error: %s\n", gnutls_strerror(rc)); }
+else
+ {
old_pool = store_pool;
store_pool = POOL_PERM;
- tls_channelbinding_b64 = b64encode(channel.data, (int)channel.size);
+ tls_channelbinding_b64 = b64encode(CUS channel.data, (int)channel.size);
store_pool = old_pool;
DEBUG(D_tls) debug_printf("Have channel bindings cached for possible auth usage.\n");
-}
+ }
#endif
/* peercert is set in peer_status() */
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(rc, US"gnutls_dh_params_init");
+if ((rc = gnutls_dh_params_init(&dh_server_params)))
+ return tls_error_gnu(US"gnutls_dh_params_init", rc, host, errstr);
m.data = NULL;
m.size = 0;
else if (exp_tls_dhparam[0] != '/')
{
if (!(m.data = US std_dh_prime_named(exp_tls_dhparam)))
- return tls_error(US"No standard prime named", CS exp_tls_dhparam, NULL, errstr);
+ return tls_error(US"No standard prime named", exp_tls_dhparam, NULL, errstr);
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(rc, US"gnutls_dh_params_import_pkcs3");
+ if ((rc = gnutls_dh_params_import_pkcs3(dh_server_params, &m, GNUTLS_X509_FMT_PEM)))
+ return tls_error_gnu(US"gnutls_dh_params_import_pkcs3", rc, host, errstr);
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 = gnutls_sec_param_to_pk_bits(GNUTLS_PK_DH, GNUTLS_SEC_PARAM_NORMAL);
-if (!dh_bits)
+
+if (!(dh_bits = gnutls_sec_param_to_pk_bits(GNUTLS_PK_DH, GNUTLS_SEC_PARAM_NORMAL)))
return tls_error(US"gnutls_sec_param_to_pk_bits() failed", NULL, NULL, errstr);
DEBUG(D_tls)
debug_printf("GnuTLS tells us that for D-H PK, NORMAL is %d bits.\n",
{
saved_errno = errno;
(void)close(fd);
- return tls_error(US"TLS cache stat failed", strerror(saved_errno), NULL, errstr);
+ return tls_error_sys(US"TLS cache stat failed", saved_errno, NULL, errstr);
}
if (!S_ISREG(statbuf.st_mode))
{
{
saved_errno = errno;
(void)close(fd);
- return tls_error(US"fdopen(TLS cache stat fd) failed",
- strerror(saved_errno), NULL, errstr);
+ return tls_error_sys(US"fdopen(TLS cache stat fd) failed",
+ saved_errno, NULL, errstr);
}
m.size = statbuf.st_size;
if (!(m.data = malloc(m.size)))
{
fclose(fp);
- return tls_error(US"malloc failed", strerror(errno), NULL, errstr);
+ return tls_error_sys(US"malloc failed", errno, NULL, errstr);
}
if (!(sz = fread(m.data, m.size, 1, fp)))
{
saved_errno = errno;
fclose(fp);
free(m.data);
- return tls_error(US"fread failed", strerror(saved_errno), NULL, errstr);
+ return tls_error_sys(US"fread failed", saved_errno, NULL, errstr);
}
fclose(fp);
rc = gnutls_dh_params_import_pkcs3(dh_server_params, &m, GNUTLS_X509_FMT_PEM);
free(m.data);
- exim_gnutls_err_check(rc, US"gnutls_dh_params_import_pkcs3");
+ if (rc)
+ return tls_error_gnu(US"gnutls_dh_params_import_pkcs3", rc, host, errstr);
DEBUG(D_tls) debug_printf("read D-H parameters from file \"%s\"\n", filename);
}
if ((PATH_MAX - Ustrlen(filename)) < 10)
return tls_error(US"Filename too long to generate replacement",
- CS filename, NULL, errstr);
+ filename, NULL, errstr);
- temp_fn = string_copy(US "%s.XXXXXXX");
+ temp_fn = string_copy(US"%s.XXXXXXX");
if ((fd = mkstemp(CS temp_fn)) < 0) /* modifies temp_fn */
- return tls_error(US"Unable to open temp file", strerror(errno), NULL, errstr);
- (void)fchown(fd, exim_uid, exim_gid); /* Probably not necessary */
+ return tls_error_sys(US"Unable to open temp file", errno, NULL, errstr);
+ (void)exim_chown(temp_fn, exim_uid, exim_gid); /* Probably not necessary */
/* GnuTLS overshoots!
* If we ask for 2236, we might get 2237 or more.
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(rc, US"gnutls_dh_params_generate2");
+ if ((rc = gnutls_dh_params_generate2(dh_server_params, dh_bits_gen)))
+ return tls_error_gnu(US"gnutls_dh_params_generate2", rc, host, errstr);
/* 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
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(rc, US"gnutls_dh_params_export_pkcs3(NULL) sizing");
+ if ( (rc = gnutls_dh_params_export_pkcs3(dh_server_params,
+ GNUTLS_X509_FMT_PEM, m.data, &sz))
+ && rc != GNUTLS_E_SHORT_MEMORY_BUFFER)
+ return tls_error_gnu(US"gnutls_dh_params_export_pkcs3(NULL) sizing",
+ rc, host, errstr);
m.size = sz;
if (!(m.data = malloc(m.size)))
- return tls_error(US"memory allocation failed", strerror(errno), NULL, errstr);
+ return tls_error_sys(US"memory allocation failed", errno, NULL, errstr);
/* 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)
+ if ((rc = gnutls_dh_params_export_pkcs3(dh_server_params, GNUTLS_X509_FMT_PEM,
+ m.data, &sz)))
{
free(m.data);
- exim_gnutls_err_check(rc, US"gnutls_dh_params_export_pkcs3() real");
+ return tls_error_gnu(US"gnutls_dh_params_export_pkcs3() real", rc, host, errstr);
}
m.size = sz; /* shrink by 1, probably */
if ((sz = write_to_fd_buf(fd, m.data, (size_t) m.size)) != m.size)
{
free(m.data);
- return tls_error(US"TLS cache write D-H params failed",
- strerror(errno), NULL, errstr);
+ return tls_error_sys(US"TLS cache write D-H params failed",
+ errno, NULL, errstr);
}
free(m.data);
if ((sz = write_to_fd_buf(fd, US"\n", 1)) != 1)
- return tls_error(US"TLS cache write D-H params final newline failed",
- strerror(errno), NULL, errstr);
+ return tls_error_sys(US"TLS cache write D-H params final newline failed",
+ errno, NULL, errstr);
if ((rc = close(fd)))
- return tls_error(US"TLS cache write close() failed", strerror(errno), NULL, errstr);
+ return tls_error_sys(US"TLS cache write close() failed", errno, NULL, errstr);
if (Urename(temp_fn, filename) < 0)
- return tls_error(string_sprintf("failed to rename \"%s\" as \"%s\"",
- temp_fn, filename), strerror(errno), NULL, errstr);
+ return tls_error_sys(string_sprintf("failed to rename \"%s\" as \"%s\"",
+ temp_fn, filename), errno, NULL, errstr);
DEBUG(D_tls) debug_printf("wrote D-H parameters to file \"%s\"\n", filename);
}
where = US"generating pkey";
if ((rc = gnutls_x509_privkey_generate(pkey, GNUTLS_PK_RSA,
#ifdef SUPPORT_PARAM_TO_PK_BITS
- gnutls_sec_param_to_pk_bits(GNUTLS_PK_RSA, GNUTLS_SEC_PARAM_LOW),
+# ifndef GNUTLS_SEC_PARAM_MEDIUM
+# define GNUTLS_SEC_PARAM_MEDIUM GNUTLS_SEC_PARAM_HIGH
+# endif
+ gnutls_sec_param_to_pk_bits(GNUTLS_PK_RSA, GNUTLS_SEC_PARAM_MEDIUM),
#else
- 1024,
+ 2048,
#endif
0)))
goto err;
where = US"configuring cert";
-now = 0;
+now = 1;
if ( (rc = gnutls_x509_crt_set_version(cert, 3))
|| (rc = gnutls_x509_crt_set_serial(cert, &now, sizeof(now)))
|| (rc = gnutls_x509_crt_set_activation_time(cert, now = time(NULL)))
return rc;
err:
- rc = tls_error(where, gnutls_strerror(rc), NULL, errstr);
+ rc = tls_error_gnu(where, rc, NULL, errstr);
goto out;
}
int rc = gnutls_certificate_set_x509_key_file(state->x509_cred,
CS certfile, CS keyfile, GNUTLS_X509_FMT_PEM);
if (rc < 0)
- return tls_error(
+ return tls_error_gnu(
string_sprintf("cert/key setup: cert=%s key=%s", certfile, keyfile),
- gnutls_strerror(rc), host, errstr);
+ rc, host, errstr);
return -rc;
}
saved_tls_crl = state->exp_tls_crl;
}
-rc = gnutls_certificate_allocate_credentials(&state->x509_cred);
-exim_gnutls_err_check(rc, US"gnutls_certificate_allocate_credentials");
+if ((rc = gnutls_certificate_allocate_credentials(&state->x509_cred)))
+ return tls_error_gnu(US"gnutls_certificate_allocate_credentials",
+ rc, host, errstr);
#ifdef SUPPORT_SRV_OCSP_STACK
gnutls_certificate_set_flags(state->x509_cred, GNUTLS_CERTIFICATE_API_V2);
or watch datestamp. */
# ifdef SUPPORT_SRV_OCSP_STACK
- rc = gnutls_certificate_set_ocsp_status_request_function2(
- state->x509_cred, gnutls_cert_index,
- server_ocsp_stapling_cb, ofile);
-
- exim_gnutls_err_check(rc,
- US"gnutls_certificate_set_ocsp_status_request_function2");
+ if ((rc = gnutls_certificate_set_ocsp_status_request_function2(
+ state->x509_cred, gnutls_cert_index,
+ server_ocsp_stapling_cb, ofile)))
+ return tls_error_gnu(
+ US"gnutls_certificate_set_ocsp_status_request_function2",
+ rc, host, errstr);
# else
if (cnt++ > 0)
{
}
if (cert_count < 0)
- {
- rc = cert_count;
- exim_gnutls_err_check(rc, US"setting certificate trust");
- }
-DEBUG(D_tls) debug_printf("Added %d certificate authorities.\n", cert_count);
+ return tls_error_gnu(US"setting certificate trust", cert_count, host, errstr);
+DEBUG(D_tls)
+ debug_printf("Added %d certificate authorities.\n", cert_count);
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(rc, US"gnutls_certificate_set_x509_crl_file");
- }
+ if ((cert_count = gnutls_certificate_set_x509_crl_file(state->x509_cred,
+ CS state->exp_tls_crl, GNUTLS_X509_FMT_PEM)) < 0)
+ return tls_error_gnu(US"gnutls_certificate_set_x509_crl_file",
+ cert_count, host, errstr);
+
DEBUG(D_tls) debug_printf("Processed %d CRLs.\n", cert_count);
}
if (!state->host)
{
if (!dh_server_params)
- {
- rc = init_server_dh(errstr);
- if (rc != OK) return rc;
- }
+ if ((rc = init_server_dh(errstr)) != 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(rc, US"gnutls_credentials_set");
+if ((rc = gnutls_credentials_set(state->session,
+ GNUTLS_CRD_CERTIFICATE, state->x509_cred)))
+ return tls_error_gnu(US"gnutls_credentials_set", rc, host, errstr);
return OK;
}
const uschar *crl,
const uschar *require_ciphers,
exim_gnutls_state_st **caller_state,
+ tls_support * tlsp,
uschar ** errstr)
{
-exim_gnutls_state_st *state;
+exim_gnutls_state_st * state;
int rc;
size_t sz;
-const char *errpos;
-uschar *p;
-BOOL want_default_priorities;
+const char * errpos;
+const uschar * p;
if (!exim_gnutls_base_init_done)
{
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(rc, US"gnutls_pkcs11_init");
- }
+ if ((rc = gnutls_pkcs11_init(GNUTLS_PKCS11_FLAG_MANUAL, NULL)))
+ return tls_error_gnu(US"gnutls_pkcs11_init", rc, host, errstr);
#endif
- rc = gnutls_global_init();
- exim_gnutls_err_check(rc, US"gnutls_global_init");
+ if ((rc = gnutls_global_init()))
+ return tls_error_gnu(US"gnutls_global_init", rc, host, errstr);
#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 */
+ /* arbitrarily chosen level; bump up to 9 for more */
gnutls_global_set_log_level(EXIM_GNUTLS_LIBRARY_LOG_LEVEL);
}
#endif
if (host)
{
- state = &state_client;
+ /* For client-side sessions we allocate a context. This lets us run
+ several in parallel. */
+ int old_pool = store_pool;
+ store_pool = POOL_PERM;
+ state = store_get(sizeof(exim_gnutls_state_st));
+ store_pool = old_pool;
+
memcpy(state, &exim_gnutls_state_init, sizeof(exim_gnutls_state_init));
- state->tlsp = &tls_out;
+ state->tlsp = tlsp;
DEBUG(D_tls) debug_printf("initialising GnuTLS client session\n");
rc = gnutls_init(&state->session, GNUTLS_CLIENT);
}
{
state = &state_server;
memcpy(state, &exim_gnutls_state_init, sizeof(exim_gnutls_state_init));
- state->tlsp = &tls_in;
+ state->tlsp = tlsp;
DEBUG(D_tls) debug_printf("initialising GnuTLS server session\n");
rc = gnutls_init(&state->session, GNUTLS_SERVER);
}
-exim_gnutls_err_check(rc, US"gnutls_init");
+if (rc)
+ return tls_error_gnu(US"gnutls_init", rc, host, errstr);
state->host = host;
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(rc, US"gnutls_server_name_set");
+ if ((rc = gnutls_server_name_set(state->session,
+ GNUTLS_NAME_DNS, state->tlsp->sni, sz)))
+ return tls_error_gnu(US"gnutls_server_name_set", rc, host, errstr);
}
}
else if (state->tls_sni)
This was backwards incompatible, but means Exim no longer needs to track
all algorithms and provide string forms for them. */
-want_default_priorities = TRUE;
-
+p = NULL;
if (state->tls_require_ciphers && *state->tls_require_ciphers)
{
if (!expand_check_tlsvar(tls_require_ciphers, errstr))
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;
+ DEBUG(D_tls) debug_printf("GnuTLS session cipher/priority \"%s\"\n", p);
}
}
-if (want_default_priorities)
+if (!p)
{
+ p = exim_default_gnutls_priority;
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;
+ debug_printf("GnuTLS using default session cipher/priority \"%s\"\n", p);
}
-exim_gnutls_err_check(rc, string_sprintf(
- "gnutls_priority_init(%s) failed at offset %ld, \"%.6s..\"",
- p, errpos - CS p, errpos));
+if ((rc = gnutls_priority_init(&state->priority_cache, CCS p, &errpos)))
+ return tls_error_gnu(string_sprintf(
+ "gnutls_priority_init(%s) failed at offset %ld, \"%.6s..\"",
+ p, errpos - CS p, errpos),
+ rc, host, errstr);
-rc = gnutls_priority_set(state->session, state->priority_cache);
-exim_gnutls_err_check(rc, US"gnutls_priority_set");
+if ((rc = gnutls_priority_set(state->session, state->priority_cache)))
+ return tls_error_gnu(US"gnutls_priority_set", rc, host, errstr);
gnutls_db_set_cache_expiration(state->session, ssl_session_timeout);
* Extract peer information *
*************************************************/
+static const uschar *
+cipher_stdname_kcm(gnutls_kx_algorithm_t kx, gnutls_cipher_algorithm_t cipher,
+ gnutls_mac_algorithm_t mac)
+{
+uschar cs_id[2];
+gnutls_kx_algorithm_t kx_i;
+gnutls_cipher_algorithm_t cipher_i;
+gnutls_mac_algorithm_t mac_i;
+
+for (size_t i = 0;
+ gnutls_cipher_suite_info(i, cs_id, &kx_i, &cipher_i, &mac_i, NULL);
+ i++)
+ if (kx_i == kx && cipher_i == cipher && mac_i == mac)
+ return cipher_stdname(cs_id[0], cs_id[1]);
+return NULL;
+}
+
+
+
/* Called from both server and client code.
Only this is allowed to set state->peerdn and state->have_set_peerdn
and we use that to detect double-calls.
static int
peer_status(exim_gnutls_state_st *state, uschar ** errstr)
{
-uschar cipherbuf[256];
const gnutls_datum_t *cert_list;
int old_pool, rc;
unsigned int cert_list_size = 0;
gnutls_mac_algorithm_t mac;
gnutls_certificate_type_t ct;
gnutls_x509_crt_t crt;
-uschar *p, *dn_buf;
+uschar *dn_buf;
size_t sz;
if (state->have_set_peerdn)
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 = POOL_PERM;
+ state->ciphersuite = string_sprintf("%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 (uschar * p = state->ciphersuite; *p; p++) if (isspace(*p)) *p = '-';
+ state->tlsp->cipher = state->ciphersuite;
+
+ state->tlsp->cipher_stdname = cipher_stdname_kcm(kx, cipher, mac);
+ }
store_pool = old_pool;
-state->tlsp->cipher = state->ciphersuite;
/* tls_peerdn */
cert_list = gnutls_certificate_get_peers(state->session, &cert_list_size);
-if (cert_list == NULL || cert_list_size == 0)
+if (!cert_list || cert_list_size == 0)
{
DEBUG(D_tls) debug_printf("TLS: no certificate from peer (%p & %d)\n",
cert_list, cert_list_size);
if (state->verify_requirement >= VERIFY_REQUIRED)
return tls_error(US"certificate verification failed",
- "no certificate received from peer", state->host, errstr);
+ US"no certificate received from peer", state->host, errstr);
return OK;
}
-ct = gnutls_certificate_type_get(state->session);
-if (ct != GNUTLS_CRT_X509)
+if ((ct = gnutls_certificate_type_get(state->session)) != GNUTLS_CRT_X509)
{
- const char *ctn = gnutls_certificate_type_get_name(ct);
+ const uschar * ctn = US gnutls_certificate_type_get_name(ct);
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: peer cert problem: %s: %s\n", \
(Label), gnutls_strerror(rc)); \
if (state->verify_requirement >= VERIFY_REQUIRED) \
- return tls_error((Label), gnutls_strerror(rc), state->host, errstr); \
+ return tls_error_gnu((Label), rc, state->host, errstr); \
return OK; \
} \
} while (0)
if (state->verify_requirement == VERIFY_NONE)
return TRUE;
+DEBUG(D_tls) debug_printf("TLS: checking peer certificate\n");
*errstr = NULL;
-if ((rc = peer_status(state, errstr)) != OK)
+if ((rc = peer_status(state, errstr)) != OK || !state->peerdn)
{
verify = GNUTLS_CERT_INVALID;
*errstr = US"certificate not supplied";
# ifdef GNUTLS_BROKEN_DANE_VALIDATION
/* Split the TLSA records into two sets, TA and EE selectors. Run the
dane-verification separately so that we know which selector verified;
- then we know whether to do CA-chain-verification and name-verification
- (needed for TA but not EE). */
+ then we know whether to do name-verification (needed for TA but not EE). */
if (usage == ((1<<DANESSL_USAGE_DANE_TA) | (1<<DANESSL_USAGE_DANE_EE)))
- { /* a mixed-usage bundle */
+ { /* a mixed-usage bundle */
int i, j, nrec;
const char ** dd;
int * ddl;
1, 0))
|| (rc = dane_verify_crt_raw(s, certlist, lsize,
gnutls_certificate_type_get(state->session),
- r, 0,
+ r, 0,
# ifdef GNUTLS_BROKEN_DANE_VALIDATION
usage == (1 << DANESSL_USAGE_DANE_EE)
? DANE_VFLAG_ONLY_CHECK_EE_USAGE : 0,
*errstr = US str.data; /* don't bother to free */
goto badcert;
}
- state->peer_dane_verified = TRUE;
# ifdef GNUTLS_BROKEN_DANE_VALIDATION
/* If a TA-mode TLSA record was used for verification we must additionally
- verify the CA chain and the cert name. For EE-mode, skip it. */
+ verify the cert name (but not the CA chain). For EE-mode, skip it. */
if (usage & (1 << DANESSL_USAGE_DANE_EE))
# endif
{
- state->peer_cert_verified = TRUE;
+ state->peer_dane_verified = state->peer_cert_verified = TRUE;
+ goto goodcert;
+ }
+# ifdef GNUTLS_BROKEN_DANE_VALIDATION
+ /* Assume that the name on the A-record is the one that should be matching
+ the cert. An alternate view is that the domain part of the email address
+ is also permissible. */
+
+ if (gnutls_x509_crt_check_hostname(state->tlsp->peercert,
+ CS state->host->name))
+ {
+ state->peer_dane_verified = state->peer_cert_verified = TRUE;
goto goodcert;
}
+# endif
}
-#endif
+#endif /*SUPPORT_DANE*/
rc = gnutls_certificate_verify_peers2(state->session, &verify);
}
{
state->peer_cert_verified = FALSE;
if (!*errstr)
+ {
+#ifdef GNUTLS_CERT_VFY_STATUS_PRINT
+ DEBUG(D_tls)
+ {
+ gnutls_datum_t txt;
+
+ if (gnutls_certificate_verification_status_print(verify,
+ gnutls_certificate_type_get(state->session), &txt, 0)
+ == GNUTLS_E_SUCCESS)
+ {
+ debug_printf("%s\n", txt.data);
+ gnutls_free(txt.data);
+ }
+ }
+#endif
*errstr = verify & GNUTLS_CERT_REVOKED
? US"certificate revoked" : US"certificate invalid";
+ }
DEBUG(D_tls)
debug_printf("TLS certificate verification failed (%s): peerdn=\"%s\"\n",
else
{
- if (state->exp_tls_verify_cert_hostnames)
+ /* Client side, check the server's certificate name versus the name on the
+ A-record for the connection we made. What to do for server side - what name
+ to use for client? We document that there is no such checking for server
+ side. */
+
+ if ( state->exp_tls_verify_cert_hostnames
+ && !gnutls_x509_crt_check_hostname(state->tlsp->peercert,
+ CS state->exp_tls_verify_cert_hostnames)
+ )
{
- int sep = 0;
- const uschar * list = state->exp_tls_verify_cert_hostnames;
- uschar * name;
- while ((name = string_nextinlist(&list, &sep, NULL, 0)))
- if (gnutls_x509_crt_check_hostname(state->tlsp->peercert, CS name))
- break;
- if (!name)
- {
- DEBUG(D_tls)
- debug_printf("TLS certificate verification failed: cert name mismatch\n");
- if (state->verify_requirement >= VERIFY_REQUIRED)
- goto badcert;
- return TRUE;
- }
+ DEBUG(D_tls)
+ debug_printf("TLS certificate verification failed: cert name mismatch\n");
+ if (state->verify_requirement >= VERIFY_REQUIRED)
+ goto badcert;
+ return TRUE;
}
+
state->peer_cert_verified = TRUE;
DEBUG(D_tls) debug_printf("TLS certificate verified: peerdn=\"%s\"\n",
state->peerdn ? state->peerdn : US"<unset>");
#ifdef SUPPORT_DANE
tlsa_prob:
- *errstr = string_sprintf("TLSA record problem: %s", dane_strerror(rc));
+ *errstr = string_sprintf("TLSA record problem: %s",
+ rc == DANE_E_REQUESTED_DATA_NOT_AVAILABLE ? "none usable" : dane_strerror(rc));
#endif
badcert:
uschar * yield;
exim_gnutls_state_st * state = gnutls_session_get_ptr(session);
-cert_list = gnutls_certificate_get_peers(session, &cert_list_size);
-if (cert_list)
+if ((cert_list = gnutls_certificate_get_peers(session, &cert_list_size)))
while (cert_list_size--)
{
- rc = import_cert(&cert_list[cert_list_size], &crt);
- if (rc != GNUTLS_E_SUCCESS)
+ if ((rc = import_cert(&cert_list[cert_list_size], &crt)) != GNUTLS_E_SUCCESS)
{
DEBUG(D_tls) debug_printf("TLS: peer cert problem: depth %d: %s\n",
cert_list_size, gnutls_strerror(rc));
#endif
+static gstring *
+ddump(gnutls_datum_t * d)
+{
+gstring * g = string_get((d->size+1) * 2);
+uschar * s = d->data;
+for (unsigned i = d->size; i > 0; i--, s++)
+ {
+ g = string_catn(g, US "0123456789abcdef" + (*s >> 4), 1);
+ g = string_catn(g, US "0123456789abcdef" + (*s & 0xf), 1);
+ }
+return g;
+}
/* ------------------------------------------------------------------------ */
/* Exported functions */
exim_gnutls_state_st * state = NULL;
/* Check for previous activation */
-if (tls_in.active >= 0)
+if (tls_in.active.sock >= 0)
{
- tls_error(US"STARTTLS received after TLS started", "", NULL, errstr);
+ tls_error(US"STARTTLS received after TLS started", US "", NULL, errstr);
smtp_printf("554 Already in TLS\r\n", FALSE);
return FAIL;
}
if ((rc = tls_init(NULL, tls_certificate, tls_privatekey,
NULL, tls_verify_certificates, tls_crl,
- require_ciphers, &state, errstr)) != OK) return rc;
+ require_ciphers, &state, &tls_in, errstr)) != OK) return rc;
/* If this is a host for which certificate verification is mandatory or
optional, set up appropriately. */
}
/* Now negotiate the TLS session. We put our own timer on it, since it seems
-that the GnuTLS library doesn't. */
+that the GnuTLS library doesn't.
+From 3.1.0 there is gnutls_handshake_set_timeout() - but it requires you
+to set (and clear down afterwards) up a pull-timeout callback function that does
+a select, so we're no better off unless avoiding signals becomes an issue. */
gnutls_transport_set_ptr2(state->session,
(gnutls_transport_ptr_t)(long) fileno(smtp_in),
state->fd_out = fileno(smtp_out);
sigalrm_seen = FALSE;
-if (smtp_receive_timeout > 0) alarm(smtp_receive_timeout);
+if (smtp_receive_timeout > 0) ALARM(smtp_receive_timeout);
do
rc = gnutls_handshake(state->session);
while (rc == GNUTLS_E_AGAIN || rc == GNUTLS_E_INTERRUPTED && !sigalrm_seen);
-alarm(0);
+ALARM_CLR(0);
if (rc != GNUTLS_E_SUCCESS)
{
if (sigalrm_seen)
{
- tls_error(US"gnutls_handshake", "timed out", NULL, errstr);
+ tls_error(US"gnutls_handshake", US"timed out", NULL, errstr);
gnutls_db_remove_session(state->session);
}
else
{
- tls_error(US"gnutls_handshake", gnutls_strerror(rc), NULL, errstr);
+ tls_error_gnu(US"gnutls_handshake", rc, NULL, errstr);
(void) gnutls_alert_send_appropriate(state->session, rc);
gnutls_deinit(state->session);
gnutls_certificate_free_credentials(state->x509_cred);
millisleep(500);
shutdown(state->fd_out, SHUT_WR);
- for (rc = 1024; fgetc(smtp_in) != EOF && rc > 0; ) rc--; /* drain skt */
+ for (int i = 1024; fgetc(smtp_in) != EOF && i > 0; ) i--; /* drain skt */
(void)fclose(smtp_out);
(void)fclose(smtp_in);
smtp_out = smtp_in = NULL;
return FAIL;
}
-DEBUG(D_tls) debug_printf("gnutls_handshake was successful\n");
+DEBUG(D_tls)
+ {
+ debug_printf("gnutls_handshake was successful\n");
+#ifdef SUPPORT_GNUTLS_SESS_DESC
+ debug_printf("%s\n", gnutls_session_get_desc(state->session));
+#endif
+#ifdef SUPPORT_GNUTLS_KEYLOG
+ {
+ gnutls_datum_t c, s;
+ gstring * gc, * gs;
+ gnutls_session_get_random(state->session, &c, &s);
+ gnutls_session_get_master_secret(state->session, &s);
+ gc = ddump(&c);
+ gs = ddump(&s);
+ debug_printf("CLIENT_RANDOM %.*s %.*s\n", (int)gc->ptr, gc->s, (int)gs->ptr, gs->s);
+ }
+#endif
+ }
/* Verify after the fact */
tls_client_setup_hostname_checks(host_item * host, exim_gnutls_state_st * state,
smtp_transport_options_block * ob)
{
-if (verify_check_given_host(&ob->tls_verify_cert_hostnames, host) == OK)
+if (verify_check_given_host(CUSS &ob->tls_verify_cert_hostnames, host) == OK)
{
state->exp_tls_verify_cert_hostnames =
#ifdef SUPPORT_I18N
static BOOL
dane_tlsa_load(exim_gnutls_state_st * state, dns_answer * dnsa)
{
-dns_record * rr;
dns_scan dnss;
int i;
const char ** dane_data;
int * dane_data_len;
-for (rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS), i = 1;
- rr;
+i = 1;
+for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)
) if (rr->type == T_TLSA) i++;
dane_data = store_get(i * sizeof(uschar *));
dane_data_len = store_get(i * sizeof(int));
-for (rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS), i = 0;
- rr;
+i = 0;
+for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)
- ) if (rr->type == T_TLSA)
+ ) if (rr->type == T_TLSA && rr->size > 3)
{
const uschar * p = rr->data;
uint8_t usage = p[0], sel = p[1], type = p[2];
}
tls_out.tlsa_usage |= 1<<usage;
- dane_data[i] = p;
+ dane_data[i] = CS p;
dane_data_len[i++] = rr->size;
}
/* Called from the smtp transport after STARTTLS has been accepted.
Arguments:
- fd the fd of the connection
- host connected host (for messages)
- addr the first address (not used)
- tb transport (always smtp)
- tlsa_dnsa non-NULL, either request or require dane for this host, and
- a TLSA record found. Therefore, dane verify required.
- Which implies cert must be requested and supplied, dane
- verify must pass, and cert verify irrelevant (incl.
- hostnames), and (caller handled) require_tls
- errstr error string pointer
-
-Returns: OK/DEFER/FAIL (because using common functions),
- but for a client, DEFER and FAIL have the same meaning
+ cctx connection context
+ conn_args connection details
+ cookie datum for randomness (not used)
+ tlsp record details of channel configuration here; must be non-NULL
+ errstr error string pointer
+
+Returns: TRUE for success with TLS session context set in smtp context,
+ FALSE on error
*/
-int
-tls_client_start(int fd, host_item *host,
- address_item *addr ARG_UNUSED,
- transport_instance * tb,
-#ifdef EXPERIMENTAL_DANE
- dns_answer * tlsa_dnsa,
-#endif
- uschar ** errstr)
+BOOL
+tls_client_start(client_conn_ctx * cctx, smtp_connect_args * conn_args,
+ void * cookie ARG_UNUSED,
+ tls_support * tlsp, uschar ** errstr)
{
-smtp_transport_options_block *ob =
- (smtp_transport_options_block *)tb->options_block;
+host_item * host = conn_args->host; /* for msgs and option-tests */
+transport_instance * tb = conn_args->tblock; /* always smtp or NULL */
+smtp_transport_options_block * ob = tb
+ ? (smtp_transport_options_block *)tb->options_block
+ : &smtp_transport_option_defaults;
int rc;
exim_gnutls_state_st * state = NULL;
+uschar * cipher_list = NULL;
+
#ifndef DISABLE_OCSP
BOOL require_ocsp =
- verify_check_given_host(&ob->hosts_require_ocsp, host) == OK;
+ verify_check_given_host(CUSS &ob->hosts_require_ocsp, host) == OK;
BOOL request_ocsp = require_ocsp ? TRUE
- : verify_check_given_host(&ob->hosts_request_ocsp, host) == OK;
+ : verify_check_given_host(CUSS &ob->hosts_request_ocsp, host) == OK;
#endif
-DEBUG(D_tls) debug_printf("initialising GnuTLS as a client on fd %d\n", fd);
+DEBUG(D_tls) debug_printf("initialising GnuTLS as a client on fd %d\n", cctx->sock);
+
+#ifdef SUPPORT_DANE
+/* If dane is flagged, have either request or require dane for this host, and
+a TLSA record found. Therefore, dane verify required. Which implies cert must
+be requested and supplied, dane verify must pass, and cert verify irrelevant
+(incl. hostnames), and (caller handled) require_tls */
-if ((rc = tls_init(host, ob->tls_certificate, ob->tls_privatekey,
+if (conn_args->dane && ob->dane_require_tls_ciphers)
+ {
+ /* not using expand_check_tlsvar because not yet in state */
+ if (!expand_check(ob->dane_require_tls_ciphers, US"dane_require_tls_ciphers",
+ &cipher_list, errstr))
+ return FALSE;
+ cipher_list = cipher_list && *cipher_list
+ ? ob->dane_require_tls_ciphers : ob->tls_require_ciphers;
+ }
+#endif
+
+if (!cipher_list)
+ cipher_list = ob->tls_require_ciphers;
+
+if (tls_init(host, ob->tls_certificate, ob->tls_privatekey,
ob->tls_sni, ob->tls_verify_certificates, ob->tls_crl,
- ob->tls_require_ciphers, &state, errstr)) != OK)
- return rc;
+ cipher_list, &state, tlsp, errstr) != OK)
+ return FALSE;
{
int dh_min_bits = ob->tls_dh_min_bits;
the specified host patterns if one of them is defined */
#ifdef SUPPORT_DANE
-if (tlsa_dnsa && dane_tlsa_load(state, tlsa_dnsa))
+if (conn_args->dane && dane_tlsa_load(state, &conn_args->tlsa_dnsa))
{
DEBUG(D_tls)
debug_printf("TLS: server certificate DANE required.\n");
&& !ob->tls_verify_hosts
&& (!ob->tls_try_verify_hosts || !*ob->tls_try_verify_hosts)
)
- || verify_check_given_host(&ob->tls_verify_hosts, host) == OK
+ || verify_check_given_host(CUSS &ob->tls_verify_hosts, host) == OK
)
{
tls_client_setup_hostname_checks(host, state, ob);
state->verify_requirement = VERIFY_REQUIRED;
gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_REQUIRE);
}
-else if (verify_check_given_host(&ob->tls_try_verify_hosts, host) == OK)
+else if (verify_check_given_host(CUSS &ob->tls_try_verify_hosts, host) == OK)
{
tls_client_setup_hostname_checks(host, state, ob);
DEBUG(D_tls)
DEBUG(D_tls) debug_printf("TLS: will request OCSP stapling\n");
if ((rc = gnutls_ocsp_status_request_enable_client(state->session,
NULL, 0, NULL)) != OK)
- return tls_error(US"cert-status-req",
- gnutls_strerror(rc), state->host, errstr);
- tls_out.ocsp = OCSP_NOT_RESP;
+ {
+ tls_error_gnu(US"cert-status-req", rc, state->host, errstr);
+ return FALSE;
+ }
+ tlsp->ocsp = OCSP_NOT_RESP;
}
#endif
#ifndef DISABLE_EVENT
-if (tb->event_action)
+if (tb && tb->event_action)
{
state->event_action = tb->event_action;
gnutls_session_set_ptr(state->session, state);
}
#endif
-gnutls_transport_set_ptr(state->session, (gnutls_transport_ptr_t)(long) fd);
-state->fd_in = fd;
-state->fd_out = fd;
+gnutls_transport_set_ptr(state->session, (gnutls_transport_ptr_t)(long) cctx->sock);
+state->fd_in = cctx->sock;
+state->fd_out = cctx->sock;
DEBUG(D_tls) debug_printf("about to gnutls_handshake\n");
/* There doesn't seem to be a built-in timeout on connection. */
sigalrm_seen = FALSE;
-alarm(ob->command_timeout);
+ALARM(ob->command_timeout);
do
rc = gnutls_handshake(state->session);
while (rc == GNUTLS_E_AGAIN || rc == GNUTLS_E_INTERRUPTED && !sigalrm_seen);
-alarm(0);
+ALARM_CLR(0);
if (rc != GNUTLS_E_SUCCESS)
+ {
if (sigalrm_seen)
{
gnutls_alert_send(state->session, GNUTLS_AL_FATAL, GNUTLS_A_USER_CANCELED);
- return tls_error(US"gnutls_handshake", "timed out", state->host, errstr);
+ tls_error(US"gnutls_handshake", US"timed out", state->host, errstr);
}
else
- return tls_error(US"gnutls_handshake", gnutls_strerror(rc), state->host, errstr);
+ tls_error_gnu(US"gnutls_handshake", rc, state->host, errstr);
+ return FALSE;
+ }
-DEBUG(D_tls) debug_printf("gnutls_handshake was successful\n");
+DEBUG(D_tls)
+ {
+ debug_printf("gnutls_handshake was successful\n");
+#ifdef SUPPORT_GNUTLS_SESS_DESC
+ debug_printf("%s\n", gnutls_session_get_desc(state->session));
+#endif
+#ifdef SUPPORT_GNUTLS_KEYLOG
+ {
+ gnutls_datum_t c, s;
+ gstring * gc, * gs;
+ gnutls_session_get_random(state->session, &c, &s);
+ gnutls_session_get_master_secret(state->session, &s);
+ gc = ddump(&c);
+ gs = ddump(&s);
+ debug_printf("CLIENT_RANDOM %.*s %.*s\n", (int)gc->ptr, gc->s, (int)gs->ptr, gs->s);
+ }
+#endif
+ }
/* Verify late */
if (!verify_certificate(state, errstr))
- return tls_error(US"certificate verification failed", *errstr, state->host, errstr);
+ {
+ tls_error(US"certificate verification failed", *errstr, state->host, errstr);
+ return FALSE;
+ }
#ifndef DISABLE_OCSP
if (require_ocsp)
gnutls_free(printed.data);
}
else
- (void) tls_error(US"ocsp decode", gnutls_strerror(rc), state->host, errstr);
+ (void) tls_error_gnu(US"ocsp decode", rc, state->host, errstr);
}
if (gnutls_ocsp_status_request_is_checked(state->session, 0) == 0)
{
- tls_out.ocsp = OCSP_FAILED;
- return tls_error(US"certificate status check failed", NULL, state->host, errstr);
+ tlsp->ocsp = OCSP_FAILED;
+ tls_error(US"certificate status check failed", NULL, state->host, errstr);
+ return FALSE;
}
DEBUG(D_tls) debug_printf("Passed OCSP checking\n");
- tls_out.ocsp = OCSP_VFIED;
+ tlsp->ocsp = OCSP_VFIED;
}
#endif
/* Figure out peer DN, and if authenticated, etc. */
-if ((rc = peer_status(state, errstr)) != OK)
- return rc;
+if (peer_status(state, errstr) != OK)
+ return FALSE;
/* Sets various Exim expansion variables; may need to adjust for ACL callouts */
extract_exim_vars_from_tls_state(state);
-return OK;
+cctx->tls_ctx = state;
+return TRUE;
}
daemon, to shut down the TLS library, without actually doing a shutdown (which
would tamper with the TLS session in the parent process).
-Arguments: TRUE if gnutls_bye is to be called
+Arguments:
+ ct_ctx client context pointer, or NULL for the one global server context
+ shutdown 1 if TLS close-alert is to be sent,
+ 2 if also response to be waited for
+
Returns: nothing
*/
void
-tls_close(BOOL is_server, BOOL shutdown)
+tls_close(void * ct_ctx, int shutdown)
{
-exim_gnutls_state_st *state = is_server ? &state_server : &state_client;
+exim_gnutls_state_st * state = ct_ctx ? ct_ctx : &state_server;
-if (!state->tlsp || state->tlsp->active < 0) return; /* TLS was not active */
+if (!state->tlsp || state->tlsp->active.sock < 0) return; /* TLS was not active */
if (shutdown)
{
- DEBUG(D_tls) debug_printf("tls_close(): shutting down TLS\n");
- gnutls_bye(state->session, GNUTLS_SHUT_WR);
+ DEBUG(D_tls) debug_printf("tls_close(): shutting down TLS%s\n",
+ shutdown > 1 ? " (with response-wait)" : "");
+
+ ALARM(2);
+ gnutls_bye(state->session, shutdown > 1 ? GNUTLS_SHUT_RDWR : GNUTLS_SHUT_WR);
+ ALARM_CLR(0);
}
gnutls_deinit(state->session);
gnutls_certificate_free_credentials(state->x509_cred);
-state->tlsp->active = -1;
+state->tlsp->active.sock = -1;
+state->tlsp->active.tls_ctx = NULL;
+if (state->xfer_buffer) store_free(state->xfer_buffer);
memcpy(state, &exim_gnutls_state_init, sizeof(exim_gnutls_state_init));
-
-if ((state_server.session == NULL) && (state_client.session == NULL))
- {
- gnutls_global_deinit();
- exim_gnutls_base_init_done = FALSE;
- }
}
state->session, state->xfer_buffer, ssl_xfer_buffer_size);
sigalrm_seen = FALSE;
-if (smtp_receive_timeout > 0) alarm(smtp_receive_timeout);
-inbytes = gnutls_record_recv(state->session, state->xfer_buffer,
- MIN(ssl_xfer_buffer_size, lim));
-alarm(0);
+if (smtp_receive_timeout > 0) ALARM(smtp_receive_timeout);
+
+do
+ inbytes = gnutls_record_recv(state->session, state->xfer_buffer,
+ MIN(ssl_xfer_buffer_size, lim));
+while (inbytes == GNUTLS_E_AGAIN);
+
+if (smtp_receive_timeout > 0) ALARM_CLR(0);
+
+if (had_command_timeout) /* set by signal handler */
+ smtp_command_timeout_exit(); /* does not return */
+if (had_command_sigterm)
+ smtp_command_sigterm_exit();
+if (had_data_timeout)
+ smtp_data_timeout_exit();
+if (had_data_sigint)
+ smtp_data_sigint_exit();
-/* Timeouts do not get this far; see command_timeout_handler().
- A zero-byte return appears to mean that the TLS session has been
- closed down, not that the socket itself has been closed down. Revert to
- non-TLS handling. */
+/* Timeouts do not get this far. A zero-byte return appears to mean that the
+TLS session has been closed down, not that the socket itself has been closed
+down. Revert to non-TLS handling. */
if (sigalrm_seen)
{
DEBUG(D_tls) debug_printf("Got tls read timeout\n");
- state->xfer_error = 1;
+ state->xfer_error = TRUE;
return FALSE;
}
gnutls_certificate_free_credentials(state->x509_cred);
state->session = NULL;
- state->tlsp->active = -1;
+ state->tlsp->active.sock = -1;
+ state->tlsp->active.tls_ctx = NULL;
state->tlsp->bits = 0;
state->tlsp->certificate_verified = FALSE;
tls_channelbinding_b64 = NULL;
else if (inbytes < 0)
{
+ DEBUG(D_tls) debug_printf("%s: err from gnutls_record_recv\n", __FUNCTION__);
record_io_error(state, (int) inbytes, US"recv", NULL);
- state->xfer_error = 1;
+ state->xfer_error = TRUE;
return FALSE;
}
#ifndef DISABLE_DKIM
This feeds DKIM and should be used for all message-body reads.
-Arguments: lim Maximum amount to read/bufffer
+Arguments: lim Maximum amount to read/buffer
Returns: the next character or EOF
*/
then the caller must feed DKIM.
Arguments:
+ ct_ctx client context pointer, or NULL for the one global server context
buff buffer of data
len size of buffer
Returns: the number of bytes read
- -1 after a failed read
+ -1 after a failed read, including EOF
*/
int
-tls_read(BOOL is_server, uschar *buff, size_t len)
+tls_read(void * ct_ctx, uschar *buff, size_t len)
{
-exim_gnutls_state_st *state = is_server ? &state_server : &state_client;
+exim_gnutls_state_st * state = ct_ctx ? ct_ctx : &state_server;
ssize_t inbytes;
if (len > INT_MAX)
debug_printf("Calling gnutls_record_recv(%p, %p, " SIZE_T_FMT ")\n",
state->session, buff, len);
-inbytes = gnutls_record_recv(state->session, buff, len);
+do
+ inbytes = gnutls_record_recv(state->session, buff, len);
+while (inbytes == GNUTLS_E_AGAIN);
+
if (inbytes > 0) return inbytes;
if (inbytes == 0)
{
DEBUG(D_tls) debug_printf("Got TLS_EOF\n");
}
-else record_io_error(state, (int)inbytes, US"recv", NULL);
+else
+ {
+ DEBUG(D_tls) debug_printf("%s: err from gnutls_record_recv\n", __FUNCTION__);
+ record_io_error(state, (int)inbytes, US"recv", NULL);
+ }
return -1;
}
/*
Arguments:
- is_server channel specifier
+ ct_ctx client context pointer, or NULL for the one global server context
buff buffer of data
len number of bytes
more more data expected soon
*/
int
-tls_write(BOOL is_server, const uschar *buff, size_t len, BOOL more)
+tls_write(void * ct_ctx, const uschar * buff, size_t len, BOOL more)
{
ssize_t outbytes;
size_t left = len;
-exim_gnutls_state_st *state = is_server ? &state_server : &state_client;
+exim_gnutls_state_st * state = ct_ctx ? ct_ctx : &state_server;
#ifdef SUPPORT_CORK
static BOOL corked = FALSE;
{
DEBUG(D_tls) debug_printf("gnutls_record_send(SSL, %p, " SIZE_T_FMT ")\n",
buff, left);
- outbytes = gnutls_record_send(state->session, buff, left);
+
+ do
+ outbytes = gnutls_record_send(state->session, buff, left);
+ while (outbytes == GNUTLS_E_AGAIN);
DEBUG(D_tls) debug_printf("outbytes=" SSIZE_T_FMT "\n", outbytes);
if (outbytes < 0)
{
+ DEBUG(D_tls) debug_printf("%s: gnutls_record_send err\n", __FUNCTION__);
record_io_error(state, outbytes, US"send", NULL);
return -1;
}
{
unsigned int r;
int i, needed_len;
-uschar *p;
uschar smallbuf[sizeof(r)];
if (max <= 1)
needed_len = sizeof(r);
/* Don't take 8 times more entropy than needed if int is 8 octets and we were
- * asked for a number less than 10. */
+asked for a number less than 10. */
+
for (r = max, i = 0; r; ++i)
r >>= 1;
i = (i + 7) / 8;
return vaguely_random_number_fallback(max);
}
r = 0;
-for (p = smallbuf; needed_len; --needed_len, ++p)
- {
- r *= 256;
- r += *p;
- }
+for (uschar * p = smallbuf; needed_len; --needed_len, ++p)
+ r = r * 256 + *p;
/* We don't particularly care about weighted results; if someone wants
* smooth distribution and cares enough then they should submit a patch then. */