# define EXIM_HAVE_OPENSSL_CHECKHOST
# define EXIM_HAVE_OPENSSL_DH_BITS
# define EXIM_HAVE_OPENSSL_TLS_METHOD
+# define EXIM_HAVE_OPENSSL_KEYLOG
+# define EXIM_HAVE_OPENSSL_CIPHER_GET_ID
# else
# define EXIM_NEED_OPENSSL_INIT
# endif
# endif
#endif
+#ifndef LIBRESSL_VERSION_NUMBER
+# if OPENSSL_VERSION_NUMBER >= 0x010101000L
+# define OPENSSL_HAVE_KEYLOG_CB
+# define OPENSSL_HAVE_NUM_TICKETS
+# define EXIM_HAVE_OPENSSL_CIPHER_STD_NAME
+# endif
+#endif
+
#if !defined(EXIM_HAVE_OPENSSL_TLSEXT) && !defined(DISABLE_OCSP)
# warning "OpenSSL library version too old; define DISABLE_OCSP in Makefile"
# define DISABLE_OCSP
# include <openssl/x509v3.h>
#endif
+#ifndef EXIM_HAVE_OPENSSL_CIPHER_STD_NAME
+# ifndef EXIM_HAVE_OPENSSL_CIPHER_GET_ID
+# define SSL_CIPHER_get_id(c) (c->id)
+# endif
+# include "tls-cipher-stdname.c"
+#endif
+
/*************************************************
* OpenSSL option parse *
*************************************************/
}
}
+#ifdef OPENSSL_HAVE_KEYLOG_CB
+static void
+keylog_callback(const SSL *ssl, const char *line)
+{
+DEBUG(D_tls) debug_printf("%.200s\n", line);
+}
+#endif
+
/*************************************************
level. */
DEBUG(D_tls) SSL_CTX_set_info_callback(ctx, (void (*)())info_callback);
+#ifdef OPENSSL_HAVE_KEYLOG_CB
+DEBUG(D_tls) SSL_CTX_set_keylog_callback(ctx, (void (*)())keylog_callback);
+#endif
/* Automatically re-try reads/writes after renegotiation. */
(void) SSL_CTX_set_mode(ctx, SSL_MODE_AUTO_RETRY);
else
DEBUG(D_tls) debug_printf("no SSL CTX options to set\n");
+#ifdef OPENSSL_HAVE_NUM_TICKETS
+SSL_CTX_set_num_tickets(ctx, 0); /* send no TLS1.3 stateful-tickets */
+#endif
+
/* We'd like to disable session cache unconditionally, but foolish Outlook
Express clients then give up the first TLS connection and make a second one
(which works). Only when there is an IMAP service on the same machine.
/*
Argument: pointer to an SSL structure for the connection
- buffer to use for answer
- size of buffer
pointer to number of bits for cipher
-Returns: nothing
+Returns: pointer to allocated string in perm-pool
*/
-static void
-construct_cipher_name(SSL *ssl, uschar *cipherbuf, int bsize, int *bits)
+static uschar *
+construct_cipher_name(SSL * ssl, int * bits)
{
+int pool = store_pool;
/* With OpenSSL 1.0.0a, 'c' needs to be const but the documentation doesn't
yet reflect that. It should be a safe change anyway, even 0.9.8 versions have
the accessor functions use const in the prototype. */
const uschar * ver = CUS SSL_get_version(ssl);
const SSL_CIPHER * c = (const SSL_CIPHER *) SSL_get_current_cipher(ssl);
+uschar * s;
SSL_CIPHER_get_bits(c, bits);
-string_format(cipherbuf, bsize, "%s:%s:%u", ver,
- SSL_CIPHER_get_name(c), *bits);
+store_pool = POOL_PERM;
+s = string_sprintf("%s:%s:%u", ver, SSL_CIPHER_get_name(c), *bits);
+store_pool = pool;
+DEBUG(D_tls) debug_printf("Cipher: %s\n", s);
+return s;
+}
+
+
+/* Get IETF-standard name for ciphersuite.
+Argument: pointer to an SSL structure for the connection
+Returns: pointer to string
+*/
-DEBUG(D_tls) debug_printf("Cipher: %s\n", cipherbuf);
+static const uschar *
+cipher_stdname_ssl(SSL * ssl)
+{
+#ifdef EXIM_HAVE_OPENSSL_CIPHER_STD_NAME
+return CUS SSL_CIPHER_standard_name(SSL_get_current_cipher(ssl));
+#else
+ushort id = 0xffff & SSL_CIPHER_get_id(SSL_get_current_cipher(ssl));
+return cipher_stdname(id >> 8, id & 0xff);
+#endif
}
uschar * expciphers;
tls_ext_ctx_cb * cbinfo;
static uschar peerdn[256];
-static uschar cipherbuf[256];
/* Check for previous activation */
}
DEBUG(D_tls) debug_printf("SSL_accept was successful\n");
+ERR_clear_error(); /* Even success can leave errors in the stack. Seen with
+ anon-authentication ciphersuite negociated. */
/* TLS has been set up. Adjust the input functions to read via TLS,
and initialize things. */
peer_cert(server_ssl, &tls_in, peerdn, sizeof(peerdn));
-construct_cipher_name(server_ssl, cipherbuf, sizeof(cipherbuf), &tls_in.bits);
-tls_in.cipher = cipherbuf;
+tls_in.cipher = construct_cipher_name(server_ssl, &tls_in.bits);
+tls_in.cipher_stdname = cipher_stdname_ssl(server_ssl);
DEBUG(D_tls)
{
uschar buf[2048];
- if (SSL_get_shared_ciphers(server_ssl, CS buf, sizeof(buf)) != NULL)
+ if (SSL_get_shared_ciphers(server_ssl, CS buf, sizeof(buf)))
debug_printf("Shared ciphers: %s\n", buf);
+
+#ifdef EXIM_HAVE_OPENSSL_KEYLOG
+ {
+ BIO * bp = BIO_new(BIO_s_mem());
+ uschar * s;
+ int len;
+ SSL_SESSION_print_keylog(bp, SSL_get_session(server_ssl));
+ len = (int) BIO_get_mem_data(bp, CSS &s);
+ debug_printf("%.*s", len, s);
+ BIO_free(bp);
+ }
+#endif
}
/* Record the certificate we presented */
/* Called from the smtp transport after STARTTLS has been accepted.
-Argument:
- fd the fd of the connection
- host connected host (for messages and option-tests)
- addr the first address (for some randomness; can be NULL)
- tb transport (always smtp)
- tlsa_dnsa tlsa lookup, if DANE, else null
- tlsp record details of channel configuration here; must be non-NULL
- errstr error string pointer
-
-Returns: Pointer to TLS session context, or NULL on error
+Arguments:
+ cctx connection context
+ conn_args connection details
+ cookie datum for randomness; can be NULL
+ tlsp record details of TLS channel configuration here; must be non-NULL
+ errstr error string pointer
+
+Returns: TRUE for success with TLS session context set in connection context,
+ FALSE on error
*/
-void *
-tls_client_start(int fd, host_item *host, address_item *addr,
- transport_instance * tb,
-#ifdef SUPPORT_DANE
- dns_answer * tlsa_dnsa,
-#endif
- tls_support * tlsp, uschar ** errstr)
+BOOL
+tls_client_start(client_conn_ctx * cctx, smtp_connect_args * conn_args,
+ void * cookie, tls_support * tlsp, uschar ** errstr)
{
+host_item * host = conn_args->host; /* for msgs and option-tests */
+transport_instance * tb = conn_args->tblock; /* always smtp or NULL */
smtp_transport_options_block * ob = tb
? (smtp_transport_options_block *)tb->options_block
: &smtp_transport_option_defaults;
exim_openssl_client_tls_ctx * exim_client_ctx;
-static uschar peerdn[256];
uschar * expciphers;
int rc;
-static uschar cipherbuf[256];
+static uschar peerdn[256];
#ifndef DISABLE_OCSP
BOOL request_ocsp = FALSE;
#ifndef DISABLE_OCSP
{
# ifdef SUPPORT_DANE
- if ( tlsa_dnsa
+ if ( conn_args->dane
&& ob->hosts_request_ocsp[0] == '*'
&& ob->hosts_request_ocsp[1] == '\0'
)
#ifndef DISABLE_OCSP
(void *)(long)request_ocsp,
#endif
- addr, &client_static_cbinfo, errstr);
-if (rc != OK) return NULL;
+ cookie, &client_static_cbinfo, errstr);
+if (rc != OK) return FALSE;
tlsp->certificate_verified = FALSE;
client_verify_callback_called = FALSE;
expciphers = NULL;
#ifdef SUPPORT_DANE
-if (tlsa_dnsa)
+if (conn_args->dane)
{
/* We fall back to tls_require_ciphers if unset, empty or forced failure, but
other failures should be treated as problems. */
if (ob->dane_require_tls_ciphers &&
!expand_check(ob->dane_require_tls_ciphers, US"dane_require_tls_ciphers",
&expciphers, errstr))
- return NULL;
+ return FALSE;
if (expciphers && *expciphers == '\0')
expciphers = NULL;
}
if (!expciphers &&
!expand_check(ob->tls_require_ciphers, US"tls_require_ciphers",
&expciphers, errstr))
- return NULL;
+ return FALSE;
/* In OpenSSL, cipher components are separated by hyphens. In GnuTLS, they
are separated by underscores. So that I can use either form in my tests, and
if (!SSL_CTX_set_cipher_list(exim_client_ctx->ctx, CS expciphers))
{
tls_error(US"SSL_CTX_set_cipher_list", host, NULL, errstr);
- return NULL;
+ return FALSE;
}
}
#ifdef SUPPORT_DANE
-if (tlsa_dnsa)
+if (conn_args->dane)
{
SSL_CTX_set_verify(exim_client_ctx->ctx,
SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
if (!DANESSL_library_init())
{
tls_error(US"library init", host, NULL, errstr);
- return NULL;
+ return FALSE;
}
if (DANESSL_CTX_init(exim_client_ctx->ctx) <= 0)
{
tls_error(US"context init", host, NULL, errstr);
- return NULL;
+ return FALSE;
}
}
else
if (tls_client_basic_ctx_init(exim_client_ctx->ctx, host, ob,
client_static_cbinfo, errstr) != OK)
- return NULL;
+ return FALSE;
if (!(exim_client_ctx->ssl = SSL_new(exim_client_ctx->ctx)))
{
tls_error(US"SSL_new", host, NULL, errstr);
- return NULL;
+ return FALSE;
}
SSL_set_session_id_context(exim_client_ctx->ssl, sid_ctx, Ustrlen(sid_ctx));
-SSL_set_fd(exim_client_ctx->ssl, fd);
+SSL_set_fd(exim_client_ctx->ssl, cctx->sock);
SSL_set_connect_state(exim_client_ctx->ssl);
if (ob->tls_sni)
{
if (!expand_check(ob->tls_sni, US"tls_sni", &tlsp->sni, errstr))
- return NULL;
+ return FALSE;
if (!tlsp->sni)
{
DEBUG(D_tls) debug_printf("Setting TLS SNI forced to fail, not sending\n");
}
#ifdef SUPPORT_DANE
-if (tlsa_dnsa)
- if (dane_tlsa_load(exim_client_ctx->ssl, host, tlsa_dnsa, errstr) != OK)
- return NULL;
+if (conn_args->dane)
+ if (dane_tlsa_load(exim_client_ctx->ssl, host, &conn_args->tlsa_dnsa, errstr) != OK)
+ return FALSE;
#endif
#ifndef DISABLE_OCSP
ALARM_CLR(0);
#ifdef SUPPORT_DANE
-if (tlsa_dnsa)
+if (conn_args->dane)
DANESSL_cleanup(exim_client_ctx->ssl);
#endif
if (rc <= 0)
{
tls_error(US"SSL_connect", host, sigalrm_seen ? US"timed out" : NULL, errstr);
- return NULL;
+ return FALSE;
}
-DEBUG(D_tls) debug_printf("SSL_connect succeeded\n");
+DEBUG(D_tls)
+ {
+ debug_printf("SSL_connect succeeded\n");
+#ifdef EXIM_HAVE_OPENSSL_KEYLOG
+ {
+ BIO * bp = BIO_new(BIO_s_mem());
+ if (bp)
+ {
+ uschar * s;
+ int len;
+ SSL_SESSION_print_keylog(bp, SSL_get_session(exim_client_ctx->ssl));
+ len = (int) BIO_get_mem_data(bp, CSS &s);
+ debug_printf("%.*s", len, s);
+ BIO_free(bp);
+ }
+ else
+ debug_printf("(alloc failure for keylog)\n");
+ }
+#endif
+ }
peer_cert(exim_client_ctx->ssl, tlsp, peerdn, sizeof(peerdn));
-construct_cipher_name(exim_client_ctx->ssl, cipherbuf, sizeof(cipherbuf), &tlsp->bits);
-tlsp->cipher = cipherbuf;
+tlsp->cipher = construct_cipher_name(exim_client_ctx->ssl, &tlsp->bits);
+tlsp->cipher_stdname = cipher_stdname_ssl(exim_client_ctx->ssl);
/* Record the certificate we presented */
{
tlsp->ourcert = crt ? X509_dup(crt) : NULL;
}
-tlsp->active.sock = fd;
+tlsp->active.sock = cctx->sock;
tlsp->active.tls_ctx = exim_client_ctx;
-return exim_client_ctx;
+cctx->tls_ctx = exim_client_ctx;
+return TRUE;
}