feat: add wave 3 local database plugins
GeoIP and ASN lookup via MaxMind GeoLite2 mmdb, Tor exit node check against local bulk exit list, IP reputation via Firehol/ET blocklist feeds, and CVE lookup against local NVD JSON mirror. Includes cron-friendly update script (scripts/update-data.sh) for all data sources and make update-data target. GeoLite2 requires a free MaxMind license key; all other sources are freely downloadable. Plugins: geoip, asn, torcheck, iprep, cve Commands: !geoip, !asn, !tor, !iprep, !cve Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
85
plugins/asn.py
Normal file
85
plugins/asn.py
Normal file
@@ -0,0 +1,85 @@
|
||||
"""Plugin: ASN lookup using MaxMind GeoLite2-ASN mmdb."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import ipaddress
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
from derp.plugin import command
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
_DB_PATHS = [
|
||||
Path("data/GeoLite2-ASN.mmdb"),
|
||||
Path("/usr/share/GeoIP/GeoLite2-ASN.mmdb"),
|
||||
Path.home() / ".local" / "share" / "GeoIP" / "GeoLite2-ASN.mmdb",
|
||||
]
|
||||
|
||||
_reader = None
|
||||
|
||||
|
||||
def _get_reader():
|
||||
"""Lazy-load the mmdb reader."""
|
||||
global _reader
|
||||
if _reader is not None:
|
||||
return _reader
|
||||
try:
|
||||
import maxminddb
|
||||
except ImportError:
|
||||
log.error("maxminddb package not installed")
|
||||
return None
|
||||
for path in _DB_PATHS:
|
||||
if path.is_file():
|
||||
_reader = maxminddb.open_database(str(path))
|
||||
log.info("asn: loaded %s", path)
|
||||
return _reader
|
||||
log.warning("asn: no GeoLite2-ASN.mmdb found")
|
||||
return None
|
||||
|
||||
|
||||
@command("asn", help="ASN lookup: !asn <ip>")
|
||||
async def cmd_asn(bot, message):
|
||||
"""Look up the Autonomous System Number for an IP address.
|
||||
|
||||
Usage:
|
||||
!asn 8.8.8.8
|
||||
"""
|
||||
parts = message.text.split(None, 2)
|
||||
if len(parts) < 2:
|
||||
await bot.reply(message, "Usage: !asn <ip>")
|
||||
return
|
||||
|
||||
addr = parts[1]
|
||||
try:
|
||||
ip = ipaddress.ip_address(addr)
|
||||
except ValueError:
|
||||
await bot.reply(message, f"Invalid IP address: {addr}")
|
||||
return
|
||||
|
||||
if ip.is_private or ip.is_loopback:
|
||||
await bot.reply(message, f"{addr}: private/loopback address")
|
||||
return
|
||||
|
||||
reader = _get_reader()
|
||||
if reader is None:
|
||||
await bot.reply(message, "ASN database not available (run update-data)")
|
||||
return
|
||||
|
||||
try:
|
||||
rec = reader.get(str(ip))
|
||||
except Exception as exc:
|
||||
await bot.reply(message, f"Lookup error: {exc}")
|
||||
return
|
||||
|
||||
if not rec:
|
||||
await bot.reply(message, f"{addr}: no ASN data")
|
||||
return
|
||||
|
||||
asn = rec.get("autonomous_system_number", "")
|
||||
org = rec.get("autonomous_system_organization", "")
|
||||
|
||||
if asn:
|
||||
await bot.reply(message, f"{addr}: AS{asn} ({org})" if org else f"{addr}: AS{asn}")
|
||||
else:
|
||||
await bot.reply(message, f"{addr}: no ASN data")
|
||||
Reference in New Issue
Block a user