#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>
#define TLS_INTERNAL
#include <tls.h>
int TLScontext_index = -1;
int TLSscache_index = -1;
NAME_MASK tls_protocol_table[] = {
SSL_TXT_SSLV2, TLS_PROTOCOL_SSLv2,
SSL_TXT_SSLV3, TLS_PROTOCOL_SSLv3,
SSL_TXT_TLSV1, TLS_PROTOCOL_TLSv1,
0, 0,
};
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;
NAME_CODE tls_cipher_level_table[] = {
"high", TLS_CIPHER_HIGH,
"medium", TLS_CIPHER_MEDIUM,
"low", TLS_CIPHER_LOW,
"export", TLS_CIPHER_EXPORT,
"null", TLS_CIPHER_NULL,
0, TLS_CIPHER_NONE,
};
typedef struct {
int major;
int minor;
int micro;
int patch;
int status;
} TLS_VINFO;
typedef struct {
char *ssl_name;
int alg_bits;
char *evp_name;
} cipher_probe_t;
static cipher_probe_t cipher_probes[] = {
"AES", 256, "AES-256-CBC",
"CAMELLIA", 256, "CAMELLIA-256-CBC",
0, 0, 0,
};
static void 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;
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]);
}
const char *tls_set_cipher_list(SSL_CTX *ssl_ctx, const char *spec)
{
static VSTRING *buf;
const char *ex_spec;
if (buf == 0)
buf = vstring_alloc(10);
vstring_strcpy(buf, spec);
tls_exclude_missing(ssl_ctx, buf);
ex_spec = vstring_str(buf);
ERR_clear_error();
if (SSL_CTX_set_cipher_list(ssl_ctx, ex_spec) != 0)
return (ex_spec);
tls_print_errors();
return (0);
}
const char *tls_cipher_list(int cipher_level,...)
{
const char *myname = "tls_cipher_list";
static VSTRING *buf;
va_list ap;
const char *exclude;
char *tok;
char *save;
char *cp;
buf = buf ? buf : vstring_alloc(10);
VSTRING_RESET(buf);
switch (cipher_level) {
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;
case TLS_CIPHER_NONE:
return 0;
default:
msg_panic("%s: invalid cipher grade: %d", myname, cipher_level);
}
if (VSTRING_LEN(buf) == 0)
msg_panic("%s: empty cipherlist", myname);
va_start(ap, cipher_level);
while ((exclude = va_arg(ap, char *)) != 0) {
if (*exclude == '\0')
continue;
save = cp = mystrdup(exclude);
while ((tok = mystrtok(&cp, "\t\n\r ,:")) != 0) {
if (strchr("!+-@", *tok)) {
msg_warn("%s: can't exclude '!+-@' modifiers, '%s' ignored",
myname, tok);
continue;
}
vstring_sprintf_append(buf, ":!%s", tok);
}
myfree(save);
}
va_end(ap);
return (vstring_str(buf));
}
TLScontext_t *tls_alloc_context(int log_level, const char *peername)
{
TLScontext_t *TLScontext;
TLScontext = (TLScontext_t *) mymalloc(sizeof(TLScontext_t));
memset((char *) TLScontext, 0, sizeof(*TLScontext));
TLScontext->con = 0;
TLScontext->internal_bio = 0;
TLScontext->network_bio = 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->peername = lowercase(mystrdup(peername));
return (TLScontext);
}
void tls_free_context(TLScontext_t *TLScontext)
{
if (TLScontext->con != 0)
SSL_free(TLScontext->con);
if (TLScontext->network_bio)
BIO_free(TLScontext->network_bio);
if (TLScontext->peername)
myfree(TLScontext->peername);
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) {
msg_info("%s:error in %s",
str, SSL_state_string_long((SSL *) s));
}
}
}
#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