-/* $Cambridge: exim/src/src/tls-openssl.c,v 1.2 2004/11/25 15:29:37 ph10 Exp $ */
+/* $Cambridge: exim/src/src/tls-openssl.c,v 1.28 2010/06/12 17:56:32 jetmore Exp $ */
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2004 */
+/* 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
/* 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 */
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;
}
}
tls_peerdn = txt;
}
-
-debug_printf("+++verify_callback_called=%d\n", verify_callback_called);
-
if (!verify_callback_called) tls_certificate_verified = TRUE;
verify_callback_called = TRUE;
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;
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
*/
static int
-tls_init(host_item *host, uschar *dhparam, uschar *certificate, uschar *privatekey,
- address_item *addr)
+tls_init(host_item *host, uschar *dhparam, uschar *certificate,
+ uschar *privatekey, address_item *addr)
{
+long init_options;
+BOOL okay;
+
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
if (!RAND_status())
{
randstuff r;
- r.t = time(NULL);
+ gettimeofday(&r.tv, NULL);
r.p = getpid();
RAND_seed((uschar *)(&r), sizeof(r));
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 */
+/* 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 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*/
+No OpenSSL version number checks: the options we accept depend upon the
+availability of the option value macros from OpenSSL. */
-if (!(SSL_CTX_set_options(ctx, SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS)))
- return tls_error(US"SSL_CTX_set_option", host);
-#endif
+okay = tls_openssl_options_parse(openssl_options, &init_options);
+if (!okay)
+ return tls_error(US"openssl_options parsing failed", host, NULL);
+
+if (init_options)
+ {
+ 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");
/* Initialize with DH parameters if supplied */
-if (!init_dh(dhparam)) return DEFER;
+if (!init_dh(dhparam, host)) return DEFER;
/* Set up certificate and key */
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);
+ "SSL_CTX_use_certificate_chain_file file=%s", expanded), host, NULL);
}
if (privatekey != NULL &&
!expand_check(privatekey, US"tls_privatekey", &expanded))
return DEFER;
- if (expanded != NULL)
+ /* 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(ctx, CS expanded, SSL_FILETYPE_PEM))
return tls_error(string_sprintf(
- "SSL_CTX_use_PrivateKey_file file=%s", expanded), host);
+ "SSL_CTX_use_PrivateKey_file file=%s", expanded), host, NULL);
}
}
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;
ver = US"UNKNOWN";
}
-c = SSL_get_current_cipher(ssl);
+c = (const SSL_CIPHER *) SSL_get_current_cipher(ssl);
SSL_CIPHER_get_bits(c, &bits);
string_format(cipherbuf, sizeof(cipherbuf), "%s:%s:%u", ver,
{
struct stat statbuf;
if (!SSL_CTX_set_default_verify_paths(ctx))
- return tls_error(US"SSL_CTX_set_default_verify_paths", host);
+ return tls_error(US"SSL_CTX_set_default_verify_paths", host, NULL);
if (Ustat(expcerts, &statbuf) < 0)
{
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);
+ return tls_error(US"SSL_CTX_load_verify_locations", host, NULL);
if (file != NULL)
{
#if OPENSSL_VERSION_NUMBER > 0x00907000L
+ /* This bit of code is now the version supplied by Lars Mainka. (I have
+ * merely reformatted it into the Exim code style.)
+
+ * "From here I changed the code to add support for multiple crl's
+ * in pem format in one file or to support hashed directory entries in
+ * pem format instead of a file. This method now uses the library function
+ * X509_STORE_load_locations to add the CRL location to the SSL context.
+ * OpenSSL will then handle the verify against CA certs and CRLs by
+ * itself in the verify callback." */
+
if (!expand_check(crl, US"tls_crl", &expcrl)) return DEFER;
if (expcrl != NULL && *expcrl != 0)
{
- BIO *crl_bio;
- X509_CRL *crl_x509;
- X509_STORE *cvstore;
-
- cvstore = SSL_CTX_get_cert_store(ctx); /* cert validation store */
-
- crl_bio = BIO_new(BIO_s_file_internal());
- if (crl_bio != NULL)
+ struct stat statbufcrl;
+ if (Ustat(expcrl, &statbufcrl) < 0)
+ {
+ log_write(0, LOG_MAIN|LOG_PANIC,
+ "failed to stat %s for certificates revocation lists", expcrl);
+ return DEFER;
+ }
+ else
{
- if (BIO_read_filename(crl_bio, expcrl))
+ /* is it a file or directory? */
+ uschar *file, *dir;
+ X509_STORE *cvstore = SSL_CTX_get_cert_store(ctx);
+ if ((statbufcrl.st_mode & S_IFMT) == S_IFDIR)
{
- crl_x509 = PEM_read_bio_X509_CRL(crl_bio, NULL, NULL, NULL);
- BIO_free(crl_bio);
- X509_STORE_add_crl(cvstore, crl_x509);
- X509_CRL_free(crl_x509);
- X509_STORE_set_flags(cvstore,
- X509_V_FLAG_CRL_CHECK|X509_V_FLAG_CRL_CHECK_ALL);
+ file = NULL;
+ dir = expcrl;
+ DEBUG(D_tls) debug_printf("SSL CRL value is a directory %s\n", dir);
}
else
{
- BIO_free(crl_bio);
- return tls_error(US"BIO_read_filename", host);
+ file = expcrl;
+ dir = 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, NULL);
+
+ /* setting the flags to check against the complete crl chain */
+
+ X509_STORE_set_flags(cvstore,
+ X509_V_FLAG_CRL_CHECK|X509_V_FLAG_CRL_CHECK_ALL);
}
- else return tls_error(US"BIO_new", host);
}
#endif /* OPENSSL_VERSION_NUMBER > 0x00907000L */
Arguments:
require_ciphers allowed ciphers
+ ------------------------------------------------------
+ require_mac list of allowed MACs ) Not used
+ require_kx list of allowed key_exchange methods ) for
+ require_proto list of allowed protocols ) OpenSSL
+ ------------------------------------------------------
Returns: OK on success
DEFER for errors before the start of the negotiation
*/
int
-tls_server_start(uschar *require_ciphers)
+tls_server_start(uschar *require_ciphers, uschar *require_mac,
+ uschar *require_kx, uschar *require_proto)
{
int rc;
uschar *expciphers;
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;
}
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);
}
/* If this is a host for which certificate verification is mandatory or
/* Prepare for new connection */
-if ((ssl = SSL_new(ctx)) == NULL) return tls_error(US"SSL_new", NULL);
+if ((ssl = SSL_new(ctx)) == NULL) return tls_error(US"SSL_new", NULL, NULL);
SSL_clear(ssl);
/* Set context and tell client to go ahead, except in the case of TLS startup
/* Now negotiate the TLS session. We put our own timer on it, since it seems
that the OpenSSL library doesn't. */
-SSL_set_fd(ssl, fileno(smtp_out));
+SSL_set_wfd(ssl, fileno(smtp_out));
+SSL_set_rfd(ssl, fileno(smtp_in));
SSL_set_accept_state(ssl);
DEBUG(D_tls) debug_printf("Calling SSL_accept\n");
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;
}
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;
Argument:
fd the fd of the connection
host connected host (for messages)
+ addr the first address
dhparam DH parameter file
certificate certificate file
privatekey private key file
verify_certs file for certificate verify
crl file containing CRL
require_ciphers list of allowed ciphers
+ ------------------------------------------------------
+ require_mac list of allowed MACs ) Not used
+ require_kx list of allowed key_exchange methods ) for
+ require_proto list of allowed protocols ) OpenSSL
+ ------------------------------------------------------
+ timeout startup timeout
Returns: OK on success
FAIL otherwise - note that tls_error() will not give DEFER
int
tls_client_start(int fd, host_item *host, address_item *addr, uschar *dhparam,
uschar *certificate, uschar *privatekey, uschar *verify_certs, uschar *crl,
- uschar *require_ciphers, int timeout)
+ uschar *require_ciphers, uschar *require_mac, uschar *require_kx,
+ uschar *require_proto, int timeout)
{
static uschar txt[256];
uschar *expciphers;
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);
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);
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 */
receive_ungetc = smtp_ungetc;
receive_feof = smtp_feof;
receive_ferror = smtp_ferror;
+ receive_smtp_buffered = smtp_buffered;
SSL_free(ssl);
ssl = NULL;
/* 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);
ssl_xfer_error = 1;
return EOF;
}
-
+#ifndef DISABLE_DKIM
+ dkim_exim_verify_feed(ssl_xfer_buffer, inbytes);
+#endif
ssl_xfer_buffer_hwm = inbytes;
ssl_xfer_buffer_lwm = 0;
}
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, "OpenSSL compile-time version: %s\n", OPENSSL_VERSION_TEXT);
+fprintf(f, "OpenSSL runtime version: %s\n", 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:
+ ==> 0.9.8n <== */
+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_SESSION_RESUMPTION_ON_RENEGOTIATION
+ { US"no_session_resumption_on_renegotiation", SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION },
+#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;
+
+/* We grandfather in as default the one option which we used to set always. */
+#ifdef SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS
+result = SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS;
+#else
+result = 0L;
+#endif
+
+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\"", 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\"", 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 */