Aktionen

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

Es schadet nicht, erst die Version 1.1 zu implementieren, um festzustellen ob auch alles wie gewünscht funktioniert, und dann auf Version 2.x umzustellen

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




Kommentare

Loading comments...