From d8f64b59180d130b94e5a6081f3924c0584e8746 Mon Sep 17 00:00:00 2001 From: Michael Tremer Date: Fri, 19 Oct 2018 12:32:01 +0100 Subject: [PATCH] location: Show blacklist status of IP addresses Signed-off-by: Michael Tremer --- src/backend/geoip.py | 226 +++++++++++++++++++++++++++++ src/scss/style.scss | 2 +- src/templates/location/lookup.html | 26 +++- src/web/location.py | 12 +- 4 files changed, 263 insertions(+), 3 deletions(-) diff --git a/src/backend/geoip.py b/src/backend/geoip.py index c9af6369..beac83be 100644 --- a/src/backend/geoip.py +++ b/src/backend/geoip.py @@ -1,12 +1,163 @@ #!/usr/bin/python +import ipaddress +import logging +import pycares import re +import socket +import tornado.gen +import tornado.platform.caresresolver from . import countries +from .decorators import * from .misc import Object +BLACKLISTS = ( + "access.redhawk.org", + "all.spamblock.unit.liu.se", + "b.barracudacentral.org", + "bl.deadbeef.com", + #"bl.emailbasura.org", + "bl.spamcannibal.org", + "bl.spamcop.net", + "blackholes.five-ten-sg.com", + #"blackholes.mail-abuse.org", + "blacklist.sci.kun.nl", + "blacklist.woody.ch", + "bogons.cymru.com", + "bsb.spamlookup.net", + "cbl.abuseat.org", + #"cbl.anti-spam.org.cn", + #"cblless.anti-spam.org.cn", + #"cblplus.anti-spam.org.cn", + #"cdl.anti-spam.org.cn", + #"combined.njabl.org", + "combined.rbl.msrbl.net", + "csi.cloudmark.com", + "db.wpbl.info", + #"dialups.mail-abuse.org", + "dnsbl-1.uceprotect.net", + "dnsbl-2.uceprotect.net", + "dnsbl-3.uceprotect.net", + "dnsbl.abuse.ch", + "dnsbl.cyberlogic.net", + "dnsbl.dronebl.org", + "dnsbl.inps.de", + "dnsbl.kempt.net", + #"dnsbl.njabl.org", + "dnsbl.sorbs.net", + "dob.sibl.support-intelligence.net", + "drone.abuse.ch", + "dsn.rfc-ignorant.org", + "duinv.aupads.org", + #"dul.blackhole.cantv.net", + "dul.dnsbl.sorbs.net", + "vdul.ru", + "dyna.spamrats.com", + "dynablock.sorbs.net", + #"dyndns.rbl.jp", + "dynip.rothen.com", + "forbidden.icm.edu.pl", + "http.dnsbl.sorbs.net", + "httpbl.abuse.ch", + "images.rbl.msrbl.net", + "ips.backscatterer.org", + "ix.dnsbl.manitu.net", + "korea.services.net", + "mail.people.it", + "misc.dnsbl.sorbs.net", + "multi.surbl.org", + "netblock.pedantic.org", + "noptr.spamrats.com", + "opm.tornevall.org", + "orvedb.aupads.org", + "pbl.spamhaus.org", + "phishing.rbl.msrbl.net", + "psbl.surriel.com", + "query.senderbase.org", + #"rbl-plus.mail-abuse.org", + "rbl.efnetrbl.org", + "rbl.interserver.net", + "rbl.spamlab.com", + "rbl.suresupport.com", + "relays.bl.gweep.ca", + "relays.bl.kundenserver.de", + #"relays.mail-abuse.org", + "relays.nether.net", + "residential.block.transip.nl", + #"rot.blackhole.cantv.net", + "sbl.spamhaus.org", + #"short.rbl.jp", + "smtp.dnsbl.sorbs.net", + "socks.dnsbl.sorbs.net", + "spam.abuse.ch", + "spam.dnsbl.sorbs.net", + "spam.rbl.msrbl.net", + "spam.spamrats.com", + "spamguard.leadmon.net", + "spamlist.or.kr", + "spamrbl.imp.ch", + "tor.dan.me.uk", + "ubl.lashback.com", + "ubl.unsubscore.com", + "uribl.swinog.ch", + #"url.rbl.jp", + "virbl.bit.nl", + #"virus.rbl.jp", + "virus.rbl.msrbl.net", + "web.dnsbl.sorbs.net", + "wormrbl.imp.ch", + "xbl.spamhaus.org", + "zen.spamhaus.org", + "zombie.dnsbl.sorbs.net", +) + +class Resolver(tornado.platform.caresresolver.CaresResolver): + def initialize(self, **kwargs): + super().initialize() + + # Overwrite Channel + self.channel = pycares.Channel(sock_state_cb=self._sock_state_cb, **kwargs) + + @tornado.gen.coroutine + def query(self, name, type=pycares.QUERY_TYPE_A): + # Create a new Future + fut = tornado.gen.Future() + + # Perform the query + self.channel.query(name, type, lambda result, error: fut.set_result((result, error))) + + # Wait for the response + result, error = yield fut + + # Handle any errors + if error: + # NXDOMAIN + if error == pycares.errno.ARES_ENOTFOUND: + return + + # Ignore responses with no data + elif error == pycares.errno.ARES_ENODATA: + return + + raise IOError( + "C-Ares returned error %s: %s while resolving %s" + % (error, pycares.errno.strerror(error), name) + ) + + # Return the result + return result + + class GeoIP(Object): + @lazy_property + def resolver(self): + return Resolver(tries=2, timeout=1, domains=[]) + + def lookup(self, address): + return Address(self.backend, address) + def guess_address_family(self, addr): if ":" in addr: return 6 @@ -51,3 +202,78 @@ class GeoIP(Object): def get_country_name(self, code): return countries.get_name(code) + + +class Address(Object): + def init(self, address): + self.address = ipaddress.ip_address(address) + + def __str__(self): + return "%s" % self.address + + @property + def reverse_pointer(self): + return self.address.reverse_pointer + + @property + def family(self): + if isinstance(self.address, ipaddress.IPv6Address): + return socket.AF_INET6 + elif isinstance(self.address, ipaddress.IPv4Address): + return socket.AF_INET + + # Blacklist + + def _make_blacklist_rr(self, blacklist): + rr = self.reverse_pointer.split(".") + + # Remove in-addr.arpa or ip6.arpa + for i in range(2): + rr.pop() + + # Append new suffix + rr.append(blacklist) + + return ".".join(rr) + + @tornado.gen.coroutine + def _resolve_blacklist(self, blacklist): + # Get resource record name + rr = self._make_blacklist_rr(blacklist) + + # Get query type from IP protocol version + if self.family == socket.AF_INET6: + type = pycares.QUERY_TYPE_AAAA + elif self.family == socket.AF_INET: + type = pycares.QUERY_TYPE_A + else: + raise NotImplementedError("Unknown IP protocol") + + # Run query + try: + res = yield self.backend.geoip.resolver.query(rr, type=type) + except IOError as e: + logging.warning(e) + + return None, "%s" % e + + # Not found + if not res: + return False, None + + # If the IP address is on a blacklist, we will try to fetch the TXT record + reason = yield self.backend.geoip.resolver.query(rr, type=pycares.QUERY_TYPE_TXT) + + # Take the first reason + if reason: + for i in reason: + return True, i.text.decode() + + # Blocked, but no reason + return True, None + + @tornado.gen.coroutine + def get_blacklists(self): + blacklists = yield { bl : self._resolve_blacklist(bl) for bl in BLACKLISTS } + + return blacklists diff --git a/src/scss/style.scss b/src/scss/style.scss index dcfa203b..82efcd76 100644 --- a/src/scss/style.scss +++ b/src/scss/style.scss @@ -75,7 +75,7 @@ body { .list-group { .list-group-item { - @extend .inverse; + color: $body-bg; } } diff --git a/src/templates/location/lookup.html b/src/templates/location/lookup.html index 862e95cf..e2bfab6b 100644 --- a/src/templates/location/lookup.html +++ b/src/templates/location/lookup.html @@ -7,7 +7,7 @@
-
+
{% if peer and peer.latitude and peer.longitude %}
{% module Map(peer.latitude, peer.longitude) %} @@ -36,6 +36,30 @@
+ + {% if blacklists %} +
+
+
{{ _("Blacklists") }}
+
+ +
    + {% for bl in sorted(blacklists) %} + {% if blacklists[bl] %} + {% set code, reason = blacklists[bl] %} + +
  • +

    {{ bl }}

    + + {% if reason %} + {{ reason }} + {% end %} +
  • + {% end %} + {% end %} +
+
+ {% end %}
{% end block %} diff --git a/src/web/location.py b/src/web/location.py index a9e5dc17..ecac92b0 100644 --- a/src/web/location.py +++ b/src/web/location.py @@ -1,6 +1,8 @@ #!/usr/bin/python +import logging +import tornado.gen import tornado.web from . import handlers_base as base @@ -11,9 +13,17 @@ class IndexHandler(base.BaseHandler): class LookupHandler(base.BaseHandler): + @tornado.gen.coroutine def get(self, address): peer = self.geoip.get_all(address) if peer: peer["country_name"] = self.geoip.get_country_name(peer.country) - self.render("location/lookup.html", address=address, peer=peer) + # Lookup address + address = self.geoip.lookup(address) + + # Lookup blacklists + blacklists = yield address.get_blacklists() + + self.render("location/lookup.html", + address=address, blacklists=blacklists, peer=peer) -- 2.47.3