\nExample: !geo 8.8.8.8\nExample: !geo example.com"
+ )
+ return
+
+ query = args[0].strip()
+ logging.info(f"Received !geo command for: {query}")
+
+ try:
+ # Determine if input is IP or domain
+ ip = query
+ if is_domain(query):
+ # Resolve domain to IP first
+ await bot.api.send_text_message(
+ room.room_id,
+ f"🔍 Resolving domain {query} to IP address..."
+ )
+ ip = await resolve_domain(query)
+ if not ip:
+ await bot.api.send_text_message(
+ room.room_id,
+ f"Failed to resolve domain {query} to IP address."
+ )
+ return
+ await bot.api.send_text_message(
+ room.room_id,
+ f"Domain {query} resolved to IP {ip}"
+ )
+ elif not await is_valid_ip(query):
+ await bot.api.send_text_message(
+ room.room_id,
+ f"Invalid IP address or domain format: {query}"
+ )
+ return
+
+ # Notify user that we're starting the lookup
+ await bot.api.send_text_message(
+ room.room_id,
+ f"🔍 Looking up geolocation for {ip}..."
+ )
+
+ # Query geolocation data with fallback
+ geo_data = await query_geolocation(ip)
+
+ # Format and send results
+ result_message = await format_geolocation_results(ip, geo_data)
+ await bot.api.send_markdown_message(room.room_id, result_message)
+ logging.info(f"Successfully sent geolocation results for {ip}")
+
+ except Exception as e:
+ await bot.api.send_text_message(
+ room.room_id,
+ f"An error occurred during geolocation lookup for {query}. Please try again later."
+ )
+ logging.error(f"Error in geo plugin for {query}: {e}", exc_info=True)
\ No newline at end of file
diff --git a/plugins/help.py b/plugins/help.py
index db5cccd..7965ad2 100644
--- a/plugins/help.py
+++ b/plugins/help.py
@@ -94,6 +94,64 @@ async def handle_command(room, message, bot, prefix, config):
Output includes: Domain/IP information, registrar, WHOIS server, creation/expiration dates, name servers, and contact details.
+🔍 !subdomains
+Enumerate subdomains using SSL certificate transparency logs. Discovers associated subdomains by querying the CertSpotter API for SSL certificates issued for a domain.
+Usage:
+
+!subdomains - Enumerate subdomains for any domain
+!subdomains example.com - Example subdomain enumeration
+
+Features:
+
+- Discovers subdomains through SSL certificate transparency logs
+- Uses the free CertSpotter API for enumeration
+- No rate limiting or API key required
+- Identifies subdomains through certificate SAN (Subject Alternative Name) enumeration
+
+Examples:
+
+!subdomains google.com
+!subdomains github.com
+!subdomains example.com
+
+Essential for reconnaissance and subdomain enumeration in penetration testing
+
+
+📍 !geo [ip/domain]
+Perform IP geolocation lookups with detailed geographic information. Resolves domains to IP addresses and provides location data including country, region, city, coordinates, and ISP information.
+Usage:
+
+!geo - Geolocate an IP address
+!geo - Geolocate a domain (automatically resolves to IP)
+
+Features:
+
+- Uses ip-api.com as primary geolocation service with ipapi.co fallback
+- Automatic domain to IP resolution
+- Comprehensive geographic information
+- No API key required for basic usage
+- Supports both IPv4 and IPv6 addresses
+
+Examples:
+
+!geo 8.8.8.8
+!geo example.com
+!geo google.com
+
+Information provided:
+
+- Country and country code
+- Region/State
+- City
+- Postal code
+- Latitude/Longitude coordinates
+- Timezone
+- ISP/Organization
+- Autonomous System Number (ASN)
+
+Essential for network reconnaissance and IP investigation
+
+
🔍 !shodan [command] [query]
Shodan.io integration for security reconnaissance and threat intelligence.
Commands:
diff --git a/plugins/subdomains.py b/plugins/subdomains.py
new file mode 100644
index 0000000..36c625e
--- /dev/null
+++ b/plugins/subdomains.py
@@ -0,0 +1,127 @@
+"""
+This plugin provides subdomain enumeration functionality using the CertSpotter API.
+It queries SSL certificates issued for domains to discover associated subdomains.
+"""
+
+import logging
+import aiohttp
+import simplematrixbotlib as botlib
+import json
+import asyncio
+
+async def query_certspotter(domain):
+ """
+ Query CertSpotter API for subdomain enumeration.
+
+ Args:
+ domain (str): The domain to enumerate subdomains for
+
+ Returns:
+ list: List of discovered subdomains
+ """
+ url = f"https://api.certspotter.com/v1/issuances?domain={domain}&include_subdomains=true&expand=dns_names"
+ subdomains = set()
+
+ try:
+ async with aiohttp.ClientSession() as session:
+ async with session.get(url) as response:
+ if response.status == 200:
+ data = await response.json()
+ for cert in data:
+ if 'dns_names' in cert:
+ for dns_name in cert['dns_names']:
+ if domain in dns_name:
+ subdomains.add(dns_name)
+ else:
+ logging.error(f"CertSpotter API returned status {response.status}")
+ return []
+ except Exception as e:
+ logging.error(f"Error querying CertSpotter API: {e}")
+ return []
+
+ return list(subdomains)
+
+async def format_subdomain_results(domain, subdomains):
+ """
+ Format subdomain results into a readable message.
+
+ Args:
+ domain (str): The queried domain
+ subdomains (list): List of discovered subdomains
+
+ Returns:
+ str: Formatted message
+ """
+ if not subdomains:
+ return f"🔍 No subdomains found for {domain} using CertSpotter API."
+
+ # Sort and remove duplicates
+ subdomains = sorted(list(set(subdomains)))
+ count = len(subdomains)
+
+ # Create the subdomain list with proper HTML list formatting
+ subdomain_list = ""
+ for sub in subdomains:
+ subdomain_list += f"- {sub}
"
+ subdomain_list += "
"
+
+ # Create collapsible content
+ content = f"🔍 Subdomain Enumeration Results for {domain}
"
+ content += f"Total subdomains found: {count}
"
+ content += f"Discovered subdomains:
{subdomain_list}"
+
+ # Wrap in details tag for Matrix compatibility
+ message = f"🔍 Subdomain Enumeration: {domain} ({count} found)
{content} "
+
+ return message
+
+async def handle_command(room, message, bot, prefix, config):
+ """
+ Function to handle the !subdomains command.
+
+ 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("subdomains"):
+ args = match.args()
+
+ if len(args) < 1:
+ await bot.api.send_text_message(
+ room.room_id,
+ "Usage: !subdomains \nExample: !subdomains example.com"
+ )
+ return
+
+ domain = args[0].strip()
+ logging.info(f"Received !subdomains command for: {domain}")
+
+ try:
+ # Notify user that we're starting the lookup
+ await bot.api.send_text_message(
+ room.room_id,
+ f"🔍 Enumerating subdomains for {domain} using CertSpotter API..."
+ )
+
+ # Query CertSpotter API
+ subdomains = await query_certspotter(domain)
+
+ # Format and send results
+ result_message = await format_subdomain_results(domain, subdomains)
+ await bot.api.send_markdown_message(room.room_id, result_message)
+ logging.info(f"Successfully sent subdomain enumeration results for {domain}")
+
+ except Exception as e:
+ await bot.api.send_text_message(
+ room.room_id,
+ f"An error occurred during subdomain enumeration for {domain}. Please try again later."
+ )
+ logging.error(f"Error in subdomains plugin for {domain}: {e}", exc_info=True)
\ No newline at end of file