#!/usr/bin/env bash set -euo pipefail ############################################################################### # mailtrace # # Ziel: # Postfix-/Amavis-/Dovecot-LMTP-Logzeilen aus /var/log/mail.log korrelieren, # damit du pro Zustell-Outcome (LMTP) sauber siehst: # - from / to # - status / dsn # - Queue-ID (qid) und Header Message-ID # - relay (Amavis-Hop 10024 / Dovecot private/dovecot-lmtp / etc.) # - client / clientip / orig_clientip (extern vs. reinject) # - sasl user (SMTP AUTH) # - TLS Daten (wenn im Log korrelierbar) # # Admin-Notizen (wichtig, wenn du dich über "fehlende Felder" wunderst): # # 1) "qid" = Postfix Queue-ID, nicht Header-Message-ID: # - qid ist ein interner Bezeichner für einen Queue-Entry in Postfix. # - Eine Mail kann mehrere qids bekommen (z.B. durch Content-Filter/Amavis # oder durch Re-Queueing). Daher korrelieren wir zusätzlich über # die Header Message-ID (cleanup: message-id=<...>). # # 2) Warum "helo=" oft leer/nie auftaucht: # - Postfix loggt das HELO/EHLO nicht standardmäßig bei jeder Mail. # - Darum nutzt mailtrace als Fallback den Client-Hostname aus # "client=host[ip]" (hilft für Herkunft/Debugging zuverlässig). # # 3) TLS-Parameter sind manchmal nicht zuordenbar: # - Die Zeile "TLS connection established ..." hat KEINE Queue-ID. # - TLS ist "pro Verbindung", qid entsteht oft erst später im SMTP-Dialog. # - Bei Amavis-Reinject entstehen neue qids (localhost), TLS gehört aber # zur externen Session. # - Wir mappen TLS zunächst per clientip und vererben TLS zusätzlich über # die Header-Message-ID (damit nach Reinject/Mehrfachzustellung möglichst # viel wieder sichtbar wird). # # 4) TLS auf 25/465/587 (dein Setup): # - Je nach master.cf siehst du TLS-Zeilen in: # postfix/smtpd[...] (Port 25) # postfix/submission/smtpd[...] (Port 587, Submission) # postfix/smtps/smtpd[...] (Port 465, SMTPS) # - Dieses Script sucht die generische Zeile: # "TLS connection established from ..." # unabhängig davon, welcher Service-Name davor steht. D.h. TLS von # 25/465/587 wird grundsätzlich erkannt – wenn die Zeile im Log ist. # - Wie viele TLS-Details im Log landen, steuert u.a. smtpd_tls_loglevel. # Höherer Loglevel => mehr Details, aber auch deutlich mehr Logvolumen. # # 5) inbound-only / outbound-only (Heuristik!): # - outbound-only: typischerweise SMTP AUTH (sasl_username) oder lokal # erzeugt (pickup uid=...), ggf. rein lokal/localhost. # - inbound-only : typischerweise von extern (orig_clientip extern), # ohne SMTP AUTH. # - Das sind Regeln “für die Praxis”, keine 100% perfekte Klassifikation. # # 6) Erfolg/Fehlschlag: # - --success zeigt nur erfolgreiche LMTP Outcomes # - --fail zeigt nur nicht-erfolgreiche Outcomes # - Erfolgreich definieren wir hier pragmatisch als status in: # sent | delivered | deliverable # # 7) Debug-Modus: # - --debug ergänzt im Output eine Zeile "debug:" mit Gründen, z.B.: # outbound: sasl set / pickup uid / local / no external origin # inbound : external origin + no sasl # Das hilft beim Nachjustieren der Heuristik, ohne den Parser umzubauen. ############################################################################### FOLLOW=0 ALL_LOGS=0 FAIL_ONLY=0 SUCCESS_ONLY=0 ADDR_FILTER="" FROM_FILTER="" TO_FILTER="" QID_FILTER="" MSGID_FILTER="" SINCE_PREFIX="" UNTIL_PREFIX="" FORMAT="block" # block|one-line|json NO_NOTE=0 # suppress note lines in block output when TLS unknown DEDUP=0 DEDUP_PREFER="final" # final|amavis|first|last SASL_ONLY=0 NO_SASL=0 INBOUND_ONLY=0 OUTBOUND_ONLY=0 DEBUG=0 usage() { cat <<'USAGE' mailtrace – Postfix/Dovecot LMTP Zustellungen aus /var/log/mail.log korrelieren Ausgabeformate: (default) Blockformat (lesbar, mit Leerzeile davor) --one-line Eine Zeile pro Zustell-Outcome --format json JSON Lines (eine Zeile pro Outcome) Filter: --addr match in from ODER to --from-addr match nur in from --to-addr match nur in to --qid match Queue-ID (Postfix Queue-ID / "qid") --message-id match Header Message-ID (cleanup: message-id=<...>) Status-Filter: --success nur erfolgreiche Outcomes (sent|delivered|deliverable) --fail nur nicht erfolgreiche Outcomes (alles andere) AUTH / Richtung: --sasl-only nur Einlieferungen mit SMTP AUTH (sasl_username vorhanden) --no-sasl nur Einlieferungen ohne SMTP AUTH (sasl_username fehlt) --inbound-only heuristisch: nur eingehend (extern, i.d.R. ohne AUTH) --outbound-only heuristisch: nur ausgehend (AUTH oder lokal erzeugt) 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) TLS/Notizen: --no-note im Blockformat keine Hinweiszeile ausgeben, wenn TLS nicht korrelierbar ist Debug: --debug ergänzt Debug-Details zur Heuristik/Klassifikation Input / Zeitraum: --follow tail -F /var/log/mail.log (live) --all-logs /var/log/mail.log* inkl. .gz (historisch) --since Timestamp prefix-match (z.B. 2026-01-13T14:47) --until stoppe sobald ts >= prefix (ISO8601 string compare) USAGE } # --- CLI Parser -------------------------------------------------------------- while [[ $# -gt 0 ]]; do case "$1" in --follow) FOLLOW=1 ;; --all-logs) ALL_LOGS=1 ;; --success) SUCCESS_ONLY=1 ;; --fail) FAIL_ONLY=1 ;; --addr) ADDR_FILTER="${2:-}"; shift ;; --from-addr) FROM_FILTER="${2:-}"; shift ;; --to-addr) TO_FILTER="${2:-}"; shift ;; --qid) QID_FILTER="${2:-}"; shift ;; --message-id) MSGID_FILTER="${2:-}"; shift ;; --since) SINCE_PREFIX="${2:-}"; shift ;; --until) UNTIL_PREFIX="${2:-}"; shift ;; --one-line) FORMAT="one-line" ;; --format) FORMAT="${2:-}"; shift ;; --no-note) NO_NOTE=1 ;; --dedup) DEDUP=1 ;; --dedup-prefer) DEDUP_PREFER="${2:-}"; shift ;; --sasl-only) SASL_ONLY=1 ;; --no-sasl) NO_SASL=1 ;; --inbound-only) INBOUND_ONLY=1 ;; --outbound-only) OUTBOUND_ONLY=1 ;; --debug) DEBUG=1 ;; --help|-h) usage; exit 0 ;; *) echo "Unbekannte Option: $1" >&2; usage; exit 2 ;; esac shift done # --- Validation -------------------------------------------------------------- if [[ "$FORMAT" != "block" && "$FORMAT" != "one-line" && "$FORMAT" != "json" ]]; then echo "Ungültiges --format: $FORMAT (erlaubt: block|one-line|json)" >&2 exit 2 fi if [[ "$DEDUP_PREFER" != "final" && "$DEDUP_PREFER" != "amavis" && "$DEDUP_PREFER" != "first" && "$DEDUP_PREFER" != "last" ]]; then echo "Ungültiges --dedup-prefer: $DEDUP_PREFER (final|amavis|first|last)" >&2 exit 2 fi if [[ $SUCCESS_ONLY -eq 1 && $FAIL_ONLY -eq 1 ]]; then echo "Bitte nur eines von --success oder --fail verwenden." >&2 exit 2 fi if [[ $SASL_ONLY -eq 1 && $NO_SASL -eq 1 ]]; then echo "Bitte nur eines von --sasl-only oder --no-sasl verwenden." >&2 exit 2 fi if [[ $INBOUND_ONLY -eq 1 && $OUTBOUND_ONLY -eq 1 ]]; then echo "Bitte nur eines von --inbound-only oder --outbound-only verwenden." >&2 exit 2 fi # --- Input selection --------------------------------------------------------- if [[ $FOLLOW -eq 1 ]]; then INPUT_CMD=(sudo tail -F /var/log/mail.log) else if [[ $ALL_LOGS -eq 1 ]]; then INPUT_CMD=(sudo zcat -f /var/log/mail.log*) else INPUT_CMD=(sudo cat /var/log/mail.log) fi fi ############################################################################### # gawk core ############################################################################### "${INPUT_CMD[@]}" | gawk \ -v success_only="$SUCCESS_ONLY" \ -v fail_only="$FAIL_ONLY" \ -v addr_filter="$ADDR_FILTER" \ -v from_filter="$FROM_FILTER" \ -v to_filter="$TO_FILTER" \ -v qid_filter="$QID_FILTER" \ -v msgid_filter="$MSGID_FILTER" \ -v since_prefix="$SINCE_PREFIX" \ -v until_prefix="$UNTIL_PREFIX" \ -v out_format="$FORMAT" \ -v no_note="$NO_NOTE" \ -v dedup="$DEDUP" \ -v dedup_prefer="$DEDUP_PREFER" \ -v follow_mode="$FOLLOW" \ -v sasl_only="$SASL_ONLY" \ -v no_sasl="$NO_SASL" \ -v inbound_only="$INBOUND_ONLY" \ -v outbound_only="$OUTBOUND_ONLY" \ -v debug_mode="$DEBUG" ' ############################################################################### # Helferfunktionen ############################################################################### function grab(re, s, m) { return match(s, re, m) ? m[1] : "" } function is_success(st) { return (st=="sent" || st=="delivered" || st=="deliverable") } function jesc(s) { gsub(/\\/,"\\\\",s); gsub(/"/,"\\\"",s) gsub(/\t/,"\\t",s); gsub(/\r/,"\\r",s); gsub(/\n/,"\\n",s) return s } function is_local_ip(ip) { return (ip=="" || ip=="-" || ip=="127.0.0.1" || ip=="::1") } ############################################################################### # Pretty printing (Blockausgabe) ############################################################################### BEGIN { labels[1]="time"; labels[2]="from"; labels[3]="to"; labels[4]="status"; labels[5]="dsn" labels[6]="qid"; labels[7]="msgid"; labels[8]="relay"; labels[9]="client"; labels[10]="clientip" labels[11]="orig_client"; labels[12]="orig_clientip"; labels[13]="helo"; labels[14]="sasl"; labels[15]="source" labels[16]="tls"; labels[17]="tls_proto"; labels[18]="tls_cipher"; labels[19]="tls_bits"; labels[20]="note"; labels[21]="debug" maxw = 0 for (i=1; i in labels; i++) { w = length(labels[i] ":"); if (w > maxw) maxw = w } LABEL_W = maxw } function pl(label, value) { printf "%-*s %s\n", LABEL_W, label ":", value } ############################################################################### # Quelle klassifizieren (grob) ############################################################################### function classify_source(qid, mid, relay, extip) { extip = (mid != "" && EXTCLIENTIP_BY_MSGID[mid] ? EXTCLIENTIP_BY_MSGID[mid] : "") if (relay ~ /127\.0\.0\.1:10024/ || relay ~ /\[127\.0\.0\.1\]:10024/) return "amavis" if (extip != "" && !is_local_ip(extip)) return "smtpd" if (is_local_ip(CLIENTIP[qid]) && extip != "") return "amavis" if (UID[qid] != "") return "local" return "local" } ############################################################################### # TLS Korrelation ############################################################################### function remember_tls(line, ip, proto, cipher, bits) { ip = grab(" from [^\\[]+\\[([^\\]]+)\\]", line) if (ip == "") return proto = grab(": (TLS[^ ]+) with cipher", line) cipher = grab(" with cipher ([^ ]+)", line) bits = grab(" \\(([0-9]+/[0-9]+) bits\\)", line) TLS_SEEN[ip] = 1 TLS_PROTO[ip] = (proto != "" ? proto : "-") TLS_CIPHER[ip] = (cipher != "" ? cipher : "-") TLS_BITS[ip] = (bits != "" ? bits : "-") } ############################################################################### # Dedup: Ein Eintrag pro (message-id,to) (fallback: (qid,to)) ############################################################################### function dedup_score(relay, idx) { if (dedup_prefer == "first") return -idx if (dedup_prefer == "last") return idx if (dedup_prefer == "final") { if (relay ~ /private\/dovecot-lmtp/) return 1000000000 + idx; return idx } if (dedup_prefer == "amavis") { if (relay ~ /127\.0\.0\.1:10024/ || relay ~ /\[127\.0\.0\.1\]:10024/) return 1000000000 + idx; return idx } return idx } ############################################################################### # inbound/outbound Heuristik + Debug-Gründe ############################################################################### function outbound_reason(sasl, uid, orig_ip, src, r) { r="" if (sasl != "" && sasl != "-") r = r (r?"; ":"") "sasl set" if (uid != "" && uid != "-") r = r (r?"; ":"") "pickup uid" if (src == "local") r = r (r?"; ":"") "source=local" if (is_local_ip(orig_ip)) r = r (r?"; ":"") "no external origin" if (r=="") r="(none)" return r } function inbound_reason(sasl, orig_ip, src, r) { r="" if (sasl != "" && sasl != "-") r = r (r?"; ":"") "sasl set (blocks inbound)" if (!is_local_ip(orig_ip)) r = r (r?"; ":"") "external origin" if (src == "smtpd") r = r (r?"; ":"") "source=smtpd" if (r=="") r="(none)" return r } function is_outbound(sasl, uid, orig_ip, src) { if (sasl != "" && sasl != "-") return 1 if (uid != "" && uid != "-") return 1 if (src == "local") return 1 if (is_local_ip(orig_ip)) return 1 return 0 } function is_inbound(sasl, orig_ip, src) { if (sasl != "" && sasl != "-") return 0 if (!is_local_ip(orig_ip)) return 1 if (src == "smtpd") return 1 return 0 } ############################################################################### # Ausgabe ############################################################################### function emit_record(k, tls_part) { if (out_format == "one-line") { tls_part = "" if (REC_tls[k] != "" && REC_tls[k] != "-") { tls_part = sprintf("\ttls=%s\tproto=%s\tcipher=%s\tbits=%s", REC_tls[k], (REC_tls_proto[k]?REC_tls_proto[k]:"-"), (REC_tls_cipher[k]?REC_tls_cipher[k]:"-"), (REC_tls_bits[k]?REC_tls_bits[k]:"-")) } if (debug_mode == 1 && REC_debug[k] != "") { tls_part = tls_part sprintf("\tdebug=%s", REC_debug[k]) } printf "%s\tfrom=%s\tto=%s\tstatus=%s\tdsn=%s\tqid=%s\tmsgid=<%s>\tsource=%s%s\trelay=%s\tclient=%s\tclientip=%s\torig_client=%s\torig_clientip=%s\thelo=%s\tsasl=%s\t%s\n", REC_time[k], REC_from[k], REC_to[k], REC_status[k], REC_dsn[k], REC_qid[k], (REC_msgid[k]?REC_msgid[k]:"-"), REC_source[k], tls_part, REC_relay[k], REC_client[k], REC_clientip[k], REC_orig_client[k], REC_orig_clientip[k], REC_helo[k], REC_sasl[k], REC_msg[k] return } if (out_format == "json") { printf "{" printf "\"time\":\"%s\",", jesc(REC_time[k]) printf "\"from\":\"%s\",", jesc(REC_from[k]) printf "\"to\":\"%s\",", jesc(REC_to[k]) printf "\"status\":\"%s\",", jesc(REC_status[k]) printf "\"dsn\":\"%s\",", jesc(REC_dsn[k]) printf "\"qid\":\"%s\",", jesc(REC_qid[k]) printf "\"message_id\":\"%s\",", jesc(REC_msgid[k]?REC_msgid[k]:"-") printf "\"source\":\"%s\",", jesc(REC_source[k]) if (REC_tls[k] != "" && REC_tls[k] != "-") { printf "\"tls\":\"%s\",", jesc(REC_tls[k]) printf "\"tls_proto\":\"%s\",", jesc(REC_tls_proto[k]?REC_tls_proto[k]:"-") printf "\"tls_cipher\":\"%s\",", jesc(REC_tls_cipher[k]?REC_tls_cipher[k]:"-") printf "\"tls_bits\":\"%s\",", jesc(REC_tls_bits[k]?REC_tls_bits[k]:"-") } if (debug_mode == 1 && REC_debug[k] != "") { printf "\"debug\":\"%s\",", jesc(REC_debug[k]) } printf "\"relay\":\"%s\",", jesc(REC_relay[k]) printf "\"client\":\"%s\",", jesc(REC_client[k]) printf "\"clientip\":\"%s\",", jesc(REC_clientip[k]) printf "\"orig_client\":\"%s\",", jesc(REC_orig_client[k]) printf "\"orig_clientip\":\"%s\",", jesc(REC_orig_clientip[k]) printf "\"helo\":\"%s\",", jesc(REC_helo[k]) printf "\"sasl\":\"%s\",", jesc(REC_sasl[k]) printf "\"message\":\"%s\"", jesc(REC_msg[k]) printf "}\n" return } # Blockformat print "" pl("time", REC_time[k]); pl("from", REC_from[k]); pl("to", REC_to[k]); pl("status", REC_status[k]); pl("dsn", REC_dsn[k]) pl("qid", REC_qid[k]); pl("msgid", "<" (REC_msgid[k]?REC_msgid[k]:"-") ">"); pl("relay", REC_relay[k]) pl("client", REC_client[k]); pl("clientip", REC_clientip[k]) pl("orig_client", REC_orig_client[k]); pl("orig_clientip", REC_orig_clientip[k]) pl("helo", REC_helo[k]); pl("sasl", REC_sasl[k]); pl("source", REC_source[k]) if (REC_tls[k] != "" && REC_tls[k] != "-") { pl("tls", REC_tls[k]) pl("tls_proto", (REC_tls_proto[k]?REC_tls_proto[k]:"-")) pl("tls_cipher", (REC_tls_cipher[k]?REC_tls_cipher[k]:"-")) pl("tls_bits", (REC_tls_bits[k]?REC_tls_bits[k]:"-")) } else if (no_note != 1) { pl("note", "TLS-Info im Log nicht eindeutig korrelierbar") } if (debug_mode == 1 && REC_debug[k] != "") { pl("debug", REC_debug[k]) } if (REC_msg[k] != "") print "\n" REC_msg[k] } ############################################################################### # Dedup Speicherlogik ############################################################################### function store_record(key, score, idx, time, from, to, status, dsn, qid, msgid, relay, client, clientip, orig_client, orig_clientip, helo, sasl, source, tls, tls_proto, tls_cipher, tls_bits, msg, debug) { if (follow_mode == 1) { # Live: first wins, direkt emit if (!(key in BEST_score)) { BEST_score[key] = score BEST_idx[key] = idx REC_time[key]=time; REC_from[key]=from; REC_to[key]=to; REC_status[key]=status; REC_dsn[key]=dsn REC_qid[key]=qid; REC_msgid[key]=msgid; REC_relay[key]=relay; REC_client[key]=client; REC_clientip[key]=clientip REC_orig_client[key]=orig_client; REC_orig_clientip[key]=orig_clientip; REC_helo[key]=helo; REC_sasl[key]=sasl REC_source[key]=source; REC_tls[key]=tls; REC_tls_proto[key]=tls_proto; REC_tls_cipher[key]=tls_cipher; REC_tls_bits[key]=tls_bits REC_msg[key]=msg; REC_debug[key]=debug emit_record(key) } return } # Historisch: best-of nach score if (!(key in BEST_score) || score > BEST_score[key]) { BEST_score[key] = score BEST_idx[key] = idx REC_time[key]=time; REC_from[key]=from; REC_to[key]=to; REC_status[key]=status; REC_dsn[key]=dsn REC_qid[key]=qid; REC_msgid[key]=msgid; REC_relay[key]=relay; REC_client[key]=client; REC_clientip[key]=clientip REC_orig_client[key]=orig_client; REC_orig_clientip[key]=orig_clientip; REC_helo[key]=helo; REC_sasl[key]=sasl REC_source[key]=source; REC_tls[key]=tls; REC_tls_proto[key]=tls_proto; REC_tls_cipher[key]=tls_cipher; REC_tls_bits[key]=tls_bits REC_msg[key]=msg; REC_debug[key]=debug } } END { if (dedup == 1 && follow_mode == 0) { # Ausgabe in Einlesereihenfolge: sortiere keys nach BEST_idx (einfaches insertion sort) n=0 for (k in BEST_idx) { n++; KEYS[n]=k } for (i=2; i<=n; i++) { k=KEYS[i]; j=i-1 while (j>=1 && BEST_idx[KEYS[j]] > BEST_idx[k]) { KEYS[j+1]=KEYS[j]; j-- } KEYS[j+1]=k } for (i=1; i<=n; i++) emit_record(KEYS[i]) } } ############################################################################### # Hauptparser: pro Logzeile State sammeln, bei LMTP status= ausgeben ############################################################################### { ts = $1 # Zeitraumfilter (prefix match; ISO8601) if (since_prefix != "" && index(ts, since_prefix) != 1) next if (until_prefix != "" && ts >= until_prefix) exit # TLS: keine qid -> per IP merken if ($0 ~ /TLS connection established from /) { remember_tls($0); next } # Postfix Queue-ID (qid) aus Zeile ziehen qid = grab(" ([A-F0-9]{7,20}):", $0) if (qid == "") next if (qid_filter != "" && index(qid, qid_filter) == 0) next # Envelope from/to sammeln if ($0 ~ / from=<[^>]*>/) FROM[qid] = grab(" from=<([^>]*)>", $0) if ($0 ~ / to=<[^>]*>/) TO[qid] = grab(" to=<([^>]*)>", $0) # cleanup: Header Message-ID sammeln + Ursprung/TLS an msgid binden if ($0 ~ / postfix\/cleanup\[/ && $0 ~ / message-id=<[^>]+>/) { MID[qid] = grab(" message-id=<([^>]+)>", $0) # Externen Ursprung puffern und an msgid binden (für spätere qids) if (MID[qid] != "" && TMP_EXTCLIENT[qid] != "") { EXTCLIENT_BY_MSGID[MID[qid]] = TMP_EXTCLIENT[qid] EXTCLIENTIP_BY_MSGID[MID[qid]] = TMP_EXTCLIENTIP[qid] TMP_EXTCLIENT[qid] = ""; TMP_EXTCLIENTIP[qid] = "" } # TLS an msgid binden (wenn TLS schon auf qid bekannt ist) if (MID[qid] != "" && TLS[qid] != "" && TLS[qid] != "-") { TLS_BY_MSGID[MID[qid]] = TLS[qid] TLS_P_BY_MSGID[MID[qid]] = (TLS_P[qid] ? TLS_P[qid] : "-") TLS_C_BY_MSGID[MID[qid]] = (TLS_C[qid] ? TLS_C[qid] : "-") TLS_B_BY_MSGID[MID[qid]] = (TLS_B[qid] ? TLS_B[qid] : "-") } } # pickup uid: lokal erzeugt if ($0 ~ / postfix\/pickup\[/ && $0 ~ / uid=/) UID[qid] = grab(" uid=([0-9]+)", $0) # smtpd: client/orig_client, HELO fallback, TLS map via clientip if ($0 ~ / postfix\/smtpd\[/ && $0 ~ / client=/) { CLIENT[qid] = grab(" client=([^\\[]+)", $0) CLIENTIP[qid] = grab(" client=[^\\[]+\\[([^\\]]+)\\]", $0) if ($0 ~ / orig_client=/) { ORIGCLIENT[qid] = grab(" orig_client=([^\\[]+)", $0) ORIGCLIENTIP[qid] = grab(" orig_client=[^\\[]+\\[([^\\]]+)\\]", $0) } # externen Ursprung puffern bis cleanup (msgid) kommt if (!is_local_ip(CLIENTIP[qid])) { TMP_EXTCLIENT[qid] = CLIENT[qid] TMP_EXTCLIENTIP[qid] = CLIENTIP[qid] } # HELO auslesen (falls vorhanden), sonst fallback auf client host if ($0 ~ / helo=/) { he = grab(" helo=<([^>]+)>", $0) if (he == "") he = grab(" helo=([^ ,;]+)", $0) if (he != "") HELO[qid] = he } if (!HELO[qid] && CLIENT[qid] != "") HELO[qid] = CLIENT[qid] # TLS pro clientip übernehmen ip = CLIENTIP[qid] if (ip != "" && TLS_SEEN[ip]) { TLS[qid]="yes"; TLS_P[qid]=TLS_PROTO[ip]; TLS_C[qid]=TLS_CIPHER[ip]; TLS_B[qid]=TLS_BITS[ip] } else { if (!is_local_ip(ip)) TLS[qid]="no" } } # SMTP AUTH User if ($0 ~ / postfix\/smtpd\[/ && $0 ~ / sasl_username=/) SASL[qid] = grab(" sasl_username=([^, ]+)", $0) # ===================== LMTP Outcome (Ausgabezeitpunkt) ===================== if ($0 ~ / postfix\/lmtp\[/ && $0 ~ / status=/) { status = grab(" status=([^ ]+)", $0) # Statusfilter if (success_only == 1 && !is_success(status)) next if (fail_only == 1 && is_success(status)) next from = (FROM[qid] ? FROM[qid] : "-") to = (TO[qid] ? TO[qid] : "-") mid = (MID[qid] ? MID[qid] : "") # Allgemeine Filter if (msgid_filter != "" && (mid == "" || index(mid, msgid_filter) == 0)) next if (addr_filter != "" && index(from, addr_filter)==0 && index(to, addr_filter)==0) next if (from_filter != "" && index(from, from_filter)==0) next if (to_filter != "" && index(to, to_filter)==0) next dsn = grab(" dsn=([^, ]+)", $0) relay = grab(" relay=([^, ]+)", $0) msg = grab(" status=[^ ]+ \\((.*)\\)$", $0) client = (CLIENT[qid] ? CLIENT[qid] : "-") clientip = (CLIENTIP[qid] ? CLIENTIP[qid] : "-") helo = (HELO[qid] ? HELO[qid] : "-") sasl = (SASL[qid] ? SASL[qid] : "-") uid = (UID[qid] ? UID[qid] : "") # SASL Filter if (sasl_only == 1 && (sasl == "" || sasl == "-")) next if (no_sasl == 1 && (sasl != "" && sasl != "-")) next # Ursprung via msgid zurückführen (Amavis) 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]:"-")) # TLS: qid oder msgid-Vererbung tls = (TLS[qid] ? TLS[qid] : "") tls_proto = (TLS_P[qid] ? TLS_P[qid] : "") tls_cipher= (TLS_C[qid] ? TLS_C[qid] : "") tls_bits = (TLS_B[qid] ? TLS_B[qid] : "") if (tls == "" && mid != "" && TLS_BY_MSGID[mid] != "") { tls = TLS_BY_MSGID[mid] tls_proto = TLS_P_BY_MSGID[mid] tls_cipher= TLS_C_BY_MSGID[mid] tls_bits = TLS_B_BY_MSGID[mid] } source = classify_source(qid, mid, relay) # inbound/outbound Filter + Debug-Grund debug = "" if (debug_mode == 1) { debug = "outbound_reason=" outbound_reason(sasl, uid, orig_clientip, source) \ " | inbound_reason=" inbound_reason(sasl, orig_clientip, source) } if (inbound_only == 1 && !is_inbound(sasl, orig_clientip, source)) next if (outbound_only == 1 && !is_outbound(sasl, uid, orig_clientip, source)) next # Dedup oder Direktausgabe if (dedup == 1) { idx = ++SEQ key = (mid != "" ? mid : qid) SUBSEP to score = dedup_score(relay, idx) store_record(key, score, idx, ts, from, to, status, (dsn?dsn:"-"), qid, (mid?mid:"-"), (relay?relay:"-"), client, clientip, orig_client, orig_clientip, helo, sasl, source, (tls!=""?tls:""), (tls_proto!=""?tls_proto:""), (tls_cipher!=""?tls_cipher:""), (tls_bits!=""?tls_bits:""), msg, debug) next } k="__direct__" REC_time[k]=ts; REC_from[k]=from; REC_to[k]=to; REC_status[k]=status; REC_dsn[k]=(dsn?dsn:"-") REC_qid[k]=qid; REC_msgid[k]=(mid?mid:"-"); REC_relay[k]=(relay?relay:"-") REC_client[k]=client; REC_clientip[k]=clientip REC_orig_client[k]=orig_client; REC_orig_clientip[k]=orig_clientip REC_helo[k]=helo; REC_sasl[k]=sasl; REC_source[k]=source REC_tls[k]=(tls!=""?tls:""); REC_tls_proto[k]=(tls_proto!=""?tls_proto:""); REC_tls_cipher[k]=(tls_cipher!=""?tls_cipher:""); REC_tls_bits[k]=(tls_bits!=""?tls_bits:"") REC_msg[k]=msg; REC_debug[k]=debug emit_record(k) } } '