diff --git a/funguy.py b/funguy.py
index 38effcc..6733289 100755
--- a/funguy.py
+++ b/funguy.py
@@ -22,7 +22,7 @@ ALLOWED_PLUGINS = {
'ai', 'config', 'cron', 'date', 'fortune', 'help', 'isup', 'karma',
'loadplugin', 'plugins', 'proxy', 'sd_text', 'stable-diffusion',
'xkcd', 'youtube-preview', 'youtube-search', 'weather', 'urbandictionary',
- 'bitcoin', 'dns', 'shodan'
+ 'bitcoin', 'dns', 'shodan', 'dnsdumpster'
}
class FunguyBot:
diff --git a/plugins/dnsdumpster.py b/plugins/dnsdumpster.py
new file mode 100644
index 0000000..01b7f56
--- /dev/null
+++ b/plugins/dnsdumpster.py
@@ -0,0 +1,270 @@
+"""
+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 = """
+🔍 DNSDumpster Commands:
+
+!dnsdumpster <domain_name> - Get comprehensive DNS reconnaissance for a domain
+!dnsdumpster test - Test API connection
+
+Examples:
+• !dnsdumpster google.com
+• !dnsdumpster github.com
+• !dnsdumpster example.com
+
+Rate Limit: 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"🔧 DNSDumpster API Test
"
+ debug_info += f"Status Code: {response.status_code}
"
+ debug_info += f"Test Domain: {test_domain}
"
+ debug_info += f"Headers Used: X-API-Key
"
+
+ if response.status_code == 200:
+ data = response.json()
+ debug_info += "✅ SUCCESS - API is working!
"
+ debug_info += f"Response Keys: {list(data.keys())}
"
+
+ # Show some sample data
+ if data.get('a'):
+ debug_info += f"A Records Found: {len(data['a'])}
"
+ if data.get('ns'):
+ debug_info += f"NS Records Found: {len(data['ns'])}
"
+ if data.get('total_a_recs'):
+ debug_info += f"Total A Records: {data['total_a_recs']}
"
+
+ elif response.status_code == 400:
+ debug_info += "❌ Bad Request - Check domain format
"
+ debug_info += f"Response: {response.text[:200]}
"
+ elif response.status_code == 401:
+ debug_info += "❌ Unauthorized - Invalid API key
"
+ elif response.status_code == 429:
+ debug_info += "⚠️ Rate Limit Exceeded - Wait 2 seconds
"
+ else:
+ debug_info += f"❌ Error: {response.status_code} - {response.text[:200]}
"
+
+ 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"🔍 DNSDumpster Report: {domain}
"
+
+ # Summary statistics
+ if data.get('total_a_recs'):
+ output += f"📊 Summary
"
+ output += f" • Total A Records: {data['total_a_recs']}
"
+
+ # A Records - Show ALL records
+ if data.get('a') and data['a']:
+ output += f"
📍 A Records (IPv4) - {len(data['a'])} found
"
+ for record in data['a']: # Show ALL A records
+ host = record.get('host', 'N/A')
+ ips = record.get('ips', [])
+
+ output += f" • {host}
"
+ 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})
"
+ output += f" └─ {asn_name}
"
+
+ # Show banner information if available
+ banners = ip_info.get('banners', {})
+ if banners.get('http') or banners.get('https'):
+ output += f" └─ Web Services: "
+ services = []
+ if banners.get('http'):
+ services.append("HTTP")
+ if banners.get('https'):
+ services.append("HTTPS")
+ output += f"{', '.join(services)}
"
+
+ # NS Records - Show ALL records
+ if data.get('ns') and data['ns']:
+ output += f"
🔗 NS Records (Name Servers) - {len(data['ns'])} found
"
+ for record in data['ns']: # Show ALL NS records
+ host = record.get('host', 'N/A')
+ ips = record.get('ips', [])
+
+ output += f" • {host}
"
+ 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})
"
+
+ # MX Records - Show ALL records
+ if data.get('mx') and data['mx']:
+ output += f"
📧 MX Records (Mail Servers) - {len(data['mx'])} found
"
+ for record in data['mx']: # Show ALL MX records
+ host = record.get('host', 'N/A')
+ ips = record.get('ips', [])
+
+ output += f" • {host}
"
+ 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})
"
+
+ # CNAME Records - Show ALL records
+ if data.get('cname') and data['cname']:
+ output += f"
🔀 CNAME Records - {len(data['cname'])} found
"
+ 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}
"
+
+ # TXT Records - Show ALL records
+ if data.get('txt') and data['txt']:
+ output += f"
📄 TXT Records - {len(data['txt'])} found
"
+ 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}
"
+
+ # 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"
🔧 {record_type.upper()} Records - {len(data[record_type])} found
"
+ 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}
"
+ else:
+ output += f" • {record}
"
+
+ # Add rate limit reminder
+ output += "
💡 Rate Limit: 1 request per 2 seconds"
+
+ # Always wrap in collapsible details since we're showing all results
+ output = f"🔍 DNSDumpster Report: {domain} (Click to expand)
{output}
Requires SHODAN_KEY environment variable
+Comprehensive DNS reconnaissance and attack surface mapping using DNSDumpster.com API.
+Commands:
+!dnsdumpster <domain>
- Complete DNS reconnaissance for any domain!dnsdumpster test
- Test API connection and key validityFeatures:
+Examples:
+!dnsdumpster google.com
!dnsdumpster github.com
!dnsdumpster example.com
Requires DNSDUMPSTER_KEY environment variable
+Rate Limit: 1 request per 2 seconds
Generates images using self-hosted Stable Diffusion. Supports options: --steps, --cfg, --h, --w, --neg, --sampler. Uses queuing system to handle multiple requests. See available options using just '!sd'.