Replace <= operator with explicit 'less than OR equal' logic in bash [[ ]] context. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
570 lines
16 KiB
Bash
Executable File
570 lines
16 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# exitrace - Remote Exim-Log-Trace & Status für einzelne Mails/Adressen/Domain-Ziele
|
|
# Maintainer: ljp
|
|
# -----------------------------------------------------------------------------
|
|
# Features:
|
|
# - Läuft lokal auf logger.in.nessus.at (wo die Exim-Logs liegen)
|
|
# - Sucht in /var/log/easyname/YYYY/MM/YYYY-MM-DD.log(.gz)
|
|
# - Zeitfenster: letzte N Stunden/Tage ODER konkretes Datum/Spanne
|
|
# - Filter: Absender (-FROM), Empfänger-Regex (-TO), Ziel-Domain (-DOMAIN), Message-ID (-MSGID), Extra-Regex (-GREP)
|
|
# - Gruppiert Treffer pro Exim-Message-ID und fasst Status zusammen
|
|
# - Klassifikation: OK / PARTIAL / DELAY / FAIL / UNKNOWN
|
|
# - Optional: MX-Übersicht für -DOMAIN mittels --mx
|
|
#
|
|
# Deployment: Mit deploy.sh auf logger.in.nessus.at installieren
|
|
# Usage: ssh logger.in.nessus.at exitrace [OPTS]
|
|
#
|
|
# Beispiele:
|
|
# exitrace -FROM sender@example.com -DOMAIN example.de -H 48
|
|
# exitrace -MSGID 1uwRvz-000Fz2-CI
|
|
# exitrace -TO '.*@example.de' -r 2025-09-09..2025-09-11 --mx
|
|
#
|
|
# Exitcodes: 0=OK (Treffer evtl. 0), 1=Fehlerhafte Parameter/Umgebung
|
|
# -----------------------------------------------------------------------------
|
|
|
|
set -euo pipefail
|
|
|
|
# Standardwerte
|
|
HOURS=""
|
|
DAYS=""
|
|
DATE=""
|
|
RANGE=""
|
|
FROM=""
|
|
TO=""
|
|
DOMAIN=""
|
|
MSGID=""
|
|
GREP=""
|
|
MX=0
|
|
DRY_RUN=0
|
|
|
|
usage() {
|
|
cat <<'USAGE'
|
|
Usage: exitrace [OPTS]
|
|
|
|
Zeitfenster (eine Option wählen):
|
|
-H, --hours N Letzte N Stunden durchsuchen
|
|
-DAYS, --days N Letzte N Tage durchsuchen
|
|
-d, --date YYYY-MM-DD Exaktes Datum
|
|
-r, --range A..B Datumsbereich (inkl.): YYYY-MM-DD..YYYY-MM-DD
|
|
|
|
Filter (beliebig kombinierbar):
|
|
-FROM, --from ADDR Envelope-From / "from <...>" (alias: -f)
|
|
-TO, --to REGEX Empfänger (Regex, matcht ' for <rcpt>' oder in '** rcpt') (alias: -t)
|
|
-DOMAIN, --domain NAME Ziel-Domain (z. B. example.de) (alias: -D)
|
|
-MSGID, --msgid ID Exim-Message-ID (z. B. 1uwRvz-000Fz2-CI) (alias: -m)
|
|
-GREP, --grep REGEX Zusätzlicher Regex auf Logzeilen (alias: -G)
|
|
|
|
Sonstiges:
|
|
--mx Zeigt MX-Einträge (und A/AAAA) für --domain
|
|
-n, --dry-run Zeigt nur, welche Logfiles untersucht würden
|
|
-h, --help Diese Hilfe
|
|
|
|
Beispiele:
|
|
exitrace -FROM sender@example.com -DOMAIN example.de -H 48
|
|
exitrace -MSGID 1uwRvz-000Fz2-CI
|
|
exitrace -TO '.*@example.de' -r 2025-09-09..2025-09-11 --mx
|
|
|
|
Backwards Compatibility:
|
|
exitrace -f sender@domain.com -D example.de -H 48
|
|
exitrace -m 1uwRvz-000Fz2-CI
|
|
exitrace -t '.*@gmail\\.com' --mx
|
|
USAGE
|
|
}
|
|
|
|
# Parse Arguments
|
|
while [[ $# -gt 0 ]]; do
|
|
case $1 in
|
|
-H|--hours)
|
|
HOURS="$2"
|
|
[[ "$HOURS" =~ ^[0-9]+$ ]] || { echo "Fehler: --hours benötigt eine Zahl" >&2; exit 1; }
|
|
shift 2
|
|
;;
|
|
-DAYS|--days)
|
|
DAYS="$2"
|
|
[[ "$DAYS" =~ ^[0-9]+$ ]] || { echo "Fehler: --days benötigt eine Zahl" >&2; exit 1; }
|
|
shift 2
|
|
;;
|
|
-d|--date)
|
|
DATE="$2"
|
|
[[ "$DATE" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]] || { echo "Fehler: --date benötigt YYYY-MM-DD" >&2; exit 1; }
|
|
shift 2
|
|
;;
|
|
-r|--range)
|
|
RANGE="$2"
|
|
[[ "$RANGE" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}\.\.[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]] || { echo "Fehler: --range benötigt YYYY-MM-DD..YYYY-MM-DD" >&2; exit 1; }
|
|
shift 2
|
|
;;
|
|
-FROM|--from|-f)
|
|
FROM="$2"
|
|
shift 2
|
|
;;
|
|
-TO|--to|-t)
|
|
TO="$2"
|
|
shift 2
|
|
;;
|
|
-DOMAIN|--domain|-D)
|
|
DOMAIN="$2"
|
|
shift 2
|
|
;;
|
|
-MSGID|--msgid|-m)
|
|
MSGID="$2"
|
|
shift 2
|
|
;;
|
|
-GREP|--grep|-G)
|
|
GREP="$2"
|
|
shift 2
|
|
;;
|
|
--mx)
|
|
MX=1
|
|
shift
|
|
;;
|
|
-n|--dry-run)
|
|
DRY_RUN=1
|
|
shift
|
|
;;
|
|
-h|--help)
|
|
usage
|
|
exit 0
|
|
;;
|
|
*)
|
|
echo "Unbekannte Option: $1" >&2
|
|
usage
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
# Validierung: Nur ein Zeitfenster
|
|
time_options=0
|
|
[[ -n "$HOURS" ]] && ((time_options++))
|
|
[[ -n "$DAYS" ]] && ((time_options++))
|
|
[[ -n "$DATE" ]] && ((time_options++))
|
|
[[ -n "$RANGE" ]] && ((time_options++))
|
|
|
|
if [[ $time_options -gt 1 ]]; then
|
|
echo "Fehler: Nur ein Zeitfenster (-H/-DAYS/-d/-r) erlaubt" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Default: Heutiges Datum
|
|
if [[ $time_options -eq 0 ]]; then
|
|
DATE=$(date +%Y-%m-%d)
|
|
fi
|
|
|
|
# Datum-Validierung für DATE
|
|
validate_date() {
|
|
local d="$1"
|
|
if ! date -d "$d" >/dev/null 2>&1; then
|
|
echo "Fehler: Ungültiges Datum: $d" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# Bestimme Logfiles basierend auf Zeitfenster
|
|
determine_logfiles() {
|
|
local logfiles=()
|
|
|
|
if [[ -n "$DATE" ]]; then
|
|
validate_date "$DATE"
|
|
local year month
|
|
year=$(date -d "$DATE" +%Y)
|
|
month=$(date -d "$DATE" +%m)
|
|
logfiles+=("/var/log/easyname/$year/$month/$DATE.log")
|
|
|
|
elif [[ -n "$RANGE" ]]; then
|
|
local start_date end_date
|
|
start_date="${RANGE%%..*}"
|
|
end_date="${RANGE##*..}"
|
|
validate_date "$start_date"
|
|
validate_date "$end_date"
|
|
|
|
local current_date="$start_date"
|
|
# Hinweis: In [[ ]] gibt es kein '<=' String-Operator.
|
|
# Daher explizit 'kleiner als ODER gleich' prüfen.
|
|
while [[ "$current_date" < "$end_date" || "$current_date" == "$end_date" ]]; do
|
|
local year month
|
|
year=$(date -d "$current_date" +%Y)
|
|
month=$(date -d "$current_date" +%m)
|
|
logfiles+=("/var/log/easyname/$year/$month/$current_date.log")
|
|
current_date=$(date -d "$current_date + 1 day" +%Y-%m-%d)
|
|
done
|
|
|
|
elif [[ -n "$HOURS" ]]; then
|
|
# Bestimme Datumsbereich für letzte N Stunden
|
|
local start_ts end_ts
|
|
end_ts=$(date +%s)
|
|
start_ts=$((end_ts - HOURS * 3600))
|
|
|
|
local current_ts="$start_ts"
|
|
local seen_dates=()
|
|
|
|
while [[ $current_ts -le $end_ts ]]; do
|
|
local date_str
|
|
date_str=$(date -d "@$current_ts" +%Y-%m-%d)
|
|
|
|
# Füge Datum nur einmal hinzu
|
|
local found=0
|
|
for seen in "${seen_dates[@]}"; do
|
|
[[ "$seen" == "$date_str" ]] && found=1 && break
|
|
done
|
|
|
|
if [[ $found -eq 0 ]]; then
|
|
seen_dates+=("$date_str")
|
|
local year month
|
|
year=$(date -d "$date_str" +%Y)
|
|
month=$(date -d "$date_str" +%m)
|
|
logfiles+=("/var/log/easyname/$year/$month/$date_str.log")
|
|
fi
|
|
|
|
current_ts=$((current_ts + 86400)) # +1 Tag
|
|
done
|
|
|
|
elif [[ -n "$DAYS" ]]; then
|
|
# Bestimme Datumsbereich für letzte N Tage
|
|
for ((i=0; i<DAYS; i++)); do
|
|
local date_str
|
|
date_str=$(date -d "-$i days" +%Y-%m-%d)
|
|
local year month
|
|
year=$(date -d "$date_str" +%Y)
|
|
month=$(date -d "$date_str" +%m)
|
|
logfiles+=("/var/log/easyname/$year/$month/$date_str.log")
|
|
done
|
|
fi
|
|
|
|
printf '%s\n' "${logfiles[@]}"
|
|
}
|
|
|
|
# MX Record Lookup
|
|
show_mx_records() {
|
|
[[ -n "$DOMAIN" ]] || return 0
|
|
[[ $MX -eq 1 ]] || return 0
|
|
|
|
if ! command -v dig >/dev/null 2>&1; then
|
|
echo "Warnung: 'dig' nicht gefunden - MX-Records werden übersprungen" >&2
|
|
return 0
|
|
fi
|
|
|
|
echo "== MX für $DOMAIN =="
|
|
|
|
# MX Records
|
|
local mx_records
|
|
mx_records=$(dig +short MX "$DOMAIN" | sort -n)
|
|
|
|
if [[ -z "$mx_records" ]]; then
|
|
echo "Keine MX-Records gefunden"
|
|
else
|
|
echo "$mx_records"
|
|
|
|
# A/AAAA Records für MX-Hosts
|
|
echo "-- A/AAAA der MX:"
|
|
while read -r priority mx_host; do
|
|
[[ -n "$mx_host" ]] || continue
|
|
mx_host="${mx_host%.}" # Trailing dot entfernen
|
|
|
|
local a_records aaaa_records all_records
|
|
a_records=$(dig +short A "$mx_host" | paste -sd',' -)
|
|
aaaa_records=$(dig +short AAAA "$mx_host" | paste -sd',' -)
|
|
|
|
all_records=""
|
|
[[ -n "$a_records" ]] && all_records="$a_records"
|
|
[[ -n "$aaaa_records" ]] && {
|
|
[[ -n "$all_records" ]] && all_records="$all_records,"
|
|
all_records="$all_records$aaaa_records"
|
|
}
|
|
|
|
printf "%-40s | %s\n" "$mx_host" "${all_records:-<keine A/AAAA Records>}"
|
|
done <<< "$mx_records"
|
|
fi
|
|
|
|
echo "========================================================================"
|
|
}
|
|
|
|
# Log Analysis - der Hauptalgorithmus
|
|
analyze_logs() {
|
|
local logfiles=()
|
|
mapfile -t logfiles < <(determine_logfiles)
|
|
|
|
if [[ $DRY_RUN -eq 1 ]]; then
|
|
echo "=== DRY-RUN: Würde folgende Logfiles untersuchen ==="
|
|
for logfile in "${logfiles[@]}"; do
|
|
if [[ -f "$logfile" ]]; then
|
|
echo " [EXISTS] $logfile"
|
|
elif [[ -f "$logfile.gz" ]]; then
|
|
echo " [EXISTS] $logfile.gz (komprimiert)"
|
|
else
|
|
echo " [MISSING] $logfile(.gz)"
|
|
fi
|
|
done
|
|
return 0
|
|
fi
|
|
|
|
# MX Records anzeigen falls gewünscht
|
|
show_mx_records
|
|
|
|
# Sammle alle relevanten Logzeilen
|
|
local temp_file
|
|
temp_file=$(mktemp)
|
|
trap "rm -f '$temp_file'" EXIT
|
|
|
|
for logfile in "${logfiles[@]}"; do
|
|
local cat_cmd=""
|
|
|
|
if [[ -f "$logfile" ]]; then
|
|
cat_cmd="cat '$logfile'"
|
|
elif [[ -f "$logfile.gz" ]]; then
|
|
cat_cmd="zcat '$logfile.gz'"
|
|
else
|
|
continue
|
|
fi
|
|
|
|
# Zeitfilter für Stunden/Tage
|
|
local time_filter=""
|
|
if [[ -n "$HOURS" ]]; then
|
|
local start_time
|
|
start_time=$(date -d "-$HOURS hours" '+%b %_d %H:%M')
|
|
time_filter="awk -v start='$start_time' '\$0 ~ start,0'"
|
|
elif [[ -n "$DAYS" && $DAYS -lt 7 ]]; then # Nur für kleine Tageszahlen
|
|
local start_time
|
|
start_time=$(date -d "-$DAYS days" '+%b %_d')
|
|
time_filter="awk -v start='$start_time' '\$0 ~ start,0'"
|
|
fi
|
|
|
|
# Baue Grep-Filter Pipeline
|
|
local filter_pipeline=""
|
|
|
|
# Basis-Filter: Envelope-From
|
|
[[ -n "$FROM" ]] && filter_pipeline="$filter_pipeline | grep -F ' <= $FROM '"
|
|
|
|
# TO-Filter: Empfänger-Regex
|
|
[[ -n "$TO" ]] && filter_pipeline="$filter_pipeline | grep -E '( for <.*$TO.*>|\\*\\* .*$TO)'"
|
|
|
|
# Domain-Filter: Ziel-Domain
|
|
[[ -n "$DOMAIN" ]] && filter_pipeline="$filter_pipeline | grep -E '(@$DOMAIN>|to $DOMAIN|for $DOMAIN)'"
|
|
|
|
# Message-ID Filter
|
|
[[ -n "$MSGID" ]] && filter_pipeline="$filter_pipeline | grep -F '$MSGID'"
|
|
|
|
# Zusätzlicher Regex
|
|
[[ -n "$GREP" ]] && filter_pipeline="$filter_pipeline | grep -E '$GREP'"
|
|
|
|
# Kommando zusammensetzen und ausführen
|
|
local full_cmd="$cat_cmd"
|
|
[[ -n "$time_filter" ]] && full_cmd="$full_cmd | $time_filter"
|
|
full_cmd="$full_cmd$filter_pipeline"
|
|
|
|
eval "$full_cmd" >> "$temp_file" 2>/dev/null || true
|
|
done
|
|
|
|
# Verarbeite gesammelte Logzeilen
|
|
process_log_entries "$temp_file"
|
|
}
|
|
|
|
# Verarbeitung der Logeinträge und Gruppierung nach Message-ID
|
|
process_log_entries() {
|
|
local input_file="$1"
|
|
|
|
[[ -s "$input_file" ]] || {
|
|
echo "Keine passenden Logeinträge gefunden."
|
|
return 0
|
|
}
|
|
|
|
# AWK-Script für Gruppierung und Status-Klassifikation
|
|
awk '
|
|
BEGIN {
|
|
# Arrays für Message-ID Daten
|
|
# msgid_data[id]["first"] = erste Zeitstempel
|
|
# msgid_data[id]["last"] = letzte Zeitstempel
|
|
# msgid_data[id]["from"] = Absender
|
|
# msgid_data[id]["rcpts"] = Empfänger (getrennt durch ",")
|
|
# msgid_data[id]["delivered"] = Anzahl erfolgreich zugestellt
|
|
# msgid_data[id]["deferred"] = Anzahl wartend
|
|
# msgid_data[id]["failed"] = Anzahl fehlgeschlagen
|
|
# msgid_data[id]["logs"] = Alle Logzeilen (getrennt durch "\n")
|
|
}
|
|
|
|
# Extrahiere Message-ID aus jeder Zeile
|
|
function extract_msgid(line) {
|
|
# Exim Message-ID Format: 1uwRvz-000Fz2-CI
|
|
match(line, /[0-9a-zA-Z]{6}-[0-9a-zA-Z]{6}-[0-9a-zA-Z]{2}/)
|
|
if (RSTART > 0) {
|
|
return substr(line, RSTART, RLENGTH)
|
|
}
|
|
return ""
|
|
}
|
|
|
|
# Extrahiere Zeitstempel (Format: 2025-09-11 08:32:15)
|
|
function extract_timestamp(line) {
|
|
match(line, /^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}/)
|
|
if (RSTART > 0) {
|
|
return substr(line, RSTART, RLENGTH)
|
|
}
|
|
return ""
|
|
}
|
|
|
|
# Hauptverarbeitung jeder Logzeile
|
|
{
|
|
msgid = extract_msgid($0)
|
|
if (msgid == "") next
|
|
|
|
timestamp = extract_timestamp($0)
|
|
|
|
# Initialisiere Message-ID falls neu
|
|
if (!(msgid in msgid_data)) {
|
|
msgid_data[msgid]["first"] = timestamp
|
|
msgid_data[msgid]["last"] = timestamp
|
|
msgid_data[msgid]["from"] = ""
|
|
msgid_data[msgid]["rcpts"] = ""
|
|
msgid_data[msgid]["delivered"] = 0
|
|
msgid_data[msgid]["deferred"] = 0
|
|
msgid_data[msgid]["failed"] = 0
|
|
msgid_data[msgid]["logs"] = ""
|
|
}
|
|
|
|
# Aktualisiere Zeitstempel
|
|
if (timestamp < msgid_data[msgid]["first"] || msgid_data[msgid]["first"] == "") {
|
|
msgid_data[msgid]["first"] = timestamp
|
|
}
|
|
if (timestamp > msgid_data[msgid]["last"] || msgid_data[msgid]["last"] == "") {
|
|
msgid_data[msgid]["last"] = timestamp
|
|
}
|
|
|
|
# Füge Logzeile hinzu
|
|
if (msgid_data[msgid]["logs"] == "") {
|
|
msgid_data[msgid]["logs"] = timestamp " | " $0
|
|
} else {
|
|
msgid_data[msgid]["logs"] = msgid_data[msgid]["logs"] "\n" timestamp " | " $0
|
|
}
|
|
|
|
# Analysiere Logzeile für Status und Details
|
|
if ($0 ~ / <= /) {
|
|
# Eingehende Mail: Extract Sender
|
|
match($0, / <= ([^ ]+)/, arr)
|
|
if (arr[1] != "") {
|
|
msgid_data[msgid]["from"] = arr[1]
|
|
}
|
|
}
|
|
else if ($0 ~ / => /) {
|
|
# Erfolgreiche Zustellung
|
|
msgid_data[msgid]["delivered"]++
|
|
|
|
# Extrahiere Empfänger
|
|
match($0, / => ([^ ]+)/, arr)
|
|
if (arr[1] != "" && index(msgid_data[msgid]["rcpts"], arr[1]) == 0) {
|
|
if (msgid_data[msgid]["rcpts"] == "") {
|
|
msgid_data[msgid]["rcpts"] = arr[1]
|
|
} else {
|
|
msgid_data[msgid]["rcpts"] = msgid_data[msgid]["rcpts"] "," arr[1]
|
|
}
|
|
}
|
|
}
|
|
else if ($0 ~ / == /) {
|
|
# Verzögerte Zustellung (deferred)
|
|
msgid_data[msgid]["deferred"]++
|
|
|
|
# Extrahiere Empfänger
|
|
match($0, / == ([^ ]+)/, arr)
|
|
if (arr[1] != "" && index(msgid_data[msgid]["rcpts"], arr[1]) == 0) {
|
|
if (msgid_data[msgid]["rcpts"] == "") {
|
|
msgid_data[msgid]["rcpts"] = arr[1]
|
|
} else {
|
|
msgid_data[msgid]["rcpts"] = msgid_data[msgid]["rcpts"] "," arr[1]
|
|
}
|
|
}
|
|
}
|
|
else if ($0 ~ / \*\* /) {
|
|
# Fehlgeschlagene Zustellung
|
|
msgid_data[msgid]["failed"]++
|
|
|
|
# Extrahiere Empfänger
|
|
match($0, / \*\* ([^ ]+)/, arr)
|
|
if (arr[1] != "" && index(msgid_data[msgid]["rcpts"], arr[1]) == 0) {
|
|
if (msgid_data[msgid]["rcpts"] == "") {
|
|
msgid_data[msgid]["rcpts"] = arr[1]
|
|
} else {
|
|
msgid_data[msgid]["rcpts"] = msgid_data[msgid]["rcpts"] "," arr[1]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
END {
|
|
# Sortiere Message-IDs nach erstem Auftreten
|
|
n = 0
|
|
for (msgid in msgid_data) {
|
|
sorted_msgids[++n] = msgid
|
|
}
|
|
|
|
# Einfache Sortierung nach first timestamp
|
|
for (i = 1; i <= n; i++) {
|
|
for (j = i + 1; j <= n; j++) {
|
|
if (msgid_data[sorted_msgids[i]]["first"] > msgid_data[sorted_msgids[j]]["first"]) {
|
|
temp = sorted_msgids[i]
|
|
sorted_msgids[i] = sorted_msgids[j]
|
|
sorted_msgids[j] = temp
|
|
}
|
|
}
|
|
}
|
|
|
|
# Ausgabe aller gefundenen Message-IDs
|
|
for (i = 1; i <= n; i++) {
|
|
msgid = sorted_msgids[i]
|
|
|
|
# Status-Klassifikation
|
|
delivered = msgid_data[msgid]["delivered"]
|
|
deferred = msgid_data[msgid]["deferred"]
|
|
failed = msgid_data[msgid]["failed"]
|
|
|
|
if (failed > 0 && delivered == 0 && deferred == 0) {
|
|
status = "FAIL"
|
|
} else if (delivered > 0 && failed == 0 && deferred == 0) {
|
|
status = "OK"
|
|
} else if (deferred > 0 && delivered == 0 && failed == 0) {
|
|
status = "DELAY"
|
|
} else if (delivered > 0 && (failed > 0 || deferred > 0)) {
|
|
status = "PARTIAL"
|
|
} else {
|
|
status = "UNKNOWN"
|
|
}
|
|
|
|
# Kurze Übersicht
|
|
printf "[%s] ID=%s | First=%s | From=%s | Rcpts=%s | Delivered=%d Deferred=%d Failed=%d\n",
|
|
status, msgid, msgid_data[msgid]["first"], msgid_data[msgid]["from"],
|
|
msgid_data[msgid]["rcpts"], delivered, deferred, failed
|
|
|
|
# Detaillierte Ausgabe
|
|
print "------------------------------------------------------------------------"
|
|
printf "ID: %s\n", msgid
|
|
printf "First: %s\n", msgid_data[msgid]["first"]
|
|
printf "Last: %s\n", msgid_data[msgid]["last"]
|
|
printf "From: %s\n", msgid_data[msgid]["from"]
|
|
printf "Rcpts: %s\n", msgid_data[msgid]["rcpts"]
|
|
printf "Status: %s (Delivered=%d, Deferred=%d, Failed=%d)\n", status, delivered, deferred, failed
|
|
print "Log:"
|
|
print msgid_data[msgid]["logs"]
|
|
|
|
# Check für bekannte Delivery-Probleme
|
|
if (msgid_data[msgid]["logs"] ~ /all hosts for.*have been failing/) {
|
|
print " Note: DELIVERY STALL erkannt: '\''all hosts for domain failing (retry/backoff)'\''"
|
|
}
|
|
|
|
print "------------------------------------------------------------------------"
|
|
print ""
|
|
}
|
|
|
|
if (n == 0) {
|
|
print "Keine passenden Exim-Message-IDs gefunden."
|
|
}
|
|
}
|
|
' "$input_file"
|
|
}
|
|
|
|
# Main Execution
|
|
main() {
|
|
analyze_logs
|
|
}
|
|
|
|
# Script ausführen
|
|
main "$@"
|
|
|
|
# Built with the help of Claude Code
|