| 1 | # -*- coding: utf-8 -*-
|
|---|
| 2 | #
|
|---|
| 3 | # Copyright (C) 2015 Edgewall Software
|
|---|
| 4 | # Copyright (C) 2006 Matthew Good <trac@matt-good.net>
|
|---|
| 5 | # Copyright (C) 2009 Vaclav Slavik <vslavik@fastmail.fm>
|
|---|
| 6 | # Copyright (C) 2015 Dirk Stöcker <trac@dstoecker.de>
|
|---|
| 7 | # All rights reserved.
|
|---|
| 8 | #
|
|---|
| 9 | # This software is licensed as described in the file COPYING, which
|
|---|
| 10 | # you should have received as part of this distribution. The terms
|
|---|
| 11 | # are also available at http://trac.edgewall.com/license.html.
|
|---|
| 12 | #
|
|---|
| 13 | # This software consists of voluntary contributions made by many
|
|---|
| 14 | # individuals. For the exact contribution history, see the revision
|
|---|
| 15 | # history and logs, available at http://projects.edgewall.com/trac/.
|
|---|
| 16 | #
|
|---|
| 17 | # Author: Vaclav Slavik <vslavik@fastmail.fm>,
|
|---|
| 18 | # Matthew Good <trac@matt-good.net>
|
|---|
| 19 |
|
|---|
| 20 | from dns.resolver import NXDOMAIN, NoAnswer, NoNameservers, Timeout, query
|
|---|
| 21 |
|
|---|
| 22 | from trac.config import Option, IntOption
|
|---|
| 23 | from trac.core import Component, implements
|
|---|
| 24 |
|
|---|
| 25 | from tracspamfilter.api import IFilterStrategy, N_
|
|---|
| 26 |
|
|---|
| 27 |
|
|---|
| 28 | class HttpBLFilterStrategy(Component):
|
|---|
| 29 | """Spam filter based on Project Honey Pot's Http:BL blacklist.
|
|---|
| 30 |
|
|---|
| 31 | Requires the dnspython module from http://www.dnspython.org/.
|
|---|
| 32 | """
|
|---|
| 33 | implements(IFilterStrategy)
|
|---|
| 34 |
|
|---|
| 35 | karma_points = IntOption('spam-filter', 'httpbl_spammer_karma', '6',
|
|---|
| 36 | """By how many points listing as "comment spammer" impacts the
|
|---|
| 37 | overall karma of a submission.""", doc_domain='tracspamfilter')
|
|---|
| 38 |
|
|---|
| 39 | api_key = Option('spam-filter', 'httpbl_api_key', '',
|
|---|
| 40 | "Http:BL API key required for use.", doc_domain='tracspamfilter')
|
|---|
| 41 |
|
|---|
| 42 | # IFilterStrategy implementation
|
|---|
| 43 |
|
|---|
| 44 | def is_external(self):
|
|---|
| 45 | return True
|
|---|
| 46 |
|
|---|
| 47 | def test(self, req, author, content, ip):
|
|---|
| 48 | if not self.api_key:
|
|---|
| 49 | self.log.warning("API key not configured.")
|
|---|
| 50 | return
|
|---|
| 51 |
|
|---|
| 52 | # IPV4 address? HTTP:BL does not yet support IPv6
|
|---|
| 53 | if ip.find(".") < 0:
|
|---|
| 54 | return
|
|---|
| 55 |
|
|---|
| 56 | reverse_octal = '.'.join(reversed(ip.split('.')))
|
|---|
| 57 | addr = '%s.%s.dnsbl.httpbl.org' % (self.api_key, reverse_octal)
|
|---|
| 58 | self.log.debug('Querying Http:BL: %s', addr)
|
|---|
| 59 |
|
|---|
| 60 | try:
|
|---|
| 61 | dns_answer = query(addr)
|
|---|
| 62 | answer = [int(i) for i in str(dns_answer[0]).split('.')]
|
|---|
| 63 | if answer[0] != 127:
|
|---|
| 64 | self.log.warning('Invalid Http:BL reply for IP "%s": %s',
|
|---|
| 65 | ip, dns_answer)
|
|---|
| 66 | return
|
|---|
| 67 |
|
|---|
| 68 | # TODO: answer[1] represents number of days since last activity
|
|---|
| 69 | # and answer[2] is treat score assigned by Project Honey
|
|---|
| 70 | # Pot. We could use both to adjust karma.
|
|---|
| 71 |
|
|---|
| 72 | is_suspicious = answer[3] & 1
|
|---|
| 73 | is_spammer = answer[3] & 4
|
|---|
| 74 |
|
|---|
| 75 | points = 0
|
|---|
| 76 | if is_suspicious:
|
|---|
| 77 | points -= abs(self.karma_points) / 3
|
|---|
| 78 | if is_spammer:
|
|---|
| 79 | points -= abs(self.karma_points)
|
|---|
| 80 |
|
|---|
| 81 | if points != 0:
|
|---|
| 82 | return points, N_("IP %s blacklisted by Http:BL"), ip
|
|---|
| 83 |
|
|---|
| 84 | except NXDOMAIN:
|
|---|
| 85 | # not blacklisted on this server
|
|---|
| 86 | return
|
|---|
| 87 | except (Timeout, NoAnswer, NoNameservers), e:
|
|---|
| 88 | self.log.warning('Error checking Http:BL for IP "%s": %s', ip, e)
|
|---|
| 89 |
|
|---|
| 90 | def train(self, req, author, content, ip, spam=True):
|
|---|
| 91 | return 0
|
|---|