* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2012 */
+/* Copyright (c) University of Cambridge 1995 - 2014 */
/* 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>
+#endif
/* GnuTLS 2 vs 3
be set to point to content in one of these instances, as appropriate for
the stage of the process lifetime.
-Not handled here: global tls_channelbinding_b64. /*XXX JGH */
+Not handled here: global tls_channelbinding_b64.
*/
typedef struct exim_gnutls_state {
uschar *exp_tls_crl;
uschar *exp_tls_require_ciphers;
- tls_support *tlsp;
+ tls_support *tlsp; /* set in tls_init() */
uschar *xfer_buffer;
int xfer_buffer_lwm;
#define HAVE_GNUTLS_SESSION_CHANNEL_BINDING
#define HAVE_GNUTLS_SEC_PARAM_CONSTANTS
#define HAVE_GNUTLS_RND
+/* The security fix we provide with the gnutls_allow_auto_pkcs11 option
+ * (4.82 PP/09) introduces a compatibility regression. The symbol simply
+ * isn't available sometimes, so this needs to become a conditional
+ * compilation; the sanest way to deal with this being a problem on
+ * older OSes is to block it in the Local/Makefile with this compiler
+ * definition */
+#ifndef AVOID_GNUTLS_PKCS11
+#define HAVE_GNUTLS_PKCS11
+#endif /* AVOID_GNUTLS_PKCS11 */
#endif
{
if (!state->received_sni)
{
- if (state->tls_certificate && Ustrstr(state->tls_certificate, US"tls_sni"))
+ if (state->tls_certificate &&
+ (Ustrstr(state->tls_certificate, US"tls_sni") ||
+ Ustrstr(state->tls_certificate, US"tls_in_sni") ||
+ Ustrstr(state->tls_certificate, US"tls_out_sni")
+ ))
{
DEBUG(D_tls) debug_printf("We will re-expand TLS session files if we receive SNI.\n");
state->trigger_sni_changes = TRUE;
{
DEBUG(D_tls) debug_printf("GnuTLS global init required.\n");
+#ifdef HAVE_GNUTLS_PKCS11
+ /* By default, gnutls_global_init will init PKCS11 support in auto mode,
+ which loads modules from a config file, which sounds good and may be wanted
+ by some sysadmin, but also means in common configurations that GNOME keyring
+ 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(US"gnutls_pkcs11_init");
+ }
+#endif
+
rc = gnutls_global_init();
exim_gnutls_err_check(US"gnutls_global_init");
/* set SNI in client, only */
if (host)
{
- if (!expand_check_tlsvar(state->tlsp->sni))
+ if (!expand_check(state->tlsp->sni, US"tls_out_sni", &state->exp_tls_sni))
return DEFER;
if (state->exp_tls_sni && *state->exp_tls_sni)
{
"have an SNI set for a client [%s]\n", state->tls_sni);
/* This is the priority string support,
-http://www.gnu.org/software/gnutls/manual/html_node/Priority-Strings.html
+http://www.gnutls.org/manual/html_node/Priority-Strings.html
and replaces gnutls_require_kx, gnutls_require_mac & gnutls_require_protocols.
This was backwards incompatible, but means Exim no longer needs to track
all algorithms and provide string forms for them. */
*error = NULL;
-rc = peer_status(state);
-if (rc != OK)
+if ((rc = peer_status(state)) != OK)
{
verify = GNUTLS_CERT_INVALID;
- *error = "not supplied";
+ *error = "certificate not supplied";
}
else
- {
rc = gnutls_certificate_verify_peers2(state->session, &verify);
- }
/* Handle the result of verification. INVALID seems to be set as well
as REVOKED, but leave the test for both. */
-if ((rc < 0) || (verify & (GNUTLS_CERT_INVALID|GNUTLS_CERT_REVOKED)) != 0)
+if (rc < 0 || verify & (GNUTLS_CERT_INVALID|GNUTLS_CERT_REVOKED))
{
state->peer_cert_verified = FALSE;
- if (*error == NULL)
- *error = ((verify & GNUTLS_CERT_REVOKED) != 0) ? "revoked" : "invalid";
+ if (!*error)
+ *error = verify & GNUTLS_CERT_REVOKED
+ ? "certificate revoked" : "certificate invalid";
DEBUG(D_tls)
debug_printf("TLS certificate verification failed (%s): peerdn=%s\n",
fd the fd of the connection
host connected host (for messages)
addr the first address (not used)
- dhparam DH parameter file (ignored, we're a client)
certificate certificate file
privatekey private key file
sni TLS SNI to send to remote host
require_ciphers list of allowed ciphers or NULL
dh_min_bits minimum number of bits acceptable in server's DH prime
timeout startup timeout
+ verify_hosts mandatory client verification
+ try_verify_hosts optional client verification
Returns: OK/DEFER/FAIL (because using common functions),
but for a client, DEFER and FAIL have the same meaning
int
tls_client_start(int fd, host_item *host,
- address_item *addr ARG_UNUSED, uschar *dhparam ARG_UNUSED,
+ address_item *addr ARG_UNUSED,
uschar *certificate, uschar *privatekey, uschar *sni,
uschar *verify_certs, uschar *verify_crl,
- uschar *require_ciphers, int dh_min_bits, int timeout)
+ uschar *require_ciphers,
+#ifdef EXPERIMENTAL_OCSP
+ uschar *require_ocsp ARG_UNUSED,
+#endif
+ int dh_min_bits, int timeout,
+ uschar *verify_hosts, uschar *try_verify_hosts)
{
int rc;
const char *error;
dh_min_bits);
gnutls_dh_set_prime_bits(state->session, dh_min_bits);
-if (verify_certs == NULL)
+/* stick to the old behaviour for compatibility if tls_verify_certificates is
+ set but both tls_verify_hosts and tls_try_verify_hosts is not set. Check only
+ the specified host patterns if one of them is defined */
+if (((state->exp_tls_verify_certificates != NULL) && (verify_hosts == NULL) && (try_verify_hosts == NULL)) ||
+ (verify_check_host(&verify_hosts) == OK))
{
- DEBUG(D_tls) debug_printf("TLS: server certificate verification not required\n");
- state->verify_requirement = VERIFY_NONE;
- /* we still ask for it, to log it, etc */
+ DEBUG(D_tls) debug_printf("TLS: server certificate verification required.\n");
+ state->verify_requirement = VERIFY_REQUIRED;
+ gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_REQUIRE);
+ }
+else if (verify_check_host(&try_verify_hosts) == OK)
+ {
+ DEBUG(D_tls) debug_printf("TLS: server certificate verification optional.\n");
+ state->verify_requirement = VERIFY_OPTIONAL;
gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_REQUEST);
}
else
{
- DEBUG(D_tls) debug_printf("TLS: server certificate verification required\n");
- state->verify_requirement = VERIFY_REQUIRED;
- gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_REQUIRE);
+ DEBUG(D_tls) debug_printf("TLS: server certificate verification not required.\n");
+ state->verify_requirement = VERIFY_NONE;
+ gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_IGNORE);
}
gnutls_transport_set_ptr(state->session, (gnutls_transport_ptr)fd);
{
exim_gnutls_state_st *state = is_server ? &state_server : &state_client;
-if (state->tlsp->active < 0) return; /* TLS was not active */
+if (!state->tlsp || state->tlsp->active < 0) return; /* TLS was not active */
if (shutdown)
{
gnutls_deinit(state->session);
+state->tlsp->active = -1;
memcpy(state, &exim_gnutls_state_init, sizeof(exim_gnutls_state_init));
if ((state_server.session == NULL) && (state_client.session == NULL))
exim_gnutls_base_init_done = FALSE;
}
-state->tlsp->active = -1;
}
log_write(0, LOG_MAIN|LOG_PANIC,
"already initialised GnuTLS, Exim developer bug");
+#ifdef HAVE_GNUTLS_PKCS11
+if (!gnutls_allow_auto_pkcs11)
+ {
+ rc = gnutls_pkcs11_init(GNUTLS_PKCS11_FLAG_MANUAL, NULL);
+ validate_check_rc(US"gnutls_pkcs11_init");
+ }
+#endif
rc = gnutls_global_init();
validate_check_rc(US"gnutls_global_init()");
exim_gnutls_base_init_done = TRUE;