Seit wohl mehr als zwei Jahren wundere ich mich hin und wieder, weshalb mein SpamAssassin bei der Beurteilung, ob es sich bei einer eintreffenden E-Mail um Spam oder nicht handelt, nicht langsam mal ein wenig cleverer wird. Nutze ich doch schliesslich sa-learn und verfüttere ihm gesammelte Spam-E-Mails, damit deren Signaturen erkannt und zukünftig nicht mehr durchgelassen werden. Irgendwie wurde es aber nie besser. Gefühlt kam immer mehr Spam durch. Aber das Thema war auch nicht so wichtig, dass ich es mit Nachdruck verfolgt hätte. Bis vor ein paar Tagen.

Ich habe auf meinem Server eine Konfiguration mit Exim4, SpamAssassin (sa-exim) und Cyrus als IMAP-Server. Der Server ist MX für einige Domains und überprüft eingehende E-Mails bereits während und nach den SMTP-Headern nach verschiedenen Methoden auf Spam. Sicher als Spam eingestufte E-Mails werden nicht angenommen, E-Mails, bei denen es nicht so klar ist (mittlerer Spam-Score), landen in einem Quarantäne-Ordner und werden hin und wieder begutachtet.

Kürzlich bin ich hinter das Rätsel gekommen, weshalb das Erlernen neuer Spam-Signaturen nicht funktioniert hat: Spam-E-Mails in diesem Quarantäne-Ordner werden von mir von Zeit zu Zeit in einen Spam-Ordner verschoben oder gelöscht. Nun bringt SpamAssassin ja die nette Funktion mit sich, dass man ihn Spam oder Ham “lernen” lassen kann. Da ich auf meinem Server allerdings IMAP verwende, die mitgelieferte Version von sa-learn nur Folder im mbox-Format lesen kann, wurde also zwei Jahre lang ein Ordner indiziert, welcher zwar (warum auch immer, vermutlich Altlast aus der Zeit vor IMAP) vorhanden war, allerdings keineswegs mit den Spam-Mails gefüttert wurde. Gna, so simpel…

Also habe ich neulich nach einer (einfachen) Methode gesucht, um dem SpamAssassin direkt meinen IMAP-Spam-Folder lesen und auswerten zu lassen. Und siehe da, auf dieser Seite wurde ich mit folgendem Script fündig:

#!/usr/bin/perl<br />
#<br />
# imap-sa-learn.pl, wrapper around sa-learn for imap mail boxes<br />
# Copyright (C) 2004  David M. Zendzian<br />
# Copyright (C) 2006  Koen Martens<br />
#<br />
# This library is free software; you can redistribute it and/or<br />
# modify it under the terms of the GNU Lesser General Public<br />
# License as published by the Free Software Foundation; either<br />
# version 2.1 of the License, or (at your option) any later version.<br />
#<br />
# This library is distributed in the hope that it will be useful,<br />
# but WITHOUT ANY WARRANTY; without even the implied warranty of<br />
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU<br />
# Lesser General Public License for more details.<br />
#<br />
# You should have received a copy of the GNU Lesser General Public<br />
# License along with this library; if not, write to the Free Software<br />
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA<br />
#<br />
# Feed mail from an imap mail folder to sa-learn. Options:<br />
#<br />
#<br />
# Derived from: http://www.dmzs.com/tools/files/spam.phtml<br />
# Derived by  : Koen Martens,<br />
# Derived on  : October, 13th (Friday), 2006<br />
# License     : LGPL<br />
#<br />
# Things to try if it doesn't work<br />
# 1) Use -v for verbose output<br />
# 2) Check your local.cf for spamassassin bayes_path settings.<br />
#<br />
# Also be sure to check that your spamassassin is truely using the<br />
# bayes files (-D manual startup of spamd to debug there)<br />
#<br />
# Modified by ZMI 20090519 ( michael.monnerie@is.it-management.at )<br />
#</p>
<p>use Mail::IMAPClient;<br />
use Getopt::Long;</p>
<p>sub usage {<br />
$rv = &quot;--host name         Host name\n&quot;;<br />
$rv = &quot;-p 1234             Port number\n&quot;;<br />
$rv = &quot;--port=1234         Port number\n&quot;;<br />
$rv.= &quot;--spam              Classify messages as spam\n&quot;;<br />
$rv.= &quot;--ham               Classify messages as ham\n&quot;;<br />
$rv.= &quot;-m name             Set mailbox name\n&quot;;<br />
$rv.= &quot;-mbox name          Set mailbox name\n&quot;;<br />
$rv.= &quot;-u username         Username to login with\n&quot;;<br />
$rv.= &quot;--user username     Username to login with\n&quot;;<br />
$rv.= &quot;-p password         Password to login with\n&quot;;<br />
$rv.= &quot;--pass password     Password to login with\n&quot;;<br />
$rv.= &quot;--password password Password to login with\n&quot;;<br />
$rv.= &quot;--debug 1           more verbose output, good for scripts\n&quot;;<br />
$rv.= &quot;--debug 2           fully verbose output\n&quot;;<br />
$rv.= &quot;-d                  Delete messages after processing\n&quot;;<br />
$rv.= &quot;--delete            Delete messages after processing\n&quot;;<br />
$rv.= &quot;-h                  Show this help text\n&quot;;<br />
$rv.= &quot;--help              Show this help text\n&quot;;<br />
return $rv;<br />
}</p>
<p>my $host='localhost';<br />
my $port=143;<br />
my $spam=0;<br />
my $ham=0;<br />
my $mbox='learnspam';<br />
my $user='foo';<br />
my $pass='bar';<br />
# ZMI 20090519: variables for easy reconfiguration:<br />
my $salearnbin=&quot;/usr/bin/sa-learn&quot;;<br />
my $tmpfile=&quot;/tmp/imapsalearn$$&quot;;</p>
<p>my $delete=0;<br />
# debug 1: terse reporting<br />
# debug 2: full reporting<br />
my $debug=0;<br />
my $help=0;</p>
<p>if(not GetOptions( 'host=s' =&amp;amp;gt; \$host,<br />
'p|port=i' =&amp;amp;gt; \$port,<br />
'spam' =&amp;amp;gt; \$spam,<br />
'ham' =&amp;amp;gt; \$ham,<br />
'm|mbox=s' =&amp;amp;gt; \$mbox,<br />
'u|user=s' =&amp;amp;gt; \$user,<br />
'p|pass|password=s' =&amp;amp;gt; \$pass,<br />
'debug=i' =&amp;amp;gt; \$debug,<br />
'd|delete' =&amp;amp;gt; \$delete,<br />
'h|help' =&amp;amp;gt; \$help)) {<br />
die(usage());<br />
}</p>
<p>if( ($spam &amp;amp;amp;&amp;amp;amp; $ham) || not($spam || $ham) || $help) {<br />
die(usage());<br />
}</p>
<p>my $spamhamopt=$spam?&quot;--spam&quot;:&quot;--ham&quot;;</p>
<p>my $salearn;</p>
<p>print &quot;connecting...\n&quot;;<br />
my $imapdebug=0;<br />
if ($debug &amp;amp;gt;= 2) { $imapdebug=1; }<br />
my $imap = Mail::IMAPClient-&amp;amp;gt;new( Server=&amp;amp;gt; &quot;$host:$port&quot;,<br />
User =&amp;amp;gt; $user,<br />
Password =&amp;amp;gt; $pass,<br />
Debug =&amp;amp;gt; $imapdebug);</p>
<p>if (!defined($imap)) { die &quot;IMAP Login Failed&quot;; }<br />
print &quot;connected...\n&quot;;</p>
<p># print out the total counts for each mailbox<br />
print &quot;getting spam count...\n&quot;;<br />
my $spamcount = $imap-&amp;amp;gt;message_count($mbox);<br />
print $spamcount, ($spam?&quot; spam&quot;:&quot; ham&quot;),&quot; to process\n&quot;;</p>
<p># Process the spam mailbox<br />
print &quot;get all messages...\n&quot;;<br />
$imap-&amp;amp;gt;select($mbox);<br />
my @msgs = $imap-&amp;amp;gt;search(&quot;ALL&quot;);<br />
$|=1;<br />
print &quot;processing...\n&quot;;<br />
for (my $i=0;$i &amp;amp;lt;= $#msgs; $i++) {   print &quot;$i&quot;;   # I put it into a file for processing, doing it into a perl var &amp;amp;amp; piping through sa-learn just didn't seem to work   $imap-&amp;amp;gt;message_to_file(&quot;$tmpfile&quot;,$msgs[$i]);</p>
<p># execute sa-learn w/data<br />
if ($debug &amp;amp;gt;= 2) {<br />
$salearn = `$salearnbin -D --no-sync $spamhamopt $tmpfile`;<br />
print &quot;-------\nlearn: &quot;,$salearn,&quot;-------\n&quot;;<br />
}<br />
else {<br />
$salearn = `$salearnbin --no-sync $spamhamopt $tmpfile`;<br />
if ($debug == 1) { print &quot;: &quot;,$salearn; }<br />
else { print &quot;\r&quot;; }<br />
}</p>
<p># delete processed message<br />
if($delete) {<br />
$imap-&amp;amp;gt;delete_message($msgs[$i]);<br />
}<br />
unlink(&quot;$tmpfile&quot;);<br />
}<br />
print &quot;\nDone processing..\n&quot;;<br />
if($delete) {<br />
$imap-&amp;amp;gt;expunge();<br />
}<br />
$imap-&amp;amp;gt;close();</p>
<p>$imap-&amp;amp;gt;logout();</p>
<p># integrate learned stuff<br />
my $sarebuild = `$salearnbin --sync`;<br />
if ($debug &amp;amp;gt;= 2) { print &quot;\n-------\nRebuild: &quot;,$sarebuild,&quot;-------\n&quot;; }<br />
else { if ($debug == 1) { print &quot;\nRebuild: &quot;,$sarebuild; } }

Habe es als Script hinterlegt, die Perl-Module nachgezogen, die Config angepasst und es funktioniert sofort. Prima, so soll’s sein. Ich empfehle, das Script ohne Parameter aufzurufen, sondern die gewünschten Werte im Code zu hinterlegen. Da ggf. ein Passwort eingetragen werden muss, muss natürlich auf die Berechtigungen geachtet werden. Ich habe es ausschliesslich für root les- und ausführbar gemacht.

Aufwand:

  • 5 Minuten nach der Ursache suchen
  • 5 Minuten wundern und Kopfschütteln
  • 2 Minuten nach einer sa-learn Lösung für IMAP suchen
  • 1 Minute Script einbauen
  • 1 Minute Config anpassen
  • ein Leben lang freuen

So einfach kann’s manchmal sein, wenn man endlich mal die Muße hat, nach der Ursache zu suchen.

Hat’s gefallen?

Wenn der Artikel gefallen hat oder hilfreich war, bitte folgende Funktionen benutzen:

Tweet

Leave a Reply