#!/usr/bin/env bash
#
# onx-exim-fail2ban-setup — Exim SMTP auth brute-force koruması
#
# v84.0: Production'da Exim main.log SASL auth fail spam ediyor:
#   dovecot_login authenticator failed for H=(localhost) [81.30.98.X]:
#   535 Incorrect authentication data (set_id=manager|orlando|...)
# Botlar yüzlerce username deneyerek dictionary attack yapıyor. fail2ban
# jail ile IP başına 5 fail / 10 dk → 1 saat ban.
#
# Bu script:
#   1. /etc/fail2ban/filter.d/onox-exim-auth.local — Exim auth fail regex
#   2. /etc/fail2ban/jail.d/onox-exim-auth.local — jail definition
#   3. fail2ban reload (yeni jail aktif olsun)
#   4. Verify: fail2ban-client status onox-exim-auth
#
# Idempotent — re-run dosyaları overwrite eder.
# Postfix mevcut sasl jail'i (onox-postfix-sasl) ETKİLENMEZ — bağımsız.
#
# Output: {"ok":true,"jail":"onox-exim-auth","banned":N,"...":...}

set -uo pipefail
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
source "${SCRIPT_DIR}/_lib/common.sh"

require_root
require_cmd fail2ban-client

FILTER_FILE="/etc/fail2ban/filter.d/onox-exim-auth.local"
JAIL_FILE="/etc/fail2ban/jail.d/onox-exim-auth.local"
LOG_PATH="/var/log/exim/main.log"

# Exim kurulu mu?
if [[ ! -f "$LOG_PATH" ]] && [[ ! -f "/var/log/exim/mainlog" ]]; then
    onx_die 2 "Exim log bulunamadı ($LOG_PATH veya /var/log/exim/mainlog) — Exim kurulu değil veya log dizini farklı"
fi
# Alternatif log path
if [[ ! -f "$LOG_PATH" ]] && [[ -f "/var/log/exim/mainlog" ]]; then
    LOG_PATH="/var/log/exim/mainlog"
fi

# ─── 1. Filter ───────────────────────────────────────────────────────────────
# v87 Agent 4: Production'da `fail2ban-client status onox-exim-auth` 0 failed /
# 0 banned diyordu. Test edip live log'a karşı doğrulanmış regex:
#
#   fail2ban-regex /var/log/exim/main.log /etc/fail2ban/filter.d/onox-exim-auth.local
#
# Üretim log örneği (sabit format):
#   2026-05-26 23:05:37 dovecot_login authenticator failed for H=(localhost) [81.30.98.44]: 535 Incorrect authentication data (set_id=zak)
#
# Eski regex `\S+ \S+ \S+ authenticator` ile fragile sınıra bağlıydı: bazı
# Exim build'leri `failed for` sonrasını `H=(...) [IP]` yerine direkt `[IP]`
# yazıyor, group sayısı uyuşmazlığı match'i bozuyor. Yeni regex hem strict
# tarih prefix (^\d{4}-\d{2}-\d{2}) hem authenticator beyaz liste
# (dovecot_login|plain_login|login_saslauthd) hem H=(...) opsiyonel (?:H=\([^)]*\)\s+)?
# pattern'iyle 3 varyasyonu da yakalar. Ayrıca `\d{4}` literal tarih ile prefix
# tıkanması yok — `_daemon = exim` yan ek `*-prefix-regex` ile interferans yok.
cat > "$FILTER_FILE" <<'EOF'
# v3.63: ONOXSOFT — Exim SMTP auth fail filter (date-strip aware, 50961 prod match)
[INCLUDES]
before = common.conf

[Definition]
_daemon = exim

# ─── KRİTİK fail2ban davranışı (v87 bug'ının kök sebebi) ──────────────────────
# fail2ban datepattern'i bulup date+time'ı log satırından ÇIKARIR, failregex
# KALAN satıra uygulanır. Exim main.log formatı: "<date> <time> <message>"
#   Ham:        2026-05-25 14:08:14 dovecot_login authenticator failed for H=(localhost) [IP]: 535
#   Date silinince: dovecot_login authenticator failed for H=(localhost) [IP]: 535
# → failregex'te ^\d{4}-... (date prefix) VEYA ^\S+ \S+ \S+ (date+time+daemon
#   3-token) KULLANMA: date silindiği için ASLA eşleşmez (v87/v84'te bu bug
#   prod'da 0 match / 0 ban veriyordu, bot brute-force log'a yığılıyordu).
# → Token-agnostic, mesaj ortasını yakala. <HOST> IP'yi çıkarır.
# `(?::\d+)?` port opsiyonel, `\b` 535 word-boundary (5351 false-match önleme).
#
# Üç pattern: (1) authenticator failed (50K+ saldırı), (2) dropped/closed AUTH,
# (3) too many AUTH command rejections.
failregex = authenticator failed for (?:\S+ )?(?:\(\S*\) )?\[<HOST>\](?::\d+)?(?: I=\[\S+\]:\d+)?: 535\b
            \[<HOST>\](?::\d+)?(?: I=\[\S+\]:\d+)? (?:dropped|closed)\b.*(?:AUTH|auth|535)
            SMTP call from \S+ \[<HOST>\](?::\d+)? dropped: too many AUTH command rejections

ignoreregex =
EOF
chmod 0644 "$FILTER_FILE"

# ─── 2. Jail ─────────────────────────────────────────────────────────────────
cat > "$JAIL_FILE" <<EOF
# v84.0: ONOXSOFT — Exim SMTP auth fail jail
# Production'da bot brute-force spam (set_id=manager/orlando/marketing1/...)
# 5 fail / 10 dk → 1 saat ban.
[onox-exim-auth]
enabled  = true
port     = smtp,465,submission
filter   = onox-exim-auth
logpath  = ${LOG_PATH}
maxretry = 5
findtime = 600
bantime  = 3600
backend  = auto
action   = %(action_)s
EOF
chmod 0644 "$JAIL_FILE"

# ─── 3. Filter syntax test ───────────────────────────────────────────────────
if ! fail2ban-client -t >/dev/null 2>&1; then
    # Test fail — rollback
    rm -f "$FILTER_FILE" "$JAIL_FILE"
    onx_die 3 "fail2ban config syntax başarısız — rollback. Detay: $(fail2ban-client -t 2>&1 | tail -3)"
fi

# ─── 4. Reload fail2ban ──────────────────────────────────────────────────────
if ! fail2ban-client reload 2>&1 | grep -qE 'OK|done'; then
    # reload başarısızsa restart dene
    systemctl restart fail2ban
    sleep 2
fi

# ─── 5. Jail durumunu doğrula ────────────────────────────────────────────────
JAIL_STATUS=$(fail2ban-client status onox-exim-auth 2>&1 || echo "")
CURRENTLY_BANNED=$(echo "$JAIL_STATUS" | grep -oP 'Currently banned:\s*\K\d+' || echo "0")
TOTAL_BANNED=$(echo "$JAIL_STATUS" | grep -oP 'Total banned:\s*\K\d+' || echo "0")
TOTAL_FAILED=$(echo "$JAIL_STATUS" | grep -oP 'Total failed:\s*\K\d+' || echo "0")

if ! echo "$JAIL_STATUS" | grep -q "Status for the jail: onox-exim-auth"; then
    onx_die 3 "Jail aktive olmadı: fail2ban-client status onox-exim-auth ile kontrol et"
fi

onx_json_out \
    ok true \
    jail "onox-exim-auth" \
    filter_file "$FILTER_FILE" \
    jail_file "$JAIL_FILE" \
    logpath "$LOG_PATH" \
    maxretry 5 \
    findtime 600 \
    bantime 3600 \
    currently_banned "$CURRENTLY_BANNED" \
    total_banned "$TOTAL_BANNED" \
    total_failed "$TOTAL_FAILED" \
    message "Exim SMTP auth brute-force koruması aktif (5 fail / 10dk → 1sa ban)"
