Installationsempfehlung

ivenae

Well-Known Member
Feb 11, 2022
125
50
48
42
Ich habe mich in den letzten Tagen intensiv mit Proxmox MG beschäftigt und fand die Anleitung nicht immer hilfreich.
Mein Spam wird um 90% reduziert, dabei möchte ich hier erklären, was ich ggü. der Out of the Box Installation verändert habe, um das zu erreichen. Das ist aus meiner Sicht etwas, was in vielen Anleitungen zu kurz kommt.
Danke an das Proxmox Team für dieses hervorragende Produkt und danke an alle, auf deren Kommentare und Empfehlungen hin ich Dinge umgesetzt habe. Kommentieren erwünscht, wenn euch dieser Beitrag gefällt, lasst mir ein Abo und ein Like ... Spaß.

Ich setze selbst einen Proxmox LXC im gleichen Netz ein, auf dem Modoboa als Mailserver läuft.

Meine Politik dabei ist, dass E-Mails gar nicht erst angenommen werden, wenn sie Spam sind, denn das größte Übel ist aus meiner Sicht, dass false positives, die in meinem Spam Ordner untergehen vom Sender als zugestellt geglaubt werden. Das schützt natürlich nicht davor, wenn bestellte Konzertkarten o.ä. bei mir nicht ankommen, weil der Absender seinen Mailserver nicht im Griff hat. Die Konzertkarten sind dann vermutlich 'weg' und erfordern erhebliche Nacharbeit. Ein Manko von "ich nehme deinen Kram nicht an".

Konsole: Unbound installieren
apt install unbound
127.0.0.1 in die /etc/resolv.conf eintragen (bei LXC Containern: Über Proxmox VE in den DNS Optionen setzen)
# Damit die DNSBL vernünftig funktionieren


Configurations -> Mail Proxy -> Relay Domains:
Eintragen meiner Domains
# E-Mails, die an diese Domains gehen, werden von Proxmox überhaupt aktzeptiert


Configurations -> Mail Proxy -> Options:
# Message Size (bytes): 104857600
Deutlich erhöhen, sonst gibt es Probleme mit Spam und DKIM Signaturen bei großen E-Mails.

Reject Unknown Clients: No
# Der rdns des Servers muss korrekt gesetzt sein. Geht in 98% gut, aber in 2% haben kleine Admins ihre Umgebung nicht korrekt gepflegt, die haben dann keine Chance euch Mails zu senden. Das greift vor jeglicher Whitelist. Daher besser ausgeschaltet lassen.

Reject Unknown Senders: Yes
# Die Absenderdomain muss existieren, damit die E-Mail angenommen wird. E-Mails von 'ausgedachten' Absenderdomains werden temporär rejected (4xx error code). Das macht ca. 20% der Spams aus, die nicht Gefahr laufen, im SA (Spamassassin) durchzurutschen. In ca. 1 % der Fälle schlägt die Prüfung fehl, dann wird die E-Mail 5 min später beim nächsten Einsendeversuch akzeptiert. In manchen Fällen wird die E-Mail von Spammern über 100x eingeliefert und nie akzeptiert, das nervt leider ein wenig.

Verify Receivers: Yes (550)
# Sicherstellung, dass E-Mails an unbekannte Mailkonten gar nicht erst angenommen werden. Ansonsten gibt es Bounces von Modoboa zum Proxmox, die beim Proxmox verbleiben, von denen der Versender jedoch nichts erfährt.

Use Greylisting for IPv4: No
# Greylisting verzögert die Annahme teilweise erheblich, was sehr schlecht bei Kontoverifizierungs E-Mails ist. Leider beherrscht PMG nicht die Möglichkeit, nur E-Mails mit fragwürdigem Spamscsore temporär zu rejecten, wie es rspamd beherrscht. In der hier gebotenen Qualität des Sendertripplets ist es aus meiner Sicht unbrauchbar.

Use SPF: (Yes)
# E-Mails mit falscher SPF werden abgelehnt. Habe ich bisher wenig Probleme mit, könnte aber durchaus mal problematisch sein. Gewisses Risko.

Client Connection Count Limit: 50
# Nicht reduzieren, wenn Before Queue Filtering eingeschaltet ist. Der Wert wird durch 5 geteilt (siehe mail.cf Template) und das sind dann die gleichzeitigen Verbindungen. Reduziert man den Wert (um Spammer mit Massenaufkommen fernzuhalten), habt ihr Probleme mit einigen Absenderservern, die ihre E-Mails bei multiplen Empfängern in mehrere Mails = mehrere Connections aufteilen.

Before Queue Filtering: Yes
# Kernanliegen, denn nachträgliche "NDRs on Blocked E-Mails" sind selbst Spam.

DNSBL Sites:
<ID>.zen.dq.spamhaus.net
# Bei Spamhaus registrieren und individuellen Link einfügen. Nicht den offenen zen.spamhaus.org nutzen.
b.barracudacentral.org
# Hier ist aktuell keine Registrierung notwendig
bl.spamcom.net
# Achtung: Blockt gelegentlich auch Google IPs, dann müssen die GMail Kunden ihre E-Mails erneut einsenden.


Configurations -> Mail Proxy -> Transports:
Eintragen meines Mailservers
<domain> -> Host: 172.30.0.3, use MX: No
# MX: No -> Weil der MX auf das PMG zeigt


Configurations -> Spam Detector -> Options:
Use Bayesian filter: No
# Bringt kaum Mehrwert für mich

Use RBL checks: Yes
# Dafür haben wir die DNSBL eingetragen

Use Razor2 checks: Yes
# Scheint viel zu bringen

Max Spam Size (bytes): 104857600
# Muss gleich sein wie unter Mail Proxy.


Mail Filter-> What Objects:
Spam Level 10 -> ändern auf Spam Level 7
Spam Level 3 -> ändern auf Spam Level 4
# Sehr individuelle Entscheidung, ich möchte Spam ab Level 7 bereits nicht mehr akzeptieren


Mail Filter:
Block Spam (Level 10): Umbenennen in Level 7 und prüfen, ob hier als What Object der Level 7 Spam steht

Quarantine Spam (Level 7): Regel Deaktivieren. Ich möchte keine Quarantäne. Ganz oder gar nicht

Mark Spam (Level 3): Umbenennen in Level 4:
Action Object: Modify Spam Subject (Kein Notify, kein Qurantine)
What Object: Spam (Level 4)

# Ganz oder gar nicht:
# Ab Level 7 Blocken (durch Before Queue Filter wird rejected)
# Ab Level 4 mit Spam Tag (ggf. durch Modoboa in Spam Ordner einsortieren)
# keine Quarantäne in einem Level dazwischen
# Hier handelt es sich sicherlich um eine sehr individuelle Entscheidung


#####Nun auf die Konsole wechseln:
Transportliste als gültige Empfänger:

Folgendes Skript erstellen, welches täglich die Transportliste nutzt, um 'BCC'-Spam auszusortieren. Das ist für mich Spam, der dadurch klassifiziert wird, dass ich nicht als Empfänger oder CC-Empfänger bin, sondern nur in einer BCC-Liste stehe. Alle E-Mails, in denen die Domains der Transportliste nicht in der Empfängerliste stehen, bekommen einen Scorepunkt. Achtung: Wer in Mailinglisten unterwegs ist, könnte hiermit Probleme bekommen und muss diese ggf. Whitelisten. Auch Weiterleitungen von Free-Mail-Accounts an die eigene Domain sind hiervon betroffen. Ggf. diese Whitelisten. ChatGPT hat tatsächlich bei diesem Skript ganze Arbeit geleistet und mir das mehr oder weniger so ausgespuckt.

~# cat > /usr/local/bin/update-sa-bcc-whitelist.sh
Code:
#!/bin/bash
DOMAINS=$(cut -d' ' -f1 /etc/pmg/transport | sed 's/\./\\./g; s/-/\\-/g' | tr '\n' '|' | sed 's/|$//')

cat <<EOF > /etc/mail/spamassassin/98-bcc-transport-domains.cf
header   BCC_SPAM_TOCC ToCc !~ /\@(${DOMAINS})/i
score    BCC_SPAM_TOCC 2.0
describe BCC_SPAM_TOCC Domain nicht in Transport-Liste
EOF
sa-compile
systemctl restart pmg-smtp-filter


Skript als ausführbar markieren und einmalig ausführen:
chmod +x /usr/local/bin/update-sa-bcc-whitelist.sh
/usr/local/bin/update-sa-bcc-whitelist.sh

Cron Eintrag für die tägliche Aktualisierung dieser Liste
echo "0 0 * * 1 root /usr/local/bin/update-sa-bcc-whitelist.sh" > /etc/cron.d/sa-bcc-update


### Custom Rules in /etc/mail/spamassassin
# Die Regel 98-bcc-transport-domains.cf sollte nun schon existieren

Regel zum erweiterten Markieren von BCC Spam
Part 1: Markiert alle E-Mails unerwünschter Absenderdomains. Diese erhalten automatisch einen Score von 3.0, weil sie nahezu immer unseriös sind
Part 2: Wenn die E-Mail von einer unseriösen TLD stammt, und ich zusätzlich nur im BCC stehe, dann wird ein weiterer "Bonus"-Score von 2.0 vergeben.
E-Mails die dieses Kriterium erfüllen sind fast immer Spam, Ausnahmen könnten ausländische Mailinglisten sein. Die müsste man in die Welcomelist eintragen.

Code:
cat <<EOF > /etc/mail/spamassassin/99-BCC_TLD.cf
header FROM_TLD_OTHER From =~ /\.(?!(?:com|net|info|org|de|at|ch|eu|shop)(?:>|$))[A-Za-z0-9-]+>?$/i
score FROM_TLD_OTHER 3.0
describe FROM_TLD_OTHER Sender uses unusual or risky TLD

meta BCC_AND_UNKNOWN_TLD (FROM_TLD_OTHER && BCC_SPAM_TOCC)
score BCC_AND_UNKNOWN_TLD 2.0
describe awkward sender and unknown recipient


"Don't be evil": 80% aller Spams von Google
Google ist bei mir für 80% des Spams [sic!] verantwortlich.

firebaseapp.com (Google Service) verschickt mehr oder weniger ausschließlich Spam und erhält "Bonuspunkte".

Spammer nutzen mittlerweile selbst angelegte Google Groups Listen, weil sie kein Opt-In des Empfängers benötigen und Mailinglisten standardmäßig seitens Spamassassin einen Score von "-1.0" zugewiesen bekommen. Nicht nur die Spam-Mails kommen an, sondern auch die Auto-Replys der anderen Empfänger oder die wütenden "Nehmt mich da raus" Antworten. Daher bekommen Google Mailing-Lists einen Score von 5 (+ 1.0 für BCC -1.0 für Spam-Assassin Mailing Default). Dadurch landen 95% aller Google Mailinglisten im Block/reject, außer die "positive Word List" greift mal versehentlich.

Und zuguterletzt: Google wird (leider) mittlerweile von einigen großen Unternehmen (Doctolib, Ryanair) im Rahmen des Google Workplace als Versender von E-Mails eingesetzt. Wer also seine Flugtickets weiterhin erhalten will, kann leider Google nicht gänzlich blockieren (erste Ideen des Filters basieren darauf, nichts von Google zu akzeptieren, was nicht als Absender googlemail.com, gmail.com, google.com oder google.de beinhaltet). Aber es ist wirksam, saftige Strafpunkte zu verteilen, sofern die Domainendung nicht aus obiger TLD-Liste ist (.com, .de, .at, ...), wenn die E-Mail von Google verschickt wurde. Achtung: Hier addieren sich ggf. Punkte verschiedener Kategorien, ist aber aufgrund der unglaublichen Schwemme an Müll seitens der Google Server zu verschmerzen.

Code:
header FROM_FIREBASEAPP From =~ /firebaseapp\.com>?$/i
score  FROM_FIREBASEAPP 7.0
describe FROM_FIREBASEAPP firebase only send spam

header GOOGLE_GROUPS List-Unsubscribe =~ /googlegroups\.com/i
score  GOOGLE_GROUPS 7.0
describe GOOGLE_GROUPS Block Google Groups mailing lists

header   __HELO_GOOGLE   Received =~ /\.google\.com/i
meta     GOOGLE_FROMTLD_OTHER  (__HELO_GOOGLE && FROM_TLD_OTHER)
score    GOOGLE_FROMTLD_OTHER  3.0
describe GOOGLE_FROMTLD_OTHER  HELO endet auf google.com und FROM_TLD_OTHER trifft zu

Positive Word List
Diese Regel(n) beinhalten Wörter, die i.d.R. darauf hindeuten, dass es sich um seriöse E-Mails handelt (z.B. Bestellbestätigungen)
Hier bitte <meine Stadt> durch euren Wohnort ersetzen und <mein Name> durch euren Vornamen
-9.0 Score gibt es für die Erwähnung meiner Stadt in der E-Mail, oft handelt es sich dann um Bestellbestätigungen etc. Das möchte ich immer erhalten

-5.0 Score gibt es für die Erwähnung meines Vornamens. Meist ist das ebenfalls ein Hinweis darauf, dass der Empfänger mich persönlich kennt.
Achtung: Die Regel führt ggf. zu Problemen, wenn euer Vorname in eurer E-Mail vorkommt, denn dann kommen generische Mails auch durch wie:
"Hallo vorname.nachname@domain.example, sie haben ggf. auch Probleme mit ihrer Potenz"

Diese Liste könnt ihr natürlich beliebig erweitern, um z.B. eure Straße oder sonstige, individuellen Wörter. Ich habe hier noch das Wort "Ticket" mit drin.
Hinweis dazu: Schreibt ihr mehrere Wörter in eine Regel, dann wird beim Auffinden mehrerer Wörter nur einmal der Score abgezogen. Schreibt ihr mehrere Wörter in mehrere Regeln, dann werden beim Auffinden mehrerer Wörter der Score mehrmals abgezogen. Beides kann sinnvoll sein.

Ihr könnt damit herumspielen, indem ihr \b einsetzt, welches dafür sorgt, dass das Wort von Leerzeichen umgeben sein muss. (Greift dann auf <Vorname>, aber nicht auf <vorname.nachname@..>

Außerdem sorgt das i am Ende dafür, dass Groß- und Kleinschreibung egal ist. Auch damit kann man "herumspielen".

Code:
cat <<EOF > /etc/mail/spamassassin/99-positive-word-list
body POSITIVE_WORDS_HIGH /<meine Stadt>/i
score POSITIVE_WORDS_HIGH -9.0
describe POSITIVE_WORDS_HIGH Enthält vertrauenswürdige Wörter

body POSITIVE_WORDS_LOW /<mein Vorname>/i
score POSITIVE_WORDS_LOW -5.0
describe POSITIVE_WORDS_LOW Enthält vertrauenswürdige Wörter
EOF


Filter kompilieren
Damit die Filter schneller abgearbeitet werden, sollten sie kompiliert werden. Wir benötigen hierzu ein paar Pakete:

Code:
apt get install make gcc re2c

Danach kompilieren und am Ende Spamassassin neustarten mit:
Code:
sa-compile
systemctl restart pmg-smtp-filter


Fazit
Die Quote der falsch positiven liegt bei < 0,5%, insgesamt betraf das 2 E-Mails:
- In einem Fall hatte der Absender sein DKIM falsch konfiguriert, deshalb hatte eine seriöse E-Mail einen Score von 6.0, wobei 3.0 vom falschen DKIM beigesteuert wurde.
- In einem Fall wurde eine Domain Verifizierungs E-Mail an eine E-Mail Adresse geschickt, die ich nur per Weiterleitung bekomme. Dadurch hatte die Mail einen Basis-Score von 2.0 und aufgrund anderer, unglücklicher Eigenschaften kamen auch hier insgesamt 6.0 als Score heraus. Die E-Mail konnte einfach neu beantragt werden, der Filter wurde dann kurzzeitig komplett deaktiviert.

Spamscores in 14 Tagen:
Score 0: 80%
Score 1-3: 5%
Score 4-5: 2 % (Spam Tag)
Score 6-9: 3 % (Block)
Score 10++ : 10% (Block)

Achtung, traue keiner Statistik, die du nicht selbst gefälscht hast:

- geschätzt 1/3 aller Score 0 E-Mails sind ausgehende E-Mails. Die werden nicht gescannt, tauchen aber in der Statistik auf.

- Als IT-ler bekomme ich viele Status E-Mails von Kunden. Diese werden per Smarthost an mich geleitet, wobei der Smarthost mein eigener Mailserver ist. Die E-Mails tauchen in der Liste auch nicht auf, weil sie direkt von meinem Mailserver einsortiert werden und nicht über das PMG gehen.

- Ca. weitere 10% (hää? Das sind ja dann 110%!!!) wurden aufgrund von Spamhaus.net rejected, d.h. hier wurde die Verbindung mit dem E-Mail Server direkt abgelehnt. Die Mails bekommen gar keinen Score und tauchen auch nicht in der Liste auf, wären aber ohne PMG vermutlich zugestellt worden.

- Darüber hinaus gibt es eine Vielzahl an Servern, die mein System auf Open-Relay testen (spameri@tiscali.it; relaycheck_please_ignore@protonmail.com) , d.h. der Empfänger liegt nicht auf meinem Mailserver; oder der Empfänger ist ein ungültiges E-Mail Postfach auf meinem Mailserver. Die E-Mails tauchen teilweise in der Statisik auf, hätten aber mein eigenes Postfach gar nicht betroffen.

Trotzdem ist die Statistik ein gutes Indiz.


Wer weniger Risiko möchte, schaltet den Block erst ab Score 10 scharf.
 
Last edited:
Kurzer Nachtrag, sofern man keinen eigenen Mailserver betreiben möchte:
Mit All-Inkl. als Mailanbieter habe ich die Konfiguration laufen. Folgendes muss beachtet werden:

- Als Relay trägt man den eigenen kasserver ein, Port 25, kein MX benutzen

- Bei All-Inkl. im DNS erstellt man einen "Sandwitch"-MX: Zwei verschiedene Domainnamen für den eigenen Proxmox PMG Server mit niedrigster und höchster Priorität (z.B. 5 und 15). Eigentlich möchte man hier zweimal den gleichen Eintrag erstellen, einmal mit Prio 5 und einmal mit Prio 15. Das mag All-Inkl. jedoch nicht, daher setzt man hier zwei unterschiedliche FQDN für die gleiche IP; alternativ einmal den FQDN und einmal die IP.

- Wichtig: In der Mitte des Sandwitches bleibt der kasserver als MX (default Prio 10).
Warum das?
Der All-Inkl. Server fühlt sich nach einigen Stunden nicht mehr zuständig, wenn er nicht einen MX Record besitzt und lehnt Mails ab mit "relay access denied", es muss demnach einen MX Eintrag für den kasserver geben. Wir lassen ihn unangetastet.
Üblicherweise versenden Mailserver an den MX mit der niedrigsten Prio, einige Spammer nutzen das aus und schicken an den mit der höchsten Prio in der Hoffnung dort weniger Gegenwehr zu erhalten. Daher setzen wir unseren kasserver in die Mitte, dann bleibt er i.d.R. verschont.

- In jeder angelegten E-Mail Adresse [sic!] muss einmalig der policyd weight Filter ausgeschaltet werden, damit keine SPF Prüfungen durchgeführt werden. Mit SPF Prüfung landen i.d.R. alle eingehenden E-Mails im Spam.
 
Last edited:
  • Like
Reactions: Johannes S
Um mal meine weiteren Erfahrungen zu teilen. Die Möglichkeiten, den Filter im Hinblick auf Qualität und Geschwindigkeit zu optimieren.

- Das Nutzen eines öffentlichen Mailservers (z.B. All-Inkl.) hinter einem Proxmox ist oft möglich, sofern der Empfänger nicht darauf besteht, SPF / DMARC zwingend zu ahnden. Lokale Spamfilter des Anbieters sind im Zweifel auszuschalten.

- In dieser Konstellation muss darauf geachtet werden, dass der Spammer nicht direkt beim Betreiber einliefert:

+ Manche Spammer senden ihre E-Mails an domain.de, mail.domain.de, smtp.domain.de, mx.domain.de, autodiscover.domain.de => Setzt diese ggf. ebenfalls auf das Spamgateway, obgleich das bei domain.de nicht immer möglich ist. Der Weg der bequemen Einrichtung eures E-Mail Programmes ist damit ggf. aber ausgehebelt. Denkt auch an Catchall Domains: *.domain.de ; am besten Catchall deaktivieren.

+ Manche Spammer senden an den letzten MX Eintrag statt den ersten. Sandwitcht euren öffentlichen MX im Hinblick auf die Prio

+ Google sendet gerne auch schonmal an mehrere MX gleichzeitig, wenn euer MX zu langsam ist. Trimmt daher auf Geschwindigkeit und lehnt Google Spams früh ab. Mehr dazu weiter unten. Darüber hinaus könnt ihr auch noch zwei oder drei weitere MX aufs Spamgateway zeigen lassen, damit "Don't be evil"-Spamkönig Google mehr zum durchprobieren hat.

- Spammails sind Herdentiere. Ich fülle mit einem Cronjob alle 15 min die /etc/postfix/senderaccess mit Domains, die mich fluten. Dabei pflege ich Ausnahmelisten und ignoriere .de Domains, schaue 14 Tage zurück, erwarte mind. 9 E-Mails von der Domain mit 90% Ablehnungsquote. Achtung: Das könnte ein Angreifer mit gespooften Domains ausnutzen. Das korrigiere ich, wenn der erste Fall eintritt. Vorteil: Wird dramatisch schneller abgewiesen, versehentliches Durchlassen einer weiteren Mail von dieser Domain aufgrund geänderter Filterkriterien ist nicht mehr möglich.
Weiterer Vorteil: Manchmal senden Spammer mit nicht existenter Absenderdomain und bekommen über 5 Tage alle 10 min einen 450er zurück. Damit ist dann auch Schluss, weil die Domain auch auf der Blockliste landet. Risiko, gespoofte Domains komplett zu blockieren: Müsst ihr wissen...

Python:
#!/usr/bin/env python3
import os
import re
import subprocess
import sys
from datetime import datetime

# --- Argument Handling ---
TEST_MODE = "--test" in sys.argv

# --- Config ---
DAYS = int(os.getenv("DAYS", 14))
MIN_COUNT = int(os.getenv("MIN_COUNT", 9))
BASE_DOMAIN = int(os.getenv("BASE_DOMAIN", 1))
SENDERACCESS = os.getenv("SENDERACCESS", "/etc/postfix/senderaccess")
TRANSPORTMAP = os.getenv("TRANSPORTMAP", "/etc/pmg/transport")
TAG = os.getenv("TAG", "autoblacklist")
RELOAD = int(os.getenv("RELOAD", 1))

# Wenn 1: Domains mit .de werden grundsätzlich NICHT automatisch geblockt
# (außer sie stehen in FORCE_BLOCK_DOMAINS).
EXEMPT_DE = int(os.getenv("EXEMPT_DE", 1))

# --- Whitelist / Exclude Domains ---
EXCLUDE_DOMAINS = {
    "amazon.com",
    "gmail.com",
    "googlemail.com",
    "mail.com",
    "amazonses.com",
    "hotmail.com",
    "outlook.com",
    "ebay.com",
    "mailjet.com",
    "sender-sib.com",
    "gmx.com",
    "gmx.net",
}

# --- Force Blocklist Domains (always blocked) ---
FORCE_BLOCK_DOMAINS = {
}

BLOCK_RE = re.compile(
    r"NOQUEUE: reject|proxy-reject:|reject:|blocked|blacklist|spam|"
    r"spamd: identified spam|rspamd|amavis.*BLOCKED|quarantin|discard|"
    r"Rejected for policy reasons|BLOCKED",
    re.IGNORECASE,
)

ACCEPT_RE = re.compile(
    r"proxy-accept:|accept mail to|status=sent \(250 2\.[0-9]\.0|queued as|relay=",
    re.IGNORECASE,
)


def read_logs_last_days():
    try:
        result = subprocess.run(
            ["journalctl", "--no-pager", "--since", f"{DAYS} days ago", "-u", "postfix"],
            capture_output=True,
            text=True,
            check=False,
        )
        return result.stdout.splitlines()
    except Exception:
        return []


def base_domain_func(dom):
    parts = dom.split(".")
    if len(parts) < 2:
        return dom

    tld = parts[-1]
    sld = parts[-2]

    if (
        tld in {"uk", "au", "nz", "za", "br", "jp"}
        and sld in {"co", "com", "net", "org", "gov", "ac", "edu"}
        and len(parts) >= 3
    ):
        return ".".join(parts[-3:])
    return ".".join(parts[-2:])


def normalize_domain(dom):
    dom = dom.lower().rstrip(".")
    if BASE_DOMAIN == 1:
        dom = base_domain_func(dom)
    return dom


def is_de_domain(dom: str) -> bool:
    # greift nach normalize_domain(), daher ist das i.d.R. "example.de"
    return dom.endswith(".de")


def extract_domain(line):
    m = re.search(r"from=<[^>]*@([^> ,]+)>", line)
    if m:
        return normalize_domain(m.group(1))
    return ""


def load_transport_domains():
    domains = set()

    if not os.path.isfile(TRANSPORTMAP):
        return domains

    with open(TRANSPORTMAP, "r", errors="ignore") as f:
        for line in f:
            line = line.strip()
            if not line or line.startswith("#"):
                continue
            key = line.split()[0].strip("[]").lower().rstrip(".")
            domains.add(normalize_domain(key))

    return domains


def find_candidates():
    total = {}
    blocked = {}
    accepted = {}

    for line in read_logs_last_days():
        dom = extract_domain(line)
        if not dom:
            continue

        if ACCEPT_RE.search(line) and not BLOCK_RE.search(line):
            accepted[dom] = accepted.get(dom, 0) + 1

        if BLOCK_RE.search(line):
            blocked[dom] = blocked.get(dom, 0) + 1

        if BLOCK_RE.search(line) or ACCEPT_RE.search(line):
            total[dom] = total.get(dom, 0) + 1

    candidates = []

    for dom in total:
        if total[dom] < MIN_COUNT:
            continue

        b = blocked.get(dom, 0)
        t = total[dom]

        # Blockrate in Prozent
        block_ratio = b / t

        # Nur Kandidaten mit > 90% geblockten Mails
        if block_ratio > 0.9:
            candidates.append((dom, t))

    return sorted(candidates, key=lambda x: x[1], reverse=True)


def append_rule(domain):
    esc = re.escape(domain)
    return f"/@.*{esc}>?$/\tREJECT [{TAG}]"


# --- Main ---

if not os.path.isfile(SENDERACCESS):
    print(f"ERROR: senderaccess file not found: {SENDERACCESS}", file=sys.stderr)
    sys.exit(1)

mode = "TEST (Dry-Run)" if TEST_MODE else "LIVE"
print(f"Running in {mode} mode")

transport_excludes = load_transport_domains()
all_excludes = {normalize_domain(d) for d in EXCLUDE_DOMAINS}
all_excludes.update(transport_excludes)

force_block = {normalize_domain(d) for d in FORCE_BLOCK_DOMAINS}

candidates = find_candidates()
new_rules = []

# Automatic candidates
for dom, cnt in candidates:
    # .de grundsätzlich ausnehmen (aber NICHT, wenn force_block)
    if EXEMPT_DE == 1 and is_de_domain(dom) and dom not in force_block:
        print(f"Skip (.de exempt): {dom}")
        continue

    if dom in all_excludes:
        print(f"Skip (excluded domain): {dom}")
        continue

    new_rules.append(append_rule(dom))

# Forced blocklist domains (always blocked)
for dom in force_block:
    print(f"Force-adding (blocklist): {dom}")
    new_rules.append(append_rule(dom))

if not new_rules:
    print("Nothing new to add.")
    sys.exit(0)

print("\nProposed additions:")
for r in new_rules:
    print(r)

if TEST_MODE:
    print("\nDry-run complete. No changes were made.")
    sys.exit(0)

with open(SENDERACCESS, "w") as f:
    f.write(f"\n# --- auto-added {datetime.now().isoformat()} [{TAG}] ---\n")
    for r in new_rules:
        f.write(r + "\n")

if RELOAD == 1:
    print("Reloading postfix...")
    subprocess.run(["/usr/sbin/postfix", "reload"], check=False)


- rspamd mit Custom-Check: Man kann einen Custom Check über rspamd einfügen (bitte hierzu im Forum suchen). Leider wird kein Spamassassin Check mehr gemacht, wenn der Score > 4.9 ist. Daher begrenze ich den zurückgegebenen Score auf 4.9; es sei denn der Score ist > 10.0, dann kann die E-Mail direkt geblockt werden.
Außerdem: Wenn eine Spamwelle anrollt (> 15 Mails in 5 min), dann führe direkt auch einmal das obige fill_senderaccess.py Skript aus, damit ggf. direkt im postscreen gefiltert wird.

Code:
#!/usr/bin/env bash
set -euo pipefail

# PMG custom check API v1: args: APIVERSION QUEUEFILENAME
if [[ $# -ne 2 ]]; then
  echo "usage: $0 APIVERSION QUEUEFILENAME" >&2
  exit 1
fi

apiver="$1"
queue_file="$2"
COUNTER_FILE="/tmp/custom.counter"
LOCK_FILE="/tmp/custom.lock"
WINDOW_SEC=300      # 5 Minuten
LIMIT=15            # Mail Count
RSPAMD_HOST="127.0.0.1"
RSPAMD_PORT="11333"
echo "v1"

# Lock-Datei für sichere Concurrency
exec 9>"$LOCK_FILE"
flock -x -w 10 9 || { echo "Konnte Lock nicht bekommen" >&2; exit 1; }

# Counter-Datei initialisieren
touch "$COUNTER_FILE"

now=$(date +%s)
window_start=$(( now - WINDOW_SEC ))

# Alte Einträge filtern (>5min)
tmpfile=$(mktemp)
awk -v ws="$window_start" '$1 >= ws' "$COUNTER_FILE" > "$tmpfile" 2>/dev/null || true
mv "$tmpfile" "$COUNTER_FILE"

# Aktuellen Aufruf eintragen
echo "$now" >> "$COUNTER_FILE"
count=$(wc -l < "$COUNTER_FILE")

# rspamc output enthält typischerweise: "Score: 3.00 / 15.00"
# Wir parsen die erste Zahl nach "Score:"
rspamc_out="$(
  rspamc -h "${RSPAMD_HOST}:${RSPAMD_PORT}" < "$queue_file" 2>/dev/null || true
)"

score="$(awk -F': ' '/^Score: /{split($2,a," "); print a[1]; exit}' <<<"$rspamc_out")"

if [[ -n "${score:-}" ]]; then
  # optional: Score skalieren/offsetten, wenn du PMG-Schwellen beibehalten willst
  capped_score=$(awk -v s="$score" 'BEGIN { if (s > 4.9 && s <= 9.9) print 4.9; else print s }')
  echo "SCORE: ${capped_score}"
else
  # bei Fehlern lieber OK liefern, damit keine Mails hängen bleiben
  echo "OK"
fi

if [ "$count" -gt "$LIMIT" ]; then
    /usr/local/bin/fill_senderaccess.py > /dev/null 2>&1
    # Counter komplett zurücksetzen
    rm "$COUNTER_FILE"
fi

exit 0




- Die positive Word List habe ich in vielen Einzelregeln gelöst und jedes positive Wort gibt nur -1.0 oder -2.0. Insbesondere positive Wörter, die aus der E-Mail Domain selbst stammen können geben weniger Punkte.

- Configuration -> Spam Detector -> Custom Scores ist ein mächtiges Werkzeug.
Achtung: Nach jeder Änderung muss unten die Konfiguration übernommen werden! Wird gerne vergessen.

Hier hat sich herausgestellt, dass einige Regeln deutlich zu hart greifen und abgeschwächt werden muss:
- KAM_MARKSPAM habe ich z.B. von 10 auf 0 gesetzt, weil es statistisch keine echten Spams findet
- DEAR_SOMETHING war für mich auch ein eher sinnloser Filter

Einige haben sich als besonders gute Spam-Indikatoren herausgestellt. Ein paar Beispiele in absteigender Reihenfolge (absteigender Score)
RAZOR2_CF_RANGE_51_100
RAZOR2_CHECK
DATE_IN_PAST_xxxxx
FORGED_OUTLOOK_HTML
FROM_FMBLA_NEWDOMxxx
RCVD_IN_MSPIKE_BL
T_SPF_PERMERROR
SPF_SOFTFAIL
FREEMAIL_FORGED_REPLY
RCVD_IN_BL_SPAMCOP
MAILING_LIST_MULTI (ist normalerweise negativ, habe ich positiv gesetzt)


- Ich habe einige negative Scores über weitere SA Regeln eingeführt, u.a. für Betreiber, die tunlichst darauf achten, dass kein Spam von ihren Systemen verteilt wird. Dazu gehört t-online.de, gmx.de, web.de, ionos/strato. Diese werden mit einem hohen Negativscore belohnt.
 
Last edited:
  • Like
Reactions: Johannes S