/* Icecast * * This program is distributed under the GNU General Public License, version 2. * A copy of this license is included with this source. * * Copyright 2000-2004, Jack Moffitt , * oddsock , * Karl Heyes * and others (see AUTHORS for details). * Copyright 2011, Dave 'justdave' Miller . * Copyright 2011-2014, Thomas B. "dm8tbr" Ruecker , * Copyright 2011-2022, Philipp "ph3-der-loewe" Schafft , */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #ifndef _WIN32 #include #endif #include #include #include #include "common/thread/thread.h" #include "cfgfile.h" #include "global.h" #include "logging.h" #include "util.h" #include "auth.h" #include "event.h" #include "refobject.h" #include "reportxml.h" /* for config_reread_config() */ #include "yp.h" #include "fserve.h" #include "stats.h" #include "connection.h" #include "main.h" #include "slave.h" #include "xslt.h" #include "prng.h" #define CATMODULE "CONFIG" #define RANGE_PORT 1, 65535 #define RANGE_ICY_INTERVAL -1, (64*1024) #define RANGE_SNDBUF 1024, (64*1024) #define CONFIG_DEFAULT_LOCATION "Earth" #define CONFIG_DEFAULT_ADMIN "icemaster@localhost" #define CONFIG_DEFAULT_CLIENT_LIMIT 256 #define CONFIG_MAX_CLIENT_LIMIT (32*1024) #define CONFIG_DEFAULT_SOURCE_LIMIT 16 #define CONFIG_MAX_SOURCE_LIMIT (CONFIG_MAX_CLIENT_LIMIT/2) #define CONFIG_MAX_LISTENERS (CONFIG_MAX_CLIENT_LIMIT/2) #define CONFIG_DEFAULT_QUEUE_SIZE_LIMIT (500*1024) #define CONFIG_MAX_QUEUE_SIZE_LIMIT (16 *1024*1024) #define CONFIG_DEFAULT_BODY_SIZE_LIMIT (4*1024) #define CONFIG_MIN_BODY_SIZE_LIMIT ( 1*1024) #define CONFIG_MAX_BODY_SIZE_LIMIT (64*1024) #define CONFIG_DEFAULT_BURST_SIZE (64*1024) #define CONFIG_DEFAULT_THREADPOOL_SIZE 4 #define CONFIG_DEFAULT_CLIENT_TIMEOUT 30 #define CONFIG_RANGE_CLIENT_TIMEOUT 2, 600 #define CONFIG_MAX_CLIENT_TIMEOUT 600 #define CONFIG_DEFAULT_HEADER_TIMEOUT 15 #define CONFIG_RANGE_HEADER_TIMEOUT CONFIG_RANGE_CLIENT_TIMEOUT #define CONFIG_DEFAULT_SOURCE_TIMEOUT 10 #define CONFIG_RANGE_SOURCE_TIMEOUT CONFIG_RANGE_CLIENT_TIMEOUT #define CONFIG_DEFAULT_BODY_TIMEOUT (10 + CONFIG_DEFAULT_HEADER_TIMEOUT) #define CONFIG_RANGE_BODY_TIMEOUT CONFIG_RANGE_CLIENT_TIMEOUT #define CONFIG_DEFAULT_MASTER_USERNAME "relay" #define CONFIG_DEFAULT_SHOUTCAST_MOUNT "/stream" #define CONFIG_DEFAULT_SHOUTCAST_USER "source" #define CONFIG_DEFAULT_FILESERVE 1 #define CONFIG_DEFAULT_HOSTNAME "localhost" #define CONFIG_DEFAULT_PLAYLIST_LOG NULL #define CONFIG_DEFAULT_ACCESS_LOG "access.log" #define CONFIG_DEFAULT_ERROR_LOG "error.log" #define CONFIG_DEFAULT_LOG_LEVEL ICECAST_LOGLEVEL_INFO #define CONFIG_DEFAULT_LOG_LINES_KEPT 64 #define CONFIG_RANGE_LOG_LINES_KEPT 8, 1024 #define CONFIG_DEFAULT_CHROOT 0 #define CONFIG_DEFAULT_CHUID 0 #define CONFIG_DEFAULT_USER NULL #define CONFIG_DEFAULT_GROUP NULL #define CONFIG_MASTER_UPDATE_INTERVAL 120 #define CONFIG_YP_URL_TIMEOUT 10 #define CONFIG_RANGE_YP_URL_TIMEOUT CONFIG_RANGE_CLIENT_TIMEOUT #define CONFIG_RANGE_YP_TOUCH_INTERVAL 30, 3600 #define CONFIG_DEFAULT_RELAY_SERVER "127.0.0.1" #define CONFIG_DEFAULT_RELAY_PORT 80 #define CONFIG_DEFAULT_RELAY_MOUNT "/" #define CONFIG_DEFAULT_CIPHER_LIST "ECDHE-ECDSA-CHACHA20-POLY1305:" \ "ECDHE-RSA-CHACHA20-POLY1305:" \ "ECDHE-ECDSA-AES128-GCM-SHA256:" \ "ECDHE-RSA-AES128-GCM-SHA256:" \ "ECDHE-ECDSA-AES256-GCM-SHA384:" \ "ECDHE-RSA-AES256-GCM-SHA384:" \ "DHE-RSA-AES128-GCM-SHA256:" \ "DHE-RSA-AES256-GCM-SHA384:" \ "ECDHE-ECDSA-AES128-SHA256:" \ "ECDHE-RSA-AES128-SHA256:" \ "ECDHE-ECDSA-AES128-SHA:" \ "ECDHE-RSA-AES256-SHA384:" \ "ECDHE-RSA-AES128-SHA:" \ "ECDHE-ECDSA-AES256-SHA384:" \ "ECDHE-ECDSA-AES256-SHA:" \ "ECDHE-RSA-AES256-SHA:" \ "DHE-RSA-AES128-SHA256:" \ "DHE-RSA-AES128-SHA:" \ "DHE-RSA-AES256-SHA256:" \ "DHE-RSA-AES256-SHA:" \ "ECDHE-ECDSA-DES-CBC3-SHA:" \ "ECDHE-RSA-DES-CBC3-SHA:" \ "EDH-RSA-DES-CBC3-SHA:" \ "AES128-GCM-SHA256:" \ "AES256-GCM-SHA384:" \ "AES128-SHA256:" \ "AES256-SHA256:" \ "AES128-SHA:" \ "AES256-SHA:" \ "DES-CBC3-SHA:" \ "!DSS:" \ "!aNULL:!eNULL:" \ "!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:" \ "!EDH-DSS-DES-CBC3-SHA:" \ "!EDH-RSA-DES-CBC3-SHA:" \ "!KRB5-DES-CBC3-SHA" #ifndef _WIN32 #define CONFIG_DEFAULT_BASE_DIR "/usr/local/icecast" #define CONFIG_DEFAULT_LOG_DIR "/usr/local/icecast/logs" #define CONFIG_DEFAULT_WEBROOT_DIR "/usr/local/icecast/webroot" #define CONFIG_DEFAULT_ADMINROOT_DIR "/usr/local/icecast/admin" #define CONFIG_DEFAULT_NULL_FILE "/dev/null" #define MIMETYPESFILE "/etc/mime.types" #else #define CONFIG_DEFAULT_BASE_DIR ".\\" #define CONFIG_DEFAULT_LOG_DIR ".\\logs" #define CONFIG_DEFAULT_WEBROOT_DIR ".\\webroot" #define CONFIG_DEFAULT_ADMINROOT_DIR ".\\admin" #define CONFIG_DEFAULT_NULL_FILE "nul:" #define MIMETYPESFILE ".\\mime.types" #endif /* Legacy values. */ #define CONFIG_LEGACY_ALL_METHODS "get,options" #define CONFIG_LEGACY_SOURCE_NAME_GLOBAL "legacy-global-source" #define CONFIG_LEGACY_SOURCE_NAME_MOUNT "legacy-mount-source" #define CONFIG_LEGACY_SOURCE_METHODS CONFIG_LEGACY_ALL_METHODS ",post,source,put,delete" #define CONFIG_LEGACY_SOURCE_ALLOW_WEB false #define CONFIG_LEGACY_SOURCE_ALLOW_ADMIN "*" #define CONFIG_LEGACY_ADMIN_NAME "legacy-admin" #define CONFIG_LEGACY_ADMIN_METHODS CONFIG_LEGACY_ALL_METHODS ",post,head,stats,delete" #define CONFIG_LEGACY_ADMIN_ALLOW_WEB true #define CONFIG_LEGACY_ADMIN_ALLOW_ADMIN "*" #define CONFIG_LEGACY_RELAY_NAME "legacy-relay" #define CONFIG_LEGACY_RELAY_METHODS CONFIG_LEGACY_ALL_METHODS #define CONFIG_LEGACY_RELAY_ALLOW_WEB true #define CONFIG_LEGACY_RELAY_ALLOW_ADMIN "streamlist.txt" #define CONFIG_LEGACY_ANONYMOUS_NAME "anonymous" #define CONFIG_LEGACY_ANONYMOUS_METHODS CONFIG_LEGACY_ALL_METHODS ",post,head" #define CONFIG_LEGACY_ANONYMOUS_ALLOW_WEB true #define CONFIG_LEGACY_ANONYMOUS_ALLOW_ADMIN NULL enum bad_tag_reason { BTR_UNKNOWN, BTR_OBSOLETE, BTR_INVALID, BTR_EMPTY, BTR_RANGE }; static ice_config_t _current_configuration; static ice_config_locks _locks; static void __found_bad_tag(ice_config_t *configuration, xmlNodePtr node, enum bad_tag_reason reason, const char *extra); static void _set_defaults(ice_config_t *c); static void _parse_root(xmlDocPtr doc, xmlNodePtr node, ice_config_t *c); static void _parse_limits(xmlDocPtr doc, xmlNodePtr node, ice_config_t *c); static void _parse_oldstyle_directory(xmlDocPtr doc, xmlNodePtr node, ice_config_t *c); static void _parse_yp_directory(xmlDocPtr doc, xmlNodePtr node, ice_config_t *c); static void _parse_paths(xmlDocPtr doc, xmlNodePtr node, ice_config_t *c); static void _parse_logging(xmlDocPtr doc, xmlNodePtr node, ice_config_t *c); static void _parse_security(xmlDocPtr doc, xmlNodePtr node, ice_config_t *c); static void _parse_authentication(xmlDocPtr doc, xmlNodePtr node, ice_config_t *c, char **source_password); static void _parse_relay(xmlDocPtr doc, xmlNodePtr node, ice_config_t *c, const char *mount); static void _parse_mount(xmlDocPtr doc, xmlNodePtr parentnode, ice_config_t *c); static void _parse_listen_socket(xmlDocPtr doc, xmlNodePtr node, ice_config_t *c); static void _parse_events(event_registration_t **events, xmlNodePtr node); static void merge_mounts(mount_proxy * dst, mount_proxy * src); static inline void _merge_mounts_all(ice_config_t *c); operation_mode config_str_to_omode(ice_config_t *configuration, xmlNodePtr node, const char *str) { if (!str || !*str) return OMODE_DEFAULT; if (strcasecmp(str, "default") == 0) { return OMODE_DEFAULT; } else if (strcasecmp(str, "normal") == 0) { return OMODE_NORMAL; } else if (strcasecmp(str, "legacy-compat") == 0 || strcasecmp(str, "legacy") == 0) { return OMODE_LEGACY; } else if (strcasecmp(str, "strict") == 0) { return OMODE_STRICT; } else { __found_bad_tag(configuration, node, BTR_INVALID, str); ICECAST_LOG_ERROR("Unknown operation mode \"%s\", falling back to DEFAULT.", str); return OMODE_DEFAULT; } } static listener_type_t config_str_to_listener_type(ice_config_t *configuration, xmlNodePtr node, const char *str) { if (!str || !*str) { return LISTENER_TYPE_NORMAL; } else if (strcasecmp(str, "normal") == 0) { return LISTENER_TYPE_NORMAL; } else if (strcasecmp(str, "virtual") == 0) { return LISTENER_TYPE_VIRTUAL; } else { __found_bad_tag(configuration, node, BTR_INVALID, str); ICECAST_LOG_ERROR("Unknown listener type \"%s\", falling back to NORMAL.", str); return LISTENER_TYPE_NORMAL; } } static fallback_override_t config_str_to_fallback_override_t(ice_config_t *configuration, xmlNodePtr node, const char *str) { if (!str || !*str || strcmp(str, "none") == 0) { return FALLBACK_OVERRIDE_NONE; } else if (strcasecmp(str, "all") == 0) { return FALLBACK_OVERRIDE_ALL; } else if (strcasecmp(str, "own") == 0) { return FALLBACK_OVERRIDE_OWN; } else { if (util_str_to_bool(str)) { ICECAST_LOG_WARN("Old style fallback override setting. Please replace %#H with \"all\".", str); return FALLBACK_OVERRIDE_ALL; } else { ICECAST_LOG_WARN("Old style fallback override setting. Please replace %#H with \"none\".", str); return FALLBACK_OVERRIDE_NONE; } } } char * config_href_to_id(ice_config_t *configuration, xmlNodePtr node, const char *href) { if (!href || !*href) return NULL; if (*href != '#') { __found_bad_tag(configuration, node, BTR_INVALID, href); ICECAST_LOG_ERROR("Can not convert string \"%H\" to ID.", href); return NULL; } return strdup(href+1); } static void create_locks(void) { thread_mutex_create(&_locks.relay_lock); thread_rwlock_create(&_locks.config_lock); } static void release_locks(void) { thread_mutex_destroy(&_locks.relay_lock); thread_rwlock_destroy(&_locks.config_lock); } void config_initialize(void) { create_locks(); } void config_shutdown(void) { config_get_config(); config_clear(&_current_configuration); config_release_config(); release_locks(); } void config_init_configuration(ice_config_t *configuration) { memset(configuration, 0, sizeof(ice_config_t)); _set_defaults(configuration); configuration->reportxml_db = refobject_new(reportxml_database_t); } static inline void __read_int(ice_config_t *configuration, xmlDocPtr doc, xmlNodePtr node, int *val, int min, int max) { char *str = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); if (!str || !*str) { __found_bad_tag(configuration, node, BTR_EMPTY, NULL); } else { int res = util_str_to_int(str, *val); if (res < min || res > max) { __found_bad_tag(configuration, node, BTR_RANGE, NULL); } else { *val = res; } } if (str) xmlFree(str); } static inline void __read_unsigned_int(ice_config_t *configuration, xmlDocPtr doc, xmlNodePtr node, unsigned int *val, unsigned int min, unsigned int max) { char *str = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); if (!str || !*str) { __found_bad_tag(configuration, node, BTR_EMPTY, NULL); } else { int res = util_str_to_unsigned_int(str, *val); if (res < 0 || ((unsigned int)res) < min || ((unsigned int)res) > max) { __found_bad_tag(configuration, node, BTR_RANGE, NULL); } else { *val = res; } } if (str) xmlFree(str); } static inline int __parse_public(const char *str) { /* values that are not bool */ if (strcasecmp(str, "client") == 0) return -1; /* old way of doing so */ if (strcmp(str, "-1") == 0) return -1; /* ok, only normal bool left! */ return util_str_to_bool(str); } /* This converts TLS mode strings to (tlsmode_t). * In older versions of Icecast2 this was just a bool. * So we need to handle boolean values as well. * See also: util_str_to_bool(). */ static tlsmode_t str_to_tlsmode(const char *str) { /* consider NULL and empty strings as auto mode */ if (!str || !*str) return ICECAST_TLSMODE_AUTO; if (strcasecmp(str, "disabled") == 0) { return ICECAST_TLSMODE_DISABLED; } else if (strcasecmp(str, "auto") == 0) { return ICECAST_TLSMODE_AUTO; } else if (strcasecmp(str, "auto_no_plain") == 0) { return ICECAST_TLSMODE_AUTO_NO_PLAIN; } else if (strcasecmp(str, "rfc2817") == 0) { return ICECAST_TLSMODE_RFC2817; } else if (strcasecmp(str, "rfc2818") == 0 || /* boolean-style values */ strcasecmp(str, "true") == 0 || strcasecmp(str, "yes") == 0 || strcasecmp(str, "on") == 0 ) { return ICECAST_TLSMODE_RFC2818; } /* old style numbers: consider everyting non-zero RFC2818 */ if (atoi(str)) return ICECAST_TLSMODE_RFC2818; /* we default to auto mode */ return ICECAST_TLSMODE_AUTO; } /* This checks for the TLS implementation of a node */ static int __check_node_impl(xmlNodePtr node, const char *def) { char *impl; int res; impl = (char *)xmlGetProp(node, XMLSTR("implementation")); if (!impl) impl = (char *)xmlGetProp(node, XMLSTR("impl")); if (!impl) impl = (char *)xmlStrdup(XMLSTR(def)); res = tls_check_impl(impl); xmlFree(impl); return res; } static char *__build_node_name(xmlNodePtr node) { char *buf[4]; size_t have; size_t i; size_t len = 4; char *ret; char *p; memset(buf, 0, sizeof(buf)); for (have = 0; have < (sizeof(buf)/sizeof(*buf)) && node->parent; have++) { int ret = -1; xmlChar *id; id = xmlGetProp(node, XMLSTR("id")); if (id) { ret = asprintf(&(buf[have]), "%s[@id=\"%s\"]", node->name, id); xmlFree(id); } else if (xmlStrcmp(node->name, XMLSTR("mount")) == 0) { xmlChar *mount = NULL; xmlNodePtr child = node->xmlChildrenNode; while (child && !mount) { if (xmlStrcmp(child->name, XMLSTR("mount-name")) == 0) { mount = xmlNodeListGetString(child->doc, child->xmlChildrenNode, 1); } child = child->next; } if (mount) { ret = asprintf(&(buf[have]), "%s[mount-name/text()=\"%s\"]", node->name, mount); xmlFree(mount); } else { ret = asprintf(&(buf[have]), "%s", node->name); } } else { ret = asprintf(&(buf[have]), "%s", node->name); } if (ret < 1) { buf[have] = NULL; for (i = 0; i < (sizeof(buf)/sizeof(*buf)); i++) free(buf[i]); return ""; } node = node->parent; } for (i = 0; i < (sizeof(buf)/sizeof(*buf)); i++) { if (buf[i]) len += strlen(buf[i]) + 1; } p = ret = malloc(len); if (!ret) { for (i = 0; i < (sizeof(buf)/sizeof(*buf)); i++) free(buf[i]); return ""; } if (node && node->type != XML_DOCUMENT_NODE) { memcpy(p, "...", 3); p += 3; } for (i = have; i > 0; i--) { char *b = buf[i - 1]; if (b) { size_t l = strlen(b); *p = '/'; p++; memcpy(p, b, l); p += l; free(b); } } *p = 0; return ret; } static void __found_bad_tag(ice_config_t *configuration, xmlNodePtr node, enum bad_tag_reason reason, const char *extra) { char *name = NULL; /* ignore non-configuration errors */ if (!configuration) return; // ignore comments. if (node->type == XML_COMMENT_NODE) return; if (node) name = __build_node_name(node); switch (reason) { case BTR_UNKNOWN: configuration->config_problems |= CONFIG_PROBLEM_UNKNOWN_NODE; if (name) { ICECAST_LOG_WARN("Unknown tag in config: %s", name); } else { ICECAST_LOG_WARN("Unknown tag in config"); } break; case BTR_OBSOLETE: configuration->config_problems |= CONFIG_PROBLEM_OBSOLETE_NODE; if (name) { ICECAST_LOG_WARN("Obsolete tag in config: %s", name); if (extra) { ICECAST_LOG_WARN("Obsolete tag %s can be replaced: %s", name, extra); } } else { ICECAST_LOG_WARN("Obsolete tag in config"); } break; case BTR_INVALID: configuration->config_problems |= CONFIG_PROBLEM_INVALID_NODE; if (name) { if (extra) { ICECAST_LOG_WARN("Invalid content for tag: %s: %s", name, extra); } else { ICECAST_LOG_WARN("Invalid content for tag: %s", name); } } else { ICECAST_LOG_WARN("Invalid content for tag"); } break; case BTR_EMPTY: configuration->config_problems |= CONFIG_PROBLEM_INVALID_NODE; if (name) { ICECAST_LOG_WARN("Invalid empty tag: %s", name); } else { ICECAST_LOG_WARN("Invalid empty tag"); } break; case BTR_RANGE: configuration->config_problems |= CONFIG_PROBLEM_INVALID_NODE; if (name) { ICECAST_LOG_WARN("Value for tag is out of range: %s", name); } else { ICECAST_LOG_WARN("Value for tag is out of range"); } break; } free(name); } static void __append_old_style_auth(ice_config_t *configuration, auth_stack_t **stack, const char *name, const char *type, const char *username, const char *password, const char *match_method, const char *allow_method, bool allow_web, const char *allow_admin) { xmlNodePtr role, user, pass; auth_t *auth; if (!type) return; role = xmlNewNode(NULL, XMLSTR("role")); xmlSetProp(role, XMLSTR("type"), XMLSTR(type)); xmlSetProp(role, XMLSTR("deny-method"), XMLSTR("*")); if (allow_method) xmlSetProp(role, XMLSTR("allow-method"), XMLSTR(allow_method)); if (name) xmlSetProp(role, XMLSTR("name"), XMLSTR(name)); if (match_method) xmlSetProp(role, XMLSTR("match-method"), XMLSTR(match_method)); if (allow_web) { xmlSetProp(role, XMLSTR("allow-web"), XMLSTR("*")); } else { xmlSetProp(role, XMLSTR("deny-web"), XMLSTR("*")); } if (allow_admin && strcmp(allow_admin, "*") == 0) { xmlSetProp(role, XMLSTR("allow-admin"), XMLSTR("*")); } else { xmlSetProp(role, XMLSTR("deny-admin"), XMLSTR("*")); if (allow_admin) xmlSetProp(role, XMLSTR("allow-admin"), XMLSTR(allow_admin)); } if (username) { user = xmlNewChild(role, NULL, XMLSTR("option"), NULL); xmlSetProp(user, XMLSTR("name"), XMLSTR("username")); xmlSetProp(user, XMLSTR("value"), XMLSTR(username)); } if (password) { pass = xmlNewChild(role, NULL, XMLSTR("option"), NULL); xmlSetProp(pass, XMLSTR("name"), XMLSTR("password")); xmlSetProp(pass, XMLSTR("value"), XMLSTR(password)); } auth = auth_get_authenticator(configuration, role); auth_stack_push(stack, auth); auth_release(auth); xmlFreeNode(role); } static void __append_option_tag(xmlNodePtr parent, const char *name, const char *value) { xmlNodePtr node; if (!name || !value) return; node = xmlNewChild(parent, NULL, XMLSTR("option"), NULL); xmlSetProp(node, XMLSTR("name"), XMLSTR(name)); xmlSetProp(node, XMLSTR("value"), XMLSTR(value)); } static void __append_old_style_urlauth(ice_config_t *configuration, auth_stack_t **stack, const char *client_add, const char *client_remove, const char *action_add, const char *action_remove, const char *username, const char *password, int is_source, const char *auth_header, const char *timelimit_header, const char *headers, const char *header_prefix) { xmlNodePtr role; auth_t *auth; if (!stack || (!client_add && !client_remove)) return; role = xmlNewNode(NULL, XMLSTR("role")); xmlSetProp(role, XMLSTR("type"), XMLSTR("url")); if (is_source) { xmlSetProp(role, XMLSTR("method"), XMLSTR("source,put")); xmlSetProp(role, XMLSTR("deny-method"), XMLSTR("*")); xmlSetProp(role, XMLSTR("allow-method"), XMLSTR("source,put")); xmlSetProp(role, XMLSTR("allow-web"), XMLSTR("*")); xmlSetProp(role, XMLSTR("allow-admin"), XMLSTR("*")); } else { xmlSetProp(role, XMLSTR("method"), XMLSTR("get,post,head,options")); xmlSetProp(role, XMLSTR("deny-method"), XMLSTR("*")); xmlSetProp(role, XMLSTR("allow-method"), XMLSTR("get,post,head,options")); xmlSetProp(role, XMLSTR("allow-web"), XMLSTR("*")); xmlSetProp(role, XMLSTR("deny-admin"), XMLSTR("*")); } __append_option_tag(role, "client_add", client_add); __append_option_tag(role, "client_remove", client_remove); __append_option_tag(role, "action_add", action_add); __append_option_tag(role, "action_remove", action_remove); __append_option_tag(role, "username", username); __append_option_tag(role, "password", password); __append_option_tag(role, "auth_header", auth_header); __append_option_tag(role, "timelimit_header", timelimit_header); __append_option_tag(role, "headers", headers); __append_option_tag(role, "header_prefix", header_prefix); auth = auth_get_authenticator(configuration, role); if (auth) { auth_stack_push(stack, auth); auth_release(auth); ICECAST_LOG_DEBUG("Pushed authenticator %p on stack %p.", auth, stack); } else { ICECAST_LOG_DEBUG("Failed to set up authenticator."); } xmlFreeNode(role); } static void __append_old_style_exec_event(event_registration_t **list, const char *trigger, const char *executable) { xmlNodePtr exec; event_registration_t *er; exec = xmlNewNode(NULL, XMLSTR("event")); xmlSetProp(exec, XMLSTR("type"), XMLSTR("exec")); xmlSetProp(exec, XMLSTR("trigger"), XMLSTR(trigger)); __append_option_tag(exec, "executable", executable); er = event_new_from_xml_node(exec); event_registration_push(list, er); event_registration_release(er); xmlFreeNode(exec); } static void __append_old_style_url_event(event_registration_t **list, const char *trigger, const char *url, const char *action, const char *username, const char *password) { xmlNodePtr exec; event_registration_t *er; exec = xmlNewNode(NULL, XMLSTR("event")); xmlSetProp(exec, XMLSTR("type"), XMLSTR("url")); xmlSetProp(exec, XMLSTR("trigger"), XMLSTR(trigger)); __append_option_tag(exec, "url", url); __append_option_tag(exec, "action", action); __append_option_tag(exec, "username", username); __append_option_tag(exec, "password", password); er = event_new_from_xml_node(exec); event_registration_push(list, er); event_registration_release(er); xmlFreeNode(exec); } void config_clear_http_header(ice_config_http_header_t *header) { ice_config_http_header_t *old; while (header) { xmlFree(header->name); if (header->value) xmlFree(header->value); old = header; header = header->next; free(old); } } static inline ice_config_http_header_t *config_copy_http_header(ice_config_http_header_t *header) { ice_config_http_header_t *ret = NULL; ice_config_http_header_t *cur = NULL; ice_config_http_header_t *old = NULL; while (header) { if (cur) { cur->next = calloc(1, sizeof(ice_config_http_header_t)); old = cur; cur = cur->next; } else { ret = calloc(1, sizeof(ice_config_http_header_t)); cur = ret; } if (!cur) return ret; /* TODO: do better error handling */ cur->type = header->type; cur->name = (char *)xmlCharStrdup(header->name); cur->value = (char *)xmlCharStrdup(header->value); cur->status = header->status; if (!cur->name || !cur->value) { if (cur->name) xmlFree(cur->name); if (cur->value) xmlFree(cur->value); if (old) { old->next = NULL; } else { ret = NULL; } free(cur); return ret; } header = header->next; } return ret; } static void config_clear_mount(mount_proxy *mount) { if (mount->mountname) xmlFree(mount->mountname); if (mount->dumpfile) xmlFree(mount->dumpfile); if (mount->intro_filename) xmlFree(mount->intro_filename); if (mount->fallback_mount) xmlFree(mount->fallback_mount); if (mount->stream_name) xmlFree(mount->stream_name); if (mount->stream_description) xmlFree(mount->stream_description); if (mount->stream_url) xmlFree(mount->stream_url); if (mount->stream_genre) xmlFree(mount->stream_genre); if (mount->bitrate) xmlFree(mount->bitrate); if (mount->type) xmlFree(mount->type); if (mount->charset) xmlFree(mount->charset); if (mount->cluster_password) xmlFree(mount->cluster_password); if (mount->authstack) auth_stack_release(mount->authstack); event_registration_release(mount->event); config_clear_http_header(mount->http_headers); free(mount); } static void config_clear_resource(resource_t *resource) { resource_t *nextresource; while (resource) { nextresource = resource->next; xmlFree(resource->source); xmlFree(resource->destination); xmlFree(resource->bind_address); xmlFree(resource->vhost); xmlFree(resource->module); xmlFree(resource->handler); free(resource->listen_socket); free(resource); resource = nextresource; } } static void config_clear_yp_directories(yp_directory_t *yp_dir) { yp_directory_t *next_yp_dir; while (yp_dir) { next_yp_dir = yp_dir->next; free(yp_dir->url); free(yp_dir->listen_socket_id); free(yp_dir); yp_dir = next_yp_dir; } } listener_t *config_clear_listener(listener_t *listener) { listener_t *next = NULL; if (listener) { next = listener->next; if (listener->id) xmlFree(listener->id); if (listener->on_behalf_of) free(listener->on_behalf_of); if (listener->bind_address) xmlFree(listener->bind_address); if (listener->shoutcast_mount) xmlFree(listener->shoutcast_mount); if (listener->authstack) auth_stack_release(listener->authstack); if (listener->http_headers) config_clear_http_header(listener->http_headers); free (listener); } return next; } static void config_clear_prng_seed(prng_seed_config_t *seed) { while (seed) { prng_seed_config_t *next = seed->next; if (seed->filename) xmlFree(seed->filename); free(seed); seed = next; } } void config_clear(ice_config_t *c) { mount_proxy *mount, *nextmount; size_t i; free(c->config_filename); xmlFree(c->server_id); if (c->location) xmlFree(c->location); if (c->admin) xmlFree(c->admin); if (c->hostname) xmlFree(c->hostname); if (c->base_dir) xmlFree(c->base_dir); if (c->log_dir) xmlFree(c->log_dir); if (c->webroot_dir) xmlFree(c->webroot_dir); if (c->adminroot_dir) xmlFree(c->adminroot_dir); if (c->null_device) xmlFree(c->null_device); if (c->pidfile) xmlFree(c->pidfile); if (c->banfile) xmlFree(c->banfile); if (c->allowfile) xmlFree(c->allowfile); if (c->playlist_log) xmlFree(c->playlist_log); if (c->access_log) xmlFree(c->access_log); if (c->error_log) xmlFree(c->error_log); if (c->shoutcast_mount) xmlFree(c->shoutcast_mount); if (c->shoutcast_user) xmlFree(c->shoutcast_user); if (c->authstack) auth_stack_release(c->authstack); if (c->master_server) xmlFree(c->master_server); if (c->master_username) xmlFree(c->master_username); if (c->master_password) xmlFree(c->master_password); if (c->user) xmlFree(c->user); if (c->group) xmlFree(c->group); if (c->mimetypes_fn) xmlFree(c->mimetypes_fn); if (c->tls_context.cert_file) xmlFree(c->tls_context.cert_file); if (c->tls_context.key_file) xmlFree(c->tls_context.key_file); if (c->tls_context.cipher_list) xmlFree(c->tls_context.cipher_list); event_registration_release(c->event); while ((c->listen_sock = config_clear_listener(c->listen_sock))); thread_mutex_lock(&(_locks.relay_lock)); for (i = 0; i < c->relay_length; i++) { relay_config_free(c->relay[i]); } free(c->relay); thread_mutex_unlock(&(_locks.relay_lock)); mount = c->mounts; while (mount) { nextmount = mount->next; config_clear_mount(mount); mount = nextmount; } config_clear_resource(c->resources); #ifdef USE_YP config_clear_yp_directories(c->yp_directories); #endif config_clear_http_header(c->http_headers); refobject_unref(c->reportxml_db); config_clear_prng_seed(c->prng_seed); memset(c, 0, sizeof(ice_config_t)); } void config_reread_config(void) { int ret; ice_config_t *config; ice_config_t new_config; /* reread config file */ config = config_grab_config(); /* Both to get the lock, and to be able to find out the config filename */ xmlSetGenericErrorFunc("config", log_parse_failure); ret = config_parse_file(config->config_filename, &new_config); if(ret < 0) { ICECAST_LOG_ERROR("Error parsing config, not replacing existing config"); switch (ret) { case CONFIG_EINSANE: ICECAST_LOG_ERROR("Config filename null or blank"); break; case CONFIG_ENOROOT: ICECAST_LOG_ERROR("Root element not found in %s", config->config_filename); break; case CONFIG_EBADROOT: ICECAST_LOG_ERROR("Not an icecast2 config file: %s", config->config_filename); break; default: ICECAST_LOG_ERROR("Parse error in reading %s", config->config_filename); break; } config_release_config(); } else { config_clear(config); config_set_config(&new_config); config = config_get_config_unlocked(); restart_logging(config); prng_configure(config); main_config_reload(config); connection_reread_config(config); yp_recheck_config(config); fserve_recheck_mime_types(config); stats_global(config); config_release_config(); slave_update_all_mounts(); xslt_clear_cache(); } } int config_initial_parse_file(const char *filename) { /* Since we're already pointing at it, we don't need to copy it in place */ return config_parse_file(filename, &_current_configuration); } int config_parse_file(const char *filename, ice_config_t *configuration) { xmlDocPtr doc; xmlNodePtr node; if (filename == NULL || strcmp(filename, "") == 0) return CONFIG_EINSANE; doc = xmlParseFile(filename); if (doc == NULL) return CONFIG_EPARSE; node = xmlDocGetRootElement(doc); if (node == NULL) { xmlFreeDoc(doc); return CONFIG_ENOROOT; } if (xmlStrcmp(node->name, XMLSTR("icecast")) != 0) { xmlFreeDoc(doc); return CONFIG_EBADROOT; } config_init_configuration(configuration); configuration->config_filename = strdup(filename); _parse_root(doc, node->xmlChildrenNode, configuration); xmlFreeDoc(doc); _merge_mounts_all(configuration); if (configuration->client_limit <= (configuration->source_limit*2)) { configuration->config_problems |= CONFIG_PROBLEM_VALIDATION; ICECAST_LOG_ERROR("Client limit (%i) is too small for given source limit (%i)", configuration->client_limit, configuration->source_limit); } return 0; } int config_parse_cmdline(int arg, char **argv) { return 0; } ice_config_locks *config_locks(void) { return &_locks; } void config_release_config(void) { thread_rwlock_unlock(&(_locks.config_lock)); } ice_config_t *config_get_config(void) { thread_rwlock_rlock(&(_locks.config_lock)); return &_current_configuration; } ice_config_t *config_grab_config(void) { thread_rwlock_wlock(&(_locks.config_lock)); return &_current_configuration; } /* MUST be called with the lock held! */ void config_set_config(ice_config_t *config) { memcpy(&_current_configuration, config, sizeof(ice_config_t)); } ice_config_t *config_get_config_unlocked(void) { return &_current_configuration; } static void _set_defaults(ice_config_t *configuration) { configuration ->location = (char *) xmlCharStrdup(CONFIG_DEFAULT_LOCATION); configuration ->server_id = (char *) xmlCharStrdup(ICECAST_VERSION_STRING); configuration ->admin = (char *) xmlCharStrdup(CONFIG_DEFAULT_ADMIN); configuration ->client_limit = CONFIG_DEFAULT_CLIENT_LIMIT; configuration ->source_limit = CONFIG_DEFAULT_SOURCE_LIMIT; configuration ->queue_size_limit = CONFIG_DEFAULT_QUEUE_SIZE_LIMIT; configuration ->body_size_limit = CONFIG_DEFAULT_BODY_SIZE_LIMIT; configuration ->client_timeout = CONFIG_DEFAULT_CLIENT_TIMEOUT; configuration ->header_timeout = CONFIG_DEFAULT_HEADER_TIMEOUT; configuration ->source_timeout = CONFIG_DEFAULT_SOURCE_TIMEOUT; configuration ->body_timeout = CONFIG_DEFAULT_BODY_TIMEOUT; configuration ->shoutcast_mount = (char *) xmlCharStrdup(CONFIG_DEFAULT_SHOUTCAST_MOUNT); configuration ->shoutcast_user = (char *) xmlCharStrdup(CONFIG_DEFAULT_SHOUTCAST_USER); configuration ->fileserve = CONFIG_DEFAULT_FILESERVE; configuration ->on_demand = 0; configuration ->hostname = (char *) xmlCharStrdup(CONFIG_DEFAULT_HOSTNAME); configuration ->mimetypes_fn = (char *) xmlCharStrdup(MIMETYPESFILE); configuration ->master_server = NULL; configuration ->master_server_port = 0; configuration ->master_update_interval = CONFIG_MASTER_UPDATE_INTERVAL; configuration ->master_username = (char *) xmlCharStrdup(CONFIG_DEFAULT_MASTER_USERNAME); configuration ->master_password = NULL; configuration ->base_dir = (char *) xmlCharStrdup(CONFIG_DEFAULT_BASE_DIR); configuration ->log_dir = (char *) xmlCharStrdup(CONFIG_DEFAULT_LOG_DIR); configuration ->null_device = (char *) xmlCharStrdup(CONFIG_DEFAULT_NULL_FILE); configuration ->webroot_dir = (char *) xmlCharStrdup(CONFIG_DEFAULT_WEBROOT_DIR); configuration ->adminroot_dir = (char *) xmlCharStrdup(CONFIG_DEFAULT_ADMINROOT_DIR); configuration ->playlist_log = (char *) xmlCharStrdup(CONFIG_DEFAULT_PLAYLIST_LOG); configuration ->access_log = (char *) xmlCharStrdup(CONFIG_DEFAULT_ACCESS_LOG); configuration ->error_log = (char *) xmlCharStrdup(CONFIG_DEFAULT_ERROR_LOG); configuration ->loglevel = CONFIG_DEFAULT_LOG_LEVEL; configuration ->playlist_log_lines_kept = CONFIG_DEFAULT_LOG_LINES_KEPT; configuration ->access_log_lines_kept = CONFIG_DEFAULT_LOG_LINES_KEPT; configuration ->error_log_lines_kept = CONFIG_DEFAULT_LOG_LINES_KEPT; configuration ->chroot = CONFIG_DEFAULT_CHROOT; configuration ->chuid = CONFIG_DEFAULT_CHUID; configuration ->user = NULL; configuration ->group = NULL; /* default to a typical prebuffer size used by clients */ configuration ->burst_size = CONFIG_DEFAULT_BURST_SIZE; configuration->tls_context .cipher_list = (char *) xmlCharStrdup(CONFIG_DEFAULT_CIPHER_LIST); } static inline void __check_hostname(ice_config_t *configuration) { int sane_hostname = 0; char *p; /* ensure we have a non-NULL buffer: */ if (!configuration->hostname) configuration->hostname = (char *)xmlCharStrdup(CONFIG_DEFAULT_HOSTNAME); /* convert to lower case: */ for (p = configuration->hostname; *p; p++) { if ( *p >= 'A' && *p <= 'Z' ) *p += 'a' - 'A'; } switch (util_hostcheck(configuration->hostname)) { case HOSTCHECK_SANE: sane_hostname = 1; break; case HOSTCHECK_ERROR: ICECAST_LOG_ERROR("Can not check hostname \"%s\".", configuration->hostname); break; case HOSTCHECK_NOT_FQDN: ICECAST_LOG_WARN("Warning, seems not to be set to a " "fully qualified domain name (FQDN). This may cause problems, " "e.g. with YP directory listings."); break; case HOSTCHECK_IS_LOCALHOST: ICECAST_LOG_WARN("Warning, not configured, using " "default value \"%s\". This will cause problems, e.g. " "this breaks YP directory listings. YP directory listing " "support will be disabled.", CONFIG_DEFAULT_HOSTNAME); /* FIXME actually disable YP */ break; case HOSTCHECK_IS_IPV4: ICECAST_LOG_WARN("Warning, seems to be set to an IPv4 " "address. This may cause problems, e.g. with YP directory " "listings."); break; case HOSTCHECK_IS_IPV6: ICECAST_LOG_WARN("Warning, seems to be set to an IPv6 " "address. This may cause problems, e.g. with YP directory " "listings."); break; case HOSTCHECK_BADCHAR: ICECAST_LOG_WARN("Warning, contains unusual " "characters. This may cause problems, e.g. with YP directory " "listings."); break; } if (!sane_hostname) configuration->config_problems |= CONFIG_PROBLEM_HOSTNAME; } static void _parse_root(xmlDocPtr doc, xmlNodePtr node, ice_config_t *configuration) { char *tmp; char *source_password = NULL; configuration ->listen_sock = calloc(1, sizeof(*configuration->listen_sock)); configuration ->listen_sock->port = 8000; configuration ->listen_sock_count = 1; do { if (node == NULL) break; if (xmlIsBlankNode(node)) continue; if (xmlStrcmp(node->name, XMLSTR("location")) == 0) { if (configuration->location) xmlFree(configuration->location); configuration->location = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); } else if (xmlStrcmp(node->name, XMLSTR("admin")) == 0) { if (configuration->admin) xmlFree(configuration->admin); configuration->admin = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); } else if (xmlStrcmp(node->name, XMLSTR("server-id")) == 0) { xmlFree(configuration->server_id); configuration->server_id = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); ICECAST_LOG_WARN("Warning, server version string override " "detected. This may lead to unexpected client software " "behavior."); } else if (xmlStrcmp(node->name, XMLSTR("authentication")) == 0) { _parse_authentication(doc, node->xmlChildrenNode, configuration, &source_password); } else if (xmlStrcmp(node->name, XMLSTR("source-password")) == 0) { /* TODO: This is the backwards-compatibility location */ ICECAST_LOG_WARN(" defined outside " ". This is deprecated and will be removed in " "version 2.X.0"); __found_bad_tag(configuration, node, BTR_OBSOLETE, "Use in ."); /* FIXME Settle target version for removal of this functionality! */ if (source_password) xmlFree(source_password); source_password = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); } else if (xmlStrcmp(node->name, XMLSTR("icelogin")) == 0) { ICECAST_LOG_ERROR(" support has been removed."); __found_bad_tag(configuration, node, BTR_OBSOLETE, NULL); } else if (xmlStrcmp(node->name, XMLSTR("fileserve")) == 0) { tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); configuration->fileserve = util_str_to_bool(tmp); if (tmp) xmlFree(tmp); } else if (xmlStrcmp(node->name, XMLSTR("relays-on-demand")) == 0) { tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); configuration->on_demand = util_str_to_bool(tmp); if (tmp) xmlFree(tmp); } else if (xmlStrcmp(node->name, XMLSTR("hostname")) == 0) { if (configuration->hostname) xmlFree(configuration->hostname); configuration->hostname = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); } else if (xmlStrcmp(node->name, XMLSTR("mime-types")) == 0) { __found_bad_tag(configuration, node, BTR_OBSOLETE, "Use in ."); if (configuration->mimetypes_fn) xmlFree(configuration->mimetypes_fn); configuration->mimetypes_fn = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); } else if (xmlStrcmp(node->name, XMLSTR("listen-socket")) == 0) { _parse_listen_socket(doc, node, configuration); } else if (xmlStrcmp(node->name, XMLSTR("port")) == 0) { tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); if (tmp && *tmp) { configuration->port = atoi(tmp); configuration->listen_sock->port = atoi(tmp); xmlFree(tmp); } else { ICECAST_LOG_WARN(" setting must not be empty."); } } else if (xmlStrcmp(node->name, XMLSTR("bind-address")) == 0) { if (configuration->listen_sock->bind_address) xmlFree(configuration->listen_sock->bind_address); configuration->listen_sock->bind_address = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); } else if (xmlStrcmp(node->name, XMLSTR("master-server")) == 0) { if (configuration->master_server) xmlFree(configuration->master_server); configuration->master_server = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); } else if (xmlStrcmp(node->name, XMLSTR("master-username")) == 0) { if (configuration->master_username) xmlFree(configuration->master_username); configuration->master_username = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); } else if (xmlStrcmp(node->name, XMLSTR("master-password")) == 0) { if (configuration->master_password) xmlFree(configuration->master_password); configuration->master_password = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); } else if (xmlStrcmp(node->name, XMLSTR("master-server-port")) == 0) { __read_int(configuration, doc, node, &configuration->master_server_port, RANGE_PORT); } else if (xmlStrcmp(node->name, XMLSTR("master-update-interval")) == 0) { __read_int(configuration, doc, node, &configuration->master_update_interval, 15, 3600); } else if (xmlStrcmp(node->name, XMLSTR("shoutcast-mount")) == 0) { if (configuration->shoutcast_mount) xmlFree(configuration->shoutcast_mount); configuration->shoutcast_mount = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); } else if (xmlStrcmp(node->name, XMLSTR("limits")) == 0) { _parse_limits(doc, node->xmlChildrenNode, configuration); } else if (xmlStrcmp(node->name, XMLSTR("http-headers")) == 0) { config_parse_http_headers(node->xmlChildrenNode, &(configuration->http_headers), configuration); } else if (xmlStrcmp(node->name, XMLSTR("relay")) == 0) { _parse_relay(doc, node->xmlChildrenNode, configuration, NULL); } else if (xmlStrcmp(node->name, XMLSTR("mount")) == 0) { _parse_mount(doc, node, configuration); } else if (xmlStrcmp(node->name, XMLSTR("directory")) == 0) { _parse_oldstyle_directory(doc, node->xmlChildrenNode, configuration); } else if (xmlStrcmp(node->name, XMLSTR("yp-directory")) == 0) { _parse_yp_directory(doc, node, configuration); } else if (xmlStrcmp(node->name, XMLSTR("paths")) == 0) { _parse_paths(doc, node->xmlChildrenNode, configuration); } else if (xmlStrcmp(node->name, XMLSTR("logging")) == 0) { _parse_logging(doc, node->xmlChildrenNode, configuration); } else if (xmlStrcmp(node->name, XMLSTR("security")) == 0) { _parse_security(doc, node->xmlChildrenNode, configuration); } else if (xmlStrcmp(node->name, XMLSTR("event-bindings")) == 0 || xmlStrcmp(node->name, XMLSTR("kartoffelsalat")) == 0) { _parse_events(&configuration->event, node->xmlChildrenNode); } else { __found_bad_tag(configuration, node, BTR_UNKNOWN, NULL); } } while ((node = node->next)); /* global source password is set. * We need to set it on default mount. * If default mount has a authstack not NULL we don't need to do anything. */ if (source_password) { mount_proxy *mount = config_find_mount(configuration, NULL, MOUNT_TYPE_DEFAULT); if (!mount) { /* create a default mount here */ xmlNodePtr node; node = xmlNewNode(NULL, XMLSTR("mount")); xmlSetProp(node, XMLSTR("type"), XMLSTR("default")); _parse_mount(doc, node, configuration); xmlFreeNode(node); mount = config_find_mount(configuration, NULL, MOUNT_TYPE_DEFAULT); } if (mount) { if (!mount->authstack) { __append_old_style_auth(configuration, &mount->authstack, CONFIG_LEGACY_SOURCE_NAME_GLOBAL, AUTH_TYPE_STATIC, "source", source_password, NULL, CONFIG_LEGACY_SOURCE_METHODS, CONFIG_LEGACY_SOURCE_ALLOW_WEB, CONFIG_LEGACY_SOURCE_ALLOW_ADMIN); } } else { ICECAST_LOG_ERROR("Can not find nor create default mount, but " "global legacy source password set. This is bad."); } xmlFree(source_password); } /* drop the first listening socket details if more than one is defined, as we only * have port or listen-socket not both */ if (configuration->listen_sock_count > 1) { configuration->listen_sock = config_clear_listener(configuration->listen_sock); configuration->listen_sock_count--; } if (configuration->port == 0) configuration->port = 8000; if (!configuration->prng_seed) { configuration->config_problems |= CONFIG_PROBLEM_PRNG; #ifndef _WIN32 configuration->prng_seed = calloc(1, sizeof(prng_seed_config_t)); if (configuration->prng_seed) { configuration->prng_seed->filename = (char*)xmlStrdup(XMLSTR("linux")); // the linux profile is also fine on BSD. configuration->prng_seed->type = PRNG_SEED_TYPE_PROFILE; configuration->prng_seed->size = -1; ICECAST_LOG_WARN("Warning, no PRNG seed configured, using default profile \"linux\"."); } else { ICECAST_LOG_ERROR("No PRNG seed configured and unable to add one. PRNG is insecure."); } #else ICECAST_LOG_ERROR("No PRNG seed configured and unable to add one. PRNG is insecure."); #endif } /* issue some warnings on bad configurations */ if (!configuration->fileserve) ICECAST_LOG_WARN("Warning, serving of static files has been disabled " "in the config, this will also affect files used by the web " "interface (stylesheets, images)."); __check_hostname(configuration); if (!configuration->location || strcmp(configuration->location, CONFIG_DEFAULT_LOCATION) == 0) { ICECAST_LOG_WARN("Warning, not configured, using default " "value \"%s\".", CONFIG_DEFAULT_LOCATION); if (!configuration->location) configuration->location = (char *) xmlCharStrdup(CONFIG_DEFAULT_LOCATION); configuration->config_problems |= CONFIG_PROBLEM_LOCATION; } if (!configuration->admin || strcmp(configuration->admin, CONFIG_DEFAULT_ADMIN) == 0) { ICECAST_LOG_WARN("Warning, contact not configured, using " "default value \"%s\". This breaks YP directory listings. " "YP directory support will be disabled.", CONFIG_DEFAULT_ADMIN); /* FIXME actually disable YP */ if (!configuration->admin) configuration->admin = (char *) xmlCharStrdup(CONFIG_DEFAULT_ADMIN); configuration->config_problems |= CONFIG_PROBLEM_ADMIN; } } static void _parse_limits(xmlDocPtr doc, xmlNodePtr node, ice_config_t *configuration) { char *tmp; do { if (node == NULL) break; if (xmlIsBlankNode(node)) continue; if (xmlStrcmp(node->name, XMLSTR("clients")) == 0) { __read_int(configuration, doc, node, &configuration->client_limit, 1, CONFIG_MAX_CLIENT_LIMIT); } else if (xmlStrcmp(node->name, XMLSTR("sources")) == 0) { __read_int(configuration, doc, node, &configuration->source_limit, 1, CONFIG_MAX_SOURCE_LIMIT); } else if (xmlStrcmp(node->name, XMLSTR("bodysize")) == 0) { __read_int(configuration, doc, node, &configuration->body_size_limit, CONFIG_MIN_BODY_SIZE_LIMIT, CONFIG_MAX_BODY_SIZE_LIMIT); } else if (xmlStrcmp(node->name, XMLSTR("queue-size")) == 0) { __read_unsigned_int(configuration, doc, node, &configuration->queue_size_limit, 1, CONFIG_MAX_QUEUE_SIZE_LIMIT); } else if (xmlStrcmp(node->name, XMLSTR("client-timeout")) == 0) { __read_int(configuration, doc, node, &configuration->client_timeout, CONFIG_RANGE_CLIENT_TIMEOUT); } else if (xmlStrcmp(node->name, XMLSTR("header-timeout")) == 0) { __read_int(configuration, doc, node, &configuration->header_timeout, CONFIG_RANGE_HEADER_TIMEOUT); } else if (xmlStrcmp(node->name, XMLSTR("source-timeout")) == 0) { __read_int(configuration, doc, node, &configuration->source_timeout, CONFIG_RANGE_SOURCE_TIMEOUT); } else if (xmlStrcmp(node->name, XMLSTR("body-timeout")) == 0) { __read_int(configuration, doc, node, &configuration->body_timeout, CONFIG_RANGE_BODY_TIMEOUT); } else if (xmlStrcmp(node->name, XMLSTR("burst-on-connect")) == 0) { __found_bad_tag(configuration, node, BTR_OBSOLETE, "Use ."); tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); if (util_str_to_int(tmp, 0) == 0) configuration->burst_size = 0; if (tmp) xmlFree(tmp); } else if (xmlStrcmp(node->name, XMLSTR("burst-size")) == 0) { __read_unsigned_int(configuration, doc, node, &configuration->burst_size, 0, CONFIG_MAX_QUEUE_SIZE_LIMIT); } else { __found_bad_tag(configuration, node, BTR_UNKNOWN, NULL); } } while ((node = node->next)); } static void _parse_authentication_node(ice_config_t *configuration, xmlNodePtr node, auth_stack_t **authstack) { xmlChar *tmp; if (xmlStrcmp(node->name, XMLSTR("authentication")) != 0) return; tmp = xmlGetProp(node, XMLSTR("type")); if (tmp) { ICECAST_LOG_ERROR("new style parser called on old style config."); xmlFree(tmp); return; } xmlNodePtr child = node->xmlChildrenNode; do { if (child == NULL) break; if (xmlIsBlankNode(child)) continue; if (xmlStrcmp(child->name, XMLSTR("role")) == 0) { auth_t *auth = auth_get_authenticator(configuration, child); auth_stack_push(authstack, auth); auth_release(auth); } } while ((child = child->next)); } static void _parse_mount_oldstyle_authentication(mount_proxy *mount, xmlNodePtr node, auth_stack_t **authstack, ice_config_t *configuration) { int allow_duplicate_users = 1; auth_t *auth; char *type; char *name; char *value; xmlNodePtr child; child = node->xmlChildrenNode; while (child) { if (xmlStrcmp(child->name, XMLSTR("option")) == 0) { name = (char *)xmlGetProp(child, XMLSTR("name")); value = (char *)xmlGetProp(child, XMLSTR("value")); if (name && value) { if (strcmp(name, "allow_duplicate_users") == 0) { allow_duplicate_users = util_str_to_bool(value); } } if (name) xmlFree(name); if (value) xmlFree(value); } child = child->next; } type = (char *)xmlGetProp(node, XMLSTR("type")); if (strcmp(type, AUTH_TYPE_HTPASSWD) == 0) { if (!allow_duplicate_users) xmlSetProp(node, XMLSTR("connections-per-user"), XMLSTR("0")); auth = auth_get_authenticator(configuration, node); if (auth) { auth_stack_push(authstack, auth); auth_release(auth); } __append_old_style_auth(configuration, authstack, NULL, AUTH_TYPE_ANONYMOUS, NULL, NULL, CONFIG_LEGACY_ANONYMOUS_METHODS, NULL, 0, NULL); } else if (strcmp(type, AUTH_TYPE_URL) == 0) { /* This block is super fun! Attention! Super fun ahead! Ladies and Gentlemen take care and watch your children! */ /* Stuff that was of help: * $ sed 's/^.*name="\([^"]*\)".*$/ const char *\1 = NULL;/' * $ sed 's/^.*name="\([^"]*\)".*$/ if (\1)\n xmlFree(\1);/' * $ sed 's/^.*name="\([^"]*\)".*$/ } else if (strcmp(name, "\1") == 0) {\n \1 = value;\n value = NULL;/' */ /* urls */ char *mount_add = NULL; char *mount_remove = NULL; char *listener_add = NULL; char *listener_remove = NULL; char *stream_auth = NULL; /* request credentials */ char *username = NULL; char *password = NULL; /* general options */ char *auth_header = NULL; char *timelimit_header = NULL; char *headers = NULL; char *header_prefix = NULL; child = node->xmlChildrenNode; while (child) { if (xmlStrcmp(child->name, XMLSTR("option")) == 0) { name = (char *)xmlGetProp(child, XMLSTR("name")); value = (char *)xmlGetProp(child, XMLSTR("value")); if (name && value) { if (strcmp(name, "mount_add") == 0) { mount_add = value; value = NULL; } else if (strcmp(name, "mount_remove") == 0) { mount_remove = value; value = NULL; } else if (strcmp(name, "listener_add") == 0) { listener_add = value; value = NULL; } else if (strcmp(name, "listener_remove") == 0) { listener_remove = value; value = NULL; } else if (strcmp(name, "username") == 0) { username = value; value = NULL; } else if (strcmp(name, "password") == 0) { password = value; value = NULL; } else if (strcmp(name, "auth_header") == 0) { auth_header = value; value = NULL; } else if (strcmp(name, "timelimit_header") == 0) { timelimit_header = value; value = NULL; } else if (strcmp(name, "headers") == 0) { headers = value; value = NULL; } else if (strcmp(name, "header_prefix") == 0) { header_prefix = value; value = NULL; } else if (strcmp(name, "stream_auth") == 0) { stream_auth = value; value = NULL; } } if (name) xmlFree(name); if (value) xmlFree(value); } child = child->next; } if (mount_add) __append_old_style_url_event(&mount->event, "source-connect", mount_add, "mount_add", username, password); if (mount_remove) __append_old_style_url_event(&mount->event, "source-disconnect", mount_add, "mount_remove", username, password); __append_old_style_urlauth(configuration, authstack, listener_add, listener_remove, "listener_add", "listener_remove", username, password, 0, auth_header, timelimit_header, headers, header_prefix); __append_old_style_urlauth(configuration, authstack, stream_auth, NULL, "stream_auth", NULL, username, password, 1, auth_header, timelimit_header, headers, header_prefix); if (listener_add) __append_old_style_auth(configuration, authstack, NULL, AUTH_TYPE_ANONYMOUS, NULL, NULL, CONFIG_LEGACY_ANONYMOUS_METHODS, NULL, false, NULL); if (stream_auth) __append_old_style_auth(configuration, authstack, NULL, AUTH_TYPE_ANONYMOUS, NULL, NULL, CONFIG_LEGACY_SOURCE_METHODS, NULL, false, NULL); if (mount_add) xmlFree(mount_add); if (mount_remove) xmlFree(mount_remove); if (listener_add) xmlFree(listener_add); if (listener_remove) xmlFree(listener_remove); if (username) xmlFree(username); if (password) xmlFree(password); if (auth_header) xmlFree(auth_header); if (timelimit_header) xmlFree(timelimit_header); if (headers) xmlFree(headers); if (header_prefix) xmlFree(header_prefix); if (stream_auth) xmlFree(stream_auth); } else { ICECAST_LOG_ERROR("Unknown authentication type in legacy mode. " "Anonymous listeners and global login for sources disabled."); __append_old_style_auth(configuration, authstack, NULL, AUTH_TYPE_ANONYMOUS, NULL, NULL, NULL, NULL, false, NULL); } xmlFree(type); } static void _parse_mount(xmlDocPtr doc, xmlNodePtr parentnode, ice_config_t *configuration) { char *tmp; mount_proxy *mount = calloc(1, sizeof(mount_proxy)); mount_proxy *current = configuration->mounts; mount_proxy *last = NULL; char *username = NULL; char *password = NULL; auth_stack_t *authstack = NULL; xmlNodePtr node; /* default settings */ mount->mounttype = MOUNT_TYPE_NORMAL; mount->max_listeners = -1; mount->burst_size = -1; mount->mp3_meta_interval = -1; mount->yp_public = -1; mount->max_history = -1; mount->allow_direct_access = true; mount->next = NULL; tmp = (char *)xmlGetProp(parentnode, XMLSTR("type")); if (tmp) { if (strcmp(tmp, "normal") == 0) { mount->mounttype = MOUNT_TYPE_NORMAL; } else if (strcmp(tmp, "default") == 0) { mount->mounttype = MOUNT_TYPE_DEFAULT; } else { ICECAST_LOG_WARN("Unknown mountpoint type: %s", tmp); config_clear_mount(mount); return; } xmlFree(tmp); } node = parentnode->xmlChildrenNode; do { if (node == NULL) break; if (xmlIsBlankNode(node)) continue; if (xmlStrcmp(node->name, XMLSTR("mount-name")) == 0) { mount->mountname = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); } else if (xmlStrcmp(node->name, XMLSTR("username")) == 0) { username = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); } else if (xmlStrcmp(node->name, XMLSTR("password")) == 0) { password = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); } else if (xmlStrcmp(node->name, XMLSTR("dump-file")) == 0) { mount->dumpfile = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); } else if (xmlStrcmp(node->name, XMLSTR("intro")) == 0) { mount->intro_filename = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); } else if (xmlStrcmp(node->name, XMLSTR("fallback-mount")) == 0) { mount->fallback_mount = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); } else if (xmlStrcmp(node->name, XMLSTR("fallback-when-full")) == 0) { tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); mount->fallback_when_full = util_str_to_bool(tmp); if(tmp) xmlFree(tmp); } else if (xmlStrcmp(node->name, XMLSTR("max-listeners")) == 0) { __read_int(configuration, doc, node, &mount->max_listeners, 1, CONFIG_MAX_LISTENERS); } else if (xmlStrcmp(node->name, XMLSTR("max-history")) == 0) { tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); mount->max_history = util_str_to_int(tmp, mount->max_history); if (mount->max_history < 1 || mount->max_history > 256) mount->max_history = 256; /* deny super huge values */ if(tmp) xmlFree(tmp); } else if (xmlStrcmp(node->name, XMLSTR("charset")) == 0) { mount->charset = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); } else if (xmlStrcmp(node->name, XMLSTR("mp3-metadata-interval")) == 0) { __found_bad_tag(configuration, node, BTR_OBSOLETE, "Use ."); /* FIXME when do we plan to remove this? */ __read_int(configuration, doc, node, &mount->mp3_meta_interval, RANGE_ICY_INTERVAL); } else if (xmlStrcmp(node->name, XMLSTR("icy-metadata-interval")) == 0) { __read_int(configuration, doc, node, &mount->mp3_meta_interval, RANGE_ICY_INTERVAL); } else if (xmlStrcmp(node->name, XMLSTR("fallback-override")) == 0) { tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); mount->fallback_override = config_str_to_fallback_override_t(configuration, node, tmp); if(tmp) xmlFree(tmp); } else if (xmlStrcmp(node->name, XMLSTR("no-mount")) == 0) { __found_bad_tag(configuration, node, BTR_OBSOLETE, "Use ."); tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); mount->allow_direct_access = !util_str_to_bool(tmp); if(tmp) xmlFree(tmp); } else if (xmlStrcmp(node->name, XMLSTR("allow-direct-access")) == 0) { tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); mount->allow_direct_access = util_str_to_bool(tmp); if(tmp) xmlFree(tmp); } else if (xmlStrcmp(node->name, XMLSTR("no-yp")) == 0) { __found_bad_tag(configuration, node, BTR_OBSOLETE, "Use ."); /* FIXME when do we plan to remove this? */ tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); mount->yp_public = util_str_to_bool(tmp) == 0 ? -1 : 0; if(tmp) xmlFree(tmp); } else if (xmlStrcmp(node->name, XMLSTR("hidden")) == 0) { tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); mount->hidden = util_str_to_bool(tmp); if(tmp) xmlFree(tmp); } else if (xmlStrcmp(node->name, XMLSTR("authentication")) == 0) { tmp = (char *)xmlGetProp(node, XMLSTR("type")); if (tmp) { xmlFree(tmp); _parse_mount_oldstyle_authentication(mount, node, &authstack, configuration); } else { _parse_authentication_node(configuration, node, &authstack); } } else if (xmlStrcmp(node->name, XMLSTR("on-connect")) == 0) { tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); if (tmp) { __append_old_style_exec_event(&mount->event, "source-connect", tmp); xmlFree(tmp); } } else if (xmlStrcmp(node->name, XMLSTR("on-disconnect")) == 0) { tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); if (tmp) { __append_old_style_exec_event(&mount->event, "source-disconnect", tmp); xmlFree(tmp); } } else if (xmlStrcmp(node->name, XMLSTR("max-listener-duration")) == 0) { __read_unsigned_int(configuration, doc, node, &mount->max_listener_duration, 0, UINT_MAX); } else if (xmlStrcmp(node->name, XMLSTR("queue-size")) == 0) { __read_unsigned_int(configuration, doc, node, &mount->queue_size_limit, 1, CONFIG_MAX_QUEUE_SIZE_LIMIT); } else if (xmlStrcmp(node->name, XMLSTR("source-timeout")) == 0) { __read_unsigned_int(configuration, doc, node, &mount->source_timeout, CONFIG_RANGE_SOURCE_TIMEOUT); } else if (xmlStrcmp(node->name, XMLSTR("burst-size")) == 0) { __read_int(configuration, doc, node, &mount->burst_size, 0, CONFIG_MAX_QUEUE_SIZE_LIMIT); } else if (xmlStrcmp(node->name, XMLSTR("cluster-password")) == 0) { mount->cluster_password = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); } else if (xmlStrcmp(node->name, XMLSTR("stream-name")) == 0) { mount->stream_name = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); } else if (xmlStrcmp(node->name, XMLSTR("stream-description")) == 0) { mount->stream_description = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); } else if (xmlStrcmp(node->name, XMLSTR("stream-url")) == 0) { mount->stream_url = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); } else if (xmlStrcmp(node->name, XMLSTR("genre")) == 0) { mount->stream_genre = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); } else if (xmlStrcmp(node->name, XMLSTR("bitrate")) == 0) { mount->bitrate = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); } else if (xmlStrcmp(node->name, XMLSTR("public")) == 0) { tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); mount->yp_public = __parse_public(tmp); if(tmp) xmlFree(tmp); } else if (xmlStrcmp(node->name, XMLSTR("type")) == 0) { mount->type = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); } else if (xmlStrcmp(node->name, XMLSTR("subtype")) == 0) { mount->subtype = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); } else if (xmlStrcmp(node->name, XMLSTR("http-headers")) == 0) { config_parse_http_headers(node->xmlChildrenNode, &(mount->http_headers), configuration); } else if (xmlStrcmp(node->name, XMLSTR("event-bindings")) == 0 || xmlStrcmp(node->name, XMLSTR("kartoffelsalat")) == 0) { _parse_events(&mount->event, node->xmlChildrenNode); } else if (xmlStrcmp(node->name, XMLSTR("relay")) == 0) { /* no-op, handled in second pass */ } else { __found_bad_tag(configuration, node, BTR_UNKNOWN, NULL); } } while ((node = node->next)); /* Do a second interation as we need to know mount->mountname, and mount->mounttype first */ node = parentnode->xmlChildrenNode; do { if (node == NULL) break; if (xmlStrcmp(node->name, XMLSTR("relay")) == 0) { if (mount->mounttype != MOUNT_TYPE_NORMAL) { ICECAST_LOG_WARN(" set within for mountpoint %s%s%s that is not type=\"normal\"", (mount->mountname ? "\"" : ""), (mount->mountname ? mount->mountname : ""), (mount->mountname ? "\"" : "")); } else if (!mount->mountname || mount->mountname[0] != '/') { ICECAST_LOG_WARN(" set within with no mountpoint defined."); } else { _parse_relay(doc, node->xmlChildrenNode, configuration, mount->mountname); } } } while ((node = node->next)); if (password) { auth_stack_t *old_style = NULL; __append_old_style_auth(configuration, &old_style, CONFIG_LEGACY_SOURCE_NAME_MOUNT, AUTH_TYPE_STATIC, username ? username : "source", password, NULL, CONFIG_LEGACY_SOURCE_METHODS, CONFIG_LEGACY_SOURCE_ALLOW_WEB, CONFIG_LEGACY_SOURCE_ALLOW_ADMIN); if (authstack) { auth_stack_append(old_style, authstack); auth_stack_release(authstack); } authstack = old_style; } if (username) xmlFree(username); if (password) xmlFree(password); if (mount->authstack) auth_stack_release(mount->authstack); auth_stack_addref(mount->authstack = authstack); ICECAST_LOG_DEBUG("Mount %p (mountpoint %s) has %sactive " "roles on authstack.", mount, mount->mountname, authstack ? "" : "no "); /* make sure we have at least the mountpoint name */ if (mount->mountname == NULL && mount->mounttype != MOUNT_TYPE_DEFAULT) { config_clear_mount(mount); return; } else if (mount->mountname != NULL && mount->mounttype == MOUNT_TYPE_DEFAULT) { ICECAST_LOG_WARN("Default mount %s has mount-name set. This is " "not supported. Behavior may not be consistent.", mount->mountname); } while (authstack) { auth_t *auth = auth_stack_get(authstack); if (mount->mountname) { auth->mount = strdup((char *)mount->mountname); } else if (mount->mounttype == MOUNT_TYPE_DEFAULT ) { auth->mount = strdup("(default mount)"); } auth_release(auth); auth_stack_next(&authstack); } while(current) { last = current; current = current->next; } if (!mount->fallback_mount && (mount->fallback_when_full || mount->fallback_override != FALLBACK_OVERRIDE_NONE)) { ICECAST_LOG_WARN("Config for mount %s contains fallback options " "but no fallback mount.", mount->mountname); } if(last) { last->next = mount; } else { configuration->mounts = mount; } } void config_parse_http_headers(xmlNodePtr node, ice_config_http_header_t **http_headers, ice_config_t *configuration) { ice_config_http_header_t *header; ice_config_http_header_t *next; char *name = NULL; char *value = NULL; char *tmp; int status; http_header_type type; do { if (node == NULL) break; if (xmlIsBlankNode(node)) continue; if (xmlStrcmp(node->name, XMLSTR("header")) != 0) continue; if (!(name = (char *)xmlGetProp(node, XMLSTR("name")))) break; value = (char *)xmlGetProp(node, XMLSTR("value")); type = HTTP_HEADER_TYPE_STATIC; /* default */ if ((tmp = (char *)xmlGetProp(node, XMLSTR("type")))) { if (strcmp(tmp, "static") == 0) { type = HTTP_HEADER_TYPE_STATIC; } else if (strcmp(tmp, "cors") == 0 || strcmp(tmp, "corpse") == 0) { type = HTTP_HEADER_TYPE_CORS; } else { __found_bad_tag(configuration, node, BTR_INVALID, tmp); xmlFree(tmp); break; } xmlFree(tmp); } status = 0; /* default: any */ if ((tmp = (char *)xmlGetProp(node, XMLSTR("status")))) { status = util_str_to_int(tmp, 0); xmlFree(tmp); } header = calloc(1, sizeof(ice_config_http_header_t)); if (!header) break; header->type = type; header->name = name; header->value = value; header->status = status; name = NULL; value = NULL; if (!*http_headers) { *http_headers = header; continue; } next = *http_headers; while (next->next) { next = next->next; } next->next = header; } while ((node = node->next)); /* in case we used break we may need to clean those up */ if (name) xmlFree(name); if (value) xmlFree(value); } static void _parse_relay_upstream(xmlDocPtr doc, xmlNodePtr node, relay_config_upstream_t *upstream, ice_config_t *configuration) { char *tmp; do { if (node == NULL) break; if (xmlIsBlankNode(node)) continue; if (xmlStrcmp(node->name, XMLSTR("server")) == 0) { if (upstream->server) xmlFree(upstream->server); upstream->server = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); } else if (xmlStrcmp(node->name, XMLSTR("port")) == 0) { __read_int(configuration, doc, node, &upstream->port, RANGE_PORT); } else if (xmlStrcmp(node->name, XMLSTR("mount")) == 0) { if (upstream->mount) xmlFree(upstream->mount); upstream->mount = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); } else if (xmlStrcmp(node->name, XMLSTR("relay-shoutcast-metadata")) == 0) { tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); upstream->mp3metadata = util_str_to_bool(tmp); if(tmp) xmlFree(tmp); } else if (xmlStrcmp(node->name, XMLSTR("username")) == 0) { if (upstream->username) xmlFree(upstream->username); upstream->username = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); } else if (xmlStrcmp(node->name, XMLSTR("password")) == 0) { if (upstream->password) xmlFree(upstream->password); upstream->password = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); } else if (xmlStrcmp(node->name, XMLSTR("bind")) == 0) { if (upstream->bind) xmlFree(upstream->bind); upstream->bind = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); } else if (xmlStrcmp(node->name, XMLSTR("uri")) == 0) { xmlChar *uri = xmlNodeListGetString(doc, node->xmlChildrenNode, 1); if (uri) { xmlURIPtr parsed_uri = xmlParseURI((const char *)uri); if (parsed_uri) { if (parsed_uri->scheme && strcmp(parsed_uri->scheme, "http") == 0) { if (parsed_uri->server) { if (upstream->server) xmlFree(upstream->server); upstream->server = (char *)xmlStrdup(XMLSTR(parsed_uri->server)); } if (parsed_uri->port) { upstream->port = parsed_uri->port; } if (parsed_uri->user) { char *username = (char *)xmlStrdup(XMLSTR(parsed_uri->user)); if (username) { char *password = strchr(username, ':'); if (upstream->username) xmlFree(upstream->username); upstream->username = username; if (password) { *password = 0; if (upstream->password) xmlFree(upstream->password); upstream->password = (char *)xmlStrdup(XMLSTR(password+1)); } } } if (parsed_uri->path) { if (upstream->mount) xmlFree(upstream->mount); upstream->mount = (char *)xmlStrdup(XMLSTR(parsed_uri->path)); } } else { __found_bad_tag(configuration, node, BTR_INVALID, (const char *)uri); } xmlFreeURI(parsed_uri); } xmlFree(uri); } } } while ((node = node->next)); } static void _parse_relay_upstream_apply_defaults(relay_config_upstream_t *upstream) { if (!upstream->server) upstream->server = (char *)xmlCharStrdup(CONFIG_DEFAULT_RELAY_SERVER); if (!upstream->port) upstream->port = CONFIG_DEFAULT_RELAY_PORT; if (!upstream->mount) upstream->mount = (char *)xmlCharStrdup(CONFIG_DEFAULT_RELAY_MOUNT); } static void _parse_relay(xmlDocPtr doc, xmlNodePtr node, ice_config_t *configuration, const char *mount) { char *tmp; relay_config_t *relay = calloc(1, sizeof(relay_config_t)); relay_config_t **n = realloc(configuration->relay, sizeof(*configuration->relay)*(configuration->relay_length + 1)); if (!n) { free(relay); ICECAST_LOG_ERROR("Can not allocate memory for additional relay."); return; } configuration->relay = n; configuration->relay[configuration->relay_length++] = relay; relay->upstream_default.mp3metadata = 1; relay->on_demand = configuration->on_demand; _parse_relay_upstream(doc, node, &(relay->upstream_default), configuration); do { if (node == NULL) break; if (xmlIsBlankNode(node)) continue; if (xmlStrcmp(node->name, XMLSTR("local-mount")) == 0) { if (mount) { ICECAST_LOG_WARN("Relay defined within mount \"%s\" defines which is ignored.", mount); } else { if (relay->localmount) xmlFree(relay->localmount); relay->localmount = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); } } else if (xmlStrcmp(node->name, XMLSTR("on-demand")) == 0) { tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); relay->on_demand = util_str_to_bool(tmp); if (tmp) xmlFree(tmp); } else if (xmlStrcmp(node->name, XMLSTR("upstream")) == 0) { tmp = (char *)xmlGetProp(node, XMLSTR("type")); if (tmp == NULL || strcmp(tmp, "normal") == 0) { relay_config_upstream_t *n = realloc(relay->upstream, sizeof(*n)*(relay->upstreams + 1)); if (n) { relay->upstream = n; memset(&(n[relay->upstreams]), 0, sizeof(relay_config_upstream_t)); _parse_relay_upstream(doc, node->xmlChildrenNode, &(n[relay->upstreams]), configuration); relay->upstreams++; } } else if (strcmp(tmp, "default") == 0) { _parse_relay_upstream(doc, node->xmlChildrenNode, &(relay->upstream_default), configuration); } else { __found_bad_tag(configuration, node, BTR_INVALID, tmp); ICECAST_LOG_WARN(" of unknown type is ignored."); } if (tmp) xmlFree(tmp); } else if (xmlStrcmp(node->name, XMLSTR("server")) == 0 || xmlStrcmp(node->name, XMLSTR("port")) == 0 || xmlStrcmp(node->name, XMLSTR("mount")) == 0 || xmlStrcmp(node->name, XMLSTR("relay-shoutcast-metadata")) == 0 || xmlStrcmp(node->name, XMLSTR("username")) == 0 || xmlStrcmp(node->name, XMLSTR("password")) == 0 || xmlStrcmp(node->name, XMLSTR("bind")) == 0 || xmlStrcmp(node->name, XMLSTR("uri")) == 0) { __found_bad_tag(configuration, node, BTR_OBSOLETE, "Use a block."); } else { __found_bad_tag(configuration, node, BTR_UNKNOWN, NULL); } } while ((node = node->next)); _parse_relay_upstream_apply_defaults(&(relay->upstream_default)); if (mount) { relay->localmount = (char *)xmlStrdup(XMLSTR(mount)); } if (relay->localmount == NULL) relay->localmount = (char *)xmlStrdup(XMLSTR(relay->upstream_default.mount)); } static void _parse_listen_socket(xmlDocPtr doc, xmlNodePtr node, ice_config_t *configuration) { char *tmp; listener_t *listener = calloc(1, sizeof(listener_t)); if (listener == NULL) return; listener->port = 8000; listener->id = (char *)xmlGetProp(node, XMLSTR("id")); tmp = (char*)xmlGetProp(node, XMLSTR("on-behalf-of")); if (tmp) { listener->on_behalf_of = config_href_to_id(configuration, node, tmp); xmlFree(tmp); } tmp = (char *)xmlGetProp(node, XMLSTR("type")); listener->type = config_str_to_listener_type(configuration, node, tmp); xmlFree(tmp); node = node->xmlChildrenNode; do { if (node == NULL) break; if (xmlIsBlankNode(node)) continue; if (xmlStrcmp(node->name, XMLSTR("port")) == 0) { tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); if (tmp) { if(configuration->port == 0) configuration->port = util_str_to_int(tmp, 0); listener->port = util_str_to_int(tmp, listener->port); xmlFree(tmp); } else { ICECAST_LOG_WARN(" setting must not be empty."); } } else if (xmlStrcmp(node->name, XMLSTR("tls")) == 0 || xmlStrcmp(node->name, XMLSTR("ssl")) == 0) { tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); listener->tls = str_to_tlsmode(tmp); if(tmp) xmlFree(tmp); } else if (xmlStrcmp(node->name, XMLSTR("shoutcast-compat")) == 0) { tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); listener->shoutcast_compat = util_str_to_bool(tmp); if(tmp) xmlFree(tmp); } else if (xmlStrcmp(node->name, XMLSTR("shoutcast-mount")) == 0) { if (listener->shoutcast_mount) xmlFree(listener->shoutcast_mount); listener->shoutcast_mount = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); } else if (xmlStrcmp(node->name, XMLSTR("bind-address")) == 0) { if (listener->bind_address) xmlFree(listener->bind_address); listener->bind_address = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); } else if (xmlStrcmp(node->name, XMLSTR("so-sndbuf")) == 0) { __read_int(configuration, doc, node, &listener->so_sndbuf, RANGE_SNDBUF); } else if (xmlStrcmp(node->name, XMLSTR("listen-backlog")) == 0) { __read_int(configuration, doc, node, &listener->listen_backlog, 1, 128); } else if (xmlStrcmp(node->name, XMLSTR("authentication")) == 0) { _parse_authentication_node(configuration, node, &(listener->authstack)); } else if (xmlStrcmp(node->name, XMLSTR("http-headers")) == 0) { config_parse_http_headers(node->xmlChildrenNode, &(listener->http_headers), configuration); } else { __found_bad_tag(configuration, node, BTR_UNKNOWN, NULL); } } while ((node = node->next)); /* we know there's at least one of these, so add this new one after the first * that way it can be removed easily later on */ listener->next = configuration->listen_sock->next; configuration->listen_sock->next = listener; configuration->listen_sock_count++; if (listener->shoutcast_mount && !listener->shoutcast_compat) { listener_t *sc_port = calloc(1, sizeof(listener_t)); sc_port->port = listener->port+1; sc_port->shoutcast_compat = 1; sc_port->shoutcast_mount = (char*)xmlStrdup(XMLSTR(listener->shoutcast_mount)); if (listener->bind_address) sc_port->bind_address = (char*)xmlStrdup(XMLSTR(listener->bind_address)); sc_port->next = listener->next; listener->next = sc_port; configuration->listen_sock_count++; } } static void _parse_authentication(xmlDocPtr doc, xmlNodePtr node, ice_config_t *configuration, char **source_password) { char *admin_password = NULL, *admin_username = NULL; char *relay_password = NULL, *relay_username = (char*)xmlCharStrdup(CONFIG_DEFAULT_MASTER_USERNAME); auth_stack_t *old_style = NULL, *new_style = NULL; do { if (node == NULL) break; if (xmlIsBlankNode(node)) continue; if (xmlStrcmp(node->name, XMLSTR("source-password")) == 0) { if (xmlGetProp(node, XMLSTR("mount"))) { ICECAST_LOG_ERROR("Mount level source password defined within global section."); } else { if (*source_password) xmlFree(*source_password); *source_password = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); } } else if (xmlStrcmp(node->name, XMLSTR("admin-password")) == 0) { if(admin_password) xmlFree(admin_password); admin_password = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); } else if (xmlStrcmp(node->name, XMLSTR("admin-user")) == 0) { if(admin_username) xmlFree(admin_username); admin_username = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); } else if (xmlStrcmp(node->name, XMLSTR("relay-password")) == 0) { if(relay_password) xmlFree(relay_password); relay_password = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); } else if (xmlStrcmp(node->name, XMLSTR("relay-user")) == 0) { if(relay_username) xmlFree(relay_username); relay_username = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); } else if (xmlStrcmp(node->name, XMLSTR("shoutcast-user")) == 0) { if (configuration->shoutcast_user) xmlFree(configuration->shoutcast_user); configuration->shoutcast_user = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); } else if (xmlStrcmp(node->name, XMLSTR("role")) == 0) { auth_t *auth = auth_get_authenticator(configuration, node); auth_stack_push(&new_style, auth); auth_release(auth); } else { __found_bad_tag(configuration, node, BTR_UNKNOWN, NULL); } } while ((node = node->next)); if (admin_password && admin_username) __append_old_style_auth(configuration, &old_style, CONFIG_LEGACY_ADMIN_NAME, AUTH_TYPE_STATIC, admin_username, admin_password, NULL, CONFIG_LEGACY_ADMIN_METHODS, CONFIG_LEGACY_ADMIN_ALLOW_WEB, CONFIG_LEGACY_ADMIN_ALLOW_ADMIN); if (relay_password && relay_username) __append_old_style_auth(configuration, &old_style, CONFIG_LEGACY_RELAY_NAME, AUTH_TYPE_STATIC, relay_username, relay_password, NULL, CONFIG_LEGACY_RELAY_METHODS, CONFIG_LEGACY_RELAY_ALLOW_WEB, CONFIG_LEGACY_RELAY_ALLOW_ADMIN); if (admin_password) xmlFree(admin_password); if (admin_username) xmlFree(admin_username); if (relay_password) xmlFree(relay_password); if (relay_username) xmlFree(relay_username); if (old_style && new_style) { auth_stack_append(old_style, new_style); auth_stack_release(new_style); } else if (new_style) { old_style = new_style; } /* default unauthed anonymous account */ __append_old_style_auth(configuration, &old_style, CONFIG_LEGACY_ANONYMOUS_NAME, AUTH_TYPE_ANONYMOUS, NULL, NULL, NULL, CONFIG_LEGACY_ANONYMOUS_METHODS, CONFIG_LEGACY_ANONYMOUS_ALLOW_WEB, CONFIG_LEGACY_ANONYMOUS_ALLOW_ADMIN); if (!old_style) ICECAST_LOG_ERROR("BAD. old_style=NULL"); if (configuration->authstack) auth_stack_release(configuration->authstack); configuration->authstack = old_style; } static void _parse_oldstyle_directory(xmlDocPtr doc, xmlNodePtr node, ice_config_t *configuration) { yp_directory_t *yp_dir, *current, *last; yp_dir = calloc(1, sizeof(*yp_dir)); if (yp_dir == NULL) { ICECAST_LOG_ERROR("Can not allocate memory for YP directory entry."); return; } do { if (node == NULL) break; if (xmlIsBlankNode(node)) continue; if (xmlStrcmp(node->name, XMLSTR("yp-url")) == 0) { if (yp_dir->url) xmlFree(yp_dir->url); yp_dir->url = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); } else if (xmlStrcmp(node->name, XMLSTR("yp-url-timeout")) == 0) { __read_int(configuration, doc, node, &yp_dir->timeout, CONFIG_RANGE_YP_URL_TIMEOUT); } else if (xmlStrcmp(node->name, XMLSTR("touch-interval")) == 0) { __read_int(configuration, doc, node, &yp_dir->touch_interval, CONFIG_RANGE_YP_TOUCH_INTERVAL); } } while ((node = node->next)); if (yp_dir->url == NULL) return; /* Append YP directory entry to the global list */ current = configuration->yp_directories; last = NULL; while (current) { last = current; current = current->next; } if (last) { last->next = yp_dir; } else { configuration->yp_directories = yp_dir; } } static void _parse_yp_directory(xmlDocPtr doc, xmlNodePtr node, ice_config_t *configuration) { char *url; config_options_t *options; yp_directory_t *yp_dir, *current, *last; url = (char *)xmlGetProp(node, XMLSTR("url")); if (url == NULL) { ICECAST_LOG_ERROR("Missing mandatory attribute 'url' for ."); return; } yp_dir = calloc(1, sizeof(*yp_dir)); if (yp_dir == NULL) { ICECAST_LOG_ERROR("Can not allocate memory for YP directory entry."); return; } yp_dir->url = url; options = config_parse_options(node); for (config_options_t *opt = options; opt; opt = opt->next) { if (!opt->name || !opt->value) { ICECAST_LOG_WARN("Invalid