martes, 8 de febrero de 2011

Problemas con adjuntos en simscan con ripmime

En la última versión del servidor de correo electrónico que desarrollé, la cual ya está en producción en los últimos tres servidores que instalé, apareció un error inusual. Básicamente, el software de control de contenidos (simscan 1.4.0) estaba rebotando mensajes porque contenían un adjunto no permitido. El problema es llamativo ya que no sucede siempre, lo cual me desorientó bastante.

Hice un desesperado pedido de ayuda en la lista de correo de la herramienta, en el sitio de Inter7 (la empresa que creó la herramienta originalmente), pero no obtuve una respuesta, así que seguí investigando por mi cuenta. UPDATE: Matt Brookings, desarrollador de simscan para Inter7 contestó a mi mensaje hoy (10 de Febrero de 2011) confirmando el bug en simscan (y también en ripmime, el cual voy a comunicar a Paul S. Daniels en estos días) y avisando que iba a revisarlo (Ver archivo de la lista de correo de Simscan: http://news.gmane.org/gmane.mail.qmail.simscan).

Simscan permite especificar una lista de extensiones de archivos los cuales serán rechazados si se adjuntan a un mensaje. Estas extensiones se especifican en un archivo llamado simcontrol, del cual luego se genera (mediante la ejecución de una herramienta complementaria llamada simscanmk) un archivo de base de datos en formato CDB, conteniendo las reglas de control. El archivo se vé de esta manera:

:clam=yes,spam=yes,spam_passthru=yes,attach=.vbs:.lnk:.scr:.cmd:.exe:.com:.bat:.reg:.pif


Esto básicamente significa que los adjuntos con extensión .vbs, .lnk, .scr, .cmd, .exe, .com, .bat, .reg y .pif están administrativamente prohibidos. Bueno, el software estaba rechazando un archivo cuyo nombre resultó ser "d" (si, solo la letra "D" minúscula). Lo extraño es que el mensaje solo tenía un único archivo de Word adjunto (con extensión ".doc").

Así se veía el error:
554 Your email was rejected because it contains a bad attachment: d


Lo extraño también es que no todos los archivos .doc fallaban, sino que el problema parecía darse en ciertos archivos solamente. Esto resultó ser lo más desconcertante de todo. Decidí probar a revisar el ripmime, con quien ya antes había tenido un altercado gracias a un error de capa 8 (si, me equivoqué de versión cuando lo instalé), y esto resultó ser interesante. Sobre una copia del mensaje que rebotaba ejecuté ripmime manualmente y me llevé una sorpresa. Ripmime extrajo varios archivos (5 en total) de un mensaje que supuestamente solo tiene un único adjunto(!).

srv:/usr/local/src/ripmime-tests # mkdir res
srv:/usr/local/src/ripmime-tests # ls -la
total 140
drwxr-xr-x 4 root root 4096 Feb 8 19:29 .
drwxr-xr-x 7 root root 4096 Feb 8 16:04 ..
drwxr-xr-x 2 root root 4096 Feb 8 19:27 res
-rw------- 1 root root 125608 Feb 8 12:37 testmessage.eml
srv:/usr/local/src/ripmime-tests # ripmime --disable-qmail-bounce -i testmessage.eml -d res
srv:/usr/local/src/ripmime-tests # cd res
srv:/usr/local/src/ripmime-tests/res # ls -la
total 96
-rw------- 1 root root 90112 Feb 8 19:27 Documento de prueba.doc
-rw-r--r-- 1 root root 0 Feb 8 19:27 d
-rw-r--r-- 1 root root 0 Feb 8 19:27 textfile0
-rw-r--r-- 1 root root 1094 Feb 8 19:27 textfile1
-rw-r--r-- 1 root root 936 Feb 8 19:27 textfile2


Ahora que sabía de donde salía el famoso archivo "d", el problema estaba en entender porqué el simscan tomaba el archivo como "bloqueado" y rechazaba el mensaje...

Configuré un recordio sobre el puerto 25 de uno de los servidores afectados para poder capturar toda la transacción del servicio SMTP y activé la función de debug del simscan (provista por el patch que escribió John Simpson para poder diagnosticar otro problema en simscan) y ahí empezó a verse la luz...

Pude obtener el siguiente registro:

@400000004d514dd628a87474 simscan: lpart: local part is **
@400000004d514dd628a8d61c simscan: cdb looking up gcastro@example.com
@400000004d514dd62991f5cc simscan: checking attachment textfile0 against .vbs
@400000004d514dd629921cdc simscan: checking attachment textfile0 against .lnk
@400000004d514dd629922894 simscan: checking attachment textfile0 against .scr
@400000004d514dd629923064 simscan: checking attachment textfile0 against .cmd
@400000004d514dd629923834 simscan: checking attachment textfile0 against .com
@400000004d514dd6299243ec simscan: checking attachment textfile0 against .exe
[...]
@400000004d514dd629992d74 simscan: checking attachment d against .com
@400000004d514dd629993544 simscan: checking attachment d against .exe
@400000004d514dd629993d14 simscan: checking attachment d against .bat
@400000004d514dd629997b94 simscan: checking attachment d against .cmd
@400000004d514dd629998364 simscan:[21057]:ATTACH:5.3975s:d:209.85.214.53:gcastrop@example2.com:gcastro@example.com
@400000004d514dd629998f1c simscan: exit error code: 82
@400000004d514dd6299996ec 21057 > 554 Your email was rejected because it contains a bad attachment: d

@400000004d514dd722f67e2c 21057 < QUIT


Y así se hizo evidente que el archivo "d" caía en la regla correspondiente a los archivos con extensión ".cmd", lo cual me intrigó bastante, ya que el nombre de archivo "d" difiere mucho de tener una extensión, y dicha extensión, si la hubiera, dudo que fuera ".cmd", dado que el archivo está vacío...

Lo obvio de todo esto es que ".cmd" termina en una letra "d", lo cual dudé que fuera una mera coincidencia... así que me puse a investigar el código fuente de simscan. Encontré que el archivo simscan.c tiene una función llamada check_attach(), la cual contiene el código que compara los nombres con las extensiones:

while((mydirent=readdir(mydir))!=NULL) {
/* skip . and .. */
if ( mydirent->d_name[0] == '.' &&
(mydirent->d_name[1] == '.' || mydirent->d_name[1] == 0) ) {
continue;
}

for(i=0;i<MaxAttach;++i) {
if ( DebugFlag > 2 ) fprintf(stderr, "simscan: checking attachment %s against %s\n", mydirent->d_name, bk_attachments[i] );
lowerit(mydirent->d_name);
if ( str_rstr(mydirent->d_name,bk_attachments[i]) == 0 ) {
strncpy(AttachName, mydirent->d_name, sizeof(AttachName)-1);
closedir(mydir);
return(1);
}
}
}


mediante otra función llamada str_rstr(), que se encarga de comparar las entradas en un vector de cadenas de texto conteniendo las extensiones, con el nombre del archivo de turno en la iteración ejecutada secuencialmente sobre todos los adjuntos del mensaje. Dicha función toma dos parámetros, el nombre de archivo y la extensión a comparar. Así se ve la función:

int str_rstr(register char *h,register char *n)
{
register char *sh;
register char *sn;

for(sh=h;*h!=0;++h); --h;
for(sn=n;*n!=0;++n); --n;

for(;h>=sh && n>=sn;--h,--n) {
if ( *h!=*n ) {
return(-1);
}
}
return(0);
}


Como puede verse, la función lee de atrás para adelante y compara los textos en forma literal, con lo cual el problema emerge finalmente. Al revisar letra por letra y empezar desde atrás hacia adelante, cuando revisa el nombre de archivo "d" con la última letra de la extensión ".cmd", las dos coinciden. Como no hay más nada para probar, dado que el nombre de archivo consta de una única letra, la función devuelve 0, con lo cual el mensaje es rechazado finalmente por contener un archivo no permitido.

A estas alturas, tenía dos soluciones posibles: o reparaba el ripmime para que deje de extraer archivos que no existen, o modificaba el código fuente del simscan para que haga un chequeo extra sobre los nombres de archivos a comparar con la lista de extensiones. Quise consultar el foro de ripmime para ver si alguien había tenido algún problema similar y discutir una posible solución, pero resultó ser que estaba cerrado por haber recibido spam, así que decidí decantarme por la modificación al código de simscan, el cual me sería más simple y fácil de probar, sin contar con que podía consultar la lista de correo de Inter7 en caso de encontrarme con algo inesperado.

Bueno, de las muchas opciones que se me ocurrieron, la que me pareció más simple y menos invasiva para con el código fué simplemente chequear que el largo del nombre del archivo a chequear en la iteración fuera como mínimo igual al largo de la extensión a chequear. Esto haría más exacto el chequeo y proporcionaría más robustez al software. Sin pensarlo demasiado, hice la modificación al código del archivo simscan.c, agregando código de debugging de la misma forma que John Simpson hizo con el resto del código para poder debuggear los errores anteriores:

for(i=0;i<MaxAttach;++i) {
if ( DebugFlag > 2 ) fprintf(stderr, "simscan: checking attachment %s against %s\n", mydirent->d_name, bk_attachments[i] );
lowerit(mydirent->d_name);
if ( strlen(mydirent->d_name) >= strlen(bk_attachments[i]) ) {
if ( str_rstr(mydirent->d_name,bk_attachments[i]) == 0 ) {
strncpy(AttachName, mydirent->d_name, sizeof(AttachName)-1);
closedir(mydir);
return(1);
}
} else {
if ( DebugFlag > 2 ) fprintf(stderr, "simscan: attachment name '%s' (%d) is shorter than '%s' (%d). IGNORED\n", mydirent->d_name, strlen( mydirent->d_name ), bk_attachments[i], strlen( bk_attachments[i] ) );
}

}


Ahora si el largo del nombre de archivo a comprobar es menor al largo de la extensión, el simscan simplemente lo ignora:

[...]
@400000004d517ee02c8894d4 simscan: attachment name d (1) is smaller than .vbs (4)
@400000004d517ee02c895054 simscan: checking attachment d against .cmd
@400000004d517ee02c8a0fbc simscan: attachment name d (1) is smaller than .cmd (4)
@400000004d517ee02c8acb3c simscan: checking attachment d against .lnk
@400000004d517ee02c8b8aa4 simscan: attachment name d (1) is smaller than .lnk (4)
[...]


Y el simscan ya no falla con el error críptico que desencadenó esta entretenida sesión de debugging.

Queda publicado este patch, el cual dejo aquí para que quien quiera pueda utilizarlo:

diff -ruN simscan-1.4.0/simscan.c simscan-1.4.0-tested/simscan.c
--- simscan-1.4.0/simscan.c 2011-02-08 20:26:06.095067924 -0200
+++ simscan-1.4.0-tested/simscan.c 2011-02-08 18:16:11.003064430 -0200
@@ -1735,10 +1735,14 @@
for(i=0;i if ( DebugFlag > 2 ) fprintf(stderr, "simscan: checking attachment %s against %s\n", mydirent->d_name, bk_attachments[i] );
lowerit(mydirent->d_name);
- if ( str_rstr(mydirent->d_name,bk_attachments[i]) == 0 ) {
- strncpy(AttachName, mydirent->d_name, sizeof(AttachName)-1);
- closedir(mydir);
- return(1);
+ if ( strlen(mydirent->d_name) >= strlen(bk_attachments[i]) ) {
+ if ( str_rstr(mydirent->d_name,bk_attachments[i]) == 0 ) {
+ strncpy(AttachName, mydirent->d_name, sizeof(AttachName)-1);
+ closedir(mydir);
+ return(1);
+ }
+ } else {
+ if ( DebugFlag > 2 ) fprintf(stderr, "simscan: attachment name '%s' (%d) is shorter than '%s' (%d). IGNORED\n", mydirent->d_name, strlen( mydirent->d_name ), bk_attachments[i], strlen( bk_attachments[i] ) );
}
}
}


Debe tomarse en cuenta que este patch está pensado para aplicarse sobre simscan 1.4.0 con el patch http://qmail.jms1.net/simscan/simscan-1.4.0-combined.4.patch de John Simpson (Thanks, John!).

Bueno, un problema menos... a seguir con los otros.

Happy hacking!

 
Gustavo Castro

Crea tu insignia