From e23a51af106c925a927b457f5999046be5230d4b Mon Sep 17 00:00:00 2001 From: Christoph Date: Tue, 7 Jul 2026 20:43:50 +0200 Subject: [PATCH] Enhance mailtrace script: add sender host and IP filters, improve progress display, and update usage instructions --- mailtrace.sh | 119 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 116 insertions(+), 3 deletions(-) diff --git a/mailtrace.sh b/mailtrace.sh index a4a3e73..10fc8a9 100755 --- a/mailtrace.sh +++ b/mailtrace.sh @@ -83,6 +83,8 @@ FROM_FILTER="" TO_FILTER="" QID_FILTER="" MSGID_FILTER="" +SENDER_HOST_FILTER="" +SENDER_IP_FILTER="" SINCE_PREFIX="" UNTIL_PREFIX="" @@ -92,7 +94,7 @@ NO_NOTE=0 # suppress note lines in block output when TLS unknown DEDUP=0 DEDUP_PREFER="final" # final|amavis|first|last -FINAL_ONLY=0 +FINAL_ONLY=1 SASL_ONLY=0 NO_SASL=0 @@ -100,6 +102,10 @@ INBOUND_ONLY=0 OUTBOUND_ONLY=0 DEBUG=0 +SHOW_PROGRESS=0 +if [[ -t 2 ]]; then + SHOW_PROGRESS=1 +fi usage() { cat <<'USAGE' @@ -116,6 +122,8 @@ Filter: --to-addr match nur in to --qid match Queue-ID (Postfix Queue-ID / "qid") --message-id match Header Message-ID (cleanup: message-id=<...>) + --sender-host match absendenden Client-Host (client/orig_client) + --sender-ip match absendende Client-IP (clientip/orig_clientip; IPv4/IPv6) Status-Filter: --success nur erfolgreiche Endzustellungen (status=success) @@ -130,7 +138,11 @@ AUTH / Richtung: Dedup (ein Eintrag pro Empfänger): --dedup dedupliziere pro (message-id,to). Fallback: (qid,to) falls msgid fehlt --dedup-prefer 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: --no-note im Blockformat keine Hinweiszeile ausgeben, wenn TLS nicht korrelierbar ist @@ -138,12 +150,38 @@ TLS/Notizen: Debug: --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: --follow tail -F /var/log/mail.log (live) --all-logs /var/log/mail.log* inkl. .gz (historisch) --log-file einzelne Logdatei (auch .gz); nicht kombinierbar mit --follow/--all-logs --since Timestamp prefix-match (z.B. 2026-01-13T14:47) --until 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 } @@ -160,6 +198,8 @@ while [[ $# -gt 0 ]]; do --to-addr) TO_FILTER="${2:-}"; shift ;; --qid) QID_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 ;; --until) UNTIL_PREFIX="${2:-}"; shift ;; --one-line) FORMAT="one-line" ;; @@ -168,6 +208,7 @@ while [[ $# -gt 0 ]]; do --dedup) DEDUP=1 ;; --dedup-prefer) DEDUP_PREFER="${2:-}"; shift ;; --final-only) FINAL_ONLY=1 ;; + --all-outcomes) FINAL_ONLY=0 ;; --sasl-only) SASL_ONLY=1 ;; --no-sasl) NO_SASL=1 ;; --inbound-only) INBOUND_ONLY=1 ;; @@ -231,6 +272,8 @@ fi -v to_filter="$TO_FILTER" \ -v qid_filter="$QID_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 until_prefix="$UNTIL_PREFIX" \ -v out_format="$FORMAT" \ @@ -243,7 +286,8 @@ fi -v no_sasl="$NO_SASL" \ -v inbound_only="$INBOUND_ONLY" \ -v outbound_only="$OUTBOUND_ONLY" \ - -v debug_mode="$DEBUG" ' + -v debug_mode="$DEBUG" \ + -v show_progress="$SHOW_PROGRESS" ' ############################################################################### # Helferfunktionen ############################################################################### @@ -291,6 +335,7 @@ BEGIN { maxw = 0 for (i=1; i in labels; i++) { w = length(labels[i] ":"); if (w > maxw) maxw = w } LABEL_W = maxw + progress_tick() } 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 } +############################################################################### +# 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 ############################################################################### +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) { + progress_clear() + count_record(k) + if (out_format == "one-line") { tls_part = "" if (REC_tls[k] != "" && REC_tls[k] != "-") { @@ -498,12 +600,15 @@ END { } for (i=1; i<=n; i++) emit_record(KEYS[i]) } + emit_stats() } ############################################################################### # Hauptparser: pro Logzeile State sammeln, bei Zustell-Outcome ausgeben ############################################################################### { + progress_tick() + ts = $1 # 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_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 = (TLS[qid] ? TLS[qid] : "") tls_proto = (TLS_P[qid] ? TLS_P[qid] : "")