====== Postscreen, l'exterminateur de zombies ======
{{ http://www.ed-diamond.com/client/cache/produit/_250____lm147_504.jpg}}
**Par [[http://jve.linuxwall.info|Julien Vehent]]**
** Cet article a été publié dans le numéro [[http://www.ed-diamond.com/produit.php?ref=lmag147&id_rubrique=1&caracteristique=1-2-&caracdisp=2-3-|147 de Gnu Linux Magazine France]] et mis a disposition sous Creative Common par les Editions Diamond.**
Il y a plus d'un an de cela, je vous présentais dans ces pages un formidable répulsif à //Sus scrofa domesticus//, j'ai nommé DSPAM. Au même moment, notre rédacteur en chef préféré m'avouait sa passion déraisonnable et certainement bien personnelle pour ce formidable résidu de cuisine qu'est le pâté de jambon, communément appelé Spam de mon coté de l'atlantique. Fort de ma machine à tokens dopée aux amphétamines (toujours DSPAM), je m'empressais de pondérer les propos du malheureux finnois d'une statistique bien négative, et éventuellement repoussai lesdits échanges aux confins de mon dossier spam (sans pour autant bannir complètement l'intéressé, dont la conversation est toujours des plus constructive et bienvenue).
Heureux de ma vie tranquille, lisant plaisamment mes courriels sans l'once d'une distraction viagriarienne "enlarge your peniche" pendant presque un an, je relâchais donc ma garde. Mais le spammer est malin, et sous prétexte de bricoler des bidules électroniques, le voilà qui libérait toute une armée de zombie spammer prêts à délivrer leurs contenus dégoûtants dans les entrailles de l'internet.
C'était sans compter sur la vigilance du Chevalier Wietse (Venema), qui décida alors que c'était assez, et qu'il était temps de faire quelque chose. C'est ainsi que naquit Postscreen, et fut intégré dans la version 2.8 de Postfix.
===== - La vraie introduction =====
Postscreen est un processus de Postfix qui filtre le courrier entrant le plus tôt possible, l'objectif étant de rejeter le spam avant que cela ne consume des ressources sur le système. Postscreen a plusieurs niveau de filtrages, chaque niveau allant un peu plus dans le détail, et donc consommant un peu plus de ressources. Plus tôt un email est rejeté, meilleur le système se portera, et Postscreen essai donc d'être le plus efficace possible dès les premiers éléments de la connection SMTP.
{{:julien:articles:postscreen.png|}}
Postscreen n'a pas vocation a protéger les utilisateurs interne du server mail. Typiquement, les emails venant de l'extérieur sont dirigés vers le port 25 de postfix, et les emails soumis par les utilisateurs internes vont sur le port 587. Postscreen va prendre en charge les emails reçus sur le port 25 uniquement, comme le montre le diagramme en figure 1.
Ce que Postscreen recherche, c'est un critère discriminant permettant de déterminer si un message provient d'un zombie ou non. S'il décide qu'un émetteur n'est pas un zombie, il place ce dernier en liste blanche pour éviter de consommer des ressources lors du prochain échange.
Certains critères discriminants sont plus simples à détecter que d'autres. Par exemple, il est courant qu'un zombie envoie l'intégralité de son message dès que la connexion est ouverte, cela pour accélérer la vitesse de délivrance avant que son IP ne se retrouve dans une Real-time Blackhole List (RBL) et soit donc bannie des principaux serveurs mails. Toutefois, le protocole SMTP déclare que c'est au serveur de parler le premier, typiquement en envoyant sa bannière. En surveillant les correspondants qui parlent avant leur tour, Postscreen peut détecter les zombies. Il ne va pas, en revanche, inspecter le corps d'un message. Dans la mesure où ce qu'il cherche à savoir, c'est si l'émetteur est un zombie ou non, le contenu du message n'est pas une métrique pertinente, et de toute façon cela consommerait trop de ressources.
D'une manière générale, Postscreen n'introduit pas de technique révolutionnaire dans la lutte contre le Spam. Mais il permet d'intégrer des contrôles efficaces de manière élégante dans un processus léger (comprendre: pas dans 40 scripts qui s'exécutent séquentiellement).
Ce que nous allons voir, c'est que les quelques 15+ lignes de configuration à ajouter à Postfix cachent en fait beaucoup d'intelligence. Et cette balade va également nous permettre de réviser les bases d'un protocole qu'on croit trop souvent être un dinosaure de l'Internet. Oui, SMTP est ancien, mais ce n'est pas pour autant qu'il est simpliste.
===== - Architecture =====
Postscreen est intégré dans Postfix 2.8. Et quand je dis intégré, c'est parce que Postscreen n'est pas dissociable de Postfix. Il n'agit pas en tant que proxy SMTP, mais en tant que module interne directement sous le contrôle du Postfix Master.
Au sein de l'architecture de Postfix, Postscreen se situe en première ligne, juste devant le processus "smtpd". Le diagramme en figure 2, bien que non exhaustif, montre un peu plus en détail comment ces processus s'arrangent.
{{:julien:articles:postfix_arch.png|}}
L'un des principe fondateur de Postfix est son architecte modulaire, pas surprenant donc de retrouver autant de composants sur le chemin d'un email. Mais dans le cas qui nous intéresse, on se concentrera sur **postscreen** et **dnsblog** (ce dernier permettant, comme on va le voir, d'interroger les RBL).
===== - Installation du serveur =====
Nous allons travailler sous Debian. Comme Postfix 2.8 n'est pas présent dans l'actuelle version stable, c'est //wheezy// que nous allons utiliser.
Partons d'une installation de base, presque sans configuration si ce n'est pour quelques commandes SASL afin d'authentifier les utilisateurs. Le fichier **main.cf** ressemble à cela:
myorigin = /etc/mailname
smtpd_banner = $myhostname ESMTP $mail_name (Debian/GNU)
biff = no
append_dot_mydomain = no
# SASL configuration
smtpd_sasl_auth_enable = yes
smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth
smtpd_sasl_local_domain = $mydomain
smtpd_sasl_security_options = noanonymous
smtpd_sasl_authenticated_header = yes
Tous les autres paramètres prenant les valeurs par défaut, on obtient un système qui accepte les emails pour le domaine local du serveur.
==== - Pour le lecteur pressé ====
La configuration de Postscreen se trouve dans **/etc/postfix/main.cf**, avec le reste des paramètres de configuration de Postfix.
On va tout d'abord créer un fichier **/etc/postfix/postscreen_access.cidr** contenant les IP et réseaux autorisés ou rejectés.
# cat /etc/postfix/postscreen_access.cidr
# Rules are evaluated in the order as specified.
# Blacklist 192.168.* except 192.168.0.1.
127.0.0.1 permit
192.168.1.1 permit
192.168.1.0/24 reject
Le reste de la configuration dans **main.cf** est ci-dessous:
# Postcreen configuration
postscreen_access_list = cidr:/etc/postfix/postscreen_access.cidr
postscreen_blacklist_action = enforce
postscreen_dnsbl_sites =
zen.spamhaus.org*3
dnsbl.njabl.org*2
bl.spameatingmonkey.net*2
dnsbl.ahbl.org
bl.spamcop.net
dnsbl.sorbs.net
postscreen_dnsbl_threshold = 3
postscreen_dnsbl_action = enforce
postscreen_greet_banner = Bienvenue et merci d'attendre qu'on vous assigne une place
postscreen_greet_action = enforce
postscreen_pipelining_enable = yes
postscreen_pipelining_action = enforce
postscreen_non_smtp_command_enable = yes
postscreen_non_smtp_command_action = enforce
postscreen_bare_newline_enable = yes
postscreen_bare_newline_action = enforce
Et finalement, il faut activer le processus **postscreen** dans le fichier **/etc/postfix/master.cf**. Par défaut, les connections sur le port 25 sont prises en charge par le processus **smtpd**:
# ==========================================================================
# service type private unpriv chroot wakeup maxproc command + args
# (yes) (yes) (yes) (never) (100)
# ==========================================================================
smtp inet n - - - - smtpd
Il faut remplacer cette ligne par les lignes suivantes, qui vont diriger les connections entrantes vers postscreen, puis vers smtpd et également activer dnsblog.
# ==========================================================================
# service type private unpriv chroot wakeup maxproc command + args
# (yes) (yes) (yes) (never) (100)
# ==========================================================================
smtp inet n - - - 1 postscreen
smtpd pass - - - - - smtpd
dnsblog unix - - - - 0 dnsblog
===== - Tests de pré-acceptance de connexion =====
En SMTP, le serveur parle le premier. C'est évidemment le client qui initie la connexion TCP, mais une fois cette dernière établie, il est de coutume d'attendre que le serveur envoi sa bannière avec le fameux code 220. Postscreen utilise ce délai pour réaliser 3 tests différents.
Le délai, tout d'abord, peut être configuré dans le paramètre **postscreen_greet_wait**. Celui-ci définit combien de temps postscreen doit attendre avant d'envoyer la seconde partie de la bannière (greet banner, voir plus loin).
Ce temps n'est pas gaché, car postscreen l'utilise pour interroger les RBLs et vérifier son access list.
Par défault, postscreen fera attendre un client inconnu pendant 6 secondes (ou 2 secondes en période de forte charge).
Voyons maintenant en détail comment fonctionnent les 3 tests pre-220: Access list, RBLs et PreGreet.
==== - Access List ====
postscreen_access_list = cidr:/etc/postfix/postscreen_access.cidr
postscreen_blacklist_action = enforce
Le fichier CIDR est une "lookup table" au format ASCII, qui n'a donc pas besoin d'être convertie au format DB via la commande **postmap**. Toutefois, la table est chargée en mémoire, donc un //reload// de Postfix (signal SIGHUP sur tous les modules) est nécessaire après chaque changement.
Le fichier **postscreen_access.cidr** est évalué séquentiellement, comme les règles d'un firewall. Donc la règle associée à la première entrée qui correspond est utilisée.
# Rules are evaluated in the order as specified.
# Blacklist 192.168.* except 192.168.0.1.
127.0.0.1 permit
192.168.1.1 permit
192.168.1.0/24 reject
Dans notre exemple précédent, la machine 192.168.1.1 est explicitement autorisée à émettre. On peut tester cela avec la commande **postmap**.
# postmap -q "192.168.1.1" cidr:/etc/postfix/postscreen_access.cidr
permit
En revanche, une autre machine du réseau sera explicitement rejetée:
# postmap -q "192.168.1.55" cidr:/etc/postfix/postscreen_access.cidr
reject
N'importe quelle autre adresse qui n'est pas listée ne retournera rien, et **postmap** sortira avec la valeur de retour "1":
# postmap -q "10.11.12.13" cidr:/etc/postfix/postscreen_access.cidr
# echo $?
1
Si un hôte doit être rejeté, postscreen appliquera l'action définie dans **postscreen_blacklist_action**. Le défaut est configuré à "ignore", ainsi postscreen prendra note du résultat du test dans les logs, mais ne bloquera pas la connexion.
On peut choisir, en plus d'**ignore** l'une des deux options suivante:
* **drop** coupe la connexion TCP avant même de renvoyer une bannière
* **enforced** laisse le client continuer jusqu'à l'étape RCPT TO, et coupe la connexion à ce moment là.
Ces trois actions seront ré-utilisables pour chaque point d'analyse que postscreen réalise. Nous allons nous y intéresser dès maintenant, et ne plus y revenir après.
=== - ignore ===
En mode **ignore**, le défaut, l'envoi fonctionne correctement:
$ nc 192.168.1.220 25
220-Bienvenue et merci d'attendre qu'on vous assigne une place
220 samchiel.localdomain ESMTP Postfix (Debian/GNU)
ehlo mail
250-samchiel.localdomain
250-PIPELINING
250-SIZE 10240000
250-VRFY
250-ETRN
250-AUTH PLAIN
250-ENHANCEDSTATUSCODES
250-8BITMIME
250 DSN
mail from:
250 2.1.0 Ok
rcpt to:
250 2.1.5 Ok
data
354 End data with .
subject: hello world.
test de postscreen.
.
250 2.0.0 Ok: queued as 4D638D40069
quit
221 2.0.0 Bye
=== - enforced ===
En mode **enforced**, c'est déjà différent. Tout d'abord, on voit que postscreen renvoie la pre-bannière et quelques secondes se passent avant de recevoir la seconde partie de la bannière. Ensuite, après la commande MAIL FROM, la connection est refusée avec un message SMTP 550:
$ nc 192.168.1.220 25
220-Bienvenue et merci d'attendre qu'on vous assigne une place
220 samchiel.localdomain ESMTP Postfix (Debian/GNU)
ehlo mail
250-samchiel.localdomain
250-SIZE 10240000
250-VRFY
250-ETRN
250-ENHANCEDSTATUSCODES
250-8BITMIME
250 DSN
mail from:
250 2.1.0 Ok
rcpt to:
550 5.3.2 Service currently unavailable
quit
221 2.0.0 Bye
Au même moment, dans les logs on voit la chose suivante:
Oct 16 16:14:24 samchiel postfix/postscreen[9324]: CONNECT from [192.168.1.165]:49618
Oct 16 16:14:24 samchiel postfix/postscreen[9324]: BLACKLISTED [192.168.1.165]:49618
Oct 16 16:14:35 samchiel postfix/postscreen[9324]: BARE NEWLINE from [192.168.1.165]:49618
Oct 16 16:14:40 samchiel postfix/postscreen[9324]: NOQUEUE: reject: RCPT from [192.168.1.165]:49618: 550 5.3.2 Service currently unavailable; from=, to=, proto=ESMTP, helo=
Oct 16 16:14:46 samchiel postfix/postscreen[9324]: DISCONNECT [192.168.1.165]:49618
Bien que la connection ait été marquée comme BLACKLISTED dès sa réception, postscreen a laissé le client poursuivre ses commandes, pour ne le rejeter qu'après avoir reçu le destinataire.
=== - drop ===
En mode **drop**, on fait moins dans la finesse, et plus dans l'efficacité :
$ nc 192.168.1.220 25
521 5.3.2 Service currently unavailable
Et dans les logs :
CONNECT from [192.168.1.165]:56567
BLACKLISTED [192.168.1.165]:56567
DISCONNECT [192.168.1.165]:56567
Clair, net et précis. On ne s'embarrasse pas d'une discussion avec le client, ce dernier est rejeté immédiatement.
=== - Pour les fanatiques ===
Une des façons d'appliquer le filtrage par IP et d'utiliser la liste des réseaux bannis par spamhaus: le DROP (Don't Route Or Peer). Avec un script simple, on peut récupérer cette liste à intervalle régulier et l'intégrer dans postscreen. L'objectif est de couper toute connexion venant d'un de ces réseaux bannis, et donc d'économiser des ressources.
#! /bin/sh
# ce script est sur github à l'adresse http://1nw.eu/!Wg
URL=http://www.spamhaus.org/drop/drop.lasso
DROPLASSO=$(mktemp)
ACLFILE=$(mktemp)
POSTSCREEN_ACCESS_FILE=/etc/postfix/postscreen_access.cidr
DATE=$(date +%Y%m%d)
wget --quiet $URL -O $DROPLASSO
if [ -e $DROPLASSO ]
then
# decoupe le fichier source sur le point virgule
# et prepare le fichier de destination
for network in $(cat $DROPLASSO | awk '{print $1}' |\
grep -E "^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\/[0-9]{1,2}$")
do
echo -e "$network\treject" >> $ACLFILE
done
fi
cp $POSTSCREEN_ACCESS_FILE{,.bkp$DATE}
cp $ACLFILE $POSTSCREEN_ACCESS_FILE
postfix reload
Ce qui va générer le fichier suivant :
[...]
95.64.43.0/24 reject
95.64.44.0/24 reject
95.64.45.0/24 reject
95.64.46.0/24 reject
95.64.59.0/24 reject
95.64.9.0/24 reject
95.64.98.0/23 reject
On pourrait aussi réaliser cela dans le firewall du serveur directement. Ou bien se baser entièrement sur la RBL que fournit spamhaus. À vous de voir.
==== - PreGreet Banner ====
postscreen_greet_banner = Bienvenue et merci d'attendre qu'on vous assigne une place
postscreen_greet_action = enforce
Lorsqu'un client se connecte à postscreen, ce dernier renvoie une première bannière définit dans le paramètre **postscreen_greet_banner** :
$ nc 192.168.1.220 25
220-Bienvenue et merci d'attendre qu'on vous assigne une place
220 samchiel.localdomain ESMTP Postfix (Debian/GNU)
Ce que l'on ne peut pas voir ici, c'est que la première ligne de la bannière est envoyée immédiatement, mais que la seconde ligne n'est envoyée que 6 secondes plus tard. L'objectif est de piéger les zombies ne respectant pas le protocole, en leur faisant croire que c'est à eux de parler.
Pour bien comprendre l'astuce, il faut jeter un oeil à la RFC 5321 sur SMTP, qui prime la chose suivante:
The format for multiline replies requires that every line, except the
last, begin with the reply code, followed immediately by a hyphen,
"-" (also known as minus), followed by text. The last line will
begin with the reply code, followed immediately by , optionally
some text, and . As noted above, servers SHOULD send the
if subsequent text is not sent, but clients MUST be prepared for it
to be omitted.
For example:
250-First line
250-Second line
250-234 Text beginning with numbers
250 The last line
Traduction: si la réponse renvoyée par le serveur est de la forme **** (ex: 220-Bienvenue blablabla), alors c'est une réponse sur plusieurs lignes. La dernière ligne de la réponse sera de la forme ****.
Postscreen se contente donc de respecter le protocole. Il envoi la première ligne d'une réponse multiligne, puis attend et envoi le reste après quelques secondes. Un client respectant la norme va attendre d'avoir reçu l'ensemble de la bannière avant de parler. Un zombie ne fera certainement pas cela, et se verra donc rejeté avec la ligne suivante dans les logs:
PREGREET 10 after 1.8 from [192.168.1.165]:37925: ehlo zombie.example.net\n
==== - Real-Time Blackhole List (RBL) ====
Les RBLs ont fait partie du paysage des serveurs mails depuis bien longtemps maintenant. Rien de nouveau ici, si ce n'est la manière dont postscreen les utilise.
Outre la possibilité d'interroger une RBL avant même d'allouer des ressources au message, c'est-à-dire sans avoir a charger 40 tonnes de modules Perl et autres joyeusetés, postscreen permet d'allouer un poids à chaque RBL qu'il va interroger. On peut ainsi avoir une relativement longue liste de RBL, certaines de bonnes qualités, d'autres moins, et les associer sans risquer de bloquer tout le traffic.
Mais avant cela, un petit rappel sur le fonctionnement des RBL.
=== - RBL et DNS ===
Une RBL est typiquement un répondeur qui va renvoyer le statut associé à une adresse IP. La particularité des RBL est que la discussion entre le client et le serveur se fait via le protocole DNS, en UDP.
Pas besoin ici d'établir une coûteuse connexion TCP, de faire de l'authentification HTTP en REST, etc etc.... Un simple paquet UDP contenant une requête DNS retourne le statut d'une IP. L'astuce est de fournir l'IP sous la forme inversée (comme pour les enregistrements PTR du reverse DNS) et d'interroger le serveur RBL comme suit:
$ dig +short 12.13.95.143.zen.spamhaus.org @localhost
127.0.0.2
On voit ici la réponse courte (+short). La requête demande le statut de l'IP 143.95.13.12 à zen.spamhaus.org, et il faut pour cela inverser l'ordre des champs et ajouter zen.spamhaus.org à la fin. On peut ensuite lancer cette requête sur n'importe quel resolver DNS (en l'occurrence, l'instance de //unbound// installée sur mon portable), le processus de résolution va chercher la réponse sur les serveurs de Spamhaus.
Si vous voulez le détail, essayez la commande "dig +trace 12.13.95.143.zen.spamhaus.org @8.8.8.8" qui va lancer la même commande au resolver de google.
Ensuite, spamhaus nous renvoie l'IP 127.0.0.2, comme pour avertir que la machine 12.13.95.143.zen.spamhaus.org peut être trouvée à cette adresse, ce qui n'a évidemment pas de sens. En fait, spamhaus retourne un code d'état qui correspond au niveau de dangerosité de l'IP.
^Return Code ^Zone ^Description^
|127.0.0.2 |SBL |Spamhaus SBL Data|
|127.0.0.3 |SBL |Spamhaus SBL CSS Data|
|127.0.0.4 |XBL |CBL Data|
|127.0.0.5 |XBL |Customized NJABL Data|
|127.0.0.10 |PBL |ISP Maintained|
|127.0.0.11 |PBL |Spamhaus Maintained|
Comme notre requête précédente a retournée 127.0.0.2, cela signifie que spamhaus a listé cette adresse dans sa liste "Spamhaus SBL Data". La description exacte est disponible sur spamhaus.org, mais pour faire court : accepter des emails provenant d'IP dans cette liste est une mauvaise idée.
=== - Mise en place ===
En mettant en jeu plusieurs RBLs en parallèle, et en jouant avec les poids de chacune, on obtient ainsi un meilleur contrôle sur le niveau de rejet. Ici, nous allons choisir de placer la barre à un score de 3.
D'une manière générale, zen.spamhaus.org est une source fiable. On peut donc attribuer un poids de valeur 3 à cette RBL. Les suivantes sont un peu moins fiables, et suivant leur réputation, on descend un peu le score. La liste ci-dessous provient d'une étude faite par rob0 sur la mailing-list de postfix début 2011 [http://1nw.eu/!De].
postscreen_dnsbl_sites =
zen.spamhaus.org*3
dnsbl.njabl.org*2
bl.spameatingmonkey.net*2
dnsbl.ahbl.org
bl.spamcop.net
dnsbl.sorbs.net
postscreen_dnsbl_threshold = 3
postscreen_dnsbl_action = enforce
Il est également possible de filtrer la réponse d'une RBL sur un code retour particulier, en précisant ce dernier à côté de l'adresse de la source.
zen.spamhaus.org=127.0.0.2*2
Pondération, filtrage des codes retour et multiples RBLs le tout dans un processus léger géré directement par Postfix. C'est probablement l'une des innovations les plus élégantes que Wietse Venema a mis en place avec cette version.
=== - Vérifier si votre serveur est dans une RBL ===
Cela m'arrive régulièrement : un des fournisseur de RBL décide de //blacklister// l'IP d'un serveur que je maintiens. Soit parce que j'ai oublié de configurer le reverse DNS (ou autre), soit parce que quelqu'un s'en est plaint (aller comprendre).
Du coup, plutôt que de découvrir dans les logs que les emails ne sont pas reçus, j'utilise un script bash tout simple, installé dans **cron.daily**, qui vérifie la présence d'une liste de serveur dans les principales RBLs. Si vous voulez dormir tranquille, je vous conseille d'en faire de même :
#!/usr/bin/env bash
# ce script est sur github à l'adresse http://1nw.eu/!u2
DEBUG="$1"
SRV="smtp.example.net smtp2.example.net smtp.example.com"
RBL="zen.spamhaus.org dnsbl.njabl.org bl.spameatingmonkey.net dnsbl.ahbl.org bl.spamcop.net dnsbl.sorbs.net b.barracudacentral.org spamtrap.trblspam.com swl.spamhaus.org list.dnswl.org"
for server in $SRV
do
ip=$(dig +short $server)
r_ip=$(echo $ip|awk -F"." '{for(i=NF;i>0;i--) printf i!=1?$i".":"%s",$i}')
for rbl in $RBL
do
if [ ! -z "$DEBUG" ]
then
echo "testing $server ($ip) against $rbl"
fi
result=$(dig +short $r_ip.$rbl)
if [ ! -z "$result" ]
then
echo "$server ($ip) is in $rbl with code $result"
fi
if [[ ! -z "$DEBUG" && -z "$result" ]]
then
echo "\`->negative"
fi
done
done
Spécifiez vos machines dans la variable **$SRV**, et lancez le script sans argument (ou avec un argument pour voir le détail).
$ /etc/cron.daily/rbl-check -d
testing smtp.example.net (10.20.30.40) against zen.spamhaus.org
`->negative
testing smtp.example.net (10.20.30.40) against dnsbl.njabl.org
`->negative
testing smtp.example.net (10.20.30.40) against bl.spameatingmonkey.net
`->negative
testing smtp.example.net (10.20.30.40) against dnsbl.ahbl.org
`->negative
Si une IP est présente dans une blacklist, alors le script retournera le code de retour renvoyé par la RBL.
$ /etc/cron.daily/rbl-check
smtp.example.net (10.20.30.40) is in zen.spamhaus.org with code 127.0.0.11
Chaque RBL ayant sa propre procédure d'enlèvement des faux positifs, je vous laisse le plaisir de découvrir cela par vous même.
===== - Tests effectués après le SMTP 220 =====
Une fois la batterie de tests pré-220 passée, postscreen peut réaliser des tests en profondeur sur la communication entre le client et le serveur. Ces tests sont essentiellement basés sur un respect de la norme, que beaucoup de zombie ignorent.
==== - Command pipelining test ====
postscreen_pipelining_enable = yes
postscreen_pipelining_action = enforce
Dans les premiers temps de SMTP, avant que ESMTP (Extended SMTP) ne soit publié en 1995, le protocole était half-duplex. Le client et le serveur ne pouvaient envoyer qu'une commande à la fois, et chacun devait attendre la réponse de l'autre avant de continuer.
Postscreen ne suppose pas ESMTP et son jeu d'extension. C'est pourquoi, lorsqu'un client se connecte à postscreen, ce dernier ne retournera pas l'extension PIPELINING. Un client respectant la norme prendra cela en compte et enverra ses commandes une par une, mais un zombie ignorera certainement cela et cherchera à émettre le plus vite possible.
nc 192.168.1.220 25
220-Bienvenue et merci d'attendre qu'on vous assigne une place
220 samchiel.localdomain ESMTP Postfix (Debian/GNU)
ehlo mail
250-samchiel.localdomain
250-SIZE 10240000
250-VRFY
250-ETRN
250-ENHANCEDSTATUSCODES
250-8BITMIME
250 DSN
mail from:
rcpt to:
data
subject: test
test
.250 2.1.0 Ok
550 5.5.1 Protocol error
554 5.5.1 Error: no valid recipients
502 5.5.2 Error: command not recognized
502 5.5.2 Error: command not recognized
Dans l'exemple ci-dessus, nous avons essayé d'envoyer au même moment un jeu complet de commandes:
mail from:
rcpt to:
data
subject: test
test
.
Postscreen a répondu OK a la première (le "mail from") et renvoyé un code 550 pour la suivante. Simultanément, on trouve la ligne suivante dans les logs:
COMMAND PIPELINING from [192.168.1.165]:39158 after rcpt
==== - Non-SMTP command test ====
postscreen_non_smtp_command_enable = yes
postscreen_non_smtp_command_action = enforce
Ce test est un simple filtre qui bloque les commandes CONNECT, GET et POST utilisées par les zombies lors de connections via des proxy. Ce filtre était déjà présent dans Postfix depuis la version 2.2, donc il s'agit la uniquement de positionner le test en amont du daemon smtpd.
==== - Bare newline test ====
postscreen_bare_newline_enable = yes
postscreen_bare_newline_action = enforce
Encore un test simple mais que beaucoup de client semblent traiter incorrectement. Dans la norme SMTP, il est imposé que chaque fin de ligne soit terminée par les caractères "Carriage Return & Line Feed". Beaucoup de zombies n'utilise que le en fin de ligne, comme c'est le cas lorsque nous testons avec netcat dans un terminal.
$ nc 192.168.1.220 25
220-Bienvenue et merci d'attendre qu'on vous assigne une place
220 samchiel.localdomain ESMTP Postfix (Debian/GNU)
mail from:
250 2.1.0 Ok
rcpt to:
550 5.5.1 Protocol error
La commande ci-dessus génère l'entrée de log suivante: **BARE NEWLINE from [192.168.1.165]:38749**.
En ajoutant l'option **-C** à netcat, ce qui modifie la façon dont ce dernier traite les fins de lignes, le problème disparaît.
$ nc -C 192.168.1.220 25
220-Bienvenue et merci d'attendre qu'on vous assigne une place
220 samchiel.localdomain ESMTP Postfix (Debian/GNU)
mail from:
250 2.1.0 Ok
rcpt to:
450 4.3.2 Service currently unavailable
quit
221 2.0.0 Bye
Et la trace suivante apparaît dans les logs:
CONNECT from [192.168.1.165]:36721
NOQUEUE: reject: RCPT from [192.168.1.165]:36721: 450 4.3.2 Service currently unavailable; from=, to=, proto=SMTP, helo=<>
PASS NEW [192.168.1.165]:36721
DISCONNECT [192.168.1.165]:36721
//"Comment ça le problème disparaît ? Pas vrai ça, postscreen a répondu avec un code 450 !"//
Oui, et merci à toi Jean-Kevin d'avoir suivi jusqu'au bout, et de nous offrir une transition impeccable. Parlons donc un peu de Greylist...
==== - Greylist ====
Comme on vient de le voir, un client qui passe tous les tests en profondeur se retrouve déconnecté sans raison apparente. Rassurez-vous, postscreen n'est pas fou, et c'est là le travail de sa dernière fonction.
Postscreen n'est pas un serveur SMTP; et il n'a qu'une connaissance très limitées des commandes SMTP. Donc ces tests en profondeur posent un problème : ils obligent postscreen à prendre en charge la communication jusqu'au point où il pourra décider si un client est légitime ou non. Mais à ce moment-là, postscreen n'a aucun moyen de donner la connexion au daemon smtpd.
La solution que Venema a trouvé est toute simple: reproduire le mécanisme de liste grise. Quand un nouveau client arrive, il est pris en charge par postscreen. Si le client passe tous les tests correctement, postscreen le met en liste blanche et coupe la connexion avec un code "450 Service indisponible".
Le client doit donc établir une nouvelle connexion, qui sera cette fois adressée directement au daemon smtpd, sans passer par postscreen.
La première connexion du client produira le comportement suivant:
$ nc 192.168.1.220 25
220-Bienvenue et merci d'attendre qu'on vous assigne une place
220 samchiel.localdomain ESMTP Postfix (Debian/GNU)
ehlo mail
250-samchiel.localdomain
250-SIZE 10240000
250-VRFY
250-ETRN
250-ENHANCEDSTATUSCODES
250-8BITMIME
250 DSN
mail from:
250 2.1.0 Ok
rcpt to:
450 4.3.2 Service currently unavailable
quit
221 2.0.0 Bye
Le client a réussi tous les tests et postscreen le déconnecte. Dans les logs, on voit la ligne **PASS NEW [192.168.1.165]** qui signifie que le client a été ajouté à la WHITELIST.
Lors de la seconde connexion, point de double bannière, le client est envoyé directement au daemon smtpd et la ligne suivante est loguée **PASS OLD [192.168.1.165]**.
$ nc 192.168.1.220 25
220 samchiel.localdomain ESMTP Postfix (Debian/GNU)
ehlo mail
250-samchiel.localdomain
250-PIPELINING
250-SIZE 10240000
250-VRFY
250-ETRN
250-AUTH PLAIN
250-ENHANCEDSTATUSCODES
250-8BITMIME
250 DSN
mail from:
250 2.1.0 Ok
rcpt to:
250 2.1.5 Ok
Et voilà comment, presque sans le vouloir, postscreen vient de réimplémenter les listes grises. On notera toutefois que mécanisme de liste grise induit toujours un délai, et que ce dernier peut parfois aller jusqu'à plusieurs heures lorsque le client ne se représente pas. Malheureusement, postscreen ne peut rien faire pour pallier à ce problème. Mais si cela vous gêne, réfléchissez à deux fois avant d'activer les //deep protocol tests//.
===== - Quelques statistiques =====
Si Postscreen écrit des logs très détaillé, pour obtenir une vision plus globale, il faut y aller à grands coups de scripts. Au cours de mes expérimentations avec Postscreen, j'ai écrit Postscreen-Stats: un script python qui produit des statistiques depuis le syslog de postfix (généralement situé dans /var/log/maillog ou mail.info).
Le script est sur github à l'adresse https://github.com/jvehent/Postscreen-Stats.
Voici les résultats pour 24h d'activité de mail.python.org, qui gère le trafic des mailing lists de python.org (statistiques cordialement fournies par Patrick Ben Koetter via la liste postfix-users)
=== Postscreen statistics ===
7 BARE NEWLINE
15 COMMAND COUNT LIMIT
165 COMMAND PIPELINING
736 COMMAND TIME LIMIT
38678 CONNECT
24189 DNSBL
25812 HANGUP
543 NOQUEUE 450 deep protocol test reconnection
4 NOQUEUE too many connections
486 PASS NEW
3455 PASS OLD
9373 PREGREET
7206 WHITELISTED
=== Clients statistics ===
3 avg. dnsbl rank
12301 clients
309 reconnections
4050 seconds avg. reco. delay
=== First reconnection delay (graylist) ===
delay| <10s |>10to30s| >30to1m| >1to5m | >5to30m|
count|4 |18 |4 |83 |110 |
% |1.3 |5.8 |1.3 |27 |36 |
delay|>30mto2h| >2hto5h|>5hto12h|>12to24h| >24h |
count|46 |18 |17 |6 |3 |
% |15 |5.8 |5.5 |1.9 |0.97 |
Ces résultats montrent que 12301 clients se sont connectés sur ce serveur, en moyenne 3 fois car le total de CONNECT est de 38678. Sur ces 38678 connections, un peu moins des deux tiers (24189) ont été bloqués par une DNSBL, environ 24% par la règle PREGREET, etc.
Une version plus récente de Postscreen-Stats permet de voir le détail par client unique. Voici les statistiques pour une installation de postscreen que je maintiens. Cela représente une semaine d'activité d'un serveur situé sur la côte Est des USA.
=== unique clients/total postscreen actions ===
2131/11010 CONNECT
1/1 BARE NEWLINE
30/33 COMMAND COUNT LIMIT
13/16 COMMAND PIPELINING
6/6 COMMAND TIME LIMIT
463/536 DNSBL
305/503 HANGUP
12/15 NON-SMTP COMMAND
1884/2258 NOQUEUE 450 deep protocol test reconnection
1/42 NOQUEUE too many connections
1577/1600 PASS NEW
866/8391 PASS OLD
181/239 PREGREET
5/84 WHITELISTED
=== clients statistics ===
4 avg. dnsbl rank
505 blocked clients
2131 clients
840 reconnections
32245.4285714 seconds avg. reco. delay
=== First reconnection delay (graylist) ===
delay| <10s |>10to30s| >30to1m| >1to5m | >5to30m|
count|12 |21 |21 |196 |261 |
% |1.4 |2.5 |2.5 |23 |31 |
delay|>30mto2h| >2hto5h|>5hto12h|>12to24h| >24h |
count|88 |40 |29 |53 |119 |
% |10 |4.8 |3.5 |6.3 |14 |
=== Top 20 Countries of Blocked Clients ===
167 (33.00%) United States
59 (12.00%) India
33 ( 6.50%) Russian Federation
26 ( 5.10%) Indonesia
23 ( 4.60%) Pakistan
21 ( 4.20%) Vietnam
20 ( 4.00%) China
13 ( 2.60%) Brazil
11 ( 2.20%) Korea, Republic of
9 ( 1.80%) Belarus
8 ( 1.60%) Turkey
7 ( 1.40%) Iran, Islamic Republic of
7 ( 1.40%) Ukraine
6 ( 1.20%) Kazakstan
6 ( 1.20%) Chile
5 ( 0.99%) Italy
5 ( 0.99%) Romania
4 ( 0.79%) Poland
4 ( 0.79%) Spain
3 ( 0.59%) Afghanistan
Ici, moins de clients (seulement 2131). On voit que 463 d'entre eux ont été bloqués par la DNSBL, certains même deux fois (le total est de 536).
Autre information intéressante : 33% des zombies sont venus des USA, 12% d'Inde et seulement 4% de Chine. Contrairement à ce que l'on pourrait penser de prime abord, la Chine n'est pas le leader dans ce domaine (en tout cas, pas sur mon serveur...).
On voit également toute l'utilité du mécanisme de pondération des RBL fourni par postscreen. En moyenne, un zombie se fait rejeter par DNSBL avec un poids de 4, ce qui signifie qu'un zombie est connu d'au moins deux RBL différentes. Cela confirme l'idée qu'une IPs ayant un poids de 2 ou moins ne devrait pas être rejetée, car c'est probablement un faux positif.
Il est également possible d'appliquer des filtres afin d'affiner les résultats. Par exemple, si vous ne voulez voir que les PREGREET, utilisez:
$ ./postscreen_stats.py -f maillog.3 -r short -y 2011 --geofile=../geoip/GeoIPCity.dat -G -a "PREGREET"
=== unique clients/total postscreen actions ===
181/246 CONNECT
1/1 BARE NEWLINE
11/14 COMMAND COUNT LIMIT
13/16 COMMAND PIPELINING
1/1 COMMAND TIME LIMIT
161/170 DNSBL
40/42 HANGUP
12/15 NON-SMTP COMMAND
37/62 NOQUEUE 450 deep protocol test reconnection
1/1 PASS NEW
1/5 PASS OLD
181/239 PREGREET
=== clients statistics ===
4 avg. dnsbl rank
181 blocked clients
181 clients
0 reconnections
Ce que l'on voit ci-dessus, en filtrant pour n'avoir que les PREGREET, c'est qu'un zombie qui se fait prendre dans la règle de PREGREET va souvent être également marqué par le DNSBL, et parfois par quelques autres règles.
Autre exemple: si vous voulez tous les HANGUP et NON-SMTP COMMAND, utilisez le filtre **-a "HANGUP|NON-SMTP COMMAND"**.
Un mot sur les paramètres :
* -f est le fichier syslog
* -r le type de rapport
* -y est l'année. syslog ne stocke pas l'année dans le format de la date, donc si, en 2012, vous travaillez sur des logs de 2011, il faut en informer le script.
* --geofile est le chemin vers une base de donnée de geoip. Maxmind fournit une version d'évaluation gratuite sur leur site.
* -G permet d'utiliser le module pygeoip plutot que GeoIP. C'est vous qui voyez, ils font la même chose.
Et cerise sur le gâteau, l'option --mapdest permet de créer un fichier HTML contenant une Google Map des zombies. C'est tout simple à utiliser:
$ ./postscreen_stats.py -f maillog.3 -r none -y 2011 --geofile=../geoip/GeoIPCity.dat -G --mapdest=postscreen_report_2012-01-15.html
Google map will be generated at postscreen_report_2012-01-15.html
using MaxMind GeoIP database from ../geoip/GeoIPCity.dat
Creating HTML map at postscreen_report_2012-01-15.html
Le fichier HTML généré peut être ouvert dans tous navigateur connecté au Net (requis par l'API de Google Maps) et affichera une carte similaire à celle ci-dessous. Cliquez sur chaque marqueur pour obtenir les détails. Vous pouvez aussi ajouter un filtre à la génération pour réduire le nombre de points.
{{:julien:articles:postscreen_map.png?700|}}
===== - En conclusion =====
Postscreen est élégant. Il est léger, s'intègre parfaitement devant Postfix, et permet de nettoyer le trafic de manière efficace sans consommer 15 CPU et des giga de mémoire.
En fin de compte, Wietse Venema, c'est un peu le //Shaun of the Dead// du mail. Et les amateurs de pâté de jambon n'ont qu'à bien se tenir car à partir de maintenant, mon serveur mail, il tire d'abord et il pose les questions après. C'est ça la puissance de Postscreen !
Une fois de plus, merci à Xavier Skapin d'avoir eu la patience de relire cet article.