mdaxfr

- Mass DNS AXFR
git clone git://git.acid.vegas/mdaxfr.git
Log | Files | Refs | Archive | README | LICENSE

commit a775d61b838959308d509024f8ec4851e100937d
parent ecbd8139f4d290d780ec30e6567e6ac59cabd049
Author: acidvegas <acid.vegas@acid.vegas>
Date: Mon, 11 Mar 2024 00:45:28 -0400

Added a preview gif, stripped pointless functions and provider simple ways to do advanced stuff via STDIN

Diffstat:
A.screens/preview.gif | 0
D.screens/preview_ripe.png | 0
D.screens/preview_root.png | 0
MREADME.md | 47+++++++++++++++++++++--------------------------
Aextras/mdaxfr.py | 193+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mmdaxfr | 101++++++++++++++-----------------------------------------------------------------
Dmdaxfr.py | 157-------------------------------------------------------------------------------

7 files changed, 231 insertions(+), 267 deletions(-)

diff --git a/.screens/preview.gif b/.screens/preview.gif
Binary files differ.
diff --git a/.screens/preview_ripe.png b/.screens/preview_ripe.png
Binary files differ.
diff --git a/.screens/preview_root.png b/.screens/preview_root.png
Binary files differ.
diff --git a/README.md b/README.md
@@ -1,45 +1,40 @@
 # Mass DNS AXFR (Zone Transfer)
 
+![](./.screens/preview.gif)
+
 ## Information
 MDAXFR allows you to perform a DNS [Zone Transfer](https://en.wikipedia.org/wiki/DNS_zone_transfer) against a target domain by resolving all of the domains nameservers to their respective A/AAAA records and making an AXFR attempt against each of the IP addresses.
 
-You can also use this tool against the [Root Nameservers](https://en.wikipedia.org/wiki/Root_name_server) and [Top-level Domains](https://en.wikipedia.org/wiki/Top-level_domain) *(TLD)*, including those in the [Public Suffix List](https://en.wikipedia.org/wiki/Public_Suffix_List) *(PSL)* aswell.
-
-![](.screens/preview_ripe.png)
-![](.screens/preview_root.png)
-
 ## Expectations & Legalities
 It is expected to set *realistic* expectations when using this tool. In contemporary network configurations, AXFR requests are typically restricted, reflecting best practices in DNS security. While many nameservers now disallow AXFR requests, there may still be occasional instances where configurations permit them. Always exercise due diligence and ensure ethical use.
 
-## Usage
-### POSIX Version
-
+## Usage:
+- AXFR a single domain
 ```shell
-./mdaxfr <option>
+./mdaxfr ripe.net
 ```
 
-###### Options
-| Argument      | Description                                                                                 |
-| ------------- | ------------------------------------------------------------------------------------------- |
-| `-tld`,       | Perform AXFR on all TLDs                                                                    |
-| `-psl`,       | Perform AXFR on all PSL TLDs                                                                |
-| `<axfr_file>` | Perform AXFR on all domains found in `<axfr_file>` *(must be an AXFR output file from dig)* |
-| `<domain>`    | Perform AXFR on `<domain>`                                                                  |
+- AXFR a list of domains
+```shell
+cat domain_list.txt | ./mdaxfr
+```
 
-### Python Version
+- AXFR all domains in an AXFR output file
 ```shell
-python mdaxfr.py <option>
+domain="ripe.net" cat axfr-ripe.log | grep -aE "\s+IN\s+NS\s+" | grep -avE "^${domain}\.\s+" | awk '{print $1}' | sort -u | sed 's/\.$//' | ./mdaxfr
 ```
 
-###### Requirements
-- [dnspython](https://pypi.org/project/dnspython/) *(`pip install dnspython`)*
+###### You can also use this tool against the [Root Nameservers](https://en.wikipedia.org/wiki/Root_name_server) and [Top-level Domains](https://en.wikipedia.org/wiki/Top-level_domain) *(TLD)*, including those in the [Public Suffix List](https://en.wikipedia.org/wiki/Public_Suffix_List) *(PSL)* aswell.
 
-###### Options
-| Argument              | Description                                          |
-| --------------------- | ---------------------------------------------------- |
-| `-c`, `--concurrency` | Maximum concurrent tasks.                            |
-| `-o`, `--output`      | Specify the output directory *(default is axfrout)*. |
-| `-t`, `--timeout`     | DNS timeout *(default: 30)*                          |
+- AXFR on all TLDs
+```shell
+curl -s 'https://data.iana.org/TLD/tlds-alpha-by-domain.txt' | tail -n +2 | tr '[:upper:]' '[:lower:] | ./mdaxfr
+```
+
+- AXFR on all PSL TLDs
+```shell
+curl -s https://publicsuffix.org/list/public_suffix_list.dat | grep -vE '^(//|.*[*!])' | grep '\.' | awk '{print $1}' | ./mdaxfr
+```
 
 ## Statistics, laughs, & further thinking...
 I only wrote this to shit on **[this bozo](https://github.com/flotwig/TLDR-2/)** who took a dead project & brought it back to life by making it even worse. Rather than making a pull request to give this bloke more credit in his "tenure" as a developer, I decided to just rewrite it all from scratch so people can fork off of *clean* code instead.
diff --git a/extras/mdaxfr.py b/extras/mdaxfr.py
@@ -0,0 +1,192 @@
+#!/usr/bin/env python
+# Mass DNS AXFR - developed by acidvegas in python (https://git.acid.vegas/mdaxfr)
+
+import logging
+import os
+import re
+import urllib.request
+
+try:
+	import dns.rdatatype
+	import dns.query
+	import dns.zone
+	import dns.resolver
+except ImportError:
+	raise SystemExit('missing required \'dnspython\' module (pip install dnspython)')
+
+
+# Colours
+BLUE   = '\033[1;34m'
+CYAN   = '\033[1;36m'
+GREEN  = '\033[1;32m'
+GREY   = '\033[1;90m'
+PINK   = '\033[1;95m'
+PURPLE = '\033[0;35m'
+RED    = '\033[1;31m'
+YELLOW = '\033[1;33m'
+RESET  = '\033[0m'
+
+
+def attempt_axfr(domain: str, nameserver: str, nameserver_ip: str):
+	'''
+	Request a zone transfer from a nameserver on a domain.
+
+	:param domain: The domain to perform the zone transfer on.
+	:param nameserver: The nameserver to perform the zone transfer on.
+	:param nameserver_ip: The IP address of the nameserver.
+	'''
+
+	print(f'                {YELLOW}Attempting AXFR for {CYAN}{domain}{RESET} on {PURPLE}{nameserver} {GREY}({nameserver_ip}){RESET}')
+
+	zone = dns.zone.from_xfr(dns.query.xfr(nameserver_ip, domain))
+
+	record_count = sum(len(node.rdatasets) for node in zone.nodes.values())
+
+	print(f'                {GREEN}AXFR successful for {CYAN}{domain}{RESET} on {PURPLE}{nameserver} {GREY}({nameserver_ip}){RESET} - {record_count:,} records')
+
+	with open(os.path.join('axfrout', f'{domain}_{nameserver}_{nameserver_ip}.log'), 'w') as file:
+		file.write(zone.to_text())
+
+
+def get_nameservers(domain: str) -> list:
+	'''
+	Generate a list of the root nameservers.
+
+	:param target: The target domain to get the nameservers for.
+	'''
+
+	ns_records  = dns.resolver.resolve(domain, 'NS', lifetime=30)
+	nameservers = [str(rr.target)[:-1] for rr in ns_records]
+
+	return nameservers
+
+
+def get_root_tlds(output_dir: str) -> list:
+	'''
+	Get the root TLDs from a root nameservers.
+
+	:param output_dir: The output directory to use.
+	'''
+	rndroot = [root for root in os.listdir(output_dir) if root.endswith('.root-servers.net.txt')]
+	if rndroot:
+		rndroot_file = rndroot[0]  # Take the first file from the list
+		tlds = sorted(set([item.split()[0][:-1] for item in open(os.path.join(root_dir, rndroot_file)).read().split('\n') if item and 'IN' in item and 'NS' in item]))
+	else:
+		logging.warning('Failed to find root nameserver list...fallback to using IANA list')
+		tlds = urllib.request.urlopen('https://data.iana.org/TLD/tlds-alpha-by-domain.txt').read().decode('utf-8').lower().split('\n')[1:]
+	return tlds
+
+
+def get_psl_tlds() -> list:
+	'''Download the Public Suffix List and return its contents.'''
+	data = urllib.request.urlopen('https://publicsuffix.org/list/public_suffix_list.dat').read().decode()
+	domains = []
+	for line in data.split('\n'):
+		if line.startswith('//') or not line:
+			continue
+		if '*' in line or '!' in line:
+			continue
+		if '.' not in line:
+			continue
+		domains.append(line)
+	return domains
+
+
+def resolve_nameserver(nameserver: str) -> list:
+	'''
+	Resolve a nameserver to its IP address.
+
+	:param nameserver: The nameserver to resolve.
+	'''
+
+	data = []
+
+	for version in ('A', 'AAAA'):
+		data.extend([ip.address for ip in dns.resolver.resolve(nameserver, version, lifetime=30)])
+
+	return data
+
+
+def process_domain(domain: str):
+	domain = re.sub(r'^https?://|^(www\.)|(/.*$)', '', domain)
+
+	print(f'{PINK}Looking up nameservers for {CYAN}{domain}{RESET}')
+
+	try:
+		nameservers = get_nameservers(domain)
+	except Exception as ex:
+		print(f'    {RED}Error resolving nameservers for {CYAN}{domain} {GREY}({ex}){RESET}')
+		return
+	
+	if not nameservers:
+		print(f'    {GREY}No nameservers found for {CYAN}{domain}{RESET}')
+		return
+	
+	print(f'    {BLUE}Found {len(nameservers):,} nameservers for {CYAN}{domain}{RESET}')
+
+	for nameserver in nameservers:
+		print(f'        {PINK}Looking up IP addresses for {PURPLE}{nameserver}{RESET}')
+
+		try:
+			nameserver_ips = resolve_nameserver(nameserver)
+		except Exception as ex:
+			print(f'            {RED}Error resolving IP addresses for {PURPLE}{nameserver} {GREY}({ex}){RESET}')
+			continue
+
+		if not nameserver_ips:
+			print(f'            {GREY}No IP addresses found for {PURPLE}{nameserver}{RESET}')
+			continue
+
+		print(f'            {BLUE}Found {len(nameserver_ips):,} IP addresses for {PURPLE}{nameserver}{RESET}')
+
+		for nameserver_ip in nameserver_ips:
+			attempt_axfr(domain, nameserver, nameserver_ip)
+
+
+
+if __name__ == '__main__':
+	import argparse
+	import concurrent.futures
+
+	parser = argparse.ArgumentParser(description='Mass DNS AXFR')
+	parser.add_argument('-d', '--domain', type=str,help='domain to perform AXFR on')
+	parser.add_argument('-i', '--input', type=str, help='input directory')
+	parser.add_argument('-t', '--tld', type=str, help='TLD to perform AXFR on')
+	parser.add_argument('-p', '--psl', action='store_true', help='use the Public Suffix List')
+	parser.add_argument('-c', '--concurrency', type=int, default=30, help='maximum concurrent tasks')
+	args = parser.parse_args()
+
+	logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
+
+	root_dir = os.path.join(args.output, 'root')
+	os.makedirs(root_dir, exist_ok=True)
+	os.makedirs(args.output, exist_ok=True)
+	dns.resolver._DEFAULT_TIMEOUT = args.timeout
+
+	logging.info('Fetching root nameservers...')
+	with concurrent.futures.ThreadPoolExecutor(max_workers=args.concurrency) as executor:
+		futures = [executor.submit(attempt_axfr, '', root, os.path.join(args.output, f'root/{root}.txt')) for root in get_nameservers('.')]
+		for future in concurrent.futures.as_completed(futures):
+			try:
+				future.result()
+			except Exception as e:
+				logging.error(f'Error in TLD task: {e}')
+
+	logging.info('Fetching root TLDs...')
+	with concurrent.futures.ThreadPoolExecutor(max_workers=args.concurrency) as executor:
+		futures = [executor.submit(attempt_axfr, tld, ns, os.path.join(args.output, tld + '.txt')) for tld in get_root_tlds(root_dir) for ns in get_nameservers(tld) if ns]
+		for future in concurrent.futures.as_completed(futures):
+			try:
+				future.result()
+			except Exception as e:
+				logging.error(f'Error in TLD task: {e}')
+
+	logging.info('Fetching PSL TLDs...')
+	os.makedirs(os.path.join(args.output, 'psl'), exist_ok=True)
+	with concurrent.futures.ThreadPoolExecutor(max_workers=args.concurrency) as executor:
+		futures = [executor.submit(attempt_axfr, tld, ns, os.path.join(args.output, f'psl/{tld}.txt')) for tld in get_psl_tlds() for ns in get_nameservers(tld) if ns]
+		for future in concurrent.futures.as_completed(futures):
+			try:
+				future.result()
+			except Exception as e:
+				logging.error(f'Error in TLD task: {e}')
+\ No newline at end of file
diff --git a/mdaxfr b/mdaxfr
@@ -1,6 +1,20 @@
 #!/bin/sh
 # Mass DNS AXFR (POSIX version) - developed by acidvegas (https://git.acid.vegas/mdaxfr)
 
+# Usage:
+#     AXFR on a single domain:
+#         ./mdaxfr <domain>
+#     AXFR on a list of domains:
+#         cat domain_list.txt | ./mdaxfr
+#     AXFR on all domains in an AXFR output file:
+#         domain="ripe.net" cat axfr-ripe.log | grep -aE "\s+IN\s+NS\s+" | grep -avE "^${domain}\.\s+" | awk '{print $1}' | sort -u | sed 's/\.$//' | ./mdaxfr
+#     AXFR on all TLDs:
+#         curl -s 'https://data.iana.org/TLD/tlds-alpha-by-domain.txt' | tail -n +2 | tr '[:upper:]' '[:lower:]' | ./mdaxfr
+#     AXFR on all PSL TLDs:
+#         curl -s https://publicsuffix.org/list/public_suffix_list.dat | grep -vE '^(//|.*[*!])' | grep '\.' | awk '{print $1}' | ./mdaxfr
+#     AXFR one-liner to rule them all:
+#         curl -s https://www.internic.net/domain/root.zone | awk '$4=="A" || $4=="AAAA" {print substr($1, 3) " " $5}' | sed 's/\.$//' | xargs -n2 sh -c 'dig AXFR "$0" "@$1"'
+
 # Colors
 BLUE="\033[1;34m"
 CYAN="\033[1;36m"
@@ -16,7 +30,6 @@ RESET="\033[0m"
 output_dir="axfrout"
 mkdir -p $output_dir
 
-
 axfr() {
     domain=$1
     ns=$2
@@ -36,7 +49,6 @@ axfr() {
     fi
 }
 
-
 process_domain() {
     domain=$1
 
@@ -65,92 +77,14 @@ process_domain() {
             axfr "$domain" "$ns" "$ip"
         done
 
-        
-    done
-}
-
-
-read_axfr_out() {
-    axfr_output_file=$1
-
-    root=$(grep -m1 '^; <<>> DiG' $axfr_output_file | awk '{print $NF}')
-    domains=$(grep -aE "\s+IN\s+NS\s+" "$axfr_output_file" | grep -avE "^${root}\.\s+" | awk '{print $1}' | sort -u | sed 's/\.$//')
-
-    [ -z "$domains" ] && echo "${GREY}No domains found for ${root}${RESET}" && return
- 
-    total_domains=$(echo "$domains" | wc -l)
-    echo "${BLUE}Found ${total_domains} domains for ${CYAN}${root}${BLUE} zone"
-
-    for domain in $domains; do
-        process_domain $domain
     done
 }
 
-
-psl_crawl() {
-    psl=$(curl -s https://publicsuffix.org/list/public_suffix_list.dat | grep -vE '^(//|.*[*!])' | grep '\.' | awk '{print $1}')
-
-    [ -z "$psl" ] && echo "${RED}No PSL TLDs found${RESET}" && exit 1
-
-    total_psl=$(echo "$psl" | wc -l)
-    echo "${BLUE}Found ${total_psl} PSL TLDs${RESET}"
-
-    # Process the PSL TLDs
-    for tld in $psl; do
-        process_domain $tld
-    done
-}
-
-
-tld_crawl() {
-    process_domain "."
-
-    rndroot=$(find $output_dir/*.root-servers.net.txt -type f | shuf -n 1)
-
-    if [ -z $rndroot ]; then
-        echo "${GREY}No root nameserver found, using IANA TLD list${RESET}"
-        tlds=$(curl -s 'https://data.iana.org/TLD/tlds-alpha-by-domain.txt' | tail -n +2 | tr '[:upper:]' '[:lower:]')
-    else 
-        tlds=$(cat $rndroot | grep -aE '\s+IN\s+NS\s+' | grep -avE "^\.\s+" | awk '{print $1}' | sed 's/\.$//' | sort -u)
-    fi
-
-    [ -z "$tlds" ] && echo "${RED}No TLDs found${RESET}" && exit 1
-
-    total_tld=$(echo "$tlds" | wc -l)
-    echo "${BLUE}Found ${total_tld} TLDs${RESET}"
-
-    # Process the TLDs
-    for tld in $tlds; do
-        process_domain $tld
-    done
-}
-
-
-
 if [ -t 0 ]; then
-    if [ $# -ne 1 0 ]; then
-        echo "Usage: $0 <option>"
-        echo ""
-        echo "Options:"
-        echo "      -tld : Perform AXFR on all TLDs"
-        echo "      -psl : Perform AXFR on all PSL TLDs"
-        echo "    <file> : Process AXFR output file (must be an AXFR output file from dig)"
-        echo "  <domain> : Perform AXFR on a single domain"
-        echo ""
-        echo "Standard Input:"
-        echo "  cat domain_list.txt | $0"
-        exit 1
-    elif [ $1 = '-tld' ]; then
-        tld_crawl
-    elif [ $1 = '-psl' ]; then
-        psl_crawl
-    elif [ -f $1 ]; then
-        read_axfr_out $1
-    else
-        process_domain $1
-    fi
+    [ $# -ne 1 ] && echo "Usage: $0 <domain> or cat domain_list.txt | $0" && exit 1
+    process_domain $1
 else
     while IFS= read -r line; do
         process_domain $line
     done
-fi
-\ No newline at end of file
+fi
diff --git a/mdaxfr.py b/mdaxfr.py
@@ -1,156 +0,0 @@
-#!/usr/bin/env python
-# Mass DNS AXFR - developed by acidvegas in python (https://git.acid.vegas/mdaxfr)
-
-import logging
-import os
-import urllib.request
-
-try:
-	import dns.rdatatype
-	import dns.query
-	import dns.zone
-	import dns.resolver
-except ImportError:
-	raise SystemExit('missing required \'dnspython\' module (pip install dnspython)')
-
-
-def attempt_axfr(tld: str, nameserver: str, filename: str):
-	'''
-	Perform a DNS zone transfer on a target domain.
-
-	:param target: The target domain to perform the zone transfer on.
-	:param nameserver: The nameserver to perform the zone transfer on.
-	:param filename: The filename to store the zone transfer results in.
-	'''
-	temp_file = filename + '.temp'
-	if not (resolvers := resolve_nameserver(nameserver)):
-		logging.error(f'Failed to resolve nameserver {nameserver}: {ex}')
-	else:
-		for ns in resolvers: # Let's try all the IP addresses for the nameserver
-			try:
-				xfr = dns.query.xfr(ns, tld, lifetime=300)
-				if next(xfr, None) is not None:
-					if not tld:
-						print(f'\033[32mSUCCESS\033[0m AXFR for \033[36m.\033[0m on \033[33m{nameserver}\033[0m \033[90m({ns})\033[0m')
-					else:
-						print(f'\033[32mSUCCESS\033[0m AXFR for \033[36m{tld}\033[0m on \033[33m{nameserver}\033[0m \033[90m({ns})\033[0m')
-					with open(temp_file, 'w') as file:
-						for msg in xfr:
-							for rrset in msg.answer:
-								for rdata in rrset:
-									file.write(f'{rrset.name}.{tld} {rrset.ttl} {rdata}\n')
-					os.rename(temp_file, filename)
-					break
-			except Exception as ex:
-				#logging.error(f'Failed to perform zone transfer from {nameserver} ({ns}) for {tld}: {ex}')
-				print(f'\033[31mFAIL\033[0m AXFR for \033[36m{tld}\033[0m on \033[33m{nameserver}\033[0m \033[90m({ns})\033[0m has failed! \033[90m({ex})\033[0m')
-				if os.path.exists(temp_file):
-					os.remove(temp_file)
-
-
-def get_nameservers(target: str) -> list:
-	'''
-	Generate a list of the root nameservers.
-
-	:param target: The target domain to get the nameservers for.
-	'''
-	try:
-		ns_records = dns.resolver.resolve(target, 'NS', lifetime=60)
-		nameservers = [str(rr.target)[:-1] for rr in ns_records]
-		return nameservers
-	except Exception as ex:
-		print(f'\033[31mFAIL\033[0m Error resolving nameservers for \033[36m{target}\033[0m \033[90m({ex})\033[0m')
-	return []
-
-
-def get_root_tlds(output_dir: str) -> list:
-	'''
-	Get the root TLDs from a root nameservers.
-
-	:param output_dir: The output directory to use.
-	'''
-	rndroot = [root for root in os.listdir(output_dir) if root.endswith('.root-servers.net.txt')]
-	if rndroot:
-		rndroot_file = rndroot[0]  # Take the first file from the list
-		tlds = sorted(set([item.split()[0][:-1] for item in open(os.path.join(root_dir, rndroot_file)).read().split('\n') if item and 'IN' in item and 'NS' in item]))
-	else:
-		logging.warning('Failed to find root nameserver list...fallback to using IANA list')
-		tlds = urllib.request.urlopen('https://data.iana.org/TLD/tlds-alpha-by-domain.txt').read().decode('utf-8').lower().split('\n')[1:]
-	return tlds
-
-
-def get_psl_tlds() -> list:
-	'''Download the Public Suffix List and return its contents.'''
-	data = urllib.request.urlopen('https://publicsuffix.org/list/public_suffix_list.dat').read().decode()
-	domains = []
-	for line in data.split('\n'):
-		if line.startswith('//') or not line:
-			continue
-		if '*' in line or '!' in line:
-			continue
-		if '.' not in line:
-			continue
-		domains.append(line)
-	return domains
-
-
-def resolve_nameserver(nameserver: str) -> list:
-	'''
-	Resolve a nameserver to its IP address.
-
-	:param nameserver: The nameserver to resolve.
-	'''
-	data = []
-	for version in ('A', 'AAAA'):
-		try:
-			data += [ip.address for ip in dns.resolver.resolve(nameserver, version, lifetime=60)]
-		except:
-			pass
-	return data
-
-
-
-if __name__ == '__main__':
-	import argparse
-	import concurrent.futures
-
-	parser = argparse.ArgumentParser(description='Mass DNS AXFR')
-	parser.add_argument('-c', '--concurrency', type=int, default=30, help='maximum concurrent tasks')
-	parser.add_argument('-o', '--output', default='axfrout', help='output directory')
-	parser.add_argument('-t', '--timeout', type=int, default=15, help='DNS timeout (default: 15)')
-	args = parser.parse_args()
-
-	logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
-
-	root_dir = os.path.join(args.output, 'root')
-	os.makedirs(root_dir, exist_ok=True)
-	os.makedirs(args.output, exist_ok=True)
-	dns.resolver._DEFAULT_TIMEOUT = args.timeout
-
-	logging.info('Fetching root nameservers...')
-	with concurrent.futures.ThreadPoolExecutor(max_workers=args.concurrency) as executor:
-		futures = [executor.submit(attempt_axfr, '', root, os.path.join(args.output, f'root/{root}.txt')) for root in get_nameservers('.')]
-		for future in concurrent.futures.as_completed(futures):
-			try:
-				future.result()
-			except Exception as e:
-				logging.error(f'Error in TLD task: {e}')
-
-	logging.info('Fetching root TLDs...')
-	with concurrent.futures.ThreadPoolExecutor(max_workers=args.concurrency) as executor:
-		futures = [executor.submit(attempt_axfr, tld, ns, os.path.join(args.output, tld + '.txt')) for tld in get_root_tlds(root_dir) for ns in get_nameservers(tld) if ns]
-		for future in concurrent.futures.as_completed(futures):
-			try:
-				future.result()
-			except Exception as e:
-				logging.error(f'Error in TLD task: {e}')
-
-	logging.info('Fetching PSL TLDs...')
-	os.makedirs(os.path.join(args.output, 'psl'), exist_ok=True)
-	with concurrent.futures.ThreadPoolExecutor(max_workers=args.concurrency) as executor:
-		futures = [executor.submit(attempt_axfr, tld, ns, os.path.join(args.output, f'psl/{tld}.txt')) for tld in get_psl_tlds() for ns in get_nameservers(tld) if ns]
-		for future in concurrent.futures.as_completed(futures):
-			try:
-				future.result()
-			except Exception as e:
-				logging.error(f'Error in TLD task: {e}')
-\ No newline at end of file