protocol_shoutcastSource.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440
  1. #ifdef _WIN32
  2. #include <winsock2.h>
  3. #endif
  4. #include "protocol_shoutcastSource.h"
  5. #include "protocol_backup.h"
  6. #include "streamData.h"
  7. #include "global.h"
  8. #include "bandwidth.h"
  9. #include "MP3Header.h"
  10. #include "ADTSHeader.h"
  11. #include "services/stdServiceImpl.h"
  12. using namespace std;
  13. using namespace uniString;
  14. using namespace stringUtil;
  15. #define DEBUG_LOG(...) do { if (gOptions.shoutcastSourceDebug()) DLOG(__VA_ARGS__); } while (0)
  16. #define LOGNAME "SRC"
  17. protocol_shoutcastSource::protocol_shoutcastSource (microConnection &mc, const uniString::utf8 &password) throw (std::exception)
  18. : runnable (mc), m_srcPort(mc.m_srcPort), m_srcAddr(mc.m_srcAddress)
  19. {
  20. m_srcStreamID = DEFAULT_SOURCE_STREAM;
  21. m_denied = false;
  22. m_remainder = new __uint8[BUF_SIZE * 4];
  23. m_remainderSize = 0;
  24. m_outBuffer = NULL;
  25. m_outBufferSize = 0;
  26. m_streamData = NULL;
  27. DEBUG_LOG(m_srcLogString + __FUNCTION__);
  28. // we're looking to see if this is an updated 1.x source
  29. // which is able to indicate the stream # for the stream
  30. // so that we're able to support multiple 1.x sources so
  31. // we need to parse the password and extract the parts
  32. utf8 m_srcPassword = password;
  33. extractPassword(m_srcPassword, m_srcUserID, m_srcStreamID);
  34. // ensure that only valid stream id's are allowed to connect (1 -> 2147483647)
  35. if (!m_srcStreamID || (m_srcStreamID > INT_MAX))
  36. {
  37. m_denied = true;
  38. ELOG(m_srcLogString + "Bad Stream ID (" + tos(m_srcStreamID) + "). Stream ID cannot be below 1 or above 2147483647.");
  39. m_outBuffer = MSG_BADSTREAMID;
  40. bandWidth::updateAmount(bandWidth::SOURCE_V1_SENT, (m_outBufferSize = MSG_BADSTREAMID_LEN));
  41. m_state = &protocol_shoutcastSource::state_SendBuffer;
  42. m_nextState = &protocol_shoutcastSource::state_CloseConnection;
  43. return;
  44. }
  45. // update the log message for the read stream number
  46. m_srcLogString = srcAddrLogString (m_srcAddr, m_srcPort, m_srcStreamID);
  47. // if we have a moved stream then now we have the stream id
  48. // then we need to check and block the source as applicable
  49. utf8 movedUrl = gOptions.stream_movedUrl(m_srcStreamID);
  50. if (!movedUrl.empty())
  51. {
  52. m_denied = true;
  53. ELOG(m_srcLogString + "Shoutcast 1 source rejected. Stream is configured as having moved.");
  54. m_outBuffer = MSG_STREAMMOVED;
  55. bandWidth::updateAmount(bandWidth::SOURCE_V1_SENT, (m_outBufferSize = MSG_STREAMMOVED_LEN));
  56. m_state = &protocol_shoutcastSource::state_SendBuffer;
  57. m_nextState = &protocol_shoutcastSource::state_CloseConnection;
  58. return;
  59. }
  60. // as we are a v1 source then we must adhere to the master password
  61. // instead of using a specific per stream password as in v2 streams
  62. // though we also accept connections as sid=1 so check for that too
  63. utf8 srcPassword = gOptions.stream_password(m_srcStreamID);
  64. if (srcPassword.empty())
  65. {
  66. srcPassword = gOptions.password();
  67. }
  68. if (m_srcPassword.empty() || (m_srcPassword != srcPassword))
  69. {
  70. m_denied = true;
  71. ELOG(m_srcLogString + "Shoutcast 1 source connection denied" + (m_srcUserID.empty() ? "" : " for user (" + m_srcUserID + ")") +
  72. ". " + (m_srcPassword.empty() ? "Empty password not allowed." : "Bad password: " + m_srcPassword), LOGNAME, m_srcStreamID);
  73. m_outBuffer = MSG_INVALIDPASSWORD;
  74. bandWidth::updateAmount(bandWidth::SOURCE_V1_SENT, (m_outBufferSize = MSG_INVALIDPASSWORD_LEN));
  75. m_state = &protocol_shoutcastSource::state_SendBuffer;
  76. m_nextState = &protocol_shoutcastSource::state_CloseConnection;
  77. }
  78. else
  79. {
  80. // if we've got a source already connected and it's not a backup
  81. // then it's better that we just abort processing now than later
  82. bool isSourceActive = false;
  83. streamData *sd = streamData::accessStream(m_srcStreamID, isSourceActive);
  84. if (sd && (isSourceActive == true) && (sd->isBackupStream(m_srcStreamID) == false))
  85. {
  86. m_denied = true;
  87. ELOG(m_srcLogString + "Shoutcast 1 source rejected. A source is already connected.", LOGNAME, m_srcStreamID);
  88. m_outBuffer = MSG_STREAMINUSE;
  89. bandWidth::updateAmount(bandWidth::SOURCE_V1_SENT, (m_outBufferSize = MSG_STREAMINUSE_LEN));
  90. m_state = &protocol_shoutcastSource::state_SendBuffer;
  91. m_nextState = &protocol_shoutcastSource::state_CloseConnection;
  92. }
  93. else
  94. {
  95. ILOG(m_srcLogString + "Shoutcast 1 source connection starting.", LOGNAME, m_srcStreamID);
  96. m_outBuffer = MSG_VALIDPASSWORD;
  97. bandWidth::updateAmount(bandWidth::SOURCE_V1_SENT, (m_outBufferSize = MSG_VALIDPASSWORD_LEN));
  98. m_state = &protocol_shoutcastSource::state_SendBuffer;
  99. m_nextState = &protocol_shoutcastSource::state_GetHeaders;
  100. }
  101. if (sd)
  102. {
  103. sd->releaseStream();
  104. }
  105. }
  106. }
  107. protocol_shoutcastSource::~protocol_shoutcastSource() throw()
  108. {
  109. DEBUG_LOG(m_srcLogString + __FUNCTION__);
  110. if (m_streamData)
  111. {
  112. streamData::streamSourceLost(m_srcLogString, m_streamData, m_srcStreamID);
  113. m_streamData = 0;
  114. }
  115. socketOps::forgetTCPSocket(m_socket);
  116. forgetArray(m_remainder);
  117. if (!m_denied)
  118. {
  119. ILOG(m_srcLogString + "Shoutcast 1 source disconnected.", LOGNAME, m_srcStreamID);
  120. }
  121. }
  122. void protocol_shoutcastSource::state_AnalyzeHeaders() throw(exception)
  123. {
  124. DEBUG_LOG(m_srcLogString + __FUNCTION__);
  125. int maxHeaderLineCount = gOptions.maxHeaderLineCount();
  126. if ((int)m_headers.size() >= maxHeaderLineCount)
  127. {
  128. m_denied = true;
  129. throwEx<runtime_error>(m_srcLogString + "Max icy header lines exceeded");
  130. }
  131. m_lineBuffer = stripWhitespace(m_lineBuffer);
  132. if (m_lineBuffer.empty())
  133. {
  134. // adjust icy headers for titleFormat and urlFormat
  135. utf8 titleFormat = gOptions.titleFormat();
  136. utf8 urlFormat = gOptions.urlFormat();
  137. if (!titleFormat.empty())
  138. {
  139. utf8::size_type pos = titleFormat.find(utf8("%s"));
  140. m_headers["icy-name"] = (pos == utf8::npos ? titleFormat : titleFormat.replace(pos,2,m_headers["icy-name"]));
  141. }
  142. if (!urlFormat.empty())
  143. {
  144. utf8::size_type pos = urlFormat.find(utf8("%s"));
  145. m_headers["icy-url"] = (pos == utf8::npos ? urlFormat : urlFormat.replace(pos,2,m_headers["icy-url"]));
  146. }
  147. // dump icy headers to log
  148. if (gOptions.shoutcastSourceDebug())
  149. {
  150. for (map<utf8,utf8>::const_iterator i = m_headers.begin(); i != m_headers.end(); ++i)
  151. {
  152. DEBUG_LOG(m_srcLogString + "Source client header [" + (*i).first + ":" + (*i).second + "]");
  153. }
  154. }
  155. config::streamConfig stream;
  156. const bool found = gOptions.getStreamConfig(stream, m_srcStreamID);
  157. if (!found && gOptions.requireStreamConfigs())
  158. {
  159. m_denied = true;
  160. throwEx<runtime_error>(m_srcLogString + "Shoutcast 1 source rejected. Stream " +
  161. tos(m_srcStreamID) + " must be defined in config file");
  162. }
  163. // check that these bitrates are allowed (looking at both max and average values)
  164. const int bitrate = getStreamBitrate(m_headers) * 1000;
  165. int streamMaxBitrate = 0, streamMinBitrate = 0;
  166. const int ret = gOptions.isBitrateDisallowed(m_srcStreamID, bitrate, streamMaxBitrate, streamMinBitrate);
  167. if (ret)
  168. {
  169. m_denied = true;
  170. utf8 mode = ((streamMaxBitrate == streamMinBitrate) ? "of" : (ret == 2 ? "up to" : "from"));
  171. throwEx<runtime_error>(m_srcLogString + "Shoutcast 1 source rejected. Only bitrates " +
  172. mode + " " + tos((ret == 1 ? streamMinBitrate : streamMaxBitrate) / 1000) +
  173. " kbps are allowed - detected " + tos(bitrate / 1000) + " kbps.");
  174. }
  175. m_streamData = streamData::createStream(streamData::streamSetup(m_srcLogString, m_srcAddr,
  176. (found ? stream.m_authHash : ""), m_srcUserID, "",
  177. stream.m_backupUrl.url(), streamData::SHOUTCAST1,
  178. m_srcStreamID, m_srcPort, stream.m_maxStreamUser,
  179. stream.m_maxStreamBitrate, stream.m_minStreamBitrate,
  180. stream.m_allowPublicRelay, false, getStreamSamplerate(m_headers),
  181. mapGet(m_headers, "icy-vbr", (bool)false), m_headers));
  182. if (!m_streamData)
  183. {
  184. m_denied = true;
  185. ELOG(m_srcLogString + "Shoutcast 1 source rejected. A source is already connected.");
  186. m_outBuffer = MSG_STREAMINUSE;
  187. bandWidth::updateAmount(bandWidth::SOURCE_V1_SENT, (m_outBufferSize = MSG_STREAMINUSE_LEN));
  188. m_state = &protocol_shoutcastSource::state_SendBuffer;
  189. m_nextState = &protocol_shoutcastSource::state_CloseConnection;
  190. m_result.run();
  191. return;
  192. }
  193. utf8 sourceIdent = mapGet(m_headers, "user-agent", utf8());
  194. m_streamData->updateSourceIdent(sourceIdent);
  195. m_state = &protocol_shoutcastSource::state_GetStreamData;
  196. m_result.read();
  197. }
  198. else
  199. {
  200. // find the colon that divides header lines into key/value fields
  201. utf8::size_type pos = m_lineBuffer.find(utf8(":"));
  202. utf8 key = toLower(stripWhitespace(m_lineBuffer.substr(0, pos)));
  203. if (pos == utf8::npos)
  204. {
  205. if (!key.empty() && ((key == "icy-name") || (key == "icy-url")))
  206. {
  207. // allow through icy-name and icy-url if there is
  208. // a titleformat and urlformat to use respectively
  209. }
  210. else
  211. {
  212. m_denied = true;
  213. throwEx<runtime_error>(m_srcLogString + "Shoutcast 1 source connection rejected. "
  214. "Bad icy header string [" + m_lineBuffer + "]");
  215. }
  216. }
  217. utf8 value = stripWhitespace(m_lineBuffer.substr(pos+1));
  218. if (key.empty() || value.empty())
  219. {
  220. if (!key.empty() && value.empty())
  221. {
  222. if (key == "icy-genre")
  223. {
  224. value = "Misc";
  225. }
  226. else if (((key == "icy-name") && !gOptions.titleFormat().empty()) ||
  227. ((key == "icy-url") && !gOptions.urlFormat().empty()))
  228. {
  229. // allow through icy-name and icy-url if there is
  230. // a titleformat and urlformat to use respectively
  231. }
  232. else
  233. {
  234. if (key == "icy-url")
  235. {
  236. value = "http://www.shoutcast.com";
  237. }
  238. else if (!((key == "icy-irc") || (key == "icy-aim") || (key == "icy-icq")))
  239. {
  240. m_denied = true;
  241. throwEx<runtime_error>(m_srcLogString + "Shoutcast 1 source connection rejected. "
  242. "Bad icy header string [" + m_lineBuffer + "]");
  243. }
  244. }
  245. }
  246. else
  247. {
  248. m_denied = true;
  249. throwEx<runtime_error>(m_srcLogString + "Shoutcast 1 source connection rejected. "
  250. "Bad icy header string [" + m_lineBuffer + "]");
  251. }
  252. }
  253. m_headers[key] = value;
  254. m_nextState = &protocol_shoutcastSource::state_AnalyzeHeaders;
  255. m_state = &protocol_shoutcastSource::state_GetLine;
  256. m_result.read();
  257. m_lineBuffer.clear();
  258. }
  259. }
  260. void protocol_shoutcastSource::timeSlice() throw(std::exception)
  261. {
  262. try
  263. {
  264. if (m_streamData && m_streamData->isDead())
  265. {
  266. m_result.done();
  267. return;
  268. }
  269. (this->*m_state)();
  270. }
  271. catch(const exception &)
  272. {
  273. if (m_streamData && !m_denied)
  274. {
  275. // if there was a failure, now see if we have a backup and attempt to run
  276. // before we remove the current handling of the dropped source connection
  277. vector<config::streamConfig> backupInfo = gOptions.getBackupUrl(m_srcStreamID);
  278. if (!backupInfo.empty())
  279. {
  280. streamData::streamInfo info;
  281. streamData::extraInfo extra;
  282. if (streamData::getStreamInfo (m_streamData->ID(), info, extra) && info.m_allowBackupURL)
  283. {
  284. m_denied = true;
  285. m_streamData->clearCachedMetadata();
  286. streamData::streamSourceLost(m_srcLogString, m_streamData, m_srcStreamID);
  287. m_streamData = 0;
  288. ILOG (m_srcLogString + "Shoutcast 1 source disconnected - trying source backup.", LOGNAME, m_srcStreamID);
  289. threadedRunner::scheduleRunnable(new protocol_backup(backupInfo[0], getStreamBitrate(m_headers),
  290. fixMimeType(m_headers["content-type"])));
  291. }
  292. else
  293. WLOG ("Stream backup URL not allowed", LOGNAME, m_srcStreamID);
  294. }
  295. }
  296. throw;
  297. }
  298. }
  299. void protocol_shoutcastSource::state_SendBuffer() throw(exception)
  300. {
  301. DEBUG_LOG(m_srcLogString + __FUNCTION__);
  302. if (sendDataBuffer(m_srcStreamID, m_outBuffer, m_outBufferSize, m_srcLogString))
  303. {
  304. m_state = m_nextState;
  305. }
  306. }
  307. void protocol_shoutcastSource::state_GetLine() throw(exception)
  308. {
  309. DEBUG_LOG(m_srcLogString + __FUNCTION__);
  310. if (getHTTPStyleHeaderLine(m_srcStreamID, m_lineBuffer, m_srcLogString))
  311. {
  312. m_state = m_nextState;
  313. }
  314. }
  315. void protocol_shoutcastSource::state_GetHeaders() throw(exception)
  316. {
  317. DEBUG_LOG(m_srcLogString + __FUNCTION__);
  318. m_state = &protocol_shoutcastSource::state_GetLine;
  319. m_nextState = &protocol_shoutcastSource::state_AnalyzeHeaders;
  320. m_result.read();
  321. }
  322. void protocol_shoutcastSource::state_GetStreamData() throw(exception)
  323. {
  324. /*#if defined(_DEBUG) || defined(DEBUG)
  325. DEBUG_LOG(m_srcLogString + __FUNCTION__);
  326. #endif*/
  327. time_t cur_time;
  328. const int autoDumpTime = detectAutoDumpTimeout (cur_time, m_srcStreamID, (m_srcLogString + "Timeout waiting for stream data"));
  329. int bitrate = m_streamData->streamBitrate();
  330. const int type = m_streamData->streamUvoxDataType();
  331. while (true)
  332. {
  333. char buf[BUF_SIZE * 4] = {0};
  334. int amt = (BUF_SIZE - 1);
  335. // if we had anything left over then now we
  336. // need to copy it back into the buffer and
  337. // adjust the max data amount to be read in
  338. if ((m_remainderSize > 0) && ((amt + m_remainderSize) <= (BUF_SIZE * 4)))
  339. {
  340. memcpy(buf, m_remainder, m_remainderSize);
  341. }
  342. else
  343. {
  344. m_remainderSize = 0;
  345. }
  346. // adjust the position in the buffer based on the prior
  347. // state of the remaining data as part of frame syncing
  348. int rval = 0;
  349. if ((rval = recv (&buf[m_remainderSize], (BUF_SIZE - 1), 0x0)) < 1)
  350. {
  351. if (rval < 0)
  352. {
  353. rval = socketOps::errCode();
  354. if (rval == SOCKETOPS_WOULDBLOCK)
  355. {
  356. m_result.schedule(85);
  357. m_result.timeout((autoDumpTime - (int)(cur_time - m_lastActivityTime)));
  358. return;
  359. }
  360. DLOG (m_srcLogString + "Socket error while waiting for data. " + socketErrString(rval), LOGNAME, m_srcStreamID);
  361. }
  362. else
  363. DLOG (m_srcLogString + "Remote socket closed while waiting for data.", LOGNAME, m_srcStreamID);
  364. throwEx<runtime_error> ((utf8)"");
  365. }
  366. // update these details before we mess with anything
  367. bandWidth::updateAmount(bandWidth::SOURCE_V1_RECV, rval);
  368. // if we're here then we account for what we already had in the total
  369. // so that we then don't skip the new data read with the original data
  370. rval += m_remainderSize;
  371. m_remainderSize = 0;
  372. amt = rval;
  373. if (m_streamData->syncToStream(m_remainderSize, m_remainder, amt,
  374. bitrate, type, buf, m_srcLogString))
  375. {
  376. m_denied = true;
  377. throwEx<runtime_error>(m_srcLogString + "Shoutcast 1 source disconnected. "
  378. "Unable to sync to the stream. Please check the "
  379. "source is valid and in a supported format.");
  380. }
  381. m_lastActivityTime = ::time(NULL);
  382. m_result.schedule(25);
  383. }
  384. }
  385. void protocol_shoutcastSource::state_CloseConnection() throw(exception)
  386. {
  387. DEBUG_LOG(m_srcLogString + __FUNCTION__);
  388. m_result.done();
  389. }