Kampf gegen Spam mit Postfix und Spamassassin

Technischer Stand: Jahr 2005. Dieser Artikel wird nicht mehr aktualisiert.

Fighting Spam with Postfix and Spamassassin

Technical status: year 2005. This article will not be updated.

Neuigkeiten

News

Ein Jahr Statistik über eingegangenen Spam hat einige Erkenntnisse zutage gefördert.

One year of spam statistics discovered some insight.

Inhalt

Contents

Version: September 2004.

Änderungen gegenüber den Vorversionen (Juli/August 2004):

  • Verbesserungen in der automatisierten Meldung von Spam
  • automatische Eintragung von Mailempfängern in der Whitelist des Absenders
  • Postfix Version 2.1

Revision: September 2004.

Changes since last version (July/August 2004):

  • improvements in automated notifications
  • automatic whitelisting of mail addressees
  • Postfix version 2.1

Einleitung

Preface

Die Flut unerwünschter elektronischer Werbung (Spam, auch UCE = Unsolicited Commercial Email genannt) hat gigantische Ausmaße angenommen. Mein privater Posteingang (einschließlich einiger externer Postfächer) mit Adressen, die schon viele Jahre aktiv sind, war vor einigen Monaten auf mehrere hundert Nachrichten täglich angewachsen, wovon mehr als 95% Spam waren. Aber es gibt Netzbürger, die über tausende Spamnachrichten täglich klagen.

Aber es geht noch schlimmer. Der Ausschnitt aus dem Maillog zeigt die zurückgewiesenen Nachrichten einer einzigen Stunde. Das Netz der Spammer hat meine Domain barnim.net zum Ziel seiner Wörterbuchattacken erkoren. Zwischen 3.000 und 5.000 Sendeversuche an nicht existierende Adressen registriere ich täglich. Und auffällig sind die fast zeitgleichen Anfragen an denselben Postfachnamen von verschiedenen Absendern. Das bedeutet: Hier ist eine Heerschar gekaperter PC in aller Welt mit sogenannten Spambots infiziert, und die Besitzer dieser Zombie-PC wissen nichts davon.

A tremendous amount of unsolicited commercial email (UCE, spam) is flooding mailboxes. Because I use a number of addresses of which some are now active for years, a few months ago my private email entry hat grown to several hundred messages a day. More than 95% of those were spam. But some netizens complain about thousands of daily spam messages.

But even worse: The mail log below shows refused messages from some randomly selected hour. The world wide network of spammers selected my domain barnim.net as a target for brute force dictionary attacks. The server logs between 3,000 and 5,000 attempts per day of sending to nonexisting adresses. You will notice coinciding requests for the same address from different servers. A horde of captured PC throughout the world is infected with so-called spambots and their unsuspecting owners are not even aware.

Jul 12 20:01:40 reject: RCPT from bzq-82-81-192-6.cablep.bezeqint.net[82.81.192.6]: 550 <sdougal@barnim.net>: Recipient address rejected: Access denied
Jul 12 20:02:19 reject: RCPT from smtp07.auna.com[62.81.186.17]: 450 <0002011402@mcimail.com>: Sender address rejected: Domain not found
Jul 12 20:04:06 reject: RCPT from 24.213.57.147.static.up.mi.chartermi.net[24.213.57.147]: 550 <richard.akre@barnim.net>: Recipient address rejected: Access denied
Jul 12 20:04:32 reject: RCPT from unknown[164.125.148.168]: 550 <mkrofchi@barnim.net>: Recipient address rejected: Access denied
Jul 12 20:05:01 reject: RCPT from adsl-68-78-135-65.dsl.emhril.ameritech.net[68.78.135.65]: 550 <mkrofchi@barnim.net>: Recipient address rejected: Access denied
Jul 12 20:05:53 reject: RCPT from c-24-30-101-39.we.client2.attbi.com[24.30.101.39]: 550 <rajagopalan_v@barnim.net>: Recipient address rejected: Access denied
Jul 12 20:08:44 reject: RCPT from rwcrmxc20.comcast.net[204.127.198.46]: 554 <notify@barnim.net>: Recipient address rejected: Access denied
Jul 12 20:09:19 reject: RCPT from cdm-66-76-192-127.mthm.cox-internet.com[66.76.192.127]: 550 <rrood@barnim.net>: Recipient address rejected: Access denied
Jul 12 20:12:14 reject: RCPT from cliente-217216140195.uBRala01.supercable.es[217.216.140.195]: 550 <simonwilkinson@barnim.net>: Recipient address rejected: Access denied
Jul 12 20:14:26 reject: RCPT from 24.229.166.105.res-cmts.mtp.ptd.net[24.229.166.105]: 550 <richard.bomber.lancaster@barnim.net>: Recipient address rejected: Access denied
Jul 12 20:27:28 reject: RCPT from c-65-34-173-182.se.client2.attbi.com[65.34.173.182]: 550 <raines@barnim.net>: Recipient address rejected: Access denied
Jul 12 20:40:54 reject: RCPT from 12-217-230-73.client.mchsi.com[12.217.230.73]: 550 <rainbowkevin@barnim.net>: Recipient address rejected: Access denied
Jul 12 20:41:04 reject: RCPT from pool-151-202-111-175.ny325.east.verizon.net[151.202.111.175]: 550 <ntziorkas@barnim.net>: Recipient address rejected: Access denied
Jul 12 20:41:32 reject: RCPT from 6.140.171.66.subscriber.vzavenue.net[66.171.140.6]: 550 <rainbowkevin@barnim.net>: Recipient address rejected: Access denied
Jul 12 20:42:13 reject: RCPT from chello213047097076.5.12.vie.surfer.at[213.47.97.76]: 550 <ntziorkas@barnim.net>: Recipient address rejected: Access denied
Jul 12 20:42:24 reject: RCPT from user-0ce2h7g.cable.mindspring.com[24.225.68.240]: 550 <ntziorkas@barnim.net>: Recipient address rejected: Access denied
Jul 12 20:42:39 reject: RCPT from c-67-173-175-195.client.comcast.net[67.173.175.195]: 550 <ntziorkas@barnim.net>: Recipient address rejected: Access denied
Jul 12 20:43:15 reject: RCPT from unknown[211.211.249.193]: 550 <nw-b5request@barnim.net>: Recipient address rejected: Access denied
Jul 12 20:44:11 reject: RCPT from dsl-082-083-068-054.arcor-ip.net[82.83.68.54]: 550 <nw-b5request@barnim.net>: Recipient address rejected: Access denied
Jul 12 20:45:39 reject: RCPT from 68-189-112-8.ca.charter.com[68.189.112.8]: 550 <nw-b5request@barnim.net>: Recipient address rejected: Access denied
Jul 12 20:45:46 reject: RCPT from pcp660796pcs.prshng01.fl.comcast.net[68.35.245.48]: 550 <nw-b5request@barnim.net>: Recipient address rejected: Access denied
Jul 12 20:45:55 reject: RCPT from c-24-14-225-53.client.comcast.net[24.14.225.53]: 550 <oerbeck@barnim.net>: Recipient address rejected: Access denied
Jul 12 20:46:08 reject: RCPT from unknown[218.51.48.248]: 550 <oerbeck@barnim.net>: Recipient address rejected: Access denied
Jul 12 20:46:48 reject: RCPT from wbar10.dal1-4-13-088-201.dsl-verizon.net[4.13.88.201]: 550 <oerbeck@barnim.net>: Recipient address rejected: Access denied
Jul 12 20:47:48 reject: RCPT from 12-220-223-24.client.insightBB.com[12.220.223.24]: 550 <ollieweb@barnim.net>: Recipient address rejected: Access denied
Jul 12 20:48:02 reject: RCPT from pcp08760834pcs.mtlrel01.nj.comcast.net[68.36.30.253]: 550 <ollieweb@barnim.net>: Recipient address rejected: Access denied
Jul 12 20:48:13 reject: RCPT from S010600055d80a442.wp.shawcable.net[24.79.194.112]: 550 <ollieweb@barnim.net>: Recipient address rejected: Access denied
Jul 12 20:49:35 reject: RCPT from unknown[218.22.149.229]: 550 <ollieweb@barnim.net>: Recipient address rejected: Access denied
Jul 12 20:50:08 reject: RCPT from c-67-162-175-186.client.comcast.net[67.162.175.186]: 550 <popeyes@barnim.net>: Recipient address rejected: Access denied
Jul 12 20:50:54 reject: RCPT from 212.199.144.202.forward.012.net.il[212.199.144.202]: 550 <popeyes@barnim.net>: Recipient address rejected: Access denied
Jul 12 20:50:58 reject: RCPT from cpe-66-189-103-225.ma.charter.com[66.189.103.225]: 550 <ordersusa@barnim.net>: Recipient address rejected: Access denied
Jul 12 20:51:06 reject: RCPT from d198-166-219-27.abhsia.telus.net[198.166.219.27]: 550 <popeyes@barnim.net>: Recipient address rejected: Access denied
Jul 12 20:51:08 reject: RCPT from modemcable123.106-203-24.mc.videotron.ca[24.203.106.123]: 550 <ordersusa@barnim.net>: Recipient address rejected: Access denied
Jul 12 20:51:29 reject: RCPT from adsl-69-104-81-136.dsl.pltn13.pacbell.net[69.104.81.136]: 550 <popeyes@barnim.net>: Recipient address rejected: Access denied
Jul 12 20:51:37 reject: RCPT from adsl-67-37-184-18.dsl.chcgil.ameritech.net[67.37.184.18]: 550 <ordersusa@barnim.net>: Recipient address rejected: Access denied
Jul 12 20:51:38 reject: RCPT from user-0c6sdlc.cable.mindspring.com[24.110.54.172]: 550 <popeyes@barnim.net>: Recipient address rejected: Access denied
Jul 12 20:53:11 reject: RCPT from unknown[67.176.126.139]: 550 <orsi_vale@barnim.net>: Recipient address rejected: Access denied
Jul 12 20:53:14 reject: RCPT from c-67-172-12-5.client.comcast.net[67.172.12.5]: 550 <pphp@barnim.net>: Recipient address rejected: Access denied
Jul 12 20:53:29 reject: RCPT from 82-35-8-42.cable.ubr02.hari.blueyonder.co.uk[82.35.8.42]: 554 <82-35-8-42.cable.ubr02.hari.blueyonder.co.uk>: Helo command rejected: Access denied
Jul 12 20:53:47 reject: RCPT from CPE-69-76-180-63.kc.rr.com[69.76.180.63]: 550 <sports4life8@barnim.net>: Recipient address rejected: Access denied
Jul 12 20:53:49 reject: RCPT from ool-18bea0de.dyn.optonline.net[24.190.160.222]: 550 <orsi_vale@barnim.net>: Recipient address rejected: Access denied
Jul 12 20:53:49 reject: RCPT from 12-220-223-24.client.insightBB.com[12.220.223.24]: 550 <pphp@barnim.net>: Recipient address rejected: Access denied
Jul 12 20:54:24 reject: RCPT from adsl-68-72-126-25.dsl.chcgil.ameritech.net[68.72.126.25]: 550 <pphp@barnim.net>: Recipient address rejected: Access denied
Jul 12 20:55:26 reject: RCPT from h004005a622fb.ne.client2.attbi.com[66.30.203.54]: 550 <outslay@barnim.net>: Recipient address rejected: Access denied
Jul 12 20:55:54 reject: RCPT from c-66-229-148-167.we.client2.attbi.com[66.229.148.167]: 550 <outslay@barnim.net>: Recipient address rejected: Access denied
Jul 12 20:56:07 reject: RCPT from c-24-126-159-19.we.client2.attbi.com[24.126.159.19]: 550 <outslay@barnim.net>: Recipient address rejected: Access denied
Jul 12 20:56:18 reject: RCPT from unknown[61.105.79.105]: 550 <outslay@barnim.net>: Recipient address rejected: Access denied
Jul 12 20:56:22 reject: RCPT from adsl-69-105-54-215.dsl.irvnca.pacbell.net[69.105.54.215]: 550 <prettyboy3@barnim.net>: Recipient address rejected: Access denied
Jul 12 20:56:51 reject: RCPT from adsl-68-92-87-113.dsl.stlsmo.swbell.net[68.92.87.113]: 550 <outslay@barnim.net>: Recipient address rejected: Access denied
Jul 12 20:57:36 reject: RCPT from dsl-aur5-b0b.dial.inet.fi[80.221.145.11]: 550 <smmaclean@barnim.net>: Recipient address rejected: Access denied
Jul 12 20:57:48 reject: RCPT from va-spotsy-cuda2-c4b-179.frbgva.adelphia.net[68.70.169.179]: 550 <prettyboy3@barnim.net>: Recipient address rejected: Access denied
Jul 12 20:57:50 reject: RCPT from lns-th2-7-82-64-99-134.adsl.proxad.net[82.64.99.134]: 550 <p0_ga@barnim.net>: Recipient address rejected: Access denied
Jul 12 20:58:31 reject: RCPT from modemcable123.106-203-24.mc.videotron.ca[24.203.106.123]: 550 <p0_ga@barnim.net>: Recipient address rejected: Access denied
Jul 12 20:59:26 reject: RCPT from M1927P029.adsl.highway.telekom.at[80.121.112.221]: 550 <prettyboy3@barnim.net>: Recipient address rejected: Access denied
Jul 12 20:59:59 reject: RCPT from unknown[212.179.163.65]: 504 <OEMCOMPUTER>: Helo command rejected: need fully-qualified hostname

Anforderungen

Requirements

Meine Anforderungen an ein Mailsystem sind:

  1. Volle Kontrolle über Schwarze und Weiße Listen, individuell einstellbar für jedes Familienmitglied.
  2. Basisschutz vor Viren und Würmern bereits auf dem Server.
  3. Einsammeln von Nachrichten aus externen Postfächern, für jedes Familienmitglied extra.
  4. Weniger als 1% unerkanntes Spam.
  5. Schutz vor Verlust fälschlich als Spam identifizierter Nachrichten (sogenannte "false positives").
  6. Senden und Empfangen von Emails sowohl über verschlüsselte als auch unverschlüsselte Verbindungen möglich.
  7. Offen für die Bereitstellung von Maildienst für familienfremde Personen (d.h. mit eingeschränkter Vertrauensstellung).
  8. Sicher gegen Mißbrauch als offenes Mailrelay.
  9. Ausschließlich Verwendung freier Software bzw. Open Source auf dem Server.

Die Lösung, die ich vorstelle, basiert auf dem Betrieb eines eigenen >Mailservers. Im Artikel über das Einrichten eines Dedicated Servers sind die benötigten Konfigurationsdateien im Detail aufgelistet. Der angehende Postmaster kann die Anleitung verwenden, um auf dieser Basis seine eigenen Ideen zu verwirklichen.

These are my basic requirements on a mail server:

  1. Full and individual control of black and white lists for each member of my family.
  2. Basic protection from virii and worms on the server.
  3. Configurable and individual collecting of mails from external mail boxes.
  4. Not more than 1% unrecognised spam.
  5. Prevent unrecognised losing of false positives.
  6. Sending and receiving of mails via encrypted as well as unencrypted connections.
  7. Extendable to serve also persons with restricted level of trust.
  8. Non-relaying mail server.
  9. Exclusive use of free or open source software.

The solution presented here is based on an own dedicated >mail server. Configuration files necessary to set up the server are listed there. The novice postmaster can use this guide and implement her own ideas.

Voraussetzungen

Prerequisites

Softwareinstallation

Software Installation

Der Mailserver wird auf einem Linux-System installiert, ich empfehle >Debian. Eine Grundeinrichtung des Servers ist im >Rootserver-Artikel beschrieben. Der Mailserver benötigt folgende Pakete, die mit dselect installiert werden:

  • postfix und postfix-tls
  • clamav, clamav-base, clamav-daemon, clamav-freshclam
  • spamassassin mit allen vorgeschlagenen Abhängigkeiten und razor
  • postgrey
  • fetchmail
  • courier-pop mit allen vorgeschlagenen Abhängigkeiten und courier-pop-ssl sowie courier-ssl
  • sasl2-bin mit allen vorgeschlagenen Abhängigkeiten
  • libsasl2-modules
  • vrfy

In Ergänzung zu den sehr komfortablen Funktionen von Postfix werden wir einige Shellscripte einführen, die eine Spambehandlung näher an den individuellen Bedürfnissen jedes einzelnen Nutzers erlauben. Durch seinen modularen Aufbau ermöglicht Postfix hervorragend solche Ergänzungen.

Einen Vergleich zwischen Postfix und Qmail kann ich nicht ziehen. Ich habe mich an Postfix gewöhnt und bin mit seinen Möglichkeiten zufrieden. Die Sicherheit scheint gut zu sein. Einige Denial-of-Service Attacken sind in 2003 bekannt geworden; inzwischen liegen neue Freigaben der Software vor. Ich gehe hier von Version 2.1 aus.

Spamassassin liefert sehr gute Ergebnisse und wird ständig weiterentwickelt.

Clam Antivirus ist ein freier serverbasierter Virenscanner. Die Software ist noch sehr jung, und die Aktualisierung der Virensignaturen hält (noch) nicht Schritt mit dem raschen Generationswechsel heutiger Viren und Würmer. C't Ausgabe 8/2004 liefert hierzu eine Auswertung. Clam AV kann derzeit nur als Ergänzung einer clientbasierten Lösung gesehen werden, sofern Sie nicht das Geld für einen kommerziellen serverbasierten Scanner ausgeben wollen.

The mail server will be installed on a Linux system, I recommend >Debian. The base installation is described in my >rootserver article. Following packages should be installed with dselect:

  • postfix and postfix-tls
  • clamav, clamav-base, clamav-daemon, clamav-freshclam
  • spamassassin with all dependencies, plus razor
  • postgrey
  • fetchmail
  • courier-pop with all dependencies, courier-pop-ssl and courier-ssl
  • sasl2-bin with all dependencies
  • libsasl2-modules
  • vrfy

We will add a number of own shell scripts that complement Postfix and allow to process spam very close to the needs of every user. The modular design of Postfix facilitates such extensions rather well.

I will not compare Postfix with Qmail or other products. I got used to Postfix and am quite satisfied with the provided functionality. Security seems to be good. A few DoS attacks were published in 2003 but new software releases are now available. This description requires Postfix release 2.1 or newer.

Spamassassin provides very good results and is continuously improved.

Clam Antivirus is a free server based virus scanner. This software is quite fresh and not always reliable, since virus signatures are not updated that frequently as those of commercial products. Refer to C't issue 8/2004 for a detailed evaluation. Thus, Clam AV in its current state should be complemented by a client based solution.

Schlüsselgenerierung

Key Generation

Bevor es mit dem Mailserver losgeht, brauchen Sie einige Schlüssel für die Sicherheit von Postfix und Courier.

Die SSL-/TLS-Schlüssel zur Absicherung des Email-Verkehrs liegen je nach Linux-Distribution unter /etc/ssl oder unter /usr/share/ssl. Wir gehen hier vom ersten Fall aus; andernfalls sind einige der nachfolgenden Konfigurationsdateien sinngemäß anzupassen. Auf eine detaillierte Erläuterung der einzelnen Kommandos verzichte ich hier. Unter http://httpd.apache.org/docs-2.0/ssl/ssl_faq.html gibt es eine gute Erläuterung. Einige Anmerkungen stehen als Kommentar oder echo-Anweisung im folgenden Script.

>make_sslkeys

Dieses Script erzeugt eine Certification Authority (CA) und Zertifikate für Webserver, SMTP-Server und POP3-Server. Außerdem werden die .p12- und .crt-Schlüssel zum Export in Outlook/Internet Explorer bzw. Netscape/Mozilla bereitgestellt. Für den Import von Zertifikaten in Outlook/Internet Explorer siehe >diese Beschreibung.

Die Bedienung von openssl ist alles andere als erfreulich. Das Script basiert in einigen Teilen auf sign.sh (Copyright 1998-1999 Ralf S. Engelschall) aus mod_ssl.

Damit keine Abfrage einer Paßphrase beim Start des Mailservers kommt, wird sie aus dem privaten Serverschlüssel gelöscht. Der ungeschützte Schlüssel muß dann auf jeden Fall unlesbar für alle außer Root sein.

Die Schlüssel gut sichern. Das Script verpackt die erzeugten Schlüssel in sslkeys.tar und gibt am Schluß noch einen Hinweis, auf welchem Server welche Schlüssel vorhanden sein sollten. Zertifikate (*.crt und *.p12) gehören immer in das Unterverzeichnis ./certs, private Schlüssel (*.key und *.pem) in ./private. Die nicht auf dem Server benötigten privaten Schlüssel sollten von dort gelöscht werden.

Der Fingerprint, von dem im Script die Rede ist, wird weiter unten noch einmal eine Rolle spielen, sofern Sie auch einen "internen" Mailserver betreiben wollen.

Before we start with the mailserver itself we need several keys to encrypt the mail traffic.

SSL/TLS keys to protect the mail traffic are stored in /etc/ssl or/usr/share/ssl, depending on the Linux version. We assume the first case, otherwise some of the configurationsfiles to follow have to be adapted appropriately. If a detailed explanation of the commands is required, please refer to the documentation under http://httpd.apache.org/docs-2.0/ssl/ssl_faq.html.

>make_sslkeys

This script generates a Certification Authority (CA), together with several certificates for web, SMTP and POP3 servers. Besides that, .p12 and .crt keys for exporting to Outlook/Internet Explorer and Netscape/Mozilla are provided. Refer to >this document to see how to import the certificates to the respective mail programs.

It is not really straightforward to use openssl. The script shown here has been derived from sign.sh (Copyright 1998-1999 by Ralf S. Engelschall) from mod_ssl.

To avoid a passphrase request when the mailserver is started, we delete it from the private server key. This unprotected key has to be unreadable for everyone except root.

Do not forget to store the keys safely. The script packs all keys in an archive sslkeys.tar and prints a notification which keys have to be installed on which server. Certificates (*.crt und *.p12) are always stored in the subdirectory ./certs, private keys (*.key und *.pem) in ./private. All unused keys should be deleted from the server

If you also want to set up an "internal" mail server, the fingerprint mentioned in the script will be needed later.

SMTP-Mailserver mit Postfix

SMTP Mailserver with Postfix

Postfix mit TLS und SASL

Postfix with TLS and SASL

In diesem Beispiel ist von den Postfächern

  • myself@mydomain.net
  • otheruser@mydomain.net
  • hosteduser@sub.mydomain.net

die Rede. Die ersten beiden gehören zu Nutzern, die einen Linux-Account besitzen. Die dritte gehört zu einem Nutzer, der einen sogenannten virtuellen Account hat: ein Postfach unter /home/hosted/hosteduser/Maildir.

Das folgende Initialisierungsscript legt die Nutzereinträge für diese drei Nutzer bei Postfix und Courier an und startet Postfix neu. Nach Änderungen an Konfigurationsdateien sollte dieses Script immer aufgerufen werden.

In this example we use the mailboxes

  • myself@mydomain.net
  • otheruser@mydomain.net
  • hosteduser@sub.mydomain.net

The first and second belong to users who have their own Linux avcount. The third belongs to a user with no Linux user name and a so-called virtual account. This is a mailbox under /home/hosted/hosteduser/Maildir.

The following script adds these three users to Postfix and Courier and restarts the mail server. You should always call this script after changing any configuration file.

$ vi /etc/postfix/config.sh
#!/bin/sh
# (c) Thomas Bez <bez@tedesca.net> 2004
# http://www.bez.tedesca.net/homeoff/antispam.html
#
# mail.mydomain.net:/etc/postfix/config.sh

cd /etc/postfix || exit 1
PATH=/etc/postfix:/usr/local/bin:/usr/sbin:$PATH; export PATH
SASLDB=/etc/sasldb2
SASL_REALM=mydomain.net

function remap
{
        postmap $1
        chmod 644 $1 $1.db
}

remap map_transport
remap map_sender_access_fakelocal
remap map_relay_clientcerts
remap map_smtpd_sender_login_maps
remap map_virtual_uid_gid
remap map_virtual_mailbox
remap map_virtual
remap map_recipient_access
remap map_alias
remap map_helo_access
remap map_sender_access_fromaddress

rm $SASLDB

function sasldbentry
{
        echo $2 | saslpasswd2 -c -a smtpd -u $SASL_REALM $1
}

sasldbentry myself                         pAssWoRd
sasldbentry otheruser                      SeSaM
sasldbentry hosteduser@sub.mydomain.net    PaRoLe

chmod 644 $SASLDB

cat >/usr/lib/sasl2/smtpd.conf <<-EOF
        mech_list: login cram-md5 digest-md5
        pwcheck_method: auxprop
        log_level: 3
EOF
chmod 644 /usr/lib/sasl2/smtpd.conf
cp /usr/lib/sasl2/smtpd.conf /etc/postfix/sasl/smtpd.conf

postfix reload

USERDB=/etc/courier/userdb
rm -rf $USERDB
mkdir $USERDB

function userdbentry
{
        userdb -f $USERDB/$1 $2 set uid=$3 gid=$4 home=$5 mail=$6
        echo $7 | userdbpw -md5 | userdb -f $USERDB/$1 $2 set pop3pw
}

userdbentry mydomain.net myself 500 500 /home/myself /home/myself/Maildir pAssWoRd
userdbentry mydomain.net otheruser 501 500 /home/otheruser /home/otheruser/Maildir SeSaM
userdbentry sub.mydomain.net hosteduser@sub.mydomain.net 601 600 /home/hosted/hosteduser /home/hosted/hosteduser/Maildir PaRoLe

chmod 700 $USERDB

makeuserdb

exit 0

In main.cf wird die Grundkonfiguration des Mailservers festgelegt.

The base configuration of the mail server is defined by main.cf.

$ vi /etc/postfix/main.cf
#
# (c) Thomas Bez <bez@tedesca.net> 2004
# http://www.bez.tedesca.net/homeoff/antispam.html
#
# mail.mydomain.net:/etc/postfix/main.cf

command_directory = /usr/sbin
daemon_directory  = /usr/lib/postfix
program_directory = /usr/lib/postfix

smtpd_banner = $myhostname ESMTP $mail_name
setgid_group = postdrop
biff         = no

append_dot_mydomain = no
myorigin            = mydomain.net
mydomain            = mydomain.net
myhostname          = mail.mydomain.net
mydestination       = mydomain.net,
                      sub.mydomain.net,
                      otherdomain.net,
                      localhost
mynetworks          = 275.184.307.3, 127.0.0.0/8
recipient_delimiter = +

virtual_mailbox_base = /

Die Konfiguationsdateien map_... werden weiter unten erläutert.

The configuration files map_... will be explained later.

relay_clientcerts    = hash:/etc/postfix/map_relay_clientcerts
transport_maps       = hash:/etc/postfix/map_transport
alias_maps           = hash:/etc/postfix/map_alias
alias_database       = hash:/etc/postfix/map_alias
virtual_mailbox_maps = hash:/etc/postfix/map_virtual_mailbox
virtual_uid_maps     = hash:/etc/postfix/map_virtual_uid_gid
virtual_gid_maps     = hash:/etc/postfix/map_virtual_uid_gid
virtual_maps         = hash:/etc/postfix/map_virtual

local_recipient_maps =
relay_domains        = $mydestination
home_mailbox         = Maildir/

mailbox_size_limit    = 150000000
message_size_limit    = 134217728
virtual_mailbox_limit = 134217728
bounce_size_limit     = 50000
header_size_limit     = 102400

unknown_local_recipient_reject_code = 450

smtpd_sasl_auth_enable      = yes
smtpd_sasl_application_name = smtpd
smtpd_sasl_security_options = noanonymous
smtpd_sasl_local_domain     = mydomain.net
broken_sasl_auth_clients    = yes

smtpd_use_tls                   = yes
smtpd_tls_ask_ccert             = yes
smtpd_tls_key_file              = /etc/ssl/private/mail.mydomain.net.key
smtpd_tls_cert_file             = /etc/ssl/certs/mail.mydomain.net.crt
smtpd_tls_CAfile                = /etc/ssl/certs/ca.crt
smtpd_tls_CApath                = /etc/ssl/certs
smtpd_tls_loglevel              = 1
smtpd_tls_received_header       = no
smtpd_tls_session_cache_timeout = 3600s
tls_random_source               = dev:/dev/urandom

Manche schwören auf "Teergruben" (engl. Tarpits), um das Internet von Spam zu befreien. Die Idee ist so einfach wie wirkungslos. Durch verzögerte Reaktion des empfangenden Servers bei einer unerwünschten Nachricht (z.B. einer Nachricht, deren Empfänger nicht bekannt ist - siehe die Logdatei oben) soll der Absender ausgebremst werden. Doch das nützt nichts mehr in Zeiten, da Tausende und Abertausende Zombie-PC als absendende Mailserver fungieren.

Die beste Maßnahme ist, nach dem ersten Fehler in der Kommunikation den Empfang sofort abzubrechen und die Wahrheit kundzutun: Empfänger nicht bekannt. Das vergeudet bei uns am wenigsten Ressourcen. Andere Methoden wie SPF werden zum beim Kampf gegen Spam mehr beitragen.

Tarpits ("Teergruben" in German) are believed to clean the internet from spam and they are hence very popular. The idea is as simple as useless: The mail sender shall be slowed down by delayed responses of a receiving server that recognises an unwanted message. This could be a message for a unknown mailbox name, see above. But who cares in times of myriads of zombie PC if some of them become unable to deliver their messages?

The best measure as often in life is to quit immediately and tell the truth: receiver unknown. This is a good way to save our own resources. There are better methods to fight spam, like SPF.

reject_code = 550


smtpd_hard_error_limit   = 1

Mit einem HELO nimmt der Absender die Verbindung zum Empfänger auf. Schon was in der HELO-Meldung steht, kann vollkommen erlogen sein. Wer keinen gültigen Namen angibt (das sollte ein vollständiger Domainname sein), darf gar nicht weitersprechen. Es lohnt allerdings nicht, auf die Existenz dieses Domainnamens im DNS zu prüfen, da viele (auch professionelle) Absender ihre Mailserver nachlässig konfigurieren und ihrem Mailserver keinen registrierten Sub-Domainnamen zuweisen.

Wer sich bereits vor dem HELO durch ein SASL-Login ausgewiesen hat oder seine Nachricht über einen registrierten vertrauenswürdigen Mailserver abliefert, wird ohne weitere Prüfung vorgelassen.

Saying HELO, the sender contacts the receivng mail server. Even the contents of the HELO line may be faked, but we can do a simple check. Who does provide a valid name (which in fact has to be a domain name), is not allowed to proceed. But I do not recomment to check this domain name for existence since many (also professional) configure their mail servers carelessly, not assigning a valid subdomain to their server.

Who already identified by a SASL login ("permit_sasl_authenticated") or who delivered his message to a registered trusted mail server ("permit_tls_clientcerts") may pass without further checks..

smtpd_helo_required     = yes
smtpd_helo_restrictions = permit_tls_clientcerts
                          permit_sasl_authenticated
                          permit_mynetworks
                          reject_invalid_hostname
                          reject_non_fqdn_hostname
                          reject_unauth_pipelining
                          check_helo_access hash:/etc/postfix/map_helo_access

Als nächstes schickt der Absender eine MAIL FROM-Nachricht. Hier wird strenger geprüft: Der dort enthaltene Domainname muß existieren.

Außerdem läßt sich hier schon feststellen, wenn jemand sich fälschlich für einen lokalen Nutzer ausgibt. Wer als Absendeadresse "myself@mydomain.net" angibt, sollte bereits von einer der ersten drei Bedingungen der smtpd_sender_restrictions angenommen worden sein, denn nur authentifizierte Benutzer dürfen unter solch einem Namen senden. Wer als "irgendwer@mydomain.net" bis zum ersten check_sender_access vordringt, kann seine Absendeadresse nur gefälscht haben.

The next piece of the mail to be delivered is a MAIL FROM line. Here control is more stringent: the doamin name givene there has to exist.

We check also if a sender falsely claims to be a local user. Users with a sending address like "myself@mydomain.net" should already have been accepted by one of the first three conditions of smtpd_sender_restrictions since only authenticated users are allowed to use such a name.

# sender restrictions (MAIL FROM)
# 1. permit_mynetworks, permit_tls_clientcerts, permit_sasl_authenticated
#    Permit everyone who is certainly welcome.
#    Note: permit_mynetworks is necessary for fetchmail (contacting smtpd from localhost);
#    fetchmail will not notice our reject (code 450), hence not flush the mail and fetch it again the next run.
#    Everyone who tries to send from one of our domain names should not pass beyond this step 1.
# 2. check_sender_access hash:/etc/postfix/map_sender_access_fakelocal
#    Check for our own domain names. Reject every faked MAIL FROM.
# 3. reject_unknown_sender_domain, reject_non_fqdn_sender, reject_sender_login_mismatch
#    Common UCE criteria.
# 4. check_sender_access hash:/etc/postfix/map_sender_access_fromaddress
#    To lock out anyone else whom we do not like.
# No step beyond permit_sasl_authenticated+permit_tls_clientcerts with faked local addresses,
# all local smtpd users have to be TLS or SASL authenticated.
smtpd_sender_restrictions = permit_mynetworks
                            permit_tls_clientcerts
                            permit_sasl_authenticated
                            reject_unknown_sender_domain
                            reject_non_fqdn_sender
                            reject_unauth_pipelining
                            check_sender_access hash:/etc/postfix/map_sender_access_fakelocal
                            reject_sender_login_mismatch
                            check_sender_access hash:/etc/postfix/map_sender_access_fromaddress
smtpd_sender_login_maps   = hash:/etc/postfix/map_smtpd_sender_login_maps

Als nächstes schickt der Absender eine RCPT TO-Nachricht. Die Prüfungen hier dürften keine Überraschungen mehr bieten. Durch reject_unauth_destination ist der Mailserver nicht als Relay verwendbar, wenn der Absender sich nicht über SASL angemeldet hat.

Greylisting

Greylisting ist die gegenwärtig wirkungsvollste Methode Spam einzudämmen, das durch Netze von Spambots erzeugt wird. Jeder einzelne Absender von Spam führt sich auf wie ein Mailserver, ist aber kein vollständiger Mailserver. Ein echter Mailserver kann mit Übermittlungsfehlern umgehen und wenn nötig die Übertragung wiederholen. Spambots sind aber klein und einfach gehalten. Der Mehraufwand, eine Fehlerbehandlung zu implementieren, lohnt sich für Spammer erst, wenn die Technik des Greylisting sich stark verbreitet hat.

Das Prinzip des Greylisting besteht darin, eine eingehende Mail beim ersten Sendeversuch mit einer fingierten Fehlermeldung (z.B. "Empfangsserver momentan nicht verfügbar") zurückzuweisen, sich den Vorgang aber zu merken. Erst beim zweiten Sendeversuch wird die Nachricht angenommen. Da "guter" Mailverkehr in der Regel über korrekt implementierte Mailserver abgewickelt wird, ist die einzige Auswirkung auf diesen, daß die Nachrichten jeweils etwa fünf Minuten verzögert werden.

Der unter localhost:60000 laufende Policyservice verwendet den Daemon postgrey, der durch Debian automatisch auf diesem lokalen Port installiert wird. Das ist ein Unterschied zum SPF-Service, der nur als Perl-Script zur Verfügung steht und deshalb noch einen zusätzlichen Eintrag in master.cf benötigt.

 

# recipient restrictions (RCPT TO)
# 1. permit_tls_clientcerts, permit_sasl_authenticated
#    Permit everything for senders who are authenticated.
# 2. reject_unknown_recipient_domain, reject_non_fqdn_recipient
#    Recipient domain checks.
# 3. reject_unauth_destination, reject_unauth_pipelining
# 4. check_recipient_access hash:/etc/postfix/map_recipient_access_grey, check_policy_service unix:private/spf
#    SPF test only for destinations that are also in the positive list.
#    Mail to unknown mailboxes is blocked here.
# 5. check_recipient_access hash:/etc/postfix/map_recipient_access_white, reject
#    Process mails on after check of positive list
smtpd_recipient_restrictions = permit_tls_clientcerts
                               permit_sasl_authenticated
                               reject_unknown_recipient_domain
                               reject_non_fqdn_recipient
                               reject_unauth_destination
                               reject_unauth_pipelining
                               check_recipient_access hash:/etc/postfix/map_recipient_access_grey
							   check_policy_service inet:127.0.0.1:60000
                               check_policy_service unix:private/spf
                               check_recipient_access hash:/etc/postfix/map_recipient_access_white
                               reject

Vertrauenswürdige Partner-Mailserver weisen sich durch ein TLS-Zertifikat aus. Die Fingerprints aller akzeptierten Zertifikate stehen in map_relay_clientcerts.

 

$ vi /etc/postfix/map_relay_clientcerts
#
# (c) Thomas Bez <bez@tedesca.net> 2004
# http://www.bez.tedesca.net/homeoff/antispam.html
#
# mail.mydomain.net:/etc/postfix/map_relay_clientcerts
# fingerprints of trusted mailservers that are allowed to inject mails

4A:F6:1G:7B:2D:A2:FD:0B:80:D6:3A:5D:57:24:F8:EE mail.intra.mydomain.net

In map_transport wird für jeden Domainnamen festgelegt, ob er für lokale oder virtuelle Nutzer steht. Im DNS sollte allerdings nicht nur mydomain.net einen MX-Eintrag haben, sondern auch sub.mydomain.net.

 

$ vi /etc/postfix/map_transport
#
# (c) Thomas Bez <bez@tedesca.net> 2004
# http://www.bez.tedesca.net/homeoff/antispam.html
#
# mail.mydomain.net:/etc/postfix/map_transport
# all domains served by this mailserver

mydomain.net       local:
otherdomain.net    local:
sub.mydomain.net   virtual:

Die Postfächer und die Linux User-/Group-IDs der Nutzer.

 

$ vi /etc/postfix/map_virtual_mailbox
#
# (c) Thomas Bez <bez@tedesca.net> 2004
# http://www.bez.tedesca.net/homeoff/antispam.html
#
# mail.mydomain.net:/etc/postfix/map_virtual_mailbox

myself@mydomain.net            /home/myself/Maildir/
otheruser@mydomain.net         /home/otheruser/Maildir/
hosteduser@sub.mydomain.net    /home/hosted/hosteduser/Maildir/
$ vi /etc/postfix/map_virtual_uid_gid
#
# (c) Thomas Bez <bez@tedesca.net> 2004
# http://www.bez.tedesca.net/homeoff/antispam.html
#
# mail.mydomain.net:/etc/postfix/map_virtual_uid_gid

myself@mydomain.net            500:500
otheruser@mydomain.net         501:500
hosteduser@sub.mydomain.net    601:600

Schließlich noch alle Alias-Namen.

 

$ vi /etc/postfix/map_virtual
#
# (c) Thomas Bez <bez@tedesca.net> 2004
# http://www.bez.tedesca.net/homeoff/antispam.html
#
# mail.mydomain.net:/etc/postfix/map_virtual
# all registered receiver aliases

# MYDOMAIN.NET ###########################################################
# family
myalias@mydomain.net           myself@mydomain.net
# special
www@mydomain.net               myself@mydomain.net
info@mydomain.net              myself@mydomain.net
support@mydomain.net           myself@mydomain.net
postmaster@mydomain.net        myself@mydomain.net
abuse@mydomain.net             myself@mydomain.net
security@mydomain.net          myself@mydomain.net
hostmaster@mydomain.net        myself@mydomain.net
webmaster@mydomain.net         myself@mydomain.net

# OTHERDOMAIN.NET ###########################################################
# special
www@otherdomain.net            myself@mydomain.net
info@otherdomain.net           myself@mydomain.net
support@otherdomain.net        myself@mydomain.net
postmaster@otherdomain.net     myself@mydomain.net
abuse@otherdomain.net          myself@mydomain.net
security@otherdomain.net       myself@mydomain.net
hostmaster@otherdomain.net     myself@mydomain.net
webmaster@otherdomain.net      myself@mydomain.net
$ vi /etc/postfix/map_alias
#
# (c) Thomas Bez <bez@tedesca.net> 2004
# http://www.bez.tedesca.net/homeoff/antispam.html
#
# mail.mydomain.net:/etc/postfix/map_alias

MAILER-DAEMON   root
root            myself

Es folgen einige Regeln zum Ablehnen oder Annehmen bestimmter Absende- und Empfangsadressen.

 

$ vi /etc/postfix/map_helo_access
#
# (c) Thomas Bez <bez@tedesca.net> 2004
# http://www.bez.tedesca.net/homeoff/antispam.html
#
# mail.mydomain.net:/etc/postfix/map_helo_access
# mailservers who may not even say HELO to us

reallybadguys.com    REJECT
.reallybadguys.com   REJECT
$ vi /etc/postfix/map_sender_access_fromaddress
#
# (c) Thomas Bez <bez@tedesca.net> 2004
# http://www.bez.tedesca.net/homeoff/antispam.html
#
# mail.mydomain.net:/etc/postfix/map_sender_access_fromaddress
# MAIL FROM addresses to be rejected without additional checks

reallybadguys.com    REJECT
$ vi /etc/postfix/map_recipient_access_grey
#
# (c) Thomas Bez <bez@tedesca.net> 2004
# http://www.bez.tedesca.net/homeoff/antispam.html
#
# mail.mydomain.net:/etc/postfix/map_recipient_access_grey
# local mail addresses to be accepted or rejected by postfix

notify@mydomain.net          REJECT

# following entries according to map_virtual_mailbox:
myself@mydomain.net          DUNNO
myself@otherdomain.net       DUNNO
otheruser@mydomain.net       DUNNO
hosteduser@sub.mydomain.net  DUNNO

# following entries according to map_virtual:
myalias@mydomain.net         DUNNO
www@mydomain.net             DUNNO
info@mydomain.net            DUNNO
support@mydomain.net         DUNNO
postmaster@mydomain.net      DUNNO
abuse@mydomain.net           DUNNO
security@mydomain.net        DUNNO
hostmaster@mydomain.net      DUNNO
webmaster@mydomain.net       DUNNO
www@otherdomain.net          DUNNO
info@otherdomain.net         DUNNO
support@otherdomain.net      DUNNO
postmaster@otherdomain.net   DUNNO
abuse@otherdomain.net        DUNNO
security@otherdomain.net     DUNNO
hostmaster@otherdomain.net   DUNNO
webmaster@otherdomain.net    DUNNO

# reject for all other (unknown) mailboxes
mydomain.net                 REJECT
otherdomain.net              REJECT
sub.mydomain.net             REJECT
$ vi /etc/postfix/map_recipient_access_white
#
# (c) Thomas Bez <bez@tedesca.net> 2004
# http://www.bez.tedesca.net/homeoff/antispam.html
#
# mail.mydomain.net:/etc/postfix/map_recipient_access_white
# local mail addresses to be accepted or rejected by postfix

# following entries according to map_virtual_mailbox:
myself@mydomain.net          OK
myself@otherdomain.net       OK
otheruser@mydomain.net       OK
hosteduser@sub.mydomain.net  OK

# following entries according to map_virtual:
myalias@mydomain.net         OK
www@mydomain.net             OK
info@mydomain.net            OK
support@mydomain.net         OK
postmaster@mydomain.net      OK
abuse@mydomain.net           OK
security@mydomain.net        OK
hostmaster@mydomain.net      OK
webmaster@mydomain.net       OK
www@otherdomain.net          OK
info@otherdomain.net         OK
support@otherdomain.net      OK
postmaster@otherdomain.net   OK
abuse@otherdomain.net        OK
security@otherdomain.net     OK
hostmaster@otherdomain.net   OK
webmaster@otherdomain.net    OK
$ vi /etc/postfix/map_sender_access_fakelocal
#
# (c) Thomas Bez <bez@tedesca.net> 2004
# http://www.bez.tedesca.net/homeoff/antispam.html
#
# mail.mydomain.net:/etc/postfix/map_sender_access_fakelocal
# REJECT for all MAIL FROM addresses that claim to come from here but actually do not

mydomain.net      REJECT
otherdomain.net   REJECT
sub.mydomain.net  REJECT
$ vi /etc/postfix/map_smtpd_sender_login_maps
#
# (c) Thomas Bez <bez@tedesca.net> 2004
# http://www.bez.tedesca.net/homeoff/antispam.html
#
# mail.mydomain.net:/etc/postfix/map_smtpd_sender_login_maps
# allowed MAIL FROM addresses with their respective SASL login names

myself@mydomain.net            myself
myself@otherdomain.net         myself
otheruser@mydomain.net         otheruser
hosteduser@sub.mydomain.net    hosteduser@sub.mydomain.net

Steuerung über Namenslisten

Flow Control by Name Lists

Wie Postfix und die verschiedenen nachfolgenden Scripte die Nachricht von einem bestimmten Absender bzw. für einen bestimmten Empfänger behandeln, wird über eine Reihe von Namenslisten gesteuert. Die auf .lst endenden Listen enthalten einfache Namen, die auf .rxp endenden Listen enthalten vorformatierte Reguläre Ausdrücke.

 

$ vi /etc/postfix/authenticated.lst
#
# (c) Thomas Bez <bez@tedesca.net> 2004
# http://www.bez.tedesca.net/homeoff/antispam.html
#
# mail.mydomain.net:/etc/postfix/authenticated.lst
# users that are SASL or TLS authenticated if sending mails

myself@mydomain.net
myself@otherdomain.net
otheruser@mydomain.net
hosteduser@sub.mydomain.net
$ vi /etc/postfix/authorized.lst
#
# (c) Thomas Bez <bez@tedesca.net> 2004
# http://www.bez.tedesca.net/homeoff/antispam.html
#
# mail.mydomain.net:/etc/postfix/authorized.lst
# users that are authorized to inject mail to postfix

# authenticated addresses:
myself@mydomain.net
myself@otherdomain.net
otheruser@mydomain.net
hosteduser@sub.mydomain.net
# addresses for server-generated messages:
MAILER-DAEMON@mydomain.net
MAILER-DAEMON@otherdomain.net
# mimicry addresses:
myself@mycompany.com
# no need to include here:
#postmaster@mydomain.net
#notify@mydomain.net
$ vi /etc/postfix/mailboxes_family.lst
#
# (c) Thomas Bez <bez@tedesca.net> 2004
# http://www.bez.tedesca.net/homeoff/antispam.html
#
# mail.mydomain.net:/etc/postfix/mailboxes_family.lst
# mailboxes of privileged (family) users

myself@mydomain.net
otheruser@mydomain.net
$ vi /etc/postfix/mailboxes_hosted.lst
#
# (c) Thomas Bez <bez@tedesca.net> 2004
# http://www.bez.tedesca.net/homeoff/antispam.html
#
# mail.mydomain.net:/etc/postfix/mailboxes_hosted.lst
# mailboxes of hosted users

hosteduser@sub.mydomain.net
$ vi /etc/postfix/sender_domains.lst
#
# (c) Thomas Bez <bez@tedesca.net> 2004
# http://www.bez.tedesca.net/homeoff/antispam.html
#
# mail.mydomain.net:/etc/postfix/sender_domains.lst
# allowed sender domains

mydomain.net
otherdomain.net
mycompany.com
$ vi /etc/postfix/spamc_individual.lst
#
# (c) Thomas Bez <bez@tedesca.net> 2004
# http://www.bez.tedesca.net/homeoff/antispam.html
#
# mail.mydomain.net:/etc/postfix/spamc_individual.lst
# users who do their individual spam filtering with own scoring schema

myself@mydomain.net
otheruser@mydomain.net
myalias@mydomain.net
www@mydomain.net
info@mydomain.net
support@mydomain.net
postmaster@mydomain.net
abuse@mydomain.net
security@mydomain.net
hostmaster@mydomain.net
webmaster@mydomain.net
myself@otherdomain.net
www@otherdomain.net
info@otherdomain.net
support@otherdomain.net
postmaster@otherdomain.net
abuse@otherdomain.net
security@otherdomain.net
hostmaster@otherdomain.net
webmaster@otherdomain.net

# not: hosteduser@sub.mydomain.net
$ vi /etc/postfix/usernames_family.lst
#
# (c) Thomas Bez <bez@tedesca.net> 2004
# http://www.bez.tedesca.net/homeoff/antispam.html
#
# mail.mydomain.net:/etc/postfix/usernames_family.lst
# receiver names that correspond to a family mailbox

myself@mydomain.net
myself@otherdomain.net
otheruser@mydomain.net
myalias@mydomain.net
www@mydomain.net
info@mydomain.net
support@mydomain.net
postmaster@mydomain.net
abuse@mydomain.net
security@mydomain.net
hostmaster@mydomain.net
webmaster@mydomain.net
myself@otherdomain.net
www@otherdomain.net
info@otherdomain.net
support@otherdomain.net
postmaster@otherdomain.net
abuse@otherdomain.net
security@otherdomain.net
hostmaster@otherdomain.net
webmaster@otherdomain.net
$ vi /etc/postfix/usernames_hosted.lst
#
# (c) Thomas Bez <bez@tedesca.net> 2004
# http://www.bez.tedesca.net/homeoff/antispam.html
#
# mail.mydomain.net:/etc/postfix/usernames_hosted.lst

hosteduser@sub.mydomain.net
$ vi /etc/postfix/own_domains.rxp
#
# (c) Thomas Bez <bez@tedesca.net> 2004
# http://www.bez.tedesca.net/homeoff/antispam.html
#
# mail.mydomain.net:/etc/postfix/own_domains.rxp

^mydomain\.net$
^otherdomain\.net$
^sub\.mydomain\.net$
^.*\.mydomain\.net$
^.*\.otherdomain\.net$
^.*\.sub\.mydomain\.net$
^275\.184\.307\.3$

Viren- und Spamfilter

Virus and Spam Filter

Jede ausgehende Email wird auf Viren geprüft und gegebenenfalls gar nicht erst hinausgelassen. EIngehende Mails werden einer SPF-Prüfung und einem Virentest unterzogen.

Postfix prüft nach dem SPF-Verfahren ("Sender Permitted From" oder auch "Sender Policy Framework") durch den Aufruf eines Policy-Scripts, ob der Absender eine zulässige Absendeadresse angegeben hat. Das soll insbesondere vor gefälschten Mails von Freemail-Accounts schützen. Es funktioniert aber nur, wenn der Eigentümer des absendenden Mailservers einen entsprechenden Eintrag im DNS hinterlegt hat.

Das ist leider selten der Fall. Wir geben ein gutes Beispiel und legen einen SPF-Eintrag für mydomain.net an.

 

$ vi /etc/postfix/master.cf
#
# (c) Thomas Bez <bez@tedesca.net> 2004
# http://www.bez.tedesca.net/homeoff/antispam.html
#
# mail.mydomain.net:/etc/postfix/master.cf

# service type  private unpriv  chroot  wakeup  maxproc command + args
#               (yes)   (yes)   (yes)   (never) (100)
# ==========================================================================
smtp      inet  n       -       n       -       -       smtpd -o content_filter=spamfilter:dummy
#628      inet  n       -       -       -       -       qmqpd
pickup    fifo  n       -       -       60      1       pickup
cleanup   unix  n       -       -       -       0       cleanup
qmgr      fifo  n       -       -       300     1       qmgr
#qmgr     fifo  n       -       -       300     1       nqmgr
rewrite   unix  -       -       -       -       -       trivial-rewrite
bounce    unix  -       -       -       -       0       bounce
defer     unix  -       -       -       -       0       bounce
flush     unix  n       -       -       1000?   0       flush
proxymap  unix  -       -       n       -       -       proxymap
smtp      unix  -       -       -       -       -       smtp
relay     unix  -       -       -       -       -       smtp
#       -o smtp_helo_timeout=5 -o smtp_connect_timeout=5
showq     unix  n       -       -       -       -       showq
error     unix  -       -       -       -       -       error
local     unix  -       n       n       -       -       local
virtual   unix  -       n       n       -       -       virtual
lmtp      unix  -       -       n       -       -       lmtp

# interfaces to non-Postfix software
spf       unix  -       n       n       -       -       spawn
  user=nobody argv=/usr/bin/perl /etc/postfix/spf-policy.pl
spamfilter unix -       n       n       -       -       pipe
  flags=Rq user=spam argv=/etc/postfix/spamfilter.sh postfix none ${sender} ${recipient}

# only used by postfix-tls
#tlsmgr   fifo  -       -       n       300     1       tlsmgr
#smtps    inet  n       -       n       -       -       smtpd
#  -o smtpd_tls_wrappermode=yes -o smtpd_sasl_auth_enable=yes
#587      inet  n       -       n       -       -       smtpd
#  -o smtpd_enforce_tls=yes -o smtpd_sasl_auth_enable=yes
trace     unix  -       -       -       -       0       bounce
verify    unix  -       -       -       -       1       verify

Das Kernstück allen weiteren Filterns ist das Shell-Script spamfilter.sh. Die Einstellungen in master.cf bewirken, daß spamfilter.sh die Nachricht zu sehen bekommt, nachdem der SMTP-Daemon die oben genannten ersten Prüfungen abgeschlossen hat.

Auf besondere Effizienz habe ich bei diesem Script verzichtet. Es fallen große Mengen an temporären Dateien an. Für einen Server, der täglich Tausende Nachrichten verdauen muß, ist dieses Script also nicht geeignet.

 

$ vi /etc/postfix/spamfilter.sh
#!/bin/sh
# (c) Thomas Bez <bez@tedesca.net> 2004
# http://www.bez.tedesca.net/homeoff/antispam.html
#
# mail.mydomain.net:/etc/postfix/spamfilter.sh
# parameter 1: modus (fetchmail, postfix)
# parameter 2: fetchmail box or "none"
# parameter 3: sender (MAIL FROM)
# parameter 4...n: recipients (RCPT TO)

#########################################################################################################
# flags
#########################################################################################################
check_virus=yes
log_spamstatus=no
debug=no
logheaders=yes
logverbose=yes

#########################################################################################################
# set up environment
#########################################################################################################
mydomain=mydomain.net
my_received_id="mail.$mydomain (Postfix)"
postmaster=myself@$mydomain
SPAMUSER=spam
cd /etc/postfix || { cat | sendmail -i -f $postmaster -- $postmaster; exit 0; }
PATH=/etc/postfix:/usr/local/bin:/usr/sbin:$PATH; export PATH
MAILHEADERS=/var/log/mailheaders
null=/dev/null
subjectlength=28
OPMODE="$1"
FETCHBOX="$2"
MAIL_FROM="$3"
shift; shift; shift
RCPT_TO="$@"
RCPT_COUNT=`echo "$RCPT_TO" | wc -w | tr -d ' '`
logname="spamfilter.sh/$OPMODE"
auth_magic=`auth_magic.sh $mydomain`

Zuerst werden die Kopfzeilen der Nachricht extrahiert. Ihre Auswertung gibt schon einige Information über die Herkunft der Email. Wir können insbesondere feststellen, welche Nachricht nicht direkt an diesen Mailserver geschickt wurde, sondern von einem externen Postfach eingesammelt wurde. An einigen Stellen müssen zum Schutz der Privatsphäre solche Nachrichten anders behandelt werden.

In authorized.lst befindet sich eine Liste aller erlaubten Absendenamen. Wenn die hier geprüfte Mail von einem dieser Absender stammt, handelt es sich mit Sicherheit um eine ausgehende Mail, denn eingehende Mails mit derart gefälschten Absendenamen hätte bereits der SMTP-Daemon aussortiert.

 

#########################################################################################################
# detect status of mail
#########################################################################################################
TMP_original=`mktemp -p /tmp` || { cat | sendmail -i -f "$MAIL_FROM" -- $postmaster; exit 0; }
cat >$TMP_original
TMP_oldheaders=`mktemp -p /tmp` || { cat $TMP_original | sendmail -i -f "$MAIL_FROM" -- $postmaster; exit 0; }
mawk -f extract_headers.awk <$TMP_original >$TMP_oldheaders
TMP_mail_plus=`mktemp -p /tmp` || { cat $TMP_original | sendmail -i -f "$MAIL_FROM" -- $postmaster; exit 0; }

LOGTEXT="mail_from=<$MAIL_FROM>"
from_authenticated=no
from_authorized=no
from_family=no
if [ $OPMODE = postfix ]
then
        # authenticated: everyone authenticated by SASL or coming from trusted mailserver
        # (e.g. internal mailserver, TLS fingerprint authenticated)
        if match_plain.sh "$MAIL_FROM" authenticated.lst >$null 2>$null
        then
                from_authenticated=yes
                LOGTEXT="$LOGTEXT authenticated"
        fi
        # authorized: superset of authenticated, comprising additionally:
        # - automatically server-generated mails, e.g. from MAILER-DAEMON@*
        # - registered mimicry addresses, e.g. faked corporate mail addresses
        if match_plain.sh "$MAIL_FROM" authorized.lst >$null 2>$null
        then
                from_authorized=yes
                [ $from_authenticated = no ] && LOGTEXT="$LOGTEXT authorized"
        fi
        # family: privileged users whose mails have to be automatically certified with Habeas stamp
        if match_plain.sh "$MAIL_FROM" usernames_family.lst >$null 2>$null
        then
                from_family=yes
                LOGTEXT="$LOGTEXT family"
        fi
fi
LOGTEXT="$LOGTEXT rcpt_to=<$RCPT_TO>"

Einige Informationen, die der SMTP-Daemon leider nicht den Kopfzeilen hinzufügen kann, fügen wir hier selbst ein: Wer die Nachricht geschickt hat, wie er sich mit HELO gemeldet hat und so weiter. Diese Information wird später in weiteren Scripten nochmals benötigt, z.B. nondelivery.sh.

 

#########################################################################################################
# identify basic SMTP envelope parameters
#########################################################################################################
HELO=""
CLIENT=""
if [ $OPMODE = postfix ]
then
        heloline=`grep 'Received: from .* by '"$my_received_id"' with' $TMP_oldheaders`
        if [ X"$heloline" != X ]
        then
                # 1=helo 2=client_ip 3=time
                # Received: from $HELO ($CLIENT_REV [$CLIENT]) by $my_received_id with $PROTOCOL id $IDENT
                #   for <$DESTINATION>; $TIME
                helopattern='^Received: from \([^ ]*\) ([^ ]* [[]\([^]]*\)]) by '"$my_received_id"' [^;]*; \(.*\)$'
                HELO=`echo $heloline |   sed -e 's/'"$helopattern"'/\1/'`
                CLIENT=`echo $heloline | sed -e 's/'"$helopattern"'/\2/'`
                TIME=`echo $heloline |   sed -e 's/'"$helopattern"'/\3/'`
                LOGTEXT="$LOGTEXT client=<$CLIENT> helo=<$HELO> time=<$TIME>"
                new_helopattern='^\(Received: from [^ ]* ([^ ]* [[][^]]*]) by '"$my_received_id"
                new_helopattern="$new_helopattern"' with [^ ]* id [^ ]*\)\( for <\([^>]*\)>\)\{0,1\}[^;]*;\(.*\)$'
                new_heloline=`echo "$heloline" | \
                        sed -e 's/'"$new_helopattern"'/\1 for <\3> count '"$RCPT_COUNT"' from <'"$MAIL_FROM"'>; \4/'`
        fi
fi

#########################################################################################################
# filter out more header information and rewrite headers
#########################################################################################################
subject=`grep -i '^Subject:' $TMP_oldheaders | sed -e 's/^[^:]*: //;s/</{/g;s/>/}/g' | tr -d '\n' | tr '[:cntrl:]' '?'`
[ `expr length "$subject"` -gt $subjectlength ] && subject=`expr substr "$subject" 1 $subjectlength`"..."
ident=`grep -i '^Message-ID:' $TMP_oldheaders | sed -e 's/^[^:]*: //;s/[><]//g' | tr -d '\n' | tr '[:cntrl:]' '?'`
mailsize=`wc -lc <$TMP_original | sed -e 's/ *\([^ ]*\) *\([^ ]*\)/\1 lines, \2 bytes/'`
LOGTEXT="$LOGTEXT subject=<$subject> ident=<$ident> size=<$mailsize>"
if [ X"$HELO" = X ]
then
        date=`grep -i '^Date:' $TMP_oldheaders | sed -e 's/^[^:]*: //'`
        LOGTEXT="$LOGTEXT date=<$date>"
fi

if [ X"$new_heloline" = X ]
then
        cat <$TMP_oldheaders >$TMP_mail_plus
else
        sed -ne ':a;/'"$helopattern"'/s/^.*$/'"$new_heloline"'/;tx;/^$/bx;p;n;ba;:x;p;n;bx' \
                <$TMP_oldheaders >$TMP_mail_plus
fi
[ $from_authenticated = yes ] && echo "X-SASL-AUTHENTICATED: $MAIL_FROM by $auth_magic" >>$TMP_mail_plus
[ $OPMODE = fetchmail ] && echo "X-Fetchmail-Retrieved: $FETCHBOX" >>$TMP_mail_plus
sed -ne '/^$/,$p' <$TMP_original >>$TMP_mail_plus

Zum Virentest benutzen wir Clam Antivirus, einen Open Source Virenscanner. Die Virensignaturen, die Clam über das Internet zur Verfügung stellt, sind nicht besonders aktuell. Auf einen zweiten Virenscanner im Client PC sollte man nicht verzichten.

Clam kann mit komplett MIME-codierten Emails umgehen. Das nochmalige Entpacken und einzelne Prüfen der Anlagen scheint überflüssig zu sein. Ich konnte bislang keinen Fall feststellen, in dem beide Prüfungen verschiedene Ergebnisse gebracht hätten.

 

#########################################################################################################
# run spam and virus tests and write log summary
#########################################################################################################
infected=no
if [ $check_virus = yes ]
then
        virusinfo=`clamscan --verbose --disable-summary $TMP_original 2>&1 1>$null`
        if [ $? = 1 ]
        then
                infected=yes
                LOGTEXT="$LOGTEXT virus=<`echo "$virusinfo" | sed -e 's/^[^:]*: //;s/ FOUND$//'`>"
        fi
fi

if [ $log_spamstatus = yes -a $infected != yes -a $from_authenticated = no ]
then
        spaminfo=`spamc -c -u $SPAMUSER <$TMP_original`
        [ $? = 1 ] && LOGTEXT="$LOGTEXT spam=<$spaminfo>"
fi

logger -p mail.info -t $logname "$LOGTEXT"
if [ $logheaders = yes ]
then
        cat $TMP_oldheaders >>$MAILHEADERS
        echo " -----------------------------------------------------------------" >>$MAILHEADERS
fi

Ausgehende Mails werden gesendet, sofern sie nicht dem Virenscanner aufgefallen sind.

Zu sendende Mails werden mit Kopfzeilen versehen, die die Virenprüfung bezeugen. Mails von privilegierten Familienmitgliedern werden außerdem mit urheberrechtlich geschützen Kopfzeilen von Habeas versehen, die einen heiligen Eid schwören, daß diese Mail kein Spam sei.

Überflüssige Kopfzeilen werden entfernt, insbesondere solche, die Aufschluß über unsere interne Netzstruktur geben könnten.

Damit das Script add_blackwhitelist.sh die Empfängeradresse zur Weißen Liste des Absenders hinzufügen kann, muß es mit den Privilegien des Absenders oder des Superusers (wie hier) ausgeführt werden. Sudo muß konfiguriert werden wie im Kommentar erläutert.

 

#########################################################################################################
# process message per recipient
#########################################################################################################
while [ X"$1" != X ]
do
        TO="$1"
        #################################################################################################
        # outbound
        #################################################################################################
        if [ $from_authorized = yes ]
        then
                # this branch is not taken for fetched mails
                if [ $infected = yes ]
                then
                        # never deliver infected mails
                        nondelivery.sh authorized null virus "$virusattach" \
                                "$MAIL_FROM" "$TO" "$RCPT_COUNT" "$HELO" \
                                "$CLIENT" "$TIME" <$TMP_mail_plus
                elif [ $from_family = yes ]
                then
                        # mails from family users are certified with Habeas stamp
                        mawk -f suppress_internals.awk <$TMP_mail_plus | \
                        mawk -f plus_habeas.awk | \
                        mawk -f plus_viruscheck.awk | \
                        sendmail -i -f "$MAIL_FROM" -- "$TO"
                        # current user is "spam", su to root in order to change user configuration files
                        sudo /etc/postfix/add_blackwhitelist.sh white "$MAIL_FROM" "$TO"
                else
                        # from hosted user
                        mawk -f suppress_internals.awk <$TMP_mail_plus | \
                        mawk -f plus_viruscheck.awk | \
                        sendmail -i -f "$MAIL_FROM" -- "$TO"
                        sudo /etc/postfix/add_blackwhitelist.sh white "$MAIL_FROM" "$TO"
                fi

Eingehende Mails werden verteilt. Kann die Mail nicht zugestellt werden, weil sie verseucht ist, oder ist sie als Spam aufgefallen, so wird nondelivery.sh aufgerufen.

 

        #################################################################################################
        # inbound
        #################################################################################################
        else
                to_family=no
                match_plain.sh "$TO" usernames_family.lst >$null 2>$null && to_family=yes
                to_hosted=no
                match_plain.sh "$TO" usernames_hosted.lst >$null 2>$null && to_hosted=yes
                if [ $to_family = $to_hosted ]
                then
                        logger -p mail.info -t $logname -- \
                                "alert: <$TO> to_family=<$to_family> to_hosted=<$to_hosted>"
                        to_family=no
                        to_hosted=yes
                fi
                [ $to_family = yes ] && nondelivery_dest=family || nondelivery_dest=hosted
                if [ $infected = yes ]
                then
                        # never deliver infected mails
                        nondelivery.sh null $nondelivery_dest virus "$virusattach" \
                                "$MAIL_FROM" "$TO" "$RCPT_COUNT" "$HELO" \
                                "$CLIENT" "$TIME" <$TMP_mail_plus
                elif match_plain.sh "$TO" spamc_individual.lst >$null 2>$null
                then
                        # no spam check here, will be done per-user, initiated by .forward
                        mawk -f plus_viruscheck.awk <$TMP_mail_plus | \
                        sendmail -i -f "$MAIL_FROM" -- "$TO"
                else
                        if TMP_spamchecked=`mktemp -p /tmp`
                        then
                                # spamc to set spam header tags only,
                                # procmail or the user client can filter by X-Spam-Status tags
                                mawk -f plus_viruscheck.awk <$TMP_mail_plus | \
                                spamc -f -u $SPAMUSER >$TMP_spamchecked
                                sendmail -i -f "$MAIL_FROM" -- "$TO" <$TMP_spamchecked
                                if grep -i '^X-Spam-Flag: YES' $TMP_spamchecked >$null 2>$null
                                then
                                        nondelivery.sh null $nondelivery_dest spam "" \
                                                "$MAIL_FROM" "$TO" "$RCPT_COUNT" "$HELO" \
                                                "$CLIENT" "$TIME" <$TMP_spamchecked
                                fi
                                rm $TMP_spamchecked
                        else
                                sendmail -i -f "$MAIL_FROM" -- $TO <$TMP_mail_plus
                        fi
                fi
        fi
        shift
done
rm -f $TMP_mail_plus $TMP_oldheaders $TMP_original

exit 0

SPF

SPF

Hier ein Perl-Programm, das den SPF-Test für eine Mailadresse durchführt.

 

$ vi /etc/postfix/spf-policy.pl
#!/usr/bin/perl

# mengwong@pobox.com
# Wed Dec 10 03:52:04 EST 2003
# postfix-policyd-spf
# version 1.06
# see http://spf.pobox.com/

use Fcntl;
use Sys::Syslog qw(:DEFAULT setlogsock);
use strict;

# ----------------------------------------------------------
#                      configuration
# ----------------------------------------------------------

# to use SPF, install Mail::SPF::Query from CPAN or from the SPF website at http://spf.pobox.com/downloads.html

  my @HANDLERS;
  push @HANDLERS, "testing";
  push @HANDLERS, "sender_permitted_from"; use Mail::SPF::Query;

my $VERBOSE = 0;

my $DEFAULT_RESPONSE = "DUNNO";

#
# Syslogging options for verbose mode and for fatal errors.
# NOTE: comment out the $syslog_socktype line if syslogging does not
# work on your system.
#

my $syslog_socktype = 'unix'; # inet, unix, stream, console
my $syslog_facility = "mail";
my $syslog_options  = "pid";
my $syslog_priority = "info";
my $syslog_ident    = "postfix/policy-spf";

# ----------------------------------------------------------
#                  minimal documentation
# ----------------------------------------------------------

#
# Usage: smtpd-policy.pl [-v]
#
# Demo delegated Postfix SMTPD policy server.
# This server implements SPF.
# Another server implements greylisting.
# Postfix has a pluggable policy server architecture.
# You can call one or both from Postfix.
#
# The SPF handler uses Mail::SPF::Query to do the heavy lifting.
#
# This documentation assumes you have read Postfix's README_FILES/SMTPD_POLICY_README
#
# Logging is sent to syslogd.
#
# How it works: each time a Postfix SMTP server process is started
# it connects to the policy service socket, and Postfix runs one
# instance of this PERL script.  By default, a Postfix SMTP server
# process terminates after 100 seconds of idle time, or after serving
# 100 clients. Thus, the cost of starting this PERL script is smoothed
# out over time.
#
# To run this from /etc/postfix/master.cf:
#
#    policy  unix  -       n       n       -       -       spawn
#      user=nobody argv=/usr/bin/perl /usr/libexec/postfix/smtpd-policy.pl
#
# To use this from Postfix SMTPD, use in /etc/postfix/main.cf:
#
#    smtpd_recipient_restrictions =
#       ...
#       reject_unknown_sender_domain
#       reject_unauth_destination
#       check_policy_service unix:private/policy
#       ...
#
# NOTE: specify check_policy_service AFTER reject_unauth_destination
# or else your system can become an open relay.
#
# To test this script by hand, execute:
#
#    % perl smtpd-policy.pl
#
# Each query is a bunch of attributes. Order does not matter, and
# the demo script uses only a few of all the attributes shown below:
#
#    request=smtpd_access_policy
#    protocol_state=RCPT
#    protocol_name=SMTP
#    helo_name=some.domain.tld
#    queue_id=8045F2AB23
#    sender=foo@bar.tld
#    recipient=bar@foo.tld
#    client_address=1.2.3.4
#    client_name=another.domain.tld
#    [empty line]
#
# The policy server script will answer in the same style, with an
# attribute list followed by a empty line:
#
#    action=dunno
#    [empty line]
#

# Jul 23 18:43:29 dumbo/dumbo policyd[21171]: Attribute: client_address=208.210.125.227
# Jul 23 18:43:29 dumbo/dumbo policyd[21171]: Attribute: client_name=newbabe.mengwong.com
# Jul 23 18:43:29 dumbo/dumbo policyd[21171]: Attribute: helo_name=newbabe.mengwong.com
# Jul 23 18:43:29 dumbo/dumbo policyd[21171]: Attribute: protocol_name=ESMTP
# Jul 23 18:43:29 dumbo/dumbo policyd[21171]: Attribute: protocol_state=RCPT
# Jul 23 18:43:29 dumbo/dumbo policyd[21171]: Attribute: queue_id=
# Jul 23 18:43:29 dumbo/dumbo policyd[21171]: Attribute: recipient=mengwong@dumbo.pobox.com
# Jul 23 18:43:29 dumbo/dumbo policyd[21171]: Attribute: request=smtpd_access_policy
# Jul 23 18:43:29 dumbo/dumbo policyd[21171]: Attribute: sender=mengwong@newbabe.mengwong.com

# ----------------------------------------------------------
#                      initialization
# ----------------------------------------------------------

#
# Log an error and abort.
#
sub fatal_exit {
  syslog(err  => "fatal_exit: @_");
  syslog(warning => "fatal_exit: @_");
  syslog(info => "fatal_exit: @_");
  die "fatal: @_";
}

#
# Unbuffer standard output.
#
select((select(STDOUT), $| = 1)[0]);

#
# This process runs as a daemon, so it can't log to a terminal. Use
# syslog so that people can actually see our messages.
#
setlogsock $syslog_socktype;
openlog $syslog_ident, $syslog_options, $syslog_facility;

# ----------------------------------------------------------
#                           main
# ----------------------------------------------------------

#
# Receive a bunch of attributes, evaluate the policy, send the result.
#
my %attr;
while (<STDIN>) {
  chomp;
  if (/=/)       { my ($k, $v) = split (/=/, $_, 2); $attr{$k} = $v; next }
  elsif (length) { syslog(warning=>sprintf("warning: ignoring garbage: %.100s", $_)); next; }

  if ($VERBOSE) {
    for (sort keys %attr) {
      syslog(debug=> "Attribute: %s=%s", $_, $attr{$_});
    }
  }

  fatal_exit ("unrecognized request type: '$attr{request}'") unless $attr{request} eq "smtpd_access_policy";

  my $action = $DEFAULT_RESPONSE;
  my %responses;
  foreach my $handler (@HANDLERS) {
    no strict 'refs';
    my $response = $handler->(attr=>\%attr);
    syslog(debug=> "handler %s: %s", $handler, $response);
    if ($response and $response !~ /^dunno/i) {
      syslog(info=> "handler %s: %s is decisive.", $handler, $response);
      $action = $response; last;
    }
  }

  syslog(info=> "decided action=%s", $action);

  print STDOUT "action=$action\n\n";
  %attr = ();
}

# ----------------------------------------------------------
#                     plugin: SPF
# ----------------------------------------------------------
sub sender_permitted_from {
  local %_ = @_;
  my %attr = %{ $_{attr} };

  my $query = eval { new Mail::SPF::Query (ip    =>$attr{client_address},
                                           sender=>$attr{sender},
                                           helo  =>$attr{helo_name}) };
  if ($@) {
    syslog(info=>"%s: Mail::SPF::Query->new(%s, %s, %s) failed: %s",
           $attr{queue_id}, $attr{client_address}, $attr{sender}, $attr{helo_name}, $@);
    return "DUNNO";
  }
  my ($result, $smtp_comment, $header_comment) = $query->result();

  syslog(info=>"%s: SPF %s: smtp_comment=%s, header_comment=%s",
         $attr{queue_id}, $result, $smtp_comment, $header_comment);

  if    ($result eq "pass")  { return "DUNNO"; }
  elsif ($result eq "fail")  { return "REJECT " . ($smtp_comment || $header_comment); }
  elsif ($result eq "error") { return "450 temporary failure: $smtp_comment"; }
  else                       { return "DUNNO"; }
  # unknown, softfail, neutral and none all return DUNNO

  # TODO XXX: prepend Received-SPF header.  Wietse says he will add that functionality soon.
}

# ----------------------------------------------------------
#                     plugin: testing
# ----------------------------------------------------------
sub testing {
  local %_ = @_;
  my %attr = %{ $_{attr} };

  if (lc address_stripped($attr{sender}) eq
      lc address_stripped($attr{recipient})
      and
      $attr{recipient} =~ /policyblock/) {

    syslog(info=>"%s: testing: will block as requested",
           $attr{queue_id});
    return "REJECT smtpd-policy blocking $attr{recipient}";
  }
  else {
    syslog(info=>"%s: testing: stripped sender=%s, stripped rcpt=%s",
           $attr{queue_id},
           address_stripped($attr{sender}),
           address_stripped($attr{recipient}),
           );

  }
  return "DUNNO";
}

sub address_stripped {
  # my $foo = localpart_lhs('foo+bar@baz.com'); # returns 'foo@baz.com'
  my $string = shift;
  for ($string) {
    s/[+-].*\@/\@/;
  }
  return $string;
}

Für den eigenen Mailserver tragen wir im DNS folgenden Wert ein: (Siehe bei SPF für weitere Informationen.)

 

mydomain.net.  3600  IN  TXT  "v=spf1 a mx ptr a:otherdomain.net a:mycompany.com -all

Bearbeitung von Kopfzeilen

Processing of Mail Headers

Das Filtern und Umgestalten der Kopfzeilen erfolgt mit awk. Es folgen die benötigten Steuerdateien.

 

$ vi /etc/postfix/extract_headers.awk
#
# (c) Thomas Bez <bez@tedesca.net> 2004
# http://www.bez.tedesca.net/homeoff/antispam.html
#
# mail.mydomain.net:/etc/postfix/extract_headers.awk

BEGIN { RS="\n\n+"; }
/.*/ { gsub("\n\t"," "); print; exit 0; }
END { exit 0; }
$ vi /etc/postfix/plus_habeas.awk
#
# (c) Thomas Bez <bez@tedesca.net> 2004
# http://www.bez.tedesca.net/homeoff/antispam.html
#
# mail.mydomain.net:/etc/postfix/plus_habeas.awk

/^$/ {
"/etc/postfix/auth_magic.sh mydomain.net" | getline signature
"date -R" | getline sendtime;
print "X-Server-Signature:", signature, ";", sendtime;
print "X-Habeas-SWE-1: winter into spring";
print "X-Habeas-SWE-2: brightly anticipated";
print "X-Habeas-SWE-3: like Habeas SWE (tm)";
print "X-Habeas-SWE-4: Copyright 2002 Habeas (tm)";
print "X-Habeas-SWE-5: Sender Warranted Email (SWE) (tm). The sender of this";
print "X-Habeas-SWE-6: email in exchange for a license for this Habeas";
print "X-Habeas-SWE-7: warrant mark warrants that this is a Habeas Compliant";
print "X-Habeas-SWE-8: Message (HCM) and not spam. Please report use of this";
print "X-Habeas-SWE-9: mark in spam to .";
print; exit 0; }
/.*/ { print; }
END { while (getline==1) print; }
$ vi /etc/postfix/plus_viruscheck.awk
#
# (c) Thomas Bez <bez@tedesca.net> 2004
# http://www.bez.tedesca.net/homeoff/antispam.html
#
# mail.mydomain.net:/etc/postfix/plus_viruscheck.awk

/^$/ {
"/usr/sbin/clamd -V" | getline clamversion;
"date -R" | getline scantime;
"hostname" | getline scanhost;
print "X-Virus-Scanned: by", scanhost, "with", clamversion, ";", scantime;
print "X-Virus-Status: No";
print; exit 0; }
/.*/ { print; }
END { while (getline==1) print; }
$ vi /etc/postfix/suppress_internals.awk
#
# (c) Thomas Bez <bez@tedesca.net> 2004
# http://www.bez.tedesca.net/homeoff/antispam.html
#
# mail.mydomain.net:/etc/postfix/suppress_internals.awk

BEGIN { skipreceived=0; }
/^$/ { print; exit 0; }
/^X-Mailer: / { next; }
/^X-Fetchmail-Retrieved: / { next; }
/^Delivered-To: / { next; }
/^X-SASL-AUTHENTICATED: / { next; }
/^X-Spam-Report: / { next; }
/^X-Spam-Status: / { next; }
/^Received: from mail.intra.mydomain.net/ { skipreceived=1; next; }
/^Received: / { if (skipreceived==0) print; next; }
/^      / { if (skipreceived==0) print; next; }
/^ / { if (skipreceived==0) print; next; }
/.*/ { skipreceived=0; print; }
END { while (getline==1) print; }

Die globale Konfiguration von Spamassassin erfolgt mit folgender Datei.

 

$ vi /etc/spamassassin/local.cf
#
# (c) Thomas Bez <bez@tedesca.net> 2004
# http://www.bez.tedesca.net/homeoff/antispam.html
#
# mail.mydomain.net:/etc/spamassassin/local.cf

report_safe            0
rewrite_subject        0
fold_headers           0
use_terse_report       0
spam_level_stars       1
check_mx_attempts      3
check_mx_delay         5
allow_user_rules       0
auto_whitelist_factor  0
use_bayes              1
bayes_auto_learn       0
required_hits          5.0

add_header spam Flag _YESNOCAPS_
add_header all Status _YESNO_, hits=_HITS_ required=_REQD_ tests=_TESTSSCORES(,)_ bayes=_BAYES_ relaysuntrusted=_RELAYSUNTRUSTED_ autolearn=_AUTOLEARN_ scanned=[_DATE_] version=_VERSION_ postmaster=_CONTACTADDRESS_

add_header all Level _STARS(*)_
add_header all Checker-Version SpamAssassin _VERSION_ (_SUBVERSION_) on _HOSTNAME_

header TO_undisclosed To =~ /undisclosed/i
describe TO_undisclosed To: undisclosed

header SASL_AUTHENTICATED X-SASL-AUTHENTICATED =~ /$auth_fingerprint/i
describe SASL_AUTHENTICATED X-SASL-AUTHENTICATED: $auth_fingerprint

body Postmaster_Magic /$auth_fingerprint/i
describe Postmaster_Magic Postmaster_Magic ($auth_fingerprint)

score Postmaster_Magic     -100
score HABEAS_SWE           -100
score HABEAS_VIOLATOR        16

Mailkonfiguration der Nutzer

User specific Mail Configuration

Jeder Nutzer kann externe Postfächer zum Absammeln der dort auflaufenden Email angeben. Die Nachrichten werden ebenfalls mit spamfilter.sh geprüft.

 
$ vi /home/myself/.fetchmailrc
#
# (c) Thomas Bez <bez@tedesca.net> 2004
# http://www.bez.tedesca.net/homeoff/antispam.html
#
# mail.mydomain.net:/home/myself/.fetchmailrc

poll pop.gmx.net protocol pop3 timeout 30
username "myself@gmx.net" password "pAssWoRd"
is "myself@mydomain.net" here smtpname "myself@mydomain.net"
mda "/etc/postfix/spamfilter.sh fetchmail myself@gmx.net %F %T"
antispam 450
flush nokeep
$ crontab -u myself -e
*/15    *    *    *    *    /usr/local/bin/fetchmail

Jeder Nutzer kann seine Mail nochmals nach eigenem Gutdünken bearbeiten und filtern.

 

$ vi /home/myself/.forward
"| /usr/bin/procmail -t"
$ vi /home/myself/.procmailrc
#
# (c) Thomas Bez  2004
# http://www.bez.tedesca.net/homeoff/antispam.html
#
# mail.mydomain.net:/home/myself/.procmailrc

:0:
* ^X-SASL-AUTHENTICATED
Maildir/

:0fw
* < 256000
| /usr/local/bin/spamc

:0:
* ^X-Spam-Flag: YES
{
  :0 c
  | /etc/postfix/logspam.sh spam

  :0:
  * ^X-Spam-Status: .*USER_IN_BLACKLIST=
  | cat >/dev/null

  :0:
  | /etc/postfix/nondelivery.sh null family spam
}

:0:
* ^X-Spam-Flag: YES
| cat >/dev/null

:0:
Maildir/

Die Nutzer dürfen aus Sicherheitsgründen keine zusätzlichen Regeln für Spamassassin aufstellen, sondern nur vordefinierten Regeln neue Bewertungen zuordnen oder Absendeadressen zu Schwarzen und Weißen Listen zuordnen.

 

$ vi /home/myself/.spamassassin/user_prefs
#
# (c) Thomas Bez <bez@tedesca.net> 2004
# http://www.bez.tedesca.net/homeoff/antispam.html
#
# mail.mydomain.net:/home/myself/.spamassassin/user_prefs

version_tag     myself
whitelist_from  *@goodguys.org
blacklist_from  *@badguys.com

Wer seine Mails nicht nochmals behandeln will, legt sie gleich im Postfach ab.

 

$ vi /home/hosted/hosteduser/.procmailrc
#
# (c) Thomas Bez <bez@tedesca.net> 2004
# http://www.bez.tedesca.net/homeoff/antispam.html
#
# mail.mydomain.net:/home/hosted/hosteduser/.procmailrc

:0:
Maildir/

Rückmeldungen an Absender und Internet Provider

Notifying Senders and Internet Providers

Rückmeldungen an wirkliche Spam-Absender sind natürlich vollkommen überflüssig. Was aber, wenn der Spammer gar kein Spammer war? Sondern unser Bekannter bei seiner Wortwahl unvorsichtig war und dem Bayes-Filter von Spamassassin auffällig geworden ist?

Durch nondelivery.sh wird die Nachricht nach verschiedenen Kriterien bewertet. Das Grundprinzip ist:

  1. Wenn die Nachricht einen niedrigen "Spamgrad" hat und die Emailadresse des Absenders erreichbar ist, erhält der Absender einen Hinweis, daß seine Nachricht (wahrscheinlich) den Empfänger nicht erreicht.
  2. Wenn die Nachricht einen so hohen "Spamgrad" hat, daß wir mit größter Wahrscheinlichkeit von tatsächlichem Spam ausgehen können, versuchen wir an den Internetprovider des Absenders (bzw. dem Besitzer des letzten Mailrelays in der Kette) eine Beschwerde zu schicken.
  3. Bei allen Nachrichten zwischendrin, bei denen wir uns nicht ganz sicher sind, tun wir nichts.
 
$ vi /etc/postfix/nondelivery.sh
#!/bin/sh
# (c) Thomas Bez <bez@tedesca.net> 2004
# http://www.bez.tedesca.net/homeoff/antispam.html
#
# mail.myself.net:/etc/postfix/nondelivery.sh
# parameter 1: from where (authorized or null)
# parameter 2: where to (family, hosted or null)
# (shift; shift)
# parameter 1: reason "spam", "spf", "unknown" or "virus"
# parameter 2: additional virus info (if reason=virus or reason=spf)
# parameter 3: $MAIL_FROM or empty
# parameter 4: $TO or empty
# parameter 5: $RCPT_COUNT or empty
# parameter 6: $HELO or empty
# parameter 7: $CLIENT or empty
# parameter 8: $TIME or empty

################################################################################################################
# initialize
################################################################################################################
cd /etc/postfix || exit 0
PATH=/etc/postfix:/usr/local/bin:/usr/sbin:$PATH; export PATH

mydomain=mydomain.net
my_received_id="mail.$mydomain (Postfix)"
notifier=notify@$mydomain
postmaster=postmaster@$mydomain
logname=nondelivery.sh
checkpoints=""
auth_magic=`