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) if __name__ == "__main__": init_db() monitor_fail2ban()