void fserve_shutdown(void) { if (!__inited) return; thread_spin_lock (&pending_lock); run_fserv = 0; while (pending_list) { fserve_t *to_go = (fserve_t *)pending_list; pending_list = to_go->next; fserve_client_destroy (to_go); } while (active_list) { fserve_t *to_go = active_list; active_list = to_go->next; fserve_client_destroy (to_go); } if (mimetypes) avl_tree_free (mimetypes, _delete_mapping); thread_spin_unlock (&pending_lock); thread_spin_destroy (&pending_lock); ICECAST_LOG_INFO("file serving stopped"); }
ogg_codec_t *initial_opus_page (format_plugin_t *plugin, ogg_page *page) { ogg_state_t *ogg_info = plugin->_state; ogg_codec_t *codec = calloc (1, sizeof (ogg_codec_t)); ogg_packet packet; ogg_stream_init (&codec->os, ogg_page_serialno (page)); ogg_stream_pagein (&codec->os, page); ogg_stream_packetout (&codec->os, &packet); ICECAST_LOG_DEBUG("checking for opus codec"); if (strncmp((char *)packet.packet, "OpusHead", 8) != 0) { ogg_stream_clear (&codec->os); free (codec); return NULL; } ICECAST_LOG_INFO("seen initial opus header"); codec->process_page = process_opus_page; codec->codec_free = opus_codec_free; codec->headers = 1; format_ogg_attach_header (ogg_info, page); return codec; }
/* wrapper function for auth thread to authenticate new listener * connection details */ static void auth_new_listener (auth_t *auth, auth_client *auth_user) { client_t *client = auth_user->client; /* make sure there is still a client at this point, a slow backend request * can be avoided if client has disconnected */ if (is_listener_connected (client) == 0) { ICECAST_LOG_DEBUG("listener is no longer connected"); client->respcode = 400; auth_release (client->auth); client->auth = NULL; return; } if (auth->authenticate) { if (auth->authenticate (auth_user) != AUTH_OK) { auth_release (client->auth); client->auth = NULL; return; } } if (auth_postprocess_listener (auth_user) < 0) { auth_release (client->auth); client->auth = NULL; ICECAST_LOG_INFO("client %lu failed", client->con->id); } }
ogg_codec_t *initial_midi_page (format_plugin_t *plugin, ogg_page *page) { ogg_state_t *ogg_info = plugin->_state; ogg_codec_t *codec = calloc (1, sizeof (ogg_codec_t)); ogg_packet packet; ogg_stream_init (&codec->os, ogg_page_serialno (page)); ogg_stream_pagein (&codec->os, page); ogg_stream_packetout (&codec->os, &packet); ICECAST_LOG_DEBUG("checking for MIDI codec"); do { if (packet.bytes < 9) break; if (memcmp (packet.packet, "OggMIDI\000", 8) != 0) break; if (packet.bytes != 12) break; ICECAST_LOG_INFO("seen initial MIDI header"); codec->process_page = process_midi_page; codec->codec_free = midi_codec_free; codec->headers = 1; codec->name = "MIDI"; format_ogg_attach_header(ogg_info, page); return codec; } while (0); ogg_stream_clear(&codec->os); free(codec); return NULL; }
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); }
void *stats_connection(void *arg) { client_t *client = (client_t *)arg; stats_event_t *event; event_listener_t listener; ICECAST_LOG_INFO("stats client starting"); event_queue_init (&listener.queue); /* increment the thread count */ thread_mutex_lock(&_stats_mutex); _stats_threads++; stats_event_args (NULL, "stats", "%d", _stats_threads); thread_mutex_unlock(&_stats_mutex); thread_mutex_create (&(listener.mutex)); _register_listener (&listener); while (_stats_running) { thread_mutex_lock (&listener.mutex); event = _get_event_from_queue (&listener.queue); thread_mutex_unlock (&listener.mutex); if (event != NULL) { if (_send_event_to_client(event, client) < 0) { _free_event(event); break; } _free_event(event); continue; } thread_sleep (500000); } thread_mutex_lock(&_stats_mutex); _unregister_listener (&listener); _stats_threads--; stats_event_args (NULL, "stats", "%d", _stats_threads); thread_mutex_unlock(&_stats_mutex); thread_mutex_destroy (&listener.mutex); client_destroy (client); ICECAST_LOG_INFO("stats client finished"); return NULL; }
/* The auth thread main loop. */ static void *auth_run_thread (void *arg) { auth_t *auth = arg; ICECAST_LOG_INFO("Authentication thread started"); while (auth->running) { /* usually no clients are waiting, so don't bother taking locks */ if (auth->head) { auth_client *auth_user; /* may become NULL before lock taken */ thread_mutex_lock (&auth->lock); auth_user = (auth_client*)auth->head; if (auth_user == NULL) { thread_mutex_unlock (&auth->lock); continue; } ICECAST_LOG_DEBUG("%d client(s) pending on %s", auth->pending_count, auth->mount); auth->head = auth_user->next; if (auth->head == NULL) auth->tailp = &auth->head; auth->pending_count--; thread_mutex_unlock (&auth->lock); auth_user->next = NULL; if (auth_user->process) auth_user->process (auth, auth_user); else ICECAST_LOG_ERROR("client auth process not set"); auth_client_free (auth_user); continue; } thread_sleep (150000); } ICECAST_LOG_INFO("Authenication thread shutting down"); return NULL; }
/* Called when a source client connects and requires authentication via the * authenticator. This is called for both source clients and admin requests * that work on a specified mountpoint. */ int auth_stream_authenticate (client_t *client, const char *mount, mount_proxy *mountinfo) { if (mountinfo && mountinfo->auth && mountinfo->auth->stream_auth) { auth_client *auth_user = auth_client_setup (mount, client); auth_user->process = stream_auth_callback; ICECAST_LOG_INFO("request source auth for \"%s\"", mount); queue_auth_client (auth_user, mountinfo); return 1; } return 0; }
/* if 0 is returned then the client should not be touched, however if -1 * is returned then the caller is responsible for handling the client */ static int add_listener_to_source (source_t *source, client_t *client) { int loop = 10; do { ICECAST_LOG_DEBUG("max on %s is %ld (cur %lu)", source->mount, source->max_listeners, source->listeners); if (source->max_listeners == -1) break; if (source->listeners < (unsigned long)source->max_listeners) break; if (loop && source->fallback_when_full && source->fallback_mount) { source_t *next = source_find_mount (source->fallback_mount); if (!next) { ICECAST_LOG_ERROR("Fallback '%s' for full source '%s' not found", source->mount, source->fallback_mount); return -1; } ICECAST_LOG_INFO("stream full trying %s", next->mount); source = next; loop--; continue; } /* now we fail the client */ return -1; } while (1); client->write_to_client = format_generic_write_to_client; client->check_buffer = format_check_http_buffer; client->refbuf->len = PER_CLIENT_REFBUF_SIZE; memset (client->refbuf->data, 0, PER_CLIENT_REFBUF_SIZE); /* lets add the client to the active list */ avl_tree_wlock (source->pending_tree); avl_insert (source->pending_tree, client); avl_tree_unlock (source->pending_tree); if (source->running == 0 && source->on_demand) { /* enable on-demand relay to start, wake up the slave thread */ ICECAST_LOG_DEBUG("kicking off on-demand relay"); source->on_demand_req = 1; } ICECAST_LOG_DEBUG("Added client to %s", source->mount); return 0; }
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; }
void fserve_initialize(void) { ice_config_t *config = config_get_config(); mimetypes = NULL; active_list = NULL; pending_list = NULL; thread_spin_create (&pending_lock); fserve_recheck_mime_types (config); config_release_config(); __inited = 1; stats_event (NULL, "file_connections", "0"); ICECAST_LOG_INFO("file serving started"); }
void stats_shutdown(void) { int n; if (!_stats_running) /* We can't shutdown if we're not running. */ return; /* wait for thread to exit */ _stats_running = 0; thread_join(_stats_thread_id); /* wait for other threads to shut down */ do { thread_sleep(300000); thread_mutex_lock(&_stats_mutex); n = _stats_threads; thread_mutex_unlock(&_stats_mutex); } while (n > 0); ICECAST_LOG_INFO("stats thread finished"); /* free the queues */ /* destroy the queue mutexes */ thread_mutex_destroy(&_global_event_mutex); thread_mutex_destroy(&_stats_mutex); avl_tree_free(_stats.source_tree, _free_source_stats); avl_tree_free(_stats.global_tree, _free_stats); while (1) { stats_event_t *event = _get_event_from_queue (&_global_event_queue); if (event == NULL) break; if(event->source) free(event->source); if(event->value) free(event->value); if(event->name) free(event->name); free(event); } }
ogg_codec_t *initial_speex_page (format_plugin_t *plugin, ogg_page *page) { ogg_state_t *ogg_info = plugin->_state; ogg_codec_t *codec = calloc (1, sizeof (ogg_codec_t)); ogg_packet packet; SpeexHeader *header; ogg_stream_init (&codec->os, ogg_page_serialno (page)); ogg_stream_pagein (&codec->os, page); ogg_stream_packetout (&codec->os, &packet); /* Check for te first packet to be at least of the minimal size for a Speex header. * The header size is 80 bytes as per specs. You can find the specs here: * https://speex.org/docs/manual/speex-manual/node8.html#SECTION00830000000000000000 * * speex_packet_to_header() will also check the header size for us. However * that function generates noise on stderr in case the header is too short. * This is dangerous as we may have closed stderr already and the handle may be use * again for something else. */ if (packet.bytes < 80) { return NULL; } ICECAST_LOG_DEBUG("checking for speex codec"); header = speex_packet_to_header ((char*)packet.packet, packet.bytes); if (header == NULL) { ogg_stream_clear (&codec->os); free (header); free (codec); return NULL; } ICECAST_LOG_INFO("seen initial speex header"); codec->process_page = process_speex_page; codec->codec_free = speex_codec_free; codec->headers = 1; format_ogg_attach_header (ogg_info, page); free (header); return codec; }
ogg_codec_t *initial_flac_page (format_plugin_t *plugin, ogg_page *page) { ogg_state_t *ogg_info = plugin->_state; ogg_codec_t *codec = calloc (1, sizeof (ogg_codec_t)); ogg_packet packet; ogg_stream_init (&codec->os, ogg_page_serialno (page)); ogg_stream_pagein (&codec->os, page); ogg_stream_packetout (&codec->os, &packet); ICECAST_LOG_DEBUG("checking for FLAC codec"); do { unsigned char *parse = packet.packet; if (page->header_len + page->body_len != 79) break; if (*parse != 0x7F) break; parse++; if (memcmp (parse, "FLAC", 4) != 0) break; ICECAST_LOG_INFO("seen initial FLAC header"); parse += 4; stats_event_args (ogg_info->mount, "FLAC_version", "%d.%d", parse[0], parse[1]); codec->process_page = process_flac_page; codec->codec_free = flac_codec_free; codec->headers = 1; codec->name = "FLAC"; format_ogg_attach_header (ogg_info, page); return codec; } while (0); ogg_stream_clear (&codec->os); free (codec); return NULL; }
static void auth_url_clear(auth_t *self) { auth_url *url; ICECAST_LOG_INFO("Doing auth URL cleanup"); url = self->state; self->state = NULL; icecast_curl_free(url->handle); free(url->username); free(url->password); free(url->pass_headers); free(url->prefix_headers); free(url->removeurl); free(url->addurl); free(url->addaction); free(url->removeaction); free(url->auth_header); free(url->timelimit_header); free(url->userpwd); free(url); }
static void auth_url_clear(auth_t *self) { auth_url *url; ICECAST_LOG_INFO("Doing auth URL cleanup"); url = self->state; self->state = NULL; curl_easy_cleanup (url->handle); free (url->username); free (url->password); free (url->pass_headers); free (url->prefix_headers); free (url->removeurl); free (url->addurl); free (url->stream_start); free (url->stream_end); free (url->auth_header); free (url->timelimit_header); free (url->userpwd); free (url); }
/* 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"); } }
/* 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; }
void auth_shutdown (void) { ICECAST_LOG_INFO("Auth shutdown"); }
static void *_stats_thread(void *arg) { stats_event_t *event; stats_event_t *copy; event_listener_t *listener; stats_event_time (NULL, "server_start"); stats_event_time_iso8601 (NULL, "server_start_iso8601"); /* global currently active stats */ stats_event (NULL, "clients", "0"); stats_event (NULL, "connections", "0"); stats_event (NULL, "sources", "0"); stats_event (NULL, "stats", "0"); stats_event (NULL, "listeners", "0"); /* global accumulating stats */ stats_event (NULL, "client_connections", "0"); stats_event (NULL, "source_client_connections", "0"); stats_event (NULL, "source_relay_connections", "0"); stats_event (NULL, "source_total_connections", "0"); stats_event (NULL, "stats_connections", "0"); stats_event (NULL, "listener_connections", "0"); ICECAST_LOG_INFO("stats thread started"); while (_stats_running) { thread_mutex_lock(&_global_event_mutex); if (_global_event_queue.head != NULL) { /* grab the next event from the queue */ event = _get_event_from_queue (&_global_event_queue); thread_mutex_unlock(&_global_event_mutex); if (event == NULL) continue; event->next = NULL; thread_mutex_lock(&_stats_mutex); /* check if we are dealing with a global or source event */ if (event->source == NULL) process_global_event (event); else process_source_event (event); /* now we have an event that's been processed into the running stats */ /* this event should get copied to event listeners' queues */ listener = (event_listener_t *)_event_listeners; while (listener) { copy = _copy_event(event); thread_mutex_lock (&listener->mutex); _add_event_to_queue (copy, &listener->queue); thread_mutex_unlock (&listener->mutex); listener = listener->next; } /* now we need to destroy the event */ _free_event(event); thread_mutex_unlock(&_stats_mutex); continue; } else { thread_mutex_unlock(&_global_event_mutex); } thread_sleep(300000); } return NULL; }
/* 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; }
/* This does the actual connection for a relay. A thread is * started off if a connection can be acquired */ static void *start_relay_stream (void *arg) { relay_server *relay = arg; source_t *src = relay->source; client_t *client; ICECAST_LOG_INFO("Starting relayed source at mountpoint \"%s\"", relay->localmount); do { client = open_relay_connection (relay); if (client == NULL) continue; src->client = client; src->parser = client->parser; src->con = client->con; if (connection_complete_source (src, 0) < 0) { ICECAST_LOG_INFO("Failed to complete source initialisation"); client_destroy (client); src->client = NULL; continue; } stats_event_inc(NULL, "source_relay_connections"); stats_event (relay->localmount, "source_ip", client->con->ip); source_main (relay->source); if (relay->on_demand == 0) { /* only keep refreshing YP entries for inactive on-demand relays */ yp_remove (relay->localmount); relay->source->yp_public = -1; relay->start = time(NULL) + 10; /* prevent busy looping if failing */ slave_update_all_mounts(); } /* we've finished, now get cleaned up */ relay->cleanup = 1; slave_rebuild_mounts(); return NULL; } while (0); /* TODO allow looping through multiple servers */ if (relay->source->fallback_mount) { source_t *fallback_source; ICECAST_LOG_DEBUG("failed relay, fallback to %s", relay->source->fallback_mount); avl_tree_rlock(global.source_tree); fallback_source = source_find_mount(relay->source->fallback_mount); if (fallback_source != NULL) source_move_clients(relay->source, fallback_source); avl_tree_unlock(global.source_tree); } source_clear_source(relay->source); /* cleanup relay, but prevent this relay from starting up again too soon */ thread_mutex_lock(&_slave_mutex); thread_mutex_lock(&(config_locks()->relay_lock)); relay->source->on_demand = 0; relay->start = time(NULL) + max_interval; relay->cleanup = 1; thread_mutex_unlock(&(config_locks()->relay_lock)); thread_mutex_unlock(&_slave_mutex); return NULL; }
int auth_get_url_auth (auth_t *authenticator, config_options_t *options) { auth_url *url_info; authenticator->free = auth_url_clear; authenticator->adduser = auth_url_adduser; authenticator->deleteuser = auth_url_deleteuser; authenticator->listuser = auth_url_listuser; url_info = calloc(1, sizeof(auth_url)); authenticator->state = url_info; /* default headers */ url_info->auth_header = strdup ("icecast-auth-user: 1\r\n"); url_info->timelimit_header = strdup ("icecast-auth-timelimit:"); /* force auth thread to call function. this makes sure the auth_t is attached to client */ authenticator->authenticate = url_add_listener; while(options) { if(!strcmp(options->name, "username")) { free (url_info->username); url_info->username = strdup (options->value); } if(!strcmp(options->name, "password")) { free (url_info->password); url_info->password = strdup (options->value); } if(!strcmp(options->name, "headers")) { free (url_info->pass_headers); url_info->pass_headers = strdup (options->value); } if(!strcmp(options->name, "header_prefix")) { free (url_info->prefix_headers); url_info->prefix_headers = strdup (options->value); } if(!strcmp(options->name, "listener_add")) { free (url_info->addurl); url_info->addurl = strdup (options->value); } if(!strcmp(options->name, "listener_remove")) { authenticator->release_listener = url_remove_listener; free (url_info->removeurl); url_info->removeurl = strdup (options->value); } if(!strcmp(options->name, "mount_add")) { authenticator->stream_start = url_stream_start; free (url_info->stream_start); url_info->stream_start = strdup (options->value); } if(!strcmp(options->name, "mount_remove")) { authenticator->stream_end = url_stream_end; free (url_info->stream_end); url_info->stream_end = strdup (options->value); } if(!strcmp(options->name, "stream_auth")) { authenticator->stream_auth = url_stream_auth; free (url_info->stream_auth); url_info->stream_auth = strdup (options->value); } if(!strcmp(options->name, "auth_header")) { free (url_info->auth_header); url_info->auth_header = strdup (options->value); } if (strcmp(options->name, "timelimit_header") == 0) { free (url_info->timelimit_header); url_info->timelimit_header = strdup (options->value); } options = options->next; } url_info->handle = curl_easy_init (); if (url_info->handle == NULL) { auth_url_clear (authenticator); return -1; } if (url_info->auth_header) url_info->auth_header_len = strlen (url_info->auth_header); if (url_info->timelimit_header) url_info->timelimit_header_len = strlen (url_info->timelimit_header); curl_easy_setopt (url_info->handle, CURLOPT_USERAGENT, ICECAST_VERSION_STRING); curl_easy_setopt (url_info->handle, CURLOPT_HEADERFUNCTION, handle_returned_header); curl_easy_setopt (url_info->handle, CURLOPT_WRITEFUNCTION, handle_returned_data); curl_easy_setopt (url_info->handle, CURLOPT_WRITEDATA, url_info->handle); curl_easy_setopt (url_info->handle, CURLOPT_NOSIGNAL, 1L); curl_easy_setopt (url_info->handle, CURLOPT_TIMEOUT, 15L); #ifdef CURLOPT_PASSWDFUNCTION curl_easy_setopt (url_info->handle, CURLOPT_PASSWDFUNCTION, my_getpass); #endif curl_easy_setopt (url_info->handle, CURLOPT_ERRORBUFFER, &url_info->errormsg[0]); if (url_info->username && url_info->password) { int len = strlen (url_info->username) + strlen (url_info->password) + 2; url_info->userpwd = malloc (len); snprintf (url_info->userpwd, len, "%s:%s", url_info->username, url_info->password); } ICECAST_LOG_INFO("URL based authentication setup"); return 0; }
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; }
int auth_get_url_auth(auth_t *authenticator, config_options_t *options) { auth_url *url_info; const char *addaction = "listener_add"; const char *removeaction = "listener_remove"; authenticator->free = auth_url_clear; authenticator->adduser = auth_url_adduser; authenticator->deleteuser = auth_url_deleteuser; authenticator->listuser = auth_url_listuser; url_info = calloc(1, sizeof(auth_url)); authenticator->state = url_info; /* default headers */ url_info->auth_header = strdup("icecast-auth-user: 1\r\n"); url_info->timelimit_header = strdup("icecast-auth-timelimit:"); /* force auth thread to call function. this makes sure the auth_t is attached to client */ authenticator->authenticate_client = url_add_client; while(options) { if(strcmp(options->name, "username") == 0) { free(url_info->username); url_info->username = strdup(options->value); } else if(strcmp(options->name, "password") == 0) { free(url_info->password); url_info->password = strdup(options->value); } else if(strcmp(options->name, "headers") == 0) { free(url_info->pass_headers); url_info->pass_headers = strdup(options->value); } else if(strcmp(options->name, "header_prefix") == 0) { free(url_info->prefix_headers); url_info->prefix_headers = strdup(options->value); } else if(strcmp(options->name, "client_add") == 0) { free(url_info->addurl); url_info->addurl = strdup(options->value); } else if(strcmp(options->name, "client_remove") == 0) { authenticator->release_client = url_remove_client; free(url_info->removeurl); url_info->removeurl = strdup(options->value); } else if(strcmp(options->name, "action_add") == 0) { addaction = options->value; } else if(strcmp(options->name, "action_remove") == 0) { removeaction = options->value; } else if(strcmp(options->name, "auth_header") == 0) { free(url_info->auth_header); url_info->auth_header = strdup(options->value); } else if (strcmp(options->name, "timelimit_header") == 0) { free(url_info->timelimit_header); url_info->timelimit_header = strdup(options->value); } else { ICECAST_LOG_ERROR("Unknown option: %s", options->name); } options = options->next; } url_info->addaction = util_url_escape(addaction); url_info->removeaction = util_url_escape(removeaction); url_info->handle = icecast_curl_new(NULL, &url_info->errormsg[0]); if (url_info->handle == NULL) { auth_url_clear(authenticator); return -1; } if (url_info->auth_header) url_info->auth_header_len = strlen (url_info->auth_header); if (url_info->timelimit_header) url_info->timelimit_header_len = strlen (url_info->timelimit_header); curl_easy_setopt(url_info->handle, CURLOPT_HEADERFUNCTION, handle_returned_header); if (url_info->username && url_info->password) { int len = strlen(url_info->username) + strlen(url_info->password) + 2; url_info->userpwd = malloc(len); snprintf(url_info->userpwd, len, "%s:%s", url_info->username, url_info->password); } ICECAST_LOG_INFO("URL based authentication setup"); return 0; }