Bayesian database opschonen

Nu SpamAssassin op automatische piloot staat en op versie 3.3 zit werd het tijd voor grafiekjes. Zeker om het gedrag te kunnen monitoren en een van de eerste onderdelen is was het Bayesian-filter. Met wat Perl-code en rrdtool kom je al redelijk vlot tot grafiekjes en hoef je alleen nog maar tijd te hebben.

Gelukkig hebben de grafiekjes voldoende tijd gehad, want recentelijk besloot de software dat de maximale vulgraad was bereikt en werd er opgeschoond, maar of dit nu geheel wenselijk was. Met bayes_expiry_max_db_size verhoogt naar 1.000.000 tokens zou de database even vooruit moeten en de performance bleek ook goed te zijn, maar waarom dan die grote afname in tokens? Zeker omdat het doel is om 75% van bayes_expiry_max_db_size te behouden of minimaal 100.000 tokens.

Een ander onderdeel van de expire is de leeftijd van de tokens en hoe vaak er een daadwerkelijke expire wordt uitgevoerd. Hieruit vallen twee dingen te herleiden. De daadwerkelijke opruimactie op de database wordt niet vaak genoeg getriggerd en hiervoor moet of de omvang van de database omlaag of moeten we sneller nieuwe tokens de database in. Een tweede wat te herleiden is dat er wel genoeg nieuwe tokens de database inkomen om oudere tokens op te ruimen.

Wat de oplossing gaat zijn ben ik nog niet geheel uit, maar wat wel interessant is om te zien of er een relatie is tussen de vulgraad van de database en de scores die worden uitgedeeld. Een ander feit is of de extra spam van week 10 invloed heeft gehad, maar dit zal moeten blijken met de volgende iteratie. Het is dus nog even wachten of dit eenmalig was of niet, want voorlopig blijkt het Bayesian-filter wel goed te werken.

SpamAssassin op automatische piloot

In de afgelopen periode is SpamAssassin al meerdere malen aanbod gekomen en vooral in de context met Bayesian-filtering, maar er is nog veel meer. Zeker nu recentelijk release 3.3 het daglicht heeft gezien wordt het tijd om eens wat dieper in SpamAssassin te duiken.

Een van de grootste probleempunten met SpamAssassin was altijd de ruleset en de strijd met de spammers om die rules net te omzeilen. Bayesian-filtering lost een deel van dit probleem op, maar het andere probleem is de ruleset zelf. Voorheen werden rules met SpamAssassin meegeleverd en werden ze pas bijgewerkt als je een nieuwe versie van SpamAssassin installeerde. Het mag duidelijk zijn dat dit een onbegonnen strijd is, maar ook een gevaarlijke strijd. Zeker voor installaties die afhankelijk waren van DNS blacklists cq whitelists en waarbij de beheerder van de lijst besloot om zijn lijst op te heffen bijvoorbeeld.

De SARE-regels zijn een hele tijd populair geweest en zijn dat helaas nog steeds. Dit terwijl ze stelselmatig worden opgenomen in de ruleset van SpamAssassin zelf. Gelukkig heeft oa SARE er wel voor gezorgd dat er een update methode kwam die met de 3.3 release verplicht is geworden. Hiermee volgt SpamAssassin eigenlijk ClamAV in de voetsporen en is het verspreiden van een vaste ruleset overbodig geworden.

De eerste stap is eigenlijk heel simpel met het draaien van het commando sa-update als de gebruiker root op Debian. Als we nu in /var/lib/spamassassin/ kijken dan zien we het volgende:

$ ls -l /var/lib/spamassassin/ /var/lib/spamassassin/*/
/var/lib/spamassassin/:
total 8
drwxr-xr-x 3 root root 4096 2010-01-02 14:46 3.002005

/var/lib/spamassassin/3.002005/:
total 8
drwxr-xr-x 2 root root 4096 2010-01-02 14:46 updates_spamassassin_org
-rw-r--r-- 1 root root 2431 2010-01-02 14:46 updates_spamassassin_org.cf

We zien netjes de betreffende versie van SpamAssassin en in die directory de ruleset zoals die nu voor versie 3.2.5 wordt aanbevolen door SpamAssassin. Deze regels worden automatisch meegenomen als SpamAssassin de volgende keer start. Nu kan je zelf een paar regels in de crontab van de gebruiker root zetten, maar in Debian zijn al voorzieningen getroffen. Door het onderstaande aan te passen in het bestand /etc/dedault/spamassassin zal automatisch de sa-update worden gedaan en wanneer nodig SpamAssassin worden geherstart.

CRON=1

Helaas stopt hier het verhaal niet, want op dit moment worden services zoals Amavisd-new, die ook de SpamAssassin ruleset kan gebruiken, niet herstart. Op moment van schrijven staan bugs 315961 en 567205 hier nog over open. Totdat die zijn opgelost kan de volgende extra code geen kwaad aan het einde van /etc/cron.daily/spamassassin

test -f /var/run/amavis/amavisd.pid && /etc/init.d/amavis force-reload

Dit laatste is natuurlijk op eigen risico, maar het lost de bug voor mij al een redelijke tijd op.

Fetchmail gebruiken om sa-learn aan te sturen

Nu zijn er al flink wat postings geweest over hoe SpamAssassin en Bayesian-filtering in te zetten, maar wat gebeurt er nog meer achter de schermen. In een van de eerste postings was al naar voren gekomen hoe vanaf de commandline de database gevuld kan worden, maar wat als de data op een IMAP-server staat?

Gelukkig zijn er op Unix duizend en één tooltjes beschikbaar en zo ook om berichte van een IMAP- of een POP3-server te halen. We beginnen dus met het installeren van fetchmail, wat dit al jaren kan en nog steeds regelmatig updates krijgt.

$ sudo apt-get install fetchmail

De tweede stap is om het bestand /etc/fetchmailrc te voorzien van de juiste informatie zoals in het voorbeeld hieronder. Wat al gelijk opvalt zijn de twee entries voor de spamuser en ook twee voor de hamuser. De mailserver plaatst berichten met een spam-label automatisch in de Junk-folder en hierdoor moet fetchmail twee folders leeghalen. Dit kan verschillen per mailserver configuratie uiteraard.

Een tweede kenmerk is dat tegen fetchmail wordt vertelt om een alternatieve Mail Delivery Agent te gebruiken en dat is /usr/bin/sa-learn in dit geval. Oplettende lezers zullen ook merken dat “-u amavis” ontbreekt, maar dat komt omdat in de SpamAssassin configuratie wordt afgedwongen om alles te leren onder de gebruiker amavis.

poll imap.example.org proto imap:
user "spamuser" password "password" ssl mda '/usr/bin/sa-learn --spam' folder 'INBOX' fetchall no rewrite
user "spamuser" password "password" ssl mda '/usr/bin/sa-learn --spam' folder 'Junk' fetchall no rewrite
user "hamuser" password "password" ssl mda '/usr/bin/sa-learn --ham' folder 'INBOX' fetchall no rewrite
user "hamuser" password "password" ssl mda '/usr/bin/sa-learn --ham' folder 'Junk' fetchall no rewrite

De derde stap is om het bestand /etc/default/fetchmail aan te passen met volgende opties. Het vertelt fetchmail om als daemon te draaien en 15 minuten na het beëindigen van een poll opnieuw te controleren of er nieuwe berichten zijn. Tevens wordt ook duidelijk gemaakt aan fetchmail dat hij onder geen beding bounce-berichten de wereld mag insturen.

OPTIONS="-d 900 --nobounce"
START_DAEMON=yes

Als laatste stap is het nu om fetchmail te starten en doordat de START_DAEMON op yes is gezet zal fetchmail ook automatisch starten als de machine herstart.

$ sudo invoke-rc.d fetchmail start

Zoals al gezegd is fetchmail redelijk lang op de markt en kent ook vele opties om sommige dingen nog verder te tunen. Een nadeel van de huidige opzet is dat voor elke nieuw bericht een sa-learn proces wordt opgestart. Het is dus wel verstandig om te kijken wat de impact is als je veel berichten per dag moet verwerken. Tevens valt er nog wat te spelen met fetchmail opties zoals expunge, limit en limitflush bijvoorbeeld.

AWL vervuiling opschonen

SpamAssassin heeft de optie om te leren en te scoren op basis van een combinatie van e-mail en IP-adres. Nu lijkt deze optie zinvol en het lijkt te werken, maar hoever het schaalt is nog de vraag. Wat het schalen gaat beïnvloeden is de hoeveelheid combinaties die in de database staan en hoe snel deze combinaties te doorzoeken zijn. Helaas is er geen standaardoplossing in SpamAssassin om de AWL-tabel op te schonen, maar gelukkig zijn er opties binnen PostgreSQL om dit te regelen.

De eerste stap is om de AWL-tabel aan te passen door een attribuut toe te voegen met het volgende SQL-commando:

alter table awl add lastupdate timestamp with time zone default now();

De tweede stap is om een trigger te definiëren en aan de tabel te koppelen met het volgende SQL-commando:

CREATE OR REPLACE FUNCTION trg_handle_awl_lastupdate() RETURNS TRIGGER AS $BODY$
BEGIN
IF NEW.lastupdate = OLD.lastupdate THEN NEW.lastupdate := now(); END IF;
RETURN NEW;
END;
$BODY$ LANGUAGE 'plpgsql';
CREATE TRIGGER trg_handle_timestamp BEFORE UPDATE ON awl FOR EACH ROW EXECUTE PROCEDURE trg_handle_awl_lastupdate();

Vanaf dit moment zal het attribuut lastupdate elke keer worden bijgewerkt wanneer de combinatie door SpamAssassin wordt gezien en daardoor ook de tabel bijwerkt. Door nu wekelijks of dagelijks een SQL-script te draaien die bijvoorbeeld elke combinatie die te lang onaangeraakt is te verwijderen. Zoals de voorbeeld code hieronder.

delete from awl
where ( lastupdate < = now() - interval '4 months' and count > 1 )
or ( lastupdate < = now() - interval '3 months' and count = 1 );

Belangrijk om mee te nemen dat het soms even kan duren voordat bepaalde combinaties weer worden gezien. Veel mailinglisten komen meestal wel eens per maand voor. De interval van 3 maanden zou deze lijsten dus voldoende tijd moeten geven om een score te vormen.

Bayesian-filtering en (bijna) geen backups

Elk bestand en database moet op tape worden gezet is het motto bij veel sysadmins, maar is dat wel zo. En in veel gevallen hebben ze gelijk, maar helaas niet als het een database betreft die wordt gebruikt voor Bayesian-filtering. Maar waarom maak je backups? Om data die waarde heeft veilig te stellen is eigenlijk de stelregel.

Maar wat maakt Bayesian-filtering nu zo anders? Laten we eens kijken op een testnode.

$ sudo sa-learn --dump magic
0.000 0 3 0 non-token data: bayes db version
0.000 0 25556 0 non-token data: nspam
0.000 0 11331 0 non-token data: nham
0.000 0 204764 0 non-token data: ntokens
0.000 0 1262669327 0 non-token data: oldest atime
0.000 0 1263329413 0 non-token data: newest atime
0.000 0 0 0 non-token data: last journal sync atime
0.000 0 1263273928 0 non-token data: last expiry atime
0.000 0 345600 0 non-token data: last expire atime delta
0.000 0 21267 0 non-token data: last expire reduction count

Wat we zien is dat de database is gevuld met z’n dikke 25000 spamberichten en 11000 hamberichten, maar ook dat de database ruim 204000 kenmerken bevat om zijn berekeningen op laten plaats vinden. Twee leuke kenmerken van dit overzicht zijn dat er een data in de journal zit omdat dit direct in echte database zit ipv in de standaard BerkelyDB en de tweede is de hoeveelheid tokens. In een standaard database zitten maximaal 150000 tokens en welke bij een expire automatisch worden opgeschoond naar 75% van die 150000 tokens. Deze installatie heeft auto-expire uitstaan waardoor dit extern moet worden geregeld, maar ook dat de database meer dan een normaal aantal tokens mag bevatten.

Maar wat heeft dit met backups te maken? Waarom zou je data in veiligheid brengen als er elke uur een nieuwe tokens worden toegevoegd en elke dag een expire wordt gedaan? Een constante stroom aan nieuwe data zorgt ervoor dat de database altijd in flux is zoals het hoort en in het slechtste geval is je database een paar uur aan het bijleren. Om dit laatste te overkomen zou je spam- en hamberichten bijvoorbeeld in de Trash-folder van de IMAP-server kunnen laten staan die na een paar dagen deze automatisch verwijdert uit standaard policy. Het verplaatsen van alle berichten van de Trash-folder naar de INBOX en bij de volgende ronde komt alles weer vanzelf in de database. Deze methode werkt ook vrij goed als de layout van de database wordt aanpast bij een upgrade en de database moet opnieuw worden opgebouwd.

De vraag die sysadmins misschien wat meer moeten vragen of de data echt naar tape moet, want we leven steeds meer in een tijdperk dat backups onmogelijk(er) beginnen te worden. Er zullen dus andere oplossingen moeten worden gezocht om data veilig te houden voor gebruikers. De eerste stap is het niet op tape zetten van data die je kan reproduceren.