Enhance mailtrace script: add sender host and IP filters, improve progress display, and update usage instructions
This commit is contained in:
+116
-3
@@ -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] : "")
|
||||||
|
|||||||
Reference in New Issue
Block a user