Table of Contents
Postscreen, l'exterminateur de zombies
Par Julien Vehent
Cet article a été publié dans le numéro 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.
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.
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:<jean-kevin@example.net> 250 2.1.0 Ok rcpt to:<julien> 250 2.1.5 Ok data 354 End data with <CR><LF>.<CR><LF> 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:<jean-kevin@example.net> 250 2.1.0 Ok rcpt to:<julien> 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=<jean-kevin@example.net>, to=<julien>, proto=ESMTP, helo=<mail> 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 <SP>, optionally some text, and <CRLF>. As noted above, servers SHOULD send the <SP> 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 <code><tiret><texte> (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 <code><espace><texte>.
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:<jean-kevin@example.net> rcpt to:<julien> 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:<jean-kevin@example.net> rcpt to:<julien> 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 <CR><LF> “Carriage Return & Line Feed”. Beaucoup de zombies n'utilise que le <LF> 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:<jean-kevin@example.net> 250 2.1.0 Ok rcpt to:<julien> 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:<jean-kevin@example.net> 250 2.1.0 Ok rcpt to:<julien> 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=<jean-kevin@example.net>, to=<julien>, 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:<jean-kevin@example.net> 250 2.1.0 Ok rcpt to:<julien> 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:<jean-kevin@example.net> 250 2.1.0 Ok rcpt to:<julien> 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.
- 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 !
<note>Une fois de plus, merci à Xavier Skapin d'avoir eu la patience de relire cet article.</note>