diff --git a/src/Makefile.am b/src/Makefile.am index 59f4bdc4..e69c236c 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -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 \ diff --git a/src/admin.c b/src/admin.c index 65c83e25..ae5e9ea1 100644 --- a/src/admin.c +++ b/src/admin.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; } diff --git a/src/cfgfile.c b/src/cfgfile.c index 4785e866..13865eca 100644 --- a/src/cfgfile.c +++ b/src/cfgfile.c @@ -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(" 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; diff --git a/src/cfgfile.h b/src/cfgfile.h index d18725c6..31717088 100644 --- a/src/cfgfile.h +++ b/src/cfgfile.h @@ -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; diff --git a/src/client.c b/src/client.c index 5fafd8cf..da95c589 100644 --- a/src/client.c +++ b/src/client.c @@ -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); } diff --git a/src/connection.c b/src/connection.c index 9e5f95d2..71e7c754 100644 --- a/src/connection.c +++ b/src/connection.c @@ -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; + const char *keyfile; - SSL_load_error_strings(); /* readable error messages */ - SSL_library_init(); /* initialize library */ + config->tls_ok = tls_ok = 0; - 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 + keyfile = config->tls_context.key_file; + if (!keyfile) + keyfile = config->tls_context.cert_file; - 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); + 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); } diff --git a/src/connection.h b/src/connection.h index 312b551a..271b0dea 100644 --- a/src/connection.h +++ b/src/connection.h @@ -16,10 +16,8 @@ #include #include -#ifdef HAVE_OPENSSL -#include -#include -#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, + /* Like ICECAST_TLSMODE_AUTO but enforces TLS */ + ICECAST_TLSMODE_AUTO_NO_PLAIN, + /* TLS via HTTP Upgrade:-header [RFC2817] */ + ICECAST_TLSMODE_RFC2817, + /* TLS for transport layer like HTTPS [RFC2818] does */ + ICECAST_TLSMODE_RFC2818 +} tlsmode_t; + typedef struct connection_tag { unsigned long id; @@ -42,9 +53,8 @@ typedef struct connection_tag sock_t serversock; int error; -#ifdef HAVE_OPENSSL - SSL *ssl; /* SSL handler */ -#endif + tlsmode_t tlsmode; + tls_t *tls; int (*send)(struct connection_tag *handle, const void *buf, size_t len); int (*read)(struct connection_tag *handle, void *buf, size_t len); @@ -53,13 +63,14 @@ typedef struct connection_tag void connection_initialize(void); void connection_shutdown(void); +void connection_reread_config(struct ice_config_tag *config); void connection_accept_loop(void); int connection_setup_sockets(struct ice_config_tag *config); void connection_close(connection_t *con); connection_t *connection_create(sock_t sock, sock_t serversock, char *ip); int connection_complete_source(struct source_tag *source, int response); void connection_queue(connection_t *con); -void connection_uses_ssl(connection_t *con); +void connection_uses_tls(connection_t *con); ssize_t connection_read_bytes(connection_t *con, void *buf, size_t len); diff --git a/src/main.c b/src/main.c index 42759082..27b688f3 100644 --- a/src/main.c +++ b/src/main.c @@ -123,6 +123,7 @@ void initialize_subsystems(void) sock_initialize(); resolver_initialize(); config_initialize(); + tls_initialize(); connection_initialize(); global_initialize(); refbuf_initialize(); @@ -145,6 +146,7 @@ void shutdown_subsystems(void) global_shutdown(); connection_shutdown(); + tls_shutdown(); config_shutdown(); resolver_shutdown(); sock_shutdown(); diff --git a/src/source.c b/src/source.c index 719835a3..d32c8cba 100644 --- a/src/source.c +++ b/src/source.c @@ -514,10 +514,8 @@ static refbuf_t *get_next_buffer (source_t *source) } source->last_read = current; refbuf = source->format->get_buffer (source); -#ifdef HAVE_OPENSSL - if (source->client->con->ssl && (SSL_get_shutdown(source->client->con->ssl) & SSL_RECEIVED_SHUTDOWN)) + if (source->client->con->tls && tls_got_shutdown(source->client->con->tls) > 1) source->client->con->error = 1; -#endif if (source->client->con && source->client->con->error) { ICECAST_LOG_INFO("End of Stream %s", source->mount); diff --git a/src/tls.c b/src/tls.c new file mode 100644 index 00000000..36edd86d --- /dev/null +++ b/src/tls.c @@ -0,0 +1,306 @@ +/* Icecast + * + * This program is distributed under the GNU General Public License, version 2. + * A copy of this license is included with this source. + * + * Copyright 2016, Philipp "ph3-der-loewe" Schafft , + */ + +/** + * TLS support functions + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#include "tls.h" + +#include "logging.h" +#define CATMODULE "tls" + +/* Check for a specific implementation. Returns 0 if supported, 1 if unsupported and -1 on error. */ +int tls_check_impl(const char *impl) +{ +#ifdef HAVE_OPENSSL + if (!strcasecmp(impl, "openssl")) + return 0; +#endif +#ifdef ICECAST_CAP_TLS + if (!strcasecmp(impl, "generic")) + return 0; +#endif + + return 1; +} + +#ifdef HAVE_OPENSSL +struct tls_ctx_tag { + size_t refc; + SSL_CTX *ctx; +}; + +struct tls_tag { + size_t refc; + SSL *ssl; + tls_ctx_t *ctx; +}; + +void tls_initialize(void) +{ + SSL_load_error_strings(); /* readable error messages */ + SSL_library_init(); /* initialize library */ +} +void tls_shutdown(void) +{ +} + +tls_ctx_t *tls_ctx_new(const char *cert_file, const char *key_file, const char *cipher_list) +{ + tls_ctx_t *ctx; + SSL_METHOD *method; + long ssl_opts; + + if (!cert_file || !key_file || !cipher_list) + return NULL; + + ctx = calloc(1, sizeof(*ctx)); + if (!ctx) + return NULL; + + method = SSLv23_server_method(); + + ctx->refc = 1; + ctx->ctx = SSL_CTX_new(method); + + ssl_opts = SSL_CTX_get_options(ctx->ctx); +#ifdef SSL_OP_NO_COMPRESSION + SSL_CTX_set_options(ctx->ctx, ssl_opts|SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3|SSL_OP_NO_COMPRESSION); +#else + SSL_CTX_set_options(ctx->ctx, ssl_opts|SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3); +#endif + + do { + if (SSL_CTX_use_certificate_chain_file(ctx->ctx, cert_file) <= 0) { + ICECAST_LOG_WARN("Invalid cert file %s", cert_file); + break; + } + if (SSL_CTX_use_PrivateKey_file(ctx->ctx, key_file, SSL_FILETYPE_PEM) <= 0) { + ICECAST_LOG_WARN("Invalid private key file %s", key_file); + break; + } + if (!SSL_CTX_check_private_key(ctx->ctx)) { + ICECAST_LOG_ERROR("Invalid %s - Private key does not match cert public key", key_file); + break; + } + if (SSL_CTX_set_cipher_list(ctx->ctx, cipher_list) <= 0) { + ICECAST_LOG_WARN("Invalid cipher list: %s", cipher_list); + } + ICECAST_LOG_INFO("Certificate found at %s", cert_file); + ICECAST_LOG_INFO("Using ciphers %s", cipher_list); + return ctx; + } while (0); + + ICECAST_LOG_INFO("Can not setup TLS."); + tls_ctx_unref(ctx); + return NULL; +} + +void tls_ctx_ref(tls_ctx_t *ctx) +{ + if (!ctx) + return; + + ctx->refc++; +} + +void tls_ctx_unref(tls_ctx_t *ctx) +{ + if (!ctx) + return; + + ctx->refc--; + + if (ctx->refc) + return; + + if (ctx->ctx) + SSL_CTX_free(ctx->ctx); + + free(ctx); +} + +tls_t *tls_new(tls_ctx_t *ctx) +{ + tls_t *tls; + SSL *ssl; + + if (!ctx) + return NULL; + + ssl = SSL_new(ctx->ctx); + if (!ssl) + return NULL; + + tls = calloc(1, sizeof(*tls)); + if (!tls) { + SSL_free(ssl); + return NULL; + } + + tls_ctx_ref(ctx); + + tls->refc = 1; + tls->ssl = ssl; + tls->ctx = ctx; + + return tls; +} +void tls_ref(tls_t *tls) +{ + if (!tls) + return; + + tls->refc++; +} +void tls_unref(tls_t *tls) +{ + if (!tls) + return; + + tls->refc--; + + if (tls->refc) + return; + + SSL_shutdown(tls->ssl); + SSL_free(tls->ssl); + + if (tls->ctx) + tls_ctx_unref(tls->ctx); + + free(tls); +} + +void tls_set_incoming(tls_t *tls) +{ + if (!tls) + return; + + SSL_set_accept_state(tls->ssl); +} +void tls_set_socket(tls_t *tls, sock_t sock) +{ + if (!tls) + return; + + SSL_set_fd(tls->ssl, sock); +} + +int tls_want_io(tls_t *tls) +{ + int what; + + if (!tls) + return -1; + + what = SSL_want(tls->ssl); + + switch (what) { + case SSL_WRITING: + case SSL_READING: + return 1; + break; + case SSL_NOTHING: + default: + return 0; + break; + } +} + +int tls_got_shutdown(tls_t *tls) +{ + if (!tls) + return -1; + + if (SSL_get_shutdown(tls->ssl) & SSL_RECEIVED_SHUTDOWN) { + return 1; + } else { + return 0; + } +} + +ssize_t tls_read(tls_t *tls, void *buffer, size_t len) +{ + if (!tls) + return -1; + + return SSL_read(tls->ssl, buffer, len); +} +ssize_t tls_write(tls_t *tls, const void *buffer, size_t len) +{ + if (!tls) + return -1; + + return SSL_write(tls->ssl, buffer, len); +} +#else +void tls_initialize(void) +{ +} +void tls_shutdown(void) +{ +} + +tls_ctx_t *tls_ctx_new(const char *cert_file, const char *key_file, const char *cipher_list) +{ + return NULL; +} +void tls_ctx_ref(tls_ctx_t *ctx) +{ +} +void tls_ctx_unref(tls_ctx_t *ctx) +{ +} + +tls_t *tls_new(tls_ctx_t *ctx) +{ + return NULL; +} +void tls_ref(tls_t *tls) +{ +} +void tls_unref(tls_t *tls) +{ +} + +void tls_set_incoming(tls_t *tls) +{ +} +void tls_set_socket(tls_t *tls, sock_t sock) +{ +} + +int tls_want_io(tls_t *tls) +{ + return -1; +} + +int tls_got_shutdown(tls_t *tls) +{ + return -1; +} + +ssize_t tls_read(tls_t *tls, void *buffer, size_t len) +{ + return -1; +} +ssize_t tls_write(tls_t *tls, const void *buffer, size_t len) +{ + return -1; +} + +#endif diff --git a/src/tls.h b/src/tls.h new file mode 100644 index 00000000..50edf047 --- /dev/null +++ b/src/tls.h @@ -0,0 +1,52 @@ +/* Icecast + * + * This program is distributed under the GNU General Public License, version 2. + * A copy of this license is included with this source. + * + * Copyright 2016, Philipp "ph3-der-loewe" Schafft , + */ + +#ifndef __TLS_H__ +#define __TLS_H__ + +#ifdef HAVE_OPENSSL +#include +#include +#endif + +#include "common/net/sock.h" + +/* Do we have TLS Support? */ +#if defined(HAVE_OPENSSL) +#define ICECAST_CAP_TLS +#endif + + +typedef struct tls_ctx_tag tls_ctx_t; +typedef struct tls_tag tls_t; + +/* Check for a specific implementation. Returns 0 if supported, 1 if unsupported and -1 on error. */ +int tls_check_impl(const char *impl); + +void tls_initialize(void); +void tls_shutdown(void); + +tls_ctx_t *tls_ctx_new(const char *cert_file, const char *key_file, const char *cipher_list); +void tls_ctx_ref(tls_ctx_t *ctx); +void tls_ctx_unref(tls_ctx_t *ctx); + +tls_t *tls_new(tls_ctx_t *ctx); +void tls_ref(tls_t *tls); +void tls_unref(tls_t *tls); + +void tls_set_incoming(tls_t *tls); +void tls_set_socket(tls_t *tls, sock_t sock); + +int tls_want_io(tls_t *tls); + +int tls_got_shutdown(tls_t *tls); + +ssize_t tls_read(tls_t *tls, void *buffer, size_t len); +ssize_t tls_write(tls_t *tls, const void *buffer, size_t len); + +#endif diff --git a/src/util.c b/src/util.c index d163d852..0068fd4f 100644 --- a/src/util.c +++ b/src/util.c @@ -676,6 +676,7 @@ ssize_t util_http_build_header(char * out, size_t len, ssize_t offset, ssize_t ret; char * extra_headers; const char *connection_header = "Close"; + const char *upgrade_header = ""; if (!out) return -1; @@ -686,6 +687,8 @@ ssize_t util_http_build_header(char * out, size_t len, ssize_t offset, case ICECAST_REUSE_KEEPALIVE: connection_header = "Keep-Alive"; break; case ICECAST_REUSE_UPGRADETLS: connection_header = "Upgrade"; break; } + if (client->con->tlsmode != ICECAST_TLSMODE_DISABLED) + upgrade_header = "Upgrade: TLS/1.0\r\n"; } if (offset == -1) @@ -758,7 +761,7 @@ ssize_t util_http_build_header(char * out, size_t len, ssize_t offset, connection_header, (client && client->admin_command == ADMIN_COMMAND_ERROR ? "GET, SOURCE" : "GET"), - (config->tls_ok ? "Upgrade: TLS/1.0\r\n" : ""), + upgrade_header, currenttime_buffer, contenttype_buffer, (status == 401 ? "WWW-Authenticate: Basic realm=\"Icecast2 Server\"\r\n" : ""),