#!/usr/bin/env python3 """Post a single abuse report to IRDB. Usage: IRDB_URL=http://localhost:8081 IRDB_TOKEN=irdb_rep_... \\ examples/reporters/python.py 203.0.113.42 brute_force '{"url":"/wp-login.php"}' The third argument (metadata) is optional and must be valid JSON. This script uses only the stdlib (`urllib`); no third-party deps. fail2ban-action wrapper (drop into `/etc/fail2ban/action.d/irdb.conf`): [Definition] actionban = /usr/local/bin/irdb-report.py brute_force '{"jail":"","host":""}' actionunban = true [Init] name = default `` and `` are substituted by fail2ban at action time. Set IRDB_URL and IRDB_TOKEN in fail2ban's environment (e.g. via `/etc/default/fail2ban`). """ from __future__ import annotations import json import os import sys import urllib.error import urllib.request def main(argv: list[str]) -> int: if len(argv) < 3: print(f"Usage: {argv[0]} []", file=sys.stderr) return 64 ip = argv[1] category = argv[2] metadata_raw = argv[3] if len(argv) > 3 else "{}" try: metadata = json.loads(metadata_raw) except json.JSONDecodeError as e: print(f"metadata must be valid JSON: {e}", file=sys.stderr) return 65 url_base = os.environ.get("IRDB_URL") token = os.environ.get("IRDB_TOKEN") if not url_base or not token: print("IRDB_URL and IRDB_TOKEN must be set", file=sys.stderr) return 78 body = json.dumps({"ip": ip, "category": category, "metadata": metadata}).encode() req = urllib.request.Request( f"{url_base.rstrip('/')}/api/v1/report", data=body, method="POST", headers={ "Authorization": f"Bearer {token}", "Content-Type": "application/json", }, ) try: with urllib.request.urlopen(req, timeout=10) as resp: print(resp.read().decode()) return 0 except urllib.error.HTTPError as e: print(f"http {e.code}: {e.read().decode(errors='replace')}", file=sys.stderr) return 1 except urllib.error.URLError as e: print(f"network error: {e}", file=sys.stderr) return 2 if __name__ == "__main__": sys.exit(main(sys.argv))