/*
* 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