1 /*
2  * Bittorrent Client using Qt and libtorrent.
3  * Copyright (C) 2006  Christophe Dumez <chris@qbittorrent.org>
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18  *
19  * In addition, as a special exception, the copyright holders give permission to
20  * link this program with the OpenSSL project's "OpenSSL" library (or with
21  * modified versions of it that use the same license as the "OpenSSL" library),
22  * and distribute the linked executables. You must obey the GNU General Public
23  * License in all respects for all of the code used other than "OpenSSL".  If you
24  * modify file(s), you may extend this exception to your version of the file(s),
25  * but you are not obligated to do so. If you do not wish to do so, delete this
26  * exception statement from your version.
27  */
28 
29 #include "filterparserthread.h"
30 
31 #include <cctype>
32 
33 #include <libtorrent/error_code.hpp>
34 
35 #include <QDataStream>
36 #include <QFile>
37 
38 #include "base/logger.h"
39 
40 namespace
41 {
42     class IPv4Parser
43     {
44     public:
tryParse(const char * str)45         bool tryParse(const char *str)
46         {
47             unsigned char octetIndex = 0;
48 
49             const char *octetStart = str;
50             char *endptr;
51             for (; *str; ++str)
52             {
53                 if (*str == '.')
54                 {
55                     const long int extractedNum = strtol(octetStart, &endptr, 10);
56                     if ((extractedNum >= 0L) && (extractedNum <= 255L))
57                         m_buf[octetIndex++] = static_cast<unsigned char>(extractedNum);
58                     else
59                         return false;
60 
61                     if (endptr != str)
62                         return false;
63                     if (octetIndex == 4)
64                         return true;
65 
66                     octetStart = str + 1;
67                 }
68             }
69 
70             if (str != octetStart)
71             {
72                 const long int extractedNum = strtol(octetStart, &endptr, 10);
73                 if ((extractedNum >= 0L) && (extractedNum <= 255L))
74                     m_buf[octetIndex] = static_cast<unsigned char>(strtol(octetStart, &endptr, 10));
75                 else
76                     return false;
77 
78                 if ((endptr == str) && (octetIndex == 3))
79                     return true;
80             }
81 
82             return false;
83         }
84 
parsed() const85         lt::address_v4::bytes_type parsed() const
86         {
87             return m_buf;
88         }
89 
90     private:
91         lt::address_v4::bytes_type m_buf;
92     };
93 
parseIPAddress(const char * data,lt::address & address)94     bool parseIPAddress(const char *data, lt::address &address)
95     {
96         IPv4Parser parser;
97         lt::error_code ec;
98 
99         if (parser.tryParse(data))
100             address = lt::address_v4(parser.parsed());
101         else
102             address = lt::make_address(data, ec);
103 
104         return !ec;
105     }
106 
107     const int BUFFER_SIZE = 2 * 1024 * 1024; // 2 MiB
108     const int MAX_LOGGED_ERRORS = 5;
109 }
110 
FilterParserThread(QObject * parent)111 FilterParserThread::FilterParserThread(QObject *parent)
112     : QThread(parent)
113     , m_abort(false)
114 {
115 }
116 
~FilterParserThread()117 FilterParserThread::~FilterParserThread()
118 {
119     m_abort = true;
120     wait();
121 }
122 
123 // Parser for eMule ip filter in DAT format
parseDATFilterFile()124 int FilterParserThread::parseDATFilterFile()
125 {
126     int ruleCount = 0;
127     QFile file(m_filePath);
128     if (!file.exists()) return ruleCount;
129 
130     if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
131     {
132         LogMsg(tr("I/O Error: Could not open IP filter file in read mode."), Log::CRITICAL);
133         return ruleCount;
134     }
135 
136     std::vector<char> buffer(BUFFER_SIZE, 0); // seems a bit faster than QVector
137     qint64 bytesRead = 0;
138     int offset = 0;
139     int start = 0;
140     int endOfLine = -1;
141     int nbLine = 0;
142     int parseErrorCount = 0;
143     const auto addLog = [&parseErrorCount](const QString &msg)
144     {
145         if (parseErrorCount <= MAX_LOGGED_ERRORS)
146             LogMsg(msg, Log::CRITICAL);
147     };
148 
149     while (true)
150     {
151         bytesRead = file.read(buffer.data() + offset, BUFFER_SIZE - offset - 1);
152         if (bytesRead < 0)
153             break;
154         const int dataSize = bytesRead + offset;
155         if ((bytesRead == 0) && (dataSize == 0))
156             break;
157 
158         for (start = 0; start < dataSize; ++start)
159         {
160             endOfLine = -1;
161             // The file might have ended without the last line having a newline
162             if (!((bytesRead == 0) && (dataSize > 0)))
163             {
164                 for (int i = start; i < dataSize; ++i)
165                 {
166                     if (buffer[i] == '\n')
167                     {
168                         endOfLine = i;
169                         // We need to NULL the newline in case the line has only an IP range.
170                         // In that case the parser won't work for the end IP, because it ends
171                         // with the newline and not with a number.
172                         buffer[i] = '\0';
173                         break;
174                     }
175                 }
176             }
177             else
178             {
179                 endOfLine = dataSize;
180                 buffer[dataSize] = '\0';
181             }
182 
183             if (endOfLine == -1)
184             {
185                 // read the next chunk from file
186                 // but first move(copy) the leftover data to the front of the buffer
187                 offset = dataSize - start;
188                 memmove(buffer.data(), buffer.data() + start, offset);
189                 break;
190             }
191 
192             ++nbLine;
193 
194             if ((buffer[start] == '#')
195                 || ((buffer[start] == '/') && ((start + 1 < dataSize) && (buffer[start + 1] == '/'))))
196                 {
197                 start = endOfLine;
198                 continue;
199             }
200 
201             // Each line should follow this format:
202             // 001.009.096.105 - 001.009.096.105 , 000 , Some organization
203             // The 3rd entry is access level and if above 127 the IP range isn't blocked.
204             const int firstComma = findAndNullDelimiter(buffer.data(), ',', start, endOfLine);
205             if (firstComma != -1)
206                 findAndNullDelimiter(buffer.data(), ',', firstComma + 1, endOfLine);
207 
208             // Check if there is an access value (apparently not mandatory)
209             if (firstComma != -1)
210             {
211                 // There is possibly one
212                 const long int nbAccess = strtol(buffer.data() + firstComma + 1, nullptr, 10);
213                 // Ignoring this rule because access value is too high
214                 if (nbAccess > 127L)
215                 {
216                     start = endOfLine;
217                     continue;
218                 }
219             }
220 
221             // IP Range should be split by a dash
222             const int endOfIPRange = ((firstComma == -1) ? (endOfLine - 1) : (firstComma - 1));
223             const int delimIP = findAndNullDelimiter(buffer.data(), '-', start, endOfIPRange);
224             if (delimIP == -1)
225             {
226                 ++parseErrorCount;
227                 addLog(tr("IP filter line %1 is malformed.").arg(nbLine));
228                 start = endOfLine;
229                 continue;
230             }
231 
232             lt::address startAddr;
233             int newStart = trim(buffer.data(), start, delimIP - 1);
234             if (!parseIPAddress(buffer.data() + newStart, startAddr))
235             {
236                 ++parseErrorCount;
237                 addLog(tr("IP filter line %1 is malformed. Start IP of the range is malformed.").arg(nbLine));
238                 start = endOfLine;
239                 continue;
240             }
241 
242             lt::address endAddr;
243             newStart = trim(buffer.data(), delimIP + 1, endOfIPRange);
244             if (!parseIPAddress(buffer.data() + newStart, endAddr))
245             {
246                 ++parseErrorCount;
247                 addLog(tr("IP filter line %1 is malformed. End IP of the range is malformed.").arg(nbLine));
248                 start = endOfLine;
249                 continue;
250             }
251 
252             if ((startAddr.is_v4() != endAddr.is_v4())
253                 || (startAddr.is_v6() != endAddr.is_v6()))
254                 {
255                 ++parseErrorCount;
256                 addLog(tr("IP filter line %1 is malformed. One IP is IPv4 and the other is IPv6!").arg(nbLine));
257                 start = endOfLine;
258                 continue;
259             }
260 
261             start = endOfLine;
262 
263             // Now Add to the filter
264             try
265             {
266                 m_filter.add_rule(startAddr, endAddr, lt::ip_filter::blocked);
267                 ++ruleCount;
268             }
269             catch (const std::exception &e)
270             {
271                 ++parseErrorCount;
272                 addLog(tr("IP filter exception thrown for line %1. Exception is: %2")
273                        .arg(nbLine).arg(QString::fromLocal8Bit(e.what())));
274             }
275         }
276 
277         if (start >= dataSize)
278             offset = 0;
279     }
280 
281     if (parseErrorCount > MAX_LOGGED_ERRORS)
282         LogMsg(tr("%1 extra IP filter parsing errors occurred.", "513 extra IP filter parsing errors occurred.")
283                .arg(parseErrorCount - MAX_LOGGED_ERRORS), Log::CRITICAL);
284     return ruleCount;
285 }
286 
287 // Parser for PeerGuardian ip filter in p2p format
parseP2PFilterFile()288 int FilterParserThread::parseP2PFilterFile()
289 {
290     int ruleCount = 0;
291     QFile file(m_filePath);
292     if (!file.exists()) return ruleCount;
293 
294     if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
295     {
296         LogMsg(tr("I/O Error: Could not open IP filter file in read mode."), Log::CRITICAL);
297         return ruleCount;
298     }
299 
300     std::vector<char> buffer(BUFFER_SIZE, 0); // seems a bit faster than QVector
301     qint64 bytesRead = 0;
302     int offset = 0;
303     int start = 0;
304     int endOfLine = -1;
305     int nbLine = 0;
306     int parseErrorCount = 0;
307     const auto addLog = [&parseErrorCount](const QString &msg)
308     {
309         if (parseErrorCount <= MAX_LOGGED_ERRORS)
310             LogMsg(msg, Log::CRITICAL);
311     };
312 
313     while (true)
314     {
315         bytesRead = file.read(buffer.data() + offset, BUFFER_SIZE - offset - 1);
316         if (bytesRead < 0)
317             break;
318         const int dataSize = bytesRead + offset;
319         if ((bytesRead == 0) && (dataSize == 0))
320             break;
321 
322         for (start = 0; start < dataSize; ++start)
323         {
324             endOfLine = -1;
325             // The file might have ended without the last line having a newline
326             if (!((bytesRead == 0) && (dataSize > 0)))
327             {
328                 for (int i = start; i < dataSize; ++i)
329                 {
330                     if (buffer[i] == '\n')
331                     {
332                         endOfLine = i;
333                         // We need to NULL the newline in case the line has only an IP range.
334                         // In that case the parser won't work for the end IP, because it ends
335                         // with the newline and not with a number.
336                         buffer[i] = '\0';
337                         break;
338                     }
339                 }
340             }
341             else
342             {
343                 endOfLine = dataSize;
344                 buffer[dataSize] = '\0';
345             }
346 
347             if (endOfLine == -1)
348             {
349                 // read the next chunk from file
350                 // but first move(copy) the leftover data to the front of the buffer
351                 offset = dataSize - start;
352                 memmove(buffer.data(), buffer.data() + start, offset);
353                 break;
354             }
355 
356             ++nbLine;
357 
358             if ((buffer[start] == '#')
359                 || ((buffer[start] == '/') && ((start + 1 < dataSize) && (buffer[start + 1] == '/'))))
360                 {
361                 start = endOfLine;
362                 continue;
363             }
364 
365             // Each line should follow this format:
366             // Some organization:1.0.0.0-1.255.255.255
367             // The "Some organization" part might contain a ':' char itself so we find the last occurrence
368             const int partsDelimiter = findAndNullDelimiter(buffer.data(), ':', start, endOfLine, true);
369             if (partsDelimiter == -1)
370             {
371                 ++parseErrorCount;
372                 addLog(tr("IP filter line %1 is malformed.").arg(nbLine));
373                 start = endOfLine;
374                 continue;
375             }
376 
377             // IP Range should be split by a dash
378             const int delimIP = findAndNullDelimiter(buffer.data(), '-', partsDelimiter + 1, endOfLine);
379             if (delimIP == -1)
380             {
381                 ++parseErrorCount;
382                 addLog(tr("IP filter line %1 is malformed.").arg(nbLine));
383                 start = endOfLine;
384                 continue;
385             }
386 
387             lt::address startAddr;
388             int newStart = trim(buffer.data(), partsDelimiter + 1, delimIP - 1);
389             if (!parseIPAddress(buffer.data() + newStart, startAddr))
390             {
391                 ++parseErrorCount;
392                 addLog(tr("IP filter line %1 is malformed. Start IP of the range is malformed.").arg(nbLine));
393                 start = endOfLine;
394                 continue;
395             }
396 
397             lt::address endAddr;
398             newStart = trim(buffer.data(), delimIP + 1, endOfLine);
399             if (!parseIPAddress(buffer.data() + newStart, endAddr))
400             {
401                 ++parseErrorCount;
402                 addLog(tr("IP filter line %1 is malformed. End IP of the range is malformed.").arg(nbLine));
403                 start = endOfLine;
404                 continue;
405             }
406 
407             if ((startAddr.is_v4() != endAddr.is_v4())
408                 || (startAddr.is_v6() != endAddr.is_v6()))
409                 {
410                 ++parseErrorCount;
411                 addLog(tr("IP filter line %1 is malformed. One IP is IPv4 and the other is IPv6!").arg(nbLine));
412                 start = endOfLine;
413                 continue;
414             }
415 
416             start = endOfLine;
417 
418             try
419             {
420                 m_filter.add_rule(startAddr, endAddr, lt::ip_filter::blocked);
421                 ++ruleCount;
422             }
423             catch (const std::exception &e)
424             {
425                 ++parseErrorCount;
426                 addLog(tr("IP filter exception thrown for line %1. Exception is: %2")
427                        .arg(nbLine).arg(QString::fromLocal8Bit(e.what())));
428             }
429         }
430 
431         if (start >= dataSize)
432             offset = 0;
433     }
434 
435     if (parseErrorCount > MAX_LOGGED_ERRORS)
436         LogMsg(tr("%1 extra IP filter parsing errors occurred.", "513 extra IP filter parsing errors occurred.")
437                .arg(parseErrorCount - MAX_LOGGED_ERRORS), Log::CRITICAL);
438     return ruleCount;
439 }
440 
getlineInStream(QDataStream & stream,std::string & name,const char delim)441 int FilterParserThread::getlineInStream(QDataStream &stream, std::string &name, const char delim)
442 {
443     char c;
444     int totalRead = 0;
445     int read;
446     do
447     {
448         read = stream.readRawData(&c, 1);
449         totalRead += read;
450         if (read > 0)
451         {
452             if (c != delim)
453             {
454                 name += c;
455             }
456             else
457             {
458                 // Delim found
459                 return totalRead;
460             }
461         }
462     }
463     while (read > 0);
464 
465     return totalRead;
466 }
467 
468 // Parser for PeerGuardian ip filter in p2p format
parseP2BFilterFile()469 int FilterParserThread::parseP2BFilterFile()
470 {
471     int ruleCount = 0;
472     QFile file(m_filePath);
473     if (!file.exists()) return ruleCount;
474 
475     if (!file.open(QIODevice::ReadOnly))
476     {
477         LogMsg(tr("I/O Error: Could not open IP filter file in read mode."), Log::CRITICAL);
478         return ruleCount;
479     }
480 
481     QDataStream stream(&file);
482     // Read header
483     char buf[7];
484     unsigned char version;
485     if (!stream.readRawData(buf, sizeof(buf))
486         || memcmp(buf, "\xFF\xFF\xFF\xFFP2B", 7)
487         || !stream.readRawData(reinterpret_cast<char*>(&version), sizeof(version)))
488         {
489         LogMsg(tr("Parsing Error: The filter file is not a valid PeerGuardian P2B file."), Log::CRITICAL);
490         return ruleCount;
491     }
492 
493     if ((version == 1) || (version == 2))
494     {
495         qDebug ("p2b version 1 or 2");
496         unsigned int start, end;
497 
498         std::string name;
499         while (getlineInStream(stream, name, '\0') && !m_abort)
500         {
501             if (!stream.readRawData(reinterpret_cast<char*>(&start), sizeof(start))
502                 || !stream.readRawData(reinterpret_cast<char*>(&end), sizeof(end)))
503                 {
504                 LogMsg(tr("Parsing Error: The filter file is not a valid PeerGuardian P2B file."), Log::CRITICAL);
505                 return ruleCount;
506             }
507 
508             // Network byte order to Host byte order
509             // asio address_v4 constructor expects it
510             // that way
511             const lt::address_v4 first(ntohl(start));
512             const lt::address_v4 last(ntohl(end));
513             // Apply to bittorrent session
514             try
515             {
516                 m_filter.add_rule(first, last, lt::ip_filter::blocked);
517                 ++ruleCount;
518             }
519             catch (const std::exception &) {}
520         }
521     }
522     else if (version == 3)
523     {
524         qDebug ("p2b version 3");
525         unsigned int namecount;
526         if (!stream.readRawData(reinterpret_cast<char*>(&namecount), sizeof(namecount)))
527         {
528             LogMsg(tr("Parsing Error: The filter file is not a valid PeerGuardian P2B file."), Log::CRITICAL);
529             return ruleCount;
530         }
531 
532         namecount = ntohl(namecount);
533         // Reading names although, we don't really care about them
534         for (unsigned int i = 0; i < namecount; ++i)
535         {
536             std::string name;
537             if (!getlineInStream(stream, name, '\0'))
538             {
539                 LogMsg(tr("Parsing Error: The filter file is not a valid PeerGuardian P2B file."), Log::CRITICAL);
540                 return ruleCount;
541             }
542 
543             if (m_abort) return ruleCount;
544         }
545 
546         // Reading the ranges
547         unsigned int rangecount;
548         if (!stream.readRawData(reinterpret_cast<char*>(&rangecount), sizeof(rangecount)))
549         {
550             LogMsg(tr("Parsing Error: The filter file is not a valid PeerGuardian P2B file."), Log::CRITICAL);
551             return ruleCount;
552         }
553 
554         rangecount = ntohl(rangecount);
555         unsigned int name, start, end;
556         for (unsigned int i = 0; i < rangecount; ++i)
557         {
558             if (!stream.readRawData(reinterpret_cast<char*>(&name), sizeof(name))
559                 || !stream.readRawData(reinterpret_cast<char*>(&start), sizeof(start))
560                 || !stream.readRawData(reinterpret_cast<char*>(&end), sizeof(end)))
561                 {
562                 LogMsg(tr("Parsing Error: The filter file is not a valid PeerGuardian P2B file."), Log::CRITICAL);
563                 return ruleCount;
564             }
565 
566             // Network byte order to Host byte order
567             // asio address_v4 constructor expects it
568             // that way
569             const lt::address_v4 first(ntohl(start));
570             const lt::address_v4 last(ntohl(end));
571             // Apply to bittorrent session
572             try
573             {
574                 m_filter.add_rule(first, last, lt::ip_filter::blocked);
575                 ++ruleCount;
576             }
577             catch (const std::exception &) {}
578 
579             if (m_abort) return ruleCount;
580         }
581     }
582     else
583     {
584         LogMsg(tr("Parsing Error: The filter file is not a valid PeerGuardian P2B file."), Log::CRITICAL);
585     }
586 
587     return ruleCount;
588 }
589 
590 // Process ip filter file
591 // Supported formats:
592 //  * eMule IP list (DAT): http://wiki.phoenixlabs.org/wiki/DAT_Format
593 //  * PeerGuardian Text (P2P): http://wiki.phoenixlabs.org/wiki/P2P_Format
594 //  * PeerGuardian Binary (P2B): http://wiki.phoenixlabs.org/wiki/P2B_Format
processFilterFile(const QString & filePath)595 void FilterParserThread::processFilterFile(const QString &filePath)
596 {
597     if (isRunning())
598     {
599         // Already parsing a filter, m_abort first
600         m_abort = true;
601         wait();
602     }
603 
604     m_abort = false;
605     m_filePath = filePath;
606     m_filter = lt::ip_filter();
607     // Run it
608     start();
609 }
610 
IPfilter()611 lt::ip_filter FilterParserThread::IPfilter()
612 {
613     return m_filter;
614 }
615 
run()616 void FilterParserThread::run()
617 {
618     qDebug("Processing filter file");
619     int ruleCount = 0;
620     if (m_filePath.endsWith(".p2p", Qt::CaseInsensitive))
621     {
622         // PeerGuardian p2p file
623         ruleCount = parseP2PFilterFile();
624     }
625     else if (m_filePath.endsWith(".p2b", Qt::CaseInsensitive))
626     {
627         // PeerGuardian p2b file
628         ruleCount = parseP2BFilterFile();
629     }
630     else if (m_filePath.endsWith(".dat", Qt::CaseInsensitive))
631     {
632         // eMule DAT format
633         ruleCount = parseDATFilterFile();
634     }
635 
636     if (m_abort) return;
637 
638     try
639     {
640         emit IPFilterParsed(ruleCount);
641     }
642     catch (const std::exception &)
643     {
644         emit IPFilterError();
645     }
646 
647     qDebug("IP Filter thread: finished parsing, filter applied");
648 }
649 
findAndNullDelimiter(char * const data,const char delimiter,const int start,const int end,const bool reverse)650 int FilterParserThread::findAndNullDelimiter(char *const data, const char delimiter, const int start, const int end, const bool reverse)
651 {
652     if (!reverse)
653     {
654         for (int i = start; i <= end; ++i)
655         {
656             if (data[i] == delimiter)
657             {
658                 data[i] = '\0';
659                 return i;
660             }
661         }
662     }
663     else
664     {
665         for (int i = end; i >= start; --i)
666         {
667             if (data[i] == delimiter)
668             {
669                 data[i] = '\0';
670                 return i;
671             }
672         }
673     }
674 
675     return -1;
676 }
677 
trim(char * const data,const int start,const int end)678 int FilterParserThread::trim(char *const data, const int start, const int end)
679 {
680     if (start >= end) return start;
681     int newStart = start;
682 
683     for (int i = start; i <= end; ++i)
684     {
685         if (isspace(data[i]) != 0)
686         {
687             data[i] = '\0';
688         }
689         else
690         {
691             newStart = i;
692             break;
693         }
694     }
695 
696     for (int i = end; i >= start; --i)
697     {
698         if (isspace(data[i]) != 0)
699             data[i] = '\0';
700         else
701             break;
702     }
703 
704     return newStart;
705 }
706