Proxmox Mail Gateway PMG Emails auf Wörter in Nachricht filtern Mailbody
Aus znilwiki
Changelog:
- 02.06.2025 Skript Version 1.0 => erste Version
- 03.06.2025 Skript Version 1.1 => mehr Details in den Logmeldungen / Syslog, Farbige Meldungen in der Konsole
- 26.06.2025 Version 2.0 => Management der Filterwörter über die Weboberfläche des PMG
Hinweis:Den Anfang des Artikel mit der Version 1.1 des Skriptes habe ich hier so gelassen. Am Ende das Artikels habe die die neue Variante 2.x angehängt bei der man die Filterwörter über die Weboberfläche managen kann
Vorwort
In letzter Zeit werden meine Familie und ich von Spam-Mails über Erektionsproblemen überflutet. Ein Großteil filtert das PMG schon aus so das dieses schon mal in Quarantäne landen. Aber es sind so viele das immer noch ein großer Teil durchrutscht.
Die Mails sehen alle so aus:
Der Text variiert, der Aufbau ist immer gleich. Die Mails werden dabei immer von vertrauenswürdigen Quellen gesendet, vermutlich also gehackte Emailkonten.
Allen gemein ist das immer auf eine URL verwiesen wird die auf
apotheke.html
endet. Die HTML-Datei liegt dabei oft auch auf den Webspace von vertrauenswürdigen Webseiten (die vermutlich auch gehackt wurden).
Also eigentlich etwas wonach man prima filtern könnte.
Es stellte sich aber heraus das es dann doch nicht ganz so einfach mit PMG ist, denn es gibt keinen direkten Weg über die Weboberfläche.
Die Grundlage der Lösung findet sich in der Hilfe:
https://pmg.proxmox.com/pmg-docs/pmg-admin-guide.html#pmgconfig_custom_check
Man kann ein eigenes Skript einbinden an welche dann alle eingehenden Emails übergeben werden.
Hinweis zur Filterung mit diesen Skripts
Bitte beachtet das aktuell per "Text in Text" Suche gefiltert wird. Es funktionieren also keine reguläre Ausdrücke, ein Punkt . ist ein Punkt und kein beliebiges Zeichen, ein Stern * wird als eben dieses Zeichen gesucht und hat keine Funktion als Platzhalter.
Version 1.1
Bei dieser Version werden die Wörter oder Sätze am Anfang des Skripts in den Variablen filterArraySPAM und filterArrayWHITE definiert.
Wollte Ihr Wörter hinzufügen oder löschen, so müsst Ihr das Skript bearbeiten.
Custom Check Skript erstellen
Die Grundlage der Lösung findet sich in der Hilfe:
https://pmg.proxmox.com/pmg-docs/pmg-admin-guide.html#pmgconfig_custom_check
Ich wollte es erst per SpamAssassin machen (mache ich vielleicht noch), die Lösung mit dem Skript gefiel mir jedoch im Moment besser.
Wir erstellen das folgende Skript:
nano /usr/local/bin/pmg-custom-check
mit folgendem Inhalt:
#!/bin/bash
# Siehe https://znil.net/index.php?title=Proxmox_Mail_Gateway_PMG_Emails_auf_W%C3%B6rter_in_Nachricht_filtern_Mailbody
#
# V1.1 vom 03.06.2025 von Bernhard Linz => Bernhard@znil.de
#
# Filter, weitere einfach mit Leerzeichen und in "..." hinzufügen, Es wird auf "String in String" getestet
# Nach SPAM
filterArraySPAM=("apotheke.html" "test1234567890")
# Spam Score der gesetzt werden soll wenn es einen SPAM Treffer gibt
spamScoreSPAM=4
# Nach Whitelisting
filterArrayWHITE=("Test Abrakadabra")
# Spam Score der gesetzt werden soll wenn es einen WHITE Treffer gibt
spamScoreWHITE=-10
###############################################################################
# schreibt nach STDERR durch 1>&2, das landet dann nur im Logfile /var/log/mail.log
# und wird nicht als Rückgabe des Skriptes interpretiert
# echo "/usr/bin/pmg-custom-check called with $*" 1>&2
echo -e "\e[31m/usr/bin/pmg-custom-check called with $*\e[0m" 1>&2
# Fehler wenn nicht 2 Argumente
if [ "$#" -ne 2 ]; then
echo "usage: $0 APIVERSION QUEUEFILENAME" 1>&2
exit 1
fi
# Argument $1 ist die API-Version
apiver="$1"
# Argument $2 ist die Datei mit der Email
queue_file="$2"
# shift würde das Argument $2 zu $1 verschieben
#shift
if [ "$apiver" != "v1" ]; then
echo "/usr/bin/pmg-custom-check wrong APIVERSION: $apiver" 1>&2
exit 2
fi
#queue_file="$1"
# Das Skirpt muss auf STDOUT 2 Ausgaben machen:
# Die API-Version, hier also v1
echo "v1"
# Und eine(!) der folgenden 3 Ausgaben:
# 1: Es ist alles ok:
#echo "OK"
# 2: Die Mail enthält einen Virus
#echo "VIRUS: <virusdescription>"
# 3: Die Mail enthält SPAM, wir können den SPAM-Score beinflussen und z.B. um 2 erhöhen (der Startwert ist dann 2)
#echo SCORE: 2
# 4: Die Mail soll auf keinen Fall als SPAM erkannt werden, wir setzen den SCORE auf einen negativen Wert:<br>
#echo SCORE: -10
# Dateinhalte (=Mail) einlesen
#queue_filecontent=$(< $queue_file)
queue_filecontent=$(cat $queue_file)
# Variable die ggf. am Ende dafür sorgt das "OK" ausgegeben wird
bNoSPAM=true
# Test auf WHITE(-listing)
for strWHITE in ${filterArrayWHITE[@]}; do
# echo $strSPAM
if [[ "$queue_filecontent" =~ $strWHITE ]]; then
bNoSPAM=false
#echo "/usr/bin/pmg-custom-check SCORE: ${spamScoreWHITE}" 1>&2
echo -e "\e[31m/usr/bin/pmg-custom-check SCORE: ${spamScoreWHITE}\e[0m" 1>&2
echo "SCORE: ${spamScoreWHITE}"
break
fi
done
# Test auf SPAM:
for strSPAM in ${filterArraySPAM[@]}; do
# echo $strSPAM
if [[ "$queue_filecontent" =~ $strSPAM ]]; then
bNoSPAM=false
#echo "/usr/bin/pmg-custom-check SCORE: ${spamScoreSPAM}" 1>&2
echo -e "\e[31m/usr/bin/pmg-custom-check SCORE: ${spamScoreSPAM}\e[0m" 1>&2
echo "SCORE: ${spamScoreSPAM}"
break
fi
done
if $bNoSPAM; then
#echo "/usr/bin/pmg-custom-check OK" 1>&2
echo -e "\e[31m/usr/bin/pmg-custom-check OK\e[0m" 1>&2
echo "OK"
fi
exit 0
Und das Skript ausführbar machen:
chmod +x /usr/local/bin/pmg-custom-check
Im Skript könnte Ihr zwei Dinge einstellen:
# Nach SPAM
filterArraySPAM=("apotheke.html" "test1234567890")
# Spam Score der gesetzt werden soll wenn es einen SPAM Treffer gibt
spamScoreSPAM=4
Da hinterlegt Ihr die Liste der Wörter, Sätze, Zeichenfolgen die als SPAM gekennzeichnet werden soll und entscheidet welche Punktzahl die Mail als Spam bekommt.
In diesem Fall hier die 4. Die Mail wird trotzdem weiter verarbeitet, also alles was im Bereich Mail Filter aktiviert ist, greift zusätzlich.
Ggf. kommen also weitere SPAM-Punkte hinzu und die Mail wird ganz ablehnt.
# Nach Whitelisting
filterArrayWHITE=("Test Abrakadabra")
# Spam Score der gesetzt werden soll wenn es einen WHITE Treffer gibt
spamScoreWHITE=-10
Das gleiche Prinzip, nur das die Mail dann mit -10 in die weitere Verarbeitung geht. Selbst wenn dann noch Spam Punkte gesammelt werden, wird die Mail also höchstwahrscheinlich zugestellt.
Skript testen Teil 1
Dazu bräuchten wir Dateien mit Mails.
Wenn Ihr eine in der SPAM-Ansicht RAW habt, könnt mit einem eindeutigen Text danach suchen, in diesem Fall ist das die SPAM-Mail mit apotheke.html:
grep -lr "minute.eicindustria.com.br" /var/spool
Beispielausgabe:
/var/spool/pmg/spam/5A/10013F683BC2482795A
Damit können wir unser Skript nun testen
/usr/local/bin/pmg-custom-check v1 /var/spool/pmg/spam/5A/10013F683BC2482795A
Beispielausgabe:
/usr/bin/pmg-custom-check called with v1 /var/spool/pmg/spam/26/1002A068303219D3E26 v1 /usr/bin/pmg-custom-check SCORE: 4
Die erste und dritte Zeile landet später nur in /var/log/mail.log und im syslog, die anderen beiden sind die notwendige Rückgabe für das PMG.
Gegenprobe mit einer anderen Email, mit
find /var/spool/pmg/spam/ -type f -printf "%T@ %p\n" | sort -nr | cut -d\ -f2-
könnt Ihr euch alle vorhandenen SPAM-Emails anzeigen lassen.
/usr/local/bin/pmg-custom-check v1 /var/spool/pmg/spam/26/1002A068303219D3E26
Beispielausgabe:
Skript scharf schalten
Wir aktivieren das Skript:
nano /etc/pmg/pmg.conf
und im ersten Abschnitt
section: admin
hängen wir die folgenden beiden Zeilen an:
custom_check_path /usr/local/bin/pmg-custom-check
custom_check 1
Leider weis ich nicht welchen der Dienste man nun neu starten müsste bzw. wie man das erneute Einlesen der Konfiguration anstößt.
Deshalb starte ich den PMG neu:
reboot
Testmail
Im Skript oben habe ich als Filter auch test1234567890 eingebaut,
also schicke ich mir mal eine Testmail von außerhalb mit der Zeichenfolge:
Und tatata! Der Spam Score ist 4! Ab 5 hätte er die Mail gleich verworfen!
Es funktioniert also!
Im Syslog
Im Mailgateway Syslog sehen wir die Aufrufe des Skriptes:
Die komischen Sonderzeichen kommen daher das ich den Text in der Konsole unbedingt in rot haben wollte.
Version 2.x
In dieser Variante des Skriptes kann man die Filter für SPAM und Whitelisting über die Proxmox-Weboberfläche pflegen. Zudem kann man auch ganze Sätze, inklusive Leerzeichen, als Filter nutzen.
Eine Einschränkung gibt es: Das Pipe | kann nicht verwendet werden, das brauchte ich im Skript um die Werte zu trennen.
Ich würde euch empfehlen, erst die Skript Version 1.1 zu implementieren um das ganze zu testen und erst dann auf die Version 2.x umzubauen.
Filter in Proxmox Mail Gateway PMG erstellen
Damit das Skript funktioniert, müssen wir in der Weboberfläche des PMG 2 x Who Objects und einen Mail Filter anlegen:
Die erste Who Objects nennen wir
pmg-custom-check-BLACKLIST
und fügen in dieser Liste eine Regular Expression für jeden Begriff, Satz oder sonstiges was in der Email vorkommen muss um diese zu filtern:
so das wir am Ende eine Liste habe. Es sollte mindestens einen Eintrag geben! Ggf. setzt einen sinnlosen Text hinein der nicht vorkommen wird.
Meine fertige Liste sieht dann so aus:
Nun das zweite Who Objects mit der Whitelist nach dem gleichen Prinzip, der Name muss wie folgt lauten:
pmg-custom-check-WHITELIST
Fertig sieht das dann so aus (mindestens 1 Eintrag!)
Jetzt erstellen wir einen Mail Filter in welchen wir die beiden Who Objects verwenden.
Der Name muss unbedingt
pmg-custom-check
sein
Die Priority setzen wir auf <nowki>1</nowiki> so das diese ganz unten landet, Direction ist In und nein, die Rule darf nicht aktiv sein!
Ihr markiert die Regel (ganz unten!) und fügt dann rechts im Dialog über From die beiden zuvor erstellten Who Objects hinzu.
Das muss dann am Ende so aussehen:
Fertig! Ja, der Mail Filter macht keinen sinn, es geht nur darum das man die Daten per Skript auslesen kann.
Wollte Ihr später Begriffe hinzufügen oder entfernen, so packt diese einfach in die passende Liste unterhalb von Who Objects.
Custom Check Skript erstellen Version 2.x
Wir erstellen oder bearbeiten das folgende Skript:
nano /usr/local/bin/pmg-custom-check
und setzen diesen Inhalt:
#!/bin/bash
# 2025 Bernhard Linz / Bernhard@znil.de
# Webseite: https://znil.net/index.php?title=Proxmox_Mail_Gateway_PMG_Emails_auf_W%C3%B6rter_in_Nachricht_filtern_Mailbody
#
# Changelog:
# ----------
# 03.06.2025 V1.1 erste öffentliche Version / first public version
# 29.06.2025 V2 Filterwörter können über Weboberfläche hinzugefügt werden / add filter words at web management
#
# Setup:
# Spam Score der gesetzt werden soll wenn es einen SPAM Treffer gibt
spamScoreBLACKLIST=10
# Spam Score der gesetzt werden soll wenn es einen WHITE Treffer gibt (negative Werte Whitelisten)
spamScoreWHITELIST=-10
# In der PMG Weboberfläche unter "Mail Filter" => "Who Objects" zwei Objekte anlegen mit den Namen
# pmg-custom-check-BLACKLIST
# pmg-custom-check-WHITELIST
# und die jeweiligen Begriffe/Sätze etc als "Regular Expression" hinterlegen (es aber trotzdem nur ein Text in Text vergleich gemacht)
# Danach unter "Mail Filter" eine neue "Rule" anlegen:
# Name : pmg-custom-check
# Priority : 1
# Direction: In
# Active : deaktiviert!
# In der Rule rechts über From die beiden Listen "pmg-custom-check-BLACKLIST" und "pmg-custom-check-WHITELIST" hinzufügen
# sonst nichts! keine Action, Regel nicht aktivieren! Eintrag dient nur dem Auslesen!
# Black- und Whitelist aus den RULES einlesen:
# Alle Regeln einlesen per pmgdb Befehl (Ergibt einen großen String, kein Array)
rulesdump=$(/usr/bin/pmgdb dump)
# In Array Zeile für Zeilenweise speichern
array_rulesdump=()
while IFS= read -r line; do array_rulesdump+=("$line"); done <<< "$rulesdump"
# Leere Arrays für die beiden Listen anlegen
filterArrayBLACKLIST=()
filterArrayWHITELIST=()
# Prüfung ob Black- und Whitelist gefunden wurden
boolFoundBlacklist="false"
boolFoundWhitelist="false"
# Auswerten des array_rulesdump:
for (( i=0; i < ${#array_rulesdump[*]}; i++ )); do
# Suche nach Rule (aka Mail Filter) mit pmg-custom-check im Namen
if [ "${array_rulesdump[$i]:0:4}" = "RULE" ]; then
# Zeile fängt mit RULE an
if [[ "${array_rulesdump[$i]}" =~ "pmg-custom-check" ]]; then
# und enthält die Zeichenfolge pmg-custom-check
#echo "${array_rulesdump[$i]}"
# den Abschnitt "FROM ... pmg-custom-check-BLACKLIST" oder "FROM ... pmg-custom-check-WHITELIST" finden, wir fangen ab aktueller Position an
for (( j=$i; j < ${#array_rulesdump[*]}; j++ )); do
if [ "${array_rulesdump[$j]:0:6}" = " FROM" ]; then
# Zeile fängt mit __FROM an
# Test auf BLACKLIST:
if [[ "${array_rulesdump[$j]}" =~ "BLACKLIST" ]]; then
# und enthält die Zeichenfolge BLACKLIST!
# aktuelle Zeile überspringen:
j=$((j + 1))
# Es müssten Zeilen folgen die mit OBJECT beginnen
for (( m=$j; m < ${#array_rulesdump[*]}; m++ )); do
if [ "${array_rulesdump[$m]:0:10}" = " OBJECT" ]; then
#echo "${array_rulesdump[$m]}"
# ": " wird durch "|" ersetzt und daran geteilt damit Doppelpunkte im Suchstring erhalten bleiben.
templine=$`echo "${array_rulesdump[$m]}" | sed 's/: /|/'`
IFS='|' read -ra filter <<< "${templine}"
#echo "${filter[1]}"
# IFS auf | damit auch Text mit Leerzeichen ins Array kommt, werden sonst einzelne Elemente
IFS='|' filterArrayBLACKLIST+=("${filter[1]}")
else
# schleife verlassen, hier kommt ein neuer Abschnitt das nächste RULE, FROM usw.
break
fi
done
boolFoundBlacklist="true"
fi
# Test auf WHITELIST:
if [[ "${array_rulesdump[$j]}" =~ "WHITELIST" ]]; then
# und enthält die Zeichenfolge WHITELIST!
# Es müssten Zeilen folgen die mit OBJECT beginnen
#echo "${array_rulesdump[$j]}"
j=$((j + 1))
for (( m=$j; m < ${#array_rulesdump[*]}; m++ )); do
if [ "${array_rulesdump[$m]:0:10}" = " OBJECT" ]; then
#echo "${array_rulesdump[$m]}"
# ": " wird durch "|" ersetzt und daran geteilt damit Doppelpunkte im Suchstring erhalten bleiben.
templine=$`echo "${array_rulesdump[$m]}" | sed 's/: /|/'`
IFS='|' read -ra filter <<< "${templine}"
#echo "${filter[1]}"
# IFS auf | damit auch Text mit Leerzeichen ins Array kommt, werden sonst einzelne Elemente
IFS='|' filterArrayWHITELIST+=("${filter[1]}")
else
# schleife verlassen, hier kommt das nächste Object oder Rule
break
fi
done
boolFoundWhitelist="true"
fi
fi
if [ "$boolFoundBlacklist" = "true" ] && [ "$filterArrayWHITELIST" = "true" ]; then
# Alles gefunden, Schleife abbrechen!
break
fi
done
fi
fi
done
# Prüfung ob wir Filterlisten haben, wenn nicht abbrechen
if [ "$boolFoundBlacklist" = "false" ]; then
echo "no Blacklist found"
fi
if [ "$boolFoundWhitelist" = "false" ]; then
echo "no Whitelist found"
fi
if [ "$boolFoundBlacklist" = "false" ] && [ "$filterArrayWHITELIST" = "false" ]; then
exit 1
fi
###############################################################################
# schreibt nach STDERR durch 1>&2, das landet dann nur im Logfile /var/log/mail.log
# und wird nicht als Rückgabe des Skriptes interpretiert
echo "$0 called with $*" 1>&2
#echo -e "\e[31m/usr/bin/pmg-custom-check called with $*\e[0m" 1>&2
# Fehler wenn nicht 2 Argumente
if [ "$#" -ne 2 ]; then
echo "usage: $0 APIVERSION QUEUEFILENAME" 1>&2
echo "---------------------------------------------------------------" 1>&2
echo "BLACKLIST:" 1>&2
echo "----------" 1>&2
for strBLACK in ${filterArrayBLACKLIST[@]}; do
echo $strBLACK 1>&2
done
echo " "
echo "WHITELIST:" 1>&2
echo "----------" 1>&2
for strWHITE in ${filterArrayWHITELIST[@]}; do
echo $strWHITE 1>&2
done
echo "---------------------------------------------------------------" 1>&2
exit 1
fi
# Argument $1 ist die API-Version
apiver="$1"
# Argument $2 ist die Datei mit der Email
queue_file="$2"
# shift würde das Argument $2 zu $1 verschieben
#shift
if [ "$apiver" != "v1" ]; then
echo "$0 wrong APIVERSION: $apiver" 1>&2
exit 2
fi
# Das Skirpt muss auf STDOUT zwei Ausgaben machen:
# Die API-Version, hier also v1
echo "v1"
# Und eine(!) der folgenden 3 Ausgaben:
# 1: Es ist alles ok:
#echo "OK"
# 2: Die Mail enthält einen Virus
#echo "VIRUS: <virusdescription>"
# 3: Die Mail enthält SPAM, wir können den SPAM-Score beinflussen und z.B. um 2 erhöhen (der Startwert ist dann 2)
#echo SCORE: 2
# 4: Die Mail soll auf keinen Fall als SPAM erkannt werden, wir setzen den SCORE auf einen negativen Wert:<br>
#echo SCORE: -10
# Dateinhalte (=Mail) einlesen
queue_filecontent=$(cat $queue_file)
# Variable die ggf. am Ende dafür sorgt das "OK" ausgegeben wird falls es keinen SCORE: oder VIRUS: gibt
bNoBlackOrWhite=true
# Test auf WHITE(-listing)
for strWHITE in ${filterArrayWHITELIST[@]}; do
# echo $strBLACK
if [[ "$queue_filecontent" =~ $strWHITE ]]; then
bNoBlackOrWhite=false
echo "$0 SCORE: ${spamScoreWHITELIST}" 1>&2
#echo -e "\e[31m/usr/bin/pmg-custom-check SCORE: ${spamScoreWHITELIST}\e[0m" 1>&2
echo "SCORE: ${spamScoreWHITELIST}"
break
fi
done
# Test auf BLACK(-listing):
for strBLACK in ${filterArrayBLACKLIST[@]}; do
# echo $strBLACK
if [[ "$queue_filecontent" =~ $strBLACK ]]; then
bNoBlackOrWhite=false
echo "$0 SCORE: ${spamScoreBLACKLIST}" 1>&2
#echo -e "\e[31m/usr/bin/pmg-custom-check SCORE: ${spamScoreBLACKLIST}\e[0m" 1>&2
echo "SCORE: ${spamScoreBLACKLIST}"
break
fi
done
if $bNoBlackOrWhite; then
echo "$0 OK" 1>&2
#echo -e "\e[31m/usr/bin/pmg-custom-check OK\e[0m" 1>&2
echo "OK"
fi
exit 0
Nun das Skript ausführbar machen:
chmod +x /usr/local/bin/pmg-custom-check
Skript V2 testen
Ihr testet das Skript indem Ihr es einfach aufruft, ohne Parameter:
/usr/local/bin/pmg-custom-check
Die Ausgabe sollte die beiden Listen anzeigen:
/usr/local/bin/pmg-custom-check called with usage: /usr/local/bin/pmg-custom-check APIVERSION QUEUEFILENAME --------------------------------------------------------------- BLACKLIST: ---------- ://apotheke. Test mit Leerzeichen apotheke.html pillen-bestellung-rezeptfrei superapotheke WHITELIST: ---------- Test Abrakadabra ---------------------------------------------------------------
Ggf. Scharf schalten
Falls noch nicht im PMG aktiviert, macht es wie bei V1 beschrieben: #Skript scharf schalten
ToDo
Folgendes möchte ich bei Gelegenheit noch in das Skript einbauen:
- Echte RegEx-Abfrage statt nur String in String
Quellen
- https://forum.proxmox.com/threads/mailbody-scan.136637/
- https://pmg.proxmox.com/pmg-docs/pmg-admin-guide.html#_custom_spamassassin_configuration
