Compare commits
22 Commits
761a3a3b34
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cc155c578a | ||
| b28b3a8316 | |||
| ecfd630612 | |||
| 68d2bd18b7 | |||
|
|
6521b03aa2 | ||
| 05723c0514 | |||
| 32b3c8b01e | |||
| f2f3797c1e | |||
| d8979faa06 | |||
| d407b2ecf0 | |||
| 13e1d575f9 | |||
| a246f5009f | |||
| 41f5f61d32 | |||
| eb18cf2a84 | |||
| 578db07b76 | |||
| 3b3652ec55 | |||
| 44c56d6083 | |||
| 8e9f35ed76 | |||
| 0303c79b03 | |||
| 20d4c95404 | |||
| 3b60d1b83e | |||
| cb9e958d39 |
14
DOC/DMARC-Report/daily-run.sh
Executable file
14
DOC/DMARC-Report/daily-run.sh
Executable file
@@ -0,0 +1,14 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
BASE="/var/lib/dmarc"
|
||||||
|
TODAY_DIR="$BASE/reports/$(date -u +%Y/%m/%d)"
|
||||||
|
OUTDIR="$BASE/exports"
|
||||||
|
CSV="$OUTDIR/records.csv"
|
||||||
|
LOGF="$BASE/logs/scan-$(date -u +%F).log"
|
||||||
|
|
||||||
|
mkdir -p "$OUTDIR"
|
||||||
|
|
||||||
|
if [[ -d "$TODAY_DIR" ]]; then
|
||||||
|
/usr/local/bin/dmarc-scan.sh "$TODAY_DIR" --domain fluechtlingsrat-brandenburg.de --csv "$CSV" --append --top 25 --outdir "$OUTDIR" >> "$LOGF" 2>&1
|
||||||
|
fi
|
||||||
47
DOC/DMARC-Report/dmarc-collect.sh
Executable file
47
DOC/DMARC-Report/dmarc-collect.sh
Executable file
@@ -0,0 +1,47 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
BASE="/var/lib/dmarc"
|
||||||
|
INBOX="$BASE/reports"
|
||||||
|
PROC="$BASE/processed"
|
||||||
|
LOGF="$BASE/logs/collector.log"
|
||||||
|
|
||||||
|
umask 027
|
||||||
|
|
||||||
|
TMPDIR="$(mktemp -d)"
|
||||||
|
EML="$TMPDIR/mail.eml"
|
||||||
|
cat > "$EML"
|
||||||
|
|
||||||
|
ripmime --no-nameless --name-by-type --overwrite -i "$EML" -d "$TMPDIR" >>"$LOGF" 2>&1 || true
|
||||||
|
|
||||||
|
TODAY="$(date -u +%Y/%m/%d)"
|
||||||
|
OUTDIR="$INBOX/$TODAY"
|
||||||
|
mkdir -p "$OUTDIR"
|
||||||
|
|
||||||
|
moved=0
|
||||||
|
shopt -s nullglob
|
||||||
|
for f in "$TMPDIR"/*; do
|
||||||
|
case "$f" in
|
||||||
|
*.xml|*.XML|*.gz|*.zip)
|
||||||
|
sha="$(sha256sum "$f" | awk '{print $1}')"
|
||||||
|
base="$(basename "$f")"
|
||||||
|
dst="$OUTDIR/$(date -u +%Y%m%dT%H%M%SZ)_${sha:0:12}_$base"
|
||||||
|
mv "$f" "$dst"
|
||||||
|
echo "$(date -Is) stored $dst" >> "$LOGF"
|
||||||
|
moved=$((moved+1))
|
||||||
|
;;
|
||||||
|
*) : ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
mkdir -p "$PROC"
|
||||||
|
mv "$EML" "$PROC/$(date -u +%Y%m%dT%H%M%SZ)_mail.eml" || true
|
||||||
|
rm -rf "$TMPDIR"
|
||||||
|
|
||||||
|
if (( moved > 0 )); then
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
echo "$(date -Is) no usable attachment in message" >> "$LOGF"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
382
DOC/DMARC-Report/dmarc-scan-setup.md
Normal file
382
DOC/DMARC-Report/dmarc-scan-setup.md
Normal file
@@ -0,0 +1,382 @@
|
|||||||
|
|
||||||
|
# DMARC-Auswertungsskript `dmarc-scan.sh` – Installation, Beschreibung & Verwendung
|
||||||
|
|
||||||
|
## 📖 Zweck
|
||||||
|
|
||||||
|
`dmarc-scan.sh` analysiert DMARC-Aggregatberichte (XML, .zip, .gz) und erzeugt:
|
||||||
|
- gut lesbare Tabellen im Terminal,
|
||||||
|
- eine fortschreibbare Records-CSV (`--append`),
|
||||||
|
- Top-Listen nach IPs (gesamt und je Fail-Kategorie) als CSV.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚙️ Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt install -y xmlstarlet unzip gzip
|
||||||
|
sudo tee /usr/local/bin/dmarc-scan.sh >/dev/null <<'EOF'
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# dmarc-scan.sh — DMARC-XML-Reports (auch .gz/.zip) einlesen, tabellarisch anzeigen,
|
||||||
|
# Records als CSV exportieren (append optional), Top-IPs ermitteln
|
||||||
|
# und Top-Listen als CSV schreiben.
|
||||||
|
#
|
||||||
|
# Nutzung:
|
||||||
|
# ./dmarc-scan.sh /pfad/zu/reports \
|
||||||
|
# [--domain DOMAIN] \
|
||||||
|
# [--csv pfad/zur/records.csv] \
|
||||||
|
# [--append] \
|
||||||
|
# [--top N] \
|
||||||
|
# [--outdir pfad/zum/ordner]
|
||||||
|
#
|
||||||
|
# Beispiele:
|
||||||
|
# ./dmarc-scan.sh /var/mail/dmarc
|
||||||
|
# ./dmarc-scan.sh /var/mail/dmarc --domain fluechtlingsrat-brandenburg.de --csv dmarc.csv --append --top 15 --outdir ./export
|
||||||
|
#
|
||||||
|
# Voraussetzungen: xmlstarlet, unzip (für .zip), gzip (für .gz)
|
||||||
|
# Debian/Ubuntu: sudo apt-get install xmlstarlet unzip gzip
|
||||||
|
#
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
REPORT_DIR="${1:-}"
|
||||||
|
shift || true
|
||||||
|
|
||||||
|
# Defaults
|
||||||
|
WANT_DOMAIN=""
|
||||||
|
CSV_PATH="./dmarc-summary.csv"
|
||||||
|
APPEND=0
|
||||||
|
TOP_N=10
|
||||||
|
OUTDIR="."
|
||||||
|
|
||||||
|
# Arg-Parsing (einfach)
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "${1:-}" in
|
||||||
|
--domain)
|
||||||
|
WANT_DOMAIN="${2:-}"; shift 2 || true ;;
|
||||||
|
--csv)
|
||||||
|
CSV_PATH="${2:-}"; shift 2 || true ;;
|
||||||
|
--append)
|
||||||
|
APPEND=1; shift || true ;;
|
||||||
|
--top)
|
||||||
|
TOP_N="${2:-10}"; shift 2 || true ;;
|
||||||
|
--outdir)
|
||||||
|
OUTDIR="${2:-.}"; shift 2 || true ;;
|
||||||
|
*)
|
||||||
|
# Unbekannte Option ignorieren
|
||||||
|
shift || true ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ -z "$REPORT_DIR" || ! -d "$REPORT_DIR" ]]; then
|
||||||
|
echo "Fehler: Bitte ein Verzeichnis mit DMARC-Reports angeben."
|
||||||
|
echo "Beispiel: $0 /var/mail/dmarc --domain fluechtlingsrat-brandenburg.de --csv dmarc.csv --append --top 15 --outdir ./export"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v xmlstarlet >/dev/null 2>&1; then
|
||||||
|
echo "Fehler: xmlstarlet nicht gefunden. Bitte installieren (z.B. 'sudo apt-get install xmlstarlet')."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$OUTDIR"
|
||||||
|
|
||||||
|
# CSV-Header für Records; bei --append nur schreiben, wenn Datei noch nicht existiert
|
||||||
|
RECORDS_HEADER="report_org,policy_domain,begin_utc,end_utc,source_ip,count,disposition,spf,dkim,header_from"
|
||||||
|
ensure_records_header() {
|
||||||
|
if [[ "$APPEND" -eq 1 ]]; then
|
||||||
|
if [[ ! -f "$CSV_PATH" ]]; then
|
||||||
|
echo "$RECORDS_HEADER" > "$CSV_PATH"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "$RECORDS_HEADER" > "$CSV_PATH"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
ensure_records_header
|
||||||
|
|
||||||
|
# Zähler & Sets
|
||||||
|
declare -A SEEN_IPS=()
|
||||||
|
declare -A IP_COUNTS=() # Summe pro IP
|
||||||
|
declare -A SPF_ONLY_FAIL_IP=() # nur SPF fail pro IP
|
||||||
|
declare -A DKIM_ONLY_FAIL_IP=() # nur DKIM fail pro IP
|
||||||
|
declare -A BOTH_FAIL_IP=() # SPF+DKIM fail pro IP
|
||||||
|
|
||||||
|
total_msgs=0
|
||||||
|
pass_msgs=0
|
||||||
|
fail_msgs=0
|
||||||
|
spf_only_fail=0
|
||||||
|
dkim_only_fail=0
|
||||||
|
both_fail=0
|
||||||
|
|
||||||
|
# Hilfsfunktion: Epoch -> Datum (UTC)
|
||||||
|
epoch2date() {
|
||||||
|
local e="$1"
|
||||||
|
if [[ -z "$e" ]]; then printf "-"; return; fi
|
||||||
|
date -u -d @"$e" +"%Y-%m-%d %H:%M:%S UTC" 2>/dev/null || printf "%s" "$e"
|
||||||
|
}
|
||||||
|
|
||||||
|
# CSV-escape (Felder in Anführungszeichen, doppelte Anführungszeichen verdoppeln)
|
||||||
|
csv_escape() {
|
||||||
|
local s="${1:-}"
|
||||||
|
s="${s//\"/\"\"}"
|
||||||
|
printf "\"%s\"" "$s"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Eine einzelne XML-Datei parsen
|
||||||
|
parse_xml() {
|
||||||
|
local xml_input="$1"
|
||||||
|
|
||||||
|
# Domain aus policy_published, ggf. für Filter
|
||||||
|
local domain
|
||||||
|
domain=$(xmlstarlet sel -T -t -v "/feedback/policy_published/domain" "$xml_input" 2>/dev/null || true)
|
||||||
|
if [[ -n "$WANT_DOMAIN" && "$domain" != "$WANT_DOMAIN" ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
local org begin end pol sp aspf adkim
|
||||||
|
org=$(xmlstarlet sel -T -t -v "/feedback/report_metadata/org_name" "$xml_input" 2>/dev/null || printf "-")
|
||||||
|
begin=$(xmlstarlet sel -T -t -v "/feedback/report_metadata/date_range/begin" "$xml_input" 2>/dev/null || printf "")
|
||||||
|
end=$(xmlstarlet sel -T -t -v "/feedback/report_metadata/date_range/end" "$xml_input" 2>/dev/null || printf "")
|
||||||
|
pol=$(xmlstarlet sel -T -t -v "/feedback/policy_published/p" "$xml_input" 2>/dev/null || printf "-")
|
||||||
|
sp=$(xmlstarlet sel -T -t -v "/feedback/policy_published/sp" "$xml_input" 2>/dev/null || printf "-")
|
||||||
|
aspf=$(xmlstarlet sel -T -t -v "/feedback/policy_published/aspf" "$xml_input" 2>/dev/null || printf "-")
|
||||||
|
adkim=$(xmlstarlet sel -T -t -v "/feedback/policy_published/adkim" "$xml_input" 2>/dev/null || printf "-")
|
||||||
|
|
||||||
|
# Report-Header
|
||||||
|
echo "=============================================================================="
|
||||||
|
echo "Report von: ${org}"
|
||||||
|
echo "Domain (Policy): ${domain} (p=${pol}, sp=${sp}, aspf=${aspf}, adkim=${adkim})"
|
||||||
|
echo "Zeitraum: $(epoch2date "$begin") – $(epoch2date "$end")"
|
||||||
|
echo "------------------------------------------------------------------------------"
|
||||||
|
printf "%-16s %7s %-10s %-6s %-6s %s\n" "Source IP" "Count" "Disposition" "SPF" "DKIM" "Header-From"
|
||||||
|
echo "------------------------------------------------------------------------------"
|
||||||
|
|
||||||
|
# Alle <record>-Einträge tabellarisch ausgeben
|
||||||
|
while IFS='|' read -r ip cnt dispo spfres dkimres hfrom; do
|
||||||
|
[[ -z "$ip$cnt$dispo$spfres$dkimres$hfrom" ]] && continue
|
||||||
|
|
||||||
|
local n=0
|
||||||
|
if [[ -n "${cnt:-}" && "$cnt" =~ ^[0-9]+$ ]]; then n="$cnt"; fi
|
||||||
|
|
||||||
|
total_msgs=$(( total_msgs + n ))
|
||||||
|
[[ -n "$ip" ]] && SEEN_IPS["$ip"]=1
|
||||||
|
[[ -n "$ip" ]] && IP_COUNTS["$ip"]=$(( ${IP_COUNTS["$ip"]:-0} + n ))
|
||||||
|
|
||||||
|
if [[ "${spfres:-}" == "pass" && "${dkimres:-}" == "pass" ]]; then
|
||||||
|
pass_msgs=$(( pass_msgs + n ))
|
||||||
|
else
|
||||||
|
fail_msgs=$(( fail_msgs + n ))
|
||||||
|
if [[ "${spfres:-}" != "pass" && "${dkimres:-}" == "pass" ]]; then
|
||||||
|
spf_only_fail=$(( spf_only_fail + n ))
|
||||||
|
[[ -n "$ip" ]] && SPF_ONLY_FAIL_IP["$ip"]=$(( ${SPF_ONLY_FAIL_IP["$ip"]:-0} + n ))
|
||||||
|
elif [[ "${spfres:-}" == "pass" && "${dkimres:-}" != "pass" ]]; then
|
||||||
|
dkim_only_fail=$(( dkim_only_fail + n ))
|
||||||
|
[[ -n "$ip" ]] && DKIM_ONLY_FAIL_IP["$ip"]=$(( ${DKIM_ONLY_FAIL_IP["$ip"]:-0} + n ))
|
||||||
|
else
|
||||||
|
both_fail=$(( both_fail + n ))
|
||||||
|
[[ -n "$ip" ]] && BOTH_FAIL_IP["$ip"]=$(( ${BOTH_FAIL_IP["$ip"]:-0} + n ))
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf "%-16s %7s %-10s %-6s %-6s %s\n" "${ip:--}" "${n}" "${dispo:--}" "${spfres:--}" "${dkimres:--}" "${hfrom:--}"
|
||||||
|
|
||||||
|
local begin_human end_human
|
||||||
|
begin_human="$(epoch2date "$begin")"
|
||||||
|
end_human="$(epoch2date "$end")"
|
||||||
|
{
|
||||||
|
csv_escape "$org"; printf ","
|
||||||
|
csv_escape "$domain"; printf ","
|
||||||
|
csv_escape "$begin_human"; printf ","
|
||||||
|
csv_escape "$end_human"; printf ","
|
||||||
|
csv_escape "${ip:-}"; printf ","
|
||||||
|
printf "%s," "$n"
|
||||||
|
csv_escape "${dispo:-}"; printf ","
|
||||||
|
csv_escape "${spfres:-}"; printf ","
|
||||||
|
csv_escape "${dkimres:-}"; printf ","
|
||||||
|
csv_escape "${hfrom:-}"; printf "\n"
|
||||||
|
} >> "$CSV_PATH"
|
||||||
|
|
||||||
|
done < <(xmlstarlet sel -T -t \
|
||||||
|
-m "/feedback/record" \
|
||||||
|
-v "row/source_ip" -o "|" \
|
||||||
|
-v "row/count" -o "|" \
|
||||||
|
-v "row/policy_evaluated/disposition" -o "|" \
|
||||||
|
-v "row/policy_evaluated/spf" -o "|" \
|
||||||
|
-v "row/policy_evaluated/dkim" -o "|" \
|
||||||
|
-v "identifiers/header_from" -n \
|
||||||
|
"$xml_input" 2>/dev/null)
|
||||||
|
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
# Alle Dateien im Verzeichnis verarbeiten
|
||||||
|
shopt -s nullglob
|
||||||
|
for f in "$REPORT_DIR"/*; do
|
||||||
|
case "$f" in
|
||||||
|
*.xml)
|
||||||
|
parse_xml "$f"
|
||||||
|
;;
|
||||||
|
*.gz)
|
||||||
|
if command -v gzip >/dev/null 2>&1; then
|
||||||
|
tmp="$(mktemp)"
|
||||||
|
if gzip -cd "$f" > "$tmp"; then
|
||||||
|
parse_xml "$tmp"
|
||||||
|
else
|
||||||
|
echo "Warnung: Konnte $f nicht entpacken."
|
||||||
|
fi
|
||||||
|
rm -f "$tmp"
|
||||||
|
else
|
||||||
|
echo "Warnung: gzip nicht verfügbar, überspringe $f"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*.zip)
|
||||||
|
if command -v unzip >/dev/null 2>&1; then
|
||||||
|
tmpdir="$(mktemp -d)"
|
||||||
|
if unzip -qq -j "$f" '*.xml' -d "$tmpdir" >/dev/null 2>&1; then
|
||||||
|
for x in "$tmpdir"/*.xml; do
|
||||||
|
[[ -e "$x" ]] || continue
|
||||||
|
parse_xml "$x"
|
||||||
|
done
|
||||||
|
else
|
||||||
|
echo "Warnung: Konnte $f nicht entpacken (oder keine XML darin)."
|
||||||
|
fi
|
||||||
|
rm -rf "$tmpdir"
|
||||||
|
else
|
||||||
|
echo "Warnung: unzip nicht verfügbar, überspringe $f"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*) : ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Zusammenfassung
|
||||||
|
unique_ips=${#SEEN_IPS[@]}
|
||||||
|
echo "=============================================================================="
|
||||||
|
echo "GESAMT-ZUSAMMENFASSUNG"
|
||||||
|
echo "Nachrichten gesamt: $total_msgs"
|
||||||
|
echo "Eindeutige Source-IPs: $unique_ips"
|
||||||
|
echo "Alle Auth PASS: $pass_msgs"
|
||||||
|
echo "SPF/DKIM Fehler gesamt: $fail_msgs"
|
||||||
|
echo " ├─ nur SPF-Fail: $spf_only_fail"
|
||||||
|
echo " ├─ nur DKIM-Fail: $dkim_only_fail"
|
||||||
|
echo " └─ SPF+DKIM-Fail: $both_fail"
|
||||||
|
[[ -n "$WANT_DOMAIN" ]] && echo "Gefilterte Domain: $WANT_DOMAIN"
|
||||||
|
echo "Records-CSV: $CSV_PATH"
|
||||||
|
echo "Hinweis: 'Fail' umfasst Records, bei denen SPF oder DKIM nicht 'pass' war."
|
||||||
|
|
||||||
|
# Top-Listen auf STDOUT
|
||||||
|
echo
|
||||||
|
echo "TOP $TOP_N IPs nach Anzahl (über alle Reports):"
|
||||||
|
{
|
||||||
|
for ip in "${!IP_COUNTS[@]}"; do
|
||||||
|
printf "%10d %s\n" "${IP_COUNTS[$ip]}" "$ip"
|
||||||
|
done
|
||||||
|
} | sort -rn | head -n "$TOP_N" | awk '{printf " %2d) %-16s %7s\n", NR, $2, $1}'
|
||||||
|
|
||||||
|
if (( fail_msgs > 0 )); then
|
||||||
|
echo
|
||||||
|
echo "Top-IPs nur SPF-Fail:"
|
||||||
|
{ for ip in "${!SPF_ONLY_FAIL_IP[@]}"; do printf "%10d %s\n" "${SPF_ONLY_FAIL_IP[$ip]}" "$ip"; done; } \
|
||||||
|
| sort -rn | head -n "$TOP_N" | awk '{printf " %2d) %-16s %7s\n", NR, $2, $1}'
|
||||||
|
echo
|
||||||
|
echo "Top-IPs nur DKIM-Fail:"
|
||||||
|
{ for ip in "${!DKIM_ONLY_FAIL_IP[@]}"; do printf "%10d %s\n" "${DKIM_ONLY_FAIL_IP[$ip]}" "$ip"; done; } \
|
||||||
|
| sort -rn | head -n "$TOP_N" | awk '{printf " %2d) %-16s %7s\n", NR, $2, $1}'
|
||||||
|
echo
|
||||||
|
echo "Top-IPs SPF+DKIM-Fail:"
|
||||||
|
{ for ip in "${!BOTH_FAIL_IP[@]}"; do printf "%10d %s\n" "${BOTH_FAIL_IP[$ip]}" "$ip"; done; } \
|
||||||
|
| sort -rn | head -n "$TOP_N" | awk '{printf " %2d) %-16s %7s\n", NR, $2, $1}'
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---- CSV-Exporte der Top-Listen ---------------------------------------------
|
||||||
|
|
||||||
|
write_top_csv () {
|
||||||
|
local outfile="$1"; shift
|
||||||
|
local -n assoc="$1" # name reference auf assoziatives Array
|
||||||
|
echo "ip,count" > "$outfile"
|
||||||
|
if [[ "${#assoc[@]}" -eq 0 ]]; then
|
||||||
|
: # leer
|
||||||
|
else
|
||||||
|
for ip in "${!assoc[@]}"; do
|
||||||
|
printf "%s,%s\n" "$ip" "${assoc[$ip]}"
|
||||||
|
done | sort -t, -k2,2nr > "$outfile"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Gesamt-Top-IPs
|
||||||
|
write_top_csv "$OUTDIR/top_ips.csv" IP_COUNTS
|
||||||
|
|
||||||
|
# Top-Listen nach Fail-Kategorien
|
||||||
|
write_top_csv "$OUTDIR/top_spf_fail_ips.csv" SPF_ONLY_FAIL_IP
|
||||||
|
write_top_csv "$OUTDIR/top_dkim_fail_ips.csv" DKIM_ONLY_FAIL_IP
|
||||||
|
write_top_csv "$OUTDIR/top_both_fail_ips.csv" BOTH_FAIL_IP
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "Exportierte Top-CSV-Dateien:"
|
||||||
|
echo " $OUTDIR/top_ips.csv"
|
||||||
|
echo " $OUTDIR/top_spf_fail_ips.csv"
|
||||||
|
echo " $OUTDIR/top_dkim_fail_ips.csv"
|
||||||
|
echo " $OUTDIR/top_both_fail_ips.csv"
|
||||||
|
|
||||||
|
EOF
|
||||||
|
sudo chmod 750 /usr/local/bin/dmarc-scan.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧩 Verwendung
|
||||||
|
|
||||||
|
Grundbeispiel:
|
||||||
|
```bash
|
||||||
|
dmarc-scan.sh /var/lib/dmarc/reports/2025/11/12
|
||||||
|
```
|
||||||
|
|
||||||
|
Mit Optionen:
|
||||||
|
```bash
|
||||||
|
dmarc-scan.sh /var/lib/dmarc/reports/2025/11/12 --domain fluechtlingsrat-brandenburg.de --csv /var/lib/dmarc/exports/records.csv --append --top 25 --outdir /var/lib/dmarc/exports/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Parameterübersicht
|
||||||
|
|
||||||
|
| Parameter | Bedeutung |
|
||||||
|
|------------|------------|
|
||||||
|
| `--domain <domain>` | Filtert Berichte auf bestimmte Domain |
|
||||||
|
| `--csv <pfad>` | Pfad zur Ausgabedatei (Records-CSV) |
|
||||||
|
| `--append` | Bestehende CSV fortschreiben statt überschreiben |
|
||||||
|
| `--top <N>` | Anzahl der angezeigten Top-IPs |
|
||||||
|
| `--outdir <pfad>` | Zielverzeichnis für Top-Listen (CSV) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 Ausgabedateien
|
||||||
|
|
||||||
|
| Datei | Inhalt |
|
||||||
|
|-------|---------|
|
||||||
|
| `records.csv` | Alle Einzel-Records |
|
||||||
|
| `top_ips.csv` | IPs mit den meisten Mails |
|
||||||
|
| `top_spf_fail_ips.csv` | IPs mit nur SPF-Fails |
|
||||||
|
| `top_dkim_fail_ips.csv` | IPs mit nur DKIM-Fails |
|
||||||
|
| `top_both_fail_ips.csv` | IPs mit SPF+DKIM-Fails |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔁 Integration in den Serverbetrieb
|
||||||
|
|
||||||
|
Das Skript wird typischerweise durch den täglichen Job `/usr/local/lib/dmarc/daily-run.sh` aufgerufen.
|
||||||
|
Manuelle Nutzung ist jederzeit möglich.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧩 Kontext – Verzeichnisstruktur (empfohlen)
|
||||||
|
|
||||||
|
```
|
||||||
|
/var/lib/dmarc/
|
||||||
|
├── reports/ # Eingehende Rohdaten (XML, ZIP, GZ) – nach Datum
|
||||||
|
├── exports/ # CSV- und Top-Dateien
|
||||||
|
└── logs/ # Logausgaben der täglichen Auswertung
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Autor:** oopen.de / Systemkonfiguration
|
||||||
|
**Stand:** November 2025
|
||||||
|
**Version:** 1.2
|
||||||
301
DOC/DMARC-Report/dmarc-scan.sh
Executable file
301
DOC/DMARC-Report/dmarc-scan.sh
Executable file
@@ -0,0 +1,301 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# dmarc-scan.sh — DMARC-XML-Reports (auch .gz/.zip) einlesen, tabellarisch anzeigen,
|
||||||
|
# Records als CSV exportieren (append optional), Top-IPs ermitteln
|
||||||
|
# und Top-Listen als CSV schreiben.
|
||||||
|
#
|
||||||
|
# Nutzung:
|
||||||
|
# ./dmarc-scan.sh /pfad/zu/reports \
|
||||||
|
# [--domain DOMAIN] \
|
||||||
|
# [--csv pfad/zur/records.csv] \
|
||||||
|
# [--append] \
|
||||||
|
# [--top N] \
|
||||||
|
# [--outdir pfad/zum/ordner]
|
||||||
|
#
|
||||||
|
# Beispiele:
|
||||||
|
# ./dmarc-scan.sh /var/mail/dmarc
|
||||||
|
# ./dmarc-scan.sh /var/mail/dmarc --domain fluechtlingsrat-brandenburg.de --csv dmarc.csv --append --top 15 --outdir ./export
|
||||||
|
#
|
||||||
|
# Voraussetzungen: xmlstarlet, unzip (für .zip), gzip (für .gz)
|
||||||
|
# Debian/Ubuntu: sudo apt-get install xmlstarlet unzip gzip
|
||||||
|
#
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
REPORT_DIR="${1:-}"
|
||||||
|
shift || true
|
||||||
|
|
||||||
|
# Defaults
|
||||||
|
WANT_DOMAIN=""
|
||||||
|
CSV_PATH="./dmarc-summary.csv"
|
||||||
|
APPEND=0
|
||||||
|
TOP_N=10
|
||||||
|
OUTDIR="."
|
||||||
|
|
||||||
|
# Arg-Parsing (einfach)
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "${1:-}" in
|
||||||
|
--domain)
|
||||||
|
WANT_DOMAIN="${2:-}"; shift 2 || true ;;
|
||||||
|
--csv)
|
||||||
|
CSV_PATH="${2:-}"; shift 2 || true ;;
|
||||||
|
--append)
|
||||||
|
APPEND=1; shift || true ;;
|
||||||
|
--top)
|
||||||
|
TOP_N="${2:-10}"; shift 2 || true ;;
|
||||||
|
--outdir)
|
||||||
|
OUTDIR="${2:-.}"; shift 2 || true ;;
|
||||||
|
*)
|
||||||
|
# Unbekannte Option ignorieren
|
||||||
|
shift || true ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ -z "$REPORT_DIR" || ! -d "$REPORT_DIR" ]]; then
|
||||||
|
echo "Fehler: Bitte ein Verzeichnis mit DMARC-Reports angeben."
|
||||||
|
echo "Beispiel: $0 /var/mail/dmarc --domain fluechtlingsrat-brandenburg.de --csv dmarc.csv --append --top 15 --outdir ./export"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v xmlstarlet >/dev/null 2>&1; then
|
||||||
|
echo "Fehler: xmlstarlet nicht gefunden. Bitte installieren (z.B. 'sudo apt-get install xmlstarlet')."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$OUTDIR"
|
||||||
|
|
||||||
|
# CSV-Header für Records; bei --append nur schreiben, wenn Datei noch nicht existiert
|
||||||
|
RECORDS_HEADER="report_org,policy_domain,begin_utc,end_utc,source_ip,count,disposition,spf,dkim,header_from"
|
||||||
|
ensure_records_header() {
|
||||||
|
if [[ "$APPEND" -eq 1 ]]; then
|
||||||
|
if [[ ! -f "$CSV_PATH" ]]; then
|
||||||
|
echo "$RECORDS_HEADER" > "$CSV_PATH"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "$RECORDS_HEADER" > "$CSV_PATH"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
ensure_records_header
|
||||||
|
|
||||||
|
# Zähler & Sets
|
||||||
|
declare -A SEEN_IPS=()
|
||||||
|
declare -A IP_COUNTS=() # Summe pro IP
|
||||||
|
declare -A SPF_ONLY_FAIL_IP=() # nur SPF fail pro IP
|
||||||
|
declare -A DKIM_ONLY_FAIL_IP=() # nur DKIM fail pro IP
|
||||||
|
declare -A BOTH_FAIL_IP=() # SPF+DKIM fail pro IP
|
||||||
|
|
||||||
|
total_msgs=0
|
||||||
|
pass_msgs=0
|
||||||
|
fail_msgs=0
|
||||||
|
spf_only_fail=0
|
||||||
|
dkim_only_fail=0
|
||||||
|
both_fail=0
|
||||||
|
|
||||||
|
# Hilfsfunktion: Epoch -> Datum (UTC)
|
||||||
|
epoch2date() {
|
||||||
|
local e="$1"
|
||||||
|
if [[ -z "$e" ]]; then printf "-"; return; fi
|
||||||
|
date -u -d @"$e" +"%Y-%m-%d %H:%M:%S UTC" 2>/dev/null || printf "%s" "$e"
|
||||||
|
}
|
||||||
|
|
||||||
|
# CSV-escape (Felder in Anführungszeichen, doppelte Anführungszeichen verdoppeln)
|
||||||
|
csv_escape() {
|
||||||
|
local s="${1:-}"
|
||||||
|
s="${s//\"/\"\"}"
|
||||||
|
printf "\"%s\"" "$s"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Eine einzelne XML-Datei parsen
|
||||||
|
parse_xml() {
|
||||||
|
local xml_input="$1"
|
||||||
|
|
||||||
|
# Domain aus policy_published, ggf. für Filter
|
||||||
|
local domain
|
||||||
|
domain=$(xmlstarlet sel -T -t -v "/feedback/policy_published/domain" "$xml_input" 2>/dev/null || true)
|
||||||
|
if [[ -n "$WANT_DOMAIN" && "$domain" != "$WANT_DOMAIN" ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
local org begin end pol sp aspf adkim
|
||||||
|
org=$(xmlstarlet sel -T -t -v "/feedback/report_metadata/org_name" "$xml_input" 2>/dev/null || printf "-")
|
||||||
|
begin=$(xmlstarlet sel -T -t -v "/feedback/report_metadata/date_range/begin" "$xml_input" 2>/dev/null || printf "")
|
||||||
|
end=$(xmlstarlet sel -T -t -v "/feedback/report_metadata/date_range/end" "$xml_input" 2>/dev/null || printf "")
|
||||||
|
pol=$(xmlstarlet sel -T -t -v "/feedback/policy_published/p" "$xml_input" 2>/dev/null || printf "-")
|
||||||
|
sp=$(xmlstarlet sel -T -t -v "/feedback/policy_published/sp" "$xml_input" 2>/dev/null || printf "-")
|
||||||
|
aspf=$(xmlstarlet sel -T -t -v "/feedback/policy_published/aspf" "$xml_input" 2>/dev/null || printf "-")
|
||||||
|
adkim=$(xmlstarlet sel -T -t -v "/feedback/policy_published/adkim" "$xml_input" 2>/dev/null || printf "-")
|
||||||
|
|
||||||
|
# Report-Header
|
||||||
|
echo "=============================================================================="
|
||||||
|
echo "Report von: ${org}"
|
||||||
|
echo "Domain (Policy): ${domain} (p=${pol}, sp=${sp}, aspf=${aspf}, adkim=${adkim})"
|
||||||
|
echo "Zeitraum: $(epoch2date "$begin") – $(epoch2date "$end")"
|
||||||
|
echo "------------------------------------------------------------------------------"
|
||||||
|
printf "%-16s %7s %-10s %-6s %-6s %s\n" "Source IP" "Count" "Disposition" "SPF" "DKIM" "Header-From"
|
||||||
|
echo "------------------------------------------------------------------------------"
|
||||||
|
|
||||||
|
# Alle <record>-Einträge tabellarisch ausgeben
|
||||||
|
while IFS='|' read -r ip cnt dispo spfres dkimres hfrom; do
|
||||||
|
[[ -z "$ip$cnt$dispo$spfres$dkimres$hfrom" ]] && continue
|
||||||
|
|
||||||
|
local n=0
|
||||||
|
if [[ -n "${cnt:-}" && "$cnt" =~ ^[0-9]+$ ]]; then n="$cnt"; fi
|
||||||
|
|
||||||
|
total_msgs=$(( total_msgs + n ))
|
||||||
|
[[ -n "$ip" ]] && SEEN_IPS["$ip"]=1
|
||||||
|
[[ -n "$ip" ]] && IP_COUNTS["$ip"]=$(( ${IP_COUNTS["$ip"]:-0} + n ))
|
||||||
|
|
||||||
|
if [[ "${spfres:-}" == "pass" && "${dkimres:-}" == "pass" ]]; then
|
||||||
|
pass_msgs=$(( pass_msgs + n ))
|
||||||
|
else
|
||||||
|
fail_msgs=$(( fail_msgs + n ))
|
||||||
|
if [[ "${spfres:-}" != "pass" && "${dkimres:-}" == "pass" ]]; then
|
||||||
|
spf_only_fail=$(( spf_only_fail + n ))
|
||||||
|
[[ -n "$ip" ]] && SPF_ONLY_FAIL_IP["$ip"]=$(( ${SPF_ONLY_FAIL_IP["$ip"]:-0} + n ))
|
||||||
|
elif [[ "${spfres:-}" == "pass" && "${dkimres:-}" != "pass" ]]; then
|
||||||
|
dkim_only_fail=$(( dkim_only_fail + n ))
|
||||||
|
[[ -n "$ip" ]] && DKIM_ONLY_FAIL_IP["$ip"]=$(( ${DKIM_ONLY_FAIL_IP["$ip"]:-0} + n ))
|
||||||
|
else
|
||||||
|
both_fail=$(( both_fail + n ))
|
||||||
|
[[ -n "$ip" ]] && BOTH_FAIL_IP["$ip"]=$(( ${BOTH_FAIL_IP["$ip"]:-0} + n ))
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf "%-16s %7s %-10s %-6s %-6s %s\n" "${ip:--}" "${n}" "${dispo:--}" "${spfres:--}" "${dkimres:--}" "${hfrom:--}"
|
||||||
|
|
||||||
|
local begin_human end_human
|
||||||
|
begin_human="$(epoch2date "$begin")"
|
||||||
|
end_human="$(epoch2date "$end")"
|
||||||
|
{
|
||||||
|
csv_escape "$org"; printf ","
|
||||||
|
csv_escape "$domain"; printf ","
|
||||||
|
csv_escape "$begin_human"; printf ","
|
||||||
|
csv_escape "$end_human"; printf ","
|
||||||
|
csv_escape "${ip:-}"; printf ","
|
||||||
|
printf "%s," "$n"
|
||||||
|
csv_escape "${dispo:-}"; printf ","
|
||||||
|
csv_escape "${spfres:-}"; printf ","
|
||||||
|
csv_escape "${dkimres:-}"; printf ","
|
||||||
|
csv_escape "${hfrom:-}"; printf "\n"
|
||||||
|
} >> "$CSV_PATH"
|
||||||
|
|
||||||
|
done < <(xmlstarlet sel -T -t \
|
||||||
|
-m "/feedback/record" \
|
||||||
|
-v "row/source_ip" -o "|" \
|
||||||
|
-v "row/count" -o "|" \
|
||||||
|
-v "row/policy_evaluated/disposition" -o "|" \
|
||||||
|
-v "row/policy_evaluated/spf" -o "|" \
|
||||||
|
-v "row/policy_evaluated/dkim" -o "|" \
|
||||||
|
-v "identifiers/header_from" -n \
|
||||||
|
"$xml_input" 2>/dev/null)
|
||||||
|
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
# Alle Dateien im Verzeichnis verarbeiten
|
||||||
|
shopt -s nullglob
|
||||||
|
for f in "$REPORT_DIR"/*; do
|
||||||
|
case "$f" in
|
||||||
|
*.xml)
|
||||||
|
parse_xml "$f"
|
||||||
|
;;
|
||||||
|
*.gz)
|
||||||
|
if command -v gzip >/dev/null 2>&1; then
|
||||||
|
tmp="$(mktemp)"
|
||||||
|
if gzip -cd "$f" > "$tmp"; then
|
||||||
|
parse_xml "$tmp"
|
||||||
|
else
|
||||||
|
echo "Warnung: Konnte $f nicht entpacken."
|
||||||
|
fi
|
||||||
|
rm -f "$tmp"
|
||||||
|
else
|
||||||
|
echo "Warnung: gzip nicht verfügbar, überspringe $f"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*.zip)
|
||||||
|
if command -v unzip >/dev/null 2>&1; then
|
||||||
|
tmpdir="$(mktemp -d)"
|
||||||
|
if unzip -qq -j "$f" '*.xml' -d "$tmpdir" >/dev/null 2>&1; then
|
||||||
|
for x in "$tmpdir"/*.xml; do
|
||||||
|
[[ -e "$x" ]] || continue
|
||||||
|
parse_xml "$x"
|
||||||
|
done
|
||||||
|
else
|
||||||
|
echo "Warnung: Konnte $f nicht entpacken (oder keine XML darin)."
|
||||||
|
fi
|
||||||
|
rm -rf "$tmpdir"
|
||||||
|
else
|
||||||
|
echo "Warnung: unzip nicht verfügbar, überspringe $f"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*) : ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Zusammenfassung
|
||||||
|
unique_ips=${#SEEN_IPS[@]}
|
||||||
|
echo "=============================================================================="
|
||||||
|
echo "GESAMT-ZUSAMMENFASSUNG"
|
||||||
|
echo "Nachrichten gesamt: $total_msgs"
|
||||||
|
echo "Eindeutige Source-IPs: $unique_ips"
|
||||||
|
echo "Alle Auth PASS: $pass_msgs"
|
||||||
|
echo "SPF/DKIM Fehler gesamt: $fail_msgs"
|
||||||
|
echo " ├─ nur SPF-Fail: $spf_only_fail"
|
||||||
|
echo " ├─ nur DKIM-Fail: $dkim_only_fail"
|
||||||
|
echo " └─ SPF+DKIM-Fail: $both_fail"
|
||||||
|
[[ -n "$WANT_DOMAIN" ]] && echo "Gefilterte Domain: $WANT_DOMAIN"
|
||||||
|
echo "Records-CSV: $CSV_PATH"
|
||||||
|
echo "Hinweis: 'Fail' umfasst Records, bei denen SPF oder DKIM nicht 'pass' war."
|
||||||
|
|
||||||
|
# Top-Listen auf STDOUT
|
||||||
|
echo
|
||||||
|
echo "TOP $TOP_N IPs nach Anzahl (über alle Reports):"
|
||||||
|
{
|
||||||
|
for ip in "${!IP_COUNTS[@]}"; do
|
||||||
|
printf "%10d %s\n" "${IP_COUNTS[$ip]}" "$ip"
|
||||||
|
done
|
||||||
|
} | sort -rn | head -n "$TOP_N" | awk '{printf " %2d) %-16s %7s\n", NR, $2, $1}'
|
||||||
|
|
||||||
|
if (( fail_msgs > 0 )); then
|
||||||
|
echo
|
||||||
|
echo "Top-IPs nur SPF-Fail:"
|
||||||
|
{ for ip in "${!SPF_ONLY_FAIL_IP[@]}"; do printf "%10d %s\n" "${SPF_ONLY_FAIL_IP[$ip]}" "$ip"; done; } \
|
||||||
|
| sort -rn | head -n "$TOP_N" | awk '{printf " %2d) %-16s %7s\n", NR, $2, $1}'
|
||||||
|
echo
|
||||||
|
echo "Top-IPs nur DKIM-Fail:"
|
||||||
|
{ for ip in "${!DKIM_ONLY_FAIL_IP[@]}"; do printf "%10d %s\n" "${DKIM_ONLY_FAIL_IP[$ip]}" "$ip"; done; } \
|
||||||
|
| sort -rn | head -n "$TOP_N" | awk '{printf " %2d) %-16s %7s\n", NR, $2, $1}'
|
||||||
|
echo
|
||||||
|
echo "Top-IPs SPF+DKIM-Fail:"
|
||||||
|
{ for ip in "${!BOTH_FAIL_IP[@]}"; do printf "%10d %s\n" "${BOTH_FAIL_IP[$ip]}" "$ip"; done; } \
|
||||||
|
| sort -rn | head -n "$TOP_N" | awk '{printf " %2d) %-16s %7s\n", NR, $2, $1}'
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---- CSV-Exporte der Top-Listen ---------------------------------------------
|
||||||
|
|
||||||
|
write_top_csv () {
|
||||||
|
local outfile="$1"; shift
|
||||||
|
local -n assoc="$1" # name reference auf assoziatives Array
|
||||||
|
echo "ip,count" > "$outfile"
|
||||||
|
if [[ "${#assoc[@]}" -eq 0 ]]; then
|
||||||
|
: # leer
|
||||||
|
else
|
||||||
|
for ip in "${!assoc[@]}"; do
|
||||||
|
printf "%s,%s\n" "$ip" "${assoc[$ip]}"
|
||||||
|
done | sort -t, -k2,2nr > "$outfile"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Gesamt-Top-IPs
|
||||||
|
write_top_csv "$OUTDIR/top_ips.csv" IP_COUNTS
|
||||||
|
|
||||||
|
# Top-Listen nach Fail-Kategorien
|
||||||
|
write_top_csv "$OUTDIR/top_spf_fail_ips.csv" SPF_ONLY_FAIL_IP
|
||||||
|
write_top_csv "$OUTDIR/top_dkim_fail_ips.csv" DKIM_ONLY_FAIL_IP
|
||||||
|
write_top_csv "$OUTDIR/top_both_fail_ips.csv" BOTH_FAIL_IP
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "Exportierte Top-CSV-Dateien:"
|
||||||
|
echo " $OUTDIR/top_ips.csv"
|
||||||
|
echo " $OUTDIR/top_spf_fail_ips.csv"
|
||||||
|
echo " $OUTDIR/top_dkim_fail_ips.csv"
|
||||||
|
echo " $OUTDIR/top_both_fail_ips.csv"
|
||||||
621
DOC/DMARC-Report/dmarc-server-setup.md
Normal file
621
DOC/DMARC-Report/dmarc-server-setup.md
Normal file
@@ -0,0 +1,621 @@
|
|||||||
|
# DMARC-Server-Sammelsystem – Einrichtung und Betrieb
|
||||||
|
|
||||||
|
## 📖 Präambel
|
||||||
|
|
||||||
|
Dieses Dokument beschreibt die vollständige Einrichtung eines automatisierten DMARC-Report-Sammelsystems auf einem Linux-Mailserver mit **Postfix**, **Amavis** und **Dovecot (LMTP)**.
|
||||||
|
Ziel: DMARC-Aggregatberichte (XML, .gz, .zip) serverseitig empfangen, speichern und regelmäßig auswerten.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 Ziel
|
||||||
|
|
||||||
|
- DMARC-Reports zentral über `dmarc-reports@oopen.de` empfangen.
|
||||||
|
- Reports automatisch extrahieren und datumsbasiert speichern.
|
||||||
|
- Regelmäßige automatische Auswertung mit `dmarc-scan.sh`.
|
||||||
|
- Speicherung der Ergebnisse in CSV-Dateien.
|
||||||
|
- Wartungsarm, sicher und robust.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📁 Verzeichnisstruktur
|
||||||
|
|
||||||
|
Alle Dateien werden unter `/var/lib/dmarc` abgelegt:
|
||||||
|
|
||||||
|
```
|
||||||
|
/var/lib/dmarc/
|
||||||
|
├── reports/ # Eingegangene XML-, GZ-, ZIP-Dateien
|
||||||
|
│ └── YYYY/MM/DD/ # Datumsbasierte Ablage
|
||||||
|
├── processed/ # Originalmails (Archiv)
|
||||||
|
├── exports/ # CSV- und Top-Auswertungen
|
||||||
|
└── logs/ # Logdateien
|
||||||
|
```
|
||||||
|
|
||||||
|
Verzeichnisse anlegen:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo install -d -o vmail -g vmail -m 750 /var/lib/dmarc/{reports,processed,exports,logs}
|
||||||
|
sudo install -d -o root -g root -m 750 /usr/local/lib/dmarc
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚙️ 1. Postfix-Integration
|
||||||
|
|
||||||
|
### 1.1 Transport und Master-Konfiguration
|
||||||
|
|
||||||
|
In `/etc/postfix/master.cf` **am Ende einfügen:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dmarc-pipe unix - n n - - pipe
|
||||||
|
flags=Rq user=vmail argv=/usr/local/bin/dmarc-collect.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
> Passe `user=` an, falls dein Mailbenutzer anders heißt (z. B. `mail` oder `amavis`).
|
||||||
|
|
||||||
|
### 1.2 Transportregel definieren
|
||||||
|
|
||||||
|
In `/etc/postfix/transport`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dmarc-reports@oopen.de dmarc-pipe:
|
||||||
|
```
|
||||||
|
|
||||||
|
Aktivieren:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo postmap /etc/postfix/transport
|
||||||
|
sudo systemctl reload postfix
|
||||||
|
```
|
||||||
|
|
||||||
|
Damit weiß Postfix, dass Mails an diese Adresse an das Skript weitergegeben werden.
|
||||||
|
|
||||||
|
### 1.3 (Optional) Kopie der Mail behalten
|
||||||
|
|
||||||
|
Wenn du zusätzlich eine Kopie im IMAP-Postfach haben willst:
|
||||||
|
|
||||||
|
In `/etc/postfix/virtual_alias_maps`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dmarc-reports@oopen.de dmarc-reports-mbox@oopen.de
|
||||||
|
```
|
||||||
|
|
||||||
|
Dann:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo postmap btree:/etc/postfix/virtual_alias_maps
|
||||||
|
sudo systemctl reload postfix
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Merke:** `/etc/postfix/transport` = ZustellWEG, `/etc/postfix/virtual_alias_maps` = EmpfängerALIAS.
|
||||||
|
> Nur Skript? → Nur `transport`-Eintrag.
|
||||||
|
> Skript + Kopie? → `virtual`-Alias **zusätzlich**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📨 2. DNS-Einträge (DMARC + External Reporting)
|
||||||
|
|
||||||
|
### Beispiel für eine Domain
|
||||||
|
|
||||||
|
```dns
|
||||||
|
_dmarc.fluechtlingsrat-brandenburg.de. IN TXT "v=DMARC1; p=none; rua=mailto:dmarc-reports@oopen.de; ruf=mailto:dmarc-reports@oopen.de; fo=1; aspf=r; adkim=r"
|
||||||
|
```
|
||||||
|
|
||||||
|
### External Reporting (oopen.de)
|
||||||
|
|
||||||
|
Wenn du Berichte für mehrere Domains auf `@oopen.de` empfängst, erlaube externes Reporting:
|
||||||
|
|
||||||
|
```dns
|
||||||
|
oopen.de._report._dmarc.oopen.de. IN TXT "v=DMARC1"
|
||||||
|
*.oopen.de._report._dmarc.oopen.de. IN TXT "v=DMARC1"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧰 3. Sammelskript `/usr/local/bin/dmarc-collect.sh`
|
||||||
|
|
||||||
|
**Datei anlegen:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo tee /usr/local/bin/dmarc-collect.sh >/dev/null <<'EOF'
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
BASE="/var/lib/dmarc"
|
||||||
|
INBOX="$BASE/reports"
|
||||||
|
PROC="$BASE/processed"
|
||||||
|
LOGF="$BASE/logs/collector.log"
|
||||||
|
|
||||||
|
umask 027
|
||||||
|
|
||||||
|
TMPDIR="$(mktemp -d)"
|
||||||
|
EML="$TMPDIR/mail.eml"
|
||||||
|
cat > "$EML"
|
||||||
|
|
||||||
|
ripmime --no-nameless --name-by-type --overwrite -i "$EML" -d "$TMPDIR" >>"$LOGF" 2>&1 || true
|
||||||
|
|
||||||
|
TODAY="$(date -u +%Y/%m/%d)"
|
||||||
|
OUTDIR="$INBOX/$TODAY"
|
||||||
|
mkdir -p "$OUTDIR"
|
||||||
|
|
||||||
|
moved=0
|
||||||
|
shopt -s nullglob
|
||||||
|
for f in "$TMPDIR"/*; do
|
||||||
|
case "$f" in
|
||||||
|
*.xml|*.XML|*.gz|*.zip)
|
||||||
|
sha="$(sha256sum "$f" | awk '{print $1}')"
|
||||||
|
base="$(basename "$f")"
|
||||||
|
dst="$OUTDIR/$(date -u +%Y%m%dT%H%M%SZ)_${sha:0:12}_$base"
|
||||||
|
mv "$f" "$dst"
|
||||||
|
echo "$(date -Is) stored $dst" >> "$LOGF"
|
||||||
|
moved=$((moved+1))
|
||||||
|
;;
|
||||||
|
*) : ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
mkdir -p "$PROC"
|
||||||
|
mv "$EML" "$PROC/$(date -u +%Y%m%dT%H%M%SZ)_mail.eml" || true
|
||||||
|
rm -rf "$TMPDIR"
|
||||||
|
|
||||||
|
if (( moved > 0 )); then
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
echo "$(date -Is) no usable attachment in message" >> "$LOGF"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
EOF
|
||||||
|
sudo apt install -y ripmime
|
||||||
|
sudo chown vmail:vmail /usr/local/bin/dmarc-collect.sh
|
||||||
|
sudo chmod 750 /usr/local/bin/dmarc-collect.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Inhalt von `dmarc-collect.sh`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
BASE="/var/lib/dmarc"
|
||||||
|
INBOX="$BASE/reports"
|
||||||
|
PROC="$BASE/processed"
|
||||||
|
LOGF="$BASE/logs/collector.log"
|
||||||
|
|
||||||
|
umask 027
|
||||||
|
|
||||||
|
TMPDIR="$(mktemp -d)"
|
||||||
|
EML="$TMPDIR/mail.eml"
|
||||||
|
cat > "$EML"
|
||||||
|
|
||||||
|
ripmime --no-nameless --name-by-type --overwrite -i "$EML" -d "$TMPDIR" >>"$LOGF" 2>&1 || true
|
||||||
|
|
||||||
|
TODAY="$(date -u +%Y/%m/%d)"
|
||||||
|
OUTDIR="$INBOX/$TODAY"
|
||||||
|
mkdir -p "$OUTDIR"
|
||||||
|
|
||||||
|
moved=0
|
||||||
|
shopt -s nullglob
|
||||||
|
for f in "$TMPDIR"/*; do
|
||||||
|
case "$f" in
|
||||||
|
*.xml|*.XML|*.gz|*.zip)
|
||||||
|
sha="$(sha256sum "$f" | awk '{print $1}')"
|
||||||
|
base="$(basename "$f")"
|
||||||
|
dst="$OUTDIR/$(date -u +%Y%m%dT%H%M%SZ)_${sha:0:12}_$base"
|
||||||
|
mv "$f" "$dst"
|
||||||
|
echo "$(date -Is) stored $dst" >> "$LOGF"
|
||||||
|
moved=$((moved+1))
|
||||||
|
;;
|
||||||
|
*) : ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
mkdir -p "$PROC"
|
||||||
|
mv "$EML" "$PROC/$(date -u +%Y%m%dT%H%M%SZ)_mail.eml" || true
|
||||||
|
rm -rf "$TMPDIR"
|
||||||
|
|
||||||
|
if (( moved > 0 )); then
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
echo "$(date -Is) no usable attachment in message" >> "$LOGF"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⏱️ 4. Automatische tägliche Auswertung
|
||||||
|
|
||||||
|
**Datei:** `/usr/local/lib/dmarc/daily-run.sh`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo tee /usr/local/lib/dmarc/daily-run.sh >/dev/null <<'EOF'
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
BASE="/var/lib/dmarc"
|
||||||
|
TODAY_DIR="$BASE/reports/$(date -u +%Y/%m/%d)"
|
||||||
|
OUTDIR="$BASE/exports"
|
||||||
|
CSV="$OUTDIR/records.csv"
|
||||||
|
LOGF="$BASE/logs/scan-$(date -u +%F).log"
|
||||||
|
|
||||||
|
mkdir -p "$OUTDIR"
|
||||||
|
|
||||||
|
if [[ -d "$TODAY_DIR" ]]; then
|
||||||
|
/usr/local/bin/dmarc-scan.sh "$TODAY_DIR" --domain fluechtlingsrat-brandenburg.de --csv "$CSV" --append --top 25 --outdir "$OUTDIR" >> "$LOGF" 2>&1
|
||||||
|
fi
|
||||||
|
EOF
|
||||||
|
sudo chmod 750 /usr/local/lib/dmarc/daily-run.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Cronjob anlegen:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
echo '17 3 * * * root /usr/local/lib/dmarc/daily-run.sh' | sudo tee /etc/cron.d/dmarc-daily >/dev/null
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧮 5. Auswertungsskript `/usr/local/bin/dmarc-scan.sh`
|
||||||
|
|
||||||
|
Installation:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt install -y xmlstarlet unzip gzip
|
||||||
|
sudo tee /usr/local/bin/dmarc-scan.sh >/dev/null <<'EOF'
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# dmarc-scan.sh — DMARC-XML-Reports (auch .gz/.zip) einlesen, tabellarisch anzeigen,
|
||||||
|
# Records als CSV exportieren (append optional), Top-IPs ermitteln
|
||||||
|
# und Top-Listen als CSV schreiben.
|
||||||
|
#
|
||||||
|
# Nutzung:
|
||||||
|
# ./dmarc-scan.sh /pfad/zu/reports \
|
||||||
|
# [--domain DOMAIN] \
|
||||||
|
# [--csv pfad/zur/records.csv] \
|
||||||
|
# [--append] \
|
||||||
|
# [--top N] \
|
||||||
|
# [--outdir pfad/zum/ordner]
|
||||||
|
#
|
||||||
|
# Beispiele:
|
||||||
|
# ./dmarc-scan.sh /var/mail/dmarc
|
||||||
|
# ./dmarc-scan.sh /var/mail/dmarc --domain fluechtlingsrat-brandenburg.de --csv dmarc.csv --append --top 15 --outdir ./export
|
||||||
|
#
|
||||||
|
# Voraussetzungen: xmlstarlet, unzip (für .zip), gzip (für .gz)
|
||||||
|
# Debian/Ubuntu: sudo apt-get install xmlstarlet unzip gzip
|
||||||
|
#
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
REPORT_DIR="${1:-}"
|
||||||
|
shift || true
|
||||||
|
|
||||||
|
# Defaults
|
||||||
|
WANT_DOMAIN=""
|
||||||
|
CSV_PATH="./dmarc-summary.csv"
|
||||||
|
APPEND=0
|
||||||
|
TOP_N=10
|
||||||
|
OUTDIR="."
|
||||||
|
|
||||||
|
# Arg-Parsing (einfach)
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "${1:-}" in
|
||||||
|
--domain)
|
||||||
|
WANT_DOMAIN="${2:-}"; shift 2 || true ;;
|
||||||
|
--csv)
|
||||||
|
CSV_PATH="${2:-}"; shift 2 || true ;;
|
||||||
|
--append)
|
||||||
|
APPEND=1; shift || true ;;
|
||||||
|
--top)
|
||||||
|
TOP_N="${2:-10}"; shift 2 || true ;;
|
||||||
|
--outdir)
|
||||||
|
OUTDIR="${2:-.}"; shift 2 || true ;;
|
||||||
|
*)
|
||||||
|
# Unbekannte Option ignorieren
|
||||||
|
shift || true ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ -z "$REPORT_DIR" || ! -d "$REPORT_DIR" ]]; then
|
||||||
|
echo "Fehler: Bitte ein Verzeichnis mit DMARC-Reports angeben."
|
||||||
|
echo "Beispiel: $0 /var/mail/dmarc --domain fluechtlingsrat-brandenburg.de --csv dmarc.csv --append --top 15 --outdir ./export"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v xmlstarlet >/dev/null 2>&1; then
|
||||||
|
echo "Fehler: xmlstarlet nicht gefunden. Bitte installieren (z.B. 'sudo apt-get install xmlstarlet')."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$OUTDIR"
|
||||||
|
|
||||||
|
# CSV-Header für Records; bei --append nur schreiben, wenn Datei noch nicht existiert
|
||||||
|
RECORDS_HEADER="report_org,policy_domain,begin_utc,end_utc,source_ip,count,disposition,spf,dkim,header_from"
|
||||||
|
ensure_records_header() {
|
||||||
|
if [[ "$APPEND" -eq 1 ]]; then
|
||||||
|
if [[ ! -f "$CSV_PATH" ]]; then
|
||||||
|
echo "$RECORDS_HEADER" > "$CSV_PATH"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "$RECORDS_HEADER" > "$CSV_PATH"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
ensure_records_header
|
||||||
|
|
||||||
|
# Zähler & Sets
|
||||||
|
declare -A SEEN_IPS=()
|
||||||
|
declare -A IP_COUNTS=() # Summe pro IP
|
||||||
|
declare -A SPF_ONLY_FAIL_IP=() # nur SPF fail pro IP
|
||||||
|
declare -A DKIM_ONLY_FAIL_IP=() # nur DKIM fail pro IP
|
||||||
|
declare -A BOTH_FAIL_IP=() # SPF+DKIM fail pro IP
|
||||||
|
|
||||||
|
total_msgs=0
|
||||||
|
pass_msgs=0
|
||||||
|
fail_msgs=0
|
||||||
|
spf_only_fail=0
|
||||||
|
dkim_only_fail=0
|
||||||
|
both_fail=0
|
||||||
|
|
||||||
|
# Hilfsfunktion: Epoch -> Datum (UTC)
|
||||||
|
epoch2date() {
|
||||||
|
local e="$1"
|
||||||
|
if [[ -z "$e" ]]; then printf "-"; return; fi
|
||||||
|
date -u -d @"$e" +"%Y-%m-%d %H:%M:%S UTC" 2>/dev/null || printf "%s" "$e"
|
||||||
|
}
|
||||||
|
|
||||||
|
# CSV-escape (Felder in Anführungszeichen, doppelte Anführungszeichen verdoppeln)
|
||||||
|
csv_escape() {
|
||||||
|
local s="${1:-}"
|
||||||
|
s="${s//\"/\"\"}"
|
||||||
|
printf "\"%s\"" "$s"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Eine einzelne XML-Datei parsen
|
||||||
|
parse_xml() {
|
||||||
|
local xml_input="$1"
|
||||||
|
|
||||||
|
# Domain aus policy_published, ggf. für Filter
|
||||||
|
local domain
|
||||||
|
domain=$(xmlstarlet sel -T -t -v "/feedback/policy_published/domain" "$xml_input" 2>/dev/null || true)
|
||||||
|
if [[ -n "$WANT_DOMAIN" && "$domain" != "$WANT_DOMAIN" ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
local org begin end pol sp aspf adkim
|
||||||
|
org=$(xmlstarlet sel -T -t -v "/feedback/report_metadata/org_name" "$xml_input" 2>/dev/null || printf "-")
|
||||||
|
begin=$(xmlstarlet sel -T -t -v "/feedback/report_metadata/date_range/begin" "$xml_input" 2>/dev/null || printf "")
|
||||||
|
end=$(xmlstarlet sel -T -t -v "/feedback/report_metadata/date_range/end" "$xml_input" 2>/dev/null || printf "")
|
||||||
|
pol=$(xmlstarlet sel -T -t -v "/feedback/policy_published/p" "$xml_input" 2>/dev/null || printf "-")
|
||||||
|
sp=$(xmlstarlet sel -T -t -v "/feedback/policy_published/sp" "$xml_input" 2>/dev/null || printf "-")
|
||||||
|
aspf=$(xmlstarlet sel -T -t -v "/feedback/policy_published/aspf" "$xml_input" 2>/dev/null || printf "-")
|
||||||
|
adkim=$(xmlstarlet sel -T -t -v "/feedback/policy_published/adkim" "$xml_input" 2>/dev/null || printf "-")
|
||||||
|
|
||||||
|
# Report-Header
|
||||||
|
echo "=============================================================================="
|
||||||
|
echo "Report von: ${org}"
|
||||||
|
echo "Domain (Policy): ${domain} (p=${pol}, sp=${sp}, aspf=${aspf}, adkim=${adkim})"
|
||||||
|
echo "Zeitraum: $(epoch2date "$begin") – $(epoch2date "$end")"
|
||||||
|
echo "------------------------------------------------------------------------------"
|
||||||
|
printf "%-16s %7s %-10s %-6s %-6s %s\n" "Source IP" "Count" "Disposition" "SPF" "DKIM" "Header-From"
|
||||||
|
echo "------------------------------------------------------------------------------"
|
||||||
|
|
||||||
|
# Alle <record>-Einträge tabellarisch ausgeben
|
||||||
|
while IFS='|' read -r ip cnt dispo spfres dkimres hfrom; do
|
||||||
|
[[ -z "$ip$cnt$dispo$spfres$dkimres$hfrom" ]] && continue
|
||||||
|
|
||||||
|
local n=0
|
||||||
|
if [[ -n "${cnt:-}" && "$cnt" =~ ^[0-9]+$ ]]; then n="$cnt"; fi
|
||||||
|
|
||||||
|
total_msgs=$(( total_msgs + n ))
|
||||||
|
[[ -n "$ip" ]] && SEEN_IPS["$ip"]=1
|
||||||
|
[[ -n "$ip" ]] && IP_COUNTS["$ip"]=$(( ${IP_COUNTS["$ip"]:-0} + n ))
|
||||||
|
|
||||||
|
if [[ "${spfres:-}" == "pass" && "${dkimres:-}" == "pass" ]]; then
|
||||||
|
pass_msgs=$(( pass_msgs + n ))
|
||||||
|
else
|
||||||
|
fail_msgs=$(( fail_msgs + n ))
|
||||||
|
if [[ "${spfres:-}" != "pass" && "${dkimres:-}" == "pass" ]]; then
|
||||||
|
spf_only_fail=$(( spf_only_fail + n ))
|
||||||
|
[[ -n "$ip" ]] && SPF_ONLY_FAIL_IP["$ip"]=$(( ${SPF_ONLY_FAIL_IP["$ip"]:-0} + n ))
|
||||||
|
elif [[ "${spfres:-}" == "pass" && "${dkimres:-}" != "pass" ]]; then
|
||||||
|
dkim_only_fail=$(( dkim_only_fail + n ))
|
||||||
|
[[ -n "$ip" ]] && DKIM_ONLY_FAIL_IP["$ip"]=$(( ${DKIM_ONLY_FAIL_IP["$ip"]:-0} + n ))
|
||||||
|
else
|
||||||
|
both_fail=$(( both_fail + n ))
|
||||||
|
[[ -n "$ip" ]] && BOTH_FAIL_IP["$ip"]=$(( ${BOTH_FAIL_IP["$ip"]:-0} + n ))
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf "%-16s %7s %-10s %-6s %-6s %s\n" "${ip:--}" "${n}" "${dispo:--}" "${spfres:--}" "${dkimres:--}" "${hfrom:--}"
|
||||||
|
|
||||||
|
local begin_human end_human
|
||||||
|
begin_human="$(epoch2date "$begin")"
|
||||||
|
end_human="$(epoch2date "$end")"
|
||||||
|
{
|
||||||
|
csv_escape "$org"; printf ","
|
||||||
|
csv_escape "$domain"; printf ","
|
||||||
|
csv_escape "$begin_human"; printf ","
|
||||||
|
csv_escape "$end_human"; printf ","
|
||||||
|
csv_escape "${ip:-}"; printf ","
|
||||||
|
printf "%s," "$n"
|
||||||
|
csv_escape "${dispo:-}"; printf ","
|
||||||
|
csv_escape "${spfres:-}"; printf ","
|
||||||
|
csv_escape "${dkimres:-}"; printf ","
|
||||||
|
csv_escape "${hfrom:-}"; printf "\n"
|
||||||
|
} >> "$CSV_PATH"
|
||||||
|
|
||||||
|
done < <(xmlstarlet sel -T -t \
|
||||||
|
-m "/feedback/record" \
|
||||||
|
-v "row/source_ip" -o "|" \
|
||||||
|
-v "row/count" -o "|" \
|
||||||
|
-v "row/policy_evaluated/disposition" -o "|" \
|
||||||
|
-v "row/policy_evaluated/spf" -o "|" \
|
||||||
|
-v "row/policy_evaluated/dkim" -o "|" \
|
||||||
|
-v "identifiers/header_from" -n \
|
||||||
|
"$xml_input" 2>/dev/null)
|
||||||
|
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
# Alle Dateien im Verzeichnis verarbeiten
|
||||||
|
shopt -s nullglob
|
||||||
|
for f in "$REPORT_DIR"/*; do
|
||||||
|
case "$f" in
|
||||||
|
*.xml)
|
||||||
|
parse_xml "$f"
|
||||||
|
;;
|
||||||
|
*.gz)
|
||||||
|
if command -v gzip >/dev/null 2>&1; then
|
||||||
|
tmp="$(mktemp)"
|
||||||
|
if gzip -cd "$f" > "$tmp"; then
|
||||||
|
parse_xml "$tmp"
|
||||||
|
else
|
||||||
|
echo "Warnung: Konnte $f nicht entpacken."
|
||||||
|
fi
|
||||||
|
rm -f "$tmp"
|
||||||
|
else
|
||||||
|
echo "Warnung: gzip nicht verfügbar, überspringe $f"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*.zip)
|
||||||
|
if command -v unzip >/dev/null 2>&1; then
|
||||||
|
tmpdir="$(mktemp -d)"
|
||||||
|
if unzip -qq -j "$f" '*.xml' -d "$tmpdir" >/dev/null 2>&1; then
|
||||||
|
for x in "$tmpdir"/*.xml; do
|
||||||
|
[[ -e "$x" ]] || continue
|
||||||
|
parse_xml "$x"
|
||||||
|
done
|
||||||
|
else
|
||||||
|
echo "Warnung: Konnte $f nicht entpacken (oder keine XML darin)."
|
||||||
|
fi
|
||||||
|
rm -rf "$tmpdir"
|
||||||
|
else
|
||||||
|
echo "Warnung: unzip nicht verfügbar, überspringe $f"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*) : ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Zusammenfassung
|
||||||
|
unique_ips=${#SEEN_IPS[@]}
|
||||||
|
echo "=============================================================================="
|
||||||
|
echo "GESAMT-ZUSAMMENFASSUNG"
|
||||||
|
echo "Nachrichten gesamt: $total_msgs"
|
||||||
|
echo "Eindeutige Source-IPs: $unique_ips"
|
||||||
|
echo "Alle Auth PASS: $pass_msgs"
|
||||||
|
echo "SPF/DKIM Fehler gesamt: $fail_msgs"
|
||||||
|
echo " ├─ nur SPF-Fail: $spf_only_fail"
|
||||||
|
echo " ├─ nur DKIM-Fail: $dkim_only_fail"
|
||||||
|
echo " └─ SPF+DKIM-Fail: $both_fail"
|
||||||
|
[[ -n "$WANT_DOMAIN" ]] && echo "Gefilterte Domain: $WANT_DOMAIN"
|
||||||
|
echo "Records-CSV: $CSV_PATH"
|
||||||
|
echo "Hinweis: 'Fail' umfasst Records, bei denen SPF oder DKIM nicht 'pass' war."
|
||||||
|
|
||||||
|
# Top-Listen auf STDOUT
|
||||||
|
echo
|
||||||
|
echo "TOP $TOP_N IPs nach Anzahl (über alle Reports):"
|
||||||
|
{
|
||||||
|
for ip in "${!IP_COUNTS[@]}"; do
|
||||||
|
printf "%10d %s\n" "${IP_COUNTS[$ip]}" "$ip"
|
||||||
|
done
|
||||||
|
} | sort -rn | head -n "$TOP_N" | awk '{printf " %2d) %-16s %7s\n", NR, $2, $1}'
|
||||||
|
|
||||||
|
if (( fail_msgs > 0 )); then
|
||||||
|
echo
|
||||||
|
echo "Top-IPs nur SPF-Fail:"
|
||||||
|
{ for ip in "${!SPF_ONLY_FAIL_IP[@]}"; do printf "%10d %s\n" "${SPF_ONLY_FAIL_IP[$ip]}" "$ip"; done; } \
|
||||||
|
| sort -rn | head -n "$TOP_N" | awk '{printf " %2d) %-16s %7s\n", NR, $2, $1}'
|
||||||
|
echo
|
||||||
|
echo "Top-IPs nur DKIM-Fail:"
|
||||||
|
{ for ip in "${!DKIM_ONLY_FAIL_IP[@]}"; do printf "%10d %s\n" "${DKIM_ONLY_FAIL_IP[$ip]}" "$ip"; done; } \
|
||||||
|
| sort -rn | head -n "$TOP_N" | awk '{printf " %2d) %-16s %7s\n", NR, $2, $1}'
|
||||||
|
echo
|
||||||
|
echo "Top-IPs SPF+DKIM-Fail:"
|
||||||
|
{ for ip in "${!BOTH_FAIL_IP[@]}"; do printf "%10d %s\n" "${BOTH_FAIL_IP[$ip]}" "$ip"; done; } \
|
||||||
|
| sort -rn | head -n "$TOP_N" | awk '{printf " %2d) %-16s %7s\n", NR, $2, $1}'
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---- CSV-Exporte der Top-Listen ---------------------------------------------
|
||||||
|
|
||||||
|
write_top_csv () {
|
||||||
|
local outfile="$1"; shift
|
||||||
|
local -n assoc="$1" # name reference auf assoziatives Array
|
||||||
|
echo "ip,count" > "$outfile"
|
||||||
|
if [[ "${#assoc[@]}" -eq 0 ]]; then
|
||||||
|
: # leer
|
||||||
|
else
|
||||||
|
for ip in "${!assoc[@]}"; do
|
||||||
|
printf "%s,%s\n" "$ip" "${assoc[$ip]}"
|
||||||
|
done | sort -t, -k2,2nr > "$outfile"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Gesamt-Top-IPs
|
||||||
|
write_top_csv "$OUTDIR/top_ips.csv" IP_COUNTS
|
||||||
|
|
||||||
|
# Top-Listen nach Fail-Kategorien
|
||||||
|
write_top_csv "$OUTDIR/top_spf_fail_ips.csv" SPF_ONLY_FAIL_IP
|
||||||
|
write_top_csv "$OUTDIR/top_dkim_fail_ips.csv" DKIM_ONLY_FAIL_IP
|
||||||
|
write_top_csv "$OUTDIR/top_both_fail_ips.csv" BOTH_FAIL_IP
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "Exportierte Top-CSV-Dateien:"
|
||||||
|
echo " $OUTDIR/top_ips.csv"
|
||||||
|
echo " $OUTDIR/top_spf_fail_ips.csv"
|
||||||
|
echo " $OUTDIR/top_dkim_fail_ips.csv"
|
||||||
|
echo " $OUTDIR/top_both_fail_ips.csv"
|
||||||
|
|
||||||
|
EOF
|
||||||
|
sudo chmod 750 /usr/local/bin/dmarc-scan.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Beschreibung: Das Skript liest XML/ZIP/GZ-Reports, zeigt eine Tabelle pro Report, schreibt eine Records-CSV (mit `--append` fortsetzbar) und exportiert Top-Listen als CSV in `--outdir`.
|
||||||
|
|
||||||
|
**Wichtige Parameter:**
|
||||||
|
|
||||||
|
- `--domain DOMAIN` (Filter)
|
||||||
|
- `--csv PFAD` (Records-CSV)
|
||||||
|
- `--append` (anhängen statt überschreiben)
|
||||||
|
- `--top N` (Top-Liste Größe)
|
||||||
|
- `--outdir PFAD` (Top-CSV Ziel)
|
||||||
|
|
||||||
|
**Beispiel:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dmarc-scan.sh /var/lib/dmarc/reports/2025/11/12 --domain fluechtlingsrat-brandenburg.de --csv /var/lib/dmarc/exports/records.csv --append --top 25 --outdir /var/lib/dmarc/exports/
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔁 6. Logrotate
|
||||||
|
|
||||||
|
**Datei:** `/etc/logrotate.d/dmarc`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/var/lib/dmarc/logs/*.log {
|
||||||
|
weekly
|
||||||
|
rotate 12
|
||||||
|
compress
|
||||||
|
delaycompress
|
||||||
|
missingok
|
||||||
|
notifempty
|
||||||
|
create 640 vmail vmail
|
||||||
|
sharedscripts
|
||||||
|
postrotate
|
||||||
|
systemctl reload postfix >/dev/null 2>&1 || true
|
||||||
|
endscript
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✅ 7. Test
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo -u vmail /usr/local/bin/dmarc-collect.sh < testmail.eml
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Autor:** oopen.de / Systemkonfiguration
|
||||||
|
**Stand:** November 2025
|
||||||
|
**Version:** 1.2
|
||||||
@@ -19,3 +19,19 @@ doveadm index -A INBOX
|
|||||||
# -the storage files will be also checked.
|
# -the storage files will be also checked.
|
||||||
# -
|
# -
|
||||||
doveadm force-resync -A INBOX
|
doveadm force-resync -A INBOX
|
||||||
|
|
||||||
|
|
||||||
|
# - Index users .Sent Folder
|
||||||
|
# -
|
||||||
|
doveadm index -u <user@domain.ltd> .Sent
|
||||||
|
|
||||||
|
|
||||||
|
# - oder alternativ und etwas allgemeiner für egal welchen Unterordner
|
||||||
|
# - ==================================================================
|
||||||
|
# -
|
||||||
|
# - Index eines Unterverzeichnisses einer Mailbox löschen
|
||||||
|
# -
|
||||||
|
# - z.Bsp. für den 'gesendet' Ordner der Mailbox presse@mbr-berlin.de
|
||||||
|
# -
|
||||||
|
# - rm /var/vmail/mbr-berlin.de/presse/Maildir/.Sent/dovecot.index*
|
||||||
|
# - systemctl restart dovecot.service
|
||||||
|
|||||||
@@ -1168,19 +1168,38 @@ fi
|
|||||||
## - If not testing as user roor, you need to change int a directora, where the testing
|
## - If not testing as user roor, you need to change int a directora, where the testing
|
||||||
## - user has read/write? access, even if the sample spam file is located at /tmp
|
## - user has read/write? access, even if the sample spam file is located at /tmp
|
||||||
## -
|
## -
|
||||||
echononl " Download a sample spam file"
|
echononl " Save a sample spam file into /root folder."
|
||||||
installation_failed=false
|
cat <<'EOF' > /root/sample-spam.txt
|
||||||
wget -O /tmp/sample-spam.txt https://opensource.apple.com/source/SpamAssassin/SpamAssassin-137.1/SpamAssassin/sample-spam.txt 2> $tmp_err_msg
|
Subject: Test spam mail (GTUBE)
|
||||||
if [[ "$?" -ne 0 ]] ; then
|
Message-ID: <GTUBE1.1010101@example.net>
|
||||||
installation_failed=true
|
Date: Wed, 23 Jul 2003 23:30:00 +0200
|
||||||
|
From: Sender <sender@example.net>
|
||||||
|
To: Recipient <recipient@example.net>
|
||||||
|
Precedence: junk
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=us-ascii
|
||||||
|
Content-Transfer-Encoding: 7bit
|
||||||
|
|
||||||
|
This is the GTUBE, the
|
||||||
|
Generic
|
||||||
|
Test for
|
||||||
|
Unsolicited
|
||||||
|
Bulk
|
||||||
|
Email
|
||||||
|
|
||||||
|
If your spam filter supports it, the GTUBE provides a test by which you
|
||||||
|
can verify that the filter is installed correctly and is detecting incoming
|
||||||
|
spam. You can send yourself a test mail containing the following string of
|
||||||
|
characters (in upper case and with no white spaces and line breaks):
|
||||||
|
|
||||||
|
XJS*C4JDBQADN1.NSBN3*2IDNEN*GTUBE-STANDARD-ANTI-UBE-TEST-EMAIL*C.34X
|
||||||
|
|
||||||
|
You should send this test mail from an account outside of your network
|
||||||
|
EOF
|
||||||
|
if [[ $? -ne 0 ]] ; then
|
||||||
|
echo_failed
|
||||||
error "$(cat $tmp_err_msg)"
|
error "$(cat $tmp_err_msg)"
|
||||||
fi
|
else
|
||||||
cp -a /tmp/sample-spam.txt /root > $tmp_err_msg 2>&1
|
|
||||||
if [[ "$?" -ne 0 ]] ; then
|
|
||||||
installation_failed=true
|
|
||||||
error "$(cat $tmp_err_msg)"
|
|
||||||
fi
|
|
||||||
if ! $installation_failed ; then
|
|
||||||
echo_ok
|
echo_ok
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -1470,27 +1489,45 @@ echo -e " \033[37m\033[1mConfigure Distributed Checksum Clearinghouses (DCC)..
|
|||||||
|
|
||||||
info "Open firewall outgoing port UDP 6277 and if DCC Server is running\n also in- and outfoing port TCP 6277."
|
info "Open firewall outgoing port UDP 6277 and if DCC Server is running\n also in- and outfoing port TCP 6277."
|
||||||
|
|
||||||
|
services=("clamav-freshclam" "clamav-daemon" "adcc")
|
||||||
|
for svc in "${services[@]}"; do
|
||||||
|
echononl " Stop Service '${svc}.."
|
||||||
|
if systemctl is-active --quiet "$svc"; then
|
||||||
|
systemctl stop "$svc" > /dev/null 2> $tmp_err_msg
|
||||||
|
if [[ $? -ne 0 ]] ; then
|
||||||
|
echo_failed
|
||||||
|
|
||||||
if ps -ax | grep /var/dcc/libexec/dccifd | grep -v grep > /dev/null 2>&1 ; then
|
|
||||||
echononl " An instance off dccifd ist already running. Stop it now."
|
|
||||||
installation_failed=false
|
|
||||||
if $systemd_exists ; then
|
|
||||||
systemctl stop adcc > /dev/null 2> $tmp_err_msg
|
|
||||||
if [[ "$?" -ne 0 ]] ; then
|
|
||||||
installation_failed=true
|
|
||||||
error "$(cat $tmp_err_msg)"
|
error "$(cat $tmp_err_msg)"
|
||||||
fi
|
|
||||||
else
|
else
|
||||||
/etc/init.d/adcc stop > /dev/null 2> $tmp_err_msg
|
|
||||||
if [[ "$?" -ne 0 ]] ; then
|
|
||||||
installation_failed=true
|
|
||||||
error "$(cat $tmp_err_msg)"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
if ! $installation_failed ; then
|
|
||||||
echo_ok
|
echo_ok
|
||||||
fi
|
fi
|
||||||
fi
|
else
|
||||||
|
echo_skipped
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#if ps -ax | grep /var/dcc/libexec/dccifd | grep -v grep > /dev/null 2>&1 ; then
|
||||||
|
# echononl " An instance off dccifd ist already running. Stop it now."
|
||||||
|
# installation_failed=false
|
||||||
|
# if $systemd_exists ; then
|
||||||
|
# systemctl stop adcc > /dev/null 2> $tmp_err_msg
|
||||||
|
# if [[ "$?" -ne 0 ]] ; then
|
||||||
|
# installation_failed=true
|
||||||
|
# error "$(cat $tmp_err_msg)"
|
||||||
|
# fi
|
||||||
|
# else
|
||||||
|
# /etc/init.d/adcc stop > /dev/null 2> $tmp_err_msg
|
||||||
|
# if [[ "$?" -ne 0 ]] ; then
|
||||||
|
# installation_failed=true
|
||||||
|
# error "$(cat $tmp_err_msg)"
|
||||||
|
# fi
|
||||||
|
# fi
|
||||||
|
# if ! $installation_failed ; then
|
||||||
|
# echo_ok
|
||||||
|
# fi
|
||||||
|
#fi
|
||||||
|
|
||||||
_dcc_src_dir="$script_dir"
|
_dcc_src_dir="$script_dir"
|
||||||
#_archiv=dcc-dccproc.tar.Z
|
#_archiv=dcc-dccproc.tar.Z
|
||||||
@@ -4003,6 +4040,7 @@ if [[ ! -f "/etc/postfix/spam_lovers" ]]; then
|
|||||||
# - adress_1@domain3.com 0
|
# - adress_1@domain3.com 0
|
||||||
# - domain3.com 1
|
# - domain3.com 1
|
||||||
# -
|
# -
|
||||||
|
# - Wichtig: letzte Zeile mit Newline abschließen!
|
||||||
EOF
|
EOF
|
||||||
if [[ $? -eq 0 ]] ; then
|
if [[ $? -eq 0 ]] ; then
|
||||||
echo_ok
|
echo_ok
|
||||||
@@ -4031,6 +4069,7 @@ if [[ ! -f "/etc/postfix/virus_lovers" ]]; then
|
|||||||
# - adress_1@domain3.com 0
|
# - adress_1@domain3.com 0
|
||||||
# - domain3.com 1
|
# - domain3.com 1
|
||||||
# -
|
# -
|
||||||
|
# - Wichtig: letzte Zeile mit Newline abschließen!
|
||||||
EOF
|
EOF
|
||||||
if [[ $? -eq 0 ]] ; then
|
if [[ $? -eq 0 ]] ; then
|
||||||
echo_ok
|
echo_ok
|
||||||
@@ -4187,203 +4226,111 @@ read_hash(\%whitelist_sender, '/etc/postfix/sender_whitelist');
|
|||||||
@whitelist_sender_maps = (\%whitelist_sender);
|
@whitelist_sender_maps = (\%whitelist_sender);
|
||||||
|
|
||||||
|
|
||||||
## ---
|
|
||||||
## - Default antivirus checking mode
|
|
||||||
## ---
|
|
||||||
|
|
||||||
## - bypass_virus_checks_maps
|
# ----------------------------------------------------------
|
||||||
## -
|
# Basis-Quarantäneverzeichnis
|
||||||
## - Addresses/Domains listet here will not be checked.
|
# ----------------------------------------------------------
|
||||||
## -
|
|
||||||
## - !! Notice !!
|
|
||||||
## -
|
|
||||||
## - Virus checks are bypassed only if all of the recipients of a message have
|
|
||||||
## - been added to one of these variables. If even one recipient is not listed,
|
|
||||||
## - virus-checking will still be performed. To ensure that virus is still delivered
|
|
||||||
## - to whitelisted recipients in such cases, use the "virus_lovers" features
|
|
||||||
## - see below.
|
|
||||||
## -
|
|
||||||
@bypass_virus_checks_maps = (
|
|
||||||
\%bypass_virus_checks, \@bypass_virus_checks_acl, \\\$bypass_virus_checks_re);
|
|
||||||
|
|
||||||
|
\$QUARANTINEDIR = '${QUARANTINE_DIR}';
|
||||||
|
|
||||||
## - We will use '%bypass_virus_checks_maps'. So we could set:
|
# Keine automatisch erzeugten Unterverzeichnisse wie a/, b/, c/, f/, g/
|
||||||
## -
|
|
||||||
## - %bypass_virus_checks = (
|
|
||||||
## - # Adresses
|
|
||||||
## - adress@domain1.com => '1',
|
|
||||||
## - [..]
|
|
||||||
## - # All addresses of a domain
|
|
||||||
## - domain2.com => '1',
|
|
||||||
## - [..]
|
|
||||||
## - # All adresses of a domain except a single user
|
|
||||||
## - address_1@domain3.com => '0',
|
|
||||||
## - domain3.com => '1',
|
|
||||||
## - );
|
|
||||||
## -
|
|
||||||
## - But we will use the read_hash function to read in a list
|
|
||||||
## - of recipients from the external file '/etc/postfix/spam_lovers'
|
|
||||||
## -
|
|
||||||
## - Example '/etc/postfix/virus_lovers'
|
|
||||||
## -
|
|
||||||
## - # Adresses
|
|
||||||
## - adress@domain1.com 1
|
|
||||||
## - [..]
|
|
||||||
## -
|
|
||||||
## - # All addresses of a domain
|
|
||||||
## - domain2.com 1
|
|
||||||
## - [..]
|
|
||||||
## -
|
|
||||||
## - # All adresses of a domain except a single user
|
|
||||||
## - adress_1@domain3.com 0
|
|
||||||
## - domain3.com 1
|
|
||||||
## -
|
|
||||||
read_hash(\%bypass_virus_checks, '/etc/postfix/virus_lovers');
|
|
||||||
|
|
||||||
|
|
||||||
## - virus_lovers_maps
|
|
||||||
## -
|
|
||||||
## - For Adresses/Domains listet at spam_lovers_maps, no spam actions (like
|
|
||||||
## - adding spam headers or discarding the mail) will be performed.
|
|
||||||
## -
|
|
||||||
@virus_lovers_maps = (
|
|
||||||
\%virus_lovers, \@virus_lovers_acl, \\\$virus_lovers_re);
|
|
||||||
|
|
||||||
## - We will use the read_hash function to read in a list of recipients
|
|
||||||
## - from the external file '/etc/postfix/spam_lovers' into '%spam_lovers'.
|
|
||||||
## -
|
|
||||||
## - For more explanations see above
|
|
||||||
## -
|
|
||||||
read_hash(\%virus_lovers, '/etc/postfix/virus_lovers');
|
|
||||||
|
|
||||||
|
|
||||||
## ---
|
|
||||||
## - Default SPAM checking mode
|
|
||||||
## ---
|
|
||||||
|
|
||||||
## - bypass_spam_checks_maps
|
|
||||||
## -
|
|
||||||
## - Addresses/Domains listet here will not be checked.
|
|
||||||
## -
|
|
||||||
## - !! Notice !!
|
|
||||||
## -
|
|
||||||
## - Spam checks are bypassed only if all of the recipients of a message have
|
|
||||||
## - been added to one of these variables. If even one recipient is not listed,
|
|
||||||
## - spam-checking will still be performed. To ensure that spam is still delivered
|
|
||||||
## - to whitelisted recipients in such cases, use the "spam_lovers" features
|
|
||||||
## - see below.
|
|
||||||
## -
|
|
||||||
@bypass_spam_checks_maps = (
|
|
||||||
\%bypass_spam_checks, \@bypass_spam_checks_acl, \\\$bypass_spam_checks_re);
|
|
||||||
|
|
||||||
## - We will use '%bypass_spam_checks'. So we could set:
|
|
||||||
## -
|
|
||||||
## - %bypass_spam_checks = (
|
|
||||||
## - # Adresses
|
|
||||||
## - adress@domain1.com => '1',
|
|
||||||
## - [..]
|
|
||||||
## - # All addresses of a domain
|
|
||||||
## - domain2.com => '1',
|
|
||||||
## - [..]
|
|
||||||
## - # All adresses of a domain except a single user
|
|
||||||
## - address_1@domain3.com => '0',
|
|
||||||
## - domain3.com => '1',
|
|
||||||
## - );
|
|
||||||
## -
|
|
||||||
## - But we will use the read_hash function to read in a list
|
|
||||||
## - of recipients from the external file '/etc/postfix/spam_lovers'
|
|
||||||
## -
|
|
||||||
## - Example '/etc/postfix/spam_lovers'
|
|
||||||
## -
|
|
||||||
## - # Adresses
|
|
||||||
## - adress@domain1.com 1
|
|
||||||
## - [..]
|
|
||||||
## -
|
|
||||||
## - # All addresses of a domain
|
|
||||||
## - domain2.com 1
|
|
||||||
## - [..]
|
|
||||||
## -
|
|
||||||
## - # All adresses of a domain except a single user
|
|
||||||
## - adress_1@domain3.com 0
|
|
||||||
## - domain3.com 1
|
|
||||||
## -
|
|
||||||
read_hash(\%bypass_spam_checks, '/etc/postfix/spam_lovers');
|
|
||||||
|
|
||||||
|
|
||||||
## - spam_lovers_maps
|
|
||||||
## -
|
|
||||||
## - For Adresses/Domains listet at spam_lovers_maps, no spam actions (like
|
|
||||||
## - adding spam headers or discarding the mail) will be performed.
|
|
||||||
## -
|
|
||||||
@spam_lovers_maps = (
|
|
||||||
\%spam_lovers, \@spam_lovers_acl, \\\$spam_lovers_re);
|
|
||||||
|
|
||||||
## - We will use the read_hash function to read in a list of recipients
|
|
||||||
## - from the external file '/etc/postfix/spam_lovers' into '%spam_lovers'.
|
|
||||||
## -
|
|
||||||
## - For more explanations see above
|
|
||||||
## -
|
|
||||||
read_hash(\%spam_lovers, '/etc/postfix/spam_lovers');
|
|
||||||
|
|
||||||
|
|
||||||
## - overrides settings in 20-debian_defaults
|
|
||||||
## -
|
|
||||||
|
|
||||||
\$final_virus_destiny = D_DISCARD; # (data not lost, see virus quarantine)
|
|
||||||
\$final_banned_destiny = D_DISCARD; # D_REJECT when front-end MTA
|
|
||||||
#\$final_spam_destiny = D_DISCARD;
|
|
||||||
\$final_spam_destiny = D_BOUNCE;
|
|
||||||
#\$final_bad_header_destiny = D_PASS; # False-positive prone (for spam)
|
|
||||||
|
|
||||||
|
|
||||||
##- Moved to file '/etc/amavis/policy_banks.conf'
|
|
||||||
## -
|
|
||||||
## - \$sa_tag_level_deflt = 2.0; # add spam info headers if at, or above that level
|
|
||||||
## - \$sa_tag2_level_deflt = 5.1; # add 'spam detected' headers at that level
|
|
||||||
## - \$sa_kill_level_deflt = 10.31; # reject/bounce/discard/pass
|
|
||||||
## -
|
|
||||||
do "/etc/amavis/policy_banks.conf"; # Externe Datei einbinden
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## - QUARANTINE
|
|
||||||
## -
|
|
||||||
\$QUARANTINEDIR = "$QUARANTINE_DIR";
|
|
||||||
\$quarantine_subdir_levels = 0;
|
\$quarantine_subdir_levels = 0;
|
||||||
|
|
||||||
|
\$sa_spam_subject_tag = undef; # Kein Prefix wie "***SPAM***" o.ä.
|
||||||
|
\$sa_spam_modifies_subj = 0; # Betreff NICHT verändern
|
||||||
|
|
||||||
## - don't store mails in quarantine directory
|
# Viren: /var/QUARANTINE/virus/virus-<msgid>.gz
|
||||||
## -
|
\$virus_quarantine_method = 'local:virus/virus-%m.gz';
|
||||||
#\$virus_quarantine_method = undef;
|
|
||||||
#\$spam_quarantine_method = undef;
|
|
||||||
#\$banned_files_quarantine_method = undef;
|
|
||||||
#\$bad_header_quarantine_method = undef;
|
|
||||||
|
|
||||||
## - store mails in quarantine directory
|
# Spam (Kill-Spam): /var/QUARANTINE/spam/spam-<msgid>.gz
|
||||||
## -
|
|
||||||
\$virus_quarantine_method = 'local:virus/virus-%m';
|
|
||||||
\$spam_quarantine_method = 'local:spam/spam-%m.gz';
|
\$spam_quarantine_method = 'local:spam/spam-%m.gz';
|
||||||
\$banned_files_quarantine_method = 'local:banned/banned-%m';
|
|
||||||
\$bad_header_quarantine_method = 'local:bad-headers/badh-%m';
|
|
||||||
\$clean_quarantine_method = undef;
|
|
||||||
\$archive_quarantine_method = undef;
|
|
||||||
|
|
||||||
#\$virus_admin ="$QUARANTINE_ADMIN";
|
# Banned: /var/QUARANTINE/banned/banned-<msgid>
|
||||||
#\$spam_admin = "$QUARANTINE_ADMIN";
|
\$banned_files_quarantine_method = 'local:banned/banned-%m';
|
||||||
#\$banned_admin = "$QUARANTINE_ADMIN";
|
|
||||||
#\$bad_header_admin = "$QUARANTINE_ADMIN";
|
# Bad headers: /var/QUARANTINE/bad-headers/badh-<msgid>
|
||||||
|
\$bad_header_quarantine_method = 'local:bad-headers/badh-%m';
|
||||||
|
|
||||||
|
|
||||||
|
# ----------------------------------------------------------
|
||||||
|
# Einbinden der Spam- und Virus-Lovers Dateien
|
||||||
|
# ----------------------------------------------------------
|
||||||
|
|
||||||
|
@bypass_spam_checks_maps = (
|
||||||
|
read_hash('/etc/postfix/spam_lovers'),
|
||||||
|
);
|
||||||
|
|
||||||
|
@bypass_virus_checks_maps = (
|
||||||
|
read_hash('/etc/postfix/virus_lovers'),
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
# ----------------------------------------------------------
|
||||||
|
# Spam-Schwellwerte
|
||||||
|
# ----------------------------------------------------------
|
||||||
|
|
||||||
|
\$sa_tag_level_deflt = 1.9; # ab hier Info-Header
|
||||||
|
\$sa_tag2_level_deflt = 5.1; # ab hier X-Spam-Flag: YES
|
||||||
|
\$sa_kill_level_deflt = 9.51; # high-spam - final destiny (DISCARD)
|
||||||
|
\$sa_dsn_cutoff_level = 20.1; # ab hier keine DSN mehr
|
||||||
|
\$sa_quarantine_cutoff_level = 30.1; # oberhalb keine Quarantäne mehr
|
||||||
|
|
||||||
|
|
||||||
|
# ----------------------------------------------------------
|
||||||
|
# Domain-/Adress-spezifische Einstellungen extern einlesen
|
||||||
|
# ----------------------------------------------------------
|
||||||
|
|
||||||
|
my \$policy_banks_file = '/etc/amavis/policy_banks.conf';
|
||||||
|
if (-r \$policy_banks_file) {
|
||||||
|
do \$policy_banks_file
|
||||||
|
or die "Fehler beim Einlesen von \$policy_banks_file: \$@";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# ----------------------------------------------------------
|
||||||
|
# spammy (zwischen Tag2 und Kill-Level)
|
||||||
|
# zusätzlich in /spammy/, Mail wird zugestellt
|
||||||
|
# ----------------------------------------------------------
|
||||||
|
|
||||||
|
# spammy in /var/QUARANTINE/spammy/
|
||||||
|
\$quarantine_method_by_ccat{+CC_SPAMMY}
|
||||||
|
= 'local:spammy/spammy-%m.gz';
|
||||||
|
\$final_destiny_by_ccat{+CC_SPAMMY} = D_PASS;
|
||||||
|
|
||||||
|
|
||||||
|
# ----------------------------------------------------------
|
||||||
|
# Final Destinies
|
||||||
|
# ----------------------------------------------------------
|
||||||
|
|
||||||
|
# High-Spam (>=9.51)
|
||||||
|
\$final_spam_destiny = D_DISCARD;
|
||||||
|
|
||||||
|
# Viren
|
||||||
|
\$final_virus_destiny = D_DISCARD;
|
||||||
|
|
||||||
|
# Banned (z.B. .exe)
|
||||||
|
\$final_banned_destiny = D_BOUNCE;
|
||||||
|
|
||||||
|
# Schlechte Header
|
||||||
|
\$final_bad_header_destiny = D_PASS;
|
||||||
|
|
||||||
|
|
||||||
|
# ----------------------------------------------------------
|
||||||
|
# Admin E-Mails / Warnungen direct von AMaViS (nicht DSN- oder Bounce-Mails)
|
||||||
|
# ----------------------------------------------------------
|
||||||
|
|
||||||
|
# Bemerkung:
|
||||||
|
# *nochmal*: das hat nichts mit den eigentlichen DSN-/Bounce-Mails zu tun.
|
||||||
|
|
||||||
\$virus_admin = undef;
|
\$virus_admin = undef;
|
||||||
\$spam_admin = undef;
|
\$spam_admin = undef;
|
||||||
\$banned_admin = undef;
|
|
||||||
\$bad_header_admin = undef;
|
|
||||||
|
|
||||||
|
\$warnvirusrecip = 0;
|
||||||
|
\$warnbannedrecip = 0;
|
||||||
|
\$warnbannedsender = 0;
|
||||||
|
\$warnbadhrecip = 0;
|
||||||
|
\$warn_offsite = 0;
|
||||||
|
|
||||||
# Pass SPAMMY but quarantine and inform admin
|
|
||||||
#
|
|
||||||
\$quarantine_to_maps_by_ccat{+CC_SPAMMY} = \\@spam_quarantine_to_maps ;
|
|
||||||
\$quarantine_method_by_ccat{+CC_SPAMMY} = 'local:spammy/spammy-%m.gz' ;
|
|
||||||
\$final_destiny_by_ccat{+CC_SPAMMY} = D_PASS ;
|
|
||||||
\$admin_maps_by_ccat{+CC_SPAMMY} = sub { ca('spam_admin_maps') };
|
|
||||||
|
|
||||||
|
|
||||||
# Bypass spam checking for trusted networks using mynetworks
|
# Bypass spam checking for trusted networks using mynetworks
|
||||||
@@ -4598,133 +4545,67 @@ echononl " Create File \"${_config_policy_banks_file}\""
|
|||||||
if [[ -f "${_config_policy_banks_file}" ]]; then
|
if [[ -f "${_config_policy_banks_file}" ]]; then
|
||||||
echo_skipped
|
echo_skipped
|
||||||
else
|
else
|
||||||
cat << EOF > ${_config_policy_banks_file}
|
cat <<'EOF' > ${_config_policy_banks_file}
|
||||||
# Externe Richtliniendatei für amavisd
|
# /etc/amavis/policy_banks.conf
|
||||||
|
#
|
||||||
use strict;
|
# ---------------------------------------------
|
||||||
|
# Domain- und adressspezifische Amavis-Settings
|
||||||
|
# ---------------------------------------------
|
||||||
# ---
|
#
|
||||||
# add spam info headers if at, or above that level
|
#
|
||||||
# ---
|
# Wichtig: KEIN "use strict;" hier, das ist schon in 50-user aktiv.
|
||||||
|
# Diese Datei wird via "do" aus /etc/amavis/conf.d/50-user eingelesen.
|
||||||
## - All recipients with identical the same setting:
|
#
|
||||||
## -
|
#
|
||||||
#\$sa_tag_level_deflt = 2.0;
|
# Tag2-Level (Schwelle für X-Spam-Flag: YES) abhängig von Empfänger/Domain
|
||||||
|
# ========================================================================
|
||||||
## - Per-recipient mapping of tag2 levels to email addresses (tag2 level):
|
#
|
||||||
## -
|
#
|
||||||
## - Set directly:
|
# Read from file using @spam_tag2_level_maps
|
||||||
## -
|
# ------------------------------------------
|
||||||
\$sa_tag_level_deflt = {
|
#
|
||||||
'oopen.de' => '-4.5',
|
# default: @spam_tag2_level_maps = ($sa_tag2_level_deflt);
|
||||||
# default
|
#
|
||||||
'.'=>'2.0'
|
# Example file '/etc/postfix/tag2_level_maps.dat'
|
||||||
};
|
#
|
||||||
|
# # Specific address first
|
||||||
## - Read from file using @spam_tag2_level_maps
|
# info@123comics.net 2.1
|
||||||
## -
|
# ckubu@oopen.de 2.2
|
||||||
## - default: @spam_tag2_level_maps = (\$sa_tag2_level_deflt);
|
# ...
|
||||||
## -
|
#
|
||||||
## - Example file '/etc/postfix/tag2_level_maps.dat'
|
# # All recipients of the domains @oopen.de / @k8h.de
|
||||||
## -
|
# oopen.de 3.1
|
||||||
## - # oopen.de
|
# k8h.de 4.5
|
||||||
## - oopen.de 2.1
|
#
|
||||||
## - ckubu@oopen.de 2.2
|
# # default
|
||||||
## - argus@oopen.de 2.3
|
# . 5.1
|
||||||
## - [..]
|
#
|
||||||
## - # k8h.de
|
#
|
||||||
## - k8h.de 6.5
|
# Read file into the variable @spam_tag2_level_maps
|
||||||
## - [..]
|
#
|
||||||
## - # default
|
# @spam_tag2_level_maps = ( read_hash('/etc/postfix/tag2_level_maps.dat') );
|
||||||
## - . 5.1
|
#
|
||||||
## -
|
#
|
||||||
#@spam_tag2_level_maps = ( read_hash('/etc/postfix/tag2_level_maps.dat') );
|
# Set the variable $sa_tag2_level_deflt directly.
|
||||||
|
# -----------------------------------------------
|
||||||
|
#
|
||||||
#\$sa_spam_subject_tag = '***SPAM*** '; # Spam-Betreff-Tag
|
# Example:
|
||||||
\$sa_spam_subject_tag = undef;
|
#
|
||||||
|
# @spam_tag2_level_maps = (
|
||||||
|
# {
|
||||||
|
# # Spezifische Adresse zuerst
|
||||||
# ---
|
# 'info@123comics.net' => 3.1,
|
||||||
# add 'spam detected' headers at that level
|
# 'info@berliner-register.de' => 3.1,
|
||||||
# ---
|
#
|
||||||
|
# # Domains (alle Empfänger @oopen.de / @123comics.net)
|
||||||
## - All recipients with identical the same setting:
|
# '.oopen.de' => 3.1,
|
||||||
## -
|
# '.123comics.net' => 4.1,
|
||||||
#\$sa_tag2_level_deflt = 5.1; # add 'spam detected' headers at that level
|
# 'regishut.de' => 2.5,
|
||||||
|
# },
|
||||||
## - Per-recipient mapping of kill levels to email addresses (kill level):
|
#
|
||||||
## -
|
# # Fallback: Standard-Tag2-Level aus 50-user
|
||||||
## - Set directly
|
# $sa_tag2_level_deflt,
|
||||||
## -
|
# );
|
||||||
\$sa_tag2_level_deflt = {
|
|
||||||
'oopen.de' => '3.1',
|
|
||||||
'123comics.net' => '4.1',
|
|
||||||
'info@123comics.net' => '3.1',
|
|
||||||
# default
|
|
||||||
'.' => '5.1',
|
|
||||||
};
|
|
||||||
|
|
||||||
## - Read from file using @spam_kill_level_maps
|
|
||||||
## -
|
|
||||||
## - default: @spam_kill_level_maps = (\$sa_kill_level_deflt);
|
|
||||||
## -
|
|
||||||
## - Example file '/etc/postfix/kill_level_maps.dat'
|
|
||||||
## -
|
|
||||||
## - # oopen.de
|
|
||||||
## - ckubu@oopen.de 1500.0
|
|
||||||
## - ckubu-adm@oopen.de 1500.0
|
|
||||||
## - [..]
|
|
||||||
## - # default
|
|
||||||
## - . 10.31
|
|
||||||
## -
|
|
||||||
#@spam_kill_level_maps = ( read_hash('/etc/postfix/kill_level_maps.dat') );
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# ---
|
|
||||||
# adding more detailed spam-related headers.
|
|
||||||
# ---
|
|
||||||
|
|
||||||
## - All recipients with identical the same setting:
|
|
||||||
## -
|
|
||||||
\$sa_tag3_level_deflt = 7.0; # threshold for sa_tag3_level_deflt
|
|
||||||
|
|
||||||
## - Note
|
|
||||||
## - Like 'sa_tag2_level_deflt' above per-recipient also possible
|
|
||||||
|
|
||||||
|
|
||||||
@sa_tag3_level_maps = (
|
|
||||||
['^Subject:', '\[HIGH-SPAM\] $&'], # Modify subject
|
|
||||||
['HEADER', 'X-High-Spam-Flag', 'YES'], # Add a custom header
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
# ---
|
|
||||||
# spam score threshold at which amavisd-new will reject (kill) an email.
|
|
||||||
# ---
|
|
||||||
|
|
||||||
## - All recipients with identical the same setting:
|
|
||||||
## -
|
|
||||||
\$sa_kill_level_deflt = 10.31; # reject/bounce/discard/pass
|
|
||||||
|
|
||||||
## - Note
|
|
||||||
## - Like 'sa_tag2_level_deflt' above per-recipient also possible
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# ---
|
|
||||||
# The threshold for sending a delivery status notification (DSN) to the sender
|
|
||||||
# ---
|
|
||||||
|
|
||||||
## - We will inform the sender about bouncing his mail with a DSN (Delivery
|
|
||||||
## - StatusNotification). That DSN message will no be send, if the spamvalue
|
|
||||||
## - exceeds the value of sa_dsn_cutoff_level
|
|
||||||
## -
|
|
||||||
#\$sa_dsn_cutoff_level = 10; # spam level beyond which a DSN is not sent
|
|
||||||
\$sa_dsn_cutoff_level = 20;
|
|
||||||
|
|
||||||
|
|
||||||
#------------ Do not modify anything below this line -------------
|
#------------ Do not modify anything below this line -------------
|
||||||
@@ -5116,6 +4997,8 @@ while IFS='' read -r _line || [[ -n $_line ]] ; do
|
|||||||
smtp inet n - y - - smtpd
|
smtp inet n - y - - smtpd
|
||||||
-o smtpd_proxy_filter=127.0.0.1:10024
|
-o smtpd_proxy_filter=127.0.0.1:10024
|
||||||
-o content_filter=
|
-o content_filter=
|
||||||
|
-o smtpd_milters=
|
||||||
|
-o non_smtpd_milters=
|
||||||
EOF
|
EOF
|
||||||
if [[ "$SASL_AUTH_ENABLED" = "no" ]] ; then
|
if [[ "$SASL_AUTH_ENABLED" = "no" ]] ; then
|
||||||
cat >> $postfix_master_cf << EOF
|
cat >> $postfix_master_cf << EOF
|
||||||
@@ -5123,19 +5006,6 @@ EOF
|
|||||||
EOF
|
EOF
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ${listen_on_additional_smtp_port} ; then
|
|
||||||
cat >> $postfix_master_cf << EOF
|
|
||||||
${additional_smtp_port} inet n - y - - smtpd
|
|
||||||
-o smtpd_proxy_filter=127.0.0.1:10024
|
|
||||||
-o content_filter=
|
|
||||||
EOF
|
|
||||||
if [[ "$SASL_AUTH_ENABLED" = "no" ]] ; then
|
|
||||||
cat >> $postfix_master_cf << EOF
|
|
||||||
-o smtpd_sasl_auth_enable=no
|
|
||||||
EOF
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! $submission_present && ! $smtps_present && ! $localhost_10025_present ; then
|
if ! $submission_present && ! $smtps_present && ! $localhost_10025_present ; then
|
||||||
cat >> $postfix_master_cf << EOF
|
cat >> $postfix_master_cf << EOF
|
||||||
localhost:10025 inet n - y - - smtpd
|
localhost:10025 inet n - y - - smtpd
|
||||||
@@ -5150,13 +5020,9 @@ localhost:10025 inet n - y - - smtpd
|
|||||||
-o mynetworks=127.0.0.0/8,[::1]/128
|
-o mynetworks=127.0.0.0/8,[::1]/128
|
||||||
-o receive_override_options=no_unknown_recipient_checks
|
-o receive_override_options=no_unknown_recipient_checks
|
||||||
EOF
|
EOF
|
||||||
if [[ -n "$(which opendkim)" && -n "$(which opendmarc)" ]] ; then
|
if [[-n "$(which opendmarc)" ]] ; then
|
||||||
cat >> $postfix_master_cf << EOF
|
cat >> $postfix_master_cf << EOF
|
||||||
-o smtpd_milters=local:/opendkim/opendkim.sock,local:/opendmarc/opendmarc.sock
|
-o smtpd_milters=local:/opendmarc/opendmarc.sock
|
||||||
EOF
|
|
||||||
elif [[ -n "$(which opendkim)" ]] ; then
|
|
||||||
cat >> $postfix_master_cf << EOF
|
|
||||||
-o smtpd_milters=local:/opendkim/opendkim.sock
|
|
||||||
EOF
|
EOF
|
||||||
fi
|
fi
|
||||||
cat >> $postfix_master_cf << EOF
|
cat >> $postfix_master_cf << EOF
|
||||||
@@ -5167,8 +5033,26 @@ EOF
|
|||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if ${listen_on_additional_smtp_port} \
|
||||||
|
&& echo "$_line" | grep -i -E "^\s*${additional_smtp_port}\s+inet" > /dev/null 2>&1 ; then
|
||||||
|
_found=true
|
||||||
|
cat >> $postfix_master_cf << EOF
|
||||||
|
${additional_smtp_port} inet n - y - - smtpd
|
||||||
|
-o smtpd_proxy_filter=127.0.0.1:10024
|
||||||
|
-o content_filter=
|
||||||
|
EOF
|
||||||
|
if [[ "$SASL_AUTH_ENABLED" = "no" ]] ; then
|
||||||
|
cat >> $postfix_master_cf << EOF
|
||||||
|
-o smtpd_sasl_auth_enable=no
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
|
||||||
if $submission_present && echo "$_line" | grep -i -E "^^submission\s+" > /dev/null 2>&1 ; then
|
continue
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
if $submission_present && echo "$_line" | grep -i -E "^submission\s+" > /dev/null 2>&1 ; then
|
||||||
_found=true
|
_found=true
|
||||||
cat >> $postfix_master_cf << EOF
|
cat >> $postfix_master_cf << EOF
|
||||||
submission inet n - y - 20 smtpd
|
submission inet n - y - 20 smtpd
|
||||||
@@ -5176,6 +5060,13 @@ submission inet n - y - 20 smtpd
|
|||||||
-o smtpd_tls_security_level=encrypt
|
-o smtpd_tls_security_level=encrypt
|
||||||
-o smtpd_sasl_auth_enable=yes
|
-o smtpd_sasl_auth_enable=yes
|
||||||
-o smtpd_client_restrictions=permit_sasl_authenticated,reject
|
-o smtpd_client_restrictions=permit_sasl_authenticated,reject
|
||||||
|
EOF
|
||||||
|
if [[ -n "$(which opendkim)" ]] ; then
|
||||||
|
cat >> $postfix_master_cf << EOF
|
||||||
|
-o smtpd_milters=local:/opendkim/opendkim.sock
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
cat >> $postfix_master_cf << EOF
|
||||||
#-o milter_macro_daemon_name=ORIGINATING
|
#-o milter_macro_daemon_name=ORIGINATING
|
||||||
EOF
|
EOF
|
||||||
if ! $smtps_present ; then
|
if ! $smtps_present ; then
|
||||||
@@ -5193,13 +5084,9 @@ localhost:10025 inet n - y - - smtpd
|
|||||||
-o mynetworks=127.0.0.0/8,[::1]/128
|
-o mynetworks=127.0.0.0/8,[::1]/128
|
||||||
-o receive_override_options=no_unknown_recipient_checks
|
-o receive_override_options=no_unknown_recipient_checks
|
||||||
EOF
|
EOF
|
||||||
if [[ -n "$(which opendkim)" && -n "$(which opendmarc)" ]] ; then
|
if [[ -n "$(which opendmarc)" ]] ; then
|
||||||
cat >> $postfix_master_cf << EOF
|
cat >> $postfix_master_cf << EOF
|
||||||
-o smtpd_milters=local:/opendkim/opendkim.sock,local:/opendmarc/opendmarc.sock
|
-o smtpd_milters=local:/opendmarc/opendmarc.sock
|
||||||
EOF
|
|
||||||
elif [[ -n "$(which opendkim)" ]] ; then
|
|
||||||
cat >> $postfix_master_cf << EOF
|
|
||||||
-o smtpd_milters=local:/opendkim/opendkim.sock
|
|
||||||
EOF
|
EOF
|
||||||
fi
|
fi
|
||||||
cat >> $postfix_master_cf << EOF
|
cat >> $postfix_master_cf << EOF
|
||||||
@@ -5230,6 +5117,13 @@ smtps inet n - y - - smtpd
|
|||||||
-o smtpd_tls_wrappermode=yes
|
-o smtpd_tls_wrappermode=yes
|
||||||
-o smtpd_sasl_auth_enable=yes
|
-o smtpd_sasl_auth_enable=yes
|
||||||
-o smtpd_client_restrictions=permit_sasl_authenticated,reject
|
-o smtpd_client_restrictions=permit_sasl_authenticated,reject
|
||||||
|
EOF
|
||||||
|
if [[ -n "$(which opendkim)" ]] ; then
|
||||||
|
cat >> $postfix_master_cf << EOF
|
||||||
|
-o smtpd_milters=local:/opendkim/opendkim.sock
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
cat >> $postfix_master_cf << EOF
|
||||||
#-o milter_macro_daemon_name=ORIGINATING
|
#-o milter_macro_daemon_name=ORIGINATING
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
@@ -5247,13 +5141,9 @@ localhost:10025 inet n - y - - smtpd
|
|||||||
-o mynetworks=127.0.0.0/8,[::1]/128
|
-o mynetworks=127.0.0.0/8,[::1]/128
|
||||||
-o receive_override_options=no_unknown_recipient_checks
|
-o receive_override_options=no_unknown_recipient_checks
|
||||||
EOF
|
EOF
|
||||||
if [[ -n "$(which opendkim)" && -n "$(which opendmarc)" ]] ; then
|
if [[ -n "$(which opendmarc)" ]] ; then
|
||||||
cat >> $postfix_master_cf << EOF
|
cat >> $postfix_master_cf << EOF
|
||||||
-o smtpd_milters=local:/opendkim/opendkim.sock,local:/opendmarc/opendmarc.sock
|
-o smtpd_milters=local:/opendmarc/opendmarc.sock
|
||||||
EOF
|
|
||||||
elif [[ -n "$(which opendkim)" ]] ; then
|
|
||||||
cat >> $postfix_master_cf << EOF
|
|
||||||
-o smtpd_milters=local:/opendkim/opendkim.sock
|
|
||||||
EOF
|
EOF
|
||||||
fi
|
fi
|
||||||
cat >> $postfix_master_cf << EOF
|
cat >> $postfix_master_cf << EOF
|
||||||
@@ -5290,13 +5180,9 @@ localhost:10025 inet n - y - - smtpd
|
|||||||
-o mynetworks=127.0.0.0/8,[::1]/128
|
-o mynetworks=127.0.0.0/8,[::1]/128
|
||||||
-o receive_override_options=no_unknown_recipient_checks
|
-o receive_override_options=no_unknown_recipient_checks
|
||||||
EOF
|
EOF
|
||||||
if [[ -n "$(which opendkim)" && -n "$(which opendmarc)" ]] ; then
|
if [[ -n "$(which opendmarc)" ]] ; then
|
||||||
cat >> $postfix_master_cf << EOF
|
cat >> $postfix_master_cf << EOF
|
||||||
-o smtpd_milters=local:/opendkim/opendkim.sock,local:/opendmarc/opendmarc.sock
|
-o smtpd_milters=local:/opendmarc/opendmarc.sock
|
||||||
EOF
|
|
||||||
elif [[ -n "$(which opendkim)" ]] ; then
|
|
||||||
cat >> $postfix_master_cf << EOF
|
|
||||||
-o smtpd_milters=local:/opendkim/opendkim.sock
|
|
||||||
EOF
|
EOF
|
||||||
fi
|
fi
|
||||||
cat >> $postfix_master_cf << EOF
|
cat >> $postfix_master_cf << EOF
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ echo -e "\n \033[32mStart Installation of OpenDKIM..\033[m"
|
|||||||
|
|
||||||
log_file="$(mktemp)"
|
log_file="$(mktemp)"
|
||||||
|
|
||||||
|
backup_date="$(date +%Y-%m-%d-%H%M)"
|
||||||
|
|
||||||
_opendkim_packages="opendkim opendkim-tools"
|
_opendkim_packages="opendkim opendkim-tools"
|
||||||
|
|
||||||
opendkim_base_dir="/etc/opendkim"
|
opendkim_base_dir="/etc/opendkim"
|
||||||
@@ -28,6 +30,7 @@ opendkim_socket_file="${opendkim_socket_dir}/opendkim.sock"
|
|||||||
postfix_needs_restart=false
|
postfix_needs_restart=false
|
||||||
opendkim_needs_restart=false
|
opendkim_needs_restart=false
|
||||||
|
|
||||||
|
|
||||||
# -------------
|
# -------------
|
||||||
# --- Some functions
|
# --- Some functions
|
||||||
# -------------
|
# -------------
|
||||||
@@ -175,6 +178,18 @@ else
|
|||||||
echo_skipped
|
echo_skipped
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
echononl " Backup existing file '${opendkim_conf_file}'.."
|
||||||
|
if [[ -f "${opendkim_conf_file}" ]] ; then
|
||||||
|
mv "${opendkim_conf_file}" "${opendkim_conf_file}.${backup_date}"
|
||||||
|
if [[ $? -eq 0 ]] ; then
|
||||||
|
echo_ok
|
||||||
|
else
|
||||||
|
echo_failed
|
||||||
|
error "$(cat $log_file)"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo_skipped
|
||||||
|
fi
|
||||||
|
|
||||||
# - Create OpenDKIM configuration
|
# - Create OpenDKIM configuration
|
||||||
# -
|
# -
|
||||||
@@ -200,6 +215,7 @@ AuthservID "DKIM check $(hostname -f)"
|
|||||||
# Modi signer (s) und verifier (v) und verwendet eine
|
# Modi signer (s) und verifier (v) und verwendet eine
|
||||||
# Socket-Datei zur Kommunikation (alternativ: lokaler Port)
|
# Socket-Datei zur Kommunikation (alternativ: lokaler Port)
|
||||||
Mode sv
|
Mode sv
|
||||||
|
|
||||||
# Socket local:/var/run/opendkim/opendkim.sock
|
# Socket local:/var/run/opendkim/opendkim.sock
|
||||||
# Socket local:$opendkim_socket_file
|
# Socket local:$opendkim_socket_file
|
||||||
# Socket inet:12345@localhost
|
# Socket inet:12345@localhost
|
||||||
@@ -492,6 +508,35 @@ milter_protocol = 6
|
|||||||
#
|
#
|
||||||
#smtpd_milters = local:/opendkim/opendkim.sock
|
#smtpd_milters = local:/opendkim/opendkim.sock
|
||||||
smtpd_milters =
|
smtpd_milters =
|
||||||
|
|
||||||
|
# Was sind non_smtpd_milters?
|
||||||
|
#
|
||||||
|
# non_smtpd_milters gilt für alle Postfix-Prozesse, die Mails verarbeiten, aber NICHT
|
||||||
|
# der smtpd-Daemon sind.
|
||||||
|
#
|
||||||
|
# Das betrifft z. B.:
|
||||||
|
#
|
||||||
|
# cleanup Header/Content-Bereinigung
|
||||||
|
# qmgr Queue-Manager
|
||||||
|
# lmtp / smtp Auslieferung nach extern
|
||||||
|
# local lokale Zustellung
|
||||||
|
#
|
||||||
|
# Das sind z. B.:
|
||||||
|
#
|
||||||
|
# - interne Bounces (MAILER-DAEMON)
|
||||||
|
#
|
||||||
|
# - Cron-Mails vom Server
|
||||||
|
#
|
||||||
|
# - Weiterleitungen, die Postfix selbst generiert
|
||||||
|
#
|
||||||
|
# - Mails, die über sendmail CLI gesendet werden
|
||||||
|
#
|
||||||
|
# - Mails, die Amavis über LMTP zurückgibt
|
||||||
|
#
|
||||||
|
# - etc.
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# DKIM soll auch die ausgehenden Mails signieren, die nicht über smtpd daemon versendet werden.
|
||||||
non_smtpd_milters = local:/opendkim/opendkim.sock
|
non_smtpd_milters = local:/opendkim/opendkim.sock
|
||||||
EOF
|
EOF
|
||||||
postfix_needs_restart=true
|
postfix_needs_restart=true
|
||||||
@@ -542,9 +587,14 @@ while IFS='' read -r _line || [[ -n $_line ]] ; do
|
|||||||
|
|
||||||
if $_found && echo "$_line" | grep -i -q -E "^\s*-o\s+smtpd_milters=\s*" ; then
|
if $_found && echo "$_line" | grep -i -q -E "^\s*-o\s+smtpd_milters=\s*" ; then
|
||||||
_found=false
|
_found=false
|
||||||
|
if ! echo "$_line" | grep -i -q -E "^\s*-o\s+smtpd_milters=\s*local:/opendkim/opendkim.sock\s*$" ; then
|
||||||
|
echo " -o smtpd_milters=local:/opendkim/opendkim.sock" >> "$tmp_master_file"
|
||||||
|
_changed=true
|
||||||
|
continue
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if echo "$_line" | grep -i -q -E "^\s*(127.0.0.1|localhost):10025\s+inet\s+" 2> /dev/null ; then
|
if echo "$_line" | grep -i -q -E "^\s*(submission|smtps)\s+inet\s+" 2> /dev/null ; then
|
||||||
_found=true
|
_found=true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -562,7 +612,7 @@ if $_changed ; then
|
|||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
echo_skipped
|
echo_skipped
|
||||||
warn "Postfix (master.cf) seems already be configured."
|
info "Postfix (master.cf) was not changed - seems already be configured right."
|
||||||
echononl " Delete previosly saved file '/etc/postfix/master.cf'.."
|
echononl " Delete previosly saved file '/etc/postfix/master.cf'.."
|
||||||
rm /etc/postfix/master.cf.$backup_date 2> $log_file
|
rm /etc/postfix/master.cf.$backup_date 2> $log_file
|
||||||
if [[ $? -eq 0 ]] ; then
|
if [[ $? -eq 0 ]] ; then
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
clear
|
clear
|
||||||
echo -e "\n \033[32mStart Installation of OpenDMARC..\033[m"
|
echo -e "\n \033[32mStart Installation of OpenDMARC..\033[m"
|
||||||
|
|
||||||
|
overwrite_config_files=true
|
||||||
|
|
||||||
|
|
||||||
# -------------
|
# -------------
|
||||||
@@ -23,13 +24,14 @@ opendmarc_socket_dir="${postfix_spool_dir}/opendmarc"
|
|||||||
opendmarc_socket_file="${opendmarc_socket_dir}/opendmarc.sock"
|
opendmarc_socket_file="${opendmarc_socket_dir}/opendmarc.sock"
|
||||||
|
|
||||||
config_file_name_value_parameters="
|
config_file_name_value_parameters="
|
||||||
AuthservID|DMARC check $(hostname -f)
|
AuthservID|$(hostname -f)
|
||||||
|
TrustedAuthservIDs|$(hostname -f)
|
||||||
PidFile|/run/opendmarc/opendmarc.pid
|
PidFile|/run/opendmarc/opendmarc.pid
|
||||||
RejectFailures|true
|
RejectFailures|true
|
||||||
Syslog|true
|
Syslog|true
|
||||||
SyslogFacility|mail
|
SyslogFacility|mail
|
||||||
TrustedAuthservIDs|$(hostname -f)
|
IgnoreHosts|${opendmarc_base_dir}/ignore.hosts
|
||||||
IgnoreHosts|/etc/opendmarc/ignore.hosts
|
IgnoreMailFrom|${opendmarc_base_dir}/ignore.mailfrom
|
||||||
IgnoreAuthenticatedClients|true
|
IgnoreAuthenticatedClients|true
|
||||||
RequiredHeaders|false
|
RequiredHeaders|false
|
||||||
UMask|002
|
UMask|002
|
||||||
@@ -194,6 +196,9 @@ if ! $(grep -q -E "^IgnoreHosts\s+" ${opendmarc_conf_file} 2> /dev/null) ; then
|
|||||||
## ignored by the filter. If not specified, defaults to "127.0.0.1" only.
|
## ignored by the filter. If not specified, defaults to "127.0.0.1" only.
|
||||||
#
|
#
|
||||||
IgnoreHosts 127.0.0.1
|
IgnoreHosts 127.0.0.1
|
||||||
|
|
||||||
|
# Optional - auch nach Absender-Domain ignorieren:
|
||||||
|
IgnoreMailFrom ${opendmarc_base_dir}/ignore.mailfrom
|
||||||
EOF
|
EOF
|
||||||
if [[ $? -eq 0 ]] ; then
|
if [[ $? -eq 0 ]] ; then
|
||||||
echo_ok
|
echo_ok
|
||||||
@@ -229,6 +234,33 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
# - Add 'TrustedAuthservIDs' with default value to the original opendmarc.conf file
|
||||||
|
#
|
||||||
|
_param="TrustedAuthservIDs"
|
||||||
|
echononl " Add '${_param}' with default value to the opendmarc.conf file.."
|
||||||
|
if ! $(grep -q -E "^${_param}\s+" ${opendmarc_conf_file} 2> /dev/null) ; then
|
||||||
|
cat << EOF >> ${opendmarc_conf_file}
|
||||||
|
|
||||||
|
# Provides a list of authserv-ids that are to be used to identify Authentication-Results
|
||||||
|
# header fields whose contents are to be assumed as valid input for the DMARC assessment.
|
||||||
|
# To provide a list, separate values by commas. If the string "HOSTNAME" is provided,
|
||||||
|
# the name of the host running the filter (as returned by the gethostname(3) function)
|
||||||
|
# will be used. Matching against this list is case-insensitive. The default is to use the
|
||||||
|
# value of AuthservID.
|
||||||
|
#
|
||||||
|
TrustedAuthservIDs OpenDMARC
|
||||||
|
EOF
|
||||||
|
if [[ $? -eq 0 ]] ; then
|
||||||
|
echo_ok
|
||||||
|
else
|
||||||
|
echo_failed
|
||||||
|
error "$(cat $log_file)"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo_skipped
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
# - Add 'RequiredHeaders' with default value to the original opendmarc.conf file
|
# - Add 'RequiredHeaders' with default value to the original opendmarc.conf file
|
||||||
#
|
#
|
||||||
_param="IgnoreAuthenticatedClients"
|
_param="IgnoreAuthenticatedClients"
|
||||||
@@ -472,6 +504,18 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
echononl " Backup existing file '${opendmarc_base_dir}/ignore.hosts'.."
|
||||||
|
if [[ -f "${opendmarc_base_dir}/ignore.hosts" ]] ; then
|
||||||
|
mv "${opendmarc_base_dir}/ignore.hosts" "${opendmarc_base_dir}/ignore.hosts.${backup_date}"
|
||||||
|
if [[ $? -eq 0 ]] ; then
|
||||||
|
echo_ok
|
||||||
|
else
|
||||||
|
echo_failed
|
||||||
|
error "$(cat $log_file)"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo_skipped
|
||||||
|
fi
|
||||||
|
|
||||||
# - Create the file ${opendmarc_base_dir}/ignore.hosts
|
# - Create the file ${opendmarc_base_dir}/ignore.hosts
|
||||||
# -
|
# -
|
||||||
@@ -480,11 +524,68 @@ if [[ -f "${opendmarc_base_dir}/ignore.hosts" ]] ; then
|
|||||||
echo_skipped
|
echo_skipped
|
||||||
else
|
else
|
||||||
cat <<EOF > ${opendmarc_base_dir}/ignore.hosts 2> $log_file
|
cat <<EOF > ${opendmarc_base_dir}/ignore.hosts 2> $log_file
|
||||||
# We are using AmaViS at 'localhost 127.0.0.1 . So we cannot bypass them
|
# /etc/opendmarc/ignore.hosts
|
||||||
#
|
#
|
||||||
# 127.0.0.1
|
# Aktuell hat OpenDMARC seinen Milter nur am Dienst
|
||||||
# localhost
|
# 'localhost:10025' hängen. Dort ist der Client
|
||||||
$(hostname -f)
|
# immer 127.0.0.1, nicht die externe Gegenstelle.
|
||||||
|
#
|
||||||
|
# Deshalb macht es in diesem Setup keinen Sinn,
|
||||||
|
# hier IP-Adressen von externen Diensten (CRSend etc.)
|
||||||
|
# einzutragen – sie würden nie matchen.
|
||||||
|
#
|
||||||
|
# WICHTIG:
|
||||||
|
# - KEIN 127.0.0.1
|
||||||
|
# - KEIN localhost
|
||||||
|
# - KEIN ::1
|
||||||
|
#
|
||||||
|
# Eintrag dieser Adressen würde DMARC komplett deaktivieren.
|
||||||
|
#
|
||||||
|
# ==> Datei bleibt absichtlich leer.
|
||||||
|
EOF
|
||||||
|
opendmarc_needs_restart=true
|
||||||
|
if [[ $? -eq 0 ]] ; then
|
||||||
|
echo_ok
|
||||||
|
else
|
||||||
|
echo_failed
|
||||||
|
error "$(cat $log_file)"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
# - Create the file ${opendmarc_base_dir}/ignore.hosts
|
||||||
|
# -
|
||||||
|
|
||||||
|
echononl " Backup existing file '${opendmarc_base_dir}/ignore.mailfrom'.."
|
||||||
|
if [[ -f "${opendmarc_base_dir}/ignore.mailfrom" ]] ; then
|
||||||
|
mv "${opendmarc_base_dir}/ignore.mailfrom" "${opendmarc_base_dir}/ignore.mailfrom.${backup_date}"
|
||||||
|
if [[ $? -eq 0 ]] ; then
|
||||||
|
echo_ok
|
||||||
|
else
|
||||||
|
echo_failed
|
||||||
|
error "$(cat $log_file)"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo_skipped
|
||||||
|
fi
|
||||||
|
|
||||||
|
echononl " Create file '${opendmarc_base_dir}/ignore.mailfrom'.."
|
||||||
|
if [[ -f "${opendmarc_base_dir}/ignore.mailfrom" ]] ; then
|
||||||
|
echo_skipped
|
||||||
|
else
|
||||||
|
cat <<EOF > ${opendmarc_base_dir}/ignore.mailfrom 2> $log_file
|
||||||
|
# /etc/opendmarc/ignore.mailfrom
|
||||||
|
#
|
||||||
|
# Hier könnte man Absender-Domains von der DMARC-Prüfung
|
||||||
|
# ausnehmen (z. B. problematische Partner-Domains).
|
||||||
|
#
|
||||||
|
# Aktuell ist das für dein Setup nicht notwendig.
|
||||||
|
#
|
||||||
|
# Beispiele (NICHT aktiv!):
|
||||||
|
# @example.org
|
||||||
|
# example.org
|
||||||
|
#
|
||||||
|
# ==> Datei bleibt absichtlich leer.
|
||||||
EOF
|
EOF
|
||||||
opendmarc_needs_restart=true
|
opendmarc_needs_restart=true
|
||||||
if [[ $? -eq 0 ]] ; then
|
if [[ $? -eq 0 ]] ; then
|
||||||
@@ -588,6 +689,101 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
|
||||||
|
# - Edit /etc/postfix/main.cf and add a section to activate
|
||||||
|
# - processing of e-mail through the OpenDKIM daemon:
|
||||||
|
# -
|
||||||
|
backup_date="$(date +%Y-%m-%d-%H%M)"
|
||||||
|
echononl " Backup existing postfix configuration (main.cf).."
|
||||||
|
cp -a /etc/postfix/main.cf /etc/postfix/main.cf.$backup_date 2> $log_file
|
||||||
|
if [[ $? -eq 0 ]] ; then
|
||||||
|
echo_ok
|
||||||
|
else
|
||||||
|
echo_failed
|
||||||
|
error "$(cat $log_file)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echononl " Activate processing of e-mail through the OpenDKIM daemon.."
|
||||||
|
if grep -q -E "milter_default_action\s*=\s*accept" /etc/postfix/main.cf ; then
|
||||||
|
echo_skipped
|
||||||
|
info "Postfix (main.cf) was not changed - seems already be configured right."
|
||||||
|
echononl " Delete previosly saved Postfix configuration.."
|
||||||
|
rm /etc/postfix/main.cf.$backup_date 2> $log_file
|
||||||
|
if [[ $? -eq 0 ]] ; then
|
||||||
|
echo_ok
|
||||||
|
else
|
||||||
|
echo_failed
|
||||||
|
error "$(cat $log_file)"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
cat <<EOF >> /etc/postfix/main.cf 2> $log_file
|
||||||
|
|
||||||
|
# ======= Milter configuration =======
|
||||||
|
|
||||||
|
# OpenDKIM
|
||||||
|
|
||||||
|
milter_default_action = accept
|
||||||
|
|
||||||
|
# Postfix ≥ 2.6 milter_protocol = 6, Postfix ≤ 2.5 milter_protocol = 2
|
||||||
|
milter_protocol = 6
|
||||||
|
|
||||||
|
# Note:
|
||||||
|
# We will sign AFTER sending through AmaVIS, just befor sending out. So
|
||||||
|
# set 'smtpd_milters =' to an emty string here and add to localhost:10025
|
||||||
|
# section in master.cf: 'smtpd_milters=local:/opendkim/opendkim.sock'
|
||||||
|
#
|
||||||
|
# If you want sign mails before sending through AmaVIS, set
|
||||||
|
# 'smtpd_milters = local:/opendkim/opendkim.sock' here and add to
|
||||||
|
# localhost:10025 section in master.cf: 'smtpd_milters='
|
||||||
|
#
|
||||||
|
#smtpd_milters = local:/opendkim/opendkim.sock
|
||||||
|
smtpd_milters =
|
||||||
|
|
||||||
|
# Was sind non_smtpd_milters?
|
||||||
|
#
|
||||||
|
# non_smtpd_milters gilt für alle Postfix-Prozesse, die Mails verarbeiten, aber NICHT
|
||||||
|
# der smtpd-Daemon sind.
|
||||||
|
#
|
||||||
|
# Das betrifft z. B.:
|
||||||
|
#
|
||||||
|
# cleanup Header/Content-Bereinigung
|
||||||
|
# qmgr Queue-Manager
|
||||||
|
# lmtp / smtp Auslieferung nach extern
|
||||||
|
# local lokale Zustellung
|
||||||
|
#
|
||||||
|
# Das sind z. B.:
|
||||||
|
#
|
||||||
|
# - interne Bounces (MAILER-DAEMON)
|
||||||
|
#
|
||||||
|
# - Cron-Mails vom Server
|
||||||
|
#
|
||||||
|
# - Weiterleitungen, die Postfix selbst generiert
|
||||||
|
#
|
||||||
|
# - Mails, die über sendmail CLI gesendet werden
|
||||||
|
#
|
||||||
|
# - Mails, die Amavis über LMTP zurückgibt
|
||||||
|
#
|
||||||
|
# - etc.
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# DKIM soll auch die ausgehenden Mails signieren, die nicht über smtpd daemon versendet werden.
|
||||||
|
non_smtpd_milters = local:/opendkim/opendkim.sock
|
||||||
|
EOF
|
||||||
|
postfix_needs_restart=true
|
||||||
|
if [[ $? -eq 0 ]] ; then
|
||||||
|
echo_ok
|
||||||
|
else
|
||||||
|
echo_failed
|
||||||
|
error "$(cat $log_file)"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
|
||||||
# - Prevent Postfix from setting the DMARC Header twice (one befor
|
# - Prevent Postfix from setting the DMARC Header twice (one befor
|
||||||
# - and one after processing amavis
|
# - and one after processing amavis
|
||||||
# -
|
# -
|
||||||
@@ -613,24 +809,58 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echononl " Adjust /etc/postfix/master.cf. Set DMARC after sending throuh AmaVIS.."
|
echononl " Adjust /etc/postfix/master.cf. Set DMARC after sending throuh AmaVIS.."
|
||||||
if $(grep -q -E "^\s*-o\s+smtpd_milters\s*=\s*.*opendkim.sock" /etc/postfix/master.cf 2> /dev/null) ; then
|
_found=false
|
||||||
if $(grep -q -E "^\s*-o\s+smtpd_milters\s*=\s*.*$(basename ${opendmarc_socket_file})" /etc/postfix/master.cf); then
|
_changed=false
|
||||||
echo_skipped
|
tmp_master_file="/tmp/postfix_master.cf"
|
||||||
else
|
> $tmp_master_file
|
||||||
perl -i -n -p -e "s&(^\s*-o\s+smtpd_milters\s*=.*)&\1,local:/$(basename "${opendmarc_socket_dir}")/$(basename "${opendmarc_socket_file}")&" \
|
while IFS='' read -r _line || [[ -n $_line ]] ; do
|
||||||
/etc/postfix/master.cf > $log_file 2>&1
|
|
||||||
|
if $_found && ! echo "$_line" | grep -i -q -E "^\s*-o" 2> /dev/null ; then
|
||||||
|
echo " -o smtpd_milters=local:/opendmarc/opendmarc.sock" >> "$tmp_master_file"
|
||||||
|
_changed=true
|
||||||
|
_found=false
|
||||||
|
fi
|
||||||
|
|
||||||
|
if $_found && echo "$_line" | grep -i -q -E "^\s*-o\s+smtpd_milters=\s*" ; then
|
||||||
|
_found=false
|
||||||
|
if ! echo "$_line" | grep -i -q -E "^\s*-o\s+smtpd_milters=\s*local:/opendmarc/opendmarc.sock\s*$" ; then
|
||||||
|
echo " -o smtpd_milters=local:/opendmarc/opendmarc.sock" >> "$tmp_master_file"
|
||||||
|
_changed=true
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
if echo "$_line" | grep -i -q -E "^\s*(localhost|127.0.0.1):10025\s+inet\s+" 2> /dev/null ; then
|
||||||
|
_found=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$_line" >> "$tmp_master_file"
|
||||||
|
|
||||||
|
done < "/etc/postfix/master.cf"
|
||||||
|
|
||||||
|
if $_changed ; then
|
||||||
|
cp $tmp_master_file /etc/postfix/master.cf 2> $log_file
|
||||||
|
postfix_needs_restart=true
|
||||||
if [[ $? -eq 0 ]] ; then
|
if [[ $? -eq 0 ]] ; then
|
||||||
echo_ok
|
echo_ok
|
||||||
postfix_needs_restart=true
|
|
||||||
else
|
else
|
||||||
echo_failed
|
echo_failed
|
||||||
error "$(cat $log_file)"
|
error "$(cat $log_file)"
|
||||||
fi
|
fi
|
||||||
fi
|
|
||||||
else
|
else
|
||||||
echo_skipped
|
echo_skipped
|
||||||
warn "Postfix is not adjusted. Complete Postfix configuration (master.cf) manually\!"
|
info "Postfix (master.cf) was not changed - seems already be configured right."
|
||||||
|
echononl " Delete previosly saved file '/etc/postfix/master.cf'.."
|
||||||
|
rm /etc/postfix/master.cf.$backup_date 2> $log_file
|
||||||
|
if [[ $? -eq 0 ]] ; then
|
||||||
|
echo_ok
|
||||||
|
else
|
||||||
|
echo_failed
|
||||||
|
error "$(cat $log_file)"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
rm -f $tmp_master_file
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
@@ -698,20 +928,6 @@ else
|
|||||||
echo_skipped
|
echo_skipped
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo ""
|
|
||||||
if [[ -f "/etc/postfix/master.cf.${backup_date}" ]] ; then
|
|
||||||
if $(diff "/etc/postfix/master.cf" "/etc/postfix/master.cf.${backup_date}"> /dev/null 2>&1) ; then
|
|
||||||
info "File \033[1m/etc/postfix/master.cf\033[m has not changed.\n\t Removing previos created backup.."
|
|
||||||
rm "/etc/postfix/master.cf.${backup_date}"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
if [[ -f "/etc/postfix/main.cf.${backup_date}" ]] ; then
|
|
||||||
if $(diff "/etc/postfix/main.cf" "/etc/postfix/main.cf.${backup_date}"> /dev/null 2>&1) ; then
|
|
||||||
info "File \033[1m/etc/postfix/main.cf\033[m has not changed.\n\t Removing previos created backup.."
|
|
||||||
rm "/etc/postfix/main.cf.${backup_date}"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
rm -f "$log_file"
|
rm -f "$log_file"
|
||||||
|
|||||||
@@ -131,6 +131,8 @@ DEFAULT_SASL_AUTH_ENABLED=no
|
|||||||
|
|
||||||
DEFAULT_LISTEN_ON_ADDITIONAL_RELAY_PORT=false
|
DEFAULT_LISTEN_ON_ADDITIONAL_RELAY_PORT=false
|
||||||
|
|
||||||
|
DEFAULT_INSTALL_DMARC_REPORT_SUPPORT=false
|
||||||
|
|
||||||
|
|
||||||
# - Is this a systemd system?
|
# - Is this a systemd system?
|
||||||
# -
|
# -
|
||||||
@@ -174,6 +176,11 @@ if [[ -z "$_LISTEN_ON_ADDITIONAL_RELAY_PORT" ]] ; then
|
|||||||
_LISTEN_ON_ADDITIONAL_RELAY_PORT=${DEFAULT_LISTEN_ON_ADDITIONAL_RELAY_PORT}
|
_LISTEN_ON_ADDITIONAL_RELAY_PORT=${DEFAULT_LISTEN_ON_ADDITIONAL_RELAY_PORT}
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [[ -z "$_INSTALL_DMARC_REPORT_SUPPORT" ]] ; then
|
||||||
|
_INSTALL_DMARC_REPORT_SUPPORT=${DEFAULT_INSTALL_DMARC_REPORT_SUPPORT}
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo ""
|
echo ""
|
||||||
echo ""
|
echo ""
|
||||||
@@ -443,6 +450,24 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
if ! ${IS_RELAY_HOST} ; then
|
||||||
|
INSTALL_DMARC_REPORT_SUPPORT=false
|
||||||
|
echo ""
|
||||||
|
echo -e "\033[32m--\033[m"
|
||||||
|
echo ""
|
||||||
|
echo "Should this mail server support DMARC reporting?"
|
||||||
|
echo ""
|
||||||
|
echononl "Support DMARC reporting ? [ ${_INSTALL_DMARC_REPORT_SUPPORT} ]: "
|
||||||
|
read INPUT
|
||||||
|
if [[ "X${INPUT}" == "X" ]]; then
|
||||||
|
INPUT=$_INSTALL_DMARC_REPORT_SUPPORT
|
||||||
|
fi
|
||||||
|
if [[ "${INPUT,,}" == "yes" || "${INPUT,,}" == "true" ]]; then
|
||||||
|
INSTALL_DMARC_REPORT_SUPPORT=true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
ADMIN_EMAIL=
|
ADMIN_EMAIL=
|
||||||
echo ""
|
echo ""
|
||||||
echo ""
|
echo ""
|
||||||
@@ -492,6 +517,8 @@ if $IS_RELAY_HOST ; then
|
|||||||
else
|
else
|
||||||
echo -e "\tConfigure as relay host?..........: $IS_RELAY_HOST"
|
echo -e "\tConfigure as relay host?..........: $IS_RELAY_HOST"
|
||||||
echo -e "\tConfigure as complete mailserver..: \033[33m\033[1mtrue\033[m"
|
echo -e "\tConfigure as complete mailserver..: \033[33m\033[1mtrue\033[m"
|
||||||
|
echo ""
|
||||||
|
echo -e "\tSupport DMARC reporting...........: \033[33m\033[1m${INSTALL_DMARC_REPORT_SUPPORT}\033[m"
|
||||||
fi
|
fi
|
||||||
echo ""
|
echo ""
|
||||||
echononl "einverstanden (yes/no): "
|
echononl "einverstanden (yes/no): "
|
||||||
@@ -535,6 +562,10 @@ EOF
|
|||||||
_ADDITIONAL_RELAY_LISTEN_PORT=${ADDITIONAL_RELAY_LISTEN_PORT}
|
_ADDITIONAL_RELAY_LISTEN_PORT=${ADDITIONAL_RELAY_LISTEN_PORT}
|
||||||
EOF
|
EOF
|
||||||
fi
|
fi
|
||||||
|
else
|
||||||
|
cat << EOF >> $conf_file
|
||||||
|
_INSTALL_DMARC_REPORT_SUPPORT=${INSTALL_DMARC_REPORT_SUPPORT}
|
||||||
|
EOF
|
||||||
fi
|
fi
|
||||||
if [[ $? -ne 0 ]]; then
|
if [[ $? -ne 0 ]]; then
|
||||||
_failed=true
|
_failed=true
|
||||||
@@ -547,6 +578,9 @@ fi
|
|||||||
|
|
||||||
[[ "$IPV6" = "disabled" ]] && IPV6=""
|
[[ "$IPV6" = "disabled" ]] && IPV6=""
|
||||||
|
|
||||||
|
exit
|
||||||
|
clean_up 1
|
||||||
|
|
||||||
|
|
||||||
# - Synchronise package index files with the repository
|
# - Synchronise package index files with the repository
|
||||||
# -
|
# -
|
||||||
@@ -592,6 +626,9 @@ _needed_packages="postfix postfix-pgsql postfix-mysql postfix-pcre libsasl2-modu
|
|||||||
if [[ "$SASL_AUTH_ENABLED" = "yes" ]]; then
|
if [[ "$SASL_AUTH_ENABLED" = "yes" ]]; then
|
||||||
_needed_packages="$_needed_packages sasl2-bin"
|
_needed_packages="$_needed_packages sasl2-bin"
|
||||||
fi
|
fi
|
||||||
|
if ${INSTALL_DMARC_REPORT_SUPPORT} ; then
|
||||||
|
_needed_packages="$_needed_packages ripmime xmlstarlet unzip gzip"
|
||||||
|
fi
|
||||||
for _pkg in $_needed_packages ; do
|
for _pkg in $_needed_packages ; do
|
||||||
if `dpkg -l | grep $_pkg | grep -e "^i" > /dev/null 2>&1` ; then
|
if `dpkg -l | grep $_pkg | grep -e "^i" > /dev/null 2>&1` ; then
|
||||||
continue
|
continue
|
||||||
@@ -2974,21 +3011,45 @@ milter_protocol = 6
|
|||||||
#
|
#
|
||||||
smtpd_milter_maps = cidr:/etc/postfix/smtpd_milter_map
|
smtpd_milter_maps = cidr:/etc/postfix/smtpd_milter_map
|
||||||
smtpd_milters =
|
smtpd_milters =
|
||||||
|
# Was sind non_smtpd_milters?
|
||||||
|
#
|
||||||
|
# non_smtpd_milters gilt für alle Postfix-Prozesse, die Mails verarbeiten, aber NICHT
|
||||||
|
# der smtpd-Daemon sind.
|
||||||
|
#
|
||||||
|
# Das betrifft z. B.:
|
||||||
|
#
|
||||||
|
# cleanup Header/Content-Bereinigung
|
||||||
|
# qmgr Queue-Manager
|
||||||
|
# lmtp / smtp Auslieferung nach extern
|
||||||
|
# local lokale Zustellung
|
||||||
|
#
|
||||||
|
# Das sind z. B.:
|
||||||
|
#
|
||||||
|
# - interne Bounces (MAILER-DAEMON)
|
||||||
|
#
|
||||||
|
# - Cron-Mails vom Server
|
||||||
|
#
|
||||||
|
# - Weiterleitungen, die Postfix selbst generiert
|
||||||
|
#
|
||||||
|
# - Mails, die über sendmail CLI gesendet werden
|
||||||
|
#
|
||||||
|
# - Mails, die Amavis über LMTP zurückgibt
|
||||||
|
#
|
||||||
|
# - etc.
|
||||||
|
#
|
||||||
|
#
|
||||||
EOF
|
EOF
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -n "$(which opendkim)" ]] ; then
|
if [[ -n "$(which opendkim)" ]] ; then
|
||||||
if [[ -n "$(which opendmarc)" ]] ; then
|
|
||||||
cat <<EOF >> /etc/postfix/main.cf
|
|
||||||
non_smtpd_milters = local:/opendkim/opendkim.sock,local:/opendmarc/opendmarc.sock
|
|
||||||
EOF
|
|
||||||
else
|
|
||||||
cat <<EOF >> /etc/postfix/main.cf
|
cat <<EOF >> /etc/postfix/main.cf
|
||||||
|
# DKIM soll auch die ausgehenden Mails signieren, die nicht über smtpd daemon versendet werden.
|
||||||
|
#
|
||||||
non_smtpd_milters = local:/opendkim/opendkim.sock
|
non_smtpd_milters = local:/opendkim/opendkim.sock
|
||||||
EOF
|
EOF
|
||||||
fi
|
else
|
||||||
elif [[ -n "$(which opendmarc)" ]] ; then
|
|
||||||
cat <<EOF >> /etc/postfix/main.cf
|
cat <<EOF >> /etc/postfix/main.cf
|
||||||
non_smtpd_milters = local:/opendmarc/opendmarc.sock
|
non_smtpd_milters =
|
||||||
EOF
|
EOF
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -3411,6 +3472,7 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
echononl " Create file \"relay_domains\""
|
echononl " Create file \"relay_domains\""
|
||||||
if [[ ! -f /etc/postfix/relay_domains ]] ; then
|
if [[ ! -f /etc/postfix/relay_domains ]] ; then
|
||||||
touch /etc/postfix/relay_domains
|
touch /etc/postfix/relay_domains
|
||||||
@@ -3752,6 +3814,133 @@ EOF
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
if ${INSTALL_DMARC_REPORT_SUPPORT} ; then
|
||||||
|
# ----
|
||||||
|
# - Add support for DMARC report
|
||||||
|
# ----
|
||||||
|
|
||||||
|
# - /var/lib/dmarc/
|
||||||
|
# - ├── reports/ # Eingegangene XML-, GZ-, ZIP-Dateien
|
||||||
|
# - │ └── YYYY/MM/DD/ # Datumsbasierte Ablage
|
||||||
|
# - ├── processed/ # Originalmails (Archiv)
|
||||||
|
# - ├── exports/ # CSV- und Top-Auswertungen
|
||||||
|
# - └── logs/ # Logdateien
|
||||||
|
echononl "Add directory Structure for collecting and analysing DMARC reports.."
|
||||||
|
install -d -o vmail -g vmail -m 750 /var/lib/dmarc/{reports,processed,exports,logs} > /dev/null 2> $log_file
|
||||||
|
if [[ $? -eq 0 ]] ; then
|
||||||
|
echo_ok
|
||||||
|
else
|
||||||
|
echo_failed
|
||||||
|
error "$(cat $log_file)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echononl "Add 'dmarc-pipe' entry to $postfix_master_cf .."
|
||||||
|
cat <<EOF >> /etc/postfix/transport 2> $log_file
|
||||||
|
|
||||||
|
# - Take care your master.cf file ($postfix_master_cf) contains:
|
||||||
|
# -
|
||||||
|
# - dmarc-pipe unix - n n - - pipe
|
||||||
|
# - flags=Rq user=vmail argv=/usr/local/bin/dmarc-collect.sh
|
||||||
|
# -
|
||||||
|
dmarc-reports@oopen.de dmarc-pipe:
|
||||||
|
|
||||||
|
EOF
|
||||||
|
if [[ $? -eq 0 ]] ; then
|
||||||
|
echo_ok
|
||||||
|
else
|
||||||
|
echo_failed
|
||||||
|
error "$(cat $log_file)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echononl "Create Postfix lookup table '/etc/postfix/transport'.."
|
||||||
|
postmap btree:/etc/postfix/transport > /dev/null 2> $log_file
|
||||||
|
if [[ $? -eq 0 ]] ; then
|
||||||
|
echo_ok
|
||||||
|
else
|
||||||
|
echo_failed
|
||||||
|
error "$(cat $log_file)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echononl "Create script '/usr/local/bin/dmarc-collect.sh'.."
|
||||||
|
tee /usr/local/bin/dmarc-collect.sh > /dev/null 2> $log_file <<'EOF'
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
BASE="/var/lib/dmarc"
|
||||||
|
INBOX="$BASE/reports"
|
||||||
|
PROC="$BASE/processed"
|
||||||
|
LOGF="$BASE/logs/collector.log"
|
||||||
|
|
||||||
|
umask 027
|
||||||
|
|
||||||
|
TMPDIR="$(mktemp -d)"
|
||||||
|
EML="$TMPDIR/mail.eml"
|
||||||
|
cat > "$EML"
|
||||||
|
|
||||||
|
ripmime --no-nameless --name-by-type --overwrite -i "$EML" -d "$TMPDIR" >>"$LOGF" 2>&1 || true
|
||||||
|
|
||||||
|
TODAY="$(date -u +%Y/%m/%d)"
|
||||||
|
OUTDIR="$INBOX/$TODAY"
|
||||||
|
mkdir -p "$OUTDIR"
|
||||||
|
|
||||||
|
moved=0
|
||||||
|
shopt -s nullglob
|
||||||
|
for f in "$TMPDIR"/*; do
|
||||||
|
case "$f" in
|
||||||
|
*.xml|*.XML|*.gz|*.zip)
|
||||||
|
sha="$(sha256sum "$f" | awk '{print $1}')"
|
||||||
|
base="$(basename "$f")"
|
||||||
|
dst="$OUTDIR/$(date -u +%Y%m%dT%H%M%SZ)_${sha:0:12}_$base"
|
||||||
|
mv "$f" "$dst"
|
||||||
|
echo "$(date -Is) stored $dst" >> "$LOGF"
|
||||||
|
moved=$((moved+1))
|
||||||
|
;;
|
||||||
|
*) : ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
mkdir -p "$PROC"
|
||||||
|
mv "$EML" "$PROC/$(date -u +%Y%m%dT%H%M%SZ)_mail.eml" || true
|
||||||
|
rm -rf "$TMPDIR"
|
||||||
|
|
||||||
|
if (( moved > 0 )); then
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
echo "$(date -Is) no usable attachment in message" >> "$LOGF"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
EOF
|
||||||
|
if [[ $? -eq 0 ]] ; then
|
||||||
|
echo_ok
|
||||||
|
else
|
||||||
|
echo_failed
|
||||||
|
error "$(cat $log_file)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
_failed=false
|
||||||
|
echononl "Set permissions for '/usr/local/bin/dmarc-collect.sh'.."
|
||||||
|
chown vmail:vmail /usr/local/bin/dmarc-collect.sh > /dev/null 2> $log_file
|
||||||
|
if [[ $? -ne 0 ]] ; then
|
||||||
|
_failed=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
chmod 750 /usr/local/bin/dmarc-collect.sh > /dev/null 2>> $log_file
|
||||||
|
if [[ $? -ne 0 ]] ; then
|
||||||
|
_failed=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
if ${_failed} ; then
|
||||||
|
echo_failed
|
||||||
|
error "$(cat $log_file)"
|
||||||
|
else
|
||||||
|
echo_ok
|
||||||
|
fi
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
## - /etc/postfix/master.cf
|
## - /etc/postfix/master.cf
|
||||||
## -
|
## -
|
||||||
## - Create Listener for user authenticated smtp connection port 587 (submission)
|
## - Create Listener for user authenticated smtp connection port 587 (submission)
|
||||||
@@ -3784,6 +3973,12 @@ else
|
|||||||
policyd_spf_present=false
|
policyd_spf_present=false
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if grep -iq -E "^dmarc-pipe\s+" $postfix_master_cf > /dev/null 2>&1 ; then
|
||||||
|
dmarc_pipe_present=true
|
||||||
|
else
|
||||||
|
dmarc_pipe_present=false
|
||||||
|
fi
|
||||||
|
|
||||||
_found=false
|
_found=false
|
||||||
echononl " Create new file \"${postfix_master_cf}\""
|
echononl " Create new file \"${postfix_master_cf}\""
|
||||||
if [[ -f "${postfix_master_cf}.$backup_date" ]]; then
|
if [[ -f "${postfix_master_cf}.$backup_date" ]]; then
|
||||||
@@ -3894,6 +4089,18 @@ smtp-ipv6-only unix - - n - - smtp
|
|||||||
EOF
|
EOF
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# - Add support for DMARC reporting
|
||||||
|
# -
|
||||||
|
if ${INSTALL_DMARC_REPORT_SUPPORT} ; then
|
||||||
|
if ! $(grep -iq -E "^dmarc-pipe\s+" "$postfix_master_cf" 2> /dev/null) ; then
|
||||||
|
cat <<EOF >> $postfix_master_cf
|
||||||
|
|
||||||
|
dmarc-pipe unix - n n - - pipe
|
||||||
|
flags=Rq user=vmail argv=/usr/local/bin/dmarc-collect.sh
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
echo_done
|
echo_done
|
||||||
warn "Please check file \"$postfix_master_cf\" !"
|
warn "Please check file \"$postfix_master_cf\" !"
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -2790,11 +2790,12 @@ fi
|
|||||||
## - Compile dovecot
|
## - Compile dovecot
|
||||||
## -
|
## -
|
||||||
echononl " Compile Dovecot Sources.."
|
echononl " Compile Dovecot Sources.."
|
||||||
make > ${_log_dir}/dovecot-${_version}-make.log 2>&1 || clean_up 1
|
make > ${_log_dir}/dovecot-${_version}-make.log 2>&1
|
||||||
if [ "$?" = 0 ]; then
|
if [ "$?" = 0 ]; then
|
||||||
echo -e "$rc_done"
|
echo -e "$rc_done"
|
||||||
else
|
else
|
||||||
echo -e "$rc_failed"
|
echo -e "$rc_failed"
|
||||||
|
echo -e "\n See file \033[1m${_log_dir}/dovecot-${_version}-make.log\033[m for more details."
|
||||||
fatal Compiling dovecot failed
|
fatal Compiling dovecot failed
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -6695,13 +6696,10 @@ EOF
|
|||||||
passdb_default_password_scheme = ${default_pass_scheme}
|
passdb_default_password_scheme = ${default_pass_scheme}
|
||||||
sql_driver = mysql
|
sql_driver = mysql
|
||||||
|
|
||||||
mysql localhost {
|
mysql /run/mysqld/mysqld.sock {
|
||||||
|
user = ${dbuser}
|
||||||
parameters {
|
password = ${dbpassword}
|
||||||
user=${dbuser}
|
dbname = ${dbname}
|
||||||
password=${dbpassword}
|
|
||||||
dbname=${dbname}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
EOF
|
EOF
|
||||||
if [ "$?" = 0 ]; then
|
if [ "$?" = 0 ]; then
|
||||||
@@ -7589,6 +7587,7 @@ if [[ $dovecot_major_version -gt 2 ]] \
|
|||||||
read -r -d '' NEW_BLOCK <<EOF
|
read -r -d '' NEW_BLOCK <<EOF
|
||||||
|
|
||||||
sieve_script personal {
|
sieve_script personal {
|
||||||
|
type = personal # kann man schreiben, ist aber Default
|
||||||
driver = file
|
driver = file
|
||||||
path = ~/sieve
|
path = ~/sieve
|
||||||
active_path = ~/.dovecot.sieve
|
active_path = ~/.dovecot.sieve
|
||||||
@@ -7606,6 +7605,8 @@ EOF
|
|||||||
# personal
|
# personal
|
||||||
# --------
|
# --------
|
||||||
#
|
#
|
||||||
|
# ** Used by both the Sieve plugin and the ManageSieve protocol **
|
||||||
|
#
|
||||||
# The personal storage serves as the user's main personal storage. Although more than a single
|
# The personal storage serves as the user's main personal storage. Although more than a single
|
||||||
# personal storage can be defined, only the first one listed in the configuration is used.
|
# personal storage can be defined, only the first one listed in the configuration is used.
|
||||||
#
|
#
|
||||||
@@ -7628,6 +7629,7 @@ EOF
|
|||||||
# no default script is executed.
|
# no default script is executed.
|
||||||
|
|
||||||
sieve_script personal {
|
sieve_script personal {
|
||||||
|
type = personal # kann man schreiben, ist aber Default
|
||||||
driver = file
|
driver = file
|
||||||
path = ~/sieve
|
path = ~/sieve
|
||||||
active_path = ~/.dovecot.sieve
|
active_path = ~/.dovecot.sieve
|
||||||
@@ -7640,16 +7642,17 @@ EOF
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
if grep -qE "^\s*sieve_script\s+before\s*{" "${_conf_file}"; then
|
if grep -qE "^\s*sieve_script\s+before_spam\s*{" "${_conf_file}"; then
|
||||||
|
|
||||||
read -r -d '' NEW_BLOCK <<EOF
|
read -r -d '' NEW_BLOCK <<EOF
|
||||||
|
|
||||||
sieve_script before {
|
sieve_script before_spam {
|
||||||
|
type = before
|
||||||
driver = file
|
driver = file
|
||||||
path = /usr/local/dovecot/etc/dovecot/sieve/
|
path = /usr/local/dovecot/etc/dovecot/sieve/
|
||||||
}
|
}
|
||||||
EOF
|
EOF
|
||||||
replace_or_append_code_block "sieve_script before" "${NEW_BLOCK}" "${_conf_file}" >> "${log_file}" 2>&1
|
replace_or_append_code_block "sieve_script before_spam" "${NEW_BLOCK}" "${_conf_file}" >> "${log_file}" 2>&1
|
||||||
if [[ $? -gt 0 ]]; then
|
if [[ $? -gt 0 ]]; then
|
||||||
_failed=true
|
_failed=true
|
||||||
fi
|
fi
|
||||||
@@ -7678,7 +7681,8 @@ EOF
|
|||||||
# A before storage behaves identical to an after storage, except the contained script or
|
# A before storage behaves identical to an after storage, except the contained script or
|
||||||
# scripts are run before user's personal script (instead of after).
|
# scripts are run before user's personal script (instead of after).
|
||||||
|
|
||||||
sieve_script before {
|
sieve_script before_spam {
|
||||||
|
type = before
|
||||||
driver = file
|
driver = file
|
||||||
path = /usr/local/dovecot/etc/dovecot/sieve/
|
path = /usr/local/dovecot/etc/dovecot/sieve/
|
||||||
}
|
}
|
||||||
@@ -7690,16 +7694,17 @@ EOF
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
if grep -qE "^\s*sieve_script\s+global\s*{" "${_conf_file}"; then
|
if grep -qE "^\s*sieve_script\s+global_includes\s*{" "${_conf_file}"; then
|
||||||
|
|
||||||
read -r -d '' NEW_BLOCK <<EOF
|
read -r -d '' NEW_BLOCK <<EOF
|
||||||
|
|
||||||
sieve_script global {
|
sieve_script global_includes {
|
||||||
|
type = global
|
||||||
driver = file
|
driver = file
|
||||||
path = /usr/local/dovecot/etc/dovecot/sieve/global/
|
path = /usr/local/dovecot/etc/dovecot/sieve/global/
|
||||||
}
|
}
|
||||||
EOF
|
EOF
|
||||||
replace_or_append_code_block "sieve_script global" "${NEW_BLOCK}" "${_conf_file}" >> "${log_file}" 2>&1
|
replace_or_append_code_block "sieve_script global_includes" "${NEW_BLOCK}" "${_conf_file}" >> "${log_file}" 2>&1
|
||||||
if [[ $? -gt 0 ]]; then
|
if [[ $? -gt 0 ]]; then
|
||||||
_failed=true
|
_failed=true
|
||||||
fi
|
fi
|
||||||
@@ -7748,7 +7753,8 @@ EOF
|
|||||||
# storages are defined in the configuration until the script is found. The order can be
|
# storages are defined in the configuration until the script is found. The order can be
|
||||||
# overridden by the sieve_script_precedence setting.
|
# overridden by the sieve_script_precedence setting.
|
||||||
|
|
||||||
sieve_script global {
|
sieve_script global_includes {
|
||||||
|
type = global
|
||||||
driver = file
|
driver = file
|
||||||
path = /usr/local/dovecot/etc/dovecot/sieve/global/
|
path = /usr/local/dovecot/etc/dovecot/sieve/global/
|
||||||
}
|
}
|
||||||
@@ -7779,6 +7785,32 @@ EOF
|
|||||||
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
_replace_key="sieve_max_redirects"
|
||||||
|
_replace_val=25
|
||||||
|
|
||||||
|
read -r -d '' COMMENT_BLOCK <<EOF
|
||||||
|
# sieve_max_redirects
|
||||||
|
#
|
||||||
|
# The maximum number of redirect actions that can be performed during a single script execution.
|
||||||
|
#
|
||||||
|
# Defaults to: 4
|
||||||
|
|
||||||
|
EOF
|
||||||
|
|
||||||
|
|
||||||
|
if grep -qE "^\s*${_replace_key}\s*=" "${_conf_file}"; then
|
||||||
|
|
||||||
|
replace_variable "${_replace_key}" "${_replace_val}" "${_conf_file}" 2>> "${log_file}" || _failed=true
|
||||||
|
|
||||||
|
else
|
||||||
|
|
||||||
|
cat <<EOF >> "${_conf_file}" 2>> "${log_file}" || _failed=true
|
||||||
|
|
||||||
|
${COMMENT_BLOCK}
|
||||||
|
${_replace_key} = ${_replace_val}
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
else
|
else
|
||||||
|
|
||||||
@@ -9211,7 +9243,7 @@ userdb sql {
|
|||||||
EOF
|
EOF
|
||||||
if grep -qE "^\s*userdb sql\s+{" "${_conf_file}"; then
|
if grep -qE "^\s*userdb sql\s+{" "${_conf_file}"; then
|
||||||
|
|
||||||
replace_code_block "userdb" "${NEW_BLOCK}" "${_conf_file}" || _failed=true
|
replace_code_block "userdb sql" "${NEW_BLOCK}" "${_conf_file}" || _failed=true
|
||||||
|
|
||||||
else
|
else
|
||||||
cat <<EOF >> "${_conf_file}" || _failed=true
|
cat <<EOF >> "${_conf_file}" || _failed=true
|
||||||
@@ -10847,24 +10879,27 @@ EOF
|
|||||||
replace_or_append_code_block "protocol sieve" "${NEW_BLOCK}" "${_conf_file}" || _failed=true
|
replace_or_append_code_block "protocol sieve" "${NEW_BLOCK}" "${_conf_file}" || _failed=true
|
||||||
|
|
||||||
|
|
||||||
read -r -d '' NEW_BLOCK <<'EOF'
|
# read -r -d '' NEW_BLOCK <<'EOF'
|
||||||
sieve_script personal {
|
#sieve_script personal {
|
||||||
path = ~/sieve
|
# type = personal # kann man schreiben, ist aber Default
|
||||||
active_path = ~/.dovecot.sieve
|
# type = personal
|
||||||
}
|
# driver = file
|
||||||
EOF
|
# path = ~/sieve
|
||||||
if grep -qE "^\s*sieve_script\s+personal\s+{" "${_conf_file}"; then
|
# active_path = ~/.dovecot.sieve
|
||||||
|
#}
|
||||||
replace_code_block "sieve_script personal" "${NEW_BLOCK}" "${_conf_file}" || _failed=true
|
#EOF
|
||||||
|
# if grep -qE "^\s*sieve_script\s+personal\s+{" "${_conf_file}"; then
|
||||||
else
|
#
|
||||||
|
# replace_code_block "sieve_script personal" "${NEW_BLOCK}" "${_conf_file}" || _failed=true
|
||||||
cat <<EOF >> "${_conf_file}" || _failed=true
|
#
|
||||||
|
# else
|
||||||
# Used by both the Sieve plugin and the ManageSieve protocol
|
#
|
||||||
${NEW_BLOCK}
|
# cat <<EOF >> "${_conf_file}" || _failed=true
|
||||||
EOF
|
#
|
||||||
fi
|
## Used by both the Sieve plugin and the ManageSieve protocol
|
||||||
|
#${NEW_BLOCK}
|
||||||
|
#EOF
|
||||||
|
# fi
|
||||||
|
|
||||||
|
|
||||||
if ! $_failed ; then
|
if ! $_failed ; then
|
||||||
|
|||||||
Reference in New Issue
Block a user