#!/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."