/*++
/* NAME
/*	tls
/* SUMMARY
/*	client-side TLS engine
/* SYNOPSIS
/*	#include <tls.h>
/*
/*	SSL_CTX	*tls_client_init(init_props)
/*	const tls_client_init_props *init_props;
/*
/*	TLScontext_t *tls_client_start(start_props)
/*	const tls_client_start_props *start_props;
/*
/*	void	tls_client_stop(client_ctx, stream, failure, TLScontext)
/*	SSL_CTX	*client_ctx;
/*	VSTREAM	*stream;
/*	int	failure;
/*	TLScontext_t *TLScontext;
/* DESCRIPTION
/*	This module is the interface between Postfix TLS clients,
/*	the OpenSSL library and the TLS entropy and cache manager.
/*
/*	The SMTP client will attempt to verify the server hostname
/*	against the names listed in the server certificate. When
/*	a hostname match is required, the verification fails
/*	on certificate verification or hostname mis-match errors.
/*	When no hostname match is required, hostname verification
/*	failures are logged but they do not affect the TLS handshake
/*	or the SMTP session.
/*
/*	The rules for peer name wild-card matching differ between
/*	RFC 2818 (HTTP over TLS) and RFC 2830 (LDAP over TLS), while
/*	RFC RFC3207 (SMTP over TLS) does not specify a rule at all.
/*	Postfix uses a restrictive match algorithm. One asterisk
/*	('*') is allowed as the left-most component of a wild-card
/*	certificate name; it matches the left-most component of
/*	the peer hostname.
/*
/*	Another area where RFCs aren't always explicit is the
/*	handling of dNSNames in peer certificates. RFC 3207 (SMTP
/*	over TLS) does not mention dNSNames. Postfix follows the
/*	strict rules in RFC 2818 (HTTP over TLS), section 3.1: The
/*	Subject Alternative Name/dNSName has precedence over
/*	CommonName.  If at least one dNSName is provided, Postfix
/*	verifies those against the peer hostname and ignores the
/*	CommonName, otherwise Postfix verifies the CommonName
/*	against the peer hostname.
/*
/*	tls_client_init() is called once when the SMTP client
/*	initializes.
/*	Certificate details are also decided during this phase,
/*	so that peer-specific behavior is not possible.
/*
/*	tls_client_start() activates the TLS session over an established
/*	stream. We expect that network buffers are flushed and
/*	the TLS handshake can begin immediately.
/*
/*	tls_client_stop() sends the "close notify" alert via
/*	SSL_shutdown() to the peer and resets all connection specific
/*	TLS data. As RFC2487 does not specify a separate shutdown, it
/*	is assumed that the underlying TCP connection is shut down
/*	immediately afterwards, so we don't care about additional data
/*	coming through the channel.
/*	If the failure flag is set, no SSL_shutdown() handshake is performed.
/*
/*	Once the TLS connection is initiated, information about the TLS
/*	state is available via the TLScontext structure:
/* .IP TLScontext->protocol
/*	the protocol name (SSLv2, SSLv3, TLSv1),
/* .IP TLScontext->cipher_name
/*	the cipher name (e.g. RC4/MD5),
/* .IP TLScontext->cipher_usebits
/*	the number of bits actually used (e.g. 40),
/* .IP TLScontext->cipher_algbits
/*	the number of bits the algorithm is based on (e.g. 128).
/* .PP
/*	The last two values may differ from each other when export-strength
/*	encryption is used.
/*
/*	The status of the peer certificate verification is available in
/*	TLScontext->peer_verified. It is set to 1 when the certificate could
/*	be verified.
/*	If the peer offered a certificate, part of the certificate data are
/*	available as:
/* .IP TLScontext->peer_CN
/*	Extracted CommonName of the peer, or zero-length string if the
/*	information could not be extracted.
/* .IP TLScontext->issuer_CN
/*	extracted CommonName of the issuer, or zero-length string if the
/*	information could not be extracted.
/* .PP
/*	Otherwise these fields are set to null pointers.
/* LICENSE
/* .ad
/* .fi
/*	This software is free. You can do with it whatever you want.
/*	The original author kindly requests that you acknowledge
/*	the use of his software.
/* AUTHOR(S)
/*	Originally written by:
/*	Lutz Jaenicke
/*	BTU Cottbus
/*	Allgemeine Elektrotechnik
/*	Universitaetsplatz 3-4
/*	D-03044 Cottbus, Germany
/*
/*	Updated by:
/*	Wietse Venema
/*	IBM T.J. Watson Research
/*	P.O. Box 704
/*	Yorktown Heights, NY 10598, USA
/*--*/

/* System library. */

#include <sys_defs.h>

#ifdef USE_TLS
#include <string.h>

#ifdef STRCASECMP_IN_STRINGS_H
#include <strings.h>
#endif

/* Utility library. */

#include <argv.h>
#include <mymalloc.h>
#include <vstring.h>
#include <vstream.h>
#include <stringops.h>
#include <msg.h>

/* Global library. */

#include <mail_params.h>

/* TLS library. */

#include <tls_mgr.h>
#define TLS_INTERNAL
#include <tls.h>

/* Application-specific. */

#define STR	vstring_str
#define LEN	VSTRING_LEN

/* load_clnt_session - load session from client cache (non-callback) */

static SSL_SESSION *load_clnt_session(TLScontext_t *TLScontext)
{
    const char *myname = "load_clnt_session";
    SSL_SESSION *session = 0;
    VSTRING *session_data = vstring_alloc(2048);

    /*
     * Prepare the query.
     */
    if (TLScontext->log_level >= 2)
	msg_info("looking for session %s in %s cache",
		 TLScontext->serverid, TLScontext->cache_type);

    /*
     * We only get here if the cache_type is not empty. This code is not
     * called unless caching is enabled and the cache_type is stored in the
     * server SSL context.
     */
    if (TLScontext->cache_type == 0)
	msg_panic("%s: null client session cache type in session lookup",
		  myname);

    /*
     * Look up and activate the SSL_SESSION object. Errors are non-fatal,
     * since caching is only an optimization.
     */
    if (tls_mgr_lookup(TLScontext->cache_type, TLScontext->serverid,
		       session_data) == TLS_MGR_STAT_OK) {
	session = tls_session_activate(STR(session_data), LEN(session_data));
	if (session) {
	    if (TLScontext->log_level >= 2)
		msg_info("reloaded session %s from %s cache",
			 TLScontext->serverid, TLScontext->cache_type);
	}
    }

    /*
     * Clean up.
     */
    vstring_free(session_data);

    return (session);
}

/* new_client_session_cb - name new session and save it to client cache */

static int new_client_session_cb(SSL *ssl, SSL_SESSION *session)
{
    const char *myname = "new_client_session_cb";
    TLScontext_t *TLScontext;
    VSTRING *session_data;

    /*
     * The cache name (if caching is enabled in tlsmgr(8)) and the cache ID
     * string for this session are stored in the TLScontext. It cannot be
     * null at this point.
     */
    if ((TLScontext = SSL_get_ex_data(ssl, TLScontext_index)) == 0)
	msg_panic("%s: null TLScontext in new session callback", myname);

    /*
     * We only get here if the cache_type is not empty. This callback is not
     * set unless caching is enabled and the cache_type is stored in the
     * server SSL context.
     */
    if (TLScontext->cache_type == 0)
	msg_panic("%s: null session cache type in new session callback",
		  myname);

    if (TLScontext->log_level >= 2)
	msg_info("save session %s to %s cache",
		 TLScontext->serverid, TLScontext->cache_type);

#if (OPENSSL_VERSION_NUMBER < 0x00906011L) || (OPENSSL_VERSION_NUMBER == 0x00907000L)

    /*
     * Ugly Hack: OpenSSL before 0.9.6a does not store the verify result in
     * sessions for the client side. We modify the session directly which is
     * version specific, but this bug is version specific, too.
     * 
     * READ: 0-09-06-01-1 = 0-9-6-a-beta1: all versions before beta1 have this
     * bug, it has been fixed during development of 0.9.6a. The development
     * version of 0.9.7 can have this bug, too. It has been fixed on
     * 2000/11/29.
     */
    session->verify_result = SSL_get_verify_result(TLScontext->con);
#endif

    /*
     * Passivate and save the session object. Errors are non-fatal, since
     * caching is only an optimization.
     */
    if ((session_data = tls_session_passivate(session)) != 0) {
	tls_mgr_update(TLScontext->cache_type, TLScontext->serverid,
		       STR(session_data), LEN(session_data));
	vstring_free(session_data);
    }

    /*
     * Clean up.
     */
    SSL_SESSION_free(session);			/* 200502 */

    return (1);
}

/* uncache_session - remove session from the external cache */

static void uncache_session(TLScontext_t *TLScontext)
{
    if (TLScontext->cache_type == 0 || TLScontext->serverid == 0)
	return;

    if (TLScontext->log_level >= 2)
	msg_info("remove session %s from client cache", TLScontext->serverid);

    tls_mgr_delete(TLScontext->cache_type, TLScontext->serverid);
}

/* tls_client_init - initialize client-side TLS engine */

SSL_CTX *tls_client_init(const tls_client_init_props *props)
{
    long    off = 0;
    SSL_CTX *client_ctx;
    int     cachable;

    /* See skeleton in OpenSSL apps/s_client.c. */

    if (props->log_level >= 2)
	msg_info("initializing the client-side TLS engine");

    /*
     * Detect mismatch between compile-time headers and run-time library.
     */
    tls_check_version();

    /*
     * Initialize the OpenSSL library by the book! To start with, we must
     * initialize the algorithms. We want cleartext error messages instead of
     * just error codes, so we load the error_strings.
     */
    SSL_load_error_strings();
    OpenSSL_add_ssl_algorithms();

    /*
     * Initialize the PRNG (Pseudo Random Number Generator) with some seed
     * from external and internal sources. Don't enable TLS without some real
     * entropy.
     */
    if (tls_ext_seed(var_tls_daemon_rand_bytes) < 0) {
	msg_warn("no entropy for TLS key generation: disabling TLS support");
	return (0);
    }
    tls_int_seed();

    /*
     * The SSL/TLS specifications require the client to send a message in the
     * oldest specification it understands with the highest level it
     * understands in the message. RFC2487 is only specified for TLSv1, but
     * we want to be as compatible as possible, so we will start off with a
     * SSLv2 greeting allowing the best we can offer: TLSv1. We can restrict
     * this with the options setting later, anyhow.
     */
    client_ctx = SSL_CTX_new(SSLv23_client_method());
    if (client_ctx == NULL) {
	tls_print_errors();
	return (0);
    }

    /*
     * Protocol selection is destination dependent, so we delay the protocol
     * selection options to the per-session SSL object.
     */
    off |= tls_bug_bits();
    SSL_CTX_set_options(client_ctx, off);

    /*
     * Set the call-back routine for verbose logging.
     */
    if (props->log_level >= 2)
	SSL_CTX_set_info_callback(client_ctx, tls_info_callback);

    /*
     * Load the CA public key certificates for both the client cert and for
     * the verification of server certificates. As provided by OpenSSL we
     * support two types of CA certificate handling: One possibility is to
     * add all CA certificates to one large CAfile, the other possibility is
     * a directory pointed to by CApath, containing separate files for each
     * CA with softlinks named after the hash values of the certificate. The
     * first alternative has the advantage that the file is opened and read
     * at startup time, so that you don't have the hassle to maintain another
     * copy of the CApath directory for chroot-jail.
     */
    if (tls_set_ca_certificate_info(client_ctx,
				    props->CAfile, props->CApath) < 0) {
	SSL_CTX_free(client_ctx);		/* 200411 */
	return (0);
    }

    /*
     * We do not need a client certificate, so the certificates are only
     * loaded (and checked) if supplied. A clever client would handle
     * multiple client certificates and decide based on the list of
     * acceptable CAs, sent by the server, which certificate to submit.
     * OpenSSL does however not do this and also has no call-back hooks to
     * easily implement it.
     * 
     * Load the client public key certificate and private key from file and
     * check whether the cert matches the key. We can use RSA certificates
     * ("cert") and DSA certificates ("dcert"), both can be made available at
     * the same time. The CA certificates for both are handled in the same
     * setup already finished. Which one is used depends on the cipher
     * negotiated (that is: the first cipher listed by the client which does
     * match the server). A client with RSA only (e.g. Netscape) will use the
     * RSA certificate only. A client with openssl-library will use RSA first
     * if not especially changed in the cipher setup.
     */
    if ((*props->cert_file != 0 || *props->dcert_file != 0)
	&& tls_set_my_certificate_key_info(client_ctx, props->cert_file,
					 props->key_file, props->dcert_file,
					   props->dkey_file) < 0) {
	SSL_CTX_free(client_ctx);		/* 200411 */
	return (0);
    }

    /*
     * According to the OpenSSL documentation, temporary RSA key is needed
     * export ciphers are in use. We have to provide one, so well, we just do
     * it.
     */
    SSL_CTX_set_tmp_rsa_callback(client_ctx, tls_tmp_rsa_cb);

    /*
     * Finally, the setup for the server certificate checking, done "by the
     * book".
     */
    SSL_CTX_set_verify(client_ctx, SSL_VERIFY_NONE,
		       tls_verify_certificate_callback);

    /*
     * Initialize the session cache.
     * 
     * Since the client does not search an internal cache, we simply disable it.
     * It is only useful for expiring old sessions, but we do that in the
     * tlsmgr(8).
     * 
     * This makes SSL_CTX_remove_session() not useful for flushing broken
     * sessions from the external cache, so we must delete them directly (not
     * via a callback).
     */

    if (TLSscache_index < 0)
	TLSscache_index =
	    SSL_CTX_get_ex_new_index(0, "TLScontext ex_data index",
				     NULL, NULL, NULL);

    if (tls_mgr_policy(props->cache_type, &cachable) != TLS_MGR_STAT_OK)
	cachable = 0;

    if (!SSL_CTX_set_ex_data(client_ctx, TLSscache_index,
			     (void *) props->cache_type)) {
	msg_warn("Session cache off: error saving cache type in SSL context.");
	tls_print_errors();
	cachable = 0;
    }

    /*
     * The external session cache is implemented by the tlsmgr(8) process.
     */
    if (cachable) {

	/*
	 * OpenSSL does not use callbacks to load sessions from a client
	 * cache, so we must invoke that function directly. Apparently,
	 * OpenSSL does not provide a way to pass session names from here to
	 * call-back routines that do session lookup.
	 * 
	 * OpenSSL can, however, automatically save newly created sessions for
	 * us by callback (we create the session name in the call-back
	 * function).
	 * 
	 * XXX gcc 2.95 can't compile #ifdef .. #endif in the expansion of
	 * SSL_SESS_CACHE_CLIENT | SSL_SESS_CACHE_NO_INTERNAL_STORE |
	 * SSL_SESS_CACHE_NO_AUTO_CLEAR.
	 */
#ifndef SSL_SESS_CACHE_NO_INTERNAL_STORE
#define SSL_SESS_CACHE_NO_INTERNAL_STORE 0
#endif

	SSL_CTX_set_session_cache_mode(client_ctx,
				       SSL_SESS_CACHE_CLIENT |
				       SSL_SESS_CACHE_NO_INTERNAL_STORE |
				       SSL_SESS_CACHE_NO_AUTO_CLEAR);
	SSL_CTX_sess_set_new_cb(client_ctx, new_client_session_cb);
    }

    /*
     * Create a global index so that we can attach TLScontext information to
     * SSL objects; this information is needed inside
     * tls_verify_certificate_callback().
     */
    if (TLScontext_index < 0)
	TLScontext_index = SSL_get_ex_new_index(0, "TLScontext ex_data index",
						NULL, NULL, NULL);
    return (client_ctx);
}

/* match_hostname -  match hostname against pattern */

static int match_hostname(const char *peerid, ARGV *cmatch_argv,
			          const char *nexthop, const char *hname)
{
    const char *pattern;
    const char *pattern_left;
    int     sub;
    int     i;
    int     idlen;
    int     patlen;

    /*
     * Match the peerid against each pattern until we find a match.
     */
    for (i = 0; i < cmatch_argv->argc; ++i) {
	sub = 0;
	if (!strcasecmp(cmatch_argv->argv[i], "nexthop"))
	    pattern = nexthop;
	else if (!strcasecmp(cmatch_argv->argv[i], "hostname"))
	    pattern = hname;
	else if (!strcasecmp(cmatch_argv->argv[i], "dot-nexthop")) {
	    pattern = nexthop;
	    sub = 1;
	} else {
	    pattern = cmatch_argv->argv[i];
	    if (*pattern == '.' && pattern[1] != '\0') {
		++pattern;
		sub = 1;
	    }
	}

	/*
	 * Sub-domain match: peerid is any sub-domain of pattern.
	 */
	if (sub) {
	    if ((idlen = strlen(peerid)) > (patlen = strlen(pattern)) + 1
		&& peerid[idlen - patlen - 1] == '.'
		&& !strcasecmp(peerid + (idlen - patlen), pattern))
		return (1);
	    else
		continue;
	}

	/*
	 * Exact match and initial "*" match. The initial "*" in a peerid
	 * matches exactly one hostname component, under the condition that
	 * the peerid contains multiple hostname components.
	 */
	if (!strcasecmp(peerid, pattern)
	    || (peerid[0] == '*' && peerid[1] == '.' && peerid[2] != 0
		&& (pattern_left = strchr(pattern, '.')) != 0
		&& strcasecmp(pattern_left + 1, peerid + 2) == 0))
	    return (1);
    }
    return (0);
}

/* verify_extract_peer - verify peer name and extract peer information */

static void verify_extract_peer(const char *nexthop, const char *hname,
			              const char *certmatch, X509 *peercert,
				        TLScontext_t *TLScontext)
{
    int     i;
    int     r;
    int     hostname_matched = 0;
    int     dNSName_found = 0;
    int     verify_peername;
    ARGV   *cmatch_argv = 0;

    STACK_OF(GENERAL_NAME) * gens;

    TLScontext->peer_verified =
	(SSL_get_verify_result(TLScontext->con) == X509_V_OK);

    verify_peername =
	(TLScontext->enforce_CN != 0 && TLScontext->peer_verified != 0);

    if (verify_peername) {
	cmatch_argv = argv_split(certmatch, "\t\n\r, :");

	/*
	 * Verify the dNSName(s) in the peer certificate against the
	 * peername.
	 * 
	 * XXX: The nexthop and host name may both be the same network address
	 * rather than a DNS name. In this case we really should be looking
	 * for GEN_IPADD entries, not GEN_DNS entries.
	 * 
	 * XXX: In ideal world the caller who used the address to build the
	 * connection would tell us that the nexthop is the connection
	 * address, but if that is not practical, we can parse the nexthop
	 * again here.
	 */
	gens = X509_get_ext_d2i(peercert, NID_subject_alt_name, 0, 0);
	if (gens) {
	    for (i = 0, r = sk_GENERAL_NAME_num(gens); i < r; ++i) {
		const GENERAL_NAME *gn = sk_GENERAL_NAME_value(gens, i);

		if (gn->type == GEN_DNS) {
		    dNSName_found++;
		    if ((hostname_matched =
		       match_hostname((char *) gn->d.ia5->data, cmatch_argv,
				      nexthop, hname)) != 0)
			break;
		}
	    }
	    sk_GENERAL_NAME_pop_free(gens, GENERAL_NAME_free);
	}
    }
    if (dNSName_found) {
	if (!hostname_matched)
	    msg_info("certificate peer name verification failed for "
		     "%s: %d dNSNames in certificate found, "
		     "but none match", hname, dNSName_found);
    }
    if ((TLScontext->peer_CN = tls_peer_CN(peercert)) == 0)
	TLScontext->peer_CN = mystrdup("");

    if ((TLScontext->issuer_CN = tls_issuer_CN(peercert)) == 0)
	TLScontext->issuer_CN = mystrdup("");

    if (!dNSName_found && verify_peername) {

	/*
	 * Verify the CommonName in the peer certificate against the
	 * peername.
	 */
	if (TLScontext->peer_CN[0] != '\0') {
	    hostname_matched = match_hostname(TLScontext->peer_CN, cmatch_argv,
					      nexthop, hname);
	    if (!hostname_matched)
		msg_info("certificate peer name verification failed "
			 "for nexthop=%s, host=%s: CommonName mis-match: %s",
			 nexthop, hname, TLScontext->peer_CN);
	}
    }
    if (cmatch_argv)
	cmatch_argv = argv_free(cmatch_argv);
    TLScontext->hostname_matched = hostname_matched;

    if (TLScontext->log_level >= 1) {
	if (TLScontext->peer_verified
	    && (!TLScontext->enforce_CN || TLScontext->hostname_matched))
	    msg_info("Verified: subject_CN=%s, issuer=%s",
		     TLScontext->peer_CN, TLScontext->issuer_CN);
	else
	    msg_info("Unverified: subject_CN=%s, issuer=%s",
		     TLScontext->peer_CN, TLScontext->issuer_CN);
    }
}

 /*
  * This is the actual startup routine for the connection. We expect that the
  * buffers are flushed and the "220 Ready to start TLS" was received by us,
  * so that we can immediately start the TLS handshake process.
  */
TLScontext_t *tls_client_start(const tls_client_start_props *props)
{
    int     sts;
    SSL_SESSION *session;
    SSL_CIPHER *cipher;
    X509   *peercert;
    TLScontext_t *TLScontext;

    if (props->log_level >= 1)
	msg_info("setting up TLS connection to %s", props->host);

    /*
     * Before we create an SSL, update the SSL_CTX cipherlist if necessary.
     */
    if (tls_set_cipher_list(props->ctx, props->cipherlist) == 0) {
	msg_warn("Invalid cipherlist \"%s\": aborting TLS session",
		 props->cipherlist);
	return (0);
    }

    /*
     * Allocate a new TLScontext for the new connection and get an SSL
     * structure. Add the location of TLScontext to the SSL to later retrieve
     * the information inside the tls_verify_certificate_callback().
     * 
     * If session caching was enabled when TLS was initialized, the cache type
     * is stored in the client SSL context.
     */
    TLScontext = tls_alloc_context(props->log_level, props->host);
    TLScontext->serverid = mystrdup(props->serverid);
    TLScontext->cache_type = SSL_CTX_get_ex_data(props->ctx, TLSscache_index);

    if ((TLScontext->con = (SSL *) SSL_new(props->ctx)) == NULL) {
	msg_warn("Could not allocate 'TLScontext->con' with SSL_new()");
	tls_print_errors();
	tls_free_context(TLScontext);
	return (0);
    }
    if (!SSL_set_ex_data(TLScontext->con, TLScontext_index, TLScontext)) {
	msg_warn("Could not set application data for 'TLScontext->con'");
	tls_print_errors();
	tls_free_context(TLScontext);
	return (0);
    }

    /*
     * Set the verification parameters to be checked in
     * tls_verify_certificate_callback().
     */
    if (props->tls_level >= TLS_LEV_VERIFY) {
	TLScontext->enforce_verify_errors = 1;
	TLScontext->enforce_CN = 1;
	SSL_set_verify(TLScontext->con, SSL_VERIFY_PEER,
		       tls_verify_certificate_callback);
    } else {
	TLScontext->enforce_verify_errors = 0;
	TLScontext->enforce_CN = 0;
    }
    TLScontext->hostname_matched = 0;

    /*
     * The TLS connection is realized by a BIO_pair, so obtain the pair.
     * 
     * XXX There is no need to make internal_bio a member of the TLScontext
     * structure. It will be attached to TLScontext->con, and destroyed along
     * with it. The network_bio, however, needs to be freed explicitly.
     */
    if (!BIO_new_bio_pair(&TLScontext->internal_bio, TLS_BIO_BUFSIZE,
			  &TLScontext->network_bio, TLS_BIO_BUFSIZE)) {
	msg_warn("Could not obtain BIO_pair");
	tls_print_errors();
	tls_free_context(TLScontext);
	return (0);
    }
#define DISABLE_ALL (SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1)

    /*
     * Per session protocol selection for sessions with mandatory encryption
     * 
     * OpenSSL will ignore cached sessions that use the wrong protocol. So we do
     * not need to filter out cached sessions with the "wrong" protocol,
     * rather OpenSSL will simply negotiate a new session.
     * 
     * Still, we expect the caller to salt the session lookup key with the
     * protocol list, so that sessions found in the cache are always
     * acceptable.
     * 
     * Not enabling any protocols explicitly, enables all.
     */
    if (props->tls_level >= TLS_LEV_ENCRYPT
	&& props->protocols != 0 && props->protocols != TLS_ALL_PROTOCOLS) {
	long    disable = DISABLE_ALL;

	if (props->protocols & TLS_PROTOCOL_TLSv1)
	    disable &= ~SSL_OP_NO_TLSv1;
	if (props->protocols & TLS_PROTOCOL_SSLv3)
	    disable &= ~SSL_OP_NO_SSLv3;
	if (props->protocols & TLS_PROTOCOL_SSLv2)
	    disable &= ~SSL_OP_NO_SSLv2;

	SSL_set_options(TLScontext->con, disable);
    }

    /*
     * Try to load an existing session from the TLS session cache.
     * 
     * By the time a TLS client is negotiating ciphers it has already offered to
     * re-use a session, it is too late to renege on the offer. So we must
     * not attempt to re-use sessions whose ciphers are too weak. We expect
     * the caller to salt the session lookup key with the cipher list, so
     * that sessions found in the cache are always acceptable.
     * 
     * XXX To avoid memory leaks we must always call SSL_SESSION_free() after
     * calling SSL_set_session(), regardless of whether or not the session
     * will be reused.
     * 
     * XXX: We rely on the client including any non-default cipherlist in the
     * serverid cache lookup key so as to avoid fetching sessions with
     * incompatible ciphers. We could save the cipher name into the cache
     * together with the serialized session, and compare it against the
     * cipherlist here, but this is unlikely to be worth the trouble. A sane
     * administrator should use at most a handful of cipherlists, especially
     * when setting policy for domains served by a common MX host.
     */
    if (TLScontext->cache_type) {
	session = load_clnt_session(TLScontext);
	if (session) {
	    SSL_set_session(TLScontext->con, session);
	    SSL_SESSION_free(session);		/* 200411 */
#if (OPENSSL_VERSION_NUMBER < 0x00906011L) || (OPENSSL_VERSION_NUMBER == 0x00907000L)

	    /*
	     * Ugly Hack: OpenSSL before 0.9.6a does not store the verify
	     * result in sessions for the client side. We modify the session
	     * directly which is version specific, but this bug is version
	     * specific, too.
	     * 
	     * READ: 0-09-06-01-1 = 0-9-6-a-beta1: all versions before beta1
	     * have this bug, it has been fixed during development of 0.9.6a.
	     * The development version of 0.9.7 can have this bug, too. It
	     * has been fixed on 2000/11/29.
	     */
	    SSL_set_verify_result(TLScontext->con, session->verify_result);
#endif

	}
    }

    /*
     * Before really starting anything, try to seed the PRNG a little bit
     * more.
     */
    tls_int_seed();
    (void) tls_ext_seed(var_tls_daemon_rand_bytes);

    /*
     * Initialize the SSL connection to connect state. This should not be
     * necessary anymore since 0.9.3, but the call is still in the library
     * and maintaining compatibility never hurts.
     */
    SSL_set_connect_state(TLScontext->con);

    /*
     * Connect the SSL connection with the Postfix side of the BIO-pair for
     * reading and writing.
     */
    SSL_set_bio(TLScontext->con, TLScontext->internal_bio,
		TLScontext->internal_bio);

    /*
     * If the debug level selected is high enough, all of the data is dumped:
     * 3 will dump the SSL negotiation, 4 will dump everything.
     * 
     * We do have an SSL_set_fd() and now suddenly a BIO_ routine is called?
     * Well there is a BIO below the SSL routines that is automatically
     * created for us, so we can use it for debugging purposes.
     */
    if (props->log_level >= 3)
	BIO_set_callback(SSL_get_rbio(TLScontext->con), tls_bio_dump_cb);

    /*
     * Start TLS negotiations. This process is a black box that invokes our
     * call-backs for certificate verification.
     * 
     * Error handling: If the SSL handhake fails, we print out an error message
     * and remove all TLS state concerning this session.
     */
    sts = tls_bio_connect(vstream_fileno(props->stream), props->timeout,
			  TLScontext);
    if (sts <= 0) {
	msg_info("SSL_connect error to %s: %d", props->host, sts);
	tls_print_errors();
	uncache_session(TLScontext);
	tls_free_context(TLScontext);
	return (0);
    }

    /*
     * The TLS engine is active. Switch to the tls_timed_read/write()
     * functions and make the TLScontext available to those functions.
     */
    tls_stream_start(props->stream, TLScontext);

    /* Only log_level==4 dumps everything */
    if (props->log_level < 4)
	BIO_set_callback(SSL_get_rbio(TLScontext->con), 0);

    /*
     * The caller may want to know if this session was reused or if a new
     * session was negotiated.
     */
    TLScontext->session_reused = SSL_session_reused(TLScontext->con);
    if (props->log_level >= 2 && TLScontext->session_reused)
	msg_info("Reusing old session");

    /*
     * Do peername verification if requested and extract useful information
     * from the certificate for later use.
     */
    if ((peercert = SSL_get_peer_certificate(TLScontext->con)) != 0) {
	verify_extract_peer(props->nexthop, props->host, props->certmatch,
			    peercert, TLScontext);
	X509_free(peercert);
    }
    if (TLScontext->enforce_CN && !TLScontext->hostname_matched) {
	msg_info("Server certificate could not be verified for %s:"
		 " hostname mismatch", props->host);
	tls_client_stop(props->ctx, props->stream, props->timeout, 0,
			TLScontext);
	return (0);
    }

    /*
     * Finally, collect information about protocol and cipher for logging
     */
    TLScontext->protocol = SSL_get_version(TLScontext->con);
    cipher = SSL_get_current_cipher(TLScontext->con);
    TLScontext->cipher_name = SSL_CIPHER_get_name(cipher);
    TLScontext->cipher_usebits = SSL_CIPHER_get_bits(cipher,
					     &(TLScontext->cipher_algbits));

    if (props->log_level >= 1)
	msg_info("TLS connection established to %s: %s with cipher %s"
		 " (%d/%d bits)", props->host,
		 TLScontext->protocol, TLScontext->cipher_name,
		 TLScontext->cipher_usebits, TLScontext->cipher_algbits);

    tls_int_seed();

    return (TLScontext);
}

#endif					/* USE_TLS */
