Commit fa86f2de authored by Philipp Schafft's avatar Philipp Schafft 🦁

Merge branch 'ph3-update-TLS'

parents b7087c38 cd9c8420
......@@ -16,7 +16,7 @@ noinst_HEADERS = admin.h cfgfile.h logging.h sighandler.h connection.h \
format_kate.h format_skeleton.h format_opus.h
icecast_SOURCES = cfgfile.c main.c logging.c sighandler.c connection.c global.c \
util.c slave.c source.c stats.c refbuf.c client.c playlist.c \
xslt.c fserve.c admin.c md5.c matchfile.c \
xslt.c fserve.c admin.c md5.c matchfile.c tls.c \
format.c format_ogg.c format_mp3.c format_midi.c format_flac.c format_ebml.c \
format_kate.c format_skeleton.c format_opus.c \
event.c event_log.c event_exec.c \
......
......@@ -695,11 +695,7 @@ static inline xmlNodePtr __add_listener(client_t *client,
if (client->role)
xmlNewTextChild(node, NULL, XMLSTR("role"), XMLSTR(client->role));
#ifdef HAVE_OPENSSL
xmlNewTextChild(node, NULL, XMLSTR("tls"), XMLSTR(client->con->ssl ? "true" : "false"));
#else
xmlNewTextChild(node, NULL, XMLSTR("tls"), XMLSTR("false"));
#endif
xmlNewTextChild(node, NULL, XMLSTR("tls"), XMLSTR(client->con->tls ? "true" : "false"));
return node;
}
......
......@@ -34,11 +34,13 @@
#include "util.h"
#include "auth.h"
#include "event.h"
#include "tls.h"
/* for config_reread_config() */
#include "yp.h"
#include "fserve.h"
#include "stats.h"
#include "connection.h"
#define CATMODULE "CONFIG"
#define CONFIG_DEFAULT_LOCATION "Earth"
......@@ -233,6 +235,60 @@ static inline int __parse_public(const char *str)
return util_str_to_bool(str);
}
/* This converts TLS mode strings to (tlsmode_t).
* In older versions of Icecast2 this was just a bool.
* So we need to handle boolean values as well.
* See also: util_str_to_bool().
*/
static tlsmode_t str_to_tlsmode(const char *str) {
/* consider NULL and empty strings as auto mode */
if (!str || !*str)
return ICECAST_TLSMODE_AUTO;
if (strcasecmp(str, "disabled") == 0) {
return ICECAST_TLSMODE_DISABLED;
} else if (strcasecmp(str, "auto") == 0) {
return ICECAST_TLSMODE_AUTO;
} else if (strcasecmp(str, "auto_no_plain") == 0) {
return ICECAST_TLSMODE_AUTO_NO_PLAIN;
} else if (strcasecmp(str, "rfc2817") == 0) {
return ICECAST_TLSMODE_RFC2817;
} else if (strcasecmp(str, "rfc2818") == 0 ||
/* boolean-style values */
strcasecmp(str, "true") == 0 ||
strcasecmp(str, "yes") == 0 ||
strcasecmp(str, "on") == 0 ) {
return ICECAST_TLSMODE_RFC2818;
}
/* old style numbers: consider everyting non-zero RFC2818 */
if (atoi(str))
return ICECAST_TLSMODE_RFC2818;
/* we default to auto mode */
return ICECAST_TLSMODE_AUTO;
}
/* This checks for the TLS implementation of a node */
static int __check_node_impl(xmlNodePtr node, const char *def)
{
char *impl;
int res;
impl = (char *)xmlGetProp(node, XMLSTR("implementation"));
if (!impl)
impl = (char *)xmlGetProp(node, XMLSTR("impl"));
if (!impl)
impl = (char *)xmlStrdup(XMLSTR(def));
res = tls_check_impl(impl);
xmlFree(impl);
return res;
}
static void __append_old_style_auth(auth_stack_t **stack,
const char *name,
const char *type,
......@@ -532,8 +588,6 @@ void config_clear(ice_config_t *c)
if (c->webroot_dir) xmlFree(c->webroot_dir);
if (c->adminroot_dir) xmlFree(c->adminroot_dir);
if (c->null_device) xmlFree(c->null_device);
if (c->cert_file) xmlFree(c->cert_file);
if (c->cipher_list) xmlFree(c->cipher_list);
if (c->pidfile) xmlFree(c->pidfile);
if (c->banfile) xmlFree(c->banfile);
if (c->allowfile) xmlFree(c->allowfile);
......@@ -549,6 +603,10 @@ void config_clear(ice_config_t *c)
if (c->group) xmlFree(c->group);
if (c->mimetypes_fn) xmlFree(c->mimetypes_fn);
if (c->tls_context.cert_file) xmlFree(c->tls_context.cert_file);
if (c->tls_context.key_file) xmlFree(c->tls_context.key_file);
if (c->tls_context.cipher_list) xmlFree(c->tls_context.cipher_list);
event_registration_release(c->event);
while ((c->listen_sock = config_clear_listener(c->listen_sock)));
......@@ -636,6 +694,7 @@ void config_reread_config(void)
config_set_config(&new_config);
config = config_get_config_unlocked();
restart_logging(config);
connection_reread_config(config);
yp_recheck_config(config);
fserve_recheck_mime_types(config);
stats_global(config);
......@@ -766,8 +825,6 @@ static void _set_defaults(ice_config_t *configuration)
->base_dir = (char *) xmlCharStrdup(CONFIG_DEFAULT_BASE_DIR);
configuration
->log_dir = (char *) xmlCharStrdup(CONFIG_DEFAULT_LOG_DIR);
configuration
->cipher_list = (char *) xmlCharStrdup(CONFIG_DEFAULT_CIPHER_LIST);
configuration
->null_device = (char *) xmlCharStrdup(CONFIG_DEFAULT_NULL_FILE);
configuration
......@@ -795,6 +852,8 @@ static void _set_defaults(ice_config_t *configuration)
/* default to a typical prebuffer size used by clients */
configuration
->burst_size = CONFIG_DEFAULT_BURST_SIZE;
configuration->tls_context
.cipher_list = (char *) xmlCharStrdup(CONFIG_DEFAULT_CIPHER_LIST);
}
static inline void __check_hostname(ice_config_t *configuration)
......@@ -1676,7 +1735,7 @@ static void _parse_listen_socket(xmlDocPtr doc,
} else if (xmlStrcmp(node->name, XMLSTR("tls")) == 0 ||
xmlStrcmp(node->name, XMLSTR("ssl")) == 0) {
tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
listener->ssl = util_str_to_bool(tmp);
listener->tls = str_to_tlsmode(tmp);
if(tmp)
xmlFree(tmp);
} else if (xmlStrcmp(node->name, XMLSTR("shoutcast-compat")) == 0) {
......@@ -1882,14 +1941,24 @@ static void _parse_paths(xmlDocPtr doc,
configuration->allowfile = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
} else if (xmlStrcmp(node->name, XMLSTR("tls-certificate")) == 0 ||
xmlStrcmp(node->name, XMLSTR("ssl-certificate")) == 0) {
if (configuration->cert_file)
xmlFree(configuration->cert_file);
configuration->cert_file = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
if (__check_node_impl(node, "generic") != 0) {
ICECAST_LOG_WARN("Node %s uses unsupported implementation.", node->name);
continue;
}
if (configuration->tls_context.cert_file)
xmlFree(configuration->tls_context.cert_file);
configuration->tls_context.cert_file = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
} else if (xmlStrcmp(node->name, XMLSTR("tls-allowed-ciphers")) == 0 ||
xmlStrcmp(node->name, XMLSTR("ssl-allowed-ciphers")) == 0) {
if (configuration->cipher_list)
xmlFree(configuration->cipher_list);
configuration->cipher_list = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
if (__check_node_impl(node, "openssl") != 0) {
ICECAST_LOG_WARN("Node %s uses unsupported implementation.", node->name);
continue;
}
if (configuration->tls_context.cipher_list)
xmlFree(configuration->tls_context.cipher_list);
configuration->tls_context.cipher_list = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
} else if (xmlStrcmp(node->name, XMLSTR("webroot")) == 0) {
if (!(temp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1))) {
ICECAST_LOG_WARN("<webroot> setting must not be empty.");
......@@ -2002,6 +2071,54 @@ static void _parse_logging(xmlDocPtr doc,
} while ((node = node->next));
}
static void _parse_tls_context(xmlDocPtr doc,
xmlNodePtr node,
ice_config_t *configuration)
{
config_tls_context_t *context = &configuration->tls_context;
node = node->xmlChildrenNode;
do {
if (node == NULL)
break;
if (xmlIsBlankNode(node))
continue;
if (xmlStrcmp(node->name, XMLSTR("tls-certificate")) == 0) {
if (__check_node_impl(node, "generic") != 0) {
ICECAST_LOG_WARN("Node %s uses unsupported implementation.", node->name);
continue;
}
if (context->cert_file)
xmlFree(context->cert_file);
context->cert_file = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
} else if (xmlStrcmp(node->name, XMLSTR("tls-key")) == 0) {
if (__check_node_impl(node, "generic") != 0) {
ICECAST_LOG_WARN("Node %s uses unsupported implementation.", node->name);
continue;
}
if (context->key_file)
xmlFree(context->key_file);
context->key_file = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
} else if (xmlStrcmp(node->name, XMLSTR("tls-allowed-ciphers")) == 0) {
if (__check_node_impl(node, "openssl") != 0) {
ICECAST_LOG_WARN("Node %s uses unsupported implementation.", node->name);
continue;
}
if (context->cipher_list)
xmlFree(context->cipher_list);
context->cipher_list = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
} else {
ICECAST_LOG_ERROR("Unknown config tag: %s", node->name);
}
} while ((node = node->next));
}
static void _parse_security(xmlDocPtr doc,
xmlNodePtr node,
ice_config_t *configuration)
......@@ -2020,6 +2137,8 @@ static void _parse_security(xmlDocPtr doc,
configuration->chroot = util_str_to_bool(tmp);
if (tmp)
xmlFree(tmp);
} else if (xmlStrcmp(node->name, XMLSTR("tls-context")) == 0) {
_parse_tls_context(doc, node, configuration);
} else if (xmlStrcmp(node->name, XMLSTR("changeowner")) == 0) {
configuration->chuid = 1;
oldnode = node;
......
......@@ -172,9 +172,15 @@ typedef struct _listener_t {
char *bind_address;
int shoutcast_compat;
char *shoutcast_mount;
int ssl;
tlsmode_t tls;
} listener_t;
typedef struct _config_tls_context {
char *cert_file;
char *key_file;
char *cipher_list;
} config_tls_context_t;
typedef struct ice_config_tag {
char *config_filename;
......@@ -229,8 +235,6 @@ typedef struct ice_config_tag {
char *null_device;
char *banfile;
char *allowfile;
char *cert_file;
char *cipher_list;
char *webroot_dir;
char *adminroot_dir;
aliases *aliases;
......@@ -242,6 +246,8 @@ typedef struct ice_config_tag {
int logsize;
int logarchive;
config_tls_context_t tls_context;
int chroot;
int chuid;
char *user;
......
......@@ -105,26 +105,24 @@ static inline void client_reuseconnection(client_t *client) {
client->con->sock = -1; /* TODO: do not use magic */
/* handle to keep the TLS connection */
#ifdef HAVE_OPENSSL
if (client->con->ssl) {
if (client->con->tls) {
/* AHhhggrr.. That pain....
* stealing SSL state...
* stealing TLS state...
*/
con->ssl = client->con->ssl;
con->tls = client->con->tls;
con->read = client->con->read;
con->send = client->con->send;
client->con->ssl = NULL;
client->con->tls = NULL;
client->con->read = NULL;
client->con->send = NULL;
}
#endif
client->reuse = ICECAST_REUSE_CLOSE;
client_destroy(client);
if (reuse == ICECAST_REUSE_UPGRADETLS)
connection_uses_ssl(con);
connection_uses_tls(con);
connection_queue(con);
}
......
......@@ -59,6 +59,7 @@
#include "admin.h"
#include "auth.h"
#include "matchfile.h"
#include "tls.h"
#define CATMODULE "connection"
......@@ -97,10 +98,8 @@ static int _initialized = 0;
static volatile client_queue_t *_req_queue = NULL, **_req_queue_tail = &_req_queue;
static volatile client_queue_t *_con_queue = NULL, **_con_queue_tail = &_con_queue;
static int ssl_ok;
#ifdef HAVE_OPENSSL
static SSL_CTX *ssl_ctx;
#endif
static int tls_ok;
static tls_ctx_t *tls_ctx;
/* filtering client connection based on IP */
static matchfile_t *banned_ip, *allowed_ip;
......@@ -108,6 +107,7 @@ static matchfile_t *banned_ip, *allowed_ip;
rwlock_t _source_shutdown_rwlock;
static void _handle_connection(void);
static void get_tls_certificate(ice_config_t *config);
void connection_initialize(void)
{
......@@ -131,9 +131,7 @@ void connection_shutdown(void)
if (!_initialized)
return;
#ifdef HAVE_OPENSSL
SSL_CTX_free (ssl_ctx);
#endif
tls_ctx_unref(tls_ctx);
matchfile_release(banned_ip);
matchfile_release(allowed_ip);
......@@ -145,6 +143,11 @@ void connection_shutdown(void)
_initialized = 0;
}
void connection_reread_config(struct ice_config_tag *config)
{
get_tls_certificate(config);
}
static unsigned long _next_connection_id(void)
{
unsigned long id;
......@@ -157,80 +160,50 @@ static unsigned long _next_connection_id(void)
}
#ifdef HAVE_OPENSSL
static void get_ssl_certificate(ice_config_t *config)
#ifdef ICECAST_CAP_TLS
static void get_tls_certificate(ice_config_t *config)
{
SSL_METHOD *method;
long ssl_opts;
config->tls_ok = ssl_ok = 0;
SSL_load_error_strings(); /* readable error messages */
SSL_library_init(); /* initialize library */
method = SSLv23_server_method();
ssl_ctx = SSL_CTX_new(method);
ssl_opts = SSL_CTX_get_options(ssl_ctx);
#ifdef SSL_OP_NO_COMPRESSION
SSL_CTX_set_options(ssl_ctx, ssl_opts|SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3|SSL_OP_NO_COMPRESSION);
#else
SSL_CTX_set_options(ssl_ctx, ssl_opts|SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3);
#endif
const char *keyfile;
do {
if (config->cert_file == NULL)
break;
if (SSL_CTX_use_certificate_chain_file (ssl_ctx, config->cert_file) <= 0) {
ICECAST_LOG_WARN("Invalid cert file %s", config->cert_file);
break;
}
if (SSL_CTX_use_PrivateKey_file (ssl_ctx, config->cert_file, SSL_FILETYPE_PEM) <= 0) {
ICECAST_LOG_WARN("Invalid private key file %s", config->cert_file);
break;
}
if (!SSL_CTX_check_private_key (ssl_ctx)) {
ICECAST_LOG_ERROR("Invalid %s - Private key does not match cert public key", config->cert_file);
break;
}
if (SSL_CTX_set_cipher_list(ssl_ctx, config->cipher_list) <= 0) {
ICECAST_LOG_WARN("Invalid cipher list: %s", config->cipher_list);
}
config->tls_ok = ssl_ok = 1;
ICECAST_LOG_INFO("Certificate found at %s", config->cert_file);
ICECAST_LOG_INFO("Using ciphers %s", config->cipher_list);
config->tls_ok = tls_ok = 0;
keyfile = config->tls_context.key_file;
if (!keyfile)
keyfile = config->tls_context.cert_file;
tls_ctx_unref(tls_ctx);
tls_ctx = tls_ctx_new(config->tls_context.cert_file, keyfile, config->tls_context.cipher_list);
if (!tls_ctx) {
ICECAST_LOG_INFO("No TLS capability on any configured ports");
return;
} while (0);
ICECAST_LOG_INFO("No TLS capability on any configured ports");
}
config->tls_ok = tls_ok = 1;
}
/* handlers for reading and writing a connection_t when there is ssl
/* handlers for reading and writing a connection_t when there is TLS
* configured on the listening port
*/
static int connection_read_ssl(connection_t *con, void *buf, size_t len)
static int connection_read_tls(connection_t *con, void *buf, size_t len)
{
int bytes = SSL_read(con->ssl, buf, len);
ssize_t bytes = tls_read(con->tls, buf, len);
if (bytes < 0) {
switch (SSL_get_error(con->ssl, bytes)) {
case SSL_ERROR_WANT_READ:
case SSL_ERROR_WANT_WRITE:
if (tls_want_io(con->tls) > 0)
return -1;
}
con->error = 1;
}
return bytes;
}
static int connection_send_ssl(connection_t *con, const void *buf, size_t len)
static int connection_send_tls(connection_t *con, const void *buf, size_t len)
{
int bytes = SSL_write (con->ssl, buf, len);
ssize_t bytes = tls_write(con->tls, buf, len);
if (bytes < 0) {
switch (SSL_get_error(con->ssl, bytes)){
case SSL_ERROR_WANT_READ:
case SSL_ERROR_WANT_WRITE:
return -1;
}
if (tls_want_io(con->tls) > 0)
return -1;
con->error = 1;
} else {
con->sent_bytes += bytes;
......@@ -239,14 +212,14 @@ static int connection_send_ssl(connection_t *con, const void *buf, size_t len)
}
#else
/* SSL not compiled in, so at least log it */
static void get_ssl_certificate(ice_config_t *config)
/* TLS not compiled in, so at least log it */
static void get_tls_certificate(ice_config_t *config)
{
ssl_ok = 0;
tls_ok = 0;
ICECAST_LOG_INFO("No TLS capability. "
"Rebuild Icecast with openSSL support to enable this.");
"Rebuild Icecast with OpenSSL support to enable this.");
}
#endif /* HAVE_OPENSSL */
#endif /* ICECAST_CAP_TLS */
/* handlers (default) for reading and writing a connection_t, no encrpytion
......@@ -284,6 +257,7 @@ connection_t *connection_create (sock_t sock, sock_t serversock, char *ip)
con->con_time = time(NULL);
con->id = _next_connection_id();
con->ip = ip;
con->tlsmode = ICECAST_TLSMODE_AUTO;
con->read = connection_read;
con->send = connection_send;
}
......@@ -291,19 +265,20 @@ connection_t *connection_create (sock_t sock, sock_t serversock, char *ip)
return con;
}
/* prepare connection for interacting over a SSL connection
/* prepare connection for interacting over a TLS connection
*/
void connection_uses_ssl(connection_t *con)
void connection_uses_tls(connection_t *con)
{
#ifdef HAVE_OPENSSL
if (con->ssl)
#ifdef ICECAST_CAP_TLS
if (con->tls)
return;
con->read = connection_read_ssl;
con->send = connection_send_ssl;
con->ssl = SSL_new(ssl_ctx);
SSL_set_accept_state(con->ssl);
SSL_set_fd(con->ssl, con->sock);
con->tlsmode = ICECAST_TLSMODE_RFC2818;
con->read = connection_read_tls;
con->send = connection_send_tls;
con->tls = tls_new(tls_ctx);
tls_set_incoming(con->tls);
tls_set_socket(con->tls, con->sock);
#endif
}
......@@ -462,8 +437,12 @@ static client_queue_t *_get_connection(void)
static void process_request_queue (void)
{
client_queue_t **node_ref = (client_queue_t **)&_req_queue;
ice_config_t *config = config_get_config();
int timeout = config->header_timeout;
ice_config_t *config;
int timeout;
char peak;
config = config_get_config();
timeout = config->header_timeout;
config_release_config();
while (*node_ref) {
......@@ -472,6 +451,14 @@ static void process_request_queue (void)
int len = PER_CLIENT_REFBUF_SIZE - 1 - node->offset;
char *buf = client->refbuf->data + node->offset;
if (client->con->tlsmode == ICECAST_TLSMODE_AUTO || client->con->tlsmode == ICECAST_TLSMODE_AUTO_NO_PLAIN) {
if (recv(client->con->sock, &peak, 1, MSG_PEEK) == 1) {
if (peak == 0x16) { /* TLS Record Protocol Content type 0x16 == Handshake */
connection_uses_tls(client->con);
}
}
}
if (len > 0) {
if (client->con->con_time + timeout <= time(NULL)) {
len = 0;
......@@ -568,8 +555,9 @@ static client_queue_t *create_client_node(client_t *client)
if (listener) {
if (listener->shoutcast_compat)
node->shoutcast = 1;
if (listener->ssl && ssl_ok)
connection_uses_ssl(client->con);
client->con->tlsmode = listener->tls;
if (listener->tls == ICECAST_TLSMODE_RFC2818 && tls_ok)
connection_uses_tls(client->con);
if (listener->shoutcast_mount)
node->shoutcast_mount = strdup(listener->shoutcast_mount);
}
......@@ -621,7 +609,7 @@ void connection_accept_loop(void)
int duration = 300;
config = config_get_config();
get_ssl_certificate(config);
get_tls_certificate(config);
config_release_config();
while (global.running == ICECAST_RUNNING) {
......@@ -1358,8 +1346,16 @@ static void _handle_connection(void)
upgrade = httpp_getvar(parser, "upgrade");
connection = httpp_getvar(parser, "connection");
if (upgrade && connection && strstr(upgrade, "TLS/1.0") != NULL && strcasecmp(connection, "upgrade") == 0) {
client_send_101(client, ICECAST_REUSE_UPGRADETLS);
if (upgrade && connection && strcasecmp(connection, "upgrade") == 0) {
if (client->con->tlsmode == ICECAST_TLSMODE_DISABLED || strstr(upgrade, "TLS/1.0") == NULL) {
client_send_error(client, 400, 1, "Can not upgrade protocol");
continue;
} else {
client_send_101(client, ICECAST_REUSE_UPGRADETLS);
continue;
}
} else if (client->con->tlsmode != ICECAST_TLSMODE_DISABLED && client->con->tlsmode != ICECAST_TLSMODE_AUTO && !client->con->tls) {
client_send_426(client, ICECAST_REUSE_UPGRADETLS);
continue;
}
......@@ -1495,8 +1491,6 @@ void connection_close(connection_t *con)
sock_close(con->sock);
if (con->ip)
free(con->ip);
#ifdef HAVE_OPENSSL
if (con->ssl) { SSL_shutdown(con->ssl); SSL_free(con->ssl); }
#endif
tls_unref(con->tls);
free(con);
}
......@@ -16,10 +16,8 @@
#include <sys/types.h>
#include <time.h>
#ifdef HAVE_OPENSSL
#include <openssl/ssl.h>
#include <openssl/err.h>
#endif
#include "tls.h"
#include "compat.h"
#include "common/httpp/httpp.h"
......@@ -30,6 +28,19 @@ struct _client_tag;
struct source_tag;
struct ice_config_tag;
typedef enum _tlsmode_tag {
/* no TLS is used at all */
ICECAST_TLSMODE_DISABLED = 0,
/* TLS mode is to be detected */
ICECAST_TLSMODE_AUTO,