From 6bc490bfc149159bc58d3c0596d243642719da91 Mon Sep 17 00:00:00 2001 From: Christoph Date: Fri, 13 Feb 2026 21:43:42 +0100 Subject: [PATCH] add script 'set_amavis_local_domains_maps.sh'. --- set_amavis_local_domains_maps.sh | 285 +++++++++++++++++++++++++++++++ 1 file changed, 285 insertions(+) create mode 100755 set_amavis_local_domains_maps.sh diff --git a/set_amavis_local_domains_maps.sh b/set_amavis_local_domains_maps.sh new file mode 100755 index 0000000..715ad2d --- /dev/null +++ b/set_amavis_local_domains_maps.sh @@ -0,0 +1,285 @@ +#!/usr/bin/env bash +# amavis-local-domains-auto.sh +# +# Zweck +# ----- +# Ersetzt in /etc/amavis/conf.d/50-user die lokale Domains-Definition +# +# @local_domains_maps = ( ["."] ); +# +# durch eine dynamische Definition, die aus der (effektiv aktiven) Postfix- +# Konfiguration ermittelt wird (pgsql oder mysql). Damit werden Spam-Header +# nur für Domains geschrieben, die Postfix tatsächlich als "lokal/relay" kennt. +# +# WICHTIG: +# - Das Skript ändert NUR dann etwas, wenn die Zeile exakt ( ["."] ); ist. +# - Backups werden IMMER nach /etc/amavis/ geschrieben (nicht neben die Datei). +# - DB-Typ wird automatisch aus "postconf -n" erkannt (robust inkl. includes). +# +# Features +# -------- +# - Auto-Detect Backend: pgsql | mysql (aus Postfix-Maps) +# - Optionaler Fallback: wenn DB nicht erkennbar, dann auf ( ["."] ); setzen +# (standardmäßig AUS, weil produktiver Betrieb) +# - Dry-run Modus +# - Optional reload/restart von amavis +# - Atomare Aktualisierung (schreibt erst tmp, dann mv) +# +# Nutzung +# ------- +# sudo ./amavis-local-domains-auto.sh --dry-run +# sudo ./amavis-local-domains-auto.sh --apply --reload +# sudo ./amavis-local-domains-auto.sh --apply --restart +# +# Optional: +# sudo ./amavis-local-domains-auto.sh --apply --allow-fallback-all +# +set -euo pipefail + +# ---------------------------- +# Konfigurierbare Pfade +# ---------------------------- +AMAVIS_50="${AMAVIS_50:-/etc/amavis/conf.d/50-user}" + +# Backup-Verzeichnis (gewünscht: /etc/amavis/) +BACKUP_DIR="${BACKUP_DIR:-/etc/amavis}" + +# Optional: Dateimap für relay_domains (falls vorhanden) +RELAY_BTREE_DEFAULT="/etc/postfix/relay_domains" + +# ---------------------------- +# Schalter / Optionen +# ---------------------------- +DO_APPLY=false +DO_DRY_RUN=false +DO_RESTART=false +DO_RELOAD=false +ALLOW_FALLBACK_ALL=false + +# Optional overrides +HOST_FQDN_OVERRIDE="" +RELAY_BTREE_OVERRIDE="" + +usage() { + cat <<'EOF' +Usage: + amavis-local-domains-auto.sh [OPTIONS] + +Options: + --dry-run Show what would change, do not modify files + --apply Apply change (only if @local_domains_maps is exactly ( ["."] );) + --restart Restart amavis after apply + --reload Reload amavis after apply (preferred if supported) + --allow-fallback-all If DB backend not detectable, replace with ( ["."] ); anyway + (default: fail safely / do nothing) + --host-fqdn FQDN Override hostname -f used in the block + --relay-btree PATH Override btree file path (default: /etc/postfix/relay_domains) + -h, --help Show this help + +Examples: + sudo ./amavis-local-domains-auto.sh --dry-run + sudo ./amavis-local-domains-auto.sh --apply --reload +EOF +} + +die(){ echo "ERROR: $*" >&2; exit 1; } +log(){ echo "INFO: $*" >&2; } + +# ---------------------------- +# Argumente parsen +# ---------------------------- +while [[ $# -gt 0 ]]; do + case "$1" in + --dry-run) DO_DRY_RUN=true; shift ;; + --apply) DO_APPLY=true; shift ;; + --restart) DO_RESTART=true; shift ;; + --reload) DO_RELOAD=true; shift ;; + --allow-fallback-all) ALLOW_FALLBACK_ALL=true; shift ;; + --host-fqdn) HOST_FQDN_OVERRIDE="${2:-}"; shift 2 ;; + --relay-btree) RELAY_BTREE_OVERRIDE="${2:-}"; shift 2 ;; + -h|--help) usage; exit 0 ;; + *) die "Unknown option: $1" ;; + esac +done + +if ! $DO_DRY_RUN && ! $DO_APPLY; then + die "Choose one: --dry-run or --apply" +fi +if $DO_RESTART && $DO_RELOAD; then + die "Choose only one: --restart or --reload" +fi + +# ---------------------------- +# Sanity Checks +# ---------------------------- +command -v postconf >/dev/null || die "postconf not found" +[[ -r "$AMAVIS_50" ]] || die "Missing $AMAVIS_50" +[[ -d "$BACKUP_DIR" ]] || die "Backup dir missing: $BACKUP_DIR" +[[ -w "$BACKUP_DIR" ]] || die "Backup dir not writable: $BACKUP_DIR" + +HOST_FQDN="${HOST_FQDN_OVERRIDE:-$(hostname -f 2>/dev/null || hostname)}" +RELAY_BTREE="${RELAY_BTREE_OVERRIDE:-$RELAY_BTREE_DEFAULT}" + +# Regex: exakt @local_domains_maps = ( ["."] ); +DOTLINE_REGEX='^[[:space:]]*@local_domains_maps[[:space:]]*=[[:space:]]*\([[:space:]]*\[[[:space:]]*"\."[[:space:]]*\][[:space:]]*\)[[:space:]]*;[[:space:]]*$' + +if ! grep -qE "$DOTLINE_REGEX" "$AMAVIS_50"; then + log "No change: @local_domains_maps is not exactly ( [\".\"] );" + exit 0 +fi + +# ---------------------------- +# DB-Typ + Map-Dateien aus Postfix ermitteln +# (postconf -n berücksichtigt includes, master.d, etc.) +# ---------------------------- +VMD_LINE="$(postconf -n virtual_mailbox_domains 2>/dev/null || true)" +RD_LINE="$(postconf -n relay_domains 2>/dev/null || true)" + +extract_db_map() { + # Findet den ersten Token, der :pgsql: oder :mysql: enthält + # und gibt "scheme|/path/to/map.cf" aus. + local line="$1" + for tok in $line; do + tok="${tok%,}" + if [[ "$tok" == *":pgsql:"* ]]; then + echo "pgsql|${tok##*:pgsql:}" + return 0 + elif [[ "$tok" == *":mysql:"* ]]; then + echo "mysql|${tok##*:mysql:}" + return 0 + fi + done + return 1 +} + +DB_SCHEME="" +VDOM_CF="" +RELAY_CF="" + +if out="$(extract_db_map "$VMD_LINE")"; then + DB_SCHEME="${out%%|*}" + VDOM_CF="${out##*|}" +else + if $ALLOW_FALLBACK_ALL; then + DB_SCHEME="all" + else + die "Could not detect pgsql/mysql from postfix virtual_mailbox_domains. Use --allow-fallback-all to force." + fi +fi + +if [[ "$DB_SCHEME" != "all" ]]; then + if out="$(extract_db_map "$RD_LINE")"; then + rd_scheme="${out%%|*}" + rd_cf="${out##*|}" + if [[ "$rd_scheme" == "$DB_SCHEME" ]]; then + RELAY_CF="$rd_cf" + fi + fi + + [[ -r "$VDOM_CF" ]] || die "Detected map not readable: $VDOM_CF" + if [[ -n "$RELAY_CF" ]]; then + [[ -r "$RELAY_CF" ]] || die "Detected relay map not readable: $RELAY_CF" + fi +fi + +# ---------------------------- +# Replacement-Block bauen (in temp Datei) +# ---------------------------- +TMPDIR="$(mktemp -d)" +trap 'rm -rf "$TMPDIR"' EXIT +BLOCKFILE="$TMPDIR/block.txt" + +if [[ "$DB_SCHEME" == "all" ]]; then + cat >"$BLOCKFILE" <<'EOF' +@local_domains_maps = ( ["."] ); +EOF +else + { + echo "@local_domains_maps = (" + echo " [qw(${HOST_FQDN} localhost)]," + echo "" + echo " # Domains, die als virtuelle Mailbox-Domains gehostet sind:" + echo " '${DB_SCHEME}:${VDOM_CF}'," + if [[ -n "$RELAY_CF" ]]; then + echo "" + echo " # Domains, die als relay_domains akzeptiert werden:" + echo " '${DB_SCHEME}:${RELAY_CF}'," + else + echo "" + echo " # relay_domains DB-map nicht erkannt – wird übersprungen" + fi + echo " 'btree:${RELAY_BTREE}'," + echo ");" + } >"$BLOCKFILE" +fi + +# ---------------------------- +# Geplante Änderung anzeigen +# ---------------------------- +log "Detected backend: ${DB_SCHEME}" +if [[ "$DB_SCHEME" != "all" ]]; then + log "virtual_mailbox_domains map: ${DB_SCHEME}:${VDOM_CF}" + if [[ -n "$RELAY_CF" ]]; then + log "relay_domains map: ${DB_SCHEME}:${RELAY_CF}" + else + log "relay_domains map: (not detected / not used)" + fi + log "btree relay domains: btree:${RELAY_BTREE}" +fi + +if $DO_DRY_RUN; then + echo "----- Would replace this line in $AMAVIS_50 -----" + echo '@local_domains_maps = ( ["."] );' + echo "----- With this block -----" + cat "$BLOCKFILE" + exit 0 +fi + +# ---------------------------- +# Backup in /etc/amavis/ erstellen +# ---------------------------- +STAMP="$(date +%F_%H%M%S)" +BACKUP_FILE="${BACKUP_DIR}/50-user.bak.${STAMP}" +cp -a "$AMAVIS_50" "$BACKUP_FILE" +log "Backup created: $BACKUP_FILE" + +# ---------------------------- +# Datei neu schreiben (atomar) +# - Wir ersetzen nur die exakt passende (["."]) Zeile durch den neuen Block. +# ---------------------------- +OUTFILE="$TMPDIR/50-user.new" + +awk -v blockfile="$BLOCKFILE" ' + BEGIN { + while ((getline line < blockfile) > 0) { + newblock = newblock line "\n" + } + close(blockfile) + } + { + if ($0 ~ /^[[:space:]]*@local_domains_maps[[:space:]]*=[[:space:]]*\([[:space:]]*\[[[:space:]]*"\."[[:space:]]*\][[:space:]]*\)[[:space:]]*;[[:space:]]*$/) { + printf "%s", newblock + } else { + print + } + } +' "$AMAVIS_50" > "$OUTFILE" + +mv "$OUTFILE" "$AMAVIS_50" +log "Updated $AMAVIS_50" + +# ---------------------------- +# Optionaler Reload/Restart von amavis +# ---------------------------- +if $DO_RELOAD; then + log "Reloading amavis..." + systemctl reload amavis || die "amavis reload failed" +elif $DO_RESTART; then + log "Restarting amavis..." + systemctl restart amavis || die "amavis restart failed" +else + log "No service action requested (--reload/--restart)." +fi + +log "Done." +