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))