summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordakkar <dakkar@luxion>2007-12-27 14:37:02 +0000
committerdakkar <dakkar@luxion>2007-12-27 14:37:02 +0000
commit24ee74f4b98c3773a4fc64225f6f9e719f9c1ac8 (patch)
tree3f233b34f608fb6fea9196c6fa36bb729b197aa5
parent r3306@rfc-1918: dakkar | 2007-12-27 13:24:29 +0100 (diff)
downloadGraylister-24ee74f4b98c3773a4fc64225f6f9e719f9c1ac8.tar.gz
Graylister-24ee74f4b98c3773a4fc64225f6f9e719f9c1ac8.tar.bz2
Graylister-24ee74f4b98c3773a4fc64225f6f9e719f9c1ac8.zip
r3307@rfc-1918: dakkar | 2007-12-27 15:36:13 +0100
almost finished first draft
-rw-r--r--article.rest.txt310
1 files changed, 310 insertions, 0 deletions
diff --git a/article.rest.txt b/article.rest.txt
index bcf69f5..ffa0d5a 100644
--- a/article.rest.txt
+++ b/article.rest.txt
@@ -1,6 +1,316 @@
+.. -*- mode: rst; coding: utf-8 -*-
+
===============================================
``Graylister`` - proviamo a fermare lo "SPAM"
===============================================
:Author: Gianni Ceccarelli
:Id: $Id $
+Un po' di storia
+================
+
+Non provo neppure a spiegare cosa si intende con "spam" parlando di
+posta elettronica. Se non lo sapete, vuol dire che non avete usato la
+posta elettronica negli ultimi <troppi> anni, per cui questo articolo
+non vi interessa ``:-)``.
+
+Ci sono svariati metodi per limitare la quantità di spazzatura che
+arriva nella nostra casella postale. Molti, tra cui SpamAssassin_ che
+è scritto in Perl ed è molto diffuso, analizzano il contenuto di
+ciascun messaggio, cercando di indovinare la probabilità che sia da
+cestinare; funzionano piuttosto bene, ma sono un po' pesanti dal punto
+di vista computazionale: finché avete un solo account di cui
+preoccuparvi, vanno bene, ma se dovete gestire un intero dominio,
+potrebbe essere il caso di aggiungere qualche altro sistema di
+filtraggio.
+
+Un sistema che ha avuto discreta popolarità fino a non molto tempo fa
+è il `black listing`: si prendono delle liste di indirizzi IP
+considerati "inaffidabili", e si rifiutano tutti i messaggi
+provenienti da essi. Semplice, ma un po' troppo drastico, specialmente
+perché è molto più facile finire per sbaglio in una di quelle liste
+che farsi togliere.
+
+Di recente è stato inventato il `gray listing`, come forma meno
+drastica del `black listing`: invece che rifiutare il messaggio, si
+segnala un errore temporaneo, e se il mittente riprova l'invio, si
+accetta. Il senso può non essere molto ovvio, per cui è bene spiegare
+meglio.
+
+Un server di posta serio gestisce una coda di messaggi da inviare. Per
+ciascun messaggio in coda, tenta l'invio contattando il gestore posta
+responsabile per l'indirizzo del destinatario. Se tale gestore accetta
+il messaggio, bene, fine del lavoro. Se lo rifiuta, si può segnalare
+il problema al mittente. Ma, in alcuni casi, il gestore destinatario
+potrebbe essere non raggiungibile, o segnalare qualche errore
+temporaneo (ovvero che, nell'opinione del gestore destinatario,
+dovrebbe risolversi tra un po' tempo). In questi ultimi casi, il
+gestore mittente rimette il messaggio in coda, e ritenta l'invio dopo
+qualche tempo. Però (e qui sta tutto il "succo" del `gray listing`)
+molti spammer non usano gestori di posta seri, ma usano programmini
+specializzati nell'inviare il maggior numero possibile di messaggi nel
+minor tempo possibile. Questi sistemi non gestiscono una coda, per cui
+trattano un errore temporaneo come un errore permanente: ignorando il
+problema, e non ritentando l'invio.
+
+Per cui, se il nostro gestore destinatario costringe ciascun mittente
+a tentare l'invio due volte, non accetterà mai messaggi da sistemi di
+invio stupidi, e eliminerà così la maggior parte dello spam. D'altra
+parte, se qualcuno ha un gestore posta serio che esce da un indirizzo
+"sospetto" secondo qualche `black list`, un sistema di `gray listing`
+accetterà i suoi messaggi (l'utilità di ciò è discutibile, ma almeno
+abbiamo la possibilità di scegliere come trattare ciascun caso).
+
+Il contorno
+===========
+
+Avendo di recente spostato il mio dominio su un server dedicato, mi
+sono trovato a dover configurare il server di posta elettronica (oltre
+a tutto il resto). Ho scelto netqmail_, principalmente perché l'avevo
+già usato e quindi ho un'idea di come si configuri. Dopo aver notato
+la non trascurabile quantità di spam che entrava, mi sono messo a
+cercare un sistema di `gray listing` da incastrare nel server. Ne ho
+trovati parecchi, ma nessuno faceva proprio quel che volevo, per cui
+mi sono ispirato alle caratteristiche migliori di ciascuno, e ne ho
+scritto uno a modo mio (ah, il bello del software libero!).
+
+Per incastrarlo dentro netqmail_, ho usato il meccanismo detto
+`qmail-spp`_, che permette di chiedere a ``qmail-smtpd`` di invocare un
+programma esterno in certe fasi del protocollo SMTP_. In particolare,
+mi farebbe comodo agire con più informazioni possibile; siccome non
+voglio leggermi l'intero messaggio, mi limiterò a usare le
+informazioni presenti sulla "busta": indirizzo IP della macchina
+mittente, indirizzo di posta del mittente, e indirizzo di posta del
+destinatario.
+
+A modo mio
+==========
+
+Ma cos'è, esattamente, che voglio ottenere?
+
+1) `Gray listing` sulla maggior parte dei messaggi
+2) Se un server si dimostra capace di passare il `gray listing` e
+ *non* compare in alcuna `black list`, viene aggiunto a una `white
+ list` e non subirà più filtraggio
+
+Semplice, no? Come lo spieghiamo alla macchina? Questa è la subroutine
+principale del mia "graylister"::
+
+ sub check {
+ my ($host,$from,$to)=@_;
+
+ remove_old_attempts();
+ if (is_whitelisted($host)) {
+ accept_message();return;
+ }
+ if (is_second_attempt($host,$from,$to)) {
+ if (is_blacklisted($host)) {
+ cleanup_attempt($host,$from,$to);
+ accept_message();return;
+ }
+ else {
+ add_to_whitelist($host);
+ accept_message();return;
+ }
+ }
+ record_first_attempt($host,$from,$to);
+ reject_temporarily($from);
+ return;
+ }
+
+Andiamo riga per riga. La sub prende 3 parametri: l'indirizzo IP del
+server che sta cercando di mandarci un messaggio, l'indirizzo di posta
+da cui dice di provenire il messaggio, e l'indirizzo di posta a cui
+sarebbe destinato::
+
+ sub check {
+ my ($host,$from,$to)=@_;
+
+``remove_old_attempts`` serve per tenere pulito il piccolo database
+che uso per tenere traccia dei tentativi di invio.
+
+Se il server mittente è nella `white list`, accetto il messaggio e
+termino l'elaborazione::
+
+ if (is_whitelisted($host)) {
+ accept_message();return;
+ }
+
+Se è la seconda volta che ricevo lo stesso messaggio, e il server
+mittente sta in qualche `black list`, pulisco la traccia del tentativo
+e accetto il messaggio; se il server non sta in nessuna `black list`,
+lo considero definitivamente affidabile, e comunque accetto il
+messaggio::
+
+ if (is_second_attempt($host,$from,$to)) {
+ if (is_blacklisted($host)) {
+ cleanup_attempt($host,$from,$to);
+ accept_message();return;
+ }
+ else {
+ add_to_whitelist($host);
+ accept_message();return;
+ }
+ }
+
+Se arrivo a questo punto, vuol dire che il messaggio è un primo
+tentativo da parte di un server non dichiarato affidabile: tengo
+traccia del tentativo, e segnalo errore temporaneo::
+
+ record_first_attempt($host,$from,$to);
+ reject_temporarily($from);
+ return;
+ }
+
+Semplice, no?
+
+I dettagli
+==========
+
+Ovviamente quella subroutine, da sola, non ha speranza di
+funzionare. Pur senza mostrare tutto il codice, è opportuno scendere
+un po' nei dettagli delle varie funzioni usate.
+
+Il database
+-----------
+
+Per tenere traccia dei tentativi, e della `white list`, ho usato un
+database SQLite_, tramite DBI_ e `DBD::SQLite`_. Contiene 3 tabelle:
+
+``version(version integer)``
+ non strettamente necessaria, ma la metto per segnare la versione del
+ programma che ha creato il database, in modo da poter prevedere
+ cambiamenti strutturali in future versioni con aggiornamento
+ automatico dei dati
+
+``whitelist(host varchar)``
+ la lista dei server dichiarati affidabili; ogni record contiene
+ un indirizzo IP in forma "dotted decimal"
+
+``attempts(host varchar,smtpfrom varchar,smtprcpt varchar,time integer)``
+ in questa tabella vengono inseriti i tentativi di invio; il
+ timestamp viene usato per pulire i tentativi vecchi per cui abbiamo
+ perso ogni speranza (ovvero, se dopo un giorno non ci ha riprovato,
+ non ci riproverà più)
+
+Le funzioni ``add_to_whitelist``, ``is_whitelisted``,
+``record_first_attempt``, ``is_second_attempt``, ``cleanup_attempt`` e
+``remove_old_attempts`` usano semplici comandi SQL per manipolare il
+database. Per rendere trasparente l'uso del database alla funzione
+chiamante, ciascuna di queste funzioni invoca ``_init_dbh``, la quale
+si preoccupa di aprire il database (se non è già stato fatto in questa
+sessione) e eventualmente crearvi le tabelle (ovviamente solo al
+momento della creazione, usando ``_prepare_db``).
+
+``cleanup_attempt`` ha una riga di logica in più: invece di cancellare
+il record del tentativo di invio, si limita ad aggiornare il
+timestamp. In questo modo, se il server mittente (sospetto) spedisce
+spesso per la stessa coppia mittente-destinatario, si evita di
+rallentare tutti gli invii (nota: questo potrebbe però aiutare gli
+spammer, forse eliminerò questo comportamento). In alcuni casi il
+mittente "sulla busta" potrebbe essere vuoto (in caso di bounce, ad
+esempio): in questi casi si cancella il record del tentativo, in
+quanto la terna ``<host,'',to>`` non identifica bene un messaggio.
+
+Le `black list`
+---------------
+
+Per controllare se l'indirizzo IP del mittente stia in qualche `black
+list` ho usato il modulo `Net::DNSBLLookup`_. Siccome però ha qualche
+stranezza (l'elenco delle liste non è molto aggiornato, non
+restituisce tutte le informazioni che potrebbe), ne ridefinisco al
+volo alcune parti (sì, prima o poi manderò una patch all'autore).
+
+Interazioni con l'esterno
+-------------------------
+
+Infine, dobbiamo preoccuparci dell'input e dell'output del programma
+rispetto a netqmail_.
+
+Per quanto riguarda l'output, abbiamo due funzioni:
+
+``accept_message``
+ non fa niente: se il programma non scrive nulla su ``STDOUT``,
+ ``qmail-smtpd`` accetterà il messaggio
+
+``reject_temporarily``
+ chiede a ``qmail-smtpd`` di rispondere con codice 451 ("temporary
+ failure", appunto); lo fa subito nella maggior parte dei casi, ma se
+ il mittente è vuoto o comincia per ``postmaster@`` (ovvero, se
+ sembra essere un altro server di posta), segnala l'errore solo dopo
+ aver ricevuto il messaggio (altrimenti il mittente potrebbe
+ offendersi e farci finire in qualche `black list`).
+
+Per l'input, basterebbe leggere qualche variabile di ambiente (vedi
+``get_from_env``):
+
+``TCPREMOTEIP``
+ indirizzo IP del server mittente, impostato da ``tcpserver``
+
+``SMTPMAILFROM``
+ indirizzo di posta del mittente sulla busta, impostato da
+ ``qmail-smtpd``
+
+``SMTPRCPTTO``
+ indirizzo di posta del destinatario sulla busta, impostato da
+ ``qmail-smtpd``
+
+``DAKGL_DBNAME``
+ path al database da usare, impostato in qualche modo (nel mio caso,
+ da ``tcpserver`` tramite apposita configurazione)
+
+Se però si leggono con attenzione altri programmi di `gray listing`,
+si scopre che serve un minimo di codice in più: EZMLM_, noto e diffuso
+programma per la gestione delle `mailing list`, cambia il mittente ad
+ogni invio, inserendoci un numero variabile (usato per scopi
+interni). Siccome non vogliamo ritardare ciascun messaggio di una
+`mailing list` (specie se, come me, siete iscritti a parecchie),
+sostituiamo il numero con un marcatore costante (in
+``_cleanup_data``).
+
+Gli script
+----------
+
+Per invocare il tutto, ho scritto un piccolo script::
+
+ #!/usr/bin/perl
+ use DAKKAR::Graylister;
+
+ exit 0 unless (defined $ENV{GRAYLISTING}) and (!defined $ENV{RELAYCLIENT});
+
+ DAKKAR::Graylister::check(DAKKAR::Graylister::get_from_env());
+
+La riga che comincia con ``exit`` permette di escludere l'elaborazione
+tramite apposite variabili di ambiente. In particolare,
+``GRAYLISTING`` deve essere definita, e il server mittente non deve
+essere già abilitato al `relay` (ovvero a inviare posta a chiunque,
+non solo agli indirizzi gestiti direttamente da questo server): se è
+abilitato al `relay`, si suppone che sia totalmente fidato, per cui è
+inutile filtrarlo (se permettete il `relay` a macchine non fidate,
+avete ben altri problemi, e meritate tutto il male che ve ne può
+derivare).
+
+Siccome, per questioni di pulizia e robustezza, dedico a ciascuna
+applicazione Perl la sua directory con i moduli che servono installati
+appositamente, serve un altro script che imposti ``PERL5LIB`` per
+permettere al compilatore di trovare i moduli::
+
+ #!/bin/bash
+
+ export PERL5LIB='/usr/local/graylist/lib/perl5:/usr/local/graylist/lib/perl5/x86_64-linux-thread-multi'
+
+ exec /usr/local/graylist/bin/dakkar-graylister
+
+Per costruire queste "librerie dedicate", consiglio l'uso di
+`local::lib`_.
+
+.. _SpamAssassin: http://spamassassin.apache.org/
+.. _EZMLM: http://www.ezmlm.org/
+.. _`qmail-spp`: http://qmail-spp.sourceforge.net/
+.. _SMTP: http://www.faqs.org/rfcs/rfc2821.html
+.. _SQLite: http://www.sqlite.org/
+.. _DBI: http://search.cpan.org/~timb/DBI/
+.. _`DBD::SQLite`: http://search.cpan.org/~msergeant/DBD-SQLite/
+.. _`Net::DNSBLLookup`: http://search.cpan.org/~tjmather/Net-DNSBLLookup/
+.. _netqmail: http://netqmail.org/
+.. _`local::lib`: http://search.cpan.org/~apeiron/local-lib/