#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 <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;
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,
0, TLS_PROTOCOL_INVALID,
};
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,
};
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,
};
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,
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 int init_done;
if (init_done)
return;
init_done = 1;
get_mail_conf_str_table(str_table);
get_mail_conf_int_table(int_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)
{
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->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_level, 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->internal_bio = 0;
TLScontext->network_bio = 0;
TLScontext->cache_type = 0;
TLScontext->serverid = 0;
TLScontext->peer_CN = 0;
TLScontext->issuer_CN = 0;
TLScontext->peer_fingerprint = 0;
TLScontext->protocol = 0;
TLScontext->cipher_name = 0;
TLScontext->log_level = log_level;
TLScontext->namaddr = lowercase(mystrdup(namaddr));
return (TLScontext);
}
void tls_free_context(TLS_SESS_STATE *TLScontext)
{
if (TLScontext->con != 0)
SSL_free(TLScontext->con);
if (TLScontext->network_bio)
BIO_free(TLScontext->network_bio);
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);
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
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