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 back-end 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 e-mail 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 résonance) était donc de découper le contenu d'un e-mail 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 devenue 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'entre 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 fichier README forment l'essentiel de ce qu'il faut savoir à 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 e-mails via des sockets TCP sur localhost. DSPAM doit donc fonctionner en mode Daemon.
2 - Chaque utilisateur sera identifié via son adresse e-mail 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 back-end 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.
Nous voici avec un répertoire /etc/dspam qui contient dspam.conf, et des binaires et bibliothèques installés dans leurs répertoires respectifs.
3. Configuration de DSPAM
Avant d'alimenter DSPAM avec le flux d'e-mails 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 e-mails innocents, mais également les spams. Sur un système en test, il est préférable de tout recevoir dans sa boîte 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'e-mail 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 e-mails 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 e-mails.
Plusieurs modes d'apprentissage existent. Il sont décrits dans man dspam. Celui qui nous intéresse ici se nomme « teft » et forcera DSPAM à apprendre sur chaque e-mail qu'il traitera, innocent comme spam.
Ce mode est particulièrement intensif car il va, pour chaque e-mail, 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 pallier ce 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.
Ces 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éfini 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. Je le remercie au passage d'avoir pris le temps de répondre à mes (nombreuses) questions.
3.2.1 Découpage du contenu
DSPAM appelle 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 mots, 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é 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 :
Token | Poids |
Heute | 1 |
Heute+Abend | 4 |
Heute+#+war | 4 |
Heute+Abend+war | 16 |
Heute+#+#+ich | 4 |
Heute+Abend+#+ich | 16 |
Heute+#+war+ich | 16 |
Heute+Abend+war+ich | 64 |
Heute+#+#+#+mit | 4 |
Heute+Abend+#+#+mit | 16 |
Heute+#+war+#+mit | 16 |
Heute+Abend+war+#+mit | 64 |
Heute+#+#+ich+mit | 16 |
Heute+Abend+#+ich+mit | 64 |
Heute+#+war+ich+mit | 64 |
Heute+Abend+war+ich+mit | 256 |
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é-rempli. 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 composant 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 spammeur peut inclure un long texte contenant des mots tout à fait communs (« 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 était possible. Ainsi, l'algorithme de Graham utilise les critères suivants :
1 - Analyser le message et sélectionner 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é vus 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 cœur 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ésultats, 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 appelle 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
3.2.3.1 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 :
Token | Nb de Spam (s) | Nb de Ham (h) | Probabilité p=s/(s+h) |
Hi | 25 | 62 | 0,29 |
Buy | 157 | 87 | 0,64 |
Viagra | 231 | 11 | 0,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 %.
3.2.3.2 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.
3.2.3.3 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 recherche 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 palier 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 100 Mo ! 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 émetteurs de messages pour un destinataire donné, et de positionner en whitelist les émetteurs qui ont envoyé 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 la 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 démon 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 CSTIME TTY TIME CMD
dspam 27473 1 003: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 démon répond bien aux requêtes sur ce port, voyons donc ce qu’il se passe lorsque l’on tente de lui envoyer un e-mail :
$ 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 a 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, 100 Mo, 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 statut 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 traite en tant que Content-Filter, et peut 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 suit :
# 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 envoie les mails entrants 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’e-mail devrait maintenant fonctionner. Recommencez le test précédent avec netcat sur localhost, et vous devriez recevoir le message. Pour déboguer, pensez aux fichiers suivants (sous Debian) :
- /var/log/mail.info contient tous les logs relatifs aux processing d'e-mails :
- /var/spool/dspam/system.log 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étaillés 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 804: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, 8Sep 2010 03:56:49 -0400 (EDT)
X-DSPAM-Result: Innocent
X-DSPAM-Processed: Wed Sep 804:02:27 2010
X-DSPAM-Signature: 4c874313289291828119542
might be a troll, but a spam... no!
!DSPAM:4c874313289291828119542!
code
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 forwards 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 négatifs
É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 commandes 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 e-mails à {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 à 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 a 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 e-mail 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 e-mail 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 e-mail contient l’identifiant DSPAM de l’utilisateur. En le positionnant à Full, on dit a DSPAM de prendre le nom d’utilisateur 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 alias, 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 e-mail 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
console
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 préfixe est supprimé). Un texte est toutefois ajouté à 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 spams ne sont pas délivrés aux utilisateurs, mais mis en quarantaine (Préférence spamAction=quarantine). Les utilisateurs doivent alors régulièrement consulter l’interface pour vérifier qu’aucun faux positif ne se trouve en quarantaine. Ils peuvent donc également utiliser l’interface pour marquer les e-mails comme spams.
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 fournie à 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 e-mail 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
À ce stade, il nous reste un peu de configuration à 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
, de réapprendre les messages déjà traités. Vous pouvez également changer les préférences, etc.Figure 1 : interface web de DSPAM
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éfinis dans dspam.conf.
8.1 dspam_clean
Les directives par défaut de dspam_clean conservent 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 fourni 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 veut 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 court e-mail à 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: 0TN: 1FP: 0FN: 0SC: 0NC: 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 821:20:22 2010
9486336444479993084 S: 00000 I: 00001 P: 0.4000 LH: Wed Sep 821:20:22 2010
18360635214432484661 S: 00000 I: 00001 P: 0.4000 LH: Wed Sep 821: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 commandes. É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: 0TN: 0FP: 0FN: 1SC: 0NC: 0
On voit bien, cette fois, que DSPAM 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 821:28:31 2010
9486336444479993084 S: 00001 I: 00000 P: 0.4000 LH: Wed Sep 821:28:31 2010
18360635214432484661 S: 00001 I: 00000 P: 0.4000 LH: Wed Sep 821: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.
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ée 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.