Files
FunguyBot/plugins/dnsdumpster.py
2025-10-16 14:30:30 -05:00

271 lines
11 KiB
Python

"""
This plugin provides DNSDumpster.com integration for domain reconnaissance and DNS mapping.
"""
import logging
import os
import requests
import simplematrixbotlib as botlib
from dotenv import load_dotenv
# Load environment variables from .env file
plugin_dir = os.path.dirname(os.path.abspath(__file__))
parent_dir = os.path.dirname(plugin_dir)
dotenv_path = os.path.join(parent_dir, '.env')
load_dotenv(dotenv_path)
DNSDUMPSTER_API_KEY = os.getenv("DNSDUMPSTER_KEY", "")
DNSDUMPSTER_API_BASE = "https://api.dnsdumpster.com"
async def handle_command(room, message, bot, prefix, config):
"""
Function to handle DNSDumpster commands.
Args:
room (Room): The Matrix room where the command was invoked.
message (RoomMessage): The message object containing the command.
bot (Bot): The bot object.
prefix (str): The command prefix.
config (dict): Configuration parameters.
Returns:
None
"""
match = botlib.MessageMatch(room, message, bot, prefix)
if match.is_not_from_this_bot() and match.prefix() and match.command("dnsdumpster"):
logging.info("Received !dnsdumpster command")
# Check if API key is configured
if not DNSDUMPSTER_API_KEY:
await bot.api.send_text_message(
room.room_id,
"DNSDumpster API key not configured. Please set DNSDUMPSTER_KEY environment variable."
)
logging.error("DNSDumpster API key not configured")
return
args = match.args()
if len(args) < 1:
await show_usage(room, bot)
return
# Check if it's a test command or domain lookup
if args[0].lower() == "test":
await test_dnsdumpster_connection(room, bot)
else:
# Treat the first argument as the domain
domain = args[0].lower().strip()
await dnsdumpster_domain_lookup(room, bot, domain)
async def show_usage(room, bot):
"""Display DNSDumpster command usage."""
usage = """
<strong>🔍 DNSDumpster Commands:</strong>
<strong>!dnsdumpster &lt;domain_name&gt;</strong> - Get comprehensive DNS reconnaissance for a domain
<strong>!dnsdumpster test</strong> - Test API connection
<strong>Examples:</strong>
• <code>!dnsdumpster google.com</code>
• <code>!dnsdumpster github.com</code>
• <code>!dnsdumpster example.com</code>
<strong>Rate Limit:</strong> 1 request per 2 seconds
"""
await bot.api.send_markdown_message(room.room_id, usage)
async def test_dnsdumpster_connection(room, bot):
"""Test DNSDumpster API connection."""
try:
test_domain = "google.com" # Changed from example.com to google.com
url = f"{DNSDUMPSTER_API_BASE}/domain/{test_domain}"
headers = {
"X-API-Key": DNSDUMPSTER_API_KEY
}
logging.info(f"Testing DNSDumpster API with domain: {test_domain}")
response = requests.get(url, headers=headers, timeout=15)
debug_info = f"<strong>🔧 DNSDumpster API Test</strong><br>"
debug_info += f"<strong>Status Code:</strong> {response.status_code}<br>"
debug_info += f"<strong>Test Domain:</strong> {test_domain}<br>"
debug_info += f"<strong>Headers Used:</strong> X-API-Key<br>"
if response.status_code == 200:
data = response.json()
debug_info += "<strong>✅ SUCCESS - API is working!</strong><br>"
debug_info += f"<strong>Response Keys:</strong> {list(data.keys())}<br>"
# Show some sample data
if data.get('a'):
debug_info += f"<strong>A Records Found:</strong> {len(data['a'])}<br>"
if data.get('ns'):
debug_info += f"<strong>NS Records Found:</strong> {len(data['ns'])}<br>"
if data.get('total_a_recs'):
debug_info += f"<strong>Total A Records:</strong> {data['total_a_recs']}<br>"
elif response.status_code == 400:
debug_info += "<strong>❌ Bad Request - Check domain format</strong><br>"
debug_info += f"<strong>Response:</strong> {response.text[:200]}<br>"
elif response.status_code == 401:
debug_info += "<strong>❌ Unauthorized - Invalid API key</strong><br>"
elif response.status_code == 429:
debug_info += "<strong>⚠️ Rate Limit Exceeded - Wait 2 seconds</strong><br>"
else:
debug_info += f"<strong>❌ Error:</strong> {response.status_code} - {response.text[:200]}<br>"
await bot.api.send_markdown_message(room.room_id, debug_info)
except Exception as e:
await bot.api.send_text_message(room.room_id, f"Test failed: {str(e)}")
async def dnsdumpster_domain_lookup(room, bot, domain):
"""Get comprehensive DNS reconnaissance for a domain."""
try:
url = f"{DNSDUMPSTER_API_BASE}/domain/{domain}"
headers = {
"X-API-Key": DNSDUMPSTER_API_KEY
}
logging.info(f"Fetching DNSDumpster data for domain: {domain}")
# Send initial processing message
await bot.api.send_text_message(room.room_id, f"🔍 Processing DNS reconnaissance for {domain}...")
response = requests.get(url, headers=headers, timeout=30)
if response.status_code == 400:
await bot.api.send_text_message(room.room_id, f"Bad request - check domain format: {domain}")
return
elif response.status_code == 401:
await bot.api.send_text_message(room.room_id, "Invalid DNSDumpster API key")
return
elif response.status_code == 403:
await bot.api.send_text_message(room.room_id, "Access denied - check API key permissions")
return
elif response.status_code == 429:
await bot.api.send_text_message(room.room_id, "Rate limit exceeded - wait 2 seconds between requests")
return
elif response.status_code != 200:
await bot.api.send_text_message(room.room_id, f"DNSDumpster API error: {response.status_code} - {response.text[:100]}")
return
data = response.json()
logging.info(f"DNSDumpster response keys: {list(data.keys())}")
# Format the comprehensive DNS report
output = await format_dnsdumpster_report(domain, data)
await bot.api.send_markdown_message(room.room_id, output)
logging.info(f"Sent DNSDumpster data for {domain}")
except requests.exceptions.Timeout:
await bot.api.send_text_message(room.room_id, "DNSDumpster API request timed out")
logging.error("DNSDumpster API timeout")
except Exception as e:
await bot.api.send_text_message(room.room_id, f"Error fetching DNSDumpster data: {str(e)}")
logging.error(f"Error in dnsdumpster_domain_lookup: {e}")
async def format_dnsdumpster_report(domain, data):
"""Format DNSDumpster JSON response into a readable report."""
output = f"<strong>🔍 DNSDumpster Report: {domain}</strong><br><br>"
# Summary statistics
if data.get('total_a_recs'):
output += f"<strong>📊 Summary</strong><br>"
output += f" • <strong>Total A Records:</strong> {data['total_a_recs']}<br>"
# A Records - Show ALL records
if data.get('a') and data['a']:
output += f"<br><strong>📍 A Records (IPv4) - {len(data['a'])} found</strong><br>"
for record in data['a']: # Show ALL A records
host = record.get('host', 'N/A')
ips = record.get('ips', [])
output += f" • <strong>{host}</strong><br>"
for ip_info in ips: # Show ALL IPs per host
ip = ip_info.get('ip', 'N/A')
country = ip_info.get('country', 'Unknown')
asn_name = ip_info.get('asn_name', 'Unknown')
output += f" └─ {ip} ({country})<br>"
output += f" └─ {asn_name}<br>"
# Show banner information if available
banners = ip_info.get('banners', {})
if banners.get('http') or banners.get('https'):
output += f" └─ <em>Web Services:</em> "
services = []
if banners.get('http'):
services.append("HTTP")
if banners.get('https'):
services.append("HTTPS")
output += f"{', '.join(services)}<br>"
# NS Records - Show ALL records
if data.get('ns') and data['ns']:
output += f"<br><strong>🔗 NS Records (Name Servers) - {len(data['ns'])} found</strong><br>"
for record in data['ns']: # Show ALL NS records
host = record.get('host', 'N/A')
ips = record.get('ips', [])
output += f" • <strong>{host}</strong><br>"
for ip_info in ips: # Show ALL IPs
ip = ip_info.get('ip', 'N/A')
country = ip_info.get('country', 'Unknown')
output += f" └─ {ip} ({country})<br>"
# MX Records - Show ALL records
if data.get('mx') and data['mx']:
output += f"<br><strong>📧 MX Records (Mail Servers) - {len(data['mx'])} found</strong><br>"
for record in data['mx']: # Show ALL MX records
host = record.get('host', 'N/A')
ips = record.get('ips', [])
output += f" • <strong>{host}</strong><br>"
for ip_info in ips: # Show ALL IPs
ip = ip_info.get('ip', 'N/A')
country = ip_info.get('country', 'Unknown')
output += f" └─ {ip} ({country})<br>"
# CNAME Records - Show ALL records
if data.get('cname') and data['cname']:
output += f"<br><strong>🔀 CNAME Records - {len(data['cname'])} found</strong><br>"
for record in data['cname']: # Show ALL CNAME records
host = record.get('host', 'N/A')
target = record.get('target', 'N/A')
output += f"{host}{target}<br>"
# TXT Records - Show ALL records
if data.get('txt') and data['txt']:
output += f"<br><strong>📄 TXT Records - {len(data['txt'])} found</strong><br>"
for txt in data['txt']: # Show ALL TXT records
# Truncate very long TXT records but show more content
if len(txt) > 200:
txt = txt[:200] + "..."
output += f"{txt}<br>"
# Additional record types that might be present - Show ALL records
other_records = ['aaaa', 'srv', 'soa', 'ptr']
for record_type in other_records:
if data.get(record_type) and data[record_type]:
output += f"<br><strong>🔧 {record_type.upper()} Records - {len(data[record_type])} found</strong><br>"
for record in data[record_type]: # Show ALL records
if isinstance(record, dict):
# Format dictionary records nicely
record_str = ", ".join([f"{k}: {v}" for k, v in record.items()])
if len(record_str) > 150:
record_str = record_str[:150] + "..."
output += f"{record_str}<br>"
else:
output += f"{record}<br>"
# Add rate limit reminder
output += "<br><em>💡 Rate Limit: 1 request per 2 seconds</em>"
# Always wrap in collapsible details since we're showing all results
output = f"<details><summary><strong>🔍 DNSDumpster Report: {domain} (Click to expand)</strong></summary>{output}</details>"
return output