140 lines
6.2 KiB
Python
140 lines
6.2 KiB
Python
"""
|
||
This plugin provides DNSDumpster.com integration for domain reconnaissance and DNS mapping.
|
||
"""
|
||
import logging
|
||
import os
|
||
import aiohttp
|
||
import simplematrixbotlib as botlib
|
||
from plugins.common import html_escape, collapsible_summary
|
||
|
||
DNSDUMPSTER_API_KEY = os.getenv("DNSDUMPSTER_KEY", "")
|
||
DNSDUMPSTER_API_BASE = "https://api.dnsdumpster.com"
|
||
|
||
async def handle_command(room, message, bot, prefix, config):
|
||
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")
|
||
|
||
if not DNSDUMPSTER_API_KEY:
|
||
await bot.api.send_text_message(
|
||
room.room_id,
|
||
"DNSDumpster API key not configured. Set DNSDUMPSTER_KEY in .env."
|
||
)
|
||
return
|
||
|
||
args = match.args()
|
||
if len(args) < 1:
|
||
await show_usage(room, bot)
|
||
return
|
||
|
||
if args[0].lower() == "test":
|
||
await test_dnsdumpster_connection(room, bot)
|
||
else:
|
||
domain = args[0].lower().strip()
|
||
await dnsdumpster_domain_lookup(room, bot, domain)
|
||
|
||
async def show_usage(room, bot):
|
||
usage = """<strong>🔍 DNSDumpster Commands:</strong>
|
||
<strong>!dnsdumpster <domain_name></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>
|
||
"""
|
||
await bot.api.send_markdown_message(room.room_id, usage)
|
||
|
||
async def test_dnsdumpster_connection(room, bot):
|
||
test_domain = "google.com"
|
||
try:
|
||
url = f"{DNSDUMPSTER_API_BASE}/domain/{test_domain}"
|
||
headers = {"X-API-Key": DNSDUMPSTER_API_KEY}
|
||
async with aiohttp.ClientSession() as session:
|
||
async with session.get(url, headers=headers, timeout=15) as response:
|
||
status = response.status
|
||
debug_info = f"<strong>🔧 DNSDumpster API Test</strong><br>Status Code: {status}<br>Test Domain: {test_domain}<br>"
|
||
|
||
if status == 200:
|
||
data = await response.json()
|
||
debug_info += "<strong>✅ SUCCESS</strong><br>"
|
||
if data.get('a'):
|
||
debug_info += f"A Records Found: {len(data['a'])}<br>"
|
||
elif status == 401:
|
||
debug_info += "<strong>❌ Unauthorized - Invalid API key</strong><br>"
|
||
elif status == 429:
|
||
debug_info += "<strong>⚠️ Rate Limit Exceeded</strong><br>"
|
||
else:
|
||
debug_info += f"<strong>❌ Error:</strong> {status}<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):
|
||
safe_domain = html_escape(domain)
|
||
try:
|
||
await bot.api.send_text_message(room.room_id, f"🔍 Processing DNS reconnaissance for {safe_domain}...")
|
||
url = f"{DNSDUMPSTER_API_BASE}/domain/{domain}"
|
||
headers = {"X-API-Key": DNSDUMPSTER_API_KEY}
|
||
async with aiohttp.ClientSession() as session:
|
||
async with session.get(url, headers=headers, timeout=30) as response:
|
||
if response.status != 200:
|
||
await bot.api.send_text_message(room.room_id, f"API error: {response.status}")
|
||
return
|
||
data = await response.json()
|
||
|
||
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 asyncio.TimeoutError:
|
||
await bot.api.send_text_message(room.room_id, "Request timed out.")
|
||
except Exception as e:
|
||
await bot.api.send_text_message(room.room_id, f"Error: {e}")
|
||
|
||
async def format_dnsdumpster_report(domain, data):
|
||
safe_domain = html_escape(domain)
|
||
output = f"<strong>🔍 DNSDumpster Report: {safe_domain}</strong><br><br>"
|
||
if data.get('total_a_recs'):
|
||
output += f"<strong>📊 Summary</strong><br>Total A Records: {data['total_a_recs']}<br>"
|
||
|
||
for record_type, label in [('a','A Records'),('ns','NS Records'),('mx','MX Records'),('cname','CNAME'),('txt','TXT')]:
|
||
if data.get(record_type) and data[record_type]:
|
||
output += f"<br><strong>{label} ({len(data[record_type])} found)</strong><br>"
|
||
for rec in data[record_type]:
|
||
if record_type == 'txt':
|
||
txt = html_escape(str(rec))
|
||
if len(txt) > 200:
|
||
txt = txt[:200] + "..."
|
||
output += f" • {txt}<br>"
|
||
elif record_type == 'a':
|
||
host = html_escape(rec.get('host','N/A'))
|
||
ips = rec.get('ips',[])
|
||
output += f" • <strong>{host}</strong><br>"
|
||
for ip_info in ips:
|
||
ip = html_escape(ip_info.get('ip','N/A'))
|
||
country = html_escape(ip_info.get('country','Unknown'))
|
||
output += f" └─ {ip} ({country})<br>"
|
||
else:
|
||
host = html_escape(rec.get('host','N/A'))
|
||
ips = rec.get('ips',[])
|
||
output += f" • <strong>{host}</strong><br>"
|
||
for ip_info in ips:
|
||
ip = html_escape(ip_info.get('ip','N/A'))
|
||
country = html_escape(ip_info.get('country','Unknown'))
|
||
output += f" └─ {ip} ({country})<br>"
|
||
|
||
output += "<br><em>💡 Rate Limit: 1 request per 2 seconds</em>"
|
||
return collapsible_summary(f"🔍 DNSDumpster Report: {safe_domain} (Click to expand)", output)
|
||
|
||
__version__ = "1.0.1"
|
||
__author__ = "Funguy Bot"
|
||
__description__ = "DNSDumpster domain reconnaissance"
|
||
__help__ = """
|
||
<details>
|
||
<summary><strong>!dnsdumpster</strong> – Comprehensive DNS mapping via DNSDumpster</summary>
|
||
<ul>
|
||
<li><code>!dnsdumpster <domain></code> – Full recon</li>
|
||
<li><code>!dnsdumpster test</code> – Test API connection</li>
|
||
</ul>
|
||
<p>Requires <strong>DNSDUMPSTER_KEY</strong> env var.</p>
|
||
</details>
|
||
"""
|