Enhance mailtrace script: add sender host and IP filters, improve progress display, and update usage instructions

This commit is contained in:
2026-07-07 20:43:50 +02:00
parent 2aa6dfaad6
commit e23a51af10
+116 -3
View File
@@ -83,6 +83,8 @@ FROM_FILTER=""
TO_FILTER="" TO_FILTER=""
QID_FILTER="" QID_FILTER=""
MSGID_FILTER="" MSGID_FILTER=""
SENDER_HOST_FILTER=""
SENDER_IP_FILTER=""
SINCE_PREFIX="" SINCE_PREFIX=""
UNTIL_PREFIX="" UNTIL_PREFIX=""
@@ -92,7 +94,7 @@ NO_NOTE=0 # suppress note lines in block output when TLS unknown
DEDUP=0 DEDUP=0
DEDUP_PREFER="final" # final|amavis|first|last DEDUP_PREFER="final" # final|amavis|first|last
FINAL_ONLY=0 FINAL_ONLY=1
SASL_ONLY=0 SASL_ONLY=0
NO_SASL=0 NO_SASL=0
@@ -100,6 +102,10 @@ INBOUND_ONLY=0
OUTBOUND_ONLY=0 OUTBOUND_ONLY=0
DEBUG=0 DEBUG=0
SHOW_PROGRESS=0
if [[ -t 2 ]]; then
SHOW_PROGRESS=1
fi
usage() { usage() {
cat <<'USAGE' cat <<'USAGE'
@@ -116,6 +122,8 @@ Filter:
--to-addr <substr> match nur in to --to-addr <substr> match nur in to
--qid <substr> match Queue-ID (Postfix Queue-ID / "qid") --qid <substr> match Queue-ID (Postfix Queue-ID / "qid")
--message-id <substr> match Header Message-ID (cleanup: message-id=<...>) --message-id <substr> match Header Message-ID (cleanup: message-id=<...>)
--sender-host <substr> match absendenden Client-Host (client/orig_client)
--sender-ip <substr> match absendende Client-IP (clientip/orig_clientip; IPv4/IPv6)
Status-Filter: Status-Filter:
--success nur erfolgreiche Endzustellungen (status=success) --success nur erfolgreiche Endzustellungen (status=success)
@@ -130,7 +138,11 @@ AUTH / Richtung:
Dedup (ein Eintrag pro Empfänger): Dedup (ein Eintrag pro Empfänger):
--dedup dedupliziere pro (message-id,to). Fallback: (qid,to) falls msgid fehlt --dedup dedupliziere pro (message-id,to). Fallback: (qid,to) falls msgid fehlt
--dedup-prefer <mode> final|amavis|first|last (Default: final) --dedup-prefer <mode> final|amavis|first|last (Default: final)
--final-only nur endgültige Zustellung ausgeben (remote SMTP oder lokales LMTP), keine Amavis-Handoffs
Umfang:
(default) nur endgültige Zustellungen ausgeben (remote SMTP oder lokales LMTP), keine Amavis-Handoffs
--final-only wie Default; nur finale Zustellungen anzeigen
--all-outcomes alle SMTP/LMTP-Outcomes anzeigen, inkl. Amavis-Handoffs
TLS/Notizen: TLS/Notizen:
--no-note im Blockformat keine Hinweiszeile ausgeben, wenn TLS nicht korrelierbar ist --no-note im Blockformat keine Hinweiszeile ausgeben, wenn TLS nicht korrelierbar ist
@@ -138,12 +150,38 @@ TLS/Notizen:
Debug: Debug:
--debug ergänzt Debug-Details zur Heuristik/Klassifikation --debug ergänzt Debug-Details zur Heuristik/Klassifikation
Fortschritt:
Bei interaktivem stderr zeigt mailtrace.sh waehrend der Verarbeitung einen
kleinen Spinner an. Der Spinner wird vor jeder Ausgabezeile entfernt.
Input / Zeitraum: Input / Zeitraum:
--follow tail -F /var/log/mail.log (live) --follow tail -F /var/log/mail.log (live)
--all-logs /var/log/mail.log* inkl. .gz (historisch) --all-logs /var/log/mail.log* inkl. .gz (historisch)
--log-file <pfad> einzelne Logdatei (auch .gz); nicht kombinierbar mit --follow/--all-logs --log-file <pfad> einzelne Logdatei (auch .gz); nicht kombinierbar mit --follow/--all-logs
--since <prefix> Timestamp prefix-match (z.B. 2026-01-13T14:47) --since <prefix> Timestamp prefix-match (z.B. 2026-01-13T14:47)
--until <prefix> stoppe sobald ts >= prefix (ISO8601 string compare) --until <prefix> stoppe sobald ts >= prefix (ISO8601 string compare)
Beispiele:
# Finale Zustellungen eines absendenden Hosts (Default)
mailtrace.sh --sender-host web0.warenform.de
# Finale Zustellungen einer IPv4-Adresse
mailtrace.sh --sender-ip 83.223.85.164
# Finale Zustellungen einer IPv6-Adresse
mailtrace.sh --sender-ip 2a01:30:0:13:286:96ff:fe4a:6eee
# Nur erfolgreiche finale Zustellungen dieses Hosts
mailtrace.sh --sender-host web0.warenform.de --success
# Nur fehlgeschlagene Zustellungen einer IP in alten und rotierten Logs
mailtrace.sh --all-logs --sender-ip 83.223.85.164 --fail
# Voller Verlauf inkl. Amavis-Handoffs und Reinject-Zustellungen
mailtrace.sh --sender-host web0.warenform.de --all-outcomes
# Kompakte Ausgabe fuer einen Zeitraum
mailtrace.sh --sender-host web0.warenform.de --since 2026-01-13T14 --one-line
USAGE USAGE
} }
@@ -160,6 +198,8 @@ while [[ $# -gt 0 ]]; do
--to-addr) TO_FILTER="${2:-}"; shift ;; --to-addr) TO_FILTER="${2:-}"; shift ;;
--qid) QID_FILTER="${2:-}"; shift ;; --qid) QID_FILTER="${2:-}"; shift ;;
--message-id) MSGID_FILTER="${2:-}"; shift ;; --message-id) MSGID_FILTER="${2:-}"; shift ;;
--sender-host) SENDER_HOST_FILTER="${2:-}"; shift ;;
--sender-ip) SENDER_IP_FILTER="${2:-}"; shift ;;
--since) SINCE_PREFIX="${2:-}"; shift ;; --since) SINCE_PREFIX="${2:-}"; shift ;;
--until) UNTIL_PREFIX="${2:-}"; shift ;; --until) UNTIL_PREFIX="${2:-}"; shift ;;
--one-line) FORMAT="one-line" ;; --one-line) FORMAT="one-line" ;;
@@ -168,6 +208,7 @@ while [[ $# -gt 0 ]]; do
--dedup) DEDUP=1 ;; --dedup) DEDUP=1 ;;
--dedup-prefer) DEDUP_PREFER="${2:-}"; shift ;; --dedup-prefer) DEDUP_PREFER="${2:-}"; shift ;;
--final-only) FINAL_ONLY=1 ;; --final-only) FINAL_ONLY=1 ;;
--all-outcomes) FINAL_ONLY=0 ;;
--sasl-only) SASL_ONLY=1 ;; --sasl-only) SASL_ONLY=1 ;;
--no-sasl) NO_SASL=1 ;; --no-sasl) NO_SASL=1 ;;
--inbound-only) INBOUND_ONLY=1 ;; --inbound-only) INBOUND_ONLY=1 ;;
@@ -231,6 +272,8 @@ fi
-v to_filter="$TO_FILTER" \ -v to_filter="$TO_FILTER" \
-v qid_filter="$QID_FILTER" \ -v qid_filter="$QID_FILTER" \
-v msgid_filter="$MSGID_FILTER" \ -v msgid_filter="$MSGID_FILTER" \
-v sender_host_filter="$SENDER_HOST_FILTER" \
-v sender_ip_filter="$SENDER_IP_FILTER" \
-v since_prefix="$SINCE_PREFIX" \ -v since_prefix="$SINCE_PREFIX" \
-v until_prefix="$UNTIL_PREFIX" \ -v until_prefix="$UNTIL_PREFIX" \
-v out_format="$FORMAT" \ -v out_format="$FORMAT" \
@@ -243,7 +286,8 @@ fi
-v no_sasl="$NO_SASL" \ -v no_sasl="$NO_SASL" \
-v inbound_only="$INBOUND_ONLY" \ -v inbound_only="$INBOUND_ONLY" \
-v outbound_only="$OUTBOUND_ONLY" \ -v outbound_only="$OUTBOUND_ONLY" \
-v debug_mode="$DEBUG" ' -v debug_mode="$DEBUG" \
-v show_progress="$SHOW_PROGRESS" '
############################################################################### ###############################################################################
# Helferfunktionen # Helferfunktionen
############################################################################### ###############################################################################
@@ -291,6 +335,7 @@ BEGIN {
maxw = 0 maxw = 0
for (i=1; i in labels; i++) { w = length(labels[i] ":"); if (w > maxw) maxw = w } for (i=1; i in labels; i++) { w = length(labels[i] ":"); if (w > maxw) maxw = w }
LABEL_W = maxw LABEL_W = maxw
progress_tick()
} }
function pl(label, value) { printf "%-*s %s\n", LABEL_W, label ":", value } function pl(label, value) { printf "%-*s %s\n", LABEL_W, label ":", value }
@@ -368,10 +413,67 @@ function is_inbound(sasl, orig_ip, src) {
return 0 return 0
} }
###############################################################################
# Fortschrittsanzeige auf stderr
###############################################################################
function progress_tick( now, ch) {
if (show_progress != 1) return
now = systime()
if (PROGRESS_LAST != "" && now - PROGRESS_LAST < 2) return
PROGRESS_LAST = now
PROGRESS_IDX = (PROGRESS_IDX % 4) + 1
ch = substr("|/-\\", PROGRESS_IDX, 1)
printf "\r%s", ch > "/dev/stderr"
fflush("/dev/stderr")
PROGRESS_DRAWN = 1
}
function progress_clear() {
if (show_progress != 1 || PROGRESS_DRAWN != 1) return
printf "\r \r" > "/dev/stderr"
fflush("/dev/stderr")
PROGRESS_DRAWN = 0
}
############################################################################### ###############################################################################
# Ausgabe # Ausgabe
############################################################################### ###############################################################################
function count_record(k) {
STAT_TOTAL++
if (REC_status[k] == "success") STAT_SUCCESS++
if (REC_status[k] == "failed") STAT_FAILED++
}
function emit_stats( width) {
progress_clear()
if (out_format == "json") {
printf "{\"type\":\"stats\",\"total\":%d,\"success\":%d,\"failed\":%d}\n", STAT_TOTAL+0, STAT_SUCCESS+0, STAT_FAILED+0
return
}
if (out_format == "one-line") {
printf "stats\ttotal=%d\tsuccess=%d\tfailed=%d\n", STAT_TOTAL+0, STAT_SUCCESS+0, STAT_FAILED+0
return
}
width = length(STAT_TOTAL+0)
if (length(STAT_SUCCESS+0) > width) width = length(STAT_SUCCESS+0)
if (length(STAT_FAILED+0) > width) width = length(STAT_FAILED+0)
print ""
print "Statistics:"
printf " total: %*d\n", width, STAT_TOTAL+0
printf " success: %*d\n", width, STAT_SUCCESS+0
printf " failed: %*d\n", width, STAT_FAILED+0
}
function emit_record(k, tls_part) { function emit_record(k, tls_part) {
progress_clear()
count_record(k)
if (out_format == "one-line") { if (out_format == "one-line") {
tls_part = "" tls_part = ""
if (REC_tls[k] != "" && REC_tls[k] != "-") { if (REC_tls[k] != "" && REC_tls[k] != "-") {
@@ -498,12 +600,15 @@ END {
} }
for (i=1; i<=n; i++) emit_record(KEYS[i]) for (i=1; i<=n; i++) emit_record(KEYS[i])
} }
emit_stats()
} }
############################################################################### ###############################################################################
# Hauptparser: pro Logzeile State sammeln, bei Zustell-Outcome ausgeben # Hauptparser: pro Logzeile State sammeln, bei Zustell-Outcome ausgeben
############################################################################### ###############################################################################
{ {
progress_tick()
ts = $1 ts = $1
# Zeitraumfilter (prefix match; ISO8601) # Zeitraumfilter (prefix match; ISO8601)
@@ -631,6 +736,14 @@ END {
orig_client = (mid != "" && EXTCLIENT_BY_MSGID[mid] ? EXTCLIENT_BY_MSGID[mid] : (ORIGCLIENT[qid]?ORIGCLIENT[qid]:"-")) orig_client = (mid != "" && EXTCLIENT_BY_MSGID[mid] ? EXTCLIENT_BY_MSGID[mid] : (ORIGCLIENT[qid]?ORIGCLIENT[qid]:"-"))
orig_clientip = (mid != "" && EXTCLIENTIP_BY_MSGID[mid] ? EXTCLIENTIP_BY_MSGID[mid] : (ORIGCLIENTIP[qid]?ORIGCLIENTIP[qid]:"-")) orig_clientip = (mid != "" && EXTCLIENTIP_BY_MSGID[mid] ? EXTCLIENTIP_BY_MSGID[mid] : (ORIGCLIENTIP[qid]?ORIGCLIENTIP[qid]:"-"))
# Absender-Host/IP Filter: direkter client plus rekonstruierter Ursprung.
if (sender_host_filter != "" &&
index(client, sender_host_filter)==0 &&
index(orig_client, sender_host_filter)==0) next
if (sender_ip_filter != "" &&
index(clientip, sender_ip_filter)==0 &&
index(orig_clientip, sender_ip_filter)==0) next
# TLS: qid oder msgid-Vererbung # TLS: qid oder msgid-Vererbung
tls = (TLS[qid] ? TLS[qid] : "") tls = (TLS[qid] ? TLS[qid] : "")
tls_proto = (TLS_P[qid] ? TLS_P[qid] : "") tls_proto = (TLS_P[qid] ? TLS_P[qid] : "")