daily system administration

Linux, Debian and the rest
any questions or comments: Tom@d7031.de

better individual spam defense with amavis and spamassassin

Postfix, amavis and spamassassin is a very good team for handling emails and fighting against SPAM. They catch ninety percent of all SPAM with there ruleset. You can also include the excellent ruleset from Heinlein-Support (german). But. I’ve some special SPAM for health ensurance advertising and so on. So I’ll show a way to catch this last percents. My email setup is very common. Postfix takes the email and delivers these to amavis. Amavis is the working horse for Virus- and SPAM scan. It extracts the content and call the scanners. With there results amavis and postfix accepts or reject the email. Now let’s take a deeper look into spamassassin.

Spamassassin checks the whole email include headers against all of his rules. It has two thresholds. One value to set a SPAM-Header (X-Spam-Score) for a suspected spam messages (2 in many configs) and the value for classify this email as SPAM (5 by default, X-Spam-Status: Yes). Amavis uses another value for rejecting email (6.91 is very good value). Amavis takes the spamassassin return value and compute this with his own test results. So if you want to reject a email as SPAM you have to hit the 6.91 in amavis.

Now let’s see how spamassassin works by scanning a email using the command:

spamassassin -t <email>

Most important is the summary:

Content analysis details: (13.5 points, 5.0 required)

pts rule name description
---- ---------------------- --------------------------------------------------
 0.0 URIBL_BLOCKED ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [URIs: guenstigepflegevorsorge.info]
-0.0 NO_RELAYS Informational: message was not relayed via SMTP
 1.2 MISSING_HEADERS Missing To: header
 0.0 PP_MIME_FAKE_ASCII_TEXT BODY: MIME text/plain claims to be ASCII but isn't
 1.0 MISSING_FROM Missing From: header
 1.4 MISSING_DATE Missing Date: header
 1.8 MISSING_SUBJECT Missing Subject: header
-0.0 NO_RECEIVED Informational: message has no Received headers
 8.0 D7031_Versicherung private health insurance
 0.1 MISSING_MID Missing Message-Id: header
 0.0 NO_HEADERS_MESSAGE Message appears to be missing most RFC-822 headers

You can see two things. All rules and scores are summarized and give your own rules a good name schema (D7031_ for me). Next step is the implementation. First of all make sure your logging is chatty enough. Set your amavis log level to 3 or higher:

$log_level = 3 ;

If you search in your logfile, you can find the spamassassin hits:

grep spam_scan /var/log/mail.log

Nov 20 16:15:57 mx amavis[10257]: (10257-19) spam_scan: score=15.671 autolearn=spam autolearn_force=no tests=[D7031_Versicherung=8,D7031_url_online=5,DATE_IN_PAST_96_XX=2.07,DKIM_SIGNED=0.1,DKIM_VALID=-0.1,DKIM_VALID_AU=-0.1,HTML_IMAGE_ONLY_20=0.7,HTML_MESSAGE=0.001,SPF_PASS=-0.001,T_REMOTE_IMAGE=0.01,T_RP_MATCHES_RCVD=-0.01,URIBL_BLOCKED=0.001] recips=0

or check your logfile on a daily basis which sender an recipient are blocked:

/bin/grep 'Blocked SPAM' /var/log/mail.log.1 | /usr/bin/awk '{print $1" "$2" "$3" "$12" - "$14}'

Nov 20 03:35:32 '<aknehms@finderboot.co.ua>' - '<tom@d7031.de>',

The rules are relatively easy to write. The power comes with the combination of header and body rules. In postfix you also have header rule and body rules, but no combination of both. Another thing with this is, you can accept or block the email. With spamassassin you can give them a score and this more fine grained. Put your rules in a file under ‘/etc/spamassassin/z_ownrules.cf’. Here is an example using meta rules:

header  __D7031_FROM_Tarif              From =~ /tarif/i
body    __D7031_Versicherung            /Krankenversicherungsvertrag/i
body    __D7031_Tarif                   /Tarif/i
body    __D7031_Krankentagegeld         /Krankentagegeld/i
body    __D7031_Premiumleistungen       /\bPremiumleistungen\b/i
body    __D7031_Krankenversicherung     /\bKrankenversicherung\b/i
meta    D7031_Versicherung              ((__D7031_FROM_Tarif + __D7031_Versicherung + __D7031_Tarif + __D7031_Krankentagegeld + __D7031_Premiumleistungen + __D7031_Krankenversicherung) >1)
describe D7031_Versicherung             private health insurance
score   D7031_Versicherung              8

With this meta line minimum two from the definitions have to match to put a score of 8 on this email.

Another idea is to score some words:

body    D7031_Sex                       /Sex/i
describe D7031_Sex                      contains 'Sex'
score   D7031_Sex                       2

I hope you find this article useful. You can find a example for distributing your rules using ansible on github.