147 lines
6.0 KiB
Python
147 lines
6.0 KiB
Python
"""
|
||
DNSDumpster.com integration for domain reconnaissance and DNS mapping.
|
||
Output uses shared code_block for aligned columns.
|
||
"""
|
||
|
||
import logging
|
||
import os
|
||
import aiohttp
|
||
import simplematrixbotlib as botlib
|
||
from plugins.common import html_escape, code_block, 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"):
|
||
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
|
||
"""
|
||
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>"
|
||
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()
|
||
|
||
sections = []
|
||
|
||
# A Records
|
||
if data.get('a'):
|
||
rows = []
|
||
for rec in data['a']:
|
||
host = rec.get('host', 'N/A')
|
||
ips = ', '.join(ip.get('ip', '') for ip in rec.get('ips', []))
|
||
rows.append(("📍", host, ips))
|
||
sections.append({"title": "A Records (IPv4)", "rows": rows})
|
||
|
||
# NS Records
|
||
if data.get('ns'):
|
||
rows = []
|
||
for rec in data['ns']:
|
||
host = rec.get('host', 'N/A')
|
||
ips = ', '.join(ip.get('ip', '') for ip in rec.get('ips', []))
|
||
rows.append(("🖧", host, ips))
|
||
sections.append({"title": "NS Records", "rows": rows})
|
||
|
||
# MX Records
|
||
if data.get('mx'):
|
||
rows = []
|
||
for rec in data['mx']:
|
||
host = rec.get('host', 'N/A')
|
||
ips = ', '.join(ip.get('ip', '') for ip in rec.get('ips', []))
|
||
rows.append(("📧", host, ips))
|
||
sections.append({"title": "MX Records", "rows": rows})
|
||
|
||
# CNAME
|
||
if data.get('cname'):
|
||
rows = []
|
||
for rec in data['cname']:
|
||
host = rec.get('host', 'N/A')
|
||
target = rec.get('target', 'N/A')
|
||
rows.append(("🔀", host, target))
|
||
sections.append({"title": "CNAME Records", "rows": rows})
|
||
|
||
# TXT
|
||
if data.get('txt'):
|
||
rows = []
|
||
for txt in data['txt']:
|
||
rows.append(("📄", "TXT", txt[:150] if len(txt) > 150 else txt))
|
||
sections.append({"title": "TXT Records", "rows": rows})
|
||
|
||
if not sections:
|
||
await bot.api.send_text_message(room.room_id, "No DNS records found.")
|
||
return
|
||
|
||
block = code_block(f"🔍 DNSDumpster Report: {safe_domain}", sections)
|
||
output = collapsible_summary(f"🔍 DNSDumpster Report: {safe_domain}", block)
|
||
await bot.api.send_markdown_message(room.room_id, output)
|
||
|
||
except aiohttp.ClientError as e:
|
||
await bot.api.send_text_message(room.room_id, f"Error: {e}")
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Plugin Metadata
|
||
# ---------------------------------------------------------------------------
|
||
__version__ = "1.0.2"
|
||
__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>
|
||
"""
|