Combattre efficacement le spam avec DSPAM

Julien Vehent – julien@linuxwall.info

Cet article a été initialement écrit pour Gnu/Linux Magazine France n.132 (novembre 2010, lien) et mis a disposition sous license Creative Common par les Editions Diamonds.

Vous avez déjà gouté du spam ? Je veux dire, du vrai spam. Celui qui est servi en boites rectangulaires et a vaguement la couleur du jambon. On m’en a servi, une fois, lors d’un réveillon, et je peux vous assurer que c’est vraiment pas bon! Mais le spam dont on va parler ici est différent, plus électronique, mais tout aussi dégoutant.

Combattre le spam est probablement la tâche la plus complexe du postmaster. Les techniques sont nombreuses, et il est indispensable de les cumuler pour obtenir un résultat pertinent.

Le choix de base en matière d'antispam Open Source se porte généralement sur spamassassin, qui, au fil des ans, a montré son efficacité. Mais depuis presque 2 ans, et la libération de DSPAM en Open Source, l'intérêt pour ce moteur d'analyse de contenu a grandi… presque au point d'avoir une version à jour dans Debian.

Cet article est un tour d'horizon de DSPAM, des technologies employées et de sa mise en place avec Postfix. Une fois n'est pas coutume, on se basera sur les sources pour l'installation, DSPAM n'ayant pas été intégré dans Squeeze à l'écriture de cet article.

1. Vue d'ensemble

DSPAM a été initialement écrit par Jonathan Zdziarski, un développeur américain, en 2003, à la suite de ses recherches sur la classification du spam. DSPAM a ensuite été vendu en 2006 à la société Sensory Networks, qui n'en a rien fait de pertinent si ce n'est fâcher la communauté par manque de communication, pour finalement, en 2009, rendre les droits au groupe dspam-community et donc publier DSPAM sous GPL.

DSPAM est écrit en C et nécessite un backend pour stocker les données. Si MySQL, Postgresql et Sqlite sont disponibles, il est également possible de se baser sur un Hash Driver sur disque, sans moteur relationnel. Ce “hash driver” est l'option par défaut que nous allons utiliser, car c'est également le driver le plus rapide accessible dans DSPAM.

L'analyse du spam est réalisée par et pour chaque utilisateur. Ainsi, chaque adresse email du domaine (sous la forme <utilisateur>@<domaine>) disposera de son répertoire personnel contenant les données de DSPAM. Il est toutefois possible de partager des informations de filtrage entre plusieurs utilisateurs sous la forme de groupes. Il y a plusieurs types de groupes, que nous détaillerons plus tard.

1.1 Technologie

Retour en 2002, Paul Graham, un autre développeur américain, publie “A Plan for Spam”, un article qui changera la vision d'alors de l'antispam. Au début des antispams, les règles étaient essentiellement basées sur des critères spécifiques au spam, tel que “titre en majuscule” ou “contient 48 points d'exclamation à la suite”. Paul Graham a travaillé sur ce type de règles, qui fonctionnaient plutôt bien, mais le problème était que le faible pourcentage de faux positifs résultant, de l'ordre de 1 a 2 %, était extrêmement difficile à filtrer.

Son idée (que d'autres avaient eu avant lui, mais sans la même raisonnance) était donc de découper le contenu d'un email en tokens, un token étant typiquement un mot, un composant des en-têtes, une balise html, etc… et de calculer des statistiques via l'algorithme de Bayes (dont le détail dépasse la portée de cet article).

Le résultat étant nettement satisfaisant, cette technique est devenu la norme et est au cœur de DSPAM.

Graham a également montré que, pour être réellement efficace, un antispam devait être centré sur un utilisateur en particulier. Utiliser une base globale pour tous les utilisateurs est moins efficace, certains mots couramment utilisés par certains pouvant être considérés comme du spam par d'autres (ceux d'entres-vous qui travaillent comme commerciaux pour des sociétés pharmaceutiques en comprendront certainement le principe).

2. Installation

Partons des sources, disponibles sur http://dspam.sourceforge.net/, et de la dernière version disponible à l'écriture de cet article: dspam-3.9.1-RC1.

L'archive contient la documentation, qui manque cruellement sur le wiki. En fait, les quelques 2153 lignes/14433 mots du fichiers README forment l'essentiel de ce qu'il faut savoir a propos de DSPAM.

Avant de lancer la compilation, définissons ce que nous souhaitons faire:

  1. DSPAM doit s'interfacer avec Postfix (en tant que content-filter), et va donc recevoir et réinjecter les emails via des sockets TCP sur localhost. DSPAM doit donc fonctionner en mode Daemon.
  2. Chaque utilisateur sera identifié via son adresse email complète.
  3. Chaque utilisateur va disposer d'un répertoire propre dans /var/spool/dspam, ce répertoire contiendra son dictionnaire (contenant les tokens et valeurs statistiques associées), les logs d'activité, l'historique qui sera utilisé pour l'apprentissage, etc… On va donc utiliser le Hash Driver comme backend de manipulation des données

DSPAM n'a pas vraiment de dépendances externes, une Debian Squeeze toute fraîche avec quelques outils de compilation suffit à l'installer (littéralement: gcc et make). Avec ces critères, on peut créer un utilisateur 'dspam' et compiler DSPAM de la façon suivante:

$ su
# useradd -r -s /bin/false -U -d /var/spool/dspam dspam
# exit
$ ./configure --enable-daemon --enable-split-configuration --enable-syslog --enable-clamav --enable-preferences-extension --enable-domain-scale --with-dspam-home=/var/spool/dspam --with-dspam-home-owner=dspam --with-dspam-home-group=dspam --with-dspam-owner=dspam --with-dspam-group=dspam --with-storage-driver=hash_drv --prefix=/usr/local/dspam --sysconfdir=/etc/dspam --mandir=/usr/share/man --bindir=/usr/bin --sbindir=/usr/sbin --libdir=/usr/lib --includedir=/usr/include
$ make
$ su
# make install

Les options de compilation sont détaillées dans './configure –help'. Activez les options de Debug ('–enable-debug –enable-bnr-debug –enable-verbose-debug') lors du './configure' si vous aimez la lecture.

Si vous souhaitez compiler DSPAM pour utiliser Postgresql plutôt que le hash driver, alors il vous faudra utiliser la ligne de configuration suivante :

$ ./configure --enable-daemon --enable-split-configuration --enable-syslog --enable-clamav --enable-preferences-extension --enable-domain-scale --with-dspam-home=/var/spool/dspam --with-dspam-home-owner=dspam --with-dspam-home-group=dspam --with-dspam-owner=dspam --with-dspam-group=dspam --with-storage-driver=pgsql_drv --with-pgsql-includes=/usr/include/postgresql/ --with-pgsql-libraries=/usr/lib/ --enable-virtual-users --prefix=/usr/local/dspam --sysconfdir=/etc/dspam --mandir=/usr/share/man --bindir=/usr/bin --sbindir=/usr/sbin --libdir=/usr/lib --includedir=/usr/include --enable-debug --enable-bnr-debug --enable-verbose-debug

Nous voici avec un répertoire /etc/dspam qui contient dspam.conf, et des binaires et librairies installés dans leurs répertoires respectifs.

3. Configuration de DSPAM

Avant d'alimenter DSPAM avec le flux d'emails venant de Postfix, nous allons le configurer et le tester.

Le fichier dspam.conf est livré avec un grand nombre de commentaires, tous n'étant pas faciles à interpréter sans une lecture attentive du README (on y revient) ou de cet article (vous y êtes).

Nous avons déjà dit à DSPAM, lors du ./configure, que l'on voulait utiliser le hash driver et conserver les répertoires des utilisateurs dans /var/spool/dspam. On retrouve donc ces directives au début du fichier de configuration (respectivement StorageDriver et Home). Comme nous l'avons dit précédemment, nous voulons que les communications avec DSPAM se fassent via des sockets TCP. Pour cela, il faut définir un point d'entrée et un point de sortie dans dspam.conf.

Le point d'entrée recevra les messages venant de Postfix. Il écoute sur le port TCP/10033 (choix arbitraire) et attend de Postfix qu'il lui parle le LMTP (une version allégée du SMTP pour les communications intra-infrastructure).

Les directives de configuration sont les suivantes:

ServerPort              10033
ServerQueueSize         32
ServerPID               /var/run/dspam/dspam.pid
ServerMode              auto
ServerParameters        « --deliver=innocent,spam -d %u »
ServerIdent             « localhost.localdomain »

La directive ServerParameters permet d'obliger DSPAM à réinjecter non seulement les emails innocents, mais également les spams. Sur un système en test, il est préférable de tout recevoir dans sa boite mail, et de filtrer sur les messages marqués ”[SPAM]”, plutôt que de mettre en quarantaine directement les supposés spams (à noter tout de même qu'il est possible d'envoyer des notifications quotidiennes listant les messages en quarantaine).

Sur le point de sortie, DSPAM va se connecter à Postfix pour lui réinjecter l'email après analyse. Plus tard, on va configurer Postfix pour qu'il écoute sur le port TCP/10034, pour le moment on place les directives suivantes dans dspam.conf:

DeliveryHost        127.0.0.1
DeliveryPort        10034
DeliveryIdent       localhost
DeliveryProto       SMTP

Ici, on parle le SMTP et non plus le LMTP.

3.1 Mode d'apprentissage

DSPAM démarre son fonctionnement avec des dictionnaires vides. Ce qui signifie que durant les premiers jours (/semaines selon la volumétrie), DSPAM ne filtrera rien et apprendra sur tout.

Cela signifie également qu'il sera de la responsabilité des utilisateurs de marquer les emails selon qu'ils sont du spam ou non (dans le second cas, sur erreur de DSPAM uniquement). Charge au postmaster de fournir un moyen simple de marquer les emails. Plusieurs modes d'apprentissage existent. Il sont décrit dans 'man dspam'. Celui qui nous intéresse ici se nomme 'teft' et forcera DSPAM à apprendre sur chaque email qu'il traitera, innocent comme spam.

Ce mode est particulièrement intensif car il va, pour chaque email, créer ou mettre à jour tous les tokens du message dans le dictionnaire de l'utilisateur. C'est parfait pour un nouvel utilisateur qui a besoin de rapidement se constituer un dictionnaire, mais peut être consommateur de CPU sur un environnement chargé.

Pour appliquer le mode 'teft', on positionne la directive suivante:

TrainingMode teft

Pour palier au problème de performance, d'autres modes d'apprentissage existent. Le mode 'tum', par exemple, n'apprendra que pendant une période de démarrage et ne modifiera le dictionnaire ensuite que sur ré-apprentissage.

Ce paramètres pouvant être fixés pour chaque utilisateur séparément, comme nous allons le voir dans les préférences, il est toujours possible d'avoir un mode 'teft' par défaut comme définit ci-dessus.

3.2 Mode de détection

Nous voici maintenant dans le core de DSPAM: le mode de détection. DSPAM n’étant ni plus ni moins qu’un moteur statistique d’analyse de contenu, son paramétrage passe essentiellement par trois directives:

  • le mode de découpage du contenu
  • l'algorithme statistique
  • le calcul de probabilité

Le contenu de ce chapitre est pour beaucoup tiré des explications de Stevan Bajic, que je remercie au passage d'avoir pris le temps de répondre à mes (nombreuses) questions.

3.2.1 Découpage du contenu

DSPAM appel cela un ‘tokenizer’. C’est le module qui va découper un contenu, en faire un token, et stocker son empreinte dans le dictionnaire de l'utilisateur. Ces tokens peuvent être de plusieurs formes selon le mode choisi, le plus basique étant de prendre les mots un par un, chaque mot étant un token diffèrent.

Mais il existe également des modules plus évolués, capables de prendre en considération différentes parties de la phrase. Pour ceux qui aiment la prose germanique, voici comment une phrase sera découpée par les différents modules:

« Heute Abend war ich mit meiner Freundin im Kino und habe viel gelacht ».

Le caractère ‘+’ désigne une combinaison de mot, le caractère ‘#’ désigne un mot non pris en compte.

Module WORD

Chaque mot représente un token, on a donc 13 tokens.

•	TOKEN: ‘Heute’ CRC: 6716984897371635712
•	TOKEN: ‘Abend’ CRC: 6670531613365895168
•	TOKEN: ‘war’ CRC: 4772677679197454336
•	TOKEN: ‘ich’ CRC: 6329956816985784320
[...]
Module CHAIN

Le mot est lié au mot qui le suit, on a donc un token de moins, soit 12 tokens.

•	TOKEN: ‘Heute+Abend’ CRC: 9299536586222406967
•	TOKEN: ‘Abend+war’ CRC: 5205867775940263209
•	TOKEN: ‘war+ich’ CRC: 6329956649787979024
•	TOKEN: ‘ich+mit’ CRC: 5158416839735805488
[...]
Module OSB (Orthogonal Sparse biGram)

Pour chaque mot, on crée une fenêtre glissante de 5 mots autour du mot. On va donc associer le mot en cours avec un voisin dans un rayon de -4/+4 positions autour du mot. Cela crée un total de 55 tokens, la formule étant ( ( nb_mots - taille_fenetre ) * taille_fenetre ) + ( ( taille_fenetre - 1 * taille_fenetre ) /2 ).

•	TOKEN: ‘Heute+#+#+#+mit’ CRC: 2006452661602586241
•	TOKEN: ‘Abend+#+#+mit’ CRC: 5482652074219693289
•	TOKEN: ‘war+#+mit’ CRC: 15707817493435847227
•	TOKEN: ‘ich+mit’ CRC: 5158416839735805488
•	TOKEN: ‘Abend+#+#+#+meiner’ CRC: 8544044731047037263
•	TOKEN: ‘war+#+#+meiner’ CRC: 14722667808637756004
[...]
Module SBPH (Sparse Binary Polynomial Hashing)

Similaire à OSB, mais plus complet, car on va ici utiliser une fenêtre glissante de 5 mots, mais également considérer les mots intermédiaires dans la fenêtre, et pas seulement les ignorer (représenté par un ‘#’ dans OSB). On a donc 159 tokens, la formule étant (nb_mots - ( taille_fenetre -1 ) )*(2^( taille_fenetre -1 ) )+( ( ( taille_fenetre-1 ) ^2 )-1 ).

•	TOKEN: ‘mit’ CRC: 5158417007107899392
•	TOKEN: ‘ich+mit’ CRC: 5158416839735805488
•	TOKEN: ‘war+#+mit’ CRC: 15707817493435847227
•	TOKEN: ‘war+ich+mit’ CRC: 6905336139605378569
•	TOKEN: ‘Abend+#+#+mit’ CRC: 5482652074219693289
•	TOKEN: ‘Abend+#+ich+mit’ CRC: 2006454003823721484

Évidemment, avec 159 tokens produits par SBPH, contre seulement 12 pour CHAIN ou WORD, le volume du dictionnaire n’est pas le même.

Mais l'énorme avantage des tokenizers comme OSB et SBPH est qu'ils peuvent identifier des phrases qu'ils n'ont jamais vu auparavant. Et ce en utilisant la combinaison (caractère '+') et le saut (caractère '#').

Par exemple, imaginons le token « Buy+#+Viagra ». Cet unique token est capable d'identifier des phrases du type:

  • Buy cheep Viagra
  • Buy good Viagra
  • Buy herbal Viagra
  • Buy exclusive Viagra
  • Buy boosting Viagra
  • Buy fresh Viagra
  • Buy qualitative Viagra

Alors que dans la même situation, le tokenizer WORD serait capable d'identifier les mots individuellement, mais pas leur combinaison. Et le tokenizer CHAIN ne verrait quasiment rien…à moins d'avoir toutes les combinaisons dans le dictionnaire.

SBPH possède également un mécanisme de poids associés aux tokens. Ainsi, un token possédant 5 mots aura un poids beaucoup plus important qu'un token ne possédant qu'un seul mot, selon la formule: poids = 2^(2*n), avec « n » représentant le nombre de mots voisins pris en compte.

En jonglant toujours avec notre prose germanique, la table de poids pour la phrase « Heute Abend war ich mit » est la suivante:

TokenPoids
Heute1
Heute+Abend4
Heute+#+war4
Heute+Abend+war16
Heute+#+#+ich4
Heute+Abend+#+ich16
Heute+#+war+ich16
Heute+Abend+war+ich64
Heute+#+#+#+mit4
Heute+Abend+#+#+mit16
Heute+#+war+#+mit16
Heute+Abend+war+#+mit64
Heute+#+#+ich+mit16
Heute+Abend+#+ich+mit64
Heute+#+war+ich+mit64
Heute+Abend+war+ich+mit256

Le poids correspondant est ensuite utilisé pour multiplier l'impact du token lors du calcul de probabilité.

Pour la configuration présentée dans cet article, nous nous contenterons du tokenizer OSB. Mais que cela ne vous empêche pas d'expérimenter avec SBPH.

La directive à placer dans 'dspam.conf' est donc la suivante:

Tokenizer osb

3.2.2 Algorithme statistique

Avec tous ces tokens, la difficulté est de déterminer lesquels influencent la décision, et dans quelles proportions. Comme nous l’avons dit, DSPAM n’est pas livré avec un dictionnaire pré-remplit. Il ne sait pas dire, immédiatement, si le token 'Abend+#+#+mit' est pertinent pour déterminer si le message est un spam, ou non. Mais il apprend, et au fur et à mesure de son apprentissage, il va moduler les probabilités associées à ces tokens, et appliquer cela aux nouveaux messages.

Au delà du pur calcul de probabilité, l'algorithme statistique permet de définir des critères à prendre en compte lors du calcul. DSPAM nous donne le choix entre plusieurs algorithmes statistiques que sont:

  • naive: Naive-Bayesian (All Tokens)
  • graham: Graham-Bayesian (“A Plan for Spam”)
  • burton: Burton-Bayesian (SpamProbe)
  • chi-square: Fisher-Robinson's Chi-Square Algorithm

Il est également possible de cumuler ces algorithmes. Une combinaison graham+burton montre généralement un bon ratio faux positifs / faux négatifs. C’est donc ce que nous allons utiliser, via la directive suivante:

Algorithm graham burton

Mais faisons un petit retour en arrière, pour bien comprendre la mécanique derrière cette directive. L'approche naïve (de l'algorithme du même nom) considère l'ensemble des tokens composants un message. Chaque token est initialisé avec une valeur statistique neutre, soit 0.5 qui signifie donc 'ni spam ni ham' (proche de 1 égal spam). Mais le problème avec ce fonctionnement naïf, c'est qu'un spammer peut inclure un long texte contenant des mots tout à fait commun (« et », « bonjour », …), et une ou deux phrases contenant le message de spam, et l'algorithme va traiter l'ensemble au même niveau, permettant au texte « support » de réduire la probabilité finale du message d'être un spam.

Cette approche a été discutée par Paul Graham, toujours lui, pour démontrer qu'une approche plus optimale été possible. Ainsi, l'algorithme de Graham utilise les critères suivants:

  1. Analyse le message et sélectionne les 15 tokens les plus pertinents. Les tokens choisis sont ceux qui ont la plus forte déviation de la probabilité neutre 0.5.
  2. Ignorer les tokens qui ont été vu moins de 5 fois par le passé.
  3. Ne prendre les tokens qu'une seule fois. Si un token est présent deux fois dans le message, la seconde occurrence ne sera pas prise en compte dans le calcul.
  4. Lors de l'ajout de nouveaux tokens, définir une probabilité initiale de 0.4 au lieu de 0.5. Cela permet de préjuger un token comme innocent.

Burton, de Brian Burton, utilise une version modifiée de Graham. Le nombre de tokens considérés passe de 15 à 27, et si un token pertinent est présent plusieurs fois, alors il sera pris en compte plusieurs fois. Son algorithme a été intégré dans l'antispam SpamProbe.

Toujours dans la mouvance du début des années 2000, Gary Robinson publia en 2003 dans Linux Journal sa version améliorée de l'algorithme de Graham. Sa propre version est au coeur du projet SpamBayes, un autre moteur de classification, et présente une approche plus performante pour traiter les tokens qui apparaissent rarement. Son approche est basée sur le test statistique Chi-Square, d'où le nom de la directive dans DSPAM.

Difficile de dire lequel de ces algorithmes est le plus pertinent. Tous obtiennent d'excellents résultants, libre à vous d'expérimenter avec celui de votre choix.

3.2.3 Calcul de probabilités

Nous avons donc des tokens, dont la valeur initiale est 0.4 en utilisant l'algorithme de Graham, et des critères de calcul permettant d'analyser les messages.

La dernière étape consiste à calculer la probabilité qu'un message soit un Spam, ou non. C'est ce que DSPAM appel la Pvalue, et il fournit trois algorithmes pour réaliser ce calcul.

Ces algorithmes statistiques sont 'markov' (du mathématicien russe Andrey Markov), 'robinson' (de Gary Robinson) et 'bcr' (Bayesian Chain Rule, de Paul Graham).

L'algorithme standard, celui souvent pris en exemple, est 'bcr', Bayesian Chain Rule, qui est également l'algorithme décrit par Paul Graham dans son article « A Plan for Spam ». C'est donc celui-ci que nous allons utiliser.

Pvalue bcr
Le filtrage Bayesien

Dès que l'on parle de technologie antispam, les travaux de Thomas Bayes sont systématiquement cités. Le théorème de Bayes permet de calculer la probabilité de survenance d'un événement sur sa survenance constatée par le passé. En d'autres termes: sur l'expérience.

En ce qui nous concerne, la formule qui permet de calculer la probabilité qu'un message soit un spam ou non est la suivante: P = S / (S + H)

Avec:

  • P: la (P)robabilité que le message soit un spam
  • S: produit des probabilité associées aux tokens composants le message
  • soit: P(token-1) * P(token-2) * … * P(token-n)
  • H: inverse des probabilités des tokens
  • soit: (1 - P(token-1)) * (1-P(token-2)) * … * (1-P(token-n))

Comme on l'a vu, lorsqu'un token est ajouté dans le dictionnaire, il prend une valeur par défaut (0.4 avec Graham). Puis, chaque fois que DSPAM apprend un message contenant le même token, il modifie cette valeur. Ainsi, si le mot « Viagra » est présent dans 10 messages, dont 9 spams, la probabilité associée à ce token sera: P(Viagra) = 9 / (9+1) = 0.9

Prenons un exemple. Considérons le message « Hi! Buy Viagra ». On va appliquer à ce message un tokenizer de type WORD (plus simple à manipuler pour l'exercice).

La première chose que fait le tokenizer est de supprimer les caractères non pris en compte, comme le point d'exclamation. Le message est donc « Hi Buy Viagra ».

Chaque mot est un token à part entière (c'est le fonctionnement de WORD), on peut donc imaginer que le dictionnaire de l'utilisateur est dans l'état suivant:

TokenNb de Spam (s)Nb de Ham (h)Probabilité p=s/(s+h)
Hi25620.29
Buy157870.64
Viagra231110.95

On peut finalement calculer la Pvalue du message avec la formule de Bayes:

S = 0.29*0.64*0.95=0.176

H = (1-0.29)*(1-0.64)*(1-0.95) = 0.71 * 0.36 * 0.05 = 0.0127

Pvalue = S / (S+H) = 0.176 / (0.176 + 0.0127) = 0.93

Donc, la probabilité finale que ce message soit un spam est de l'ordre de 93%.

Markov

Toutefois, dans le cas particulier où le tokenizer est SBPH, il est possible d'utiliser la notion de poids des tokens dans le calcul statistique. C'est ce que fait la méthode 'markov', qui ne fonctionne donc que si SBPH est activé (et aucun autre, pas même OSB).

Donc en pratique, markov est une amélioration de bcr qui permet de dire que si un token est très précis ( par exemple, 5 mots sur 5), alors son impact dans le calcul est très important (256 fois plus important qu'un token n'ayant qu'un mot). La valeur de poids d'un token est ainsi utilisée pour multiplier l'importance de la probabilité associée au token dans le calcul global.

Confiance

DSPAM exporte une valeur de confiance par rapport au résultat produit. La confiance est calculé en fonction de la probabilité que le message soit un spam ou non.

Quand le message est innocent, plus la probabilité associée est basse (proche de zéro), plus DSPAM est confiant dans son résultat: la confiance est élevée. Ainsi, si le message est innocent, confiance égal (1 – probabilité). (exemple: probabilité = 0.0184 ; confiance = 1 – 0.0184 = 0.9816).

Quand le message est un spam, plus la probabilité associée est élevée (proche de un), plus DSPAM est confiant dans son résultat. Ainsi, si le message est un spam, confiance égal probabilité.

Ce sera tout pour les mathématiques. Si le sujet vous passionne, n'hésitez pas à poser vos questions sur la mailing list de DSPAM, les gentils développeurs ne manqueront pas de vous envoyer tous les papiers de recherches correspondants.

3.3 Volume des dictionnaires

Les Tokens que nous allons générer prennent de la place, beaucoup de place. DSPAM permet de configurer la taille du fichier de Hash que chaque utilisateur remplira au fur et à mesure (son dictionnaire), et avec un tokenizer comme OSB, il faut prévoir assez large.

Par exemple, un compte plutôt actif, recevant entre 200 et 300 messages par jour, arrivera au alentour de 2.5 millions de tokens en l'espace de deux semaines. Évidemment, cette valeur va énormément varier selon que les messages contiennent des tokens similaires, ou non.

En positionnant la valeur de 'HashRecMax' à plus de 6 millions d’entrées, on donne un peu de marge de manœuvre à DSPAM, mais on va toutefois lui laisser la possibilité d’augmenter cette valeur jusqu’à 16 millions (par pallier de 50000), juste au cas où.

HashRecMax              6291469
HashAutoExtend          on
HashMaxExtents          10000000
HashExtentSize          49157

Cela signifie également que le fichier de hash d’un utilisateur sera initialisé avec une taille proche de 100Mo ! Ca peut être gênant sur un système gérant un grand nombre d’utilisateurs.

3.4 Whitelist

DSPAM a la possibilité d’observer les émetteur de messages pour un destinataire donné, et de positionner en whitelist les émetteurs qui ont envoyés plus de 20 messages sans qu’aucun n’ait été marqué comme spam. Cette fonction, plutôt pratique, n’a pas besoin d’autre configuration que la directive:

Feature whitelist

3.5 Les préférences

Chaque utilisateur peut déterminer son jeu de préférences via l’interface web que nous installerons plus tard. Il est toutefois possible de positionner des valeurs par défaut que l’utilisateur pourra, ou non, modifier.

Par exemple, la configuration par défaut ne délivre pas les spams aux utilisateurs, mais les placent en quarantaine. Pour changer ce comportement, il faut modifier les directives suivantes:

Preference « spamAction=tag »     # { quarantine | tag | deliver } -> default:quarantine
Preference « spamSubject=[SPAM] » # { string } -> default:[SPAM]
Preference « tagSpam=on »         # { on | off }
Preference « tagNonspam=off »     # { on | off }

On laisse toutefois la possibilité aux utilisateurs de modifier ces options (dans l’interface web) via les directives:

AllowOverride spamAction
AllowOverride spamSubject
AllowOverride tagSpam
AllowOverride tagNonspam

Il est également possible d’enlever la signature DSPAM du corps des messages via la préférence:

Preference « signatureLocation=message »  # { message | headers } -> default:message

Dans le mesure où cette dernière se fait rapidement oublier, et permet de réapprendre les spams sur un simple forward, comme nous allons le voir juste après, il est préférable de conserver la signature dans le corps des messages.

3.6 dspam.conf

En résumé,votre fichier de configuration final devrait ressembler au listing ci-dessous. De nombreuses options sont encore configurables, mais pour un rapide tour d’horizon, cette configuration est fonctionnelle.

Home /var/spool/dspam/
StorageDriver /usr/lib/dspam/libhash_drv.so
TrustedDeliveryAgent « /usr/bin/procmail »
DeliveryHost            127.0.0.1
DeliveryPort            10034
DeliveryIdent           localhost
DeliveryProto           SMTP
OnFail error
Trust root
Trust dspam
TrainingMode teft
TestConditionalTraining on
Feature whitelist
Feature tb=5
Algorithm graham burton
Tokenizer osb
Pvalue bcr
WebStats on
Preference « trainingMode=TEFT »
Preference « spamAction=tag »
Preference « spamSubject=[SPAM] »
Preference « statisticalSedation=5 »
Preference « enableBNR=on »
Preference « enableWhitelist=on »
Preference « signatureLocation=message »
Preference « tagSpam=on »
Preference « tagNonspam=off »
Preference « showFactors=on »
Preference « optIn=off »
Preference « optOut=off »
Preference « whitelistThreshold=20 »
Preference « makeCorpus=off »
Preference « storeFragments=off »
Preference « localStore= »
Preference « processorBias=on »
Preference « fallbackDomain=off »
Preference « trainPristine=off »
Preference « optOutClamAV=off »
Preference « ignoreRBLLookups=off »
Preference « RBLInoculate=off »
Preference « notifications=on »
AllowOverride enableBNR
AllowOverride enableWhitelist
AllowOverride fallbackDomain
AllowOverride ignoreGroups
AllowOverride ignoreRBLLookups
AllowOverride localStore
AllowOverride makeCorpus
AllowOverride optIn
AllowOverride optOut
AllowOverride optOutClamAV
AllowOverride processorBias
AllowOverride RBLInoculate
AllowOverride showFactors
AllowOverride signatureLocation
AllowOverride spamAction
AllowOverride spamSubject
AllowOverride statisticalSedation
AllowOverride storeFragments
AllowOverride tagNonspam
AllowOverride tagSpam
AllowOverride trainPristine
AllowOverride trainingMode
AllowOverride whitelistThreshold
AllowOverride dailyQuarantineSummary
AllowOverride notifications
HashRecMax              6291469
HashAutoExtend          on
HashMaxExtents          10000000
HashExtentSize          49157
HashPctIncrease         10
HashMaxSeek             10
HashConnectionCache     10
Notifications   on
PurgeSignatures 14
PurgeNeutral    90
PurgeUnused     90
PurgeHapaxes    30
PurgeHits1S     15
PurgeHits1I     15
LocalMX 127.0.0.1
SystemLog       on
UserLog         on
Opt out
ServerHost              127.0.0.1
ServerPort              10033
ServerQueueSize 32
ServerPID               /var/run/dspam.pid
ServerMode auto
ServerParameters        « --deliver=innocent,spam -d %u »
ServerIdent             « localhost.localdomain »
ProcessorURLContext on
ProcessorBias on
StripRcptDomain off

4. Un petit test qui ne va pas marcher

Pour lancer le daemon sous l’utilisateur 'dspam', la méthode standard Debian est de passer par start-stop-daemon, de la façon suivante:

# start-stop-daemon --start --chuid dspam --exec /usr/bin/dspam -- --daemon

DSPAM crée son pid automatiquement dans /var/run. Assurez vous que l’utilisateur dspam peut écrire dans ce répertoire.

On obtient un processus démarré et un port en écoute:

UID        PID  PPID  C STIME TTY          TIME CMD
dspam    27473     1  0 03:26 pts/0    00:00:00 /usr/bin/dspam --daemon

Proto Recv-Q Send-Q Local Address    Foreign Address State  User Inode  PID/Program name 
tcp     0            0          127.0.0.1:10033  0.0.0.0:*           LISTEN 999  18244  27473/dspam

Le daemon répond bien au requête sur ce port, voyons donc ce qu’il se passe lorsque l’on tente de lui envoyer un email :

$ nc localhost 10033
220 DSPAM LMTP 3.9.1 Ready
lhlo mail
250-localhost.localdomain
250-PIPELINING
250-ENHANCEDSTATUSCODES
250-8BITMIME
250 SIZE
mail from:<jp.troll@gmail.com>
250 2.1.0 OK
rcpt to:<jean-kevin@debian.lab>
250 2.1.5 OK
data
354 Enter mail, end with « . » on a line by itself
From: Jean-Pierre Troll <jp.troll@gmail.com>
To: Jean-Kevin De La Motte <jean-kevin@debian.lab>
Subject: This is Not a Spam
might be a troll, but a spam... no!
.
421 4.3.0 <jean-kevin@debian.lab> Unable to connect to server quit
221 2.0.0 OK

DSPAM prend bien en compte notre message mais semble avoir des difficultés à l’envoyer à destination, ce qui est tout à fait normal car nous n’avons pas configuré Postfix pour qu’il reprenne les messages traités par DSPAM.

Toutefois, jetons un coup d’œil au répertoire Home de DSPAM. Ce dernier à créé une arborescence pour l’utilisateur qui vient d’arriver dans /var/spool/dspam/data/debian.lab/jean-kevin/:

# tree -s
.
+-- [         23]  data
¦   +-- [         23]  debian.lab
¦       +-- [        114]  jean-kevin
¦           +-- [  100663544]  jean-kevin.css
¦           +-- [          0]  jean-kevin.lock
¦           +-- [         85]  jean-kevin.log
¦           +-- [         40]  jean-kevin.sig
¦           ¦   +-- [        384]  4c873bcd274731106759975.sig
¦           +-- [         12]  jean-kevin.stats
+-- [          6]  log
+-- [        115]  system.log

Regardons de plus près ces fichiers, on a un fichier ‘jean-kevin.css’ dont la taille, 100Mo, nous rappelle fortement celle spécifiée pour le fichier de hash dans dspam.conf: c'est le dictionnaire.

Ensuite, le fichier ‘jean-kevin.log’ contient la liste des messages traités. On y retrouve la trace de notre message:

# cat jean-kevin.log
1283931466 I Jean-Pierre Troll <jp.troll@gmail.com>  4c873d4a274731062016872 This is Not a Spam Delivered

Chaque ligne a 6 colonnes: un timestamp au format unix, un statut d’inspection (Inspected, Whitelisted, …), l’émetteur, la signature, le sujet du message et enfin le status DSPAM. Dans notre cas, le message est marqué ‘Delivered’ car, malgré l’erreur de connexion vers Postfix, DSPAM considère ce message comme valide.

Le répertoire ‘jean-kevin.sig’ contient un fichier dont le nom est la signature du message traité. Cette signature est en fait localisée en trois points: dans le log que nous venons de voir, dans le répertoire de signatures de l'utilisateur, et dans le corps du messages reçu par l'utilisateur.

Lorsque jean-kevin veux ré-apprendre un message, DSPAM va prendre la signature, rechercher un fichier ayant comme nom cette signature dans le répertoire 'jean-kevin.sig', et mettre à jour les tokens contenus dans 'jean-kevin.css' à partir de ce fichier.

Cette configuration de DSPAM est donc fonctionnelle, passons maintenant à la communication avec Postfix.

5. Configurer Postfix pour communiquer avec DSPAM

Postfix a une méthode générique pour communiquer avec les logiciels comme DSPAM. Il les traitent en tant que Content-Filter, et peux, très facilement, faire suivre un message reçu vers un content-filter via son fichier master.cf.

Sur une configuration vierge de Postfix, on peut ajouter le content-filter directement dans le service smtp principal (celui qui écoute sur le port TCP/25). Pour cela, il faut modifier /etc/postfix/master.cf comme suis:

# Postfix master process configuration file.  For details on the format 
# of the file, see the master(5) manual page (command: « man 5 master »).
#
# ==========================================================================
# service type  private unpriv  chroot  wakeup  maxproc command + args 
#               (yes)   (yes) (yes)   (never) (100)
# ==========================================================================
smtp      inet  n       -       -       -       -       smtpd
	-o content_filter=lmtp:127.0.0.1:10033

Cela suffit pour que Postfix envoi les mails entrant vers DSPAM. Toutefois, pour configurer la route retour, il faut, cette fois, ouvrir un nouveau service dans master.cf, qui écoutera en SMTP sur le port TCP/10034. On mettra cette fois les nouvelles lignes à la fin du fichier master.cf.

127.0.0.1:10034 inet n  -       n       -        -      smtpd
	-o content_filter=
	-o receive_override_options=no_unknown_recipient_checks,no_header_body_checks
	-o smtpd_helo_restrictions=
	-o smtpd_client_restrictions=
	-o smtpd_sender_restrictions=
	-o smtpd_recipient_restrictions=permit_mynetworks,reject
	-o mynetworks=127.0.0.0/8
	-o smtpd_authorized_xforward_hosts=127.0.0.0/8

Il faut ensuite recharger postfix avec ‘postfix reload’. La réception d’email devrait maintenant fonctionner. Recommencez le test précédent avec netcat sur localhost, et vous devriez recevoir le message. Pour débugger, pensez aux fichiers suivants (sous Debian):

  • /var/log/mail.info contient tous les logs relatifs aux processing d'emails
  • /var/spool/dspam/system.log qui contient l'activité globale de DSPAM (une ligne par message traité)
  • si vous avez compilé avec le mode debug, activez 'Debug *' dans dspam.conf et vous aurez des logs détaillez dans /var/spool/dspam/log/
  • et, dans le pire des cas, 'tcpdump -s 16436 -SvnXi lo tcp and port 10033' (ou 10034) pour écouter la communication entre postfix et DSPAM

Une fois le mail passé par Postfix, puis DSPAM, puis Postfix, il devrait être reçu par le destinataire sous la forme suivante:

From jp.troll@gmail.com  
Wed Sep  8 04:02:27 2010 
Return-Path: <jp.troll@gmail.com>
X-Original-To: jean-kevin@debian.lab
Delivered-To: jean-kevin@debian.lab
From: Jean-Pierre Troll <jp.troll@gmail.com>
To: Jean-Kevin De La Motte <jean-kevin@debian.lab> 
Subject: This is Not a Spam
Date: Wed,  8 Sep 2010 03:56:49 -0400 (EDT)
X-DSPAM-Result: Innocent
X-DSPAM-Processed: Wed Sep  8 04:02:27 2010
X-DSPAM-Confidence: 0.9899
X-DSPAM-Probability: 0.0000 
X-DSPAM-Signature: 4c874313289291828119542

might be a troll, but a spam... no!

!DSPAM:4c874313289291828119542!

Le message est ‘innocent’, comme indiqué dans ‘X-DSPAM-Result’.

‘X-DSPAM-Probability’ nous indique la probabilité que le message soit un spam (plus la valeur est proche de 1, plus la probabilité d’être un spam est forte).

Enfin, ‘X-DSPAM-Confidence’ indique le niveau de confiance du filtre.

Si vous voulez un peu plus de détails sur les tests réalisés et les tokens pris en compte, activez la préférence « showFactors=on ». C’est verbeux, mais instructif. Chaque token est alors listé avec la valeur statistique associée.

X-DSPAM-Factors: 27,
	To*La+#+#+kevin, 0.01000,
	Subject*This+#+#+a, 0.01000,
	To*La+#+<jean, 0.01000,
	To*Kevin+#+La, 0.01000,
	To*Motte+<jean, 0.01000,
	[...]

Le corps du mail contient également la signature sous la forme « !DSPAM:<signature>! ». On l'a déjà dit, il est préférable de conserver la signature dans le corps des message car, de cette façon, elle n’est pas supprimée lors des éventuels forward pour apprentissage. L'autre option serait de placer la signature dans les headers uniquement, mais ces derniers sont généralement supprimés lors d'un forward, DSPAM ne retrouvera donc pas ses petits.

6. Gérer les faux-positifs et les faux-negatifs

Évidemment, il ne faut pas attendre de DSPAM qu’il apprenne tout, parfaitement, tout de suite. Il faut l’alimenter.

Tout d’abord, il est possible de le faire via la ligne de commande et la signature du message. Ainsi, en reprenant notre message précédent, on peut le signaler comme spam via la commande:

# dspam --source=error --class=spam --user jean-kevin@debian.lab --signature=’4c874313289291828119542’

Dans les logs de l’utilisateur, on verra alors que le message portant cette signature a été ‘retrained’, soit réappris en fonction de la classe spécifiée: spam ou innocent.

# tail -n 1 jean-kevin.log
1283934571 M <None Specified> 4c874313289291828119542 <None Specified> Retrained 

Cette solution n’étant pas la meilleure lorsque l’on a 15000 utilisateurs, il est possible de faire mieux: forwarder les emails à {spam|notspam}-<user>@<domaine>, ou passer par l’interface web. Les deux étant laissés à la main de l’utilisateur.

6.1 Apprentissage en mode forward

L’apprentissage en mode forward fonctionne de la manière suivante: lorsque DSPAM inspecte un message, il positionne une signature dans le corps du message. Un utilisateur peut donc faire suivre (forward) le même message a DSPAM pour lui indiquer qu’il a pris la mauvaise décision: le supposé spam est en fait innocent, ou l’inverse.

Pour cela, DSPAM à besoin de deux choses: la signature du message, et l’identité de l’utilisateur.

La signature permet à DSPAM de retrouver le message dans son historique et d’enregistrer le changement d’état. Sans cette signature, DSPAM n’a pas la possibilité d’identifier le message dans son historique. (Note: l’historique est par défaut conservé 14 jours, c’est la directive ‘PurgeSignatures’).

L’identité de l’utilisateur peut être automatiquement déduite par DSPAM. On va ajouter un préfixe a l’adresse email de l’utilisateur sous la forme {spam|notspam)-<adresse email>. Notre utilisateur ‘jean-kevin@debian.lab’ aura donc deux alias ‘spam-jean-kevin@debian.lab’ et ‘notspam-jean-kevin@debian.lab’ qui seront dédiés au ré-apprentissage.

DSPAM dispose d’une fonctionnalité pour ré-apprendre automatiquement quand un email est délivré à ces alias. En fait, pour chaque message entrant, il va regarder le champ ‘To:’ du corps du message, et si ce dernier contient {spam|notspam}, il va découper le contenu et déclencher un ‘retrain’. La configuration de cette fonction est tout à fait basique, elle passe par les trois directives suivantes du fichier ‘dspam.conf’:

ParseToHeaders on
ChangeModeOnParse on
ChangeUserOnParse full

La directive ‘ParseToHeaders’ informe DSPAM qu’il doit découper le champ ‘To:’ du mail reçu pour déterminer si le message contient les mots clés {spam|notspam}. Ce champ ‘To:’ fait partie du corps du message, à ne pas confondre avec la commande SMTP ‘rcpt to’.

Le parsing activé, DSPAM peut changer de mode d’apprentissage en fonction de la première partie du champ ‘To:’. Cela est contrôlé par ‘ChangeModeOnParse’, qui va donc activer la classe ‘spam’ si l’adresse est ‘spam-*’, et la classe ‘innocent’ si l’adresse est ‘notspam-*’.

Enfin, ‘ChangeUserOnParse’ permet de dire que la partie restante de l’adresse email contient l’identifiant DSPAM de l’utilisateur. En le positionnant à Full, on dit a DSPAM de prendre le user et le domaine comme identifiant, soit ‘jean-kevin@debian.lab’.

Il faut maintenant dire à Postfix que les utilisateurs ‘spam-jean-kevin@debian.lab’ et ‘notspam-jean-kevin@debian.lab’ existent. Dans un environnement de production, vous aurez certainement une base SQL ou un LDAP pour gérer les aliases, mais dans notre cas, on va se contenter de créer deux entrées dans /etc/aliases. Ce sera suffisant pour le test.

# vim /etc/aliases
[...]
spam-jean-kevin: jean-kevin
notspam-jean-kevin: jean-kevin

# postalias /etc/aliases

On peut maintenant se reconnecter à Postfix via netcat et réinjecter le même email que précédemment, en changeant toutefois le destinataire. Les headers peuvent être oubliés, les sections importantes étant le champ To: et la signature DSPAM à la fin du corps du message.

$ nc localhost 25
220 debian.lab ESMTP Postfix (Debian/GNU)
ehlo mail
250-debian.lab
250-PIPELINING
250-SIZE 10240000
250-VRFY
250-ETRN
250-STARTTLS
250-ENHANCEDSTATUSCODES
250-8BITMIME
250 DSN
mail from:<jean-kevin@debian.lab>
250 2.1.0 Ok
rcpt to:<spam-jean-kevin@debian.lab>
250 2.1.5 Ok
data
354 End data with <CR><LF>.<CR><LF>
From:  Jean-Kevin De La Motte <jean-kevin@debian.lab> 
To: <spam-jean-kevin@debian.lab>
Subject: This is Not a Spam
might be a troll, but a spam... no!

!DSPAM:4c874313289291828119542!

250 2.0.0 Ok: queued as 42509114E28
quit
221 2.0.0 Bye

Regardons maintenant les logs de jean-kevin dans DSPAM, on y voit que le message a été ‘retrained’.

1283936972      M       Jean-Kevin De La Motte <jean-kevin@debian.lab> 4c874313289291828119542      This is Not a Spam      Retrained <20100908090905.42509114E28@debian.lab>

DSPAM va ensuite laisser le message continuer son chemin, il retournera donc vers l'utilisateur (le prefixe est supprimé). Un texte est toutefois ajouté a la fin du message pour informer l'utilisateur que le message a fait l'objet d'un ré-apprentissage.

Il faut créer ce texte manuellement. Un pour le spam, un pour le ham. Cela peut être fait de la façon suivante:

# echo 'Scanned and tagged as SPAM by DSPAM on Debian.Lab' > /var/spool/dspam/txt/msgtag.spam
# echo 'Scanned and tagged as HAM by DSPAM on Debian.Lab' > /var/spool/dspam/txt/msgtag.nonspam

6.2 Apprentissage par l'interface web

L’utilisation de l’interface web est indispensable lorsque les messages détectés comme spam ne sont pas délivrés aux utilisateurs mais mis en quarantaine (Preference « spamAction=quarantine »). Les utilisateurs doivent alors régulièrement consulter l’interface pour vérifier qu’aucun faux positif ne se trouve en quarantaine. Il peuvent donc également utiliser l’interface pour marquer les emails comme spam.

Les sources de DSPAM fournissent un répertoire nommé ‘webui’. Il s’agit d’un ensemble de scripts CGI pour contrôler le travail de DSPAM au travers d’une interface web. Pas de surprise, c’est écrit en Perl. Pour l’exécuter, il faudra donc configurer {Apache|lighttpd|nginx|<ton serveur web ici>} pour exécuter des scripts perl en CGI.

Comme la doc existe déjà pour Apache et lighttpd, j’ai choisi de décrire la configuration pour Nginx.

En fait, c’est un plus compliqué que cela, car le CGI doit être capable de déterminer l’identité de l’utilisateur qui se connecte. Donc, Nginx, dans notre cas, va devoir authentifier l’utilisateur et faire suivre son identité à DSPAM.

Nginx ne sait pas exécuter de scripts externes. La seule chose qu’il sait faire, c’est envoyer des requêtes FastCGI vers un socket. On va donc avoir besoin d’un autre programme, qui va se placer entre Nginx et nos scripts CGI, pour exécuter ces derniers, ce programme se nomme ‘fcgiwrap’.

On va également avoir besoin de quelques packages Perl nécessaires aux CGI de DSPAM (pour parser du HTML, afficher des graphes avec GD, etc…).

Donc, en une ligne, nous installons les packages suivants:

# aptitude install nginx fcgiwrap libcgi-pm-perl libhtml-parser-perl libgd-graph-perl libgd-graph3d-perl

L’interface de DSPAM a besoin des droits pour accéder aux données dans ‘/var/spool/dspam’, en lecture comme en écriture puisque nous allons modifier les préférences et les états des dictionnaires. Comme c’est fcgiwrap qui va exécuter les scripts, nous allons lancer celui-ci sous l’utilisateur/groupe ‘dspam’.

On va également donner les droits en écriture à tout le monde dans le socket de fcgiwrap pour que nginx puisse y écrire

Il s’agit ici d’une configuration de test, comme dit le proverbe « Don’t do this at home ».

# vim /etc/init.d/fcgiwrap
[..]
FCGI_USER= »dspam »
FCGI_GROUP= »dspam »
[...]
# /etc/init.d/fcgiwrap restart
# chmod o+w /var/run/fcgiwrap.socket

La configuration de Nginx est ensuite rapide, on fait suivre les requêtes CGI vers fcgiwrap. Il faut également authentifier les utilisateurs afin que DSPAM détermine l’identité du visiteur. Cette identité est stockée dans la variable REMOTE_USER qui est fournit à fcgiwrap.

# vim /etc/nginx/sites-available/default
[...]
	location /dspam/cgi-bin {
		auth_basic      « DSPAM »;
		auth_basic_user_file  /var/www/dspam/passwords; 
		include /etc/nginx/fastcgi_params;
		index dspam.cgi;
		fastcgi_param  SCRIPT_FILENAME $document_root$fastcgi_script_name;
		fastcgi_param REMOTE_USER  $remote_user;
		if ($uri ~ « \.cgi$ »){
			fastcgi_pass  unix:/var/run/fcgiwrap.socket;
	             }
	}
# /etc/init.d/nginx restart

Il faut ensuite créer un fichier ‘/var/www/dspam/passwords’ via l’outil htpasswd. Ce fichier devra contenir une ligne par utilisateur, l’identifiant étant l’adresse email complète.

# htpasswd -c /var/www/dspam/passwords jean-kevin@debian.lab 
New password:
Re-type new password:
Adding password for user jean-kevin@debian.lab
# cat /var/www/dspam/passwords
jean-kevin@debian.lab:H2CigqsDz1U4E
# chown dspam:www-data /var/www/dspam/passwords 
# chmod o-rwx /var/www/dspam/passwords

L’infrastructure étant prête, il faut copier les fichiers de l’interface web. Ils sont disponibles dans les sources de DSPAM, dans le répertoire ‘webui’. On peut copier ces fichiers directement dans le ‘document root’ de nginx.

# cp -r ~/dspam-3.9.1-RC1/webui/* /var/www/dspam/
# chown dspam:www-data /var/www/dspam -R

A ce stade, il nous reste un peu de configuration a faire. Le script ‘/var/www/dspam/cgi-bin/configure.pl’ contient la configuration permettant à l’interface web de consulter les répertoires de DSPAM. Il faut donc vérifier les valeurs de $CONFIG{’DSPAM_HOME’}, $CONFIG{’DSPAM_BIN’}, etc… afin que cela corresponde à notre environnement.

$CONFIG{’DSPAM_HOME’}   = « /var/spool/dspam »; 
$CONFIG{’DSPAM_BIN’}    = « /usr/bin »;
[...]
$CONFIG{’WEB_ROOT’}     = « /dspam/htdocs/ »;
[...]
$CONFIG{’LOCAL_DOMAIN’} = « debian.lab »;

Voilà, avec tout cela, vous devriez être capable d’ouvrir la page http://monserveur/dspam/cgi-bin/. Il faut alors vous loguer, avec l’utilisateur jean-kevin@debian.lab, et vous arriverez sur l’interface de DSPAM.

Elle permet, entre autres, dans l’onglet ‘History’, de réapprendre les messages déjà traités. Vous pouvez également changer les préférences, etc…

L’interface fournit une section d'administration. Pour y accéder, il faut déclarer un administrateur dans le fichier ‘/var/www/dspam/cgi-bin/admins’.

# echo ‘jean-kevin@debian.lab’ >> /var/www/dspam/cgi-bin/admins 

On peut ensuite accéder a l’URL http://monserveur/dspam/cgi-bin/admin.cgi et admirer les beaux graphiques d’activité, ou changer les options par défaut.

7. Gestion des groupes et inoculation

Si DSPAM focalise l’analyse sur l’utilisateur, il permet également à des groupes d’utilisateurs de partager des données. En définissant correctement ces groupes, via leur activité par exemple, on peut s’attendre à des contenus de messages similaires et donc à des statistiques par tokens similaires. Partager ces informations permet d’accélérer l’apprentissage de DSPAM.

En pratique, chaque utilisateur dispose de son dictionnaire de tokens dans le fichier ‘<user>.css’. Ce dictionnaire contient les tokens et les statistiques associées, durement produites par l’utilisateur.

DSPAM permet de partager ces tokens et statistiques de différentes manières (ou types de groupes).

  • Shared: les dictionnaires des utilisateurs sont fusionnés en un seul et unique dictionnaire pour tous les membres du groupe, chaque membre conserve toutefois son propre répertoire de quarantaine. Problème: si un utilisateur a un comportement diffèrent du reste du groupe, il va perturber l'ensemble, à commencer par lui-même.
  • Classification: permet de partager les dictionnaires individuels. Si le dictionnaire d'un utilisateur ne permet pas de déterminer si un message est de type spam ou innocent (confiance<0.65 ou dictionnaire contenant moins de 1000 messages innocents et 250 spams), alors les dictionnaires des autres membres listés dans le groupe sont utilisés. L'analyse s'arrête dès qu'un dictionnaire classe formellement le message. En pratique, ce groupe est une chaîne contenant tous les utilisateurs du groupe et qui est parcourue linéairement jusqu'à ce qu'une décision soit obtenue. Il faut être listé comme membre du groupe pour pouvoir interroger les dictionnaires des autres membres.
  • Global: une variante de Classification. Le type Global permet de définir un groupe de classification dans lequel tous les membres du système peuvent interroger les dictionnaires des membres listés. Si le dictionnaire d'un utilisateur est insuffisant pour classer un message, il demandera alors l'avis des membres du Global, en parcourant la chaîne jusqu'à ce qu'une décision formelle soit obtenue. En clair, Global est une sorte de “conseil des sages” que chaque utilisateur peut interroger.
  • Merged: permet de cumuler un dictionnaire de référence au dictionnaire des utilisateurs. Merged assemble le dictionnaire de l'utilisateur et le dictionnaire de référence pour n'en faire qu'un, et utilise ce nouveau dictionnaire pour l'analyse.
  • Inoculation: Ce dernier type de groupe est un peu particulier. Il correspond au principe de la vaccination, et permet à un utilisateur ayant reçu un spam non détecté d'informer tous les autres utilisateurs que ce message est un spam. Ainsi, chaque utilisateur dispose de son propre dictionnaire, qu'il utilise exclusivement pour l'analyse, mais les utilisateurs peuvent s'échanger des tokens entre eux. Le premier utilisateur est contaminé, les autres sont vaccinés. Ce principe de l’inoculation permet également de définir un utilisateur poubelle, un honeypot à spam, qui recevra exclusivement du spam et permettra donc d’accélérer l’apprentissage pour tout le monde. Ce deuxième mode de fonctionnement est appelé ‘external inoculation’.

7.1 Configuration d'un groupe

La mise en place d’un groupe est plutôt simple, la partie la plus complexe étant de déterminer le bon type de groupe selon l’environnement, et surtout de monitorer le comportement au fil des semaines.

Dans notre exemple, nous allons mettre en place un groupe de type classification. Ce type permettant à chacun de conserver son dictionnaire personnel, nous prenons peu de risques.

DSPAM lit la configuration des groupes dans un fichier texte localisé dans son ‘Home Directory’, sous ‘/var/spool/dspam/group’. Le fichier prend une ligne par groupe sous la forme : <nom du groupe>:<type>:<utilisateur 1>,…,<utilisateur n>

On va créer un groupe de type ‘classification’ comprenant les utilisateurs jean-kevin, julien et root, et l’on va nommer ce groupe ‘class-debian-lab’.

# echo « class-debian-lab:classification:jean-kevin@debian.lab,julien@debian.lab,root@debian.lab » > /var/spool/dspam/group
# chown dspam:dspam /var/spool/dspam/group
# kill `pidof dspam`
# start-stop-daemon --start --chuid dspam --exec /usr/bin/dspam -- --daemon

En activant les traces de debug dans dspam.conf (directive ‘Debug *’ quand dspam est compilé avec le mode debug), on peut constater la création du groupe dans le fichier ‘/var/spool/dspam/log/dspam.debug’.

10150: [09/08/2010 14:43:19] user jean-kevin@debian.lab is member of classification group class-debian-lab
10150: [09/08/2010 14:43:19] adding user julien@debian.lab to classification network group
10150: [09/08/2010 14:43:19] adding user root@debian.lab to classification network group

8. Administration

DSPAM stocke une grande quantité d’informations, que ce soit pour les tokens ou l'historique. Il fournit donc deux outils permettant de faire un peu de nettoyage. Ces outils, ‘dspam_clean’ et ‘dspam_logrotate’, vont respectivement nettoyer les dictionnaires et les fichiers de logs selon les paramètres définit dans dspam.conf.

8.1 dspam_clean

Les directives par défaut de ‘dspam_clean’ conserve les signatures pendant 14 jours et nettoient les tokens peu utilisés au bout de 15, 30 et 90 jours selon le type. Encore une fois, le fichier de configuration fournit par les sources est plutôt bien commenté.

#
# Purge configuration: Set dspam_clean purge default options, if not otherwise 
# specified on the commandline
#
PurgeSignatures 14      # Stale signatures
PurgeNeutral    90      # Tokens with neutralish probabilities
PurgeUnused     90      # Unused tokens
PurgeHapaxes    30      # Tokens with less than 5 hits (hapaxes)
PurgeHits1S     15      # Tokens with only 1 spam hit
PurgeHits1I     15      # Tokens with only 1 innocent hit

Afin de réaliser une purge périodique, il faut ajouter dspam_clean dans cron. Par exemple, avec une commande dans /etc/crontab qui se lance tous les jours à 5h:

0  5    * * *   dspam   /usr/bin/dspam_clean -s -p -u

Cette commande va réaliser la purge des trois types d’informations, à savoir signatures, tokens neutres et tokens non utilisés.

8.2 dspam_logrotate

Ce programme permet de réaliser une rotation des logs système et utilisateur de dspam (ceux stockés dans /var/spool/dspam).

La commande peut être lancée pour un utilisateur précis ou pour l’ensemble du répertoire dspam. Dans notre cas, on veux réaliser la rotation pour tout le monde lorsque les logs dépassent 60 jours. On va donc placer la commande suivante dans crontab:

30 5    * * *   dspam   dspam_logrotate -a 60 -d /var/spool/dspam/data/

9. Procédure de test

Pour terminer cette article, nous allons dérouler la procédure de test indiquée dans la documentation de DSPAM. Cette procédure permet non seulement de vérifier que la configuration est opérationnelle, mais également de se familiariser avec les commandes internes de DSPAM.

Étape 1: créer un utilisateur vierge

# useradd -d /home/michel-rene -U -m michel-rene
# passwd michel-rene

Étape 2: envoyer un cours email à notre nouvel utilisateur

# nc localhost 25 << EOF
ehlo mail
mail from:<jp.troll@gmail.com>
rcpt to:<michel-rene@debian.lab>
data
From: <jp.troll@gmail.com>
To: <michel-rene@debian.lab>
Subject: Cours message de test
10 mots c'est pas assez long pour un troll.
.
quit
EOF

Étape 3: Vérifier les statistiques du compte avec la commande dspam_stats

# dspam_stats michel-rene@debian.lab
michel-rene@debian.lab  TP:     0 TN:     1 FP:     0 FN:     0 SC:     0 NC:     0

On a bien un seul message inspecté par DSPAM dans les statistiques.

Étape 4: Vérifier la liste des tokens et les probabilités associées via dspam_dump

# dspam_dump michel-rene@debian.lab
4311867737599848632  S: 00000  I: 00001  P: 0.4000 LH: Wed Sep  8 21:20:22 2010
9486336444479993084  S: 00000  I: 00001  P: 0.4000 LH: Wed Sep  8 21:20:22 2010
18360635214432484661 S: 00000  I: 00001  P: 0.4000 LH: Wed Sep  8 21:20:22 2010
[…]

Les tokens sont associés à un message innocent, c'est pour cela que la valeur S (pour Spam) est à zéro, et la valeur I (pour Innocent) est à un. Au passage, on notera que le tokenizer 'osb' crée 114 tokens pour ce petit message (quelques headers ont toutefois été ajoutés par Postfix entre deux).

On peut voir la statistique associée à un token particulier en le saisissant sous forme texte dans la ligne de commande. Évidemment, avec OSB comme tokenizer, la difficulté est de connaître la forme de ce token.

# dspam_dump michel-rene@debian.lab un+troll
1157728372545618534  S: 00000  I: 00001  P: 0.4000
# dspam_dump michel-rene@debian.lab assez+#+#+#+troll
695260355258399736   S: 00000  I: 000001  P: 0.4000

Étape 5: Marquer le message comme Spam, par exemple dans l'interface web.

Étape 6: Vérifier les statistiques DSPAM de l'utilisateur encore une fois:

# dspam_stats michel-rene@debian.lab
michel-rene@debian.lab  TP:     0 TN:     0 FP:     0 FN:     1 SC:     0 NC:     0

On voit bien, cette fois, que DSPAM a marque le message comme False Negative (FN).

Étape 7: Vérifier l'état des tokens encore une fois:

# dspam_dump michel-rene@debian.lab
4311867737599848632  S: 00001  I: 00000  P: 0.4000 LH: Wed Sep  8 21:28:31 2010
9486336444479993084  S: 00001  I: 00000  P: 0.4000 LH: Wed Sep  8 21:28:31 2010
18360635214432484661 S: 00001  I: 00000  P: 0.4000 LH: Wed Sep  8 21:28:31 2010
[…]

La mise à jour a bien été faite, ces tokens sont désormais associés à un spam (S est à un, I à zéro).

Ces quelques commandes permettent non seulement de vérifier que notre antispam est fonctionnel, mais également de suivre la vie des tokens au fil du temps.

10. Conclusion

Notre visite guidée de DSPAM est terminée. Je n'ai pas vraiment parlé de taux de réussite et autres critères généralement utilisés pour classer les solutions antispam entre elles. Cela pour deux raisons: La première c'est que ces chiffres sont généralement menteurs, et que les résultats d'un antispam dépendent énormément du comportement d'un utilisateur, difficile donc de sortir des chiffres reproductibles. La seconde raison c'est qu'il n'y a pas vraiment de sens, aujourd'hui, à baser une infrastructure antispam sur un seul produit. Intégrer un système de greylist avec Postfix est trivial, et il est même possible de coupler Spamassasin et DSPAM l'un derrière l'autre (il suffit d'appeler un content-filter vers spamassassin depuis le service de retour de DSPAM dans Postfix).

Ainsi, ma propre configuration ne reçoit qu'entre 2 et 3 % de spams (postgrey est diablement efficace), et sur ce faible pourcentage, plus de la moitié est marqué comme Spam par DSPAM après seulement quelques semaines d'utilisation. Encore un mois ou deux, et ce devrait être 99% des Spams qui seront détectés.

Donc, au final, combattre le spam, c'est multiplier les techniques, mais ce que nous avons vu dans ces pages c'est que DSPAM est un superbe outil pour réaliser ce travail. Peut être un peu difficile à prendre en main au départ, mais le résultat et la flexibilité du produit en valent largement l'investissement initial.

fr/ressources/dossiers/dspam.txt · Last modified: 2011/04/02 19:55 by julien
CC Attribution-Noncommercial-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0