X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/83da1223921fe30362e8374951360dcc8f21c4e7..7be682ca5ebd9571a01b762195b11c34cd231830:/src/src/tls-openssl.c diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c index 1ae725b86..8cc2457e5 100644 --- a/src/src/tls-openssl.c +++ b/src/src/tls-openssl.c @@ -1,10 +1,8 @@ -/* $Cambridge: exim/src/src/tls-openssl.c,v 1.10 2007/01/18 15:35:42 ph10 Exp $ */ - /************************************************* * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2007 */ +/* Copyright (c) University of Cambridge 1995 - 2009 */ /* See the file NOTICE for conditions of use and distribution. */ /* This module provides the TLS (aka SSL) support for Exim using the OpenSSL @@ -26,8 +24,8 @@ functions from the OpenSSL library. */ /* Structure for collecting random data for seeding. */ typedef struct randstuff { - time_t t; - pid_t p; + struct timeval tv; + pid_t p; } randstuff; /* Local static variables */ @@ -36,6 +34,7 @@ static BOOL verify_callback_called = FALSE; static const uschar *sid_ctx = US"exim"; static SSL_CTX *ctx = NULL; +static SSL_CTX *ctx_sni = NULL; static SSL *ssl = NULL; static char ssl_errstring[256]; @@ -43,8 +42,26 @@ static char ssl_errstring[256]; static int ssl_session_timeout = 200; static BOOL verify_optional = FALSE; +static BOOL reexpand_tls_files_for_sni = FALSE; + +typedef struct tls_ext_ctx_cb { + uschar *certificate; + uschar *privatekey; + uschar *dhparam; + /* these are cached from first expand */ + uschar *server_cipher_list; + /* only passed down to tls_error: */ + host_item *host; +} tls_ext_ctx_cb; +/* should figure out a cleanup of API to handle state preserved per +implementation, for various reasons, which can be void * in the APIs. +For now, we hack around it. */ +tls_ext_ctx_cb *static_cbinfo = NULL; + +static int +setup_certs(SSL_CTX *sctx, uschar *certs, uschar *crl, host_item *host, BOOL optional); /************************************************* @@ -62,25 +79,33 @@ Argument: prefix text to include in the logged error host NULL if setting up a server; the connected host if setting up a client + msg error message or NULL if we should ask OpenSSL Returns: OK/DEFER/FAIL */ static int -tls_error(uschar *prefix, host_item *host) +tls_error(uschar *prefix, host_item *host, uschar *msg) { -ERR_error_string(ERR_get_error(), ssl_errstring); +if (msg == NULL) + { + ERR_error_string(ERR_get_error(), ssl_errstring); + msg = (uschar *)ssl_errstring; + } + if (host == NULL) { - log_write(0, LOG_MAIN, "TLS error on connection from %s (%s): %s", - (sender_fullhost != NULL)? sender_fullhost : US"local process", - prefix, ssl_errstring); + uschar *conn_info = smtp_get_connection_info(); + if (Ustrncmp(conn_info, US"SMTP ", 5) == 0) + conn_info += 5; + log_write(0, LOG_MAIN, "TLS error on %s (%s): %s", + conn_info, prefix, msg); return DEFER; } else { log_write(0, LOG_MAIN, "TLS error on connection to %s [%s] (%s): %s", - host->name, host->address, prefix, ssl_errstring); + host->name, host->address, prefix, msg); return FAIL; } } @@ -195,8 +220,8 @@ return 1; /* accept */ *************************************************/ /* The SSL library functions call this from time to time to indicate what they -are doing. We copy the string to the debugging output when the level is high -enough. +are doing. We copy the string to the debugging output when TLS debugging has +been requested. Arguments: s the SSL connection @@ -224,12 +249,13 @@ DEBUG(D_tls) debug_printf("SSL info: %s\n", SSL_state_string_long(s)); Arguments: dhparam DH parameter file + host connected host, if client; NULL if server Returns: TRUE if OK (nothing to set up, or setup worked) */ static BOOL -init_dh(uschar *dhparam) +init_dh(uschar *dhparam, host_item *host) { BOOL yield = TRUE; BIO *bio; @@ -243,16 +269,16 @@ if (dhexpanded == NULL) return TRUE; if ((bio = BIO_new_file(CS dhexpanded, "r")) == NULL) { - log_write(0, LOG_MAIN, "DH: could not read %s: %s", dhexpanded, - strerror(errno)); + tls_error(string_sprintf("could not read dhparams file %s", dhexpanded), + host, (uschar *)strerror(errno)); yield = FALSE; } else { if ((dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL)) == NULL) { - log_write(0, LOG_MAIN, "DH: could not load params from %s", - dhexpanded); + tls_error(string_sprintf("could not read dhparams file %s", dhexpanded), + host, NULL); yield = FALSE; } else @@ -272,6 +298,145 @@ return yield; +/************************************************* +* Expand key and cert file specs * +*************************************************/ + +/* Called once during tls_init and possibly againt during TLS setup, for a +new context, if Server Name Indication was used and tls_sni was seen in +the certificate string. + +Arguments: + sctx the SSL_CTX* to update + cbinfo various parts of session state + +Returns: OK/DEFER/FAIL +*/ + +static int +tls_expand_session_files(SSL_CTX *sctx, const tls_ext_ctx_cb *cbinfo) +{ +uschar *expanded; + +if (cbinfo->certificate == NULL) + return OK; + +if (Ustrstr(cbinfo->certificate, US"tls_sni")) + reexpand_tls_files_for_sni = TRUE; + +if (!expand_check(cbinfo->certificate, US"tls_certificate", &expanded)) + return DEFER; + +if (expanded != NULL) + { + DEBUG(D_tls) debug_printf("tls_certificate file %s\n", expanded); + if (!SSL_CTX_use_certificate_chain_file(sctx, CS expanded)) + return tls_error(string_sprintf( + "SSL_CTX_use_certificate_chain_file file=%s", expanded), + cbinfo->host, NULL); + } + +if (cbinfo->privatekey != NULL && + !expand_check(cbinfo->privatekey, US"tls_privatekey", &expanded)) + return DEFER; + +/* If expansion was forced to fail, key_expanded will be NULL. If the result +of the expansion is an empty string, ignore it also, and assume the private +key is in the same file as the certificate. */ + +if (expanded != NULL && *expanded != 0) + { + DEBUG(D_tls) debug_printf("tls_privatekey file %s\n", expanded); + if (!SSL_CTX_use_PrivateKey_file(sctx, CS expanded, SSL_FILETYPE_PEM)) + return tls_error(string_sprintf( + "SSL_CTX_use_PrivateKey_file file=%s", expanded), cbinfo->host, NULL); + } + +return OK; +} + + + + +/************************************************* +* Callback to handle SNI * +*************************************************/ + +/* Called when acting as server during the TLS session setup if a Server Name +Indication extension was sent by the client. + +API documentation is OpenSSL s_server.c implementation. + +Arguments: + s SSL* of the current session + ad unknown (part of OpenSSL API) (unused) + arg Callback of "our" registered data + +Returns: SSL_TLSEXT_ERR_{OK,ALERT_WARNING,ALERT_FATAL,NOACK} +*/ + +static int +tls_servername_cb(SSL *s, int *ad ARG_UNUSED, void *arg); +/* pre-declared for SSL_CTX_set_tlsext_servername_callback call within func */ + +static int +tls_servername_cb(SSL *s, int *ad ARG_UNUSED, void *arg) +{ +const char *servername = SSL_get_servername(s, TLSEXT_NAMETYPE_host_name); +const tls_ext_ctx_cb *cbinfo = (tls_ext_ctx_cb *) arg; +int rc; + +if (!servername) + return SSL_TLSEXT_ERR_OK; + +DEBUG(D_tls) debug_printf("TLS SNI: %s%s\n", servername, + reexpand_tls_files_for_sni ? "" : " (unused for certificate selection)"); + +/* Make the extension value available for expansion */ +tls_sni = servername; + +if (!reexpand_tls_files_for_sni) + return SSL_TLSEXT_ERR_OK; + +/* Can't find an SSL_CTX_clone() or equivalent, so we do it manually; +not confident that memcpy wouldn't break some internal reference counting. +Especially since there's a references struct member, which would be off. */ + +ctx_sni = SSL_CTX_new(SSLv23_server_method()); +if (!ctx_sni) + { + ERR_error_string(ERR_get_error(), ssl_errstring); + DEBUG(D_tls) debug_printf("SSL_CTX_new() failed: %s\n", ssl_errstring); + return SSL_TLSEXT_ERR_NOACK; + } + +/* Not sure how many of these are actually needed, since SSL object +already exists. Might even need this selfsame callback, for reneg? */ + +SSL_CTX_set_info_callback(ctx_sni, SSL_CTX_get_info_callback(ctx)); +SSL_CTX_set_mode(ctx_sni, SSL_CTX_get_mode(ctx)); +SSL_CTX_set_options(ctx_sni, SSL_CTX_get_options(ctx)); +SSL_CTX_set_timeout(ctx_sni, SSL_CTX_get_timeout(ctx)); +SSL_CTX_set_tlsext_servername_callback(ctx_sni, tls_servername_cb); +SSL_CTX_set_tlsext_servername_arg(ctx_sni, cbinfo); +if (cbinfo->server_cipher_list) + SSL_CTX_set_cipher_list(ctx_sni, CS cbinfo->server_cipher_list); + +rc = tls_expand_session_files(ctx_sni, cbinfo); +if (rc != OK) return SSL_TLSEXT_ERR_NOACK; + +rc = setup_certs(ctx_sni, tls_verify_certificates, tls_crl, NULL, FALSE); +if (rc != OK) return SSL_TLSEXT_ERR_NOACK; + +DEBUG(D_tls) debug_printf("Switching SSL context.\n"); +SSL_set_SSL_CTX(s, ctx_sni); + +return SSL_TLSEXT_ERR_OK; +} + + + + /************************************************* * Initialize for TLS * *************************************************/ @@ -293,15 +458,32 @@ static int tls_init(host_item *host, uschar *dhparam, uschar *certificate, uschar *privatekey, address_item *addr) { +long init_options; +int rc; +BOOL okay; +tls_ext_ctx_cb *cbinfo; + +cbinfo = store_malloc(sizeof(tls_ext_ctx_cb)); +cbinfo->certificate = certificate; +cbinfo->privatekey = privatekey; +cbinfo->dhparam = dhparam; +cbinfo->host = host; + SSL_load_error_strings(); /* basic set up */ OpenSSL_add_ssl_algorithms(); +#if (OPENSSL_VERSION_NUMBER >= 0x0090800fL) && !defined(OPENSSL_NO_SHA256) +/* SHA256 is becoming ever more popular. This makes sure it gets added to the +list of available digests. */ +EVP_add_digest(EVP_sha256()); +#endif + /* Create a context */ ctx = SSL_CTX_new((host == NULL)? SSLv23_server_method() : SSLv23_client_method()); -if (ctx == NULL) return tls_error(US"SSL_CTX_new", host); +if (ctx == NULL) return tls_error(US"SSL_CTX_new", host, NULL); /* It turns out that we need to seed the random number generator this early in order to get the full complement of ciphers to work. It took me roughly a day @@ -314,7 +496,7 @@ afterwards. */ if (!RAND_status()) { randstuff r; - r.t = time(NULL); + gettimeofday(&r.tv, NULL); r.p = getpid(); RAND_seed((uschar *)(&r), sizeof(r)); @@ -322,82 +504,57 @@ if (!RAND_status()) if (addr != NULL) RAND_seed((uschar *)addr, sizeof(addr)); if (!RAND_status()) - { - if (host == NULL) - { - log_write(0, LOG_MAIN, "TLS error on connection from %s: " - "unable to seed random number generator", - (sender_fullhost != NULL)? sender_fullhost : US"local process"); - return DEFER; - } - else - { - log_write(0, LOG_MAIN, "TLS error on connection to %s [%s]: " - "unable to seed random number generator", - host->name, host->address); - return FAIL; - } - } + return tls_error(US"RAND_status", host, + US"unable to seed random number generator"); } /* Set up the information callback, which outputs if debugging is at a suitable level. */ -if (!(SSL_CTX_set_info_callback(ctx, (void (*)())info_callback))) - return tls_error(US"SSL_CTX_set_info_callback", host); +SSL_CTX_set_info_callback(ctx, (void (*)())info_callback); -/* The following patch was supplied by Robert Roselius */ +/* Automatically re-try reads/writes after renegotiation. */ +(void) SSL_CTX_set_mode(ctx, SSL_MODE_AUTO_RETRY); -#if OPENSSL_VERSION_NUMBER > 0x00906040L -/* Enable client-bug workaround. - Versions of OpenSSL as of 0.9.6d include a "CBC countermeasure" feature, - which causes problems with some clients (such as the Certicom SSL Plus - library used by Eudora). This option, SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS, - disables the coutermeasure allowing Eudora to connect. - Some poppers and MTAs use SSL_OP_ALL, which enables all such bug - workarounds. */ -/* XXX (Silently?) ignore failure here? XXX*/ +/* Apply administrator-supplied work-arounds. +Historically we applied just one requested option, +SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS, but when bug 994 requested a second, we +moved to an administrator-controlled list of options to specify and +grandfathered in the first one as the default value for "openssl_options". -if (!(SSL_CTX_set_options(ctx, SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS))) - return tls_error(US"SSL_CTX_set_option", host); -#endif - -/* Initialize with DH parameters if supplied */ +No OpenSSL version number checks: the options we accept depend upon the +availability of the option value macros from OpenSSL. */ -if (!init_dh(dhparam)) return DEFER; +okay = tls_openssl_options_parse(openssl_options, &init_options); +if (!okay) + return tls_error(US"openssl_options parsing failed", host, NULL); -/* Set up certificate and key */ - -if (certificate != NULL) +if (init_options) { - uschar *expanded; - if (!expand_check(certificate, US"tls_certificate", &expanded)) - return DEFER; + DEBUG(D_tls) debug_printf("setting SSL CTX options: %#lx\n", init_options); + if (!(SSL_CTX_set_options(ctx, init_options))) + return tls_error(string_sprintf( + "SSL_CTX_set_option(%#lx)", init_options), host, NULL); + } +else + DEBUG(D_tls) debug_printf("no SSL CTX options to set\n"); - if (expanded != NULL) - { - DEBUG(D_tls) debug_printf("tls_certificate file %s\n", expanded); - if (!SSL_CTX_use_certificate_chain_file(ctx, CS expanded)) - return tls_error(string_sprintf( - "SSL_CTX_use_certificate_chain_file file=%s", expanded), host); - } +/* Initialize with DH parameters if supplied */ - if (privatekey != NULL && - !expand_check(privatekey, US"tls_privatekey", &expanded)) - return DEFER; +if (!init_dh(dhparam, host)) return DEFER; - /* If expansion was forced to fail, key_expanded will be NULL. If the result - of the expansion is an empty string, ignore it also, and assume the private - key is in the same file as the certificate. */ +/* Set up certificate and key */ - if (expanded != NULL && *expanded != 0) - { - DEBUG(D_tls) debug_printf("tls_privatekey file %s\n", expanded); - if (!SSL_CTX_use_PrivateKey_file(ctx, CS expanded, SSL_FILETYPE_PEM)) - return tls_error(string_sprintf( - "SSL_CTX_use_PrivateKey_file file=%s", expanded), host); - } - } +rc = tls_expand_session_files(ctx, cbinfo); +if (rc != OK) return rc; + +/* If we need to handle SNI, do so */ +#if OPENSSL_VERSION_NUMBER >= 0x0090806fL && !defined(OPENSSL_NO_TLSEXT) +/* We always do this, so that $tls_sni is available even if not used in +tls_certificate */ +SSL_CTX_set_tlsext_servername_callback(ctx, tls_servername_cb); +SSL_CTX_set_tlsext_servername_arg(ctx, cbinfo); +#endif /* Set up the RSA callback */ @@ -407,6 +564,9 @@ SSL_CTX_set_tmp_rsa_callback(ctx, rsa_callback); SSL_CTX_set_timeout(ctx, ssl_session_timeout); DEBUG(D_tls) debug_printf("Initialized TLS\n"); + +static_cbinfo = cbinfo; + return OK; } @@ -428,9 +588,11 @@ static void construct_cipher_name(SSL *ssl) { static uschar cipherbuf[256]; -SSL_CIPHER *c; +/* With OpenSSL 1.0.0a, this 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 SSL_CIPHER *c; uschar *ver; -int bits; switch (ssl->session->ssl_version) { @@ -446,15 +608,27 @@ switch (ssl->session->ssl_version) ver = US"TLSv1"; break; +#ifdef TLS1_1_VERSION + case TLS1_1_VERSION: + ver = US"TLSv1.1"; + break; +#endif + +#ifdef TLS1_2_VERSION + case TLS1_2_VERSION: + ver = US"TLSv1.2"; + break; +#endif + default: ver = US"UNKNOWN"; } -c = SSL_get_current_cipher(ssl); -SSL_CIPHER_get_bits(c, &bits); +c = (const SSL_CIPHER *) SSL_get_current_cipher(ssl); +SSL_CIPHER_get_bits(c, &tls_bits); string_format(cipherbuf, sizeof(cipherbuf), "%s:%s:%u", ver, - SSL_CIPHER_get_name(c), bits); + SSL_CIPHER_get_name(c), tls_bits); tls_cipher = cipherbuf; DEBUG(D_tls) debug_printf("Cipher: %s\n", cipherbuf); @@ -471,6 +645,7 @@ DEBUG(D_tls) debug_printf("Cipher: %s\n", cipherbuf); /* Called by both client and server startup Arguments: + sctx SSL_CTX* to initialise certs certs file or NULL crl CRL file or NULL host NULL in a server; the remote host in a client @@ -481,7 +656,7 @@ Returns: OK/DEFER/FAIL */ static int -setup_certs(uschar *certs, uschar *crl, host_item *host, BOOL optional) +setup_certs(SSL_CTX *sctx, uschar *certs, uschar *crl, host_item *host, BOOL optional) { uschar *expcerts, *expcrl; @@ -491,8 +666,8 @@ if (!expand_check(certs, US"tls_verify_certificates", &expcerts)) if (expcerts != NULL) { struct stat statbuf; - if (!SSL_CTX_set_default_verify_paths(ctx)) - return tls_error(US"SSL_CTX_set_default_verify_paths", host); + if (!SSL_CTX_set_default_verify_paths(sctx)) + return tls_error(US"SSL_CTX_set_default_verify_paths", host, NULL); if (Ustat(expcerts, &statbuf) < 0) { @@ -514,12 +689,12 @@ if (expcerts != NULL) says no certificate was supplied.) But this is better. */ if ((file == NULL || statbuf.st_size > 0) && - !SSL_CTX_load_verify_locations(ctx, CS file, CS dir)) - return tls_error(US"SSL_CTX_load_verify_locations", host); + !SSL_CTX_load_verify_locations(sctx, CS file, CS dir)) + return tls_error(US"SSL_CTX_load_verify_locations", host, NULL); if (file != NULL) { - SSL_CTX_set_client_CA_list(ctx, SSL_load_client_CA_file(CS file)); + SSL_CTX_set_client_CA_list(sctx, SSL_load_client_CA_file(CS file)); } } @@ -551,7 +726,7 @@ if (expcerts != NULL) { /* is it a file or directory? */ uschar *file, *dir; - X509_STORE *cvstore = SSL_CTX_get_cert_store(ctx); + X509_STORE *cvstore = SSL_CTX_get_cert_store(sctx); if ((statbufcrl.st_mode & S_IFMT) == S_IFDIR) { file = NULL; @@ -565,7 +740,7 @@ if (expcerts != NULL) DEBUG(D_tls) debug_printf("SSL CRL value is a file %s\n", file); } if (X509_STORE_load_locations(cvstore, CS file, CS dir) == 0) - return tls_error(US"X509_STORE_load_locations", host); + return tls_error(US"X509_STORE_load_locations", host, NULL); /* setting the flags to check against the complete crl chain */ @@ -578,7 +753,7 @@ if (expcerts != NULL) /* If verification is optional, don't fail if no certificate */ - SSL_CTX_set_verify(ctx, + SSL_CTX_set_verify(sctx, SSL_VERIFY_PEER | (optional? 0 : SSL_VERIFY_FAIL_IF_NO_PEER_CERT), verify_callback); } @@ -616,14 +791,13 @@ tls_server_start(uschar *require_ciphers, uschar *require_mac, { int rc; uschar *expciphers; +tls_ext_ctx_cb *cbinfo; /* Check for previous activation */ if (tls_active >= 0) { - log_write(0, LOG_MAIN, "STARTTLS received in already encrypted " - "connection from %s", - (sender_fullhost != NULL)? sender_fullhost : US"local process"); + tls_error(US"STARTTLS received after TLS started", NULL, US""); smtp_printf("554 Already in TLS\r\n"); return FAIL; } @@ -633,6 +807,7 @@ the error. */ rc = tls_init(NULL, tls_dhparam, tls_certificate, tls_privatekey, NULL); if (rc != OK) return rc; +cbinfo = static_cbinfo; if (!expand_check(require_ciphers, US"tls_require_ciphers", &expciphers)) return FAIL; @@ -647,7 +822,8 @@ if (expciphers != NULL) while (*s != 0) { if (*s == '_') *s = '-'; s++; } DEBUG(D_tls) debug_printf("required ciphers: %s\n", expciphers); if (!SSL_CTX_set_cipher_list(ctx, CS expciphers)) - return tls_error(US"SSL_CTX_set_cipher_list", NULL); + return tls_error(US"SSL_CTX_set_cipher_list", NULL, NULL); + cbinfo->server_cipher_list = expciphers; } /* If this is a host for which certificate verification is mandatory or @@ -658,21 +834,33 @@ verify_callback_called = FALSE; if (verify_check_host(&tls_verify_hosts) == OK) { - rc = setup_certs(tls_verify_certificates, tls_crl, NULL, FALSE); + rc = setup_certs(ctx, tls_verify_certificates, tls_crl, NULL, FALSE); if (rc != OK) return rc; verify_optional = FALSE; } else if (verify_check_host(&tls_try_verify_hosts) == OK) { - rc = setup_certs(tls_verify_certificates, tls_crl, NULL, TRUE); + rc = setup_certs(ctx, tls_verify_certificates, tls_crl, NULL, TRUE); if (rc != OK) return rc; verify_optional = TRUE; } /* Prepare for new connection */ -if ((ssl = SSL_new(ctx)) == NULL) return tls_error(US"SSL_new", NULL); -SSL_clear(ssl); +if ((ssl = SSL_new(ctx)) == NULL) return tls_error(US"SSL_new", NULL, NULL); + +/* Warning: we used to SSL_clear(ssl) here, it was removed. + * + * With the SSL_clear(), we get strange interoperability bugs with + * OpenSSL 1.0.1b and TLS1.1/1.2. It looks as though this may be a bug in + * OpenSSL itself, as a clear should not lead to inability to follow protocols. + * + * The SSL_clear() call is to let an existing SSL* be reused, typically after + * session shutdown. In this case, we have a brand new object and there's no + * obvious reason to immediately clear it. I'm guessing that this was + * originally added because of incomplete initialisation which the clear fixed, + * in some historic release. + */ /* Set context and tell client to go ahead, except in the case of TLS startup on connection, where outputting anything now upsets the clients and tends to @@ -703,11 +891,10 @@ alarm(0); if (rc <= 0) { - if (sigalrm_seen) Ustrcpy(ssl_errstring, "timed out"); - else ERR_error_string(ERR_get_error(), ssl_errstring); - log_write(0, LOG_MAIN, "TLS error on connection from %s (SSL_accept): %s", - (sender_fullhost != NULL)? sender_fullhost : US"local process", - ssl_errstring); + tls_error(US"SSL_accept", NULL, sigalrm_seen ? US"timed out" : NULL); + if (ERR_get_error() == 0) + log_write(0, LOG_MAIN, + "TLS client disconnected cleanly (rejected our certificate?)"); return FAIL; } @@ -734,6 +921,7 @@ receive_getc = tls_getc; receive_ungetc = tls_ungetc; receive_feof = tls_feof; receive_ferror = tls_ferror; +receive_smtp_buffered = tls_smtp_buffered; tls_active = fileno(smtp_out); return OK; @@ -801,13 +989,13 @@ if (expciphers != NULL) while (*s != 0) { if (*s == '_') *s = '-'; s++; } DEBUG(D_tls) debug_printf("required ciphers: %s\n", expciphers); if (!SSL_CTX_set_cipher_list(ctx, CS expciphers)) - return tls_error(US"SSL_CTX_set_cipher_list", host); + return tls_error(US"SSL_CTX_set_cipher_list", host, NULL); } -rc = setup_certs(verify_certs, crl, host, FALSE); +rc = setup_certs(ctx, verify_certs, crl, host, FALSE); if (rc != OK) return rc; -if ((ssl = SSL_new(ctx)) == NULL) return tls_error(US"SSL_new", host); +if ((ssl = SSL_new(ctx)) == NULL) return tls_error(US"SSL_new", host, NULL); SSL_set_session_id_context(ssl, sid_ctx, Ustrlen(sid_ctx)); SSL_set_fd(ssl, fd); SSL_set_connect_state(ssl); @@ -821,22 +1009,20 @@ rc = SSL_connect(ssl); alarm(0); if (rc <= 0) - { - if (sigalrm_seen) - { - log_write(0, LOG_MAIN, "TLS error on connection to %s [%s]: " - "SSL_connect timed out", host->name, host->address); - return FAIL; - } - else return tls_error(US"SSL_connect", host); - } + return tls_error(US"SSL_connect", host, sigalrm_seen ? US"timed out" : NULL); DEBUG(D_tls) debug_printf("SSL_connect succeeded\n"); +/* Beware anonymous ciphers which lead to server_cert being NULL */ server_cert = SSL_get_peer_certificate (ssl); -tls_peerdn = US X509_NAME_oneline(X509_get_subject_name(server_cert), - CS txt, sizeof(txt)); -tls_peerdn = txt; +if (server_cert) + { + tls_peerdn = US X509_NAME_oneline(X509_get_subject_name(server_cert), + CS txt, sizeof(txt)); + tls_peerdn = txt; + } +else + tls_peerdn = NULL; construct_cipher_name(ssl); /* Sets tls_cipher */ @@ -867,8 +1053,8 @@ if (ssl_xfer_buffer_lwm >= ssl_xfer_buffer_hwm) int error; int inbytes; - DEBUG(D_tls) debug_printf("Calling SSL_read(%lx, %lx, %u)\n", (long)ssl, - (long)ssl_xfer_buffer, ssl_xfer_buffer_size); + DEBUG(D_tls) debug_printf("Calling SSL_read(%p, %p, %u)\n", ssl, + ssl_xfer_buffer, ssl_xfer_buffer_size); if (smtp_receive_timeout > 0) alarm(smtp_receive_timeout); inbytes = SSL_read(ssl, CS ssl_xfer_buffer, ssl_xfer_buffer_size); @@ -887,6 +1073,7 @@ if (ssl_xfer_buffer_lwm >= ssl_xfer_buffer_hwm) receive_ungetc = smtp_ungetc; receive_feof = smtp_feof; receive_ferror = smtp_ferror; + receive_smtp_buffered = smtp_buffered; SSL_free(ssl); ssl = NULL; @@ -899,6 +1086,14 @@ if (ssl_xfer_buffer_lwm >= ssl_xfer_buffer_hwm) /* Handle genuine errors */ + else if (error == SSL_ERROR_SSL) + { + ERR_error_string(ERR_get_error(), ssl_errstring); + log_write(0, LOG_MAIN, "TLS error (SSL_read): %s", ssl_errstring); + ssl_xfer_error = 1; + return EOF; + } + else if (error != SSL_ERROR_NONE) { DEBUG(D_tls) debug_printf("Got SSL error %d\n", error); @@ -906,6 +1101,9 @@ if (ssl_xfer_buffer_lwm >= ssl_xfer_buffer_hwm) return EOF; } +#ifndef DISABLE_DKIM + dkim_exim_verify_feed(ssl_xfer_buffer, inbytes); +#endif ssl_xfer_buffer_hwm = inbytes; ssl_xfer_buffer_lwm = 0; } @@ -936,8 +1134,8 @@ tls_read(uschar *buff, size_t len) int inbytes; int error; -DEBUG(D_tls) debug_printf("Calling SSL_read(%lx, %lx, %u)\n", (long)ssl, - (long)buff, (unsigned int)len); +DEBUG(D_tls) debug_printf("Calling SSL_read(%p, %p, %u)\n", ssl, + buff, (unsigned int)len); inbytes = SSL_read(ssl, CS buff, len); error = SSL_get_error(ssl, inbytes); @@ -979,10 +1177,10 @@ int outbytes; int error; int left = len; -DEBUG(D_tls) debug_printf("tls_do_write(%lx, %d)\n", (long)buff, left); +DEBUG(D_tls) debug_printf("tls_do_write(%p, %d)\n", buff, left); while (left > 0) { - DEBUG(D_tls) debug_printf("SSL_write(SSL, %lx, %d)\n", (long)buff, left); + DEBUG(D_tls) debug_printf("SSL_write(SSL, %p, %d)\n", buff, left); outbytes = SSL_write(ssl, CS buff, left); error = SSL_get_error(ssl, outbytes); DEBUG(D_tls) debug_printf("outbytes=%d error=%d\n", outbytes, error); @@ -1041,4 +1239,306 @@ ssl = NULL; tls_active = -1; } + + + +/************************************************* +* Report the library versions. * +*************************************************/ + +/* There have historically been some issues with binary compatibility in +OpenSSL libraries; if Exim (like many other applications) is built against +one version of OpenSSL but the run-time linker picks up another version, +it can result in serious failures, including crashing with a SIGSEGV. So +report the version found by the compiler and the run-time version. + +Arguments: a FILE* to print the results to +Returns: nothing +*/ + +void +tls_version_report(FILE *f) +{ +fprintf(f, "Library version: OpenSSL: Compile: %s\n" + " Runtime: %s\n", + OPENSSL_VERSION_TEXT, + SSLeay_version(SSLEAY_VERSION)); +} + + + + +/************************************************* +* Pseudo-random number generation * +*************************************************/ + +/* Pseudo-random number generation. The result is not expected to be +cryptographically strong but not so weak that someone will shoot themselves +in the foot using it as a nonce in input in some email header scheme or +whatever weirdness they'll twist this into. The result should handle fork() +and avoid repeating sequences. OpenSSL handles that for us. + +Arguments: + max range maximum +Returns a random number in range [0, max-1] +*/ + +int +pseudo_random_number(int max) +{ +unsigned int r; +int i, needed_len; +uschar *p; +uschar smallbuf[sizeof(r)]; + +if (max <= 1) + return 0; + +/* OpenSSL auto-seeds from /dev/random, etc, but this a double-check. */ +if (!RAND_status()) + { + randstuff r; + gettimeofday(&r.tv, NULL); + r.p = getpid(); + + RAND_seed((uschar *)(&r), sizeof(r)); + } +/* We're after pseudo-random, not random; if we still don't have enough data +in the internal PRNG then our options are limited. We could sleep and hope +for entropy to come along (prayer technique) but if the system is so depleted +in the first place then something is likely to just keep taking it. Instead, +we'll just take whatever little bit of pseudo-random we can still manage to +get. */ + +needed_len = sizeof(r); +/* Don't take 8 times more entropy than needed if int is 8 octets and we were +asked for a number less than 10. */ +for (r = max, i = 0; r; ++i) + r >>= 1; +i = (i + 7) / 8; +if (i < needed_len) + needed_len = i; + +/* We do not care if crypto-strong */ +(void) RAND_pseudo_bytes(smallbuf, needed_len); +r = 0; +for (p = smallbuf; needed_len; --needed_len, ++p) + { + r *= 256; + r += *p; + } + +/* We don't particularly care about weighted results; if someone wants +smooth distribution and cares enough then they should submit a patch then. */ +return r % max; +} + + + + +/************************************************* +* OpenSSL option parse * +*************************************************/ + +/* Parse one option for tls_openssl_options_parse below + +Arguments: + name one option name + value place to store a value for it +Returns success or failure in parsing +*/ + +struct exim_openssl_option { + uschar *name; + long value; +}; +/* We could use a macro to expand, but we need the ifdef and not all the +options document which version they were introduced in. Policylet: include +all options unless explicitly for DTLS, let the administrator choose which +to apply. + +This list is current as of: + ==> 1.0.1b <== */ +static struct exim_openssl_option exim_openssl_options[] = { +/* KEEP SORTED ALPHABETICALLY! */ +#ifdef SSL_OP_ALL + { US"all", SSL_OP_ALL }, +#endif +#ifdef SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION + { US"allow_unsafe_legacy_renegotiation", SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION }, +#endif +#ifdef SSL_OP_CIPHER_SERVER_PREFERENCE + { US"cipher_server_preference", SSL_OP_CIPHER_SERVER_PREFERENCE }, +#endif +#ifdef SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS + { US"dont_insert_empty_fragments", SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS }, +#endif +#ifdef SSL_OP_EPHEMERAL_RSA + { US"ephemeral_rsa", SSL_OP_EPHEMERAL_RSA }, +#endif +#ifdef SSL_OP_LEGACY_SERVER_CONNECT + { US"legacy_server_connect", SSL_OP_LEGACY_SERVER_CONNECT }, +#endif +#ifdef SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER + { US"microsoft_big_sslv3_buffer", SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER }, +#endif +#ifdef SSL_OP_MICROSOFT_SESS_ID_BUG + { US"microsoft_sess_id_bug", SSL_OP_MICROSOFT_SESS_ID_BUG }, +#endif +#ifdef SSL_OP_MSIE_SSLV2_RSA_PADDING + { US"msie_sslv2_rsa_padding", SSL_OP_MSIE_SSLV2_RSA_PADDING }, +#endif +#ifdef SSL_OP_NETSCAPE_CHALLENGE_BUG + { US"netscape_challenge_bug", SSL_OP_NETSCAPE_CHALLENGE_BUG }, +#endif +#ifdef SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG + { US"netscape_reuse_cipher_change_bug", SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG }, +#endif +#ifdef SSL_OP_NO_COMPRESSION + { US"no_compression", SSL_OP_NO_COMPRESSION }, +#endif +#ifdef SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION + { US"no_session_resumption_on_renegotiation", SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION }, +#endif +#ifdef SSL_OP_NO_SSLv2 + { US"no_sslv2", SSL_OP_NO_SSLv2 }, +#endif +#ifdef SSL_OP_NO_SSLv3 + { US"no_sslv3", SSL_OP_NO_SSLv3 }, +#endif +#ifdef SSL_OP_NO_TICKET + { US"no_ticket", SSL_OP_NO_TICKET }, +#endif +#ifdef SSL_OP_NO_TLSv1 + { US"no_tlsv1", SSL_OP_NO_TLSv1 }, +#endif +#ifdef SSL_OP_NO_TLSv1_1 +#if SSL_OP_NO_TLSv1_1 == 0x00000400L + /* Error in chosen value in 1.0.1a; see first item in CHANGES for 1.0.1b */ +#warning OpenSSL 1.0.1a uses a bad value for SSL_OP_NO_TLSv1_1, ignoring +#else + { US"no_tlsv1_1", SSL_OP_NO_TLSv1_1 }, +#endif +#endif +#ifdef SSL_OP_NO_TLSv1_2 + { US"no_tlsv1_2", SSL_OP_NO_TLSv1_2 }, +#endif +#ifdef SSL_OP_SINGLE_DH_USE + { US"single_dh_use", SSL_OP_SINGLE_DH_USE }, +#endif +#ifdef SSL_OP_SINGLE_ECDH_USE + { US"single_ecdh_use", SSL_OP_SINGLE_ECDH_USE }, +#endif +#ifdef SSL_OP_SSLEAY_080_CLIENT_DH_BUG + { US"ssleay_080_client_dh_bug", SSL_OP_SSLEAY_080_CLIENT_DH_BUG }, +#endif +#ifdef SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG + { US"sslref2_reuse_cert_type_bug", SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG }, +#endif +#ifdef SSL_OP_TLS_BLOCK_PADDING_BUG + { US"tls_block_padding_bug", SSL_OP_TLS_BLOCK_PADDING_BUG }, +#endif +#ifdef SSL_OP_TLS_D5_BUG + { US"tls_d5_bug", SSL_OP_TLS_D5_BUG }, +#endif +#ifdef SSL_OP_TLS_ROLLBACK_BUG + { US"tls_rollback_bug", SSL_OP_TLS_ROLLBACK_BUG }, +#endif +}; +static int exim_openssl_options_size = + sizeof(exim_openssl_options)/sizeof(struct exim_openssl_option); + + +static BOOL +tls_openssl_one_option_parse(uschar *name, long *value) +{ +int first = 0; +int last = exim_openssl_options_size; +while (last > first) + { + int middle = (first + last)/2; + int c = Ustrcmp(name, exim_openssl_options[middle].name); + if (c == 0) + { + *value = exim_openssl_options[middle].value; + return TRUE; + } + else if (c > 0) + first = middle + 1; + else + last = middle; + } +return FALSE; +} + + + + +/************************************************* +* OpenSSL option parsing logic * +*************************************************/ + +/* OpenSSL has a number of compatibility options which an administrator might +reasonably wish to set. Interpret a list similarly to decode_bits(), so that +we look like log_selector. + +Arguments: + option_spec the administrator-supplied string of options + results ptr to long storage for the options bitmap +Returns success or failure +*/ + +BOOL +tls_openssl_options_parse(uschar *option_spec, long *results) +{ +long result, item; +uschar *s, *end; +uschar keep_c; +BOOL adding, item_parsed; + +result = 0L; +/* Prior to 4.78 we or'd in SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS; removed + * from default because it increases BEAST susceptibility. */ + +if (option_spec == NULL) + { + *results = result; + return TRUE; + } + +for (s=option_spec; *s != '\0'; /**/) + { + while (isspace(*s)) ++s; + if (*s == '\0') + break; + if (*s != '+' && *s != '-') + { + DEBUG(D_tls) debug_printf("malformed openssl option setting: " + "+ or - expected but found \"%s\"\n", s); + return FALSE; + } + adding = *s++ == '+'; + for (end = s; (*end != '\0') && !isspace(*end); ++end) /**/ ; + keep_c = *end; + *end = '\0'; + item_parsed = tls_openssl_one_option_parse(s, &item); + if (!item_parsed) + { + DEBUG(D_tls) debug_printf("openssl option setting unrecognised: \"%s\"\n", s); + return FALSE; + } + DEBUG(D_tls) debug_printf("openssl option, %s from %lx: %lx (%s)\n", + adding ? "adding" : "removing", result, item, s); + if (adding) + result |= item; + else + result &= ~item; + *end = keep_c; + s = end; + } + +*results = result; +return TRUE; +} + /* End of tls-openssl.c */