/*
 * Copyright (c) 2003-2006 Sendmail, Inc. and its suppliers.
 *	All rights reserved.
 *
 * By using this file, you agree to the terms and conditions set
 * forth in the LICENSE file which can be found at the top level of
 * the sendmail distribution.
 *
 */

/*
**  This code contains parts of Lutz Jaenicke's TLS patch for postfix.
**  http://www.aet.tu-cottbus.de/personen/jaenicke/pfixtls/
*/

#include "sm/generic.h"
SM_RCSID("@(#)$Id: smtls.c,v 1.44 2007/05/04 03:14:21 ca Exp $")
#include "sm/error.h"
#include "sm/assert.h"
#include "sm/io.h"
#define TLSL_LOG_DEFINES 1
#include "sm/tls.h"

#if MTA_USE_TLS

/*
use SSL extension data instead of passing tlsl_ctx around?
some callback functions don't have an explicit user context, e.g.,
tmp_rsa_key(), apps_ssl_info_cb().

check other programs how to do that.
*/

#if !TLS_NO_RSA
static RSA *rsa_tmp = NULL;	/* temporary RSA key */
static RSA *tmp_rsa_key(SSL *con, int _export, int k_eylength);
# define RSA_KEYLENGTH	512
#endif

#if !defined(OPENSSL_VERSION_NUMBER) || OPENSSL_VERSION_NUMBER < 0x00907000L
static int	tls_verify_cb(X509_STORE_CTX *);
# define CONST097
#else
static int	tls_verify_cb(X509_STORE_CTX *, void *);
# define CONST097 const
#endif

#define MAXNAME	256
#define bitset(bit, word)	(((bit) & (word)) != 0)

#if !NO_DH
static DH *get_dh512(void);

static uchar dh512_p[] =
{
	0xDA,0x58,0x3C,0x16,0xD9,0x85,0x22,0x89,0xD0,0xE4,0xAF,0x75,
	0x6F,0x4C,0xCA,0x92,0xDD,0x4B,0xE5,0x33,0xB8,0x04,0xFB,0x0F,
	0xED,0x94,0xEF,0x9C,0x8A,0x44,0x03,0xED,0x57,0x46,0x50,0xD3,
	0x69,0x99,0xDB,0x29,0xD7,0x76,0x27,0x6B,0xA2,0xD3,0xD4,0x12,
	0xE2,0x18,0xF4,0xDD,0x1E,0x08,0x4C,0xF6,0xD8,0x00,0x3E,0x7C,
	0x47,0x74,0xE8,0x33
};
static uchar dh512_g[] =
{
	0x02
};

static DH *
get_dh512(void)
{
	DH *dh = NULL;

	if ((dh = DH_new()) == NULL)
		return NULL;
	dh->p = BN_bin2bn(dh512_p, sizeof(dh512_p), NULL);
	dh->g = BN_bin2bn(dh512_g, sizeof(dh512_g), NULL);
	if ((dh->p == NULL) || (dh->g == NULL))
		return NULL;
	return dh;
}
#endif /* !NO_DH */

/*
**  TLSLOGERR -- log the errors from the TLS error stack
**
**	Parameters:
**		who -- server/client (for logging).
**
**	Returns:
**		none.
*/

void
tlslogerr(tlsl_ctx_P tlsl_ctx, char *who)
{
	ulong l;
	int line, flags;
	ulong es;
	char *file, *data;
	char buf[256];
# define CP (const char **)

	es = CRYPTO_thread_id();
	while ((l = ERR_get_error_line_data(CP &file, &line, CP &data, &flags))
		!= 0)
	{
		sm_log_write(tlsl_ctx->tlsl_lctx,
			TL_LCAT_INIT, TL_LMOD_CONF,
			SM_LOG_WARN, 12,
			"STARTTLS=%s: %lu:%s:%s:%d:%s", who, es,
			ERR_error_string(l, buf),
			file, line,
			bitset(ERR_TXT_STRING, flags) ? data : "");
	}
}

/*
**  SM_TLS_INIT_LIBRARY -- setup TLS library for global use
**
**	Parameters:
**		ptlsl_ctx -- (pointer to) TLS library context (output)
**
**	Returns:
**		succeeded?
*/

sm_ret_T
sm_tls_init_library(tlsl_ctx_P *ptlsl_ctx)
{
	sm_ret_T ret;
	tlsl_ctx_P tlsl_ctx;

	SM_REQUIRE(ptlsl_ctx != NULL);
	*ptlsl_ctx = NULL;
	tlsl_ctx = (tlsl_ctx_P) sm_zalloc(sizeof(*tlsl_ctx));
	if (tlsl_ctx == NULL)
		return sm_error_temp(SM_EM_TLS, ENOMEM);

	/* basic TLS initialization, ignore result for now */
	SSL_library_init();
	SSL_load_error_strings();
	ret = sm_log_create(NULL, &tlsl_ctx->tlsl_lctx, &tlsl_ctx->tlsl_lcfg);
	if (sm_is_err(ret))
		goto error;
	ret = sm_log_setfile(tlsl_ctx->tlsl_lctx, smioerr);
	if (sm_is_err(ret))
		goto error;
	ret = sm_log_setdebuglevel(tlsl_ctx->tlsl_lctx, 9);	/* XXX */
	if (sm_is_err(ret))
		goto error;

	*ptlsl_ctx = tlsl_ctx;
	return SM_SUCCESS;

  error:
	/* cleanup? */
	return ret;
}

/*
**  SET_TLS_LOG -- set TLS logging
**
**	Parameters:
**		tlsl_ctx -- TLS library context
**		fp -- file to use
**		fd -- fd to use
**		loglevel -- loglevel
**
**	Returns:
**		succeeded?
*/

sm_ret_T
sm_set_tls_log(tlsl_ctx_P tlsl_ctx, sm_file_T *fp, int fd, int loglevel)
{
	sm_ret_T ret;

	ret = sm_log_setfp_fd(tlsl_ctx->tlsl_lctx, fp, fd);
	if (sm_is_err(ret))
		goto error;
	ret = sm_log_setdebuglevel(tlsl_ctx->tlsl_lctx, loglevel);
	if (sm_is_err(ret))
		goto error;
	return SM_SUCCESS;

  error:
	return ret;
}

/*
**  status in initialization
**  these flags keep track of the status of the initialization
**  i.e., whether a file exists (_EX) and whether it can be used (_OK)
**  [due to permissions]
*/

#define TLS_S_NONE	0x00000000	/* none yet */
#define TLS_S_CERT_EX	0x00000001	/* cert file exists */
#define TLS_S_CERT_OK	0x00000002	/* cert file is ok */
#define TLS_S_KEY_EX	0x00000004	/* key file exists */
#define TLS_S_KEY_OK	0x00000008	/* key file is ok */
#define TLS_S_CERTP_EX	0x00000010	/* CA cert path exists */
#define TLS_S_CERTP_OK	0x00000020	/* CA cert path is ok */
#define TLS_S_CERTF_EX	0x00000040	/* CA cert file exists */
#define TLS_S_CERTF_OK	0x00000080	/* CA cert file is ok */

#define TLS_S_CERT2_EX	0x00001000	/* 2nd cert file exists */
#define TLS_S_CERT2_OK	0x00002000	/* 2nd cert file is ok */
#define TLS_S_KEY2_EX	0x00004000	/* 2nd key file exists */
#define TLS_S_KEY2_OK	0x00008000	/* 2nd key file is ok */

#define TLS_S_DH_OK	0x00200000	/* DH cert is ok */
#define TLS_S_DHPAR_EX	0x00400000	/* DH param file exists */
#define TLS_S_DHPAR_OK	0x00800000	/* DH param file is ok to use */

/*
**  TLS_SET_VERIFY -- request client certificate?
**
**	Parameters:
**		ctx -- TLS context
**		ssl -- TLS structure
**		vrfy -- require certificate?
**
**	Returns:
**		none.
**
**	Side Effects:
**		Sets verification state for TLS
**
#if TLS_VRFY_PER_CTX
**	Notice:
**		This is per TLS context, not per TLS structure;
**		the former is global, the latter per connection.
**		It would be nice to do this per connection, but this
**		doesn't work in the current TLS libraries :-(
#endif * TLS_VRFY_PER_CTX *
*/

void
tls_set_verify(SSL_CTX *ctx, SSL *ssl, bool vrfy)
{
#if !TLS_VRFY_PER_CTX
	SSL_set_verify(ssl, vrfy ? SSL_VERIFY_PEER : SSL_VERIFY_NONE, NULL);
#else
	SSL_CTX_set_verify(ctx, vrfy ? SSL_VERIFY_PEER : SSL_VERIFY_NONE,
			NULL);
#endif
}

/*
**  SM_TLS_INIT -- initialize TLS
**
**	Parameters:
**		tlsl_ctx -- TLS library context
**		ctx -- pointer to context (output)
**		req -- requirements for initialization
**		srv -- server side?
**		tls_cnf -- TLS configuration data
**
**	Returns:
**		succeeded?
*/

/*
**  The session_id_context identifies the service that created a session.
**  This information is used to distinguish between multiple TLS-based
**  servers running on the same server. We use the name of the mail system.
**  Note: the session cache is not persistent.
*/

static char server_session_id_context[] = "MeTA1";

/* 0.9.8a and b have a problem with SSL_OP_TLS_BLOCK_PADDING_BUG */
#if (OPENSSL_VERSION_NUMBER >= 0x0090800fL)
# define SM_SSL_OP_TLS_BLOCK_PADDING_BUG	1
#else
# define SM_SSL_OP_TLS_BLOCK_PADDING_BUG	0
#endif

sm_ret_T
sm_tls_init(tlsl_ctx_P tlsl_ctx, SSL_CTX **ctx, ulong req, bool srv, tls_cnf_P tls_cnf)
{
#if !NO_DH
	static DH *dh = NULL;
#endif
	int r;
	bool ok;
	long status, options;
	char *who;
	const char *dhparam;
#if SM_SSL_OP_TLS_BLOCK_PADDING_BUG
	long rt_version;
        STACK_OF(SSL_COMP) *comp_methods;
#endif


	SM_REQUIRE(ctx != NULL);
	SM_REQUIRE(tls_cnf != NULL);
	status = TLS_S_NONE;
	who = srv ? "server" : "client";
	dhparam = tls_cnf->tlscnf_dhparam;

	/* already initialized? (we could re-init...) */
	if (*ctx != NULL)
		return SM_SUCCESS;
	ok = true;

	/*
	**  valid values for dhparam are (only the first char is checked)
	**  none	no parameters: don't use DH
	**  512		generate 512 bit parameters (fixed)
	**  1024	generate 1024 bit parameters
	**  /file/name	read parameters from /file/name
	**  default is: 1024 for server, 512 for client (OK? XXX)
	*/

	if (bitset(TLS_I_TRY_DH, req))
	{
		if (dhparam != NULL)
		{
			char c;

			c = *dhparam;
			if (c == '1')
				req |= TLS_I_DH1024;
			else if (c == '5')
				req |= TLS_I_DH512;
			else if (c != 'n' && c != 'N' && c != '/')
			{
				sm_log_write(tlsl_ctx->tlsl_lctx,
					TL_LCAT_INIT, TL_LMOD_CONF,
					SM_LOG_WARN, 12,
					"STARTTLS=%s, DHParam=%s, error=illegal_value",
					who, dhparam);
				dhparam = NULL;
			}
		}
		if (dhparam == NULL)
			dhparam = srv ? "1" : "5";
		else if (*dhparam == '/')
		{
#if 0
			TLS_OK_F(dhparam, "DHParameters",
				 bitset(TLS_I_DHPAR_EX, req),
				 TLS_S_DHPAR_EX, srv);
#endif
		}
	}
	if (!ok)
		return sm_error_perm(SM_EM_TLS, EINVAL);

#define TLS_OK_F(var, fn, req, st, srv) if (ok)	\
	{						\
		r = (var != NULL && *var != '\0');	\
		if (r) \
			status |= st; \
		else if (req) \
			ok = false; \
	}

	TLS_OK_F(tls_cnf->tlscnf_certfile, "CertFile",
		bitset(TLS_I_CERT_EX, req), TLS_S_CERT_EX|TLS_S_CERT_OK, srv);
	TLS_OK_F(tls_cnf->tlscnf_keyfile, "KeyFile", bitset(TLS_I_KEY_EX, req),
		TLS_S_KEY_EX|TLS_S_KEY_OK, srv);
	TLS_OK_F(tls_cnf->tlscnf_cacertpath, "CACertPath",
		bitset(TLS_I_CERTP_EX, req),
		TLS_S_CERTP_EX|TLS_S_CERTP_OK, srv);
	TLS_OK_F(tls_cnf->tlscnf_cacertfile, "CACertFile",
		bitset(TLS_I_CERTF_EX, req),
		TLS_S_CERTF_EX|TLS_S_CERTF_OK, srv);

	if (tls_cnf->tlscnf_dsa_certfile != NULL &&
	    tls_cnf->tlscnf_dsa_keyfile != NULL)
	{
		TLS_OK_F(tls_cnf->tlscnf_dsa_certfile, "Cert2File",
			bitset(TLS_I_CERT_EX, req),
			TLS_S_CERT2_EX, srv ? TLS_T_SRV : TLS_T_CLT);
		TLS_OK_F(tls_cnf->tlscnf_dsa_keyfile, "Key2File",
			bitset(TLS_I_KEY_EX, req),
			TLS_S_KEY2_EX, srv ? TLS_T_SRV : TLS_T_CLT);
	}

	/* create a method and a new context */
	if ((*ctx = SSL_CTX_new(srv ? SSLv23_server_method() :
				      SSLv23_client_method())) == NULL)
	{
		sm_log_write(tlsl_ctx->tlsl_lctx,
			TL_LCAT_INIT, TL_LMOD_CONF,
			SM_LOG_WARN, 7,
			"STARTTLS=%s, SSL_CTX_new(SSLv23_%s_method())=failed",
			who, who);
		tlslogerr(tlsl_ctx, who);
		return sm_error_perm(SM_EM_TLS, EINVAL);
	}

#if TLS_NO_RSA
	/* turn off backward compatibility, required for no-rsa */
	SSL_CTX_set_options(*ctx, SSL_OP_NO_SSLv2);
#endif

#if !TLS_NO_RSA
	/*
	**  Create a temporary RSA key
	**  XXX  Maybe we shouldn't create this always (even though it
	**  is only at startup).
	**  It is a time-consuming operation and it is not always necessary.
	**  maybe we should do it only on demand...
	*/

	if (bitset(TLS_I_RSA_TMP, req) &&
	    (rsa_tmp = RSA_generate_key(RSA_KEYLENGTH, RSA_F4, NULL, NULL))
		== NULL
	   )
	{
		sm_log_write(tlsl_ctx->tlsl_lctx,
			TL_LCAT_INIT, TL_LMOD_CONF,
			SM_LOG_WARN, 7,
			"STARTTLS=%s, RSA_generate_key=failed", who);
		tlslogerr(tlsl_ctx, who);
		return sm_error_perm(SM_EM_TLS, EINVAL);
	}
#endif /* !TLS_NO_RSA */

	/* load private key */
	if (bitset(TLS_S_KEY_OK, status) &&
	    (r = SSL_CTX_use_PrivateKey_file(*ctx, tls_cnf->tlscnf_keyfile,
					 SSL_FILETYPE_PEM)) <= 0)
	{
		sm_log_write(tlsl_ctx->tlsl_lctx,
			TL_LCAT_INIT, TL_LMOD_CONF,
			SM_LOG_WARN, 7,
			"STARTTLS=%s, SSL_CTX_use_PrivateKey_file=failed, file=%s, ret=%d",
			who, tls_cnf->tlscnf_keyfile, r);
		tlslogerr(tlsl_ctx, who);
		if (bitset(TLS_I_USE_KEY, req))
			return sm_error_perm(SM_EM_TLS, EINVAL);
	}

	/* get the certificate file */
	if (bitset(TLS_S_CERT_OK, status) &&
	    SSL_CTX_use_certificate_file(*ctx, tls_cnf->tlscnf_certfile,
					 SSL_FILETYPE_PEM) <= 0)
	{
		sm_log_write(tlsl_ctx->tlsl_lctx,
			TL_LCAT_INIT, TL_LMOD_CONF,
			SM_LOG_WARN, 7,
			"STARTTLS=%s, SSL_CTX_use_certificate_file=failed, file=%s",
			who, tls_cnf->tlscnf_certfile);
		tlslogerr(tlsl_ctx, who);
		if (bitset(TLS_I_USE_CERT, req))
			return sm_error_perm(SM_EM_TLS, EINVAL);
	}

	/* check the private key */
	if (bitset(TLS_S_KEY_OK, status) &&
	    (r = SSL_CTX_check_private_key(*ctx)) <= 0)
	{
		/* Private key does not match the certificate public key */
		sm_log_write(tlsl_ctx->tlsl_lctx,
			TL_LCAT_INIT, TL_LMOD_CONF,
			SM_LOG_WARN, 7,
			"STARTTLS=%s, SSL_CTX_check_private_key=failed, file=%s, error=%d",
			who, tls_cnf->tlscnf_keyfile, r);
		tlslogerr(tlsl_ctx, who);
		if (bitset(TLS_I_USE_KEY, req))
			return sm_error_perm(SM_EM_TLS, EINVAL);
	}

	/* XXX this code is pretty much duplicated from above! */

	/* load private key */
	if (bitset(TLS_S_KEY2_OK, status) &&
	    SSL_CTX_use_PrivateKey_file(*ctx, tls_cnf->tlscnf_dsa_keyfile,
					SSL_FILETYPE_PEM) <= 0)
	{
		sm_log_write(tlsl_ctx->tlsl_lctx,
			TL_LCAT_INIT, TL_LMOD_CONF,
			SM_LOG_WARN, 7,
			"STARTTLS=%s, SSL_CTX_use_PrivateKey_file=failed, file=%s",
			who, tls_cnf->tlscnf_dsa_keyfile);
		tlslogerr(tlsl_ctx, who);
	}

	/* get the certificate file */
	if (bitset(TLS_S_CERT2_OK, status) &&
	    SSL_CTX_use_certificate_file(*ctx, tls_cnf->tlscnf_dsa_certfile,
					SSL_FILETYPE_PEM) <= 0)
	{
		sm_log_write(tlsl_ctx->tlsl_lctx,
			TL_LCAT_INIT, TL_LMOD_CONF,
			SM_LOG_WARN, 7,
			"STARTTLS=%s, SSL_CTX_use_certificate_file=failed, file=%s",
			who, tls_cnf->tlscnf_dsa_certfile);
		tlslogerr(tlsl_ctx, who);
	}

	/* also check the private key */
	if (bitset(TLS_S_KEY2_OK, status) &&
	    (r = SSL_CTX_check_private_key(*ctx)) <= 0)
	{
		/* Private key does not match the certificate public key */
		sm_log_write(tlsl_ctx->tlsl_lctx,
			TL_LCAT_INIT, TL_LMOD_CONF,
			SM_LOG_WARN, 7,
			"STARTTLS=%s, SSL_CTX_check_private_key/2=failed, error=%d",
			who, r);
		tlslogerr(tlsl_ctx, who);
	}

	/* SSL_CTX_set_quiet_shutdown(*ctx, 1); violation of standard? */

	options = (tls_cnf->tlscnf_options_isset != 0)
			? tls_cnf->tlscnf_options : SSL_OP_ALL;
#if SM_SSL_OP_TLS_BLOCK_PADDING_BUG

	/*
	**  In OpenSSL 0.9.8[ab], enabling zlib compression breaks the
	**  padding bug work-around, leading to false positives and
	**  failed connections. We may not interoperate with systems
	**  with the bug, but this is better than breaking on all 0.9.8[ab]
	**  systems that have zlib support enabled.
	**  Note: this checks the runtime version of the library, not
	**  just the compile time version.
	*/

	rt_version = SSLeay();
	if (rt_version >= 0x00908000L && rt_version <= 0x0090802fL)
	{
		comp_methods = SSL_COMP_get_compression_methods();
		if (comp_methods != NULL && sk_SSL_COMP_num(comp_methods) > 0)
			options &= ~SSL_OP_TLS_BLOCK_PADDING_BUG;
	}
#endif
	SSL_CTX_set_options(*ctx, options);

#if !NO_DH
	/* Diffie-Hellman initialization */
	if (bitset(TLS_I_TRY_DH, req))
	{
		if (bitset(TLS_S_DHPAR_OK, status))
		{
			BIO *bio;

			if ((bio = BIO_new_file(dhparam, "r")) != NULL)
			{
				dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL);
				BIO_free(bio);
				if (dh == NULL)
				{
					ulong err;

					err = ERR_get_error();
					sm_log_write(tlsl_ctx->tlsl_lctx,
						TL_LCAT_INIT, TL_LMOD_CONF,
						SM_LOG_WARN, 7,
						"STARTTLS=%s, DH_parameters=%s, error=cannot read, status=%s",
						who, dhparam,
						ERR_error_string(err, NULL));
					tlslogerr(tlsl_ctx, who);
				}
			}
			else
			{
				sm_log_write(tlsl_ctx->tlsl_lctx,
					TL_LCAT_INIT, TL_LMOD_CONF,
					SM_LOG_WARN, 5,
					"STARTTLS=%s, DH_parameters=%s, BIO_new_file=failed",
					who, dhparam);
				tlslogerr(tlsl_ctx, who);
			}
		}
		if (dh == NULL && bitset(TLS_I_DH1024, req))
		{
			DSA *dsa;

			/* this takes a while! (7-130s on a 450MHz AMD K6-2) */
			dsa = DSA_generate_parameters(1024, NULL, 0, NULL,
						      NULL, 0, NULL);
			dh = DSA_dup_DH(dsa);
			DSA_free(dsa);
		}
		else
		if (dh == NULL && bitset(TLS_I_DH512, req))
			dh = get_dh512();

		if (dh == NULL)
		{
			ulong err;

			err = ERR_get_error();
			sm_log_write(tlsl_ctx->tlsl_lctx,
				TL_LCAT_INIT, TL_LMOD_CONF,
				SM_LOG_WARN, 9,
				"STARTTLS=%s, DH_parameters=%s, error=cannot_read_or_set, status=%s",
				who, dhparam, ERR_error_string(err, NULL));
			if (bitset(TLS_I_REQ_DH, req))
				return sm_error_perm(SM_EM_TLS, EINVAL);
		}
		else
		{
			SSL_CTX_set_tmp_dh(*ctx, dh);

			/* important to avoid small subgroup attacks */
			SSL_CTX_set_options(*ctx, SSL_OP_SINGLE_DH_USE);
			sm_log_write(tlsl_ctx->tlsl_lctx,
				TL_LCAT_INIT, TL_LMOD_CONF,
				SM_LOG_INFO, 13,
				"STARTTLS=%s, Diffie-Hellman=init, key=%d, dhparam=%c",
				who, 8 * DH_size(dh), *dhparam);
			DH_free(dh);
		}
	}
#endif /* !NO_DH */

	/* do we need this cache here?? */
	if (bitset(TLS_I_CACHE, req) && tls_cnf->tlscnf_cache_size > 0)
	{
		r = SSL_CTX_sess_set_cache_size(*ctx,
				tls_cnf->tlscnf_cache_size);
		r = SSL_CTX_set_timeout(*ctx, tls_cnf->tlscnf_cache_tmout);
		r = SSL_CTX_set_session_id_context(*ctx,
			(const unsigned char *) &server_session_id_context,
			sizeof(server_session_id_context));
		if (r != 1)
		{
			sm_log_write(tlsl_ctx->tlsl_lctx,
				TL_LCAT_INIT, TL_LMOD_CONF,
				SM_LOG_ERR, 6,
				"STARTTLS=%s, SSL_CTX_set_session_id_context=failed"
				, who);
		}
		(void) SSL_CTX_set_session_cache_mode(*ctx,
				SSL_SESS_CACHE_SERVER);
	}
	else
		r = 0;
	if (r != 1)
	{
		(void) SSL_CTX_set_session_cache_mode(*ctx,
				SSL_SESS_CACHE_OFF);
	}

	/* load certificate locations and default CA paths */
	if (bitset(TLS_S_CERTP_EX, status) && bitset(TLS_S_CERTF_EX, status))
	{
		if ((r = SSL_CTX_load_verify_locations(*ctx,
				tls_cnf->tlscnf_cacertfile,
				tls_cnf->tlscnf_cacertpath)) == 1)
		{
#if !TLS_NO_RSA
			if (bitset(TLS_I_RSA_TMP, req))
				SSL_CTX_set_tmp_rsa_callback(*ctx, tmp_rsa_key);
#endif

			/*
			**  We have to install our own verify callback:
			**  SSL_VERIFY_PEER requests a client cert but even
			**  though *FAIL_IF* isn't set, the connection
			**  will be aborted if the client presents a cert
			**  that is not "liked" (can't be verified?) by
			**  the TLS library :-(
			*/

			/*
			**  XXX currently we could call tls_set_verify()
			**  but we hope that that function will later on
			**  only set the mode per connection.
			*/

			SSL_CTX_set_verify(*ctx,
				bitset(TLS_I_NO_VRFY, req) ? SSL_VERIFY_NONE
							   : SSL_VERIFY_PEER,
				NULL);

			/* install verify callback */
			SSL_CTX_set_cert_verify_callback(*ctx, tls_verify_cb,
							 (char *) tlsl_ctx);
			SSL_CTX_set_client_CA_list(*ctx,
				SSL_load_client_CA_file(
						tls_cnf->tlscnf_cacertfile));
		}
		else
		{
			/*
			**  can't load CA data; do we care?
			**  the data is necessary to authenticate the client,
			**  which in turn would be necessary
			**  if we want to allow relaying based on it.
			*/

			sm_log_write(tlsl_ctx->tlsl_lctx,
				TL_LCAT_INIT, TL_LMOD_CONF,
				SM_LOG_WARN, 7,
				"STARTTLS=%s, load_verify_locs=failed, CACertPath=%s, CACertFile=%s, error=%d",
				who, tls_cnf->tlscnf_cacertpath,
				tls_cnf->tlscnf_cacertfile, r);
			tlslogerr(tlsl_ctx, who);
			if (bitset(TLS_I_VRFY_LOC, req))
				return sm_error_perm(SM_EM_TLS, EINVAL);
		}
	}

#if 0
	/* XXX: make this dependent on an option? */
	if (tTd(96, 9))
		SSL_CTX_set_info_callback(*ctx, apps_ssl_info_cb);
#endif

#if _FFR_TLS_1
	/* install our own cipher list */
	if (CipherList != NULL && *CipherList != '\0')
	{
		if (SSL_CTX_set_cipher_list(*ctx, CipherList) <= 0)
		{
			sm_log_write(tlsl_ctx->tlsl_lctx,
				TL_LCAT_INIT, TL_LMOD_CONF,
				SM_LOG_WARN, 7,
				"STARTTLS=%s, SSL_CTX_set_cipher_list=failed, list=%s, comment=ignored",
				who, CipherList);
			tlslogerr(tlsl_ctx, who);
			/* failure if setting to this list is required? */
		}
	}
#endif /* _FFR_TLS_1 */

	return ok ? SM_SUCCESS : sm_error_perm(SM_EM_TLS, EINVAL);
}

/*
**  TLS_GET_INFO -- get information about TLS connection
**
**	Parameters:
**		ssl -- TLS connection structure
**		flags -- server or client, and other flags
**		host -- hostname of other side
**		tlsi_ctx -- TLS information context
**
**	Returns:
**		usual error code
**
**	Side Effects:
**		sets data in tlsi_ctx
*/

sm_ret_T
tls_get_info(tlsl_ctx_P tlsl_ctx, SSL *ssl, uint flags, char *host, tlsi_ctx_P tlsi_ctx)
{
	int b, r;
	char *s;
#if 0
	char *who;
#endif
	SSL_CIPHER *c;
	X509 *cert;

	SM_REQUIRE(ssl != NULL);
	SM_REQUIRE(tlsi_ctx != NULL);
	c = SSL_get_current_cipher(ssl);

#define TLS_IS_SRV	(SM_IS_FLAG(flags, TLS_F_SRV))
#define TLS_CERT_REQ	(SM_IS_FLAG(flags, TLS_F_CERT_REQ))

#define TLS_SET_STR(s, str)						\
	do								\
	{								\
		if ((s) != NULL)					\
		{							\
			r = strlen(s);					\
			if ((str) == NULL)				\
				(str) = sm_str_new(NULL, r + 1, r + 2);	\
			if ((str) != NULL)				\
				(void) sm_str_scopy((str), (s));	\
		}							\
	} while (0)

#define TLS_SET_XSTR(s, str)						\
	do								\
	{								\
		if ((s) != NULL)					\
		{							\
			r = strlen(s);					\
			if ((str) == NULL)				\
				(str) = sm_str_new(NULL, r + 2, r * 3);	\
			else						\
				sm_str_clr(str);			\
			if ((str) != NULL)				\
				(void) xtextify((s), "<>\")", str);	\
		}							\
	} while (0)

	s = (char *) SSL_CIPHER_get_name(c);
	TLS_SET_STR(s, tlsi_ctx->tlsi_cipher);

	b = SSL_CIPHER_get_bits(c, &r);
	tlsi_ctx->tlsi_cipher_bits = b;
	tlsi_ctx->tlsi_algs_bits = r;

	s = SSL_CIPHER_get_version(c);
	TLS_SET_STR(s, tlsi_ctx->tlsi_version);

#if 0
	who = TLS_IS_SRV ? "server" : "client";
#endif
	cert = SSL_get_peer_certificate(ssl);
	if (cert != NULL)
	{
#if SM_TLSI_CERT_MD5
		uint n;
		uchar md[EVP_MAX_MD_SIZE];
#endif
		char buf[MAXNAME];

		X509_NAME_oneline(X509_get_subject_name(cert),
				  buf, sizeof buf);
		TLS_SET_XSTR(buf, tlsi_ctx->tlsi_cert_subject);
		X509_NAME_oneline(X509_get_issuer_name(cert),
				  buf, sizeof buf);
		TLS_SET_XSTR(buf, tlsi_ctx->tlsi_cert_issuer);
		X509_NAME_get_text_by_NID(X509_get_subject_name(cert),
					  NID_commonName, buf, sizeof buf);
		TLS_SET_XSTR(buf, tlsi_ctx->tlsi_cn_subject);
		X509_NAME_get_text_by_NID(X509_get_issuer_name(cert),
					  NID_commonName, buf, sizeof buf);
		TLS_SET_XSTR(buf, tlsi_ctx->tlsi_cn_issuer);

#if SM_TLSI_CERT_MD5
		n = 0;
		if (X509_digest(cert, EVP_md5(), md, &n) != 0 && n > 0)
		{
			sm_str_P str;
			static const char hexcodes[] = "0123456789ABCDEF";

			r = (n * 3) + 2;
			if (tlsi_ctx->tlsi_cert_md5 == NULL)
				tlsi_ctx->tlsi_cert_md5 = sm_str_new(NULL,
								r, r + 2);
			str = tlsi_ctx->tlsi_cert_md5;
			if (str != NULL)
			{
				sm_str_clr(str);
				for (r = 0; r < (int) n; r++)
				{
					(void) sm_str_put(str,
						hexcodes[(md[r] & 0xf0) >> 4]);
					(void) sm_str_put(str,
						hexcodes[(md[r] & 0x0f)]);
					(void) sm_str_put(str, ':');
				}
				(void) sm_str_term(str);
			}
		}
		else
			TLS_SET_STR("", tlsi_ctx->tlsi_cert_md5);
#endif /* SM_TLSI_CERT_MD5 */
	}
	else
	{
		TLS_SET_STR("", tlsi_ctx->tlsi_cert_subject);
		TLS_SET_STR("", tlsi_ctx->tlsi_cert_issuer);
		TLS_SET_STR("", tlsi_ctx->tlsi_cn_subject);
		TLS_SET_STR("", tlsi_ctx->tlsi_cn_issuer);
#if SM_TLSI_CERT_MD5
		TLS_SET_STR("", tlsi_ctx->tlsi_cert_md5);
#endif
	}

	switch (SSL_get_verify_result(ssl))
	{
	  case X509_V_OK:
		if (cert != NULL)
			r = TLS_VRFY_OK;
		else if (TLS_CERT_REQ)
			r = TLS_VRFY_NO;
		else
			r = TLS_VRFY_NOTR;
		break;
	  default:
		r = TLS_VRFY_FAIL;
		break;
	}
	tlsi_ctx->tlsi_vrfy = r;
	if (cert != NULL)
		X509_free(cert);

#if 0
	/* do some logging? leave it to application */
	sm_log_write(tlsl_ctx->tlsl_lctx,
		TL_LCAT_INIT, TL_LMOD_CONF,
		SM_LOG_INFO, 9,
		"STARTTLS=%s, verify=%s",
		who, s);
#endif /* 0 */
	return SM_SUCCESS;
}

/*
**  ENDTLS -- shutdown secure connection
**
**	Parameters:
**		ssl -- SSL connection information.
**		side -- server/client (for logging).
**
**	Returns:
**		success? (EX_* code)
*/

int
endtls(tlsl_ctx_P tlsl_ctx, SSL *ssl, char *side)
{
	int ret = SM_SUCCESS;

	if (ssl != NULL)
	{
		int r;

		if ((r = SSL_shutdown(ssl)) < 0)
		{
			sm_log_write(tlsl_ctx->tlsl_lctx,
				TL_LCAT_INIT, TL_LMOD_CONF,
				SM_LOG_WARN, 10,
				"STARTTLS=%s, SSL_shutdown=failed, error=%d",
				side, r);
			tlslogerr(tlsl_ctx, side);
			ret = sm_error_temp(SM_EM_TLS, r);
		}
#if !defined(OPENSSL_VERSION_NUMBER) || OPENSSL_VERSION_NUMBER > 0x0090602fL

		/*
		**  Bug in OpenSSL (at least up to 0.9.6b):
		**  From: Lutz.Jaenicke@aet.TU-Cottbus.DE
		**  Message-ID: <20010723152244.A13122@serv01.aet.tu-cottbus.de>
		**  To: openssl-users@openssl.org
		**  Subject: Re: SSL_shutdown() woes (fwd)
		**
		**  The side sending the shutdown alert first will
		**  not care about the answer of the peer but will
		**  immediately return with a return value of "0"
		**  (ssl/s3_lib.c:ssl3_shutdown()). SSL_get_error will evaluate
		**  the value of "0" and as the shutdown alert of the peer was
		**  not received (actually, the program did not even wait for
		**  the answer), an SSL_ERROR_SYSCALL is flagged, because this
		**  is the default rule in case everything else does not apply.
		**
		**  For your server the problem is different, because it
		**  receives the shutdown first (setting SSL_RECEIVED_SHUTDOWN),
		**  then sends its response (SSL_SENT_SHUTDOWN), so for the
		**  server the shutdown was successfull.
		**
		**  As is by know, you would have to call SSL_shutdown() once
		**  and ignore an SSL_ERROR_SYSCALL returned. Then call
		**  SSL_shutdown() again to actually get the server's response.
		**
		**  In the last discussion, Bodo Moeller concluded that a
		**  rewrite of the shutdown code would be necessary, but
		**  probably with another API, as the change would not be
		**  compatible to the way it is now.  Things do not become
		**  easier as other programs do not follow the shutdown
		**  guidelines anyway, so that a lot error conditions and
		**  compitibility issues would have to be caught.
		**
		**  For now the recommondation is to ignore the error message.
		*/

		else if (r == 0)
		{
			sm_log_write(tlsl_ctx->tlsl_lctx,
				TL_LCAT_INIT, TL_LMOD_CONF,
				SM_LOG_WARN, 15,
				"STARTTLS=%s, SSL_shutdown=not_done",
				side);
			tlslogerr(tlsl_ctx, side);
			ret = sm_error_temp(SM_EM_TLS, EINVAL); /* XXX */
		}
#endif /* !defined(OPENSSL_VERSION_NUMBER) || OPENSSL_VERSION_NUMBER > 0x0090602fL */
		SSL_free(ssl);
		ssl = NULL;
	}
	return ret;
}

#if !TLS_NO_RSA
/*
**  TMP_RSA_KEY -- return temporary RSA key
**
**	Parameters:
**		s -- TLS connection structure
**		export --
**		keylength --
**
**	Returns:
**		temporary RSA key.
*/

# ifndef MAX_RSA_TMP_CNT
#  define MAX_RSA_TMP_CNT	100	/* XXX better value? */
# endif

/* ARGUSED0 */
static RSA *
tmp_rsa_key(SSL *s, int export, int keylength)
{
	static int RSATmpCnt = 0;

	/*
	**  NOT thread safe!
	**  for thread safety: see comment at begin of file.
	**  Note: currently this is not used by any program that uses
	**  POSIX threads (only statethreads).
	*/

	if (++RSATmpCnt < MAX_RSA_TMP_CNT)
		return rsa_tmp;
	RSATmpCnt = 0;
	if (rsa_tmp != NULL)
		RSA_free(rsa_tmp);
	rsa_tmp = RSA_generate_key(RSA_KEYLENGTH, RSA_F4, NULL, NULL);
	if (rsa_tmp == NULL) {
		/* COMPLAIN, but HOW? */
	}
	return rsa_tmp;
}
#endif /* !TLS_NO_RSA */

#if 0
/*
**  APPS_SSL_INFO_CB -- info callback for TLS connections
**
**	Parameters:
**		s -- TLS connection structure
**		where -- state in handshake
**		ret -- return code of last operation
**
**	Returns:
**		none.
*/

static void
apps_ssl_info_cb(CONST097 SSL *s, int where, int ret)
{
	int w;
	char *str;
	BIO *bio_err;

	bio_err = NULL;
#if 0
	if (LogLevel > 14)
		sm_syslog(LOG_INFO, NOQID,
			  "STARTTLS: info_callback where=0x%x, ret=%d",
			  where, ret);
#endif

	w = where & ~SSL_ST_MASK;
	if (bio_err == NULL)
		bio_err = BIO_new_fp(stderr, BIO_NOCLOSE);

	if (bitset(SSL_ST_CONNECT, w))
		str = "SSL_connect";
	else if (bitset(SSL_ST_ACCEPT, w))
		str = "SSL_accept";
	else
		str = "undefined";

	if (bitset(SSL_CB_LOOP, where))
	{
#if 0
		if (LogLevel > 12)
			sm_syslog(LOG_NOTICE, NOQID,
				"STARTTLS: %s:%s",
				str, SSL_state_string_long(s));
#endif
	}
	else if (bitset(SSL_CB_ALERT, where))
	{
		str = bitset(SSL_CB_READ, where) ? "read" : "write";
#if 0
		if (LogLevel > 12)
			sm_syslog(LOG_NOTICE, NOQID,
				"STARTTLS: SSL3 alert %s:%s:%s",
				str, SSL_alert_type_string_long(ret),
				SSL_alert_desc_string_long(ret));
#endif
	}
	else if (bitset(SSL_CB_EXIT, where))
	{
		if (ret == 0)
		{
#if 0
			if (LogLevel > 7)
				sm_syslog(LOG_WARNING, NOQID,
					"STARTTLS: %s:failed in %s",
					str, SSL_state_string_long(s));
#endif
		}
		else if (ret < 0)
		{
#if 0
			if (LogLevel > 7)
				sm_syslog(LOG_WARNING, NOQID,
					"STARTTLS: %s:error in %s",
					str, SSL_state_string_long(s));
#endif
		}
	}
}
#endif /* 0 */
/*
**  TLS_VERIFY_LOG -- log verify error for TLS certificates
**
**	Parameters:
**		ok -- verify ok?
**		ctx -- x509 context
**
**	Returns:
**		0 -- fatal error
**		1 -- ok
*/

static int
tls_verify_log(int ok, X509_STORE_CTX *ctx, void *myctx)
{
	SSL *ssl;
	X509 *cert;
	int reason, depth;
	char buf[512];
	tlsl_ctx_P tlsl_ctx;

	tlsl_ctx = (tlsl_ctx_P) myctx;
	cert = X509_STORE_CTX_get_current_cert(ctx);
	reason = X509_STORE_CTX_get_error(ctx);
	depth = X509_STORE_CTX_get_error_depth(ctx);
	ssl = (SSL *) X509_STORE_CTX_get_ex_data(ctx,
			SSL_get_ex_data_X509_STORE_CTX_idx());

	if (ssl == NULL)
	{
		/* internal error */
		sm_log_write(tlsl_ctx->tlsl_lctx,
			TL_LCAT_INIT, TL_LMOD_CONF,
			SM_LOG_ERROR, 9,
			"STARTTLS=internal_error, func=tls_verify_cb, ssl=NULL");
		return 0;
	}

	X509_NAME_oneline(X509_get_subject_name(cert), buf, sizeof buf);
	sm_log_write(tlsl_ctx->tlsl_lctx,
		TL_LCAT_INIT, TL_LMOD_CONF,
		SM_LOG_INFO, 14,
		"STARTTLS=info, func=tls_verify_cb, depth=%d, cert_subject=%s, stat=%d, reason=%s",
		depth, buf, ok, X509_verify_cert_error_string(reason));
	return 1;
}

/*
**  TLS_VERIFY_CB -- verify callback for TLS certificates
**
**	Parameters:
**		ctx -- x509 context
**
**	Returns:
**		accept connection?
**		currently: always yes.
*/

static int
# if !defined(OPENSSL_VERSION_NUMBER) || OPENSSL_VERSION_NUMBER < 0x00907000L
tls_verify_cb(X509_STORE_CTX *ctx)
# else
tls_verify_cb(X509_STORE_CTX *ctx, void *myctx)
# endif
{
	int ok;

	ok = X509_verify_cert(ctx);
	if (ok == 0)
	{
# if !defined(OPENSSL_VERSION_NUMBER) || OPENSSL_VERSION_NUMBER < 0x00907000L
		return 1;	/* override it */
# else
		return tls_verify_log(ok, ctx, myctx);
# endif
	}
	return ok;
}

/*
**  TLS_VRFY2TXT -- return textual representation of verification result
**
**	Parameters:
**		vrfy -- verification result
**
**	Returns:
**		textual representation of verification result
*/

char *
tls_vrfy2txt(int vrfy)
{
	switch (vrfy)
	{
	  case TLS_VRFY_OK:
		return "OK";
	  case TLS_VRFY_NO:
		return "NO";
	  case TLS_VRFY_NOTR:
		return "NOT";
	  case TLS_VRFY_FAIL:
		return "FAIL";
	  default:
		return "Unknown";
	}
	return "OOPS";
}
#endif /* MTA_USE_TLS */


syntax highlighted by Code2HTML, v. 0.9.1