"Fossies" - the Fresh Open Source Software Archive  

Source code changes of the file "src/tls-gnu.c" between
exim-4.91.tar.xz and exim-4.92.tar.xz

About: Exim is a message transfer agent (MTA).

tls-gnu.c  (exim-4.91.tar.xz):tls-gnu.c  (exim-4.92.tar.xz)
skipping to change at line 42 skipping to change at line 42
(I wasn't looking for libraries quite that old, when updating to get rid of (I wasn't looking for libraries quite that old, when updating to get rid of
compiler warnings of deprecated APIs. If it turns out that a lot of the rest compiler warnings of deprecated APIs. If it turns out that a lot of the rest
require current GnuTLS, then we'll drop support for the ancient libraries). require current GnuTLS, then we'll drop support for the ancient libraries).
*/ */
#include <gnutls/gnutls.h> #include <gnutls/gnutls.h>
/* needed for cert checks in verification and DN extraction: */ /* needed for cert checks in verification and DN extraction: */
#include <gnutls/x509.h> #include <gnutls/x509.h>
/* man-page is incorrect, gnutls_rnd() is not in gnutls.h: */ /* man-page is incorrect, gnutls_rnd() is not in gnutls.h: */
#include <gnutls/crypto.h> #include <gnutls/crypto.h>
/* needed to disable PKCS11 autoload unless requested */ /* needed to disable PKCS11 autoload unless requested */
#if GNUTLS_VERSION_NUMBER >= 0x020c00 #if GNUTLS_VERSION_NUMBER >= 0x020c00
# include <gnutls/pkcs11.h> # include <gnutls/pkcs11.h>
# define SUPPORT_PARAM_TO_PK_BITS # define SUPPORT_PARAM_TO_PK_BITS
#endif #endif
#if GNUTLS_VERSION_NUMBER < 0x030103 && !defined(DISABLE_OCSP) #if GNUTLS_VERSION_NUMBER < 0x030103 && !defined(DISABLE_OCSP)
# warning "GnuTLS library version too old; define DISABLE_OCSP in Makefile" # warning "GnuTLS library version too old; define DISABLE_OCSP in Makefile"
# define DISABLE_OCSP # define DISABLE_OCSP
#endif #endif
#if GNUTLS_VERSION_NUMBER < 0x020a00 && !defined(DISABLE_EVENT) #if GNUTLS_VERSION_NUMBER < 0x020a00 && !defined(DISABLE_EVENT)
skipping to change at line 63 skipping to change at line 64
# define DISABLE_EVENT # define DISABLE_EVENT
#endif #endif
#if GNUTLS_VERSION_NUMBER >= 0x030306 #if GNUTLS_VERSION_NUMBER >= 0x030306
# define SUPPORT_CA_DIR # define SUPPORT_CA_DIR
#else #else
# undef SUPPORT_CA_DIR # undef SUPPORT_CA_DIR
#endif #endif
#if GNUTLS_VERSION_NUMBER >= 0x030014 #if GNUTLS_VERSION_NUMBER >= 0x030014
# define SUPPORT_SYSDEFAULT_CABUNDLE # define SUPPORT_SYSDEFAULT_CABUNDLE
#endif #endif
#if GNUTLS_VERSION_NUMBER >= 0x030104
# define GNUTLS_CERT_VFY_STATUS_PRINT
#endif
#if GNUTLS_VERSION_NUMBER >= 0x030109 #if GNUTLS_VERSION_NUMBER >= 0x030109
# define SUPPORT_CORK # define SUPPORT_CORK
#endif #endif
#if GNUTLS_VERSION_NUMBER >= 0x030506 && !defined(DISABLE_OCSP) #if GNUTLS_VERSION_NUMBER >= 0x030506 && !defined(DISABLE_OCSP)
# define SUPPORT_SRV_OCSP_STACK # define SUPPORT_SRV_OCSP_STACK
#endif #endif
#ifdef SUPPORT_DANE #ifdef SUPPORT_DANE
# if GNUTLS_VERSION_NUMBER >= 0x030000 # if GNUTLS_VERSION_NUMBER >= 0x030000
# define DANESSL_USAGE_DANE_TA 2 # define DANESSL_USAGE_DANE_TA 2
skipping to change at line 127 skipping to change at line 131
gnutls_session_t session; gnutls_session_t session;
gnutls_certificate_credentials_t x509_cred; gnutls_certificate_credentials_t x509_cred;
gnutls_priority_t priority_cache; gnutls_priority_t priority_cache;
enum peer_verify_requirement verify_requirement; enum peer_verify_requirement verify_requirement;
int fd_in; int fd_in;
int fd_out; int fd_out;
BOOL peer_cert_verified; BOOL peer_cert_verified;
BOOL peer_dane_verified; BOOL peer_dane_verified;
BOOL trigger_sni_changes; BOOL trigger_sni_changes;
BOOL have_set_peerdn; BOOL have_set_peerdn;
const struct host_item *host; const struct host_item *host; /* NULL if server */
gnutls_x509_crt_t peercert; gnutls_x509_crt_t peercert;
uschar *peerdn; uschar *peerdn;
uschar *ciphersuite; uschar *ciphersuite;
uschar *received_sni; uschar *received_sni;
const uschar *tls_certificate; const uschar *tls_certificate;
const uschar *tls_privatekey; const uschar *tls_privatekey;
const uschar *tls_sni; /* client send only, not received */ const uschar *tls_sni; /* client send only, not received */
const uschar *tls_verify_certificates; const uschar *tls_verify_certificates;
const uschar *tls_crl; const uschar *tls_crl;
const uschar *tls_require_ciphers; const uschar *tls_require_ciphers;
skipping to change at line 216 skipping to change at line 220
it's held in globals, GnuTLS doesn't appear to let us register callback data it's held in globals, GnuTLS doesn't appear to let us register callback data
for callbacks, or as part of the session, so we have to keep a "this is the for callbacks, or as part of the session, so we have to keep a "this is the
context we're currently dealing with" pointer and rely upon being context we're currently dealing with" pointer and rely upon being
single-threaded to keep from processing data on an inbound TLS connection while single-threaded to keep from processing data on an inbound TLS connection while
talking to another TLS connection for an outbound check. This does mean that talking to another TLS connection for an outbound check. This does mean that
there's no way for heart-beats to be responded to, for the duration of the there's no way for heart-beats to be responded to, for the duration of the
second connection. second connection.
XXX But see gnutls_session_get_ptr() 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; /* 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 if we used TLS in a long-lived daemon, we'd have to reconsider this. But we
don't want to repeat this. */ don't want to repeat this. */
static gnutls_dh_params_t dh_server_params = NULL; static gnutls_dh_params_t dh_server_params = NULL;
/* No idea how this value was chosen; preserving it. Default is 3600. */ /* No idea how this value was chosen; preserving it. Default is 3600. */
static const int ssl_session_timeout = 200; static const int ssl_session_timeout = 200;
skipping to change at line 264 skipping to change at line 268
/* With GnuTLS 2.12.x+ we have gnutls_sec_param_to_pk_bits() with which we /* With GnuTLS 2.12.x+ we have gnutls_sec_param_to_pk_bits() with which we
can ask for a bit-strength. Without that, we stick to the constant we had can ask for a bit-strength. Without that, we stick to the constant we had
before, for now. */ before, for now. */
#ifndef EXIM_SERVER_DH_BITS_PRE2_12 #ifndef EXIM_SERVER_DH_BITS_PRE2_12
# define EXIM_SERVER_DH_BITS_PRE2_12 1024 # define EXIM_SERVER_DH_BITS_PRE2_12 1024
#endif #endif
#define exim_gnutls_err_check(rc, Label) do { \ #define exim_gnutls_err_check(rc, Label) do { \
if ((rc) != GNUTLS_E_SUCCESS) \ if ((rc) != GNUTLS_E_SUCCESS) \
return tls_error((Label), gnutls_strerror(rc), host, errstr); \ return tls_error((Label), US gnutls_strerror(rc), host, errstr); \
} while (0) } while (0)
#define expand_check_tlsvar(Varname, errstr) \ #define expand_check_tlsvar(Varname, errstr) \
expand_check(state->Varname, US #Varname, &state->exp_##Varname, errstr) expand_check(state->Varname, US #Varname, &state->exp_##Varname, errstr)
#if GNUTLS_VERSION_NUMBER >= 0x020c00 #if GNUTLS_VERSION_NUMBER >= 0x020c00
# define HAVE_GNUTLS_SESSION_CHANNEL_BINDING # define HAVE_GNUTLS_SESSION_CHANNEL_BINDING
# define HAVE_GNUTLS_SEC_PARAM_CONSTANTS # define HAVE_GNUTLS_SEC_PARAM_CONSTANTS
# define HAVE_GNUTLS_RND # define HAVE_GNUTLS_RND
/* The security fix we provide with the gnutls_allow_auto_pkcs11 option /* The security fix we provide with the gnutls_allow_auto_pkcs11 option
skipping to change at line 325 skipping to change at line 329
msg additional error string (may be NULL) msg additional error string (may be NULL)
usually obtained from gnutls_strerror() usually obtained from gnutls_strerror()
host NULL if setting up a server; host NULL if setting up a server;
the connected host if setting up a client the connected host if setting up a client
errstr pointer to returned error string errstr pointer to returned error string
Returns: OK/DEFER/FAIL Returns: OK/DEFER/FAIL
*/ */
static int 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) uschar ** errstr)
{ {
if (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; return host ? FAIL : DEFER;
} }
/************************************************* /*************************************************
* Deal with logging errors during I/O * * Deal with logging errors during I/O *
*************************************************/ *************************************************/
/* We have to get the identity of the peer from saved data. /* We have to get the identity of the peer from saved data.
Argument: Argument:
skipping to change at line 351 skipping to change at line 355
rc the GnuTLS error code, or 0 if it's a local error rc the GnuTLS error code, or 0 if it's a local error
when text identifying read or write when text identifying read or write
text local error text when ec is 0 text local error text when ec is 0
Returns: nothing Returns: nothing
*/ */
static void static void
record_io_error(exim_gnutls_state_st *state, int rc, uschar *when, uschar *text) record_io_error(exim_gnutls_state_st *state, int rc, uschar *when, uschar *text)
{ {
const char * msg; const uschar * msg;
uschar * errstr; uschar * errstr;
if (rc == GNUTLS_E_FATAL_ALERT_RECEIVED) if (rc == GNUTLS_E_FATAL_ALERT_RECEIVED)
msg = CS string_sprintf("%s: %s", US gnutls_strerror(rc), msg = string_sprintf("%s: %s", US gnutls_strerror(rc),
US gnutls_alert_get_name(gnutls_alert_get(state->session))); US gnutls_alert_get_name(gnutls_alert_get(state->session)));
else else
msg = gnutls_strerror(rc); msg = US gnutls_strerror(rc);
(void) tls_error(when, msg, state->host, &errstr); (void) tls_error(when, msg, state->host, &errstr);
if (state->host) if (state->host)
log_write(0, LOG_MAIN, "H=%s [%s] TLS error on connection %s", log_write(0, LOG_MAIN, "H=%s [%s] TLS error on connection %s",
state->host->name, state->host->address, errstr); state->host->name, state->host->address, errstr);
else else
{ {
uschar * conn_info = smtp_get_connection_info(); uschar * conn_info = smtp_get_connection_info();
if (Ustrncmp(conn_info, US"SMTP ", 5) == 0) conn_info += 5; if (Ustrncmp(conn_info, US"SMTP ", 5) == 0) conn_info += 5;
skipping to change at line 438 skipping to change at line 442
extract_exim_vars_from_tls_state(exim_gnutls_state_st * state) extract_exim_vars_from_tls_state(exim_gnutls_state_st * state)
{ {
gnutls_cipher_algorithm_t cipher; gnutls_cipher_algorithm_t cipher;
#ifdef HAVE_GNUTLS_SESSION_CHANNEL_BINDING #ifdef HAVE_GNUTLS_SESSION_CHANNEL_BINDING
int old_pool; int old_pool;
int rc; int rc;
gnutls_datum_t channel; gnutls_datum_t channel;
#endif #endif
tls_support * tlsp = state->tlsp; 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); cipher = gnutls_cipher_get(state->session);
/* returns size in "bytes" */ /* returns size in "bytes" */
tlsp->bits = gnutls_cipher_get_key_size(cipher) * 8; tlsp->bits = gnutls_cipher_get_key_size(cipher) * 8;
tlsp->cipher = state->ciphersuite; tlsp->cipher = state->ciphersuite;
DEBUG(D_tls) debug_printf("cipher: %s\n", state->ciphersuite); DEBUG(D_tls) debug_printf("cipher: %s\n", state->ciphersuite);
tlsp->certificate_verified = state->peer_cert_verified; tlsp->certificate_verified = state->peer_cert_verified;
skipping to change at line 543 skipping to change at line 548
else if (Ustrcmp(exp_tls_dhparam, "historic") == 0) else if (Ustrcmp(exp_tls_dhparam, "historic") == 0)
use_file_in_spool = TRUE; use_file_in_spool = TRUE;
else if (Ustrcmp(exp_tls_dhparam, "none") == 0) 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; return OK;
} }
else if (exp_tls_dhparam[0] != '/') else if (exp_tls_dhparam[0] != '/')
{ {
if (!(m.data = US std_dh_prime_named(exp_tls_dhparam))) if (!(m.data = US std_dh_prime_named(exp_tls_dhparam)))
return tls_error(US"No standard prime named", CS exp_tls_dhparam, NULL, errs tr); return tls_error(US"No standard prime named", exp_tls_dhparam, NULL, errstr) ;
m.size = Ustrlen(m.data); m.size = Ustrlen(m.data);
} }
else else
{ {
use_fixed_file = TRUE; use_fixed_file = TRUE;
filename = exp_tls_dhparam; filename = exp_tls_dhparam;
} }
if (m.data) if (m.data)
{ {
skipping to change at line 606 skipping to change at line 611
if ((fd = Uopen(filename, O_RDONLY, 0)) >= 0) if ((fd = Uopen(filename, O_RDONLY, 0)) >= 0)
{ {
struct stat statbuf; struct stat statbuf;
FILE *fp; FILE *fp;
int saved_errno; int saved_errno;
if (fstat(fd, &statbuf) < 0) /* EIO */ if (fstat(fd, &statbuf) < 0) /* EIO */
{ {
saved_errno = errno; saved_errno = errno;
(void)close(fd); (void)close(fd);
return tls_error(US"TLS cache stat failed", strerror(saved_errno), NULL, err str); return tls_error(US"TLS cache stat failed", US strerror(saved_errno), NULL, errstr);
} }
if (!S_ISREG(statbuf.st_mode)) if (!S_ISREG(statbuf.st_mode))
{ {
(void)close(fd); (void)close(fd);
return tls_error(US"TLS cache not a file", NULL, NULL, errstr); return tls_error(US"TLS cache not a file", NULL, NULL, errstr);
} }
if (!(fp = fdopen(fd, "rb"))) if (!(fp = fdopen(fd, "rb")))
{ {
saved_errno = errno; saved_errno = errno;
(void)close(fd); (void)close(fd);
return tls_error(US"fdopen(TLS cache stat fd) failed", return tls_error(US"fdopen(TLS cache stat fd) failed",
strerror(saved_errno), NULL, errstr); US strerror(saved_errno), NULL, errstr);
} }
m.size = statbuf.st_size; m.size = statbuf.st_size;
if (!(m.data = malloc(m.size))) if (!(m.data = malloc(m.size)))
{ {
fclose(fp); fclose(fp);
return tls_error(US"malloc failed", strerror(errno), NULL, errstr); return tls_error(US"malloc failed", US strerror(errno), NULL, errstr);
} }
if (!(sz = fread(m.data, m.size, 1, fp))) if (!(sz = fread(m.data, m.size, 1, fp)))
{ {
saved_errno = errno; saved_errno = errno;
fclose(fp); fclose(fp);
free(m.data); free(m.data);
return tls_error(US"fread failed", strerror(saved_errno), NULL, errstr); return tls_error(US"fread failed", US strerror(saved_errno), NULL, errstr);
} }
fclose(fp); fclose(fp);
rc = gnutls_dh_params_import_pkcs3(dh_server_params, &m, GNUTLS_X509_FMT_PEM); rc = gnutls_dh_params_import_pkcs3(dh_server_params, &m, GNUTLS_X509_FMT_PEM);
free(m.data); free(m.data);
exim_gnutls_err_check(rc, US"gnutls_dh_params_import_pkcs3"); exim_gnutls_err_check(rc, US"gnutls_dh_params_import_pkcs3");
DEBUG(D_tls) debug_printf("read D-H parameters from file \"%s\"\n", filename); DEBUG(D_tls) debug_printf("read D-H parameters from file \"%s\"\n", filename);
} }
/* If the file does not exist, fall through to compute new data and cache it. /* If the file does not exist, fall through to compute new data and cache it.
skipping to change at line 668 skipping to change at line 673
try to be clever and support both formats; we just regenerate new data in this try to be clever and support both formats; we just regenerate new data in this
case. */ case. */
if (rc < 0) if (rc < 0)
{ {
uschar *temp_fn; uschar *temp_fn;
unsigned int dh_bits_gen = dh_bits; unsigned int dh_bits_gen = dh_bits;
if ((PATH_MAX - Ustrlen(filename)) < 10) if ((PATH_MAX - Ustrlen(filename)) < 10)
return tls_error(US"Filename too long to generate replacement", 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 */ if ((fd = mkstemp(CS temp_fn)) < 0) /* modifies temp_fn */
return tls_error(US"Unable to open temp file", strerror(errno), NULL, errstr ); return tls_error(US"Unable to open temp file", US strerror(errno), NULL, err str);
(void)fchown(fd, exim_uid, exim_gid); /* Probably not necessary */ (void)fchown(fd, exim_uid, exim_gid); /* Probably not necessary */
/* GnuTLS overshoots! /* GnuTLS overshoots!
* If we ask for 2236, we might get 2237 or more. * If we ask for 2236, we might get 2237 or more.
* But there's no way to ask GnuTLS how many bits there really are. * But there's no way to ask GnuTLS how many bits there really are.
* We can ask how many bits were used in a TLS session, but that's it! * We can ask how many bits were used in a TLS session, but that's it!
* The prime itself is hidden behind too much abstraction. * The prime itself is hidden behind too much abstraction.
* So we ask for less, and proceed on a wing and a prayer. * So we ask for less, and proceed on a wing and a prayer.
* First attempt, subtracted 3 for 2233 and got 2240. * First attempt, subtracted 3 for 2233 and got 2240.
*/ */
skipping to change at line 709 skipping to change at line 714
sample apps handle this. */ sample apps handle this. */
sz = 0; sz = 0;
m.data = NULL; m.data = NULL;
rc = gnutls_dh_params_export_pkcs3(dh_server_params, GNUTLS_X509_FMT_PEM, rc = gnutls_dh_params_export_pkcs3(dh_server_params, GNUTLS_X509_FMT_PEM,
m.data, &sz); m.data, &sz);
if (rc != GNUTLS_E_SHORT_MEMORY_BUFFER) if (rc != GNUTLS_E_SHORT_MEMORY_BUFFER)
exim_gnutls_err_check(rc, US"gnutls_dh_params_export_pkcs3(NULL) sizing"); exim_gnutls_err_check(rc, US"gnutls_dh_params_export_pkcs3(NULL) sizing");
m.size = sz; m.size = sz;
if (!(m.data = malloc(m.size))) if (!(m.data = malloc(m.size)))
return tls_error(US"memory allocation failed", strerror(errno), NULL, errstr ); return tls_error(US"memory allocation failed", US strerror(errno), NULL, err str);
/* this will return a size 1 less than the allocation size above */ /* 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, rc = gnutls_dh_params_export_pkcs3(dh_server_params, GNUTLS_X509_FMT_PEM,
m.data, &sz); m.data, &sz);
if (rc != GNUTLS_E_SUCCESS) if (rc != GNUTLS_E_SUCCESS)
{ {
free(m.data); free(m.data);
exim_gnutls_err_check(rc, US"gnutls_dh_params_export_pkcs3() real"); exim_gnutls_err_check(rc, US"gnutls_dh_params_export_pkcs3() real");
} }
m.size = sz; /* shrink by 1, probably */ m.size = sz; /* shrink by 1, probably */
if ((sz = write_to_fd_buf(fd, m.data, (size_t) m.size)) != m.size) if ((sz = write_to_fd_buf(fd, m.data, (size_t) m.size)) != m.size)
{ {
free(m.data); free(m.data);
return tls_error(US"TLS cache write D-H params failed", return tls_error(US"TLS cache write D-H params failed",
strerror(errno), NULL, errstr); US strerror(errno), NULL, errstr);
} }
free(m.data); free(m.data);
if ((sz = write_to_fd_buf(fd, US"\n", 1)) != 1) if ((sz = write_to_fd_buf(fd, US"\n", 1)) != 1)
return tls_error(US"TLS cache write D-H params final newline failed", return tls_error(US"TLS cache write D-H params final newline failed",
strerror(errno), NULL, errstr); US strerror(errno), NULL, errstr);
if ((rc = close(fd))) if ((rc = close(fd)))
return tls_error(US"TLS cache write close() failed", strerror(errno), NULL, errstr); return tls_error(US"TLS cache write close() failed", US strerror(errno), NUL L, errstr);
if (Urename(temp_fn, filename) < 0) if (Urename(temp_fn, filename) < 0)
return tls_error(string_sprintf("failed to rename \"%s\" as \"%s\"", return tls_error(string_sprintf("failed to rename \"%s\" as \"%s\"",
temp_fn, filename), strerror(errno), NULL, errstr); temp_fn, filename), US strerror(errno), NULL, errstr);
DEBUG(D_tls) debug_printf("wrote D-H parameters to file \"%s\"\n", filename); DEBUG(D_tls) debug_printf("wrote D-H parameters to file \"%s\"\n", filename);
} }
DEBUG(D_tls) debug_printf("initialized server D-H parameters\n"); DEBUG(D_tls) debug_printf("initialized server D-H parameters\n");
return OK; return OK;
} }
/* Create and install a selfsigned certificate, for use in server mode */ /* Create and install a selfsigned certificate, for use in server mode */
skipping to change at line 766 skipping to change at line 771
where = US"initialising pkey"; where = US"initialising pkey";
if ((rc = gnutls_x509_privkey_init(&pkey))) goto err; if ((rc = gnutls_x509_privkey_init(&pkey))) goto err;
where = US"initialising cert"; where = US"initialising cert";
if ((rc = gnutls_x509_crt_init(&cert))) goto err; if ((rc = gnutls_x509_crt_init(&cert))) goto err;
where = US"generating pkey"; where = US"generating pkey";
if ((rc = gnutls_x509_privkey_generate(pkey, GNUTLS_PK_RSA, if ((rc = gnutls_x509_privkey_generate(pkey, GNUTLS_PK_RSA,
#ifdef SUPPORT_PARAM_TO_PK_BITS #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 #else
1024, 2048,
#endif #endif
0))) 0)))
goto err; goto err;
where = US"configuring cert"; where = US"configuring cert";
now = 0; now = 1;
if ( (rc = gnutls_x509_crt_set_version(cert, 3)) if ( (rc = gnutls_x509_crt_set_version(cert, 3))
|| (rc = gnutls_x509_crt_set_serial(cert, &now, sizeof(now))) || (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_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, now + 60 * 60)) /* 1 hr */
|| (rc = gnutls_x509_crt_set_key(cert, pkey)) || (rc = gnutls_x509_crt_set_key(cert, pkey))
|| (rc = gnutls_x509_crt_set_dn_by_oid(cert, || (rc = gnutls_x509_crt_set_dn_by_oid(cert,
GNUTLS_OID_X520_COUNTRY_NAME, 0, "UK", 2)) GNUTLS_OID_X520_COUNTRY_NAME, 0, "UK", 2))
|| (rc = gnutls_x509_crt_set_dn_by_oid(cert, || (rc = gnutls_x509_crt_set_dn_by_oid(cert,
GNUTLS_OID_X520_ORGANIZATION_NAME, 0, "Exim Developers", 15)) GNUTLS_OID_X520_ORGANIZATION_NAME, 0, "Exim Developers", 15))
skipping to change at line 807 skipping to change at line 815
goto err; goto err;
rc = OK; rc = OK;
out: out:
if (cert) gnutls_x509_crt_deinit(cert); if (cert) gnutls_x509_crt_deinit(cert);
if (pkey) gnutls_x509_privkey_deinit(pkey); if (pkey) gnutls_x509_privkey_deinit(pkey);
return rc; return rc;
err: err:
rc = tls_error(where, gnutls_strerror(rc), NULL, errstr); rc = tls_error(where, US gnutls_strerror(rc), NULL, errstr);
goto out; goto out;
} }
/* Add certificate and key, from files. /* Add certificate and key, from files.
Return: Return:
Zero or negative: good. Negate value for certificate index if < 0. Zero or negative: good. Negate value for certificate index if < 0.
Greater than zero: FAIL or DEFER code. Greater than zero: FAIL or DEFER code.
*/ */
static int static int
tls_add_certfile(exim_gnutls_state_st * state, const host_item * host, tls_add_certfile(exim_gnutls_state_st * state, const host_item * host,
uschar * certfile, uschar * keyfile, uschar ** errstr) uschar * certfile, uschar * keyfile, uschar ** errstr)
{ {
int rc = gnutls_certificate_set_x509_key_file(state->x509_cred, int rc = gnutls_certificate_set_x509_key_file(state->x509_cred,
CS certfile, CS keyfile, GNUTLS_X509_FMT_PEM); CS certfile, CS keyfile, GNUTLS_X509_FMT_PEM);
if (rc < 0) if (rc < 0)
return tls_error( return tls_error(
string_sprintf("cert/key setup: cert=%s key=%s", certfile, keyfile), string_sprintf("cert/key setup: cert=%s key=%s", certfile, keyfile),
gnutls_strerror(rc), host, errstr); US gnutls_strerror(rc), host, errstr);
return -rc; return -rc;
} }
/************************************************* /*************************************************
* Variables re-expanded post-SNI * * Variables re-expanded post-SNI *
*************************************************/ *************************************************/
/* Called from both server and client code, via tls_init(), and also from /* 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". the SNI callback after receiving an SNI, if tls_certificate includes "tls_sni".
skipping to change at line 1235 skipping to change at line 1243
static int static int
tls_init( tls_init(
const host_item *host, const host_item *host,
const uschar *certificate, const uschar *certificate,
const uschar *privatekey, const uschar *privatekey,
const uschar *sni, const uschar *sni,
const uschar *cas, const uschar *cas,
const uschar *crl, const uschar *crl,
const uschar *require_ciphers, const uschar *require_ciphers,
exim_gnutls_state_st **caller_state, exim_gnutls_state_st **caller_state,
tls_support * tlsp,
uschar ** errstr) uschar ** errstr)
{ {
exim_gnutls_state_st *state; exim_gnutls_state_st *state;
int rc; int rc;
size_t sz; size_t sz;
const char *errpos; const char *errpos;
uschar *p; uschar *p;
BOOL want_default_priorities; BOOL want_default_priorities;
if (!exim_gnutls_base_init_done) if (!exim_gnutls_base_init_done)
skipping to change at line 1268 skipping to change at line 1277
} }
#endif #endif
rc = gnutls_global_init(); rc = gnutls_global_init();
exim_gnutls_err_check(rc, US"gnutls_global_init"); exim_gnutls_err_check(rc, US"gnutls_global_init");
#if EXIM_GNUTLS_LIBRARY_LOG_LEVEL >= 0 #if EXIM_GNUTLS_LIBRARY_LOG_LEVEL >= 0
DEBUG(D_tls) DEBUG(D_tls)
{ {
gnutls_global_set_log_function(exim_gnutls_logger_cb); 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); gnutls_global_set_log_level(EXIM_GNUTLS_LIBRARY_LOG_LEVEL);
} }
#endif #endif
#ifndef DISABLE_OCSP #ifndef DISABLE_OCSP
if (tls_ocsp_file && (gnutls_buggy_ocsp = tls_is_buggy_ocsp())) if (tls_ocsp_file && (gnutls_buggy_ocsp = tls_is_buggy_ocsp()))
log_write(0, LOG_MAIN, "OCSP unusable with this GnuTLS library version"); log_write(0, LOG_MAIN, "OCSP unusable with this GnuTLS library version");
#endif #endif
exim_gnutls_base_init_done = TRUE; exim_gnutls_base_init_done = TRUE;
} }
if (host) 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)); 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"); DEBUG(D_tls) debug_printf("initialising GnuTLS client session\n");
rc = gnutls_init(&state->session, GNUTLS_CLIENT); rc = gnutls_init(&state->session, GNUTLS_CLIENT);
} }
else else
{ {
state = &state_server; state = &state_server;
memcpy(state, &exim_gnutls_state_init, sizeof(exim_gnutls_state_init)); 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"); DEBUG(D_tls) debug_printf("initialising GnuTLS server session\n");
rc = gnutls_init(&state->session, GNUTLS_SERVER); rc = gnutls_init(&state->session, GNUTLS_SERVER);
} }
exim_gnutls_err_check(rc, US"gnutls_init"); exim_gnutls_err_check(rc, US"gnutls_init");
state->host = host; state->host = host;
state->tls_certificate = certificate; state->tls_certificate = certificate;
state->tls_privatekey = privatekey; state->tls_privatekey = privatekey;
state->tls_require_ciphers = require_ciphers; state->tls_require_ciphers = require_ciphers;
skipping to change at line 1481 skipping to change at line 1496
/* tls_peerdn */ /* tls_peerdn */
cert_list = gnutls_certificate_get_peers(state->session, &cert_list_size); cert_list = gnutls_certificate_get_peers(state->session, &cert_list_size);
if (cert_list == NULL || cert_list_size == 0) if (cert_list == NULL || cert_list_size == 0)
{ {
DEBUG(D_tls) debug_printf("TLS: no certificate from peer (%p & %d)\n", DEBUG(D_tls) debug_printf("TLS: no certificate from peer (%p & %d)\n",
cert_list, cert_list_size); cert_list, cert_list_size);
if (state->verify_requirement >= VERIFY_REQUIRED) if (state->verify_requirement >= VERIFY_REQUIRED)
return tls_error(US"certificate verification failed", 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; return OK;
} }
ct = gnutls_certificate_type_get(state->session); ct = gnutls_certificate_type_get(state->session);
if (ct != GNUTLS_CRT_X509) if (ct != 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(D_tls)
debug_printf("TLS: peer cert not X.509 but instead \"%s\"\n", ctn); debug_printf("TLS: peer cert not X.509 but instead \"%s\"\n", ctn);
if (state->verify_requirement >= VERIFY_REQUIRED) if (state->verify_requirement >= VERIFY_REQUIRED)
return tls_error(US"certificate verification not possible, unhandled type", return tls_error(US"certificate verification not possible, unhandled type",
ctn, state->host, errstr); ctn, state->host, errstr);
return OK; return OK;
} }
#define exim_gnutls_peer_err(Label) \ #define exim_gnutls_peer_err(Label) \
do { \ do { \
if (rc != GNUTLS_E_SUCCESS) \ if (rc != GNUTLS_E_SUCCESS) \
{ \ { \
DEBUG(D_tls) debug_printf("TLS: peer cert problem: %s: %s\n", \ DEBUG(D_tls) debug_printf("TLS: peer cert problem: %s: %s\n", \
(Label), gnutls_strerror(rc)); \ (Label), gnutls_strerror(rc)); \
if (state->verify_requirement >= VERIFY_REQUIRED) \ if (state->verify_requirement >= VERIFY_REQUIRED) \
return tls_error((Label), gnutls_strerror(rc), state->host, errstr); \ return tls_error((Label), US gnutls_strerror(rc), state->host, errstr); \
return OK; \ return OK; \
} \ } \
} while (0) } while (0)
rc = import_cert(&cert_list[0], &crt); rc = import_cert(&cert_list[0], &crt);
exim_gnutls_peer_err(US"cert 0"); exim_gnutls_peer_err(US"cert 0");
state->tlsp->peercert = state->peercert = crt; state->tlsp->peercert = state->peercert = crt;
sz = 0; sz = 0;
skipping to change at line 1558 skipping to change at line 1573
static BOOL static BOOL
verify_certificate(exim_gnutls_state_st * state, uschar ** errstr) verify_certificate(exim_gnutls_state_st * state, uschar ** errstr)
{ {
int rc; int rc;
uint verify; uint verify;
if (state->verify_requirement == VERIFY_NONE) if (state->verify_requirement == VERIFY_NONE)
return TRUE; return TRUE;
DEBUG(D_tls) debug_printf("TLS: checking peer certificate\n");
*errstr = NULL; *errstr = NULL;
if ((rc = peer_status(state, errstr)) != OK) if ((rc = peer_status(state, errstr)) != OK)
{ {
verify = GNUTLS_CERT_INVALID; verify = GNUTLS_CERT_INVALID;
*errstr = US"certificate not supplied"; *errstr = US"certificate not supplied";
} }
else else
{ {
skipping to change at line 1585 skipping to change at line 1601
dane_state_t s; dane_state_t s;
dane_query_t r; dane_query_t r;
uint lsize; uint lsize;
const gnutls_datum_t * certlist = const gnutls_datum_t * certlist =
gnutls_certificate_get_peers(state->session, &lsize); gnutls_certificate_get_peers(state->session, &lsize);
int usage = tls_out.tlsa_usage; int usage = tls_out.tlsa_usage;
# ifdef GNUTLS_BROKEN_DANE_VALIDATION # ifdef GNUTLS_BROKEN_DANE_VALIDATION
/* Split the TLSA records into two sets, TA and EE selectors. Run the /* Split the TLSA records into two sets, TA and EE selectors. Run the
dane-verification separately so that we know which selector verified; dane-verification separately so that we know which selector verified;
then we know whether to do CA-chain-verification and name-verification then we know whether to do name-verification (needed for TA but not EE). */
(needed for TA but not EE). */
if (usage == ((1<<DANESSL_USAGE_DANE_TA) | (1<<DANESSL_USAGE_DANE_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; int i, j, nrec;
const char ** dd; const char ** dd;
int * ddl; int * ddl;
for(nrec = 0; state->dane_data_len[nrec]; ) nrec++; for(nrec = 0; state->dane_data_len[nrec]; ) nrec++;
nrec++; nrec++;
skipping to change at line 1668 skipping to change at line 1683
goto tlsa_prob; goto tlsa_prob;
} }
if (verify != 0) /* verification failed */ if (verify != 0) /* verification failed */
{ {
gnutls_datum_t str; gnutls_datum_t str;
(void) dane_verification_status_print(verify, &str, 0); (void) dane_verification_status_print(verify, &str, 0);
*errstr = US str.data; /* don't bother to free */ *errstr = US str.data; /* don't bother to free */
goto badcert; goto badcert;
} }
state->peer_dane_verified = TRUE;
# ifdef GNUTLS_BROKEN_DANE_VALIDATION # ifdef GNUTLS_BROKEN_DANE_VALIDATION
/* If a TA-mode TLSA record was used for verification we must additionally /* 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)) if (usage & (1 << DANESSL_USAGE_DANE_EE))
# endif # endif
{ {
state->peer_cert_verified = TRUE; state->peer_dane_verified = state->peer_cert_verified = TRUE;
goto goodcert; 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); rc = gnutls_certificate_verify_peers2(state->session, &verify);
} }
/* Handle the result of verification. INVALID is set if any others are. */ /* Handle the result of verification. INVALID is set if any others are. */
if (rc < 0 || verify & (GNUTLS_CERT_INVALID|GNUTLS_CERT_REVOKED)) if (rc < 0 || verify & (GNUTLS_CERT_INVALID|GNUTLS_CERT_REVOKED))
{ {
state->peer_cert_verified = FALSE; state->peer_cert_verified = FALSE;
if (!*errstr) 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 *errstr = verify & GNUTLS_CERT_REVOKED
? US"certificate revoked" : US"certificate invalid"; ? US"certificate revoked" : US"certificate invalid";
}
DEBUG(D_tls) DEBUG(D_tls)
debug_printf("TLS certificate verification failed (%s): peerdn=\"%s\"\n", debug_printf("TLS certificate verification failed (%s): peerdn=\"%s\"\n",
*errstr, state->peerdn ? state->peerdn : US"<unset>"); *errstr, state->peerdn ? state->peerdn : US"<unset>");
if (state->verify_requirement >= VERIFY_REQUIRED) if (state->verify_requirement >= VERIFY_REQUIRED)
goto badcert; goto badcert;
DEBUG(D_tls) DEBUG(D_tls)
debug_printf("TLS verify failure overridden (host in tls_try_verify_hosts)\n "); debug_printf("TLS verify failure overridden (host in tls_try_verify_hosts)\n ");
} }
else 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; DEBUG(D_tls)
const uschar * list = state->exp_tls_verify_cert_hostnames; debug_printf("TLS certificate verification failed: cert name mismatch\n");
uschar * name; if (state->verify_requirement >= VERIFY_REQUIRED)
while ((name = string_nextinlist(&list, &sep, NULL, 0))) goto badcert;
if (gnutls_x509_crt_check_hostname(state->tlsp->peercert, CS name)) return TRUE;
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;
}
} }
state->peer_cert_verified = TRUE; state->peer_cert_verified = TRUE;
DEBUG(D_tls) debug_printf("TLS certificate verified: peerdn=\"%s\"\n", DEBUG(D_tls) debug_printf("TLS certificate verified: peerdn=\"%s\"\n",
state->peerdn ? state->peerdn : US"<unset>"); state->peerdn ? state->peerdn : US"<unset>");
} }
goodcert: goodcert:
state->tlsp->peerdn = state->peerdn; state->tlsp->peerdn = state->peerdn;
return TRUE; return TRUE;
#ifdef SUPPORT_DANE #ifdef SUPPORT_DANE
tlsa_prob: 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 #endif
badcert: badcert:
gnutls_alert_send(state->session, GNUTLS_AL_FATAL, GNUTLS_A_BAD_CERTIFICATE); gnutls_alert_send(state->session, GNUTLS_AL_FATAL, GNUTLS_A_BAD_CERTIFICATE);
return FALSE; return FALSE;
} }
/* ------------------------------------------------------------------------ */ /* ------------------------------------------------------------------------ */
/* Callbacks */ /* Callbacks */
skipping to change at line 1936 skipping to change at line 1979
continue running. continue running.
*/ */
int int
tls_server_start(const uschar * require_ciphers, uschar ** errstr) tls_server_start(const uschar * require_ciphers, uschar ** errstr)
{ {
int rc; int rc;
exim_gnutls_state_st * state = NULL; exim_gnutls_state_st * state = NULL;
/* Check for previous activation */ /* 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); smtp_printf("554 Already in TLS\r\n", FALSE);
return FAIL; return FAIL;
} }
/* Initialize the library. If it fails, it will already have logged the error /* Initialize the library. If it fails, it will already have logged the error
and sent an SMTP response. */ and sent an SMTP response. */
DEBUG(D_tls) debug_printf("initialising GnuTLS as a server\n"); DEBUG(D_tls) debug_printf("initialising GnuTLS as a server\n");
if ((rc = tls_init(NULL, tls_certificate, tls_privatekey, if ((rc = tls_init(NULL, tls_certificate, tls_privatekey,
NULL, tls_verify_certificates, tls_crl, 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 /* If this is a host for which certificate verification is mandatory or
optional, set up appropriately. */ optional, set up appropriately. */
if (verify_check_host(&tls_verify_hosts) == OK) if (verify_check_host(&tls_verify_hosts) == OK)
{ {
DEBUG(D_tls) 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; state->verify_requirement = VERIFY_REQUIRED;
gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_REQUIRE); gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_REQUIRE);
skipping to change at line 2005 skipping to change at line 2048
the response. Other smtp_printf() calls do not need it, because in non-TLS the response. Other smtp_printf() calls do not need it, because in non-TLS
mode, the fflush() happens when smtp_getc() is called. */ mode, the fflush() happens when smtp_getc() is called. */
if (!state->tlsp->on_connect) if (!state->tlsp->on_connect)
{ {
smtp_printf("220 TLS go ahead\r\n", FALSE); smtp_printf("220 TLS go ahead\r\n", FALSE);
fflush(smtp_out); fflush(smtp_out);
} }
/* Now negotiate the TLS session. We put our own timer on it, since it seems /* 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_set_ptr2(state->session,
(gnutls_transport_ptr_t)(long) fileno(smtp_in), (gnutls_transport_ptr_t)(long) fileno(smtp_in),
(gnutls_transport_ptr_t)(long) fileno(smtp_out)); (gnutls_transport_ptr_t)(long) fileno(smtp_out));
state->fd_in = fileno(smtp_in); state->fd_in = fileno(smtp_in);
state->fd_out = fileno(smtp_out); state->fd_out = fileno(smtp_out);
sigalrm_seen = FALSE; sigalrm_seen = FALSE;
if (smtp_receive_timeout > 0) alarm(smtp_receive_timeout); if (smtp_receive_timeout > 0) ALARM(smtp_receive_timeout);
do do
rc = gnutls_handshake(state->session); rc = gnutls_handshake(state->session);
while (rc == GNUTLS_E_AGAIN || rc == GNUTLS_E_INTERRUPTED && !sigalrm_seen); while (rc == GNUTLS_E_AGAIN || rc == GNUTLS_E_INTERRUPTED && !sigalrm_seen);
alarm(0); ALARM_CLR(0);
if (rc != GNUTLS_E_SUCCESS) if (rc != GNUTLS_E_SUCCESS)
{ {
/* It seems that, except in the case of a timeout, we have to close the /* It seems that, except in the case of a timeout, we have to close the
connection right here; otherwise if the other end is running OpenSSL it hangs connection right here; otherwise if the other end is running OpenSSL it hangs
until the server times out. */ until the server times out. */
if (sigalrm_seen) 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); gnutls_db_remove_session(state->session);
} }
else else
{ {
tls_error(US"gnutls_handshake", gnutls_strerror(rc), NULL, errstr); tls_error(US"gnutls_handshake", US gnutls_strerror(rc), NULL, errstr);
(void) gnutls_alert_send_appropriate(state->session, rc); (void) gnutls_alert_send_appropriate(state->session, rc);
gnutls_deinit(state->session); gnutls_deinit(state->session);
gnutls_certificate_free_credentials(state->x509_cred); gnutls_certificate_free_credentials(state->x509_cred);
millisleep(500); millisleep(500);
shutdown(state->fd_out, SHUT_WR); shutdown(state->fd_out, SHUT_WR);
for (rc = 1024; fgetc(smtp_in) != EOF && rc > 0; ) rc--; /* drain skt */ for (rc = 1024; fgetc(smtp_in) != EOF && rc > 0; ) rc--; /* drain skt */
(void)fclose(smtp_out); (void)fclose(smtp_out);
(void)fclose(smtp_in); (void)fclose(smtp_in);
smtp_out = smtp_in = NULL; smtp_out = smtp_in = NULL;
} }
skipping to change at line 2092 skipping to change at line 2138
receive_ferror = tls_ferror; receive_ferror = tls_ferror;
receive_smtp_buffered = tls_smtp_buffered; receive_smtp_buffered = tls_smtp_buffered;
return OK; return OK;
} }
static void static void
tls_client_setup_hostname_checks(host_item * host, exim_gnutls_state_st * state, tls_client_setup_hostname_checks(host_item * host, exim_gnutls_state_st * state,
smtp_transport_options_block * ob) 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 = state->exp_tls_verify_cert_hostnames =
#ifdef SUPPORT_I18N #ifdef SUPPORT_I18N
string_domain_utf8_to_alabel(host->name, NULL); string_domain_utf8_to_alabel(host->name, NULL);
#else #else
host->name; host->name;
#endif #endif
DEBUG(D_tls) 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); state->exp_tls_verify_cert_hostnames);
skipping to change at line 2134 skipping to change at line 2180
rr; rr;
rr = dns_next_rr(dnsa, &dnss, RESET_NEXT) rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)
) if (rr->type == T_TLSA) i++; ) if (rr->type == T_TLSA) i++;
dane_data = store_get(i * sizeof(uschar *)); dane_data = store_get(i * sizeof(uschar *));
dane_data_len = store_get(i * sizeof(int)); dane_data_len = store_get(i * sizeof(int));
for (rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS), i = 0; for (rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS), i = 0;
rr; rr;
rr = dns_next_rr(dnsa, &dnss, RESET_NEXT) 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; const uschar * p = rr->data;
uint8_t usage = p[0], sel = p[1], type = p[2]; uint8_t usage = p[0], sel = p[1], type = p[2];
DEBUG(D_tls) DEBUG(D_tls)
debug_printf("TLSA: %d %d %d size %d\n", usage, sel, type, rr->size); debug_printf("TLSA: %d %d %d size %d\n", usage, sel, type, rr->size);
if ( (usage != DANESSL_USAGE_DANE_TA && usage != DANESSL_USAGE_DANE_EE) if ( (usage != DANESSL_USAGE_DANE_TA && usage != DANESSL_USAGE_DANE_EE)
|| (sel != 0 && sel != 1) || (sel != 0 && sel != 1)
) )
skipping to change at line 2158 skipping to change at line 2204
case 0: /* Full: cannot check at present */ case 0: /* Full: cannot check at present */
break; break;
case 1: if (rr->size != 3 + 256/8) continue; /* sha2-256 */ case 1: if (rr->size != 3 + 256/8) continue; /* sha2-256 */
break; break;
case 2: if (rr->size != 3 + 512/8) continue; /* sha2-512 */ case 2: if (rr->size != 3 + 512/8) continue; /* sha2-512 */
break; break;
default: continue; default: continue;
} }
tls_out.tlsa_usage |= 1<<usage; tls_out.tlsa_usage |= 1<<usage;
dane_data[i] = p; dane_data[i] = CS p;
dane_data_len[i++] = rr->size; dane_data_len[i++] = rr->size;
} }
if (!i) return FALSE; if (!i) return FALSE;
dane_data[i] = NULL; dane_data[i] = NULL;
dane_data_len[i] = 0; dane_data_len[i] = 0;
state->dane_data = (char * const *)dane_data; state->dane_data = (char * const *)dane_data;
state->dane_data_len = dane_data_len; state->dane_data_len = dane_data_len;
skipping to change at line 2181 skipping to change at line 2227
#endif #endif
/************************************************* /*************************************************
* Start a TLS session in a client * * Start a TLS session in a client *
*************************************************/ *************************************************/
/* Called from the smtp transport after STARTTLS has been accepted. /* Called from the smtp transport after STARTTLS has been accepted.
Arguments: Arguments:
fd the fd of the connection fd the fd of the connection
host connected host (for messages) host connected host (for messages and option-tests)
addr the first address (not used) addr the first address (not used)
tb transport (always smtp) tb transport (always smtp)
tlsa_dnsa non-NULL, either request or require dane for this host, and tlsa_dnsa non-NULL, either request or require dane for this host, and
a TLSA record found. Therefore, dane verify required. a TLSA record found. Therefore, dane verify required.
Which implies cert must be requested and supplied, dane Which implies cert must be requested and supplied, dane
verify must pass, and cert verify irrelevant (incl. verify must pass, and cert verify irrelevant (incl.
hostnames), and (caller handled) require_tls hostnames), and (caller handled) require_tls
tlsp record details of channel configuration
errstr error string pointer errstr error string pointer
Returns: OK/DEFER/FAIL (because using common functions), Returns: Pointer to TLS session context, or NULL on error
but for a client, DEFER and FAIL have the same meaning
*/ */
int void *
tls_client_start(int fd, host_item *host, tls_client_start(int fd, host_item *host,
address_item *addr ARG_UNUSED, address_item *addr ARG_UNUSED,
transport_instance * tb, transport_instance * tb,
#ifdef SUPPORT_DANE #ifdef SUPPORT_DANE
dns_answer * tlsa_dnsa, dns_answer * tlsa_dnsa,
#endif #endif
uschar ** errstr) tls_support * tlsp, uschar ** errstr)
{ {
smtp_transport_options_block *ob = smtp_transport_options_block *ob = tb
(smtp_transport_options_block *)tb->options_block; ? (smtp_transport_options_block *)tb->options_block
: &smtp_transport_option_defaults;
int rc; int rc;
exim_gnutls_state_st * state = NULL; exim_gnutls_state_st * state = NULL;
uschar *cipher_list = NULL; uschar *cipher_list = NULL;
#ifndef DISABLE_OCSP #ifndef DISABLE_OCSP
BOOL require_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 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 #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", fd);
#ifdef SUPPORT_DANE #ifdef SUPPORT_DANE
if (tlsa_dnsa && ob->dane_require_tls_ciphers) if (tlsa_dnsa && 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", if (!expand_check(ob->dane_require_tls_ciphers, US"dane_require_tls_ciphers",
&cipher_list, errstr)) &cipher_list, errstr))
return DEFER; return NULL;
cipher_list = cipher_list && *cipher_list cipher_list = cipher_list && *cipher_list
? ob->dane_require_tls_ciphers : ob->tls_require_ciphers; ? ob->dane_require_tls_ciphers : ob->tls_require_ciphers;
} }
#endif #endif
if (!cipher_list) if (!cipher_list)
cipher_list = ob->tls_require_ciphers; cipher_list = ob->tls_require_ciphers;
if ((rc = tls_init(host, ob->tls_certificate, ob->tls_privatekey, if (tls_init(host, ob->tls_certificate, ob->tls_privatekey,
ob->tls_sni, ob->tls_verify_certificates, ob->tls_crl, ob->tls_sni, ob->tls_verify_certificates, ob->tls_crl,
cipher_list, &state, errstr)) != OK) cipher_list, &state, tlsp, errstr) != OK)
return rc; return NULL;
{ {
int dh_min_bits = ob->tls_dh_min_bits; int dh_min_bits = ob->tls_dh_min_bits;
if (dh_min_bits < EXIM_CLIENT_DH_MIN_MIN_BITS) if (dh_min_bits < EXIM_CLIENT_DH_MIN_MIN_BITS)
{ {
DEBUG(D_tls) DEBUG(D_tls)
debug_printf("WARNING: tls_dh_min_bits far too low," debug_printf("WARNING: tls_dh_min_bits far too low,"
" clamping %d up to %d\n", " clamping %d up to %d\n",
dh_min_bits, EXIM_CLIENT_DH_MIN_MIN_BITS); dh_min_bits, EXIM_CLIENT_DH_MIN_MIN_BITS);
dh_min_bits = EXIM_CLIENT_DH_MIN_MIN_BITS; dh_min_bits = EXIM_CLIENT_DH_MIN_MIN_BITS;
skipping to change at line 2273 skipping to change at line 2321
debug_printf("TLS: server certificate DANE required.\n"); debug_printf("TLS: server certificate DANE required.\n");
state->verify_requirement = VERIFY_DANE; state->verify_requirement = VERIFY_DANE;
gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_REQUIRE); gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_REQUIRE);
} }
else else
#endif #endif
if ( ( state->exp_tls_verify_certificates if ( ( state->exp_tls_verify_certificates
&& !ob->tls_verify_hosts && !ob->tls_verify_hosts
&& (!ob->tls_try_verify_hosts || !*ob->tls_try_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); tls_client_setup_hostname_checks(host, state, ob);
DEBUG(D_tls) DEBUG(D_tls)
debug_printf("TLS: server certificate verification required.\n"); debug_printf("TLS: server certificate verification required.\n");
state->verify_requirement = VERIFY_REQUIRED; state->verify_requirement = VERIFY_REQUIRED;
gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_REQUIRE); 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); tls_client_setup_hostname_checks(host, state, ob);
DEBUG(D_tls) DEBUG(D_tls)
debug_printf("TLS: server certificate verification optional.\n"); debug_printf("TLS: server certificate verification optional.\n");
state->verify_requirement = VERIFY_OPTIONAL; state->verify_requirement = VERIFY_OPTIONAL;
gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_REQUEST); gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_REQUEST);
} }
else else
{ {
DEBUG(D_tls) DEBUG(D_tls)
skipping to change at line 2305 skipping to change at line 2353
gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_IGNORE); gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_IGNORE);
} }
#ifndef DISABLE_OCSP #ifndef DISABLE_OCSP
/* supported since GnuTLS 3.1.3 */ /* supported since GnuTLS 3.1.3 */
if (request_ocsp) if (request_ocsp)
{ {
DEBUG(D_tls) debug_printf("TLS: will request OCSP stapling\n"); DEBUG(D_tls) debug_printf("TLS: will request OCSP stapling\n");
if ((rc = gnutls_ocsp_status_request_enable_client(state->session, if ((rc = gnutls_ocsp_status_request_enable_client(state->session,
NULL, 0, NULL)) != OK) NULL, 0, NULL)) != OK)
return tls_error(US"cert-status-req", {
gnutls_strerror(rc), state->host, errstr); tls_error(US"cert-status-req", US gnutls_strerror(rc), state->host, errstr);
tls_out.ocsp = OCSP_NOT_RESP; return NULL;
}
tlsp->ocsp = OCSP_NOT_RESP;
} }
#endif #endif
#ifndef DISABLE_EVENT #ifndef DISABLE_EVENT
if (tb->event_action) if (tb && tb->event_action)
{ {
state->event_action = tb->event_action; state->event_action = tb->event_action;
gnutls_session_set_ptr(state->session, state); gnutls_session_set_ptr(state->session, state);
gnutls_certificate_set_verify_function(state->x509_cred, verify_cb); gnutls_certificate_set_verify_function(state->x509_cred, verify_cb);
} }
#endif #endif
gnutls_transport_set_ptr(state->session, (gnutls_transport_ptr_t)(long) fd); gnutls_transport_set_ptr(state->session, (gnutls_transport_ptr_t)(long) fd);
state->fd_in = fd; state->fd_in = fd;
state->fd_out = fd; state->fd_out = fd;
DEBUG(D_tls) debug_printf("about to gnutls_handshake\n"); DEBUG(D_tls) debug_printf("about to gnutls_handshake\n");
/* There doesn't seem to be a built-in timeout on connection. */ /* There doesn't seem to be a built-in timeout on connection. */
sigalrm_seen = FALSE; sigalrm_seen = FALSE;
alarm(ob->command_timeout); ALARM(ob->command_timeout);
do do
rc = gnutls_handshake(state->session); rc = gnutls_handshake(state->session);
while (rc == GNUTLS_E_AGAIN || rc == GNUTLS_E_INTERRUPTED && !sigalrm_seen); while (rc == GNUTLS_E_AGAIN || rc == GNUTLS_E_INTERRUPTED && !sigalrm_seen);
alarm(0); ALARM_CLR(0);
if (rc != GNUTLS_E_SUCCESS) if (rc != GNUTLS_E_SUCCESS)
{
if (sigalrm_seen) if (sigalrm_seen)
{ {
gnutls_alert_send(state->session, GNUTLS_AL_FATAL, GNUTLS_A_USER_CANCELED); 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 else
return tls_error(US"gnutls_handshake", gnutls_strerror(rc), state->host, err tls_error(US"gnutls_handshake", US gnutls_strerror(rc), state->host, errstr)
str); ;
return NULL;
}
DEBUG(D_tls) debug_printf("gnutls_handshake was successful\n"); DEBUG(D_tls) debug_printf("gnutls_handshake was successful\n");
/* Verify late */ /* Verify late */
if (!verify_certificate(state, errstr)) if (!verify_certificate(state, errstr))
return tls_error(US"certificate verification failed", *errstr, state->host, er {
rstr); tls_error(US"certificate verification failed", *errstr, state->host, errstr);
return NULL;
}
#ifndef DISABLE_OCSP #ifndef DISABLE_OCSP
if (require_ocsp) if (require_ocsp)
{ {
DEBUG(D_tls) DEBUG(D_tls)
{ {
gnutls_datum_t stapling; gnutls_datum_t stapling;
gnutls_ocsp_resp_t resp; gnutls_ocsp_resp_t resp;
gnutls_datum_t printed; gnutls_datum_t printed;
if ( (rc= gnutls_ocsp_status_request_get(state->session, &stapling)) == 0 if ( (rc= gnutls_ocsp_status_request_get(state->session, &stapling)) == 0
&& (rc= gnutls_ocsp_resp_init(&resp)) == 0 && (rc= gnutls_ocsp_resp_init(&resp)) == 0
&& (rc= gnutls_ocsp_resp_import(resp, &stapling)) == 0 && (rc= gnutls_ocsp_resp_import(resp, &stapling)) == 0
&& (rc= gnutls_ocsp_resp_print(resp, GNUTLS_OCSP_PRINT_FULL, &printed)) = = 0 && (rc= gnutls_ocsp_resp_print(resp, GNUTLS_OCSP_PRINT_FULL, &printed)) = = 0
) )
{ {
debug_printf("%.4096s", printed.data); debug_printf("%.4096s", printed.data);
gnutls_free(printed.data); gnutls_free(printed.data);
} }
else else
(void) tls_error(US"ocsp decode", gnutls_strerror(rc), state->host, errstr ); (void) tls_error(US"ocsp decode", US gnutls_strerror(rc), state->host, err str);
} }
if (gnutls_ocsp_status_request_is_checked(state->session, 0) == 0) if (gnutls_ocsp_status_request_is_checked(state->session, 0) == 0)
{ {
tls_out.ocsp = OCSP_FAILED; tlsp->ocsp = OCSP_FAILED;
return tls_error(US"certificate status check failed", NULL, state->host, err tls_error(US"certificate status check failed", NULL, state->host, errstr);
str); return NULL;
} }
DEBUG(D_tls) debug_printf("Passed OCSP checking\n"); DEBUG(D_tls) debug_printf("Passed OCSP checking\n");
tls_out.ocsp = OCSP_VFIED; tlsp->ocsp = OCSP_VFIED;
} }
#endif #endif
/* Figure out peer DN, and if authenticated, etc. */ /* Figure out peer DN, and if authenticated, etc. */
if ((rc = peer_status(state, errstr)) != OK) if (peer_status(state, errstr) != OK)
return rc; return NULL;
/* Sets various Exim expansion variables; may need to adjust for ACL callouts */ /* Sets various Exim expansion variables; may need to adjust for ACL callouts */
extract_exim_vars_from_tls_state(state); extract_exim_vars_from_tls_state(state);
return OK; return state;
} }
/************************************************* /*************************************************
* Close down a TLS session * * Close down a TLS session *
*************************************************/ *************************************************/
/* This is also called from within a delivery subprocess forked from the /* This is also called from within a delivery subprocess forked from the
daemon, to shut down the TLS library, without actually doing a shutdown (which daemon, to shut down the TLS library, without actually doing a shutdown (which
would tamper with the TLS session in the parent process). would tamper with the TLS session in the parent process).
Arguments: Arguments:
ct_ctx client context pointer, or NULL for the one global server context
shutdown 1 if TLS close-alert is to be sent, shutdown 1 if TLS close-alert is to be sent,
2 if also response to be waited for 2 if also response to be waited for
Returns: nothing Returns: nothing
*/ */
void void
tls_close(BOOL is_server, int 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) if (shutdown)
{ {
DEBUG(D_tls) debug_printf("tls_close(): shutting down TLS%s\n", DEBUG(D_tls) debug_printf("tls_close(): shutting down TLS%s\n",
shutdown > 1 ? " (with response-wait)" : ""); shutdown > 1 ? " (with response-wait)" : "");
alarm(2); ALARM(2);
gnutls_bye(state->session, shutdown > 1 ? GNUTLS_SHUT_RDWR : GNUTLS_SHUT_WR); gnutls_bye(state->session, shutdown > 1 ? GNUTLS_SHUT_RDWR : GNUTLS_SHUT_WR);
alarm(0); ALARM_CLR(0);
} }
gnutls_deinit(state->session); gnutls_deinit(state->session);
gnutls_certificate_free_credentials(state->x509_cred); 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); if (state->xfer_buffer) store_free(state->xfer_buffer);
memcpy(state, &exim_gnutls_state_init, sizeof(exim_gnutls_state_init)); memcpy(state, &exim_gnutls_state_init, sizeof(exim_gnutls_state_init));
if (!state_server.session && !state_client.session)
{
gnutls_global_deinit();
exim_gnutls_base_init_done = FALSE;
}
} }
static BOOL static BOOL
tls_refill(unsigned lim) tls_refill(unsigned lim)
{ {
exim_gnutls_state_st * state = &state_server; exim_gnutls_state_st * state = &state_server;
ssize_t inbytes; ssize_t inbytes;
DEBUG(D_tls) debug_printf("Calling gnutls_record_recv(%p, %p, %u)\n", DEBUG(D_tls) debug_printf("Calling gnutls_record_recv(%p, %p, %u)\n",
state->session, state->xfer_buffer, ssl_xfer_buffer_size); state->session, state->xfer_buffer, ssl_xfer_buffer_size);
sigalrm_seen = FALSE; sigalrm_seen = FALSE;
if (smtp_receive_timeout > 0) alarm(smtp_receive_timeout); if (smtp_receive_timeout > 0) ALARM(smtp_receive_timeout);
inbytes = gnutls_record_recv(state->session, state->xfer_buffer,
MIN(ssl_xfer_buffer_size, lim)); do
alarm(0); inbytes = gnutls_record_recv(state->session, state->xfer_buffer,
MIN(ssl_xfer_buffer_size, lim));
/* Timeouts do not get this far; see command_timeout_handler(). while (inbytes == GNUTLS_E_AGAIN);
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 if (smtp_receive_timeout > 0) ALARM_CLR(0);
non-TLS handling. */
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. 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) if (sigalrm_seen)
{ {
DEBUG(D_tls) debug_printf("Got tls read timeout\n"); DEBUG(D_tls) debug_printf("Got tls read timeout\n");
state->xfer_error = TRUE; state->xfer_error = TRUE;
return FALSE; return FALSE;
} }
else if (inbytes == 0) else if (inbytes == 0)
{ {
skipping to change at line 2482 skipping to change at line 2547
receive_get_cache = smtp_get_cache; receive_get_cache = smtp_get_cache;
receive_ungetc = smtp_ungetc; receive_ungetc = smtp_ungetc;
receive_feof = smtp_feof; receive_feof = smtp_feof;
receive_ferror = smtp_ferror; receive_ferror = smtp_ferror;
receive_smtp_buffered = smtp_buffered; receive_smtp_buffered = smtp_buffered;
gnutls_deinit(state->session); gnutls_deinit(state->session);
gnutls_certificate_free_credentials(state->x509_cred); gnutls_certificate_free_credentials(state->x509_cred);
state->session = NULL; state->session = NULL;
state->tlsp->active = -1; state->tlsp->active.sock = -1;
state->tlsp->active.tls_ctx = NULL;
state->tlsp->bits = 0; state->tlsp->bits = 0;
state->tlsp->certificate_verified = FALSE; state->tlsp->certificate_verified = FALSE;
tls_channelbinding_b64 = NULL; tls_channelbinding_b64 = NULL;
state->tlsp->cipher = NULL; state->tlsp->cipher = NULL;
state->tlsp->peercert = NULL; state->tlsp->peercert = NULL;
state->tlsp->peerdn = NULL; state->tlsp->peerdn = NULL;
return FALSE; return FALSE;
} }
/* Handle genuine errors */ /* Handle genuine errors */
else if (inbytes < 0) 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); record_io_error(state, (int) inbytes, US"recv", NULL);
state->xfer_error = TRUE; state->xfer_error = TRUE;
return FALSE; return FALSE;
} }
#ifndef DISABLE_DKIM #ifndef DISABLE_DKIM
dkim_exim_verify_feed(state->xfer_buffer, inbytes); dkim_exim_verify_feed(state->xfer_buffer, inbytes);
#endif #endif
state->xfer_buffer_hwm = (int) inbytes; state->xfer_buffer_hwm = (int) inbytes;
state->xfer_buffer_lwm = 0; state->xfer_buffer_lwm = 0;
return TRUE; return TRUE;
skipping to change at line 2519 skipping to change at line 2586
/************************************************* /*************************************************
* TLS version of getc * * TLS version of getc *
*************************************************/ *************************************************/
/* This gets the next byte from the TLS input buffer. If the buffer is empty, /* This gets the next byte from the TLS input buffer. If the buffer is empty,
it refills the buffer via the GnuTLS reading function. it refills the buffer via the GnuTLS reading function.
Only used by the server-side TLS. Only used by the server-side TLS.
This feeds DKIM and should be used for all message-body reads. 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 Returns: the next character or EOF
*/ */
int int
tls_getc(unsigned lim) tls_getc(unsigned lim)
{ {
exim_gnutls_state_st * state = &state_server; exim_gnutls_state_st * state = &state_server;
if (state->xfer_buffer_lwm >= state->xfer_buffer_hwm) if (state->xfer_buffer_lwm >= state->xfer_buffer_hwm)
if (!tls_refill(lim)) if (!tls_refill(lim))
skipping to change at line 2586 skipping to change at line 2653
} }
/************************************************* /*************************************************
* Read bytes from TLS channel * * Read bytes from TLS channel *
*************************************************/ *************************************************/
/* This does not feed DKIM, so if the caller uses this for reading message body, /* This does not feed DKIM, so if the caller uses this for reading message body,
then the caller must feed DKIM. then the caller must feed DKIM.
Arguments: Arguments:
ct_ctx client context pointer, or NULL for the one global server context
buff buffer of data buff buffer of data
len size of buffer len size of buffer
Returns: the number of bytes read Returns: the number of bytes read
-1 after a failed read -1 after a failed read, including EOF
*/ */
int 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; ssize_t inbytes;
if (len > INT_MAX) if (len > INT_MAX)
len = INT_MAX; len = INT_MAX;
if (state->xfer_buffer_lwm < state->xfer_buffer_hwm) if (state->xfer_buffer_lwm < state->xfer_buffer_hwm)
DEBUG(D_tls) DEBUG(D_tls)
debug_printf("*** PROBABLY A BUG *** " \ debug_printf("*** PROBABLY A BUG *** " \
"tls_read() called with data in the tls_getc() buffer, %d ignored\n", "tls_read() called with data in the tls_getc() buffer, %d ignored\n",
state->xfer_buffer_hwm - state->xfer_buffer_lwm); state->xfer_buffer_hwm - state->xfer_buffer_lwm);
DEBUG(D_tls) DEBUG(D_tls)
debug_printf("Calling gnutls_record_recv(%p, %p, " SIZE_T_FMT ")\n", debug_printf("Calling gnutls_record_recv(%p, %p, " SIZE_T_FMT ")\n",
state->session, buff, len); 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) return inbytes;
if (inbytes == 0) if (inbytes == 0)
{ {
DEBUG(D_tls) debug_printf("Got TLS_EOF\n"); 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; return -1;
} }
/************************************************* /*************************************************
* Write bytes down TLS channel * * Write bytes down TLS channel *
*************************************************/ *************************************************/
/* /*
Arguments: Arguments:
is_server channel specifier ct_ctx client context pointer, or NULL for the one global server context
buff buffer of data buff buffer of data
len number of bytes len number of bytes
more more data expected soon more more data expected soon
Returns: the number of bytes after a successful write, Returns: the number of bytes after a successful write,
-1 after a failed write -1 after a failed write
*/ */
int 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; ssize_t outbytes;
size_t left = len; 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 #ifdef SUPPORT_CORK
static BOOL corked = FALSE; static BOOL corked = FALSE;
if (more && !corked) gnutls_record_cork(state->session); if (more && !corked) gnutls_record_cork(state->session);
#endif #endif
DEBUG(D_tls) debug_printf("%s(%p, " SIZE_T_FMT "%s)\n", __FUNCTION__, DEBUG(D_tls) debug_printf("%s(%p, " SIZE_T_FMT "%s)\n", __FUNCTION__,
buff, left, more ? ", more" : ""); buff, left, more ? ", more" : "");
while (left > 0) while (left > 0)
{ {
DEBUG(D_tls) debug_printf("gnutls_record_send(SSL, %p, " SIZE_T_FMT ")\n", DEBUG(D_tls) debug_printf("gnutls_record_send(SSL, %p, " SIZE_T_FMT ")\n",
buff, left); 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); DEBUG(D_tls) debug_printf("outbytes=" SSIZE_T_FMT "\n", outbytes);
if (outbytes < 0) if (outbytes < 0)
{ {
DEBUG(D_tls) debug_printf("%s: gnutls_record_send err\n", __FUNCTION__);
record_io_error(state, outbytes, US"send", NULL); record_io_error(state, outbytes, US"send", NULL);
return -1; return -1;
} }
if (outbytes == 0) if (outbytes == 0)
{ {
record_io_error(state, 0, US"send", US"TLS channel closed on write"); record_io_error(state, 0, US"send", US"TLS channel closed on write");
return -1; return -1;
} }
left -= outbytes; left -= outbytes;
 End of changes. 112 change blocks. 
136 lines changed or deleted 213 lines changed or added

Home  |  About  |  Features  |  All  |  Newest  |  Dox  |  Diffs  |  RSS Feeds  |  Screenshots  |  Comments  |  Imprint  |  Privacy  |  HTTP(S)