#include <sys_defs.h>
#include <ctype.h>
#include <string.h>
#ifdef USE_TLS
#include <vstream.h>
#include <msg.h>
#include <mymalloc.h>
#include <vstring.h>
#include <stringops.h>
#include <argv.h>
#include <name_mask.h>
#include <name_code.h>
#include <mail_params.h>
#include <mail_conf.h>
#define TLS_INTERNAL
#include <tls.h>
char *var_tls_high_clist;
char *var_tls_medium_clist;
char *var_tls_low_clist;
char *var_tls_export_clist;
char *var_tls_null_clist;
int var_tls_daemon_rand_bytes;
char *var_tls_eecdh_strong;
char *var_tls_eecdh_ultra;
bool var_tls_append_def_CA;
char *var_tls_bug_tweaks;
#ifdef VAR_TLS_PREEMPT_CLIST
bool var_tls_preempt_clist;
#endif
int TLScontext_index = -1;
static const NAME_CODE protocol_table[] = {
SSL_TXT_SSLV2, TLS_PROTOCOL_SSLv2,
SSL_TXT_SSLV3, TLS_PROTOCOL_SSLv3,
SSL_TXT_TLSV1, TLS_PROTOCOL_TLSv1,
#ifdef SSL_TXT_TLSV1_1
SSL_TXT_TLSV1_1, TLS_PROTOCOL_TLSv1_1,
#endif
#ifdef SSL_TXT_TLSV1_2
SSL_TXT_TLSV1_2, TLS_PROTOCOL_TLSv1_2,
#endif
0, TLS_PROTOCOL_INVALID,
};
#define NAMEBUG(x) #x, SSL_OP_##x
static const LONG_NAME_MASK ssl_bug_tweaks[] = {
#if defined(SSL_OP_MICROSOFT_SESS_ID_BUG)
NAMEBUG(MICROSOFT_SESS_ID_BUG),
#endif
#if defined(SSL_OP_NETSCAPE_CHALLENGE_BUG)
NAMEBUG(NETSCAPE_CHALLENGE_BUG),
#endif
#if defined(SSL_OP_LEGACY_SERVER_CONNECT)
NAMEBUG(LEGACY_SERVER_CONNECT),
#endif
#if defined(SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG)
NAMEBUG(NETSCAPE_REUSE_CIPHER_CHANGE_BUG),
"CVE-2010-4180", SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG,
#endif
#if defined(SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG)
NAMEBUG(SSLREF2_REUSE_CERT_TYPE_BUG),
#endif
#if defined(SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER)
NAMEBUG(MICROSOFT_BIG_SSLV3_BUFFER),
#endif
#if defined(SSL_OP_MSIE_SSLV2_RSA_PADDING)
NAMEBUG(MSIE_SSLV2_RSA_PADDING),
"CVE-2005-2969", SSL_OP_MSIE_SSLV2_RSA_PADDING,
#endif
#if defined(SSL_OP_SSLEAY_080_CLIENT_DH_BUG)
NAMEBUG(SSLEAY_080_CLIENT_DH_BUG),
#endif
#if defined(SSL_OP_TLS_D5_BUG)
NAMEBUG(TLS_D5_BUG),
#endif
#if defined(SSL_OP_TLS_BLOCK_PADDING_BUG)
NAMEBUG(TLS_BLOCK_PADDING_BUG),
#endif
#if defined(SSL_OP_TLS_ROLLBACK_BUG)
NAMEBUG(TLS_ROLLBACK_BUG),
#endif
#if defined(SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS)
NAMEBUG(DONT_INSERT_EMPTY_FRAGMENTS),
#endif
#if defined(SSL_OP_CRYPTOPRO_TLSEXT_BUG)
NAMEBUG(CRYPTOPRO_TLSEXT_BUG),
#endif
0, 0,
};
const NAME_CODE tls_cipher_grade_table[] = {
"high", TLS_CIPHER_HIGH,
"medium", TLS_CIPHER_MEDIUM,
"low", TLS_CIPHER_LOW,
"export", TLS_CIPHER_EXPORT,
"null", TLS_CIPHER_NULL,
"invalid", TLS_CIPHER_NONE,
0, TLS_CIPHER_NONE,
};
#define TLS_LOG_0 TLS_LOG_NONE
#define TLS_LOG_1 TLS_LOG_SUMMARY
#define TLS_LOG_2 (TLS_LOG_1 | TLS_LOG_VERBOSE | TLS_LOG_CACHE | TLS_LOG_DEBUG)
#define TLS_LOG_3 (TLS_LOG_2 | TLS_LOG_TLSPKTS)
#define TLS_LOG_4 (TLS_LOG_3 | TLS_LOG_ALLPKTS)
static const NAME_MASK tls_log_table[] = {
"0", TLS_LOG_0,
"none", TLS_LOG_NONE,
"1", TLS_LOG_1,
"routine", TLS_LOG_1,
"2", TLS_LOG_2,
"debug", TLS_LOG_2,
"3", TLS_LOG_3,
"ssl-expert", TLS_LOG_3,
"4", TLS_LOG_4,
"ssl-developer", TLS_LOG_4,
"5", TLS_LOG_4,
"6", TLS_LOG_4,
"7", TLS_LOG_4,
"8", TLS_LOG_4,
"9", TLS_LOG_4,
"summary", TLS_LOG_SUMMARY,
"untrusted", TLS_LOG_UNTRUSTED,
"peercert", TLS_LOG_PEERCERT,
"certmatch", TLS_LOG_CERTMATCH,
"verbose", TLS_LOG_VERBOSE,
"cache", TLS_LOG_CACHE,
"ssl-debug", TLS_LOG_DEBUG,
"ssl-handshake-packet-dump", TLS_LOG_TLSPKTS,
"ssl-session-packet-dump", TLS_LOG_TLSPKTS | TLS_LOG_ALLPKTS,
0, 0,
};
typedef struct {
int major;
int minor;
int micro;
int patch;
int status;
} TLS_VINFO;
typedef struct {
const char *ssl_name;
const int alg_bits;
const char *evp_name;
} cipher_probe_t;
static const cipher_probe_t cipher_probes[] = {
"AES", 256, "AES-256-CBC",
"CAMELLIA", 256, "CAMELLIA-256-CBC",
0, 0, 0,
};
int tls_log_mask(const char *log_param, const char *log_level)
{
int mask;
mask = name_mask_opt(log_param, tls_log_table, log_level,
NAME_MASK_ANY_CASE | NAME_MASK_RETURN);
return (mask);
}
static const char *tls_exclude_missing(SSL_CTX *ctx, VSTRING *buf)
{
const char *myname = "tls_exclude_missing";
static ARGV *exclude;
SSL *s = 0;
STACK_OF(SSL_CIPHER) * ciphers;
SSL_CIPHER *c;
const cipher_probe_t *probe;
int alg_bits;
int num;
int i;
if (exclude == 0) {
exclude = argv_alloc(1);
for (probe = cipher_probes; probe->ssl_name; ++probe) {
if (EVP_get_cipherbyname(probe->evp_name))
continue;
ERR_clear_error();
if (s == 0 && (s = SSL_new(ctx)) == 0) {
tls_print_errors();
msg_fatal("%s: error allocating SSL object", myname);
}
if (SSL_set_cipher_list(s, probe->ssl_name) == 0
|| (ciphers = SSL_get_ciphers(s)) == 0
|| (num = sk_SSL_CIPHER_num(ciphers)) == 0) {
ERR_clear_error();
continue;
}
for (i = 0; i < num; ++i) {
c = sk_SSL_CIPHER_value(ciphers, i);
(void) SSL_CIPHER_get_bits(c, &alg_bits);
if (alg_bits == probe->alg_bits)
argv_add(exclude, SSL_CIPHER_get_name(c), ARGV_END);
}
}
if (s != 0)
SSL_free(s);
}
for (i = 0; i < exclude->argc; ++i)
vstring_sprintf_append(buf, ":!%s", exclude->argv[i]);
return (vstring_str(buf));
}
static const char *tls_apply_cipher_list(TLS_APPL_STATE *app_ctx,
const char *context, VSTRING *spec)
{
const char *new = tls_exclude_missing(app_ctx->ssl_ctx, spec);
ERR_clear_error();
if (SSL_CTX_set_cipher_list(app_ctx->ssl_ctx, new) == 0) {
tls_print_errors();
vstring_sprintf(app_ctx->why, "invalid %s cipher list: \"%s\"",
context, new);
return (0);
}
return (new);
}
int tls_protocol_mask(const char *plist)
{
char *save;
char *tok;
char *cp;
int code;
int exclude = 0;
int include = 0;
save = cp = mystrdup(plist);
while ((tok = mystrtok(&cp, "\t\n\r ,:")) != 0) {
if (*tok == '!')
exclude |= code =
name_code(protocol_table, NAME_CODE_FLAG_NONE, ++tok);
else
include |= code =
name_code(protocol_table, NAME_CODE_FLAG_NONE, tok);
if (code == TLS_PROTOCOL_INVALID)
return TLS_PROTOCOL_INVALID;
}
myfree(save);
return (include ? (exclude | (TLS_KNOWN_PROTOCOLS & ~include)) : exclude);
}
void tls_param_init(void)
{
static const CONFIG_STR_TABLE str_table[] = {
VAR_TLS_HIGH_CLIST, DEF_TLS_HIGH_CLIST, &var_tls_high_clist, 1, 0,
VAR_TLS_MEDIUM_CLIST, DEF_TLS_MEDIUM_CLIST, &var_tls_medium_clist, 1, 0,
VAR_TLS_LOW_CLIST, DEF_TLS_LOW_CLIST, &var_tls_low_clist, 1, 0,
VAR_TLS_EXPORT_CLIST, DEF_TLS_EXPORT_CLIST, &var_tls_export_clist, 1, 0,
VAR_TLS_NULL_CLIST, DEF_TLS_NULL_CLIST, &var_tls_null_clist, 1, 0,
VAR_TLS_EECDH_STRONG, DEF_TLS_EECDH_STRONG, &var_tls_eecdh_strong, 1, 0,
VAR_TLS_EECDH_ULTRA, DEF_TLS_EECDH_ULTRA, &var_tls_eecdh_ultra, 1, 0,
VAR_TLS_BUG_TWEAKS, DEF_TLS_BUG_TWEAKS, &var_tls_bug_tweaks, 0, 0,
0,
};
static const CONFIG_INT_TABLE int_table[] = {
VAR_TLS_DAEMON_RAND_BYTES, DEF_TLS_DAEMON_RAND_BYTES, &var_tls_daemon_rand_bytes, 1, 0,
0,
};
static const CONFIG_BOOL_TABLE bool_table[] = {
VAR_TLS_APPEND_DEF_CA, DEF_TLS_APPEND_DEF_CA, &var_tls_append_def_CA,
#if OPENSSL_VERSION_NUMBER >= 0x0090700fL
VAR_TLS_PREEMPT_CLIST, DEF_TLS_PREEMPT_CLIST, &var_tls_preempt_clist,
#endif
0,
};
static int init_done;
if (init_done)
return;
init_done = 1;
get_mail_conf_str_table(str_table);
get_mail_conf_int_table(int_table);
get_mail_conf_bool_table(bool_table);
}
const char *tls_set_ciphers(TLS_APPL_STATE *app_ctx, const char *context,
const char *grade, const char *exclusions)
{
const char *myname = "tls_set_ciphers";
static VSTRING *buf;
int new_grade;
char *save;
char *cp;
char *tok;
const char *new_list;
new_grade = tls_cipher_grade(grade);
if (new_grade == TLS_CIPHER_NONE) {
vstring_sprintf(app_ctx->why, "invalid %s cipher grade: \"%s\"",
context, grade);
return (0);
}
if (buf == 0)
buf = vstring_alloc(10);
VSTRING_RESET(buf);
if (app_ctx->cipher_list) {
if (new_grade == app_ctx->cipher_grade
&& strcmp(app_ctx->cipher_exclusions, exclusions) == 0)
return (app_ctx->cipher_list);
app_ctx->cipher_grade = TLS_CIPHER_NONE;
myfree(app_ctx->cipher_exclusions);
app_ctx->cipher_exclusions = 0;
myfree(app_ctx->cipher_list);
app_ctx->cipher_list = 0;
}
switch (new_grade) {
case TLS_CIPHER_HIGH:
vstring_strcpy(buf, var_tls_high_clist);
break;
case TLS_CIPHER_MEDIUM:
vstring_strcpy(buf, var_tls_medium_clist);
break;
case TLS_CIPHER_LOW:
vstring_strcpy(buf, var_tls_low_clist);
break;
case TLS_CIPHER_EXPORT:
vstring_strcpy(buf, var_tls_export_clist);
break;
case TLS_CIPHER_NULL:
vstring_strcpy(buf, var_tls_null_clist);
break;
default:
msg_panic("invalid %s cipher grade: %d", context, new_grade);
}
if (VSTRING_LEN(buf) == 0)
msg_panic("%s: empty \"%s\" cipherlist", myname, grade);
#define CIPHER_SEP "\t\n\r ,:"
if (exclusions != 0) {
cp = save = mystrdup(exclusions);
while ((tok = mystrtok(&cp, CIPHER_SEP)) != 0) {
if (strchr("!+-@", *tok)) {
vstring_sprintf(app_ctx->why,
"invalid unary '!+-@' in %s cipher "
"exclusion: \"%s\"", context, tok);
return (0);
}
vstring_sprintf_append(buf, ":!%s", tok);
}
myfree(save);
}
if ((new_list = tls_apply_cipher_list(app_ctx, context, buf)) == 0)
return (0);
app_ctx->cipher_grade = new_grade;
app_ctx->cipher_exclusions = mystrdup(exclusions);
return (app_ctx->cipher_list = mystrdup(new_list));
}
TLS_APPL_STATE *tls_alloc_app_context(SSL_CTX *ssl_ctx, int log_mask)
{
TLS_APPL_STATE *app_ctx;
app_ctx = (TLS_APPL_STATE *) mymalloc(sizeof(*app_ctx));
memset((char *) app_ctx, 0, sizeof(*app_ctx));
app_ctx->ssl_ctx = ssl_ctx;
app_ctx->log_mask = log_mask;
app_ctx->cipher_grade = TLS_CIPHER_NONE;
app_ctx->cipher_exclusions = 0;
app_ctx->cipher_list = 0;
app_ctx->cache_type = 0;
app_ctx->why = vstring_alloc(1);
return (app_ctx);
}
void tls_free_app_context(TLS_APPL_STATE *app_ctx)
{
if (app_ctx->ssl_ctx)
SSL_CTX_free(app_ctx->ssl_ctx);
if (app_ctx->cache_type)
myfree(app_ctx->cache_type);
if (app_ctx->cipher_exclusions)
myfree(app_ctx->cipher_exclusions);
if (app_ctx->cipher_list)
myfree(app_ctx->cipher_list);
if (app_ctx->why)
vstring_free(app_ctx->why);
myfree((char *) app_ctx);
}
TLS_SESS_STATE *tls_alloc_sess_context(int log_mask, const char *namaddr)
{
TLS_SESS_STATE *TLScontext;
TLScontext = (TLS_SESS_STATE *) mymalloc(sizeof(TLS_SESS_STATE));
memset((char *) TLScontext, 0, sizeof(*TLScontext));
TLScontext->con = 0;
TLScontext->cache_type = 0;
TLScontext->serverid = 0;
TLScontext->peer_CN = 0;
TLScontext->issuer_CN = 0;
TLScontext->peer_fingerprint = 0;
TLScontext->peer_pkey_fprint = 0;
TLScontext->protocol = 0;
TLScontext->cipher_name = 0;
TLScontext->log_mask = log_mask;
TLScontext->namaddr = lowercase(mystrdup(namaddr));
TLScontext->fpt_dgst = 0;
return (TLScontext);
}
void tls_free_context(TLS_SESS_STATE *TLScontext)
{
if (TLScontext->con != 0)
SSL_free(TLScontext->con);
if (TLScontext->namaddr)
myfree(TLScontext->namaddr);
if (TLScontext->serverid)
myfree(TLScontext->serverid);
if (TLScontext->peer_CN)
myfree(TLScontext->peer_CN);
if (TLScontext->issuer_CN)
myfree(TLScontext->issuer_CN);
if (TLScontext->peer_fingerprint)
myfree(TLScontext->peer_fingerprint);
if (TLScontext->peer_pkey_fprint)
myfree(TLScontext->peer_pkey_fprint);
if (TLScontext->fpt_dgst)
myfree(TLScontext->fpt_dgst);
myfree((char *) TLScontext);
}
static void tls_version_split(long version, TLS_VINFO *info)
{
if (version < 0x0930) {
info->status = 0;
info->patch = version & 0x0f;
version >>= 4;
info->micro = version & 0x0f;
version >>= 4;
info->minor = version & 0x0f;
version >>= 4;
info->major = version & 0x0f;
} else if (version < 0x00905800L) {
info->patch = version & 0xff;
version >>= 8;
info->status = version & 0xf;
version >>= 4;
info->micro = version & 0xff;
version >>= 8;
info->minor = version & 0xff;
version >>= 8;
info->major = version & 0xff;
} else {
info->status = version & 0xf;
version >>= 4;
info->patch = version & 0xff;
version >>= 8;
info->micro = version & 0xff;
version >>= 8;
info->minor = version & 0xff;
version >>= 8;
info->major = version & 0xff;
if (version < 0x00906000L)
info->patch &= ~0x80;
}
}
void tls_check_version(void)
{
TLS_VINFO hdr_info;
TLS_VINFO lib_info;
tls_version_split(OPENSSL_VERSION_NUMBER, &hdr_info);
tls_version_split(SSLeay(), &lib_info);
if (lib_info.major != hdr_info.major
|| lib_info.minor != hdr_info.minor
|| lib_info.micro != hdr_info.micro)
msg_warn("run-time library vs. compile-time header version mismatch: "
"OpenSSL %d.%d.%d may not be compatible with OpenSSL %d.%d.%d",
lib_info.major, lib_info.minor, lib_info.micro,
hdr_info.major, hdr_info.minor, hdr_info.micro);
}
long tls_bug_bits(void)
{
long bits = SSL_OP_ALL;
#if OPENSSL_VERSION_NUMBER >= 0x00908000L
long lib_version = SSLeay();
if (lib_version >= 0x00908000L && lib_version <= 0x0090802fL) {
STACK_OF(SSL_COMP) * comp_methods;
comp_methods = SSL_COMP_get_compression_methods();
if (comp_methods != 0 && sk_SSL_COMP_num(comp_methods) > 0)
bits &= ~SSL_OP_TLS_BLOCK_PADDING_BUG;
}
#endif
if (*var_tls_bug_tweaks) {
bits &= ~long_name_mask_opt(VAR_TLS_BUG_TWEAKS, ssl_bug_tweaks,
var_tls_bug_tweaks, NAME_MASK_ANY_CASE |
NAME_MASK_NUMBER | NAME_MASK_WARN);
}
return (bits);
}
void tls_print_errors(void)
{
unsigned long err;
char buffer[1024];
const char *file;
const char *data;
int line;
int flags;
unsigned long thread;
thread = CRYPTO_thread_id();
while ((err = ERR_get_error_line_data(&file, &line, &data, &flags)) != 0) {
ERR_error_string_n(err, buffer, sizeof(buffer));
if (flags & ERR_TXT_STRING)
msg_warn("TLS library problem: %lu:%s:%s:%d:%s:",
thread, buffer, file, line, data);
else
msg_warn("TLS library problem: %lu:%s:%s:%d:",
thread, buffer, file, line);
}
}
void tls_info_callback(const SSL *s, int where, int ret)
{
char *str;
int w;
w = where & ~SSL_ST_MASK;
if (w & SSL_ST_CONNECT)
str = "SSL_connect";
else if (w & SSL_ST_ACCEPT)
str = "SSL_accept";
else
str = "unknown";
if (where & SSL_CB_LOOP) {
msg_info("%s:%s", str, SSL_state_string_long((SSL *) s));
} else if (where & SSL_CB_ALERT) {
str = (where & SSL_CB_READ) ? "read" : "write";
if ((ret & 0xff) != SSL3_AD_CLOSE_NOTIFY)
msg_info("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)
msg_info("%s:failed in %s",
str, SSL_state_string_long((SSL *) s));
else if (ret < 0) {
#ifndef LOG_NON_ERROR_STATES
switch (SSL_get_error((SSL *) s, ret)) {
case SSL_ERROR_WANT_READ:
case SSL_ERROR_WANT_WRITE:
break;
default:
#endif
msg_info("%s:error in %s",
str, SSL_state_string_long((SSL *) s));
#ifndef LOG_NON_ERROR_STATES
}
#endif
}
}
}
#define TRUNCATE_SPACE_NULL
#define DUMP_WIDTH 16
#define VERT_SPLIT 7
static void tls_dump_buffer(const unsigned char *start, int len)
{
VSTRING *buf = vstring_alloc(100);
const unsigned char *last = start + len - 1;
const unsigned char *row;
const unsigned char *col;
int ch;
#ifdef TRUNCATE_SPACE_NULL
while (last >= start && (*last == ' ' || *last == 0))
last--;
#endif
for (row = start; row <= last; row += DUMP_WIDTH) {
VSTRING_RESET(buf);
vstring_sprintf(buf, "%04lx ", (unsigned long) (row - start));
for (col = row; col < row + DUMP_WIDTH; col++) {
if (col > last) {
vstring_strcat(buf, " ");
} else {
ch = *col;
vstring_sprintf_append(buf, "%02x%c",
ch, col - row == VERT_SPLIT ? '|' : ' ');
}
}
VSTRING_ADDCH(buf, ' ');
for (col = row; col < row + DUMP_WIDTH; col++) {
if (col > last)
break;
ch = *col;
if (!ISPRINT(ch))
ch = '.';
VSTRING_ADDCH(buf, ch);
if (col - row == VERT_SPLIT)
VSTRING_ADDCH(buf, ' ');
}
VSTRING_TERMINATE(buf);
msg_info("%s", vstring_str(buf));
}
#ifdef TRUNCATE_SPACE_NULL
if ((last + 1) - start < len)
msg_info("%04lx - <SPACES/NULLS>",
(unsigned long) ((last + 1) - start));
#endif
vstring_free(buf);
}
long tls_bio_dump_cb(BIO *bio, int cmd, const char *argp, int argi,
long unused_argl, long ret)
{
if (cmd == (BIO_CB_READ | BIO_CB_RETURN)) {
msg_info("read from %08lX [%08lX] (%d bytes => %ld (0x%lX))",
(unsigned long) bio, (unsigned long) argp, argi,
ret, (unsigned long) ret);
tls_dump_buffer((unsigned char *) argp, (int) ret);
} else if (cmd == (BIO_CB_WRITE | BIO_CB_RETURN)) {
msg_info("write to %08lX [%08lX] (%d bytes => %ld (0x%lX))",
(unsigned long) bio, (unsigned long) argp, argi,
ret, (unsigned long) ret);
tls_dump_buffer((unsigned char *) argp, (int) ret);
}
return (ret);
}
#else
int tls_dummy_for_broken_linkers;
#endif