1 /*
2  * Bittorrent Client using Qt and libtorrent.
3  * Copyright (C) 2019  Mike Tzou (Chocobo1)
4  * Copyright (C) 2015  Vladimir Golovnev <glassez@yandex.ru>
5  * Copyright (C) 2006  Christophe Dumez <chris@qbittorrent.org>
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
20  *
21  * In addition, as a special exception, the copyright holders give permission to
22  * link this program with the OpenSSL project's "OpenSSL" library (or with
23  * modified versions of it that use the same license as the "OpenSSL" library),
24  * and distribute the linked executables. You must obey the GNU General Public
25  * License in all respects for all of the code used other than "OpenSSL".  If you
26  * modify file(s), you may extend this exception to your version of the file(s),
27  * but you are not obligated to do so. If you do not wish to do so, delete this
28  * exception statement from your version.
29  */
30 
31 #include "tracker.h"
32 
33 #include <libtorrent/bencode.hpp>
34 #include <libtorrent/entry.hpp>
35 
36 #include <QHostAddress>
37 
38 #include "base/exceptions.h"
39 #include "base/global.h"
40 #include "base/http/httperror.h"
41 #include "base/http/server.h"
42 #include "base/http/types.h"
43 #include "base/logger.h"
44 #include "base/preferences.h"
45 
46 namespace
47 {
48     // static limits
49     const int MAX_TORRENTS = 10000;
50     const int MAX_PEERS_PER_TORRENT = 200;
51     const int ANNOUNCE_INTERVAL = 1800;  // 30min
52 
53     // constants
54     const int PEER_ID_SIZE = 20;
55 
56     const char ANNOUNCE_REQUEST_PATH[] = "/announce";
57 
58     const char ANNOUNCE_REQUEST_COMPACT[] = "compact";
59     const char ANNOUNCE_REQUEST_INFO_HASH[] = "info_hash";
60     const char ANNOUNCE_REQUEST_IP[] = "ip";
61     const char ANNOUNCE_REQUEST_LEFT[] = "left";
62     const char ANNOUNCE_REQUEST_NO_PEER_ID[] = "no_peer_id";
63     const char ANNOUNCE_REQUEST_NUM_WANT[] = "numwant";
64     const char ANNOUNCE_REQUEST_PEER_ID[] = "peer_id";
65     const char ANNOUNCE_REQUEST_PORT[] = "port";
66 
67     const char ANNOUNCE_REQUEST_EVENT[] = "event";
68     const char ANNOUNCE_REQUEST_EVENT_COMPLETED[] = "completed";
69     const char ANNOUNCE_REQUEST_EVENT_EMPTY[] = "empty";
70     const char ANNOUNCE_REQUEST_EVENT_STARTED[] = "started";
71     const char ANNOUNCE_REQUEST_EVENT_STOPPED[] = "stopped";
72     const char ANNOUNCE_REQUEST_EVENT_PAUSED[] = "paused";
73 
74     const char ANNOUNCE_RESPONSE_COMPLETE[] = "complete";
75     const char ANNOUNCE_RESPONSE_EXTERNAL_IP[] = "external ip";
76     const char ANNOUNCE_RESPONSE_FAILURE_REASON[] = "failure reason";
77     const char ANNOUNCE_RESPONSE_INCOMPLETE[] = "incomplete";
78     const char ANNOUNCE_RESPONSE_INTERVAL[] = "interval";
79     const char ANNOUNCE_RESPONSE_PEERS6[] = "peers6";
80     const char ANNOUNCE_RESPONSE_PEERS[] = "peers";
81 
82     const char ANNOUNCE_RESPONSE_PEERS_IP[] = "ip";
83     const char ANNOUNCE_RESPONSE_PEERS_PEER_ID[] = "peer id";
84     const char ANNOUNCE_RESPONSE_PEERS_PORT[] = "port";
85 
86     class TrackerError : public RuntimeError
87     {
88     public:
89         using RuntimeError::RuntimeError;
90     };
91 
toBigEndianByteArray(const QHostAddress & addr)92     QByteArray toBigEndianByteArray(const QHostAddress &addr)
93     {
94         // translate IP address to a sequence of bytes in big-endian order
95         switch (addr.protocol())
96         {
97         case QAbstractSocket::IPv4Protocol:
98         case QAbstractSocket::AnyIPProtocol:
99         {
100                 const quint32 ipv4 = addr.toIPv4Address();
101                 QByteArray ret;
102                 ret.append(static_cast<char>((ipv4 >> 24) & 0xFF))
103                    .append(static_cast<char>((ipv4 >> 16) & 0xFF))
104                    .append(static_cast<char>((ipv4 >> 8) & 0xFF))
105                    .append(static_cast<char>(ipv4 & 0xFF));
106                 return ret;
107             }
108 
109         case QAbstractSocket::IPv6Protocol:
110         {
111                 const Q_IPV6ADDR ipv6 = addr.toIPv6Address();
112                 QByteArray ret;
113                 for (const quint8 i : ipv6.c)
114                     ret.append(i);
115                 return ret;
116             }
117 
118         case QAbstractSocket::UnknownNetworkLayerProtocol:
119         default:
120             return {};
121         };
122     }
123 }
124 
125 namespace BitTorrent
126 {
127     // Peer
uniqueID() const128     QByteArray Peer::uniqueID() const
129     {
130         return (QByteArray::fromStdString(address) + ':' + QByteArray::number(port));
131     }
132 
operator ==(const Peer & left,const Peer & right)133     bool operator==(const Peer &left, const Peer &right)
134     {
135         return (left.uniqueID() == right.uniqueID());
136     }
137 
operator !=(const Peer & left,const Peer & right)138     bool operator!=(const Peer &left, const Peer &right)
139     {
140         return !(left == right);
141     }
142 
qHash(const Peer & key,const uint seed)143     uint qHash(const Peer &key, const uint seed)
144     {
145         return qHash(key.uniqueID(), seed);
146     }
147 }
148 
149 using namespace BitTorrent;
150 
151 // TrackerAnnounceRequest
152 struct Tracker::TrackerAnnounceRequest
153 {
154     QHostAddress socketAddress;
155     QByteArray claimedAddress;  // self claimed by peer
156     TorrentID torrentID;
157     QString event;
158     Peer peer;
159     int numwant = 50;
160     bool compact = true;
161     bool noPeerId = false;
162 };
163 
164 // Tracker::TorrentStats
setPeer(const Peer & peer)165 void Tracker::TorrentStats::setPeer(const Peer &peer)
166 {
167     // always replace existing peer
168     if (!removePeer(peer))
169     {
170         // Too many peers, remove a random one
171         if (peers.size() >= MAX_PEERS_PER_TORRENT)
172             removePeer(*peers.begin());
173     }
174 
175     // add peer
176     if (peer.isSeeder)
177         ++seeders;
178     peers.insert(peer);
179 }
180 
removePeer(const Peer & peer)181 bool Tracker::TorrentStats::removePeer(const Peer &peer)
182 {
183     const auto iter = peers.find(peer);
184     if (iter == peers.end())
185         return false;
186 
187     if (iter->isSeeder)
188         --seeders;
189     peers.remove(*iter);
190     return true;
191 }
192 
193 // Tracker
Tracker(QObject * parent)194 Tracker::Tracker(QObject *parent)
195     : QObject(parent)
196     , m_server(new Http::Server(this, this))
197 {
198 }
199 
start()200 bool Tracker::start()
201 {
202     const QHostAddress ip = QHostAddress::Any;
203     const int port = Preferences::instance()->getTrackerPort();
204 
205     if (m_server->isListening())
206     {
207         if (m_server->serverPort() == port)
208         {
209             // Already listening on the right port, just return
210             return true;
211         }
212 
213         // Wrong port, closing the server
214         m_server->close();
215     }
216 
217     // Listen on the predefined port
218     const bool listenSuccess = m_server->listen(ip, port);
219 
220     if (listenSuccess)
221     {
222         LogMsg(tr("Embedded Tracker: Now listening on IP: %1, port: %2")
223             .arg(ip.toString(), QString::number(port)), Log::INFO);
224     }
225     else
226     {
227         LogMsg(tr("Embedded Tracker: Unable to bind to IP: %1, port: %2. Reason: %3")
228                 .arg(ip.toString(), QString::number(port), m_server->errorString())
229             , Log::WARNING);
230     }
231 
232     return listenSuccess;
233 }
234 
processRequest(const Http::Request & request,const Http::Environment & env)235 Http::Response Tracker::processRequest(const Http::Request &request, const Http::Environment &env)
236 {
237     clear();  // clear response
238 
239     m_request = request;
240     m_env = env;
241 
242     status(200);
243 
244     try
245     {
246         // Is it a GET request?
247         if (request.method != Http::HEADER_REQUEST_METHOD_GET)
248             throw MethodNotAllowedHTTPError();
249 
250         if (request.path.startsWith(ANNOUNCE_REQUEST_PATH, Qt::CaseInsensitive))
251             processAnnounceRequest();
252         else
253             throw NotFoundHTTPError();
254     }
255     catch (const HTTPError &error)
256     {
257         status(error.statusCode(), error.statusText());
258         if (!error.message().isEmpty())
259             print(error.message(), Http::CONTENT_TYPE_TXT);
260     }
261     catch (const TrackerError &error)
262     {
263         clear();  // clear response
264         status(200);
265 
266         const lt::entry::dictionary_type bencodedEntry =
267         {
268             {ANNOUNCE_RESPONSE_FAILURE_REASON, {error.message().toStdString()}}
269         };
270         QByteArray reply;
271         lt::bencode(std::back_inserter(reply), bencodedEntry);
272         print(reply, Http::CONTENT_TYPE_TXT);
273     }
274 
275     return response();
276 }
277 
processAnnounceRequest()278 void Tracker::processAnnounceRequest()
279 {
280     const QHash<QString, QByteArray> &queryParams = m_request.query;
281     TrackerAnnounceRequest announceReq;
282 
283     // ip address
284     announceReq.socketAddress = m_env.clientAddress;
285     announceReq.claimedAddress = queryParams.value(ANNOUNCE_REQUEST_IP);
286 
287     // Enforce using IPv4 if address is indeed IPv4 or if it is an IPv4-mapped IPv6 address
288     bool ok = false;
289     const qint32 decimalIPv4 = announceReq.socketAddress.toIPv4Address(&ok);
290     if (ok)
291         announceReq.socketAddress = QHostAddress(decimalIPv4);
292 
293     // 1. info_hash
294     const auto infoHashIter = queryParams.find(ANNOUNCE_REQUEST_INFO_HASH);
295     if (infoHashIter == queryParams.end())
296         throw TrackerError("Missing \"info_hash\" parameter");
297 
298     const auto torrentID = TorrentID::fromString(infoHashIter->toHex());
299     if (!torrentID.isValid())
300         throw TrackerError("Invalid \"info_hash\" parameter");
301 
302     announceReq.torrentID = torrentID;
303 
304     // 2. peer_id
305     const auto peerIdIter = queryParams.find(ANNOUNCE_REQUEST_PEER_ID);
306     if (peerIdIter == queryParams.end())
307         throw TrackerError("Missing \"peer_id\" parameter");
308 
309     if (peerIdIter->size() > PEER_ID_SIZE)
310         throw TrackerError("Invalid \"peer_id\" parameter");
311 
312     announceReq.peer.peerId = *peerIdIter;
313 
314     // 3. port
315     const auto portIter = queryParams.find(ANNOUNCE_REQUEST_PORT);
316     if (portIter == queryParams.end())
317         throw TrackerError("Missing \"port\" parameter");
318 
319     const ushort portNum = portIter->toUShort();
320     if (portNum == 0)
321         throw TrackerError("Invalid \"port\" parameter");
322 
323     announceReq.peer.port = portNum;
324 
325     // 4. numwant
326     const auto numWantIter = queryParams.find(ANNOUNCE_REQUEST_NUM_WANT);
327     if (numWantIter != queryParams.end())
328     {
329         const int num = numWantIter->toInt();
330         if (num < 0)
331             throw TrackerError("Invalid \"numwant\" parameter");
332         announceReq.numwant = num;
333     }
334 
335     // 5. no_peer_id
336     // non-formal extension
337     announceReq.noPeerId = (queryParams.value(ANNOUNCE_REQUEST_NO_PEER_ID) == "1");
338 
339     // 6. left
340     announceReq.peer.isSeeder = (queryParams.value(ANNOUNCE_REQUEST_LEFT) == "0");
341 
342     // 7. compact
343     announceReq.compact = (queryParams.value(ANNOUNCE_REQUEST_COMPACT) != "0");
344 
345     // 8. cache `peers` field so we don't recompute when sending response
346     const QHostAddress claimedIPAddress {QString::fromLatin1(announceReq.claimedAddress)};
347     announceReq.peer.endpoint = toBigEndianByteArray(!claimedIPAddress.isNull() ? claimedIPAddress : announceReq.socketAddress)
348         .append(static_cast<char>((announceReq.peer.port >> 8) & 0xFF))
349         .append(static_cast<char>(announceReq.peer.port & 0xFF))
350         .toStdString();
351 
352     // 9. cache `address` field so we don't recompute when sending response
353     announceReq.peer.address = !announceReq.claimedAddress.isEmpty()
354         ? announceReq.claimedAddress.constData()
355         : announceReq.socketAddress.toString().toLatin1().constData(),
356 
357     // 10. event
358     announceReq.event = queryParams.value(ANNOUNCE_REQUEST_EVENT);
359 
360     if (announceReq.event.isEmpty()
361         || (announceReq.event == ANNOUNCE_REQUEST_EVENT_EMPTY)
362         || (announceReq.event == ANNOUNCE_REQUEST_EVENT_COMPLETED)
363         || (announceReq.event == ANNOUNCE_REQUEST_EVENT_STARTED)
364         || (announceReq.event == ANNOUNCE_REQUEST_EVENT_PAUSED))
365         {
366         // [BEP-21] Extension for partial seeds
367         // (partial support - we don't support BEP-48 so the part that concerns that is not supported)
368         registerPeer(announceReq);
369     }
370     else if (announceReq.event == ANNOUNCE_REQUEST_EVENT_STOPPED)
371     {
372         unregisterPeer(announceReq);
373     }
374     else
375     {
376         throw TrackerError("Invalid \"event\" parameter");
377     }
378 
379     prepareAnnounceResponse(announceReq);
380 }
381 
registerPeer(const TrackerAnnounceRequest & announceReq)382 void Tracker::registerPeer(const TrackerAnnounceRequest &announceReq)
383 {
384     if (!m_torrents.contains(announceReq.torrentID))
385     {
386         // Reached max size, remove a random torrent
387         if (m_torrents.size() >= MAX_TORRENTS)
388             m_torrents.erase(m_torrents.begin());
389     }
390 
391     m_torrents[announceReq.torrentID].setPeer(announceReq.peer);
392 }
393 
unregisterPeer(const TrackerAnnounceRequest & announceReq)394 void Tracker::unregisterPeer(const TrackerAnnounceRequest &announceReq)
395 {
396     const auto torrentStatsIter = m_torrents.find(announceReq.torrentID);
397     if (torrentStatsIter == m_torrents.end())
398         return;
399 
400     torrentStatsIter->removePeer(announceReq.peer);
401 
402     if (torrentStatsIter->peers.isEmpty())
403         m_torrents.erase(torrentStatsIter);
404 }
405 
prepareAnnounceResponse(const TrackerAnnounceRequest & announceReq)406 void Tracker::prepareAnnounceResponse(const TrackerAnnounceRequest &announceReq)
407 {
408     const TorrentStats &torrentStats = m_torrents[announceReq.torrentID];
409 
410     lt::entry::dictionary_type replyDict
411     {
412         {ANNOUNCE_RESPONSE_INTERVAL, ANNOUNCE_INTERVAL},
413         {ANNOUNCE_RESPONSE_COMPLETE, torrentStats.seeders},
414         {ANNOUNCE_RESPONSE_INCOMPLETE, (torrentStats.peers.size() - torrentStats.seeders)},
415 
416         // [BEP-24] Tracker Returns External IP (partial support - might not work properly for all IPv6 cases)
417         {ANNOUNCE_RESPONSE_EXTERNAL_IP, toBigEndianByteArray(announceReq.socketAddress).toStdString()}
418     };
419 
420     // peer list
421     // [BEP-7] IPv6 Tracker Extension (partial support - only the part that concerns BEP-23)
422     // [BEP-23] Tracker Returns Compact Peer Lists
423     if (announceReq.compact)
424     {
425         lt::entry::string_type peers;
426         lt::entry::string_type peers6;
427 
428         if (announceReq.event != ANNOUNCE_REQUEST_EVENT_STOPPED)
429         {
430             int counter = 0;
431             for (const Peer &peer : asConst(torrentStats.peers))
432             {
433                 if (counter++ >= announceReq.numwant)
434                     break;
435 
436                 if (peer.endpoint.size() == 6)  // IPv4 + port
437                     peers.append(peer.endpoint);
438                 else if (peer.endpoint.size() == 18)  // IPv6 + port
439                     peers6.append(peer.endpoint);
440             }
441         }
442 
443         replyDict[ANNOUNCE_RESPONSE_PEERS] = peers;  // required, even it's empty
444         if (!peers6.empty())
445             replyDict[ANNOUNCE_RESPONSE_PEERS6] = peers6;
446     }
447     else
448     {
449         lt::entry::list_type peerList;
450 
451         if (announceReq.event != ANNOUNCE_REQUEST_EVENT_STOPPED)
452         {
453             int counter = 0;
454             for (const Peer &peer : torrentStats.peers)
455             {
456                 if (counter++ >= announceReq.numwant)
457                     break;
458 
459                 lt::entry::dictionary_type peerDict =
460                 {
461                     {ANNOUNCE_RESPONSE_PEERS_IP, peer.address},
462                     {ANNOUNCE_RESPONSE_PEERS_PORT, peer.port}
463                 };
464 
465                 if (!announceReq.noPeerId)
466                     peerDict[ANNOUNCE_RESPONSE_PEERS_PEER_ID] = peer.peerId.constData();
467 
468                 peerList.emplace_back(peerDict);
469             }
470         }
471 
472         replyDict[ANNOUNCE_RESPONSE_PEERS] = peerList;
473     }
474 
475     // bencode
476     QByteArray reply;
477     lt::bencode(std::back_inserter(reply), replyDict);
478     print(reply, Http::CONTENT_TYPE_TXT);
479 }
480