Files
mailsystem/set_amavis_local_domains_maps.sh

286 lines
8.2 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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."