X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/7044dd8fd62e215572ecf5a2c7f1bb9581cf6628..5c161fa615ab4a2d40170fedd71d1b9a7f079ff6:/src/src/tls-gnu.c diff --git a/src/src/tls-gnu.c b/src/src/tls-gnu.c index cf3804982..b14bca483 100644 --- a/src/src/tls-gnu.c +++ b/src/src/tls-gnu.c @@ -145,6 +145,9 @@ builtin_macro_create(US"_HAVE_TLS_OCSP"); # ifdef SUPPORT_SRV_OCSP_STACK builtin_macro_create(US"_HAVE_TLS_OCSP_LIST"); # endif +#if defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT) +builtin_macro_create(US"_HAVE_TLS_CA_CACHE"); +# endif } #else @@ -178,8 +181,11 @@ Not handled here: global tlsp->tls_channelbinding. typedef struct exim_gnutls_state { gnutls_session_t session; - gnutls_certificate_credentials_t x509_cred; - gnutls_priority_t priority_cache; + + exim_tlslib_state lib_state; +#define x509_cred libdata0 +#define pri_cache libdata1 + enum peer_verify_requirement verify_requirement; int fd_in; int fd_out; @@ -245,7 +251,11 @@ second connection. XXX But see gnutls_session_get_ptr() */ -static exim_gnutls_state_st state_server; +static exim_gnutls_state_st state_server = { + /* all elements not explicitly intialised here get 0/NULL/FALSE */ + .fd_in = -1, + .fd_out = -1, +}; #ifndef GNUTLS_AUTO_DHPARAMS /* dh_params are initialised once within the lifetime of a process using TLS; @@ -298,7 +308,7 @@ before, for now. */ # define EXIM_SERVER_DH_BITS_PRE2_12 1024 #endif -#define expand_check_tlsvar(Varname, errstr) \ +#define Expand_check_tlsvar(Varname, errstr) \ expand_check(state->Varname, US #Varname, &state->exp_##Varname, errstr) #if GNUTLS_VERSION_NUMBER >= 0x020c00 @@ -335,27 +345,6 @@ tls_server_ticket_cb(gnutls_session_t sess, u_int htype, unsigned when, #endif -/* Daemon one-time initialisation */ -void -tls_daemon_init(void) -{ -#ifdef EXIM_HAVE_TLS_RESUME -/* We are dependent on the GnuTLS implementation of the Session Ticket -encryption; both the strength and the key rotation period. We hope that -the strength at least matches that of the ciphersuite (but GnuTLS does not -document this). */ - -static BOOL once = FALSE; -if (once) return; -once = TRUE; -gnutls_session_ticket_key_generate(&server_sessticket_key); /* >= 2.10.0 */ -if (f.running_in_test_harness) ssl_session_timeout = 6; -#endif -} - -/* ------------------------------------------------------------------------ */ -/* Static functions */ - /************************************************* * Handle TLS error * *************************************************/ @@ -403,6 +392,121 @@ return tls_error(prefix, US strerror(err), host, errstr); } +/* ------------------------------------------------------------------------ */ +/* Initialisation */ + +#ifndef DISABLE_OCSP + +static BOOL +tls_is_buggy_ocsp(void) +{ +const uschar * s; +uschar maj, mid, mic; + +s = CUS gnutls_check_version(NULL); +maj = atoi(CCS s); +if (maj == 3) + { + while (*s && *s != '.') s++; + mid = atoi(CCS ++s); + if (mid <= 2) + return TRUE; + else if (mid >= 5) + return FALSE; + else + { + while (*s && *s != '.') s++; + mic = atoi(CCS ++s); + return mic <= (mid == 3 ? 16 : 3); + } + } +return FALSE; +} + +#endif + + +static int +tls_g_init(uschar ** errstr) +{ +int rc; +DEBUG(D_tls) debug_printf("GnuTLS global init required\n"); + +#if defined(HAVE_GNUTLS_PKCS11) && !defined(GNUTLS_AUTO_PKCS11_MANUAL) +/* 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) + if ((rc = gnutls_pkcs11_init(GNUTLS_PKCS11_FLAG_MANUAL, NULL))) + return tls_error_gnu(US"gnutls_pkcs11_init", rc, NULL, errstr); +#endif + +#ifndef GNUTLS_AUTO_GLOBAL_INIT +if ((rc = gnutls_global_init())) + return tls_error_gnu(US"gnutls_global_init", rc, NULL, errstr); +#endif + +#if EXIM_GNUTLS_LIBRARY_LOG_LEVEL >= 0 +DEBUG(D_tls) + { + gnutls_global_set_log_function(exim_gnutls_logger_cb); + /* arbitrarily chosen level; bump up to 9 for more */ + gnutls_global_set_log_level(EXIM_GNUTLS_LIBRARY_LOG_LEVEL); + } +#endif + +#ifndef DISABLE_OCSP +if (tls_ocsp_file && (gnutls_buggy_ocsp = tls_is_buggy_ocsp())) + log_write(0, LOG_MAIN, "OCSP unusable with this GnuTLS library version"); +#endif + +exim_gnutls_base_init_done = TRUE; +return OK; +} + + + +/* Daemon-call before each connection. Nothing to do for GnuTLS. */ + +static void +tls_per_lib_daemon_tick(void) +{ +} + +/* Daemon one-time initialisation */ + +static void +tls_per_lib_daemon_init(void) +{ +uschar * dummy_errstr; +static BOOL once = FALSE; + +if (!exim_gnutls_base_init_done) + tls_g_init(&dummy_errstr); + +if (!once) + { + once = TRUE; + +#ifdef EXIM_HAVE_TLS_RESUME + /* We are dependent on the GnuTLS implementation of the Session Ticket + encryption; both the strength and the key rotation period. We hope that + the strength at least matches that of the ciphersuite (but GnuTLS does not + document this). */ + + gnutls_session_ticket_key_generate(&server_sessticket_key); /* >= 2.10.0 */ + if (f.running_in_test_harness) ssl_session_timeout = 6; +#endif + + tls_daemon_creds_reload(); + } +} + +/* ------------------------------------------------------------------------ */ + /************************************************* * Deal with logging errors during I/O * *************************************************/ @@ -550,7 +654,10 @@ else /* peercert is set in peer_status() */ tlsp->peerdn = state->peerdn; -tlsp->sni = state->received_sni; + +/* do not corrupt sni sent by client; record sni rxd by server */ +if (!state->host) + tlsp->sni = state->received_sni; /* record our certificate */ { @@ -595,7 +702,7 @@ uschar *exp_tls_dhparam; BOOL use_file_in_spool = FALSE; host_item *host = NULL; /* dummy for macros */ -DEBUG(D_tls) debug_printf("Initialising GnuTLS server params.\n"); +DEBUG(D_tls) debug_printf("Initialising GnuTLS server params\n"); if ((rc = gnutls_dh_params_init(&dh_server_params))) return tls_error_gnu(US"gnutls_dh_params_init", rc, host, errstr); @@ -613,7 +720,7 @@ 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"); + DEBUG(D_tls) debug_printf("Requested no DH parameters\n"); return OK; } else if (exp_tls_dhparam[0] != '/') @@ -640,12 +747,12 @@ different filename and ensure we have sufficient 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", + 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", + debug_printf("GnuTLS lacks gnutls_sec_param_to_pk_bits(), using %d bits\n", dh_bits); #endif @@ -653,7 +760,7 @@ DEBUG(D_tls) if (dh_bits > tls_dh_max_bits) { DEBUG(D_tls) - debug_printf("tls_dh_max_bits clamping override, using %d bits instead.\n", + debug_printf("tls_dh_max_bits clamping override, using %d bits instead\n", tls_dh_max_bits); dh_bits = tls_dh_max_bits; } @@ -822,7 +929,7 @@ return OK; -/* Create and install a selfsigned certificate, for use in server mode */ +/* Create and install a selfsigned certificate, for use in server mode. */ static int tls_install_selfsign(exim_gnutls_state_st * state, uschar ** errstr) @@ -839,6 +946,7 @@ rc = GNUTLS_E_NO_CERTIFICATE_FOUND; if (TRUE) goto err; #endif +DEBUG(D_tls) debug_printf("TLS: generating selfsigned server cert\n"); where = US"initialising pkey"; if ((rc = gnutls_x509_privkey_init(&pkey))) goto err; @@ -863,7 +971,7 @@ 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))) - || (rc = gnutls_x509_crt_set_expiration_time(cert, now + 60 * 60)) /* 1 hr */ + || (rc = gnutls_x509_crt_set_expiration_time(cert, (long)2 * 60 * 60)) /* 2 hour */ || (rc = gnutls_x509_crt_set_key(cert, pkey)) || (rc = gnutls_x509_crt_set_dn_by_oid(cert, @@ -881,7 +989,8 @@ if ((rc = gnutls_x509_crt_sign(cert, cert, pkey))) goto err; where = US"installing selfsign cert"; /* Since: 2.4.0 */ -if ((rc = gnutls_certificate_set_x509_key(state->x509_cred, &cert, 1, pkey))) +if ((rc = gnutls_certificate_set_x509_key(state->lib_state.x509_cred, + &cert, 1, pkey))) goto err; rc = OK; @@ -908,10 +1017,10 @@ Return: static int tls_add_certfile(exim_gnutls_state_st * state, const host_item * host, - uschar * certfile, uschar * keyfile, uschar ** errstr) + const uschar * certfile, const uschar * keyfile, uschar ** errstr) { -int rc = gnutls_certificate_set_x509_key_file(state->x509_cred, - CS certfile, CS keyfile, GNUTLS_X509_FMT_PEM); +int rc = gnutls_certificate_set_x509_key_file(state->lib_state.x509_cred, + CCS certfile, CCS keyfile, GNUTLS_X509_FMT_PEM); if (rc < 0) return tls_error_gnu( string_sprintf("cert/key setup: cert=%s key=%s", certfile, keyfile), @@ -972,6 +1081,7 @@ return gnutls_ext_raw_parse(NULL, tls_server_clienthello_ext, msg, } +# ifdef notdef_crashes /* Make a note that we saw a status-response */ static int tls_server_servercerts_ext(void * ctx, unsigned tls_id, @@ -987,6 +1097,7 @@ if (FALSE && tls_id == 5) /* status_request */ } return 0; } +# endif /* Callback for certificates packet, on server, if we think we might serve stapled-OCSP */ static int @@ -994,12 +1105,12 @@ tls_server_servercerts_cb(gnutls_session_t session, unsigned int htype, unsigned when, unsigned int incoming, const gnutls_datum_t * msg) { /* Call fn for each extension seen. 3.6.3 onwards */ -#ifdef notdef -/*XXX crashes */ +# ifdef notdef_crashes + /*XXX crashes */ return gnutls_ext_raw_parse(NULL, tls_server_servercerts_ext, msg, 0); -#endif +# endif } -#endif +#endif /*SUPPORT_GNUTLS_EXT_RAW_PARSE*/ /*XXX in tls1.3 the cert-status travel as an extension next to the cert, in the "Handshake Protocol: Certificate" record. @@ -1065,283 +1176,165 @@ if (environ) for (uschar ** p = USS environ; *p; p++) } #endif -/************************************************* -* Variables re-expanded post-SNI * -*************************************************/ - -/* Called from both server and client code, via tls_init(), and also from -the SNI callback after receiving an SNI, if tls_certificate includes "tls_sni". - -We can tell the two apart by state->received_sni being non-NULL in callback. - -The callback should not call us unless state->trigger_sni_changes is true, -which we are responsible for setting on the first pass through. - -Arguments: - state exim_gnutls_state_st * - errstr error string pointer - -Returns: OK/DEFER/FAIL -*/ +/************************************************** +* One-time init credentials for server and client * +**************************************************/ -static int -tls_expand_session_files(exim_gnutls_state_st * state, uschar ** errstr) +static void +creds_basic_init(gnutls_certificate_credentials_t x509_cred, BOOL server) { -struct stat statbuf; -int rc; -const host_item *host = state->host; /* macro should be reconsidered? */ -uschar *saved_tls_certificate = NULL; -uschar *saved_tls_privatekey = NULL; -uschar *saved_tls_verify_certificates = NULL; -uschar *saved_tls_crl = NULL; -int cert_count; - -/* We check for tls_sni *before* expansion. */ -if (!host) /* server */ - if (!state->received_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; - } - } - 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; - saved_tls_crl = state->exp_tls_crl; - } - -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); +gnutls_certificate_set_flags(x509_cred, GNUTLS_CERTIFICATE_API_V2); # if !defined(DISABLE_OCSP) && defined(SUPPORT_GNUTLS_EXT_RAW_PARSE) -if (!host && tls_ocsp_file) +if (server && tls_ocsp_file) { if (f.running_in_test_harness) tls_server_testharness_ocsp_fiddle(); if (exim_testharness_disable_ocsp_validity_check) - gnutls_certificate_set_flags(state->x509_cred, + gnutls_certificate_set_flags(x509_cred, GNUTLS_CERTIFICATE_API_V2 | GNUTLS_CERTIFICATE_SKIP_OCSP_RESPONSE_CHECK); } # endif #endif +DEBUG(D_tls) + debug_printf("TLS: basic cred init, %s\n", server ? "server" : "client"); +} -/* 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. */ - -/* check if we at least have a certificate, before doing expensive -D-H generation. */ - -if (!expand_check_tlsvar(tls_certificate, errstr)) - return DEFER; - -/* certificate is mandatory in server, optional in client */ - -if ( !state->exp_tls_certificate - || !*state->exp_tls_certificate - ) - if (!host) - return tls_install_selfsign(state, errstr); - else - DEBUG(D_tls) debug_printf("TLS: no client certificate specified; okay\n"); - -if (state->tls_privatekey && !expand_check_tlsvar(tls_privatekey, errstr)) - return DEFER; - -/* tls_privatekey is optional, defaulting to same file as certificate */ - -if (!state->tls_privatekey || !*state->tls_privatekey) - { - state->tls_privatekey = state->tls_certificate; - state->exp_tls_privatekey = state->exp_tls_certificate; - } - - -if (state->exp_tls_certificate && *state->exp_tls_certificate) - { - DEBUG(D_tls) debug_printf("certificate file = %s\nkey file = %s\n", - state->exp_tls_certificate, state->exp_tls_privatekey); - - if (state->received_sni) - if ( Ustrcmp(state->exp_tls_certificate, saved_tls_certificate) == 0 - && Ustrcmp(state->exp_tls_privatekey, saved_tls_privatekey) == 0 - ) - { - DEBUG(D_tls) debug_printf("TLS SNI: cert and key unchanged\n"); - } - else - { - DEBUG(D_tls) debug_printf("TLS SNI: have a changed cert/key pair.\n"); - } - - if (!host) /* server */ - { - const uschar * clist = state->exp_tls_certificate; - const uschar * klist = state->exp_tls_privatekey; - const uschar * olist; - int csep = 0, ksep = 0, osep = 0, cnt = 0; - uschar * cfile, * kfile, * ofile; +static int +creds_load_server_certs(exim_gnutls_state_st * state, const uschar * cert, + const uschar * pkey, const uschar * ocsp, uschar ** errstr) +{ +const uschar * clist = cert; +const uschar * klist = pkey; +const uschar * olist; +int csep = 0, ksep = 0, osep = 0, cnt = 0, rc; +uschar * cfile, * kfile, * ofile; #ifndef DISABLE_OCSP # ifdef SUPPORT_GNUTLS_EXT_RAW_PARSE - gnutls_x509_crt_fmt_t ocsp_fmt = GNUTLS_X509_FMT_DER; +gnutls_x509_crt_fmt_t ocsp_fmt = GNUTLS_X509_FMT_DER; # endif - if (!expand_check(tls_ocsp_file, US"tls_ocsp_file", &ofile, errstr)) - return DEFER; - olist = ofile; +if (!expand_check(ocsp, US"tls_ocsp_file", &ofile, errstr)) + return DEFER; +olist = ofile; #endif - while (cfile = string_nextinlist(&clist, &csep, NULL, 0)) +while (cfile = string_nextinlist(&clist, &csep, NULL, 0)) - if (!(kfile = string_nextinlist(&klist, &ksep, NULL, 0))) - return tls_error(US"cert/key setup: out of keys", NULL, host, errstr); - else if (0 < (rc = tls_add_certfile(state, host, cfile, kfile, errstr))) - return rc; - else - { - int gnutls_cert_index = -rc; - DEBUG(D_tls) debug_printf("TLS: cert/key %d %s registered\n", - gnutls_cert_index, cfile); + if (!(kfile = string_nextinlist(&klist, &ksep, NULL, 0))) + return tls_error(US"cert/key setup: out of keys", NULL, NULL, errstr); + else if ((rc = tls_add_certfile(state, NULL, cfile, kfile, errstr)) > 0) + return rc; + else + { + int gnutls_cert_index = -rc; + DEBUG(D_tls) debug_printf("TLS: cert/key %d %s registered\n", + gnutls_cert_index, cfile); #ifndef DISABLE_OCSP - if (tls_ocsp_file) - { - /* Set the OCSP stapling server info */ - if (gnutls_buggy_ocsp) - { - DEBUG(D_tls) - debug_printf("GnuTLS library is buggy for OCSP; avoiding\n"); - } - else if ((ofile = string_nextinlist(&olist, &osep, NULL, 0))) - { - DEBUG(D_tls) debug_printf("OCSP response file %d = %s\n", - gnutls_cert_index, ofile); + if (ocsp) + { + /* Set the OCSP stapling server info */ + if (gnutls_buggy_ocsp) + { + DEBUG(D_tls) + debug_printf("GnuTLS library is buggy for OCSP; avoiding\n"); + } + else if ((ofile = string_nextinlist(&olist, &osep, NULL, 0))) + { + DEBUG(D_tls) debug_printf("OCSP response file %d = %s\n", + gnutls_cert_index, ofile); # ifdef SUPPORT_GNUTLS_EXT_RAW_PARSE - if (Ustrncmp(ofile, US"PEM ", 4) == 0) - { - ocsp_fmt = GNUTLS_X509_FMT_PEM; - ofile += 4; - } - else if (Ustrncmp(ofile, US"DER ", 4) == 0) - { - ocsp_fmt = GNUTLS_X509_FMT_DER; - ofile += 4; - } - - if ((rc = gnutls_certificate_set_ocsp_status_request_file2( - state->x509_cred, CCS ofile, gnutls_cert_index, - ocsp_fmt)) < 0) - return tls_error_gnu( - US"gnutls_certificate_set_ocsp_status_request_file2", - rc, host, errstr); - DEBUG(D_tls) - debug_printf(" %d response%s loaded\n", rc, rc>1 ? "s":""); + if (Ustrncmp(ofile, US"PEM ", 4) == 0) + { + ocsp_fmt = GNUTLS_X509_FMT_PEM; + ofile += 4; + } + else if (Ustrncmp(ofile, US"DER ", 4) == 0) + { + ocsp_fmt = GNUTLS_X509_FMT_DER; + ofile += 4; + } + + if ((rc = gnutls_certificate_set_ocsp_status_request_file2( + state->lib_state.x509_cred, CCS ofile, gnutls_cert_index, + ocsp_fmt)) < 0) + return tls_error_gnu( + US"gnutls_certificate_set_ocsp_status_request_file2", + rc, NULL, errstr); + DEBUG(D_tls) + debug_printf(" %d response%s loaded\n", rc, rc>1 ? "s":""); + + /* Arrange callbacks for OCSP request observability */ - /* Arrange callbacks for OCSP request observability */ + if (state->session) + gnutls_handshake_set_hook_function(state->session, + GNUTLS_HANDSHAKE_ANY, GNUTLS_HOOK_POST, tls_server_hook_cb); + else + state->lib_state.ocsp_hook = TRUE; - gnutls_handshake_set_hook_function(state->session, - GNUTLS_HANDSHAKE_ANY, GNUTLS_HOOK_POST, tls_server_hook_cb); # else # if defined(SUPPORT_SRV_OCSP_STACK) - 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 ((rc = gnutls_certificate_set_ocsp_status_request_function2( + state->lib_state.x509_cred, gnutls_cert_index, + server_ocsp_stapling_cb, ofile))) + return tls_error_gnu( + US"gnutls_certificate_set_ocsp_status_request_function2", + rc, NULL, errstr); + else # endif - { - if (cnt++ > 0) - { - DEBUG(D_tls) - debug_printf("oops; multiple OCSP files not supported\n"); - break; - } - gnutls_certificate_set_ocsp_status_request_function( - state->x509_cred, server_ocsp_stapling_cb, ofile); - } -# endif /* SUPPORT_GNUTLS_EXT_RAW_PARSE */ + { + if (cnt++ > 0) + { + DEBUG(D_tls) + debug_printf("oops; multiple OCSP files not supported\n"); + break; } - else - DEBUG(D_tls) debug_printf("ran out of OCSP response files in list\n"); + gnutls_certificate_set_ocsp_status_request_function( + state->lib_state.x509_cred, server_ocsp_stapling_cb, ofile); } -#endif /* DISABLE_OCSP */ +# endif /* SUPPORT_GNUTLS_EXT_RAW_PARSE */ } + else + DEBUG(D_tls) debug_printf("ran out of OCSP response files in list\n"); + } +#endif /* DISABLE_OCSP */ } - else /* client */ - { - if (0 < (rc = tls_add_certfile(state, host, - state->exp_tls_certificate, state->exp_tls_privatekey, errstr))) - return rc; - DEBUG(D_tls) debug_printf("TLS: cert/key registered\n"); - } - - } /* tls_certificate */ +return 0; +} +static int +creds_load_client_certs(exim_gnutls_state_st * state, const host_item * host, + const uschar * cert, const uschar * pkey, uschar ** errstr) +{ +int rc = tls_add_certfile(state, host, cert, pkey, errstr); +if (rc > 0) return rc; +DEBUG(D_tls) debug_printf("TLS: cert/key registered\n"); +return 0; +} -/* 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 -error message is provided. However, if we just refrain from setting anything up -in that case, certificate verification fails, which seems to be the correct -behaviour. */ +static int +creds_load_cabundle(exim_gnutls_state_st * state, const uschar * bundle, + const host_item * host, uschar ** errstr) +{ +int cert_count; +struct stat statbuf; -if (state->tls_verify_certificates && *state->tls_verify_certificates) +#ifdef SUPPORT_SYSDEFAULT_CABUNDLE +if (Ustrcmp(bundle, "system") == 0 || Ustrncmp(bundle, "system,", 7) == 0) + cert_count = gnutls_certificate_set_x509_system_trust(state->lib_state.x509_cred); +else +#endif { - if (!expand_check_tlsvar(tls_verify_certificates, errstr)) + if (Ustat(bundle, &statbuf) < 0) + { + log_write(0, LOG_MAIN|LOG_PANIC, "could not stat '%s' " + "(tls_verify_certificates): %s", bundle, strerror(errno)); return DEFER; -#ifndef SUPPORT_SYSDEFAULT_CABUNDLE - if (Ustrcmp(state->exp_tls_verify_certificates, "system") == 0) - state->exp_tls_verify_certificates = NULL; -#endif - if (state->tls_crl && *state->tls_crl) - if (!expand_check_tlsvar(tls_crl, errstr)) - return DEFER; - - if (!(state->exp_tls_verify_certificates && - *state->exp_tls_verify_certificates)) - { - 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; - } - -#ifdef SUPPORT_SYSDEFAULT_CABUNDLE -if (Ustrcmp(state->exp_tls_verify_certificates, "system") == 0) - cert_count = gnutls_certificate_set_x509_system_trust(state->x509_cred); -else -#endif - { - 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; - } + } #ifndef SUPPORT_CA_DIR /* The test suite passes in /dev/null; we could check for that path explicitly, @@ -1351,18 +1344,14 @@ else So s/!S_ISREG/S_ISDIR/ and change some messaging ... */ if (S_ISDIR(statbuf.st_mode)) { - DEBUG(D_tls) - debug_printf("verify certificates path is a dir: \"%s\"\n", - state->exp_tls_verify_certificates); log_write(0, LOG_MAIN|LOG_PANIC, - "tls_verify_certificates \"%s\" is a directory", - state->exp_tls_verify_certificates); + "tls_verify_certificates \"%s\" is a directory", bundle); return DEFER; } #endif DEBUG(D_tls) debug_printf("verify certificates = %s size=" OFF_T_FMT "\n", - state->exp_tls_verify_certificates, statbuf.st_size); + bundle, statbuf.st_size); if (statbuf.st_size == 0) { @@ -1376,37 +1365,508 @@ else #ifdef SUPPORT_CA_DIR (statbuf.st_mode & S_IFMT) == S_IFDIR ? - gnutls_certificate_set_x509_trust_dir(state->x509_cred, - CS state->exp_tls_verify_certificates, GNUTLS_X509_FMT_PEM) + gnutls_certificate_set_x509_trust_dir(state->lib_state.x509_cred, + CS bundle, GNUTLS_X509_FMT_PEM) : #endif - gnutls_certificate_set_x509_trust_file(state->x509_cred, - CS state->exp_tls_verify_certificates, GNUTLS_X509_FMT_PEM); + gnutls_certificate_set_x509_trust_file(state->lib_state.x509_cred, + CS bundle, GNUTLS_X509_FMT_PEM); #ifdef SUPPORT_CA_DIR /* Mimic the behaviour with OpenSSL of not advertising a usable-cert list when using the directory-of-certs config model. */ if ((statbuf.st_mode & S_IFMT) == S_IFDIR) - gnutls_certificate_send_x509_rdn_sequence(state->session, 1); + if (state->session) + gnutls_certificate_send_x509_rdn_sequence(state->session, 1); + else + state->lib_state.ca_rdn_emulate = TRUE; #endif } if (cert_count < 0) return tls_error_gnu(US"setting certificate trust", cert_count, host, errstr); DEBUG(D_tls) - debug_printf("Added %d certificate authorities.\n", cert_count); + debug_printf("Added %d certificate authorities\n", cert_count); + +return OK; +} + + +static int +creds_load_crl(exim_gnutls_state_st * state, const uschar * crl, uschar ** errstr) +{ +int cert_count; +DEBUG(D_tls) debug_printf("loading CRL file = %s\n", crl); +if ((cert_count = gnutls_certificate_set_x509_crl_file(state->lib_state.x509_cred, + CS crl, GNUTLS_X509_FMT_PEM)) < 0) + return tls_error_gnu(US"gnutls_certificate_set_x509_crl_file", + cert_count, state->host, errstr); + +DEBUG(D_tls) debug_printf("Processed %d CRLs\n", cert_count); +return OK; +} + + +static int +creds_load_pristring(exim_gnutls_state_st * state, const uschar * p, + const char ** errpos) +{ +if (!p) + { + p = exim_default_gnutls_priority; + DEBUG(D_tls) + debug_printf("GnuTLS using default session cipher/priority \"%s\"\n", p); + } +return gnutls_priority_init( (gnutls_priority_t *) &state->lib_state.pri_cache, + CCS p, errpos); +} + +static unsigned +tls_server_creds_init(void) +{ +uschar * dummy_errstr; +unsigned lifetime = 0; + +state_server.lib_state = null_tls_preload; +if (gnutls_certificate_allocate_credentials( + (gnutls_certificate_credentials_t *) &state_server.lib_state.x509_cred)) + { + state_server.lib_state.x509_cred = NULL; + return lifetime; + } +creds_basic_init(state_server.lib_state.x509_cred, TRUE); + +#if defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT) +/* If tls_certificate has any $ indicating expansions, it is not good. +If tls_privatekey is set but has $, not good. Likewise for tls_ocsp_file. +If all good (and tls_certificate set), load the cert(s). */ + +if ( opt_set_and_noexpand(tls_certificate) +# ifndef DISABLE_OCSP + && opt_unset_or_noexpand(tls_ocsp_file) +# endif + && opt_unset_or_noexpand(tls_privatekey)) + { + /* Set watches on the filenames. The implementation does de-duplication + so we can just blindly do them all. + */ + + if ( tls_set_watch(tls_certificate, TRUE) +# ifndef DISABLE_OCSP + && tls_set_watch(tls_ocsp_file, TRUE) +# endif + && tls_set_watch(tls_privatekey, TRUE)) + { + DEBUG(D_tls) debug_printf("TLS: preloading server certs\n"); + if (creds_load_server_certs(&state_server, tls_certificate, + tls_privatekey && *tls_privatekey ? tls_privatekey : tls_certificate, +# ifdef DISABLE_OCSP + NULL, +# else + tls_ocsp_file, +# endif + &dummy_errstr) == 0) + state_server.lib_state.conn_certs = TRUE; + } + } +else if ( !tls_certificate && !tls_privatekey +# ifndef DISABLE_OCSP + && !tls_ocsp_file +# endif + ) + { /* Generate & preload a selfsigned cert. No files to watch. */ + if ((tls_install_selfsign(&state_server, &dummy_errstr)) == OK) + { + state_server.lib_state.conn_certs = TRUE; + lifetime = f.running_in_test_harness ? 2 : 60 * 60; /* 1 hour */ + } + } +else + DEBUG(D_tls) debug_printf("TLS: not preloading server certs\n"); + +/* If tls_verify_certificates is non-empty and has no $, load CAs */ + +if (opt_set_and_noexpand(tls_verify_certificates)) + { + if (tls_set_watch(tls_verify_certificates, FALSE)) + { + DEBUG(D_tls) debug_printf("TLS: preloading CA bundle for server\n"); + if (creds_load_cabundle(&state_server, tls_verify_certificates, + NULL, &dummy_errstr) != OK) + return lifetime; + state_server.lib_state.cabundle = TRUE; + + /* If CAs loaded and tls_crl is non-empty and has no $, load it */ + + if (opt_set_and_noexpand(tls_crl)) + { + if (tls_set_watch(tls_crl, FALSE)) + { + DEBUG(D_tls) debug_printf("TLS: preloading CRL for server\n"); + if (creds_load_crl(&state_server, tls_crl, &dummy_errstr) != OK) + return lifetime; + state_server.lib_state.crl = TRUE; + } + } + else + DEBUG(D_tls) debug_printf("TLS: not preloading CRL for server\n"); + } + } +else + DEBUG(D_tls) debug_printf("TLS: not preloading CA bundle for server\n"); +#endif /* EXIM_HAVE_INOTIFY */ + +/* If tls_require_ciphers is non-empty and has no $, load the +ciphers priority cache. If unset, load with the default. +(server-only as the client one depends on non/DANE) */ + +if (!tls_require_ciphers || opt_set_and_noexpand(tls_require_ciphers)) + { + const char * dummy_errpos; + DEBUG(D_tls) debug_printf("TLS: preloading cipher list for server: %s\n", + tls_require_ciphers); + if ( creds_load_pristring(&state_server, tls_require_ciphers, &dummy_errpos) + == OK) + state_server.lib_state.pri_string = TRUE; + } +else + DEBUG(D_tls) debug_printf("TLS: not preloading cipher list for server\n"); +return lifetime; +} + + +/* Preload whatever creds are static, onto a transport. The client can then +just copy the pointer as it starts up. */ + +static void +tls_client_creds_init(transport_instance * t, BOOL watch) +{ +smtp_transport_options_block * ob = t->options_block; +exim_gnutls_state_st tpt_dummy_state; +host_item * dummy_host = (host_item *)1; +uschar * dummy_errstr; + +if ( !exim_gnutls_base_init_done + && tls_g_init(&dummy_errstr) != OK) + return; + +ob->tls_preload = null_tls_preload; +if (gnutls_certificate_allocate_credentials( + (struct gnutls_certificate_credentials_st **)&ob->tls_preload.x509_cred)) + { + ob->tls_preload.x509_cred = NULL; + return; + } +creds_basic_init(ob->tls_preload.x509_cred, FALSE); + +tpt_dummy_state.session = NULL; +tpt_dummy_state.lib_state = ob->tls_preload; + +#if defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT) +if ( opt_set_and_noexpand(ob->tls_certificate) + && opt_unset_or_noexpand(ob->tls_privatekey)) + { + if ( !watch + || ( tls_set_watch(ob->tls_certificate, FALSE) + && tls_set_watch(ob->tls_privatekey, FALSE) + ) ) + { + const uschar * pkey = ob->tls_privatekey; + + DEBUG(D_tls) + debug_printf("TLS: preloading client certs for transport '%s'\n", t->name); + + /* The state->lib_state.x509_cred is used for the certs load, and is the sole + structure element used. So we can set up a dummy. The hoat arg only + selects a retcode in case of fail, so any value */ + + if (creds_load_client_certs(&tpt_dummy_state, dummy_host, + ob->tls_certificate, pkey ? pkey : ob->tls_certificate, + &dummy_errstr) == OK) + ob->tls_preload.conn_certs = TRUE; + } + } +else + DEBUG(D_tls) + debug_printf("TLS: not preloading client certs, for transport '%s'\n", t->name); + +if (opt_set_and_noexpand(ob->tls_verify_certificates)) + { + if (!watch || tls_set_watch(ob->tls_verify_certificates, FALSE)) + { + DEBUG(D_tls) + debug_printf("TLS: preloading CA bundle for transport '%s'\n", t->name); + if (creds_load_cabundle(&tpt_dummy_state, ob->tls_verify_certificates, + dummy_host, &dummy_errstr) != OK) + return; + ob->tls_preload.cabundle = TRUE; + + if (opt_set_and_noexpand(ob->tls_crl)) + { + if (!watch || tls_set_watch(ob->tls_crl, FALSE)) + { + DEBUG(D_tls) debug_printf("TLS: preloading CRL for transport '%s'\n", t->name); + if (creds_load_crl(&tpt_dummy_state, ob->tls_crl, &dummy_errstr) != OK) + return; + ob->tls_preload.crl = TRUE; + } + } + else + DEBUG(D_tls) debug_printf("TLS: not preloading CRL, for transport '%s'\n", t->name); + } + } +else + DEBUG(D_tls) + debug_printf("TLS: not preloading CA bundle, for transport '%s'\n", t->name); + +/* We do not preload tls_require_ciphers to to the transport as it implicitly +depends on DANE or plain usage. */ + +#endif +} + + +#if defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT) +/* Invalidate the creds cached, by dropping the current ones. +Call when we notice one of the source files has changed. */ + +static void +tls_server_creds_invalidate(void) +{ +if (state_server.lib_state.pri_cache) + gnutls_priority_deinit(state_server.lib_state.pri_cache); +state_server.lib_state.pri_cache = NULL; + +if (state_server.lib_state.x509_cred) + gnutls_certificate_free_credentials(state_server.lib_state.x509_cred); +state_server.lib_state = null_tls_preload; +} + + +static void +tls_client_creds_invalidate(transport_instance * t) +{ +smtp_transport_options_block * ob = t->options_block; +if (ob->tls_preload.x509_cred) + gnutls_certificate_free_credentials(ob->tls_preload.x509_cred); +ob->tls_preload = null_tls_preload; +} +#endif + + +/************************************************* +* Variables re-expanded post-SNI * +*************************************************/ + +/* Called from both server and client code, via tls_init(), and also from +the SNI callback after receiving an SNI, if tls_certificate includes "tls_sni". + +We can tell the two apart by state->received_sni being non-NULL in callback. + +The callback should not call us unless state->trigger_sni_changes is true, +which we are responsible for setting on the first pass through. + +Arguments: + state exim_gnutls_state_st * + errstr error string pointer + +Returns: OK/DEFER/FAIL +*/ + +static int +tls_expand_session_files(exim_gnutls_state_st * state, uschar ** errstr) +{ +int rc; +const host_item *host = state->host; /* macro should be reconsidered? */ +const uschar *saved_tls_certificate = NULL; +const uschar *saved_tls_privatekey = NULL; +const uschar *saved_tls_verify_certificates = NULL; +const uschar *saved_tls_crl = NULL; +int cert_count; + +/* We check for tls_sni *before* expansion. */ +if (!host) /* server */ + if (!state->received_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; + } + } + else /* SNI callback case */ + { + /* 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; + saved_tls_crl = state->exp_tls_crl; + } + +if (!state->lib_state.x509_cred) + { + if ((rc = gnutls_certificate_allocate_credentials( + (gnutls_certificate_credentials_t *) &state->lib_state.x509_cred))) + return tls_error_gnu(US"gnutls_certificate_allocate_credentials", + rc, host, errstr); + creds_basic_init(state->lib_state.x509_cred, !host); + } + + +/* 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. */ + +/* check if we at least have a certificate, before doing expensive +D-H generation. */ + +if (!state->lib_state.conn_certs) + { + if (!Expand_check_tlsvar(tls_certificate, errstr)) + return DEFER; + + /* certificate is mandatory in server, optional in client */ + + if ( !state->exp_tls_certificate + || !*state->exp_tls_certificate + ) + if (!host) + return tls_install_selfsign(state, errstr); + else + DEBUG(D_tls) debug_printf("TLS: no client certificate specified; okay\n"); + + if (state->tls_privatekey && !Expand_check_tlsvar(tls_privatekey, errstr)) + return DEFER; + + /* tls_privatekey is optional, defaulting to same file as certificate */ + + if (!state->tls_privatekey || !*state->tls_privatekey) + { + state->tls_privatekey = state->tls_certificate; + state->exp_tls_privatekey = state->exp_tls_certificate; + } + + if (state->exp_tls_certificate && *state->exp_tls_certificate) + { + BOOL load = TRUE; + DEBUG(D_tls) debug_printf("certificate file = %s\nkey file = %s\n", + state->exp_tls_certificate, state->exp_tls_privatekey); + + if (state->received_sni) + if ( Ustrcmp(state->exp_tls_certificate, saved_tls_certificate) == 0 + && Ustrcmp(state->exp_tls_privatekey, saved_tls_privatekey) == 0 + ) + { + DEBUG(D_tls) debug_printf("TLS SNI: cert and key unchanged\n"); + load = FALSE; /* avoid re-loading the same certs */ + } + else /* unload the pre-SNI certs before loading new ones */ + { + DEBUG(D_tls) debug_printf("TLS SNI: have a changed cert/key pair\n"); + gnutls_certificate_free_keys(state->lib_state.x509_cred); + } + + if ( load + && (rc = host + ? creds_load_client_certs(state, host, state->exp_tls_certificate, + state->exp_tls_privatekey, errstr) + : creds_load_server_certs(state, state->exp_tls_certificate, + state->exp_tls_privatekey, +#ifdef DISABLE_OCSP + NULL, +#else + tls_ocsp_file, +#endif + errstr) + ) ) return rc; + } + } +else + { + DEBUG(D_tls) + debug_printf("%s certs were preloaded\n", host ? "client" : "server"); + + if (!state->tls_privatekey) state->tls_privatekey = state->tls_certificate; + state->exp_tls_certificate = US state->tls_certificate; + state->exp_tls_privatekey = US state->tls_privatekey; + +#ifdef SUPPORT_GNUTLS_EXT_RAW_PARSE + if (state->lib_state.ocsp_hook) + gnutls_handshake_set_hook_function(state->session, + GNUTLS_HANDSHAKE_ANY, GNUTLS_HOOK_POST, tls_server_hook_cb); +#endif + } + -if (state->tls_crl && *state->tls_crl && - state->exp_tls_crl && *state->exp_tls_crl) +/* 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 +error message is provided. However, if we just refrain from setting anything up +in that case, certificate verification fails, which seems to be the correct +behaviour. */ + +if (!state->lib_state.cabundle) + { + if (state->tls_verify_certificates && *state->tls_verify_certificates) + { + if (!Expand_check_tlsvar(tls_verify_certificates, errstr)) + return DEFER; +#ifndef SUPPORT_SYSDEFAULT_CABUNDLE + if (Ustrcmp(state->exp_tls_verify_certificates, "system") == 0) + state->exp_tls_verify_certificates = NULL; +#endif + if (state->tls_crl && *state->tls_crl) + if (!Expand_check_tlsvar(tls_crl, errstr)) + return DEFER; + + if (!(state->exp_tls_verify_certificates && + *state->exp_tls_verify_certificates)) + { + 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; + } + rc = creds_load_cabundle(state, state->exp_tls_verify_certificates, host, errstr); + if (rc != OK) return rc; + } +else { - DEBUG(D_tls) debug_printf("loading CRL file = %s\n", state->exp_tls_crl); - 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("%s CA bundle was preloaded\n", host ? "client" : "server"); + state->exp_tls_verify_certificates = US state->tls_verify_certificates; - DEBUG(D_tls) debug_printf("Processed %d CRLs.\n", cert_count); +#ifdef SUPPORT_CA_DIR +/* Mimic the behaviour with OpenSSL of not advertising a usable-cert list +when using the directory-of-certs config model. */ + if (state->lib_state.ca_rdn_emulate) + gnutls_certificate_send_x509_rdn_sequence(state->session, 1); +#endif + } + + +if (!state->lib_state.crl) + { + if ( state->tls_crl && *state->tls_crl + && state->exp_tls_crl && *state->exp_tls_crl) + return creds_load_crl(state, state->exp_tls_crl, errstr); + } +else + { + DEBUG(D_tls) + debug_printf("%s CRL was preloaded\n", host ? "client" : "server"); + state->exp_tls_crl = US state->tls_crl; } return OK; @@ -1449,14 +1909,14 @@ if (!state->host) if ((rc = init_server_dh(errstr)) != OK) return rc; /* Unnecessary & discouraged with 3.6.0 or later */ - gnutls_certificate_set_dh_params(state->x509_cred, dh_server_params); + gnutls_certificate_set_dh_params(state->lib_state.x509_cred, dh_server_params); } #endif /* Link the credentials to the session. */ if ((rc = gnutls_credentials_set(state->session, - GNUTLS_CRD_CERTIFICATE, state->x509_cred))) + GNUTLS_CRD_CERTIFICATE, state->lib_state.x509_cred))) return tls_error_gnu(US"gnutls_credentials_set", rc, host, errstr); return OK; @@ -1467,47 +1927,12 @@ return OK; *************************************************/ -#ifndef DISABLE_OCSP - -static BOOL -tls_is_buggy_ocsp(void) -{ -const uschar * s; -uschar maj, mid, mic; - -s = CUS gnutls_check_version(NULL); -maj = atoi(CCS s); -if (maj == 3) - { - while (*s && *s != '.') s++; - mid = atoi(CCS ++s); - if (mid <= 2) - return TRUE; - else if (mid >= 5) - return FALSE; - else - { - while (*s && *s != '.') s++; - mic = atoi(CCS ++s); - return mic <= (mid == 3 ? 16 : 3); - } - } -return FALSE; -} - -#endif - - /* Called from both server and client code. In the case of a server, errors before actual TLS negotiation return DEFER. Arguments: host connected host, if client; NULL if server - certificate certificate file - privatekey private key file - sni TLS SNI to send, sometimes when client; else NULL - cas CA certs file - crl CRL file + ob tranport options block, if client; NULL if server require_ciphers tls_require_ciphers setting caller_state returned state-info structure errstr error string pointer @@ -1518,12 +1943,8 @@ Returns: OK/DEFER/FAIL static int tls_init( const host_item *host, - const uschar *certificate, - const uschar *privatekey, - const uschar *sni, - const uschar *cas, - const uschar *crl, - const uschar *require_ciphers, + smtp_transport_options_block * ob, + const uschar * require_ciphers, exim_gnutls_state_st **caller_state, tls_support * tlsp, uschar ** errstr) @@ -1531,85 +1952,61 @@ tls_init( exim_gnutls_state_st * state; int rc; size_t sz; -const char * errpos; -const uschar * p; - -if (!exim_gnutls_base_init_done) - { - DEBUG(D_tls) debug_printf("GnuTLS global init required.\n"); - -#if defined(HAVE_GNUTLS_PKCS11) && !defined(GNUTLS_AUTO_PKCS11_MANUAL) - /* 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) - if ((rc = gnutls_pkcs11_init(GNUTLS_PKCS11_FLAG_MANUAL, NULL))) - return tls_error_gnu(US"gnutls_pkcs11_init", rc, host, errstr); -#endif - -#ifndef GNUTLS_AUTO_GLOBAL_INIT - if ((rc = gnutls_global_init())) - return tls_error_gnu(US"gnutls_global_init", rc, host, errstr); -#endif - -#if EXIM_GNUTLS_LIBRARY_LOG_LEVEL >= 0 - DEBUG(D_tls) - { - gnutls_global_set_log_function(exim_gnutls_logger_cb); - /* arbitrarily chosen level; bump up to 9 for more */ - gnutls_global_set_log_level(EXIM_GNUTLS_LIBRARY_LOG_LEVEL); - } -#endif - -#ifndef DISABLE_OCSP - if (tls_ocsp_file && (gnutls_buggy_ocsp = tls_is_buggy_ocsp())) - log_write(0, LOG_MAIN, "OCSP unusable with this GnuTLS library version"); -#endif - exim_gnutls_base_init_done = TRUE; - } +if ( !exim_gnutls_base_init_done + && (rc = tls_g_init(errstr)) != OK) + return rc; if (host) { /* 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), FALSE); store_pool = old_pool; memcpy(state, &exim_gnutls_state_init, sizeof(exim_gnutls_state_init)); + state->lib_state = ob->tls_preload; state->tlsp = tlsp; DEBUG(D_tls) debug_printf("initialising GnuTLS client session\n"); rc = gnutls_init(&state->session, GNUTLS_CLIENT); + + state->tls_certificate = ob->tls_certificate; + state->tls_privatekey = ob->tls_privatekey; + state->tls_sni = ob->tls_sni; + state->tls_verify_certificates = ob->tls_verify_certificates; + state->tls_crl = ob->tls_crl; } else { + /* Server operations always use the one state_server context. It is not + shared because we have forked a fresh process for every receive. However it + can get re-used for successive TLS sessions on a single TCP connection. */ + state = &state_server; - memcpy(state, &exim_gnutls_state_init, sizeof(exim_gnutls_state_init)); state->tlsp = tlsp; DEBUG(D_tls) debug_printf("initialising GnuTLS server session\n"); rc = gnutls_init(&state->session, GNUTLS_SERVER); + + state->tls_certificate = tls_certificate; + state->tls_privatekey = tls_privatekey; + state->tls_sni = NULL; + state->tls_verify_certificates = tls_verify_certificates; + state->tls_crl = tls_crl; } if (rc) return tls_error_gnu(US"gnutls_init", rc, host, errstr); +state->tls_require_ciphers = require_ciphers; state->host = host; -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; - /* This handles the variables that might get re-expanded after TLS SNI; -that's tls_certificate, tls_privatekey, tls_verify_certificates, tls_crl */ +tls_certificate, tls_privatekey, tls_verify_certificates, tls_crl */ DEBUG(D_tls) - debug_printf("Expanding various TLS configuration options for session credentials.\n"); + debug_printf("Expanding various TLS configuration options for session credentials\n"); if ((rc = tls_expand_session_files(state, errstr)) != OK) return rc; /* These are all other parts of the x509_cred handling, since SNI in GnuTLS @@ -1620,7 +2017,7 @@ if ((rc = tls_set_remaining_x509(state, errstr)) != OK) return rc; /* set SNI in client, only */ if (host) { - if (!expand_check(sni, US"tls_out_sni", &state->tlsp->sni, errstr)) + if (!expand_check(state->tls_sni, US"tls_out_sni", &state->tlsp->sni, errstr)) return DEFER; if (state->tlsp->sni && *state->tlsp->sni) { @@ -1636,37 +2033,42 @@ else if (state->tls_sni) DEBUG(D_tls) debug_printf("*** PROBABLY A BUG *** " \ "have an SNI set for a server [%s]\n", state->tls_sni); -/* This is the priority string support, -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. */ - -p = NULL; -if (state->tls_require_ciphers && *state->tls_require_ciphers) +if (!state->lib_state.pri_string) { - if (!expand_check_tlsvar(tls_require_ciphers, errstr)) - return DEFER; - if (state->exp_tls_require_ciphers && *state->exp_tls_require_ciphers) + const uschar * p = NULL; + const char * errpos; + + /* This is the priority string support, + 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. */ + + if (state->tls_require_ciphers && *state->tls_require_ciphers) { - p = state->exp_tls_require_ciphers; - DEBUG(D_tls) debug_printf("GnuTLS session cipher/priority \"%s\"\n", p); + if (!Expand_check_tlsvar(tls_require_ciphers, errstr)) + return DEFER; + if (state->exp_tls_require_ciphers && *state->exp_tls_require_ciphers) + { + p = state->exp_tls_require_ciphers; + DEBUG(D_tls) debug_printf("GnuTLS session cipher/priority \"%s\"\n", p); + } } + + if ((rc = creds_load_pristring(state, 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); } -if (!p) +else { - p = exim_default_gnutls_priority; - DEBUG(D_tls) - debug_printf("GnuTLS using default session cipher/priority \"%s\"\n", p); + DEBUG(D_tls) debug_printf("cipher list preloaded\n"); + state->exp_tls_require_ciphers = US state->tls_require_ciphers; } -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); -if ((rc = gnutls_priority_set(state->session, state->priority_cache))) +if ((rc = gnutls_priority_set(state->session, state->lib_state.pri_cache))) return tls_error_gnu(US"gnutls_priority_set", rc, host, errstr); /* This also sets the server ticket expiration time to the same, and @@ -2193,7 +2595,7 @@ 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"); + 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); @@ -2404,7 +2806,6 @@ the STARTTLS command. It must respond to that command, and then negotiate a TLS session. Arguments: - require_ciphers list of allowed ciphers or NULL errstr pointer to error string Returns: OK on success @@ -2414,7 +2815,7 @@ Returns: OK on success */ int -tls_server_start(const uschar * require_ciphers, uschar ** errstr) +tls_server_start(uschar ** errstr) { int rc; exim_gnutls_state_st * state = NULL; @@ -2438,9 +2839,8 @@ DEBUG(D_tls) debug_printf("initialising GnuTLS as a server\n"); gettimeofday(&t0, NULL); #endif - if ((rc = tls_init(NULL, tls_certificate, tls_privatekey, - NULL, tls_verify_certificates, tls_crl, - require_ciphers, &state, &tls_in, errstr)) != OK) return rc; + if ((rc = tls_init(NULL, NULL, + tls_require_ciphers, &state, &tls_in, errstr)) != OK) return rc; #ifdef MEASURE_TIMING report_time_since(&t0, US"server tls_init (delta)"); @@ -2457,21 +2857,21 @@ optional, set up appropriately. */ if (verify_check_host(&tls_verify_hosts) == OK) { DEBUG(D_tls) - debug_printf("TLS: a client certificate will be required.\n"); + debug_printf("TLS: a client certificate will be required\n"); state->verify_requirement = VERIFY_REQUIRED; gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_REQUIRE); } else if (verify_check_host(&tls_try_verify_hosts) == OK) { DEBUG(D_tls) - debug_printf("TLS: a client certificate will be requested but not required.\n"); + debug_printf("TLS: a client certificate will be requested but not required\n"); state->verify_requirement = VERIFY_OPTIONAL; gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_REQUEST); } else { DEBUG(D_tls) - debug_printf("TLS: a client certificate will not be requested.\n"); + debug_printf("TLS: a client certificate will not be requested\n"); state->verify_requirement = VERIFY_NONE; gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_IGNORE); } @@ -2481,7 +2881,7 @@ if (event_action) { state->event_action = event_action; gnutls_session_set_ptr(state->session, state); - gnutls_certificate_set_verify_function(state->x509_cred, verify_cb); + gnutls_certificate_set_verify_function(state->lib_state.x509_cred, verify_cb); } #endif @@ -2538,7 +2938,8 @@ if (rc != GNUTLS_E_SUCCESS) 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); + gnutls_certificate_free_credentials(state->lib_state.x509_cred); + state->lib_state = null_tls_preload; millisleep(500); shutdown(state->fd_out, SHUT_WR); for (int i = 1024; fgetc(smtp_in) != EOF && i > 0; ) i--; /* drain skt */ @@ -2611,7 +3012,7 @@ if (verify_check_given_host(CUSS &ob->tls_verify_cert_hostnames, host) == OK) host->certname; #endif DEBUG(D_tls) - debug_printf("TLS: server cert verification includes hostname: \"%s\".\n", + debug_printf("TLS: server cert verification includes hostname: \"%s\"\n", state->exp_tls_verify_cert_hostnames); } } @@ -2872,7 +3273,7 @@ be requested and supplied, dane verify must pass, and cert verify irrelevant if (conn_args->dane && ob->dane_require_tls_ciphers) { - /* not using expand_check_tlsvar because not yet in state */ + /* 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; @@ -2890,9 +3291,7 @@ if (!cipher_list) gettimeofday(&t0, NULL); #endif - if (tls_init(host, ob->tls_certificate, ob->tls_privatekey, - ob->tls_sni, ob->tls_verify_certificates, ob->tls_crl, - cipher_list, &state, tlsp, errstr) != OK) + if (tls_init(host, ob, cipher_list, &state, tlsp, errstr) != OK) return FALSE; #ifdef MEASURE_TIMING @@ -2925,7 +3324,7 @@ the specified host patterns if one of them is defined */ if (conn_args->dane && dane_tlsa_load(state, &conn_args->tlsa_dnsa)) { DEBUG(D_tls) - debug_printf("TLS: server certificate DANE required.\n"); + debug_printf("TLS: server certificate DANE required\n"); state->verify_requirement = VERIFY_DANE; gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_REQUIRE); } @@ -2940,7 +3339,7 @@ else { tls_client_setup_hostname_checks(host, state, ob); DEBUG(D_tls) - debug_printf("TLS: server certificate verification required.\n"); + debug_printf("TLS: server certificate verification required\n"); state->verify_requirement = VERIFY_REQUIRED; gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_REQUIRE); } @@ -2948,14 +3347,14 @@ 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_printf("TLS: server certificate verification optional.\n"); + 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 not required.\n"); + debug_printf("TLS: server certificate verification not required\n"); state->verify_requirement = VERIFY_NONE; gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_IGNORE); } @@ -2984,7 +3383,7 @@ if (tb && tb->event_action) { state->event_action = tb->event_action; gnutls_session_set_ptr(state->session, state); - gnutls_certificate_set_verify_function(state->x509_cred, verify_cb); + gnutls_certificate_set_verify_function(state->lib_state.x509_cred, verify_cb); } #endif @@ -3136,7 +3535,8 @@ if (!ct_ctx) /* server */ } gnutls_deinit(state->session); -gnutls_certificate_free_credentials(state->x509_cred); +gnutls_certificate_free_credentials(state->lib_state.x509_cred); +state->lib_state = null_tls_preload; tlsp->active.sock = -1; tlsp->active.tls_ctx = NULL; @@ -3145,7 +3545,6 @@ tlsp->channelbinding = NULL; if (state->xfer_buffer) store_free(state->xfer_buffer); -memcpy(state, &exim_gnutls_state_init, sizeof(exim_gnutls_state_init)); } @@ -3163,6 +3562,7 @@ DEBUG(D_tls) debug_printf("Calling gnutls_record_recv(session=%p, buffer=%p, buf sigalrm_seen = FALSE; if (smtp_receive_timeout > 0) ALARM(smtp_receive_timeout); +errno = 0; do inbytes = gnutls_record_recv(state->session, state->xfer_buffer, MIN(ssl_xfer_buffer_size, lim)); @@ -3323,6 +3723,7 @@ DEBUG(D_tls) debug_printf("Calling gnutls_record_recv(session=%p, buffer=%p, len=" SIZE_T_FMT ")\n", state->session, buff, len); +errno = 0; do inbytes = gnutls_record_recv(state->session, buff, len); while (inbytes == GNUTLS_E_AGAIN); @@ -3386,6 +3787,7 @@ while (left > 0) DEBUG(D_tls) debug_printf("gnutls_record_send(session=%p, buffer=%p, left=" SIZE_T_FMT ")\n", state->session, buff, left); + errno = 0; do outbytes = gnutls_record_send(state->session, buff, left); while (outbytes == GNUTLS_E_AGAIN); @@ -3500,7 +3902,7 @@ if (i < needed_len) i = gnutls_rnd(GNUTLS_RND_NONCE, smallbuf, needed_len); if (i < 0) { - DEBUG(D_all) debug_printf("gnutls_rnd() failed, using fallback.\n"); + DEBUG(D_all) debug_printf("gnutls_rnd() failed, using fallback\n"); return vaguely_random_number_fallback(max); } r = 0;