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