static void queue_auth_client (auth_client *auth_user, mount_proxy *mountinfo) { auth_t *auth; if (auth_user == NULL) return; auth_user->next = NULL; if (mountinfo) { auth = mountinfo->auth; thread_mutex_lock (&auth->lock); if (auth_user->client) auth_user->client->auth = auth; auth->refcount++; } else { if (auth_user->client == NULL || auth_user->client->auth == NULL) { ICECAST_LOG_WARN("internal state is incorrect for %p", auth_user->client); return; } auth = auth_user->client->auth; thread_mutex_lock (&auth->lock); } ICECAST_LOG_DEBUG("...refcount on auth_t %s is now %d", auth->mount, auth->refcount); *auth->tailp = auth_user; auth->tailp = &auth_user->next; auth->pending_count++; ICECAST_LOG_INFO("auth on %s has %d pending", auth->mount, auth->pending_count); thread_mutex_unlock (&auth->lock); }
/* create a client_t with the provided connection and parser details. Return * 0 on success, -1 if server limit has been reached. In either case a * client_t is returned just in case a message needs to be returned. Should * be called with global lock held. */ int client_create (client_t **c_ptr, connection_t *con, http_parser_t *parser) { ice_config_t *config; client_t *client = (client_t *)calloc(1, sizeof(client_t)); int ret = -1; if (client == NULL) abort(); config = config_get_config (); global.clients++; if (config->client_limit < global.clients) ICECAST_LOG_WARN("server client limit reached (%d/%d)", config->client_limit, global.clients); else ret = 0; config_release_config (); stats_event_args (NULL, "clients", "%d", global.clients); client->con = con; client->parser = parser; client->refbuf = refbuf_new (PER_CLIENT_REFBUF_SIZE); client->refbuf->len = 0; /* force reader code to ignore buffer contents */ client->pos = 0; client->write_to_client = format_generic_write_to_client; *c_ptr = client; return ret; }
/* wrapper for stats_event, this takes a charset to convert from */ void stats_event_conv(const char *mount, const char *name, const char *value, const char *charset) { const char *metadata = value; xmlBufferPtr conv = xmlBufferCreate (); if (charset) { xmlCharEncodingHandlerPtr handle = xmlFindCharEncodingHandler (charset); if (handle) { xmlBufferPtr raw = xmlBufferCreate (); xmlBufferAdd (raw, (const xmlChar *)value, strlen (value)); if (xmlCharEncInFunc (handle, conv, raw) > 0) metadata = (char *)xmlBufferContent (conv); xmlBufferFree (raw); xmlCharEncCloseFunc (handle); } else ICECAST_LOG_WARN("No charset found for \"%s\"", charset); } stats_event (mount, name, metadata); xmlBufferFree (conv); }
format_type_t format_get_type (const char *contenttype) { if(strcmp(contenttype, "application/x-ogg") == 0) return FORMAT_TYPE_OGG; /* Backwards compatibility */ else if(strcmp(contenttype, "application/ogg") == 0) return FORMAT_TYPE_OGG; /* Now blessed by IANA */ else if(strcmp(contenttype, "audio/ogg") == 0) return FORMAT_TYPE_OGG; else if(strcmp(contenttype, "video/ogg") == 0) return FORMAT_TYPE_OGG; else if(strcmp(contenttype, "audio/webm") == 0) return FORMAT_TYPE_EBML; else if(strcmp(contenttype, "video/webm") == 0) return FORMAT_TYPE_EBML; else if(strcmp(contenttype, "audio/x-matroska") == 0) return FORMAT_TYPE_EBML; else if(strcmp(contenttype, "video/x-matroska") == 0) return FORMAT_TYPE_EBML; else if(strcmp(contenttype, "video/x-matroska-3d") == 0) return FORMAT_TYPE_EBML; else /* We default to the Generic format handler, which can handle many more formats than just mp3. Let's warn that this is not well supported */ ICECAST_LOG_WARN("Unsupported or legacy stream type: \"%s\". Falling back to generic minimal handler for best effort.", contenttype); return FORMAT_TYPE_GENERIC; }
static void url_stream_auth (auth_client *auth_user) { ice_config_t *config; int port; client_t *client = auth_user->client; auth_url *url = client->auth->state; char *mount, *host, *user, *pass, *ipaddr, *admin=""; char post [4096]; if (strchr (url->stream_auth, '@') == NULL) { if (url->userpwd) curl_easy_setopt (url->handle, CURLOPT_USERPWD, url->userpwd); else curl_easy_setopt (url->handle, CURLOPT_USERPWD, ""); } else curl_easy_setopt (url->handle, CURLOPT_USERPWD, ""); curl_easy_setopt (url->handle, CURLOPT_URL, url->stream_auth); curl_easy_setopt (url->handle, CURLOPT_POSTFIELDS, post); curl_easy_setopt (url->handle, CURLOPT_WRITEHEADER, auth_user); if (strcmp (auth_user->mount, httpp_getvar (client->parser, HTTPP_VAR_URI)) != 0) admin = "&admin=1"; mount = util_url_escape (auth_user->mount); config = config_get_config (); host = util_url_escape (config->hostname); port = config->port; config_release_config (); ipaddr = util_url_escape (client->con->ip); if (client->username) { user = util_url_escape(client->username); } else { user = strdup(""); } if (client->password) { pass = util_url_escape(client->password); } else { pass = strdup(""); } snprintf (post, sizeof (post), "action=stream_auth&mount=%s&ip=%s&server=%s&port=%d&user=%s&pass=%s%s", mount, ipaddr, host, port, user, pass, admin); free (ipaddr); free (user); free (pass); free (mount); free (host); client->authenticated = 0; if (curl_easy_perform (url->handle)) ICECAST_LOG_WARN("auth to server %s failed with %s", url->stream_auth, url->errormsg); }
static void write_mp3_to_file (struct source_tag *source, refbuf_t *refbuf) { if (refbuf->len == 0) return; if (fwrite (refbuf->data, 1, refbuf->len, source->dumpfile) < (size_t)refbuf->len) { ICECAST_LOG_WARN("Write to dump file failed, disabling"); fclose (source->dumpfile); source->dumpfile = NULL; } }
/* simple name=tag stat create/update */ void stats_event(const char *source, const char *name, const char *value) { stats_event_t *event; if (value && xmlCheckUTF8 ((unsigned char *)value) == 0) { ICECAST_LOG_WARN("seen non-UTF8 data, probably incorrect metadata (%s, %s)", name, value); return; } event = build_event(source, name, value); if (event) queue_global_event(event); }
static int write_ogg_data (struct source_tag *source, refbuf_t *refbuf) { int ret = 1; if (fwrite (refbuf->data, 1, refbuf->len, source->dumpfile) != refbuf->len) { ICECAST_LOG_WARN("Write to dump file failed, disabling"); fclose (source->dumpfile); source->dumpfile = NULL; ret = 0; } return ret; }
static void url_stream_end (auth_client *auth_user) { char *mount, *server; ice_config_t *config = config_get_config (); mount_proxy *mountinfo = config_find_mount (config, auth_user->mount, MOUNT_TYPE_NORMAL); auth_t *auth = mountinfo->auth; auth_url *url = auth->state; char *stream_end_url; int port; char post [4096]; if (url->stream_end == NULL) { config_release_config (); return; } server = util_url_escape (config->hostname); port = config->port; stream_end_url = strdup (url->stream_end); /* we don't want this auth disappearing from under us while * the connection is in progress */ mountinfo->auth->refcount++; config_release_config (); mount = util_url_escape (auth_user->mount); snprintf (post, sizeof (post), "action=mount_remove&mount=%s&server=%s&port=%d", mount, server, port); free (server); free (mount); if (strchr (url->stream_end, '@') == NULL) { if (url->userpwd) curl_easy_setopt (url->handle, CURLOPT_USERPWD, url->userpwd); else curl_easy_setopt (url->handle, CURLOPT_USERPWD, ""); } else curl_easy_setopt (url->handle, CURLOPT_USERPWD, ""); curl_easy_setopt (url->handle, CURLOPT_URL, url->stream_end); curl_easy_setopt (url->handle, CURLOPT_POSTFIELDS, post); curl_easy_setopt (url->handle, CURLOPT_WRITEHEADER, auth_user); if (curl_easy_perform (url->handle)) ICECAST_LOG_WARN("auth to server %s failed with %s", stream_end_url, url->errormsg); auth_release (auth); free (stream_end_url); return; }
/* Called from auth thread to process any request for source client * authentication. Only applies to source clients, not relays. */ static void stream_auth_callback (auth_t *auth, auth_client *auth_user) { client_t *client = auth_user->client; if (auth->stream_auth) auth->stream_auth (auth_user); auth_release (auth); client->auth = NULL; if (client->authenticated) auth_postprocess_source (auth_user); else ICECAST_LOG_WARN("Failed auth for source \"%s\"", auth_user->mount); }
/* helper to apply specialised changes to a stats node */ static void modify_node_event(stats_node_t *node, stats_event_t *event) { char *str; if (event->action == STATS_EVENT_HIDDEN) { if (event->value) node->hidden = 1; else node->hidden = 0; return; } if (event->action != STATS_EVENT_SET) { int64_t value = 0; switch (event->action) { case STATS_EVENT_INC: value = atoi (node->value)+1; break; case STATS_EVENT_DEC: value = atoi (node->value)-1; break; case STATS_EVENT_ADD: value = atoi (node->value)+atoi (event->value); break; case STATS_EVENT_SUB: value = atoll (node->value) - atoll (event->value); break; default: ICECAST_LOG_WARN("unhandled event (%d) for %s", event->action, event->source); break; } str = malloc (16); snprintf (str, 16, "%" PRId64, value); if (event->value == NULL) event->value = strdup (str); } else str = (char *)strdup (event->value); free (node->value); node->value = str; if (event->source) ICECAST_LOG_DEBUG("update \"%s\" %s (%s)", event->source, node->name, node->value); else ICECAST_LOG_DEBUG("update global %s (%s)", node->name, node->value); }
static auth_client *auth_client_setup (const char *mount, client_t *client) { /* This will look something like "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" */ const char *header = httpp_getvar(client->parser, "authorization"); char *userpass, *tmp; char *username, *password; auth_client *auth_user; do { if (header == NULL) break; if (strncmp(header, "Basic ", 6) == 0) { userpass = util_base64_decode (header+6); if (userpass == NULL) { ICECAST_LOG_WARN("Base64 decode of Authorization header \"%s\" failed", header+6); break; } tmp = strchr(userpass, ':'); if (tmp == NULL) { free (userpass); break; } *tmp = 0; username = userpass; password = tmp+1; client->username = strdup (username); client->password = strdup (password); free (userpass); break; } ICECAST_LOG_INFO("unhandled authorization header: %s", header); } while (0); auth_user = calloc (1, sizeof(auth_client)); auth_user->mount = strdup (mount); auth_user->client = client; return auth_user; }
static xsltStylesheetPtr xslt_get_stylesheet(const char *fn) { int i; int empty = -1; struct stat file; if(stat(fn, &file)) { ICECAST_LOG_WARN("Error checking for stylesheet file \"%s\": %s", fn, strerror(errno)); return NULL; } for(i=0; i < CACHESIZE; i++) { if(cache[i].filename) { #ifdef _WIN32 if(!stricmp(fn, cache[i].filename)) #else if(!strcmp(fn, cache[i].filename)) #endif { if(file.st_mtime > cache[i].last_modified) { xsltFreeStylesheet(cache[i].stylesheet); cache[i].last_modified = file.st_mtime; cache[i].stylesheet = xsltParseStylesheetFile (XMLSTR(fn)); cache[i].cache_age = time(NULL); } ICECAST_LOG_DEBUG("Using cached sheet %i", i); return cache[i].stylesheet; } } else empty = i; } if(empty>=0) i = empty; else i = evict_cache_entry(); cache[i].last_modified = file.st_mtime; cache[i].filename = strdup(fn); cache[i].stylesheet = xsltParseStylesheetFile (XMLSTR(fn)); cache[i].cache_age = time(NULL); return cache[i].stylesheet; }
/* printf style formatting for stat create/update */ void stats_event_args(const char *source, char *name, char *format, ...) { char buf[1024]; va_list val; int ret; if (name == NULL) return; va_start(val, format); ret = vsnprintf(buf, sizeof(buf), format, val); va_end(val); if (ret < 0 || (unsigned int)ret >= sizeof (buf)) { ICECAST_LOG_WARN("problem with formatting %s stat %s", source==NULL ? "global" : source, name); return; } stats_event(source, name, buf); }
static int get_authenticator (auth_t *auth, config_options_t *options) { if (auth->type == NULL) { ICECAST_LOG_WARN("no authentication type defined"); return -1; } do { ICECAST_LOG_DEBUG("type is %s", auth->type); if (strcmp (auth->type, "url") == 0) { #ifdef HAVE_AUTH_URL if (auth_get_url_auth (auth, options) < 0) return -1; break; #else ICECAST_LOG_ERROR("Auth URL disabled"); return -1; #endif } if (strcmp (auth->type, "htpasswd") == 0) { if (auth_get_htpasswd_auth (auth, options) < 0) return -1; break; } ICECAST_LOG_ERROR("Unrecognised authenticator type: \"%s\"", auth->type); return -1; } while (0); while (options) { if (strcmp (options->name, "allow_duplicate_users") == 0) auth->allow_duplicate_users = atoi ((char*)options->value); options = options->next; } return 0; }
/* Add a listener. Check for any mount information that states any * authentication to be used. */ void auth_add_listener (const char *mount, client_t *client) { mount_proxy *mountinfo; ice_config_t *config = config_get_config(); mountinfo = config_find_mount (config, mount, MOUNT_TYPE_NORMAL); if (mountinfo && mountinfo->no_mount) { config_release_config (); client_send_403 (client, "mountpoint unavailable"); return; } if (mountinfo && mountinfo->auth) { auth_client *auth_user; if (mountinfo->auth->pending_count > 100) { config_release_config (); ICECAST_LOG_WARN("too many clients awaiting authentication"); client_send_403 (client, "busy, please try again later"); return; } auth_user = auth_client_setup (mount, client); auth_user->process = auth_new_listener; ICECAST_LOG_INFO("adding client for authentication"); queue_auth_client (auth_user, mountinfo); config_release_config (); } else { int ret = add_authenticated_listener (mount, mountinfo, client); config_release_config (); if (ret < 0) client_send_403 (client, "max listeners reached"); } }
static auth_result url_add_listener (auth_client *auth_user) { client_t *client = auth_user->client; auth_t *auth = client->auth; auth_url *url = auth->state; int res = 0, port; const char *agent; char *user_agent, *username, *password; const char *mountreq; char *mount, *ipaddr, *server; ice_config_t *config; char *userpwd = NULL, post [4096]; ssize_t post_offset; char *pass_headers, *cur_header, *next_header; const char *header_val; char *header_valesc; if (url->addurl == NULL) return AUTH_OK; config = config_get_config (); server = util_url_escape (config->hostname); port = config->port; config_release_config (); agent = httpp_getvar (client->parser, "user-agent"); if (agent) user_agent = util_url_escape (agent); else user_agent = strdup ("-"); if (client->username) username = util_url_escape (client->username); else username = strdup (""); if (client->password) password = util_url_escape (client->password); else password = strdup (""); /* get the full uri (with query params if available) */ mountreq = httpp_getvar (client->parser, HTTPP_VAR_RAWURI); if (mountreq == NULL) mountreq = httpp_getvar (client->parser, HTTPP_VAR_URI); mount = util_url_escape (mountreq); ipaddr = util_url_escape (client->con->ip); post_offset = snprintf (post, sizeof (post), "action=listener_add&server=%s&port=%d&client=%lu&mount=%s" "&user=%s&pass=%s&ip=%s&agent=%s", server, port, client->con->id, mount, username, password, ipaddr, user_agent); free (server); free (mount); free (user_agent); free (username); free (password); free (ipaddr); pass_headers = NULL; if (url->pass_headers) pass_headers = strdup (url->pass_headers); if (pass_headers) { cur_header = pass_headers; while (cur_header) { next_header = strstr (cur_header, ","); if (next_header) { *next_header=0; next_header++; } header_val = httpp_getvar (client->parser, cur_header); if (header_val) { header_valesc = util_url_escape (header_val); post_offset += snprintf (post+post_offset, sizeof (post)-post_offset, "&%s%s=%s", url->prefix_headers ? url->prefix_headers : "", cur_header, header_valesc); free (header_valesc); } cur_header = next_header; } } if (strchr (url->addurl, '@') == NULL) { if (url->userpwd) curl_easy_setopt (url->handle, CURLOPT_USERPWD, url->userpwd); else { /* auth'd requests may not have a user/pass, but may use query args */ if (client->username && client->password) { size_t len = strlen (client->username) + strlen (client->password) + 2; userpwd = malloc (len); snprintf (userpwd, len, "%s:%s", client->username, client->password); curl_easy_setopt (url->handle, CURLOPT_USERPWD, userpwd); } else curl_easy_setopt (url->handle, CURLOPT_USERPWD, ""); } } else { /* url has user/pass but libcurl may need to clear any existing settings */ curl_easy_setopt (url->handle, CURLOPT_USERPWD, ""); } curl_easy_setopt (url->handle, CURLOPT_URL, url->addurl); curl_easy_setopt (url->handle, CURLOPT_POSTFIELDS, post); curl_easy_setopt (url->handle, CURLOPT_WRITEHEADER, auth_user); url->errormsg[0] = '\0'; res = curl_easy_perform (url->handle); free (userpwd); if (res) { ICECAST_LOG_WARN("auth to server %s failed with %s", url->addurl, url->errormsg); return AUTH_FAILED; } /* we received a response, lets see what it is */ if (client->authenticated) return AUTH_OK; ICECAST_LOG_INFO("client auth (%s) failed with \"%s\"", url->addurl, url->errormsg); return AUTH_FAILED; }
static auth_result url_remove_listener (auth_client *auth_user) { client_t *client = auth_user->client; auth_t *auth = client->auth; auth_url *url = auth->state; time_t duration = time(NULL) - client->con->con_time; char *username, *password, *mount, *server; const char *mountreq; ice_config_t *config; int port; char *userpwd = NULL, post [4096]; const char *agent; char *user_agent, *ipaddr; if (url->removeurl == NULL) return AUTH_OK; config = config_get_config (); server = util_url_escape (config->hostname); port = config->port; config_release_config (); agent = httpp_getvar (client->parser, "user-agent"); if (agent) user_agent = util_url_escape (agent); else user_agent = strdup ("-"); if (client->username) username = util_url_escape (client->username); else username = strdup (""); if (client->password) password = util_url_escape (client->password); else password = strdup (""); /* get the full uri (with query params if available) */ mountreq = httpp_getvar (client->parser, HTTPP_VAR_RAWURI); if (mountreq == NULL) mountreq = httpp_getvar (client->parser, HTTPP_VAR_URI); mount = util_url_escape (mountreq); ipaddr = util_url_escape (client->con->ip); snprintf (post, sizeof (post), "action=listener_remove&server=%s&port=%d&client=%lu&mount=%s" "&user=%s&pass=%s&duration=%lu&ip=%s&agent=%s", server, port, client->con->id, mount, username, password, (long unsigned)duration, ipaddr, user_agent); free (server); free (mount); free (username); free (password); free (ipaddr); free (user_agent); if (strchr (url->removeurl, '@') == NULL) { if (url->userpwd) curl_easy_setopt (url->handle, CURLOPT_USERPWD, url->userpwd); else { /* auth'd requests may not have a user/pass, but may use query args */ if (client->username && client->password) { size_t len = strlen (client->username) + strlen (client->password) + 2; userpwd = malloc (len); snprintf (userpwd, len, "%s:%s", client->username, client->password); curl_easy_setopt (url->handle, CURLOPT_USERPWD, userpwd); } else curl_easy_setopt (url->handle, CURLOPT_USERPWD, ""); } } else { /* url has user/pass but libcurl may need to clear any existing settings */ curl_easy_setopt (url->handle, CURLOPT_USERPWD, ""); } curl_easy_setopt (url->handle, CURLOPT_URL, url->removeurl); curl_easy_setopt (url->handle, CURLOPT_POSTFIELDS, post); curl_easy_setopt (url->handle, CURLOPT_WRITEHEADER, auth_user); if (curl_easy_perform (url->handle)) ICECAST_LOG_WARN("auth to server %s failed with %s", url->removeurl, url->errormsg); free (userpwd); return AUTH_OK; }
void xslt_transform(xmlDocPtr doc, const char *xslfilename, client_t *client) { xmlDocPtr res; xsltStylesheetPtr cur; xmlChar *string; int len, problem = 0; const char *mediatype = NULL; const char *charset = NULL; xmlSetGenericErrorFunc ("", log_parse_failure); xsltSetGenericErrorFunc ("", log_parse_failure); thread_mutex_lock(&xsltlock); cur = xslt_get_stylesheet(xslfilename); if (cur == NULL) { thread_mutex_unlock(&xsltlock); ICECAST_LOG_ERROR("problem reading stylesheet \"%s\"", xslfilename); client_send_404 (client, "Could not parse XSLT file"); return; } res = xsltApplyStylesheet(cur, doc, NULL); if (xsltSaveResultToString (&string, &len, res, cur) < 0) problem = 1; /* lets find out the content type and character encoding to use */ if (cur->encoding) charset = (char *)cur->encoding; if (cur->mediaType) mediatype = (char *)cur->mediaType; else { /* check method for the default, a missing method assumes xml */ if (cur->method && xmlStrcmp (cur->method, XMLSTR("html")) == 0) mediatype = "text/html"; else if (cur->method && xmlStrcmp (cur->method, XMLSTR("text")) == 0) mediatype = "text/plain"; else mediatype = "text/xml"; } if (problem == 0) { ssize_t ret; int failed = 0; refbuf_t *refbuf; size_t full_len = strlen (mediatype) + len + 1024; if (full_len < 4096) full_len = 4096; refbuf = refbuf_new (full_len); if (string == NULL) string = xmlCharStrdup (""); ret = util_http_build_header(refbuf->data, full_len, 0, 0, 200, NULL, mediatype, charset, NULL, NULL); if (ret == -1) { ICECAST_LOG_ERROR("Dropping client as we can not build response headers."); client_send_500(client, "Header generation failed."); } else { if ( full_len < (ret + len + 64) ) { void *new_data; full_len = ret + len + 64; new_data = realloc(refbuf->data, full_len); if (new_data) { ICECAST_LOG_DEBUG("Client buffer reallocation succeeded."); refbuf->data = new_data; refbuf->len = full_len; ret = util_http_build_header(refbuf->data, full_len, 0, 0, 200, NULL, mediatype, charset, NULL, NULL); if (ret == -1) { ICECAST_LOG_ERROR("Dropping client as we can not build response headers."); client_send_500(client, "Header generation failed."); failed = 1; } } else { ICECAST_LOG_ERROR("Client buffer reallocation failed. Dropping client."); client_send_500(client, "Buffer reallocation failed."); failed = 1; } } if (!failed) { snprintf(refbuf->data + ret, full_len - ret, "Content-Length: %d\r\n\r\n%s", len, string); client->respcode = 200; client_set_queue (client, NULL); client->refbuf = refbuf; refbuf->len = strlen (refbuf->data); fserve_add_client (client, NULL); } } xmlFree (string); } else { ICECAST_LOG_WARN("problem applying stylesheet \"%s\"", xslfilename); client_send_404 (client, "XSLT problem"); } thread_mutex_unlock (&xsltlock); xmlFreeDoc(res); }
static void ebml_write_buf_to_file_fail (source_t *source) { ICECAST_LOG_WARN("Write to dump file failed, disabling"); fclose (source->dumpfile); source->dumpfile = NULL; }
auth_t *auth_get_authenticator (xmlNodePtr node) { auth_t *auth = calloc (1, sizeof (auth_t)); config_options_t *options = NULL, **next_option = &options; xmlNodePtr option; if (auth == NULL) return NULL; option = node->xmlChildrenNode; while (option) { xmlNodePtr current = option; option = option->next; if (xmlStrcmp (current->name, XMLSTR("option")) == 0) { config_options_t *opt = calloc (1, sizeof (config_options_t)); opt->name = (char *)xmlGetProp (current, XMLSTR("name")); if (opt->name == NULL) { free(opt); continue; } opt->value = (char *)xmlGetProp (current, XMLSTR("value")); if (opt->value == NULL) { xmlFree (opt->name); free (opt); continue; } *next_option = opt; next_option = &opt->next; } else if (xmlStrcmp (current->name, XMLSTR("text")) != 0) ICECAST_LOG_WARN("unknown auth setting (%s)", current->name); } auth->type = (char*)xmlGetProp (node, XMLSTR("type")); if (get_authenticator (auth, options) < 0) { xmlFree (auth->type); free (auth); auth = NULL; } else { auth->tailp = &auth->head; thread_mutex_create (&auth->lock); auth->refcount = 1; auth->running = 1; auth->thread = thread_create ("auth thread", auth_run_thread, auth, THREAD_ATTACHED); } while (options) { config_options_t *opt = options; options = opt->next; xmlFree (opt->name); xmlFree (opt->value); free (opt); } return auth; }
/* 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) { int bytes; struct stat file_buf; const char *range = NULL; off_t new_content_len = 0; off_t rangenumber = 0, content_length; int rangeproblem = 0; int ret = 0; char *fullpath; int m3u_requested = 0, m3u_file_available = 1; const char * xslt_playlist_requested = NULL; int xslt_playlist_file_available = 1; ice_config_t *config; FILE *file; fullpath = util_get_path_from_normalised_uri(httpclient->uri); ICECAST_LOG_INFO("checking for file %H (%H)", httpclient->uri, fullpath); if (strcmp (util_get_extension (fullpath), "m3u") == 0) m3u_requested = 1; if (strcmp (util_get_extension (fullpath), "xspf") == 0) xslt_playlist_requested = "xspf.xsl"; if (strcmp (util_get_extension (fullpath), "vclt") == 0) xslt_playlist_requested = "vclt.xsl"; /* check for the actual file */ if (stat (fullpath, &file_buf) != 0) { /* the m3u can be generated, but send an m3u file if available */ if (m3u_requested == 0 && xslt_playlist_requested == NULL) { ICECAST_LOG_WARN("req for file \"%H\" %s", fullpath, strerror (errno)); client_send_error_by_id(httpclient, ICECAST_ERROR_FSERV_FILE_NOT_FOUND); free (fullpath); return -1; } m3u_file_available = 0; xslt_playlist_file_available = 0; } httpclient->refbuf->len = PER_CLIENT_REFBUF_SIZE; if (m3u_requested && m3u_file_available == 0) { char *sourceuri = strdup(httpclient->uri); char *dot = strrchr(sourceuri, '.'); *dot = 0; httpclient->respcode = 200; ret = util_http_build_header (httpclient->refbuf->data, BUFSIZE, 0, 0, 200, NULL, "audio/x-mpegurl", NULL, "", NULL, httpclient); if (ret == -1 || ret >= (BUFSIZE - 512)) { /* we want at least 512 bytes left for the content of the playlist */ ICECAST_LOG_ERROR("Dropping client as we can not build response headers."); client_send_error_by_id(httpclient, ICECAST_ERROR_GEN_HEADER_GEN_FAILED); free(sourceuri); return -1; } client_get_baseurl(httpclient, NULL, httpclient->refbuf->data + ret, BUFSIZE - ret, NULL, NULL, NULL, sourceuri, "\r\n"); httpclient->refbuf->len = strlen (httpclient->refbuf->data); fserve_add_client (httpclient, NULL); free (sourceuri); free (fullpath); return 0; } if (xslt_playlist_requested && xslt_playlist_file_available == 0) { xmlDocPtr doc; char *reference = strdup(httpclient->uri); char *eol = strrchr (reference, '.'); if (eol) *eol = '\0'; doc = stats_get_xml (0, reference, httpclient); free (reference); admin_send_response (doc, httpclient, ADMIN_FORMAT_HTML, xslt_playlist_requested); xmlFreeDoc(doc); free (fullpath); return 0; } /* on demand file serving check */ config = config_get_config(); if (config->fileserve == 0) { ICECAST_LOG_DEBUG("on demand file \"%H\" refused. Serving static files has been disabled in the config", fullpath); client_send_error_by_id(httpclient, ICECAST_ERROR_FSERV_FILE_NOT_FOUND); config_release_config(); free(fullpath); return -1; } config_release_config(); if (S_ISREG (file_buf.st_mode) == 0) { client_send_error_by_id(httpclient, ICECAST_ERROR_FSERV_FILE_NOT_FOUND); ICECAST_LOG_WARN("found requested file but there is no handler for it: %H", fullpath); free (fullpath); return -1; } file = fopen (fullpath, "rb"); if (file == NULL) { ICECAST_LOG_WARN("Problem accessing file \"%H\"", fullpath); client_send_error_by_id(httpclient, ICECAST_ERROR_FSERV_FILE_NOT_READABLE); free (fullpath); return -1; } free (fullpath); content_length = file_buf.st_size; range = httpp_getvar (httpclient->parser, "range"); /* full http range handling is currently not done but we deal with the common case */ if (range != NULL) { ret = 0; if (strncasecmp (range, "bytes=", 6) == 0) ret = sscanf (range+6, "%" SCN_OFF_T "-", &rangenumber); if (ret != 1) { /* format not correct, so lets just assume we start from the beginning */ rangeproblem = 1; } if (rangenumber < 0) { rangeproblem = 1; } if (!rangeproblem) { ret = fseeko (file, rangenumber, SEEK_SET); if (ret != -1) { new_content_len = content_length - rangenumber; if (new_content_len < 0) { rangeproblem = 1; } } else { rangeproblem = 1; } if (!rangeproblem) { off_t endpos = rangenumber+new_content_len-1; char *type; if (endpos < 0) { endpos = 0; } httpclient->respcode = 206; type = fserve_content_type(httpclient->uri); bytes = util_http_build_header (httpclient->refbuf->data, BUFSIZE, 0, 0, 206, NULL, type, NULL, NULL, NULL, httpclient); if (bytes == -1 || bytes >= (BUFSIZE - 512)) { /* we want at least 512 bytes left */ ICECAST_LOG_ERROR("Dropping client as we can not build response headers."); client_send_error_by_id(httpclient, ICECAST_ERROR_GEN_HEADER_GEN_FAILED); return -1; } bytes += snprintf (httpclient->refbuf->data + bytes, BUFSIZE - bytes, "Accept-Ranges: bytes\r\n" "Content-Length: %" PRI_OFF_T "\r\n" "Content-Range: bytes %" PRI_OFF_T \ "-%" PRI_OFF_T "/%" PRI_OFF_T "\r\n\r\n", new_content_len, rangenumber, endpos, content_length); free (type); } else { goto fail; } } else { goto fail; } } else { char *type = fserve_content_type(httpclient->uri); httpclient->respcode = 200; bytes = util_http_build_header (httpclient->refbuf->data, BUFSIZE, 0, 0, 200, NULL, type, NULL, NULL, NULL, httpclient); if (bytes == -1 || bytes >= (BUFSIZE - 512)) { /* we want at least 512 bytes left */ ICECAST_LOG_ERROR("Dropping client as we can not build response headers."); client_send_error_by_id(httpclient, ICECAST_ERROR_GEN_HEADER_GEN_FAILED); fclose(file); return -1; } bytes += snprintf (httpclient->refbuf->data + bytes, BUFSIZE - bytes, "Accept-Ranges: bytes\r\n" "Content-Length: %" PRI_OFF_T "\r\n\r\n", content_length); free (type); } httpclient->refbuf->len = bytes; httpclient->pos = 0; stats_event_inc (NULL, "file_connections"); fserve_add_client (httpclient, file); return 0; fail: fclose (file); client_send_error_by_id(httpclient, ICECAST_ERROR_FSERV_REQUEST_RANGE_NOT_SATISFIABLE); return -1; }
/* wrapper for starting the provided relay stream */ static void check_relay_stream (relay_server *relay) { if (relay->source == NULL) { if (relay->localmount[0] != '/') { ICECAST_LOG_WARN("relay mountpoint \"%s\" does not start with /, skipping", relay->localmount); return; } /* new relay, reserve the name */ relay->source = source_reserve (relay->localmount); if (relay->source) { ICECAST_LOG_DEBUG("Adding relay source at mountpoint \"%s\"", relay->localmount); if (relay->on_demand) { ice_config_t *config = config_get_config (); mount_proxy *mountinfo = config_find_mount (config, relay->localmount, MOUNT_TYPE_NORMAL); relay->source->on_demand = relay->on_demand; if (mountinfo == NULL) source_update_settings (config, relay->source, mountinfo); config_release_config (); stats_event (relay->localmount, "listeners", "0"); slave_update_all_mounts(); } } else { if (relay->start == 0) { ICECAST_LOG_WARN("new relay but source \"%s\" already exists", relay->localmount); relay->start = 1; } return; } } do { source_t *source = relay->source; /* skip relay if active, not configured or just not time yet */ if (relay->source == NULL || relay->running || relay->start > time(NULL)) break; /* check if an inactive on-demand relay has a fallback that has listeners */ if (relay->on_demand && source->on_demand_req == 0) { relay->source->on_demand = relay->on_demand; if (source->fallback_mount && source->fallback_override) { source_t *fallback; avl_tree_rlock (global.source_tree); fallback = source_find_mount (source->fallback_mount); if (fallback && fallback->running && fallback->listeners) { ICECAST_LOG_DEBUG("fallback running %d with %lu listeners", fallback->running, fallback->listeners); source->on_demand_req = 1; } avl_tree_unlock (global.source_tree); } if (source->on_demand_req == 0) break; } relay->start = time(NULL) + 5; relay->running = 1; relay->thread = thread_create ("Relay Thread", start_relay_stream, relay, THREAD_ATTACHED); return; } while (0); /* the relay thread may of shut down itself */ if (relay->cleanup) { if (relay->thread) { ICECAST_LOG_DEBUG("waiting for relay thread for \"%s\"", relay->localmount); thread_join (relay->thread); relay->thread = NULL; } relay->cleanup = 0; relay->running = 0; if (relay->on_demand && relay->source) { ice_config_t *config = config_get_config (); mount_proxy *mountinfo = config_find_mount (config, relay->localmount, MOUNT_TYPE_NORMAL); source_update_settings (config, relay->source, mountinfo); config_release_config (); stats_event (relay->localmount, "listeners", "0"); } } }
/* Actually open the connection and do some http parsing, handle any 302 * responses within here. */ static client_t *open_relay_connection (relay_server *relay) { int redirects = 0; char *server_id = NULL; ice_config_t *config; http_parser_t *parser = NULL; connection_t *con=NULL; char *server = strdup (relay->server); char *mount = strdup (relay->mount); int port = relay->port; char *auth_header; char header[4096]; config = config_get_config (); server_id = strdup (config->server_id); config_release_config (); /* build any authentication header before connecting */ if (relay->username && relay->password) { char *esc_authorisation; unsigned len = strlen(relay->username) + strlen(relay->password) + 2; auth_header = malloc (len); snprintf (auth_header, len, "%s:%s", relay->username, relay->password); esc_authorisation = util_base64_encode(auth_header, len); free(auth_header); len = strlen (esc_authorisation) + 24; auth_header = malloc (len); snprintf (auth_header, len, "Authorization: Basic %s\r\n", esc_authorisation); free(esc_authorisation); } else auth_header = strdup (""); while (redirects < 10) { sock_t streamsock; ICECAST_LOG_INFO("connecting to %s:%d", server, port); streamsock = sock_connect_wto_bind (server, port, relay->bind, 10); if (streamsock == SOCK_ERROR) { ICECAST_LOG_WARN("Failed to connect to %s:%d", server, port); break; } con = connection_create (streamsock, -1, strdup (server)); /* At this point we may not know if we are relaying an mp3 or vorbis * stream, but only send the icy-metadata header if the relay details * state so (the typical case). It's harmless in the vorbis case. If * we don't send in this header then relay will not have mp3 metadata. */ sock_write(streamsock, "GET %s HTTP/1.0\r\n" "User-Agent: %s\r\n" "Host: %s\r\n" "%s" "%s" "\r\n", mount, server_id, server, relay->mp3metadata?"Icy-MetaData: 1\r\n":"", auth_header); memset (header, 0, sizeof(header)); if (util_read_header (con->sock, header, 4096, READ_ENTIRE_HEADER) == 0) { ICECAST_LOG_ERROR("Header read failed for %s (%s:%d%s)", relay->localmount, server, port, mount); break; } parser = httpp_create_parser(); httpp_initialize (parser, NULL); if (! httpp_parse_response (parser, header, strlen(header), relay->localmount)) { ICECAST_LOG_ERROR("Error parsing relay request for %s (%s:%d%s)", relay->localmount, server, port, mount); break; } if (strcmp (httpp_getvar (parser, HTTPP_VAR_ERROR_CODE), "302") == 0) { /* better retry the connection again but with different details */ const char *uri, *mountpoint; int len; uri = httpp_getvar (parser, "location"); ICECAST_LOG_INFO("redirect received %s", uri); if (strncmp (uri, "http://", 7) != 0) break; uri += 7; mountpoint = strchr (uri, '/'); free (mount); if (mountpoint) mount = strdup (mountpoint); else mount = strdup ("/"); len = strcspn (uri, ":/"); port = 80; if (uri [len] == ':') port = atoi (uri+len+1); free (server); server = calloc (1, len+1); strncpy (server, uri, len); connection_close (con); httpp_destroy (parser); con = NULL; parser = NULL; } else { client_t *client = NULL; if (httpp_getvar (parser, HTTPP_VAR_ERROR_MESSAGE)) { ICECAST_LOG_ERROR("Error from relay request: %s (%s)", relay->localmount, httpp_getvar(parser, HTTPP_VAR_ERROR_MESSAGE)); break; } global_lock (); if (client_create (&client, con, parser) < 0) { global_unlock (); /* make sure only the client_destory frees these */ con = NULL; parser = NULL; client_destroy (client); break; } global_unlock (); sock_set_blocking (streamsock, 0); client_set_queue (client, NULL); free (server); free (mount); free (server_id); free (auth_header); return client; } redirects++; } /* failed, better clean up */ free (server); free (mount); free (server_id); free (auth_header); if (con) connection_close (con); if (parser) httpp_destroy (parser); return NULL; }
void fserve_recheck_mime_types(ice_config_t *config) { FILE *mimefile; char line[4096]; char *type, *ext, *cur; mime_type *mapping; avl_tree *new_mimetypes; if (config->mimetypes_fn == NULL) return; mimefile = fopen (config->mimetypes_fn, "r"); if (mimefile == NULL) { ICECAST_LOG_WARN("Cannot open mime types file %s", config->mimetypes_fn); return; } new_mimetypes = avl_tree_new(_compare_mappings, NULL); while(fgets(line, 4096, mimefile)) { line[4095] = 0; if(*line == 0 || *line == '#') continue; type = line; cur = line; while(*cur != ' ' && *cur != '\t' && *cur) cur++; if(*cur == 0) continue; *cur++ = 0; while(1) { while(*cur == ' ' || *cur == '\t') cur++; if(*cur == 0) break; ext = cur; while(*cur != ' ' && *cur != '\t' && *cur != '\n' && *cur) cur++; *cur++ = 0; if(*ext) { void *tmp; /* Add a new extension->type mapping */ mapping = malloc(sizeof(mime_type)); mapping->ext = strdup(ext); mapping->type = strdup(type); if (!avl_get_by_key (new_mimetypes, mapping, &tmp)) avl_delete (new_mimetypes, mapping, _delete_mapping); avl_insert (new_mimetypes, mapping); } } } fclose(mimefile); thread_spin_lock (&pending_lock); if (mimetypes) avl_tree_free (mimetypes, _delete_mapping); mimetypes = new_mimetypes; thread_spin_unlock (&pending_lock); }
/* called from the source thread when the metadata has been updated. * The artist title are checked and made ready for clients to send */ static void mp3_set_title (source_t *source) { const char streamtitle[] = "StreamTitle='"; const char streamurl[] = "StreamUrl='"; size_t size; unsigned char len_byte; refbuf_t *p; unsigned int len = sizeof(streamtitle) + 2; /* the StreamTitle, quotes, ; and null */ mp3_state *source_mp3 = source->format->_state; /* make sure the url data does not disappear from under us */ thread_mutex_lock (&source_mp3->url_lock); /* work out message length */ if (source_mp3->url_artist) len += strlen (source_mp3->url_artist); if (source_mp3->url_title) len += strlen (source_mp3->url_title); if (source_mp3->url_artist && source_mp3->url_title) len += 3; if (source_mp3->inline_url) { char *end = strstr (source_mp3->inline_url, "';"); if (end) len += end - source_mp3->inline_url+2; } else if (source_mp3->url) len += strlen (source_mp3->url) + strlen (streamurl) + 2; #define MAX_META_LEN 255*16 if (len > MAX_META_LEN) { thread_mutex_unlock (&source_mp3->url_lock); ICECAST_LOG_WARN("Metadata too long at %d chars", len); return; } /* work out the metadata len byte */ len_byte = (len-1) / 16 + 1; /* now we know how much space to allocate, +1 for the len byte */ size = len_byte * 16 + 1; p = refbuf_new (size); if (p) { mp3_state *source_mp3 = source->format->_state; int r; memset (p->data, '\0', size); if (source_mp3->url_artist && source_mp3->url_title) r = snprintf (p->data, size, "%c%s%s - %s';", len_byte, streamtitle, source_mp3->url_artist, source_mp3->url_title); else r = snprintf (p->data, size, "%c%s%s';", len_byte, streamtitle, source_mp3->url_title); if (r > 0) { if (source_mp3->inline_url) { char *end = strstr (source_mp3->inline_url, "';"); ssize_t urllen = size; if (end) urllen = end - source_mp3->inline_url + 2; if ((ssize_t)(size-r) > urllen) snprintf (p->data+r, size-r, "StreamUrl='%s';", source_mp3->inline_url+11); } else if (source_mp3->url) snprintf (p->data+r, size-r, "StreamUrl='%s';", source_mp3->url); } ICECAST_LOG_DEBUG("shoutcast metadata block setup with %s", p->data+1); filter_shoutcast_metadata (source, p->data, size); refbuf_release (source_mp3->metadata); source_mp3->metadata = p; } thread_mutex_unlock (&source_mp3->url_lock); }