#include "config.h" #include "stl/stringUtils.h" #include #include "cpucount.h" #include "file/fileUtils.h" #include "services/stdServiceImpl.h" #ifdef _WIN32 #include "win32/rezFuncs.h" #else #include "unixversion.h" #endif #include "banList.h" #include "ripList.h" #include "adminList.h" #include "agentList.h" #include "w3cLog.h" #include "global.h" #include #include #include #include #define LOGNAME "[CONFIG] " using namespace std; using namespace stringUtil; using namespace uniString; config::config() throw() { #define OPTINIT(n) accessor_t(&config::assign_##n, &config::fetch_##n, &config::count_##n, &config::multi_##n, &config::def_##n) m_optMap["configrewrite"] = OPTINIT(configRewrite); m_optMap["log"] = OPTINIT(log); m_optMap["screenlog"] = OPTINIT(screenLog); m_optMap["logfile"] = OPTINIT(logFile); m_optMap["reallogfile"] = OPTINIT(realLogFile); m_optMap["logrotates"] = OPTINIT(logRotates); m_optMap["logarchive"] = OPTINIT(logArchive); m_optMap["rotateinterval"] = OPTINIT(rotateInterval); // networking m_optMap["namelookups"] = OPTINIT(nameLookups); m_optMap["alternateports"] = OPTINIT(alternatePorts); m_optMap["portbase"] = OPTINIT(portBase); m_optMap["publicport"] = OPTINIT(publicPort); m_optMap["portlegacy"] = OPTINIT(portLegacy); m_optMap["autodumptime"] = OPTINIT(autoDumpTime); m_optMap["maxheaderlinesize"] = OPTINIT(maxHeaderLineSize); m_optMap["maxheaderlinecount"] = OPTINIT(maxHeaderLineCount); m_optMap["password"] = OPTINIT(password); m_optMap["adminpassword"] = OPTINIT(adminPassword); m_optMap["sslcertificatefile"] = OPTINIT(sslCertificateFile); m_optMap["sslcertificatekeyfile"] = OPTINIT(sslCertificateKeyFile); m_optMap["buffertype"] = OPTINIT(bufferType); m_optMap["fixedbuffersize"] = OPTINIT(fixedBufferSize); m_optMap["adaptivebuffersize"] = OPTINIT(adaptiveBufferSize); m_optMap["bufferhardlimit"] = OPTINIT(bufferHardLimit); m_optMap["metainterval"] = OPTINIT(metaInterval); m_optMap["adtestfileloop"] = OPTINIT(adTestFileLoop); m_optMap["adtestfile"] = OPTINIT(adTestFile); m_optMap["adtestfile2"] = OPTINIT(adTestFile2); m_optMap["adtestfile3"] = OPTINIT(adTestFile3); m_optMap["adtestfile4"] = OPTINIT(adTestFile4); m_optMap["introfile"] = OPTINIT(introFile); m_optMap["backupfile"] = OPTINIT(backupFile); m_optMap["backuptitle"] = OPTINIT(backupTitle); m_optMap["backuploop"] = OPTINIT(backupLoop); m_optMap["maxspecialfilesize"] = OPTINIT(maxSpecialFileSize); m_optMap["artworkfile"] = OPTINIT(artworkFile); m_optMap["uvoxcipherkey"] = OPTINIT(uvoxCipherKey); //// w3c logging m_optMap["w3cenable"] = OPTINIT(w3cEnable); m_optMap["w3clog"] = OPTINIT(w3cLog); m_optMap["pidfile"] = OPTINIT(pidFile); //// relaying m_optMap["relayreconnecttime"] = OPTINIT(relayReconnectTime); m_optMap["relayconnectretries"] = OPTINIT(relayConnectRetries); m_optMap["maxhttpredirects"] = OPTINIT(maxHTTPRedirects); m_optMap["allowrelay"] = OPTINIT(allowRelay); m_optMap["allowpublicrelay"] = OPTINIT(allowPublicRelay); ///// //// stream configuration m_optMap["streamid"] = OPTINIT(stream_ID); m_optMap["streamauthhash"] = OPTINIT(stream_authHash); m_optMap["streampath"] = OPTINIT(stream_path); m_optMap["streamrelayurl"] = OPTINIT(stream_relayURL); m_optMap["streambackupurl"] = OPTINIT(stream_backupURL); m_optMap["streamminbitrate"] = OPTINIT(stream_minBitrate); m_optMap["streammaxbitrate"] = OPTINIT(stream_maxBitrate); m_optMap["streammaxuser"] = OPTINIT(stream_maxUser); m_optMap["streampassword"] = OPTINIT(stream_password); m_optMap["streamadminpassword"] = OPTINIT(stream_adminPassword); m_optMap["streampublicserver"] = OPTINIT(stream_publicServer); m_optMap["streamallowrelay"] = OPTINIT(stream_allowRelay); m_optMap["streamallowpublicrelay"] = OPTINIT(stream_allowPublicRelay); m_optMap["streamriponly"] = OPTINIT(stream_ripOnly); m_optMap["streamautodumpsourcetime"] = OPTINIT(stream_autoDumpSourceTime); m_optMap["streamautodumptime"] = OPTINIT(stream_autoDumpTime); m_optMap["streamautodumpusers"] = OPTINIT(stream_autoDumpUsers); m_optMap["streamlistenertime"] = OPTINIT(stream_listenerTime); m_optMap["streamsonghistory"] = OPTINIT(stream_songHistory); m_optMap["streamuvoxcipherkey"] = OPTINIT(stream_uvoxCipherKey); m_optMap["streamlogfile"] = OPTINIT(stream_logFile); m_optMap["streamadtestfileloop"] = OPTINIT(stream_adTestFileLoop); m_optMap["streamadtestfile"] = OPTINIT(stream_adTestFile); m_optMap["streamadtestfile2"] = OPTINIT(stream_adTestFile2); m_optMap["streamadtestfile3"] = OPTINIT(stream_adTestFile3); m_optMap["streamadtestfile4"] = OPTINIT(stream_adTestFile4); m_optMap["streamintrofile"] = OPTINIT(stream_introFile); m_optMap["streambackupfile"] = OPTINIT(stream_backupFile); m_optMap["streambackuptitle"] = OPTINIT(stream_backupTitle); m_optMap["streambackuploop"] = OPTINIT(stream_backupLoop); m_optMap["streambanfile"] = OPTINIT(stream_banFile); m_optMap["streamripfile"] = OPTINIT(stream_ripFile); m_optMap["streamagentfile"] = OPTINIT(stream_agentFile); m_optMap["streamartworkfile"] = OPTINIT(stream_artworkFile); m_optMap["streamw3clog"] = OPTINIT(stream_w3cLog); m_optMap["streamhidestats"] = OPTINIT(stream_hideStats); m_optMap["streamredirecturl"] = OPTINIT(stream_redirectUrl); m_optMap["streammovedurl"] = OPTINIT(stream_movedUrl); m_optMap["streamratelimitwait"] = OPTINIT(stream_rateLimitWait); m_optMap["requirestreamconfigs"] = OPTINIT(requireStreamConfigs); m_optMap["userid"] = OPTINIT(userId); m_optMap["licenceid"] = OPTINIT(licenceId); //// cdn m_optMap["cdn"] = OPTINIT(cdn); m_optMap["cdnmaster"] = OPTINIT(cdn_master); m_optMap["cdnslave"] = OPTINIT(cdn_slave); //// flash m_optMap["flashpolicyfile"] = OPTINIT(flashPolicyFile); m_optMap["flashpolicyserverport"] = OPTINIT(flashPolicyServerPort); //// yp m_optMap["yptimeout"] = OPTINIT(ypTimeout); m_optMap["ypaddr"] = OPTINIT(ypAddr); m_optMap["ypport"] = OPTINIT(ypPort); m_optMap["yport"] = OPTINIT(ypPort); m_optMap["yppath"] = OPTINIT(ypPath); m_optMap["ypmaxretries"] = OPTINIT(ypMaxRetries); m_optMap["ypreportinterval"] = OPTINIT(ypReportInterval); m_optMap["ypminreportinterval"] = OPTINIT(ypMinReportInterval); m_optMap["publicserver"] = OPTINIT(publicServer); /// agent m_optMap["agentfile"] = OPTINIT(agentFile); m_optMap["saveagentlistonexit"] = OPTINIT(saveAgentListOnExit); m_optMap["blockemptyuseragent"] = OPTINIT(blockEmptyUserAgent); //// stats m_optMap["hidestats"] = OPTINIT(hideStats); m_optMap["minbitrate"] = OPTINIT(minBitrate); m_optMap["maxbitrate"] = OPTINIT(maxBitrate); m_optMap["maxuser"] = OPTINIT(maxUser); // radionomy metrics m_optMap["admetricsdebug"] = OPTINIT(adMetricsDebug); m_optMap["metricsmaxqueue"] = OPTINIT(metricsMaxQueue); m_optMap["authdebug"] = OPTINIT(authDebug); /// client behaviour m_optMap["listenertime"] = OPTINIT(listenerTime); m_optMap["autodumpusers"] = OPTINIT(autoDumpUsers); m_optMap["srcdns"] = OPTINIT(srcIP); m_optMap["srcip"] = OPTINIT(srcIP); m_optMap["destdns"] = OPTINIT(destIP); m_optMap["destip"] = OPTINIT(destIP); m_optMap["dstip"] = OPTINIT(destIP); m_optMap["publicdns"] = OPTINIT(publicIP); m_optMap["publicip"] = OPTINIT(publicIP); m_optMap["titleformat"] = OPTINIT(titleFormat); m_optMap["urlformat"] = OPTINIT(urlFormat); //// banning m_optMap["banfile"] = OPTINIT(banFile); m_optMap["savebanlistonexit"] = OPTINIT(saveBanListOnExit); //// rip m_optMap["ripfile"] = OPTINIT(ripFile); m_optMap["saveriplistonexit"] = OPTINIT(saveRipListOnExit); m_optMap["riponly"] = OPTINIT(ripOnly); m_optMap["adminfile"] = OPTINIT(adminFile); //// debugging m_optMap["webclientdebug"] = OPTINIT(webClientDebug); m_optMap["yp2debug"] = OPTINIT(yp2Debug); m_optMap["shoutcastsourcedebug"] = OPTINIT(shoutcastSourceDebug); m_optMap["uvox2sourcedebug"] = OPTINIT(uvox2SourceDebug); m_optMap["httpsourcedebug"] = OPTINIT(HTTPSourceDebug); m_optMap["streamdatadebug"] = OPTINIT(streamDataDebug); m_optMap["microserverdebug"] = OPTINIT(microServerDebug); m_optMap["httpstyledebug"] = OPTINIT(httpStyleDebug); m_optMap["shoutcast1clientdebug"] = OPTINIT(shoutcast1ClientDebug); m_optMap["shoutcast2clientdebug"] = OPTINIT(shoutcast2ClientDebug); m_optMap["httpclientdebug"] = OPTINIT(HTTPClientDebug); m_optMap["flvclientdebug"] = OPTINIT(flvClientDebug); m_optMap["m4aclientdebug"] = OPTINIT(m4aClientDebug); m_optMap["relaydebug"] = OPTINIT(relayDebug); m_optMap["relayshoutcastdebug"] = OPTINIT(relayShoutcastDebug); m_optMap["relayuvoxdebug"] = OPTINIT(relayUvoxDebug); m_optMap["statsdebug"] = OPTINIT(statsDebug); m_optMap["threadrunnerdebug"] = OPTINIT(threadRunnerDebug); m_optMap["songhistory"] = OPTINIT(songHistory); m_optMap["showlastsongs"] = OPTINIT(songHistory); ///// misc nonsense m_optMap["unique"] = OPTINIT(unique); m_optMap["include"] = OPTINIT(include); m_optMap["cpucount"] = OPTINIT(cpuCount); m_optMap["clacks"] = OPTINIT(clacks); m_optMap["startinactive"] = OPTINIT(startInactive); m_optMap["ratelimit"] = OPTINIT(rateLimit); m_optMap["ratelimitwait"] = OPTINIT(rateLimitWait); m_optMap["usexff"] = OPTINIT(useXFF); m_optMap["logclients"] = OPTINIT(logClients); m_optMap["admincssfile"] = OPTINIT(adminCSSFile); m_optMap["faviconfile"] = OPTINIT(faviconFile); m_optMap["faviconmimetype"] = OPTINIT(faviconFileMimeType); m_optMap["robotstxtfile"] = OPTINIT(robotstxtFile); m_optMap["redirecturl"] = OPTINIT(redirectUrl); m_optMap["forceshortsends"] = OPTINIT(forceShortSends); m_optMap["adminnowrap"] = OPTINIT(adminNoWrap); // used to control the cache handling m_favIconTime = m_styleCustomHeaderTime = 0; } // return the streamConfig entries that reference relays const vector config::getRelayList() { vector result; streams_t stream_configs; getStreamConfigs(stream_configs); // get all stream configs and then make a vector list of only valid configs // which ensures we're only getting known stream configs unlike prior to // the behaviour with builds 24 which usually gave an extra config than was for (config::streams_t::const_iterator i = stream_configs.begin(); i != stream_configs.end(); ++i) { if ((*i).second.m_relayUrl.isSet()) { result.push_back((*i).second); } } return result; } // return the streamConfig entry that references the backup url asked const vector config::getBackupUrl(const size_t streamID) throw(exception) { vector result; streams_t stream_configs; getStreamConfigs(stream_configs); config::streams_t::const_iterator i = stream_configs.find(streamID); if (i != stream_configs.end()) { if ((*i).second.m_backupUrl.isSet()) { result.push_back((*i).second); } } return result; } unsigned short config::getMetaInterval(const size_t streamID) const throw() { unsigned short metainterval = stream_metaInterval(streamID); if (!read_stream_metaInterval(streamID)) { metainterval = metaInterval(); } // don't allow less than 256 and // due to overflow we don't need // to do an upper check as it'll // have wrapped around for this. return (metainterval < 256 ? 256 : metainterval); } int config::getBackupLoop(const size_t streamID) const throw() { int backuploop = stream_backupLoop(streamID); if (!read_stream_backupLoop(streamID)) { backuploop = backupLoop(); } return backuploop; } int config::getRateLimitWait(const size_t streamID) const throw() { int ratelimitwait = stream_rateLimitWait(streamID); if (!read_stream_rateLimitWait(streamID)) { ratelimitwait = rateLimitWait(); } // just to make sure that we're giving a sane value return (ratelimitwait > 0 ? ratelimitwait : def_rateLimitWait().toInt()); } const int config::isBitrateDisallowed(const size_t streamID, const int bitrate, int &streamMinBitrate, int &streamMaxBitrate) const throw() { int ret = 0; // check that these bitrates are allowed (looking at both max and average values) streamMinBitrate = stream_minBitrate(streamID); if (!read_stream_minBitrate(streamID) || !streamMinBitrate) { streamMinBitrate = minBitrate(); } if ((streamMinBitrate > 0) && (bitrate < streamMinBitrate)) { ret |= 1; } streamMaxBitrate = stream_maxBitrate(streamID); if (!read_stream_maxBitrate(streamID) || !streamMaxBitrate) { streamMaxBitrate = maxBitrate(); } if ((streamMaxBitrate > 0) && (bitrate > streamMaxBitrate)) { ret |= 2; } return ret; } size_t config::getSongHistorySize(const size_t streamID) const throw() { size_t songhistory = stream_songHistory(streamID); if (!read_stream_songHistory(streamID)) { songhistory = songHistory(); } return songhistory; } const int config::getAutoDumpTime(const size_t streamID) const throw() { size_t sid = (!streamID ? streamID : DEFAULT_SOURCE_STREAM); int dumpTime = stream_autoDumpTime(sid); if (!read_stream_autoDumpTime(sid) || !dumpTime) { dumpTime = autoDumpTime(); } return dumpTime; } const int config::getCPUCount() const throw() { int cpu_count = cpuCount(); // check options if (cpu_count < 1) { int maxclients = gOptions.maxUser(); if (maxclients < 1100) cpu_count = (maxclients/350) +1; else { int hwcpus = cpucount(); if (hwcpus < 5) cpu_count = hwcpus; else cpu_count = (((int)::log10 ((double)maxclients) - 2) * 6) - 2; // should be range of 4-12 } } if (cpu_count < 1) cpu_count = 2; // eh? can never be less than 1 return cpu_count; } bool config::setupPasswords(const streams_t &stream_configs) throw(exception) { // now form the per stream versions of passwords if there are any bool passwordError = false; for (streams_t::const_iterator i = stream_configs.begin(); i != stream_configs.end(); ++i) { utf8 tempAdminPassword = (*i).second.m_adminPassword; if (tempAdminPassword.empty()) { tempAdminPassword = gOptions.adminPassword(); } utf8 tempPassword = (*i).second.m_password; if (tempPassword.empty()) { tempPassword = gOptions.password(); } // follow legacy behaviour (just incase) if (tempAdminPassword.empty()) { tempAdminPassword = tempPassword; } else { // otherwise if explicitly set as the same then we abort if (tempAdminPassword == tempPassword) { WLOG(logSectionName() + "Stream " + tos((*i).first) + " should not have matching passwords for `adminpassword' and `password'."); } } if (tempAdminPassword.empty() && tempPassword.empty()) { ELOG(logSectionName() + "Stream " + tos((*i).first) + " does not have any passwords specified."); passwordError = true; } } return passwordError; } config::streamConfig& config::getPerStreamConfig(streamConfig& stream, const size_t sid, const bool useParent) { // tweak the maxuser setting on the stream so it can be made to follow the // per stream setting or for it to revert to the global server limit int tempMaxUser = native_fetch_stream_maxUser(sid); if (!tempMaxUser || (tempMaxUser > gOptions.maxUser())) { if (useParent) { tempMaxUser = 0; } } int tempMaxBitrate = native_fetch_stream_maxBitrate(sid); if (!read_stream_maxBitrate(sid)) { if (useParent) { tempMaxBitrate = gOptions.maxBitrate(); } } int tempMinBitrate = native_fetch_stream_minBitrate(sid); if (!read_stream_minBitrate(sid)) { if (useParent) { tempMinBitrate = gOptions.minBitrate(); } } bool tempAllowRelay = native_fetch_stream_allowRelay(sid); if (!read_stream_allowRelay(sid)) { if (useParent) { tempAllowRelay = gOptions.allowRelay(); } } bool tempAllowPublicRelay = native_fetch_stream_allowPublicRelay(sid); if (!read_stream_allowPublicRelay(sid)) { if (useParent) { tempAllowPublicRelay = gOptions.allowPublicRelay(); } } utf8 tempAdminPassword = native_fetch_stream_adminPassword(sid); if (tempAdminPassword.empty()) { if (useParent) { tempAdminPassword = gOptions.adminPassword(); } } utf8 tempPassword = native_fetch_stream_password(sid); if (tempPassword.empty()) { if (useParent) { tempPassword = gOptions.password(); } } utf8 tempPublicServer = native_fetch_stream_publicServer(sid); if (tempPublicServer.empty()) { if (useParent) { tempPublicServer = gOptions.publicServer(); } } return (stream = streamConfig(native_fetch_stream_ID(sid), native_fetch_stream_authHash(sid), native_fetch_stream_path(sid), native_fetch_stream_relayURL(sid), native_fetch_stream_backupURL(sid), tempMaxUser, tempMaxBitrate, tempMinBitrate, tempAdminPassword, tempPassword, tempPublicServer, tempAllowRelay, tempAllowPublicRelay)); } // return a streamConfig entry for the streamID const bool config::getStreamConfig(streamConfig& stream, const size_t streamID) { stackLock sml(m_lock); map::iterator i = m_stream_ID.find(streamID); if (i != m_stream_ID.end()) { config::getPerStreamConfig(stream, streamID); return true; } return false; } // return all streamConfig entries organized by streamID void config::getStreamConfigs(streams_t& streams, const bool useParent) { stackLock sml(m_lock); for (map::const_iterator i = m_stream_ID.begin(); i != m_stream_ID.end(); ++i) { streamConfig stream; streams[native_fetch_stream_ID((*i).second)] = getPerStreamConfig(stream, (*i).second, useParent); } } // attempt to update an existing stream configuration with the new data // and then attempt to pass it to the currently active stream based on it __uint64 config::updateStreamConfig(config &readConfig, streamConfig update) throw(exception) { if (!update.m_streamID) { return 0; } stackLock sml(m_lock); size_t streamID = update.m_streamID; __uint64 updated = 0; if (native_fetch_stream_authHash(streamID) != update.m_authHash) { native_assign_stream_authHash(streamID, update.m_authHash); updated |= AUTH_HASH; #if defined(_DEBUG) || defined(DEBUG) ELOG("AUTH_HASH"); #endif } if (native_fetch_stream_path(streamID) != update.m_urlPath) { native_assign_stream_path(streamID, update.m_urlPath); updated |= URL_PATH; #if defined(_DEBUG) || defined(DEBUG) ELOG("URL_PATH"); #endif } if (native_fetch_stream_relayURL(streamID) != update.m_relayUrl.url()) { native_assign_stream_relayURL(streamID, update.m_relayUrl.url()); updated |= RELAY_URL; #if defined(_DEBUG) || defined(DEBUG) ELOG("RELAY_URL"); #endif } if (native_fetch_stream_backupURL(streamID) != update.m_backupUrl.url()) { native_assign_stream_backupURL(streamID, update.m_backupUrl.url()); updated |= BACKUP_URL; #if defined(_DEBUG) || defined(DEBUG) ELOG("BACKUP_URL"); #endif } if (native_fetch_stream_maxUser(streamID) != update.m_maxStreamUser) { native_assign_stream_maxUser(streamID, update.m_maxStreamUser); updated |= MAX_USER; #if defined(_DEBUG) || defined(DEBUG) ELOG("MAX_USER"); #endif } if (native_fetch_stream_maxBitrate(streamID) != update.m_maxStreamBitrate) { native_assign_stream_maxBitrate(streamID, update.m_maxStreamBitrate); updated |= MAX_BITRATE; #if defined(_DEBUG) || defined(DEBUG) ELOG("MAX_BITRATE"); #endif } if (native_fetch_stream_minBitrate(streamID) != update.m_minStreamBitrate) { native_assign_stream_minBitrate(streamID, update.m_minStreamBitrate); updated |= MIN_BITRATE; #if defined(_DEBUG) || defined(DEBUG) ELOG("MIN_BITRATE"); #endif } utf8 publicServer = readConfig.stream_publicServer(streamID); if (native_fetch_stream_publicServer(streamID) != publicServer) { if (!readConfig.read_stream_publicServer(streamID)) { publicServer = gOptions.publicServer(); } else { updated |= PUBLIC_SRV; #if defined(_DEBUG) || defined(DEBUG) ELOG("PUBLIC_SRV"); #endif } native_assign_stream_publicServer(streamID, publicServer); } bool allowRelay = readConfig.stream_allowRelay(streamID); if (native_fetch_stream_allowRelay(streamID) != allowRelay) { if (!readConfig.read_stream_allowRelay(streamID)) { allowRelay = gOptions.allowRelay(); } native_assign_stream_allowRelay(streamID, allowRelay); updated |= ALLOW_RELAY; #if defined(_DEBUG) || defined(DEBUG) ELOG("ALLOW_RELAY"); #endif } bool allowPublicRelay = readConfig.stream_allowPublicRelay(streamID); if (native_fetch_stream_allowPublicRelay(streamID) != allowPublicRelay) { if (!readConfig.read_stream_allowPublicRelay(streamID)) { allowPublicRelay = gOptions.allowPublicRelay(); } native_assign_stream_allowPublicRelay(streamID, allowPublicRelay); updated |= ALLOW_PUBLIC_RELAY; #if defined(_DEBUG) || defined(DEBUG) ELOG("ALLOW_PUBLIC_RELAY"); #endif } bool ripOnly = readConfig.stream_ripOnly(streamID); if (native_fetch_stream_ripOnly(streamID) != ripOnly) { if (!readConfig.read_stream_ripOnly(streamID)) { ripOnly = gOptions.ripOnly(); } native_assign_stream_ripOnly(streamID, ripOnly); updated |= RIP_ONLY; #if defined(_DEBUG) || defined(DEBUG) ELOG("RIP_ONLY"); #endif } int autoDumpTime = readConfig.stream_autoDumpTime(streamID); if (native_fetch_stream_autoDumpTime(streamID) != autoDumpTime) { if (!readConfig.read_stream_autoDumpTime(streamID)) { autoDumpTime = gOptions.autoDumpTime(); } native_assign_stream_autoDumpTime(streamID, autoDumpTime); updated |= DUMP_TIME; #if defined(_DEBUG) || defined(DEBUG) ELOG("DUMP_TIME"); #endif } bool autoDumpUsers = readConfig.stream_autoDumpUsers(streamID); if (native_fetch_stream_autoDumpUsers(streamID) != autoDumpUsers) { if (!readConfig.read_stream_autoDumpUsers(streamID)) { autoDumpUsers = gOptions.autoDumpUsers(); } native_assign_stream_autoDumpUsers(streamID, autoDumpUsers); updated |= DUMP_USER; #if defined(_DEBUG) || defined(DEBUG) ELOG("DUMP_USER"); #endif } size_t listenerTime = readConfig.stream_listenerTime(streamID); if (native_fetch_stream_listenerTime(streamID) != listenerTime) { if (!readConfig.read_stream_listenerTime(streamID)) { listenerTime = gOptions.listenerTime(); } native_assign_stream_listenerTime(streamID, listenerTime); updated |= LIST_TIME; #if defined(_DEBUG) || defined(DEBUG) ELOG("LIST_TIME"); #endif } int songHistory = readConfig.stream_songHistory(streamID); if (native_fetch_stream_songHistory(streamID) != songHistory) { if (!readConfig.read_stream_songHistory(streamID)) { songHistory = gOptions.songHistory(); } native_assign_stream_songHistory(streamID, songHistory); updated |= SONG_HIST; #if defined(_DEBUG) || defined(DEBUG) ELOG("SONG_HIST"); #endif } utf8 uvoxCipherKey = readConfig.stream_uvoxCipherKey(streamID); if (native_fetch_stream_uvoxCipherKey(streamID) != uvoxCipherKey) { if (!readConfig.read_stream_uvoxCipherKey(streamID)) { uvoxCipherKey = gOptions.uvoxCipherKey(); } native_assign_stream_uvoxCipherKey(streamID, uvoxCipherKey); updated |= CIPHER_KEY; #if defined(_DEBUG) || defined(DEBUG) ELOG("CIPHER_KEY"); #endif } // passwords are handled slightly differently as clearing of the // password will lead to an invalid scenario so we check and block utf8 streamPassword = readConfig.stream_password(streamID); if (native_fetch_stream_password(streamID) != streamPassword) { if (!readConfig.read_stream_password(streamID)) { // clear what was being previously stored if possible m_stream_password[streamID].clear(); updated |= SOURCE_PWD; } else { // if empty then set back to the master password if(streamPassword.empty()) { streamPassword = readConfig.password(); } // if empty then set back to the master admin password if(streamPassword.empty()) { ELOG(LOGNAME "'password' cannot be set to empty. Not applying this change to stream config# " + tos(streamID)); } else { native_assign_stream_password(streamID, streamPassword); updated |= SOURCE_PWD; } } #if defined(_DEBUG) || defined(DEBUG) if (updated & SOURCE_PWD) { ELOG("SOURCE_PWD"); } #endif } utf8 adminPassword = readConfig.stream_adminPassword(streamID); if (native_fetch_stream_adminPassword(streamID) != adminPassword) { if (!readConfig.read_stream_adminPassword(streamID)) { // clear what was being previously stored if possible m_stream_adminPassword[streamID].clear(); updated |= ADMIN_PWD; } // if empty then set back to the master admin password else if (adminPassword.empty()) { ELOG(LOGNAME "'adminpassword' cannot be set to empty. Not applying this change to stream config# " + tos(streamID)); } else { native_assign_stream_adminPassword(streamID, adminPassword); updated |= ADMIN_PWD; } #if defined(_DEBUG) || defined(DEBUG) if (updated & ADMIN_PWD) { ELOG("ADMIN_PWD"); } #endif } utf8 movedUrl = readConfig.stream_movedUrl(streamID); if (native_fetch_stream_movedUrl(streamID) != movedUrl) { native_assign_stream_movedUrl(streamID, movedUrl); updated |= MOVED_URL; #if defined(_DEBUG) || defined(DEBUG) ELOG("MOVED_URL"); #endif } utf8 artworkFile = readConfig.stream_artworkFile(streamID); if (native_fetch_stream_artworkFile(streamID) != artworkFile) { if (!readConfig.read_stream_artworkFile(streamID)) { artworkFile = gOptions.artworkFile(); } native_assign_stream_artworkFile(streamID, artworkFile); updated |= ARTWORK_FILE; #if defined(_DEBUG) || defined(DEBUG) ELOG("ARTWORK_FILE"); #endif } utf8 adTestFile = readConfig.stream_adTestFile(streamID); if (native_fetch_stream_adTestFile(streamID) != adTestFile) { if (!readConfig.read_stream_adTestFile(streamID)) { adTestFile = gOptions.adTestFile(); } native_assign_stream_adTestFile(streamID, adTestFile); updated |= AD_TEST_FILE; #if defined(_DEBUG) || defined(DEBUG) ELOG("AD_TEST_FILE"); #endif } utf8 adTestFile2 = readConfig.stream_adTestFile2(streamID); if (native_fetch_stream_adTestFile2(streamID) != adTestFile2) { if (!readConfig.read_stream_adTestFile2(streamID)) { adTestFile2 = gOptions.adTestFile2(); } native_assign_stream_adTestFile2(streamID, adTestFile2); updated |= AD_TEST_FILE_2; #if defined(_DEBUG) || defined(DEBUG) ELOG("AD_TEST_FILE_2"); #endif } utf8 adTestFile3 = readConfig.stream_adTestFile3(streamID); if (native_fetch_stream_adTestFile3(streamID) != adTestFile3) { if (!readConfig.read_stream_adTestFile3(streamID)) { adTestFile3 = gOptions.adTestFile3(); } native_assign_stream_adTestFile3(streamID, adTestFile3); updated |= AD_TEST_FILE_3; #if defined(_DEBUG) || defined(DEBUG) ELOG("AD_TEST_FILE_3"); #endif } utf8 adTestFile4 = readConfig.stream_adTestFile4(streamID); if (native_fetch_stream_adTestFile4(streamID) != adTestFile4) { if (!readConfig.read_stream_adTestFile4(streamID)) { adTestFile4 = gOptions.adTestFile4(); } native_assign_stream_adTestFile4(streamID, adTestFile4); updated |= AD_TEST_FILE_4; #if defined(_DEBUG) || defined(DEBUG) ELOG("AD_TEST_FILE_4"); #endif } int adTestFileLoop = readConfig.stream_adTestFileLoop(streamID); if (native_fetch_stream_adTestFileLoop(streamID) != adTestFileLoop) { if (!readConfig.read_stream_adTestFileLoop(streamID)) { adTestFileLoop = gOptions.adTestFileLoop(); } native_assign_stream_adTestFileLoop(streamID, adTestFileLoop); updated |= AD_TEST_FILE_LOOP; #if defined(_DEBUG) || defined(DEBUG) ELOG("AD_TEST_FILE_LOOP"); #endif } utf8 introFile = readConfig.stream_introFile(streamID); if (native_fetch_stream_introFile(streamID) != introFile) { if (!readConfig.read_stream_introFile(streamID)) { introFile = gOptions.introFile(); } native_assign_stream_introFile(streamID, introFile); updated |= INTRO_FILE; #if defined(_DEBUG) || defined(DEBUG) ELOG("INTRO_FILE"); #endif } utf8 backupFile = readConfig.stream_backupFile(streamID); if (native_fetch_stream_backupFile(streamID) != backupFile) { if (!readConfig.read_stream_backupFile(streamID)) { backupFile = gOptions.backupFile(); } native_assign_stream_backupFile(streamID, backupFile); updated |= BACKUP_FILE; #if defined(_DEBUG) || defined(DEBUG) ELOG("BACKUP_FILE"); #endif } utf8 backupTitle = readConfig.stream_backupTitle(streamID); if (native_fetch_stream_backupTitle(streamID) != backupTitle) { if (!readConfig.read_stream_backupTitle(streamID)) { backupTitle = gOptions.backupTitle(); } native_assign_stream_backupTitle(streamID, backupTitle); updated |= BACKUP_TITLE; #if defined(_DEBUG) || defined(DEBUG) ELOG("BACKUP_TITLE"); #endif } utf8 banFile = readConfig.stream_banFile(streamID); if (native_fetch_stream_banFile(streamID) != banFile) { if (!readConfig.read_stream_banFile(streamID)) { m_stream_banFile[streamID].clear(); gOptions.unread_stream_banFile(streamID); // unload the IPs from the list g_banList.remove("", 0, streamID, true); } else { // load the IPs from the list size_t sID = (!banFile.empty() ? streamID : 0); g_banList.load(banFile,sID); } native_assign_stream_banFile(streamID, banFile); updated |= BAN_FILE; #if defined(_DEBUG) || defined(DEBUG) ELOG("BAN_FILE"); #endif } utf8 ripFile = readConfig.stream_ripFile(streamID); if (native_fetch_stream_ripFile(streamID) != ripFile) { if (!readConfig.read_stream_ripFile(streamID)) { m_stream_ripFile[streamID].clear(); gOptions.unread_stream_ripFile(streamID); // unload the IPs from the list g_ripList.remove("",streamID,true); } else { // load the IPs from the list size_t sID = (!ripFile.empty() ? streamID : 0); g_ripList.load(ripFile,sID); } native_assign_stream_ripFile(streamID, ripFile); updated |= RIP_FILE; #if defined(_DEBUG) || defined(DEBUG) ELOG("RIP_FILE"); #endif } utf8 agentFile = readConfig.stream_agentFile(streamID); if (native_fetch_stream_agentFile(streamID) != agentFile) { if (!readConfig.read_stream_agentFile(streamID)) { m_stream_agentFile[streamID].clear(); gOptions.unread_stream_agentFile(streamID); // unload the agents from the list g_agentList.remove("", streamID, true); } else { // load the agents from the list size_t sID = (!agentFile.empty() ? streamID : 0); g_agentList.load(agentFile, sID); } native_assign_stream_agentFile(streamID, agentFile); updated |= AGENT_FILE; #if defined(_DEBUG) || defined(DEBUG) ELOG("AGENT_FILE"); #endif } utf8 w3cLog = readConfig.stream_w3cLog(streamID); if (native_fetch_stream_w3cLog(streamID) != w3cLog) { if (!readConfig.read_stream_w3cLog(streamID)) { m_stream_w3cLog[streamID].clear(); gOptions.unread_stream_w3cLog(streamID); w3cLog::close(streamID); } else { // could have just changed so update as needed utf8 oldw3cLog = native_fetch_stream_w3cLog(streamID); if (!oldw3cLog.empty()) { w3cLog::close(streamID); } size_t sID = (!w3cLog.empty() ? streamID : 0); if (gOptions.w3cEnable()) { w3cLog::open(w3cLog, sID); } } native_assign_stream_w3cLog(streamID, w3cLog); updated |= W3C_FILE; #if defined(_DEBUG) || defined(DEBUG) ELOG("W3C_FILE"); #endif } utf8 hideStats = readConfig.stream_hideStats(streamID); if (native_fetch_stream_hideStats(streamID) != hideStats) { if (!readConfig.read_stream_hideStats(streamID)) { hideStats = gOptions.hideStats(); } native_assign_stream_hideStats(streamID, hideStats); updated |= HIDE_STATS; #if defined(_DEBUG) || defined(DEBUG) ELOG("HIDE_STATS"); #endif } int cdnMaster = readConfig.cdn_master(streamID); if (native_fetch_cdn_master(streamID) != cdnMaster) { if (!readConfig.read_cdn_master(streamID)) { cdnMaster = -1; } native_assign_cdn_master(streamID, cdnMaster); updated |= CDN_MASTER; #if defined(_DEBUG) || defined(DEBUG) ELOG("CDN_MASTER"); #endif } int cdnSlave = readConfig.cdn_slave(streamID); if (native_fetch_cdn_slave(streamID) != cdnSlave) { if (!readConfig.read_cdn_slave(streamID)) { cdnSlave = -1; } native_assign_cdn_slave(streamID, cdnSlave); updated |= CDN_SLAVE; #if defined(_DEBUG) || defined(DEBUG) ELOG("CDN_SLAVE"); #endif } int backupLoop = readConfig.stream_backupLoop(streamID); if (native_fetch_stream_backupLoop(streamID) != backupLoop) { if (!readConfig.read_stream_backupLoop(streamID)) { backupLoop = gOptions.backupLoop(); } native_assign_stream_backupLoop(streamID, backupLoop); updated |= DUMP_USER; #if defined(_DEBUG) || defined(DEBUG) ELOG("BACKUP_LOOP"); #endif } int rateLimitWait = readConfig.stream_rateLimitWait(streamID); if (native_fetch_stream_rateLimitWait(streamID) != rateLimitWait) { if (!readConfig.read_stream_rateLimitWait(streamID)) { rateLimitWait = gOptions.rateLimitWait(); } native_assign_stream_rateLimitWait(streamID, rateLimitWait); updated |= RATE_LIMIT_WAIT; #if defined(_DEBUG) || defined(DEBUG) ELOG("RATE_LIMIT_WAIT"); #endif } int metainterval = readConfig.stream_metaInterval(streamID); if (native_fetch_stream_metaInterval(streamID) != metainterval) { if (!readConfig.read_stream_metaInterval(streamID)) { metainterval = gOptions.metaInterval(); } native_assign_stream_metaInterval(streamID, metainterval); updated |= METAINTERVAL; #if defined(_DEBUG) || defined(DEBUG) ELOG("METAINTERVAL"); #endif } if (updated) { ILOG(LOGNAME "Updates applied to stream config# " + tos(streamID) + " [code: 0x" + tohex(updated) + "]"); } else { ILOG(LOGNAME "No updates required for stream config# " + tos(streamID)); } return updated; } void config::addStreamConfig(config &readConfig, streamConfig add) throw(exception) { if (!add.m_streamID) { return; } stackLock sml(m_lock); size_t streamID = add.m_streamID; native_assign_stream_ID(streamID, streamID); native_assign_stream_adminPassword(streamID, add.m_adminPassword); native_assign_stream_allowPublicRelay(streamID, add.m_allowPublicRelay); native_assign_stream_allowRelay(streamID, add.m_allowRelay); native_assign_stream_authHash(streamID, add.m_authHash); native_assign_stream_password(streamID, add.m_password); native_assign_stream_publicServer(streamID, add.m_publicServer); native_assign_stream_maxBitrate(streamID, add.m_maxStreamBitrate); native_assign_stream_minBitrate(streamID, add.m_minStreamBitrate); native_assign_stream_maxUser(streamID, add.m_maxStreamUser); native_assign_stream_relayURL(streamID, add.m_relayUrl.url()); native_assign_stream_backupURL(streamID, add.m_backupUrl.url()); native_assign_stream_path(streamID, add.m_urlPath); if (gOptions.w3cEnable()) { utf8 w3cLog = readConfig.stream_w3cLog(streamID); if (readConfig.read_stream_w3cLog(streamID)) { if (gOptions.w3cEnable()) { w3cLog::open(w3cLog, streamID); } } } if (gOptions.saveBanListOnExit()) { if (readConfig.read_stream_banFile(streamID) && !gOptions.stream_banFile(streamID).empty()) { g_banList.save(gOptions.stream_banFile(streamID),streamID); } } if (gOptions.saveRipListOnExit()) { if (readConfig.read_stream_ripFile(streamID) && !gOptions.stream_ripFile(streamID).empty()) { g_ripList.save(gOptions.stream_ripFile(streamID),streamID); } } if (gOptions.saveAgentListOnExit()) { if (readConfig.read_stream_agentFile(streamID) && !gOptions.stream_agentFile(streamID).empty()) { g_agentList.save(gOptions.stream_agentFile(streamID),streamID); } } ILOG(LOGNAME "Added stream config# " + tos(streamID)); } void config::removeStreamConfig(streamConfig remove) throw(exception) { if (!remove.m_streamID) { return; } stackLock sml(m_lock); size_t streamID = remove.m_streamID; // clean up the per-stream files if (gOptions.w3cEnable()) { w3cLog::close(streamID); } if (gOptions.saveBanListOnExit()) { if (gOptions.read_stream_banFile(streamID) && !gOptions.stream_banFile(streamID).empty()) { g_banList.save(gOptions.stream_banFile(streamID),streamID); } } if (gOptions.saveRipListOnExit()) { if (gOptions.read_stream_ripFile(streamID) && !gOptions.stream_ripFile(streamID).empty()) { g_ripList.save(gOptions.stream_ripFile(streamID),streamID); } } if (gOptions.saveAgentListOnExit()) { if (gOptions.read_stream_agentFile(streamID) && !gOptions.stream_agentFile(streamID).empty()) { g_agentList.save(gOptions.stream_agentFile(streamID),streamID); } } // now clear out where possible the per-stream values gOptions.unread_stream_ID(streamID); gOptions.unread_stream_adminPassword(streamID); gOptions.unread_stream_agentFile(streamID); gOptions.unread_stream_allowPublicRelay(streamID); gOptions.unread_stream_allowRelay(streamID); gOptions.unread_stream_authHash(streamID); gOptions.unread_stream_autoDumpTime(streamID); gOptions.unread_stream_autoDumpUsers(streamID); gOptions.unread_stream_backupFile(streamID); gOptions.unread_stream_backupTitle(streamID); gOptions.unread_stream_backupLoop(streamID); gOptions.unread_stream_banFile(streamID); gOptions.unread_stream_introFile(streamID); gOptions.unread_stream_listenerTime(streamID); gOptions.unread_stream_maxBitrate(streamID); gOptions.unread_stream_minBitrate(streamID); gOptions.unread_stream_maxUser(streamID); gOptions.unread_stream_password(streamID); gOptions.unread_stream_path(streamID); gOptions.unread_stream_publicServer(streamID); gOptions.unread_stream_relayURL(streamID); gOptions.unread_stream_backupURL(streamID); gOptions.unread_stream_ripFile(streamID); gOptions.unread_stream_ripOnly(streamID); gOptions.unread_stream_agentFile(streamID); gOptions.unread_stream_songHistory(streamID); gOptions.unread_stream_uvoxCipherKey(streamID); gOptions.unread_stream_w3cLog(streamID); gOptions.unread_cdn_master(streamID); gOptions.unread_cdn_slave(streamID); map::iterator i = m_stream_ID.find(streamID); if (i != m_stream_ID.end()) { m_stream_ID.erase(i); } ILOG(LOGNAME "Removed stream config# " + tos(streamID)); } config::~config() throw() { } static void write_option(const utf8 &name, const utf8 &value, FILE *f, const uniFile::filenameType &fn, bool &written) throw(exception) { const char *delimiter = "="; const size_t delimiter_size = 1; size_t name_size = name.size(); size_t value_size = value.size(); size_t eol_size = eol().size(); if (written && (::fwrite(eol().c_str(), 1, eol_size, f) != eol_size)) { throw runtime_error("I/O error writing to file " + fn.hideAsString()); } if (::fwrite(name.c_str(), 1, name_size, f) != name_size) { throw runtime_error("I/O error writing to file " + fn.hideAsString()); } if (::fwrite(delimiter, 1, delimiter_size, f) != delimiter_size) { throw runtime_error("I/O error writing to file " + fn.hideAsString()); } if (::fwrite(value.c_str(), 1, value_size, f) != value_size) { throw runtime_error("I/O error writing to file " + fn.hideAsString()); } written = true; } std::string config::logSectionName() { return LOGNAME; } bool config::rewriteConfigurationFile(bool minimal, bool messages, bool setup) const throw(exception) { if (!cdn().empty()) { WLOG(LOGNAME "CDN mode prevents re-writing of the configuration file"); return false; } if (messages) { ILOG(LOGNAME "Rewriting config file"); } stackLock sml(m_lock); // add any config options to be ignored when re-writing set ignore_set; ignore_set.insert("streamid"); ignore_set.insert("include"); ignore_set.insert("dstip"); ignore_set.insert("yport"); ignore_set.insert("showlastsongs"); ignore_set.insert("streambitrate"); ignore_set.insert("relayserver"); ignore_set.insert("relayport"); ignore_set.insert("reallogfile"); ignore_set.insert("clacks"); if (flashPolicyServerPort() == -1) { ignore_set.insert("flashpolicyserverport"); } if (portLegacy() == -1) { ignore_set.insert("portlegacy"); } if (publicPort() == -1) { ignore_set.insert("publicport"); } bool saved = true; FILE *f = 0; try { uniFile::filenameType fn = _confFile(); // create a backup of the current config file and then use that if (fileUtil::fileExists(fn)) { uniFile::filenameType fn_backup(_confFile() + ".backup"); uniFile::unlink(fn_backup); #ifdef _WIN32 if (!::MoveFileW(fn.toWString().c_str(), fn_backup.toWString().c_str())) { throw runtime_error("Cannot create backup of the current config file `" + fn.hideAsString() + "' (" + errMessage().hideAsString() + ")"); } #else if (::rename(fn.hideAsString().c_str(), fn_backup.hideAsString().c_str()) < 0) { throw runtime_error("Cannot create backup of the current config file `" + fn.hideAsString() + "' (" + errMessage().hideAsString() + ")"); } #endif } f = uniFile::fopen(fn,"wb"); if (!f) { throw runtime_error("Cannot open config file `" + fn.hideAsString() + "' for writing (" + errMessage().hideAsString() + ")"); } map deferredOptions = m_deferredOptions; bool written = false; for (optMap_t::const_iterator i = m_optMap.begin(); i != m_optMap.end(); ++i) { const utf8 &optName = (*i).first; const accessor_t &a = (*i).second; if (ignore_set.find(optName) != ignore_set.end()) { continue; } map::iterator deferred_i = deferredOptions.find(optName); if (deferred_i != deferredOptions.end()) { if (!minimal || (!((*deferred_i).second == (this->*a.m_defaultFunc)()))) { write_option(optName,(*deferred_i).second, f, fn, written); } deferredOptions.erase(deferred_i); } else { if ((this->*a.m_multiFunc)()) { const size_t num = (this->*a.m_countFunc)(); for (size_t x = 0; x < num; ++x) { size_t index = 0; if (!minimal || (!((this->*a.m_fetchFunc)(x, &index) == (this->*a.m_defaultFunc)()))) { write_option(optName + "_" + tos(index), (this->*a.m_fetchFunc)(index, 0), f, fn, written); } } } else { if (!minimal || (!((this->*a.m_fetchFunc)(1, 0) == (this->*a.m_defaultFunc)()))) { write_option(optName,(this->*a.m_fetchFunc)(1, 0), f, fn, written); } } } } // remaining deferred options for (map::const_iterator i = deferredOptions.begin(); i != deferredOptions.end(); ++i) { if (ignore_set.find((*i).first) != ignore_set.end()) { continue; } optMap_t::const_iterator m = m_optMap.find((*i).first); const accessor_t &a = (*m).second; if (!minimal || (!((*i).second == (this->*a.m_defaultFunc)()))) { write_option((*i).first, (*i).second, f, fn, written); } } deferredOptions.clear(); ::fclose(f); } catch(const exception &ex) { saved = false; ELOG(utf8(setup ? "[SETUP] " : LOGNAME) + ex.what()); if (f) { ::fclose(f); } } return saved; } utf8 config::dumpConfigFile() throw() { utf8 streams, general, debug; stackLock sml(m_lock); // add any config options to be ignored when re-writing set ignore_set; ignore_set.insert("streamid"); ignore_set.insert("include"); ignore_set.insert("dstip"); ignore_set.insert("yport"); ignore_set.insert("showlastsongs"); ignore_set.insert("streambitrate"); ignore_set.insert("relayserver"); ignore_set.insert("relayport"); ignore_set.insert("reallogfile"); if (flashPolicyServerPort() == -1) { ignore_set.insert("flashpolicyserverport"); } if (portLegacy() == -1) { ignore_set.insert("portlegacy"); } if (publicPort() == -1) { ignore_set.insert("publicport"); } for (optMap_t::const_iterator i = m_optMap.begin(); i != m_optMap.end(); ++i) { const utf8 &optName = (*i).first; const accessor_t &a = (*i).second; if (ignore_set.find(optName) != ignore_set.end()) { continue; } if ((this->*a.m_multiFunc)()) { size_t num = (this->*a.m_countFunc)(); for (size_t x = 0; x < num; ++x) { size_t index = 0; if (!((this->*a.m_fetchFunc)(x, &index) == (this->*a.m_defaultFunc)())) { if (optName.find((utf8)"stream") == 0) { streams += optName + "_" + tos(index) + "=" + (this->*a.m_fetchFunc)(index, 0) + "
"; } else if (optName.find((utf8)"debug") != utf8::npos) { debug += optName + "_" + tos(index) + "=" + (this->*a.m_fetchFunc)(index, 0) + "
"; } else { general += optName + "_" + tos(index) + "=" + (this->*a.m_fetchFunc)(index, 0) + "
"; } } } } else { if (!((this->*a.m_fetchFunc)(1, 0) == (this->*a.m_defaultFunc)())) { if (optName.find((utf8)"debug") != utf8::npos) { debug += optName + "=" + (this->*a.m_fetchFunc)(1, 0) + "
"; } else { general += optName + "=" + (this->*a.m_fetchFunc)(1, 0) + "
"; } } } } return "
" + (!streams.empty() ? "
Stream Settings:

" : (utf8)"") + streams + "
" + (!general.empty() ? "
General Settings:

" : "") + general + "
" + (!debug.empty() ? "
Debugging Settings:

" : "") + debug + "
"; } int config::promptConfigFile() throw() { if (!sDaemon) { #ifdef _WIN32 vector fileList = fileUtil::directoryFileList(gStartupDirectory.toWString() + L"*.ini", L"", true, true); vector fileListConf = fileUtil::directoryFileList(gStartupDirectory.toWString() + L"*.conf", L"", true, true); #else vector fileList = fileUtil::directoryFileList(gStartupDirectory.hideAsString() + "*.ini", ""); vector fileListConf = fileUtil::directoryFileList(gStartupDirectory.hideAsString() + "*.conf", ""); #endif if (!fileList.empty()) { #ifdef _WIN32 // exclude desktop.ini on windows builds as that can cause other issues for (vector::const_iterator i = fileList.begin(); i != fileList.end(); ++i) { if ((*i) == L".\\desktop.ini") { fileList.erase(i); break; } } #endif fileList.insert(fileList.end(), fileListConf.begin(), fileListConf.end()); } else { fileList = fileListConf; } if (!fileList.empty()) { ILOG(LOGNAME "Choose one of the listed config file(s) to load or"); ILOG(LOGNAME "enter `s' to enable the setup mode for the DNAS or"); #if CONFIG_BUILDER ILOG(LOGNAME "enter `b' to enable the builder mode for the DNAS or"); #endif ILOG(LOGNAME "enter `x' to close this instance of the DNAS:"); ILOG(LOGNAME "Note: Press `Enter' after choosing the config file."); if (fileList.size() > 10) { ILOG(LOGNAME " Only the first 10 config files detected will be shown."); } int option = 0; if (fileList.size() > 10) { fileList.resize(10); } #ifdef _WIN32 for (vector::const_iterator i = fileList.begin(); i != fileList.end(); ++i, option++) #else for (vector::const_iterator i = fileList.begin(); i != fileList.end(); ++i, option++) #endif { #ifdef _WIN32 utf32 u32file(*i); utf8 u8f(u32file.toUtf8().substr(1 + u32file.rfind(utf32("\\")))); ILOG(LOGNAME " [" + tos(option) + "] : " + u8f + (fileUtil::getSuffix(*i) == L"ini" ? " [1.x config file]" : "")); #else ILOG(LOGNAME " [" + tos(option) + "] : " + (*i).substr(1 + (*i).rfind("/")) + (fileUtil::getSuffix(*i) == "ini" ? " [1.x config file]" : "")); #endif } string input; cin >> input; while ((!input.empty() && (input[0] != 'x') && (input[0] != 's') && (!isdigit(input[0]) || !((size_t)(input[0] - '0') < fileList.size())))) { ELOG(LOGNAME "You have entered an invalid option. Please enter a number matching the config file required."); input.clear(); cin >> input; } if (input[0] == 'x') { return -2; } if (input[0] == 's') { ILOG(LOGNAME "Entering setup mode. Open 127.0.0.1:8000/setup in a"); ILOG(LOGNAME "browser on the same machine the DNAS is started on."); return 2; } #ifdef CONFIG_BUILDER if (input[0] == 'b') { ILOG(LOGNAME "Entering builder mode. Open 127.0.0.1:8000/builder in"); ILOG(LOGNAME "a browser on the same machine the DNAS is started on."); return 2; } #endif if (!input.empty() && isdigit(input[0])) { size_t index = input[0] - '0'; // clamped to 0 - 9 (fileList can be larger but input is only 0-9) if (index < fileList.size()) { #ifdef _WIN32 utf32 u32file(fileList[index]); utf8 u8f(u32file.toUtf8().substr(2)); return load(u8f); #else return load(fileList[index]); #endif } } } else { return -1; } } #ifndef _WIN32 else { // if running as a daemon then make checks just for the default // if we've not had a configuration file specified to be used. vector fileList = fileUtil::directoryFileList(gStartupDirectory.hideAsString() + "sc_serv.ini", ""); vector fileListConf = fileUtil::directoryFileList(gStartupDirectory.hideAsString() + "sc_serv.conf", ""); if (!fileList.empty()) { fileList.insert(fileList.end(), fileListConf.begin(), fileListConf.end()); } else { fileList = fileListConf; } if (!fileList.empty()) { load(fileList[0]); } } #endif return 0; } bool config::load(const uniFile::filenameType &filename, bool load) throw() { stackLock sml(m_lock); // set the option that returns the name of the config file m_confFile[DEFAULT_CLIENT_STREAM_ID] = filename; // only need to set the filename when creating a new file if (!load) { return true; } // check for 'setup' and abort config file loading #ifdef CONFIG_BUILDER if ((filename == "builder") || (filename == "setup")) #else if (filename == "setup") #endif { return false; } // setup the legacy relay config options to cope with reloads, etc m_legacyRelayServer.clear(); m_legacyRelayPort = ":80"; bool result = _load(filename, (utf8)"", true); // if the server hasn't been read then skip over this as it's // likely there wasn't any legacy relay config options read if (!m_legacyRelayServer.empty() && !(m_legacyRelayServer == "http://")) { // make sure that the old relay url is added by force setting streamid=1 optMap_t::const_iterator i = m_optMap.find("streamid"); (this->*((*i).second.m_assignFunc))(tos(DEFAULT_CLIENT_STREAM_ID), DEFAULT_CLIENT_STREAM_ID); utf8 legacyRelay = m_legacyRelayServer + m_legacyRelayPort; i = m_optMap.find("streamrelayurl"); (this->*((*i).second.m_assignFunc))(legacyRelay, DEFAULT_CLIENT_STREAM_ID); } // use this to map the v2.0.0 value to that from 2.0.1+ if (hideStats() == "1") { optMap_t::const_iterator i = m_optMap.find("hidestats"); (this->*((*i).second.m_assignFunc))("stats", 0); } // detect a failed attempt to load a config file and ensure we follow defaults on fail if (m_log.empty()) { m_log[DEFAULT_CLIENT_STREAM_ID] = true; } if (result == true) { if (m_logFile.empty()) { m_log[DEFAULT_CLIENT_STREAM_ID] = true; } else if (m_logFile[DEFAULT_CLIENT_STREAM_ID].size() < 1) { m_log[DEFAULT_CLIENT_STREAM_ID] = false; } else if (compareStringsWithoutCase(m_logFile[0], uniFile::filenameType("none"))) { m_log[DEFAULT_CLIENT_STREAM_ID] = false; } else if (compareStringsWithoutCase(m_logFile[0], uniFile::filenameType("/dev/null"))) { m_log[DEFAULT_CLIENT_STREAM_ID] = false; } } return result; } // load options from a config file. Generally only done at startup. // Config file should be in utf8 format const bool config::_load(const uniFile::filenameType &filename, const utf8& uniqueStr, const bool parent = false) throw() { const utf8 unique_matching_token("$"); utf8 unique(unique_matching_token); if (uniqueStr.empty()) { unique = unique_matching_token; } else { unique = uniqueStr; } int l = 0; // line counter bool loaded = true; // open it. Use the deferred messages since this happens at startup FILE *conf = uniFile::fopen(filename, "rb"); if (!conf) { loaded = false; m_deferredWarnLogMessages.push_back(LOGNAME "Could not find `" + fileUtil::getFullFilePath(filename) + (parent == true ? "' - looking for config file to load..." : "'")); goto no_read_conf; } // parse each line of the file while (true) { int subIndex = DEFAULT_CLIENT_STREAM_ID; // for items of form xxxxxx_# (multi-options) char buffer[4096] = {0}; if (!fgets(buffer, sizeof(buffer), conf)) { break; // get a line } size_t offset = strlen(buffer) - 1; while (buffer[offset] == '\n' || buffer[offset] == '\r') { buffer[offset]='\0'; // get rid of cr/lf } ++l; // increment line counter char *pbuffer = buffer; while (pbuffer && (*pbuffer == ' ' || *pbuffer == '\t')) { ++pbuffer; // remove trailing whitespace } if (!pbuffer || !*pbuffer || (*pbuffer == ';') || (*pbuffer == '[') || (*pbuffer == '#')) { continue; // blank lines or comments skipped } char *tok = pbuffer; while (pbuffer && *pbuffer && (*pbuffer != '=') && (*pbuffer != '\r')) { ++pbuffer; // look for = sign } if (!pbuffer || !*pbuffer) { m_deferredWarnLogMessages.push_back(LOGNAME "Invalid statement on line " + tos(l) + " of " + filename + " -> `" + utf8(buffer) + "'"); continue; } *pbuffer++=0; // skip utf-8 BOM if ((strlen(tok) > 2) && (((unsigned char*)tok)[0] == 0xef) && (((unsigned char*)tok)[1] == 0xbb) && (((unsigned char*)tok)[2] == 0xbf)) { tok += 3; } utf8 stok = tok; // see if it's a multi option and set the subIndex value const vector tokens = tokenizer(stok, '_'); if (tokens.size() == 2) { stok = tokens[0]; subIndex = atoi((const char *)stripWhitespace(tokens[1]).c_str()); // this ensure the subIndex has been specified even if there is no streamid // this is to fix a load of config setup issues from people not setting it. optMap_t::const_iterator sid = m_optMap.find("streamid"); (this->*((*sid).second.m_assignFunc))(tos(subIndex), subIndex); } stok = stripWhitespace(stok); // cleanup some more whitespace just in case // look for the base option name in the option map utf8 base_option_name = toLower(stok); if (base_option_name.empty()) { // skip over empty values continue; } // used for mapping *autodumpsourcetime to *autodumptime int autoDumpTime = 0; if (base_option_name == "autodumpsourcetime") { base_option_name = "autodumptime"; autoDumpTime = 1; } else if (base_option_name == "streamautodumpsourcetime") { base_option_name = "streamautodumptime"; autoDumpTime = 2; } else if (base_option_name == "reallogfile" || base_option_name == "autoauthhash") { // skip over as not pubiically exposed continue; } optMap_t::const_iterator i = m_optMap.find(base_option_name); bool legacyRelay = false; bool stream_bitrate = (base_option_name == "streambitrate"); if (i == m_optMap.end()) { if (!((base_option_name == "relayserver") || (base_option_name == "relayport") || stream_bitrate)) { m_deferredWarnLogMessages.push_back(LOGNAME "Invalid item on line " + tos(l) + " of " + filename + " -> `" + base_option_name + "'"); continue; } else { if (!stream_bitrate) { legacyRelay = true; } } } else { // do we have a stream* entry and no stream id mentioned / working against default // if yes then force streamid=1 to be set so that the options will be recognised uniString::utf8::size_type spos = base_option_name.find(utf8("stream")); if ((spos != uniString::utf8::npos) && (spos == 0) && (subIndex == DEFAULT_CLIENT_STREAM_ID)) { // this ensure the subIndex has been specified even if there is no streamid // this is to fix a load of config setup issues from people not setting it. const optMap_t::const_iterator sid = m_optMap.find("streamid"); (this->*((*sid).second.m_assignFunc))(tos(subIndex), subIndex); } } // move tok to the value tok = pbuffer; while (tok && *tok && *tok == ' ') { ++tok; } utf8 value = stripWhitespace(utf8(tok)); // if we're re-mapping *autodumpsourcetime then indicate in the log output if (autoDumpTime > 0) { m_deferredWarnLogMessages.push_back(LOGNAME "Deprecated statement found on line " + tos(l) + " of " + filename + " -> change " + (autoDumpTime == 2 ? "stream" : "") + "autodumpsourcetime_" + tos(subIndex) + "=" + value + " to " + (autoDumpTime == 2 ? "stream" : "") + "autodumptime_" + tos(subIndex) + "=" + value); } // this is used in the mapping of the old ocnfig options to the new style streamrelayurl if (legacyRelay == true) { if (base_option_name == "relayserver") { utf8::size_type check = (!value.empty() ? value.find(utf8("http://")) : 0); if (check == utf8::npos) { m_legacyRelayServer = "http://" + value; } else { m_legacyRelayServer = value; } } if (base_option_name == "relayport") { utf8::size_type check = value.find(utf8(":")); if ((check == utf8::npos) || (check != 0)) { m_legacyRelayPort = ":" + value; } else { m_legacyRelayPort = value; } } continue; } // check for yes / no values and map them to 1 or 0 to cope with legacy config loading if ((base_option_name == "w3cenable") || (base_option_name == "allowrelay") || (base_option_name == "allowpublicrelay") || (base_option_name == "riponly")) { if (toLower(value) == "yes") { value = "1"; } if (toLower(value) == "no") { value = "0"; } } // validation checks on the 'cdn' parameter to ensure we're all good else where if (base_option_name == "cdn") { value = toLower(value); if (!(value == "on") && !(value == "always") && !(value == "master")) { value = ""; } } if (stream_bitrate) { const optMap_t::const_iterator min = m_optMap.find("streamminbitrate"); (this->*((*min).second.m_assignFunc))(value, subIndex); const optMap_t::const_iterator max = m_optMap.find("streammaxbitrate"); (this->*((*max).second.m_assignFunc))(value, subIndex); continue; } // validation checks on the port parameters to detect the 'any' // case which is meant to be treated the same as an empty value if ((base_option_name == "srcdns") || (base_option_name == "srcip") || (base_option_name == "destdns") || (base_option_name == "destip") || (base_option_name == "dstip") || (base_option_name == "publicdns") || (base_option_name == "publicip")) { if (toLower(value) == "any") { value = ""; } } // check for streampath values and fix to have / on the start to ensure it will // work for client connections without impacting on client connection time, etc if (base_option_name == "streampath") { utf8::size_type path = value.find(utf8(":")); if (path != utf8::npos) { m_deferredWarnLogMessages.push_back(LOGNAME "Ignoring streampath_" + tos(subIndex) + "=" + value + " as this will produce an invalid path."); value.clear(); continue; } path = value.find(utf8("var/www")); // catches var/www... or /var/www... style paths // which are best just filtered as it gives away // paths on the host machine which looks crappy! if ((path != utf8::npos) && (path < 2)) { m_deferredWarnLogMessages.push_back(LOGNAME "Ignoring streampath_" + tos(subIndex) + "=" + value + " as this will produce an invalid path."); value.clear(); continue; } // check for empty as we can incorrectly end up // setting streampath_xx=/ which causes issues! if (!value.empty()) { path = value.find(utf8("/")); if ((path == utf8::npos) || (path != 0)) { value = "/" + value; } } // additionally we check for specific 'default' paths and block their usage // based on a match against the start of the streampath (might block some // possibly valid streampaths but it'll avoid stupid configuration issues). const char * disallowed[] = { "/listen.pls", "/listen.m3u", "/listen.asx", "/listen.xspf", "/listen.qtl", "/listen", "/7.html", "/index.html", "/played", "/played.html", "/admin.cgi", "/statistics", "/stats", "/streamart", "/playingart", "/nextsong", "/home", "/home.html", "/nextsongs", "/currentsong", "/shoutcast.swf", "/crossdomain.xml", "/index.css", "/images/", "/favicon.ico", "/robots.txt", "/images/favicon.ico", "/images/listen.png", "/images/history.png", "/images/lock.png", "/images/noadavail.png", "/images/streamart.png", "/images/adavail.png", "/images/v2.png", "/images/playingart.png", "/adplayed/adplayed.png", "/images/v1.png", "/images/relay.png", "/images/wa.png", "/images/chrome.png", "/images/firefox.png", "/images/safari.png", "/images/ie.png", "/images/vlc.png", "/images/fb2k.png", "/images/wmp.png", "/images/icecast.png", "/images/html5.png", "/images/rtb.png", "/images/ps.png", "/images/mplayer.png", "/images/apple.png", "/images/roku.png", "/images/itunes.png", "/images/warn.png", "/images/xff.png", "/images/radionomy.png", "/images/curl.png", "/images/flash.png", "/images/synology.png", "/images/wiimc.png" }; for (size_t x = 0; x < sizeof(disallowed) / sizeof(disallowed[0]); x++) { path = value.find(utf8(disallowed[x])); if ((path != utf8::npos) && !path) { m_deferredWarnLogMessages.push_back(LOGNAME "Ignoring streampath_" + tos(subIndex) + "=" + value + " as this contains a reserved path."); value.clear(); continue; } } } // check for redirecturl and streamredirecturl and ensure there is a http:// // otherwise we need to process multiple times on page hits which isn't good if ((base_option_name == "redirecturl" || base_option_name == "streamredirecturl") && !value.empty()) { utf8::size_type url = (!value.empty() ? value.find(utf8("http://")) : 0); if (url == utf8::npos) { value = "http://" + value; } } // check the passwords for validity e.g. they're not allowed to contain a colon // as that can break the multiple-1.x source support as well as 2.x source join if (base_option_name == "password" || base_option_name == "streampassword" || base_option_name == "adminpassword" || base_option_name == "streamadminpassword") { if (value.find(utf8(":")) != utf8::npos) { m_deferredErrorLogMessages.push_back(LOGNAME "`" + base_option_name + (subIndex > 1 ? "_" + tos(subIndex) : "") + "' contains a reserved character and will be ignored. " "Please remove all colons to resolve this issue."); value.clear(); continue; } } // special case to mimic old sc_serv conf file behaviour. In old sc_serv, the conf variable "unique" can // be subsituted for $ in any value related to a filename if (base_option_name == "unique") { unique = value; } if ((base_option_name == "logfile") || (base_option_name == "adtestfile") || (base_option_name == "adtestfile2") || (base_option_name == "adtestfile3") || (base_option_name == "adtestfile4") || (base_option_name == "streamadtestfile") || (base_option_name == "streamadtestfile2") || (base_option_name == "streamadtestfile3") || (base_option_name == "streamadtestfile4") || (base_option_name == "introfile") || (base_option_name == "streamintrofile") || (base_option_name == "backupfile") || (base_option_name == "streambackupfile") || (base_option_name == "banfile") || (base_option_name == "streambanfile") || (base_option_name == "ripfile") || (base_option_name == "streamripfile") || (base_option_name == "agentfile") || (base_option_name == "artworkfile") || (base_option_name == "streamagentfile") || (base_option_name == "streamartworkfile") || (base_option_name == "include") || (base_option_name == "w3clog") || (base_option_name == "streamw3clog") || (base_option_name == "portbase")) { if (unique != unique_matching_token) { utf8::size_type pos = value.find(unique_matching_token); while (pos != utf8::npos) { value.replace(pos, 1, unique); pos = value.find(unique_matching_token); } } // attempt to convert \ to / and vice versa as needed for cross-platform sharing of configuration files fileUtil::convertOSFilePathDelimiter(value); } if (base_option_name == "include") { // get the current folder so we can load from include=common.conf if using full paths, etc uniFile::filenameType currentPath = fileUtil::onlyPath(filename); // this will handle wildcard matching as applicable to what has been set in the 'include' // value so will allow us to have individual configs via 'include=./stream/stream_*.conf' #ifdef _WIN32 vector fileList = fileUtil::directoryFileList(value.toWString(), currentPath.toWString(), true, true); #else vector fileList = fileUtil::directoryFileList(value.hideAsString(), currentPath.hideAsString()); #endif if (!fileList.empty()) { #ifdef _WIN32 for (vector::const_iterator i = fileList.begin(); i != fileList.end(); ++i) #else for (vector::const_iterator i = fileList.begin(); i != fileList.end(); ++i) #endif { #ifdef _WIN32 utf32 u32file(*i); utf8 u8f(u32file.toUtf8()); _load(u8f, unique); #else _load(*i, unique); #endif } } } else { (this->*((*i).second.m_assignFunc))(value, subIndex); } } // close the config file ::fclose(conf); no_read_conf: return loaded; } bool config::editConfigFileEntry(size_t sid, const uniFile::filenameType &filename, const uniString::utf8 &authhash, const uniString::utf8 ¶m, const bool add, bool &handled, bool &idHandled, const bool parent = false) throw() { bool loaded = true; FILE *conf = NULL, *newconf = NULL; char buffer[4096] = {0}; char copyBuffer[4096] = {0}; char *pbuffer = buffer; int l = 0; // line counter // create a backup of the current config file and then use that uniFile::filenameType fn(filename + ".backup"); uniFile::unlink(fn); #ifdef _WIN32 if (!::MoveFileW(filename.toWString().c_str(), fn.toWString().c_str())) #else if (::rename(filename.hideAsString().c_str(), fn.hideAsString().c_str())) #endif { loaded = false; ELOG(LOGNAME "Could not backup original file `" + filename + "'"); goto no_write_conf; } conf = uniFile::fopen(filename+".backup","rb"); if (!conf) { loaded = false; ELOG(LOGNAME "Could not open `" + filename + "'(" + errMessage().hideAsString() + ")"); goto no_write_conf; } newconf = uniFile::fopen(filename,"wb"); if (!newconf) { loaded = false; ELOG(LOGNAME "Could not open `" + filename+".backup' (" + errMessage().hideAsString() + ")"); goto no_write_conf; } // parse each line of the file while (true) { size_t subIndex = DEFAULT_CLIENT_STREAM_ID; // for items of form xxxxxx_# (multi-options) if (!fgets(buffer, sizeof(buffer), conf)) { break; // get a line } memcpy(copyBuffer, buffer, sizeof(copyBuffer)); size_t offset = strlen(buffer)-1; while (buffer[offset]=='\n' || buffer[offset]=='\r') { buffer[offset]='\0'; // get rid of cr/lf } ++l; // increment line counter pbuffer = buffer; while (pbuffer && (*pbuffer == ' ' || *pbuffer == '\t')) { ++pbuffer; // remove trailing whitespace } if (!pbuffer || !*pbuffer || *pbuffer == ';' || *pbuffer == '[' || *pbuffer == '#') { fwrite(copyBuffer,1,strlen(copyBuffer),newconf); continue; // blank lines or comments skipped } char *tok = pbuffer; while (pbuffer && *pbuffer && *pbuffer != '=' && *pbuffer != '\r') { ++pbuffer; // look for = sign } if (!pbuffer || !*pbuffer) { fwrite(copyBuffer,1,strlen(copyBuffer),newconf); continue; } *pbuffer++=0; // skip utf-8 BOM if ((strlen(tok) > 2) && (((unsigned char*)tok)[0] == 0xef) && (((unsigned char*)tok)[1] == 0xbb) && (((unsigned char*)tok)[2] == 0xbf)) { tok += 3; } utf8 stok = tok; // see if it's a multi option and set the subIndex value const vector tokens = tokenizer(stok, '_'); if (tokens.size() == 2) { stok = tokens[0]; subIndex = atoi((const char *)stripWhitespace(tokens[1]).c_str()); } stok = stripWhitespace(stok); // cleanup some more whitespace just in case // look for the base option name in the option map utf8 base_option_name = toLower(stok); optMap_t::const_iterator i = m_optMap.find(base_option_name); if (i == m_optMap.end()) { fwrite(copyBuffer,1,strlen(copyBuffer),newconf); continue; } // move tok to the value tok = pbuffer; while (tok && *tok && *tok == ' ') { ++tok; } utf8 value = stripWhitespace(utf8(tok)); // this will attempt to check if streamid is present, otherwise we need to add otherwise // when the DNAS is restarted then it will not find the authhash despite being in there if (base_option_name == "streamid" && subIndex == sid && add == true) { idHandled = true; } // this will attempt to update a dummy entry in the config or remove depending on the mode if (base_option_name == "streamauthhash" && subIndex == sid) { // attempt to match the line breaks already in the file so it's consistent if (add == true) { // default to the current platforms encoding so it'll at least have a line break utf8 readEol = eol(); if (copyBuffer[0]) { // determine if just \n or \r\n size_t offset = strlen(copyBuffer)-1; if (copyBuffer[offset] == '\n') { if (copyBuffer[offset-1] == '\r') { readEol = "\r\n"; } else { readEol = "\n"; } } else if (copyBuffer[offset] == '\r') { readEol = "\r"; } } uniString::utf8 newEntry("streamauthhash_"+tos(sid)+"="+authhash+readEol); fwrite(newEntry.c_str(),1,newEntry.size(),newconf); } handled = true; } // this will attempt to update a dummy entry in the config or remove depending on the mode else if (base_option_name == authhash && !sid) { // attempt to match the line breaks already in the file so it's consistent if (add == true) { // default to the current platforms encoding so it'll at least have a line break utf8 readEol = eol(); if (copyBuffer[0]) { // determine if just \n or \r\n size_t offset = strlen(copyBuffer)-1; if (copyBuffer[offset] == '\n') { if (copyBuffer[offset-1] == '\r') { readEol = "\r\n"; } else { readEol = "\n"; } } else if (copyBuffer[offset] == '\r') { readEol = "\r"; } } if (param == "1") { uniString::utf8 newEntry(authhash+"="+param+eol()); fwrite(newEntry.c_str(),1,newEntry.size(),newconf); } } handled = true; } else { fwrite(copyBuffer,1,strlen(copyBuffer),newconf); } if (base_option_name == "include") { // get the current folder so we can load from include=common.conf if using full paths, etc uniFile::filenameType currentPath = fileUtil::onlyPath(filename); // this will handle wildcard matching as applicable to what has been set in the 'include' // value so will allow us to have individual configs via 'include=./stream/stream_*.conf' #ifdef _WIN32 vector fileList = fileUtil::directoryFileList(value.toWString(), currentPath.toWString(), true, true); #else vector fileList = fileUtil::directoryFileList(value.hideAsString(), currentPath.hideAsString()); #endif if (!fileList.empty()) { #ifdef _WIN32 for (vector::const_iterator i = fileList.begin(); i != fileList.end(); ++i) #else for (vector::const_iterator i = fileList.begin(); i != fileList.end(); ++i) #endif { #ifdef _WIN32 utf32 u32file(*i); utf8 u8f(u32file.toUtf8()); editConfigFileEntry(sid, u8f, authhash, param, add, handled, idHandled); #else editConfigFileEntry(sid, *i, authhash, param, add, handled, idHandled); #endif } } } } // only save back to the first config file if nothing was done in any included files if (parent == true && add == true && (handled == false || idHandled == false)) { if (sid && idHandled == false) { // look at and append a newline if there isn't one already at the config's eof if (copyBuffer[0]) { size_t len = strlen(copyBuffer)-1; if (copyBuffer[len] != '\r' && copyBuffer[len] != '\n') { fwrite(eol().c_str(),1,eol().size(),newconf); } } idHandled = true; } if (sid && handled == false) { // look at and append a newline if there isn't one already at the config's eof if (copyBuffer[0]) { size_t len = strlen(copyBuffer)-1; if (copyBuffer[len] != '\r' && copyBuffer[len] != '\n') { fwrite(eol().c_str(),1,eol().size(),newconf); } } uniString::utf8 newEntry("streamauthhash_"+tos(sid)+"="+authhash+eol()); fwrite(newEntry.c_str(),1,newEntry.size(),newconf); handled = true; } // for anything else, if it's at the default then no need to add, just remove it if (!sid && handled == false && (param == "1")) { // look at and append a newline if there isn't one already at the config's eof if (copyBuffer[0]) { size_t len = strlen(copyBuffer)-1; if (copyBuffer[len] != '\r' && copyBuffer[len] != '\n') { fwrite(eol().c_str(),1,eol().size(),newconf); } } uniString::utf8 newEntry(authhash+"="+param+eol()); fwrite(newEntry.c_str(),1,newEntry.size(),newconf); handled = true; } } // close the config file ::fclose(conf); ::fclose(newconf); no_write_conf: return loaded; } ///////////////////////////////////////////////// ///// service template interface methods //////// ///////////////////////////////////////////////// const vector config::fromArgs(const vector &cl) throw() { vector result; // only attempt to load a passed configuration file otherwise beforehand // this would cause sc_serv to load and appear to do nothing so make sure // that we let the defaults stay in effect so it will do work correctly if (!cl.empty()) { load(cl.front()); vector::const_iterator i = cl.begin(); ++i; while (i != cl.end()) { result.push_back(*(i++)); } } #ifdef _WIN32 if (_logFile() == DEFAULT_LOG) { // this will fill in the default log path as required wchar_t m_fileName[MAX_PATH] = {0}; ExpandEnvironmentStringsW(DEFAULT_LOGW, m_fileName, MAX_PATH); assign_logFile(utf32(m_fileName).toUtf8(), DEFAULT_CLIENT_STREAM_ID); } #endif return result; } bool config::getConsoleLogging() const throw() { stackLock sml(m_lock); return (!sDaemon ? _screenLog() && _log() : false); } const uniFile::filenameType config::getFileLog() const throw() { stackLock sml(m_lock); static const uniFile::filenameType empty; return (_log() ? _logFile() : empty); } #ifdef _WIN32 utf8 config::getSystemLogConfigString() throw() { return AOL_logger::systemLogger_element::panicConfiguration(); } #else utf8 config::getSystemLogConfigString() throw() { return "";} #endif utf8 config::getVersionBuildStrings() throw() { static utf8 version = ""; if (version.empty()) { #ifdef _WIN32 getVersionInfo(version); #else for (int x = 0; x < VENT; ++x) { if (x) { version += "."; } version += tos(PRODUCTVERSION[x]); } #endif #ifdef LICENCE_FREE version += " no-licence-check"; #endif } return version; } utf8 config::streamConfig::urlObj::parse(const utf8 &in_url, utf8 &out_server, u_short &out_port, utf8 &out_path) throw(exception) { utf8 url(in_url); // quick out if (in_url.empty()) { out_server.clear(); out_port = 0; out_path.clear(); return in_url; } ///////////// utf8 server; utf8 path("/"); u_short port = 80; if (url.empty()) { throwEx(LOGNAME "Parse error in url (" + url + ")"); } url = stripHTTPprefix(url); utf8::size_type pos = url.find(utf8(":")), pos2 = url.find(utf8("/")); if ((pos != utf8::npos) && ((pos2 == utf8::npos) || (pos < pos2))) { // port server = url.substr(0, pos); url.erase(0,pos + 1); pos2 = url.find(utf8("/")); port = utf8(url.substr(0, pos2)).toInt(); url.erase(0,pos2); } else { server = url.substr(0,pos2); url.erase(0,pos2); } if (!url.empty()) { path = url; } out_server = server; out_port = port; out_path = path; return in_url; } uniString::utf8 config::getCrossDomainFile(const bool compressed) throw() { if (m_crossdomainStr.empty()) { utf8 body = loadLocalFile(fileUtil::getFullFilePath(flashPolicyFile())); if (body.empty()) { utf8 ports = tos(portBase()); if (g_legacyPort >= 1 && g_legacyPort <= 65535) { ports += "," + tos(g_legacyPort); } if (!m_usedAlternatePorts.empty()) { ports += m_usedAlternatePorts; } body = "\n\n" "\n" "\n" "\n" ""; } m_crossdomainStrGZ = m_crossdomainStr = body; if (!compressData(m_crossdomainStrGZ)) { m_crossdomainStrGZ.clear(); } } // default to returning the non-compressed version return (compressed ? (!m_crossdomainStrGZ.empty() ? m_crossdomainStrGZ : m_crossdomainStr) : m_crossdomainStr); } uniString::utf8 config::getShoutcastSWF(const bool compressed) throw() { if (m_shoutcastSWFStr.empty()) { utf8 body = loadLocalFile(gStartupDirectory + "shoutcast.swf"); if (body.empty()) { body = MSG_HTTP404; } m_shoutcastSWFStrGZ = m_shoutcastSWFStr = body; if (!compressData(m_shoutcastSWFStrGZ)) { m_shoutcastSWFStrGZ.clear(); } } // default to returning the non-compressed version return (compressed ? (!m_shoutcastSWFStrGZ.empty() ? m_shoutcastSWFStrGZ : m_shoutcastSWFStr) : m_shoutcastSWFStr); } uniString::utf8 config::getIndexCSS(const bool compressed) throw() { if (m_styleCustomStr.empty()) { utf8 body = loadLocalFile(fileUtil::getFullFilePath(adminCSSFile())); if (!body.empty()) { m_styleCustomStrGZ = m_styleCustomStr = body; if (!compressData(m_styleCustomStrGZ)) { m_styleCustomStrGZ.clear(); } } } // default to returning the non-compressed version return (compressed ? (!m_styleCustomStrGZ.empty() ? m_styleCustomStrGZ : m_styleCustomStr) : m_styleCustomStr); } const uniString::utf8 config::getStreamHideStats(const size_t streamID) const { if (!streamID) { return hideStats(); } else { uniString::utf8 hide = stream_hideStats(streamID); if (!read_stream_hideStats(streamID) || hide.empty()) { hide = hideStats(); } return hide; } } const uniString::utf8 config::getStreamRedirectURL(const size_t streamID, const bool isStats, const bool homeSet, const bool compress, const bool force) const throw() { utf8 url; // check if hiding of public pages is enabled with some // specific handling as required for 'all' or 'stats'. bool all = (getStreamHideStats(streamID) == "all"), //none = !(getStreamHideStats(streamID) == "none"), stats = ((getStreamHideStats(streamID) == "stats") && isStats); if (all || stats || force) { // if no streamid then look at global redirect option only if (!streamID) { // if not set then we look at the streamurl from source // and if that is not valid then and return a 403 error if (!redirectUrl().empty()) { url = redirect(redirectUrl(), compress); } else { // but we check if it's for a stats only hide on a // stats method and then redirect to /index.html if (stats && !force) { url = redirect("index.html", compress); } else { url = MSG_HTTP403; } } } else { // look for a stream specific utf8 surl = stream_redirectUrl(streamID); if (!read_stream_redirectUrl(streamID) || surl.empty()) { // see if it's a stats page and redirect to /index.html if (stats && !force) { url = redirect("index.html", compress); } else { // otherwise attempt to use the streamurl (if available) if (homeSet && !force) { url = redirect("home.html?sid="+tos(streamID), compress); } else { // and if not then try the global redirect if (!redirectUrl().empty()) { url = redirect(redirectUrl(), compress); } // before reverting to a 403 error else { url = MSG_HTTP403; } } } } else { url = redirect(surl, compress); } } } return url; } void config::setOption(uniString::utf8 key, uniString::utf8 value) throw(exception) { stackLock sml(m_lock); size_t subIndex = DEFAULT_CLIENT_STREAM_ID; // for items of form xxxxxx_# (multi-options) uniString::utf8 stok = key; // see if it's a multi option and set the subIndex value const vector tokens = tokenizer(stok, '_'); if (tokens.size() == 2) { stok = tokens[0]; subIndex = atoi((const char *)stripWhitespace(tokens[1]).c_str()); } stok = stringUtil::stripWhitespace(stok); // cleanup some more whitespace just in case // look for the base option name in the option map uniString::utf8 base_option_name = stringUtil::toLower(stok); std::map::const_iterator mi = m_optMap.find(base_option_name); if (mi == m_optMap.end()) { throw std::runtime_error("Unknown option " + base_option_name.hideAsString()); } // first see if the value has changed. If not then we don't worry about it if ((this->*(*mi).second.m_fetchFunc)(subIndex, 0) != value) { if ((base_option_name == "log") || (base_option_name == "screenlog")) { m_deferredOptions[base_option_name] = value; } else { // tweak things as needed with checking the type so we can use the sid or not (this->*(*mi).second.m_assignFunc)(value, subIndex); } } }