diff --git a/fail2ban_discord.py b/fail2ban_discord.py new file mode 100644 index 0000000..fa758a3 --- /dev/null +++ b/fail2ban_discord.py @@ -0,0 +1,118 @@ +import requests +import time +import os +import re +import sqlite3 +from dotenv import load_dotenv + +# .env laden +load_dotenv() + +LOGFILE = "/var/log/fail2ban.log" +WEBHOOK_URL = os.getenv("DISCORD_WEBHOOK_URL") +GEO_API_URL = "https://ipapi.co/{ip}/json/" + +# SQLite-Datenbank +DB_FILE = "bans.db" + +def init_db(): + conn = sqlite3.connect(DB_FILE) + c = conn.cursor() + c.execute(""" + CREATE TABLE IF NOT EXISTS bans ( + ip TEXT PRIMARY KEY, + jail TEXT, + country TEXT, + timestamp DATETIME DEFAULT CURRENT_TIMESTAMP + ) + """) + conn.commit() + conn.close() + +def is_ip_already_banned(ip): + conn = sqlite3.connect(DB_FILE) + c = conn.cursor() + c.execute("SELECT 1 FROM bans WHERE ip = ?", (ip,)) + result = c.fetchone() + conn.close() + return result is not None + +def save_ban(ip, jail, country): + conn = sqlite3.connect(DB_FILE) + c = conn.cursor() + c.execute("INSERT OR IGNORE INTO bans (ip, jail, country) VALUES (?, ?, ?)", (ip, jail, country)) + conn.commit() + conn.close() + +def get_country(ip): + try: + resp = requests.get(GEO_API_URL.format(ip=ip), timeout=5) + data = resp.json() + country_name = data.get("country_name", "Unbekannt") + country_code = data.get("country_code", "").upper() + return country_name, country_code + except Exception as e: + print(f"[!] Fehler bei Geo-Abfrage: {e}") + return "Unbekannt", "" + +def country_code_to_flag(country_code): + if not country_code or len(country_code) != 2: + return "🏳️" # neutrale Flagge + return chr(ord(country_code[0].upper()) + 127397) + chr(ord(country_code[1].upper()) + 127397) + +def send_discord_embed(ip, jail, country_name, country_code): + flag = country_code_to_flag(country_code) + embed = { + "title": "🚨 Neue IP gesperrt durch Fail2Ban", + "color": 16711680, + "fields": [ + {"name": "📛 Jail", "value": jail, "inline": True}, + {"name": "🌍 IP-Adresse", "value": ip, "inline": True}, + {"name": f"{flag} Herkunftsland", "value": f"{country_name} (`{country_code}`)", "inline": True} + ], + "footer": {"text": "Fail2Ban Benachrichtigung"} + } + + try: + resp = requests.post(WEBHOOK_URL, json={"embeds": [embed]}) + if resp.status_code >= 400: + print(f"[!] Discord-Webhook fehlgeschlagen: {resp.status_code} - {resp.text}") + except Exception as e: + print(f"[!] Fehler beim Senden an Discord: {e}") + +def tail_f(path): + with open(path, "r") as f: + f.seek(0, os.SEEK_END) + while True: + line = f.readline() + if not line: + time.sleep(0.2) + continue + yield line + +def monitor_fail2ban(): + log_lines = tail_f(LOGFILE) + ban_pattern = re.compile(r"Ban\s+(\d+\.\d+\.\d+\.\d+)") + jail_pattern = re.compile(r"\[(\w+)\]") + + current_jail = "Unbekannt" + + for line in log_lines: + jail_match = jail_pattern.search(line) + if jail_match: + current_jail = jail_match.group(1) + + match = ban_pattern.search(line) + if match: + ip = match.group(1) + if not is_ip_already_banned(ip): + country_name, country_code = get_country(ip) + send_discord_embed(ip, current_jail, country_name, country_code) + save_ban(ip, current_jail, country_name) + print(f"[+] Neue IP gesperrt und gemeldet: {ip} ({country_name})") + +if __name__ == "__main__": + print("[*] Initialisiere Datenbank...") + init_db() + print("[*] Starte Fail2Ban-Log-Monitor mit Discord-Integration...") + monitor_fail2ban()