From 7e5604b993e5d5d86fbc8a7fd17259b634e6d735 Mon Sep 17 00:00:00 2001 From: Karl Heyes Date: Wed, 29 Aug 2007 03:51:22 +0000 Subject: [PATCH] merge work. allow sockets to be marked as ssl capable. This is mainly for /admin requests but can be used for sources and listeners svn path=/icecast/trunk/icecast/; revision=13650 --- configure.in | 7 +++ src/cfgfile.c | 8 +++ src/cfgfile.h | 2 + src/client.c | 31 ++++------ src/connection.c | 152 ++++++++++++++++++++++++++++++++++++++++++++++- src/connection.h | 11 ++++ 6 files changed, 190 insertions(+), 21 deletions(-) diff --git a/configure.in b/configure.in index 2dd6ccf9..1515cd01 100644 --- a/configure.in +++ b/configure.in @@ -129,6 +129,13 @@ then else AC_MSG_NOTICE([YP support disabled]) fi +XIPH_PATH_OPENSSL([ + XIPH_VAR_APPEND([XIPH_CPPFLAGS],[$OPENSSL_CFLAGS]) + XIPH_VAR_APPEND([XIPH_LDFLAGS],[$OPENSSL_LDFLAGS]) + XIPH_VAR_PREPEND([XIPH_LIBS],[$OPENSSL_LIBS]) + ], + [ AC_MSG_NOTICE([SSL disabled!]) + ]) dnl Make substitutions diff --git a/src/cfgfile.c b/src/cfgfile.c index cff13f7e..b2c860ec 100644 --- a/src/cfgfile.c +++ b/src/cfgfile.c @@ -787,6 +787,11 @@ static void _parse_listen_socket(xmlDocPtr doc, xmlNodePtr node, listener->port = atoi(tmp); if(tmp) xmlFree(tmp); } + else if (strcmp(node->name, "ssl") == 0) { + tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + listener->ssl = atoi(tmp); + if(tmp) xmlFree(tmp); + } else if (strcmp(node->name, "shoutcast-compat") == 0) { tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); listener->shoutcast_compat = atoi(tmp); @@ -898,6 +903,9 @@ static void _parse_paths(xmlDocPtr doc, xmlNodePtr node, } else if (strcmp(node->name, "pidfile") == 0) { if (configuration->pidfile) xmlFree(configuration->pidfile); configuration->pidfile = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + } else if (strcmp(node->name, "ssl-certificate") == 0) { + if (configuration->cert_file) xmlFree(configuration->cert_file); + configuration->cert_file = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); } else if (strcmp(node->name, "webroot") == 0) { if (configuration->webroot_dir && configuration->webroot_dir != CONFIG_DEFAULT_WEBROOT_DIR) xmlFree(configuration->webroot_dir); configuration->webroot_dir = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); diff --git a/src/cfgfile.h b/src/cfgfile.h index e5e7ef5a..ce4cb7a6 100644 --- a/src/cfgfile.h +++ b/src/cfgfile.h @@ -99,6 +99,7 @@ typedef struct { int port; char *bind_address; int shoutcast_compat; + int ssl; } listener_t; typedef struct ice_config_tag @@ -149,6 +150,7 @@ typedef struct ice_config_tag char *base_dir; char *log_dir; char *pidfile; + char *cert_file; char *webroot_dir; char *adminroot_dir; aliases *aliases; diff --git a/src/client.c b/src/client.c index 2fb21d97..47acbb23 100644 --- a/src/client.c +++ b/src/client.c @@ -101,7 +101,7 @@ void client_destroy(client_t *client) */ if (client->respcode && client->parser) logging_access(client); - + if (client->con) connection_close(client->con); if (client->parser) @@ -127,7 +127,7 @@ void client_destroy(client_t *client) int client_read_bytes (client_t *client, void *buf, unsigned len) { int bytes; - + if (client->refbuf && client->refbuf->len) { /* we have data to read from a refbuf first */ @@ -142,18 +142,12 @@ int client_read_bytes (client_t *client, void *buf, unsigned len) client->refbuf->len -= len; return len; } - bytes = sock_read_bytes (client->con->sock, buf, len); - if (bytes > 0) - return bytes; + bytes = client->con->read (client->con, buf, len); - if (bytes < 0) - { - if (sock_recoverable (sock_error())) - return -1; - WARN0 ("source connection has died"); - } - client->con->error = 1; - return -1; + if (bytes == -1 && client->con->error) + DEBUG0 ("reading from connection has failed"); + + return bytes; } @@ -205,14 +199,11 @@ void client_send_403(client_t *client, const char *reason) /* helper function for sending the data to a client */ int client_send_bytes (client_t *client, const void *buf, unsigned len) { - int ret = sock_write_bytes (client->con->sock, buf, len); - if (ret < 0 && !sock_recoverable (sock_error())) - { + int ret = client->con->send (client->con, buf, len); + + if (client->con->error) DEBUG0 ("Client connection died"); - client->con->error = 1; - } - if (ret > 0) - client->con->sent_bytes += ret; + return ret; } diff --git a/src/connection.c b/src/connection.c index 1d104e0d..e5184c61 100644 --- a/src/connection.c +++ b/src/connection.c @@ -103,6 +103,11 @@ static volatile client_queue_t *_con_queue = NULL, **_con_queue_tail = &_con_que static mutex_t _con_queue_mutex; static mutex_t _req_queue_mutex; +static int ssl_ok; +#ifdef HAVE_OPENSSL +static SSL_CTX *ssl_ctx; +#endif + rwlock_t _source_shutdown_rwlock; static void *_handle_connection(void *arg); @@ -129,6 +134,10 @@ void connection_shutdown(void) { if (!_initialized) return; +#ifdef HAVE_OPENSSL + SSL_CTX_free (ssl_ctx); +#endif + thread_cond_destroy(&global.shutdown_cond); thread_rwlock_destroy(&_source_shutdown_rwlock); thread_mutex_destroy(&_con_queue_mutex); @@ -150,6 +159,126 @@ static unsigned long _next_connection_id(void) return id; } + +#ifdef HAVE_OPENSSL +static void get_ssl_certificate () +{ + SSL_METHOD *method; + ice_config_t *config; + 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); + + config = config_get_config (); + do + { + if (config->cert_file == NULL) + break; + if (SSL_CTX_use_certificate_file (ssl_ctx, config->cert_file, SSL_FILETYPE_PEM) <= 0) + { + WARN1 ("Invalid cert file %s", config->cert_file); + break; + } + if (SSL_CTX_use_PrivateKey_file (ssl_ctx, config->cert_file, SSL_FILETYPE_PEM) <= 0) + { + WARN1 ("Invalid private key file %s", config->cert_file); + break; + } + if (!SSL_CTX_check_private_key (ssl_ctx)) + { + ERROR0 ("Invalid icecast.pem - Private key doesn't" + " match cert public key"); + break; + } + ssl_ok = 1; + INFO1 ("SSL certificate found at %s", config->cert_file); + } while (0); + config_release_config (); + if (ssl_ok == 0) + INFO0 ("No SSL capability on any configured ports"); +} + + +/* handlers for reading and writing a connection_t when there is ssl + * configured on the listening port + */ +static int connection_read_ssl (connection_t *con, void *buf, size_t len) +{ + int bytes = SSL_read (con->ssl, buf, len); + + if (bytes < 0) + { + switch (SSL_get_error (con->ssl, bytes)) + { + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + return -1; + } + con->error = 1; + } + return bytes; +} + +static int connection_send_ssl (connection_t *con, const void *buf, size_t len) +{ + int bytes = SSL_write (con->ssl, buf, len); + + if (bytes < 0) + { + switch (SSL_get_error (con->ssl, bytes)) + { + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + return -1; + } + con->error = 1; + } + else + con->sent_bytes += bytes; + return bytes; +} +#else + +/* SSL not compiled in, so at least log it */ +static void get_ssl_certificate () +{ + ssl_ok = 0; + INFO0 ("No SSL capability"); +} +#endif /* HAVE_OPENSSL */ + + +/* handlers (default) for reading and writing a connection_t, no encrpytion + * used just straight access to the socket + */ +static int connection_read (connection_t *con, void *buf, size_t len) +{ + int bytes = sock_read_bytes (con->sock, buf, len); + if (bytes == 0) + con->error = 1; + if (bytes == -1 && !sock_recoverable (sock_error())) + con->error = 1; + return bytes; +} + +static int connection_send (connection_t *con, const void *buf, size_t len) +{ + int bytes = sock_write_bytes (con->sock, buf, len); + if (bytes < 0) + { + if (!sock_recoverable (sock_error())) + con->error = 1; + } + else + con->sent_bytes += bytes; + return bytes; +} + + connection_t *connection_create (sock_t sock, sock_t serversock, char *ip) { connection_t *con; @@ -161,11 +290,26 @@ 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->read = connection_read; + con->send = connection_send; } return con; } +/* prepare connection for interacting over a SSL connection + */ +void connection_uses_ssl (connection_t *con) +{ +#ifdef HAVE_OPENSSL + 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); +#endif +} + static int wait_for_serversock(int timeout) { #ifdef HAVE_POLL @@ -437,6 +581,7 @@ void connection_accept_loop(void) { connection_t *con; + get_ssl_certificate (); tid = thread_create ("connection thread", _handle_connection, NULL, THREAD_ATTACHED); while (global.running == ICE_RUNNING) @@ -478,9 +623,11 @@ void connection_accept_loop(void) { if (config->listeners[i].shoutcast_compat) node->shoutcast = 1; + if (config->listeners[i].ssl && ssl_ok) + connection_uses_ssl (client->con); } } - config_release_config(); + config_release_config(); sock_set_blocking (client->con->sock, SOCK_NONBLOCK); sock_set_nodelay (client->con->sock); @@ -1069,5 +1216,8 @@ void connection_close(connection_t *con) sock_close(con->sock); if (con->ip) free(con->ip); if (con->host) free(con->host); +#ifdef HAVE_OPENSSL + if (con->ssl) { SSL_shutdown (con->ssl); SSL_free (con->ssl); } +#endif free(con); } diff --git a/src/connection.h b/src/connection.h index 9a52bfdb..dbf83dc8 100644 --- a/src/connection.h +++ b/src/connection.h @@ -15,6 +15,11 @@ #include #include +#ifdef HAVE_OPENSSL +#include +#include +#endif + #include "compat.h" #include "httpp/httpp.h" #include "thread/thread.h" @@ -35,6 +40,12 @@ typedef struct connection_tag int serversock; int error; +#ifdef HAVE_OPENSSL + SSL *ssl; /* SSL handler */ +#endif + int (*send)(struct connection_tag *handle, const void *buf, size_t len); + int (*read)(struct connection_tag *handle, void *buf, size_t len); + char *ip; char *host;