#!/usr/bin/env python3

#
# Reverse resolve a range of network-addresses against a specific nameserver. 
# Basically "dig -x" in a loop.
# Written by @c0dmtr1x (info@codemetrix.net)
# 


import sys, ipaddress, dns, dns.resolver, dns.reversename, argparse
from time import sleep

results = {1:"FOUND",2:"NO_ENTRY",3:"REFUSED", 4:"NO_ANSWER", 5:"TIMEOUT"}
private_networks=["10.0.0.0/8","172.16.0.0/12","192.168.0.0/16","fc00::/7","fd00::/8"]

# resolve an ip against given resolver
def resolve(resolver,ip, quiet=False):
    try:
        answer = resolver.query(ip.reverse_pointer,'ptr')
        if not quiet:
            print("[+] " + str(ip) + " : " + str(answer[0]))
        return 1, str(answer[0])
    except dns.resolver.NXDOMAIN:
        if not quiet:
            print("[.] Resolved but no entry for " + str(ip))
        return 2, None
    except dns.resolver.NoNameservers:
        if not quiet:
            print("[-] Answer refused for " + str(ip))
        return 3, None
    except dns.resolver.NoAnswer:
        if not quiet:
            print("[-] No answer section for " + str(ip))
        return 4, None
    except dns.exception.Timeout:
        if not quiet:
            print("[-] Timeout")
        return 5, None

# log output to file
def write_file(outfile,nameserver,ip,result, answer = None):
    outfile.write(str(nameserver) + "," + str(ip) + "," + results[result] + "," + str(answer) + "\n")


# check range of network addresses
def quickcheck(resolver):
    for cidr in private_networks:
        network = ipaddress.ip_network(str(cidr))
        hosts = network.hosts()
        ip = hosts.__next__()
        print("[*] Checking network " + cidr)
        result, answer = resolve(resolver,ip,True)
        if args.outfile:
            write_file(outfile,custom_ns,ip,result)
        if result in (1,2,3,4): #skip timeouts
            print("    * Nameserver responded, further checks ")
            if result == 1:
                print("[+] Entry found in nameserver " + str(custom_ns) + " for " + str(ip) + " : " + answer)
            for i in range(1,args.max_queries):
                ip = hosts.__next__()
                result, answer = resolve(resolver,ip,True)
                if args.outfile:
                    write_file(outfile,custom_ns,ip,result,answer)
                if result == 1:
                    print("[+] Entry found in nameserver " + str(custom_ns) + " for " + str(ip) + " : " + answer)
        else:
            print("    - No response, timeout or denied")
            if args.outfile:
                write_file(outfile,custom_ns,ip,result,answer)


# Resolve nameserver
def get_nameserver(nameserver):
    custom_ns = None
    try:
        custom_ns = str(ipaddress.ip_address(nameserver))
    except ValueError:
        # no ip, so we try to resolve it as hostname.
        import socket
        custom_ns = socket.gethostbyname(nameserver)
    
    return custom_ns


# configure a resolver with a specific nameserver
def get_resolver(nameserver):
    resolver = dns.resolver.Resolver()
    resolver.nameservers = [nameserver]
    resolver.timeout = args.timeout
    resolver.lifetime = args.timeout
    return resolver

# argparse
parser = argparse.ArgumentParser()
parser.add_argument('--quickcheck', '-q', help='Quick check: Use for batch testing. Scans the first entries of each private network.', action='store_true')
parser.add_argument('--timeout', '-t', help="Manually adjust timout in seconds. Default is 5", nargs="?", type=int, default=5)
parser.add_argument('--max-queries','-m', help="Maximal number of queries for a network. In quickcheck mode each private network will be called with the first number of ips specified here. Default 15.", type=int, default=15)
parser.add_argument('nameserver', help="nameserver IP or hostname",nargs="?")
parser.add_argument('network', help="network range in CIDR notation to check, e.g. 10.0.0.0/8", nargs="?")
parser.add_argument('--outfile', '-o', help="write results in file", nargs="?")
parser.add_argument('--infile', '-i', help="Read nameservers from here, 1 per line", nargs="?")
args = parser.parse_args()


if not args.nameserver and not args.infile:
    print("Please specifiy nameserver or nameserver list")
    parser.print_help()
    exit(0)


maxips = args.max_queries

# Set outfile
outfile = None
if args.outfile:
    outfile = open(args.outfile,'w')

# default global values
custom_ns = None
resolver = None
if args.nameserver:
    custom_ns = get_nameserver(args.nameserver)
    resolver = get_resolver(custom_ns)
    print ("[*] Checking nameserver " + str(custom_ns))

# try to resolve the first ip of each network
if not args.network and not args.quickcheck:
    for cidr in private_networks:
        network = ipaddress.ip_network(str(cidr))
        ip = network.hosts().__next__()
        result,answer = resolve(resolver,ip)
        if args.outfile:
            write_file(outfile,custom_ns,ip,result,answer)

# check whole network
if args.network:
    network = ipaddress.ip_network(args.network)
    for ip in network.hosts():
        result,answer = resolve(resolver,ip)
        if args.outfile:
            write_file(outfile,custom_ns,ip,result,answer)
        # stay quit - more or less
        sleep(0.03)

# check each network up to max_queries ips
if args.quickcheck:
    if not args.infile:
        quickcheck(resolver)
    else:
        infile = open(args.infile,"r")
        if not infile:
            print("Could not open input file. Exiting.")
            exit(0)

        for line in infile:
            custom_ns = get_nameserver(line.strip())
            resolver = get_resolver(custom_ns)
            print ("[*] Checking nameserver " + str(custom_ns))
            quickcheck(resolver)