Unas de las cosas que más gustan de MailScanner es la feature de que cada una de las directivas de configuración, en su gran mayoría, pueden ser un archivo de reglas o una llamada a una función. Esto permite que para cada configuración típica de un sistema antispam pueda diferenciarse en base a cuenta origen/destino, IP origen, dominio origen/destino…etc, lo cuál, permite la personalización absoluta del sistema.
Por ejemplo, a la hora de decidir si un mensaje va a ser escaneado o no, en vez de tener un valor de «yes» o «no» para la directiva de configuración que lo controla, que se aplicaría a todos los mensajes, puede hacerse mediante un archivo de reglas externo:
Scan Messages = %rules-dir%/scanmessages.rules
donde se establecerán las reglas que se deseen, por ejemplo:
From: origen@confiable.com no
From: 192.168.1.10 no
From: /^192\.168\.13[4567]\./ no
FromOrTo: default yes
evitando que se escaneen los mensajes de un determinado FROM, de una determinada IP o de un conjunto de IPs que cumplan la expresión regular. Con el resto de mensajes, se hará lo que el «default» diga, en este caso, «yes» (escanearse).
La otra opción es tener una llamada a una función, por ejemplo:
Scan Messages = &ScanMsgs
lo que provoca que la configuración de dicha directiva se consulte ejecutando dicha función. Estas funciones suelen contener un código base de inicio, un código principal y la finalización de la función, como el que sigue:
package MailScanner::CustomConfig;
sub InitScanMsgs {
MailScanner::Log::InfoLog("Starting ScanMsgs...");
}
sub ScanMsgs {
my($message) = @_;
my $msgid=$message->{id};
MailScanner::Log::WarnLog("$msgid: ScanMsgs checking.");
return 0;
}
sub EndScanMsgs {
MailScanner::Log::InfoLog("Ending ScanMsgs...");
exit;
}
Reiniciando el servicio de MailScanner, cuando llega un mensaje, se muestra esto en los logs:
Sep 23 17:15:05 MailScanner[22554]: Starting ScanMsgs...
Sep 23 17:15:05 MailScanner[22554]: 4694D2180A7.AC573: ScanMsgs checking.
Sep 23 17:15:05 MailScanner[22554]: 4694D2180A7.AC573: ScanMsgs checking.
Sep 23 17:15:05 MailScanner[22554]: Ending ScanMsgs...
Como se puede ver, hay 2 llamadas a nuestra función ScanMsgs para un mismo mensaje, algo que no debería ser así.
Debugeando código, se ve cómo MessageBatch.pm (el escaneo de mensajes en MailScanner se realiza por procesos batch que agrupan un número determinado de ellos y los procesan) llama a la función CreateBatch() de Postfix.pm (en el caso de usar Postfix como MTA); hecho esto, se llama al constructor de Message.pm (cada batch contiene mensajes que necesitan de su constructor) que contiene, entre otras cosas, lo siguiente:
# Decide if we want to scan this message at all
$this->{scanmail} = MailScanner::Config::Value('scanmail', $this);
if ($this->{scanmail} =~ /[12]/) {
$this->{scanmail} = 1;
} else {
# Make sure it is set to something, and not left as undef.
$this->{scanmail} = 0;
}
if ($this->{scanmail} !~ /1/) {
$this->{scanvirusonly} = 1;
} else {
$this->{scanvirusonly} = 0;
}
En la primera línea se ve la llamada a la función Value de MailScanner::Config que es una función que coge el valor de una directiva y calculca el valor que le corresponde, en nuestro caso «scanmail»; esta función realiza las operaciones necesarias para calcularla, ya sea leer un archivo de reglas, llamar a funciones creadas…etc. Por tanto, en este punto, ya tendríamos en $this->{scanmail} el valor correspondiente a si un mensaje debe escanearse o no.
El problema viene después de todo esto, en esa misma función que comentábamos antes, CreateBatch() de Postfix.pm, hay el siguiente código:
if (MailScanner::Config::Value("scanmail", $newmessage) =~ /[12]/ ||
MailScanner::Config::Value("virusscan", $newmessage) =~ /1/ ||
MailScanner::Config::Value("dangerscan", $newmessage) =~ /1/) {
es decir, se vuelve a llamar a la función Value de MailScanner::Config que ya fue llamada anteriormente, de ahí la segunda ejecución que veíamos en los logs.
La forma de corregir esto es sencilla; veíamos en el código anterior que el valor de «scanmail» ya era calculado y guardado en la variable $this->{scanmail}. Este código del constructor de Message.pm es llamado desde Postfix.pm en líneas anteriores:
$newmessage = MailScanner::Message->new($id, $queuedirname, $getipfromheader);
por lo que la variable $newmessage tiene ya ese valor. Por tanto, sustituyendo el código anterior por:
if ($newmessage->{"scanmail"} =~ /[12]/ ||
$newmessage->{"virusscan"} =~ /1/ ||
$newmessage->{"dangerscan"} =~ /1/) {
hacemos que la llamada a la función que consume más recursos (búsqueda en archivos de reglas, llamada a funciones externas…etc), sea solo una vez, como debe ser.
Ya comenté el bug en la lista de distribución, esperemos que para próximas releases esté ya corregido, aunque el cambio de código que indico, es suficiente para subsanarlo.