#ifdef _WIN32 #include #endif #include #include "protocol_shoutcastClient.h" #include "protocol_admincgi.h" #include "protocol_HTTPStyle.h" #include "protocol_relay.h" #include "base64.h" #include "banList.h" #include "ripList.h" #include "adminList.h" #include "agentList.h" #include "uvox2Common.h" #include "w3cLog.h" #include "yp2.h" #include "updater.h" #include "aolxml/aolxml.h" #include "webNet/urlUtils.h" #include "file/fileUtils.h" #include "services/stdServiceImpl.h" #include "bandwidth.h" #include "cpucount.h" using namespace std; using namespace stringUtil; using namespace uniString; time_t last_update_check = 0; utf8 logId, logTailId, listenerId; #define DEBUG_LOG(...) do { if (gOptions.httpStyleDebug()) DLOG(__VA_ARGS__); } while (0) #define LOG_NAME "ADMINCGI" #define LOGNAME "[" LOG_NAME "] " #define HEAD_REQUEST (m_httpRequestInfo.m_request == protocol_HTTPStyle::HTTP_HEAD) #define SHRINK (m_httpRequestInfo.m_AcceptEncoding & protocol_HTTPStyle::ACCEPT_GZIP) #define COMPRESS(header, body) if (SHRINK && compressData(body)) header += "Content-Encoding:gzip\r\n" static bool sortUniqueClientDataByTime(const stats::uniqueClientData_t &a, const stats::uniqueClientData_t &b) { return (a.m_connectTime < b.m_connectTime); } utf8 getStreamAdminHeader(const streamData::streamID_t sid, const utf8& headerTitle, const int refreshRequired = 0, const bool style = false) { return "" "" "" "Shoutcast Administrator" "" "" + (abs(refreshRequired) > 0 ? "" : "") + (style ? "" : (utf8)"") + "" "" "" "" "
Shoutcast " + headerTitle + "
" "
Shoutcast Server v" + addWBR(gOptions.getVersionBuildStrings() + "/" SERV_OSNAME) + "
" "
" "" "
 | 
" "" "
 | 
" + (!(gOptions.read_stream_adminPassword(sid) && !gOptions.stream_adminPassword(sid).empty()) ? "
Log " "(Tailing" " | Save)
" "
 | 
" : "") + /*+ utf8(info.m_radionomyID.empty() ? warningImage(false) + "  Please Register Your Authhash

" : "") +*/ "" "
 | 
" "" "
 | 
" "" "
 | 
" "" "
 | 
" "" "
 | 
" "" "
"; } utf8 getServerAdminHeader(const utf8& headerTitle, const int refreshRequired = 0, const utf8& childPage = "", const int style = 0) { return "" "" "" "Shoutcast Server Administrator" "" "" + (abs(refreshRequired) > 0 ? "" : "") + (style ? "" : (utf8)"") + "" "" "" "" "
Shoutcast " + headerTitle + "
" "
Shoutcast Server v" + addWBR(gOptions.getVersionBuildStrings() + "/" SERV_OSNAME) + "
" "" "
" "" "
 | 
" "" "
 | 
" "
Log " "(Tailing | " "Save)
" "
 | 
" "" "
 | 
" "" "
 | 
" "" "
 | 
" "" "
 | 
" "" "
"; } static utf8 formatSizeString(const __uint64 size) { utf8::value_type buf[128] = {0}; if (size < 1024) { snprintf((char *)buf, sizeof(buf), "%llu B", size); } else if(size < 1048576) { snprintf((char *)buf, sizeof(buf), "%.02f KiB", size/1024.0f); } else if(size < 1073741824) { snprintf((char *)buf, sizeof(buf), "%.02f MiB", size/1048576.0f); } else if(size < 1099511627776LL) { snprintf((char *)buf, sizeof(buf), "%.02f GiB", size/1073741824.0f); } else { snprintf((char *)buf, sizeof(buf), "%.02f TiB", size/1099511627776.0f); } return buf; } utf8 getCheckedDuration(const size_t time) { if (time >= 60) { if (time < 3600) { size_t min = (time / 60); return (tos(min) + " minute" + (min != 1 ? "s" : "") + " ago"); } else if (time < 86400) { size_t hour = (time / 3600); return (tos(hour) + " hour" + (hour != 1 ? "s" : "") + " ago"); } else { size_t week = (time / 86400); return (tos(week) + " week" + (week != 1 ? "s" : "") + " ago"); } } return "less than a minute ago"; } utf8 niceURL(utf8 srcAddr) { if (!srcAddr.empty()) { utf8::size_type pos = srcAddr.find(utf8("://")); if (pos != utf8::npos && ((pos == 4) || (pos == 5))) { srcAddr = srcAddr.substr(pos + 3); } srcAddr = aolxml::escapeXML(srcAddr); // look for a /stream/x/ path and strip off the end / so the // link goes to the admin page instead of playing the stream if (!srcAddr.empty()) { utf8::size_type pos2 = srcAddr.find(utf8("/stream/")), pos3 = srcAddr.rfind(utf8("/")); if ((pos2 != utf8::npos) && ((pos3 != utf8::npos) && (pos3 > pos2) && (pos3 == srcAddr.size()-1))) { srcAddr = srcAddr.substr(0, pos3); } } } return srcAddr; } void restartRelay(const config::streamConfig &info) throw() { bool noEntry = false; const int relayActive = (streamData::isRelayActive(info.m_streamID, noEntry) & 12); if (!relayActive || !noEntry) { threadedRunner::scheduleRunnable(new protocol_relay(info)); } } void checkVersion(const time_t t) { utf8 tempId; httpHeaderMap_t queryParameters; queryParameters["id"] = randomId(tempId); yp2::runAuthHashAction(tempId, yp2::VER_CHECK, "/yp2", queryParameters, ""\ ""\ "" + gOptions.getVersionBuildStrings() + "/" SERV_OSNAME ""); last_update_check = t; } utf8 getUptimeScript(const bool base = false, const bool stream = false, const time_t streamUptime = 0) { // TODO need to consider improving this to better deal with leap years, etc // used to increment the uptime value on the server admin page return (!base ? "" : ""); } const bool reloadConfig(const int force) { bool m_reloadRefresh = false; ILOG(gOptions.logSectionName() + "Starting stream config reload from `" + fileUtil::getFullFilePath(gOptions.confFile()) + "'"); config newOptions; newOptions.load(gOptions.confFile()); vector relays; // to ease testing, especially on remote systems, will // allow toggling of the debugging options for v2.1+ ILOG(gOptions.logSectionName() + "Processing global configuration settings..."); if (newOptions.cdn() != gOptions.cdn()) { ILOG(gOptions.logSectionName() + "Changing CDN mode from " + (!gOptions.cdn().empty() ? gOptions.cdn() : "off") + " to " + (!newOptions.cdn().empty() ? newOptions.cdn() : "off")); try { gOptions.setOption(utf8("cdn"),utf8(newOptions.cdn())); } catch(const exception &) { } } if (newOptions.maxUser() != gOptions.maxUser()) { const int old_maxUser = gOptions.maxUser(), new_maxUser = newOptions.maxUser(); ILOG(gOptions.logSectionName() + "Changing server maxuser from " + (old_maxUser > 0 ? tos(old_maxUser) : "unlimited") + " to " + (new_maxUser > 0 ? tos(new_maxUser) : "unlimited")); gOptions.setOption(utf8("maxuser"),utf8(tos(newOptions.maxUser()))); } if (newOptions.maxBitrate() != gOptions.maxBitrate()) { ILOG(gOptions.logSectionName() + "Changing server maxbitrate from " + (gOptions.maxBitrate() > 0 ? tos(gOptions.maxBitrate()) + "bps" : "unlimited") + " to " + (newOptions.maxBitrate() > 0 ? tos(newOptions.maxBitrate()) + "bps" : "unlimited")); gOptions.setOption(utf8("maxbitrate"),utf8(tos(newOptions.maxBitrate()))); } if (newOptions.minBitrate() != gOptions.minBitrate()) { ILOG(gOptions.logSectionName() + "Changing server minbitrate from " + (gOptions.minBitrate() > 0 ? tos(gOptions.minBitrate()) + "bps" : "unlimited") + " to " + (newOptions.minBitrate() > 0 ? tos(newOptions.minBitrate()) + "bps" : "unlimited")); gOptions.setOption(utf8("minbitrate"),utf8(tos(newOptions.minBitrate()))); } bool publicChanged = false; if (newOptions.publicServer() != gOptions.publicServer()) { ILOG(gOptions.logSectionName() + "Changing state of publicserver - killing sources as applicable"); gOptions.setOption(utf8("publicserver"),newOptions.publicServer()); publicChanged = true; } // update the server-wide hiding and redirection options if (newOptions.hideStats() != gOptions.hideStats()) { ILOG(gOptions.logSectionName() + "Changing 'hidestats'"); gOptions.setOption(utf8("hidestats"),utf8(newOptions.hideStats())); } if (newOptions.redirectUrl() != gOptions.redirectUrl()) { ILOG(gOptions.logSectionName() + "Changing 'redirecturl'"); gOptions.setOption(utf8("redirecturl"),utf8(newOptions.redirectUrl())); } if (newOptions.ripOnly() != gOptions.ripOnly()) { ILOG(gOptions.logSectionName() + "Changing 'riponly'"); gOptions.setOption(utf8("riponly"),utf8(tos(newOptions.ripOnly()))); } if (newOptions.blockEmptyUserAgent() != gOptions.blockEmptyUserAgent()) { ILOG(gOptions.logSectionName() + "Changing 'blockemptyuseragent'"); gOptions.setOption(utf8("blockemptyuseragent"),utf8(tos(newOptions.blockEmptyUserAgent()))); } if (newOptions.metricsMaxQueue() != gOptions.metricsMaxQueue()) { ILOG(gOptions.logSectionName() + "Changing 'metricsmaxqueue'"); gOptions.setOption(utf8("metricsmaxqueue"),utf8(tos(newOptions.metricsMaxQueue()))); } metrics::metrics_apply(newOptions); if (newOptions.relayReconnectTime() != gOptions.relayReconnectTime()) { ILOG(gOptions.logSectionName() + "Changing 'relayreconnecttime'"); gOptions.setOption(utf8("relayreconnecttime"),utf8(tos(newOptions.relayReconnectTime()))); } if (newOptions.relayConnectRetries() != gOptions.relayConnectRetries()) { ILOG(gOptions.logSectionName() + "Changing 'relayconnectretries'"); gOptions.setOption(utf8("relayconnectretries"),utf8(tos(newOptions.relayConnectRetries()))); } if (newOptions.backupLoop() != gOptions.backupLoop()) { ILOG(gOptions.logSectionName() + "Changing 'backuploop'"); gOptions.setOption(utf8("backuploop"),utf8(tos(newOptions.backupLoop()))); } if (newOptions.songHistory() != gOptions.songHistory()) { ILOG(gOptions.logSectionName() + "Changing 'songhistory'"); gOptions.setOption(utf8("songhistory"),utf8(tos(newOptions.songHistory()))); } if (newOptions.adminFile() != gOptions.adminFile()) { ILOG(gOptions.logSectionName() + "Changing 'adminfile'"); gOptions.setOption(utf8("adminfile"),newOptions.adminFile()); } if (newOptions.clacks() != gOptions.clacks()) { ILOG(gOptions.logSectionName() + "Changing 'clacks'"); gOptions.setOption(utf8("clacks"),utf8(tos(newOptions.clacks()))); } if (newOptions.startInactive() != gOptions.startInactive()) { ILOG(gOptions.logSectionName() + "Changing 'startinactive'"); gOptions.setOption(utf8("startinactive"),utf8(tos(newOptions.startInactive()))); } if (newOptions.rateLimit() != gOptions.rateLimit()) { ILOG(gOptions.logSectionName() + "Changing 'rateLimit'"); gOptions.setOption(utf8("ratelimit"),utf8(tos(newOptions.rateLimit()))); } if (newOptions.adTestFile() != gOptions.adTestFile()) { ILOG(gOptions.logSectionName() + "Changing 'adtestfile'"); gOptions.setOption(utf8("adtestfile"),utf8(newOptions.adTestFile())); } if (newOptions.adTestFile2() != gOptions.adTestFile2()) { ILOG(gOptions.logSectionName() + "Changing 'adtestfile2'"); gOptions.setOption(utf8("adtestfile2"),utf8(newOptions.adTestFile2())); } if (newOptions.adTestFile3() != gOptions.adTestFile3()) { ILOG(gOptions.logSectionName() + "Changing 'adtestfile3'"); gOptions.setOption(utf8("adtestfile3"),utf8(newOptions.adTestFile3())); } if (newOptions.adTestFile4() != gOptions.adTestFile4()) { ILOG(gOptions.logSectionName() + "Changing 'adtestfile4'"); gOptions.setOption(utf8("adtestfile4"),utf8(newOptions.adTestFile4())); } if (newOptions.adTestFileLoop() != gOptions.adTestFileLoop()) { ILOG(gOptions.logSectionName() + "Changing 'adtestfileloop'"); gOptions.setOption(utf8("adtestfileloop"),utf8(tos(newOptions.adTestFileLoop()))); } if (newOptions.metaInterval() != gOptions.metaInterval()) { ILOG(gOptions.logSectionName() + "Changing 'metainterval'"); gOptions.setOption(utf8("metainterval"),utf8(tos(newOptions.metaInterval()))); } if (newOptions.useXFF() != gOptions.useXFF()) { ILOG(gOptions.logSectionName() + "Changing 'usexff'"); gOptions.setOption(utf8("usexff"),utf8(tos(newOptions.useXFF()))); } if (newOptions.forceShortSends() != gOptions.forceShortSends()) { ILOG(gOptions.logSectionName() + "Changing 'forceshortsends'"); gOptions.setOption(utf8("forceshortsends"),utf8(tos(newOptions.forceShortSends()))); } if (newOptions.adminNoWrap() != gOptions.adminNoWrap()) { ILOG(gOptions.logSectionName() + "Changing 'adminnowrap'"); gOptions.setOption(utf8("adminnowrap"),utf8(tos(newOptions.adminNoWrap()))); } if (newOptions.adminCSSFile() != gOptions.adminCSSFile()) { ILOG(gOptions.logSectionName() + "Changing 'admincssfile'"); gOptions.setOption(utf8("admincssfile"),utf8(newOptions.adminCSSFile())); gOptions.m_styleCustomStr.clear(); gOptions.m_styleCustomStrGZ.clear(); gOptions.m_styleCustomHeaderTime = 0; } if (newOptions.destIP() != gOptions.destIP()) { utf8 destBindAddr = metrics::metrics_verifyDestIP(newOptions, false); ILOG(gOptions.logSectionName() + "Changing 'destip'"); gOptions.setOption(utf8("destip"), destBindAddr); // if we're updating then attempt to behave like it was a new load g_IPAddressForClients = destBindAddr; if (g_IPAddressForClients.empty()) { char s[MAXHOSTNAMELEN] = {0}; if (!::gethostname(s,MAXHOSTNAMELEN - 1)) { g_IPAddressForClients = socketOps::hostNameToAddress(s,g_portForClients); } } } if (newOptions.publicIP() != gOptions.publicIP()) { utf8 publicAddr = stripWhitespace(newOptions.publicIP()); publicAddr = stripHTTPprefix(publicAddr); ILOG(gOptions.logSectionName() + "Changing 'publicip'"); gOptions.setOption(utf8("publicip"),publicAddr); } if (newOptions.autoDumpTime() != gOptions.autoDumpTime()) { ILOG(gOptions.logSectionName() + "Changing 'autodumptime'"); gOptions.setOption(utf8("autodumptime"),utf8(tos(newOptions.autoDumpTime()))); } if (newOptions.nameLookups() != gOptions.nameLookups()) { ILOG(gOptions.logSectionName() + "Changing 'namelookups'"); gOptions.setOption(utf8("namelookups"),utf8(tos(newOptions.nameLookups()))); } // update the YP details (can do on fly without any actual updates) bool ypChanged = false; bool https = ((gOptions.ypAddr() == DEFAULT_YP_ADDRESS) && uniFile::fileExists(gOptions.m_certPath)); utf8 oldYP = (https ? "https://" : "http://") + gOptions.ypAddr() + ":" + tos(gOptions.ypPort()) + gOptions.ypPath(); if (newOptions.ypAddr() != gOptions.ypAddr()) { ypChanged = true; gOptions.setOption(utf8("ypaddr"),utf8(newOptions.ypAddr())); } if (newOptions.ypPort() != gOptions.ypPort()) { ypChanged = true; gOptions.setOption(utf8("ypport"),utf8(tos(newOptions.ypPort()))); } if (newOptions.ypPath() != gOptions.ypPath()) { ypChanged = true; gOptions.setOption(utf8("yppath"),utf8(newOptions.ypPath())); } if (ypChanged) { bool https = ((newOptions.ypAddr() == DEFAULT_YP_ADDRESS) && uniFile::fileExists(gOptions.m_certPath)); utf8 newYP = (https ? "https://" : "http://") + newOptions.ypAddr() + ":" + tos(newOptions.ypPort()) + newOptions.ypPath(); ILOG(gOptions.logSectionName() + "Changing YP details from " + oldYP + " to " + newYP); } gOptions.setOption(utf8("yp2debug"),utf8(newOptions.yp2Debug()?"1":"0")); gOptions.setOption(utf8("shoutcastsourcedebug"),utf8(newOptions.shoutcastSourceDebug()?"1":"0")); gOptions.setOption(utf8("uvox2sourcedebug"),utf8(newOptions.uvox2SourceDebug()?"1":"0")); gOptions.setOption(utf8("httpsourcedebug"),utf8(newOptions.HTTPSourceDebug()?"1":"0")); gOptions.setOption(utf8("shoutcast1clientdebug"),utf8(newOptions.shoutcast1ClientDebug()?"1":"0")); gOptions.setOption(utf8("shoutcast2clientdebug"),utf8(newOptions.shoutcast2ClientDebug()?"1":"0")); gOptions.setOption(utf8("httpclientdebug"),utf8(newOptions.HTTPClientDebug()?"1":"0")); gOptions.setOption(utf8("flvclientdebug"),utf8(newOptions.flvClientDebug()?"1":"0")); gOptions.setOption(utf8("m4aclientdebug"),utf8(newOptions.m4aClientDebug()?"1":"0")); gOptions.setOption(utf8("relayshoutcastdebug"),utf8(newOptions.relayShoutcastDebug()?"1":"0")); gOptions.setOption(utf8("relayuvoxdebug"),utf8(newOptions.relayUvoxDebug()?"1":"0")); gOptions.setOption(utf8("relaydebug"),utf8(newOptions.relayDebug()?"1":"0")); gOptions.setOption(utf8("streamdatadebug"),utf8(newOptions.streamDataDebug()?"1":"0")); gOptions.setOption(utf8("httpstyledebug"),utf8(newOptions.httpStyleDebug()?"1":"0")); gOptions.setOption(utf8("statsdebug"),utf8(newOptions.statsDebug()?"1":"0")); gOptions.setOption(utf8("microserverdebug"),utf8(newOptions.microServerDebug()?"1":"0")); gOptions.setOption(utf8("threadrunnerdebug"),utf8(newOptions.threadRunnerDebug()?"1":"0")); gOptions.setOption(utf8("admetricsdebug"),utf8(newOptions.adMetricsDebug()?"1":"0")); gOptions.setOption(utf8("authdebug"),utf8(newOptions.authDebug()?"1":"0")); ILOG(gOptions.logSectionName() + "Processed global configuration settings."); // test for the source password having changed this will be // applied in general though per stream changes are likely // to have been set in the earlier checks before we got here if (newOptions.password() != gOptions.password()) { gOptions.setOption(utf8("password"),newOptions.password()); streamData *sd = streamData::accessStream(DEFAULT_SOURCE_STREAM); if (sd) { ILOG(gOptions.logSectionName() + "Killing all stream sources due to change of relay options."); sd->killAllSources(); m_reloadRefresh = true; sd->releaseStream(); } } config::streams_t new_streams, old_streams; newOptions.getStreamConfigs(new_streams, false); gOptions.getStreamConfigs(old_streams, false); config::streams_t::const_iterator iNSC = new_streams.begin(), iOSC = old_streams.begin(); // if no configurations found then we can just remove everything if (new_streams.empty()) { // kick the source and clients as required on a removal for (; iOSC != old_streams.end(); ++iOSC) { size_t streamID = (*iOSC).second.m_streamID; streamData *sd = streamData::accessStream(streamID); if (sd) { sd->killSource(streamID, sd); m_reloadRefresh = true; } gOptions.removeStreamConfig((*iOSC).second); } } // otherwise if the same or more then we can update / add as required else if (new_streams.size() >= old_streams.size()) { // if no configs specified and we're starting to add new stream configs // then we really need to kick any existing sources otherwise it'll stay // with the current details which will prevent a YP connection with extra // checks in build 21+ to make sure it only works if there was a change. if ((new_streams.size() != old_streams.size()) && (old_streams.size() == DEFAULT_SOURCE_STREAM)) { // kick the source and clients as required on a complete addition // (wouldn't have a valid config via authhash, etc so is sensible) streamData *sd = streamData::accessStream(DEFAULT_SOURCE_STREAM); if (sd) { ILOG(gOptions.logSectionName() + "Forcing stream source disconnect due to addition of stream config(s)."); sd->killAllSources(); m_reloadRefresh = true; sd->releaseStream(); } } for (; iNSC != new_streams.end(); ++iNSC) { config::streams_t::const_iterator iOSC2 = old_streams.find((*iNSC).first); if (iOSC2 != old_streams.end()) { // update required __uint64 updated = gOptions.updateStreamConfig(newOptions, (*iNSC).second); size_t streamID = (*iNSC).second.m_streamID; streamData *sd = streamData::accessStream(streamID); if (sd) { // otherwise do an in-place update as long as it is applicable bool isRelay = sd->isRelayStream(streamID); if (publicChanged || force || (!force && (updated & RELAY_URL || updated & SOURCE_PWD || updated & PUBLIC_SRV || (updated & ALLOW_RELAY && isRelay) || (updated & ALLOW_PUBLIC_RELAY && isRelay) || updated & CIPHER_KEY || updated & INTRO_FILE || updated & BACKUP_FILE || updated & BACKUP_URL || updated & MOVED_URL))) { sd->killSource(streamID); m_reloadRefresh = true; if (!(*iNSC).second.m_relayUrl.url().empty() && !sd->isSourceConnected(streamID) && gOptions.stream_movedUrl(streamID).empty()) { relays.push_back((*iNSC).second); } } // otherwise do an in-place update as long as it is applicable else { sd->streamUpdate(streamID, (*iNSC).second.m_authHash, (*iNSC).second.m_maxStreamUser, (*iNSC).second.m_maxStreamBitrate, (*iNSC).second.m_minStreamBitrate); } // refresh the played history size as needed if (updated & SONG_HIST) { sd->updateSongHistorySize(); } if (updated & ARTWORK_FILE) { if (gOptions.read_stream_artworkFile(streamID)) { gOptions.m_artworkBody[streamID] = loadLocalFile(fileUtil::getFullFilePath(gOptions.stream_artworkFile(streamID)), LOGNAME, 523872/*32 x (16384 - 6 - 6 - 1)*/); } else { gOptions.m_artworkBody[streamID].clear(); sd->clearCachedArtwork(0); } } sd->releaseStream(); } // otherwise if no proper update or a relay was added / is known and not connected then bump it else { // force flag a source relay kick if this is called bool noEntry = false; if ((streamData::isRelayActive(streamID, noEntry) & 12)) { // if there is a relay attempt and no new one // then we need to signal it to abort trying if ((*iNSC).second.m_relayUrl.url().empty()) { streamData::setRelayActiveFlags (streamID, noEntry, 2); } // otherwise we need update the relay url // which the attempt is trying to join to // which is done by the relay handling. } if (((*iOSC2).second.m_relayUrl.url() != (*iNSC).second.m_relayUrl.url() || (updated & MOVED_URL)) && !(*iNSC).second.m_relayUrl.url().empty()) { relays.push_back((*iNSC).second); m_reloadRefresh = true; } } } else { // addition required // no need to do anything else as there shouldn't be anything connected on this at the time gOptions.addStreamConfig(newOptions, (*iNSC).second); m_reloadRefresh = true; // only attempt to start a relay url if added and a relay url exists if (!(*iNSC).second.m_relayUrl.url().empty()) { relays.push_back((*iNSC).second); } } } } // otherwise if there are fewer stream configurations then we can update / remove as required else { for (; iOSC != old_streams.end(); ++iOSC) { config::streams_t::const_iterator iOSC2 = new_streams.find((*iOSC).first); if (iOSC2 != new_streams.end()) { // update required __uint64 updated = gOptions.updateStreamConfig(newOptions, (*iOSC2).second); size_t streamID = (*iOSC2).second.m_streamID; streamData *sd = streamData::accessStream(streamID); if (sd) { // check what has been updated and kick the source as applicable bool isRelay = sd->isRelayStream(streamID); if (publicChanged || force || (!force && (updated & RELAY_URL || updated & SOURCE_PWD || updated & PUBLIC_SRV || (updated & ALLOW_RELAY && isRelay) || (updated & ALLOW_PUBLIC_RELAY && isRelay) || updated & CIPHER_KEY || updated & INTRO_FILE || updated & BACKUP_FILE || updated & BACKUP_URL || updated & MOVED_URL))) { sd->killSource(streamID); m_reloadRefresh = true; if (!(*iOSC2).second.m_relayUrl.url().empty() && !sd->isSourceConnected(streamID) && gOptions.stream_movedUrl(streamID).empty()) { relays.push_back((*iOSC2).second); } } // otherwise do an in-place update as long as it is applicable else { sd->streamUpdate(streamID, (*iOSC2).second.m_authHash, (*iOSC2).second.m_maxStreamUser, (*iOSC2).second.m_maxStreamBitrate, (*iOSC2).second.m_minStreamBitrate); } // refresh the played history size as needed if (updated & SONG_HIST) { sd->updateSongHistorySize(); } if (updated & ARTWORK_FILE) { if (gOptions.read_stream_artworkFile(streamID)) { gOptions.m_artworkBody[streamID] = loadLocalFile(fileUtil::getFullFilePath(gOptions.stream_artworkFile(streamID)), LOGNAME, 523872/*32 x (16384 - 6 - 6 - 1)*/); } else { gOptions.m_artworkBody[streamID].clear(); sd->clearCachedArtwork(0); } } sd->releaseStream(); } } else { // kick the source and clients as required on a removal size_t streamID = (*iOSC).second.m_streamID; streamData *sd = streamData::accessStream(streamID); if (sd) { sd->killSource(streamID, sd); m_reloadRefresh = true; } // removal required gOptions.removeStreamConfig((*iOSC).second); } } } // test for the require stream configs having changed // only need to kick streams not known if enabled if (newOptions.requireStreamConfigs() != gOptions.requireStreamConfigs()) { gOptions.setOption(utf8("requirestreamconfigs"),utf8(newOptions.requireStreamConfigs()?"1":"0")); if (newOptions.requireStreamConfigs()) { size_t inc = 0; size_t sid; do { sid = streamData::enumStreams(inc); config::streams_t streams; gOptions.getStreamConfigs(streams, false); config::streams_t::const_iterator ics = streams.find(sid); if (ics == streams.end()) { streamData *sd = streamData::accessStream(sid); if (sd) { ILOG(gOptions.logSectionName() + "Killing source for stream #" + tos(sid) + " as it is not in the known stream config(s).", LOG_NAME, sid); sd->killSource(sid, sd); m_reloadRefresh = true; } } ++inc; } while (sid); } } // finally we refresh the passwords so they're correct after any changes config::streams_t streams; gOptions.getStreamConfigs(streams, false); gOptions.setupPasswords(streams); // if a force was done then we really need to restart any relays if (force) { // schedule relays vector relayList(gOptions.getRelayList()); if (!relayList.empty()) { for_each(relayList.begin(),relayList.end(),restartRelay); } } // otherwise only attempt to restart any which were added, changed, etc else { if (!relays.empty()) { for_each(relays.begin(),relays.end(),restartRelay); } } ILOG(gOptions.logSectionName() + "Completed stream config reload from `" + fileUtil::getFullFilePath(gOptions.confFile())); return m_reloadRefresh; } void reloadBanLists() { // load up ban file int loaded = g_banList.load(gOptions.banFile(),0), count = loaded; // per-stream options config::streams_t streams; gOptions.getStreamConfigs(streams); for (config::streams_t::const_iterator i = streams.begin(); i != streams.end(); ++i) { // load up ban file if (gOptions.read_stream_banFile((*i).first)) { ++count; if (g_banList.load(gOptions.stream_banFile((*i).first),(*i).first)) { ++loaded; } } } if (!count) { ILOG("[BAN] No banned lists reloaded."); } else { if (count == loaded) { ILOG("[BAN] Reloaded all banned list(s)."); } else { ILOG("[BAN] Partially reloaded banned list(s) - check error messages above."); } } } void reloadRipLists() { // load up rip file int loaded = g_ripList.load(gOptions.ripFile(),0), count = loaded; // per-stream options config::streams_t streams; gOptions.getStreamConfigs(streams); for (config::streams_t::const_iterator i = streams.begin(); i != streams.end(); ++i) { // load up rip file if (gOptions.read_stream_ripFile((*i).first)) { ++count; if (g_ripList.load(gOptions.stream_ripFile((*i).first),(*i).first)) { ++loaded; } } } if (!count) { ILOG("[RIP] No reserved lists reloaded."); } else { if (count == loaded) { ILOG("[RIP] Reloaded all reserved list(s)."); } else { ILOG("[RIP] Partially reloaded reserved list(s) - check error messages above."); } } } void reloadAdminAccessList() { // load up admin access file g_adminList.load(gOptions.adminFile()); ILOG("[ADMINCGI] Reloaded admin access list."); } void reloadAgentLists() { // load up agent file int loaded = g_agentList.load(gOptions.agentFile(),0), count = loaded; // per-stream options config::streams_t streams; gOptions.getStreamConfigs(streams); for (config::streams_t::const_iterator i = streams.begin(); i != streams.end(); ++i) { // load up agent file if (gOptions.read_stream_agentFile((*i).first)) { ++count; if (g_agentList.load(gOptions.stream_agentFile((*i).first),(*i).first)) { ++loaded; } } } if (!count) { ILOG("[AGENT] No user agent lists reloaded."); } else { if (count == loaded) { ILOG("[AGENT] Reloaded all user agent list(s)."); } else { ILOG("[AGENT] Partially reloaded user agent list(s) - check error messages above."); } } } void printUpdateMessage() { // display update message where applicable updater::verInfo ver; if (updater::getNewVersion(ver)) { ULOG(string(YP2_LOGNAME) + "A new DNAS version is now available: " + ver.ver); ULOG(string(YP2_LOGNAME) + "The suggested download for your setup is: " + ver.url); ULOG(string(YP2_LOGNAME) + "See " + ver.log + " for more information about this update and alternative download links"); } } utf8 warningImage(const bool right = true) { return utf8(right ? " " : "") + ""; } utf8 baseImage(const utf8& image, const utf8& title, const bool left = false, const bool prefix = true) { return (prefix ? " " : (utf8)"") + ""; } #define xffImage() baseImage("xff", "XFF", true) #define mplayerImage() baseImage("mplayer", "MPlayer Client") #define psImage() baseImage("ps", "PlayStation Client") #define radioToolboxImage() baseImage("rtb", "Radio Toolbox Monitor / Player") #define v1Image() baseImage("v1", "v1 Client") #define v2Image() baseImage("v2", "v2 Client") #define relayImage() baseImage("relay", "Relay Connection", false, false) #define html5Image() baseImage("html5", "HTTP / HTML5 Client") #define flashImage() baseImage("flash", "Flash Client") #define icecastImage() baseImage("icecast", "Icecast Client / Relay") #define vlcImage() baseImage("vlc", "VLC Client") #define waImage() baseImage("wa", "Winamp Client") #define scImage() baseImage("", "Shoutcast Client / Relay") #define iOSImage() baseImage("", "iOS Client") #define curlImage() baseImage("curl", "cURL / libcurl Based Client") #define radionomyImage() baseImage("radionomy", "Radionomy Stats Collector") #define fb2kImage() baseImage("fb2k", "Foobar2000 Client") #define rokuImage() baseImage("roku", "Roku Streaming Player") #define WiiMCImage() baseImage("v1", "Wii Media Centre Player") #define synologyImage() baseImage("synology", "Audio Station (Synology) Player") #define appleImage() baseImage("apple", "Apple Device / OS / Client") #define iTunesImage() baseImage("itunes", "iTunes Client") #define wmpImage() baseImage("wmp", "Windows Media Player Client") #define chromeImage() baseImage("chrome", "Chrome Web Browser") #define safariImage() baseImage("safari", "Safari Web Browser") #define ieImage() baseImage("ie", "Internet Explorer Web Browser") #define firefoxImage() baseImage("firefox", "Firefox Web Browser") utf8 advertImage(const streamData::streamID_t sid, const int group, const size_t count) { if (streamData::knownAdvertGroup(sid, group)) { if (count > 0) { return baseImage("adplayed", (tos(count) + " Advert Breaks Have Been Played\nAdvert Group: " + tos(group)), false, false); } return baseImage("adavail", ("Waiting For First Advert Break To Play\nAdvert Group: " + tos(group)), false, false); } if (count > 0) { return baseImage("", (tos(count) + " Advert Breaks Have Been Played\nNo Applicable " "Adverts Currently Available\nAdvert Group: " + tos(group)), false, false); } return baseImage("noadavail", ("No Applicable Adverts Available\nAdvert Group: " + tos(group)), false, false); } utf8 getClientImage(const streamData::source_t type) { return ((type & streamData::RADIONOMY) ? radionomyImage() : ((type & streamData::FLV) ? flashImage() : ((type & streamData::CURL_TOOL) ? curlImage() : ((type & streamData::HTTP) ? html5Image() : //((type & streamData::M4A) ? m4aImage() : ((type & streamData::SHOUTCAST2) ? ((type & streamData::RELAY) ? scImage() : waImage()) : ((type & streamData::ICECAST) ? icecastImage() : ((type & streamData::VLC) ? vlcImage() : ((type & streamData::WINAMP) ? waImage() : ((type & streamData::APPLE) ? appleImage() : ((type & streamData::ITUNES) ? iTunesImage() : ((type & streamData::WMP) ? wmpImage() : ((type & streamData::ROKU) ? rokuImage() : ((type & streamData::WIIMC) ? WiiMCImage() : ((type & streamData::SYNOLOGY) ? synologyImage() : ((type & streamData::CHROME) ? chromeImage() : ((type & streamData::SAFARI) ? safariImage() : ((type & streamData::IE) ? ieImage() : ((type & streamData::FIREFOX) ? firefoxImage() : ((type & streamData::MPLAYER) ? mplayerImage() : ((type & streamData::PS) ? psImage() : ((type & streamData::RADIO_TOOLBOX) ? radioToolboxImage() : ((type & streamData::HTML5) ? html5Image() : ((type & streamData::WARNING) ? warningImage() : ((type & streamData::SC_IRADIO) ? iOSImage() : ((type & streamData::FB2K) ? fb2kImage() : v1Image() ))))))))))))))))))))))))) + ((type & streamData::RELAY) ? relayImage() : ""); } /* Handles all HTTP requests to admin.cgi */ protocol_admincgi::protocol_admincgi(const socketOps::tSOCKET s, const streamData::streamID_t sid, const bool no_sid, const bool zero_sid, const utf8 &clientLogString, const uniString::utf8 &password, const uniString::utf8 &referer, const uniString::utf8 &hostIP, const uniString::utf8 &userAgent, const protocol_HTTPStyle::HTTPRequestInfo &httpRequestInfo) throw(std::exception) : runnable(s), m_noSID(no_sid), m_zeroSID(zero_sid), m_saveLogFile(0), m_clientLogString(clientLogString), m_httpRequestInfo(httpRequestInfo), m_password(password), m_referer(referer), m_hostIP(hostIP), m_userAgent(userAgent), m_sid(sid), m_state(&protocol_admincgi::state_ConfirmPassword), m_nextState(0), m_outBuffer(0), m_outBufferSize(0), m_tailLogFile(false), lastChar(-1), inMsg(false), first(false), m_logFile(0) { memset(&m_stream, 0, sizeof(m_stream)); // check for updates weekly from the last update // so we're taking into account manual checks... if ((m_lastActivityTime - last_update_check) > 604800) { checkVersion(m_lastActivityTime); } DEBUG_LOG(utf8(LOGNAME) + __FUNCTION__); } protocol_admincgi::~protocol_admincgi() throw() { DEBUG_LOG(utf8(LOGNAME) + __FUNCTION__); socketOps::forgetTCPSocket(m_socket); if (m_logFile) { ::fclose(m_logFile); m_logFile = 0; } } void protocol_admincgi::timeSlice() throw(exception) { (this->*m_state)(); } void protocol_admincgi::state_Close() throw(exception) { m_result.done(); } // send buffer text void protocol_admincgi::state_Send() throw(exception) { if (sendDataBuffer(DEFAULT_CLIENT_STREAM_ID, m_outBuffer, m_outBufferSize, m_clientLogString)) { m_state = m_nextState; } } void protocol_admincgi::sendMessageAndClose(const utf8 &msg) throw() { m_outMsg = msg; m_outBuffer = m_outMsg.c_str(); bandWidth::updateAmount(bandWidth::PRIVATE_WEB, (m_outBufferSize = (int)m_outMsg.size())); m_state = &protocol_admincgi::state_Send; m_nextState = &protocol_admincgi::state_Close; m_result.write(); m_result.run(); } /// update the metatdata in the shoutcast ring buffer void protocol_admincgi::state_UpdateMetadata() throw(exception) { DEBUG_LOG(utf8(LOGNAME) + __FUNCTION__); streamData::streamInfo info; streamData::extraInfo extra; streamData::getStreamInfo(m_sid, info, extra); utf8 titleMsg, urlMsg, djMsg, nextMsg; if (!m_updinfoSong.empty()) { // use this as a way to try to ensure we've got a utf-8 // encoded title to improve legacy source title support if (!m_updinfoSong.isValid()) { m_updinfoSong = asciiToUtf8(m_updinfoSong.toANSI(true)); } if (streamData::validateTitle(m_updinfoSong)) { titleMsg = ("[ADMINCGI sid=" + tos(m_sid) + "] Title updated [" + m_updinfoSong + "]"); } else { WLOG("Title update rejected - value not allowed: " + m_updinfoSong, LOG_NAME, m_sid); m_updinfoSong = info.m_currentSong; } } else { if (!info.m_currentSong.empty()) { titleMsg = ("[ADMINCGI sid=" + tos(m_sid) + "] Title cleared"); } } if (!m_updinfoURL.empty()) { urlMsg = ("[ADMINCGI sid=" + tos(m_sid) + "] Song url updated [" + m_updinfoURL + "]"); } else { if (!info.m_currentURL.empty()) { // TODO if there's a custom image then indicate using it or do not show this urlMsg = ("[ADMINCGI sid=" + tos(m_sid) + "] Song url cleared"); } } if (!m_updinfoDJ.empty()) { djMsg = ("[ADMINCGI sid=" + tos(m_sid) + "] DJ name updated [" + m_updinfoDJ + "]"); } else { if (!info.m_streamUser.empty()) { djMsg = ("[ADMINCGI sid=" + tos(m_sid) + "] DJ name cleared"); } } if (!m_updinfoNext.empty()) { // use this as a way to try to ensure we've got a utf-8 // encoded title to improve legacy source title support if (!m_updinfoNext.isValid()) { m_updinfoNext = asciiToUtf8(m_updinfoNext.toANSI(true)); } if (streamData::validateTitle(m_updinfoNext)) { titleMsg = ("[ADMINCGI sid=" + tos(m_sid) + "] Next title updated [" + m_updinfoNext + "]"); } else { WLOG("[ADMINCGI sid=" + tos(m_sid) + "] Next title update rejected - value not allowed: " + m_updinfoNext); m_updinfoNext = info.m_comingSoon; } } else { if (!info.m_comingSoon.empty()) { nextMsg = ("[ADMINCGI sid=" + tos(m_sid) + "] Next title cleared"); } } streamData *sd = streamData::accessStream(m_sid); if (sd) { utf8 sourceIdent = (!m_userAgent.empty() ? m_userAgent : utf8("Legacy / Unknown")); sd->updateSourceIdent(sourceIdent, sd->isRelayStream(m_sid)); sd->addSc1MetadataAtCurrentPosition(LOGNAME, m_updinfoSong, m_updinfoURL, m_updinfoNext); sd->updateStreamUser(m_updinfoDJ); // this will call streamData::releaseStream(..) streamData::streamClientLost(LOGNAME, sd, m_sid); // we'll output any earlier generated messages now that we've sent // the details to be used as otherwise they were being reported as // ok which if there was an issue (e.g. bad sid#) was confusing if (!titleMsg.empty()) { ILOG(titleMsg, LOG_NAME, m_sid); } if (!nextMsg.empty()) { ILOG(nextMsg, LOG_NAME, m_sid); } if (!urlMsg.empty()) { ILOG(urlMsg, LOG_NAME, m_sid); } if (!djMsg.empty()) { ILOG(djMsg, LOG_NAME, m_sid); } } else { WLOG("[ADMINCGI sid=" + tos(m_sid) + "] Metadata update rejected as the stream does not exist", LOG_NAME, m_sid); } m_updinfoSong.clear(); m_updinfoURL.clear(); m_updinfoDJ.clear(); sendMessageAndClose(MSG_200); } /// update the metatdata in the shoutcast ring buffer void protocol_admincgi::state_UpdateXMLMetadata() throw(exception) { DEBUG_LOG(utf8(LOGNAME) + __FUNCTION__); if (!m_updinfoSong.empty()) { ILOG("XML title update [" + m_updinfoSong + "]", LOG_NAME, m_sid); streamData *sd = streamData::accessStream(m_sid); if (sd) { vector<__uint8> assembledData; assembledData.insert(assembledData.end(), m_updinfoSong.begin(), m_updinfoSong.end()); utf8 sourceIdent = (!m_userAgent.empty() ? m_userAgent : utf8("Legacy / Unknown")); sd->updateSourceIdent(sourceIdent); sd->addUvoxMetadataAtCurrentPosition(MSG_METADATA_XML_NEW, assembledData); // this will call streamData::releaseStream(..) streamData::streamClientLost(LOGNAME, sd, m_sid); } else { WLOG("XML title update rejected as stream #" + tos(m_sid) + " does not exist", LOG_NAME, m_sid); } m_updinfoSong.clear(); m_updinfoURL.clear(); } sendMessageAndClose(MSG_200); } void protocol_admincgi::state_ConfirmPassword() throw(std::exception) { DEBUG_LOG(utf8(LOGNAME) + __FUNCTION__); // this will see if we've been refered from the base admin.cgi and will allow through // if the password / auth matches with the key admin account so the page is useable. utf8::size_type pos; if ((pos = m_referer.rfind(utf8("admin.cgi"))) != utf8::npos) { m_referer = m_referer.substr(pos); } const utf8& mode = mapGet(m_httpRequestInfo.m_QueryParameters, "mode", (utf8)""); const bool updinfo = (mode == "updinfo"); const bool viewxml = (mode == "viewxml"); const bool viewjson = (mode == "viewjson"); const bool _register = (mode == "register"); // on the root admin summary page we can only allow referer admin through if looking at the stats const bool adminRefer = (m_referer.find(utf8("admin.cgi")) == 0 || m_referer.find(utf8("admin.cgi?sid=0")) == 0); int okReferer = (adminRefer && (viewxml || viewjson || _register)); // on the root admin summary page we can check if it's an authhash action and allow // through if things match up + also allow updinfo (not ideal but maintains legacy) if ((!okReferer && ((mode == "manualauthhash") || _register)) || updinfo) { if (!m_referer.empty()) { okReferer = (m_referer.find(utf8("admin.cgi?sid=" + tos(m_sid) + "&mode=")) != utf8::npos); } } DEBUG_LOG(LOGNAME "Referrer status: " + tos(okReferer)); // fixed in Build 18 to revert to the master passwords if there's nothing for the stream utf8 streamPassword = gOptions.stream_password(m_sid); if (!gOptions.read_stream_password(m_sid) && streamPassword.empty()) { streamPassword = gOptions.password(); } utf8 streamAdminPassword = gOptions.stream_adminPassword(m_sid); if (!gOptions.read_stream_adminPassword(m_sid) && streamAdminPassword.empty()) { streamAdminPassword = gOptions.adminPassword(); } // this is so we can allow source password connections access to the // mode=viewxml&page=1 (/stats) and mode=viewxml&page=4 (/played) so // we maintain compatibility with 1.x DNAS which only had these ones int page = 0; bool compat_mode = false; if (viewxml || viewjson) { page = mapGet(m_httpRequestInfo.m_QueryParameters, "page", (int)page); // this will act like /stats or / played (or both // if page=0)so as to better emulate the 1.x DNAS if ((page == 0) || (page == 1) || (page == 4)) { compat_mode = true; } } bool proceed = (((!streamPassword.empty() && (updinfo || compat_mode) && (m_password == streamPassword)) || (!streamAdminPassword.empty() && (m_password == streamAdminPassword)) || (!gOptions.adminPassword().empty() && (m_password == gOptions.adminPassword())))); DEBUG_LOG(LOGNAME "Password status: " + tos(proceed)); if (proceed) { const streamData::streamID_t this_sid = (m_zeroSID || m_noSID ? 0 : m_sid); const utf8& p1 = mapGet(m_httpRequestInfo.m_QueryParameters, mode, (utf8)""); DEBUG_LOG(LOGNAME "Requested mode: " + (!mode.empty() ? mode : "Not Specified")); if (updinfo) { m_updinfoSong = stripWhitespace(mapGet(m_httpRequestInfo.m_QueryParameters, "song", (utf8)"")); m_updinfoURL = stripWhitespace(mapGet(m_httpRequestInfo.m_QueryParameters, "url", (utf8)"")); m_updinfoDJ = stripWhitespace(mapGet(m_httpRequestInfo.m_QueryParameters, "dj", (utf8)"")); m_updinfoNext = stripWhitespace(mapGet(m_httpRequestInfo.m_QueryParameters, "next", (utf8)"")); m_state = ((m_updinfoSong.find(utf8("streamUpdate(m_sid, (utf8)"", sd->streamMaxUser(), sd->streamMaxBitrate(), sd->streamMinBitrate()); sd->releaseStream(); } } } else { connected = true; // sanity checks to ensure that we're not re-adding when there is one, etc streamData::streamInfo info; streamData::extraInfo extra; if (!streamData::getStreamInfo(m_sid, info, extra)) { info.m_authHash = gOptions.stream_authHash(m_sid); } if (connected == true || extra.isRelay) { mode_register(m_sid, info); } } if (connected == false) { sendMessageAndClose(redirect("admin.cgi?sid=" + tos(this_sid), SHRINK)); } } } else if (mode == "listeners") { mode_listeners(this_sid); } else if (mode == "kicksrc") { if (m_noSID) { if (adminRefer) { sendMessageAndClose(redirect("index.html", SHRINK)); } else { sendMessageAndClose(MSG_200); } } else { // kick source off system streamData::killStreamSource(m_sid); if (!m_referer.empty()) { utf8 check = ("admin.cgi?sid=" + tos(m_sid)); // if the referer is the server summary page then we need to go back to it sendMessageAndClose(redirect((m_referer == check ? check : "admin.cgi?sid=0"), SHRINK)); } else { sendMessageAndClose(MSG_200); } } } else if (mode == "kickdst" && (!p1.empty())) { if (m_noSID) { if (adminRefer) { sendMessageAndClose(redirect("index.html", SHRINK)); } else { sendMessageAndClose(MSG_200); } } else { mode_kickdst(m_sid, p1); } } else if (mode == "viewban") { mode_viewban(this_sid); } else if (mode == "bandst" && (!p1.empty())) { const int mask = mapGet(m_httpRequestInfo.m_QueryParameters, "banmsk", (int)255); mode_ban(this_sid, p1, mask); } else if (mode == "banip") { const utf8 &ip1 = mapGet(m_httpRequestInfo.m_QueryParameters, "ip1", (utf8)""), &ip2 = mapGet(m_httpRequestInfo.m_QueryParameters, "ip2", (utf8)""), &ip3 = mapGet(m_httpRequestInfo.m_QueryParameters, "ip3", (utf8)""), &ip4 = mapGet(m_httpRequestInfo.m_QueryParameters, "ip4", (utf8)""), &mask = mapGet(m_httpRequestInfo.m_QueryParameters, "banmsk", (utf8)""); if (ip1.empty() || ip2.empty() || ip3.empty() || ip4.empty() || mask.empty()) { if ((m_referer != utf8("admin.cgi?sid=" + tos(this_sid) + "&mode=viewban")) && (m_referer != utf8("admin.cgi?mode=viewban"))) { sendMessageAndClose(MSG_STD200 + utf8(!HEAD_REQUEST ? "" "Shoutcast Server" "Invalid resource" : "")); } else { if (!m_referer.empty()) { sendMessageAndClose(redirect("admin.cgi?sid=" + tos(this_sid) + "&mode=viewban", SHRINK)); } else { sendMessageAndClose(MSG_200); } } } else { mode_ban(this_sid, (ip1 + "." + ip2 + "." + ip3 + "." + ip4), strtol((const char *)mask.c_str(),0,10)); } } else if (mode == "unbandst") { const utf8 &ip = mapGet(m_httpRequestInfo.m_QueryParameters, "bandst", (utf8)""), &mask = mapGet(m_httpRequestInfo.m_QueryParameters, "banmsk", (utf8)""); if (ip.empty() || mask.empty()) { if ((m_referer != utf8("admin.cgi?sid=" + tos(this_sid) + "&mode=viewban")) && (m_referer != utf8("admin.cgi?mode=viewban"))) { sendMessageAndClose(MSG_STD200 + utf8(!HEAD_REQUEST ? "" "Shoutcast Server" "Invalid resource" : "")); } else { if (!m_referer.empty()) { sendMessageAndClose(redirect("admin.cgi?sid=" + tos(this_sid) + "&mode=viewban", SHRINK)); } else { sendMessageAndClose(MSG_200); } } } else { mode_unban(this_sid, ip, strtol((const char *)mask.c_str(), 0, 10)); } } else if (mode == "viewrip") { mode_viewrip(this_sid); } else if (mode == "ripip") { const utf8 &ip1 = mapGet(m_httpRequestInfo.m_QueryParameters, "ip1", (utf8)""), &ip2 = mapGet(m_httpRequestInfo.m_QueryParameters, "ip2", (utf8)""), &ip3 = mapGet(m_httpRequestInfo.m_QueryParameters, "ip3", (utf8)""), &ip4 = mapGet(m_httpRequestInfo.m_QueryParameters, "ip4", (utf8)""); if (ip1.empty() || ip2.empty() || ip3.empty() || ip4.empty()) { // see if we've got a host add attempt and handle as appropriately if (m_hostIP.empty()) { if ((m_referer != utf8("admin.cgi?sid=" + tos(m_sid) + "&mode=viewrip")) && (m_referer != utf8("admin.cgi?mode=viewrip"))) { sendMessageAndClose(MSG_STD200 + utf8(!HEAD_REQUEST ? "" "Shoutcast Server" "Invalid resource" : "")); } else { if (!m_referer.empty()) { sendMessageAndClose(redirect("admin.cgi?sid=" + tos(m_sid) + "&mode=viewrip", SHRINK)); } else { sendMessageAndClose(MSG_200); } } } else { const utf8 &raw = mapGet(m_httpRequestInfo.m_QueryParameters, "ripdstraw", (utf8)""); mode_rip(this_sid, m_hostIP, raw); } } else { const utf8 &raw = mapGet(m_httpRequestInfo.m_QueryParameters, "ripdstraw", (utf8)""); mode_rip(this_sid, (ip1 + "." + ip2 + "." + ip3 + "." + ip4), raw); } } else if (mode == "unripdst") { const utf8 &ip = mapGet(m_httpRequestInfo.m_QueryParameters, "ripdst", (utf8)""), &raw = mapGet(m_httpRequestInfo.m_QueryParameters, "ripdstraw", (utf8)""); if (!isAddress(ip)) { if ((m_referer != utf8("admin.cgi?sid=" + tos(m_sid) + "&mode=viewrip")) && (m_referer != utf8("admin.cgi?mode=viewrip"))) { sendMessageAndClose(MSG_STD200 + utf8(!HEAD_REQUEST ? "" "Shoutcast Server" "Invalid resource" : "")); } else { if (!m_referer.empty()) { sendMessageAndClose(redirect("admin.cgi?sid=" + tos(m_sid) + "&mode=viewrip", SHRINK)); } else { sendMessageAndClose(MSG_200); } } } else { mode_unrip(this_sid, ip, raw); } } else if (mode == "ripdst" && (!p1.empty())) { const utf8 &raw = mapGet(m_httpRequestInfo.m_QueryParameters, "ripdstraw", (utf8)""); mode_rip(this_sid, p1, raw); } else if (mode == "viewagent") { mode_viewagent(this_sid); } else if (mode == "unagent") { const utf8 &agent = mapGet(m_httpRequestInfo.m_QueryParameters, "agent", (utf8)""); if (agent.empty()) { if ((m_referer != utf8("admin.cgi?sid=" + tos(m_sid) + "&mode=viewagent")) && (m_referer != utf8("admin.cgi?mode=viewagent"))) { sendMessageAndClose(MSG_STD200 + utf8(!HEAD_REQUEST ? "" "Shoutcast Server" "Invalid resource" : "")); } else { sendMessageAndClose(redirect("admin.cgi?sid=" + tos(m_sid) + "&mode=viewagent", SHRINK)); } } else { mode_unagent(this_sid, agent); } } else if (mode == "agent") { const utf8 &agent = mapGet(m_httpRequestInfo.m_QueryParameters, "agent", (utf8)""); mode_agent(this_sid, agent); } else if (mode == "resetxml") { if (m_noSID) { if (adminRefer) { sendMessageAndClose(redirect("index.html", SHRINK)); } else { sendMessageAndClose(MSG_200); } } else { stats::resetStats(m_sid); if (!m_referer.empty()) { sendMessageAndClose(redirect("admin.cgi?sid=" + tos(m_sid), SHRINK)); } else { sendMessageAndClose(MSG_200); } } } else if (mode == "clearcache") { // TODO consider clearing out the intro / backup files as ell as part of this... // clear out all cached resource copies gOptions.m_crossdomainStr.clear(); gOptions.m_crossdomainStrGZ.clear(); gOptions.m_shoutcastSWFStr.clear(); gOptions.m_shoutcastSWFStrGZ.clear(); gOptions.m_robotsTxtBody.clear(); gOptions.m_robotsTxtBodyGZ.clear(); gOptions.m_faviconBody.clear(); gOptions.m_faviconBodyGZ.clear(); gOptions.m_favIconTime = 0; gOptions.m_styleCustomStr.clear(); gOptions.m_styleCustomStrGZ.clear(); gOptions.m_styleCustomHeaderTime = 0; DeleteAllCaches(); last_update_check = 0; ILOG(LOGNAME "Cleared resource cache(s)."); if (adminRefer) { sendMessageAndClose(redirect("admin.cgi", SHRINK)); } else { sendMessageAndClose(MSG_200); } } else if (mode == "bannedlist") { reloadBanLists(); if (adminRefer) { sendMessageAndClose(redirect("admin.cgi", SHRINK)); } else { sendMessageAndClose(MSG_200); } } else if (mode == "reservelist") { reloadRipLists(); if (adminRefer) { sendMessageAndClose(redirect("admin.cgi", SHRINK)); } else { sendMessageAndClose(MSG_200); } } else if (mode == "adminlist") { reloadAdminAccessList(); if (adminRefer) { sendMessageAndClose(redirect("admin.cgi", SHRINK)); } else { sendMessageAndClose(MSG_200); } } else if (mode == "useragentlist") { reloadAgentLists(); if (adminRefer) { sendMessageAndClose(redirect("admin.cgi", SHRINK)); } else { sendMessageAndClose(MSG_200); } } else if (mode == "reload") { const int force = mapGet(m_httpRequestInfo.m_QueryParameters, "force", (int)0); if (adminRefer) { sendMessageAndClose(redirect((reloadConfig(force) == true ? "admin.cgi?sid=0&refresh=3" : "admin.cgi?sid=0"), SHRINK)); } else { reloadConfig(force); sendMessageAndClose(MSG_200); } } else if (mode == "viewlog") { if (m_noSID) { const utf8 &server = mapGet(m_httpRequestInfo.m_QueryParameters, "server", (utf8)""); if (!server.empty() && ((server == logId) || (server == logTailId))) { mode_viewlog(m_sid, (p1 == "tail"), (p1 == "save"), true); } else { sendMessageAndClose(redirect("index.html", SHRINK)); } } else { mode_viewlog(m_sid, (p1 == "tail"), (p1 == "save"), false); } } else if (mode == "history") { if (m_noSID) { if (adminRefer) { sendMessageAndClose(redirect("index.html", SHRINK)); } else { sendMessageAndClose(MSG_200); } } else { const utf8 &type = mapGet(m_httpRequestInfo.m_QueryParameters, "type", (utf8)""); const bool json = (type == "json"), xml = (type == "xml"); if (!json && !xml) { mode_history(m_sid); } else { utf8 header, body; if (json) { const utf8& callback = mapGet(m_httpRequestInfo.m_QueryParameters, "callback", (utf8)""); body = protocol_HTTPStyle::getPlayedJSON(m_sid, header, callback, true); } else { body = protocol_HTTPStyle::getPlayedXML(m_sid, header, true); } COMPRESS(header, body); header += "Content-Length:" + tos(body.size()) + "\r\n\r\n"; sendMessageAndClose(header + (!HEAD_REQUEST ? body : "")); } } } else if (mode == "art") { if (m_noSID) { if (adminRefer) { sendMessageAndClose(redirect("index.html", SHRINK)); } else { sendMessageAndClose(MSG_200); } } else { mode_art(m_sid, (p1 == "playing")); } } else if (mode == "manualauthhash") { bool handled = false; const utf8 &authhash = mapGet(m_httpRequestInfo.m_QueryParameters, "authhash", (utf8)""); // make sure that we're only allow valid values if (authhash.empty() || yp2::isValidAuthhash(authhash)) { bool idHandled = false; if (gOptions.editConfigFileEntry(m_sid, gOptions.confFile(), authhash, "", true, handled, idHandled, true) == false) { handled = false; } // changed in b69 to force the change through even if there is a config saving issue // as there are cases where the authhash is gone but only updating on success would // end up in the inability to start things over again e.g. with CentroCast v3 quirks gOptions.setOption(utf8("streamauthhash_" + tos(m_sid)), authhash); } streamData *sd = streamData::accessStream(m_sid); if (sd) { if (sd->isSourceConnected(m_sid)) { utf8 oldAuthhash = sd->streamAuthhash(); sd->streamUpdate(m_sid, authhash, sd->streamMaxUser(), sd->streamMaxBitrate(), sd->streamMinBitrate()); ILOG(gOptions.logSectionName() + "Changed authhash for stream #" + tos(m_sid) + " to " + (authhash.empty() ? "empty" : authhash) + " [was " + (oldAuthhash.empty() ? "empty" : oldAuthhash) + "]"); } sd->releaseStream(); } // now attempt to update internal states as appropriate if all went ok if (handled == true) { sendMessageAndClose("HTTP/1.1 200 OK\r\n" "Content-Type:text/plain\r\n" "Content-Length:5\r\n" "Cache-Control:no-cache\r\n" "Access-Control-Allow-Origin:*\r\n" "Connection:close\r\n\r\n200\r\n"); } else { utf8 message = "Error saving changes to the configuration file.
" "Check that you have write access and the
" "specified configuration file still exists.

" "The requested authhash change was applied."; utf8 header = "HTTP/1.1 667\r\n" "Content-Type:text/plain\r\n" "Access-Control-Allow-Origin:*\r\n" "Connection:close\r\n"; COMPRESS(header, message); header += "Content-Length:" + tos(message.size()) + "\r\n\r\n"; sendMessageAndClose(header + (!HEAD_REQUEST ? message : "")); } } else if (mode == "rotate") { const utf8 &files = mapGet(m_httpRequestInfo.m_QueryParameters, "files", (utf8)""), &rotateType = (!(files == "log" || files == "w3c") ? "all " : (files == "log" ? "": "W3C ")); ILOG(LOGNAME "Rotating " + rotateType + "log file(s)"); if ((files == "log") || (files == "")) { ROTATE; printUpdateMessage(); if (m_logFile) { ::fclose(m_logFile); m_logFile = 0; } } // and now rotate the w3c logs (going upto the configured number of old copies to be like the log rotate) rotatew3cFiles(files); ILOG(LOGNAME "Rotated " + rotateType + "log file(s) [PID: " + tos(getpid()) + "]"); if (adminRefer) { sendMessageAndClose(redirect("admin.cgi", SHRINK)); } else { sendMessageAndClose(MSG_200); } } else if (mode == "startrelay") { if (m_noSID) { if (adminRefer) { sendMessageAndClose(redirect("index.html", SHRINK)); } else { sendMessageAndClose(MSG_200); } } else { // only attempt a source relay connection if it is not signaled // as active or pending to prevent multiple attempts being run. bool noEntry = false, active = (streamData::isRelayActive(m_sid, noEntry) == 1); if (!active) { config::streamConfig stream; if (gOptions.getStreamConfig(stream, m_sid)) { restartRelay(stream); } } if (!m_referer.empty()) { utf8 check = ("admin.cgi?sid=" + tos(m_sid)); // if the referer is the server summary page then we need to go back to it sendMessageAndClose(redirect((m_referer == check ? check : "admin.cgi?sid=0") + (!active ? "&refresh=3" : ""), SHRINK)); } else { sendMessageAndClose(MSG_200); } } } else if (mode == "startrelays") { bool refresh = false; config::streams_t streams; gOptions.getStreamConfigs(streams); for (config::streams_t::const_iterator i = streams.begin(); i != streams.end(); ++i) { if (!(*i).second.m_relayUrl.url().empty() && !streamData::isSourceConnected((*i).first)) { // only attempt a source relay connection if it is not signaled // as active or pending to prevent multiple attempts being run. bool noEntry = false, active = (streamData::isRelayActive((*i).first, noEntry) == 1); if (!active) { ILOG(gOptions.logSectionName() + "Starting source for stream #" + tos((*i).first) + "."); restartRelay((*i).second); refresh = true; } } } if (adminRefer) { sendMessageAndClose(redirect("admin.cgi?sid=0" + (refresh ? "&refresh=3" : (utf8)""), SHRINK)); } else { sendMessageAndClose(MSG_200); } } else if (mode == "kicksources") { bool refresh = false; streamData::streamIDs_t streamIds = streamData::getStreamIds(true); if (!streamIds.empty()) { for (streamData::streamIDs_t::const_iterator i = streamIds.begin(); i != streamIds.end(); ++i) { // kick source off system streamData::killStreamSource((*i)); refresh = true; } } if (adminRefer) { sendMessageAndClose(redirect("admin.cgi?sid=0" + (refresh ? "&refresh=1" : (utf8)""), SHRINK)); } else { sendMessageAndClose(MSG_200); } } else if(mode == "bandwidth") { const utf8 &type = mapGet(m_httpRequestInfo.m_QueryParameters, "type", (utf8)""); const bool json = (type == "json"), xml = (type == "xml"); if (!json && !xml) { const int refresh = mapGet(m_httpRequestInfo.m_QueryParameters, "refresh", 0); mode_bandwidth_html(refresh); } else { if (json) { const utf8 &callback = mapGet(m_httpRequestInfo.m_QueryParameters, "callback", (utf8)""); mode_bandwidth_json(callback); } else { mode_bandwidth_xml(); } } } else if (mode == "ypstatus") { const utf8 &type = mapGet(m_httpRequestInfo.m_QueryParameters, "type", (utf8)""); if ((type == "json")) { const utf8 &callback = mapGet(m_httpRequestInfo.m_QueryParameters, "callback", (utf8)""); mode_ypstatus_json(callback); } else { mode_ypstatus_xml(); } } else if (mode == "sources") { mode_sources(m_hostIP); } else if (mode == "adgroups") { mode_adgroups(); } else if (mode == "debug") { const utf8 &option = mapGet(m_httpRequestInfo.m_QueryParameters, "option", (utf8)""); if (!option.empty() && !adminRefer) { // prevent direct access to being able to edit this // and force a redirection to the non-param version sendMessageAndClose(redirect("admin.cgi?mode=debug", SHRINK)); return; } const utf8 &on_off = mapGet(m_httpRequestInfo.m_QueryParameters, "on", (utf8)""); mode_debug(option, (on_off == "true"), adminRefer); } else if (mode == "help") { mode_help(); } else if (mode == "config") { mode_config(); } #if 0 else if (mode == "logs") { mode_logs(result); } #endif else if (mode == "version") { // only allow from the admin page to avoid possible spamming sttempts if (adminRefer) { checkVersion(m_lastActivityTime); sendMessageAndClose(redirect("admin.cgi?sid=0&refresh=-3", SHRINK)); } else { sendMessageAndClose(MSG_200); } } else { if (m_noSID) { // do a sanity check so that we're only accessing this with the true adminpassword if (m_zeroSID) { mode_summary(mapGet(m_httpRequestInfo.m_QueryParameters, "refresh", 0)); } // not matching the master password so show the simple summary else { sendMessageAndClose(redirect("index.html", SHRINK)); } } else { const int refresh = mapGet(m_httpRequestInfo.m_QueryParameters, "refresh", 0); mode_none(m_sid, refresh); } } } else { sendMessageAndClose(MSG_AUTHFAILURE401 + utf8(!HEAD_REQUEST ? "UnauthorizedShoutcast " "Administrator" : "")); } } void protocol_admincgi::state_SendFileHeader() throw(std::exception) { if ((!m_saveLogFile && SHRINK) && compressDataStart(m_logFileBodyPrefix, &m_stream, (Bytef*)"sc_serv.log\0", false)) { m_logFileHeader += "Content-Encoding:gzip\r\n"; } m_logFileHeader += "\r\n"; m_outMsg = m_logFileHeader + (!m_saveLogFile ? tohex(m_logFileBodyPrefix.size()) + "\r\n" + m_logFileBodyPrefix + "\r\n" : ""); m_outBuffer = m_outMsg.c_str(); bandWidth::updateAmount(bandWidth::PRIVATE_WEB, (m_outBufferSize = (int)m_outMsg.size())); m_state = &protocol_admincgi::state_Send; m_nextState = &protocol_admincgi::state_SendFileContents; m_result.write(); if (m_tailLogFile) { m_result.read(fileno(m_logFile)); } } void protocol_admincgi::state_SendFileFooter() throw(std::exception) { if (SHRINK) { compressDataCont(m_logFileBodyFooter, &m_stream); } m_logFileBodyFooter = tohex(m_logFileBodyFooter.size()) + "\r\n" + m_logFileBodyFooter + "\r\n"; m_outBuffer = m_logFileBodyFooter.c_str(); bandWidth::updateAmount(bandWidth::PRIVATE_WEB, (m_outBufferSize = (int)m_logFileBodyFooter.size())); m_state = &protocol_admincgi::state_Send; m_nextState = &protocol_admincgi::state_SendFileEnd; m_result.write(); } void protocol_admincgi::state_SendFileEnd() throw(std::exception) { strncpy((char*)&(m_logFileBuffer[0]), "0\r\n\r\n", 1024); m_outBuffer = &(m_logFileBuffer[0]); m_outBufferSize = 5; m_nextState = &protocol_admincgi::state_Close; bandWidth::updateAmount(bandWidth::PRIVATE_WEB, m_outBufferSize); compressDataEnd(&m_stream); m_state = &protocol_admincgi::state_Send; m_nextState = &protocol_admincgi::state_Close; m_result.write(); } // this will only be receiving an already converted // string so no need to do the commented part again utf8 protocol_admincgi::escapeText(const std::vector &s) throw() { utf8 result; result.resize(0); bool inHead = false; int headCount = 0; const size_t amt = s.size(); for (size_t x = 0; x < amt; ++x) { if ((s[x] == '*') && (((x + 1) < amt) && (s[x + 1] == '*'))) { inHead = true; } else if (s[x] == '\n') { if (!inHead) { if ((((x + 1) < amt) && isdigit(s[x + 1]))) { if (inMsg) { result += ""; } result += "\n"; inMsg = false; } else { if (((x + 1) == amt)) { if (inMsg) { result += ""; } result += "\n"; inMsg = false; } else { result += "\n\t\t\t"; } } } else { ++headCount; } } else if ((s[x] == 'D') && (((x > 0) && (s[x - 1] == '\t')) || (!x && (lastChar == '\t')))) { result += "D"; inMsg = true; } else if ((s[x] == 'E') && (((x > 0) && (s[x - 1] == '\t')) || (!x && (lastChar == '\t')))) { result += "E"; inMsg = true; } else if ((s[x] == 'W') && (((x > 0) && (s[x - 1] == '\t')) || (!x && (lastChar == '\t')))) { result += "W"; inMsg = true; } else if ((s[x] == 'U') && (((x > 0) && (s[x-1] == '\t')) || (!x && (lastChar == '\t')))) { result += "U"; inMsg = true; } else if ((s[x] == 'I') && (((x > 0) && (s[x-1] == '\t')) || (!x && (lastChar == '\t')))) { if (!inHead) { result += "I"; inMsg = true; } } else { if (!inHead) { if (s[x] == '<') { result += "<"; } else if (s[x] == '>') { result += ">"; } else if (s[x] == '&') { result += "&"; } else if (s[x] == '\'') { result += "'"; } else if (s[x] == '"') { result += """; } else { result += s[x]; } } else { if (inHead && (headCount > 3) && (s[x] == '\t')) { inHead = false; headCount = 0; x += 5; } } } if (!s[x]) { break; } } lastChar = s[amt - 1]; return result; } void protocol_admincgi::state_SendFileContents() throw(std::exception) { m_logFileBuffer.clear(); m_logFileBuffer.resize(SEND_SIZE); const size_t amt = fread(&(m_logFileBuffer[0]), 1, (SEND_SIZE - 1), m_logFile); if (amt > 0) { static utf8 out; out.clear(); if (!m_saveLogFile) { m_logFileBuffer.resize(amt); out = escapeText(m_logFileBuffer); } else { out = utf8(&(m_logFileBuffer[0]), amt); } if (!out.empty()) { if (m_saveLogFile || (SHRINK)) { if (m_saveLogFile == 1) { if (compressDataStart(out, &m_stream, (Bytef*)m_logFileName.c_str())) { m_saveLogFile = 2; } } else { compressDataCont(out, &m_stream); } } out = tohex(out.size()) + "\r\n" + out + "\r\n"; m_outBuffer = out.c_str(); bandWidth::updateAmount(bandWidth::PRIVATE_WEB, (m_outBufferSize = (int)out.size())); m_nextState = &protocol_admincgi::state_SendFileContents; } else { m_nextState = (m_saveLogFile ? &protocol_admincgi::state_SendFileEnd : &protocol_admincgi::state_SendFileFooter); } m_state = &protocol_admincgi::state_Send; m_result.write(); if (m_tailLogFile) { m_result.timeout(1); m_result.read(fileno(m_logFile)); } } else if (ferror(m_logFile) || !m_logFile) { m_state = &protocol_admincgi::state_Close; m_result.run(); } else if (feof(m_logFile) && (!m_tailLogFile)) { if (m_saveLogFile) { static utf8 out; out.clear(); compressDataFinish(out, &m_stream); out = tohex(out.size()) + "\r\n" + out + "\r\n"; m_outBuffer = out.c_str(); bandWidth::updateAmount(bandWidth::PRIVATE_WEB, (m_outBufferSize = (int)out.size())); m_state = &protocol_admincgi::state_Send; m_nextState = &protocol_admincgi::state_SendFileEnd; } else { m_state = &protocol_admincgi::state_SendFileFooter; } m_result.write(); m_result.run(); } else { m_result.timeout(10); } } // display log void protocol_admincgi::mode_viewlog(const streamData::streamID_t sid, const bool tail, const bool save, const bool server) throw() { if (!save) { utf8 headerTitle = utf8(utf8("Server Log") + (tail ? " (Tailing)":"")); m_logFileHeader = MSG_NO_CLOSE_200; m_logFileBodyPrefix += (server ? getServerAdminHeader(headerTitle) : getStreamAdminHeader(sid, headerTitle)) + getUptimeScript() + (tail ? "
Showing log output from the current log " "position and onwards. Click here to view the complete log output or here " "to save the complete log output. When tailing the log output, there may be " "a delay before any new log output will appear and it may also stop updating if " "there is no log activity depending on configured timeouts. Reload the page if this " "should happens.
" : "") + getIEFlexFix() + "
";

		// doing to just make sure that the end of the output should be correct
		m_logFileBodyFooter = "\n
" + getfooterStr(); m_tailLogFile = tail; if (server || !(gOptions.read_stream_adminPassword(sid) && !gOptions.stream_adminPassword(sid).empty())) { const utf8 &file = mapGet(m_httpRequestInfo.m_QueryParameters, "file", gOptions.realLogFile()); m_logFile = uniFile::fopen(file, "r"); if (m_logFile) { m_logFileName = fileUtil::stripPath(file); m_logFileName.push_back('\0'); if (m_tailLogFile) { ::fseek(m_logFile, 0, SEEK_END); } } } else { utf8 body = m_logFileBodyPrefix + "Viewing Not Allowed With Current Permissions" + m_logFileBodyFooter; COMPRESS(m_logFileHeader, body); m_logFileHeader += "Content-Length:" + tos(body.size()) + "\r\n\r\n"; sendMessageAndClose(m_logFileHeader + (!HEAD_REQUEST ? body : "")); return; } if (!m_logFile) { utf8 body = m_logFileBodyPrefix + "Log File Not Found (" + stripWhitespace(errMessage()) + ")" + m_logFileBodyFooter; COMPRESS(m_logFileHeader, body); m_logFileHeader += "Content-Length:" + tos(body.size()) + "\r\n\r\n"; sendMessageAndClose(m_logFileHeader + (!HEAD_REQUEST ? body : "")); } else { m_logFileHeader += "Transfer-Encoding:chunked\r\n"; m_state = &protocol_admincgi::state_SendFileHeader; m_result.run(); } } else { if (server || !(gOptions.read_stream_adminPassword(sid) && !gOptions.stream_adminPassword(sid).empty())) { const utf8 &file = mapGet(m_httpRequestInfo.m_QueryParameters, "file", gOptions.realLogFile()); m_logFile = uniFile::fopen(file, "r"); if (m_logFile) { m_logFileName = fileUtil::stripPath(file); m_logFileHeader = "HTTP/1.1 200 OK\r\n" "Content-Type:application/x-gzip-compressed\r\n" "Content-Disposition:attachment;filename=\"" + m_logFileName + ".gz\"\r\n"; m_saveLogFile = true; } } else { sendMessageAndClose(MSG_HTTP403); return; } if (!m_logFile) { sendMessageAndClose(MSG_HTTP404); } else { m_logFileHeader += "Transfer-Encoding:chunked\r\n"; m_state = &protocol_admincgi::state_SendFileHeader; m_result.run(); } } } // shown played history (as is also shown on the public pages if enabled) void protocol_admincgi::mode_history(const streamData::streamID_t sid) throw() { utf8 header = MSG_NO_CLOSE_200, body = getStreamAdminHeader(sid, "Stream History") + "
" + protocol_HTTPStyle::getPlayedBody(sid) + "
" + getUptimeScript() + getIEFlexFix() + getfooterStr(); COMPRESS(header, body); header += "Content-Length:" + tos(body.size()) + "\r\n\r\n"; sendMessageAndClose(header + (!HEAD_REQUEST ? body : "")); } // remove an IP from the rip list void protocol_admincgi::mode_unrip(const streamData::streamID_t sid, const utf8 &ripAddr, const utf8 &rawIpAddr) throw() { utf8 msg; try { size_t stream_ID = ((gOptions.read_stream_ripFile(sid) && !gOptions.stream_ripFile(sid).empty()) ? sid : 0); if (isAddress(ripAddr)) { bool ret = g_ripList.remove(ripAddr,stream_ID,false), usingRaw = false; if (!ret && !rawIpAddr.empty()) { ret = g_ripList.remove(rawIpAddr,stream_ID,false); if (ret) usingRaw = true; } if (ret) { ILOG("[RIP] Removed `" + (!usingRaw ? ripAddr : rawIpAddr) + "' from " + (!stream_ID ? "global" : "sid=" + tos(stream_ID)) + " rip list"); if (gOptions.saveRipListOnExit()) { if (stream_ID && gOptions.read_stream_ripFile(stream_ID) && !gOptions.stream_ripFile(stream_ID).empty()) { g_ripList.save(gOptions.stream_ripFile(stream_ID),stream_ID); } else { g_ripList.save(gOptions.ripFile(),0); } } stats::updateRipClients(stream_ID, (!usingRaw ? ripAddr : rawIpAddr), false); } else { ILOG("[RIP] Unable to remove `" + ripAddr + "' from " + (!stream_ID ? "global" : "sid=" + tos(stream_ID)) + " rip list"); } } else { ILOG("[RIP] `" + ripAddr + "' is not a valid value. Skipping removing from the " + (!stream_ID ? "global" : "sid=" + tos(stream_ID)) + " rip list"); } msg = (!m_referer.empty() ? redirect("admin.cgi?sid=" + tos(sid) + (m_referer == "admin.cgi?sid=" + tos(sid) ? "" : "&mode=viewrip"), SHRINK) : MSG_200); } catch(const exception &ex) { msg = (!m_referer.empty() ? redirect("admin.cgi?sid=" + tos(sid) + (m_referer != utf8("admin.cgi?sid=" + tos(sid) + "&mode=viewrip") ? "" : "&mode=viewrip"), SHRINK) : MSG_200); ELOG(ex.what()); } sendMessageAndClose(msg); } // add an IP / hostname to the rip list void protocol_admincgi::mode_rip(const streamData::streamID_t sid, const utf8 &ripAddr, const utf8 &rawIpAddr) throw() { utf8 msg; try { const size_t stream_ID = ((gOptions.read_stream_ripFile(sid) && !gOptions.stream_ripFile(sid).empty()) ? sid : 0); if (isAddress(ripAddr)) { if (!g_ripList.find(ripAddr,stream_ID)) { bool added = g_ripList.add(ripAddr,stream_ID, true), usingRaw = false; if (!added && !rawIpAddr.empty()) { if (g_ripList.add(rawIpAddr,stream_ID, false)) { usingRaw = true; } } ILOG("[RIP] Added `" + (!usingRaw ? ripAddr : rawIpAddr) + "' to " + (!stream_ID ? "global" : "sid=" + tos(stream_ID)) + " rip list"); if (gOptions.saveRipListOnExit()) { if (stream_ID && gOptions.read_stream_ripFile(stream_ID) && !gOptions.stream_ripFile(stream_ID).empty()) { g_ripList.save(gOptions.stream_ripFile(stream_ID),stream_ID); } else { g_ripList.save(gOptions.ripFile(),0); } } stats::updateRipClients(stream_ID, (!usingRaw ? ripAddr : rawIpAddr), true); } else { ILOG("[RIP] `" + ripAddr + "' already in the " + (!stream_ID ? "global" : "sid=" + tos(stream_ID)) + " rip list"); } } else { ILOG("[RIP] `" + ripAddr + "' is not a valid value. Skipping adding to the " + (!stream_ID ? "global" : "sid=" + tos(stream_ID)) + " rip list"); } msg = (!m_referer.empty() ? redirect("admin.cgi?sid=" + tos(sid) + (m_referer == "admin.cgi?sid=" + tos(sid) ? "" : "&mode=viewrip"), SHRINK) : MSG_200); } catch(const exception &ex) { msg = (!m_referer.empty() ? redirect("admin.cgi?sid=" + tos(sid) + (m_referer != utf8("admin.cgi?sid=" + tos(sid) + "&mode=viewrip") ? "" : "&mode=viewrip"), SHRINK) : MSG_200); ELOG(ex.what()); } sendMessageAndClose(msg); } // show rip list void protocol_admincgi::mode_viewrip(const streamData::streamID_t sid) throw() { vector rip_list; g_ripList.get(rip_list,((gOptions.read_stream_ripFile(sid) && !gOptions.stream_ripFile(sid).empty()) ? sid : 0)); utf8 header = MSG_NO_CLOSE_200, headerTitle = (!sid ? "Server Reserved List" : "Stream Reserved List"), body = (!sid ? getServerAdminHeader(headerTitle) : getStreamAdminHeader(sid, headerTitle)) + "" "" "" "" "
"; if (rip_list.empty()) { body += " No Reserved Entries
"; } else { body += " Reserved Entry List:
    "; for (vector::const_iterator i = rip_list.begin(); i != rip_list.end(); ++i) { body += "
  1. " + aolxml::escapeXML((*i).m_numericIP) + "" + (!(*i).m_hostIP.empty() ? " (" + aolxml::escapeXML((*i).m_hostIP) + ")" : "") + " - remove"; } body += "
"; } body += "

" "" "" "" "" "" "" "" "" "
Reserve Connection Slot by IP
" "" "" "" "" "" "" "" "" "" "
Enter the IP address:
(example: 127.0.0.1)
" "" "" "." "." "." "" "
" " 
" "
" "

" "" "" "" "" "" "" "" "" "
Reserve Connection Slot by Host
" "" "" "" "" "" "" "" "" "" "
Enter the hostname:
(example: my.example.com)
" "" "" "" "
" " 
" "
" "
" + getUptimeScript() + getfooterStr(); COMPRESS(header, body); header += "Content-Length:" + tos(body.size()) + "\r\n\r\n"; sendMessageAndClose(header + (!HEAD_REQUEST ? body : "")); } // show ban lists void protocol_admincgi::mode_viewban(const streamData::streamID_t sid) throw() { vector ban_list; g_banList.get(ban_list,((gOptions.read_stream_banFile(sid) && !gOptions.stream_banFile(sid).empty()) ? sid : 0)); utf8 header = MSG_NO_CLOSE_200, headerTitle = (!sid ? "Server Ban List" : "Stream Ban List"), body = (!sid ? getServerAdminHeader(headerTitle) : getStreamAdminHeader(sid, headerTitle)) + "" "" "" "" "
"; if (ban_list.empty()) { body += " No Banned Entries
"; } else { body += " Ban Entry List:
    "; for (vector::const_iterator i = ban_list.begin(); i != ban_list.end(); ++i) { body += "
  1. " + aolxml::escapeXML((*i).m_numericIP) + "" + (!(*i).m_comment.empty() ? " : " + aolxml::escapeXML((*i).m_comment) + "" : "") + " - " + ((*i).m_mask == 255 ? "Single IP" : "Subnet") + " ban - " "remove"; } body += "
"; } body += "

" "" "" "" "" "" "" "" "" "" "
Ban a Single IP
" "" "" "" "" "" "" "" "" "" "
Enter the IP address:
(example: 127.0.0.1)
" "" "" "." "." "." "" "
" " 
" "
" "

" "" "" "" "" "" "" "" "" "
Ban an Entire Subnet
" "" "" "" "" "" "" "" "" "" "
Enter the Subnet address:
(example: 255.255.255)
" "" "" "." "." ".0-255" "" "
" " 
" "
" "
" + getUptimeScript() + getIEFlexFix() + getfooterStr(); COMPRESS(header, body); header += "Content-Length:" + tos(body.size()) + "\r\n\r\n"; sendMessageAndClose(header + (!HEAD_REQUEST ? body : "")); } // remove an IP and mask from the ban list void protocol_admincgi::mode_unban(const streamData::streamID_t sid, const utf8 &banAddr, const int banMask) throw() { utf8 msg; try { const size_t stream_ID = ((gOptions.read_stream_banFile(sid) && !gOptions.stream_banFile(sid).empty()) ? sid : 0); if (isAddress(banAddr)) { if (g_banList.remove(banAddr,banMask,stream_ID,false)) { ILOG("[BAN] Removed `" + banAddr + "/" + tos(banMask) + "' from " + (!stream_ID ? "global" : "sid=" + tos(stream_ID)) + " ban list"); if (gOptions.saveBanListOnExit()) { if (stream_ID && gOptions.read_stream_banFile(stream_ID) && !gOptions.stream_banFile(stream_ID).empty()) { g_banList.save(gOptions.stream_banFile(stream_ID),stream_ID); } else { g_banList.save(gOptions.banFile(),0); } } } else { ILOG("[BAN] Unable to remove `" + banAddr + "' from " + (!stream_ID ? "global" : "sid=" + tos(stream_ID)) + " ban list"); } } else { ILOG("[BAN] `" + banAddr + "' is not a valid value. Skipping removing from the " + (!stream_ID ? "global" : "sid=" + tos(stream_ID)) + " ban list"); } msg = (!m_referer.empty() ? redirect("admin.cgi?sid=" + tos(sid) + (m_referer == "admin.cgi?sid=" + tos(sid) ? "" : "&mode=viewban"), SHRINK) : MSG_200); } catch(const exception &ex) { msg = (!m_referer.empty() ? redirect("admin.cgi?sid=" + tos(sid) + (m_referer != utf8("admin.cgi?sid=" + tos(sid) + "&mode=viewban") ? "" : "&mode=viewban"), SHRINK) : MSG_200); ELOG(ex.what()); } sendMessageAndClose(msg); } // add an IP and mask to the ban list void protocol_admincgi::mode_ban(const streamData::streamID_t sid, const utf8 &banAddrs, const int banMask) throw() { utf8 msg; try { const size_t stream_ID = ((gOptions.read_stream_banFile(sid) && !gOptions.stream_banFile(sid).empty()) ? sid : 0); if (!g_banList.find(banAddrs,banMask,stream_ID)) { g_banList.add(banAddrs,banMask,"",stream_ID); ILOG("[BAN] Added `" + banAddrs + "/" + tos(banMask) + "' to " + (!stream_ID ? "global" : "sid=" + tos(stream_ID)) + " ban list"); if (gOptions.saveBanListOnExit()) { if (stream_ID && gOptions.read_stream_banFile(stream_ID) && !gOptions.stream_banFile(stream_ID).empty()) { g_banList.save(gOptions.stream_banFile(stream_ID),stream_ID); } else { g_banList.save(gOptions.banFile(),0); } } } else { ILOG("[BAN] `" + banAddrs + "' already in the " + (!stream_ID ? "global" : "sid=" + tos(stream_ID)) + " ban list"); } msg = (!m_referer.empty() ? redirect("admin.cgi?sid=" + tos(sid) + (m_referer == "admin.cgi?sid=" + tos(sid) ? "" : "&mode=viewban"), SHRINK) : MSG_200); } catch(const exception &ex) { msg = (!m_referer.empty() ? redirect("admin.cgi?sid=" + tos(sid) + (m_referer != utf8("admin.cgi?sid=" + tos(sid) + "&mode=viewban") ? "" : "&mode=viewban"), SHRINK) : MSG_200); ELOG(ex.what()); } // additionally when a ban happens then we attempt to kick the client(s) at the same time const utf8 &p1 = mapGet(m_httpRequestInfo.m_QueryParameters, "kickdst", (utf8)""); if (!p1.empty()) { // split out multiple clients to kick (split by a ,) std::vector addrs = tokenizer(p1, ','); for (vector::const_iterator i = addrs.begin(); i != addrs.end(); ++i) { if ((*i).find(utf8(".")) == utf8::npos) { stats::kickClient(sid, atoi((*i).hideAsString().c_str())); } else { stats::kickClient(sid, (*i)); } } } sendMessageAndClose(msg); } // remove an agent from the agent list void protocol_admincgi::mode_unagent(const streamData::streamID_t sid, const utf8 &agent) throw() { utf8 msg; try { const size_t stream_ID = ((gOptions.read_stream_agentFile(sid) && !gOptions.stream_agentFile(sid).empty()) ? sid : 0); bool ret = g_agentList.remove(agent,stream_ID,false); if (!ret && !agent.empty()) { ret = g_agentList.remove(agent,stream_ID,false); } if (ret) { ILOG("[AGENT] Removed `" + agent + "' from " + (!stream_ID ? "global" : "sid=" + tos(stream_ID)) + " user agent list"); if (gOptions.saveAgentListOnExit()) { if (stream_ID && gOptions.read_stream_agentFile(stream_ID) && !gOptions.stream_agentFile(stream_ID).empty()) { g_agentList.save(gOptions.stream_agentFile(stream_ID),stream_ID); } else { g_agentList.save(gOptions.agentFile(),0); } } } else { ILOG("[AGENT] Unable to remove `" + agent + "' from " + (!stream_ID ? "global" : "sid=" + tos(stream_ID)) + " user agent list"); } msg = (!m_referer.empty() ? redirect("admin.cgi?sid=" + tos(sid) + (m_referer == "admin.cgi?sid=" + tos(sid) ? "" : "&mode=viewagent"), SHRINK) : MSG_200); } catch(const exception &ex) { msg = (!m_referer.empty() ? redirect("admin.cgi?sid=" + tos(sid) + (m_referer != utf8("admin.cgi?sid=" + tos(sid) + "&mode=viewagent") ? "" : "&mode=viewagent"), SHRINK) : MSG_200); ELOG(ex.what()); } sendMessageAndClose(msg); } // add an IP / hostname to the user agent list void protocol_admincgi::mode_agent(const streamData::streamID_t sid, const utf8 &agent) throw() { utf8 msg; try { const size_t stream_ID = ((gOptions.read_stream_agentFile(sid) && !gOptions.stream_agentFile(sid).empty()) ? sid : 0); if (!g_agentList.find(agent,stream_ID)) { if (g_agentList.add(agent, stream_ID, true)) { ILOG("[AGENT] Added `" + agent + "' to " + (!stream_ID ? "global" : "sid=" + tos(stream_ID)) + " user agent list"); } stats::kickClientList_t kick_data; stats::getClientDataForKicking(stream_ID, kick_data); for (stats::kickClientList_t::const_iterator i = kick_data.begin(); i != kick_data.end(); ++i) { if (!(*i)->m_kicked && ((*i)->m_userAgent == agent)) { stats::kickClient(sid, (*i)->m_unique); } delete (*i); } if (gOptions.saveAgentListOnExit()) { if (stream_ID && gOptions.read_stream_agentFile(stream_ID) && !gOptions.stream_agentFile(stream_ID).empty()) { g_agentList.save(gOptions.stream_agentFile(stream_ID),stream_ID); } else { g_agentList.save(gOptions.agentFile(),0); } } } else { ILOG("[AGENT] `" + agent + "' already in the " + (!stream_ID ? "global" : "sid=" + tos(stream_ID)) + " user agent list"); } msg = (!m_referer.empty() ? redirect("admin.cgi?sid=" + tos(sid) + (m_referer == "admin.cgi?sid=" + tos(sid) ? "" : "&mode=viewagent"), SHRINK) : MSG_200); } catch(const exception &ex) { msg = (!m_referer.empty() ? redirect("admin.cgi?sid=" + tos(sid) + (m_referer != utf8("admin.cgi?sid=" + tos(sid) + "&mode=viewagent") ? "" : "&mode=viewagent"), SHRINK) : MSG_200); ELOG(ex.what()); } sendMessageAndClose(msg); } // show agent list void protocol_admincgi::mode_viewagent(const streamData::streamID_t sid) throw() { vector agent_list; g_agentList.get(agent_list,((gOptions.read_stream_agentFile(sid) && !gOptions.stream_agentFile(sid).empty()) ? sid : 0)); utf8 header = MSG_NO_CLOSE_200, headerTitle = (!sid ? "Server Blocked User Agent List" : "Stream Blocked User Agent List"), body = (!sid ? getServerAdminHeader(headerTitle, 0, "", 2) : getStreamAdminHeader(sid, headerTitle, 0, true)) + "" "" "" "" "
"; if (agent_list.empty()) { body += " No Blocked User Agents
"; } else { body += " Blocked User Agent List:
    "; for (vector::const_iterator i = agent_list.begin(); i != agent_list.end(); ++i) { const streamData::source_t clientType = ((streamData::source_t)streamData::UNKNOWN); body += "
  1. " + getClientImage(streamData::getClientType(clientType, stringUtil::toLower((*i).m_agent))) + " " + aolxml::escapeXML((*i).m_agent) + " - remove"; } body += "
"; } body += "

" "" "" "" "" "" "" "" "" "
Block User Agent
" "" "" "" "" "" "" "" "" "" "
Enter the user agent:
(example: streamripper)
" "" "" "" "
" " 
" "
" "
" + getUptimeScript() + getIEFlexFix() + getfooterStr(); COMPRESS(header, body); header += "Content-Length:" + tos(body.size()) + "\r\n\r\n"; sendMessageAndClose(header + (!HEAD_REQUEST ? body : "")); } // kick client(s) void protocol_admincgi::mode_kickdst(const streamData::streamID_t sid, const utf8 &kickAddrs) throw() { bool refresh = false; if (kickAddrs == "all") { // check if this is set to kick all refresh = stats::kickAllClients(sid); } else if (kickAddrs == "duplicates") { // check if this is set to kick all // duplicates (doing oldest first). refresh = stats::kickDuplicateClients(sid); } else { // split out multiple clients to kick (split by a ,) std::vector addrs = tokenizer(kickAddrs,','); for (vector::const_iterator i = addrs.begin(); i != addrs.end(); ++i) { if ((*i).find(utf8(".")) == utf8::npos) { stats::kickClient(sid, atoi((*i).hideAsString().c_str())); } else { stats::kickClient(sid,(*i)); } } } if (!m_referer.empty()) { const utf8 check = ("admin.cgi?sid=" + tos(sid)); // if the referer is the server summary page then we need to go back to it sendMessageAndClose(redirect((m_referer == check ? check : "admin.cgi?sid=0") + (refresh ? "&refresh=1" : ""), SHRINK)); } else { sendMessageAndClose(MSG_200); } } void protocol_admincgi::mode_art(const streamData::streamID_t sid, const int mode) throw() { utf8 header = "HTTP/1.1 200 OK\r\n", body; streamData *sd = streamData::accessStream(sid); if (sd) { vector<__uint8> sc21_albumart = (mode == 0 ? sd->streamAlbumArt() : sd->streamPlayingAlbumArt()); if (!sc21_albumart.empty()) { utf8 mimeType[] = { "image/jpeg", "image/png", "image/bmp", "image/gif" }; const size_t mime = (mode == 0 ? sd->streamAlbumArtMime() : sd->streamPlayingAlbumArtMime()); // if not in the valid range then don't report the mime type in the generated response if (mime < 4) { header += "Content-Type:" + mimeType[mime] + "\r\n"; } body += utf8(&sc21_albumart[0],sc21_albumart.size()); } sd->releaseStream(); } header += "Content-Length:" + tos(body.size()) + "\r\n\r\n"; sendMessageAndClose(header + (!HEAD_REQUEST ? body : "")); } void protocol_admincgi::mode_register(const streamData::streamID_t sid, const streamData::streamInfo &info) throw() { // construct a temp id for this action utf8 tempId = ""; randomId(tempId); bool loaded = false; streamData *sd = streamData::accessStream(sid); if (sd) { int addFailIgnore = 0, errorCode = 0; loaded = sd->YP2_addSuccessful(addFailIgnore, errorCode); sd->releaseStream(); } utf8 header = "HTTP/1.1 200 OK\r\n" "Content-Type:text/html;charset=utf-8\r\n" "Cache-Control:no-cache\r\n", authhash = (!info.m_authHash.empty() ? info.m_authHash.substr(0, 36) : (utf8)""), body = getStreamAdminHeader(sid, "Stream Authhash") + "
" "
" "
" "
" "
" "
Information
" "This page allows you to enter or amend the authhash to be used for this stream." "



Authhash information is now managed online. " "Login " "to create an authhash or update the details of an existing authhash." "



The same authhash should be used for all stream instances of a station " "(e.g. 128kbps MP3 and 64kbps AAC streams for 'Super Awesome Radio').

This also includes all DNAS " "providing the stream(s) for a station to ensure the correct listing of all stream instances." // TODO need to have a link to the account page... "



If you remove an authhash by mistake then you can either recover it from your " "Shoutcast account or you will need " "to contact support directly for " "assistance.

" // TODO need to consider customising some of this ?? or just above ?? "
" "
" "" "" "" "" "" "" "" "" "" "
" + utf8(!authhash.empty() && loaded && (info.m_streamSampleRate > 0) && info.m_radionomyID.empty() ? warningImage(false) + "  Please Register Your Authhash

" : "") + "
" "
" "The authhash currently configured for this stream is " + authhash + "


" "To change the authhash, enter it below and click 'Save'." : "Create Authhash\">" "An authhash needs to be created for this stream.


" "If you have just created an authhash via your " "account " "or have an existing one for the stream, please enter it below and click 'Save'.") + "

Authhash:

" "  " "  " "
" "
" "
" "
" "" + getUptimeScript() + getIEFlexFix() + getfooterStr(); COMPRESS(header, body); header += "Content-Length:" + tos(body.size()) + "\r\n\r\n"; sendMessageAndClose(header + (!HEAD_REQUEST ? body : "")); } void protocol_admincgi::mode_listeners(const streamData::streamID_t sid) throw() { utf8 header = MSG_NO_CLOSE_200, body; // just to make sure we came from an appropriate page const utf8 &server = mapGet(m_httpRequestInfo.m_QueryParameters, "server", (utf8)""), &check = ("admin.cgi?sid=" + tos(sid)); const bool nowrap = mapGet(m_httpRequestInfo.m_QueryParameters, "nw", (bool)gOptions.adminNoWrap()); const int fh = mapGet(m_httpRequestInfo.m_QueryParameters, "fh", (int)0); if ((sid > 0) && (m_referer.find(check) == 0) && !server.empty() && (listenerId == server)) { stats::currentClientList_t client_data; stats::getClientDataForStream(sid, client_data); if (!client_data.empty()) { streamData::streamInfo info; streamData::extraInfo extra; streamData::getStreamInfo(sid, info, extra); stats::statsData_t data; stats::getStats(sid, data); utf8 clientsBody = "
" "" "" "" "" "" + utf8(!info.m_radionomyID.empty() ? "" : "") + (data.connectedListeners != data.uniqueListeners ? "" : (utf8)"") + "" ""; const time_t t = ::time(NULL); size_t rowCount = 0; // if we have non-unique clients then we need to process the list differently so as to group // them together and then re-sort the output by client listener duration on the first match // otherwise we revert the code back to the original un-grouped method to not waste resources if (data.connectedListeners != data.uniqueListeners) { map unique_clients; for (stats::currentClientList_t::const_iterator i = client_data.begin(); i != client_data.end(); ++i) { // if set to kicked then no need to show those (since they may re-appear if in-progress) if (!(*i)->m_kicked) { stats::uniqueClientData_t client; const bool localhost = ((*i)->m_ipAddr.find(utf8("127.")) == 0); // look for existing instances and append the new details to the existing details const map::const_iterator im = unique_clients.find((*i)->m_ipAddr); if (im != unique_clients.end()) { client = (*im).second; client.m_userAgent += "m_ripClient || localhost ? utf8(" style=\"font-style:italic;\"") : "") + ">"; client.m_unique += ","; ++client.m_total; } else { client.m_connectTime = (*i)->m_startTime; client.m_ipAddr = (*i)->m_ipAddr; client.m_hostName = (*i)->m_hostName; client.m_XFF = (*i)->m_XFF; client.m_total = 1; } const int slave = ((*i)->m_clientType & streamData::SC_CDN_SLAVE); client.m_userAgent += ""; if ((*i)->m_ripClient) { client.m_ripAddr = true; } const time_t connected = (t - (*i)->m_startTime); utf8 timer = timeString(connected, true), timerTip; if (timer.empty()) { timer = "Starting..."; } else { timerTip = timeString(connected); } client.m_userAgent += "" + aolxml::escapeXML(timer) + ""; client.m_unique = (*i)->m_ipAddr; if (!info.m_radionomyID.empty()) { client.m_userAgent += "" + (((*i)->m_group > 0) || (*i)->m_triggers ? advertImage(sid, (*i)->m_group, (*i)->m_triggers) : "
N/A
") + ""; } client.m_userAgent += "m_unique) + "\">Kick" + (client.m_total == 1 ? "" : "") + "" + ((*i)->m_userAgent.empty() || ((*i)->m_userAgent == EMPTY_AGENT) ? "N/A" : "m_userAgent) + "\">Block") + ""; unique_clients[(*i)->m_ipAddr] = client; } delete (*i); } // take the map and convert to a vector so we can then re-sort the list back into longest to least duration vector clients; for (map::const_iterator i = unique_clients.begin(); i != unique_clients.end(); ++i) { clients.push_back((*i).second); } std::sort(clients.begin(), clients.end(), sortUniqueClientDataByTime); // and now we dump the generated list for (vector::const_iterator i = clients.begin(); i != clients.end(); ++i) { const utf8 host = ((*i).m_hostName != (*i).m_ipAddr ? aolxml::escapeXML((*i).m_hostName) + " (" + (*i).m_ipAddr + ")" : (*i).m_ipAddr); const bool localhost = ((*i).m_ipAddr.find(utf8("127.")) == 0); // if we have a multiple client block then re-process so the relevant parts can // be listed individually with adjustment of some of the visual styles as needed utf8 multiBlock = (*i).m_userAgent; uniString::utf8::size_type tpos = multiBlock.find(utf8(" 1 ? "")); if (tpos != uniString::utf8::npos) { const bool hasHostName = ((*i).m_hostName != (*i).m_ipAddr); utf8 endBlock = " 1 ? " rowspan=\"" + tos((*i).m_total) + "\"" : (utf8)"") + ">" + (!localhost ? "Ban" : "N/A") + " 1 ? " rowspan=\"" + tos((*i).m_total) + "\"" : (utf8)"") + ">" + (!localhost ? "Ban " : "N/A") + "" " 1 ? " rowspan=\"" + tos((*i).m_total) + "\"" : (utf8)"") + ">" + (!localhost ? "" + ((*i).m_ripAddr ? "Remove" : "Add") + "" : "N/A") + ""; multiBlock.replace(tpos, 6, utf8(((*i).m_total > 1 ? "
" + endBlock : "" + endBlock))); } rowCount += (*i).m_total; clientsBody += "" " 1 ? " rowspan=\"" + tos((*i).m_total) + "\"" : "") + ">" + (gOptions.useXFF() && !(*i).m_XFF.empty() ? xffImage() + " " : "") + host + "" + multiBlock; } } else { for (stats::currentClientList_t::const_iterator i = client_data.begin(); i != client_data.end(); ++i) { // if set to kicked then no need to show those (since they may re-appear if in-progress) if (!(*i)->m_kicked) { const time_t connected = (::time(NULL) - (*i)->m_startTime); utf8 timer = timeString(connected, true), timerTip; if (timer.empty()) { timer = "Starting..."; } else { timerTip = timeString(connected); } const utf8 host = ((*i)->m_hostName != (*i)->m_ipAddr ? aolxml::escapeXML((*i)->m_hostName) + " (" + (*i)->m_ipAddr + ")" : (*i)->m_ipAddr); const bool localhost = ((*i)->m_ipAddr.find(utf8("127.")) == 0); const bool hasHostName = ((*i)->m_hostName != (*i)->m_ipAddr); ++rowCount; const int slave = ((*i)->m_clientType & streamData::SC_CDN_SLAVE); clientsBody += "m_ripClient || localhost ? utf8(" style=\"font-style:italic;\"") : "") + ">" "" + aolxml::escapeXML(timer) + ""; if (!info.m_radionomyID.empty()) { clientsBody += "" + (((*i)->m_group > 0) || (*i)->m_triggers ? advertImage(sid, (*i)->m_group, (*i)->m_triggers) : "
N/A
") + ""; } const utf8 unique = tos((*i)->m_unique); clientsBody += "Kick
" + ((*i)->m_userAgent.empty() || ((*i)->m_userAgent == EMPTY_AGENT) ? "N/A" : "m_userAgent) + "\">Block") + ""; } delete (*i); } } if (rowCount > 0) { if (rowCount > 1) { clientsBody += ""; } // only output the table if we actually had clients to show body += clientsBody + "
Current Listeners
Listener Address
(Host Address)
User AgentConnected
Duration
" + baseImage("adavail", "Advert Status", false, false) + "Kick
Client
Kick
IP
Ban
IP
Ban
Subnet
Reserve
Listener
Block
User Agent
" + (((*i)->m_clientType & streamData::RADIONOMY) ? "Radionomy Stats Collector" : (!nowrap ? addWBR((*i)->m_userAgent) : "
" + aolxml::escapeXML((*i)->m_userAgent)) + "
") + "
" + getClientImage((*i)->m_clientType) + "
Kickm_XFF.empty() ? "title=\"XFF: " + aolxml::escapeXML((*i)->m_XFF) + "\">" + xffImage() + " " : ">") + host + "m_XFF.empty() ? "title=\"XFF: " + aolxml::escapeXML((*i)->m_XFF) + "\" " : "") + "style=\"" + (!nowrap ? "" : "white-space:nowrap;") + "\"" + utf8(slave ? " class=\"thr\"" : "") + ">" + (((*i)->m_clientType & streamData::RADIONOMY) ? "Radionomy Stats Collector" : (!nowrap ? addWBR((*i)->m_userAgent) : "
" + aolxml::escapeXML((*i)->m_userAgent)) + "
") + "
" + getClientImage((*i)->m_clientType) + "
" + "
" + (!localhost ? "m_ipAddr) + "&banmsk=255" + "&kickdst=" + unique + "\">Ban" : "N/A") + "" + (!localhost ? "m_ipAddr) + "&banmsk=0" "&kickdst=" + unique + "\">Ban " : "N/A") + "" + (!localhost ? "" + ((*i)->m_ripClient ? "Remove" : "Add") + "" : "N/A") + "
Kick Duplicates" "" + timeString(data.avgUserListenTime, true) + "Kick All
"; } } } else { body = "
The current " "listener list could not be loaded.
Click here to reload this page. If this issue " "
persists, try logging out and back in again.
"; } COMPRESS(header, body); header += "Content-Length:" + tos(body.size()) + "\r\n\r\n"; sendMessageAndClose(header + (!HEAD_REQUEST ? body : "")); } const utf8 protocol_admincgi::getClientIP(const bool streamPublic, const utf8 &publicIP) throw() { // test for potentially invalid IPs or ones that will cause the playlist link generation to fail // attempting to use the server path provided by the YP if in public mode, otherwise uses 'host' return (!streamPublic || publicIP.empty() ? ((g_IPAddressForClients.find(utf8("0.")) == 0 || // allow localhost / loopback connections through for admin access ((!g_IPAddressForClients.empty() && g_IPAddressForClients.find(utf8("127.")) == 0)) || g_IPAddressForClients.empty()) && !m_hostIP.empty() ? m_hostIP : (!m_hostIP.empty() ? m_hostIP : g_IPAddressForClients)) : publicIP); } void protocol_admincgi::mode_none(const streamData::streamID_t sid, const int refreshRequired) throw() { utf8 header = MSG_NO_CLOSE_200, body = getStreamAdminHeader(sid, "Stream Status & Listeners", refreshRequired); time_t streamUptime = 0; bool hasListeners = false, isConnected = false; if (refreshRequired > 0) { body += "
" "
" "
Waiting " + tos(refreshRequired) + " second" + ((refreshRequired > 1) ? "s" : (utf8)"") + " for any configuration changes to take effect.
" "If not automatically redirected or do not want to wait, " "click here." "

"; } else { streamData::streamInfo info; streamData::extraInfo extra; streamData::getStreamInfo(sid, info, extra); stats::statsData_t data; stats::getStats(sid, data); isConnected = extra.isConnected; hasListeners = (data.connectedListeners > 0); // this is a placeholder for the listener details which we grab asynchronously for speed since #615 body += "
" + (data.connectedListeners > 0 ? "
Loading current " "listener list...
" : (utf8)"") + "
Current Stream Information
"; utf8 detailsBody = ""; bool showing = false; if (!(gOptions.read_stream_adminPassword(sid) && !gOptions.stream_adminPassword(sid).empty())) { utf8 log = gOptions.realLogFile(); utf8::size_type pos = log.rfind(fileUtil::getFilePathDelimiter()); if ((pos != utf8::npos)) { log = log.substr(pos + 1); } utf8 conf = gOptions.confFile(); pos = conf.rfind(fileUtil::getFilePathDelimiter()); if ((pos != utf8::npos)) { conf = conf.substr(pos + 1); } // trim down the file paths shown to make things less cluttered on hosted setups // places the full path in the 'title' so it can still be found if required, etc detailsBody += "Log file: " + aolxml::escapeXML(fileUtil::stripPath(log)) + "
" "Configuration file: " + aolxml::escapeXML(fileUtil::stripPath(conf)) + "
"; showing = true; } utf8 introFile = gOptions.stream_introFile(sid); if (!gOptions.read_stream_introFile(sid)) { introFile = gOptions.introFile(); } utf8 backupFile = gOptions.stream_backupFile(sid); if (!gOptions.read_stream_backupFile(sid)) { backupFile = gOptions.backupFile(); } utf8 backupTitle = gOptions.stream_backupTitle(sid); if (!gOptions.read_stream_backupTitle(sid)) { backupTitle = gOptions.backupTitle(); } streamData *sd = streamData::accessStream(sid); detailsBody += utf8(showing ? "


" : "") + "Intro file is getIntroFile().gotData()) ? (!introFile.empty() ? fileUtil::getFullFilePath(introFile) : (utf8)"") : (utf8)"") + "\">" + aolxml::escapeXML((!introFile.empty() || (sd && sd->getIntroFile().gotData()) ? (!introFile.empty() ? fileUtil::stripPath(introFile) : "from source") : "empty")) + "
Backup file is getBackupFile().gotData())) ? (!backupFile.empty() ? fileUtil::getFullFilePath(backupFile) : (utf8)"") : (utf8)"") + "\">" + aolxml::escapeXML((!backupFile.empty() || (sd && sd->getBackupFile().gotData())) ? (!backupFile.empty() ? fileUtil::stripPath(backupFile) : "from source") : "empty")) + "
"; if (!backupTitle.empty() && !backupFile.empty() && !extra.isConnected) { detailsBody += "Backup title is: " + aolxml::escapeXML(backupTitle) + "
"; } detailsBody += "


Idle timeouts are " + tos(gOptions.getAutoDumpTime(sid)) + "s
"; if (extra.isConnected) { detailsBody += "


Source connection type: " + utf8(info.m_sourceType == streamData::SHOUTCAST1 ? "v1" : (info.m_sourceType == streamData::SHOUTCAST2 ? "v2" : "HTTP")) + (extra.isRelay ? " relay" + (extra.isBackup ? utf8(" backup") : "") : (extra.isBackup ? utf8(" backup") : "")) + "
Source user agent: " + addWBR((!info.m_sourceIdent.empty() ? info.m_sourceIdent : "Legacy / Unknown")) + """
"; } if (sd) { detailsBody += (extra.isConnected ? "


" : "
"); if (sd->streamAlbumArt().empty()) { detailsBody += "Stream artwork not available"; } else { detailsBody += "Stream artwork availableview ]
"; } detailsBody += "
"; if (sd->streamPlayingAlbumArt().empty()) { detailsBody += "Playing artwork not available"; } else { detailsBody += "Playing artwork availableview ]
"; } if (!info.m_currentURL.empty() && (info.m_currentURL.find(utf8("DNAS/")) == utf8::npos)) { detailsBody += "



Song url from source [ view ]
" "
Note: " "This may not be a valid url and is intended for internal use.
"; } sd->releaseStream(); } const utf8& message = streamData::getStreamMessage(sid); if (!message.empty()) { detailsBody += "
" "
" "
" "Official Message Received
" + message + ""; } body += "

" ""; const utf8 movedUrl = gOptions.stream_movedUrl(sid); if (movedUrl.empty()) { body += ""; } const int maxUsers = ((info.m_streamMaxUser > 0) && (info.m_streamMaxUser < gOptions.maxUser()) ? info.m_streamMaxUser : gOptions.maxUser()); if (extra.isConnected) { const bool isListable = streamData::isAllowedType(info.m_uvoxDataType); utf8 listenLink = "" + (sd && !sd->radionomyID().empty() && sd->streamAdvertMode() ? " " : (utf8)""); body += "
" "
Stream Details
" + detailsBody + "
" "" "" "" ""; if (data.peakListeners > 0) { body += ""; } const utf8 avgTime = timeString(data.avgUserListenTime); if (!avgTime.empty()) { body += "" ""; } body += "" + (info.m_streamPublic && extra.ypConnected ? "" : ""); if (!info.m_streamGenre[0].empty()) { body += "" ""; } if (!info.m_streamUser.empty()) { body += "" ""; } if (!info.m_streamURL.empty()) { body += "" ""; } if (!info.m_currentSong.empty()) { body += "" ""; // only show if we have a valid current song if (!info.m_comingSoon.empty()) { body += "" ""; } } // strip down the source address for display output to an appropriate output based on settings utf8 srcAddr = niceURL(extra.isBackup ? info.m_backupURL : (extra.isRelay ? info.m_relayURL : info.m_srcAddr)); if (gOptions.nameLookups()) { if (!extra.isBackup && !extra.isRelay) { u_short port = 0; string addr, hostName; socketOps::getpeername(m_socket, addr, port); string src = (extra.isBackup ? info.m_backupURL : (extra.isRelay ? info.m_relayURL : info.m_srcAddr)).hideAsString(); hostName = src; if (!socketOps::addressToHostName(addr,port,hostName)) { srcAddr = hostName + " (" + niceURL(src) + ")"; } } } body += "" "" ""; if (!info.m_contentType.empty() && (info.m_uvoxDataType == MP3_DATA)) { body += streamData::getHTML5Player(sid); } body += "
" + getNewVersionMessage() + "
Listing Status: Stream is currently up " + (info.m_streamPublic && isListable ? (extra.ypConnected != 1 ? "" : utf8("and public") + listenLink) : utf8("and private (not listed)") + listenLink) + (info.m_streamPublic && isListable ? (extra.ypConnected != 1 ? (!yp2::isValidAuthhash(info.m_authHash) ? string(info.m_authHash.empty() ? "but requires registration in the Shoutcast Directory.
" : "but not listed due to an invalid authhash.
") + (info.m_authHash.empty() ? "Listeners are allowed and the stream will act like it is private until resolved." "

To create an authhash you will need to register the stream with us.
If you already have an existing authhash " "then you can enter it here.

" : "Listeners are allowed and the stream will act like it is private until resolved.") : (extra.ypErrorCode == 200 ? "waiting on a Directory response." : (extra.ypErrorCode == YP_COMMS_FAILURE ? "unable to access the Directory.
Listeners are allowed and the stream will act like it is private until resolved." : (extra.ypErrorCode == YP_MAINTENANCE_CODE ? "received a Directory maintenance notification: " + tos(extra.ypErrorCode) + "
Listeners will be allowed though the stream will not be listed in the Directory." : "received Directory error code: " + tos(extra.ypErrorCode) + "
" + (extra.ypConnected != 2 ? "" : "during a listing update. The stream may no longer appear.") + "
Check the server log and / or contact the server administrator.")))) : "") : "") + "
Stream Status: Stream is up (" + streamData::getContentType(info) + " @ " + (info.m_streamBitrate > 0 ? tos(info.m_streamBitrate) : "unknown") + " kbps" + (info.m_vbr ? " (VBR)" : "") + ", " + sampleRateStr(info.m_streamSampleRate) + ") with " + tos(data.connectedListeners) + (maxUsers > 0 ? " of " + tos(maxUsers) : "") + " listeners" + (!maxUsers ? " (unlimited)" : "") + (data.connectedListeners != data.uniqueListeners ? (" (" + tos(data.uniqueListeners) + " unique)") : "") + "
Listener Peak: " + tos(data.peakListeners) + "
Avg. Play Time: " + avgTime + "
Stream Name: " + (info.m_streamPublic && extra.ypConnected ? "" + aolxml::escapeXML(info.m_streamName) + "" : aolxml::escapeXML(info.m_streamName)) + "
" " ID: "+info.m_stationID+"
Stream Genre(s): " + (info.m_streamPublic && extra.ypConnected ? "" + aolxml::escapeXML(info.m_streamGenre[0]) + "" : aolxml::escapeXML(info.m_streamGenre[0])) + ""; for (int i = 1; i < 5; i++) { if (!info.m_streamGenre[i].empty()) { body += " , " + (info.m_streamPublic && extra.ypConnected ? "" + aolxml::escapeXML(info.m_streamGenre[i]) + "" : aolxml::escapeXML(info.m_streamGenre[i])) + ""; } } body += "
Stream DJ: " + aolxml::escapeXML(info.m_streamUser) + "
Stream Website: " + urlLink(info.m_streamURL) + "
Playing Now: " + getCurrentSong(info.m_currentSong) + "
Playing Next: " + aolxml::escapeXML(info.m_comingSoon) + "
Stream Source: " + (extra.isRelay || extra.isBackup ? urlLink(srcAddr) : srcAddr) + " " + (extra.isRelay ? "(relaying" + (extra.isBackup ? utf8(" backup") : "") + ") " : (extra.isBackup ? "(backup) " : "")) + "" "[ " + (extra.isRelay || extra.isBackup ? "stop" : "kick") + " ]
Stream Uptime: " + timeString((streamUptime = ::time(NULL) - streamData::getStreamUptime(sid))) + "
"; } else { body += "" "" "" ""; if (data.peakListeners > 0) { body += ""; } utf8 avgTime = timeString(data.avgUserListenTime); if (!avgTime.empty()) { body += "" ""; } // add in an option to restart a relay url... if (!gOptions.stream_relayURL(sid).empty() && movedUrl.empty()) { // strip down the source address for display output utf8 srcAddr = niceURL(gOptions.stream_relayURL(sid)); // make sure we're not exposing the option to try re-connecting to a pending source relay bool noEntry = false; if (!(streamData::isRelayActive(sid, noEntry) == 1)) { body += ""; } else { body += ""; } } body += "
" + getNewVersionMessage("
") + "
Stream Status: "; if (movedUrl.empty()) { body += "Stream is currently down" + (data.connectedListeners > 0 ? " with " + tos(data.connectedListeners) + (maxUsers > 0 ? " of " + tos(maxUsers) : "") + " listeners" + (!maxUsers ? " (unlimited)" : "") + (data.connectedListeners != data.uniqueListeners ? (" (" + tos(data.uniqueListeners) + " unique)") : "") : ".") + "
There is no " "source connected or no stream is configured for stream #" + tos(sid) + "."; } else { body += "Stream has been moved to " + urlLink(movedUrl) + "
No source connections will be allowed for this stream."; } body += "
Listener Peak: " + tos(data.peakListeners) + "
Avg. Play Time: " + avgTime + "

Start Relay:

" + urlLink(srcAddr) + " " "[ start relay ]

Starting Relay:

Connection pending to " + urlLink(srcAddr) + " [ abort ]
"; } body += "
"; } // for a refresh, we'll show a countdown so it's obvious that something is happening if (refreshRequired) { body += ""; } body += getUptimeScript(false, isConnected, streamUptime) + getIEFlexFix() + getHTML5Remover() + (!refreshRequired && hasListeners ? getStreamListeners(sid, mapGet(m_httpRequestInfo.m_QueryParameters, "nw", (bool)gOptions.adminNoWrap()), mapGet(m_httpRequestInfo.m_QueryParameters, "fh", (int)0)) : "") + getfooterStr(); COMPRESS(header, body); header += "Content-Length: " + tos(body.size()) + "\r\n\r\n"; sendMessageAndClose(header + (!HEAD_REQUEST ? body : "")); } utf8 getCDNMessage(const bool master, const bool slave) { if (slave && !master) { return "Configured as a CDN slave
Authhash inheritance enabled from master"; } else if (master && !slave) { return "Configured as a CDN master
Authhash inheritance enabled for slaves"; } return "Configured as a CDN intermediary
Authhash inheritance enabled both ways"; } utf8 getBadAuthhashMessage(const streamData::streamID_t sid, const utf8 &authHash) { return "
" "Invalid Authhash Detected   

" "Clear Authhash" "  |  Manage Authhash
"; } void protocol_admincgi::mode_summary(const int refreshRequired) throw() { utf8 header = MSG_NO_CLOSE_200, streams = "", body = getServerAdminHeader("Server Summary", refreshRequired); size_t totalListeners = 0, totalPeakListeners = 0, streamTotal = 0, movedTotal = 0; map streamBlocks; if (refreshRequired == 0) { size_t inc = 0, sid = DEFAULT_SOURCE_STREAM; do { utf8 streamBody = ""; sid = streamData::enumStreams(inc); // check if we have an active source and valid sid before attempting to add if (sid >= DEFAULT_SOURCE_STREAM) { streamData::streamInfo info; streamData::extraInfo extra; if (streamData::getStreamInfo(sid, info, extra)) { stats::statsData_t data; stats::getStats(sid, data); // increment our stream total now that we know we have one totalListeners += data.connectedListeners; totalPeakListeners += data.peakListeners; utf8 streamBody2 = ""; const bool slave = isCDNSlave(sid); const bool master = isCDNMaster(sid); if (master || slave) { streamBody2 += "
" + getCDNMessage(master, slave) + "


"; } const bool isListable = streamData::isAllowedType(info.m_uvoxDataType); if (!isListable) { streamBody2 += "
" + (info.m_uvoxDataType == OGG_DATA ? "OGG Vorbis based streams are not fully supported
and will not" : utf8("NSV based streams are no longer able
to ")) + " be listed in the Shoutcast Directory.

"; } /*else if (!info.m_streamPublic && gOptions.cdn().empty() && !slave && !master) { streamBody2 += "
" "An authhash is not required for private streams.


"; }*/ if (isListable) { if (info.m_authHash.empty()) { streamBody2 += ""; } else { // check that the authhash is a valid length if (!yp2::isValidAuthhash(info.m_authHash)) { streamBody2 += getBadAuthhashMessage(sid, info.m_authHash); } else { streamBody2 += "
" + utf8((master || slave || info.m_streamPublic) && (info.m_streamSampleRate > 0) && info.m_radionomyID.empty() && ((extra.ypErrorCode != YP_NOT_VISIBLE) && (extra.ypErrorCode != YP_AUTH_ISSUE_CODE) && (extra.ypErrorCode != -1)) ? warningImage(false) + "  Please Register Your Authhash

" : "") + "Update Authhash  | " " Manage Authhash
"; } } } else { streamBody2 += ""; } streamBody2 += ""; streamData *sd = streamData::accessStream(sid); streamBody += "" + (sd && !sd->radionomyID().empty() && sd->streamAdvertMode() ? " " : (utf8)"") + "Stream #" + tos(sid) + " " "(Stream Login)" + " " + (sd && !sd->streamAlbumArt().empty() ? " " "\"View" : "") + (sd && !sd->streamPlayingAlbumArt().empty() ? " " "\"View" : "") + "" + (!info.m_contentType.empty() && (info.m_uvoxDataType == MP3_DATA) ? streamData::getHTML5Player(sid) : "") + streamBody2; utf8 content = streamData::getContentType(info); if (!info.m_streamUser.empty()) { content = info.m_streamUser + " - " + content; } const int maxUsers = ((info.m_streamMaxUser > 0) && (info.m_streamMaxUser < gOptions.maxUser()) ? info.m_streamMaxUser : gOptions.maxUser()); const utf8 listeners = (data.connectedListeners ? (tos(data.connectedListeners) + (data.connectedListeners != data.uniqueListeners ? (" (" + tos(data.uniqueListeners) + " unique)") : "")) : "0") + (maxUsers > 0 ? " of " + tos(maxUsers) : " (unlimited)"); const utf8 listenLink = ""; streamBody += "" + (info.m_streamPublic && extra.ypConnected ? "" + aolxml::escapeXML(info.m_streamName) + "" : aolxml::escapeXML(info.m_streamName)) + " (" + content + " @ " + (info.m_streamBitrate > 0 ? tos(info.m_streamBitrate) : "unknown") + " kbps" + (info.m_vbr ? " (VBR)" : "") + ", " + sampleRateStr(info.m_streamSampleRate) + ")" + (!info.m_currentSong.empty() ? "Playing: " "" + aolxml::escapeXML(info.m_currentSong) + "" + (!info.m_comingSoon.empty() ? "Coming: " "" + aolxml::escapeXML(info.m_comingSoon) + "" : "") : "") + "" "
" "
Listeners: " + listeners + "" + (data.peakListeners > 0 ? "
Peak: " + tos(data.peakListeners) + "" : "") + (data.connectedListeners > 0 ? " [ kick all ]" : "") + "
   Status: " + string(info.m_streamPublic && isListable ? (extra.ypConnected != 1 ? "" : string("Public
" "")) + (extra.ypConnected != 1 ? (!yp2::isValidAuthhash(info.m_authHash) ? " Not Listed - " + string(info.m_authHash.empty() ? "Empty" : "Invalid") + " Authhash" : (extra.ypErrorCode == 200 ? " Waiting on a Directory response" : (extra.ypErrorCode == -1 ? "Unable to access the Directory.
Check the server log for more details.
The stream will behave like it is private." : (extra.ypErrorCode == YP_MAINTENANCE_CODE ? "Directory is down for maintenance: " + tos(extra.ypErrorCode) + "
Listeners are allowed, stream will not be listed" : (extra.ypErrorCode == YP_AUTH_ISSUE_CODE ? " Please contact support as there is an issue with the authhash" : " Directory returned error code: " + tos(extra.ypErrorCode) + "
" + (extra.ypConnected != 2 ? "" : "during a listing update. The stream may not
appear in the Directory due to the error. The
server will attempt to re-list the stream.")))))) : "") : "Private") + "
   Source: " + utf8(info.m_sourceType == streamData::SHOUTCAST1 ? "v1" : (info.m_sourceType == streamData::SHOUTCAST2 ? "v2" : "HTTP")) + (extra.isRelay ? " relay" + (extra.isBackup ? utf8(" backup") : "") : (extra.isBackup ? " backup" : "")) + "" + (extra.isRelay || extra.isBackup ? "stop" : "kick") + " ]
" "" "
" "Summary  |  " "  |  " "History  |  " "Metadata  |  " "Statistics " "
"; if (sd) { sd->releaseStream(); } const utf8& message = streamData::getStreamMessage(sid); if (!message.empty()) { streamBody += "" "

" "
" "Official Message Received
" + message + "
"; } streamBlocks[sid] = streamBody; } } ++inc; } while (sid); // now we check through for any known but inactive relays and then get them listed as well vector relayList(gOptions.getRelayList()); if (!relayList.empty()) { for (vector::const_iterator i = relayList.begin(); i != relayList.end(); ++i) { sid = (*i).m_streamID; const bool exists = !(*i).m_relayUrl.url().empty(); if (exists && !streamData::isSourceConnected(sid)) { stats::statsData_t data; stats::getStats(sid, data); // increment our stream total now that we know we have one totalListeners += data.connectedListeners; totalPeakListeners += data.peakListeners; streamData::streamInfo info; streamData::extraInfo extra; streamData::getStreamInfo(sid, info, extra); utf8 listeners, content, streamBody2, streamBody = "" "Stream #" + tos(sid) + " " "(Stream Login)"; const utf8 movedUrl = gOptions.stream_movedUrl(sid); if (movedUrl.empty()) { const bool slave = isCDNSlave(sid); const bool master = isCDNMaster(sid); if (master || slave) { streamBody2 = "
" + getCDNMessage(master, slave) + "


"; } else { if ((*i).m_authHash.empty()) { streamBody2 += ""; } else { // check that the authhash is a valid length if (!yp2::isValidAuthhash((*i).m_authHash)) { streamBody2 += getBadAuthhashMessage(sid, info.m_authHash); } else { streamBody2 += "
" + utf8((master || slave || info.m_streamPublic) && (info.m_streamSampleRate > 0) && info.m_radionomyID.empty() && ((extra.ypErrorCode != YP_NOT_VISIBLE) && (extra.ypErrorCode != YP_AUTH_ISSUE_CODE) && (extra.ypErrorCode != -1)) ? warningImage(false) + "  Please Register Your Authhash

" : "") + "Update Authhash  | " " Manage Authhash
"; } } if (!streamBody2.empty()) { streamBody2 += "
"; } } } else { streamBody2 = "This stream is configured as having been moved or retired.
" "No source connections will be allowed for this stream.

" "All client connections received will be redirected to:
" + urlLink(movedUrl) + "
"; ++movedTotal; } if (!streamBody2.empty()) { streamBody += "" + streamBody2 + ""; } // strip down the source address for display output const utf8 srcAddr = niceURL(gOptions.stream_relayURL(sid)); if (movedUrl.empty()) { bool noEntry = false; const bool isListable = streamData::isAllowedType(info.m_uvoxDataType); streamBody += "" "Status: " + string(info.m_streamPublic && isListable ? (extra.ypConnected != 1 ? "" : string("Public")) + (extra.ypConnected != 1 ? (!yp2::isValidAuthhash(info.m_authHash) ? " Not Listed - " + string(info.m_authHash.empty() ? "Empty" : "Invalid") + " Authhash" : (extra.ypErrorCode == 200 ? " Waiting on a Directory response" : (extra.ypErrorCode == -1 ? "Unable to access the Directory.
Check the error server for more details.
The stream will behave like it is private." : (extra.ypErrorCode == YP_MAINTENANCE_CODE ? "Directory is down for maintenance: " + tos(extra.ypErrorCode) + "
Listeners are allowed, stream will not be listed" : (extra.ypErrorCode == YP_AUTH_ISSUE_CODE ? " Please contact support as there is an issue with the authhash" : " Directory returned " "error code: " + tos(extra.ypErrorCode) + "
" + (extra.ypConnected != 2 ? "" : "during a listing update. The stream may not
appear in the Directory due to the error. The
server will attempt to re-list the stream.")))))) : "") : "Private") + "
   " "
" + (data.connectedListeners > 0 ? "" "" : "") + "
" "Source: " + (!(streamData::isRelayActive(sid, noEntry) == 1) ? "Inactive relaystart relay ]
Using: " + urlLink(srcAddr) + "" : "Connection pending to " + urlLink(srcAddr) + "" " [ abort ]") + // if it's an inactive stream then we also want to show any remaining connections "
Listeners: " + tos(data.connectedListeners) + "" + (data.peakListeners > 0 ? "  |  Peak: " + tos(data.peakListeners) + "" : "") + " [ kick all ]" + "
"; } streamBlocks[sid] = streamBody; } } } // now we check through for any known but in-active sources & then get them listed as well // new to build 70 but for fire builds makes it easier to see if a feed stream is inactive config::streams_t stream_configs; gOptions.getStreamConfigs(stream_configs); if (!stream_configs.empty()) { for (config::streams_t::const_iterator i = stream_configs.begin(); i != stream_configs.end(); ++i) { if (streamBlocks.find((*i).first) == streamBlocks.end()) { stats::statsData_t data; stats::getStats((*i).first, data); // increment our stream total now that we know we have one totalListeners += data.connectedListeners; totalPeakListeners += data.peakListeners; utf8 streamBody = "" "Stream #" + tos((*i).first) + " " "(Stream Login)", streamBody2; const utf8 movedUrl = gOptions.stream_movedUrl((*i).first); if (movedUrl.empty()) { utf8 authhash = (*i).second.m_authHash; if (authhash.empty()) { authhash = gOptions.stream_authHash((*i).first); } if (authhash.empty()) { streamBody2 += ""; } else { // check that the authhash is a valid length if (!yp2::isValidAuthhash(authhash)) { streamBody2 += getBadAuthhashMessage((*i).first, authhash); } else { streamBody2 += "
" // TODO if the stream is not active then we don't know the admode // status and so it's easier to not show the warning until // we've got something that will allow us to check authhash. //+ utf8(info.m_radionomyID.empty() ? warningImage(false) + "  Please Register Your Authhash

" : "") + "Update Authhash" "  | Manage Authhash
"; } } if (!streamBody2.empty()) { streamBody2 += "
"; } streamBody2 += "This stream is configured but has no source connected." + (data.connectedListeners > 0 ? "
Listeners: " "" + tos(data.connectedListeners) + "" + (data.peakListeners > 0 ? "  |  " "Peak: " + tos(data.peakListeners) + "" : "") + " [ kick all ]" + "
" : ""); } else { streamBody2 += "This stream is configured as having been moved or retired.
" "No source connections will be allowed for this stream.

" "All client connections received will be redirected to:
" + urlLink(movedUrl) + "
"; ++movedTotal; } if (!streamBody2.empty()) { streamBody += "" + streamBody2 + ""; } streamBlocks[(*i).first] = streamBody; } } } // this will now do a final check for any listeners which are on // an un-confiured stream but are being provided a 'backupfile'. const streamData::streamIDs_t activeIds = stats::getActiveStreamIds(); if (!activeIds.empty()) { for (streamData::streamIDs_t::const_iterator i = activeIds.begin(); i != activeIds.end(); ++i) { if (streamBlocks.find((*i)) == streamBlocks.end()) { stats::statsData_t data; stats::getStats((*i), data); // increment our stream total now that we know we have one totalListeners += data.connectedListeners; totalPeakListeners += data.peakListeners; utf8 streamBody = "" "Stream #" + tos((*i)) + " " "(Stream Login)", streamBody2; streamBody2 += "This stream is not configured and has no source connected." + (data.connectedListeners > 0 ? "
Listeners: " "" + tos(data.connectedListeners) + "" + (data.peakListeners > 0 ? "  |  " "Peak: " + tos(data.peakListeners) + "" : "") + " [ kick all ]" + "
" : ""); if (!streamBody2.empty()) { streamBody += "" + streamBody2 + ""; } streamBlocks[(*i)] = streamBody; } } } // now build up the output since if we've factored in inactive relay connections // then we could otherwise be showing the streams in an out of order manner for (map::const_iterator i = streamBlocks.begin(); i != streamBlocks.end(); ++i) { if (!(*i).second.empty()) { if (streamTotal > 0) { streams += "


"; } streams += (*i).second; ++streamTotal; } } // output a refresh option if there were no active streams found if (!streamTotal) { if (isPostSetup() && isPostSetup() <= 2) { streams += "" "
" "Setup Successfully Completed
" "No stream sources are currently connected.
" "To find newly connected sources, click here."; setPostSetup(isPostSetup() + 1); } else { streams += "No stream sources are currently connected.
" "To find newly connected sources, click here.

"; } } else { setPostSetup(0); } } else if (refreshRequired > 0) { streams += "" "Waiting " + tos(refreshRequired) + " second" + ((refreshRequired > 1) ? "s" : (utf8)"") + " for any configuration changes to take effect.
" "If not automatically redirected or do not want to wait, " "click here.

"; } else if (refreshRequired < 0) { streams += "" "Waiting " + tos(abs(refreshRequired)) + " second" + ((abs(refreshRequired) > 1) ? "s" : (utf8)"") + " whilst checking for any DNAS updates.
If not automatically redirected or do not want to wait, " "click here.

"; } utf8 log = gOptions.realLogFile(); utf8::size_type pos = log.rfind(fileUtil::getFilePathDelimiter()); if ((pos != utf8::npos)) { log = log.substr(pos + 1); } utf8 conf = gOptions.confFile(); pos = conf.rfind(fileUtil::getFilePathDelimiter()); if ((pos != utf8::npos)) { conf = conf.substr(pos + 1); } const bool requires = gOptions.requireStreamConfigs(); body += "" "
" "Available Streams: " + tos(streamTotal - movedTotal) + "   |   " "Server Listeners: " + tos(totalListeners) + "   |   " "Peak Server Listeners: " + tos(totalPeakListeners) + "   |   " "Unique Listeners: " + tos(stats::getTotalUniqueListeners()) + "
" "
" "
" ""; } body += "
" "" "
" "
Server Management
" "Rotate Log File(s):" " [ All ]" "  [ Log ]" "  [ W3C ]" "

" // trim down the file paths shown to make things less cluttered on hosted setups // places the full path in the 'title' so it can still be found if required, etc "Log File: " + aolxml::escapeXML(fileUtil::stripPath(log)) + "

" "Configuration File: " + aolxml::escapeXML(fileUtil::stripPath(conf)) + "
" "[ View ]" "  [ Update ]" "  [ Forced ]" "



Source Connection Details:" " [ View ]
" + (requires ? "
Note: Only pre-configured streams are " "allowed to connect / be run on this server.
" : "") + "
Advert Group Details:" " [ View ]
" "


Stream Configuration(s):" " [ JSON ]" "  [ XML ]
" "
View Stream Statistics:" " [ JSON ]" "  [ XML ]
" "


Manage Stream Source(s):
" "[ Start Relays ]" "  [ Kick / Stop All ]
" "


Reload Banned List(s):" " [ Update ]
" "
Reload Reserved List(s):" " [ Update ]
" "
Reload User Agent List(s):" " [ Update ]
" "
Reload Admin Access List:" " [ Update ]
" "
Clear Resource / Page Cache:" " [ Clear ]
" "


Debugging Options:
" "[ Enable All ]" "  [ Disable All ]" "  [ Edit ]" "



New DNAS Release: [ Check ]
" "Last checked: " + getCheckedDuration((size_t)(m_lastActivityTime - last_update_check)) + "" "
"; // display update message where applicable updater::verInfo ver; utf8 update_msg; if (updater::getNewVersion(ver)) { update_msg += "
" "
" "
New DNAS Version Available
A new version of the DNAS is now available:" " " + ver.ver + "

" + (!ver.url.empty() ? ((ver.downloaded && !ver.fn.empty()) ? "The new version has been automatically downloaded to:

" + aolxml::escapeXML(ver.fn) + "

" "Please install this update as soon as possible, thank you.

" "Specific changes for this version can be found here." : "Click here to download the new version of the DNAS.

" "Please install this update as soon as possible, thank you.

" "Specific changes for this version can be found here.") : "") + (!ver.message.empty() ? "
" + ver.message : (!ver.log.empty() ? "
For more details of the changes in this version see " + "here." : "")) + "



" + update_msg + streams + "
" + "" + getIEFlexFix() + getHTML5Remover() + getfooterStr(); COMPRESS(header, body); header += "Content-Length: " + tos(body.size()) + "\r\n\r\n"; sendMessageAndClose(header + (!HEAD_REQUEST ? body : "")); } void protocol_admincgi::mode_bandwidth_html(const int refreshRequired) throw() { const __uint64 all = bandWidth::getAmount(bandWidth::ALL), sent = bandWidth::getAmount(bandWidth::ALL_SENT), recv = bandWidth::getAmount(bandWidth::ALL_RECV); const time_t running = (::time(NULL) - g_upTime); utf8 header = MSG_NO_CLOSE_200, body = getServerAdminHeader("Server Bandwidth Usage", refreshRequired, "&mode=bandwidth&refresh=" + tos(abs(refreshRequired)), 1) + "" "
" "Total: " + formatSizeString(all) + (all > 0 ? " (~" + formatSizeString((all / running) * 86400) + " / day)" : "") + "" "   |   " "Sent: " + formatSizeString(sent) + (sent > 0 ? " (~" + formatSizeString((sent / running) * 86400) + " / day)" : "") + "" "   |   " "Received: " + formatSizeString(recv) + (recv > 0 ? " (~" + formatSizeString((recv / running) * 86400) + " / day)" : "") + "" "

" "" "" "" "" "" "" "" "" "" "" "" #if 0 "" "" #endif "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "
 Total Client Data Sent" + formatSizeString(bandWidth::getAmount(bandWidth::ALL_CLIENT_SENT)) + "
v2 Client(s)" + formatSizeString(bandWidth::getAmount(bandWidth::CLIENT_V2_SENT)) + "
v1 Client(s)" + formatSizeString(bandWidth::getAmount(bandWidth::CLIENT_V1_SENT)) + "
HTTP Client(s)" + formatSizeString(bandWidth::getAmount(bandWidth::CLIENT_HTTP_SENT)) + "
FLV Client(s)" + formatSizeString(bandWidth::getAmount(bandWidth::CLIENT_FLV_SENT)) + "
M4A Client(s)" + formatSizeString(bandWidth::getAmount(bandWidth::CLIENT_M4A_SENT)) + "
 
 Total Source Data Received" + formatSizeString(bandWidth::getAmount(bandWidth::ALL_SOURCE_RECV)) + "
v2 Source(s)" + formatSizeString(bandWidth::getAmount(bandWidth::SOURCE_V2_RECV)) + "
v1 Source(s)" + formatSizeString(bandWidth::getAmount(bandWidth::SOURCE_V1_RECV)) + "
 
 Total Source Data Sent" + formatSizeString(bandWidth::getAmount(bandWidth::ALL_SOURCE_SENT)) + "
v2 Source(s)" + formatSizeString(bandWidth::getAmount(bandWidth::SOURCE_V2_SENT)) + "
v1 Source(s)" + formatSizeString(bandWidth::getAmount(bandWidth::SOURCE_V1_SENT)) + "
 
 Total Relay Data Received" + formatSizeString(bandWidth::getAmount(bandWidth::ALL_RELAY_RECV)) + "
Handshaking" + formatSizeString(bandWidth::getAmount(bandWidth::RELAY_MISC_RECV)) + "
v2 Relay(s)" + formatSizeString(bandWidth::getAmount(bandWidth::RELAY_V2_RECV)) + "
v1 Relay(s)" + formatSizeString(bandWidth::getAmount(bandWidth::RELAY_V1_RECV)) + "
 
 Total Web Page, XML and Resoures  " + formatSizeString(bandWidth::getAmount(bandWidth::ALL_WEB)) + "
Public (e.g. /stats or index.html)" + formatSizeString(bandWidth::getAmount(bandWidth::PUBLIC_WEB)) + "
Private (e.g. /admin.cgi pages)" + formatSizeString(bandWidth::getAmount(bandWidth::PRIVATE_WEB)) + "
 
 Total Other Data" + formatSizeString(bandWidth::getAmount(bandWidth::ALL_OTHER)) + "
Flash Policy Server" + formatSizeString(bandWidth::getAmount(bandWidth::FLASH_POLICY)) + "
v2 Relay(s) Sent" + formatSizeString(bandWidth::getAmount(bandWidth::RELAY_V2_SENT)) + "
YP Sent" + formatSizeString(bandWidth::getAmount(bandWidth::YP_SENT)) + "
YP Received" + formatSizeString(bandWidth::getAmount(bandWidth::YP_RECV)) + "
Listener Authentication and Metrics" + formatSizeString(bandWidth::getAmount(bandWidth::AUTH_AND_METRICS)) + "
Advert Retrieval" + formatSizeString(bandWidth::getAmount(bandWidth::ADVERTS)) + "
" + getUptimeScript() + getIEFlexFix() + getfooterStr(); COMPRESS(header, body); header += "Content-Length: " + tos(body.size()) + "\r\n\r\n"; sendMessageAndClose(header + (!HEAD_REQUEST ? body : "")); } void protocol_admincgi::mode_bandwidth_xml() throw() { utf8 header = "HTTP/1.1 200 OK\r\nContent-Type:text/xml\r\n", body = "" "" "" + tos(bandWidth::getAmount(bandWidth::ALL)) + "" "" + tos(bandWidth::getAmount(bandWidth::ALL_SENT)) + "" "" + tos(bandWidth::getAmount(bandWidth::ALL_RECV)) + "" "" "" "" + tos(bandWidth::getAmount(bandWidth::ALL_CLIENT_SENT)) + "" "" + tos(bandWidth::getAmount(bandWidth::CLIENT_V2_SENT)) + "" "" + tos(bandWidth::getAmount(bandWidth::CLIENT_V1_SENT)) + "" "" + tos(bandWidth::getAmount(bandWidth::CLIENT_HTTP_SENT)) + "" "" + tos(bandWidth::getAmount(bandWidth::CLIENT_FLV_SENT)) + "" #if 0 "" + tos(bandWidth::getAmount(bandWidth::CLIENT_M4A_SENT)) + "" #endif "" "" "" + tos(bandWidth::getAmount(bandWidth::ALL_SOURCE_RECV)) + "" "" + tos(bandWidth::getAmount(bandWidth::SOURCE_V2_RECV)) + "" "" + tos(bandWidth::getAmount(bandWidth::SOURCE_V1_RECV)) + "" "" "" "" + tos(bandWidth::getAmount(bandWidth::ALL_SOURCE_SENT)) + "" "" + tos(bandWidth::getAmount(bandWidth::SOURCE_V2_SENT)) + "" "" + tos(bandWidth::getAmount(bandWidth::SOURCE_V1_SENT)) + "" "" "" "" + tos(bandWidth::getAmount(bandWidth::ALL_RELAY_RECV)) + "" "" + tos(bandWidth::getAmount(bandWidth::RELAY_MISC_RECV)) + "" "" + tos(bandWidth::getAmount(bandWidth::RELAY_V2_RECV)) + "" "" + tos(bandWidth::getAmount(bandWidth::RELAY_V1_RECV)) + "" "" "" "" + tos(bandWidth::getAmount(bandWidth::ALL_WEB)) + "" "" + tos(bandWidth::getAmount(bandWidth::PUBLIC_WEB)) + "" "" + tos(bandWidth::getAmount(bandWidth::PRIVATE_WEB)) + "" "" "" "" + tos(bandWidth::getAmount(bandWidth::ALL_OTHER)) + "" "" + tos(bandWidth::getAmount(bandWidth::FLASH_POLICY)) + "" "" + tos(bandWidth::getAmount(bandWidth::RELAY_V2_SENT)) + "" "" + tos(bandWidth::getAmount(bandWidth::YP_SENT)) + "" "" + tos(bandWidth::getAmount(bandWidth::YP_RECV)) + "" "" + tos(bandWidth::getAmount(bandWidth::AUTH_AND_METRICS)) + "" "" + tos(bandWidth::getAmount(bandWidth::ADVERTS)) + "" "" ""; COMPRESS(header, body); header += "Content-Length:" + tos(body.size()) + "\r\n\r\n"; sendMessageAndClose(header + (!HEAD_REQUEST ? body : "")); } void protocol_admincgi::mode_bandwidth_json(const uniString::utf8& callback) throw() { const bool jsonp = !callback.empty(); utf8 header = "HTTP/1.1 200 OK\r\nContent-Type:application/json;charset=utf-8\r\n", body = (jsonp ? callback + "(" : "") + "{" "\"total\":" + tos(bandWidth::getAmount(bandWidth::ALL)) + "," "\"sent\":" + tos(bandWidth::getAmount(bandWidth::ALL_SENT)) + "," "\"recv\":" + tos(bandWidth::getAmount(bandWidth::ALL_RECV)) + "," "\"time\":" + tos((::time(NULL) - g_upTime)) + "," "\"clientsent\":{" "\"total\":" + tos(bandWidth::getAmount(bandWidth::ALL_CLIENT_SENT)) + "," "\"v2\":" + tos(bandWidth::getAmount(bandWidth::CLIENT_V2_SENT)) + "," "\"v1\":" + tos(bandWidth::getAmount(bandWidth::CLIENT_V1_SENT)) + "," "\"http\":" + tos(bandWidth::getAmount(bandWidth::CLIENT_HTTP_SENT)) + "," "\"flv\":" + tos(bandWidth::getAmount(bandWidth::CLIENT_FLV_SENT)) + #if 0 "," "\"m4a\":" + tos(bandWidth::getAmount(bandWidth::CLIENT_M4A_SENT)) + #endif "},\"sourcerecv\":{" "\"total\":" + tos(bandWidth::getAmount(bandWidth::ALL_SOURCE_RECV)) + "," "\"v2\":" + tos(bandWidth::getAmount(bandWidth::SOURCE_V2_RECV)) + "," "\"v1\":" + tos(bandWidth::getAmount(bandWidth::SOURCE_V1_RECV)) + "},\"sourcesent\":{" "\"total\":" + tos(bandWidth::getAmount(bandWidth::ALL_SOURCE_SENT)) + "," "\"v2\":" + tos(bandWidth::getAmount(bandWidth::SOURCE_V2_SENT)) + "," "\"v1\":" + tos(bandWidth::getAmount(bandWidth::SOURCE_V2_SENT)) + "},\"relayrecv\":{" "\"total\":" + tos(bandWidth::getAmount(bandWidth::ALL_SOURCE_RECV)) + "," "\"misc\":" + tos(bandWidth::getAmount(bandWidth::RELAY_MISC_RECV)) + "," "\"v2\":" + tos(bandWidth::getAmount(bandWidth::RELAY_V2_RECV)) + "," "\"v1\":" + tos(bandWidth::getAmount(bandWidth::RELAY_V1_RECV)) + "},\"webpages\":{" "\"total\":" + tos(bandWidth::getAmount(bandWidth::ALL_WEB)) + "," "\"public\":" + tos(bandWidth::getAmount(bandWidth::PUBLIC_WEB)) + "," "\"private\":" + tos(bandWidth::getAmount(bandWidth::PRIVATE_WEB)) + "},\"other\":{" "\"total\":" + tos(bandWidth::getAmount(bandWidth::ALL_OTHER)) + "," "\"flash\":" + tos(bandWidth::getAmount(bandWidth::FLASH_POLICY)) + "," "\"relaysentv2\":" + tos(bandWidth::getAmount(bandWidth::RELAY_V2_SENT)) + "," "\"ypsent\":" + tos(bandWidth::getAmount(bandWidth::YP_SENT)) + "," + "\"yprecv\":" + tos(bandWidth::getAmount(bandWidth::YP_RECV)) + "," + "\"auth_metrics\":" + tos(bandWidth::getAmount(bandWidth::AUTH_AND_METRICS)) + "," + "\"adverts\":" + tos(bandWidth::getAmount(bandWidth::ADVERTS)) + "}}" + (jsonp ? utf8(")") : ""); COMPRESS(header, body); header += "Content-Length:" + tos(body.size()) + "\r\n\r\n"; sendMessageAndClose(header + (!HEAD_REQUEST ? body : "")); } void protocol_admincgi::mode_ypstatus_xml() throw() { utf8 header = "HTTP/1.1 200 OK\r\nContent-Type:text/xml\r\n", body = "" ""; streamData::streamIDs_t streamIds = streamData::getStreamIds(); config::streams_t streams; gOptions.getStreamConfigs(streams); for (config::streams_t::const_iterator i = streams.begin(); i != streams.end(); ++i) { if ((*i).second.m_streamID) { streamIds.insert((*i).second.m_streamID); } } for (streamData::streamIDs_t::const_iterator i = streamIds.begin(); i != streamIds.end(); ++i) { streamData::streamInfo info; streamData::extraInfo extra; streamData::getStreamInfo((*i), info, extra); const utf8 movedUrl = gOptions.stream_movedUrl((*i)); if (movedUrl.empty()) { body += "" + (!extra.isConnected ? "NOSOURCE" : (info.m_streamPublic ? (extra.ypConnected != 1 ? (!yp2::isValidAuthhash(info.m_authHash) ? (info.m_authHash.empty() ? "EMPTY_AUTHHASH" : "INVALID_AUTHHASH") : (extra.ypErrorCode == 200 ? "WAITING" : (extra.ypErrorCode == YP_COMMS_FAILURE ? "YP_NOT_FOUND" : (extra.ypErrorCode == YP_MAINTENANCE_CODE ? "YP_MAINTENANCE" : "ERROR" + tos(extra.ypErrorCode) + "")))) : "PUBLIC" + info.m_stationID + "") : "PRIVATE")) + ""; } else { body += "MOVED"; } } body += ""; COMPRESS(header, body); header += "Content-Length:" + tos(body.size()) + "\r\n\r\n"; sendMessageAndClose(header + (!HEAD_REQUEST ? body : "")); } void protocol_admincgi::mode_ypstatus_json(const uniString::utf8& callback) throw() { utf8 header = "HTTP/1.1 200 OK\r\nContent-Type:application/json;charset=utf-8\r\n"; streamData::streamIDs_t streamIds = streamData::getStreamIds(); config::streams_t streams; gOptions.getStreamConfigs(streams); for (config::streams_t::const_iterator i = streams.begin(); i != streams.end(); ++i) { if ((*i).second.m_streamID) { streamIds.insert((*i).second.m_streamID); } } const bool jsonp = !callback.empty(); utf8 body = (jsonp ? callback + "(" : "") + "{" + (!streamIds.empty() ? "\"streams\":[" : ""); bool read = false; for (streamData::streamIDs_t::const_iterator i = streamIds.begin(); i != streamIds.end(); ++i) { streamData::streamInfo info; streamData::extraInfo extra; streamData::getStreamInfo((*i), info, extra); const utf8 movedUrl = gOptions.stream_movedUrl((*i)); if (movedUrl.empty()) { body += (read ? utf8(",") : "") + "{" "\"id\":" + tos((*i)) + "," + (!extra.isConnected ? "\"status\":\"" + escapeJSON("nosource") + "\"" : (info.m_streamPublic ? (extra.ypConnected != 1 ? (!yp2::isValidAuthhash(info.m_authHash) ? (info.m_authHash.empty() ? "\"status\":\"" + escapeJSON("empty_authhash") + "\"" : "\"status\":\"" + escapeJSON("invalid_authhash") + "\"") : (extra.ypErrorCode == 200 ? "\"status\":\"" + escapeJSON("waiting") + "\"" : (extra.ypErrorCode == YP_COMMS_FAILURE ? "\"status\":\"" + escapeJSON("yp_not_found") + "\"" : (extra.ypErrorCode == YP_MAINTENANCE_CODE ? "\"status\":\"" + escapeJSON("yp_maintenance") + "\"" : "\"status\":\"" + escapeJSON("error") + "\",\"code\":" + tos(extra.ypErrorCode))))) : "\"status\":\"" + escapeJSON("public") + "\",\"stnid\":" + info.m_stationID) : "\"status\":\"" + escapeJSON("private") + "\"")) + "}"; } else { body += (read ? utf8(",") : "") + "{\"id\":" + tos((*i)) + "," "\"status\":\"" + escapeJSON("moved") + "\"}"; } read = true; } body += (!streamIds.empty() ? utf8("]") : "") + "}" + (jsonp ? utf8(")") : ""); COMPRESS(header, body); header += "Content-Length:" + tos(body.size()) + "\r\n\r\n"; sendMessageAndClose(header + (!HEAD_REQUEST ? body : "")); } void protocol_admincgi::mode_viewxml(const streamData::streamID_t sid, int page, const bool iponly, const bool ipcount) throw() { // abort as there's nothing generated for this now if (page == 2) { sendMessageAndClose("HTTP/1.1 200 OK\r\nContent-Type:text/xml\r\n\r\n"); return; } if ((page > 6) || (page < 0)) { page = 0; } utf8 header = "HTTP/1.1 200 OK\r\nContent-Type:text/xml\r\n", body = ""; if (page < 2) { stats::statsData_t data; body += protocol_HTTPStyle::getStatsXMLBody(sid, true, true, m_socket, data); } if ((page == 0) || (page == 3)) { time_t t = ::time(NULL); body += ""; stats::currentClientList_t client_data; stats::getClientDataForStream(sid, client_data); map ip_counts; for (stats::currentClientList_t::const_iterator i = client_data.begin(); i != client_data.end(); ++i) { const utf8& host = ((*i)->m_hostName != (*i)->m_ipAddr ? (*i)->m_hostName : (*i)->m_ipAddr); if (!ipcount) { if (!iponly) { body += "" "" + aolxml::escapeXML(host) + "" "" + aolxml::escapeXML((*i)->m_userAgent) + "" "" + tos(t - (*i)->m_startTime) + "" "" + tos((*i)->m_unique) + "" "" + tos((*i)->m_clientType) + "" "" + aolxml::escapeXML((*i)->m_referer) + "" "" + aolxml::escapeXML((*i)->m_XFF) + "" "" + tos((*i)->m_group) + "" "" + tos((*i)->m_triggers) + "" ""; } else { body += "" "" + aolxml::escapeXML(host) + "" ""; } } else { ++ip_counts[host]; } delete (*i); } if (ipcount) { for (map::const_iterator i = ip_counts.begin(); i != ip_counts.end(); ++i) { body += "" "" + aolxml::escapeXML((*i).first) + "" "" + tos((*i).second) + "" ""; } } body += ""; } if ((page == 0) || (page == 4) || (page == 5)) { streamData::streamHistory_t songHistory; streamData::getStreamSongHistory(sid, songHistory); // only provide this as an option feature so as not to bloat the main xml stats unnecessarily if (!(page == 5)) { body += ""; for (streamData::streamHistory_t::const_iterator i = songHistory.begin(); i != songHistory.end(); ++i) { body += "" + tos((*i).m_when) + "" "" + aolxml::escapeXML((*i).m_title) + "" + protocol_HTTPStyle::getCurrentXMLMetadataBody(true, (*i).m_metadata) + ""; } body += ""; } else { body += protocol_HTTPStyle::getCurrentXMLMetadataBody(false, (!songHistory.empty() ? songHistory[0].m_metadata : "")); } } if (page == 6) { const int maxUsers = gOptions.maxUser(); body += "" "" + tos(gOptions.requireStreamConfigs()) + "" "" + (maxUsers > 0 ? tos(maxUsers) : "UNLIMITED") + "" "" + tos(gOptions.minBitrate()) + "" "" + tos(gOptions.maxBitrate()) + "" ""; config::streams_t streams; gOptions.getStreamConfigs(streams); utf8 block = ""; size_t moved = 0; for (config::streams_t::const_iterator i = streams.begin(); i != streams.end(); ++i) { const utf8 movedUrl = gOptions.stream_movedUrl((*i).second.m_streamID); if (movedUrl.empty()) { utf8 streamTag = ((*i).second.m_streamID > 1 ? "/stream/" + tos((*i).second.m_streamID) + "/" : "/"); if (!(*i).second.m_urlPath.empty()) { streamTag = (*i).second.m_urlPath; } const utf8& authhash = (*i).second.m_authHash; block += "" "" + aolxml::escapeXML(authhash.empty() ? "EMPTY" : !yp2::isValidAuthhash(authhash) ? "INVALID" : authhash) + "" "" + aolxml::escapeXML(streamTag) + "" "" + aolxml::escapeXML((*i).second.m_relayUrl.url()) + "" "" + aolxml::escapeXML((*i).second.m_backupUrl.url()) + "" "" + ((*i).second.m_maxStreamUser > 0 && (*i).second.m_maxStreamUser < gOptions.maxUser() ? tos((*i).second.m_maxStreamUser) : "SERVERMAXLISTENERS") + "" "" + ((*i).second.m_minStreamBitrate > 0 ? tos((*i).second.m_minStreamBitrate) : "SERVERMINBITRATE") + "" "" + ((*i).second.m_maxStreamBitrate > 0 ? tos((*i).second.m_maxStreamBitrate) : "SERVERMAXBITRATE") + "" "" + aolxml::escapeXML((*i).second.m_publicServer) + "" "" + tos((*i).second.m_allowRelay) + "" "" + tos((*i).second.m_allowPublicRelay) + "" ""; } else { ++moved; } } body += tos((streams.size() - moved)) + "" + block + ""; } body += ""; COMPRESS(header, body); header += "Content-Length:" + tos(body.size()) + "\r\n\r\n"; sendMessageAndClose(header + (!HEAD_REQUEST ? body : "")); } void protocol_admincgi::mode_viewjson(const streamData::streamID_t sid, int page, const bool iponly, const bool ipcount, const uniString::utf8& callback) throw() { // abort as there's nothing generated for this now if (page == 2) { sendMessageAndClose("HTTP/1.1 200 OK\r\nContent-Type:application/json;charset=utf-8\r\n\r\n"); return; } if (page > 6 || page < 0) { page = 0; } const bool jsonp = !callback.empty(); utf8 header = "HTTP/1.1 200 OK\r\nContent-Type:application/json;charset=utf-8\r\n", body = (jsonp ? callback + "(" : "") + (page < 2 ? "{" : ""); if (page < 2) { stats::statsData_t data; body += protocol_HTTPStyle::getStatsJSONBody(sid, true, true, m_socket, data); } if ((page == 0) || (page == 3)) { time_t t = ::time(NULL); body += (!page ? utf8(",\"listeners\":[") : "["); stats::currentClientList_t client_data; stats::getClientDataForStream(sid, client_data); map ip_counts; for (stats::currentClientList_t::const_iterator i = client_data.begin(); i != client_data.end(); ++i) { const utf8& host = ((*i)->m_hostName != (*i)->m_ipAddr ? (*i)->m_hostName : (*i)->m_ipAddr); if (!ipcount) { if (!iponly) { body += (i != client_data.begin() ? utf8(",") : "") + "{" "\"hostname\":\"" + escapeJSON(host) + "\"," "\"useragent\":\"" + escapeJSON((*i)->m_userAgent) + "\"," "\"connecttime\":" + tos(t - (*i)->m_startTime) + "," "\"uid\":" + tos((*i)->m_unique) + "," "\"type\":" + tos((*i)->m_clientType) + "," "\"referer\":\"" + escapeJSON((*i)->m_referer) + "\"," "\"xff\":\"" + escapeJSON((*i)->m_XFF) + "\"," "\"grid\":" + tos((*i)->m_group) + "," "\"triggers\":" + tos((*i)->m_triggers) + "" "}"; } else { body += (i != client_data.begin() ? utf8(",") : "") + "{" "\"hostname\":\"" + escapeJSON(host) + "\"" "}"; } } else { ++ip_counts[host]; } delete (*i); } if (ipcount) { for (map::const_iterator i = ip_counts.begin(); i != ip_counts.end(); ++i) { body += (i != ip_counts.begin() ? utf8(",") : "") + "{" "\"hostname\":\"" + escapeJSON((*i).first) + "\"," "\"total\":" + tos((*i).second) + "" "}"; } } body += "]"; } if ((page == 0) || (page == 4) || (page == 5)) { streamData::streamHistory_t songHistory; streamData::getStreamSongHistory(sid, songHistory); // only provide this as an option feature so as not to bloat the main xml stats unnecessarily if (!(page == 5)) { bool first = true; body += (!page ? utf8(",\"songs\":[") : "["); for (streamData::streamHistory_t::const_iterator i = songHistory.begin(); i != songHistory.end(); ++i) { body += (!first ? utf8(",") : "") + "{\"playedat\":" + tos((*i).m_when) + "," "\"title\":\"" + escapeJSON((*i).m_title) + "\",\"metadata\":" + protocol_HTTPStyle::getCurrentJSONMetadataBody((*i).m_metadata) + "}"; first = false; } body += "]"; } else { body += protocol_HTTPStyle::getCurrentJSONMetadataBody((!songHistory.empty() ? songHistory[0].m_metadata : "")); } } if (page == 6) { const int maxUsers = gOptions.maxUser(); body += "{" "\"requireconfigs\":" + tos(gOptions.requireStreamConfigs()) + "," "\"maxlisteners\":" + (maxUsers > 0 ? tos(maxUsers) : "\"unlimited\"") + "," "\"minbitrate\":" + tos(gOptions.minBitrate()) + "," "\"maxbitrate\":" + tos(gOptions.maxBitrate()) + ","; config::streams_t streams; gOptions.getStreamConfigs(streams); bool read = false; utf8 block = ""; size_t moved = 0; for (config::streams_t::const_iterator i = streams.begin(); i != streams.end(); ++i) { const utf8 movedUrl = gOptions.stream_movedUrl((*i).second.m_streamID); if (movedUrl.empty()) { utf8 streamTag = ((*i).second.m_streamID > 1 ? "/stream/" + tos((*i).second.m_streamID) + "/" : "/"); if (!(*i).second.m_urlPath.empty()) { streamTag = (*i).second.m_urlPath; } utf8 authhash = (*i).second.m_authHash; block += (read ? utf8(",") : "") + "{" "\"id\":" + tos((*i).second.m_streamID) + "," "\"authhash\":\"" + escapeJSON(authhash.empty() ? "empty" : !yp2::isValidAuthhash(authhash) ? "invalid" : authhash) + "\"," "\"path\":\"" + escapeJSON(streamTag) + "\"," "\"relayurl\":\"" + escapeJSON((*i).second.m_relayUrl.url()) + "\"," "\"backupurl\":\"" + escapeJSON((*i).second.m_backupUrl.url()) + "\"," "\"maxlisteners\":\"" + escapeJSON(((*i).second.m_maxStreamUser > 0) && ((*i).second.m_maxStreamUser < gOptions.maxUser()) ? tos((*i).second.m_maxStreamUser) : escapeJSON("maxlisteners")) + "\"," "\"minbitrate\":\"" + escapeJSON((*i).second.m_minStreamBitrate > 0 ? tos((*i).second.m_minStreamBitrate) : escapeJSON("minbitrate")) + "\"," "\"maxbitrate\":\"" + escapeJSON((*i).second.m_maxStreamBitrate > 0 ? tos((*i).second.m_maxStreamBitrate) : escapeJSON("maxbitrate")) + "\"," "\"public\":\"" + escapeJSON((*i).second.m_publicServer) + "\"," "\"allowrelay\":\"" + tos((*i).second.m_allowRelay) + "\"," "\"publicrelay\":\"" + tos((*i).second.m_allowPublicRelay) + "\"" "}"; read = true; } else { ++moved; } } const size_t total = (streams.size() - moved); body += "\"total\":\"" + tos(total) + "\"" + (total > 0 ? ",\"streams\":[" : "") + block + (total > 0 ? utf8("]") : "") + "}"; } body += (page < 2 ? utf8("}") : "") + (jsonp ? ")" : ""); COMPRESS(header, body); header += "Content-Length:" + tos(body.size()) + "\r\n\r\n"; sendMessageAndClose(header + (!HEAD_REQUEST ? body : "")); } void protocol_admincgi::mode_sources(const uniString::utf8& host) throw() { utf8 header = MSG_NO_CLOSE_200, body = getServerAdminHeader("Server Source Connection Details") + "" "
" + utf8(gOptions.requireStreamConfigs() ? "Sources are only able to connect on the configured streams shown" : "Sources are able to connect on any \"Stream ID\" (using the appropriate details)") + "
" "

" "
" "
Information
" "Here are the login details to enter in your chosen source software so it can then be used to connect to the server.

" "Note: Names of options in the source software may vary from those shown.

" "* This may not be correct and is a best guess from the current connection.



" "The 'Legacy Password' is for use with legacy sources (which are sources that are not able to directly specify the " "desired stream number).

This allows for use of any Shoutcast compatible source on any of the currently configured streams.

" "If connecting a legacy source to stream #1, the 'Password' value can be used.



" "The example JSON-P response uses callback=func which can be changed to a custom value as required for usage.



" "For further information on connecting sources to the server, as well as ensuring that all of the required ports have been opened, " "see this wiki page.

"; config::streams_t streams; gOptions.getStreamConfigs(streams); utf8 ids = ""; for (config::streams_t::const_iterator i = streams.begin(); i != streams.end(); ++i) { ids += (i != streams.begin() ? ", ": "") + tos((*i).second.m_streamID); // skip over moved streams as they cannot be used, though we include the // streamid above (whether or not that'll confuse people i do not know). const utf8 movedUrl = gOptions.stream_movedUrl((*i).second.m_streamID); if (movedUrl.empty()) { int maxbitrate = ((*i).second.m_maxStreamBitrate > 0 ? (*i).second.m_maxStreamBitrate : gOptions.maxBitrate()), minbitrate = ((*i).second.m_minStreamBitrate > 0 ? (*i).second.m_minStreamBitrate : gOptions.minBitrate()); // test for being a relay which is active - if so then we need to not show // direct source login details as they will not be able to connect anyway bool noEntry = false, isRelay = (*i).second.m_relayUrl.isSet(); if (isRelay && streamData::isRelayActive((*i).second.m_streamID, noEntry)) { body += "
" "
" "Stream #" + tos((*i).second.m_streamID) + "
" "This stream has been configured for use as a relay which is currently active.

" "Any source connections attempted will be blocked whilst the relay is active.

" "Password: " + aolxml::escapeXML((*i).second.m_password) + "
" "Source: " + urlLink(niceURL((*i).second.m_relayUrl.url())) + "
" + (!(*i).second.m_backupUrl.url().empty() ? "Backup Source: " + urlLink(niceURL((*i).second.m_backupUrl.url())) + "
" : ""); } // otherwise show direct source login details for specific as well as generic stream // logins (even if configured to be a relay as long as it is inactive at the time) else { body += "
" "
Stream #" + tos((*i).second.m_streamID) + "
" + (isRelay ? "Note: Configured as a relay to use:
" + niceURL((*i).second.m_relayUrl.url()) + "

" : "") + "Stream ID: " + tos((*i).second.m_streamID) + "
" "Server Address: " + (!host.empty() ? host : "") + " (*)
" "Source Port: " + tos(gOptions.portBase()) + "

" + "Password: " + aolxml::escapeXML((*i).second.m_password) + "
" + ((*i).second.m_streamID > 1 && (g_legacyPort > 0) ? "Legacy Password: " + aolxml::escapeXML((*i).second.m_password) + ":#" + tos((*i).second.m_streamID) + "

" : "
") + "Min Bitrate Allowed: " + (!minbitrate ? "Unlimited" : tos(minbitrate / 1000) + " kbps") + "
" "Max Bitrate Allowed: " + (!maxbitrate ? "Unlimited" : tos(maxbitrate / 1000) + " kbps") + "

" "Protocol Mode: " + ((g_legacyPort > 0) ? "v2, v1" : "v2") + "
"; } const utf8 address = "http://" + ((!host.empty() ? host : "") + ":" + tos(gOptions.portBase())), params = "?sid=" + tos((*i).second.m_streamID) + "&pass=" + aolxml::escapeXML((*i).second.m_password); body += "


Listener Playlist(s):
" "PLS,  " "M3U,  " "ASX,  " "XSPF,  " "QTL" "

Direct Stream URL(s):
" "" " FLV,  " "" " HTTP,  " "" " 1.x,  " "" " 2.x" "

History:
" "XML,  " "JSON,  " "JSON-P" "

Stream Statistics:
" "XML,  " "JSON,  " "JSON-P
"; } } if (!gOptions.requireStreamConfigs()) { const int minbitrate = gOptions.minBitrate(), maxbitrate = gOptions.maxBitrate(); body += "
" "
All" + (!ids.empty() ? " Other" : (utf8)"") + " Streams
" "Stream ID: <any value" + (!ids.empty() ? " other than " + ids : "") + ">
" "Server Address: " + aolxml::escapeXML(!host.empty() ? host : "") + " (*)
" "Server Port: " + tos(gOptions.portBase()) + "

" + "Password: " + aolxml::escapeXML(gOptions.password()) + "
" + ((g_legacyPort > 0) ? "Legacy Password: " + aolxml::escapeXML(gOptions.password()) + ":#xx

" : "
") + "Min Bitrate Allowed: " + (!minbitrate ? "Unlimited" : tos(minbitrate / 1000) + " kbps") + "
" "Max Bitrate Allowed: " + (!maxbitrate ? "Unlimited" : tos(maxbitrate / 1000) + " kbps") + "

" "Protocol Mode: " + ((g_legacyPort > 0) ? "v2, v1" : "v2") + "
"; const utf8 address = "http://" + ((!host.empty() ? host : "") + ":" + tos(gOptions.portBase())), params = "?sid=xx&pass=" + aolxml::escapeXML(gOptions.password()); body += "


Listener Playlist(s):
" "PLS,  " "M3U,  " "ASX,  " "XSPF,  " "QTL" "

History:
" "XML,  " "JSON,  " "JSON-P" "

Stream Statistics:
" "XML,  " "JSON,  " "JSON-P" "

Note: Replace the 'xx' in 'sid=xx' in all of the example links " "and in the 'Legacy Password' with the stream number.
"; } body += "
" + getUptimeScript() + getIEFlexFix() + getfooterStr(); COMPRESS(header, body); header += "Content-Length: " + tos(body.size()) + "\r\n\r\n"; sendMessageAndClose(header + (!HEAD_REQUEST ? body : "")); } void protocol_admincgi::mode_adgroups() throw() { utf8 header = MSG_NO_CLOSE_200, body = getServerAdminHeader("Server Advert Group Details") + "

" "" "
" "
" "
" "
Information
" "Here you can see which active stream(s) have adverts enabled and their status of obtaining the advert details." "

This can help to see if a listener should be receiving adverts or not by " "checking for the listener's group id with the group ids shown for the required stream.

" "
" ""; const streamData::streamIDs_t streamIds = streamData::getStreamIds(); if (!streamIds.empty()) { body += "" ""; for (streamData::streamIDs_t::const_iterator i = streamIds.begin(); i != streamIds.end(); ++i) { streamData *sd = streamData::accessStream((*i)); if (sd) { if (sd->isSourceConnected((*i))) { body += "" ""; } sd->releaseStream(); } } } else { body += ""; } body += "
Stream #Advert Group(s)
" + tos((*i)) + "" + sd->getAdvertGroup() + "
There are no active streams.
" + getUptimeScript() + getIEFlexFix() + getfooterStr(); COMPRESS(header, body); header += "Content-Length: " + tos(body.size()) + "\r\n\r\n"; sendMessageAndClose(header + (!HEAD_REQUEST ? body : "")); } void protocol_admincgi::mode_debug(const uniString::utf8& option, const int on_off, const bool adminRefer) throw() { struct debug_options { utf8 option; utf8 desc; utf8 msg; bool value; }; debug_options debug[] = { {"Listener Connections", "", ""}, {"shoutcast1clientdebug", "Shoutcast 1.x Listener Connections", "Used to diagnose connection issues with listener connections using the legacy Shoutcast 1.x protocol" "
(e.g. HTTP connections indicating they support icy-metadata).", gOptions.shoutcast1ClientDebug()}, {"shoutcast2clientdebug", "Shoutcast 2.x Listener Connections", "Used to diagnose connection issues with listener connections using the Shoutcast 2.x protocol.", gOptions.shoutcast2ClientDebug()}, {"httpclientdebug", "HTTP Listener Connections", "Used to diagnose connection issues with listener connections using the HTTP protocol.", gOptions.HTTPClientDebug()}, {"flvclientdebug", "FLV Listener Connections", "Used to diagnose connection issues with listener connections using the FLV encapsulation.", gOptions.flvClientDebug()}, #if 0 {"m4aclientdebug", "M4A Listener Connections", "Used to diagnose connection issues with listener connections using the M4A encapsulation.", gOptions.m4aClientDebug()}, #endif {"Direct Source Connections", "", ""}, {"shoutcastsourcedebug", "Shoutcast 1.x Source Connections", "Used to diagnose connection issues with source connections using the legacy Shoutcast 1.x protocol.", gOptions.shoutcastSourceDebug()}, {"uvox2sourcedebug", "Shoutcast 2.x Source Connections", "Used to diagnose connection issues with source connections using the Shoutcast 2.x protocol.", gOptions.uvox2SourceDebug()}, {"httpsourcedebug", "HTTP Source Connections", "Used to diagnose connection issues with source connections using HTTP PUT protocol (e.g. Icecast sources).", gOptions.HTTPSourceDebug()}, {"Relay Source Connections", "", ""}, {"relayshoutcastdebug", "Shoutcast 1.x Relay Connections", "Used to diagnose connection issues with relay connections using the legacy Shoutcast 1.x protocol.", gOptions.relayShoutcastDebug()}, {"relayuvoxdebug", "Shoutcast 2.x Relay Connections", "Used to diagnose connection issues with relay connections using the Shoutcast 2.x protocol.", gOptions.relayUvoxDebug()}, {"relaydebug", "Common Relay Handling", "Used to diagnose issues with general relay handling when a relay connection begins (non-protcool specific).", gOptions.relayDebug()}, {"HTTP Connections", "", ""}, {"httpstyledebug", "HTTP Requests", "Used to inspect HTTP requests made to the server (e.g. listener or statistic requests).", gOptions.httpStyleDebug()}, {"webclientdebug", "HTTP Connections", "Used to inspect and diagnose issues with HTTP connections issued by the server.", gOptions.webClientDebug()}, {"Shoutcast Services", "", ""}, {"admetricsdebug", "Advert & Metrics Handling", "Used to inspect and diagnose issues with the advert and metric activity for client connections.", gOptions.adMetricsDebug()}, {"yp2debug", "YP / Directory Connections", "Used to diagnose failures to connect to the Directory servers or to be listed.", gOptions.yp2Debug()}, #if defined(_DEBUG) || defined(DEBUG) // this is not enabled as we don't really want to promote the existence of this {"authdebug", "Listener Auth Handling", "Used to diagnose issues with the client auth handling when clients connect to the stream.", gOptions.authDebug()}, #endif {"Miscellaneous", "", ""}, {"streamdatadebug", "Common Stream Handling", "Used to diagnose issues with general streaming code (non-protcool specific).", gOptions.streamDataDebug()}, {"statsdebug", "Client Statistics", "Used to inspect client statistics tracked by the server.", gOptions.statsDebug()}, {"microserverdebug", "Common Server Activity", "Used to diagnose and track common server activity.", gOptions.microServerDebug()}, {"threadrunnerdebug", "Thread Manager", "Used to diagnose and track the processing of threads by the thread manager.", gOptions.threadRunnerDebug()}, }; if (option.empty()) { utf8 header = "HTTP/1.1 200 OK\r\n" "Cache-Control:no-cache\r\n" "Content-Type:text/html;charset=utf-8\r\n", body = getServerAdminHeader("Server Debugging Options") + "

" "" "
" "
" "
" "
Information
" "Here you can find are all debugging options provided by the " "DNAS server.

Changing the debugging options will update " "the value saved in the DNAS server configuration file(s) as " "needed.

Any changes are applied immediately.



" "
All Options

" "" "   " "



" "
Listener Options

" "" "   " "



" "
Source Options

" "" "   " "



" "
Force Short Sends
[" + (gOptions.forceShortSends() ? "Enabled" : "Disabled") + "]


This inserts delays into sending stream(s)" "to replicate network limiting and bandwidth issues (Default: Disabled).

" "" "   " "



" "
Rate Limiting
" "[" + (gOptions.rateLimit() ? "Enabled" : "Disabled") + "]


Controls the rate of output to even " "it out and prefer sending larger blocks in one go instead of " "doing smaller blocks more often (Default: Enabled).

" "" "   " "
"; for (size_t i = 0; i < sizeof(debug) / sizeof(debug[0]); i++) { if (!debug[i].desc.empty()) { body += "" "" "
" + debug[i].msg + "

"; } else { body += (i ? "
" : (utf8)"") + "" + debug[i].option + ":

"; } } body += "
" + getUptimeScript() + "" + getIEFlexFix() + getfooterStr(); COMPRESS(header, body); header += "Content-Length: " + tos(body.size()) + "\r\n\r\n"; sendMessageAndClose(header + (!HEAD_REQUEST ? body : "")); } else { if (option == "all") { ILOG(LOGNAME + utf8(on_off ? "Enabling" : "Disabling") + " all debugging options"); for (size_t i = 0; i < sizeof(debug) / sizeof(debug[0]); i++) { if (!debug[i].desc.empty()) { // always set even if the saving fails so we've got something working try { gOptions.setOption(debug[i].option, utf8(tos(on_off))); } catch(const exception &) { } bool handled = false, idHandled = false; // if we get a clear then just remove from the config and reload the page if (gOptions.editConfigFileEntry(0, gOptions.confFile(), debug[i].option, tos(on_off), true, handled, idHandled, true) == false) { ELOG(LOGNAME "Error saving debug option: `" + debug[i].option + "'"); } } } } else if (option == "listener") { ILOG(LOGNAME + utf8(on_off ? "Enabling" : "Disabling") + " all listener debugging options"); const utf8 options[] = {"shoutcast1clientdebug", "shoutcast2clientdebug", "httpclientdebug", "flvclientdebug"/*, "m4aclientdebug"*/}; for (size_t i = 0; i < sizeof(options) / sizeof(options[0]); i++) { // always set even if the saving fails so we've got something working try { gOptions.setOption(options[i], utf8(tos(on_off))); } catch(const exception &) { } bool handled = false, idHandled = false; // if we get a clear then just remove from the config and reload the page if (gOptions.editConfigFileEntry(0, gOptions.confFile(), options[i], tos(on_off), true, handled, idHandled, true) == false) { ELOG(LOGNAME "Error saving debug option: `" + options[i] + "'"); } } } else if (option == "source") { ILOG(LOGNAME + utf8(on_off ? "Enabling" : "Disabling") + " all source debugging options"); const utf8 options[] = {"shoutcastsourcedebug", "uvox2sourcedebug", "httpsourcedebug", "relayshoutcastdebug", "relayuvoxdebug", "relaydebug"}; for (size_t i = 0; i < sizeof(options) / sizeof(options[0]); i++) { // always set even if the saving fails so we've got something working try { gOptions.setOption(options[i], utf8(tos(on_off))); } catch(const exception &) { } bool handled = false, idHandled = false; // if we get a clear then just remove from the config and reload the page if (gOptions.editConfigFileEntry(0, gOptions.confFile(), options[i], tos(on_off), true, handled, idHandled, true) == false) { ELOG(LOGNAME "Error saving debug option: `" + options[i] + "'"); } } } else { ILOG(LOGNAME + utf8(on_off ? "Enabling" : "Disabling") + " the `" + option + "' debugging options"); // always set even if the saving fails so we've got something working gOptions.setOption(option, utf8(tos(on_off))); bool handled = false, idHandled = false; // if we get a clear then just remove from the config and reload the page if (gOptions.editConfigFileEntry(0, gOptions.confFile(), option, tos(on_off), true, handled, idHandled, true) == false) { ELOG(LOGNAME "Error saving debug option: `" + option + "'"); } } if (adminRefer) { sendMessageAndClose(redirect("admin.cgi", SHRINK)); } else { sendMessageAndClose(MSG_200); } } } void protocol_admincgi::mode_help() throw() { utf8 libs = "Supporting Libraries:

"; std::vector parts = tokenizer(utf8(curl_version()), ' '); for (vector::const_iterator i = parts.begin(); i != parts.end(); ++i) { utf8::size_type pos = (*i).find('/'); if (pos != utf8::npos) { libs += (*i).substr(0, pos) + utf8(": ") + (*i).substr(pos + 1) + utf8(""); } else { libs += "" + (*i) + ""; } if ((*i).find((utf8)"libcurl") != utf8::npos) { libs += " (site)"; } else if ((*i).find((utf8)"OpenSSL") != utf8::npos) { libs += " (site)"; } else if ((*i).find((utf8)"zlib") != utf8::npos) { libs += " (site)"; } libs += "
"; } const int cpu_count = gOptions.getCPUCount(); const utf8 cpu = "CPU Count: " + tos(cpucount()) + " -> " + (cpu_count == cpucount() ? "using " + utf8(cpu_count > 1 ? "all" : "the") + " available CPU" + (cpu_count > 1 ? "s" : "") : tos(cpu_count) + " specified to be used") + "

"; XML_Expat_Version expat = XML_ExpatVersionInfo(); utf8 header = MSG_NO_CLOSE_200, body = getServerAdminHeader("Server Help & Documentation") + "

" "
" "
Help
" "If you are having issues, you should first try to contact your " "hosting provider.

If running the DNAS server yourself (or if instructed " "to do so), then you can use our forum " "or you can contact " "Shoutcast support directly (e.g. if the issue relates to an " "account / authhash issue).

Note: If using the forum, " "do not post any information which could be used to compromise " "your account / authhash / etc (e.g. passwords and authhash(s)).

" "
" "
Documentation
" "Supporting documentation for using the DNAS server from setup " "to getting statistics from the server are online.

" "Otherwise a local copy can usually be found on the host machine at:

" "
" + aolxml::escapeXML(gStartupDirectory) + "

Note: The local copy is usually correct for the version being " "used and the " "online version will be for the most recent release.

" "
" "
About
" "Information about the DNAS server and the supporting libraries currently used.

" "Version: " + addWBR(gOptions.getVersionBuildStrings()) + "
" "Platform: " + SERV_OSNAME "
" "Built: " __DATE__"

" + libs + // libcurl, openssl, zlib "expat: " + tos(expat.major) + "." + tos(expat.minor) + "." + tos(expat.micro) + "" " (site)" //#ifdef _WIN32 //"
pthread-win32: " PTW32_VERSION_STR "-mod" //" (site)" //#endif "

" "
Diagnostics
" + cpu + "Current thread & runner usage:

" + threadedRunner::getRunnabledetails() + "
" + getUptimeScript() + getIEFlexFix() + getfooterStr(); COMPRESS(header, body); header += "Content-Length: " + tos(body.size()) + "\r\n\r\n"; sendMessageAndClose(header + (!HEAD_REQUEST ? body : "")); } void protocol_admincgi::mode_config() throw() { utf8 conf = gOptions.confFile(); utf8::size_type pos = conf.rfind(fileUtil::getFilePathDelimiter()); if ((pos != utf8::npos)) { conf = conf.substr(pos + 1); } utf8 header = MSG_NO_CLOSE_200, body = getServerAdminHeader("Server Configuration Settings") + "
This page shows the custom configuration settings " "that this DNAS server is currently using. (Settings which match DNAS server defaults " "may not be shown.)

Note #1: To change these values, you will need to edit the " "" + (!conf.empty() ? conf : "sc_serv.conf") + " file on your server. See here " "for more information.

Note #2: This is not the same as the actual configuration file " "(i.e. the structure of the configuration file(s) being used is not shown)
" + gOptions.dumpConfigFile() + "
" + getUptimeScript() + getIEFlexFix() + getfooterStr(); COMPRESS(header, body); header += "Content-Length: " + tos(body.size()) + "\r\n\r\n"; sendMessageAndClose(header + (!HEAD_REQUEST ? body : "")); } #if 0 void protocol_admincgi::mode_logs() throw() { utf8 header = "HTTP/1.1 200 OK\r\n" "Cache-Control:no-cache\r\n" "Content-Type:text/html;charset=utf-8\r\n", headerTitle = "Server Log Management", childPage = ""; bool bwstyle = false; int refreshRequired = 0; utf8 body = s_serverAdminHeading; body += "

" "" "
" "
" "
" "
Information
" "Here you can find are all debugging options provided by the DNAS server.

Changing the debugging " "options will update the value saved in the DNAS server configuration file(s) as needed.

" "Any changes are applied immediately.

" "
"; utf8 logfile = gOptions.realLogFile(); body += "
Current log file:
"; body += "
" + aolxml::escapeXML(fileUtil::stripPath(logfile)) + "" "   View" "   Save" "
Size: " + formatSizeString(uniFile::fileSize(logfile)) + "

"; utf8 rotated = fileUtil::stripSuffix(logfile) + "_*." + fileUtil::getSuffix(logfile); body += "
Older log file(s):
"; body += "
"; #ifdef _WIN32 vector fileList = fileUtil::directoryFileList(utf8(fileUtil::getFullFilePath(rotated)).toWString(), L"", true, true); #else vector fileList = fileUtil::directoryFileList(fileUtil::getFullFilePath(rotated), ""); #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()); body += "
" + fileUtil::stripPath(u8f) + "" "
   View   " "Save

Size: " + formatSizeString(uniFile::fileSize(u8f)) + "" "
Last Modified: " + getRFCDate(uniFile::fileTime(u8f)) + "

"; #else body += "
" + fileUtil::stripPath(*i) + "   " "Save
"; #endif } } body += "
"; #if 0 utf8 archived = fileUtil::stripSuffix(logfile) + "_*.gz"; body += "
Log file (archived): "/* + aolxml::escapeXML(fileUtil::getFullFilePath(archived)) + */"

"; body += "
"; #ifdef _WIN32 fileList = fileUtil::directoryFileList(utf8(fileUtil::getFullFilePath(archived)).toWString(), L"", true, true); #else fileList = fileUtil::directoryFileList(fileUtil::getFullFilePath(archived), ""); #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()); body += "
" + fileUtil::stripPath(u8f) + "
"; #else body += "
" + fileUtil::stripPath(*i) + "
"; #endif } } body += "
"; body += "
"; utf8 w3cfile = gOptions.w3cLog(); body += "
W3C file: " + aolxml::escapeXML(fileUtil::getFullFilePath(w3cfile)) + "

"; utf8 archived = fileUtil::stripSuffix(w3cfile) + "_*.gz"; body += "
W3C file (archived): " + aolxml::escapeXML(fileUtil::getFullFilePath(archived)) + "

"; archived = fileUtil::stripSuffix(w3cfile) + "_*_w3c.gz"; body += "
W3C file (archived 2): " + aolxml::escapeXML(fileUtil::getFullFilePath(archived)) + "

"; body += "
"; config::streams_t streams; gOptions.getStreamConfigs(streams); for (config::streams_t::const_iterator i = streams.begin(); i != streams.end(); ++i) { // w3c logging if (gOptions.read_stream_w3cLog((*i).first)) { body += "
Stream W3C file: " + aolxml::escapeXML(fileUtil::getFullFilePath(gOptions.stream_w3cLog((*i).first))) + "

"; } } body += "
"; body += "
"; body += "
"; #ifdef _WIN32 fileList = fileUtil::directoryFileList(gStartupDirectory.toWString() + L"sc_w3c*.log", L"", true, true); #else fileList = fileUtil::directoryFileList(gStartupDirectory.hideAsString() + "sc_w3c*.log", ""); #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()); body += "
" + fileUtil::stripPath(u8f) + "
"; #else body += "
" + fileUtil::stripPath(*i) + "
"; #endif } } body += "
"; #endif body += "
" + getUptimeScript() + "" + getfooterStr(); COMPRESS(header, body); header += "Content-Length: " + tos(body.size()) + "\r\n\r\n"; sendMessageAndClose(header + (!HEAD_REQUEST ? body : "")); } #endif