87 lines
3.1 KiB
Python
87 lines
3.1 KiB
Python
#!/usr/bin/env python3
|
|
import os
|
|
import re
|
|
import time
|
|
import subprocess
|
|
import threading
|
|
from datetime import datetime
|
|
try:
|
|
from croniter import croniter
|
|
except ImportError:
|
|
print("Bitte 'croniter' via pip installieren!")
|
|
exit(1)
|
|
|
|
LOG_FILE = '/var/log/mail/mail.log'
|
|
WHITELIST_DURATION_SEC = 24 * 60 * 60 # 24 Stunden
|
|
CRON_SCHEDULE = "0 * * * *" # Jede Stunde
|
|
|
|
active_ips = {}
|
|
|
|
# Regex für Dovecot IMAP/POP3 erfolgreiche Logins
|
|
LOGIN_REGEX = re.compile(r"dovecot: (?:imap|pop3)-login: Login: user=<[^>]+>.*rip=([0-9]{1,3}(?:\.[0-9]{1,3}){3}),")
|
|
# Private Netze (Docker/Local) ignorieren
|
|
IGNORE_REGEX = re.compile(r"^(172\.|10\.|192\.168\.|127\.)")
|
|
|
|
def run_command(cmd):
|
|
try:
|
|
subprocess.run(cmd, shell=True, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
except Exception as e:
|
|
print(f"Fehler bei: {cmd} - {e}")
|
|
|
|
def cleanup_job():
|
|
"""Cron-Thread für das stündliche Aufräumen abgelaufener IPs."""
|
|
iter = croniter(CRON_SCHEDULE, datetime.now())
|
|
while True:
|
|
next_run = iter.get_next(datetime)
|
|
sleep_seconds = (next_run - datetime.now()).total_seconds()
|
|
|
|
if sleep_seconds > 0:
|
|
time.sleep(sleep_seconds)
|
|
|
|
print(f"[{datetime.now()}] Starte stündlichen Whitelist-Cleanup...")
|
|
now = time.time()
|
|
expired_ips = [ip for ip, timestamp in active_ips.items() if now - timestamp > WHITELIST_DURATION_SEC]
|
|
|
|
for ip in expired_ips:
|
|
print(f"[{datetime.now()}] Whitelist für {ip} abgelaufen. Entferne...")
|
|
run_command(f"fail2ban-client set dovecot delignoreip {ip}")
|
|
run_command(f"fail2ban-client set postfix delignoreip {ip}")
|
|
del active_ips[ip]
|
|
|
|
def follow_log():
|
|
"""Verwendet System 'tail -F', da dies Log-Rotation automatisch handhabt."""
|
|
print(f"[{datetime.now()}] Dynamic Whitelist Monitor gestartet...")
|
|
|
|
while not os.path.exists(LOG_FILE):
|
|
time.sleep(2)
|
|
|
|
process = subprocess.Popen(['tail', '-F', LOG_FILE], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True)
|
|
|
|
for line in process.stdout:
|
|
match = LOGIN_REGEX.search(line)
|
|
if match:
|
|
ip = match.group(1)
|
|
|
|
if IGNORE_REGEX.match(ip):
|
|
continue
|
|
|
|
now = time.time()
|
|
|
|
# Neue IP in die Fail2ban Whitelist eintragen
|
|
if ip not in active_ips:
|
|
print(f"[{datetime.now()}] Neuer erfolgreicher Login von {ip}. Setze auf Whitelist...")
|
|
run_command(f"fail2ban-client set dovecot addignoreip {ip}")
|
|
run_command(f"fail2ban-client set postfix addignoreip {ip}")
|
|
|
|
# Timestamp (Last Seen) aktualisieren
|
|
active_ips[ip] = now
|
|
|
|
if __name__ == '__main__':
|
|
# Warte kurz, bis Fail2ban nach einem Container-Start hochgefahren ist
|
|
time.sleep(15)
|
|
|
|
# Cron-Cleanup im Hintergrund starten
|
|
threading.Thread(target=cleanup_job, daemon=True).start()
|
|
|
|
# Log-Überwachung in der Endlosschleife starten
|
|
follow_log() |