Kampf gegen Spam mit Postfix und SpamassassinTechnischer Stand: Jahr 2005. Dieser Artikel wird nicht mehr aktualisiert. |
Fighting Spam with Postfix and SpamassassinTechnical 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):
|
Revision: September 2004. Changes since last version (July/August 2004):
|
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:
Die Lösung, die ich vorstelle, basiert auf dem Betrieb eines
eigenen |
These are my basic requirements on a mail server:
The solution presented here is based on an own dedicated |
Voraussetzungen |
Prerequisites |
Softwareinstallation |
Software Installation |
|
Der Mailserver wird auf einem Linux-System installiert, ich empfehle
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
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. 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 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. 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 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
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
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. |
#!/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. |
#
# (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
# 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. GreylistingGreylisting 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. |
|
# # (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. |
|
# # (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. |
|
# # (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/
# # (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. |
|
# # (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
# # (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. |
|
# # (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
# # (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
# # (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
# # (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
# # (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
# # (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
# # (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
# # (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
# # (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
# # (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
# # (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
# # (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
# # (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
# # (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
# # (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. |
|
# # (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. |
|
#!/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. |
|
#!/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.) |
|
Bearbeitung von Kopfzeilen |
Processing of Mail Headers |
Das Filtern und Umgestalten der Kopfzeilen erfolgt mit awk. Es folgen die benötigten Steuerdateien. |
|
#
# (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; }
#
# (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; }
#
# (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; }
#
# (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. |
|
# # (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
# # (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. |
|
"| /usr/bin/procmail -t"
# # (c) Thomas Bez2004 # 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. |
|
# # (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/
#!/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=`auth_magic.sh $mydomain` subjectlength=28 # either notify: if the message was classified spam but there is a chance that this is a false positive or # if the message contains malicious code but was possibly not sent with malicious intent # or reportabuse: if there is no doubt that this was real spam or a malicious attack # or none: if we are not absolutely sure or # if notification or abuse reporting is not possible for some reason notify=yes reportabuse=yes check_whois=no abuse_manual=no abuse_guess=no infopostmaster=no
Die hier festgelegten Grenzwerte sind Erfahrungswerte. Es empfiehlt sich, die Standard-Scores von Spamassassin nicht zu sehr zu verbiegen, da sonst dieser Bewertungsalgorithmus irregeleitet werden könnte. Und es wäre peinlich, wenn wir uns bei einem ISP über Spam beschwerten, welches gar keines ist. |
|
# fine-tunable conditions maxrcptcount=2 # maxnotifyspamscore: maximum spam score for notifications # maxnotifyaddrscore: maximum address heuristics score for notifications # minreportspamscore: minimum spam score for abuse reports (spam) # minreportspamscorevrfy: minimum spam score for abuse reports (spam) if MAIL FROM could not be verified # minreportaddrscorevirus: minimum address heuristics score for abuse reports (virus) # minreportaddrscorevirusvrfy: minimum address heuristics score for abuse reports (virus) if MAIL FROM # could not be verified maxnotifyspamscore=12 maxnotifyaddrscore=4 minreportspamscore=20 minreportspamscorevrfy=15 minreportaddrscorevirus=6 minreportaddrscorevirusvrfy=3 # buffer message and headers TMP_headers=`mktemp -p /tmp` || exit 0 TMP_full_message=`mktemp -p /tmp` || exit 0 cat >$TMP_full_message sed -ne '/^$/q;p' <$TMP_full_message >$TMP_headers
Die Informationen über absendenden Mailserver, HELO-Identifikation etc. wird wieder zusammengesucht. Da nondelivery.sh nicht immer direkt von spamfilter.sh aus aufgerufen wird, können wir diese Werte nicht immer als Parameter mitgeben, sodaß wir sie eventuell wieder aus den Kopfzeilen zusammensetzen müssen. |
|
date=`grep -i '^Date:' $TMP_headers | sed -e 's/^[^:]*: //'`
ident=`grep -i '^Message-ID:' $TMP_headers | sed -e 's/^[^:]*: //;s/[><]//g' | tr -d '\n' | tr '[:cntrl:]' '?'`
# collect parameters
origin="$1"
destination="$2"
shift; shift
reason="$1"
addinfo="$2"
if [ X"$3" != X ]
then
mailfrom="$3"
rcptcount="$5"
helo="$6"
client_ip="$7"
time="$8"
else
if heloline=`grep 'Received: from .* by '"$my_received_id"' with' $TMP_headers`
then
# 1=helo 2=client_ip 5=rcpt_count_or_empty 7=mailfrom 8=time
# Received: from $HELO ($CLIENT_REV [$CLIENT]) by $my_received_id with $PROTOCOL id $IDENT
# for <$DESTINATION> count $RCPT_COUNT from <$SOURCE>; $TIME
helopattern='^Received: from \([^ ]*\) ([^ ]* [[]\([^]]*\)]) by '"$my_received_id"
helopattern="$helopattern"' with [^ ]* id [^ ]*'
helopattern="$helopattern"'\( for <[^>]*>\)\{0,1\}\( count \([[:digit:]]*\)\)\{0,1\}'
helopattern="$helopattern"'\( from <\([^>]*\)>\)\{0,1\}[^;]*; \(.*\)$'
helo=`echo "$heloline" | sed -e 's/'"$helopattern"'/\1/'`
client_ip=`echo "$heloline" | sed -e 's/'"$helopattern"'/\2/'`
rcptcount=`echo "$heloline" | sed -e 's/'"$helopattern"'/\5/'`
mailfrom=`echo "$heloline" | sed -e 's/'"$helopattern"'/\7/'`
time=`echo "$heloline" | sed -e 's/'"$helopattern"'/\8/'`
fi
[ X"$mailfrom" = X ] && \
mailfrom=`grep -i '^Return-Path:' $TMP_headers | sed -e 's/^[^:]*: //' | extractmail.sh single`
fi
# notifications got to MAIL FROM, no notification if mail address can not be extracted unambiguously
[ X"$mailfrom" = X ] && notify=no
[ X"$rcptcount" = X ] && rcptcount=0
if [ X"$client_ip" = X ]
then
logger -p mail.info -t $logname -- "error: no client info"
exit 1
fi
client_rev=`dig +short -x "$client_ip" | sed -e 's/^\(.*\)\.$/\1/'`
# extract additional header info
spamscore=`grep '^X-Spam-Level:' $TMP_headers | sed -e 's/[^*]//g' | wc -c | tr -d ' '`
[ X"$spamscore" = X ] && spamscore=0
receiver=`grep -i '^To:' $TMP_headers | sed -e 's/^[^:]*: //' | tr -d '\n' | tr '[:cntrl:]' '?'`
sender=`grep -i '^From:' $TMP_headers | sed -e 's/^[^:]*: //' | extractmail.sh single` || notify=no
subject=`grep -i '^Subject:' $TMP_headers | sed -e 's/^[^:]*: //;s/</{/g;s/>/}/g' | tr -d '\n' | tr '[:cntrl:]' '?'`
[ `expr length "$subject"` -gt $subjectlength ] && subject=`expr substr "$subject" 1 $subjectlength`"..."
Mit heuristischer Methode versuchen wir aus den CLIENT-, HELO- und MAIL FROM-Informationen zu ermitteln, ob der Absender seriös sein könnte, und vergeben dafür eine Punktzahl. |
|
################################################################################################################
# some heuristical consideration if the sender information is valid
################################################################################################################
# set only one of those:
score_from_differs=3
score_fromdomain_differs=5
# set any of those:
score_client_unknown=2
score_probable_dialup=2
score_client_unequal_helo=1
# set only one of those:
score_helo_void=3
score_numeric_helo=2
score_no_helo_ip=3
addrscore=0
scores=""
if [ X"$mailfrom" != X"$sender" ]
then
mailfromdom=`echo "$mailfrom" | sed -e 's/^[^@]*@\(.*\)$/\1/'`
senderdom=`echo "$sender" | sed -e 's/^[^@]*@\(.*\)$/\1/'`
if [ X"$mailfromdom" != X"$senderdom" ]
then
{ addrscore=`expr $addrscore + $score_fromdomain_differs`; scores="$scores fromdomain"; }
else
{ addrscore=`expr $addrscore + $score_from_differs`; scores="$scores from"; }
fi
fi
if [ X"$client_rev" = X -o X"$client_rev" = Xunknown ]
then
{ addrscore=`expr $addrscore + $score_client_unknown`; scores="$scores client=unknown"; }
else
echo "$client_rev" | grep -q '^.*-.*-.*-.*$' && \
{ addrscore=`expr $addrscore + $score_probable_dialup`; scores="$scores dialup"; }
fi
if [ X"$helo" = X ]
then
{ addrscore=`expr $addrscore + $score_helo_void`; scores="$scores nohelo"; }
heloip=""
{ addrscore=`expr $addrscore + $score_client_unequal_helo`; scores="$scores client!=helo"; }
elif echo "$helo" | grep -q '[0-9]$'
then
{ addrscore=`expr $addrscore + $score_numeric_helo`; scores="$scores numhelo"; }
heloip="$helo"
[ X"$helo" != X"$client_ip" ] && \
{ addrscore=`expr $addrscore + $score_client_unequal_helo`; scores="$scores client!=helo"; }
else
heloip=`dig +short "$helo"`
[ X"$heloip" = X ] && \
{ addrscore=`expr $addrscore + $score_no_helo_ip`; scores="$scores heloip"; }
[ X"$helo" != X"$client_rev" ] && \
{ addrscore=`expr $addrscore + $score_client_unequal_helo`; scores="$scores client!=helo"; }
fi
[ $addrscore -gt 9 ] && addrscore=9
Mit dem Kommando vrfy wird eine Emailadresse verifiziert, ohne daß wir wirklich eine Testmail senden müssen. Wenn eine Nachricht als Spam aussortiert wurde, weil der Absender auf der Schwarzen Liste eine unserer Nutzer steht, senden wir weder Mitteilung noch Beschwerde aus. Es könnte sich um Personen handeln, die nicht erfahren sollen, daß sie nicht mehr beliebt sind. |
|
################################################################################################################
# check all conditions if notification or reporting is necessary and possible
################################################################################################################
# if there are too many RCPT TO addresses, this is most likely spam
[ $rcptcount -gt $maxrcptcount ] && notify=no
if [ $spamscore -gt $maxnotifyspamscore -o $addrscore -gt $maxnotifyaddrscore ]
then
checkpoints=${checkpoints}A
notify=no
fi
addinfo1=""
vrfy_message=`vrfy -hlsn "$mailfrom"`
vrfy_result=$?
if [ $vrfy_result != 0 ]
then
checkpoints=${checkpoints}B
notify=no
fi
# do not report abuse for hosted user destinations, except for SPF violations
[ $notify = yes -o X"$destination" != Xfamily ] && reportabuse=no
case $reason in
unknown )
reportabuse=no
;;
spam )
checkpoints=${checkpoints}C
if [ $vrfy_result != 0 ]
then
[ $spamscore -lt $minreportspamscorevrfy ] && reportabuse=no
else
[ $spamscore -lt $minreportspamscore ] && reportabuse=no
fi
# blacklisted senders are not spammers,
# usually these are parties with which we were but do not want to stay in contact
if grep -i '^X-Spam-Status: .*USER_IN_BLACKLIST=' $TMP_headers 1>/dev/null 2>/dev/null
then
checkpoints=${checkpoints}Z
notify=no
reportabuse=no
fi
;;
virus )
checkpoints=${checkpoints}D
if [ $vrfy_result != 0 ]
then
[ $addrscore -lt $minreportaddrscorevirusvrfy ] && reportabuse=no
else
[ $addrscore -lt $minreportaddrscorevirus ] && reportabuse=no
fi
;;
spf )
checkpoints=${checkpoints}E
notify=no
reportabuse=yes
;;
esac
if [ X"$origin" = Xauthorized ]
then
# of course, do not report abuse for our own users
checkpoints=${checkpoints}F
reportabuse=no
else
checkpoints=${checkpoints}G
if match_wildcard.sh "$helo" own_domains.rxp 1>/dev/null 2>/dev/null
then
# always report abuse if someone uses our own address for HELO
checkpoints=${checkpoints}H
notify=no
reportabuse=yes
addinfo2="forged HELO identification: $helo"
fi
fi
if [ $reportabuse = yes ] && match_wildcard.sh "$helo" helo_whitelist.rxp 1>/dev/null 2>/dev/null
then
checkpoints=${checkpoints}N
reportabuse=no
fi
if [ $reportabuse = yes ]
then
checkpoints=${checkpoints}I
notify=no
[ X"$client_rev" = X -o X"$client_rev" = Xunknown ] && check_whois=yes
fi
abuse_mail=""
Die einzige Information zur Email, auf die wir uns fast sicher verlassen können, ist die IP-Adresse des absendenden Clients. Über Reverse DNS hat bereits Postfix dazu (wenn möglich) einen Domainnamen gefunden. Mit Hilfe des Network Abuse Clearinghouse suchen wir dazu eine Emailadresse, über die dem Provider der Mißbrauch seines Netzes mitgeteilt werden kann. |
|
abuse_mail=""
if [ $reportabuse = yes -a $check_whois = no ]
then
checkpoints=${checkpoints}J
# get abuse reporting address for sending client from www.abuse.net
abuse_mail=`dig +short $client_rev.contacts.abuse.net TXT 2>/dev/null | head -n 1 | sed -e 's/"//g;s/;//g'`
if [ `echo "$abuse_mail" | wc -w` -eq 1 ]
then
# check if abuse reporting address responds
if vrfy -hlsn "$abuse_mail" >/dev/null 2>/dev/null
then
checkpoints=${checkpoints}K
else
checkpoints=${checkpoints}L
check_whois=yes
fi
else
checkpoints=${checkpoints}M
check_whois=yes
fi
fi
Leider scheitert die Methode über das Network Abuse Clearinghouse außerordentlich oft. Wir wollen die Provider, die es nicht nötig habe, sich dort zu registrieren, aber nicht in Ruhe lassen. Wenn jemand etwas gegen Netzmißbrauch tun kann, sind es schließlich die Provider. Wir benutzen die Information, die das Kommando whois liefert, um eine geeignete Adresse zur Rückmeldung herauszufischen. Dazu wird, beginnend mit whois.arin.net, zuerst das richtige Whois-Register gesucht. |
|
################################################################################################################
# derive abuse reporting information from WHOIS entries available
################################################################################################################
if [ $check_whois = yes ]
then
checkpoints=${checkpoints}O
abuse_mail=""
TMP_whois=`mktemp -p /tmp` || exit 0
# get first information from ARIN
whois_server=whois.arin.net
whois -h $whois_server +"$client_ip" >$TMP_whois
# figure out referenced regional source WHOIS registries
if grep -Ei 'ReferralServer.*whois\.ripe\.net' $TMP_whois 1>/dev/null 2>/dev/null
then
checkpoints=${checkpoints}'*'
whois_server=whois.ripe.net
whois -h $whois_server "$client_ip" >$TMP_whois
elif grep -Ei 'ReferralServer.*whois\.lacnic\.net' $TMP_whois 1>/dev/null 2>/dev/null
then
checkpoints=${checkpoints}'+'
whois_server=whois.lacnic.net
whois -h $whois_server "$client_ip" >$TMP_whois
elif grep -Ei 'ReferralServer.*whois\.apnic\.net' $TMP_whois 1>/dev/null 2>/dev/null
then
checkpoints=${checkpoints}'-'
whois_server=whois.apnic.net
whois -h $whois_server "$client_ip" >$TMP_whois
fi
# find referenced national registries
if grep -Ei '^remarks.*http://whois\.nic\.or\.kr/english/index\.html' $TMP_whois 1>/dev/null 2>/dev/null
then
checkpoints=${checkpoints}1
whois_server=whois.nic.or.kr
whois -h $whois_server "$client_ip" >$TMP_whois
elif grep -Ei 'Allocated to KRNIC Member' $TMP_whois 1>/dev/null 2>/dev/null
then
if grep -Ei 'KRNIC Whois Database at' $TMP_whois 1>/dev/null 2>/dev/null
then
checkpoints=${checkpoints}2
whois_server=whois.krnic.net
whois -h $whois_server "$client_ip" >$TMP_whois
fi
elif grep -Ei 'Korea under APNIC' $TMP_whois 1>/dev/null 2>/dev/null
then
if grep -Ei 'KRNIC Whois DB' $TMP_whois 1>/dev/null 2>/dev/null
then
checkpoints=${checkpoints}3
whois_server=whois.nic.or.kr
whois -h $whois_server "$client_ip" >$TMP_whois
fi
elif grep -Ei 'mirrored by APNIC' $TMP_whois 1>/dev/null 2>/dev/null
then
if grep -Ei 'TWNIC whois server at' $TMP_whois 1>/dev/null 2>/dev/null
then
checkpoints=${checkpoints}4
whois_server=whois.twnic.net
whois -h $whois_server "$client_ip" >$TMP_whois
elif grep -Ei 'JPNIC whois server at' $TMP_whois 1>/dev/null 2>/dev/null
then
checkpoints=${checkpoints}5
whois_server=whois.nic.ad.jp
whois -h $whois_server "$client_ip" >$TMP_whois
fi
elif grep -Ei 'assigned to Brazilian users' $TMP_whois 1>/dev/null 2>/dev/null
then
if grep -Ei 'can be found at the WHOIS server' $TMP_whois 1>/dev/null 2>/dev/null
then
checkpoints=${checkpoints}6
whois_server=whois.nic.br
whois -h $whois_server "$client_ip" >$TMP_whois
fi
fi
Das am besten passende Whois-Register wird nach einer geeigneten Mailadresse abgesucht. |
|
# scan for abuse notification address
while [ X"$abuse_mail" = X ]
do
if grep -Ei 'spam[^[:space:]]*@' $TMP_whois 1>/dev/null 2>/dev/null
then
checkpoints=${checkpoints}a
abuse_mail=`cat $TMP_whois | \
grep -Ei 'spam[^[:space:]]*@' 2>/dev/null | head -n 1 | \
sed -e 's/^.*[[:space:]]\([^[:space:]]*@[^[:space:]]*\).*$/\1/'`
fi
[ X"$abuse_mail" != X ] && break
if grep -Ei 'abuse[^[:space:]]*@' $TMP_whois 1>/dev/null 2>/dev/null
then
checkpoints=${checkpoints}b
abuse_mail=`cat $TMP_whois | \
grep -Ei 'abuse[^[:space:]]*@' 2>/dev/null | head -n 1 | \
sed -e 's/^.*[[:space:]]\([^[:space:]]*@[^[:space:]]*\).*$/\1/'`
fi
[ X"$abuse_mail" != X ] && break
if grep -Ei '^[[] ISP Network Abuse Contact Information ]' $TMP_whois 1>/dev/null 2>/dev/null
then
checkpoints=${checkpoints}c
abuse_mail=`cat $TMP_whois | \
sed -ne '/^[[] ISP Network Abuse Contact Information ]/,$p' | \
grep -Ei '^e-mail' 2>/dev/null | head -n 1 | \
sed -e 's/^.*[[:space:]]\([^[:space:]]*@[^[:space:]]*\).*$/\1/' 2>/dev/null`
fi
[ X"$abuse_mail" != X ] && break
if grep -Ei 'spam.*issues' $TMP_whois 1>/dev/null 2>/dev/null
then
checkpoints=${checkpoints}d
abuse_mail=`cat $TMP_whois | \
sed -ne '/[Ss][Pp][Aa][Mm].*issues/,$p' | \
grep -i 'send mail' 2>/dev/null | head -n 1 | \
sed -e 's/^.*[[:space:]]\([^[:space:]]*@[^[:space:]]*\).*$/\1/' 2>/dev/null`
fi
[ X"$abuse_mail" != X ] && break
if grep -Ei 'abuse.*issues' $TMP_whois 1>/dev/null 2>/dev/null
then
checkpoints=${checkpoints}e
abuse_mail=`cat $TMP_whois | \
sed -ne '/[Aa][Bb][Uu][Ss][Ee].*issues/,$p' | \
grep -i 'send mail' 2>/dev/null | head -n 1 | \
sed -e 's/^.*[[:space:]]\([^[:space:]]*@[^[:space:]]*\).*$/\1/' 2>/dev/null`
fi
[ X"$abuse_mail" != X ] && break
if grep -Ei '^OrgAbuseEmail' $TMP_whois 1>/dev/null 2>/dev/null
then
checkpoints=${checkpoints}f
abuse_mail=`cat $TMP_whois | \
grep -Ei '^OrgAbuseEmail' 2>/dev/null | head -n 1 | \
sed -e 's/^.*[[:space:]]\([^[:space:]]*@[^[:space:]]*\).*$/\1/' 2>/dev/null`
fi
[ X"$abuse_mail" != X ] && break
if grep -Ei '^person:[[:space:]]*Host Master' $TMP_whois 1>/dev/null 2>/dev/null
then
checkpoints=${checkpoints}g
abuse_mail=`cat $TMP_whois | \
sed -ne '/^[Pp]erson:.*[Hh]ost [Mm]aster/,$p' | \
grep -Ei '^e-mail' 2>/dev/null | head -n 1 | \
sed -e 's/^.*[[:space:]]\([^[:space:]]*@[^[:space:]]*\).*$/\1/' 2>/dev/null`
fi
[ X"$abuse_mail" != X ] && break
if grep -Ei '^person' $TMP_whois 1>/dev/null 2>/dev/null
then
checkpoints=${checkpoints}h
abuse_mail=`cat $TMP_whois | \
sed -ne '/^[Pp]erson/,$p' | \
grep -Ei '^e-mail' 2>/dev/null | head -n 1 | \
sed -e 's/^.*[[:space:]]\([^[:space:]]*@[^[:space:]]*\).*$/\1/' 2>/dev/null`
fi
[ X"$abuse_mail" != X ] && break
if grep -Ei '^TechEmail' $TMP_whois 1>/dev/null 2>/dev/null
then
checkpoints=${checkpoints}i
abuse_mail=`cat $TMP_whois | \
grep -Ei '^TechEmail' 2>/dev/null | head -n 1 | \
sed -e 's/^.*[[:space:]]\([^[:space:]]*@[^[:space:]]*\).*$/\1/' 2>/dev/null`
fi
[ X"$abuse_mail" != X ] && break
if grep -Ei '[[]Reply Mail]' $TMP_whois 1>/dev/null 2>/dev/null
then
checkpoints=${checkpoints}j
abuse_mail=`cat $TMP_whois | \
grep -Ei '[[]Reply Mail]' 2>/dev/null | head -n 1 | \
sed -e 's/^.*[[:space:]]\([^[:space:]]*@[^[:space:]]*\).*$/\1/' 2>/dev/null`
fi
[ X"$abuse_mail" != X ] && break
if grep -i 'hostmaster@' $TMP_whois 1>/dev/null 2>/dev/null
then
checkpoints=${checkpoints}k
abuse_mail=`cat $TMP_whois | \
grep -i 'hostmaster@' 2>/dev/null | head -n 1 | \
sed -e 's/^.*[[:space:]]\([^[:space:]]*@[^[:space:]]*\).*$/\1/' 2>/dev/null`
fi
[ X"$abuse_mail" != X ] && break
if grep -i 'admin@' $TMP_whois 1>/dev/null 2>/dev/null
then
checkpoints=${checkpoints}l
abuse_mail=`cat $TMP_whois | \
grep -i 'admin@' 2>/dev/null | head -n 1 | \
sed -e 's/^.*[[:space:]]\([^[:space:]]*@[^[:space:]]*\).*$/\1/' 2>/dev/null`
fi
[ X"$abuse_mail" != X ] && break
if grep -Ei '^Email' $TMP_whois 1>/dev/null 2>/dev/null
then
checkpoints=${checkpoints}m
abuse_mail=`cat $TMP_whois | \
grep -Ei '^Email' 2>/dev/null | head -n 1 | \
sed -e 's/^.*[[:space:]]\([^[:space:]]*@[^[:space:]]*\).*$/\1/' 2>/dev/null`
fi
[ X"$abuse_mail" != X ] && break
if grep '@' $TMP_whois 1>/dev/null 2>/dev/null
then
checkpoints=${checkpoints}n
abuse_mail=`cat $TMP_whois | \
grep '@' 2>/dev/null | head -n 1 | \
sed -e 's/^.*[[:space:]]\([^[:space:]]*@[^[:space:]]*\).*$/\1/' 2>/dev/null`
fi
[ X"$abuse_mail" != X ] && break
Für Provider, die nicht einmal im Whois-Eintrag eine Mailadresse angeben, pflegen wir selbst eine kleine Liste in special_abuse.rxp. |
|
# providers which do not publish abuse reporting addresses in their WHOIS record
first_line=`head -n 1 $TMP_whois`
[ X"$first_line" != X ] && abuse_mail=`match_wildcard.sh "$first_line" special_abuse.rxp`
if [ X"$abuse_mail" != X ]
then
checkpoints=${checkpoints}x
abuse_manual=yes
break
fi
if cat $TMP_whois | grep -Ei '^IMPSAT CORP' 1>/dev/null 2>/dev/null
then
checkpoints=${checkpoints}y
abuse_mail="abuse@impsat.net"
abuse_manual=yes
elif [ X"$client_rev" != X -a "$client_rev" != unknown ] && \
echo "$client_rev" | grep -E '^.*\.[^.]*\.[^.]*$' 1>/dev/null 2>/dev/null
then
checkpoints=${checkpoints}z
abuse_mail=`echo "$client_rev" | sed -e 's/^.*\.\([^.]*\.[^.]*\)$/abuse@\1/'`
abuse_guess=yes
fi
break
done
if [ X"$abuse_mail" = X ]
then
checkpoints=${checkpoints}P
abuse_mail="NONE"
reportabuse=no
infopostmaster=yes
else
if echo "$abuse_mail" | grep -E '^mailto:' 1>/dev/null 2>/dev/null
then
abuse_mail=`echo "$abuse_mail" | sed -e 's/^mailto:\(.*\)$/\1/'`
fi
if vrfy -hlsn "$abuse_mail" >/dev/null 2>/dev/null
then
checkpoints=${checkpoints}Q
else
checkpoints=${checkpoints}R
abuse_mail="DEAD:$abuse_mail"
reportabuse=no
fi
fi
fi
return_mail="$mailfrom"
[ $notify = yes ] || return_mail=""
logger -p mail.info -t $logname -- \
"reason=<$reason> mail_from=<$mailfrom> subject=<$subject> helo=<$helo> client_ip=<$client_ip> time=<$time> origin=<$origin> destination=<$destination> rcptcount=<$rcptcount> spamscore=<$spamscore> heuristics=<$addrscore> verify=<$vrfy_result> notify=<$return_mail> reportabuse=<$abuse_mail> check=<$checkpoints>"
Wenn wir festgestellt haben, daß der Absender der Email benachrichtigt werden sollte, konstruieren wir für ihn eine Benachrichtigung und senden sie ab. Das Anhängen unserer Serversignatur bewirkt, daß diese Benachrichtigung ihrerseits nicht im Spamfilter hängenbleiben kann. Jede Antwort auf diesen Hinweis unterbinden wir dadurch, daß wir Postfix Nachrichten an notify@mydomain.net zurückweisen lassen. |
|
if [ $notify = yes ]
then
TMP_notification=`mktemp -p /tmp` || exit 0
(
# use a dead sender address for this notificaton in order to prevent bounce mails
echo "From: $notifier"
echo "To: $return_mail"
echo "Cc: $postmaster"
echo "Subject: Nachricht nicht zugestellt / Message not delivered"
echo "MIME-Version: 1.0"
echo "Content-Type: multipart/mixed; boundary=nextpart"
echo "Importance: normal"
echo "Content-Type: text/plain; charset=iso-8859-1"
echo "Content-Transfer-Encoding: quoted-printable"
echo
# echo '----+----1----+----2----+----3----+----4----+----5----+----6----+----7--'
cat <<-EOF
Dies ist eine automatisch erzeugte Meldung von
$postmaster an $return_mail.
Ihre Nachricht vom $date
von $sender an $receiver
konnte nicht zugestellt werden.
EOF
case $reason in
spam)
echo "Grund: Die Nachricht wurde als elektronische Werbung (Spam) erkannt."
;;
spf)
echo "Grund: Die Nachricht tr=E4gt eine unerlaubte Absendeadresse."
echo "$addinfo" | sed -e 's/=/=3D/g'
;;
unknown)
echo "Grund: Der angegebene Empf=E4nger ist nicht bekannt."
;;
virus)
echo "Grund: Die Nachricht ist mit sch=E4dlichem Code infiziert."
echo "$addinfo" | sed -e 's/=/=3D/g'
;;
esac
echo
# echo '----+----1----+----2----+----3----+----4----+----5----+----6----+----7--'
[ $reason = spam ] && cat <<-EOF
Entsprechend der Disposition des Postverwalters und des Empf=E4ngers
wird die Nachricht automatisch gel=F6scht oder archiviert. Bitte gehen
Sie davon aus, da=DF die Nachricht nicht den gew=FCnschten Adressaten
erreicht.
#######################################################################
# Wenn Ihre Nachricht keine Massensendung war und f=FCr den Empf=E4nger
# pers=F6nlich bestimmt war, sollten Sie auf anderem Wege Kontakt
# mit dem Empf=E4nger aufnehmen und ihn um Aufnahme Ihrer Adresse
# $sender in die Wei=DFe Liste bitten.
#######################################################################
Benachrichtigen Sie $postmaster, wenn Sie meinen, da=DF Ihre
Nachricht ungerechtfertigt zur=FCckgewiesen wurde. Bitte f=FCgen Sie
den Text dieser Email Ihrer Antwort bei. Bitte antworten Sie nicht
direkt auf diese Information, sondern senden Sie Ihre Nachricht an
$postmaster.
EOF
# echo '----+----1----+----2----+----3----+----4----+----5----+----6----+----7--'
cat <<-EOF
------------------------------------------------------------------------
This is an automatically generated notification from
$postmaster to $return_mail.
Your message of $date
from $sender to $receiver
could not be delivered.
EOF
case $reason in
spam)
echo "Reason: Your message was classified as UCE (spam)."
;;
spf)
echo "Reason: The sender address is not legitimate."
echo "$addinfo" | sed -e 's/=/=3D/g'
;;
unknown)
echo "Reason: The recipient address is unknown."
;;
virus)
echo "Reason: The message is infected with maliciuos code."
echo "$addinfo" | sed -e 's/=/=3D/g'
;;
esac
echo
[ $reason = spam ] && cat <<-EOF
According to the disposition of postmaster and recipient, the message
will be automatically deleted or quarantined. You should assume that
your message will not reach its desired destination.
#######################################################################
# If your message was directed at the receiver directly and was not
# bulk mail, you should contact the receiver by other means and ask to
# become whitelisted with your address $sender.
#######################################################################
Notify $postmaster if you feel that the the rejection
of your message is unjustified. Please add the text of this message
to your reply. Please do not respond directly to this information,
send your mail to $postmaster instead.
EOF
# echo '----+----1----+----2----+----3----+----4----+----5----+----6----+----7--'
cat <<-EOF
------------------------------------------------------------------------
Message headers:
EOF
# take mailheaders from standard input
# encode '=' to comply with Quoted-Printable
# suppress some internals from notification mail
mawk -f suppress_internals.awk <$TMP_headers | sed -e 's/=/=3D/g'
# include my server signature here, replies containing the magic will pass through the spam scanner
cat <<-EOF
------------------------------------------------------------------------
Signature: $auth_magic
Contact: $postmaster
Please include this message and above signature line in your replies.
EOF
) | cat >$TMP_notification
cat $TMP_notification | /usr/sbin/sendmail -i -f $notifier -- "$return_mail"
# store a copy of this message
cat $TMP_notification | /usr/sbin/sendmail -i -f $notifier -- "$postmaster"
rm $TMP_notification
War die empfangene Email zweifelsfrei Spam, versuchen wir den Internetprovider des Absenders zu kontaktieren. Wir hängen alle verfügbare Information an außer im Fall virenverseuchter Mails. Wir identifizieren uns als Postmaster und warten ab, ob der Internetprovider sich bei uns meldet. Etliche tun das mit einer automatisierten Antwort. So unterhalten sich Automaten mit Automaten. |
|
elif [ $reportabuse = yes ]
then
TMP_report=`mktemp -p /tmp` || exit 0
(
# use postmaster as sender here in order to get automated ISP abuse department replies
echo "From: $postmaster"
echo "To: $abuse_mail"
echo "Cc: $postmaster"
echo "Subject: Network abuse notification"
echo "MIME-Version: 1.0"
echo "Content-Type: multipart/mixed; boundary=nextpart"
echo "Importance: normal"
echo "Content-Type: text/plain; charset=iso-8859-1"
echo "Content-Transfer-Encoding: quoted-printable"
echo
# echo '----+----1----+----2----+----3----+----4----+----5----+----6----+----7--'
cat <<-EOF
This automatically generated message shall inform you about a case of
network abuse in your area of concern.
EOF
case $reason in
spam)
echo "The message below was classified as UCE (spam)."
;;
spf)
echo "According to SPF test the sender address is not legitimate."
;;
virus)
echo "The message is infected with maliciuos code $addinfo." | sed -e 's/=/=3D/g'
;;
esac
cat <<-EOF
The message was sent at $time,
sending client was $client_ip.
EOF
if [ $abuse_manual = yes ]
then
# echo '----+----1----+----2----+----3----+----4----+----5----+----6----+----7--'
cat <<-EOF
It is good common practice to maintain usable WHOIS records. Since no
abuse reporting information could be extracted from $whois_server,
your email $abuse_mail was retrieved from a public
abuse registry service.
EOF
elif [ $abuse_guess = yes ]
then
cat <<-EOF
It is good common practice to maintain usable WHOIS records. Since no
abuse reporting information could be extracted from $whois_server,
your email $abuse_mail was automatically derived
from the IP reverse mapping $client_rev.
EOF
elif [ $check_whois = yes ]
then
cat <<-EOF
Your email $abuse_mail was automatically extracted
from your $whois_server database entry.
EOF
else
cat <<-EOF
Your email $abuse_mail is recorded as an
abuse reporting address for $client_rev
by the Network Abuse Clearinghouse (http://www.abuse.net).
EOF
fi
cat <<-EOF
We suggest that you follow up this abuse of your network resources
or check with your customers for possible spambot infections of their
infrastructure.
Please contact $postmaster if you have any questions.
Kindly attach this message to your response.
EOF
if [ $reason = virus ]
then
# remove the infected body, it would be filtered out by the ISP's mail server anyway
cat <<-EOF
Message headers (infected body removed):
------ snip ------------------------------------------------------------
EOF
mawk -f suppress_internals.awk <$TMP_headers | sed -e 's/=/=3D/g'
cat <<-EOF
------ snip ------------------------------------------------------------
(End of message headers)
EOF
else
cat <<-EOF
Complete message:
------ snip ------------------------------------------------------------
EOF
mawk -f suppress_internals.awk <$TMP_full_message | sed -e 's/=/=3D/g'
cat <<-EOF
------ snip ------------------------------------------------------------
(End of message)
EOF
fi
if [ $check_whois = yes ]
then
cat <<-EOF
WHOIS entry for $client_ip from $whois_server:
------ snip ------------------------------------------------------------
EOF
cat $TMP_whois | sed -e 's/=/=3D/g'
cat <<-EOF
------ snip ------------------------------------------------------------
EOF
fi
# include my server signature here, replies containing the magic will pass through the spam scanner
cat <<-EOF
Signature: $auth_magic
Contact: $postmaster
Please include this message and above signature line in your replies.
EOF
) | cat >$TMP_report
cat $TMP_report | /usr/sbin/sendmail -i -f $notifier -- "$abuse_mail"
# store a copy of this message
cat $TMP_report | /usr/sbin/sendmail -i -f $notifier -- "$postmaster"
rm $TMP_report
fi
Wir lassen uns informieren, falls es nicht gelungen ist, eine Adresse für die Abuse-Meldung zu finden. Wir können dann unser Script nachbessern. In der Regel wird ein weiterer Eintrag zu special_abuse.rxp hinzuzufügen sein. |
|
if [ $infopostmaster = yes ]
then
(
echo "From: $notifier"
echo "To: $postmaster"
echo "Subject: Could not send abuse report"
echo "MIME-Version: 1.0"
echo "Content-Type: multipart/mixed; boundary=nextpart"
echo "Importance: normal"
echo "Content-Type: text/plain; charset=iso-8859-1"
echo "Content-Transfer-Encoding: quoted-printable"
echo
# echo '----+----1----+----2----+----3----+----4----+----5----+----6----+----7--'
cat <<-EOF
Abuse report could not be sent.
Reporting address detected: $abuse_mail.
Check process: <$checkpoints>.
EOF
case $reason in
spam)
echo "The message below was classified as UCE (spam)."
;;
spf)
echo "According to SPF test the sender address is not legitimate."
;;
virus)
echo "The message is infected with maliciuos code $addinfo." | sed -e 's/=/=3D/g'
;;
esac
if [ $reason = virus ]
then
# remove the infected body, it would be filtered out by the ISP's mail server anyway
cat <<-EOF
Message headers (infected body removed):
------ snip ------------------------------------------------------------
EOF
mawk -f suppress_internals.awk <$TMP_headers | sed -e 's/=/=3D/g'
cat <<-EOF
------ snip ------------------------------------------------------------
(End of message headers)
EOF
else
cat <<-EOF
Complete message:
------ snip ------------------------------------------------------------
EOF
mawk -f suppress_internals.awk <$TMP_full_message | sed -e 's/=/=3D/g'
cat <<-EOF
------ snip ------------------------------------------------------------
(End of message)
EOF
fi
cat <<-EOF
Reverse domain lookup for $client_ip from $whois_server:
------ snip ------------------------------------------------------------
EOF
dig -x "$client_ip" | sed -e 's/=/=3D/g'
cat <<-EOF
------ snip ------------------------------------------------------------
WHOIS entry for $client_ip from $whois_server:
------ snip ------------------------------------------------------------
EOF
cat $TMP_whois | sed -e 's/=/=3D/g'
cat <<-EOF
------ snip ------------------------------------------------------------
Signature: $auth_magic
Contact: $postmaster
Please include this message and above signature line in your replies.
EOF
) | /usr/sbin/sendmail -i -f $notifier -- "$postmaster"
fi
[ $check_whois = yes ] && rm -f $TMP_whois
rm -f $TMP_headers $TMP_full_message
exit 0
Dies sind die Steuerdateien, die nondelivery.sh benötigt: |
|
# # (c) Thomas Bez <bez@tedesca.net> 2004 # http://www.bez.tedesca.net/homeoff/antispam.html # # mail.mydomain.net:/etc/postfix/helo_whitelist.rxp ^.*\.intra\.mydomain\.net$
# # (c) Thomas Bez <bez@tedesca.net> 2004 # http://www.bez.tedesca.net/homeoff/antispam.html # # mail.mydomain.net:/etc/postfix/special_abuse.rxp # Format: "WHOIS entry search pattern" abuse_address@isp.net # "^Qwest Communications" abuse@qwest.net
# # (c) Thomas Bez <bez@tedesca.net> 2004 # http://www.bez.tedesca.net/homeoff/antispam.html # # mail.mydomain.net:/etc/postfix/map_smtpd_sender_accounts # MAIL FROM addresses with their respective local accounts for automatic b/w list management myself@mydomain.net myself myself@otherdomain.net myself otheruser@mydomain.net otheruser
Weitere benötigte Shellscripte folgen hier. |
|
#!/bin/sh
# (c) Thomas Bez <bez@tedesca.net> 2004
# http://www.bez.tedesca.net/homeoff/antispam.html
#
# mail.mydomain.net:/etc/postfix/add_blackwhitelist.sh
# add a black or white list entry
# parameter 1: "black" or "white"
# parameter 2: local mail user address
# parameter 3: mail address to be blacklisted
logname=add_blackwhitelist.sh
cd /etc/postfix || exit 1
pattern1=`echo "$2" | sed -e 's/\./\\./g'`
username=`grep -E "^$pattern1[[:space:]]" map_smtpd_sender_accounts 2>/dev/null | head -n 1 | \
sed -e 's/^[^[:space:]]*[[:space:]]*\(.*\)$/\1/'` || exit 1
logger -t $logname "add to ${1}list of $username: $3"
if [ ! -d /home/$username -o ! -f /home/$username/.spamassassin/user_prefs ]
then
logger -t $logname "... $username directory error"
exit 1
fi
pattern2=`echo "$3" | sed -e 's/\./\\./g;s/\*/.*/g'`
if grep -E "^[[:space:]]*(black|white)list_from[[:space:]]*$pattern2[[:space:]]*$" \
/home/$username/.spamassassin/user_prefs >/dev/null 2>/dev/null
then
logger -t $logname "... $3 already listed"
exit 0
fi
echo "# `date`" >>/home/$username/.spamassassin/add_blackwhitelist
echo ${1}list_from $3 >>/home/$username/.spamassassin/add_blackwhitelist
echo ${1}list_from $3 >>/home/$username/.spamassassin/user_prefs
logger -t $logname "... $3 added"
exit 0
#!/bin/sh # (c) Thomas Bez <bez@tedesca.net> 2004 # http://www.bez.tedesca.net/homeoff/antispam.html # # mail.mydomain.net:/etc/postfix/auth_magic.sh # parameter 1: mydomain servername=mail.$1 mailcert=/etc/ssl/certs/$servername.crt fingerprint=`openssl x509 -noout -fingerprint -in $mailcert | sed -e 's/MD5 Fingerprint=//'` echo "$servername ($fingerprint)" exit 0
#!/bin/sh # (c) Thomas Bez <bez@tedesca.net> 2004 # http://www.bez.tedesca.net/homeoff/antispam.html # # mail.mydomain.net:/etc/postfix/match_plain.sh # matches plain or wildcard names against a list containing plain entries # parameter 1: actual address (e.g. myself@mydomain.net or *@mydomain.net) # parameter 2: match list filename (entries e.g. myself@mydomain.net) rline=`echo $1 | sed -e 's/\./\\\\./g;s/\*/.*/g;s/^/^/;s/$/$/'` grep -v '^#' $2 | grep -v '^$' | grep -i "$rline" exit $?
#!/bin/sh
# (c) Thomas Bez <bez@tedesca.net> 2004
# http://www.bez.tedesca.net/homeoff/antispam.html
#
# mail.mydomain.net:/etc/postfix/match_wildcard.sh
# matches plain names against a list containing plain or wildcard entries (preformatted regex)
# parameter 1: actual address (e.g. myself@mydomain.net)
# parameter 2: match list filename (entries are preformatted regular expressions, e.g. ^.*@mydomain\.net$)
# match is analysed until first white space character, return value is string after white space
# match/return entire line if no white space contained
TEMP1=`mktemp -p /tmp` || exit 1;
echo "$1" >$TEMP1
IFS='
'
for line in `grep -v '^#' "$2" | grep -v '^$'`
do
case "$line" in
"'"* )
match=`echo "$line" | sed -e 's/^'"'"'\([^'"'"']*\)'"'"'[[:space:]]*.*$/\1/' 2>/dev/null`
;;
'"'* )
match=`echo "$line" | sed -e 's/^"\([^"]*\)"[[:space:]]*.*$/\1/' 2>/dev/null`
;;
* )
match=`echo "$line" | sed -e 's/^\([^[:space:]]*\)[[:space:]]*.*$/\1/' 2>/dev/null`
;;
esac
grep -Ei "$match" $TEMP1 1>/dev/null 2>/dev/null
if [ $? = 0 ]
then
rm -f $TEMP1
case "$line" in
"'"* )
reply=`echo "$line" | sed -e 's/^'"'"'[^'"'"']*'"'"'[[:space:]]*\(.*\)$/\1/' 2>/dev/null`
;;
'"'* )
reply=`echo "$line" | sed -e 's/^"[^"]*"[[:space:]]*\(.*\)$/\1/' 2>/dev/null`
;;
* )
reply=`echo "$line" | sed -e 's/^[^[:space:]]*[[:space:]]*\(.*\)$/\1/' 2>/dev/null`
[ X"$reply" = X ] && reply="$line"
;;
esac
echo "$reply" | sed -e 's/^^//;s/$$//;s/\.\*/*/g;s/\\\././g'
exit 0
fi
done
rm -f $TEMP1
exit 1
#!/bin/sh
# (c) Thomas Bez <bez@tedesca.net> 2004
# http://www.bez.tedesca.net/homeoff/antispam.html
#
# mail.mydomain.net:/etc/postfix/extractmail.sh
#parameter 1: single or multiple (default)
error=no
count=0
IFS='
'
for line in `sed -e 's/"[^"]*"//g;s/"[^"]*$//' | tr ',;' '\n'`
do
if echo $line | grep -q '^[[:space:]]*"[^"]*"[[:space:]]*<\([a-zA-Z0-9_.-]*@[a-zA-Z0-9_.-]*\)>[[:space:]]*$'
then
echo $line | sed -e 's/^[[:space:]]*"[^"]*"[[:space:]]*<\([a-zA-Z0-9_.-]*@[a-zA-Z0-9_.-]*\)>[[:space:]]*$/\1/'
count=`expr $count + 1`
continue
fi
if echo $line | grep -q '^[^<]*<\([a-zA-Z0-9_.-]*@[a-zA-Z0-9_.-]*\)>[[:space:]]*$'
then
echo $line | sed -e 's/^[^<]*<\([a-zA-Z0-9_.-]*@[a-zA-Z0-9_.-]*\)>[[:space:]]*$/\1/'
count=`expr $count + 1`
continue
fi
if echo $line | grep -q '^[[:space:]]*\([a-zA-Z0-9_.-]*@[a-zA-Z0-9_.-]*\)[[:space:]]*$'
then
echo $line | sed -e 's/^[[:space:]]*\([a-zA-Z0-9_.-]*@[a-zA-Z0-9_.-]*\)[[:space:]]*$/\1/'
count=`expr $count + 1`
continue
fi
error=yes
done
[ $count -eq 0 ] && error=yes
[ X"$1" = Xsingle -a $count -gt 1 ] && error=yes
[ $error = no ] && exit 0 || exit 1
-rwxr-xr-x 1 500 500 1281 Sep 11 14:42 add_blackwhitelist.sh* -rwxr-xr-x 1 500 500 370 Sep 11 14:49 auth_magic.sh* -rw-r--r-- 1 500 500 303 Sep 11 14:50 authenticated.lst -rw-r--r-- 1 500 500 536 Sep 11 14:50 authorized.lst -rwxr--r-- 1 500 500 1657 Sep 11 14:43 config.sh* -rw-r--r-- 1 500 500 601 Sep 11 14:50 dynamicmaps.cf -rw-r--r-- 1 500 500 231 Sep 11 14:50 extract_headers.awk -rwxr-xr-x 1 500 500 1138 Sep 11 14:44 extractmail.sh* -rw-r--r-- 1 500 500 177 Sep 11 14:51 helo_whitelist.rxp -rw-r--r-- 1 500 500 236 Sep 11 14:51 mailboxes_family.lst -rw-r--r-- 1 500 500 208 Sep 11 14:51 mailboxes_hosted.lst -rw-r--r-- 1 500 500 5567 Sep 11 14:51 main.cf -rw-r--r-- 1 500 500 185 Sep 11 14:51 map_alias -rw-r--r-- 1 500 500 249 Sep 11 14:52 map_helo_access -rw-r--r-- 1 500 500 1243 Sep 11 14:52 map_recipient_access_grey -rw-r--r-- 1 500 500 992 Sep 11 14:52 map_recipient_access_white -rw-r--r-- 1 500 500 296 Sep 11 14:53 map_relay_clientcerts -rw-r--r-- 1 500 500 320 Sep 11 14:53 map_sender_access_fakelocal -rw-r--r-- 1 500 500 252 Sep 11 14:53 map_sender_access_fromaddress -rw-r--r-- 1 500 500 346 Sep 11 14:53 map_smtpd_sender_accounts -rw-r--r-- 1 500 500 404 Sep 11 14:53 map_smtpd_sender_login_maps -rw-r--r-- 1 500 500 265 Sep 11 14:54 map_transport -rw-r--r-- 1 500 500 1227 Sep 11 14:54 map_virtual -rw-r--r-- 1 500 500 324 Sep 11 14:54 map_virtual_mailbox -rw-r--r-- 1 500 500 268 Sep 11 14:54 map_virtual_uid_gid -rw-r--r-- 1 500 500 1538 Sep 11 14:54 master.cf -rwxr-xr-x 1 500 500 495 Sep 11 15:35 match_plain.sh* -rwxr-xr-x 1 500 500 1955 Sep 11 15:36 match_wildcard.sh* -rwxr-xr-x 1 500 500 30539 Sep 11 14:46 nondelivery.sh* -rw-r--r-- 1 500 500 290 Sep 11 14:55 own_domains.rxp -rw-r--r-- 1 500 500 940 Sep 11 14:55 plus_habeas.awk -rw-r--r-- 1 500 500 433 Sep 11 14:55 plus_viruscheck.awk -rw-r--r-- 1 500 500 218 Sep 11 14:55 sender_domains.lst -rw-r--r-- 1 500 500 1240 Sep 11 14:57 spamassassin_local_cf -rw-r--r-- 1 500 500 706 Sep 11 14:57 spamc_individual.lst -rwxr-xr-x 1 500 500 9715 Sep 11 14:47 spamfilter.sh* -rw-r--r-- 1 500 500 257 Sep 11 14:57 special_abuse.rxp -rwxr-xr-x 1 500 500 7633 Sep 11 14:30 spf-policy.pl* -rw-r--r-- 1 500 500 659 Sep 11 14:57 suppress_internals.awk -rw-r--r-- 1 500 500 383 Sep 11 14:59 user_fetchmailrc -rw-r--r-- 1 500 500 25 Jul 13 19:11 user_forward -rw-r--r-- 1 500 500 667 Sep 11 14:59 user_procmailrc_full -rw-r--r-- 1 500 500 167 Sep 11 14:59 user_procmailrc_null -rw-r--r-- 1 500 500 234 Sep 11 14:59 user_spamassassin_userprefs -rw-r--r-- 1 500 500 675 Sep 11 14:59 usernames_family.lst -rw-r--r-- 1 500 500 180 Sep 11 15:00 usernames_hosted.lst
Copyright (c) 2004 Thomas Bez <bez@tedesca.net>. Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.2 or any later version published by the Free Software Foundation. See http://www.gnu.org/copyleft/fdl.html for license conditions.