Combattre le spam sur Mac OS X Server : quelques astuces

55k à 65k mails par jour en entrée de site, c'est une charge plutôt respectable pour un MX assumant à lui seul la réception, le filtrage antispam et le filtrage antivirus. Quand ce serveur est un XServe G5 2x2GHz sous Mac OS X Server, il convient de surveiller de prêt les performances de l'installation.
Avant Mac OS X 10.4, le serveur de mail (Postfix) était livré sans antispam ni antivirus. Notre choix s'est donc porté sur quelque chose de répandu, de largement éprouvé, et pour le quel nous savions pouvoir trouver facilement du support : amavisd-new (utilisant spamassassin), et clamav. Il s'est avéré par la suite qu'Apple a choisi les mêmes outils pour sa solution de filtrage de courier dans la version 10.4 de Mac OS X Server.

Malgré tout, ce n'est pas un choix idéal en terme de performances, surtout si on ajoute par dessus rules_du_jour avec ses plus de 360k lignes de filtres spamassassin. Voyons quelques points à optimiser.

1. Si le serveur frontal n'est qu'une passerelle vers un serveur de distribution du courier, il faut veiller à ce que le frontal connaisse la liste des adresses pouvant recevoir du courier. Ça permet à ce frontal de refuser tous les couriers mal adressés avant qu'ils ne passent dans les filtres antispam et antivirus. C'est autant de temps gagné pour traiter la correspondance légitime.
Nous avons une table d'alias de 60k lignes sur notre passerelle interne. À chaque fois que cette table change, la liste des destinataires possibles est exportée sur le serveur frontal sous forme de relay_recipient_maps. Ainsi, nous ne filtrons les couriers que si ils ont une chance d'aboutir, les autres sont rejetés avant d'entrer dans le système.

2. "keep current". Quoi qu'on en dise ("si ça marche, ne le répare pas"...) certaines évolutions sont bonnes à prendre. Dans le cas d'amavisd-new c'est assez flagrant. Le passage de la version 2.3.x à la 2.4.x a fait des miracles. Sur ce graphique, représentant l'évolution du nombre de timeout spamassassin par jour, on voit nettement l'effet de la mise à jour d'amavisd-new vers la mi-avril.

3. Déporter certains filtres de spamassassin vers Postfix peut être une très bonne solution pour améliorer le rendement du serveur. Quand j'ai installé rules_du_jour sur le frontal, le premier résultat n'a pas été un meilleur filtrage du spam (installation fin décembre), ça a été environ 10k timeout SA en 2 ou 3 heures (pic début janvier). Rules_du_jour apporte environ 360k lignes de filtre, dont un peu moins de 340k pour un seul fichier : blacklist.cf. Mettre 340k serveurs en liste noire dans SA n'a aucun intérêt. Cela alourdi considérablement la configuration, SA devient très lent, et le nombre de timeout explose. J'ai donc opté pour quelque chose de plus radical : placer la liste noire dans les paramètres de Postfix. Régulièrement, le serveur télécharge la dernière version de blacklist.cf, la converti en fichier sender_access, compile la table en sender_access.db et recharge Postfix. Ce dernier utilise la table via la ligne suivante dans le main.cf :

check_sender_access hash:/etc/postfix/sender_access

On voit d'ailleurs que l'efficacité de cette table est plus que modeste par rapport à sa taille impressionnante. Elle ne justifie pas en tout cas le nombre énorme de timeout qu'elle génère si on l'utilise via SA sur un serveur fortement chargé. En dehors des périodes de pointe, cette liste noire arrête un peu moins de 400 couriers par jour.

4. Sur Mac OS X Server, l'usage sous très forte charge d'amavisd-new n'est pas sans poser des problèmes. On constate que les différents processus Perl s'engluent au fil de leur exécution. Après quelques heures à plein régime, on note un ralentissement progressif du traitement des couriers. Les processus fils du master amavisd-new sont de plus en plus nombreux à tourner en concurrence, et pour finir la charge de la machine peut atteindre allègrement 30-40, ne laissant passer les couriers qu'au compte-goutte. Pour palier ce problème j'ai mis au point un script lancé en hourly et dont la vocation est de relancer amavisd-new si la charge de la machine est trop grande. Cela donne plutôt satisfaction.
Note : je n'ai pas testé l'amavisd-new fourni par Apple sur MOSXS 10.4 dans des conditions de forte charge. Je ne sais pas s'il se comporte de la même manière.

Annexe 1 : Le script de mise à jour de liste noire sur Postfix.

#!/bin/sh
#
# $Id: 150.fetch_blacklist_postfix 471 2006-02-20 12:25:04Z patpro $
#
# Fetch, format, postmap and install sender blacklist for Postfix.
# the blacklist is too big for Amavis/SA, so it's installed as binary
# table in postfix smtpd_sender_restrictions
#
# 1.3 - 20060220
#   Added a test to prevent http error files to replace proper blacklist file.
#
# 1.2 - 20060201
#   Filtering the diff output.
#
# 1.1 - 20060131
#   Replacing wget with cURL for compatibility sake.
#
# 1.0 - 20060130
#   First version.
#


usage="Usage: `basename $0`";

SUCCESS=0
INFO=1
BAD_CONFIG=2
OTHER_ERR=3

echo ""
echo "Blacklist update from http://www.sa-blacklist.stearns.org ..."
# FETCH
cd /tmp/
/usr/bin/curl --silent --remote-time --show-error \
  --time-cond repere_de_date -O \
  --url http://www.sa-blacklist.stearns.org/sa-blacklist/sa-blacklist.current
if [ `wc -m sa-blacklist.current | awk '{print $1}'` -lt 5000000 ];then
        echo "Update too small for being honest, something must be wrong. bye"
        exit $OTHER_ERR
fi

if [ sa-blacklist.current -nt repere_de_date ]; then
        touch -r sa-blacklist.current repere_de_date
else
        echo "No update available for blacklist, bye-bye"
        exit $INFO
fi

# CONVERT
cd /tmp/
# nettoyage
awk '/^blacklist_from / {print $2"  REJECT BLACKLIST"}' sa-blacklist.current \
      | sed 's,^\*@\**,,' > postfix-blacklist.current
# recup des 100 premieres lignes du fichier AVANT la blacklist dynamique.
grep -B100 "from RulesDuJour : blacklist.cf" /etc/postfix/sender_access \ 
        > sender_access.header
cat sender_access.header postfix-blacklist.current > sender_access
# diff entre le nouveau et l'ancien, pour controler ce qu'on ajoute
echo "Diff with old blacklist:"
diff sender_access /etc/postfix/sender_access | awk '
        BEGIN {DEL=0; ADD=0;}
        /^/ {DEL=DEL+1;}
        END {print "Added: "ADD; print "Removed: "DEL}'

# INSTALL
cd /tmp/
echo "Update for blacklist is beeing installed..."
( postmap sender_access && echo "postmap OK" && \
  mv -f sender_access sender_access.db /etc/postfix/ && \
  echo "Install OK" && postfix reload ) || exit $OTHER_ERR

exit $INFO

Annexe 2 : Le script de relance d'amavisd-new.

#!/bin/bash
# $Id: 050.amavisd_hourly_reload.sh 523 2006-04-12 07:27:12Z joladmin $
#
# 1.0.1 - 12 avril 2006 - 20060412 - joladmin
#   Utilisation d'une variable pour la charge maximum déclenchant le
#   redémarrage d'amavis.
#   Correction de la valeur de sortie dans le cas ou un redémarrage d'amavis
#   est nécessaire.
#
# version initiale : Patrick Proniewski
#
# Hourly pour le frontal amavis. Permet de relancer amavis en cas de surcharge
# importante.
#
usage="Usage: `basename $0`";

SUCCESS=0
INFO=1
BAD_CONFIG=2
OTHER_ERR=3

RC=$SUCCESS

CHARGE_MAX=10

#
# this is way faster than parsing the output of `uptime`
CHARGE=$(sysctl vm.loadavg)                # vm.loadavg: 0.67 0.67 1.57
CHARGE_A_15_M=${CHARGE//* /}               # 1.57
CHARGE_A_15_M_INT=${CHARGE_A_15_M/.*/}     # 1

amavisd_starter() {
    NBR_MASTER=$(/bin/ps -axo "command" | grep amavisd | grep -c master)
    if [ $NBR_MASTER -eq 0 ] ; then
        LANG=C LC_ALL=C /usr/local/sbin/amavisd -u amavis start
        sleep 20
        # finally, a little kick in postfix: 
        /usr/sbin/postqueue -f
        RC=$INFO
    fi
}

amavisd_reload() {
    RELOAD="NO"
    PS_AMAV=1
    # let's check for a PID file
    test -f /var/amavis/amavisd.pid && \
    [ $(/bin/ps -axo "pid" | grep  `cat /var/amavis/amavisd.pid`) ] && \
    RELOAD="OK"
    # let's check for a running amavisd without a PID file:
    NBR_MASTER=$(/bin/ps -axo "command" | grep amavisd | grep -c master)
    # if I find a master, I get its PID
    [ ! $NBR_MASTER -eq 0 ] && \
    PS_AMAV=$(/bin/ps -axo "pid,command" | grep amavisd | grep master | \
    cut -f 1 -d " ")
    # processing...
    if [ $RELOAD == "OK" ]; then
        LANG=C LC_ALL=C /usr/local/sbin/amavisd -u amavis reload 
        echo $?
    elif [ $PS_AMAV -gt 1 ]; then
        kill $PS_AMAV && sleep 30 && amavisd_starter
    fi
}

# if the 15 minutes load average is greater than CHARGE_MAX we issue a reload
# then we sleep 1 minute, and check for amavisd availability.
# if 15m load average is lesser than CHARGE_MAX we check that amavisd 
# is running.
if [ $CHARGE_A_15_M_INT -gt $CHARGE_MAX ]; then
    amavisd_reload
    # now we wait for amavisd to get back to life
    sleep 60
    # and then we test the availability of amavisd. 
    # If not available (0 master), we start it. 
    amavisd_starter
    # finally, a little kick in postfix: 
    /usr/sbin/postqueue -f
    RC=$INFO
else
    # load is not that huge, we check if amavisd is running, 
    # if not, we start it
    amavisd_starter
fi

exit $RC

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.