#include <config.h>
#ifdef HAVE_SSL
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <syslog.h>
#include <openssl/lhash.h>
#include <openssl/bn.h>
#include <openssl/err.h>
#include <openssl/pem.h>
#include <openssl/x509.h>
#include <openssl/ssl.h>
#include "AppleOD.h"
#include "assert.h"
#include "xmalloc.h"
#include "tls.h"
#include "global.h"
#include "cyrusdb.h"
#define DB (config_tlscache_db)
static CallbackUserData *sUserData = NULL;
static struct db *sessdb = NULL;
static int sess_dbopen = 0;
static const char hexcodes[] = "0123456789ABCDEF";
enum {
var_imapd_tls_loglevel = 0,
var_proxy_tls_loglevel = 0,
CCERT_BUFSIZ = 256
};
static int verify_depth = 5;
static int verify_error = X509_V_OK;
static SSL_CTX *s_ctx = NULL, *c_ctx = NULL;
static int tls_serverengine = 0;
static int tls_clientengine = 0;
static int do_dump = 0;
int tls_enabled(void)
{
const char *val;
val = config_getstring(IMAPOPT_TLS_CERT_FILE);
if (!val || !strcasecmp(val, "disabled")) return 0;
val = config_getstring(IMAPOPT_TLS_KEY_FILE);
if (!val || !strcasecmp(val, "disabled")) return 0;
return 1;
}
static void apps_ssl_info_callback(SSL * s, int where, int ret)
{
char *str;
int w;
if (var_imapd_tls_loglevel==0) return;
w = where & ~SSL_ST_MASK;
if (w & SSL_ST_CONNECT)
str = "SSL_connect";
else if (w & SSL_ST_ACCEPT)
str = "SSL_accept";
else
str = "undefined";
if (where & SSL_CB_LOOP) {
if (tls_serverengine && (var_imapd_tls_loglevel >= 2))
syslog(LOG_DEBUG, "%s:%s", str, SSL_state_string_long(s));
} else if (where & SSL_CB_ALERT) {
str = (where & SSL_CB_READ) ? "read" : "write";
if ((tls_serverengine && (var_imapd_tls_loglevel >= 2)) ||
((ret & 0xff) != SSL3_AD_CLOSE_NOTIFY))
syslog(LOG_DEBUG, "SSL3 alert %s:%s:%s", str,
SSL_alert_type_string_long(ret),
SSL_alert_desc_string_long(ret));
} else if (where & SSL_CB_EXIT) {
if (ret == 0)
syslog(LOG_DEBUG, "%s:failed in %s",
str, SSL_state_string_long(s));
else if (ret < 0) {
syslog(LOG_DEBUG, "%s:error in %s",
str, SSL_state_string_long(s));
}
}
}
static RSA *tmp_rsa_cb(SSL * s __attribute__((unused)),
int export __attribute__((unused)),
int keylength)
{
static RSA *rsa_tmp = NULL;
if (rsa_tmp == NULL) {
rsa_tmp = RSA_generate_key(keylength, RSA_F4, NULL, NULL);
}
return (rsa_tmp);
}
static int verify_callback(int ok, X509_STORE_CTX * ctx)
{
char buf[256];
X509 *err_cert;
int err;
int depth;
syslog(LOG_ERR,"Doing a peer verify");
err_cert = X509_STORE_CTX_get_current_cert(ctx);
err = X509_STORE_CTX_get_error(ctx);
depth = X509_STORE_CTX_get_error_depth(ctx);
X509_NAME_oneline(X509_get_subject_name(err_cert), buf, sizeof(buf));
if (ok==0)
{
syslog(LOG_ERR, "verify error:num=%d:%s", err,
X509_verify_cert_error_string(err));
if (verify_depth >= depth) {
ok = 0;
verify_error = X509_V_OK;
} else {
ok = 0;
verify_error = X509_V_ERR_CERT_CHAIN_TOO_LONG;
}
}
switch (ctx->error) {
case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
X509_NAME_oneline(X509_get_issuer_name(ctx->current_cert), buf, sizeof(buf));
syslog(LOG_NOTICE, "issuer= %s", buf);
break;
case X509_V_ERR_CERT_NOT_YET_VALID:
case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD:
syslog(LOG_NOTICE, "cert not yet valid");
break;
case X509_V_ERR_CERT_HAS_EXPIRED:
case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD:
syslog(LOG_NOTICE, "cert has expired");
break;
}
return (ok);
}
#define TRUNCATE
#define DUMP_WIDTH 16
static int tls_dump(const char *s, int len)
{
int ret = 0;
char buf[160 + 1];
char *ss;
int i;
int j;
int rows;
int trunc;
unsigned char ch;
trunc = 0;
#ifdef TRUNCATE
for (; (len > 0) && ((s[len - 1] == ' ') || (s[len - 1] == '\0')); len--)
trunc++;
#endif
rows = (len / DUMP_WIDTH);
if ((rows * DUMP_WIDTH) < len)
rows++;
for (i = 0; i < rows; i++) {
unsigned int val;
buf[0] = '\0';
ss = buf;
val = i * DUMP_WIDTH;
assert(val <= 0xFFFF);
sprintf(ss, "%04x ", i * DUMP_WIDTH);
ss += strlen(ss);
for (j = 0; j < DUMP_WIDTH; j++) {
if (((i * DUMP_WIDTH) + j) >= len) {
strcpy(ss, " ");
} else {
ch = ((unsigned char) *((char *) (s) + i * DUMP_WIDTH + j))
& 0xFF;
sprintf(ss, "%02x%c", ch, j == 7 ? '|' : ' ');
ss += 3;
}
}
ss += strlen(ss);
*ss+= ' ';
for (j = 0; j < DUMP_WIDTH; j++) {
if (((i * DUMP_WIDTH) + j) >= len)
break;
ch = ((unsigned char) *((char *) (s) + i * DUMP_WIDTH + j)) & 0xff;
*ss+= (((ch >= ' ') && (ch <= '~')) ? ch : '.');
if (j == 7) *ss+= ' ';
}
*ss = 0;
if (var_imapd_tls_loglevel>0)
syslog(LOG_DEBUG, "%s", buf);
ret += strlen(buf);
}
#ifdef TRUNCATE
if (trunc > 0) {
snprintf(buf, sizeof(buf), "%04x - <SPACES/NULS>\n", len+ trunc);
if (var_imapd_tls_loglevel>0)
syslog(LOG_DEBUG, "%s", buf);
ret += strlen(buf);
}
#endif
return (ret);
}
static int set_cert_stuff(SSL_CTX * ctx,
const char *cert_file, const char *key_file)
{
if (cert_file != NULL) {
if (SSL_CTX_use_certificate_file(ctx, cert_file,
SSL_FILETYPE_PEM) <= 0) {
syslog(LOG_ERR, "unable to get certificate from '%s'", cert_file);
return (0);
}
if (key_file == NULL)
key_file = cert_file;
if ( strlen( key_file ) < FILENAME_MAX )
{
if ( sUserData == NULL )
{
sUserData = xmalloc( sizeof(CallbackUserData) );
if ( sUserData != NULL )
{
memset( sUserData, 0, sizeof(CallbackUserData) );
}
}
char tmp[ FILENAME_MAX ];
strlcpy( tmp, key_file, FILENAME_MAX );
char *p = tmp;
char *q = tmp;
while ( *p != '\0' )
{
if ( *p == '/' )
{
q = p;
}
p++;
}
if ( (*q != '\0') && (*q == '/') && (*p+1 != '\0') )
{
p = ++q;
int len = strlen( p );
if ( sUserData != NULL )
{
if ( strncmp( p+(len-4), ".key", 4 ) == 0 )
{
len = len - 4;
strncpy( sUserData->key, p, len );
sUserData->key[ len ] = '\0';
sUserData->len = len;
}
else
{
strcpy( sUserData->key, p );
sUserData->len = strlen( p );
}
SSL_CTX_set_default_passwd_cb_userdata( ctx, (void *)sUserData );
SSL_CTX_set_default_passwd_cb( ctx, &apple_password_callback );
}
else
{
syslog( LOG_NOTICE, "Could not allocate memory for custom callback: %s", key_file );
}
}
else
{
syslog( LOG_NOTICE, "Could not set custom callback: %s", key_file );
}
}
else
{
syslog( LOG_NOTICE, "Key file path too big for custom callback: %s", key_file );
}
if (SSL_CTX_use_PrivateKey_file(ctx, key_file,
SSL_FILETYPE_PEM) <= 0) {
syslog(LOG_ERR, "unable to get private key from '%s'", key_file);
return (0);
}
if (!SSL_CTX_check_private_key(ctx)) {
syslog(LOG_ERR,
"Private key does not match the certificate public key");
return (0);
}
}
return (1);
}
static int new_session_cb(SSL *ssl __attribute__((unused)),
SSL_SESSION *sess)
{
int len;
unsigned char *data = NULL, *asn;
time_t expire;
int ret = -1;
assert(sess);
if (!sess_dbopen) return 0;
len = i2d_SSL_SESSION(sess, NULL);
data = (unsigned char *) xmalloc(sizeof(time_t)+len*sizeof(unsigned char));
if (data) {
asn = data + sizeof(time_t);
len = i2d_SSL_SESSION(sess, &asn);
if (!len) syslog(LOG_ERR, "i2d_SSL_SESSION failed");
}
expire = SSL_SESSION_get_time(sess) + SSL_SESSION_get_timeout(sess);
memcpy(data, &expire, sizeof(time_t));
if (data && len) {
do {
ret = DB->store(sessdb, sess->session_id,
sess->session_id_length,
data, len + sizeof(time_t), NULL);
} while (ret == CYRUSDB_AGAIN);
}
if (data) free(data);
if (var_imapd_tls_loglevel > 0) {
unsigned int i;
char idstr[SSL_MAX_SSL_SESSION_ID_LENGTH*2 + 1];
for (i = 0; i < sess->session_id_length; i++) {
sprintf(idstr+i*2, "%02X", sess->session_id[i]);
}
syslog(LOG_DEBUG, "new TLS session: id=%s, expire=%s, status=%s",
idstr, ctime(&expire), ret ? "failed" : "ok");
}
return (ret == 0);
}
static void remove_session(unsigned char *id, int idlen)
{
int ret;
assert(id);
assert(idlen <= SSL_MAX_SSL_SESSION_ID_LENGTH);
if (!sess_dbopen) return;
do {
ret = DB->delete(sessdb, id, idlen, NULL, 1);
} while (ret == CYRUSDB_AGAIN);
if (var_imapd_tls_loglevel > 0) {
int i;
char idstr[SSL_MAX_SSL_SESSION_ID_LENGTH*2 + 1];
for (i = 0; i < idlen; i++) {
sprintf(idstr+i*2, "%02X", id[i]);
}
syslog(LOG_DEBUG, "remove TLS session: id=%s", idstr);
}
}
static void remove_session_cb(SSL_CTX *ctx __attribute__((unused)),
SSL_SESSION *sess)
{
assert(sess);
remove_session(sess->session_id, sess->session_id_length);
}
static SSL_SESSION *get_session_cb(SSL *ssl __attribute__((unused)),
unsigned char *id, int idlen, int *copy)
{
int ret;
const char *data = NULL;
unsigned char *asn;
int len = 0;
time_t expire = 0, now = time(0);
SSL_SESSION *sess = NULL;
assert(id);
assert(idlen <= SSL_MAX_SSL_SESSION_ID_LENGTH);
if (!sess_dbopen) return NULL;
do {
ret = DB->fetch(sessdb, id, idlen, &data, &len, NULL);
} while (ret == CYRUSDB_AGAIN);
if (!ret && data) {
assert(len >= (int) sizeof(time_t));
memcpy(&expire, data, sizeof(time_t));
if (expire < now) {
remove_session(id, idlen);
}
else {
asn = (unsigned char*) data + sizeof(time_t);
sess = d2i_SSL_SESSION(NULL, &asn, len - sizeof(time_t));
if (!sess) syslog(LOG_ERR, "d2i_SSL_SESSION failed: %m");
}
}
if (var_imapd_tls_loglevel > 0) {
int i;
char idstr[SSL_MAX_SSL_SESSION_ID_LENGTH*2 + 1];
for (i = 0; i < idlen; i++)
sprintf(idstr+i*2, "%02X", id[i]);
syslog(LOG_DEBUG, "get TLS session: id=%s, expire=%s, status=%s",
idstr, ctime(&expire),
!data ? "not found" : expire < now ? "expired" : "ok");
}
*copy = 0;
return sess;
}
static int tls_rand_init(void)
{
#ifdef EGD_SOCKET
return (RAND_egd(EGD_SOCKET));
#else
return 0;
#endif
}
int tls_init_serverengine(const char *ident,
int verifydepth,
int askcert,
int tlsonly)
{
int off = 0;
int verify_flags = SSL_VERIFY_NONE;
const char *cipher_list;
const char *CApath;
const char *CAfile;
const char *s_cert_file;
const char *s_key_file;
int requirecert;
int timeout;
if (tls_serverengine)
return (0);
if (var_imapd_tls_loglevel >= 2)
syslog(LOG_DEBUG, "starting TLS server engine");
SSL_library_init();
SSL_load_error_strings();
if (tls_rand_init() == -1) {
syslog(LOG_ERR,"TLS server engine: cannot seed PRNG");
return -1;
}
#if 0
if (tlsonly) {
s_ctx = SSL_CTX_new(TLSv1_server_method());
} else {
s_ctx = SSL_CTX_new(SSLv23_server_method());
}
#endif
s_ctx = SSL_CTX_new(SSLv23_server_method());
if (s_ctx == NULL) {
return (-1);
};
off |= SSL_OP_ALL;
if (tlsonly) {
off |= SSL_OP_NO_SSLv2;
off |= SSL_OP_NO_SSLv3;
}
SSL_CTX_set_options(s_ctx, off);
SSL_CTX_set_info_callback(s_ctx, (void (*)()) apps_ssl_info_callback);
SSL_CTX_sess_set_cache_size(s_ctx, 1);
SSL_CTX_set_session_cache_mode(s_ctx, SSL_SESS_CACHE_SERVER |
SSL_SESS_CACHE_NO_AUTO_CLEAR |
SSL_SESS_CACHE_NO_INTERNAL_LOOKUP);
timeout = config_getint(IMAPOPT_TLS_SESSION_TIMEOUT);
if (timeout < 0) timeout = 0;
if (timeout > 1440) timeout = 1440;
if (timeout) {
char dbdir[1024];
int r;
SSL_CTX_set_session_id_context(s_ctx, (void*) ident, strlen(ident));
SSL_CTX_set_timeout(s_ctx, timeout*60);
SSL_CTX_sess_set_new_cb(s_ctx, new_session_cb);
SSL_CTX_sess_set_remove_cb(s_ctx, remove_session_cb);
SSL_CTX_sess_set_get_cb(s_ctx, get_session_cb);
strlcpy(dbdir, config_dir, sizeof(dbdir));
strlcat(dbdir, FNAME_TLSSESSIONS, sizeof(dbdir));
r = DB->open(dbdir, CYRUSDB_CREATE, &sessdb);
if (r != 0) {
syslog(LOG_ERR, "DBERROR: opening %s: %s",
dbdir, cyrusdb_strerror(ret));
}
else
sess_dbopen = 1;
}
cipher_list = config_getstring(IMAPOPT_TLS_CIPHER_LIST);
if (!SSL_CTX_set_cipher_list(s_ctx, cipher_list)) {
syslog(LOG_ERR,"TLS server engine: cannot load cipher list '%s'",
cipher_list);
return (-1);
}
CAfile = config_getstring(IMAPOPT_TLS_CA_FILE);
CApath = config_getstring(IMAPOPT_TLS_CA_PATH);
if ((!SSL_CTX_load_verify_locations(s_ctx, CAfile, CApath)) ||
(!SSL_CTX_set_default_verify_paths(s_ctx))) {
syslog(LOG_NOTICE,"TLS server engine: cannot load CA data");
}
s_cert_file = config_getstring(IMAPOPT_TLS_CERT_FILE);
s_key_file = config_getstring(IMAPOPT_TLS_KEY_FILE);
if (!set_cert_stuff(s_ctx, s_cert_file, s_key_file)) {
syslog(LOG_ERR,"TLS server engine: cannot load cert/key data");
return (-1);
}
SSL_CTX_set_tmp_rsa_callback(s_ctx, tmp_rsa_cb);
verify_depth = verifydepth;
if (askcert!=0)
verify_flags |= SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE;
requirecert = config_getswitch(IMAPOPT_TLS_REQUIRE_CERT);
if (requirecert)
verify_flags |= SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT
| SSL_VERIFY_CLIENT_ONCE;
SSL_CTX_set_verify(s_ctx, verify_flags, verify_callback);
if (askcert || requirecert) {
if (CAfile == NULL) {
syslog(LOG_ERR,
"TLS server engine: No CA file specified. "
"Client side certs may not work");
} else {
SSL_CTX_set_client_CA_list(s_ctx, SSL_load_client_CA_file(CAfile));
}
}
tls_serverengine = 1;
return (0);
}
static long bio_dump_cb(BIO * bio, int cmd, const char *argp, int argi,
long argl __attribute__((unused)), long ret)
{
if (!do_dump)
return (ret);
if (cmd == (BIO_CB_READ | BIO_CB_RETURN)) {
printf("read from %08X [%08lX] (%d bytes => %ld (0x%X))",
(unsigned int) bio, (long unsigned int) argp,
argi, ret, (unsigned int) ret);
tls_dump(argp, (int) ret);
return (ret);
} else if (cmd == (BIO_CB_WRITE | BIO_CB_RETURN)) {
printf("write to %08X [%08lX] (%d bytes => %ld (0x%X))",
(unsigned int) bio, (long unsigned int)argp,
argi, ret, (unsigned int) ret);
tls_dump(argp, (int) ret);
}
return (ret);
}
int tls_start_servertls(int readfd, int writefd,
int *layerbits, char **authid, SSL **ret)
{
int sts;
int j;
unsigned int n;
SSL_CIPHER *cipher;
X509 *peer;
const char *tls_protocol = NULL;
const char *tls_cipher_name = NULL;
int tls_cipher_usebits = 0;
int tls_cipher_algbits = 0;
SSL *tls_conn;
int r = 0;
assert(tls_serverengine);
assert(ret);
if (var_imapd_tls_loglevel >= 1)
syslog(LOG_DEBUG, "setting up TLS connection");
if (authid) *authid = NULL;
tls_conn = (SSL *) SSL_new(s_ctx);
if (tls_conn == NULL) {
*ret = NULL;
r = -1;
goto done;
}
SSL_clear(tls_conn);
if ((SSL_set_rfd(tls_conn, readfd) == 0) ||
(SSL_set_wfd(tls_conn, writefd) == 0)) {
r = -1;
goto done;
}
SSL_set_accept_state(tls_conn);
if (var_imapd_tls_loglevel >= 3)
BIO_set_callback(SSL_get_rbio(tls_conn), bio_dump_cb);
if (var_imapd_tls_loglevel >= 3)
do_dump = 1;
if ((sts = SSL_accept(tls_conn)) <= 0) {
SSL_SESSION *session = SSL_get_session(tls_conn);
if (session) {
SSL_CTX_remove_session(s_ctx, session);
}
r = -1;
goto done;
}
if (var_imapd_tls_loglevel < 4)
do_dump = 0;
peer = SSL_get_peer_certificate(tls_conn);
if (peer != NULL) {
char fingerprint[EVP_MAX_MD_SIZE * 3];
char issuer_CN[CCERT_BUFSIZ];
char peer_issuer[CCERT_BUFSIZ];
char peer_CN[CCERT_BUFSIZ];
char peer_subject[CCERT_BUFSIZ];
unsigned char md[EVP_MAX_MD_SIZE];
syslog(LOG_DEBUG, "received client certificate");
X509_NAME_oneline(X509_get_subject_name(peer),
peer_subject, CCERT_BUFSIZ);
syslog(LOG_DEBUG, "subject=%s", peer_subject);
X509_NAME_oneline(X509_get_issuer_name(peer),
peer_issuer, CCERT_BUFSIZ);
if (var_imapd_tls_loglevel >= 2)
syslog(LOG_DEBUG, "issuer=%s", peer_issuer);
if (X509_digest(peer, EVP_md5(), md, &n)) {
for (j = 0; j < (int) n; j++)
{
fingerprint[j * 3] = hexcodes[(md[j] & 0xf0) >> 4];
fingerprint[(j * 3) + 1] = hexcodes[(md[j] & 0x0f)];
if (j + 1 != (int) n) {
fingerprint[(j * 3) + 2] = '_';
} else {
fingerprint[(j * 3) + 2] = '\0';
}
}
if (var_imapd_tls_loglevel >= 2)
syslog(LOG_DEBUG, "fingerprint=%s", fingerprint);
}
X509_NAME_get_text_by_NID(X509_get_subject_name(peer),
NID_commonName, peer_CN, CCERT_BUFSIZ);
X509_NAME_get_text_by_NID(X509_get_issuer_name(peer),
NID_commonName, issuer_CN, CCERT_BUFSIZ);
if (var_imapd_tls_loglevel >= 3)
syslog(LOG_DEBUG, "subject_CN=%s, issuer_CN=%s",
peer_CN, issuer_CN);
if (authid != NULL) {
*authid = peer_CN ? xstrdup(peer_CN) : NULL;
}
X509_free(peer);
}
tls_protocol = SSL_get_version(tls_conn);
cipher = SSL_get_current_cipher(tls_conn);
tls_cipher_name = SSL_CIPHER_get_name(cipher);
tls_cipher_usebits = SSL_CIPHER_get_bits(cipher, &tls_cipher_algbits);
if (layerbits != NULL) {
*layerbits = tls_cipher_usebits;
}
if (authid && *authid) {
syslog(LOG_NOTICE, "starttls: %s with cipher %s (%d/%d bits %s)"
" authenticated as %s",
tls_protocol, tls_cipher_name,
tls_cipher_usebits, tls_cipher_algbits,
SSL_session_reused(tls_conn) ? "reused" : "new",
*authid);
} else {
syslog(LOG_NOTICE, "starttls: %s with cipher %s (%d/%d bits %s)"
" no authentication",
tls_protocol, tls_cipher_name,
tls_cipher_usebits, tls_cipher_algbits,
SSL_session_reused(tls_conn) ? "reused" : "new");
}
done:
if (r && tls_conn) {
SSL_free(tls_conn);
tls_conn = NULL;
}
*ret = tls_conn;
return r;
}
int tls_reset_servertls(SSL **conn)
{
int r = 0;
if (*conn) {
if (TLS_FAST_SHUTDOWN) {
SSL_set_shutdown(*conn,SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN);
}
else {
r = SSL_shutdown(*conn);
if (r == 0) {
r = SSL_shutdown(*conn);
}
if (r == 0) r = -1;
}
SSL_free(*conn);
}
return r;
}
int tls_shutdown_serverengine(void)
{
int r;
if (tls_serverengine && sess_dbopen) {
r = DB->close(sessdb);
if (r) {
syslog(LOG_ERR, "DBERROR: error closing tlsdb: %s",
cyrusdb_strerror(r));
}
sessdb = NULL;
sess_dbopen = 0;
}
return 0;
}
struct prunerock {
int count;
int deletions;
};
static int prune_p(void *rock, const char *id, int idlen,
const char *data, int datalen)
{
struct prunerock *prock = (struct prunerock *) rock;
time_t expire;
prock->count++;
assert(datalen >= (int) sizeof(time_t));
memcpy(&expire, data, sizeof(time_t));
if (var_imapd_tls_loglevel > 0) {
int i;
char idstr[SSL_MAX_SSL_SESSION_ID_LENGTH*2 + 1];
assert(idlen <= SSL_MAX_SSL_SESSION_ID_LENGTH);
for (i = 0; i < idlen; i++) {
sprintf(idstr+i*2, "%02X", (unsigned char) id[i]);
}
syslog(LOG_DEBUG, "found TLS session: id=%s, expire=%s",
idstr, ctime(&expire));
}
return (expire < time(0));
}
static int prune_cb(void *rock, const char *id, int idlen,
const char *data __attribute__((unused)),
int datalen __attribute__((unused)))
{
struct prunerock *prock = (struct prunerock *) rock;
prock->deletions++;
remove_session((unsigned char*) id, idlen);
return 0;
}
int tls_prune_sessions(void)
{
char dbdir[1024];
int ret;
struct prunerock prock;
strlcpy(dbdir, config_dir, sizeof(dbdir));
strlcat(dbdir, FNAME_TLSSESSIONS, sizeof(dbdir));
ret = DB->open(dbdir, CYRUSDB_CREATE, &sessdb);
if (ret != CYRUSDB_OK) {
syslog(LOG_ERR, "DBERROR: opening %s: %s",
dbdir, cyrusdb_strerror(ret));
return 1;
}
else {
sess_dbopen = 1;
prock.count = prock.deletions = 0;
DB->foreach(sessdb, "", 0, &prune_p, &prune_cb, &prock, NULL);
DB->close(sessdb);
sessdb = NULL;
sess_dbopen = 0;
syslog(LOG_NOTICE, "tls_prune: purged %d out of %d entries",
prock.deletions, prock.count);
}
return 0;
}
int tls_get_info(SSL *conn, char *buf, size_t len)
{
int usebits = 0;
int algbits = 0;
usebits = SSL_get_cipher_bits(conn, &algbits);
snprintf(buf, len, "version=%s cipher=%s bits=%d/%d verify=%s",
SSL_get_cipher_version(conn), SSL_get_cipher_name(conn),
usebits, algbits,
SSL_get_verify_result(conn) == X509_V_OK ? "YES" : "NO");
return (strlen(buf));
}
int tls_init_clientengine(int verifydepth,
char *var_tls_cert_file,
char *var_tls_key_file)
{
int off = 0;
int verify_flags = SSL_VERIFY_NONE;
const char *CApath;
const char *CAfile;
char *c_cert_file;
char *c_key_file;
if (tls_clientengine)
return (0);
if (var_proxy_tls_loglevel >= 2)
syslog(LOG_DEBUG, "starting TLS client engine");
SSL_library_init();
SSL_load_error_strings();
if (tls_rand_init() == -1) {
printf("TLS client engine: cannot seed PRNG\n");
return -1;
}
c_ctx = SSL_CTX_new(TLSv1_client_method());
if (c_ctx == NULL) {
return (-1);
};
off |= SSL_OP_ALL;
SSL_CTX_set_options(c_ctx, off);
SSL_CTX_set_info_callback(c_ctx, (void (*)()) apps_ssl_info_callback);
CAfile = config_getstring(IMAPOPT_TLS_CA_FILE);
CApath = config_getstring(IMAPOPT_TLS_CA_PATH);
if ((!SSL_CTX_load_verify_locations(c_ctx, CAfile, CApath)) ||
(!SSL_CTX_set_default_verify_paths(c_ctx))) {
syslog(LOG_NOTICE,"TLS client engine: cannot load CA data");
}
if (strlen(var_tls_cert_file) == 0)
c_cert_file = NULL;
else
c_cert_file = var_tls_cert_file;
if (strlen(var_tls_key_file) == 0)
c_key_file = NULL;
else
c_key_file = var_tls_key_file;
if (c_cert_file || c_key_file) {
if (!set_cert_stuff(c_ctx, c_cert_file, c_key_file)) {
syslog(LOG_ERR,"TLS client engine: cannot load cert/key data");
return (-1);
}
}
SSL_CTX_set_tmp_rsa_callback(c_ctx, tmp_rsa_cb);
verify_depth = verifydepth;
SSL_CTX_set_verify(c_ctx, verify_flags, verify_callback);
tls_clientengine = 1;
return (0);
}
int tls_start_clienttls(int readfd, int writefd,
int *layerbits, char **authid, SSL **ret,
SSL_SESSION **sess)
{
int sts;
SSL_CIPHER *cipher;
X509 *peer;
const char *tls_protocol = NULL;
const char *tls_cipher_name = NULL;
int tls_cipher_usebits = 0;
int tls_cipher_algbits = 0;
SSL *tls_conn;
int r = 0;
assert(tls_clientengine);
assert(ret);
if (var_proxy_tls_loglevel >= 1)
syslog(LOG_DEBUG, "setting up TLS connection");
if (authid) *authid = NULL;
tls_conn = (SSL *) SSL_new(c_ctx);
if (tls_conn == NULL) {
*ret = NULL;
r = -1;
goto done;
}
SSL_clear(tls_conn);
if ((SSL_set_rfd(tls_conn, readfd) == 0) ||
(SSL_set_wfd(tls_conn, writefd) == 0)) {
r = -1;
goto done;
}
SSL_set_connect_state(tls_conn);
if (var_proxy_tls_loglevel >= 3)
BIO_set_callback(SSL_get_rbio(tls_conn), bio_dump_cb);
if (var_proxy_tls_loglevel >= 3)
do_dump = 1;
if (sess && *sess)
SSL_set_session(tls_conn, *sess);
if ((sts = SSL_connect(tls_conn)) <= 0) {
SSL_SESSION *session = SSL_get_session(tls_conn);
if (session) {
SSL_CTX_remove_session(c_ctx, session);
}
if (sess) *sess = NULL;
r = -1;
goto done;
}
if (sess) *sess = SSL_get_session(tls_conn);
if (var_proxy_tls_loglevel < 4)
do_dump = 0;
peer = SSL_get_peer_certificate(tls_conn);
if (peer != NULL) {
char issuer_CN[CCERT_BUFSIZ];
char peer_CN[CCERT_BUFSIZ];
syslog(LOG_DEBUG, "received server certificate");
X509_NAME_get_text_by_NID(X509_get_subject_name(peer),
NID_commonName, peer_CN, CCERT_BUFSIZ);
X509_NAME_get_text_by_NID(X509_get_issuer_name(peer),
NID_commonName, issuer_CN, CCERT_BUFSIZ);
if (var_proxy_tls_loglevel >= 3)
syslog(LOG_DEBUG, "subject_CN=%s, issuer_CN=%s",
peer_CN, issuer_CN);
if (authid != NULL) {
*authid = peer_CN ? xstrdup(peer_CN) : NULL;
}
X509_free(peer);
}
tls_protocol = SSL_get_version(tls_conn);
cipher = SSL_get_current_cipher(tls_conn);
tls_cipher_name = SSL_CIPHER_get_name(cipher);
tls_cipher_usebits = SSL_CIPHER_get_bits(cipher, &tls_cipher_algbits);
if (layerbits != NULL)
*layerbits = tls_cipher_usebits;
syslog(LOG_NOTICE, "starttls: %s with cipher %s (%d/%d bits %s)"
" no authentication",
tls_protocol, tls_cipher_name,
tls_cipher_usebits, tls_cipher_algbits,
SSL_session_reused(tls_conn) ? "reused" : "new");
done:
if (r && tls_conn) {
SSL_free(tls_conn);
tls_conn = NULL;
}
*ret = tls_conn;
return r;
}
#else
int tls_enabled(void)
{
return 0;
}
#endif