From 75f5b62ac4873304257e07d18aba444da084bbc9 Mon Sep 17 00:00:00 2001 From: "craig.p.drummond" Date: Tue, 24 Dec 2013 19:15:51 +0000 Subject: [PATCH] Improve internal HTTP security - only server files to MPD host, and only serve files in playqueue. BUG: 356 --- ChangeLog | 2 + http/httpsocket.cpp | 89 +++++++++++++++++++++++++++++++++++++++---- http/httpsocket.h | 33 +++++++++++----- mpd/mpdconnection.cpp | 48 +++++++++++++++++++---- mpd/mpdconnection.h | 7 ++++ 5 files changed, 154 insertions(+), 25 deletions(-) diff --git a/ChangeLog b/ChangeLog index 17b633e9e..1c23e91da 100644 --- a/ChangeLog +++ b/ChangeLog @@ -12,6 +12,8 @@ 1.2.2 ----- 1. Fix British English translation. +2. Improve internal HTTP security - only server files to MPD host, and only + serve files in playqueue. 1.2.1 ----- diff --git a/http/httpsocket.cpp b/http/httpsocket.cpp index 63b6b8336..c2f6e40b7 100644 --- a/http/httpsocket.cpp +++ b/http/httpsocket.cpp @@ -250,6 +250,11 @@ HttpSocket::HttpSocket(const QString &iface, quint16 port) if (isListening() && ifaceAddress.isEmpty()) { ifaceAddress=QLatin1String("127.0.0.1"); } + + connect(MPDConnection::self(), SIGNAL(socketAddress(QString)), this, SLOT(mpdAddress(QString))); + connect(MPDConnection::self(), SIGNAL(cantataStreams(QList,bool)), this, SLOT(cantataStreams(QList,bool))); + connect(MPDConnection::self(), SIGNAL(cantataStreams(QStringList)), this, SLOT(cantataStreams(QStringList))); + connect(MPDConnection::self(), SIGNAL(removedIds(QSet)), this, SLOT(removedIds(QSet))); } bool HttpSocket::openPort(const QHostAddress &a, quint16 p) @@ -306,6 +311,20 @@ void HttpSocket::readClient() DBUG << "params" << params << "tokens" << tokens; if (!isFromMpd(params)) { + sendErrorResponse(socket, 400); + socket->close(); + DBUG << "Not from MPD"; + return; + } + + QString peer=socket->peerAddress().toString(); + bool hostOk=peer==ifaceAddress || peer==mpdAddr || peer==QLatin1String("127.0.0.1"); + + DBUG << "peer:" << peer << "mpd:" << mpdAddr << "iface:" << ifaceAddress << "ok:" << hostOk; + if (!hostOk) { + sendErrorResponse(socket, 400); + socket->close(); + DBUG << "Not from valid host"; return; } @@ -321,9 +340,16 @@ void HttpSocket::readClient() qint32 readBytesTo=0; getRange(params, readBytesFrom, readBytesTo); - DBUG << url.toString() << q.hasQueryItem("cantata"); if (q.hasQueryItem("cantata")) { Song song=HttpServer::self()->decodeUrl(url); + + if (!isCantataStream(song.file)) { + sendErrorResponse(socket, 400); + socket->close(); + DBUG << "Not cantata stream file"; + return; + } + if (song.isCdda()) { #if defined CDDB_FOUND || defined MUSICBRAINZ5_FOUND QStringList parts=song.file.split("/", QString::SkipEmptyParts); @@ -427,12 +453,7 @@ void HttpSocket::readClient() } if (!ok) { - QTextStream os(socket); - os.setAutoDetectUnicode(true); - os << "HTTP/1.0 404 OK\r\n" - "Content-Type: text/html; charset=\"utf-8\"\r\n" - "\r\n" - "

Nothing to see here

\n"; + sendErrorResponse(socket, 404); } socket->close(); @@ -450,6 +471,60 @@ void HttpSocket::discardClient() socket->deleteLater(); } + +void HttpSocket::mpdAddress(const QString &a) +{ + mpdAddr=a; +} + +bool HttpSocket::isCantataStream(const QString &file) const +{ + DBUG << file << newlyAddedFiles.contains(file) << streamIds.values().contains(file); + return newlyAddedFiles.contains(file) || streamIds.values().contains(file); +} + +void HttpSocket::sendErrorResponse(QTcpSocket *socket, int code) +{ + QTextStream os(socket); + os.setAutoDetectUnicode(true); + os << "HTTP/1.0 " << code << " OK\r\n" + "Content-Type: text/html; charset=\"utf-8\"\r\n" + "\r\n"; +} + +void HttpSocket::cantataStreams(const QStringList &files) +{ + DBUG << files; + foreach (const QString &f, files) { + Song s=HttpServer::self()->decodeUrl(f); + if (s.isCantataStream() || s.isCdda()) { + DBUG << s.file; + newlyAddedFiles+=s.file; + } + } +} + +void HttpSocket::cantataStreams(const QList &songs, bool isUpdate) +{ + DBUG << isUpdate << songs.count(); + if (!isUpdate) { + streamIds.clear(); + } + + foreach (const Song &s, songs) { + DBUG << s.file; + streamIds.insert(s.id, s.file); + newlyAddedFiles.remove(s.file); + } +} + +void HttpSocket::removedIds(const QSet &ids) +{ + foreach (qint32 id, ids) { + streamIds.remove(id); + } +} + bool HttpSocket::write(QTcpSocket *socket, char *buffer, qint32 bytesRead, bool &stop) { if (bytesRead<0 || terminated) { diff --git a/http/httpsocket.h b/http/httpsocket.h index d3a981f8b..2ac951bd3 100644 --- a/http/httpsocket.h +++ b/http/httpsocket.h @@ -25,7 +25,11 @@ #define _HTTP_SOCKET_H_ #include +#include +#include +#include +class Song; class QHostAddress; class QTcpSocket; @@ -34,27 +38,36 @@ class HttpSocket : public QTcpServer Q_OBJECT public: - HttpSocket(const QString &iface, quint16 port); - virtual ~HttpSocket() { } + HttpSocket(const QString &iface, quint16 port); + virtual ~HttpSocket() { } - void terminate(); - void incomingConnection(int socket); - QString address() const { return ifaceAddress; } - QString configuredInterface() { return cfgInterface; } + void terminate(); + void incomingConnection(int socket); + QString address() const { return ifaceAddress; } + QString configuredInterface() { return cfgInterface; } private: - bool openPort(const QHostAddress &a, quint16 p); + bool openPort(const QHostAddress &a, quint16 p); + bool isCantataStream(const QString &file) const; + void sendErrorResponse(QTcpSocket *socket, int code); private Q_SLOTS: - void readClient(); - void discardClient(); + void readClient(); + void discardClient(); + void mpdAddress(const QString &a); + void cantataStreams(const QStringList &files); + void cantataStreams(const QList &songs, bool isUpdate); + void removedIds(const QSet &ids); private: - bool write(QTcpSocket *socket, char *buffer, qint32 bytesRead, bool &stop); + bool write(QTcpSocket *socket, char *buffer, qint32 bytesRead, bool &stop); private: + QSet newlyAddedFiles; // Holds cantata strema filenames as added to MPD via "add" + QMap streamIds; // Maps MPD playqueue song ID to fileName QString cfgInterface; QString ifaceAddress; + QString mpdAddr; bool terminated; }; diff --git a/mpd/mpdconnection.cpp b/mpd/mpdconnection.cpp index e8c9bce0c..fe044fa33 100644 --- a/mpd/mpdconnection.cpp +++ b/mpd/mpdconnection.cpp @@ -252,6 +252,7 @@ MPDConnection::ConnectionReturn MPDConnection::connectToMPD(MpdSocket &socket, b lastUpdatePlayQueueVersion=lastStatusPlayQueueVersion=0; playQueueIds.clear(); + emit cantataStreams(QList(), false); int min, maj, patch; if (3==sscanf(&(recvdata.constData()[7]), "%d.%d.%d", &maj, &min, &patch)) { long v=((maj&0xFF)<<16)+((min&0xFF)<<8)+(patch&0xFF); @@ -300,6 +301,7 @@ MPDConnection::ConnectionReturn MPDConnection::connectToMPD() ConnectionReturn status=Failed; if (Success==(status=connectToMPD(sock)) && Success==(status=connectToMPD(idleSocket, true))) { state=State_Connected; + emit socketAddress(sock.address()); } else { disconnectFromMPD(); state=State_Disconnected; @@ -323,6 +325,7 @@ void MPDConnection::disconnectFromMPD() idleSocket.close(); state=State_Disconnected; ver=0; + emit socketAddress(QString()); } // This function is mainly intended to be called after the computer (laptop) has been 'resumed' @@ -555,19 +558,24 @@ void MPDConnection::add(const QStringList &files, quint32 pos, quint32 size, int bool havePlaylist=false; bool usePrio=!priority.isEmpty() && canUsePriority() && (1==priority.count() || priority.count()==files.count()); quint8 singlePrio=usePrio && 1==priority.count() ? priority.at(0) : 0; + QStringList cStreamFiles; for (int i = 0; i < files.size(); i++) { - if (CueFile::isCue(files.at(i))) { - send += "load "+CueFile::getLoadLine(files.at(i))+"\n"; + QString fileName=files.at(i); + if (fileName.startsWith(QLatin1String("http://")) && fileName.contains(QLatin1String("cantata=song"))) { + cStreamFiles.append(fileName); + } + if (CueFile::isCue(fileName)) { + send += "load "+CueFile::getLoadLine(fileName)+"\n"; } else { - if (isPlaylist(files.at(i))) { + if (isPlaylist(fileName)) { send+="load "; havePlaylist=true; } else { // addedFile=true; send += "add "; } - send += encodeName(files.at(i))+"\n"; + send += encodeName(fileName)+"\n"; } if (!havePlaylist) { if (0!=size) { @@ -584,6 +592,10 @@ void MPDConnection::add(const QStringList &files, quint32 pos, quint32 size, int send += "command_list_end"; if (sendCommand(send).ok) { + if (!cStreamFiles.isEmpty()) { + emit cantataStreams(cStreamFiles); + } + if (AddReplaceAndPlay==action /*&& addedFile */&& !files.isEmpty()) { // Dont emit error if fail plays, might be that playlist was not loaded... sendCommand("play "+QByteArray::number(0), false); @@ -617,6 +629,7 @@ void MPDConnection::clear() if (sendCommand("clear").ok) { lastUpdatePlayQueueVersion=0; playQueueIds.clear(); + emit cantataStreams(QList(), false); } } @@ -726,6 +739,7 @@ void MPDConnection::playListChanges() bool first=true; quint32 firstPos=0; QList songs; + QList newCantataStreams; QList ids; QSet prevIds=playQueueIds.toSet(); QSet strmIds; @@ -765,8 +779,12 @@ void MPDConnection::playListChanges() s.id=idp.id; // s.pos=idp.pos; songs.append(s); - if (s.isStream() && !s.isCantataStream()) { - strmIds.insert(s.id); + if (s.isStream()) { + if (s.isCantataStream()) { + newCantataStreams.append(s); + } else { + strmIds.insert(s.id); + } } } ids.append(idp.id); @@ -788,6 +806,13 @@ void MPDConnection::playListChanges() playQueueIds=ids; streamIds=strmIds; + if (!newCantataStreams.isEmpty()) { + emit cantataStreams(newCantataStreams, true); + } + QSet removed=prevIds-playQueueIds.toSet(); + if (!removed.isEmpty()) { + emit removedIds(removed); + } emit playlistUpdated(songs); return; } @@ -804,12 +829,19 @@ void MPDConnection::playListInfo() QList songs=MPDParseUtils::parseSongs(response.data); playQueueIds.clear(); streamIds.clear(); + + QList cStreams; foreach (const Song &s, songs) { playQueueIds.append(s.id); - if (s.isStream() && !s.isCantataStream()) { - streamIds.insert(s.id); + if (s.isStream()) { + if (s.isCantataStream()) { + cStreams.append(s); + } else { + streamIds.insert(s.id); + } } } + emit cantataStreams(cStreams, false); emit playlistUpdated(songs); } } diff --git a/mpd/mpdconnection.h b/mpd/mpdconnection.h index c1f462750..5ca2961cd 100644 --- a/mpd/mpdconnection.h +++ b/mpd/mpdconnection.h @@ -29,6 +29,7 @@ #include #include +#include #include #include #include "mpdstats.h" @@ -114,6 +115,7 @@ public: } bool isLocal() const { return 0!=local; } + QString address() const { return tcp ? tcp->peerAddress().toString() : QString(); } Q_SIGNALS: void stateChanged(QAbstractSocket::SocketState state); @@ -310,6 +312,11 @@ Q_SIGNALS: void searchResponse(int id, const QList &songs); + void socketAddress(const QString &addr); + void cantataStreams(const QStringList &files); + void cantataStreams(const QList &songs, bool isUpdate); + void removedIds(const QSet &ids); + private Q_SLOTS: void idleDataReady(); void onSocketStateChanged(QAbstractSocket::SocketState socketState);