├── MANIFEST.in ├── rblwatch ├── __init__.py └── rblwatch.py ├── setup.cfg ├── .gitignore ├── CHANGES.txt ├── setup.py ├── README.md ├── bin ├── rblwatch └── rblcheck └── LICENSE.txt /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *.txt 2 | -------------------------------------------------------------------------------- /rblwatch/__init__.py: -------------------------------------------------------------------------------- 1 | from .rblwatch import RBLSearch 2 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # python 2 | .idea 3 | *.pyc 4 | # distutils 5 | dist/ 6 | MANIFEST 7 | -------------------------------------------------------------------------------- /CHANGES.txt: -------------------------------------------------------------------------------- 1 | v0.2.4, 2016-04-24 -- Fix import for python3, thanks to rakiru 2 | v0.2.3, 2016-04-07 -- Remove rbl.interserver.net because no longer in service 3 | v0.2.2, 2015-02-09 -- Extend hostname with suffix dot for valid host checking 4 | v0.2.1, 2015-01-06 -- Remove additional ahbl lists that are no longer in service 5 | v0.2.0, 2014-12-27 -- Remove tor.ahbl.org because no longer in service 6 | v0.1.0, 2014-08-28 -- Initial release with pip package 7 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from distutils.core import setup 2 | setup( 3 | name = 'rblwatch', 4 | packages = ['rblwatch'], 5 | scripts = ['bin/rblcheck', 'bin/rblwatch'], 6 | version = '0.2.4', 7 | description = 'RBL lookups with Python', 8 | author = 'James Polera', 9 | author_email = 'james@uncryptic.com', 10 | maintainer = 'Thomas Merkel', 11 | maintainer_email = 'tm@core.io', 12 | url = 'https://github.com/drscream/rblwatch', 13 | keywords = ['rbl', 'blacklist', 'mail'], 14 | install_requires = ['IPy', 'dnspython'], 15 | ) 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | rblwatch 2 | == 3 | rblwatch is a utility for doing [RBL](http://en.wikipedia.org/wiki/DNSBL) lookups with Python. 4 | This is the code that provides the lookup functionality for [http://www.mxutils.com](http://www.mxutils.com) 5 | 6 | Requirements 7 | == 8 | Python 2.x, PyPy 9 | 10 | dnspython==1.11.1 11 | IPy==0.81 12 | 13 | Python3 14 | 15 | dnspython3==1.11.1 16 | IPy==0.81 17 | 18 | Author 19 | == 20 | - James Polera 21 | - Thomas Merkel 22 | 23 | Usage 24 | == 25 | from rblwatch import RBLSearch 26 | 27 | # Do the lookup (for smtp.gmail.com) 28 | searcher = RBLSearch('74.125.93.109') 29 | 30 | # Display a simply formatted report of the results 31 | searcher.print_results() 32 | 33 | # Use the result data for something else 34 | result_data = searcher.listed 35 | 36 | License 37 | == 38 | This code is released under the BSD license. 39 | -------------------------------------------------------------------------------- /bin/rblwatch: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # vim:expandtab shiftwidth=4 softtabstop=4 tabstop=8 3 | 4 | from rblwatch import RBLSearch 5 | from IPy import IP 6 | import socket 7 | import sys 8 | 9 | try: 10 | if len(sys.argv) > 1: 11 | print("Looking up: %s (please wait)" % sys.argv[1]) 12 | param = sys.argv[1] 13 | ips = set() 14 | 15 | try: 16 | # Is IP 17 | ips.add(IP(param)) 18 | except ValueError: 19 | # Is maybe an hostname 20 | try: 21 | for response in socket.getaddrinfo(param, None, 0, 1): 22 | ips.add(response[4][0]) 23 | except socket.error: 24 | print("IP %s can't be resolved" % param) 25 | 26 | # Output all information to stdout 27 | for ip in ips: 28 | searcher = RBLSearch(ip) 29 | searcher.print_results() 30 | else: 31 | print("""Usage summary: 32 | 33 | rblwatch """) 34 | except KeyboardInterrupt: 35 | pass 36 | 37 | -------------------------------------------------------------------------------- /bin/rblcheck: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # vim:expandtab shiftwidth=4 softtabstop=4 tabstop=8 3 | 4 | from rblwatch import RBLSearch 5 | import socket 6 | 7 | try: 8 | hostname = socket.gethostname() + '.' 9 | for response in socket.getaddrinfo(hostname, None, 0, 1): 10 | ip = response[4][0] 11 | searcher = RBLSearch(ip) 12 | listed = searcher.listed 13 | for key in listed: 14 | if key == 'SEARCH_HOST': 15 | continue 16 | if not listed[key].get('ERROR'): 17 | if listed[key]['LISTED']: 18 | print("Results for %s: %s" % (key, listed[key]['LISTED'])) 19 | print(" + Host information: %s" % \ 20 | (listed[key]['HOST'])) 21 | if 'TEXT' in listed[key].keys(): 22 | print(" + Additional information: %s" % \ 23 | (listed[key]['TEXT'])) 24 | else: 25 | pass 26 | except: 27 | print("Hostname %s can't be resolved" % hostname) 28 | ip = "" 29 | 30 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, James Polera 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /rblwatch/rblwatch.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import sys 4 | import socket 5 | import re 6 | from IPy import IP 7 | from dns.resolver import Resolver, NXDOMAIN, NoNameservers, Timeout, NoAnswer 8 | from threading import Thread 9 | 10 | RBLS = [ 11 | 'aspews.ext.sorbs.net', 12 | 'b.barracudacentral.org', 13 | 'bl.deadbeef.com', 14 | 'bl.emailbasura.org', 15 | 'bl.spamcannibal.org', 16 | 'bl.spamcop.net', 17 | 'blackholes.five-ten-sg.com', 18 | 'blacklist.woody.ch', 19 | 'bogons.cymru.com', 20 | 'cbl.abuseat.org', 21 | 'cdl.anti-spam.org.cn', 22 | 'combined.abuse.ch', 23 | 'combined.rbl.msrbl.net', 24 | 'db.wpbl.info', 25 | 'dnsbl-1.uceprotect.net', 26 | 'dnsbl-2.uceprotect.net', 27 | 'dnsbl-3.uceprotect.net', 28 | 'dnsbl.cyberlogic.net', 29 | 'dnsbl.dronebl.org', 30 | 'dnsbl.inps.de', 31 | 'dnsbl.njabl.org', 32 | 'dnsbl.sorbs.net', 33 | 'drone.abuse.ch', 34 | 'duinv.aupads.org', 35 | 'dul.dnsbl.sorbs.net', 36 | 'dul.ru', 37 | 'dyna.spamrats.com', 38 | 'dynip.rothen.com', 39 | 'http.dnsbl.sorbs.net' 40 | 'images.rbl.msrbl.net', 41 | 'ips.backscatterer.org', 42 | 'ix.dnsbl.manitu.net', 43 | 'korea.services.net', 44 | 'misc.dnsbl.sorbs.net', 45 | 'noptr.spamrats.com', 46 | 'ohps.dnsbl.net.au', 47 | 'omrs.dnsbl.net.au', 48 | 'orvedb.aupads.org', 49 | 'osps.dnsbl.net.au', 50 | 'osrs.dnsbl.net.au', 51 | 'owfs.dnsbl.net.au', 52 | 'owps.dnsbl.net.au' 53 | 'pbl.spamhaus.org', 54 | 'phishing.rbl.msrbl.net', 55 | 'probes.dnsbl.net.au' 56 | 'proxy.bl.gweep.ca', 57 | 'proxy.block.transip.nl', 58 | 'psbl.surriel.com', 59 | 'rdts.dnsbl.net.au', 60 | 'relays.bl.gweep.ca', 61 | 'relays.bl.kundenserver.de', 62 | 'relays.nether.net', 63 | 'residential.block.transip.nl', 64 | 'ricn.dnsbl.net.au', 65 | 'rmst.dnsbl.net.au', 66 | 'sbl.spamhaus.org', 67 | 'short.rbl.jp', 68 | 'smtp.dnsbl.sorbs.net', 69 | 'socks.dnsbl.sorbs.net', 70 | 'spam.abuse.ch', 71 | 'spam.dnsbl.sorbs.net', 72 | 'spam.rbl.msrbl.net', 73 | 'spam.spamrats.com', 74 | 'spamlist.or.kr', 75 | 'spamrbl.imp.ch', 76 | 't3direct.dnsbl.net.au', 77 | 'tor.dnsbl.sectoor.de', 78 | 'torserver.tor.dnsbl.sectoor.de', 79 | 'ubl.lashback.com', 80 | 'ubl.unsubscore.com', 81 | 'virbl.bit.nl', 82 | 'virus.rbl.jp', 83 | 'virus.rbl.msrbl.net', 84 | 'web.dnsbl.sorbs.net', 85 | 'wormrbl.imp.ch', 86 | 'xbl.spamhaus.org', 87 | 'zen.spamhaus.org', 88 | 'zombie.dnsbl.sorbs.net', 89 | ] 90 | 91 | 92 | class Lookup(Thread): 93 | def __init__(self, host, dnslist, listed, resolver): 94 | Thread.__init__(self) 95 | self.host = host 96 | self.listed = listed 97 | self.dnslist = dnslist 98 | self.resolver = resolver 99 | 100 | def run(self): 101 | try: 102 | host_record = self.resolver.query(self.host, "A") 103 | if len(host_record) > 0: 104 | self.listed[self.dnslist]['LISTED'] = True 105 | self.listed[self.dnslist]['HOST'] = host_record[0].address 106 | text_record = self.resolver.query(self.host, "TXT") 107 | if len(text_record) > 0: 108 | self.listed[self.dnslist]['TEXT'] = "\n".join(text_record[0].strings) 109 | self.listed[self.dnslist]['ERROR'] = False 110 | except NXDOMAIN: 111 | self.listed[self.dnslist]['ERROR'] = True 112 | self.listed[self.dnslist]['ERRORTYPE'] = NXDOMAIN 113 | except NoNameservers: 114 | self.listed[self.dnslist]['ERROR'] = True 115 | self.listed[self.dnslist]['ERRORTYPE'] = NoNameservers 116 | except Timeout: 117 | self.listed[self.dnslist]['ERROR'] = True 118 | self.listed[self.dnslist]['ERRORTYPE'] = Timeout 119 | except NameError: 120 | self.listed[self.dnslist]['ERROR'] = True 121 | self.listed[self.dnslist]['ERRORTYPE'] = NameError 122 | except NoAnswer: 123 | self.listed[self.dnslist]['ERROR'] = True 124 | self.listed[self.dnslist]['ERRORTYPE'] = NoAnswer 125 | 126 | class RBLSearch(object): 127 | def __init__(self, lookup_host): 128 | self.lookup_host = lookup_host 129 | self._listed = None 130 | self.resolver = Resolver() 131 | self.resolver.timeout = 0.2 132 | self.resolver.lifetime = 1.0 133 | 134 | def search(self): 135 | if self._listed is not None: 136 | pass 137 | else: 138 | ip = IP(self.lookup_host) 139 | host = ip.reverseName() 140 | if ip.version() == 4: 141 | host = re.sub('.in-addr.arpa.', '', host) 142 | elif ip.version() == 6: 143 | host = re.sub('.ip6.arpa.', '', host) 144 | self._listed = {'SEARCH_HOST': self.lookup_host} 145 | threads = [] 146 | for LIST in RBLS: 147 | self._listed[LIST] = {'LISTED': False} 148 | query = Lookup("%s.%s" % (host, LIST), LIST, self._listed, self.resolver) 149 | threads.append(query) 150 | query.start() 151 | for thread in threads: 152 | thread.join() 153 | return self._listed 154 | listed = property(search) 155 | 156 | def print_results(self): 157 | listed = self.listed 158 | print("") 159 | print("--- DNSBL Report for %s ---" % listed['SEARCH_HOST']) 160 | for key in listed: 161 | if key == 'SEARCH_HOST': 162 | continue 163 | if not listed[key].get('ERROR'): 164 | if listed[key]['LISTED']: 165 | print("Results for %s: %s" % (key, listed[key]['LISTED'])) 166 | print(" + Host information: %s" % \ 167 | (listed[key]['HOST'])) 168 | if 'TEXT' in listed[key].keys(): 169 | print(" + Additional information: %s" % \ 170 | (listed[key]['TEXT'])) 171 | else: 172 | #print "*** Error contacting %s ***" % key 173 | pass 174 | 175 | if __name__ == "__main__": 176 | # Tests! 177 | try: 178 | if len(sys.argv) > 1: 179 | print("Looking up: %s (please wait)" % sys.argv[1]) 180 | ip = sys.argv[1] 181 | pat = re.compile("\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}") 182 | is_ip_address = pat.match(ip) 183 | if not is_ip_address: 184 | try: 185 | ip = socket.gethostbyname(ip) 186 | print("Hostname %s resolved to ip %s" % (sys.argv[1],ip)) 187 | except socket.error: 188 | print("IP %s can't be resolved" % ip) 189 | ip = "" 190 | if ip: 191 | searcher = RBLSearch(ip) 192 | searcher.print_results() 193 | else: 194 | print("""Usage summary: 195 | 196 | rblwatch """) 197 | except KeyboardInterrupt: 198 | pass 199 | --------------------------------------------------------------------------------