###   .procmailrc für "NiX Spam", iX 11/2003  ###
###   ftp.ix.de/pub/ix/ix_listings/2003/11    ###
###   /nixspam.procmailrc                     ###
###   Autor: Bert Ungerer <un@ix.de>          ###

# 2003-10-10:
# Stark überarbeitete Version des in iX 05/2003
# vorgestellten Filters 

# Der vorliegende Spamfilter testet besonders
# die Received:-Header-Zeilen intensiv.
# Das funktioniert wahrscheinlich nicht in jeder 
# Umgebung ohne Modifikation, da es je nach MTA
# und je nach Konfiguration Abweichungen gibt. 
# Bitte nutzen Sie das Leserforum des 
# zugrundeliegenden Artikels zur Klaerung
# von Fragen:
# http://www.ix.de/artikel/2003/11/123/

#################################
### persönliche Einstellungen ###
#################################
MAILDIR=$HOME/.procmail
LOGFILE=log
# Zu personalisierende Daten:
MY_MAILHOST=imap.meinedomain.de
# Beispiel mit Umlaut:
MY_FIRSTNAME="(Jürgen|J=FCrgen|J.e?rgen)"
# Ohne Umlaut ist es einfacher:
MY_LASTNAME=Musteruser
# Aliases für Spam-Erkennungsrezept:
MY_ALIASES="(\<$LOGNAME\>|juergen\.?musteruser)"
# in diesem IMAP-Folder landen "unsichere" Mails:
MY_SPAMFOLDER=Spam
# Liste unerwünschter Zeichensätze (fernöstlich, kyrillisch):
CHARSET="=.?.?.?(Big5|euc-|GB2312|shift_|iso-2022-jp|ks_c_)"
# Listen "guter" Domains und Subjects:
DOMAIN_OK="(heise|ix)"
SUBJECT_OK="(cebit|onferen|ongress|einlad|messe|veranst)"
# Mails ab dieser Größe weniger genau untersuchen:
XXL_SIZE=250000
# Falls Spam-Archivierung gewünscht:
BACKUP=yes

##############################################
### Möglicher Beginn einer INCLUDERC-Datei ###
##############################################

# zentrale, unbedingt anzupassende Deklarationen

# eigener MTA aus Sicht des Internet:
MY_MTA="(relay\.heise\.de|193\.99\.145\.50)"
MY_DOMAIN="(@|\.)(heise|ix)\.de"

# Accounts, die garantiert keine erwünschte Mail bekommen:
SPAMTRAPS="(niemail@|neverused@)"

# Gemeinsames Verzeichnis für alle Anwender:
NIXDIR="/net/homeserver/home/nixspam"

# Die folgenden Dateien müssen für alle Anwender 
# schreib- und lesbar sein.

#	Archivierung "eindeutiger" Spam-Mails:
SPAMBACKUP="spam-mail-backup"
#	Hashes aller Mails, die nicht sicher Spam sind:
MD5CACHE="$NIXDIR/md5cache"
#	Hashes aller eindeutigen Spam-Mails:
SPAMCACHE="$NIXDIR/spamcache"
#	Liste aller Spam-Hash-Treffer:
CACHEMATCHES="$NIXDIR/cachematches"
#	Spam einliefernde Gateways: 
BLACKLIST="$NIXDIR/blacklist"
#	Liste aller Blacklist-Treffer:
BLACKMATCHES="$NIXDIR/blackmatches"
#	Ham einliefernde Gateways:
WHITELIST="$NIXDIR/whitelist"
#	Liste aller Whitelist-Treffer:
WHITEMATCHES="$NIXDIR/whitematches"
#	Spamfilter-Aktionen im Überblick:
STATS="$NIXDIR/stats"

# STATS ist ein Kandidat für logrotate.
# Per Cron-Job sollten zudem folgende Ersetzungen
# stattfinden, damit die Dateien nicht ausufern:
# z. B. dreimal pro Woche:
# SPAMCACHE <- CACHEMATCHES <- /dev/null
# BLACKLIST <- BLACKMATCHES <- /dev/null
# MD5CACHE <- /dev/null
# z. B. monatlich oder seltener:
# WHITELIST <- WHITEMATCHES <- /dev/null

### Ende des anzupassenden Deklarationsteils ###

# Zeilenumbruch (Line Feed):
LF="
"

# Prüfsumme von gar nichts oder einer Leerzeile (hier für MD5)
EMPTYHASH="^^(d41d8cd98f00b204e9800998ecf8427e|68b329da9893e34099c7d8ad5cb9c940)^^"

# Envelope-To und Local Part ermitteln:
:0
* ^Envelope-to: \/.+@.+
  { CHECK="Envelope-To: $MATCH
$CHECK" 
    :0
    { ENVELOPE_TO=$MATCH }
  }
:0 A
* ^Envelope-to: \/[^@]+
  { ENV_TO=$MATCH }

:0
* $ ! ^Received.*$MY_MTA
{ CHECK="HEADER_TESTS_SKIPPED
-OK-
-OK-HOST (interne E-Mail)
$CHECK" }

:0 E
* $ ^Resent-Message-Id:.*$MY_DOMAIN
{ CHECK="HEADER_TESTS_SKIPPED
-OK-
-OK-HOST (neu gesendete E-Mail)
$CHECK" }

# Auch inoffizielle Verteileradresse durchlassen:
:0 E
* ^((Envelope-)?To|CC):.*\/\<geheimer_verteiler@
{ CHECK="HEADER_TESTS_SKIPPED
-OK-
-OK-VERTEILER: $MATCH
$CHECK" } 

# Black-/Whitelist-Check
:0E
# Nicht wenn von Freemailern weitergeleitet:
* ! (^X-WEBDE-FORWARD|by mxng.*kundenserver)
{
  # Received-Zeile mit dem Absender-Gateway extrahieren:
  :0
  * $ ^Received: from \/.*by $MY_MTA
    { RECEIVED=$MATCH }
  # IP-Nummer in Black- oder Whitelist?
  :0
  * RECEIVED ?? .*[[(]\/[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+
  { EXTERNALMTA="$MATCH"
    :0 w
    # nur die ersten drei Adress-Bytes checken:
    * MATCH ?? ()\/.+\.
    * ? fgrep -s -- "$MATCH" $WHITELIST
    { CHECK="HEADER_TESTS_SKIPPED
-OK-WHITELIST_MATCH: $MATCH
$CHECK" }
    :0 Ew
    * ? fgrep -s -- "$MATCH" $BLACKLIST
    { CHECK="NiX-
NiX-
NiX-BLACKLIST_MATCH: $MATCH
$CHECK" }
  }
} # Ende Black-/Whitelist-Check
:0E
{ CHECK="BLACK-/WHITELIST CHECK SKIPPED
$CHECK" }

# Prüfsummen bilden:
:0 D
* ! CHECK ?? ^-OK-(HOST|VERTEILER)
{
  # Verdächtig: E-Mail ohne jeden Klartext,
  # entsprechender Hinweis schon im Header:
  :0
  * ^Content-.*base64
  { CHECK="NiX-
NiX-BASE64ONLY
$CHECK" }
  :0E
  {
    # Erste Checksum: Nur Returns und Leerraum belassen:
    :0 bw
    md5hash=|tr -s '[:space:]' \
            |tr -d '[:graph:]äöüéÄÖÜÉß' \
            |md5sum
    # Hashsumme bereits in der Datei?
    :0 ADw
    * ! CHECK ?? WHITELIST_
    * $ ! md5hash ?? $EMPTYHASH
    * ? fgrep -s $md5hash $SPAMCACHE
    { CHECK="NiX-
NiX-
NiX-
NiX-
NiX-
NiX-
NiX-
NiX-CHECKSUM1_SPAM: $md5hash
HEADER_TESTS_SKIPPED
$CHECK" }
    :0 Ew
    * $ ! md5hash ?? $EMPTYHASH
    * ? fgrep -s $md5hash $MD5CACHE
    { CHECK="NiX-
NiX-CHECKSUM1: $md5hash
$CHECK" }
    :0E
    { :0 bw
      # Zweite Checksum: Ziffern, Buchstaben, '=' und Returns entfernen:
      md5hash2=|tr -d '[:cntrl:][:alnum:]äöüéÄÖÜÉß=' \
               |tr -s '[:print:]' \
               |md5sum
      # Hashsumme bereits in der Datei?
      :0 ADw
      * ! CHECK ?? ^NiX-CHECKSUM
      * ! CHECK ?? WHITELIST_
      * $ ! md5hash2 ?? $EMPTYHASH
      * ? fgrep -s $md5hash2 $SPAMCACHE
      { CHECK="NiX-
NiX-
NiX-
NiX-
NiX-
NiX-
NiX-
NiX-CHECKSUM2_SPAM: $md5hash2
HEADER_TESTS_SKIPPED
$CHECK" }
      :0 EDw
      * ! CHECK ?? ^NiX-CHECKSUM
      * $ ! md5hash2 ?? $EMPTYHASH
      * ? fgrep -s $md5hash2 $MD5CACHE
      { CHECK="NiX-
NiX-CHECKSUM2: $md5hash2
$CHECK" }
    }
    :0
    * $ md5hash ?? $EMPTYHASH
    { md5hash="" }
    :0
    * $ ! md5hash2 ?? $EMPTYHASH
    { md5hash="$md5hash	$md5hash2" }
  }
  :0D
  # Normale Hash-Summe bei Base64- oder anderen strukturlosen Mails:
  * ! md5hash ?? .
  * B ?? ....
  {
    :0 bw
    md5hash=|md5sum
    :0 ADw
    * ! CHECK ?? WHITELIST_
    * ? fgrep -s $md5hash $SPAMCACHE
    { CHECK="NiX-
NiX-
NiX-
NiX-
NiX-
NiX-
NiX-
NiX-CHECKSUM_SPAM: $md5hash
HEADER_TESTS_SKIPPED
$CHECK" }
    :0 Ew
    * ? fgrep -s $md5hash $MD5CACHE
    { CHECK="NiX-
NiX-CHECKSUM: $md5hash
$CHECK" }
  }
# (Ende des Prüfsummen-Teils)
}

# alle anderen Mails genauer unter die Lupe nehmen:
:0 D
* ! CHECK ?? ^HEADER_TESTS_SKIPPED$
{
:0
* $ ! ^((Resent-)?To|CC):.*($MY_ALIASES|$LOGNAME)$MY_DOMAIN
{ CHECK="NiX-BCC
$CHECK" }

# stark verdächtige Bestandteile von Absender-Adressen:
:0 E
* ^\/(From|Received|Reply).*(deals|jackpot|mortgage|penis|netscap[^e]|\
  auction|price|offers|sales|promo|bargain|porn|casino|vendi|j-connect|\
  (pre|sub)scri|girls|cheap|free[^nm]|(per|sub)mis|special|[1-9][1-9][1-9]\.co)
{ CHECK="NiX-
NiX-
NiX-SPAM_ADDRESS: $MATCH
$CHECK" } 

# diese Headerzeilen kommen fast nur in Spam vor:
:0
* ^\/(X-)?(XRef|(Anti)?Abuse|listserver)(: |-).*
{ CHECK="NiX-
NiX-
NiX-SPAM_HEADER: $MATCH
$CHECK" } 

# Keine externen Received-Zeilen (Direktspam, gerne
# auch Viren- und Wurm-Output):
:0
* 1^0
* -1^1 ^Received:
* $ 1^1 ^Received:\/.*by [.-a-z0-9]*($MY_DOMAIN|$MY_MTA)
{ CHECK="NiX-
NiX-DIRECT_DELIVERY
$CHECK" }

# Vorübergehend angemietete, verwurmte oder trojanisierte Müll-Gateways:
:0
* MATCH ?? ([0-9][0-9].[0-9][0-9]|[^a-z](.?dsl|dial(in|up)?|\
   pool|dhcp|dyn|client|user|ppp?o?e?|wll|ipt\.aol|seed|\
   charter|cust|bluerocketonline)[^a-z]).*\([1-9]
* MATCH ?? ^^\/[^(]+
{ CHECK="NiX-
NiX-
NiX-SUSPECT_GATEWAY: $MATCH
$CHECK" }

:0 B
* ^^($| |	|=20)*.*[0-9a-z].*($| |	|=20)*^^
{ CHECK="NiX-
NiX-ONE_LINE_BODY
$CHECK" }
:0 EB
* ^^($| |	|=20)*^^
{ CHECK="NiX-
NiX-EMPTY_BODY
$CHECK" }
# Große Mails sind selten Spam:
:0 EB
* $ > $XXL_SIZE
{ CHECK="-OK-XXL
$CHECK" }

# MTA-Hostnamen ohne Punkt sind suspekt und sollten noch 
# anderswo im Header auftauchen (z.B. Firmen-/Anwendername):
:0
#* (claiming to be \"|(he|eh)lo(=| ))\/[^.]+[")]
* $ ^Received:.*(claiming to be \"|(he|eh)lo(=| ))\/[^.]+[")].*by $MY_MTA
* MATCH ?? .*\/[^")]+
* $ ! ^[^R  ].*$MATCH
{ CHECK="NiX-
NiX-CLAIMING_SUSPECT_HOSTNAME: $MATCH
$CHECK" } 
# Noch schlimmer - MTAs, die es nicht gibt:
:0 E
* ^Received:.*(claiming to be \"|(he|eh)lo(=| ))\/(aol|gmx)\.[^")]+
{ CHECK="NiX-
NiX-
NiX-CLAIMING_SUSPECT_HOSTNAME: $MATCH
$CHECK" } 

# Absender hat angeblich unsere Gateway-Adresse:
:0
* $ ^Received:.*\/(claiming to be \"|(he|eh)lo(=| ))$MY_MTA(\"|\))
{ CHECK="NiX-
NiX-
NiX-
NiX-
NiX-GATEWAY_FAKE: $MATCH
$CHECK" } 

# Adresse aus Massen-Domain:
:0
* ^((X-)?Sender|Reply|From).*@\/(angelfire|aol|excite|earthlink|\
  lycos|tripod|msn|yahoo|netscap.|uni|juno|usa|(s|ch)ina|iname|\
  bluewin|chat.*|\
  [.-_a-z0-9]*(mail[-_a-z0-9]*|63))(\.com?)?\.[a-z]+[^-._a-z0-9]
{ CHECK="NiX-
NiX-SPAM_DOMAIN: $MATCH
$CHECK" }

# Adresse mit Ziffern und Buchstaben: 
:0
* ^\/(Reply|From).*([a-z][0-9]|[0-9][a-z])[^ =<>(]*@
{ CHECK="NiX-
NiX-NUMER1C_ACCOUNT: $MATCH
$CHECK" } 

# Adresse mit superlangem Account ohne Punkt:
:0
* ^((X-)?Sender|Reply|From).*\/<..................[^. "]+@
* ! MATCH ?? [.]
{ CHECK="NiX-
NiX-
NiX-LONG_ACCOUNT: $MATCH
$CHECK" }

# Adresse mit ZeiChEnsalAt: 
:0 D
* ^\/((X-)?Sender|Reply|From).*[a-z][A-Z][^ =<>(]*[a-z][A-Z][^ =<>(]*@
{ CHECK="NiX-
NiX-sPaM_ACCOUNT: $MATCH
$CHECK" } 

# Bounce-Adresse:
:0
* ^\/((X-)?Sender|Reply|From).*(delete|owner|bounce|\
  no(reply|list)|error|return|\<list)[^ =<>(]*@
{ CHECK="NiX-SPAM_ACCOUNT: $MATCH
$CHECK" }

# Pseudo-Antwort: 
:0
* ^\/Subject: *(re|aw)\>
* ! ^(Reference|In-Reply)
# Der kann das nicht:
* ! ^X-Mailer.*Internet Mail Service
{ CHECK="NiX-REPLY_WITHOUT_REFERENCE: $MATCH
$CHECK" }
# ... und das Gegenteil davon:
:0 E
* ^\/(Reference|In-Reply).*
* ! ^\/Subject:.*\<(warn.ng|autoreply|re|aw):
{ CHECK="NiX-REFERENCE_WITHOUT_REPLY: $MATCH
$CHECK" }

# Pseudo-Referenz mit Zeichensalat:
:0
* ^\/(References|In-Reply-To): <[^>]+@.*([a-z][0-9]|[0-9][a-z])[^.>]*>
{ CHECK="NiX-BAD_REFERENCE: $MATCH
$CHECK" }
# Ordentliche Rückbezüge dagegen sind Spam-untypisch:
:0 E
* ! CHECK ?? ^NiX-REFERENCE_WITHOUT_REPLY
* ^\/(References|In-Reply-To): <[^>]+@[^>]+>
* B ?? ^>
{ CHECK="-OK-REFERENCE: $MATCH
$CHECK" }

# Message-ID nicht vom Absender:
:0
# vom MTA generierte (Fehler-)Mails nicht testen:
* ^^From .+@.+
* $ ^Message-ID:.*$MY_MTA
{ CHECK="NiX-
NiX-
NiX-MSG_ID_NOT_FROM_SENDER
$CHECK" }
# externe Message-IDs untersuchen und sichern:
:0 E
* ^Message-ID:\/.+
{ MSGID=$MATCH
  # kommt das Ende der ID sonst im Header vor?
  :0
  * MSGID ?? ()@\/[^>]+
  # Beliebte Fehlkonfigurationen ausnehmen:
  * ! MATCH ?? (192|10)\.(168|10)\.
  * MATCH ?? ()\/\.?[^.]+^^
  # Beliebte Fehlkonfigurationen ausnehmen:
  * ! MATCH ?? (local|\])
  * $ ! ^[^M].*$MATCH\>
  { CHECK="NiX-MSG_ID_SUSPECT: $MSGID
$CHECK"
    :0
    # Test auf besonders krasse Fakes mit Punkt, wenigstens
    # die "TLD" sollte irgendwo anders im Header auftauchen:
    * MATCH ?? ()\.
    * ! MATCH ?? ()\]
    { CHECK="NiX-
NiX-MSG_ID_DOMAIN_FAKE: $MATCH
$CHECK" }
  }
  :0 E
  * ! MSGID ?? [^<]+@[^>]+
  { CHECK="NiX-
NiX-MSG_ID_INVALID: $MSGID
$CHECK" }

  # allgemeine Suche nach nachträglich eingefügten IDs:
  :0
  * ^Message-ID:\/(.|$)+^Received:
  { CHECK="NiX-MSG_ID_NOT_FROM_SENDER
$CHECK" }

}

# Spam-typische Mailer-Einträge:
:0
* ^X-Mailer: \/(.*(bulk|egroup).*| *[^/-. ]*)$
{ CHECK="NiX-X_MAILER_SUSPECT: $MATCH$CHECK" }

# MIME-Attachment(s) abklappern:
:0
* ^Content.*multipart
{
  # Multipart-Mails mit nur einem MIME-Attachment,
  # ausschließlich Base64-kodierten oder gar einem leeren:
  :0
  * ! CHECK ?? ^-OK-XXL
  # verschachtelte Anhänge nicht untersuchen:
  * ! B ?? boundary="
  {
    # Gibts ein nicht-leeres, nicht base64-kodiertes Attachment?
    # Sonst Spam-Verdacht:
    :0 B
    * ^Content-Transfer-Encoding: base64
    * 1^0
    * -1^1 ^Content-type:
    * 1^1 ^Content-Transfer-Encoding: ([^b]|bi).*$$+--
    * 1^1 ^Content-Transfer-Encoding: base64
    { 
      CHECK="NiX-
NiX-BASE64ONLY
$CHECK"
      :0 B
      * ^Content-Type: text/html
      { CHECK="NiX-
NiX-
NiX-HTML_IN_BASE64ONLY
$CHECK" }
    }
    :0 B
    # Leere Attachments kommen normalerweise nur bei 
    # Weiterleitungen vor:
    * ^Content-.*$$$+--.*$+(Content-|^^)
    * ! ^Content-Type: message
    { CHECK="NiX-EMPTY_MIME_ATTACHMENT
$CHECK" }
    :0 B
    # Test, ob wirklich mehrere nicht-leere Parts vorhanden sind,
    # leeres Attachment daher auf Score draufschlagen:
    * 1^0 CHECK ?? ^NiX-EMPTY_MIME_ATTACHMENT
    * 2^0
    * -1^1 ^--.*$Content-
    { 
      CHECK="NiX-MULTIPART_FAKE
$CHECK" 
      :0 B
      * ^Content-Type: text/html
      { CHECK="NiX-
NiX-
NiX-HTML_ONLY_IN_MULTIPART_FAKE
$CHECK" }
    }
  }
}

# Verdächtig: reines HTML,
# entsprechender Hinweis schon im Header:
:0
* ^Content-.*html
{ 
  CHECK="NiX-BODY_HTML_ONLY
$CHECK" 
  :0
  * CHECK ?? ^NiX-BASE64ONLY
  { CHECK="NiX-
NiX-
NiX-HTML_IN_BASE64ONLY
$CHECK" }
  :0 E
  * ^Content-Transfer-Encoding: quoted-printable
  { CHECK="NiX-HTML_IN_QUOTED_PRINTABLE
$CHECK" }
}

# Unwillkommene Zeichensätze:
:0
* $ .*\/$CHARSET
 { CHECK="NiX-
NiX-
NiX-
NiX-CHARSET_IN_HEADER: $MATCH
$CHECK" }
# oder doch ein willkommener?
:0 E
* =\?\/iso-8859-1\?Q.*=(C4|E4|D6|F6|DC|FC|DF)
{ CHECK="-OK-UMLAUT_IN_HEADER: $MATCH
$CHECK" }
:0 EB
* ! CHECK ?? ^(-OK-XXL|NiX-BASE64ONLY)
* $ .*\/$CHARSET
 { CHECK="NiX-
NiX-
NiX-CHARSET_IN_BODY: $MATCH
$CHECK" }

# Base64-Kodierung im Header:
:0
* ^Subject: \/=\?.*\?B\?.*\?=$
{ CHECK="NiX-BASE64_SUBJECT: $MATCH$CHECK" }

# kein einziger Kleinbuchstabe im bzw. kein Betreff:
:0 D
* ! ^[Ss][Uu][Bb][Jj][Ee][Cc][Tt]:.*[a-z]
{ CHECK="NiX-SUBJECT_CAPSLOCK_OR_EMPTY
$CHECK" }

# This is "your" Spam:
:0
* $ ^Subject:\/.*(y(o|0)u|(\<|_)adv(\>|_)|$MY_DOMAIN)
{ CHECK="NiX-
NiX-SPAM_SUBJECT:$MATCH
$CHECK" }

# Betreff mit Zeichensalat, Massen an Leerzeichen etc.:
:0
* ^Subject: .*\/\<\<\<\<\<\<\<\<\<\<.*
# ISO-Geraffel darf durch:
* ! MATCH ?? =\?iso
{ CHECK="NiX-
NiX-SUBJECT_GARBAGE: $MATCH
$CHECK" }

# Spam mit persönlicher Note (eigene Adresse im Subject):
:0
* -1^0 ! ENV_TO ?? .
* $ 1^0 ^Subject.*\/\<$ENV_TO\>
* $ 2^0 ^Subject.*\/$MY_ALIASES
{ CHECK="NiX-
NiX-SUBJECT_WITH_MY_ACCOUNT: $MATCH
$CHECK" }

# PDF-Anhänge sind meist vertrauenswürdig:
:0 HB
* ^--.*$.*Content.*$?.*name=\/".*(\.|=2E)pdf([?]=)?"
{ CHECK="-OK-
-OK-PDF_ATTACHMENT: $MATCH
$CHECK" }

# So sehen manche "Nicht-Spam-Mails" aus,
# die sonst sehr verdächtig sind:
:0
* ^X-MS-Has-Attach:
* ^X-MS-TNEF-Correlator:
{ CHECK="-OK-
-OK-MS_MAILER
$CHECK" }

# Weiteres Zugeständnis an manche PR-Aussender:
:0
* ^\/Received:.*(antivir|avmailgate|sweep|scan|viru).*
* $ ! MATCH ?? $MY_DOMAIN
{ CHECK="-OK-ANTIVIRUS: $MATCH
$CHECK" }

# Diese Headerzeilen hats noch nicht in Spam gegeben (oder?):
:0
* ^\/(Resent-|X-Mms)
{ CHECK="-OK-
-OK-HAM_HEADER: $MATCH
$CHECK" }

# mehrere einzelne Buchstaben:
:0
* ^Subject:.*\/[^0-9a-z][a-z][^0-9a-z]+[a-z][^0-9a-z]+[a-z][^0-9a-z]
{ CHECK="NiX-SUBJECT_SPACED: $MATCH
$CHECK" } 

# Persönliche Betreff-Whitelist auswerten:
:0
* $ ^Subject.*\/($SUBJECT_OK)
* $ ! MATCH ?? $MY_ALIASES
{ CHECK="-OK-SUBJECT: $MATCH
$CHECK" }

# Persönliche Absender-Whitelist auswerten:
:0
* $ ^From.*\/(@|\.)$DOMAIN_OK\.
{ CHECK="-OK-
-OK-SENDER: $MATCH
$CHECK" }

# Eigener Name in Adresszeile, aber nicht in Adresse selbst:
:0
* $ ^\/(CC|(Resent-)?To):.*(([^-+a-z0-9.@_]($MY_FIRSTNAME|\
    $MY_LASTNAME)[^+-a-z0-9.@_])|\
    (=\?.*($MY_FIRSTNAME|$MY_LASTNAME).*\?=))
{ CHECK="-OK-
-OK-
-OK-TO_WITH_NAME: $MATCH
$CHECK" }

# Fehlender Realname:
:0
* ! ^From:.*((.+ )+(.+ )+.+|=\?.*_.*\?=)
{ CHECK="NiX-FROM_NOREALNAME
$CHECK" }

# Verdächtig: MTA trägt sich mangels ausreichender
# Angaben selbst in die Adresse ein:
:0
* $ ^\/To.*$MY_MTA
{ CHECK="NiX-TO_CONTAINS_MTA: $MATCH
$CHECK" }

}

# Erster Versuch einer eindeutigen Einordnung:
:0 D
* 1^0 ! CHECK ?? ^NiX-
* 1^0 CHECK ?? WHITELIST_MATCH
{ RESULT="HAM" }
:0 ED
* 10^0 CHECK ?? CHECKSUM.?_SPAM
* $ 10^0 ENVELOPE_TO ?? ^^$SPAMTRAPS
* -6^0
* -2^3 CHECK ?? ^-OK-
* 1^1 CHECK ?? ^NiX-
{ RESULT="SPAM" }
:0 ED
* -5^0
* 2^3 CHECK ?? ^-OK-
* -1^1 CHECK ?? ^NiX-
{ RESULT="HAM" }
:0 ED
* CHECK ?? ^(-OK-XXL|NiX-(BASE64ONLY|EMPTY_BODY))
* -3^0
* -2^3 CHECK ?? ^-OK-
* 1^1 CHECK ?? ^NiX-
{ RESULT="MAYBESPAM" }

# Weitere Body-Untersuchung nur bei kleinen Klartext-Mails,
# die noch keinen deutlichen Ham- oder Spam-Status haben:
:0 D
* ! RESULT ?? .
* ! CHECK ?? ^(-OK-(HOST|XXL)|NiX-(BASE64ONLY|EMPTY_BODY))
{
  # Für Signatures sind Spammer zu faul (min. 1, max. 10 Zeilen):
  :0 B
  * ^--( |=20)?$\/.*$.*$?.*$?.*$?.*$?.*$?.*$?.*$?.*$?.*$?(^^|$?^--)
  * ! MATCH ?? netmail
  { CHECK="-OK-SIGNATURE: $MATCH
$CHECK" }

  # Absender-Adresse im Body?
  :0
  * ^^From .*\/[-_.a-z0-9]+@[-_.a-z0-9]+
  * $ B ?? $MATCH
  { CHECK="-OK-SENDER_ADDRESS_IN_BODY: $MATCH
$CHECK" }

  # Persönliche Anrede, nur nicht mit Punkt im Namen:
  :0 B
  * $ .*\/\<(moin|dear|h[aeiu]|(ver|ge)ehrt|liebe|g(ood|ute))\
      .*(\<$MY_FIRSTNAME[^.a-z0-9]|[ _]$MY_LASTNAME)
  { CHECK="-OK-ANREDE: $MATCH
$CHECK" }

  # "Click here" etc.:
  :0 B
  * .*\/\<(go|clic?k?|press|touch|push|check)[^.?!a-z]+(http://|<a |now|here|(on.)?th)
  * ! MATCH ?? (["'`]|=2E|=21|=3F)
  { CHECK="NiX-SPAM_KEYWORDS: $MATCH
$CHECK" }

  # "Click here" auf Deutsch:
  :0 B
  * .*\/\<(klick|verwende|folge|benutz)([^.?!]|$)+\<(http|link|url)\>
  * ! MATCH ?? (["'`]|=2E|=21|=3F)
  { CHECK="NiX-SPAM_KEYWORDS: $MATCH
$CHECK" }
  
  # Wie schon die TV-Werbung zeigte:
  :0 HB
  * .*\/\<as\<seen\>on
  * ! MATCH ?? ["'`]
  { CHECK="NiX-
NiX-
NiX-SPAM_KEYWORDS: $MATCH
$CHECK" }

  # "my name is" kommt fast nur in anonymen Mails vor:
  :0 B
  * .*\/\<my\<name\>is
  * ! MATCH ?? ["'`]
  { CHECK="NiX-
NiX-
NiX-SPAM_KEYWORDS: $MATCH
$CHECK" }

  # Ich bin Mdubi Avimbi, also spamme ich:
  :0 DB
  * .*\/\<I(\<\<?[aA]|'`)[mM]\>\>?[A-Z][a-zA-Z]
  * ! MATCH ?? ["]
  { CHECK="NiX-
NiX-SPAM_KEYWORDS: $MATCH
$CHECK" }
  
  # Auf den Empfänger mit Gebrüll:
  :0 B
  * .*\/!.*!.*!.*!
  { CHECK="NiX-SPAM_KEYWORDS: $MATCH
$CHECK" }

  # Ein paar HTML-Tests, falls HTML-Mail:
  :0 HB
  * ^Content.*/html
  {
    # viele Wörter durch Tags zerhackt:
    : 0 B
    # Erstmal Zeilen suchen, die sichtbaren Text enthalten:
    * -1^1 ^(.*(=3E|>)([^<]|=[^3])*[a-z]|[^<]*[a-z])
    # nur in jeder zweiten davon darf ein HTML-Tag
    # mitten im Wort stehen:
    * 2^1 .*\/[a-z](=3C|<)[^>]*(>|=3E)[a-z]
    { CHECK="NiX-
NiX-
NiX-
NiX-HTML_TRASH: $MATCH
$CHECK" }

    # User-Tracking mit nachgeladenen Bildern etc.:
    :0 B
    * .*(<|=3C)[a-z]([^>]|$)+(backgr|ound|src)([^>]|$)*=([^>]|$)*\/http(:|.3A)[^>"]*
    # IVW-Zähler gehen aber gerade noch durch:
    * ! MATCH ?? [^a-z]ivw
    { CHECK="NiX-
NiX-
NiX-REMOTE_CONTENT: $MATCH
$CHECK" }
  }

  # Schlanker Spam mit URL gleich zu Beginn ...
  :0 B
  * ^^($|\>|(<|=3C).*(>|=3E))*\/.*http(://|.3A.2F.2F)
  { CHECK="NiX-
NiX-FIRST_LINE_URL: $MATCH
$CHECK" }

  # ... und / oder mit URLs in jeder Zeile:
  :0 B
  * 1^1 ^.*http(://|.3A.2F.2F)
  * -1^1 ^[^=<].
  { CHECK="NiX-
NiX-URLS_ONLY
$CHECK" }

  # numerisch, hexadezimal oder mit "@" verkleidete URL:
  :0 B
  * .*\/http(://|.3A.2F.2F)([.0-9]+|[^/ >]*[@%])[^/ >]*(/|(=|%)2F)
  { CHECK="NiX-
NiX-
NiX-CRYPTIC_URL: $MATCH
$CHECK" }

  # URLs, die man besser nicht anklickt:
  :0 B
  * $ .*\/http(:|.3A)[^> ]+(casino|teen|free[^nm]|porn|m.rtgage|\
    penis|price|auction|sales|deals|promo|offers|yeah|thebest|\
    lycos|you|tripod|marketing|geocities|hgh|domains|dialer|safely|365|\
    girls|cheap|special|bargain|stuff|pharm|herb(al|power)|sluts|\
    grow|watchme|large|love|trade|j-connect|drugs|discount)
  * ! MATCH ?? (=20|=3E|itssecure)
  { CHECK="NiX-
NiX-
NiX-SPAM_LINK: $MATCH
$CHECK" }

  # Weitere URLs, die man besser nicht anklickt:
  :0 B
  * $ .*\/http(:|.3A)[^> ]*(quit|exit|\.to/|\
    click|id=|goodb|re?mo?v|(pre|sub)scr|unsub|opt.?(in|out)|\
    thank|pleas|\<stop|off.?list|club|member|join|submi|affil|\
    $MY_ALIASES|(/|(=|%)2F)[^> ]*((@|(=|%)40)|(\.|(=|%)2E)exe\>))
  * ! MATCH ?? (=20|=3E|itssecure)
  { CHECK="NiX-
NiX-
NiX-SPAM_LINK: $MATCH
$CHECK" }

  # Diverse Spam-Formulierungen: 
  :0 B
  * .*\/[^"`']your(=|20|$| |	)*(priva|domain|bank|account|credit|\
     pen|free|purch|money|own|busi|home|cable|mortg|heal|situ|web|response)
  { CHECK="NiX-
NiX-SPAM_KEYWORDS: $MATCH
$CHECK" }

  # "Dies ist kein Spam" 1: 
  :0 B
  * .*\/\<w(ir|e)\>([^.?!]|$)+\<(sen(d|t)|do)([^.?!]|$)+(unsoli|spam)
  * ! MATCH ?? (["'`]|=2E|=21|=3F)
  { CHECK="NiX-
NiX-SPAM_KEYWORDS: $MATCH
$CHECK" }

  # "Dies ist kein Spam" 2: 
  :0 B
  * .*\/\<((bill|S\.)([^.?!]|$)*1618|3113([^.?!]|$)+act)
  * ! MATCH ?? (["'`]|=2E|=21|=3F)
  { CHECK="NiX-
NiX-
NiX-SPAM_BY_LAW: $MATCH
$CHECK" }

  # "Dies ist kein Spam" 3a (leider auch in "Disclaimern"): 
  :0 B
  * .*\/\<you\>([^.?!]|$)*this([^.?!]|$)*(message|mail|letter|info)
  * ! MATCH ?? (["'`]|=2E|=21|=3F|delete|recipi|virus|sender)
  { CHECK="NiX-SPAM_KEYWORDS: $MATCH
$CHECK" }

  # "Dies ist kein Spam" 3b (leider auch in "Disclaimern"): 
  :0 B
  * .*\/\<sie\>([^.?!]|$)*dies([^.?!]|$)*(mail|letter|info)
  * ! MATCH ?? (["'`]|=2E|=21|=3F|nger|virus|sender|schen|benach)
  { CHECK="NiX-SPAM_KEYWORDS: $MATCH
$CHECK" }

  # "Dies ist kein Spam" 4: 
  :0 B
  * .*\/\<(dies|this)([^.?!]|$)+(one.?time|spam|unsoli)
  * ! MATCH ?? (["'`]|=2E|=21|=3F)
  { CHECK="NiX-
NiX-SPAM_KEYWORDS: $MATCH
$CHECK" }

  # Werden Sie Mitglied:
  :0 B
  * .*\/\<you([^.?!]|$)+\<member
  * ! MATCH ?? (["'`]|=2E|=21|=3F)
  { CHECK="NiX-
NiX-SPAM_KEYWORDS: $MATCH
$CHECK" }

  # Hier kriegen Sie Geld:
  :0 B
  * .*\/\<(get|receive)([^.?!]|$)*\<(money|pa(i|y)|\$|Dollar)
  * ! MATCH ?? (["'`]|=2E|=21|=3F)
  { CHECK="NiX-
NiX-SPAM_KEYWORDS: $MATCH
$CHECK" }

  # Sie können sich gerne abmelden 1:
  :0 B
  * .*\/\<you([^.?!]|$)+opt.?.?.?(in|out)
  * ! MATCH ?? (["'`]|=2E|=21|=3F)
  { CHECK="NiX-
NiX-SPAM_KEYWORDS: $MATCH
$CHECK" }

  # Sie können sich gerne abmelden 2:
  :0 B
  * .*\/\<(zuk|future|further)([^.?!]|$)+(inform|letter|mail|offer)
  * ! MATCH ?? (["'`]|=2E|=21|=3F)
  { CHECK="NiX-
NiX-SPAM_KEYWORDS: $MATCH
$CHECK" }

  # Sie können sich gerne abmelden 3:
  :0 B
  * .*\/\<(opt|remov)([^.?!]|$)+instruct
  * ! MATCH ?? (["'`]|=2E|=21|=3F)
  { CHECK="NiX-
NiX-SPAM_KEYWORDS: $MATCH
$CHECK" }

  # Sie können sich gerne abmelden 4:
  :0 B
  * .*\/\<honor([^.?!]|$)+remov|remov([^.?!]|$)+(http|honor|request)
  * ! MATCH ?? (["'`]|=2E|=21|=3F)
  { CHECK="NiX-
NiX-SPAM_KEYWORDS: $MATCH
$CHECK" }

  # Sie können sich gerne abmelden 5:
  :0 B
  * .*\/\<(to|you)[^.?!]*\<(unsu|remov)
  * ! MATCH ?? (["'`]|=2E|=21|=3F|virus)
  { CHECK="NiX-SPAM_KEYWORDS: $MATCH
$CHECK" }

  # Betteln um Reaktion:
  :0 B
  * .*\/\<please([^.?!a-z]|$)+(visit|send|respond|reply|(fo|a)llow)
  * ! MATCH ?? (["'`]|=2E|=21|=3F)
  { CHECK="NiX-SPAM_KEYWORDS: $MATCH
$CHECK" }

  # Danke fürs Lesen oder: Tut uns echt Leid, diese Mail:
  :0 B
  * [.?!]\/([^.?!]|$)+(thank|apolog)([^.?!]|$)+[.?!]
  * MATCH ?? this($|.)*(mail|message|letter)
  * ! MATCH ?? (["'`]|=2E|=21|=3F)
  { CHECK="NiX-
NiX-SPAM_KEYWORDS: $MATCH
$CHECK" }

  # Nur heute im Sonderangebot:
  :0 B
  * .*\/\<((save|less|only)([^.?!]|$)*\<(\$|dollar|day)|\
    \<(\$|dollar|day)([^.?!]|$)*\<(save|less|only)|\
    \<(valu|this|special|great|limited)([^.?!]|$)*\<(offer|price))
  * ! MATCH ?? (["'`]|=2E|=21|=3F)
  { CHECK="NiX-
NiX-SPAM_KEYWORDS: $MATCH
$CHECK" }

   # Bestellen Sie endlich:
  :0 B
  * .*\/\<(order([^:.?!]|$)+(here|you|now|today)|you([^:.?!]|$)*\<order)
  * ! MATCH ?? (["'`]|=2E|=21|=3F)
  { CHECK="NiX-
NiX-SPAM_KEYWORDS: $MATCH
$CHECK" }

   # Rufen Sie an:
  :0 B
  * .*\/\<(please|you)([^.?!]|$)*\<call
  * ! MATCH ?? (["'`]|=2E|=21|=3F)
  { CHECK="NiX-SPAM_KEYWORDS: $MATCH
$CHECK" }

  # 0190-Werbung:
  :0 B
  * ^\/.*(\<(0|null)[^a-z0-9]*(1|eins)[^a-z0-9]*(9|neun)[^a-z0-9]*(0|null)).*$?.*$?
  * MATCH ?? (\>|[0-9])+(=80|euro?|cent|ct|s(\>|e(c|k))|min(\>|ute))
  { CHECK="NiX-
NiX-
NiX-SPAM_KEYWORDS: $MATCH$CHECK" }

  # mehrere einzelne Zeichen:
  :0 B
  * .*\/[^0-9a-z][a-z][^0-9a-z][a-z][^0-9a-z]\
    [a-z][^0-9a-z][a-z][^0-9a-z]
  { CHECK="NiX-BODY_SPACED: $MATCH
$CHECK" }
  
  # Endgültige Einordnung in Spam / verdächtig / Ham:
  :0 D
  * -7^0
  * -2^3 CHECK ?? ^-OK-
  * 1^1 CHECK ?? ^NiX-
  { RESULT="SPAM" }
  :0 ED
  * -3^0
  * -2^2 CHECK ?? ^-OK-
  * 1^1 CHECK ?? ^NiX-
  { RESULT="MAYBESPAM" }
  :0 ED
  { RESULT="HAM" }
}
:0E
{ CHECK="BODY_TESTS_SKIPPED
$CHECK" }

# Datum und Uhrzeit extrahieren:
:0
* ()\/[^	 ]+ +[^	 ]+ +..:..:..
{ DATE=$MATCH }
  
# Reverse Lookup:
:0
* RECEIVED ?? .*\/[^[(]+
{ REVERSELOOKUP=$MATCH }

# Ausliefernden Host sichern, falls noch nicht geschehen:
:0
* ! CHECK ?? LIST_MATCH
* EXTERNALMTA ?? .
{
  :0 Dw
  * RESULT ?? ^^SPAM^^
  # nochmals checken zur Vermeidung mehrfacher Eintragungen:
  * ! ? fgrep -s -- "$EXTERNALMTA" $BLACKLIST
  { CHECK="NEW BLACKLIST ENTRY:	$EXTERNALMTA	$REVERSELOOKUP
$CHECK"
    :0 hci:
    | echo "$DATE	$EXTERNALMTA	$REVERSELOOKUP" >> $BLACKLIST
  }
  # Nur "besonders erwünschte" Mails sorgen für Whitelist-Eintrag:
  :0 EDw
  * RESULT ?? ^^HAM^^
  * 1^0
  * 3^1 CHECK ?? ^-OK-
  * -1^1 CHECK ?? ^NiX-
  # nochmals checken zur Vermeidung mehrfacher Eintragungen:
  * ! ? fgrep -s -- "$EXTERNALMTA" $WHITELIST
  { CHECK="NEW WHITELIST ENTRY:	$EXTERNALMTA	$REVERSELOOKUP
$CHECK"
    :0 hci:
    | echo "$DATE	$EXTERNALMTA	$REVERSELOOKUP" >> $WHITELIST
  }
}

# Eintrag in bereinigte Blacklist nur bei genauem IP-Treffer:
:0 EDhciw:
* RESULT ?? ^^SPAM^^
* EXTERNALMTA ?? .
* ? fgrep -s -- "$EXTERNALMTA" $BLACKLIST
* ! ? fgrep -s -- "$EXTERNALMTA" $BLACKMATCHES
  | echo "$DATE	$EXTERNALMTA	$REVERSELOOKUP" >> $BLACKMATCHES

# Eintrag in bereinigte Whitelist nur bei genauem IP-Treffer:
:0 EDhciw:
* RESULT ?? ^^HAM^^
* EXTERNALMTA ?? .
* ? fgrep -s -- "$EXTERNALMTA" $WHITELIST
* ! ? fgrep -s -- "$EXTERNALMTA" $WHITEMATCHES
  | echo "$DATE	$EXTERNALMTA	$REVERSELOOKUP" >> $WHITEMATCHES

# Auch Checksum-Matches extra mitführen (nur bei Spam):
:0 D
* CHECK ?? CHECKSUM.?_SPAM: \/.*
{ CACHEMATCH=$MATCH
  :0 hciw:
  * ! ? fgrep -s -- "$CACHEMATCH" $CACHEMATCHES
  | echo "$DATE	$CACHEMATCH" >> $CACHEMATCHES
}

# sonst Hash - wenn vorhanden - sichern:
:0 EDhci:
* RESULT ?? ^^SPAM^^
* md5hash ?? .
# ... Hash in Spam-Cache speichern, falls noch nicht in Datei:
| echo "$DATE	$md5hash" >> $SPAMCACHE

# Zentrale Spam-Statistik:
:0
* RESULT ?? ^^SPAM^^
{ 
  :0 Dhci:
  * CHECK ?? .*\/(CHECKSUM.?_SPAM|BLACKLIST_MATCH).*
  | echo "$DATE	$LOGNAME	$MATCH" >> $STATS
  :0ED
  * 1^1 CHECK ?? ^NiX
  { NIXCOUNT="$="
    :0 hci:
    | echo "$DATE	$LOGNAME	no match, NiX count: $NIXCOUNT" >> $STATS
  }
}

:0E
# Test nicht zu spammiger Mails auf schon eingegangene Bodies:
* ! CHECK ?? ^-OK-(HOST|VERTEILER)
* md5hash ?? .
# Nicht bei Mails an Verteiler oder mit mehreren Adressaten laut Header:
* $ ! ^(Resent|CC:).*$MY_DOMAIN
* $ ! ^To:.*${MY_DOMAIN}[^"')].*${MY_DOMAIN}[^"')]
# Nicht bei Mails an web.de-Accounts (weitergeleitet):
* ! ^X-WEBDE-FORWARD
# Nicht bei Mails an 1&1-Accounts (weitergeleitet):
* ! ^Received.*kundenserver
{
  :0Dci:
  # ... Hash speichern, falls noch nicht in Datei:
  * ! CHECK ?? ^NiX-CHECKSUM
  | echo "$DATE	$md5hash" >> $MD5CACHE
  :0E
  { CHECK="MEHRFACH
$CHECK"
    :0fhw
    | formail -i "To: Diese Mail ist mehrmals in der Abteilung! <$ENVELOPE_TO>"
  }
}

# Spam sichern, falls Variable "BACKUP" entsprechend gesetzt:
:0 D
* BACKUP ?? y
# * ! CHECK ?? ^NiX-CHECKSUM
* ! RESULT ?? ^^HAM^^
{ CHECK="BACKUP
$CHECK" 
  :0c:
  $NIXDIR/$SPAMBACKUP
}
:0 E
{ CHECK="NO_BACKUP
$CHECK" }

#############################################
### Mögliches Ende einer INCLUDERC-Datei, ###
### so bindet man sie z.B. ggf. ein:      ###
### INCLUDERC=/KOMPLETTERPFAD/nixspam.rc  ###
#############################################

:0
* LOGFILE ?? .
{ LOG=$LF$CHECK }

# Spam entsorgen (Vorsichtige nutzen einen eigenen IMAP-Ordner):
:0 D
* RESULT ?? ^^SPAM^^
/dev/null

# Nicht so sicheren Spam zustellen:
:0 D
* RESULT ?? ^^MAYBESPAM^^
!$LOGNAME+$MY_SPAMFOLDER@$MY_MAILHOST

# Vacation-Rezept bei Bedarf hier einfügen.

# Hier landen alle restlichen Mails:
:0
!$LOGNAME@$MY_MAILHOST
