Una de las herramientas fundamentales a la hora de montar nuestro propio servidor de correo es la implementación de filtros (Mail Filters) para seleccionar de forma eficiente el correo que vamos a procesar. Entre otras cosas, podremos:
- Hacer más eficiente nuestro servicio ya que enviaremos solo los correos que cumplan unas determinadas reglas (eliminan SPAM, virus y otros tipos de cosas que no queremos recibir.
- Modificar ciertos mensajes para automatizar procesos en nuestra compañía, por ejemplo, añadir destinatarios en copia automáticamente, añadir una cabecera adicional al correo, incluir un sistema de seguimiento al mensaje…
- Monitorizar el número de correos recibidos y enviados por el servidor. Con fines estadísticos y, por supuesto para mejorar el servicio que estamos prestando.
- Restringir el envío de correo a determinados hosts/IPs/dominios/usuarios. Por ejemplo, teniendo un sistema de créditos, o impidiendo que se envíen de forma automatizada muchos mensajes a través de nuestro servidor como una protección extra contra spammers u ordenadores zombies (incluso podríamos avisar a alguien si esto pasa).
- Implementación de listas blancas, negras o grises.
- Espiar mensajes. Vale, como administrador de sistemas siempre me dicen que si espío los mensajes de la gente y siempre gasto la broma de que tengo un programa que me avisa cuando hablan de mí. Aunque no lo tengo, porque me da pereza, con este tipo de filtros podría hacerlo.
Por otro lado, cuando montamos un nuevo sistema debemos pensar en la futura escalabilidad del mismo. Es decir, vamos a pensar en cuando nuestro sistema sea grande. Los servicios tanto de entrega, recepción de correo, incluso filtros podrán estar en máquinas separadas. Así que la forma de atacar a estos sistemas será haciendo conexiones de red. Puede que para un sistema muy pequeño perdamos algo de rendimiento, ya que el establecimiento de la conexión y la negociación de la misma puede tardar un tiempo que no tardaría la ejecución de un programa local, pero que hacen posible la separación de los servicios, incluso podríamos colocar los filtros detrás de un balanceador de carga y destinar varias máquinas a éstos. De todas formas, para este ejemplo, vamos a utilizar sockets unix por lo que, en local, será muy rápido y no será complicado hacer que en lugar de un socket Unix estemos utilizando un host/puerto.
Tabla de contenidos
Configuración de Postfix
Postfix es capaz de ejecutar muchos milters cada vez que viene un mensaje. Estos milters, con respecto a cuándo se ejecutan antes de encolar los mensajes y pueden ser:
- tipo smtpd: Los que se ejecutan para los mensajes que vienen desde conexiones SMTP. Pueden ser de usuarios que se conectan a nuestro servidor para enviar mensajes o de otros servidores que se conectan a nosotros para entregar mensajes.
- tipo no-smtpd: Serán mayormente los que vengan de manera local. Mensajes generados en el propio servidor o alguna de las aplicaciones que hay en él, como sendmail.
Solo tenemos que ir al fichero /etc/postfix/main.cf y, por ejemplo al final, añadir:
smtpd_milters = milter1, milter2, {milter3, …},…
milter_default_action = accept
De esta forma, por defecto, si algún milter no se ejecuta, por defecto aceptamos el mensaje. Por ejemplo, si estamos utilizando un milter de monitorización, si el milter falla, que por lo menos se entregue el mensaje. Si es un milter de filtro de correo, debemos ver qué es más importante, que cuando el milter falle rechace todo (reject) o si deberíamos entregarlo. Otras acciones que pueden suceder cuando el milter falle son tempfail (fallo temporal) o quarantine (cuarentena).
Como hemos visto, podemos separar una serie de filtros por comas, incluso, a veces, ponemos configuración del milter entre llaves. Eso sí, si solo tenemos uno, no hacen falta comas, y si la configuración es sencilla, llaves tampoco. Vamos a ver un poco el por qué de todo esto.
En principio, con respecto a cómo se ejecutan, podemos tener otros dos tipos de milters:
- unix:/ruta/al/socket : serán los milters asociados a un socket Unix accesible a través de una ruta local.
- inet:host:port : serán los milters asociados a un host y un puerto. Es decir, los que podemos colocar en una máquina diferente.
También hemos visto que los milters podemos ponerlos entre llaves, esto es cuando su configuración es más compleja que solo decir dónde tiene que conectar, por ejemplo podemos controlar:
- connection_timeout : Cuánto tiempo va a esperar Postfix a la conexión con el milter. Ya que es un servicio que puede ser externo, la conexión puede no ser inmediata. Por defecto son 30s.
- command_timeout : Cuánto tiempo va a esperar Postfix a que el milter responda tras el envío de un comando. Nuestro filtro tiene tiempo para realizar operaciones, pero tampoco se puede eternizar porque constantemente están llegando mensajes y el proceso no puede parar. Son 30s por defecto.
- content_timeout : Cuánto tiempo va a esperar Postfix tras el envío de contenidos del mensaje para una respuesta por parte del milter. Por defecto son 300s.
- default_action : Podemos especificar la acción por defecto para cada milter. Esta puede ser accept, reject, tempfail, quarantine
- Podemos consultar una lista completa de configuraciones aquí.
Así, un ejemplo de configuración de milter puede ser:
smtpd_milters = unix:/var/run/milters/monitor, {inet:blacklist.mydomain.com:6234, connection_timeout=5x, default_action=tempfail, command_timeout=1s}
milter_default_action = accept
Milters en Python
Aunque podemos crear nuestros milters en C o C++, es más, podemos encontrar muchos ejemplos que utilizan libmilter y funcionan muy bien. Hoy le toca el turno a Python, de hecho la biblioteca de Python para trabajar con milters se apoya en la biblioteca de C. Aunque no tendrá tanto rendimiento como uno programado en C, en el hipotético caso en el que el filtro lo hagamos optimizado en cada lenguaje, Python nos hace mucho más fácil el mantenimiento y el desarrollo, pudiendo crecer muy rápidamente. Lo primero será preparar las dependencias. Podemos instalarlas con pip:
Aunque, dependiendo de nuestro filtro, podremos tener muchas más dependencias. Ya nuestra imaginación no tiene límites.
Creando el milter en Python
Vamos a crear un milter de ejemplo en Python. En este ejemplo, solo vamos a permitir mensajes que se envíen hacia las direcciones de correo que figurarán en un archivo llamado whitelist. Por lo que nuestro servidor no podrá mandar correo a cualquiera. El contenido de whitelist puede ser el siguiente:
mi@correo.com
yo@dominio.com
otromail@otrodominio.com
El milter está basado en este. Ahí va el código:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 | # -*- coding: utf-8 -*- # Milter para filtrar los mails que vienen de direcciones entrantes import Milter import StringIO import time import email import sys from socket import AF_INET, AF_INET6 from Milter.utils import parse_addr if True: from multiprocessing import Process as Thread, Queue else: from threading import Thread from Queue import Queue logq = Queue(maxsize=4) class myMilter(Milter.Base): def __init__(self): # Se crea una instancia con cada conexión entrante self.id = Milter.uniqueID() @Milter.noreply def connect(self, IPname, family, hostaddr): self.IP = hostaddr[0] self.port = hostaddr[1] if family == AF_INET6: self.flow = hostaddr[2] self.scope = hostaddr[3] else: self.flow = None self.scope = None self.IPname = IPname self.H = None self.fp = None self.receiver = self.getsymval('j') self.log("connect from %s at %s" % (IPname, hostaddr) ) return Milter.CONTINUE def hello(self, heloname): # Cuando entra un nuevo mail vemos esto. self.log("Recibo HELO") self.H = heloname self.log("HELO %s" % heloname) return Milter.CONTINUE def envfrom(self, mailfrom, *str): # Cuando se indica un remitente entra por aquí. Podemos elegir si continuar o no self.F = mailfrom self.R = [] self.fromparms = Milter.dictfromlist(str) self.user = self.getsymval('{auth_authen}') self.log("mail from:", mailfrom, *str) self.fp = StringIO.StringIO() self.canon_from = '@'.join(parse_addr(mailfrom)) self.fp.write('From %s %s\n' % (self.canon_from,time.ctime())) return Milter.CONTINUE def envrcpt(self, to, *str): # Cuando recibimos un destinatario, entra por aquí filename = 'whitelist' # Obtenemos el listado de correos aceptados addresses = tuple(el.strip() for el in open(filename, 'r') if el.strip()) tomail = '@'.join(parse_addr(to)) if not any(tomail.lower() in l for l in addresses): return Milter.REJECT rcptinfo = to,Milter.dictfromlist(str) self.R.append(rcptinfo) return Milter.CONTINUE @Milter.noreply def header(self, name, hval): # Cuando recibimos un encabezado entramos por aquí self.fp.write("%s: %s\n" % (name,hval)) return Milter.CONTINUE @Milter.noreply def eoh(self): # Cuando terminamos los encabezados entramos a esta función self.fp.write("\n") return Milter.CONTINUE @Milter.noreply def body(self, chunk): # Cuando se recibe cada uno de los trozos del cuerpo del mensaje entramos a este punto self.fp.write(chunk) return Milter.CONTINUE def eom(self): # Cuando terminamos de recibir el mensaje entramos a esta función self.fp.seek(0) msg = email.message_from_file(self.fp) return Milter.ACCEPT def close(self): # Cierre de conexión y limpieza de recursos. Si se llama a abort() se llamará luego a close() # automáticamente return Milter.CONTINUE def abort(self): # El cliente se ha desconectado de forma prematura. ¿Un fallo en postfix o al enviar el mensaje? return Milter.CONTINUE def log(self,*msg): # Logea algo logq.put((msg,self.id,time.time())) def background(): while True: t = logq.get() if not t: break msg,id,ts = t print "%s [%d]" % (time.strftime('%Y%b%d %H:%M:%S',time.localtime(ts)),id), for i in msg: print i, ## === def main(): bt = Thread(target=background) bt.start() socketname = "/var/run/milters/whitelistmilter" timeout = 600 Milter.factory = myMilter flags = Milter.CHGBODY + Milter.CHGHDRS + Milter.ADDHDRS flags += Milter.ADDRCPT flags += Milter.DELRCPT # Le decimos al Milter los puntos que "interceptaremos" Milter.set_flags(flags) print "%s milter startup" % time.strftime('%Y%b%d %H:%M:%S') sys.stdout.flush() Milter.runmilter("pythonfilter",socketname,timeout) logq.put(None) bt.join() print "%s bms milter shutdown" % time.strftime('%Y%b%d %H:%M:%S') if __name__ == "__main__": main() |
Probando nuestro milter
Para probar esto, desde un terminal, podemos ejecutar nuestro script python de nuestro milter, reiniciar Postfix e intentar enviar un correo a través del servidor SMTP elegido. Si queremos hacerlo todo desde terminal, podemos probar utilizar este script para enviar correos desde terminal.
Aunque todo esto podemos hacerlo, si la configuración nos lo permite a pelo. Y puede que en el futuro haga un post dedicaco a esto. Pero por ahora nos apañaremos con este apartado. Nuestro servidor de correo requeire autentificación, de hecho para las pruebas he utilizado un Postfix configurado como relay, con toda la configuración del anterior post. incluyendo el nombre de usuario y contraseña para poder acceder al servidor de correo. Para ello, y como vamos a utilizar telnet, vamos a prepararnos en un terminal la siguiente información:
Como estamos utilizando en el servidor el modo AUTH LOGIN, tendremos que escribir el usuario y la contraseña codificados en base64. Los textos codificados, podemos dejarlos visibles para copiarlos y pegarlos cuando convenga. Ahora, accederemos al servidor de correo (el puerto será el 25, y si lo tenemos instalado en un VPS, tendremos que asegurarnos de que nuestro proveedor tiene abierto el puerto 25. A veces necesitamos enviar un mensaje al proveedor para que lo activen). ¡Fijaos, la IP es digna de CSI!
Como vemos, cuando introducimos una dirección de correo que no está en la lista, nos aparece el comando como rechazado (Command rejected), mientras que cuando la dirección está en la lista, nos devuelve Ok. Si queremos, podemos mirar el log en la ventana donde estamos ejecutando el script para ver el resultado y observar que efectivamente están llegando los comandos a nuestro milter.
Creación del servicio
Como queremos que nuestro milter siempre esté en ejecución, debemos crear un servicio. Por ejemplo, para systemd podemos crear el siguiente archivo: whitelistmilter.service
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | [Unit] Description=Milter startup After=networking.target [Service] WorkingDirectory=/home/user/milters/ Type=simple ExecStart=/usr/bin/python /home/user/milters/whitelistmilter.py KillSignal=SIGTERM User=postfix Group=postfix [Install] WantedBy=multi-user.target |
Luego podemos crear un enlace a /etc/systemd/system y arrancar el servicio:
En este punto ya podemos reiniciar Postfix para empezar a utilizar el filtro:
Problemas que pueden surgir
Postfix chroot y acceso a sockets Unix
Uno de los problemas más comunes que podemos tener es que Postfix se ejecuta en un chroot o jaula. Por lo que, cuando especificamos la ruta de un socket Unix, la ruta esté correcta y todo deba funcionar perfectamente. En los ficheros de log veremos que la ruta no se encuentra. Esto se debe a que el unix socket no está dentro del chroot de postfix y efectivamete no se verá.
Una posible solución a esto es meter nuestro sockets unix de los milters en el directorio /var/run/milters y luego hacer:
Con esto, creamos un directorio var/run dentro de la ruta de la jaula de postfix y luego montamos el directorio local /var/run/milters dentro de la jaula. De esta forma, postfix ya tendrá acceso a los milters bajo su ruta absoluta /var/run/milters (recordemos que para Postfix, el directorio raíz / es el directorio /var/spool/postfix para el resto de los mortales.
Foto principal: andrew welch
Gracias por este gran artículo!
Thank you for giving these helpful hints on how to use milters in Python and configure Postfix to filter email. This article provided me some useful information. Thank you for taking the time to tell us about it.
Families from all around Virginia and beyond have made Lake Anna their vacation destination of choice for phrazle generations. In the 1970s, a freshwater reservoir was built to supply cooling water to an adjacent power plant.
I really like the way that you have expressed yourself. There is a lot to be admired from this post. You might want to click onDavid Martinez Jacket
nice post keep it up. buy call of duty accounts
Such an awesome read. This is so helpful. Keep sharing! residential dumpster rental
Según casinosonlineespaña.org los mejores casinos online de España son los que tienen licencias y bonos jugosos, entra y conoce más información de estos casinos.
WooCommerce is an Open source eCommerce plugin that transforms your WordPress website into a fully featured eCommerce store. You can send your customer and order information from woocommerce quickbooks integration
I love these kind of games casinoschileonline.com. You can win on the first try
Exercises for erectile dysfunction. The Best Exercises You Can Try Right Now.Hello friends, in this video. exercises to eliminate erectile dysfunction, has told. you have to do 2 sets of 30 seconds in daily routine.
Thanks for a wonderful share. Here is the great example related to you blog. Coventry tree service
Very much appreciated. Thank you for this excellent article. Keep posting!
I had a lot of harvest after seeing this post of yours! Before, I used to play games run 3, this is a fun game for entertainment, but now I will follow you, read your articles will have more knowledge.
It’s important to consider future scalability when setting up a new system, including delivery services, mail reception, and filters.
«Thank you very much for this wonderful topic!
Landscaping Red Deer
‘»