diff --git a/src/admin.c b/src/admin.c index f4db32dc..51d30301 100644 --- a/src/admin.c +++ b/src/admin.c @@ -1123,7 +1123,7 @@ static void command_shoutcast_metadata(client_t *client, source_t *source) /* catch all function for admin requests. If file has xsl extension then - * transform it usinf the available stats, else send the XML tree of the + * transform it using the available stats, else send the XML tree of the * stats */ static void command_stats (client_t *client) diff --git a/src/auth.c b/src/auth.c index 813386c8..5595da01 100644 --- a/src/auth.c +++ b/src/auth.c @@ -324,7 +324,7 @@ static int add_authenticated_client (const char *mount, mount_proxy *mountinfo, else { avl_tree_unlock (global.source_tree); - ret = fserve_client_create (client, mount); + fserve_client_create (client, mount); } return ret; } diff --git a/src/cfgfile.c b/src/cfgfile.c index 5b47418f..3c6f2609 100644 --- a/src/cfgfile.c +++ b/src/cfgfile.c @@ -463,6 +463,10 @@ static void _parse_root(xmlDocPtr doc, xmlNodePtr node, tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); configuration->master_relay_auth = atoi(tmp); xmlFree (tmp); + } else if (strcmp(node->name, "master-ssl-port") == 0) { + tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + configuration->master_ssl_port = atoi(tmp); + xmlFree (tmp); } else if (strcmp(node->name, "shoutcast-mount") == 0) { if (configuration->shoutcast_mount && configuration->shoutcast_mount != CONFIG_DEFAULT_SHOUTCAST_MOUNT) diff --git a/src/cfgfile.h b/src/cfgfile.h index 72628fcb..883a3bef 100644 --- a/src/cfgfile.h +++ b/src/cfgfile.h @@ -140,6 +140,7 @@ typedef struct ice_config_tag char *master_username; char *master_password; int master_relay_auth; + int master_ssl_port; relay_server *relay; diff --git a/src/client.h b/src/client.h index ed858a74..f92b5fb8 100644 --- a/src/client.h +++ b/src/client.h @@ -38,7 +38,7 @@ typedef struct _client_tag /* http response code for this client */ int respcode; - /* auth completed, 0 not yet, 1 passed, 2 failed */ + /* auth completed, 0 not yet, 1 passed */ int authenticated; /* is client getting intro data */ diff --git a/src/connection.c b/src/connection.c index 959c3b4e..cba57a53 100644 --- a/src/connection.c +++ b/src/connection.c @@ -242,6 +242,8 @@ static int connection_send_ssl (connection_t *con, const char *buf, unsigned len static int connection_read (connection_t *con, char *buf, unsigned 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; diff --git a/src/fserve.c b/src/fserve.c index 340689e4..1a7494d4 100644 --- a/src/fserve.c +++ b/src/fserve.c @@ -370,6 +370,9 @@ static void fserve_client_destroy(fserve_t *fclient) } +/* client has requested a file, so check for it and send the file. Do not + * refer to the client_t afterwards. return 0 for success, -1 on error. + */ int fserve_client_create (client_t *httpclient, const char *path) { struct stat file_buf; @@ -397,7 +400,7 @@ int fserve_client_create (client_t *httpclient, const char *path) WARN2 ("req for file \"%s\" %s", fullpath, strerror (errno)); client_send_404 (httpclient, "The file you requested could not be found"); free (fullpath); - return 0; + return -1; } m3u_file_available = 0; } @@ -448,7 +451,7 @@ int fserve_client_create (client_t *httpclient, const char *path) client_send_404 (httpclient, "The file you requested could not be found"); config_release_config(); free (fullpath); - return 0; + return -1; } config_release_config(); @@ -457,7 +460,7 @@ int fserve_client_create (client_t *httpclient, const char *path) client_send_404 (httpclient, "The file you requested could not be found"); WARN1 ("found requested file but there is no handler for it: %s", fullpath); free (fullpath); - return 0; + return -1; } file = fopen (fullpath, "rb"); @@ -466,7 +469,7 @@ int fserve_client_create (client_t *httpclient, const char *path) { WARN1 ("Problem accessing file \"%s\"", fullpath); client_send_404 (httpclient, "File not readable"); - return 0; + return -1; } content_length = (int64_t)file_buf.st_size; @@ -537,7 +540,7 @@ int fserve_client_create (client_t *httpclient, const char *path) /* If we run into any issues with the ranges we fallback to a normal/non-range request */ client_send_416 (httpclient); - return 0; + return -1; } diff --git a/src/main.c b/src/main.c index cd7e710e..be2eaf1b 100644 --- a/src/main.c +++ b/src/main.c @@ -49,7 +49,6 @@ #include "xslt.h" #include "fserve.h" #include "yp.h" -#include "format.h" #include diff --git a/src/slave.c b/src/slave.c index f742e85a..56d8bea1 100644 --- a/src/slave.c +++ b/src/slave.c @@ -38,6 +38,9 @@ #define strcasecmp stricmp #define strncasecmp strnicmp #endif +#ifdef HAVE_CURL +#include +#endif #include "os.h" @@ -563,112 +566,202 @@ static void relay_check_streams (relay_server *to_start, relay_server *to_free) } -static int update_from_master(ice_config_t *config) +#ifdef HAVE_CURL +struct master_conn_details { - char *master = NULL, *password = NULL, *username= NULL; + char *server; int port; - sock_t mastersock; - int ret = 0; - char buf[256]; - do + int ssl_port; + int send_auth; + int on_demand; + int previous; + int ok; + char *buffer; + char *username; + char *password; + relay_server *new_relays; +}; + + +/* process a single HTTP header from streamlist response */ +static size_t streamlist_header (void *ptr, size_t size, size_t nmemb, void *stream) +{ + size_t passed_len = size*nmemb; + char *eol = memchr (ptr, '\r', passed_len); + struct master_conn_details *master = stream; + + /* drop EOL chars if any */ + if (eol) + *eol = '\0'; + else { - char *authheader, *data; - relay_server *new_relays = NULL, *cleanup_relays; - int len, count = 1; - int on_demand, send_auth; - - username = strdup (config->master_username); - if (config->master_password) - password = strdup (config->master_password); - - if (config->master_server) - master = strdup (config->master_server); - - port = config->master_server_port; - - if (password == NULL || master == NULL || port == 0) - break; - on_demand = config->on_demand; - send_auth = config->master_relay_auth; - ret = 1; - config_release_config(); - mastersock = sock_connect_wto (master, port, 0); - - if (mastersock == SOCK_ERROR) + eol = memchr (ptr, '\n', passed_len); + if (eol) + *eol = '\0'; + else + return -1; + } + if (strncmp (ptr, "HTTP", 4) == 0) + { + int respcode; + if (sscanf (ptr, "HTTP%*s %d OK", &respcode) == 1 && respcode == 200) { - WARN0("Relay slave failed to contact master server to fetch stream list"); + master->ok = 1; + } + else + { + WARN1 ("Failed response from master \"%s\"", (char*)ptr); + return -1; + } + } + return passed_len; +} + + +/* process mountpoint list from master server. This may be called multiple + * times so watch for the last line in this block as it may be incomplete + */ +static size_t streamlist_data (void *ptr, size_t size, size_t nmemb, void *stream) +{ + struct master_conn_details *master = stream; + size_t passed_len = size*nmemb; + size_t len = passed_len + master->previous + 1; + char *buffer, *buf; + + /* append newly read data to the end of any previous unprocess data */ + buffer = realloc (master->buffer, len); + memcpy (buffer + master->previous, ptr, passed_len); + buffer [len] = '\0'; + + buf = buffer; + while (len) + { + int offset; + char *eol = strchr (buf, '\n'); + if (eol) + { + offset = (eol - buf) + 1; + *eol = '\0'; + eol = strchr (buf, '\r'); + if (eol) *eol = '\0'; + } + else + { + /* incomplete line, the rest may be in the next read */ + unsigned rest = strlen (buf); + memmove (buffer, buf, rest); + master->previous = rest; break; } - len = strlen(username) + strlen(password) + 2; - authheader = malloc(len); - snprintf (authheader, len, "%s:%s", username, password); - data = util_base64_encode(authheader); - sock_write (mastersock, - "GET /admin/streamlist.txt HTTP/1.0\r\n" - "Authorization: Basic %s\r\n" - "\r\n", data); - free(authheader); - free(data); - - if (sock_read_line(mastersock, buf, sizeof(buf)) == 0 || - strncmp (buf, "HTTP/1.0 200", 12) != 0) + DEBUG1 ("read from master \"%s\"", buf); + if (strlen (buf)) { - sock_close (mastersock); - WARN0 ("Master rejected streamlist request"); - break; - } - - while (sock_read_line(mastersock, buf, sizeof(buf))) - { - if (!strlen(buf)) - break; - } - while (sock_read_line(mastersock, buf, sizeof(buf))) - { - relay_server *r; - if (!strlen(buf)) - continue; - DEBUG2 ("read %d from master \"%s\"", count++, buf); - r = calloc (1, sizeof (relay_server)); - if (r) + relay_server *r = calloc (1, sizeof (relay_server)); + r->server = xmlStrdup (master->server); + r->port = master->port; + r->mount = xmlStrdup (buf); + r->localmount = xmlStrdup (buf); + r->mp3metadata = 1; + r->on_demand = master->on_demand; + r->enable = 1; + if (master->send_auth) { - r->server = xmlStrdup (master); - r->port = port; - r->mount = xmlStrdup (buf); - r->localmount = xmlStrdup (buf); - r->mp3metadata = 1; - r->on_demand = on_demand; - r->enable = 1; - if (send_auth) - { - r->username = xmlStrdup (username); - r->password = xmlStrdup (password); - } - r->next = new_relays; - new_relays = r; + r->username = xmlStrdup (master->username); + r->password = xmlStrdup (master->password); } + r->next = master->new_relays; + master->new_relays = r; } - sock_close (mastersock); + buf += offset; + len -= offset; + } + master->buffer = buffer; + return passed_len; +} + + +/* retrieve streamlist from master server. The streamlist can be retrieved + * from an SSL port if curl is capable and the config is aware of the port + * to use + */ +static void *streamlist_thread (void *arg) +{ + struct master_conn_details *master = arg; + CURL *handle; + const char *protocol = "http"; + int port = master->port; + char error [CURL_ERROR_SIZE]; + char url [300], auth [100]; + + if (master->ssl_port) + { + protocol = "https"; + port = master->ssl_port; + } + snprintf (auth, sizeof (auth), "%s:%s", master->username, master->password); + snprintf (url, sizeof (url), "%s://%s:%d/admin/streamlist.txt", + protocol, master->server, port); + handle = curl_easy_init (); + curl_easy_setopt (handle, CURLOPT_USERAGENT, ICECAST_VERSION_STRING); + curl_easy_setopt (handle, CURLOPT_URL, url); + curl_easy_setopt (handle, CURLOPT_HEADERFUNCTION, streamlist_header); + curl_easy_setopt (handle, CURLOPT_HEADERDATA, master); + curl_easy_setopt (handle, CURLOPT_WRITEFUNCTION, streamlist_data); + curl_easy_setopt (handle, CURLOPT_WRITEDATA, master); + curl_easy_setopt (handle, CURLOPT_USERPWD, auth); + curl_easy_setopt (handle, CURLOPT_ERRORBUFFER, error); + curl_easy_setopt (handle, CURLOPT_SSL_VERIFYPEER, FALSE); + curl_easy_setopt (handle, CURLOPT_NOSIGNAL, 1L); + curl_easy_setopt (handle, CURLOPT_TIMEOUT, 15L); + + if (curl_easy_perform (handle) != 0) + WARN2 ("Failed URL access \"%s\" (%s)", url, error); + if (master->ok) + { + /* process retrieved relays */ + relay_server *cleanup_relays; thread_mutex_lock (&(config_locks()->relay_lock)); - cleanup_relays = update_relays (&global.master_relays, new_relays); - + cleanup_relays = update_relays (&global.master_relays, master->new_relays); + relay_check_streams (global.master_relays, cleanup_relays); - relay_check_streams (NULL, new_relays); + relay_check_streams (NULL, master->new_relays); thread_mutex_unlock (&(config_locks()->relay_lock)); + } - } while(0); + curl_easy_cleanup (handle); + free (master->server); + free (master->username); + free (master->password); + free (master->buffer); + free (master); + return NULL; +} +#endif - if (master) - free (master); - if (username) - free (username); - if (password) - free (password); - return ret; +static void update_from_master (ice_config_t *config) +{ +#ifdef HAVE_CURL + struct master_conn_details *details = calloc (1, sizeof (*details)); + + if (config->master_password == NULL || config->master_server == NULL || + config->master_server_port == 0) + return; + details->server = strdup (config->master_server); + details->port = config->master_server_port; + details->ssl_port = config->master_ssl_port; + details->username = strdup (config->master_username); + details->password = strdup (config->master_password); + details->send_auth = config->master_relay_auth; + details->on_demand = config->on_demand; + + thread_create ("streamlist", streamlist_thread, details, THREAD_DETACHED); +#else + WARN0 ("streamlist request disabled, rebuild with libcurl if required"); +#endif } @@ -724,9 +817,7 @@ static void *_slave_thread(void *arg) max_interval = config->master_update_interval; update_master_as_slave (config); - /* the connection could take some time, so the lock can drop */ - if (update_from_master (config)) - config = config_get_config(); + update_from_master (config); thread_mutex_lock (&(config_locks()->relay_lock));