rspamd_report.sh wertet /var/log/mail/rspamd.log lesbar aus: Uebersicht als Tabelle (Score, Aktion, Scanzeit, Von, An) und Detailansicht einer Mail mit nach Gewicht sortierter Symbolaufschluesselung samt Summe. Getestet gegen echte Logzeilen, die Summe deckt sich mit dem Score im Log. train_bayes.sh: --only akzeptiert jetzt mehrere Muster (mehrfach angebbar oder kommasepariert), damit sich das Training auf kuratierte Postfaecher begrenzen laesst. Ohne Filter wird das jetzt explizit ausgegeben. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
180 lines
6.8 KiB
Bash
Executable File
180 lines
6.8 KiB
Bash
Executable File
#!/bin/bash
|
|
# ===========================================================================
|
|
# rspamd_report.sh - lesbare Auswertung von /var/log/mail/rspamd.log
|
|
#
|
|
# Die Logzeilen von rspamd enthalten alles, sind aber unlesbar. Dieses Skript
|
|
# macht daraus zwei Ansichten:
|
|
#
|
|
# Uebersicht: eine Zeile je Mail mit Score, Aktion, Scanzeit, Von, An
|
|
# Detail: die Symbolaufschluesselung einer Mail, nach Gewicht sortiert,
|
|
# mit Summe - also warum genau diese Mail diesen Score hat
|
|
#
|
|
# AUSFUEHRUNG - immer IM Container:
|
|
#
|
|
# docker exec mailserver bash /tmp/docker-mailserver/rspamd_report.sh
|
|
# docker exec mailserver bash /tmp/docker-mailserver/rspamd_report.sh -n 40
|
|
# docker exec mailserver bash /tmp/docker-mailserver/rspamd_report.sh --detail
|
|
# docker exec mailserver bash /tmp/docker-mailserver/rspamd_report.sh --detail C0D8A320066
|
|
#
|
|
# Optionen:
|
|
# -n N Anzahl Mails in der Uebersicht (Default 15)
|
|
# --detail [ID] Symbolaufschluesselung. ID ist ein beliebiges Fragment aus
|
|
# Queue-ID oder Message-ID. Ohne ID die letzte Mail.
|
|
# --slow MS Nur Mails, deren Scan laenger als MS Millisekunden dauerte
|
|
# --log PFAD Anderes Logfile (Default /var/log/mail/rspamd.log)
|
|
#
|
|
# Hinweis: Das Log enthaelt nur Mails, die seit dem Umstellen auf
|
|
# level = "info" (rspamd/local.d/logging.inc) verarbeitet wurden.
|
|
# ===========================================================================
|
|
|
|
set -uo pipefail
|
|
|
|
LOGFILE="/var/log/mail/rspamd.log"
|
|
COUNT=15
|
|
DETAIL=0
|
|
DETAIL_ID=""
|
|
SLOW_MS=0
|
|
|
|
usage() { sed -n '2,30p' "$0" | sed 's/^# \{0,1\}//' ; }
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
-n) COUNT="${2:-15}"; shift 2 ;;
|
|
--detail) DETAIL=1
|
|
if [[ ${2:-} != "" && ${2:-} != -* ]]; then DETAIL_ID="$2"; shift 2; else shift; fi ;;
|
|
--slow) SLOW_MS="${2:-0}"; shift 2 ;;
|
|
--log) LOGFILE="${2:-}"; shift 2 ;;
|
|
-h|--help) usage; exit 0 ;;
|
|
*) echo "Unbekannte Option: $1" >&2; exit 1 ;;
|
|
esac
|
|
done
|
|
|
|
if [[ ! -r ${LOGFILE} ]]; then
|
|
echo "FEHLER: ${LOGFILE} nicht lesbar. Laeuft das Skript wirklich IM Container?" >&2
|
|
exit 1
|
|
fi
|
|
|
|
TASKS=$(grep 'rspamd_task_write_log' "${LOGFILE}" 2>/dev/null)
|
|
if [[ -z ${TASKS} ]]; then
|
|
echo "Keine ausgewerteten Mails im Log gefunden."
|
|
echo "Steht in rspamd/local.d/logging.inc wirklich level = \"info\"?"
|
|
exit 0
|
|
fi
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Detailansicht einer einzelnen Mail
|
|
# ---------------------------------------------------------------------------
|
|
if (( DETAIL )); then
|
|
if [[ -n ${DETAIL_ID} ]]; then
|
|
LINE=$(grep -F "${DETAIL_ID}" <<<"${TASKS}" | tail -1)
|
|
[[ -z ${LINE} ]] && { echo "Keine Mail mit '${DETAIL_ID}' im Log gefunden." >&2; exit 1; }
|
|
else
|
|
LINE=$(tail -1 <<<"${TASKS}")
|
|
fi
|
|
|
|
awk '
|
|
{
|
|
# Kopfdaten
|
|
ts = substr($0, 1, 19)
|
|
match($0, /qid: <[^>]*>/); qid = extract()
|
|
match($0, /from: <[^>]*>/); from = extract()
|
|
match($0, /rcpts: <[^>]*>/); rcpt = extract()
|
|
match($0, /settings_id: [^,]*/); sid = substr($0, RSTART+13, RLENGTH-13)
|
|
match($0, /time: [0-9.]+ms/); tm = substr($0, RSTART+6, RLENGTH-8)
|
|
match($0, /dns req: [0-9]+/); dns = substr($0, RSTART+9, RLENGTH-9)
|
|
|
|
# Aktion und Score: (default: F (no action): [4.74/500.00]
|
|
if (match($0, /\([a-z ]+\): \[-?[0-9.]+\//)) {
|
|
chunk = substr($0, RSTART, RLENGTH)
|
|
action = substr(chunk, 2, index(chunk, ")") - 2)
|
|
score = substr(chunk, index(chunk, "[") + 1, length(chunk) - index(chunk, "[") - 1)
|
|
}
|
|
|
|
printf "Zeit: %s\n", ts
|
|
printf "Queue-ID: %s\n", qid
|
|
printf "Von: %s\n", from
|
|
printf "An: %s\n", rcpt
|
|
printf "Regel: %s\n", sid
|
|
printf "Aktion: %s Score: %s Scanzeit: %s ms DNS: %s\n\n", action, score, tm, dns
|
|
|
|
# Symbolblock zwischen "] [" und "]), len:"
|
|
i = index($0, "] [")
|
|
if (i == 0) { print " (keine Symbole im Log)"; exit }
|
|
rest = substr($0, i + 3)
|
|
j = index(rest, "]), len:")
|
|
if (j == 0) j = length(rest)
|
|
syms = substr(rest, 1, j - 1)
|
|
|
|
# auf Kommas der obersten Ebene splitten (Klammern koennen Kommas enthalten)
|
|
depth = 0; cur = ""; n = 0
|
|
for (k = 1; k <= length(syms); k++) {
|
|
c = substr(syms, k, 1)
|
|
if (c == "{") depth++
|
|
else if (c == "}") depth--
|
|
if (c == "," && depth == 0) { if (cur != "") entries[++n] = cur; cur = ""; continue }
|
|
cur = cur c
|
|
}
|
|
if (cur != "") entries[++n] = cur
|
|
|
|
printf " %8s %-38s %s\n", "GEWICHT", "SYMBOL", "DETAIL"
|
|
printf " %8s %-38s %s\n", "-------", "--------------------------------------", "------"
|
|
|
|
total = 0
|
|
# zwei Durchlaeufe: erst die mit Gewicht, dann die neutralen
|
|
for (pass = 1; pass <= 2; pass++) {
|
|
for (e = 1; e <= n; e++) {
|
|
entry = entries[e]
|
|
p1 = index(entry, "(")
|
|
if (p1 == 0) continue
|
|
name = substr(entry, 1, p1 - 1)
|
|
p2 = index(entry, ")")
|
|
w = substr(entry, p1 + 1, p2 - p1 - 1)
|
|
payload = ""
|
|
b1 = index(entry, "{")
|
|
if (b1 > 0) payload = substr(entry, b1 + 1, length(entry) - b1 - 1)
|
|
gsub(/;$/, "", payload)
|
|
|
|
if (pass == 1 && w + 0 == 0) continue
|
|
if (pass == 2 && w + 0 != 0) continue
|
|
if (pass == 1) total += w
|
|
|
|
printf " %8.2f %-38s %s\n", w, name, substr(payload, 1, 60)
|
|
}
|
|
if (pass == 1) printf " %8s %s\n", "-------", ""
|
|
if (pass == 1) printf " %8.2f %s\n\n neutrale Symbole (Gewicht 0):\n", total, "SUMME"
|
|
}
|
|
}
|
|
function extract( s) { s = substr($0, RSTART, RLENGTH); return substr(s, index(s, "<") + 1, length(s) - index(s, "<") - 1) }
|
|
' <<<"${LINE}"
|
|
exit 0
|
|
fi
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Uebersicht
|
|
# ---------------------------------------------------------------------------
|
|
printf '%-19s %7s %-12s %7s %-30s %-30s\n' "ZEIT" "SCORE" "AKTION" "MS" "VON" "AN"
|
|
printf -- '--------------------------------------------------------------------------------------------------------------------\n'
|
|
|
|
awk -v slow="${SLOW_MS}" '
|
|
{
|
|
ts = substr($0, 1, 19)
|
|
match($0, /from: <[^>]*>/); from = extract()
|
|
match($0, /rcpts: <[^>]*>/); rcpt = extract()
|
|
match($0, /time: [0-9.]+ms/); tm = substr($0, RSTART+6, RLENGTH-8) + 0
|
|
|
|
if (match($0, /\([a-z ]+\): \[-?[0-9.]+\//)) {
|
|
chunk = substr($0, RSTART, RLENGTH)
|
|
action = substr(chunk, 2, index(chunk, ")") - 2)
|
|
score = substr(chunk, index(chunk, "[") + 1, length(chunk) - index(chunk, "[") - 1) + 0
|
|
} else { action = "?"; score = 0 }
|
|
|
|
if (tm < slow) next
|
|
printf "%-19s %7.2f %-12s %7.0f %-30.30s %-30.30s\n", ts, score, action, tm, from, rcpt
|
|
}
|
|
function extract( s) { s = substr($0, RSTART, RLENGTH); return substr(s, index(s, "<") + 1, length(s) - index(s, "<") - 1) }
|
|
' <<<"${TASKS}" | tail -n "${COUNT}"
|
|
|
|
echo
|
|
echo "Detail zu einer Mail: $0 --detail <Queue-ID>"
|
|
echo "Nur langsame Scans: $0 --slow 1000"
|