-
-
Save TheRook/95f2b872bdc81bac2371 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
import dns.resolver | |
import dns.name | |
#import netaddr | |
from urlparse import urlparse | |
INFO = "\033[1m\033[36m[*]\033[0m " | |
WARN = "\033[1m\033[31m[!]\033[0m " | |
LOW = "\033[1m\033[34m[-]\033[0m " | |
MAYBE = "\033[1m\033[35m[?]\033[0m " | |
MONEY = "\033[1m\033[38m[$]\033[0m " | |
MAX_LOOKUPS = 100 | |
CURRENT_LOOKUPS = 0 | |
class SPFRecord(object): | |
def __init__(self, domain): | |
self.version = None | |
self.includes = [] | |
self.ip4 = [] | |
self.ip6 = [] | |
self._dns_response = dns.resolver.query(domain, 'TXT') | |
self.txt_records = [txt.to_text() for txt in self._dns_response] | |
for txt in self.txt_records: | |
self._parse_txt(txt) | |
def _parse_txt(self, txt): | |
''' Parses a raw txt record ''' | |
for entry in txt.split(' '): | |
if entry.startswith('v') and '=' in entry: | |
self._add_version(entry) | |
elif entry.startswith('include') and ':' in entry: | |
self._add_include(entry) | |
elif entry.startswith('redirect') and '=' in entry: | |
self._add_redirect(entry) | |
elif entry.startswith('ip4') and ':' in entry: | |
self._add_ip4(entry) | |
elif entry.startswith('ip6') and ':' in entry: | |
self._add_ip6(entry) | |
@property | |
def ips(self): | |
return self.ip4 + self.ip6 | |
def _add_version(self, entry): | |
self.version = entry.split('=')[1] | |
def _add_include(self, entry): | |
self.includes.append(entry.split(':')[1]) | |
def _add_redirect(self, entry): | |
self.includes.append(entry.split('=')[1].strip('"')) | |
def _add_ip4(self, entry): | |
ip = entry.split(':')[1] | |
self.ip4.append(ip) | |
def _add_ip6(self, entry): | |
ip = entry.split(':')[1] | |
self.ip6.append(ip) | |
def is_expired(domain): | |
try: | |
dns.resolver.query(domain) | |
return False | |
except dns.resolver.NXDOMAIN: | |
return True | |
except dns.resolver.NoAnswer: | |
return False | |
def get_spf_record(domain): | |
if is_expired(domain): | |
print WARN + "%s does not resolve" % domain | |
return None | |
print INFO + domain | |
try: | |
return SPFRecord(domain) | |
except dns.resolver.NoAnswer: | |
print '\t' + LOW + 'No TXT record for', domain | |
except dns.exception.Timeout: | |
print '\t' + WARN + "DNS timeout for", domain | |
except dns.resolver.NoNameservers: | |
print '\t' + WARN + "No name servers were found for", domain | |
except: | |
print '\t' + WARN + "Something went wrong ..." | |
return None | |
def check_spf(spf, domain, max_lookups = 0, depth = 0): | |
for inc_domain in spf.includes: | |
try: | |
url = urlparse("mail://%s" % inc_domain).netloc | |
parent = '.'.join(url.split('.')[-2:]) | |
if is_expired(parent): | |
print '\t' + MONEY, | |
print "%s's parent domain \"%s\" is not regiestered" % ( | |
inc_domain, parent) | |
else: | |
print '\t' + LOW + \ | |
"%s's parent domain is registered" % inc_domain | |
except dns.resolver.NoAnswer: | |
print '\t' + MAYBE + 'No answer for lookup of', inc_domain | |
except dns.exception.Timeout: | |
print '\t' + WARN + "DNS timeout for", parent | |
except dns.resolver.NoNameservers: | |
print '\t' + WARN + "No name servers were found for", parent | |
if max_lookups - depth: | |
if depth == 9: | |
print '\t' + WARN + "SPF permerror, more than 8 lookups needed.", parent | |
inc_spf = get_spf_record(inc_domain) | |
depth = check_spf(inc_spf, domain, max_lookups, depth + 1) | |
return depth | |
if __name__ == '__main__': | |
import os | |
import argparse | |
def read_files(files): | |
domains = [] | |
for f in files: | |
if os.path.exists(f) and os.path.isfile(f): | |
with open(f, 'r') as fp: | |
domains += [line.split(',')[1].strip() for line in fp.readlines()] | |
else: | |
print WARN + "File does not exist", f | |
return domains | |
parser = argparse.ArgumentParser( | |
description='Check domains for expired SPF records', | |
) | |
parser.add_argument('--version', | |
action='version', | |
version='%(prog)s v0.0.1' | |
) | |
parser.add_argument('--domains', '-d', | |
help='domains to check', | |
dest='domains', | |
nargs='*', | |
) | |
parser.add_argument('--csv', '-c', | |
help='read domains from csv file(s)', | |
dest='files', | |
nargs='*', | |
) | |
parser.add_argument('--maxlookups', '-m', | |
help='Maximum number of DNS queries used to resolve an SPF record.', | |
dest='maxlookups', | |
default=100, | |
nargs='*', | |
) | |
args = parser.parse_args() | |
_domains = [] | |
if args.files: | |
_domains += read_files(args.files) | |
if args.domains: | |
_domains += args.domains | |
for domain in _domains: | |
spf = get_spf_record(domain) | |
if spf is not None: | |
print "Depth:" + str(check_spf(spf, domain, args.maxlookups)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment