#!/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
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