massrdap

- A high-performance RDAP resolver for bulk lookups and reconnaissance
git clone git://git.acid.vegas/massrdap.git
Log | Files | Refs | Archive | README | LICENSE

massrdap.py (5228B)

      1 #!/usr/bin/env python
      2 # MassRDAP - developed by acidvegas (https://git.acid.vegas/massrdap)
      3 
      4 import argparse
      5 import asyncio
      6 import logging
      7 import json
      8 import re
      9 
     10 try:
     11     import aiofiles
     12 except ImportError:
     13     raise ImportError('missing required aiofiles library (pip install aiofiles)')
     14 
     15 try:
     16     import aiohttp
     17 except ImportError:
     18     raise ImportError('missing required aiohttp library (pip install aiohttp)')
     19 
     20 # Color codes
     21 BLUE   = '\033[1;34m'
     22 CYAN   = '\033[1;36m'
     23 GREEN  = '\033[1;32m'
     24 GREY   = '\033[1;90m'
     25 PINK   = '\033[1;95m'
     26 PURPLE = '\033[0;35m'
     27 RED    = '\033[1;31m'
     28 YELLOW = '\033[1;33m'
     29 RESET  = '\033[0m'
     30 
     31 # Setup basic logging
     32 logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
     33 
     34 # Global variable to store RDAP servers
     35 RDAP_SERVERS = {}
     36 
     37 async def fetch_rdap_servers():
     38     '''Fetches RDAP servers from IANA's RDAP Bootstrap file.'''
     39 
     40     async with aiohttp.ClientSession() as session:
     41         async with session.get('https://data.iana.org/rdap/dns.json') as response:
     42             data = await response.json()
     43             for entry in data['services']:
     44                 tlds = entry[0]
     45                 rdap_url = entry[1][0]
     46                 for tld in tlds:
     47                     RDAP_SERVERS[tld] = rdap_url
     48 
     49 
     50 def get_tld(domain: str):
     51     '''Extracts the top-level domain from a domain name.'''
     52     parts = domain.split('.')
     53     return '.'.join(parts[1:]) if len(parts) > 1 else parts[0]
     54 
     55 
     56 async def lookup_domain(domain: str, proxy_url: str, semaphore: asyncio.Semaphore, success_file, failure_file):
     57     '''
     58     Looks up a domain using the RDAP protocol.
     59     
     60     :param domain: The domain to look up.
     61     :param proxy_url: The proxy URL to use for the request.
     62     :param semaphore: The semaphore to use for concurrency limiting.
     63     '''
     64 
     65     async with semaphore:
     66         tld = get_tld(domain)
     67 
     68         rdap_url = RDAP_SERVERS.get(tld)
     69 
     70         if not rdap_url:
     71             return
     72 
     73         query_url = f'{rdap_url}domain/{domain}'
     74 
     75         try:
     76             async with aiohttp.ClientSession() as session:
     77                 async with session.get(query_url, proxy=proxy_url if proxy_url else None) as response:
     78 
     79                     if response.status == 200:
     80                         data = await response.json()
     81                         await success_file.write(json.dumps(data) + '\n')
     82                         print(f'{GREEN}SUCCESS {GREY}| {BLUE}{response.status} {GREY}| {PURPLE}{rdap_url.ljust(50)} {GREY}| {CYAN}{domain}{GREEN}')
     83 
     84                     else:
     85                         await failure_file.write(domain + '\n')
     86                         print(f'{RED}FAILED  {GREY}| {YELLOW}{response.status} {GREY}| {PURPLE}{rdap_url.ljust(50)} {GREY}| {CYAN}{domain}{RESET}')
     87 
     88         except Exception as e:
     89             print(f'{RED}FAILED  {GREY}| --- | {PURPLE}{rdap_url.ljust(50)} {GREY}| {CYAN}{domain} {RED}| {e}{RESET}')
     90 
     91 
     92 async def process_domains(args: argparse.Namespace):
     93     '''
     94     Processes a list of domains, performing RDAP lookups for each one.
     95     
     96     :param args: The parsed command-line arguments.
     97     '''
     98     
     99     await fetch_rdap_servers() # Populate RDAP_SERVERS with TLDs and their RDAP servers
    100 
    101     if not RDAP_SERVERS:
    102         logging.error('No RDAP servers found.')
    103         return
    104     
    105     semaphore = asyncio.Semaphore(args.concurrency)
    106 
    107     async with aiofiles.open(args.output, 'w') as success_file, aiofiles.open(args.failed, 'w') as failure_file:
    108         async with aiofiles.open(args.input_file) as file:
    109             async for domain in file:
    110                 domain = domain.strip()
    111                 if domain:
    112                     await semaphore.acquire()
    113                     task = asyncio.create_task(lookup_domain(domain, args.proxy, semaphore, success_file, failure_file))
    114                     task.add_done_callback(lambda t: semaphore.release())
    115 
    116 
    117 if __name__ == '__main__':
    118     parser = argparse.ArgumentParser(description='Perform RDAP lookups for a list of domains.')
    119     parser.add_argument('-i', '--input_file', required=True, help='File containing list of domains (one per line).')
    120     parser.add_argument('-p', '--proxy', help='Proxy in user:pass@host:port format. If not supplied, none is used.')
    121     parser.add_argument('-c', '--concurrency', type=int, default=25, help='Number of concurrent requests to make. (default: 25)')
    122     parser.add_argument('-o', '--output', default='output.json', help='Output file to write successful RDAP data to. (default: output.json)')
    123     parser.add_argument('-f', '--failed', default='failed.txt', help='Output file to write failed domains to. (optional)')
    124     args = parser.parse_args()
    125 
    126     if not args.input_file:
    127         raise ValueError('File path is required.')
    128 
    129     if args.concurrency < 1:
    130         raise ValueError('Concurrency must be at least 1.')
    131         
    132     if args.proxy:
    133         if not re.match(r'^https?:\/\/[^:]+:[^@]+@[^:]+:\d+$', args.proxy):
    134             raise ValueError('Invalid proxy format. Must be in user:pass@host:port format.')
    135         
    136     if not args.output:
    137         raise ValueError('Output file path is required.')
    138     
    139     if not args.failed:
    140         print(f'{YELLOW}Failed domains will not be saved.{RESET}')
    141         
    142     asyncio.run(process_domains(args))