- Make SCRIPT_DIR robust for symlink execution - Resolves lib/ dependency loading when called via ~/bin symlink - Maintains compatibility with direct execution 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
182 lines
5.8 KiB
Bash
Executable File
182 lines
5.8 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# exitrace - Exim-Log-Trace & Status für einzelne Mails/Adressen/Domain-Ziele
|
|
# Maintainer: ljp
|
|
# Modular version - requires lib/ directory
|
|
# -----------------------------------------------------------------------------
|
|
# Features:
|
|
# - Sucht in /var/log/easyname/YYYY/MM/YYYY-MM-DD.log(.gz) (anpassbar mit $LOGROOT)
|
|
# - Zeitfenster: letzte N Stunden/Tage ODER konkretes Datum/Spanne
|
|
# - Filter: Absender (-f), Empfänger-Regex (-t), Ziel-Domain (-D), Message-ID (-m), Extra-Regex (-G)
|
|
# - Gruppiert Treffer pro Exim-Message-ID und fasst Status zusammen
|
|
# - Klassifikation: OK / PARTIAL / DELAY / FAIL / UNKNOWN
|
|
# - Optional: MX-Übersicht für -D/--domain mittels --mx
|
|
#
|
|
# Beispiele:
|
|
# exitrace -f sender@example.com -D example.de -H 48
|
|
# exitrace -m 1uwRvz-000Fz2-CI
|
|
# exitrace -t '.*@example.de' -r 2025-09-09..2025-09-11 --mx
|
|
#
|
|
# Exitcodes: 0=OK (Treffer evtl. 0), 1=Fehlerhafte Parameter/Umgebung
|
|
# -----------------------------------------------------------------------------
|
|
|
|
set -euo pipefail
|
|
|
|
# Script directory (symlink-robust)
|
|
SCRIPT_DIR="$(cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")" && pwd)"
|
|
|
|
# Load modules
|
|
if [[ -f "$SCRIPT_DIR/lib/date_utils.sh" ]]; then
|
|
source "$SCRIPT_DIR/lib/date_utils.sh"
|
|
else
|
|
echo "Fehler: lib/date_utils.sh nicht gefunden in $SCRIPT_DIR" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [[ -f "$SCRIPT_DIR/lib/mx_lookup.sh" ]]; then
|
|
source "$SCRIPT_DIR/lib/mx_lookup.sh"
|
|
else
|
|
echo "Fehler: lib/mx_lookup.sh nicht gefunden in $SCRIPT_DIR" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Global settings
|
|
LOGROOT="${LOGROOT:-/var/log/easyname}"
|
|
TODAY="$(date +%Y-%m-%d)"
|
|
|
|
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):
|
|
-f, --from ADDR Envelope-From / "from <...>"
|
|
-t, --to REGEX Empfänger (Regex, matcht ' for <rcpt>' oder in '** rcpt')
|
|
-D, --domain NAME Ziel-Domain (z. B. example.de)
|
|
-m, --msgid ID Exim-Message-ID (z. B. 1uwRvz-000Fz2-CI)
|
|
-G, --grep REGEX Zusätzlicher Regex auf Logzeilen
|
|
|
|
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 -f sender@example.com -D example.de -H 48
|
|
exitrace -m 1uwRvz-000Fz2-CI
|
|
exitrace -t '.*@example.de' -r 2025-09-09..2025-09-11 --mx
|
|
USAGE
|
|
}
|
|
|
|
# --------------------------- Argumente parsen --------------------------------
|
|
HOURS=""; DAYS=""; DATE_ONE=""; DATE_RANGE=""
|
|
FROM=""; TO_REGEX=""; TARGET_DOMAIN=""; MSGID=""; GREPEXTRA=""
|
|
DRYRUN=0; WANT_MX=0
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
-H|--hours)
|
|
HOURS="${2:-}"
|
|
validate_positive_integer "$HOURS" "--hours"
|
|
shift 2 ;;
|
|
-DAYS|--days)
|
|
DAYS="${2:-}"
|
|
validate_positive_integer "$DAYS" "--days"
|
|
shift 2 ;;
|
|
-d|--date)
|
|
DATE_ONE="${2:-}"
|
|
validate_date "$DATE_ONE" "--date"
|
|
shift 2 ;;
|
|
-r|--range)
|
|
DATE_RANGE="${2:-}"
|
|
validate_date_range "$DATE_RANGE"
|
|
shift 2 ;;
|
|
-f|--from) FROM="${2:-}"; shift 2 ;;
|
|
-t|--to) TO_REGEX="${2:-}"; shift 2 ;;
|
|
-D|--domain) TARGET_DOMAIN="${2:-}"; shift 2 ;;
|
|
-m|--msgid) MSGID="${2:-}"; shift 2 ;;
|
|
-G|--grep) GREPEXTRA="${2:-}"; shift 2 ;;
|
|
--mx) WANT_MX=1; shift ;;
|
|
-n|--dry-run) DRYRUN=1; shift ;;
|
|
-h|--help) usage; exit 0 ;;
|
|
*) echo "Unbekannte Option: $1" >&2; usage; exit 1 ;;
|
|
esac
|
|
done
|
|
|
|
# ------------------------- Datums-Liste bauen --------------------------------
|
|
declare -a DATES=()
|
|
if [[ -n "$HOURS" ]]; then
|
|
mapfile -t DATES < <(gen_dates_hours_back "$HOURS")
|
|
elif [[ -n "$DAYS" ]]; then
|
|
mapfile -t DATES < <(gen_dates_days_back "$DAYS")
|
|
elif [[ -n "$DATE_ONE" ]]; then
|
|
DATES=("$DATE_ONE")
|
|
elif [[ -n "$DATE_RANGE" ]]; then
|
|
mapfile -t DATES < <(gen_dates_range "$DATE_RANGE")
|
|
else
|
|
DATES=("$TODAY")
|
|
fi
|
|
|
|
declare -a FILES=()
|
|
for d in "${DATES[@]}"; do
|
|
while read -r f; do FILES+=("$f"); done < <(files_for_date "$d" || true)
|
|
done
|
|
|
|
if (( DRYRUN )); then
|
|
echo "Würde durchsuchen:"
|
|
printf ' %s\n' "${FILES[@]}"
|
|
exit 0
|
|
fi
|
|
|
|
if [[ "${#FILES[@]}" -eq 0 ]]; then
|
|
echo "Keine Logfiles gefunden unter ${LOGROOT}/YYYY/MM/DATE.log(.gz) fürs gewählte Zeitfenster." >&2
|
|
exit 0
|
|
fi
|
|
|
|
# --------------------------- Vorfilter bauen ---------------------------------
|
|
declare -a PREF_GREP=()
|
|
[[ -n "$MSGID" ]] && PREF_GREP+=( "$MSGID" )
|
|
[[ -n "$FROM" ]] && PREF_GREP+=( "$FROM" )
|
|
[[ -n "$TARGET_DOMAIN" ]] && PREF_GREP+=( "$TARGET_DOMAIN" )
|
|
[[ -n "$GREPEXTRA" ]] && PREF_GREP+=( "$GREPEXTRA" )
|
|
|
|
# ------------------------------- Reader --------------------------------------
|
|
cat_files() {
|
|
# .log direkt lesen, .gz via zcat
|
|
local f
|
|
for f in "${FILES[@]}"; do
|
|
if [[ "$f" == *.gz ]]; then
|
|
zcat -- "$f"
|
|
else
|
|
cat -- "$f"
|
|
fi
|
|
done
|
|
}
|
|
|
|
grep_chain() {
|
|
if [[ "${#PREF_GREP[@]}" -eq 0 ]]; then
|
|
cat_files
|
|
else
|
|
# Mehrere -F -e Muster
|
|
cat_files | grep -F -e "${PREF_GREP[0]}" $(for s in "${PREF_GREP[@]:1}"; do printf ' -e %q' "$s"; done)
|
|
fi
|
|
}
|
|
|
|
# ------------------------------- MX Block ------------------------------------
|
|
if (( WANT_MX )) && [[ -n "$TARGET_DOMAIN" ]]; then
|
|
show_mx_records "$TARGET_DOMAIN"
|
|
fi
|
|
|
|
# ---------------------------- Auswertung (awk) --------------------------------
|
|
if [[ ! -f "$SCRIPT_DIR/lib/log_analyzer.awk" ]]; then
|
|
echo "Fehler: lib/log_analyzer.awk nicht gefunden in $SCRIPT_DIR" >&2
|
|
exit 1
|
|
fi
|
|
|
|
grep_chain | awk -v TO_RE="${TO_REGEX:-}" -v WANTDOM="${TARGET_DOMAIN:-}" -v MSGIDREQ="${MSGID:-}" -v FROMREQ="${FROM:-}" -v EXTRA="${GREPEXTRA:-}" -f "$SCRIPT_DIR/lib/log_analyzer.awk"
|
|
|
|
# Built with the help of Claude Code |